blob: b0242269851b13a284cc0e453fe6261b235fbe2b [file] [log] [blame]
/*
* Copyright (c) 2016, 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.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met: redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer;
* redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution;
* neither the name of the copyright holders nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "kern/linux/helpers.hh"
#include <type_traits>
#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 linux {
namespace {
namespace pre5_10 {
/** Dmesg entry for Linux versions pre-v5.10 */
struct GEM5_PACKED DmesgEntry
{
uint64_t ts_nsec;
uint16_t len;
uint16_t text_len;
uint16_t dict_len;
uint8_t facility;
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,
std::ostream &os)
{
const size_t max_length = end - base;
DmesgEntry de;
if (max_length < sizeof(de)) {
warn("Malformed dmesg entry\n");
return -1;
}
memcpy(&de, base, sizeof(de));
de.ts_nsec = gtoh(de.ts_nsec, bo);
de.len = gtoh(de.len, bo);
de.text_len = gtoh(de.text_len, bo);
if (de.len < sizeof(de) ||
max_length < de.len ||
max_length < sizeof(DmesgEntry) + de.text_len) {
warn("Malformed dmesg entry:\n");
warn("\tMax length: %i\n", max_length);
warn("\tde.len: %i\n", de.len);
warn("\tde.text_len: %i\n", de.text_len);
return -1;
}
ccprintf(os, "[%.6f] ", de.ts_nsec * 10e-9);
os.write((char *)base + sizeof(de), de.text_len);
os << std::endl;
return de.len;
}
/** Dump the kernel Dmesg ringbuffer for Linux versions pre-v5.10 */
void
dumpDmesg(ThreadContext *tc, std::ostream &os)
{
System *system = tc->getSystemPtr();
const ByteOrder bo = system->getGuestByteOrder();
const auto &symtab = system->workload->symtab(tc);
TranslatingPortProxy proxy(tc);
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");
auto end_it = symtab.end();
if (lb == end_it || lb_len == end_it ||
first == end_it || next == end_it) {
warn("Failed to find kernel dmesg symbols.\n");
return;
}
uint32_t log_buf_len = proxy.read<uint32_t>(lb_len->address, bo);
uint32_t log_first_idx = proxy.read<uint32_t>(first->address, bo);
uint32_t log_next_idx = proxy.read<uint32_t>(next->address, bo);
if (log_first_idx >= log_buf_len || log_next_idx >= log_buf_len) {
warn("dmesg pointers/length corrupted\n");
return;
}
// Normalize and read the dmesg ring buffer
std::vector<uint8_t> log_buf(log_buf_len);
int length;
if (log_first_idx < log_next_idx) {
length = log_next_idx - log_first_idx;
if (length < 0 || length > log_buf.size()) {
warn("Unexpected dmesg buffer length\n");
return;
}
proxy.readBlob(lb->address + log_first_idx, log_buf.data(), length);
} else {
const int length_2 = log_buf_len - log_first_idx;
if (length_2 < 0 || length_2 + log_next_idx > log_buf.size()) {
warn("Unexpected dmesg buffer length\n");
return;
}
length = log_buf_len;
proxy.readBlob(lb->address + log_first_idx, log_buf.data(), length_2);
proxy.readBlob(lb->address, log_buf.data() + length_2, log_next_idx);
}
// Print dmesg buffer content
const uint8_t *cur = log_buf.data(), *end = log_buf.data() + length;
while (cur < end) {
int ret = dumpDmesgEntry(cur, end, bo, os);
if (ret < 0)
return;
cur += ret;
}
}
} // 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<typename atomic_var_t>
struct GEM5_PACKED DmesgMetadataRecord
{
using guest_ptr_t = typename std::make_unsigned_t<atomic_var_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<typename AtomicVarType>
struct GEM5_PACKED DmesgRingbuffer
{
static_assert(
std::disjunction<
std::is_same<AtomicVarType, int32_t>,
std::is_same<AtomicVarType, int64_t>
>::value,
"AtomicVarType must be int32_t or int64_t");
using atomic_var_t = AtomicVarType;
using guest_ptr_t = typename std::make_unsigned_t<atomic_var_t>;
using metadata_record_t = DmesgMetadataRecord<atomic_var_t>;
// 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<typename as_type>
static as_type
make_offset_mask_as(const unsigned int mask_bits)
{
using unsigned_atomic_var_t =
typename std::make_unsigned<atomic_var_t>::type;
const atomic_var_t offset_mask =
static_cast<atomic_var_t>(
(static_cast<unsigned_atomic_var_t>(1) << mask_bits) - 1);
return static_cast<as_type>(offset_mask);
}
/** Make a mask for an offset into the metadata or info ringbuffers. */
template<typename metadata_offset_t>
metadata_offset_t
make_metadata_offset_mask() const
{
return make_offset_mask_as<metadata_offset_t>(metadata.mask_bits);
}
/** Make a mask for an offset into the data ringbuffer. */
template<typename data_offset_t>
data_offset_t
make_data_offset_mask() const
{
return make_offset_mask_as<data_offset_t>(data.mask_bits);
}
/** Apply the correct masking to an offset into the metadata or info
ringbuffers. */
template<typename metadata_offset_t>
metadata_offset_t
mask_metadata_offset(const metadata_offset_t metadata_offset) const
{
const atomic_var_t MASK =
make_metadata_offset_mask<metadata_offset_t>();
return metadata_offset & MASK;
}
/** Apply the correct masking to an offset into the data ringbuffer. */
template<typename data_offset_t>
data_offset_t
mask_data_offset(const data_offset_t data_offset) const
{
const atomic_var_t MASK = make_data_offset_mask<data_offset_t>();
return data_offset & MASK;
}
};
// Aliases for the two types of Ringbuffer that could be used.
using Linux64_Ringbuffer = DmesgRingbuffer<int64_t>;
using Linux32_Ringbuffer = DmesgRingbuffer<int32_t>;
/** 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 <typename ringbuffer_t,
typename atomic_var_t=typename ringbuffer_t::atomic_var_t,
typename guest_ptr_t=typename ringbuffer_t::guest_ptr_t>
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<guest_ptr_t>();
// 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<guest_ptr_t>(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<atomic_var_t>(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<uint8_t> 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 <typename ringbuffer_t>
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<guest_ptr_t>(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<guest_ptr_t>(rb.data.data_ring_ptr, bo));
const guest_ptr_t invalid_metadata_offset =
rb.template make_metadata_offset_mask<guest_ptr_t>() + 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<ringbuffer_t>(
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<ringbuffer_t>(
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<Linux64_Ringbuffer>(tc, os);
} else {
dumpDmesgImpl<Linux32_Ringbuffer>(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