blob: d961988bd09ce7c3286ec79e2cf67ebd012ec07b [file] [log] [blame]
/* Copyright 2023 The ChromiumOS Authors
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
/*
* ec_coredump utility for extracting a coredump from the EC and storing it in
* the Zephyr coredump format. If the EC does not support coredump, this utility
* will fail and not create any files. On success, two files will be created:
* `coredump` and `panicinfo`. The panicinfo file is useful for coupling with
* a panicinfo captured by the crash collector.
*/
#include "comm-host.h"
#include "misc_util.h"
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <inttypes.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <chromeos/ec/panic_defs.h>
#include <fstream>
#include <iostream>
#include <vector>
#define CROS_EC_DEV_NAME "cros_ec"
bool verbose = false;
struct mem_segment {
uint32_t addr_start;
uint32_t addr_end;
uint32_t size;
uint8_t *mem;
struct mem_segment *next;
};
#define COREDUMP_HDR_VER 1
#define COREDUMP_ARCH_HDR_ID 'A'
#define COREDUMP_MEM_HDR_ID 'M'
#define COREDUMP_MEM_HDR_VER 1
#define COREDUMP_BASENAME "coredump"
#define PANICINFO_BASENAME "panicinfo"
/* Target code */
enum coredump_tgt_code {
COREDUMP_TGT_UNKNOWN = 0,
COREDUMP_TGT_X86,
COREDUMP_TGT_X86_64,
COREDUMP_TGT_ARM_CORTEX_M,
COREDUMP_TGT_RISC_V,
COREDUMP_TGT_XTENSA,
COREDUMP_TGT_NDS32,
};
struct arm_arch_block {
struct {
uint32_t r0;
uint32_t r1;
uint32_t r2;
uint32_t r3;
uint32_t r12;
uint32_t lr;
uint32_t pc;
uint32_t xpsr;
uint32_t sp;
/* callee registers - optionally collected in V2 */
uint32_t r4;
uint32_t r5;
uint32_t r6;
uint32_t r7;
uint32_t r8;
uint32_t r9;
uint32_t r10;
uint32_t r11;
} r;
} __packed;
/* Coredump header */
struct coredump_hdr_t {
/* 'Z', 'E' */
char id[2];
/* Header version */
uint16_t hdr_version;
/* Target code */
uint16_t tgt_code;
/* Pointer size in Log2 */
uint8_t ptr_size_bits;
uint8_t flag;
/* Coredump Reason given */
unsigned int reason;
} __packed;
/* Architecture-specific block header */
struct coredump_arch_hdr_t {
/* COREDUMP_ARCH_HDR_ID */
char id;
/* Header version */
uint16_t hdr_version;
/* Number of bytes in this block (excluding header) */
uint16_t num_bytes;
} __packed;
/* Memory block header */
struct coredump_mem32_hdr_t {
/* COREDUMP_MEM_HDR_ID */
char id;
/* Header version */
uint16_t hdr_version;
/* Address of start of memory region */
uint32_t start;
/* Address of end of memory region */
uint32_t end;
} __packed;
struct coredump_mem64_hdr_t {
/* COREDUMP_MEM_HDR_ID */
char id;
/* Header version */
uint16_t hdr_version;
/* Address of start of memory region */
uint64_t start;
/* Address of end of memory region */
uint64_t end;
} __packed;
static int write_zephyr_coredump_memory_block(struct mem_segment &segment,
std::ofstream &output_file)
{
struct coredump_mem32_hdr_t hdr = {
.id = COREDUMP_MEM_HDR_ID,
.hdr_version = COREDUMP_MEM_HDR_VER,
.start = segment.addr_start,
.end = segment.addr_end,
};
if (verbose) {
std::cout << "Writing Zephyr coredump memory block..."
<< std::endl;
std::cout << "\tStart: " << std::hex << hdr.start << std::endl
<< "\tEnd: " << std::hex << hdr.end << std::endl;
}
output_file.write((char *)&hdr, sizeof(hdr));
output_file.write((char *)segment.mem, segment.size);
return 0;
}
static int write_zephyr_coredump_cortex_arch_info(struct panic_data &pdata,
std::ofstream &output_file)
{
struct arm_arch_block arch_blk;
struct coredump_arch_hdr_t hdr = {
.id = COREDUMP_ARCH_HDR_ID,
.hdr_version = 2,
.num_bytes = sizeof(arch_blk),
};
if (verbose)
std::cout << "Writing " << hdr.num_bytes
<< " byte arch info header..." << std::endl;
(void)memset(&arch_blk, 0, sizeof(arch_blk));
arch_blk.r.r0 = pdata.cm.frame[CORTEX_PANIC_FRAME_REGISTER_R0];
arch_blk.r.r1 = pdata.cm.frame[CORTEX_PANIC_FRAME_REGISTER_R1];
arch_blk.r.r2 = pdata.cm.frame[CORTEX_PANIC_FRAME_REGISTER_R2];
arch_blk.r.r3 = pdata.cm.frame[CORTEX_PANIC_FRAME_REGISTER_R3];
arch_blk.r.r12 = pdata.cm.frame[CORTEX_PANIC_FRAME_REGISTER_R12];
arch_blk.r.lr = pdata.cm.frame[CORTEX_PANIC_FRAME_REGISTER_LR];
arch_blk.r.pc = pdata.cm.frame[CORTEX_PANIC_FRAME_REGISTER_PC];
arch_blk.r.xpsr = pdata.cm.frame[CORTEX_PANIC_FRAME_REGISTER_PSR];
arch_blk.r.sp = pdata.cm.regs[CORTEX_PANIC_REGISTER_PSP];
arch_blk.r.r4 = pdata.cm.regs[CORTEX_PANIC_REGISTER_R4];
arch_blk.r.r5 = pdata.cm.regs[CORTEX_PANIC_REGISTER_R5];
arch_blk.r.r6 = pdata.cm.regs[CORTEX_PANIC_REGISTER_R6];
arch_blk.r.r7 = pdata.cm.regs[CORTEX_PANIC_REGISTER_R7];
arch_blk.r.r8 = pdata.cm.regs[CORTEX_PANIC_REGISTER_R8];
arch_blk.r.r9 = pdata.cm.regs[CORTEX_PANIC_REGISTER_R9];
arch_blk.r.r10 = pdata.cm.regs[CORTEX_PANIC_REGISTER_R10];
arch_blk.r.r11 = pdata.cm.regs[CORTEX_PANIC_REGISTER_R11];
output_file.write((char *)&hdr, sizeof(hdr));
output_file.write((char *)&arch_blk, sizeof(arch_blk));
return 0;
}
static int write_zephyr_coredump_header(struct panic_data &pdata,
std::ofstream &output_file)
{
struct coredump_hdr_t hdr = {
.id = { 'Z', 'E' },
.hdr_version = COREDUMP_HDR_VER,
};
if (verbose)
std::cout << "Writing Zephyr coredump header..." << std::endl;
switch (pdata.arch) {
case PANIC_ARCH_CORTEX_M:
hdr.tgt_code = COREDUMP_TGT_ARM_CORTEX_M;
hdr.ptr_size_bits = 5; /* 2^5 = 32 */
hdr.reason = pdata.cm.regs[CORTEX_PANIC_REGISTER_R4];
break;
case PANIC_ARCH_NDS32_N8:
hdr.tgt_code = COREDUMP_TGT_NDS32;
hdr.ptr_size_bits = 5; /* 2^5 = 32 */
break;
case PANIC_ARCH_RISCV_RV32I:
hdr.tgt_code = COREDUMP_TGT_RISC_V;
hdr.ptr_size_bits = 5; /* 2^5 = 32 */
hdr.reason = pdata.cm.regs[11];
break;
default:
std::cerr << "Error: Unknown architecture" << std::endl;
return -1;
}
output_file.write((char *)&hdr, sizeof(hdr));
return 0;
}
static int get_panic_info(struct panic_data &pdata)
{
int bytes_read;
struct ec_params_get_panic_info_v1 params = {
.preserve_old_hostcmd_flag = 1,
};
if (verbose)
std::cout << "Getting panic info..." << std::endl;
bytes_read = ec_command(EC_CMD_GET_PANIC_INFO, 1, &params,
sizeof(params), ec_inbuf, ec_max_insize);
if (bytes_read < 0) {
std::cerr << "Error: Failed to get panic info" << std::endl;
return -1;
}
if (bytes_read == 0) {
std::cerr << "Error: Panic info is empty" << std::endl;
return -1;
}
if (bytes_read > sizeof(pdata)) {
std::cerr << "Error: Panic info larger than expected"
<< std::endl;
return -1;
}
memcpy(&pdata, ec_inbuf, bytes_read);
if (pdata.struct_version > 2 || pdata.struct_version == 0) {
std::cerr << "Error: Unexpected struct version: "
<< pdata.struct_version << std::endl;
return -1;
}
if (pdata.struct_size != bytes_read) {
std::cerr
<< "Error: Panic info struct_size does not match bytes read"
<< std::endl;
return -1;
}
if (pdata.reserved != 0) {
std::cerr << "Error: Unexpected panic reserved value "
<< pdata.reserved << std::endl;
return -1;
}
return 0;
}
static int write_panic_info(struct panic_data &pdata,
std::ofstream &output_file)
{
if (verbose)
std::cout << "Writing " << std::dec << pdata.struct_size
<< " bytes of panic info..." << std::endl;
output_file.write((char *)&pdata, sizeof(pdata));
return 0;
}
static void print_segment(struct mem_segment *segment)
{
std::cout << "\tStart: " << std::hex << segment->addr_start << std::endl
<< "\tEnd: " << std::hex << segment->addr_end << std::endl
<< "\tSize: " << std::hex << segment->size << std::endl;
}
static void print_segments(struct mem_segment *root)
{
struct mem_segment *current = root;
int i = 0;
while (current) {
std::cout << "Segment " << i++ << ":" << std::endl;
print_segment(current);
current = current->next;
}
}
static struct mem_segment *get_segment(uint16_t index)
{
int rv;
uint32_t offset = 0;
struct mem_segment *segment = nullptr;
struct ec_response_memory_dump_get_entry_info entry_info_response;
struct ec_params_memory_dump_get_entry_info entry_info_params = {
.memory_dump_entry_index = index,
};
if (verbose)
std::cout << "Fetching memory dump entry " << index << "..."
<< std::endl;
rv = ec_command(EC_CMD_MEMORY_DUMP_GET_ENTRY_INFO, 0,
&entry_info_params, sizeof(entry_info_params),
&entry_info_response, sizeof(entry_info_response));
if (rv < 0) {
std::cerr << "Error: Failed to get memory dump info for entry "
<< std::endl;
goto cleanup;
}
if (verbose)
std::cout << "\tStart address: " << std::hex
<< entry_info_response.address << std::endl
<< "\tSize: " << std::hex << entry_info_response.size
<< std::endl;
segment = (struct mem_segment *)malloc(sizeof(struct mem_segment));
if (segment == NULL) {
std::cerr << "Error: malloc failed\n";
goto cleanup;
}
segment->addr_start = entry_info_response.address;
segment->addr_end =
entry_info_response.address + entry_info_response.size;
segment->size = entry_info_response.size;
segment->mem = (uint8_t *)malloc(segment->size);
if (segment->mem == NULL) {
std::cerr << "Error: malloc failed\n";
goto cleanup;
}
/* Keep fetching until entire segment is copied */
while (offset < segment->size) {
struct ec_params_memory_dump_read_memory read_mem_params = {
.memory_dump_entry_index = index,
.address = segment->addr_start + offset,
.size = segment->size - offset,
};
rv = ec_command(EC_CMD_MEMORY_DUMP_READ_MEMORY, 0,
&read_mem_params, sizeof(read_mem_params),
ec_inbuf, ec_max_insize);
if (rv <= 0) {
std::cerr << "Error: Failed to read EC memory at "
<< std::hex << read_mem_params.address
<< std::endl;
goto cleanup;
}
memcpy(segment->mem + offset, ec_inbuf, rv);
offset += rv;
};
return segment;
cleanup:
if (segment) {
free(segment->mem);
free(segment);
}
return nullptr;
}
/* Insert segment into linked list sorted by start address */
static struct mem_segment *insert_segment_sorted(struct mem_segment *root,
struct mem_segment *segment)
{
struct mem_segment *current = root;
struct mem_segment *prev = nullptr;
if (verbose)
std::cout << "Inserting segment into sorted list..."
<< std::endl;
while (current && current->addr_start < segment->addr_start) {
prev = current;
current = current->next;
}
if (prev == nullptr) {
/* Insert segment at the beginning of the list. */
segment->next = root;
root = segment;
} else {
/* Insert segment before the current segment. */
segment->next = current;
prev->next = segment;
}
return root;
}
/* Merge memory segments that are overlapping or touching.
* Assumes list is already sorted by starting address
*/
int merge_segments(struct mem_segment *root)
{
struct mem_segment *current = root;
int i = 0;
if (verbose)
std::cout << "Merging segments..." << std::endl;
while (current && current->next) {
struct mem_segment *next = current->next;
if (current->addr_end < next->addr_start) {
// No overlap
current = next;
i++;
continue;
}
int32_t overlap = current->addr_end - next->addr_start;
int32_t new_size = current->size + next->size - overlap;
if (verbose) {
std::cout << "Merging segment " << i++ << " and " << i++
<< ", with " << overlap << " byte overlap..."
<< std::endl;
}
current->mem = (uint8_t *)realloc(current->mem, new_size);
if (current->mem == NULL) {
std::cerr << "Error: realloc failed" << std::endl;
return -1;
}
memcpy(current->mem + current->size, next->mem + overlap,
next->size - overlap);
current->size = new_size;
current->addr_end = next->addr_end;
current->next = next->next;
free(next->mem);
free(next);
current = current->next;
}
return 0;
}
struct mem_segment *get_segments(void)
{
int rv;
/* Simple local structs for storing a memory dump */
uint16_t entry_count;
struct mem_segment *root = nullptr;
struct ec_response_memory_dump_get_metadata metadata_response;
/* Fetch memory dump metadata */
if (verbose)
std::cout << "Getting memory dump metadata..." << std::endl;
rv = ec_command(EC_CMD_MEMORY_DUMP_GET_METADATA, 0, NULL, 0,
&metadata_response, sizeof(metadata_response));
if (rv < 0) {
std::cerr << "Error: Failed to get memory dump metadata from EC"
<< std::endl;
goto cleanup;
}
entry_count = metadata_response.memory_dump_entry_count;
if (entry_count == 0) {
std::cerr << "Error: EC memory dump is empty" << std::endl;
goto cleanup;
}
if (verbose)
std::cout << "Fetching " << entry_count
<< " memory dump entries..." << std::endl;
/* Fetch all memory segments */
for (uint16_t index = 0; index < entry_count; index++) {
struct mem_segment *segment = get_segment(index);
if (segment == NULL) {
std::cerr << "Error: Failed to get segment " << index
<< std::endl;
goto cleanup;
}
/* Insert segment into sorted linked list */
root = insert_segment_sorted(root, segment);
}
if (verbose)
print_segments(root);
if (merge_segments(root) != 0) {
std::cerr << "Failed to merge segments" << std::endl;
goto cleanup;
}
if (verbose)
print_segments(root);
return root;
cleanup:
struct mem_segment *current = root;
while (current) {
struct mem_segment *next = current->next;
free(current->mem);
free(current);
current = next;
}
return nullptr;
}
void print_help()
{
std::cout << "Usage: ec_coredump [OPTIONS] OUTPUT_PATH\n"
<< "Options:\n"
<< " -v, --verbose\tDisplay verbose output\n"
<< " -h, --help\tShow this help message and exit\n";
}
int main(int argc, char *argv[])
{
int rv;
std::vector<std::string> args(argv + 1, argv + argc);
std::string output_path;
struct panic_data pdata;
for (size_t i = 0; i < args.size(); ++i) {
if (args[i] == "-v" || args[i] == "--verbose") {
verbose = true;
} else if (args[i] == "-h" || args[i] == "--help") {
print_help();
return 0;
} else if (output_path.empty()) {
output_path = args[i];
} else {
std::cerr << "Error: Invalid argument '" << args[i]
<< "'.\n";
return -1;
}
}
if (comm_init_dev(CROS_EC_DEV_NAME)) {
std::cerr << "Error: Failed to initialize " << CROS_EC_DEV_NAME
<< std::endl;
return -1;
}
if (comm_init_buffer()) {
std::cerr << "Error: Failed to initialize buffers\n";
return -1;
}
if (output_path.empty()) {
std::cerr << "Error: No coredump output path provided"
<< std::endl;
return -1;
}
rv = get_panic_info(pdata);
if (rv != 0) {
std::cerr << "Error: Failed to get panic info" << std::endl;
return -1;
}
std::string panic_output_filename(output_path + "/" +
PANICINFO_BASENAME);
if (verbose)
std::cout << "Opening panicinfo output file '"
<< panic_output_filename << "'..." << std::endl;
std::ofstream panic_output_file(panic_output_filename);
if (!panic_output_file) {
std::cerr << "Error: Unable to open panic output file '"
<< panic_output_filename << "'" << std::endl;
return -1;
}
rv = write_panic_info(pdata, panic_output_file);
panic_output_file.close();
if (rv != 0) {
std::cerr << "Error: Failed to write panic info to "
<< panic_output_filename << std::endl;
return -1;
}
std::string coredump_output_filename(output_path + "/" +
COREDUMP_BASENAME);
if (verbose)
std::cout << "Opening coredump output file '"
<< coredump_output_filename << "'..." << std::endl;
std::ofstream coredump_output_file(coredump_output_filename);
if (!coredump_output_file) {
std::cerr << "Error: Unable to open coredump output file '"
<< coredump_output_filename << "'" << std::endl;
return -1;
}
rv = write_zephyr_coredump_header(pdata, coredump_output_file);
if (rv != 0) {
std::cerr << "Error: Failed to write zephyr coredump header"
<< std::endl;
coredump_output_file.close();
return -1;
}
switch (pdata.arch) {
case PANIC_ARCH_CORTEX_M:
rv = write_zephyr_coredump_cortex_arch_info(
pdata, coredump_output_file);
break;
default:
std::cerr << "Error: Unhandled architecture" << std::endl;
rv = -1;
}
if (rv != 0) {
std::cerr << "Error: Failed to write zephyr coredump arch info"
<< std::endl;
coredump_output_file.close();
return -1;
}
struct mem_segment *segments = get_segments();
if (segments == nullptr) {
std::cerr << "Error: Failed to get segments" << std::endl;
coredump_output_file.close();
return -1;
}
while (segments != nullptr) {
struct mem_segment *next = segments->next;
/* Loop will continue after error so segments are free'd */
if (rv == 0) {
rv = write_zephyr_coredump_memory_block(
*segments, coredump_output_file);
if (rv != 0)
std::cerr
<< "Error: Failed to write zephyr coredump memory block"
<< std::endl;
}
free(segments->mem);
free(segments);
segments = next;
}
coredump_output_file.close();
return rv;
}
OSZAR »