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