mirror of
https://github.com/linux-msm/qdl.git
synced 2026-10-07 06:29:17 -07:00
When the kernel minidump backend is active, the Sahara debug table contains a KELF region — a complete ELF core header describing the physical address and output offset of every other kernel region. Detect KELF after download and stitch all kernel regions into a single minidump.elf, ready for crash or gdb, without any manual concatenation. Devices without minidump enabled are unaffected. Signed-off-by: Mukesh Ojha <mukesh.ojha@oss.qualcomm.com>
901 lines
23 KiB
C
901 lines
23 KiB
C
// SPDX-License-Identifier: BSD-3-Clause
|
|
/*
|
|
* Copyright (c) 2016-2017, Linaro Ltd.
|
|
* All rights reserved.
|
|
*/
|
|
#include <sys/types.h>
|
|
#include <ctype.h>
|
|
#include <dirent.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <inttypes.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include "qdl.h"
|
|
#include "oscompat.h"
|
|
|
|
/* Minimal ELF64 definitions — <elf.h> is not available on all platforms */
|
|
#define ELFMAG "\177ELF"
|
|
#define SELFMAG 4
|
|
#define ET_CORE 4
|
|
#define PT_LOAD 1
|
|
|
|
#define EI_NIDENT 16
|
|
typedef uint16_t Elf64_Half;
|
|
typedef uint32_t Elf64_Word;
|
|
typedef uint64_t Elf64_Addr;
|
|
typedef uint64_t Elf64_Off;
|
|
typedef uint64_t Elf64_Xword;
|
|
|
|
typedef struct {
|
|
unsigned char e_ident[EI_NIDENT];
|
|
Elf64_Half e_type;
|
|
Elf64_Half e_machine;
|
|
Elf64_Word e_version;
|
|
Elf64_Addr e_entry;
|
|
Elf64_Off e_phoff;
|
|
Elf64_Off e_shoff;
|
|
Elf64_Word e_flags;
|
|
Elf64_Half e_ehsize;
|
|
Elf64_Half e_phentsize;
|
|
Elf64_Half e_phnum;
|
|
Elf64_Half e_shentsize;
|
|
Elf64_Half e_shnum;
|
|
Elf64_Half e_shstrndx;
|
|
} Elf64_Ehdr;
|
|
|
|
typedef struct {
|
|
Elf64_Word p_type;
|
|
Elf64_Word p_flags;
|
|
Elf64_Off p_offset;
|
|
Elf64_Addr p_vaddr;
|
|
Elf64_Addr p_paddr;
|
|
Elf64_Xword p_filesz;
|
|
Elf64_Xword p_memsz;
|
|
Elf64_Xword p_align;
|
|
} Elf64_Phdr;
|
|
|
|
#define SAHARA_HELLO_CMD 0x1 /* Min protocol version 1.0 */
|
|
#define SAHARA_HELLO_RESP_CMD 0x2 /* Min protocol version 1.0 */
|
|
#define SAHARA_READ_DATA_CMD 0x3 /* Min protocol version 1.0 */
|
|
#define SAHARA_END_OF_IMAGE_CMD 0x4 /* Min protocol version 1.0 */
|
|
#define SAHARA_DONE_CMD 0x5 /* Min protocol version 1.0 */
|
|
#define SAHARA_DONE_RESP_CMD 0x6 /* Min protocol version 1.0 */
|
|
#define SAHARA_RESET_CMD 0x7 /* Min protocol version 1.0 */
|
|
#define SAHARA_RESET_RESP_CMD 0x8 /* Min protocol version 1.0 */
|
|
#define SAHARA_MEM_DEBUG_CMD 0x9 /* Min protocol version 2.0 */
|
|
#define SAHARA_MEM_READ_CMD 0xa /* Min protocol version 2.0 */
|
|
#define SAHARA_CMD_READY_CMD 0xb /* Min protocol version 2.1 */
|
|
#define SAHARA_SWITCH_MODE_CMD 0xc /* Min protocol version 2.1 */
|
|
#define SAHARA_EXECUTE_CMD 0xd /* Min protocol version 2.1 */
|
|
#define SAHARA_EXECUTE_RESP_CMD 0xe /* Min protocol version 2.1 */
|
|
#define SAHARA_EXECUTE_DATA_CMD 0xf /* Min protocol version 2.1 */
|
|
#define SAHARA_MEM_DEBUG64_CMD 0x10 /* Min protocol version 2.5 */
|
|
#define SAHARA_MEM_READ64_CMD 0x11 /* Min protocol version 2.5 */
|
|
#define SAHARA_READ_DATA64_CMD 0x12 /* Min protocol version 2.8 */
|
|
#define SAHARA_RESET_STATE_CMD 0x13 /* Min protocol version 2.9 */
|
|
#define SAHARA_WRITE_DATA_CMD 0x14 /* Min protocol version 3.0 */
|
|
|
|
#define SAHARA_VERSION 2
|
|
#define SAHARA_SUCCESS 0
|
|
|
|
#define SAHARA_MODE_IMAGE_TX_PENDING 0x0
|
|
#define SAHARA_MODE_IMAGE_TX_COMPLETE 0x1
|
|
#define SAHARA_MODE_MEMORY_DEBUG 0x2
|
|
#define SAHARA_MODE_COMMAND 0x3
|
|
|
|
#define SAHARA_HELLO_LENGTH 0x30
|
|
#define SAHARA_READ_DATA_LENGTH 0x14
|
|
#define SAHARA_READ_DATA64_LENGTH 0x20
|
|
#define SAHARA_END_OF_IMAGE_LENGTH 0x10
|
|
#define SAHARA_MEM_READ64_LENGTH 0x18
|
|
#define SAHARA_MEM_DEBUG64_LENGTH 0x18
|
|
#define SAHARA_DONE_LENGTH 0x8
|
|
#define SAHARA_DONE_RESP_LENGTH 0xc
|
|
#define SAHARA_RESET_LENGTH 0x8
|
|
|
|
#define DEBUG_BLOCK_SIZE (512u * 1024u)
|
|
|
|
#define SAHARA_CMD_TIMEOUT_MS 1000
|
|
|
|
struct sahara_pkt {
|
|
uint32_t cmd;
|
|
uint32_t length;
|
|
|
|
union {
|
|
struct {
|
|
uint32_t version;
|
|
uint32_t compatible;
|
|
uint32_t max_len;
|
|
uint32_t mode;
|
|
uint32_t reserved[6];
|
|
} hello_req;
|
|
struct {
|
|
uint32_t version;
|
|
uint32_t compatible;
|
|
uint32_t status;
|
|
uint32_t mode;
|
|
uint32_t reserved[6];
|
|
} hello_resp;
|
|
struct {
|
|
uint32_t image;
|
|
uint32_t offset;
|
|
uint32_t length;
|
|
} read_req;
|
|
struct {
|
|
uint32_t image;
|
|
uint32_t status;
|
|
} eoi;
|
|
struct {
|
|
} done_req;
|
|
struct {
|
|
uint32_t status;
|
|
} done_resp;
|
|
struct {
|
|
uint64_t addr;
|
|
uint64_t length;
|
|
} debug64_req;
|
|
struct {
|
|
uint64_t image;
|
|
uint64_t offset;
|
|
uint64_t length;
|
|
} read64_req;
|
|
};
|
|
};
|
|
|
|
struct sahara_debug_region64 {
|
|
uint64_t type;
|
|
uint64_t addr;
|
|
uint64_t length;
|
|
char region[20];
|
|
char filename[20];
|
|
};
|
|
|
|
static void sahara_send_reset(struct qdl_device *qdl)
|
|
{
|
|
struct sahara_pkt resp;
|
|
|
|
resp.cmd = SAHARA_RESET_CMD;
|
|
resp.length = SAHARA_RESET_LENGTH;
|
|
|
|
qdl_write(qdl, &resp, resp.length, SAHARA_CMD_TIMEOUT_MS);
|
|
}
|
|
|
|
static int sahara_send_hello_resp(struct qdl_device *qdl, unsigned int mode)
|
|
{
|
|
struct sahara_pkt resp = {};
|
|
|
|
resp.cmd = SAHARA_HELLO_RESP_CMD;
|
|
resp.length = SAHARA_HELLO_LENGTH;
|
|
resp.hello_resp.version = SAHARA_VERSION;
|
|
resp.hello_resp.compatible = 1;
|
|
resp.hello_resp.status = SAHARA_SUCCESS;
|
|
resp.hello_resp.mode = mode;
|
|
|
|
qdl_write(qdl, &resp, resp.length, SAHARA_CMD_TIMEOUT_MS);
|
|
return 0;
|
|
}
|
|
|
|
static int sahara_hello(struct qdl_device *qdl, struct sahara_pkt *pkt)
|
|
{
|
|
if (pkt->length != SAHARA_HELLO_LENGTH) {
|
|
ux_err("unexpected HELLO packet length %u\n", pkt->length);
|
|
sahara_send_reset(qdl);
|
|
return -1;
|
|
}
|
|
|
|
ux_debug("HELLO version: 0x%x compatible: 0x%x max_len: %d mode: %d\n",
|
|
pkt->hello_req.version, pkt->hello_req.compatible, pkt->hello_req.max_len, pkt->hello_req.mode);
|
|
|
|
return sahara_send_hello_resp(qdl, pkt->hello_req.mode);
|
|
}
|
|
|
|
static int sahara_read(struct qdl_device *qdl, struct sahara_pkt *pkt,
|
|
const struct sahara_image *images)
|
|
{
|
|
const struct sahara_image *image;
|
|
unsigned int image_idx;
|
|
size_t offset;
|
|
size_t len;
|
|
int ret;
|
|
|
|
if (pkt->length != SAHARA_READ_DATA_LENGTH) {
|
|
ux_err("unexpected READ_DATA packet length %u\n", pkt->length);
|
|
sahara_send_reset(qdl);
|
|
return -1;
|
|
}
|
|
|
|
ux_debug("READ image: %d offset: 0x%x length: 0x%x\n",
|
|
pkt->read_req.image, pkt->read_req.offset, pkt->read_req.length);
|
|
|
|
image_idx = pkt->read_req.image;
|
|
if (image_idx >= MAPPING_SZ || !images[image_idx].ptr) {
|
|
ux_err("device requested unknown image id %u, ensure that all Sahara images are provided\n",
|
|
image_idx);
|
|
sahara_send_reset(qdl);
|
|
return -1;
|
|
}
|
|
|
|
offset = pkt->read_req.offset;
|
|
len = pkt->read_req.length;
|
|
|
|
image = &images[image_idx];
|
|
if (offset > image->len || offset + len > image->len) {
|
|
ux_err("device requested invalid range of image %d\n", image_idx);
|
|
return -1;
|
|
}
|
|
|
|
if (offset == 0)
|
|
ux_info("Sahara: sending %s (%zu bytes)\n",
|
|
image->name ? image->name : "(unknown)", image->len);
|
|
ux_progress("%s", offset + len, image->len, image->name ? image->name : "image");
|
|
|
|
ret = qdl_write(qdl, image->ptr + offset, len, SAHARA_CMD_TIMEOUT_MS);
|
|
if (ret < 0 || ((size_t)ret != len)) {
|
|
ux_err("failed to write %zu bytes to sahara\n", len);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sahara_read64(struct qdl_device *qdl, struct sahara_pkt *pkt,
|
|
const struct sahara_image *images)
|
|
{
|
|
const struct sahara_image *image;
|
|
unsigned int image_idx;
|
|
size_t offset;
|
|
size_t len;
|
|
int ret;
|
|
|
|
if (pkt->length != SAHARA_READ_DATA64_LENGTH) {
|
|
ux_err("unexpected READ_DATA64 packet length %u\n", pkt->length);
|
|
sahara_send_reset(qdl);
|
|
return -1;
|
|
}
|
|
|
|
ux_debug("READ64 image: %" PRId64 " offset: 0x%" PRIx64 " length: 0x%" PRIx64 "\n",
|
|
pkt->read64_req.image, pkt->read64_req.offset, pkt->read64_req.length);
|
|
|
|
image_idx = pkt->read64_req.image;
|
|
if (image_idx >= MAPPING_SZ || !images[image_idx].ptr) {
|
|
ux_err("device requested unknown image id %u, ensure that all Sahara images are provided\n",
|
|
image_idx);
|
|
sahara_send_reset(qdl);
|
|
return -1;
|
|
}
|
|
|
|
offset = pkt->read64_req.offset;
|
|
len = pkt->read64_req.length;
|
|
|
|
image = &images[image_idx];
|
|
if (offset > image->len || offset + len > image->len) {
|
|
ux_err("device requested invalid range of image %d\n", image_idx);
|
|
return -1;
|
|
}
|
|
|
|
if (offset == 0)
|
|
ux_info("Sahara: sending %s (%zu bytes)\n",
|
|
image->name ? image->name : "(unknown)", image->len);
|
|
ux_progress("%s", offset + len, image->len, image->name ? image->name : "image");
|
|
|
|
ret = qdl_write(qdl, image->ptr + offset, len, SAHARA_CMD_TIMEOUT_MS);
|
|
if (ret < 0 || ((size_t)ret != len)) {
|
|
ux_err("failed to write %zu bytes to sahara\n", len);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sahara_eoi(struct qdl_device *qdl, struct sahara_pkt *pkt)
|
|
{
|
|
struct sahara_pkt done;
|
|
|
|
if (pkt->length != SAHARA_END_OF_IMAGE_LENGTH) {
|
|
ux_err("unexpected END_OF_IMAGE packet length %u\n", pkt->length);
|
|
sahara_send_reset(qdl);
|
|
return -1;
|
|
}
|
|
|
|
ux_debug("END OF IMAGE image: %d status: %d\n", pkt->eoi.image, pkt->eoi.status);
|
|
|
|
if (pkt->eoi.status != 0) {
|
|
ux_err("received non-successful end-of-image result\n");
|
|
return -1;
|
|
}
|
|
|
|
done.cmd = SAHARA_DONE_CMD;
|
|
done.length = SAHARA_DONE_LENGTH;
|
|
qdl_write(qdl, &done, done.length, SAHARA_CMD_TIMEOUT_MS);
|
|
return 0;
|
|
}
|
|
|
|
static int sahara_done(struct qdl_device *qdl, struct sahara_pkt *pkt)
|
|
{
|
|
if (pkt->length != SAHARA_DONE_RESP_LENGTH) {
|
|
ux_err("unexpected DONE_RESP packet length %u\n", pkt->length);
|
|
sahara_send_reset(qdl);
|
|
return -1;
|
|
}
|
|
|
|
ux_debug("DONE status: %d\n", pkt->done_resp.status);
|
|
|
|
// 0 == PENDING, 1 == COMPLETE. Device expects more images if
|
|
// PENDING is set in status.
|
|
return pkt->done_resp.status;
|
|
}
|
|
|
|
static ssize_t sahara_debug64_one(struct qdl_device *qdl,
|
|
struct sahara_debug_region64 region,
|
|
const char *ramdump_path)
|
|
{
|
|
struct sahara_pkt read_req;
|
|
uint64_t remain;
|
|
size_t offset, buf_offset;
|
|
size_t chunk;
|
|
size_t written;
|
|
ssize_t n;
|
|
void *buf;
|
|
int fd;
|
|
|
|
buf = malloc(DEBUG_BLOCK_SIZE);
|
|
if (!buf)
|
|
return -1;
|
|
|
|
char path[PATH_MAX];
|
|
|
|
snprintf(path, sizeof(path), "%s/%s", ramdump_path, region.filename);
|
|
|
|
fd = open(path, O_WRONLY | O_CREAT | O_BINARY, 0644);
|
|
if (fd < 0) {
|
|
warn("failed to open \"%s\"", region.filename);
|
|
free(buf);
|
|
return -1;
|
|
}
|
|
|
|
chunk = 0;
|
|
while (chunk < region.length) {
|
|
remain = MIN((uint64_t)(region.length - chunk), DEBUG_BLOCK_SIZE);
|
|
|
|
read_req.cmd = SAHARA_MEM_READ64_CMD;
|
|
read_req.length = SAHARA_MEM_READ64_LENGTH;
|
|
read_req.debug64_req.addr = region.addr + chunk;
|
|
read_req.debug64_req.length = remain;
|
|
n = qdl_write(qdl, &read_req, read_req.length, SAHARA_CMD_TIMEOUT_MS);
|
|
if (n < 0)
|
|
break;
|
|
|
|
offset = 0;
|
|
while (offset < remain) {
|
|
buf_offset = 0;
|
|
n = qdl_read(qdl, buf, DEBUG_BLOCK_SIZE, 30000);
|
|
if (n < 0) {
|
|
warn("failed to read ramdump chunk");
|
|
goto out;
|
|
}
|
|
|
|
while (buf_offset < (size_t)n) {
|
|
written = write(fd, buf + buf_offset, n - buf_offset);
|
|
if (written <= 0) {
|
|
warn("failed to write ramdump chunk to \"%s\"", region.filename);
|
|
goto out;
|
|
}
|
|
buf_offset += written;
|
|
}
|
|
|
|
offset += buf_offset;
|
|
}
|
|
|
|
qdl_read(qdl, buf, DEBUG_BLOCK_SIZE, 10);
|
|
|
|
chunk += DEBUG_BLOCK_SIZE;
|
|
|
|
ux_progress("%s", chunk, region.length, region.filename);
|
|
}
|
|
out:
|
|
|
|
close(fd);
|
|
free(buf);
|
|
|
|
return 0;
|
|
}
|
|
|
|
// simple pattern matching function supporting * and ?
|
|
bool pattern_match(const char *pattern, const char *string)
|
|
{
|
|
if (*pattern == '\0' && *string == '\0')
|
|
return true;
|
|
|
|
if (*pattern == '*')
|
|
return pattern_match(pattern + 1, string) ||
|
|
(*string != '\0' && pattern_match(pattern, string + 1));
|
|
|
|
if (*pattern == '?')
|
|
return (*string != '\0') && pattern_match(pattern + 1, string + 1);
|
|
|
|
if (*pattern == *string)
|
|
return pattern_match(pattern + 1, string + 1);
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool sahara_debug64_filter(const char *filename, const char *filter)
|
|
{
|
|
char stem[PATH_MAX] = {};
|
|
bool anymatch = false;
|
|
const char *dot;
|
|
char *ptr;
|
|
char *tmp;
|
|
char *s;
|
|
|
|
if (!filter)
|
|
return false;
|
|
|
|
/* Build a stem (filename without extension) for bare-name filter tokens */
|
|
dot = strrchr(filename, '.');
|
|
if (dot && dot != filename) {
|
|
size_t len = dot - filename;
|
|
|
|
if (len < sizeof(stem)) {
|
|
memcpy(stem, filename, len);
|
|
}
|
|
}
|
|
|
|
tmp = strdup(filter);
|
|
for (s = strtok_r(tmp, ",", &ptr); s; s = strtok_r(NULL, ",", &ptr)) {
|
|
if (pattern_match(s, filename) || (stem[0] && pattern_match(s, stem))) {
|
|
anymatch = true;
|
|
break;
|
|
}
|
|
}
|
|
free(tmp);
|
|
|
|
return !anymatch;
|
|
}
|
|
|
|
static int write_zeroes(FILE *out, size_t count)
|
|
{
|
|
static const char zeros[4096];
|
|
size_t chunk;
|
|
|
|
while (count > 0) {
|
|
chunk = count > sizeof(zeros) ? sizeof(zeros) : count;
|
|
if (fwrite(zeros, 1, chunk, out) != chunk)
|
|
return -1;
|
|
count -= chunk;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t copy_file_to(FILE *out, const char *path)
|
|
{
|
|
char buf[65536];
|
|
ssize_t written = 0;
|
|
FILE *in;
|
|
size_t n;
|
|
|
|
in = fopen(path, "rb");
|
|
if (!in)
|
|
return -1;
|
|
|
|
while ((n = fread(buf, 1, sizeof(buf), in)) > 0) {
|
|
if (fwrite(buf, 1, n, out) != n) {
|
|
fclose(in);
|
|
return -1;
|
|
}
|
|
written += n;
|
|
}
|
|
fclose(in);
|
|
return written;
|
|
}
|
|
|
|
/*
|
|
* sahara_build_minidump_elf() - assemble a minidump ELF from downloaded regions
|
|
* @ramdump_path: directory containing the downloaded region files
|
|
* @filter: the segment filter that was applied during download (may be NULL)
|
|
* @table: the Sahara debug region table received from the device
|
|
* @n_regions: number of entries in @table
|
|
*
|
|
* When the kernel minidump backend is active, one of the downloaded regions is
|
|
* named "KELF". It is a fully formed ELF core header whose PT_LOAD program
|
|
* headers describe the physical address and output offset of every other kernel
|
|
* region. A second region ("Kvmcorein") carries the vmcoreinfo data that fills
|
|
* the tail of the PT_NOTE section and has no PT_LOAD entry of its own.
|
|
*
|
|
* This function detects those two special regions, then iterates the PT_LOAD
|
|
* headers in p_offset order, matches each one to a downloaded region file by
|
|
* physical address, and concatenates everything into a single minidump.elf
|
|
* ready for crash or gdb.
|
|
*
|
|
* All segments are written sequentially because meminspect assigns p_offset
|
|
* values in a strictly increasing, contiguous order.
|
|
*/
|
|
static void sahara_build_minidump_elf(const char *ramdump_path, const char *filter,
|
|
const struct sahara_debug_region64 *table,
|
|
size_t n_regions)
|
|
{
|
|
char path[PATH_MAX];
|
|
char out_path[PATH_MAX];
|
|
Elf64_Ehdr *ehdr;
|
|
Elf64_Phdr *phdrs;
|
|
void *kelf_buf = NULL;
|
|
size_t kelf_size;
|
|
ssize_t kelf_idx = -1;
|
|
ssize_t vmcore_idx = -1;
|
|
ssize_t written;
|
|
FILE *out = NULL;
|
|
FILE *in;
|
|
size_t i, j;
|
|
bool matched;
|
|
|
|
/* Locate the KELF region; bail out silently if absent (not a minidump) */
|
|
for (i = 0; i < n_regions; i++) {
|
|
if (!strncmp(table[i].region, "md_KELF", 7)) {
|
|
kelf_idx = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (kelf_idx < 0)
|
|
return;
|
|
|
|
/* If KELF itself was excluded by the filter, nothing to do */
|
|
if (sahara_debug64_filter(table[kelf_idx].filename, filter))
|
|
return;
|
|
|
|
/* Read the KELF file into memory so we can parse its program headers */
|
|
snprintf(path, sizeof(path), "%s/%s", ramdump_path, table[kelf_idx].filename);
|
|
in = fopen(path, "rb");
|
|
if (!in) {
|
|
ux_err("minidump: failed to open KELF file %s\n", path);
|
|
return;
|
|
}
|
|
|
|
long kelf_size_l;
|
|
|
|
fseek(in, 0, SEEK_END);
|
|
kelf_size_l = ftell(in);
|
|
if (kelf_size_l <= 0) {
|
|
ux_err("minidump: failed to get KELF file size\n");
|
|
fclose(in);
|
|
return;
|
|
}
|
|
fseek(in, 0, SEEK_SET);
|
|
kelf_size = (size_t)kelf_size_l;
|
|
|
|
kelf_buf = malloc(kelf_size);
|
|
if (!kelf_buf) {
|
|
fclose(in);
|
|
return;
|
|
}
|
|
|
|
if (fread(kelf_buf, 1, kelf_size, in) != kelf_size) {
|
|
ux_err("minidump: failed to read KELF file\n");
|
|
fclose(in);
|
|
goto out_free;
|
|
}
|
|
fclose(in);
|
|
|
|
ehdr = kelf_buf;
|
|
if (kelf_size < sizeof(*ehdr) ||
|
|
memcmp(ehdr->e_ident, ELFMAG, SELFMAG) ||
|
|
ehdr->e_type != ET_CORE ||
|
|
ehdr->e_phentsize != sizeof(Elf64_Phdr) ||
|
|
ehdr->e_phoff > kelf_size ||
|
|
(size_t)ehdr->e_phnum * sizeof(Elf64_Phdr) > kelf_size - ehdr->e_phoff) {
|
|
ux_err("minidump: KELF does not look like a 64-bit ELF core file\n");
|
|
goto out_free;
|
|
}
|
|
|
|
phdrs = (Elf64_Phdr *)((char *)kelf_buf + ehdr->e_phoff);
|
|
|
|
/*
|
|
* Identify the vmcoreinfo region: among all K-prefixed regions, it is
|
|
* the one whose physical address has no matching PT_LOAD p_paddr entry.
|
|
* It fills the tail of the PT_NOTE section and must be placed immediately
|
|
* after the KELF content in the output file.
|
|
*/
|
|
for (i = 0; i < n_regions; i++) {
|
|
if ((ssize_t)i == kelf_idx || strncmp(table[i].region, "md_K", 4) != 0)
|
|
continue;
|
|
|
|
matched = false;
|
|
for (j = 0; j < ehdr->e_phnum; j++) {
|
|
if (phdrs[j].p_type == PT_LOAD &&
|
|
phdrs[j].p_paddr == table[i].addr) {
|
|
matched = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!matched) {
|
|
vmcore_idx = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
snprintf(out_path, sizeof(out_path), "%s/minidump.elf", ramdump_path);
|
|
out = fopen(out_path, "wb");
|
|
if (!out) {
|
|
ux_err("minidump: failed to create %s\n", out_path);
|
|
goto out_free;
|
|
}
|
|
|
|
/* 1. Write KELF: provides the ELF header, phdrs, and partial PT_NOTE */
|
|
if (fwrite(kelf_buf, 1, kelf_size, out) != kelf_size) {
|
|
ux_err("minidump: failed to write KELF to output\n");
|
|
goto out_close;
|
|
}
|
|
|
|
/* 2. Write vmcoreinfo immediately after KELF to complete the PT_NOTE */
|
|
if (vmcore_idx >= 0) {
|
|
snprintf(path, sizeof(path), "%s/%s",
|
|
ramdump_path, table[vmcore_idx].filename);
|
|
if (copy_file_to(out, path) < 0)
|
|
ux_err("minidump: vmcoreinfo region missing, crash may not load correctly\n");
|
|
}
|
|
|
|
/*
|
|
* 3. Write PT_LOAD segments in ascending p_offset order.
|
|
* meminspect assigns p_offset values contiguously in registration
|
|
* order so iterating the phdr array gives the correct sequence.
|
|
*/
|
|
for (j = 0; j < ehdr->e_phnum; j++) {
|
|
if (phdrs[j].p_type != PT_LOAD)
|
|
continue;
|
|
|
|
matched = false;
|
|
for (i = 0; i < n_regions; i++) {
|
|
if (table[i].addr != phdrs[j].p_paddr)
|
|
continue;
|
|
|
|
snprintf(path, sizeof(path), "%s/%s",
|
|
ramdump_path, table[i].filename);
|
|
written = copy_file_to(out, path);
|
|
if (written < 0) {
|
|
ux_err("minidump: region %s missing, skipping\n",
|
|
table[i].filename);
|
|
matched = true;
|
|
break;
|
|
}
|
|
|
|
/* Zero-pad to p_filesz to keep offsets aligned */
|
|
if ((size_t)written < phdrs[j].p_filesz &&
|
|
write_zeroes(out, phdrs[j].p_filesz - (size_t)written)) {
|
|
ux_err("minidump: failed to pad region %s\n",
|
|
table[i].filename);
|
|
goto out_close;
|
|
}
|
|
|
|
matched = true;
|
|
break;
|
|
}
|
|
|
|
if (!matched)
|
|
ux_err("minidump: no region found for PT_LOAD at paddr 0x%"
|
|
PRIx64 "\n", phdrs[j].p_paddr);
|
|
}
|
|
|
|
ux_info("minidump: ELF written to %s\n", out_path);
|
|
|
|
out_close:
|
|
fclose(out);
|
|
out_free:
|
|
free(kelf_buf);
|
|
}
|
|
|
|
static void sahara_debug64(struct qdl_device *qdl, struct sahara_pkt *pkt,
|
|
const char *ramdump_path, const char *filter)
|
|
{
|
|
struct sahara_debug_region64 *table;
|
|
struct sahara_pkt read_req;
|
|
ssize_t n;
|
|
size_t i;
|
|
|
|
if (pkt->length != SAHARA_MEM_DEBUG64_LENGTH) {
|
|
ux_err("unexpected MEM_DEBUG64 packet length %u\n", pkt->length);
|
|
sahara_send_reset(qdl);
|
|
return;
|
|
}
|
|
|
|
ux_debug("DEBUG64 address: 0x%" PRIx64 " length: 0x%" PRIx64 "\n",
|
|
pkt->debug64_req.addr, pkt->debug64_req.length);
|
|
|
|
read_req.cmd = SAHARA_MEM_READ64_CMD;
|
|
read_req.length = SAHARA_MEM_READ64_LENGTH;
|
|
read_req.debug64_req.addr = pkt->debug64_req.addr;
|
|
read_req.debug64_req.length = pkt->debug64_req.length;
|
|
|
|
n = qdl_write(qdl, &read_req, read_req.length, SAHARA_CMD_TIMEOUT_MS);
|
|
if (n < 0)
|
|
return;
|
|
|
|
if (read_req.debug64_req.length > 64 * 1024) {
|
|
ux_err("DEBUG64 table length 0x%" PRIx64 " exceeds limit\n",
|
|
read_req.debug64_req.length);
|
|
return;
|
|
}
|
|
|
|
table = malloc(read_req.debug64_req.length);
|
|
if (!table)
|
|
return;
|
|
|
|
n = qdl_read(qdl, table, pkt->debug64_req.length, SAHARA_CMD_TIMEOUT_MS);
|
|
if (n < 0) {
|
|
free(table);
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < pkt->debug64_req.length / sizeof(table[0]); i++) {
|
|
if (sahara_debug64_filter(table[i].filename, filter)) {
|
|
ux_info("%s skipped per filter\n", table[i].filename);
|
|
continue;
|
|
}
|
|
|
|
ux_debug("%-2d: type 0x%" PRIx64 " address: 0x%" PRIx64 " length: 0x%"
|
|
PRIx64 " region: %s filename: %s\n",
|
|
i, table[i].type, table[i].addr, table[i].length,
|
|
table[i].region, table[i].filename);
|
|
|
|
n = sahara_debug64_one(qdl, table[i], ramdump_path);
|
|
if (n < 0)
|
|
break;
|
|
|
|
ux_info("%s dumped successfully\n", table[i].filename);
|
|
}
|
|
|
|
sahara_build_minidump_elf(ramdump_path, filter, table,
|
|
pkt->debug64_req.length / sizeof(table[0]));
|
|
|
|
free(table);
|
|
|
|
sahara_send_reset(qdl);
|
|
}
|
|
|
|
static bool sahara_has_done_pending_quirk(const struct sahara_image *images)
|
|
{
|
|
unsigned int count = 0;
|
|
int i;
|
|
|
|
/*
|
|
* E.g MSM8916 EDL reports done = pending, allow this when one a single
|
|
* image is provided, and it's used as SAHARA_ID_EHOSTDL_IMG.
|
|
*/
|
|
for (i = 0; i < MAPPING_SZ; i++) {
|
|
if (images[i].ptr)
|
|
count++;
|
|
}
|
|
|
|
return count == 1 && images[SAHARA_ID_EHOSTDL_IMG].ptr;
|
|
}
|
|
|
|
static void sahara_debug_list_images(const struct sahara_image *images)
|
|
{
|
|
int i;
|
|
|
|
ux_debug("Sahara images:\n");
|
|
for (i = 0; i < MAPPING_SZ; i++) {
|
|
if (images[i].ptr)
|
|
ux_debug(" %2d: %s\n", i, images[i].name ? : "(unknown)");
|
|
}
|
|
}
|
|
|
|
int sahara_run(struct qdl_device *qdl, const struct sahara_image *images,
|
|
const char *ramdump_path,
|
|
const char *ramdump_filter)
|
|
{
|
|
/*
|
|
* Auto-detect that the device is already running the Firehose
|
|
* programmer (e.g. left running by a previous --skip-reset
|
|
* invocation): if the first read times out or returns a Firehose XML
|
|
* banner instead of a Sahara HELLO, skip Sahara entirely. Disabled
|
|
* in ramdump mode, where Sahara is the only valid protocol.
|
|
*/
|
|
const bool detect_firehose = !ramdump_path;
|
|
bool first_read = true;
|
|
struct sahara_pkt *pkt;
|
|
char buf[4096];
|
|
char tmp[32];
|
|
bool done = false;
|
|
int n;
|
|
|
|
if (images)
|
|
sahara_debug_list_images(images);
|
|
|
|
/*
|
|
* Don't need to do anything in simulation mode with Sahara,
|
|
* we care only about Firehose protocol
|
|
*/
|
|
if (qdl->dev_type == QDL_DEVICE_SIM)
|
|
return 0;
|
|
|
|
while (!done) {
|
|
n = qdl_read(qdl, buf, sizeof(buf), SAHARA_CMD_TIMEOUT_MS);
|
|
if (n < 0) {
|
|
if (first_read && detect_firehose && n == -ETIMEDOUT) {
|
|
/*
|
|
* The QUD driver will eat the HELLO request on
|
|
* many modern targets, so send an unsolicited
|
|
* HELLO response.
|
|
* If the device is already in Firehose mode,
|
|
* the programmer will fail to parse the "XML"
|
|
* message and report an error, which will
|
|
* trigger below detection of a <?xml response.
|
|
*/
|
|
if (qdl->dev_type == QDL_DEVICE_QUD || qdl->dev_type == QDL_DEVICE_AUTO) {
|
|
sahara_send_hello_resp(qdl, 0);
|
|
continue;
|
|
}
|
|
ux_info("no Sahara HELLO received; assuming Firehose programmer is already running\n");
|
|
return 0;
|
|
}
|
|
ux_err("failed to read sahara request from device\n");
|
|
break;
|
|
}
|
|
|
|
if (first_read && detect_firehose &&
|
|
n >= 5 && !memcmp(buf, "<?xml", 5)) {
|
|
ux_info("device is already in Firehose mode, skipping Sahara\n");
|
|
return 0;
|
|
}
|
|
first_read = false;
|
|
|
|
pkt = (struct sahara_pkt *)buf;
|
|
if ((uint32_t)n != pkt->length) {
|
|
ux_err("request length not matching received request\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
switch (pkt->cmd) {
|
|
case SAHARA_HELLO_CMD:
|
|
if (sahara_hello(qdl, pkt) < 0)
|
|
return -1;
|
|
break;
|
|
case SAHARA_READ_DATA_CMD:
|
|
if (sahara_read(qdl, pkt, images) < 0)
|
|
return -1;
|
|
break;
|
|
case SAHARA_END_OF_IMAGE_CMD:
|
|
if (sahara_eoi(qdl, pkt) < 0)
|
|
return -1;
|
|
break;
|
|
case SAHARA_DONE_RESP_CMD:
|
|
n = sahara_done(qdl, pkt);
|
|
if (n < 0)
|
|
return -1;
|
|
done = n;
|
|
|
|
/* E.g MSM8916 EDL reports done = 0 here */
|
|
if (sahara_has_done_pending_quirk(images))
|
|
done = true;
|
|
break;
|
|
case SAHARA_MEM_DEBUG64_CMD:
|
|
sahara_debug64(qdl, pkt, ramdump_path, ramdump_filter);
|
|
break;
|
|
case SAHARA_READ_DATA64_CMD:
|
|
if (sahara_read64(qdl, pkt, images) < 0)
|
|
return -1;
|
|
break;
|
|
case SAHARA_RESET_RESP_CMD:
|
|
if (pkt->length != SAHARA_RESET_LENGTH) {
|
|
ux_err("unexpected RESET_RESP packet length %u\n", pkt->length);
|
|
return -1;
|
|
}
|
|
if (ramdump_path)
|
|
done = true;
|
|
break;
|
|
default:
|
|
sprintf(tmp, "CMD%x", pkt->cmd);
|
|
print_hex_dump(tmp, buf, n);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return done ? 0 : -1;
|
|
}
|