From b1d10aa7b61e007f1dee65a3d241b802847a03af Mon Sep 17 00:00:00 2001 From: Richard Cooper Date: Mon, 6 Dec 2021 10:19:43 +0000 Subject: [PATCH] misc: Update Dmesg dump for changes to printk in Linux v5.10+. The Linux Kernel's printk mechanism and data structures were overhauled in v5.10. This patch updates gem5's facility to dump the kernel Dmesg buffer on kernel panic to account for these changes. The new mechanism splits the Demsg ringbuffer into three separate ringbuffers, one containing the message data, one containing descriptors used for lock-free synchronisation, and one containing the message infos. For a detailed description please see the header of `kernel/printk/printk_ringbuffer.c` in the Linux source code. The new gem5 implementation tests for the correct version to run (pre-v5.10 or post-v5.10) by testing for the presence of symbols in the kernel. The new, post-v5.10 dump code is templated on types compatible with the kernel's atomic_long_t to account for differences between the 64-bit and 32-bit Linux kernels. Because the new Dmesg buffer dump code in gem5 is intended for disaster recovery, it intentionally prints the full Dmesg buffer with minimal checking of the validity of the messages. Partially finished and/or uncommitted messages will be printed along with the finalised messages. Change-Id: I62ac20735e0679f1ba2062ca7bb13692a5ca1eae Reviewed-on: https://gem5-review.googlesource.com/c/public/gem5/+/59509 Reviewed-by: Bobby Bruce Reviewed-by: Jason Lowe-Power Tested-by: kokoro Maintainer: Jason Lowe-Power --- src/base/loader/object_file.cc | 27 ++ src/base/loader/object_file.hh | 16 ++ src/kern/linux/helpers.cc | 498 ++++++++++++++++++++++++++++++++- 3 files changed, 536 insertions(+), 5 deletions(-) diff --git a/src/base/loader/object_file.cc b/src/base/loader/object_file.cc index bfa0a1dd64..3aa5915cdb 100644 --- a/src/base/loader/object_file.cc +++ b/src/base/loader/object_file.cc @@ -1,4 +1,16 @@ /* + * Copyright (c) 2022 Arm Limited + * All rights reserved + * + * The license below extends only to copyright in the software and shall + * not be construed as granting a license to any other intellectual + * property including but not limited to intellectual property relating + * to a hardware implementation of the functionality of the software + * licensed hereunder. You may use the software subject to the license + * terms below provided that you ensure that this notice is replicated + * unmodified and in its entirety in all distributions of the software, + * modified or unmodified, in source code or in binary form. + * * Copyright (c) 2002-2004 The Regents of The University of Michigan * All rights reserved. * @@ -136,5 +148,20 @@ createObjectFile(const std::string &fname, bool raw) return nullptr; } +bool +archIs64Bit(const loader::Arch arch) +{ + switch (arch) { + case SPARC64: + case X86_64: + case Arm64: + case Power64: + case Riscv64: + return true; + default: + return false; + } +} + } // namespace loader } // namespace gem5 diff --git a/src/base/loader/object_file.hh b/src/base/loader/object_file.hh index e8a96dc2b1..0415bec62e 100644 --- a/src/base/loader/object_file.hh +++ b/src/base/loader/object_file.hh @@ -1,4 +1,16 @@ /* + * Copyright (c) 2022 Arm Limited + * All rights reserved + * + * The license below extends only to copyright in the software and shall + * not be construed as granting a license to any other intellectual + * property including but not limited to intellectual property relating + * to a hardware implementation of the functionality of the software + * licensed hereunder. You may use the software subject to the license + * terms below provided that you ensure that this notice is replicated + * unmodified and in its entirety in all distributions of the software, + * modified or unmodified, in source code or in binary form. + * * Copyright (c) 2002-2004 The Regents of The University of Michigan * All rights reserved. * @@ -139,6 +151,10 @@ class ObjectFileFormat ObjectFile *createObjectFile(const std::string &fname, bool raw=false); +/** Determine whether the loader::Arch is 64-bit or 32-bit. */ +bool +archIs64Bit(const Arch arch); + } // namespace loader } // namespace gem5 diff --git a/src/kern/linux/helpers.cc b/src/kern/linux/helpers.cc index 3c648f1e34..b024226985 100644 --- a/src/kern/linux/helpers.cc +++ b/src/kern/linux/helpers.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016 ARM Limited + * Copyright (c) 2016, 2022 Arm Limited * All rights reserved * * The license below extends only to copyright in the software and shall @@ -37,16 +37,25 @@ #include "kern/linux/helpers.hh" +#include + #include "base/compiler.hh" +#include "base/loader/object_file.hh" #include "cpu/thread_context.hh" #include "mem/port_proxy.hh" #include "mem/translating_port_proxy.hh" #include "sim/byteswap.hh" #include "sim/system.hh" -namespace gem5 -{ +namespace gem5 { +namespace linux { + +namespace { + +namespace pre5_10 { + +/** Dmesg entry for Linux versions pre-v5.10 */ struct GEM5_PACKED DmesgEntry { uint64_t ts_nsec; @@ -57,8 +66,10 @@ struct GEM5_PACKED DmesgEntry uint8_t flags; }; +/** Dump a Linux Demsg entry, pre-v5.10. */ static int -dumpDmesgEntry(const uint8_t *base, const uint8_t *end, const ByteOrder bo, +dumpDmesgEntry(const uint8_t *base, const uint8_t *end, + const ByteOrder bo, std::ostream &os) { const size_t max_length = end - base; @@ -92,8 +103,9 @@ dumpDmesgEntry(const uint8_t *base, const uint8_t *end, const ByteOrder bo, return de.len; } +/** Dump the kernel Dmesg ringbuffer for Linux versions pre-v5.10 */ void -linux::dumpDmesg(ThreadContext *tc, std::ostream &os) +dumpDmesg(ThreadContext *tc, std::ostream &os) { System *system = tc->getSystemPtr(); const ByteOrder bo = system->getGuestByteOrder(); @@ -153,4 +165,480 @@ linux::dumpDmesg(ThreadContext *tc, std::ostream &os) } } +} // namespace pre5_10 + +namespace post5_10 { + +/** Metadata record for the Linux dmesg ringbuffer, post-v5.10. + * + * Struct data members are compatible with the equivalent Linux data + * structure. Should be templated on atomic_var_t=int32_t for 32-bit + * Linux and atomic_var_t=int64_t for 64-bit Linux. + * + * Also includes helper methods for reading the data structure into + * the gem5 world. + * + */ +template +struct GEM5_PACKED DmesgMetadataRecord +{ + using guest_ptr_t = typename std::make_unsigned_t; + + // Struct data members + atomic_var_t state; + struct + { + guest_ptr_t curr_offset; + guest_ptr_t next_offset; + } data_buffer; + + /** Read a DmesgMetadataRecord from guest memory. */ + static DmesgMetadataRecord + read(const TranslatingPortProxy & proxy, + Addr address, + guest_ptr_t data_offset_mask, + const ByteOrder & bo) + { + DmesgMetadataRecord metadata; + proxy.readBlob(address, &metadata, sizeof(metadata)); + + // Convert members to host byte order + metadata.state = gtoh(metadata.state, bo); + metadata.data_buffer.curr_offset = + gtoh(metadata.data_buffer.curr_offset, bo); + metadata.data_buffer.next_offset = + gtoh(metadata.data_buffer.next_offset, bo); + + // Mask the offsets + metadata.data_buffer.curr_offset = + metadata.data_buffer.curr_offset & data_offset_mask; + metadata.data_buffer.next_offset = + metadata.data_buffer.next_offset & data_offset_mask; + + return metadata; + } +}; + +/** Info record for the Linux dmesg ringbuffer, post-v5.10. + * + * Struct data members are compatible with the equivalent Linux data + * structure. Should be templated on atomic_var_t=int32_t for 32-bit + * Linux and atomic_var_t=int64_t for 64-bit Linux. + * + * Also includes helper methods for reading the data structure into + * the gem5 world. + * + */ +struct GEM5_PACKED DmesgInfoRecord +{ + // Struct data members + uint64_t unused1; + uint64_t ts_nsec; + uint16_t message_size; + uint8_t unused2; + uint8_t unused3; + uint32_t unused4; + struct + { + char unused5_1[16]; + char unused5_2[48]; + } unused5; + + /** Read a DmesgInfoRecord from guest memory. */ + static DmesgInfoRecord + read(const TranslatingPortProxy & proxy, + Addr address, + const ByteOrder & bo) + { + DmesgInfoRecord info; + proxy.readBlob(address, &info, sizeof(info)); + + // Convert members to host byte order + info.ts_nsec = gtoh(info.ts_nsec, bo); + info.message_size = gtoh(info.message_size, bo); + + return info; + } +}; + +/** Top-level ringbuffer record for the Linux dmesg ringbuffer, post-v5.10. + * + * Struct data members are compatible with the equivalent Linux data + * structure. Should be templated on AtomicVarType=int32_t for 32-bit + * Linux and AtomicVarType=int64_t for 64-bit Linux. + * + * Also includes helper methods for reading the data structure into + * the gem5 world, and reading/generating appropriate masks. + * + */ +template +struct GEM5_PACKED DmesgRingbuffer +{ + static_assert( + std::disjunction< + std::is_same, + std::is_same + >::value, + "AtomicVarType must be int32_t or int64_t"); + + using atomic_var_t = AtomicVarType; + using guest_ptr_t = typename std::make_unsigned_t; + using metadata_record_t = DmesgMetadataRecord; + + // Struct data members + struct + { + unsigned int mask_bits; + guest_ptr_t metadata_ring_ptr; + guest_ptr_t info_ring_ptr; + atomic_var_t unused1; + atomic_var_t unused2; + } metadata; + struct + { + unsigned int mask_bits; + guest_ptr_t data_ring_ptr; + atomic_var_t head_offset; + atomic_var_t tail_offset; + } data; + atomic_var_t fail; + + /** Read a DmesgRingbuffer from guest memory. */ + static DmesgRingbuffer + read(const TranslatingPortProxy & proxy, + const Addr address, + const ByteOrder & bo) + { + DmesgRingbuffer rb; + proxy.readBlob(address, &rb, sizeof(rb)); + + // Convert members to host byte order + rb.metadata.mask_bits = + gtoh(rb.metadata.mask_bits, bo); + rb.metadata.metadata_ring_ptr = + gtoh(rb.metadata.metadata_ring_ptr, bo); + rb.metadata.info_ring_ptr = + gtoh(rb.metadata.info_ring_ptr, bo); + + rb.data.mask_bits = gtoh(rb.data.mask_bits, bo); + rb.data.data_ring_ptr = gtoh(rb.data.data_ring_ptr, bo); + rb.data.head_offset = gtoh(rb.data.head_offset, bo); + rb.data.tail_offset = gtoh(rb.data.tail_offset, bo); + + // Mask offsets to the correct number of bits + rb.data.head_offset = + rb.mask_data_offset(rb.data.head_offset); + rb.data.tail_offset = + rb.mask_data_offset(rb.data.tail_offset); + + return rb; + } + + /** Make a mask for the bottom mask_bits of an `atomic_var_t`, then + * cast it to the required `as_type`. + */ + template + static as_type + make_offset_mask_as(const unsigned int mask_bits) + { + using unsigned_atomic_var_t = + typename std::make_unsigned::type; + const atomic_var_t offset_mask = + static_cast( + (static_cast(1) << mask_bits) - 1); + return static_cast(offset_mask); + } + + /** Make a mask for an offset into the metadata or info ringbuffers. */ + template + metadata_offset_t + make_metadata_offset_mask() const + { + return make_offset_mask_as(metadata.mask_bits); + } + + /** Make a mask for an offset into the data ringbuffer. */ + template + data_offset_t + make_data_offset_mask() const + { + return make_offset_mask_as(data.mask_bits); + } + + /** Apply the correct masking to an offset into the metadata or info + ringbuffers. */ + template + metadata_offset_t + mask_metadata_offset(const metadata_offset_t metadata_offset) const + { + const atomic_var_t MASK = + make_metadata_offset_mask(); + return metadata_offset & MASK; + } + + /** Apply the correct masking to an offset into the data ringbuffer. */ + template + data_offset_t + mask_data_offset(const data_offset_t data_offset) const + { + const atomic_var_t MASK = make_data_offset_mask(); + return data_offset & MASK; + } +}; + +// Aliases for the two types of Ringbuffer that could be used. +using Linux64_Ringbuffer = DmesgRingbuffer; +using Linux32_Ringbuffer = DmesgRingbuffer; + +/** Print the record at the specified offset into the data ringbuffer, + * and return the offset of the next entry in the data ringbuffer, + * post-v5.10. + * + * The `first_metadata_offset` argument is used to check for + * wraparound. If the final data record of the ringbuffer is not large + * enough to hold the message, the record will be left empty and + * repeated at the beginning of the data ringbuffer. In this case the + * metadata offset at the beginning of the last record will match the + * metadata offset of the first record of the ringbuffer, and the last + * record of the ring buffer should be skipped. + * + */ +template +atomic_var_t +iterateDataRingbuffer(std::ostream & os, + const TranslatingPortProxy & proxy, + const ringbuffer_t & rb, + const atomic_var_t offset, + const guest_ptr_t first_metadata_offset, + const ByteOrder bo) +{ + using metadata_record_t = typename ringbuffer_t::metadata_record_t; + + constexpr size_t METADATA_RECORD_SIZE = + sizeof(typename ringbuffer_t::metadata_record_t); + constexpr size_t INFO_RECORD_SIZE = sizeof(DmesgInfoRecord); + + const guest_ptr_t DATA_OFFSET_MASK = + rb.template make_data_offset_mask(); + + // Read the offset of the metadata record from the beginning of + // the data record. + guest_ptr_t metadata_info_offset = rb.mask_metadata_offset( + proxy.read(rb.data.data_ring_ptr + offset, bo)); + + // If the metadata offset of the block is the same as the metadata + // offset of the first block of the data ringbuffer, then this + // data block is unsused (padding), and the iteration can wrap + // around to the beginning of the data ringbuffer (offset == 0). + if (metadata_info_offset == first_metadata_offset) { + return static_cast(0); + } + + // Read the metadata record from the metadata ringbuffer. + guest_ptr_t metadata_address = + rb.metadata.metadata_ring_ptr + + (metadata_info_offset * METADATA_RECORD_SIZE); + metadata_record_t metadata = + metadata_record_t::read(proxy, metadata_address, DATA_OFFSET_MASK, bo); + + // Read the info record from the info ringbuffer. + guest_ptr_t info_address = + rb.metadata.info_ring_ptr + + (metadata_info_offset * INFO_RECORD_SIZE); + DmesgInfoRecord info = + DmesgInfoRecord::read(proxy, info_address, bo); + + // The metadata record should point back to the same data record + // in the data ringbuffer. + if (metadata.data_buffer.curr_offset != offset) { + warn_once("Dmesg dump: metadata record (at 0x%08x) does not point " + "back to the correponding data record (at 0x%08x). Dmesg " + "buffer may be corrupted", + metadata.data_buffer.next_offset, offset); + } + + // Read the message from the data record. This is placed + // immediately after the `guest_ptr_t` sized metadata offset at + // the beginning of the record. + std::vector message(info.message_size); + proxy.readBlob(rb.data.data_ring_ptr + offset + sizeof(guest_ptr_t), + message.data(), info.message_size); + + // Print the record + ccprintf(os, "[%.6f] ", info.ts_nsec * 10e-9); + os.write((char *)message.data(), info.message_size); + os << "\n"; + + // Return the offset of the next data record in the data + // ringbuffer. + return metadata.data_buffer.next_offset; +} + +/** Dump the kernel Dmesg ringbuffer for Linux versions post-v5.10. + + Templated implementation specific to 32-bit or 64-bit Linux. +*/ +template +void +dumpDmesgImpl(ThreadContext *tc, std::ostream &os) +{ + using atomic_var_t = typename ringbuffer_t::atomic_var_t; + using guest_ptr_t = typename ringbuffer_t::guest_ptr_t; + + System *system = tc->getSystemPtr(); + const ByteOrder bo = system->getGuestByteOrder(); + const auto &symtab = system->workload->symtab(tc); + TranslatingPortProxy proxy(tc); + + auto symtab_end_it = symtab.end(); + + // Read the dynamic ringbuffer structure from guest memory, if present. + ringbuffer_t dynamic_rb; + auto dynamic_rb_symbol = symtab.find("printk_rb_dynamic"); + if (dynamic_rb_symbol != symtab_end_it) { + dynamic_rb = ringbuffer_t::read(proxy, dynamic_rb_symbol->address, bo); + } else { + warn("Failed to find required dmesg symbols.\n"); + return; + } + + // Read the static ringbuffer structure from guest memory, if present. + ringbuffer_t static_rb; + auto static_rb_symbol = symtab.find("printk_rb_static"); + if (static_rb_symbol != symtab_end_it) { + static_rb = ringbuffer_t::read(proxy, static_rb_symbol->address, bo); + } else { + warn("Failed to find required dmesg symbols.\n"); + return; + } + + // Read the pointer to the active ringbuffer structure from guest + // memory. This should point to one of the two ringbuffer + // structures already read from guest memory. + guest_ptr_t active_ringbuffer_ptr = 0x0; + auto active_ringbuffer_ptr_symbol = symtab.find("prb"); + if (active_ringbuffer_ptr_symbol != symtab_end_it) { + active_ringbuffer_ptr = + proxy.read(active_ringbuffer_ptr_symbol->address, bo); + } else { + warn("Failed to find required dmesg symbols.\n"); + return; + } + + if (active_ringbuffer_ptr == 0 || + (active_ringbuffer_ptr != dynamic_rb_symbol->address && + active_ringbuffer_ptr != static_rb_symbol->address)) { + warn("Kernel Dmesg ringbuffer appears to be invalid.\n"); + return; + } + + ringbuffer_t & rb = + (active_ringbuffer_ptr == dynamic_rb_symbol->address) + ? dynamic_rb : static_rb; + + atomic_var_t head_offset = rb.data.head_offset; + atomic_var_t tail_offset = rb.data.tail_offset; + + // Get some marker offsets into the data ringbuffer which will be + // used as end values to control the iteration. + const guest_ptr_t first_metadata_offset = rb.mask_metadata_offset( + proxy.read(rb.data.data_ring_ptr, bo)); + const guest_ptr_t invalid_metadata_offset = + rb.template make_metadata_offset_mask() + 1; + + // Iterate over the active ringbuffer, printing each message to + // `os`. Use the maximum number of possible info records plus one + // (invalid_metadata_offset) as an escape counter to make sure the + // process doesn't iterate infinitely if the kernel data + // structures have been corrupted. + + // When head is behind tail, read to the end of the ringbuffer, + // then loop back to the begining. + // + // iterateDataRingbuffer will return offset at the beginning of + // the data ringbuffer when it loops back. + // + // `first_metadata_offset` is used to detect cases where the final data + // block is unused. + guest_ptr_t count = 0; + while (head_offset < tail_offset && count < invalid_metadata_offset) { + tail_offset = + iterateDataRingbuffer( + os, proxy, rb, tail_offset, first_metadata_offset, bo); + ++count; + } + + // When tail is behind head, read forwards from the tail offset to + // the head offset. + count = 0; + while (tail_offset < head_offset && count < invalid_metadata_offset) { + tail_offset = + iterateDataRingbuffer( + os, proxy, rb, tail_offset, invalid_metadata_offset, bo); + ++count; + } +} + +/** Dump the kernel Dmesg ringbuffer for Linux versions post-v5.10. + * + * Delegates to an architecture specific template funtion instance. + * + */ +void +dumpDmesg(ThreadContext *tc, std::ostream &os) +{ + System *system = tc->getSystemPtr(); + const bool os_is_64_bit = loader::archIs64Bit(system->workload->getArch()); + + if (os_is_64_bit) { + dumpDmesgImpl(tc, os); + } else { + dumpDmesgImpl(tc, os); + } +} + +} // namespace post5_10 + +} // anonymous namespace + +void +dumpDmesg(ThreadContext *tc, std::ostream &os) +{ + System *system = tc->getSystemPtr(); + const auto &symtab = system->workload->symtab(tc); + + auto end_it = symtab.end(); + + // Search for symbols associated with the Kernel Dmesg ringbuffer, + // pre-v5.10. + auto lb = symtab.find("__log_buf"); + auto lb_len = symtab.find("log_buf_len"); + auto first = symtab.find("log_first_idx"); + auto next = symtab.find("log_next_idx"); + + if (lb != end_it && lb_len != end_it && + first != end_it && next != end_it) { + linux::pre5_10::dumpDmesg(tc, os); + return; + } + + // Search for symbols associated with the Kernel Dmesg ringbuffer, + // post-v5.10. + auto printk_rb_static = symtab.find("printk_rb_static"); + auto printk_rb_dynamic = symtab.find("printk_rb_dynamic"); + + if (printk_rb_dynamic != end_it || printk_rb_static != end_it) { + linux::post5_10::dumpDmesg(tc, os); + return; + } + + // Required symbols relating to the Kernel Dmesg buffer were not + // found for any supported version of Linux. + warn("Failed to find kernel dmesg symbols.\n"); +} + +} // namespace linux + } // namespace gem5