* [PATCH 01/10] util_lib: add paddr_to_offset function and expose raw vmcoreinfo data
2026-06-22 20:54 [PATCH 00/10] vmcore-tasks: lightweight task and backtrace extractor for vmcore files Pnina Feder
@ 2026-06-22 20:54 ` Pnina Feder
2026-07-20 9:24 ` Simon Horman
2026-06-22 20:54 ` [PATCH 02/10] util_lib: Add vmcoreinfo parser and memory reader Pnina Feder
` (10 subsequent siblings)
11 siblings, 1 reply; 15+ messages in thread
From: Pnina Feder @ 2026-06-22 20:54 UTC (permalink / raw)
To: kexec; +Cc: horms, Pnina Feder
Add paddr_to_offset() to translate physical addresses to file offsets,
complementing the existing vaddr_to_offset() for virtual addresses.
Store raw vmcoreinfo note data when parsing ELF notes and provide
elf_get_raw_vmcoreinfo() accessor to retrieve it. This allows callers
to access the unparsed vmcoreinfo content for external processing.
and fix compilation warnings.
Change-Id: Icc75c22d6b2d95b6c951ccf647819d7e9cc4e7de
Signed-off-by: Pnina Feder <pnina.feder@mobileye.com>
---
util_lib/elf_info.c | 53 ++++++++++++++++++++++++++++++++-----
util_lib/include/elf_info.h | 11 ++++----
2 files changed, 52 insertions(+), 12 deletions(-)
diff --git a/util_lib/elf_info.c b/util_lib/elf_info.c
index 589bc1ad..fd7d1d5b 100644
--- a/util_lib/elf_info.c
+++ b/util_lib/elf_info.c
@@ -16,7 +16,8 @@
*/
#include <inttypes.h>
-#include <elf_info.h>
+
+#include "elf_info.h"
/* The 32bit and 64bit note headers make it clear we don't care */
typedef Elf32_Nhdr Elf_Nhdr;
@@ -27,6 +28,9 @@ static Elf64_Phdr *phdr;
static int num_pt_loads;
static char osrelease[4096];
+/* Raw vmcoreinfo storage */
+static char *raw_vmcoreinfo = NULL;
+static size_t raw_vmcoreinfo_len = 0;
/* VMCOREINFO symbols for lockless printk ringbuffer */
static loff_t prb_vaddr;
@@ -140,6 +144,21 @@ static uint64_t vaddr_to_offset(uint64_t vaddr)
exit(30);
}
+uint64_t paddr_to_offset(uint64_t paddr){
+ ssize_t i;
+
+ for (i = 0; i < ehdr.e_phnum; i++) {
+ if (phdr[i].p_paddr > paddr)
+ continue;
+ if ((phdr[i].p_paddr + phdr[i].p_memsz) <= paddr)
+ continue;
+ return phdr[i].p_offset + (paddr - phdr[i].p_paddr);
+ }
+ fprintf(stderr, "No program header covering paddr 0x%llx found kexec bug?\n",
+ (unsigned long long)paddr);
+ return (uint64_t)-1;
+}
+
static unsigned machine_pointer_bits(void)
{
uint8_t bits = 0;
@@ -646,6 +665,16 @@ static int scan_notes(int fd, loff_t start, loff_t lsize)
if ((memcmp(n_name, "VMCOREINFO", 11) != 0) || (n_type != 0))
continue;
+
+ /* Save raw vmcoreinfo for later use */
+ if (raw_vmcoreinfo)
+ free(raw_vmcoreinfo);
+ raw_vmcoreinfo = malloc(n_descsz);
+ if (raw_vmcoreinfo) {
+ memcpy(raw_vmcoreinfo, n_desc, n_descsz);
+ raw_vmcoreinfo_len = n_descsz;
+ }
+
scan_vmcoreinfo(n_desc, n_descsz);
}
@@ -776,7 +805,7 @@ static void dump_dmesg_legacy(int fd, void (*handler)(char*, unsigned int))
* To collect full dmesg including the part before `dmesg -c` is useful
* for later debugging. Use same logic as what crash utility is using.
*/
- logged_chars = log_end < log_buf_len ? log_end : log_buf_len;
+ logged_chars = log_end < (unsigned int)log_buf_len ? log_end : (unsigned int)log_buf_len;
if (handler)
handler(buf + (log_buf_len - logged_chars), logged_chars);
@@ -873,7 +902,7 @@ static void dump_dmesg_structured(int fd, void (*handler)(char*, unsigned int))
uint16_t loglen;
ret = pread(fd, buf, log_sz, log_buf_offset + current_idx);
- if (ret != log_sz) {
+ if (ret != (ssize_t)log_sz) {
fprintf(stderr, "Failed to read log of size %" PRId64 " bytes:"
" %s\n", log_sz, strerror(errno));
exit(65);
@@ -1165,7 +1194,7 @@ static void dump_dmesg_lockless(int fd, void (*handler)(char*, unsigned int))
exit(64);
}
ret = pread(fd, m.prb, printk_ringbuffer_sz, vaddr_to_offset(kaddr));
- if (ret != printk_ringbuffer_sz) {
+ if (ret != (ssize_t)printk_ringbuffer_sz) {
fprintf(stderr, "Failed to read prb of size %zu bytes: %s\n",
printk_ringbuffer_sz, strerror(errno));
exit(65);
@@ -1184,7 +1213,7 @@ static void dump_dmesg_lockless(int fd, void (*handler)(char*, unsigned int))
}
ret = pread(fd, m.descs, prb_desc_sz * m.desc_ring_count,
vaddr_to_offset(kaddr));
- if (ret != prb_desc_sz * m.desc_ring_count) {
+ if (ret != (ssize_t)(prb_desc_sz * m.desc_ring_count)) {
fprintf(stderr,
"Failed to read descs of size %lu bytes: %s\n",
prb_desc_sz * m.desc_ring_count, strerror(errno));
@@ -1201,7 +1230,7 @@ static void dump_dmesg_lockless(int fd, void (*handler)(char*, unsigned int))
}
ret = pread(fd, m.infos, printk_info_sz * m.desc_ring_count,
vaddr_to_offset(kaddr));
- if (ret != printk_info_sz * m.desc_ring_count) {
+ if (ret != (ssize_t)(printk_info_sz * m.desc_ring_count)) {
fprintf(stderr,
"Failed to read infos of size %lu bytes: %s\n",
printk_info_sz * m.desc_ring_count, strerror(errno));
@@ -1222,7 +1251,7 @@ static void dump_dmesg_lockless(int fd, void (*handler)(char*, unsigned int))
}
ret = pread(fd, m.text_data, m.text_data_ring_size,
vaddr_to_offset(kaddr));
- if (ret != m.text_data_ring_size) {
+ if (ret != (ssize_t)(m.text_data_ring_size)) {
fprintf(stderr,
"Failed to read text_data of size %lu bytes: %s\n",
m.text_data_ring_size, strerror(errno));
@@ -1321,3 +1350,13 @@ int read_phys_offset_elf_kcore(int fd, long *phys_off)
return 2;
}
+
+int elf_get_raw_vmcoreinfo(const char **data, size_t *size)
+{
+ if (!raw_vmcoreinfo || raw_vmcoreinfo_len == 0)
+ return -1;
+
+ *data = raw_vmcoreinfo;
+ *size = raw_vmcoreinfo_len;
+ return 0;
+}
diff --git a/util_lib/include/elf_info.h b/util_lib/include/elf_info.h
index fdf4c3d0..c2873365 100644
--- a/util_lib/include/elf_info.h
+++ b/util_lib/include/elf_info.h
@@ -24,13 +24,14 @@
#include <ctype.h>
int get_pt_load(int idx,
- unsigned long long *phys_start,
- unsigned long long *phys_end,
- unsigned long long *virt_start,
- unsigned long long *virt_end);
+ unsigned long long *phys_start,
+ unsigned long long *phys_end,
+ unsigned long long *virt_start,
+ unsigned long long *virt_end);
int read_phys_offset_elf_kcore(int fd, long *phys_off);
int read_elf(int fd);
void dump_dmesg(int fd, void (*handler)(char*, unsigned int));
extern void (*arch_scan_vmcoreinfo)(char *pos);
-
+uint64_t paddr_to_offset(uint64_t paddr);
+int elf_get_raw_vmcoreinfo(const char **data, size_t *size);
#endif /* ELF_INFO_H */
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread* Re: [PATCH 01/10] util_lib: add paddr_to_offset function and expose raw vmcoreinfo data
2026-06-22 20:54 ` [PATCH 01/10] util_lib: add paddr_to_offset function and expose raw vmcoreinfo data Pnina Feder
@ 2026-07-20 9:24 ` Simon Horman
0 siblings, 0 replies; 15+ messages in thread
From: Simon Horman @ 2026-07-20 9:24 UTC (permalink / raw)
To: Pnina Feder; +Cc: kexec
On Mon, Jun 22, 2026 at 11:54:39PM +0300, Pnina Feder wrote:
> Add paddr_to_offset() to translate physical addresses to file offsets,
> complementing the existing vaddr_to_offset() for virtual addresses.
>
> Store raw vmcoreinfo note data when parsing ELF notes and provide
> elf_get_raw_vmcoreinfo() accessor to retrieve it. This allows callers
> to access the unparsed vmcoreinfo content for external processing.
> and fix compilation warnings.
>
> Change-Id: Icc75c22d6b2d95b6c951ccf647819d7e9cc4e7de
> Signed-off-by: Pnina Feder <pnina.feder@mobileye.com>
Overall this looks good to me.
I have provided some minor feedback inline.
> ---
> util_lib/elf_info.c | 53 ++++++++++++++++++++++++++++++++-----
> util_lib/include/elf_info.h | 11 ++++----
> 2 files changed, 52 insertions(+), 12 deletions(-)
>
> diff --git a/util_lib/elf_info.c b/util_lib/elf_info.c
...
> @@ -776,7 +805,7 @@ static void dump_dmesg_legacy(int fd, void (*handler)(char*, unsigned int))
> * To collect full dmesg including the part before `dmesg -c` is useful
> * for later debugging. Use same logic as what crash utility is using.
> */
> - logged_chars = log_end < log_buf_len ? log_end : log_buf_len;
> + logged_chars = log_end < (unsigned int)log_buf_len ? log_end : (unsigned int)log_buf_len;
Adding this cast seems to be a clean-up which is not strictly related to
this patch. I'm not a huge fan of adding casts, but that aside, it
seems like a cleanup that would be best put in a separate patch.
Likewise for similar updates in this patch.
>
> if (handler)
> handler(buf + (log_buf_len - logged_chars), logged_chars);
...
> diff --git a/util_lib/include/elf_info.h b/util_lib/include/elf_info.h
> index fdf4c3d0..c2873365 100644
> --- a/util_lib/include/elf_info.h
> +++ b/util_lib/include/elf_info.h
> @@ -24,13 +24,14 @@
> #include <ctype.h>
>
> int get_pt_load(int idx,
> - unsigned long long *phys_start,
> - unsigned long long *phys_end,
> - unsigned long long *virt_start,
> - unsigned long long *virt_end);
> + unsigned long long *phys_start,
> + unsigned long long *phys_end,
> + unsigned long long *virt_start,
> + unsigned long long *virt_end);
Similarly, this whitespace change also seems to be a clean-up and if so
doesn't belong in this patch.
> int read_phys_offset_elf_kcore(int fd, long *phys_off);
> int read_elf(int fd);
> void dump_dmesg(int fd, void (*handler)(char*, unsigned int));
> extern void (*arch_scan_vmcoreinfo)(char *pos);
> -
> +uint64_t paddr_to_offset(uint64_t paddr);
> +int elf_get_raw_vmcoreinfo(const char **data, size_t *size);
> #endif /* ELF_INFO_H */
> --
> 2.43.0
>
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH 02/10] util_lib: Add vmcoreinfo parser and memory reader
2026-06-22 20:54 [PATCH 00/10] vmcore-tasks: lightweight task and backtrace extractor for vmcore files Pnina Feder
2026-06-22 20:54 ` [PATCH 01/10] util_lib: add paddr_to_offset function and expose raw vmcoreinfo data Pnina Feder
@ 2026-06-22 20:54 ` Pnina Feder
2026-07-20 9:24 ` Simon Horman
2026-06-22 20:54 ` [PATCH 03/10] util_lib: Add maple tree walker for VMA enumeration Pnina Feder
` (9 subsequent siblings)
11 siblings, 1 reply; 15+ messages in thread
From: Pnina Feder @ 2026-06-22 20:54 UTC (permalink / raw)
To: kexec; +Cc: horms, Pnina Feder
Add the foundational utility libraries for parsing vmcore files:
- vmcore_info: Parser for vmcoreinfo note section. Supports SYMBOL(),
OFFSET(), SIZE(), NUMBER(), LENGTH() lookups via hash table.
Provides cached get_page_size()/get_page_shift() accessors.
Handles plain KEY=VALUE entries like PAGESIZE and OSRELEASE.
- memory: Virtual-to-physical address translation dispatcher and
readmem() for reading from vmcore via kernel or user virtual
addresses, or physical addresses.Uses paddr_to_offset() from
elf_info for physical-to-file-offset mapping.
- vmcore_tasks_util: Shared macros, typedefs, and debug helpers
used by vmcore_info and memory (debug verbosity levels, type
accessors, __weak definition).
These files are placed in util_lib/ but are currently only built by
the vmcore_tasks Makefile. Integration into the main util_lib build
can be done later if other tools need them.
Signed-off-by: Pnina Feder <pnina.feder@mobileye.com>
---
util_lib/include/memory.h | 37 +++
util_lib/include/vmcore_info.h | 72 +++++
util_lib/include/vmcore_tasks_util.h | 62 +++++
util_lib/memory.c | 327 +++++++++++++++++++++++
util_lib/vmcore_info.c | 382 +++++++++++++++++++++++++++
5 files changed, 880 insertions(+)
create mode 100644 util_lib/include/memory.h
create mode 100644 util_lib/include/vmcore_info.h
create mode 100644 util_lib/include/vmcore_tasks_util.h
create mode 100644 util_lib/memory.c
create mode 100644 util_lib/vmcore_info.c
diff --git a/util_lib/include/memory.h b/util_lib/include/memory.h
new file mode 100644
index 00000000..492e0d79
--- /dev/null
+++ b/util_lib/include/memory.h
@@ -0,0 +1,37 @@
+#ifndef _MEMORY_H_
+#define _MEMORY_H_
+
+#include <stdint.h>
+#include <errno.h>
+#include <sys/types.h>
+
+#include "vmcore_info.h"
+
+#define KVADDR (0x1)
+#define UVADDR (0x2)
+#define PADDR (0x3)
+
+/* Page size */
+#define SYS_PAGE_SIZE (get_page_size())
+#define page_off(vaddr) ((vaddr) & (SYS_PAGE_SIZE - 1))
+
+/* Set the current user context (mm_pgd) for UVADDR reads */
+void set_context(uint64_t mm_pgd);
+
+/* Get current context */
+uint64_t get_context(void);
+
+/* Arch-overridable functions (weak in memory.c, strong in arch/) */
+uint64_t get_page_offset(void);
+bool is_kvaddr(uint64_t vaddr);
+bool vtop_direct(uint64_t kvaddr, uint64_t *paddr);
+
+/* Arch vtop function pointer for page table walk (set by arch_init) */
+typedef bool (*vtop_fn_t)(uint64_t pgd, uint64_t vaddr, uint64_t *paddr);
+extern vtop_fn_t mem_vtop;
+
+ssize_t readmem(uint64_t addr, void *buffer, size_t size, char *type, int memtype);
+
+int memory_config_init(void);
+
+#endif /* _MEMORY_H_ */
\ No newline at end of file
diff --git a/util_lib/include/vmcore_info.h b/util_lib/include/vmcore_info.h
new file mode 100644
index 00000000..26c92701
--- /dev/null
+++ b/util_lib/include/vmcore_info.h
@@ -0,0 +1,72 @@
+#ifndef VMCORE_INFO_H
+#define VMCORE_INFO_H
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <stddef.h>
+
+/* Maximum number of entries per table */
+#define VMCI_MAX_ENTRIES 256
+#define VMCI_MAX_KEY_LEN 64
+
+/* Single entry in a table */
+struct vmci_entry {
+ char key[VMCI_MAX_KEY_LEN];
+ uint64_t value;
+ bool valid;
+};
+
+/* Table for one type of entries */
+struct vmci_table {
+ struct vmci_entry entries[VMCI_MAX_ENTRIES];
+ int count;
+};
+
+/* All vmcoreinfo tables */
+struct vmcoreinfo {
+ struct vmci_table symbols; /* SYMBOL entries */
+ struct vmci_table sizes; /* SIZE entries */
+ struct vmci_table offsets; /* OFFSET entries */
+ struct vmci_table numbers; /* NUMBER entries */
+ struct vmci_table lengths; /* LENGTH entries */
+ char osrelease[256];
+ char pagesize[16];
+ int vmcore_fd;
+ bool initialized;
+};
+
+/* Initialize vmcoreinfo from raw data */
+int vmcoreinfo_init(const char *data, size_t size);
+
+/* Cleanup */
+void vmcoreinfo_cleanup(void);
+
+/* Getters - crash if not found */
+uint64_t SYMBOL(const char *name);
+uint64_t SIZE(const char *name);
+uint64_t OFFSET(const char *name);
+uint64_t NUMBER(const char *name);
+uint64_t LENGTH(const char *name);
+
+/* Check if entry exists */
+bool SYMBOL_EXISTS(const char *name);
+bool SIZE_EXISTS(const char *name);
+bool OFFSET_EXISTS(const char *name);
+bool NUMBER_EXISTS(const char *name);
+bool LENGTH_EXISTS(const char *name);
+
+/* Cached system values */
+unsigned long get_page_size(void);
+unsigned int get_page_shift(void);
+
+/* Get OS release string */
+const char *vmcoreinfo_osrelease(void);
+
+/* Vmcore file descriptor - global access */
+void vmcore_set_fd(int fd);
+int vmcore_fd(void);
+
+/* Debug: dump all entries */
+void vmcoreinfo_dump(void);
+
+#endif /* VMCORE_INFO_H */
\ No newline at end of file
diff --git a/util_lib/include/vmcore_tasks_util.h b/util_lib/include/vmcore_tasks_util.h
new file mode 100644
index 00000000..95c0ad8a
--- /dev/null
+++ b/util_lib/include/vmcore_tasks_util.h
@@ -0,0 +1,62 @@
+#ifndef VMCORE_TASKS_UTIL_H
+#define VMCORE_TASKS_UTIL_H
+
+#include <sys/types.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#ifndef __weak
+#define __weak __attribute__((weak))
+#endif
+
+/* Debug verbosity levels */
+#define DBG_NONE 0
+#define DBG_INFO 1
+#define DBG_VERBOSE 2
+#define DBG_TRACE 3
+#define DBG_MEM 4
+
+extern int debug_level;
+
+#define pr_info(...) \
+ do { \
+ if (debug_level >= DBG_INFO) \
+ fprintf(stderr, "[INFO] " __VA_ARGS__); \
+ } while (0)
+
+#define pr_verbose(...) \
+ do { \
+ if (debug_level >= DBG_VERBOSE) \
+ fprintf(stderr, "[VERBOSE] " __VA_ARGS__); \
+ } while (0)
+
+#define pr_trace(...) \
+ do { \
+ if (debug_level >= DBG_TRACE) \
+ fprintf(stderr, "[TRACE] " __VA_ARGS__); \
+ } while (0)
+
+#define pr_mem(...) \
+ do { \
+ if (debug_level >= DBG_MEM) \
+ fprintf(stderr, "[MEM] " __VA_ARGS__); \
+ } while (0)
+
+#define is_info() (debug_level >= DBG_INFO)
+#define is_verbose() (debug_level >= DBG_VERBOSE)
+#define is_trace() (debug_level >= DBG_TRACE)
+#define is_mem() (debug_level >= DBG_MEM)
+
+typedef unsigned long int ulong;
+typedef unsigned short int ushort;
+typedef unsigned int uint;
+
+#define ULONG(ADDR) *((ulong *)((char *)(ADDR)))
+#define UINT32(ADDR) *((uint32_t *)((char *)(ADDR)))
+#define UINT16(ADDR) *((uint16_t *)((char *)(ADDR)))
+#define UINT64(ADDR) *((uint64_t *)((char *)(ADDR)))
+#define UCHAR(ADDR) *((unsigned char *)((char *)(ADDR)))
+#define VOID_PTR(ADDR) *((void **)((char *)(ADDR)))
+#define UINT(ADDR) *((uint *)((char *)(ADDR)))
+
+#endif /* VMCORE_TASKS_UTIL_H */
diff --git a/util_lib/memory.c b/util_lib/memory.c
new file mode 100644
index 00000000..60986cc4
--- /dev/null
+++ b/util_lib/memory.c
@@ -0,0 +1,327 @@
+#include <stdint.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/param.h>
+
+#include "memory.h"
+#include "vmcore_info.h"
+#include "elf_info.h"
+#include "vmcore_tasks_util.h"
+
+/* Arch vtop function pointer (set by arch_init) */
+vtop_fn_t mem_vtop;
+
+/* Memory layout (from vmcoreinfo) */
+static uint64_t page_offset; // PAGE_OFFSET
+static uint64_t phys_offset; // Physical RAM base
+static uint64_t va_pa_offset; // Linear mapping offset
+
+static uint64_t number_vmalloc_start; // VMALLOC_START
+static uint64_t number_vmalloc_end; // VMALLOC_END
+static uint64_t number_vmemmap_start; // VMEMMAP_START
+static uint64_t number_vmemmap_end; // VMEMMAP_END
+static uint64_t number_modules_vaddr; // MODULES_VADDR
+static uint64_t number_modules_end; // MODULES_END
+
+static uint64_t number_kernel_link_addr; // KERNEL_LINK_ADDR (Linux >= 5.13)
+static uint64_t number_va_kernel_pa_offset; // Kernel VA to PA offset (Linux >= 6.4)
+
+/* Kernel page table base (from symbol) */
+static uint64_t symbol_swapper_pg_dir; // Kernel PGD virtual address
+static uint64_t kernel_pgd_paddr; // Kernel PGD physical address
+
+/* Kernel version for conditional logic */
+static const char *kernel_version; // e.g., LINUX(5,13,0)
+static uint64_t current_context_pgd = 0;
+
+void set_context(uint64_t mm_pgd)
+{
+ current_context_pgd = mm_pgd;
+}
+
+uint64_t get_context(void)
+{
+ return current_context_pgd;
+}
+
+
+/**
+ * Check if address is in vmalloc/vmemmap/modules region
+ * (requires page table walk)
+ */
+static bool is_vmalloc_addr(uint64_t vaddr)
+{
+ if (number_vmalloc_start &&
+ vaddr >= number_vmalloc_start && vaddr <= number_vmalloc_end)
+ return true;
+
+ if (number_vmemmap_start &&
+ vaddr >= number_vmemmap_start && vaddr <= number_vmemmap_end)
+ return true;
+
+ if (number_modules_vaddr &&
+ vaddr >= number_modules_vaddr && vaddr <= number_modules_end)
+ return true;
+
+ return false;
+}
+
+__weak uint64_t get_page_offset(void)
+{
+ return NUMBER("PAGE_OFFSET");
+}
+
+/* Check if address is a kernel virtual address */
+__weak bool is_kvaddr(uint64_t vaddr)
+{
+ if (is_vmalloc_addr(vaddr))
+ return true;
+ return (vaddr >= page_offset);
+}
+
+/*
+ * Direct translation for linear-mapped addresses (fast path)
+ * No page table walk needed
+ */
+__weak bool vtop_direct(uint64_t kvaddr, uint64_t *paddr)
+{
+ /*
+ * Linux >= 5.13 on RISCV: kernel text uses different offset
+ * Only check if KERNEL_LINK_ADDR exists and address is in that range
+ */
+ if(number_kernel_link_addr && kvaddr >= number_kernel_link_addr) {
+ *paddr = kvaddr - number_va_kernel_pa_offset;
+ return true;
+ }
+
+ /* Standard linear mapping: VA = PA + PAGE_OFFSET - PHYS_OFFSET */
+ *paddr = kvaddr - va_pa_offset;
+
+ return true;
+}
+
+/*
+ * High-level kvaddr to paddr translation
+ * Handles both direct mapping and page table walk
+ */
+static bool kvtop(uint64_t kvaddr, uint64_t *paddr)
+{
+ /* Validate it's a kernel address */
+ if (!is_kvaddr(kvaddr)){
+ fprintf(stderr, "kvtop: Address 0x%llx is not a kernel virtual address\n",
+ (unsigned long long)kvaddr);
+ return false;
+ }
+
+ /* Fast path: direct-mapped address */
+ if (!is_vmalloc_addr(kvaddr)) {
+ return vtop_direct(kvaddr, paddr);
+ }
+
+ /* Slow path: page table walk required */
+ if (!mem_vtop) {
+ fprintf(stderr, "kvtop: mem_vtop function pointer is NULL\n");
+ return false;
+ }
+ return mem_vtop(kernel_pgd_paddr, kvaddr, paddr);
+}
+
+static bool uvtop(uint64_t uvaddr, uint64_t mm_pgd, uint64_t *paddr)
+{
+ uint64_t pgd_paddr;
+ /* Get physical address of process's PGD */
+ if (!kvtop(mm_pgd, &pgd_paddr)) {
+ fprintf(stderr, "uvtop: failed to translate mm_pgd 0x%llx to physical\n",
+ (unsigned long long)mm_pgd);
+ return false;
+ }
+
+ if (!mem_vtop) {
+ fprintf(stderr, "uvtop: mem_vtop function pointer is NULL\n");
+ return false;
+ }
+
+ if (!mem_vtop(pgd_paddr, uvaddr, paddr)) {
+ /* Page not present - this is common for sleeping processes.
+ * The caller (backtrace code) should handle this gracefully.
+ * Only log in debug mode to avoid spam for expected cases.
+ */
+ pr_trace("uvtop: page table walk failed for user addr 0x%llx (page not present?)\n",
+ (unsigned long long)uvaddr);
+ return false;
+ }
+
+ return true;
+}
+
+
+/*
+ * Parse vmcoreinfo to get required values
+ * Returns 0 on success, -1 on error
+ */
+int memory_config_init(void)
+{
+ kernel_version = vmcoreinfo_osrelease();
+ page_offset = get_page_offset();
+
+ /* Physical RAM base - different names on different archs */
+ if (NUMBER_EXISTS("phys_ram_base"))
+ phys_offset = NUMBER("phys_ram_base");
+ else if (NUMBER_EXISTS("PHYS_OFFSET"))
+ phys_offset = NUMBER("PHYS_OFFSET");
+ else
+ phys_offset = 0; /* x86 and some others start at 0 */
+
+ if(NUMBER_EXISTS("VMALLOC_START")) {
+ number_vmalloc_start = NUMBER("VMALLOC_START");
+ if (NUMBER_EXISTS("VMALLOC_END"))
+ number_vmalloc_end = NUMBER("VMALLOC_END");
+ else
+ number_vmalloc_end = 0;
+ } else {
+ number_vmalloc_start = 0;
+ number_vmalloc_end = 0;
+ }
+
+ /* VMEMMAP region - may not exist on all configs */
+ if (NUMBER_EXISTS("VMEMMAP_START")) {
+ number_vmemmap_start = NUMBER("VMEMMAP_START");
+ if (NUMBER_EXISTS("VMEMMAP_END"))
+ number_vmemmap_end = NUMBER("VMEMMAP_END");
+ else
+ number_vmemmap_end = 0;
+ } else {
+ number_vmemmap_start = 0;
+ number_vmemmap_end = 0;
+ }
+
+ if (NUMBER_EXISTS("KERNEL_LINK_ADDR")) {
+ number_kernel_link_addr = NUMBER("KERNEL_LINK_ADDR");
+
+ /* va_kernel_pa_offset exists only on linux 6.4+ */
+ if (NUMBER_EXISTS("va_kernel_pa_offset"))
+ number_va_kernel_pa_offset = NUMBER("va_kernel_pa_offset");
+ else
+ number_va_kernel_pa_offset = number_kernel_link_addr - phys_offset;
+ } else {
+ number_kernel_link_addr = 0;
+ }
+
+ /* MODULES region - Linux >= 5.13 has separate, older uses VMALLOC */
+ if (NUMBER_EXISTS("MODULES_VADDR"))
+ number_modules_vaddr = NUMBER("MODULES_VADDR");
+ else
+ number_modules_vaddr = number_vmalloc_start;
+
+ if (NUMBER_EXISTS("MODULES_END"))
+ number_modules_end = NUMBER("MODULES_END");
+ else
+ number_modules_end = number_vmalloc_end;
+
+ symbol_swapper_pg_dir = SYMBOL("swapper_pg_dir");
+
+ /* Calculate va_pa_offset if not directly available */
+ va_pa_offset = page_offset - phys_offset;
+
+ if (!vtop_direct(symbol_swapper_pg_dir, &kernel_pgd_paddr))
+ return -1;
+
+ return 0;
+}
+
+static ssize_t read_phys(uint64_t paddr, void *buffer, size_t size)
+{
+ uint64_t file_offset;
+ ssize_t ret;
+ int fd = vmcore_fd();
+ if (fd < 0) {
+ fprintf(stderr, "read_phys: invalid vmcore fd\n");
+ return -1;
+ }
+ file_offset = paddr_to_offset(paddr);
+
+ /* paddr_to_offset returns (uint64_t)-1 on error */
+ if (file_offset == (uint64_t)-1) {
+ fprintf(stderr, "read_phys: failed to translate paddr 0x%llx to file offset\n",
+ (unsigned long long)paddr);
+ return -1;
+ }
+
+ pr_mem("read_phys: paddr=0x%llx -> file_offset=0x%llx size=%zu\n",
+ (unsigned long long)paddr, (unsigned long long)file_offset, size);
+
+ ret = pread(fd, buffer, size, file_offset);
+
+ if (ret < 0 || (size_t)ret != size) {
+ fprintf(stderr, "read_phys: Failed to read %zu bytes from paddr 0x%llx: %s\n",
+ size, (unsigned long long)paddr, strerror(errno));
+ return -1;
+ }
+ return ret;
+}
+
+ssize_t readmem(uint64_t addr, void *buffer, size_t size, char *type, int memtype)
+{
+ uint64_t paddr;
+ ssize_t ret;
+ size_t bytes_read = 0;
+ size_t chunk_size;
+ pr_mem("readmem: Reading %zu bytes from addr 0x%llx (%s)\n",
+ size, (unsigned long long)addr, type ? type : "unknown");
+ if (!buffer) {
+ fprintf(stderr, "readmem: NULL buffer\n");
+ return -1;
+ }
+ if (size == 0) {
+ fprintf(stderr, "readmem: zero size requested\n");
+ return -1;
+ }
+
+ /* Direct physical address read */
+ if(memtype == PADDR){
+ ret = read_phys(addr, buffer, size);
+ return ret;
+ }
+
+ while(bytes_read < size) {
+ chunk_size = MIN(size - bytes_read, SYS_PAGE_SIZE - page_off(addr + bytes_read));
+ /* Convert virtual address to paddr */
+ switch (memtype)
+ {
+ case KVADDR:
+ if (!kvtop(addr + bytes_read, &paddr)) {
+ fprintf(stderr, "readmem: kvtop failed for addr 0x%llx\n", (unsigned long long)(addr + bytes_read));
+ return -1;
+ }
+ break;
+ case UVADDR:
+ if (!current_context_pgd) {
+ fprintf(stderr, "readmem: mm_pgd is zero for UVADDR read\n");
+ return -1;
+ }
+ if (!uvtop(addr + bytes_read, current_context_pgd, &paddr)) {
+ /* uvtop already logged in debug mode.
+ * For user addresses, page-not-present is common
+ * for sleeping processes - caller should handle gracefully.
+ */
+ return -1;
+ }
+ break;
+ default:
+ fprintf(stderr, "readmem: Unknown memory type %d\n", memtype);
+ return -1;
+ break;
+ }
+ ret = read_phys(paddr, (char *)buffer + bytes_read, chunk_size);
+ if (ret < 0 || (size_t)ret != chunk_size) {
+ fprintf(stderr, "readmem: Failed to read memory at addr 0x%llx (read %zd, expected %zu)\n",
+ (unsigned long long)(addr + bytes_read), ret, chunk_size);
+ return -1;
+ }
+ bytes_read += ret;
+ }
+ return (ssize_t)bytes_read;
+}
diff --git a/util_lib/vmcore_info.c b/util_lib/vmcore_info.c
new file mode 100644
index 00000000..a1e1d51f
--- /dev/null
+++ b/util_lib/vmcore_info.c
@@ -0,0 +1,382 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "vmcore_info.h"
+#include "vmcore_tasks_util.h"
+
+/* Global vmcoreinfo instance */
+static struct vmcoreinfo vmci = {0};
+
+/* Cached page size — read once from vmcoreinfo */
+static unsigned long cached_page_size = 0;
+static unsigned int cached_page_shift = 0;
+
+unsigned long get_page_size(void)
+{
+ if (cached_page_size)
+ return cached_page_size;
+
+ if (vmci.pagesize[0] != '\0') {
+ cached_page_size = strtoul(vmci.pagesize, NULL, 0);
+ }
+
+ if (cached_page_size == 0 || (cached_page_size & (cached_page_size - 1)) != 0) {
+ fprintf(stderr, "Warning: invalid PAGESIZE='%s', defaulting to 4096\n",
+ vmci.pagesize);
+ cached_page_size = 4096;
+ }
+
+ unsigned long tmp = cached_page_size;
+ while (tmp > 1) {
+ tmp >>= 1;
+ cached_page_shift++;
+ }
+
+ return cached_page_size;
+}
+
+unsigned int get_page_shift(void)
+{
+ if (!cached_page_size)
+ get_page_size(); /* ensure initialized */
+ return cached_page_shift;
+}
+
+/* Simple hash function for faster lookups */
+static unsigned int hash_key(const char *key)
+{
+ unsigned int hash = 5381;
+ int c;
+ while ((c = *key++))
+ hash = ((hash << 5) + hash) + c;
+ return hash % VMCI_MAX_ENTRIES;
+}
+
+/* Add entry to a table */
+static int table_add(struct vmci_table *table, const char *key, uint64_t value)
+{
+ if (table->count >= VMCI_MAX_ENTRIES) {
+ fprintf(stderr, "vmcoreinfo: table full, cannot add '%s'\n", key);
+ return -1;
+ }
+
+ /* Use hash for initial position, linear probe on collision */
+ unsigned int idx = hash_key(key);
+ int attempts = 0;
+
+ while (table->entries[idx].valid && attempts < VMCI_MAX_ENTRIES) {
+ /* Check for duplicate key */
+ if (strcmp(table->entries[idx].key, key) == 0) {
+ /* Update existing entry */
+ table->entries[idx].value = value;
+ return 0;
+ }
+ idx = (idx + 1) % VMCI_MAX_ENTRIES;
+ attempts++;
+ }
+
+ if (attempts >= VMCI_MAX_ENTRIES) {
+ fprintf(stderr, "vmcoreinfo: hash table full\n");
+ return -1;
+ }
+
+ if (strlen(key) >= VMCI_MAX_KEY_LEN) {
+ fprintf(stderr, "vmcoreinfo: key '%s' truncated to %d chars\n",
+ key, VMCI_MAX_KEY_LEN - 1);
+ }
+ strncpy(table->entries[idx].key, key, VMCI_MAX_KEY_LEN - 1);
+ table->entries[idx].key[VMCI_MAX_KEY_LEN - 1] = '\0';
+ table->entries[idx].value = value;
+ table->entries[idx].valid = true;
+ table->count++;
+
+ return 0;
+}
+
+/* Lookup entry in a table */
+static struct vmci_entry *table_lookup(struct vmci_table *table, const char *key)
+{
+ unsigned int idx = hash_key(key);
+ int attempts = 0;
+
+ while (attempts < VMCI_MAX_ENTRIES) {
+ if (!table->entries[idx].valid)
+ return NULL; // empty slot means key was never inserted
+ if (strcmp(table->entries[idx].key, key) == 0)
+ return &table->entries[idx];
+ idx = (idx + 1) % VMCI_MAX_ENTRIES;
+ attempts++;
+ }
+
+ return NULL;
+}
+
+/* Parse a single line of vmcoreinfo */
+static void parse_line(char *line)
+{
+ char *eq = strchr(line, '=');
+ if (!eq)
+ return;
+
+ *eq = '\0';
+ char *key_part = line;
+ char *value_str = eq + 1;
+
+ /* Trim whitespace from value */
+ while (*value_str && isspace(*value_str))
+ value_str++;
+
+ /* Detect type and extract key */
+ if (strncmp(key_part, "SYMBOL(", 7) == 0) {
+ char *end = strchr(key_part + 7, ')');
+ if (end) {
+ *end = '\0';
+ uint64_t val = strtoull(value_str, NULL, 16);
+ table_add(&vmci.symbols, key_part + 7, val);
+ }
+ }
+ else if (strncmp(key_part, "SIZE(", 5) == 0) {
+ char *end = strchr(key_part + 5, ')');
+ if (end) {
+ *end = '\0';
+ uint64_t val = strtoull(value_str, NULL, 10);
+ table_add(&vmci.sizes, key_part + 5, val);
+ }
+ }
+ else if (strncmp(key_part, "OFFSET(", 7) == 0) {
+ char *end = strchr(key_part + 7, ')');
+ if (end) {
+ *end = '\0';
+ uint64_t val = strtoull(value_str, NULL, 10);
+ table_add(&vmci.offsets, key_part + 7, val);
+ }
+ }
+ else if (strncmp(key_part, "NUMBER(", 7) == 0) {
+ char *end = strchr(key_part + 7, ')');
+ if (end) {
+ *end = '\0';
+ /* NUMBER can be hex or decimal */
+ uint64_t val;
+ if (strncmp(value_str, "0x", 2) == 0 || strncmp(value_str, "0X", 2) == 0)
+ val = strtoull(value_str, NULL, 16);
+ else
+ val = strtoull(value_str, NULL, 0); /* auto-detect base */
+ table_add(&vmci.numbers, key_part + 7, val);
+ }
+ }
+ else if (strncmp(key_part, "LENGTH(", 7) == 0) {
+ char *end = strchr(key_part + 7, ')');
+ if (end) {
+ *end = '\0';
+ uint64_t val = strtoull(value_str, NULL, 10);
+ table_add(&vmci.lengths, key_part + 7, val);
+ }
+ }
+ else if (strncmp(key_part, "OSRELEASE", 9) == 0) {
+ strncpy(vmci.osrelease, value_str, sizeof(vmci.osrelease) - 1);
+ vmci.osrelease[sizeof(vmci.osrelease) - 1] = '\0';
+ }
+ else if (strncmp(key_part, "PAGESIZE", 8) == 0) {
+ strncpy(vmci.pagesize, value_str, sizeof(vmci.pagesize) - 1);
+ vmci.pagesize[sizeof(vmci.pagesize) - 1] = '\0';
+ }
+ else if (strcmp(key_part, "CRASHTIME") == 0) {
+ long long sval = strtoll(value_str, NULL, 10);
+ table_add(&vmci.numbers, "CRASHTIME", (uint64_t)(int64_t)sval);
+ }
+}
+
+int vmcoreinfo_init(const char *data, size_t size)
+{
+ if (!data || size == 0)
+ return -1;
+
+ if (vmci.initialized) {
+ fprintf(stderr, "vmcoreinfo: already initialized, reinitializing\n");
+ return -1;
+ }
+
+ /* Clear existing data */
+ memset(&vmci, 0, sizeof(vmci));
+
+ /* Make a mutable copy */
+ char *buf = malloc(size + 1);
+ if (!buf)
+ return -1;
+
+ memcpy(buf, data, size);
+ buf[size] = '\0';
+
+ /* Parse line by line */
+ char *line = buf;
+ char *end = buf + size;
+
+ while (line < end) {
+ /* Find end of line */
+ char *eol = strchr(line, '\n');
+ if (eol)
+ *eol = '\0';
+ else
+ eol = end - 1;
+
+ /* Skip empty lines */
+ if (*line)
+ parse_line(line);
+
+ line = eol + 1;
+ }
+
+ free(buf);
+ vmci.initialized = true;
+
+ return 0;
+}
+
+void vmcoreinfo_cleanup(void)
+{
+ memset(&vmci, 0, sizeof(vmci));
+}
+
+/* Getter implementations - crash if not found */
+uint64_t SYMBOL(const char *name)
+{
+ struct vmci_entry *e = table_lookup(&vmci.symbols, name);
+ if (!e) {
+ fprintf(stderr, "FATAL: SYMBOL '%s' not found in vmcoreinfo\n", name);
+ abort();
+ }
+ return e->value;
+}
+
+uint64_t SIZE(const char *name)
+{
+ struct vmci_entry *e = table_lookup(&vmci.sizes, name);
+ if (!e) {
+ fprintf(stderr, "FATAL: SIZE '%s' not found in vmcoreinfo\n", name);
+ abort();
+ }
+ return e->value;
+}
+
+uint64_t OFFSET(const char *name)
+{
+ struct vmci_entry *e = table_lookup(&vmci.offsets, name);
+ if (!e) {
+ fprintf(stderr, "FATAL: OFFSET '%s' not found in vmcoreinfo\n", name);
+ abort();
+ }
+ return e->value;
+}
+
+uint64_t NUMBER(const char *name)
+{
+ struct vmci_entry *e = table_lookup(&vmci.numbers, name);
+ if (!e) {
+ fprintf(stderr, "FATAL: NUMBER '%s' not found in vmcoreinfo\n", name);
+ abort();
+ }
+ return e->value;
+}
+
+uint64_t LENGTH(const char *name)
+{
+ struct vmci_entry *e = table_lookup(&vmci.lengths, name);
+ if (!e) {
+ fprintf(stderr, "FATAL: LENGTH '%s' not found in vmcoreinfo\n", name);
+ abort();
+ }
+ return e->value;
+}
+
+/* Existence checks */
+bool SYMBOL_EXISTS(const char *name)
+{
+ return table_lookup(&vmci.symbols, name) != NULL;
+}
+
+bool SIZE_EXISTS(const char *name)
+{
+ return table_lookup(&vmci.sizes, name) != NULL;
+}
+
+bool OFFSET_EXISTS(const char *name)
+{
+ return table_lookup(&vmci.offsets, name) != NULL;
+}
+
+bool NUMBER_EXISTS(const char *name)
+{
+ return table_lookup(&vmci.numbers, name) != NULL;
+}
+
+bool LENGTH_EXISTS(const char *name)
+{
+ return table_lookup(&vmci.lengths, name) != NULL;
+}
+
+const char *vmcoreinfo_osrelease(void)
+{
+ if (vmci.osrelease[0] == '\0') {
+ fprintf(stderr, "FATAL: OSRELEASE not found in vmcoreinfo\n");
+ abort();
+ }
+ return vmci.osrelease;
+}
+
+/* Debug helper */
+void vmcoreinfo_dump(void)
+{
+ int i;
+
+ pr_trace("=== VMCOREINFO DUMP ===\n");
+ pr_trace("OSRELEASE: %s\n", vmci.osrelease);
+ pr_trace("PAGESIZE: %s\n", vmci.pagesize);
+ pr_trace("SYMBOLS (%d entries):\n", vmci.symbols.count);
+
+ for (i = 0; i < VMCI_MAX_ENTRIES; i++) {
+ if (vmci.symbols.entries[i].valid)
+ pr_trace(" %s = 0x%llx\n", vmci.symbols.entries[i].key,
+ (unsigned long long) vmci.symbols.entries[i].value);
+ }
+
+ pr_trace("SIZES (%d entries):\n", vmci.sizes.count);
+ for (i = 0; i < VMCI_MAX_ENTRIES; i++) {
+ if (vmci.sizes.entries[i].valid)
+ pr_trace(" %s = %llu\n", vmci.sizes.entries[i].key,
+ (unsigned long long) vmci.sizes.entries[i].value);
+ }
+
+ pr_trace("OFFSETS (%d entries):\n", vmci.offsets.count);
+ for (i = 0; i < VMCI_MAX_ENTRIES; i++) {
+ if (vmci.offsets.entries[i].valid)
+ pr_trace(" %s = %llu\n", vmci.offsets.entries[i].key,
+ (unsigned long long) vmci.offsets.entries[i].value);
+ }
+
+ pr_trace("NUMBERS (%d entries):\n", vmci.numbers.count);
+ for (i = 0; i < VMCI_MAX_ENTRIES; i++) {
+ if (vmci.numbers.entries[i].valid)
+ pr_trace(" %s = 0x%llx\n", vmci.numbers.entries[i].key,
+ (unsigned long long) vmci.numbers.entries[i].value);
+ }
+
+ pr_trace("LENGTHS (%d entries):\n", vmci.lengths.count);
+ for (i = 0; i < VMCI_MAX_ENTRIES; i++) {
+ if (vmci.lengths.entries[i].valid)
+ pr_trace(" %s = %llu\n", vmci.lengths.entries[i].key,
+ (unsigned long long)vmci.lengths.entries[i].value);
+ }
+}
+
+/* Vmcore file descriptor management */
+void vmcore_set_fd(int fd)
+{
+ vmci.vmcore_fd = fd;
+}
+
+int vmcore_fd(void)
+{
+ return vmci.vmcore_fd;
+}
\ No newline at end of file
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread* Re: [PATCH 02/10] util_lib: Add vmcoreinfo parser and memory reader
2026-06-22 20:54 ` [PATCH 02/10] util_lib: Add vmcoreinfo parser and memory reader Pnina Feder
@ 2026-07-20 9:24 ` Simon Horman
0 siblings, 0 replies; 15+ messages in thread
From: Simon Horman @ 2026-07-20 9:24 UTC (permalink / raw)
To: Pnina Feder; +Cc: kexec
On Mon, Jun 22, 2026 at 11:54:40PM +0300, Pnina Feder wrote:
> Add the foundational utility libraries for parsing vmcore files:
>
> - vmcore_info: Parser for vmcoreinfo note section. Supports SYMBOL(),
> OFFSET(), SIZE(), NUMBER(), LENGTH() lookups via hash table.
> Provides cached get_page_size()/get_page_shift() accessors.
> Handles plain KEY=VALUE entries like PAGESIZE and OSRELEASE.
>
> - memory: Virtual-to-physical address translation dispatcher and
> readmem() for reading from vmcore via kernel or user virtual
> addresses, or physical addresses.Uses paddr_to_offset() from
> elf_info for physical-to-file-offset mapping.
>
> - vmcore_tasks_util: Shared macros, typedefs, and debug helpers
> used by vmcore_info and memory (debug verbosity levels, type
> accessors, __weak definition).
>
> These files are placed in util_lib/ but are currently only built by
> the vmcore_tasks Makefile. Integration into the main util_lib build
> can be done later if other tools need them.
>
> Signed-off-by: Pnina Feder <pnina.feder@mobileye.com>
There are whitepsace errors when applying using git am.
Likewise in patch 5/10, 8/10 and 9/10.
I'd appreciate it if you could address that along with
the minor concerns I raised with patch 1/10.
...
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH 03/10] util_lib: Add maple tree walker for VMA enumeration
2026-06-22 20:54 [PATCH 00/10] vmcore-tasks: lightweight task and backtrace extractor for vmcore files Pnina Feder
2026-06-22 20:54 ` [PATCH 01/10] util_lib: add paddr_to_offset function and expose raw vmcoreinfo data Pnina Feder
2026-06-22 20:54 ` [PATCH 02/10] util_lib: Add vmcoreinfo parser and memory reader Pnina Feder
@ 2026-06-22 20:54 ` Pnina Feder
2026-06-22 20:54 ` [PATCH 04/10] vmcore_tasks: Add common definitions and project infrastructure Pnina Feder
` (8 subsequent siblings)
11 siblings, 0 replies; 15+ messages in thread
From: Pnina Feder @ 2026-06-22 20:54 UTC (permalink / raw)
To: kexec; +Cc: horms, Pnina Feder
Port the maple tree traversal logic from crash-utility to support
walking the kernel's VMA maple tree structure. Supports count,
search, gather, and dump operations.
This is needed for enumerating process virtual memory areas (VMAs)
when building per-task memory snapshots.
Needed for Linux 6.x and up.
Signed-off-by: Pnina Feder <pnina.feder@mobileye.com>
---
util_lib/include/maple_tree.h | 137 +++++++
util_lib/maple_tree.c | 715 ++++++++++++++++++++++++++++++++++
2 files changed, 852 insertions(+)
create mode 100644 util_lib/include/maple_tree.h
create mode 100644 util_lib/maple_tree.c
diff --git a/util_lib/include/maple_tree.h b/util_lib/include/maple_tree.h
new file mode 100644
index 00000000..c17c528c
--- /dev/null
+++ b/util_lib/include/maple_tree.h
@@ -0,0 +1,137 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+#ifndef _MAPLE_TREE_H
+#define _MAPLE_TREE_H
+/*
+ * Maple Tree - An RCU-safe adaptive tree for storing ranges
+ * Copyright (c) 2018-2022 Oracle
+ * Authors: Liam R. Howlett <Liam.Howlett@Oracle.com>
+ * Matthew Wilcox <willy@infradead.org>
+ *
+ * eXtensible Arrays
+ * Copyright (c) 2017 Microsoft Corporation
+ * Author: Matthew Wilcox <willy@infradead.org>
+ *
+ * See Documentation/core-api/xarray.rst for how to use the XArray.
+ */
+#include <stdbool.h>
+#include <limits.h>
+#include <sys/types.h>
+
+#include "vmcore_tasks_util.h"
+
+#define MAPLE_TREE_COUNT (1)
+#define MAPLE_TREE_SEARCH (2)
+#define MAPLE_TREE_DUMP (3)
+#define MAPLE_TREE_GATHER (4)
+#define MAPLE_TREE_DUMP_CB (5)
+
+/*
+ * The following are copied and modified from include/linux/maple_tree.h
+ */
+
+enum maple_type {
+ maple_dense,
+ maple_leaf_64,
+ maple_range_64,
+ maple_arange_64,
+};
+
+#define MAPLE_NODE_MASK 255UL
+
+#define MT_FLAGS_HEIGHT_OFFSET 0x02
+#define MT_FLAGS_HEIGHT_MASK 0x7C
+
+#define MAPLE_NODE_TYPE_MASK 0x0F
+#define MAPLE_NODE_TYPE_SHIFT 0x03
+
+#define MAPLE_RESERVED_RANGE 4096
+
+#define VERBOSE (0x1)
+#define TREE_ROOT_OFFSET_ENTERED (VERBOSE << 1)
+#define TREE_NODE_OFFSET_ENTERED (VERBOSE << 2)
+#define TREE_NODE_POINTER (VERBOSE << 3)
+#define TREE_POSITION_DISPLAY (VERBOSE << 4)
+#define TREE_STRUCT_RADIX_10 (VERBOSE << 5)
+#define TREE_STRUCT_RADIX_16 (VERBOSE << 6)
+#define TREE_PARSE_MEMBER (VERBOSE << 7)
+#define TREE_READ_MEMBER (VERBOSE << 8)
+#define TREE_LINEAR_ORDER (VERBOSE << 9)
+#define TREE_STRUCT_VERBOSE (VERBOSE << 10)
+
+/*Copied from linux/maple_tree.h*/
+/* 64bit sizes */
+#define MAPLE_NODE_SLOTS 31 /* 256 bytes including ->parent */
+#define MAPLE_RANGE64_SLOTS 16 /* 256 bytes */
+#define MAPLE_ARANGE64_SLOTS 10 /* 240 bytes */
+#define MAPLE_ALLOC_SLOTS (MAPLE_NODE_SLOTS - 1)
+
+/*
+ * The following are copied and modified from include/linux/xarray.h
+ */
+
+#define XA_ZERO_ENTRY xa_mk_internal(257)
+
+static inline ulong xa_mk_internal(ulong v)
+{
+ return (v << 2) | 2;
+}
+
+static inline bool xa_is_internal(ulong entry)
+{
+ return (entry & 3) == 2;
+}
+
+static inline bool xa_is_node(ulong entry)
+{
+ return xa_is_internal(entry) && entry > 4096;
+}
+
+static inline bool xa_is_value(ulong entry)
+{
+ return entry & 1;
+}
+
+static inline bool xa_is_zero(ulong entry)
+{
+ return entry == XA_ZERO_ENTRY;
+}
+
+static inline unsigned long xa_to_internal(ulong entry)
+{
+ return entry >> 2;
+}
+
+static inline unsigned long xa_to_value(ulong entry)
+{
+ return entry >> 1;
+}
+
+struct tree_data {
+ ulong flags;
+ ulong start;
+ long node_member_offset;
+ char **structname;
+ int structname_args;
+ int count;
+};
+
+struct list_pair {
+ ulong index;
+ void *value;
+};
+
+struct req_entry {
+ char *arg, *name, **member;
+ int *is_str, *is_ptr;
+ ulong *width, *offset;
+ int count;
+};
+
+extern const unsigned char mt_slots[];
+extern const unsigned char mt_pivots[];
+
+void maple_init(void);
+int do_mptree(struct tree_data *);
+ulong do_maple_tree(ulong, int, struct list_pair *);
+
+#endif /* _MAPLE_TREE_H */
diff --git a/util_lib/maple_tree.c b/util_lib/maple_tree.c
new file mode 100644
index 00000000..909dfdbf
--- /dev/null
+++ b/util_lib/maple_tree.c
@@ -0,0 +1,715 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Maple Tree implementation
+ * Copyright (c) 2018-2022 Oracle Corporation
+ * Authors: Liam R. Howlett <Liam.Howlett@oracle.com>
+ * Matthew Wilcox <willy@infradead.org>
+ *
+ * The following are copied and modified from lib/maple_tree.c
+ */
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "maple_tree.h"
+#include "memory.h"
+#include "vmcore_info.h"
+
+const unsigned char mt_slots[] = {
+ [maple_dense] = MAPLE_NODE_SLOTS,
+ [maple_leaf_64] = MAPLE_RANGE64_SLOTS,
+ [maple_range_64] = MAPLE_RANGE64_SLOTS,
+ [maple_arange_64] = MAPLE_ARANGE64_SLOTS,
+};
+
+const unsigned char mt_pivots[] = {
+ [maple_dense] = 0,
+ [maple_leaf_64] = MAPLE_RANGE64_SLOTS - 1,
+ [maple_range_64] = MAPLE_RANGE64_SLOTS - 1,
+ [maple_arange_64] = MAPLE_ARANGE64_SLOTS - 1,
+};
+
+ulong mt_max[4] = {0};
+static FILE* fp;
+static bool maple_initialized = false;
+
+static uint64_t size_maple_tree;
+static uint64_t offset_maple_tree_ma_root;
+static uint64_t offset_maple_tree_ma_flags;
+
+/* maple_node offsets */
+static uint64_t size_maple_node;
+static uint64_t offset_maple_node_slot;
+static uint64_t offset_maple_node_parent;
+static uint64_t offset_maple_node_ma64;
+static uint64_t offset_maple_node_mr64;
+
+/* maple_range_64 size + offsets */
+static uint64_t offset_maple_range_64_pivot;
+static uint64_t offset_maple_range_64_slot;
+
+/* maple_metadata offsets */
+static uint64_t offset_maple_metadata_end;
+static uint64_t offset_maple_metadata_gap;
+
+/* maple_arange_64 size + offsets */
+static uint64_t offset_maple_arange_64_pivot;
+static uint64_t offset_maple_arange_64_slot;
+static uint64_t offset_maple_arange_64_gap;
+static uint64_t offset_maple_arange_64_meta;
+
+
+#define MAPLE_BUFSIZE 512
+#define BUFSIZE (1500)
+
+static inline ulong mte_to_node(ulong maple_enode_entry)
+{
+ return maple_enode_entry & ~MAPLE_NODE_MASK;
+}
+
+static inline enum maple_type mte_node_type(ulong maple_enode_entry)
+{
+ return (maple_enode_entry >> MAPLE_NODE_TYPE_SHIFT) &
+ MAPLE_NODE_TYPE_MASK;
+}
+
+static inline ulong mt_slot(void **slots, unsigned char offset)
+{
+ return (ulong)slots[offset];
+}
+
+static inline bool ma_is_leaf(const enum maple_type type)
+{
+ return type < maple_range_64;
+}
+
+/*************** For cmd_tree ********************/
+
+struct do_maple_tree_info {
+ ulong maxcount;
+ ulong count;
+ void *data;
+};
+
+struct maple_tree_ops {
+ void (*entry)(ulong node, ulong slot, const char *path,
+ ulong index, void *private);
+ void *private;
+ bool is_td;
+};
+
+static const char spaces[] = " ";
+
+static void do_mt_range64(ulong, ulong, ulong, uint, char *, ulong *,
+ struct maple_tree_ops *);
+static void do_mt_arange64(ulong, ulong, ulong, uint, char *, ulong *,
+ struct maple_tree_ops *);
+static void do_mt_entry(ulong, ulong, ulong, uint, uint, char *, ulong *,
+ struct maple_tree_ops *);
+static void do_mt_node(ulong, ulong, ulong, uint, char *, ulong *,
+ struct maple_tree_ops *);
+
+static int count_chars(const char *s, char c) {
+ int count = 0;
+ while (*s) if (*s++ == c) count++;
+ return count;
+}
+
+static void dump_struct(const char *name, ulong addr, int radix) {
+ printf("dump_struct not implemented\n");
+}
+
+struct req_entry *fill_member_offsets(char *name){
+ printf("fill_member_offsets - Not inmplemented\n");
+ return NULL;
+}
+
+void dump_struct_members_fast(struct req_entry *e, int radix , ulong addr){
+ printf("dump_struct_members_fast - Not inmplemented");
+}
+
+void dump_struct_members_for_tree(struct tree_data *td, int index, ulong addr){
+ printf("dump_struct_members_for_tree - Not inmplemented");
+}
+
+static void mt_dump_range(ulong min, ulong max, uint depth)
+{
+ if (min == max)
+ fprintf(fp, "%.*s%lu: ", depth * 2, spaces, min);
+ else
+ fprintf(fp, "%.*s%lu-%lu: ", depth * 2, spaces, min, max);
+}
+
+static inline bool mt_is_reserved(ulong entry)
+{
+ return (entry < MAPLE_RESERVED_RANGE) && xa_is_internal(entry);
+}
+
+static inline bool mte_is_leaf(ulong maple_enode_entry)
+{
+ return ma_is_leaf(mte_node_type(maple_enode_entry));
+}
+
+static uint mt_height(char *mt_buf)
+{
+ return (UINT(mt_buf + offset_maple_tree_ma_flags) &
+ MT_FLAGS_HEIGHT_MASK)
+ >> MT_FLAGS_HEIGHT_OFFSET;
+}
+
+static void dump_mt_range64(char *mr64_buf)
+{
+ int i;
+
+ fprintf(fp, " contents: ");
+ for (i = 0; i < mt_slots[maple_range_64] - 1; i++)
+ fprintf(fp, "%p %lu ",
+ VOID_PTR(mr64_buf + offset_maple_range_64_slot
+ + sizeof(void *) * i),
+ ULONG(mr64_buf + offset_maple_range_64_pivot
+ + sizeof(ulong) * i));
+ fprintf(fp, "%p\n", VOID_PTR(mr64_buf + offset_maple_range_64_slot
+ + sizeof(void *) * i));
+}
+
+static void dump_mt_arange64(char *ma64_buf)
+{
+ int i;
+
+ fprintf(fp, " contents: ");
+ for (i = 0; i < mt_slots[maple_arange_64]; i++)
+ fprintf(fp, "%lu ", ULONG(ma64_buf + offset_maple_arange_64_gap
+ + sizeof(ulong) * i));
+
+ fprintf(fp, "| %02X %02X| ",
+ UCHAR(ma64_buf + offset_maple_arange_64_meta +
+ offset_maple_metadata_end),
+ UCHAR(ma64_buf + offset_maple_arange_64_meta +
+ offset_maple_metadata_gap));
+
+ for (i = 0; i < mt_slots[maple_arange_64] - 1; i++)
+ fprintf(fp, "%p %lu ",
+ VOID_PTR(ma64_buf + offset_maple_arange_64_slot +
+ sizeof(void *) * i),
+ ULONG(ma64_buf + offset_maple_arange_64_pivot +
+ sizeof(ulong) * i));
+ fprintf(fp, "%p\n", VOID_PTR(ma64_buf + offset_maple_arange_64_slot +
+ sizeof(void *) * i));
+}
+
+static void dump_mt_entry(ulong entry, ulong min, ulong max, uint depth)
+{
+ mt_dump_range(min, max, depth);
+
+ if (xa_is_value(entry))
+ fprintf(fp, "value %ld (0x%lx) [0x%lx]\n", xa_to_value(entry),
+ xa_to_value(entry), entry);
+ else if (xa_is_zero(entry))
+ fprintf(fp, "zero (%ld)\n", xa_to_internal(entry));
+ else if (mt_is_reserved(entry))
+ fprintf(fp, "UNKNOWN ENTRY (0x%lx)\n", entry);
+ else
+ fprintf(fp, "0x%lx\n", entry);
+}
+
+static void dump_mt_node(ulong maple_node, char *node_data, uint type,
+ ulong min, ulong max, uint depth)
+{
+ mt_dump_range(min, max, depth);
+
+ fprintf(fp, "node 0x%lx depth %d type %d parent %p",
+ maple_node, depth, type,
+ maple_node ? VOID_PTR(node_data + offset_maple_node_parent) :
+ NULL);
+}
+
+static void do_mt_range64(ulong entry, ulong min, ulong max,
+ uint depth, char *path, ulong *global_index,
+ struct maple_tree_ops *ops)
+{
+ ulong maple_node_m_node = mte_to_node(entry);
+ char node_buf[MAPLE_BUFSIZE];
+ bool leaf = mte_is_leaf(entry);
+ ulong first = min, last;
+ int i;
+ int len = strlen(path);
+ struct tree_data *td = ops->is_td ? (struct tree_data *)ops->private : NULL;
+ char *mr64_buf;
+
+ if (size_maple_node > MAPLE_BUFSIZE) {
+ fprintf(fp, "MAPLE_BUFSIZE should be larger than maple_node struct");
+ return;
+ }
+
+ if (readmem(maple_node_m_node, node_buf, size_maple_node, "mt_dump_range64 read maple_node", KVADDR) < 0) {
+ fprintf(stderr, "do_mt_range64: failed to read maple_node at 0x%lx\n", maple_node_m_node);
+ return;
+ }
+
+ mr64_buf = node_buf + offset_maple_node_mr64;
+
+ if (td && td->flags & TREE_STRUCT_VERBOSE) {
+ dump_mt_range64(mr64_buf);
+ }
+
+ for (i = 0; i < mt_slots[maple_range_64]; i++) {
+ last = max;
+
+ if (i < (mt_slots[maple_range_64] - 1))
+ last = ULONG(mr64_buf + offset_maple_range_64_pivot +
+ sizeof(ulong) * i);
+
+ else if (!VOID_PTR(mr64_buf + offset_maple_range_64_slot +
+ sizeof(void *) * i) &&
+ max != mt_max[mte_node_type(entry)])
+ break;
+ if (last == 0 && i > 0)
+ break;
+ if (leaf)
+ do_mt_entry(mt_slot((void **)(mr64_buf +
+ offset_maple_range_64_slot), i),
+ first, last, depth + 1, i, path, global_index, ops);
+ else if (VOID_PTR(mr64_buf + offset_maple_range_64_slot +
+ sizeof(void *) * i)) {
+ sprintf(path + len, "/%d", i);
+ do_mt_node(mt_slot((void **)(mr64_buf +
+ offset_maple_range_64_slot), i),
+ first, last, depth + 1, path, global_index, ops);
+ }
+
+ if (last == max)
+ break;
+ if (last > max) {
+ fprintf(fp, "node %p last (%lu) > max (%lu) at pivot %d!\n",
+ mr64_buf, last, max, i);
+ break;
+ }
+ first = last + 1;
+ }
+}
+
+static void do_mt_arange64(ulong entry, ulong min, ulong max,
+ uint depth, char *path, ulong *global_index,
+ struct maple_tree_ops *ops)
+{
+ ulong maple_node_m_node = mte_to_node(entry);
+ char node_buf[MAPLE_BUFSIZE];
+ bool leaf = mte_is_leaf(entry);
+ ulong first = min, last;
+ int i;
+ int len = strlen(path);
+ struct tree_data *td = ops->is_td ? (struct tree_data *)ops->private : NULL;
+ char *ma64_buf;
+
+ if (size_maple_node > MAPLE_BUFSIZE) {
+ fprintf(fp, "MAPLE_BUFSIZE should be larger than maple_node struct");
+ return;
+ }
+
+ if (readmem(maple_node_m_node, node_buf, size_maple_node, "mt_dump_arange64 read maple_node", KVADDR) < 0) {
+ fprintf(stderr, "do_mt_arange64: failed to read maple_node at 0x%lx\n", maple_node_m_node);
+ return;
+ }
+
+ ma64_buf = node_buf + offset_maple_node_ma64;
+
+ if (td && td->flags & TREE_STRUCT_VERBOSE) {
+ dump_mt_arange64(ma64_buf);
+ }
+
+ for (i = 0; i < mt_slots[maple_arange_64]; i++) {
+ last = max;
+
+ if (i < (mt_slots[maple_arange_64] - 1))
+ last = ULONG(ma64_buf + offset_maple_arange_64_pivot +
+ sizeof(ulong) * i);
+ else if (!VOID_PTR(ma64_buf + offset_maple_arange_64_slot +
+ sizeof(void *) * i))
+ break;
+ if (last == 0 && i > 0)
+ break;
+
+ if (leaf)
+ do_mt_entry(mt_slot((void **)(ma64_buf +
+ offset_maple_arange_64_slot), i),
+ first, last, depth + 1, i, path, global_index, ops);
+ else if (VOID_PTR(ma64_buf + offset_maple_arange_64_slot +
+ sizeof(void *) * i)) {
+ sprintf(path + len, "/%d", i);
+ do_mt_node(mt_slot((void **)(ma64_buf +
+ offset_maple_arange_64_slot), i),
+ first, last, depth + 1, path, global_index, ops);
+ }
+
+ if (last == max)
+ break;
+ if (last > max) {
+ fprintf(fp, "node %p last (%lu) > max (%lu) at pivot %d!\n",
+ ma64_buf, last, max, i);
+ break;
+ }
+ first = last + 1;
+ }
+}
+
+static void do_mt_entry(ulong entry, ulong min, ulong max, uint depth,
+ uint index, char *path, ulong *global_index,
+ struct maple_tree_ops *ops)
+{
+ int print_radix = 0, i;
+ static struct req_entry **e = NULL;
+ struct tree_data *td = ops->is_td ? (struct tree_data *)ops->private : NULL;
+
+ if (ops->entry && entry)
+ ops->entry(entry, entry, path, max, ops->private);
+
+ if (!td)
+ return;
+
+ if (!td->count && td->structname_args) {
+ /*
+ * Retrieve all members' info only once (count == 0)
+ * After last iteration all memory will be freed up
+ */
+ e = (struct req_entry **)malloc(sizeof(*e) * td->structname_args);
+ for (i = 0; i < td->structname_args; i++)
+ e[i] = fill_member_offsets(td->structname[i]);
+ }
+
+ td->count++;
+
+ if (td->flags & TREE_STRUCT_VERBOSE) {
+ dump_mt_entry(entry, min, max, depth);
+ } else if (td->flags & VERBOSE && entry)
+ fprintf(fp, "%lx\n", entry);
+ if (td->flags & TREE_POSITION_DISPLAY && entry)
+ fprintf(fp, " index: %ld position: %s/%u\n",
+ ++(*global_index), path, index);
+
+ if (td->structname && entry) {
+ if (td->flags & TREE_STRUCT_RADIX_10)
+ print_radix = 10;
+ else if (td->flags & TREE_STRUCT_RADIX_16)
+ print_radix = 16;
+ else
+ print_radix = 0;
+
+ for (i = 0; i < td->structname_args; i++) {
+ switch (count_chars(td->structname[i], '.')) {
+ case 0:
+ dump_struct(td->structname[i], entry, print_radix);
+ break;
+ default:
+ if (td->flags & TREE_PARSE_MEMBER)
+ dump_struct_members_for_tree(td, i, entry);
+ else if (td->flags & TREE_READ_MEMBER)
+ dump_struct_members_fast(e[i], print_radix, entry);
+ }
+ }
+ }
+
+ if (e) {
+ for (i = 0; i < td->structname_args; i++)
+ free(e[i]);
+ free(e);
+ e = NULL;
+ }
+}
+
+static void do_mt_node(ulong entry, ulong min, ulong max,
+ uint depth, char *path, ulong *global_index,
+ struct maple_tree_ops *ops)
+{
+ ulong maple_node = mte_to_node(entry);
+ uint type = mte_node_type(entry);
+ uint i;
+ char node_buf[MAPLE_BUFSIZE];
+ struct tree_data *td = ops->is_td ? (struct tree_data *)ops->private : NULL;
+
+ if (size_maple_node > MAPLE_BUFSIZE) {
+ fprintf(fp, "MAPLE_BUFSIZE should be larger than maple_node struct");
+ return;
+ }
+
+ if (readmem(maple_node, node_buf, size_maple_node, "mt_dump_node read maple_node", KVADDR) < 0) {
+ fprintf(stderr, "do_mt_node: failed to read maple_node at 0x%lx\n", maple_node);
+ return;
+ }
+
+ if (td && td->flags & TREE_STRUCT_VERBOSE) {
+ dump_mt_node(maple_node, node_buf, type, min, max, depth);
+ }
+
+ switch (type) {
+ case maple_dense:
+ for (i = 0; i < mt_slots[maple_dense]; i++) {
+ if (min + i > max)
+ fprintf(fp, "OUT OF RANGE: ");
+ do_mt_entry(mt_slot((void **)(node_buf + offset_maple_node_slot), i),
+ min + i, min + i, depth, i, path, global_index, ops);
+ }
+ break;
+ case maple_leaf_64:
+ case maple_range_64:
+ do_mt_range64(entry, min, max, depth, path, global_index, ops);
+ break;
+ case maple_arange_64:
+ do_mt_arange64(entry, min, max, depth, path, global_index, ops);
+ break;
+ default:
+ fprintf(fp, " UNKNOWN TYPE\n");
+ }
+}
+
+static int do_maple_tree_traverse(ulong ptr, int is_root,
+ struct maple_tree_ops *ops)
+{
+ char path[BUFSIZE] = {0};
+ char tree_buf[MAPLE_BUFSIZE];
+ ulong entry;
+ struct tree_data *td = ops->is_td ? (struct tree_data *)ops->private : NULL;
+ ulong global_index = 0;
+
+ if (size_maple_tree > MAPLE_BUFSIZE) {
+ fprintf(fp, "MAPLE_BUFSIZE should be larger than maple_tree struct");
+ return -1;
+ }
+
+ if (!is_root) {
+ strcpy(path, "direct");
+ do_mt_node(ptr, 0, mt_max[mte_node_type(ptr)],
+ 0, path, &global_index, ops);
+ } else {
+ if (readmem(ptr, tree_buf, size_maple_tree, "mt_dump read maple_tree", KVADDR) < 0) {
+ fprintf(stderr, "do_maple_tree_traverse: failed to read maple_tree at 0x%lx\n", ptr);
+ return -1;
+ }
+
+ entry = ULONG(tree_buf + offset_maple_tree_ma_root);
+
+ if (td && td->flags & TREE_STRUCT_VERBOSE) {
+ fprintf(fp, "maple_tree(%lx) flags %X, height %u root 0x%lx\n\n",
+ ptr, UINT(tree_buf + offset_maple_tree_ma_flags),
+ mt_height(tree_buf), entry);
+ }
+
+ if (!xa_is_node(entry))
+ do_mt_entry(entry, 0, 0, 0, 0, path, &global_index, ops);
+ else if (entry) {
+ strcpy(path, "root");
+ do_mt_node(entry, 0, mt_max[mte_node_type(entry)], 0,
+ path, &global_index, ops);
+ }
+ }
+ return 0;
+}
+
+int do_mptree(struct tree_data *td)
+{
+ maple_init();
+
+ if (!fp) {
+ fprintf(stderr, "maple_tree: not initialized, call maple_init() first\n");
+ return -1;
+ }
+
+ struct maple_tree_ops ops = {
+ .entry = NULL,
+ .private = td,
+ .is_td = true,
+ };
+
+ int is_root = !(td->flags & TREE_NODE_POINTER);
+
+ do_maple_tree_traverse(td->start, is_root, &ops);
+
+ return 0;
+}
+
+/************* For do_maple_tree *****************/
+static void do_maple_tree_count(ulong node, ulong slot, const char *path,
+ ulong index, void *private)
+{
+ struct do_maple_tree_info *info = private;
+ info->count++;
+}
+
+static void do_maple_tree_search(ulong node, ulong slot, const char *path,
+ ulong index, void *private)
+{
+ struct do_maple_tree_info *info = private;
+ struct list_pair *lp = info->data;
+
+ if (lp->index == index) {
+ lp->value = (void *)slot;
+ info->count = 1;
+ }
+}
+
+static void do_maple_tree_dump(ulong node, ulong slot, const char *path,
+ ulong index, void *private)
+{
+ struct do_maple_tree_info *info = private;
+ fprintf(fp, "[%lu] %lx\n", index, slot);
+ info->count++;
+}
+
+static void do_maple_tree_gather(ulong node, ulong slot, const char *path,
+ ulong index, void *private)
+{
+ struct do_maple_tree_info *info = private;
+ struct list_pair *lp = info->data;
+
+ if (info->maxcount) {
+ lp[info->count].index = index;
+ lp[info->count].value = (void *)slot;
+
+ info->count++;
+ info->maxcount--;
+ }
+}
+
+static void do_maple_tree_dump_cb(ulong node, ulong slot, const char *path,
+ ulong index, void *private)
+{
+ struct do_maple_tree_info *info = private;
+ struct list_pair *lp = info->data;
+ int (*cb)(ulong) = lp->value;
+
+ /* Caller defined operation */
+ if (!cb(slot)) {
+ fprintf(fp, "do_maple_tree: callback "
+ "operation failed: entry: %ld item: %lx\n",
+ info->count, slot);
+ return;
+ }
+ info->count++;
+}
+
+/*
+ * do_maple_tree argument usage:
+ *
+ * root: Address of a maple_tree_root structure
+ *
+ * flag: MAPLE_TREE_COUNT - Return the number of entries in the tree.
+ * MAPLE_TREE_SEARCH - Search for an entry at lp->index; if found,
+ * store the entry in lp->value and return a count of 1; otherwise
+ * return a count of 0.
+ * MAPLE_TREE_DUMP - Dump all existing index/value pairs.
+ * MAPLE_TREE_GATHER - Store all existing index/value pairs in the
+ * passed-in array of list_pair structs starting at lp,
+ * returning the count of entries stored; the caller can/should
+ * limit the number of returned entries by putting the array size
+ * (max count) in the lp->index field of the first structure
+ * in the passed-in array.
+ * MAPLE_TREE_DUMP_CB - Similar with MAPLE_TREE_DUMP, but for each
+ * maple tree entry, a user defined callback at lp->value will
+ * be invoked.
+ *
+ * lp: Unused by MAPLE_TREE_COUNT and MAPLE_TREE_DUMP.
+ * A pointer to a list_pair structure for MAPLE_TREE_SEARCH.
+ * A pointer to an array of list_pair structures for
+ * MAPLE_TREE_GATHER; the dimension (max count) of the array may
+ * be stored in the index field of the first structure to avoid
+ * any chance of an overrun.
+ * For MAPLE_TREE_DUMP_CB, the lp->value must be initialized as a
+ * callback function. The callback prototype must be: int (*)(ulong);
+ */
+ulong
+do_maple_tree(ulong root, int flag, struct list_pair *lp)
+{
+ maple_init();
+
+ if (!fp) {
+ fprintf(stderr, "maple_tree: not initialized, call maple_init() first\n");
+ return 0;
+ }
+
+ struct do_maple_tree_info info = {
+ .count = 0,
+ .data = lp,
+ };
+ struct maple_tree_ops ops = {
+ .private = &info,
+ .is_td = false,
+ };
+
+ switch (flag)
+ {
+ case MAPLE_TREE_COUNT:
+ ops.entry = do_maple_tree_count;
+ break;
+
+ case MAPLE_TREE_SEARCH:
+ ops.entry = do_maple_tree_search;
+ break;
+
+ case MAPLE_TREE_DUMP:
+ ops.entry = do_maple_tree_dump;
+ break;
+
+ case MAPLE_TREE_GATHER:
+ if (!(info.maxcount = lp->index))
+ info.maxcount = (ulong)(-1); /* caller beware */
+
+ ops.entry = do_maple_tree_gather;
+ break;
+
+ case MAPLE_TREE_DUMP_CB:
+ if (lp->value == NULL) {
+ fprintf(fp, "do_maple_tree: need set callback function");
+ return 0;
+ }
+ ops.entry = do_maple_tree_dump_cb;
+ break;
+
+ default:
+ fprintf(fp, "do_maple_tree: invalid flag: %d\n", flag);
+ return 0;
+ }
+
+ do_maple_tree_traverse(root, true, &ops);
+ return info.count;
+}
+
+/***********************************************/
+void maple_init(void)
+{
+ if (maple_initialized)
+ return;
+ maple_initialized = true;
+
+ if (SIZE_EXISTS("maple_tree")) {
+ fp = stdout;
+
+ size_maple_tree = SIZE("maple_tree");
+ size_maple_node = SIZE("maple_node");
+
+ offset_maple_tree_ma_root = OFFSET("maple_tree.ma_root");
+ offset_maple_tree_ma_flags = OFFSET("maple_tree.ma_flags");
+
+ offset_maple_node_parent = OFFSET("maple_node.parent");
+ offset_maple_node_ma64 = OFFSET("maple_node.ma64");
+ offset_maple_node_mr64 = OFFSET("maple_node.mr64");
+ offset_maple_node_slot = OFFSET("maple_node.slot");
+
+ offset_maple_arange_64_pivot = OFFSET("maple_arange_64.pivot");
+ offset_maple_arange_64_slot = OFFSET("maple_arange_64.slot");
+ offset_maple_arange_64_gap = OFFSET("maple_arange_64.gap");
+ offset_maple_arange_64_meta = OFFSET("maple_arange_64.meta");
+
+ offset_maple_range_64_pivot = OFFSET("maple_range_64.pivot");
+ offset_maple_range_64_slot = OFFSET("maple_range_64.slot");
+
+ offset_maple_metadata_end = OFFSET("maple_metadata.end");
+ offset_maple_metadata_gap = OFFSET("maple_metadata.gap");
+
+ mt_max[maple_dense] = mt_slots[maple_dense];
+ mt_max[maple_leaf_64] = ULONG_MAX;
+ mt_max[maple_range_64] = ULONG_MAX;
+ mt_max[maple_arange_64] = ULONG_MAX;
+
+ } else {
+ pr_info("maple_tree not found in vmcoreinfo.\n");
+ }
+}
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread* [PATCH 04/10] vmcore_tasks: Add common definitions and project infrastructure
2026-06-22 20:54 [PATCH 00/10] vmcore-tasks: lightweight task and backtrace extractor for vmcore files Pnina Feder
` (2 preceding siblings ...)
2026-06-22 20:54 ` [PATCH 03/10] util_lib: Add maple tree walker for VMA enumeration Pnina Feder
@ 2026-06-22 20:54 ` Pnina Feder
2026-06-22 20:54 ` [PATCH 05/10] vmcore_tasks: arch: Add RISC-V 64-bit architecture support Pnina Feder
` (7 subsequent siblings)
11 siblings, 0 replies; 15+ messages in thread
From: Pnina Feder @ 2026-06-22 20:54 UTC (permalink / raw)
To: kexec; +Cc: horms, Pnina Feder
Add the shared header and build system for the vmcore-tasks tool:
- vmcore_tasks_defs.h: Core data structures for task extraction
(task_data, thread_data, vma_metadata, stackframe, system_minicore),
architecture dispatch via compile-time RISCV64/MIPS64 selection,
unwind_arch_ops callbacks for arch-specific instruction decode,
and VM flag constants.
- Makefile: Standalone build system with auto-detection of target
architecture from cross-compiler triplet, or explicit TARGET=
selection. Supports out-of-tree builds via O= parameter.
Compiles sources from both vmcore_tasks/ and util_lib/.
Signed-off-by: Pnina Feder <pnina.feder@mobileye.com>
---
vmcore_tasks/Makefile | 103 +++++++++++++++++++++++++
vmcore_tasks/vmcore_tasks_defs.h | 124 +++++++++++++++++++++++++++++++
2 files changed, 227 insertions(+)
create mode 100644 vmcore_tasks/Makefile
create mode 100644 vmcore_tasks/vmcore_tasks_defs.h
diff --git a/vmcore_tasks/Makefile b/vmcore_tasks/Makefile
new file mode 100644
index 00000000..0219c6d4
--- /dev/null
+++ b/vmcore_tasks/Makefile
@@ -0,0 +1,103 @@
+# Simple Makefile to build vmcore-tasks: task lister similar to vmcore-dmesg
+# Produces binary: vmcore-tasks
+
+CC ?= gcc
+CFLAGS ?= -O2 -g
+CFLAGS += -Wall -Wextra
+CFLAGS += -I$(SRC_DIR) -I$(SRC_DIR)/../util_lib/include
+
+# Support out-of-tree builds via O= parameter
+# Usage: make O=/path/to/build/dir
+SRC_DIR := $(dir $(realpath $(lastword $(MAKEFILE_LIST))))
+ifdef O
+ BUILD_DIR := $(O)
+else
+ BUILD_DIR := $(SRC_DIR)
+endif
+
+# Auto-detect target architecture from compiler, unless TARGET is specified
+# Usage: make # auto-detect from gcc
+# make TARGET=RISCV64 # force specific target
+# make TARGET=MIPS64 # force specific target
+
+ifndef TARGET
+ # Get compiler's target triplet (e.g., riscv64-unknown-linux-gnu, x86_64-linux-gnu)
+ CC_TARGET := $(shell $(CC) -dumpmachine)
+
+ ifneq (,$(findstring riscv64,$(CC_TARGET)))
+ TARGET := RISCV64
+ else ifneq (,$(findstring riscv32,$(CC_TARGET)))
+ TARGET := RISCV32
+ else ifneq (,$(findstring mips,$(CC_TARGET)))
+ TARGET := MIPS64
+ else ifneq (,$(findstring mips32,$(CC_TARGET)))
+ TARGET := MIPS32
+ else ifneq (,$(findstring aarch64,$(CC_TARGET)))
+ TARGET := ARM64
+ else ifneq (,$(findstring x86_64,$(CC_TARGET)))
+ TARGET := X86_64
+ else
+ $(error Unsupported architecture: $(CC_TARGET). Use TARGET=<arch> to specify.)
+ endif
+endif
+
+# Add architecture define
+CFLAGS += -D$(TARGET)
+
+# Arch-specific sources based on target
+ifeq ($(TARGET),RISCV64)
+ CFLAGS += -I$(SRC_DIR)/arch/riscv64
+ ARCH_SRC = $(wildcard $(SRC_DIR)/arch/riscv64/*.c)
+else ifeq ($(TARGET),MIPS64)
+ CFLAGS += -I$(SRC_DIR)/arch/mips64
+ # MIPS ABI/endian flags only when using a MIPS cross-compiler
+ ifneq (,$(findstring mips,$(shell $(CC) -dumpmachine)))
+ CFLAGS += -EL -mabi=64 -march=mips64r6 -fPIC
+ LDFLAGS += -static
+ endif
+ ARCH_SRC = $(wildcard $(SRC_DIR)/arch/mips64/*.c)
+else
+ ARCH_SRC =
+endif
+
+$(info Building for target: $(TARGET) (compiler: $(CC)))
+
+# Sources (relative to SRC_DIR)
+MAIN_SRC = $(wildcard $(SRC_DIR)/*.c)
+UTIL_SRC = $(addprefix $(SRC_DIR)/../,util_lib/elf_info.c util_lib/maple_tree.c util_lib/memory.c util_lib/vmcore_info.c)
+
+# Headers (for dependency tracking)
+HEADERS = $(wildcard $(SRC_DIR)/*.h) $(wildcard $(SRC_DIR)/../util_lib/include/*.h) $(wildcard $(SRC_DIR)/arch/$(TARGET)/*.h)
+
+# Objects (placed in BUILD_DIR, mirroring source structure)
+MAIN_OBJ = $(patsubst $(SRC_DIR)/%.c,$(BUILD_DIR)/%.o,$(MAIN_SRC))
+ARCH_OBJ = $(patsubst $(SRC_DIR)/%.c,$(BUILD_DIR)/%.o,$(ARCH_SRC))
+UTIL_OBJ = $(patsubst $(SRC_DIR)/../%.c,$(BUILD_DIR)/%.o,$(UTIL_SRC))
+ALL_OBJ = $(MAIN_OBJ) $(ARCH_OBJ) $(UTIL_OBJ)
+
+BIN = $(BUILD_DIR)/vmcore-tasks
+
+.PHONY: all clean
+
+all: $(BIN)
+
+$(BIN): $(ALL_OBJ)
+ $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^
+
+# All objects depend on all headers
+$(ALL_OBJ) : $(HEADERS)
+
+.SECONDEXPANSION:
+
+# Create build subdirectories as needed
+$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c | $$(dir $$@)
+ $(CC) $(CFLAGS) -c $< -o $@
+
+$(BUILD_DIR)/%.o: $(SRC_DIR)/../%.c | $$(dir $$@)
+ $(CC) $(CFLAGS) -c $< -o $@
+
+%/:
+ mkdir -p $@
+
+clean:
+ rm -f $(ALL_OBJ) $(BIN)
diff --git a/vmcore_tasks/vmcore_tasks_defs.h b/vmcore_tasks/vmcore_tasks_defs.h
new file mode 100644
index 00000000..cfc5864d
--- /dev/null
+++ b/vmcore_tasks/vmcore_tasks_defs.h
@@ -0,0 +1,124 @@
+#ifndef VMCORE_TASKS_DEFS_H
+#define VMCORE_TASKS_DEFS_H
+
+#include <stdbool.h>
+#include "vmcore_tasks_util.h"
+
+#ifdef MIPS64
+#include "arch/mips64/mips64_defs.h"
+#elif defined(RISCV64)
+#include "arch/riscv64/riscv64_defs.h"
+#else
+#error "No architecture defined."
+#endif
+
+#define CODE_BUF_SIZE 4096
+#define MAX_TASK_NAME 16
+#define MAX_MAPPING_FILENAME 48
+#define MAX_MAPPING_TYPE 16
+#define BACKTRACE_DEPTH 30
+
+/* VM flags from linux/mm.h */
+#define VM_READ 0x00000001
+#define VM_WRITE 0x00000002
+#define VM_EXEC 0x00000004
+
+
+struct stackframe {
+ unsigned long pc;
+ unsigned long sp;
+ unsigned long ra;
+};
+
+struct signal_unwind_layout {
+ const char *rt_sigframe_uc_key;
+ const char *ucontext_mcontext_key;
+ const char *sigcontext_regs_key;
+ const char *sigcontext_pc_key;
+ unsigned int reg_width;
+ int sp_reg_index;
+ int ra_reg_index;
+ int pc_reg_index;
+ int pc_reg_alt_index;
+};
+
+struct backtrace_entries {
+ unsigned long abs_addr;
+ unsigned long vm_start;
+ unsigned long vm_end;
+ char filename[MAX_MAPPING_FILENAME];
+};
+
+struct vma_metadata {
+ unsigned long start;
+ unsigned long end;
+ unsigned long flags;
+ unsigned long file;
+ char filename[MAX_MAPPING_FILENAME];
+ char type[MAX_MAPPING_TYPE];
+};
+
+struct task_vmas {
+ int vma_count;
+ struct vma_metadata *vma_meta;
+};
+
+struct thread_data {
+ char comm[MAX_TASK_NAME];
+ pid_t tid;
+ int state;
+ int state_idx;
+ uint32_t flags;
+ struct pt_regs regs;
+ struct backtrace_entries bt_entry[BACKTRACE_DEPTH];
+};
+
+struct task_data{
+ char comm[MAX_TASK_NAME];
+ pid_t pid;
+ uint8_t is_user_task;
+ uint64_t mm_pgd;
+ struct task_vmas vmas;
+ int num_threads;
+ uint32_t flags;
+ int state;
+ int state_idx;
+ struct thread_data *threads;
+};
+
+struct system_minicore {
+ int task_count;
+ struct task_data *tasks;
+};
+
+struct unwind_arch_ops {
+ unsigned int insn_size;
+ unsigned int ra_width;
+ bool (*is_sp_move_ins)(uint32_t insn, int *stack_adj);
+ bool (*is_ra_save_ins)(uint32_t insn, unsigned long *ra_offset);
+ bool (*is_end_of_backtrace)(uint32_t insn); /* NULL = not used */
+ int (*post_unwind_fixup)(struct stackframe *frame, unsigned int max_check); /* NULL = not used */
+ bool (*likely_signal_pc)(uint64_t pc);
+ const struct signal_unwind_layout *signal_layout;
+};
+
+int unwind_user_frame(struct stackframe *old_frame,
+ const unsigned int max_check);
+
+struct arch_deps {
+ bool (*vtop)(uint64_t, uint64_t, uint64_t *);
+ struct unwind_arch_ops unwind_ops;
+};
+extern struct arch_deps arch;
+
+#ifdef MIPS64
+void mips64_init();
+#define arch_init() mips64_init()
+#elif defined(RISCV64)
+void riscv64_init();
+#define arch_init() riscv64_init()
+#else
+#error "No architecture defined."
+#endif
+
+#endif /* VMCORE_TASKS_DEFS_H */
\ No newline at end of file
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread* [PATCH 05/10] vmcore_tasks: arch: Add RISC-V 64-bit architecture support
2026-06-22 20:54 [PATCH 00/10] vmcore-tasks: lightweight task and backtrace extractor for vmcore files Pnina Feder
` (3 preceding siblings ...)
2026-06-22 20:54 ` [PATCH 04/10] vmcore_tasks: Add common definitions and project infrastructure Pnina Feder
@ 2026-06-22 20:54 ` Pnina Feder
2026-06-22 20:54 ` [PATCH 06/10] vmcore_tasks: arch: Add MIPS64 " Pnina Feder
` (6 subsequent siblings)
11 siblings, 0 replies; 15+ messages in thread
From: Pnina Feder @ 2026-06-22 20:54 UTC (permalink / raw)
To: kexec; +Cc: horms, Pnina Feder
Add RISC-V 64-bit page table translation and instruction helpers:
- riscv64_defs.h: SV39 page table constants, PTE format and
extraction macros, pt_regs register structure.
- SV39 3-level page table walk (PGD -> PMD -> PTE) with support
for 1GB gigapages and 2MB megapages at leaf entries.
SV48 and SV57 stubs selected from vmcoreinfo VA_BITS but not
yet implemented.
- Instruction decode helpers for the generic unwinder:
is_sp_move_ins: detects addi sp, sp, imm (stack frame setup)
is_ra_save_ins: detects sd ra, offset(sp) (RA save to stack)
- Signal frame detection: scans for ecall + li a7,__NR_rt_sigreturn
pattern near PC to identify signal trampolines. Signal frame
layout driven by vmcoreinfo offsets (rt_sigframe, ucontext,
sigcontext).
- Register formatting for pt_regs dump in mini coredump output.
Page size read at runtime from vmcoreinfo via get_page_size().
Signed-off-by: Pnina Feder <pnina.feder@mobileye.com>
---
vmcore_tasks/arch/riscv64/riscv64.c | 327 +++++++++++++++++++++++
vmcore_tasks/arch/riscv64/riscv64_defs.h | 127 +++++++++
2 files changed, 454 insertions(+)
create mode 100644 vmcore_tasks/arch/riscv64/riscv64.c
create mode 100644 vmcore_tasks/arch/riscv64/riscv64_defs.h
diff --git a/vmcore_tasks/arch/riscv64/riscv64.c b/vmcore_tasks/arch/riscv64/riscv64.c
new file mode 100644
index 00000000..7123d037
--- /dev/null
+++ b/vmcore_tasks/arch/riscv64/riscv64.c
@@ -0,0 +1,327 @@
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "memory.h"
+#include "vmcore_info.h"
+
+#include "vmcore_tasks_defs.h"
+
+#define RISCV_ECALL_INS 0x00000073U
+#define RISCV_ADDI_OPCODE 0x13U
+#define RISCV_RT_SIGRETURN_NR_DEF 139U
+
+/*
+ * Sv39 Page Table Walk - Main Implementation
+ *
+ * @param pgd_paddr Physical address of PGD (page table root)
+ * @param vaddr Virtual address to translate
+ * @param paddr Output: physical address
+ * @return true if translation succeeded
+ */
+bool riscv64_sv39_vtop(uint64_t pgd_paddr,
+ uint64_t vaddr,
+ uint64_t *paddr)
+{
+ uint64_t pgd_entry, pmd_entry, pte_entry;
+ uint64_t pgd_entry_paddr, pmd_paddr, pte_paddr;
+ uint64_t pfn;
+
+ pr_trace("riscv64_sv39_vtop: Translating vaddr 0x%llx using PGD at paddr 0x%llx\n",
+ (unsigned long long)vaddr, (unsigned long long)pgd_paddr);
+ /*
+ * STEP 1: Read PGD entry
+ *
+ * PGD entry address = pgd_paddr + (pgd_index * 8)
+ * Each entry is 8 bytes (64-bit)
+ */
+ pgd_entry_paddr = pgd_paddr + (pgd_index(vaddr) * sizeof(uint64_t));
+
+ if (readmem(pgd_entry_paddr, &pgd_entry, sizeof(pgd_entry), "pgd_entry", PADDR) < 0)
+ return false;
+
+ /* Check if PGD entry is valid */
+ if (!(pgd_entry & _PAGE_PRESENT))
+ return false;
+
+ /*
+ * Check for 1GB gigapage (leaf PGD entry)
+ * If R, W, or X bits are set, this is a leaf entry
+ */
+ if (pgd_entry & (_PAGE_READ | _PAGE_WRITE | _PAGE_EXEC)) {
+ /* 1GB page: PA = PPN[2] | vaddr[29:0] */
+ pfn = pte_pfn(pgd_entry);
+ *paddr = pfn_to_phys(pfn) | (vaddr & ((1UL << PGD_SHIFT) - 1));
+ return true;
+ }
+
+ /* Extract physical address of PMD table */
+ pmd_paddr = pte_to_phys(pgd_entry);
+
+ /*
+ * STEP 2: Read PMD entry
+ */
+ uint64_t pmd_entry_paddr = pmd_paddr + (pmd_index(vaddr) * sizeof(uint64_t));
+
+ if (readmem(pmd_entry_paddr, &pmd_entry, sizeof(pmd_entry), "pmd_entry", PADDR) < 0)
+ return false;
+
+ if (!(pmd_entry & _PAGE_PRESENT))
+ return false;
+
+ /*
+ * Check for 2MB megapage (leaf PMD entry)
+ */
+ if (pmd_entry & (_PAGE_READ | _PAGE_WRITE | _PAGE_EXEC)) {
+ /* 2MB page: PA = PPN[2:1] | vaddr[20:0] */
+ pfn = pte_pfn(pmd_entry);
+ *paddr = pfn_to_phys(pfn) | (vaddr & ((1UL << PMD_SHIFT) - 1));
+ return true;
+ }
+
+ /* Extract physical address of PTE table */
+ pte_paddr = pte_to_phys(pmd_entry);
+
+ /*
+ * STEP 3: Read PTE entry
+ */
+ uint64_t pte_entry_paddr = pte_paddr + (pte_index(vaddr) * sizeof(uint64_t));
+
+ if (readmem(pte_entry_paddr, &pte_entry, sizeof(pte_entry), "pte_entry", PADDR) < 0)
+ return false;
+
+ if (!(pte_entry & _PAGE_PRESENT)) {
+ pr_trace("PTE not present for vaddr 0x%llx: pte_entry=0x%llx (V=%d R=%d W=%d X=%d)\n",
+ (unsigned long long)vaddr,
+ (unsigned long long)pte_entry,
+ !!(pte_entry & _PAGE_PRESENT),
+ !!(pte_entry & _PAGE_READ),
+ !!(pte_entry & _PAGE_WRITE),
+ !!(pte_entry & _PAGE_EXEC));
+ return false;
+ }
+
+ /*
+ * STEP 4: Extract final physical address
+ * PA = PPN << 12 | rv_page_offset(vaddr)
+ */
+ pfn = pte_pfn(pte_entry);
+ *paddr = pfn_to_phys(pfn) | rv_page_offset(vaddr);
+
+ return true;
+}
+
+bool riscv64_sv48_vtop(uint64_t pgd_paddr,
+ uint64_t vaddr,
+ uint64_t *paddr)
+{
+ // Not implemented yet
+ (void)pgd_paddr; (void)vaddr; (void)paddr;
+ fprintf(stderr, "riscv64_sv48_vtop: SV48 translation not implemented\n");
+ return false;
+}
+
+bool riscv64_sv57_vtop(uint64_t pgd_paddr,
+ uint64_t vaddr,
+ uint64_t *paddr)
+{
+ // Not implemented yet
+ (void)pgd_paddr; (void)vaddr; (void)paddr;
+ fprintf(stderr, "riscv64_sv57_vtop: SV57 translation not implemented\n");
+ return false;
+}
+
+/* Helper - extract RISC-V fields from a 32-bit instruction word */
+static inline unsigned int rv_opcode(uint32_t insn) { return insn & 0x7f; }
+static inline unsigned int rv_rd(uint32_t insn) { return (insn >> 7) & 0x1f; }
+static inline unsigned int rv_funct3(uint32_t insn) { return (insn >> 12) & 0x7; }
+static inline unsigned int rv_rs1(uint32_t insn) { return (insn >> 15) & 0x1f; }
+static inline unsigned int rv_rs2(uint32_t insn) { return (insn >> 20) & 0x1f; }
+static inline unsigned int rv_funct7(uint32_t insn) { return (insn >> 25) & 0x7f; }
+
+/* Decode I-type signed imm (12-bit) */
+static inline int32_t rv_imm_i(uint32_t insn)
+{
+ int32_t imm = (int32_t)(insn >> 20) & 0xfff;
+ /* sign extend 12-bit */
+ if (imm & 0x800)
+ imm |= ~0xfff;
+ return imm;
+}
+
+/* Decode S-type signed imm (store), imm[11:5] = bits 31:25, imm[4:0] = bits 11:7 */
+static inline int32_t rv_imm_s(uint32_t insn)
+{
+ int32_t imm = ((insn >> 25) & 0x7f) << 5;
+
+ imm |= ((insn >> 7) & 0x1f);
+ /* sign extend 12-bit */
+ if (imm & 0x800)
+ imm |= ~0xfff;
+ return imm;
+}
+
+/* helper: detect addi sp, sp, imm -> stack adjustment */
+/* returns true and sets *stack_adj (signed) if matches */
+static inline bool is_sp_move_ins_rv64(uint32_t insn, int *stack_adj)
+{
+ /* we look for addi rd=sp(2), rs1=sp(2), opcode = OP_IMM, funct3 = 0 (addi) */
+ if (rv_opcode(insn) == OPCODE_OP_IMM && rv_rd(insn) == REG_SP &&
+ rv_rs1(insn) == REG_SP && rv_funct3(insn) == 0) {
+ /* immediate is sign-extended 12-bit */
+ int32_t imm = rv_imm_i(insn);
+ *stack_adj = imm; /* imm can be negative for "addi sp, sp, -N" */
+ return true;
+ }
+ return false;
+}
+
+/* helper: detect store of ra into stack frame: sd ra, offset(sp) or sw ra, offset(sp) */
+/* returns true and sets *ra_offset (unsigned) if matches */
+static inline bool is_ra_save_ins_rv64(uint32_t insn, unsigned long *ra_offset)
+{
+ /* store opcode (S-type). stores have opcode == OPCODE_STORE (0x23).
+ * funct3 selects width: 0=sb,1=sh,2=sw,3=sd (on RV64)
+ *
+ * We accept any store where rs2 == ra (x1) and rs1 == sp (x2).
+ * Then the offset is the signed S-type immediate. Caller will treat offset as unsigned
+ * and compute new_frame.ra = old_sp + offset.
+ */
+ if (rv_opcode(insn) == OPCODE_STORE && rv_rs2(insn) == REG_RA &&
+ rv_rs1(insn) == REG_SP) {
+ int32_t imm = rv_imm_s(insn);
+ /* imm might be negative for weird encodings, but typical prologue uses positive offset */
+ if (imm < 0)
+ return false;
+ *ra_offset = (unsigned long)imm;
+ return true;
+ }
+ return false;
+}
+
+/* Check if instruction is signal syscall */
+static bool riscv_is_addi_a7_x0_imm(uint32_t insn, uint32_t imm12)
+{
+ uint32_t opcode = insn & 0x7f;
+ uint32_t rd = (insn >> 7) & 0x1f;
+ uint32_t funct3 = (insn >> 12) & 0x7;
+ uint32_t rs1 = (insn >> 15) & 0x1f;
+ uint32_t imm = (insn >> 20) & 0xfff;
+
+ return opcode == RISCV_ADDI_OPCODE &&
+ rd == 17 && /* a7 */
+ funct3 == 0 &&
+ rs1 == 0 && /* x0 */
+ imm == (imm12 & 0xfff);
+}
+
+static bool riscv64_likely_signal_pc(uint64_t pc)
+{
+ uint32_t nr = RISCV_RT_SIGRETURN_NR_DEF;
+ uint32_t insn[9];
+ int i, j;
+
+ if (NUMBER_EXISTS("__NR_rt_sigreturn"))
+ nr = (uint32_t)NUMBER("__NR_rt_sigreturn");
+
+ /* read window [pc-16, pc+16], 4-byte aligned */
+ for (i = -4; i <= 4; i++) {
+ uint64_t a = pc + ((int64_t)i * 4);
+ if (readmem(a, &insn[i + 4], sizeof(uint32_t),
+ "riscv sigtramp window", UVADDR) != (ssize_t)sizeof(uint32_t))
+ return false;
+ }
+
+ /* require ecall + nearby 'li a7,__NR_rt_sigreturn' */
+ for (i = 0; i < 9; i++) {
+ if (insn[i] != RISCV_ECALL_INS)
+ continue;
+
+ for (j = i - 3; j <= i - 1; j++) {
+ if (j < 0 || j >= 9)
+ continue;
+ if (riscv_is_addi_a7_x0_imm(insn[j], nr))
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/* vmcoreinfo-driven signal-frame layout for RISC-V */
+static const struct signal_unwind_layout riscv64_sig_layout = {
+ /* sp + OFFSET(rt_sigframe.uc) */
+ .rt_sigframe_uc_key = "rt_sigframe.uc",
+ /* + OFFSET(ucontext.uc_mcontext) */
+ .ucontext_mcontext_key = "ucontext.uc_mcontext",
+ /* + OFFSET(sigcontext.sc_regs) */
+ .sigcontext_regs_key = "sigcontext.sc_regs",
+
+ /* no dedicated sigcontext PC key exported for RISC-V in your setup */
+ .sigcontext_pc_key = NULL,
+
+ .reg_width = 8, /* 64-bit regs */
+ .ra_reg_index = REG_RA,
+ .sp_reg_index = REG_SP,
+ .pc_reg_index = REG_PC,
+ .pc_reg_alt_index = -1,
+};
+
+void riscv64_init(void)
+{
+ arch.unwind_ops.insn_size = 4; /* 32-bit instructions */
+ arch.unwind_ops.ra_width = 8; /* RA is 64-bit */
+ arch.unwind_ops.is_sp_move_ins = is_sp_move_ins_rv64;
+ arch.unwind_ops.is_ra_save_ins = is_ra_save_ins_rv64;
+ arch.unwind_ops.is_end_of_backtrace = NULL; /* not used */
+ arch.unwind_ops.post_unwind_fixup = NULL; /* not used */
+ arch.unwind_ops.likely_signal_pc = riscv64_likely_signal_pc;
+ arch.unwind_ops.signal_layout = &riscv64_sig_layout;
+
+ int vtop_type = NUMBER("VA_BITS");
+ switch(vtop_type) {
+ case 39:
+ arch.vtop = riscv64_sv39_vtop;
+ break;
+ case 48:
+ arch.vtop = riscv64_sv48_vtop;
+ break;
+ case 57:
+ arch.vtop = riscv64_sv57_vtop;
+ break;
+ default:
+ fprintf(stderr, "Unsupported or missing RISCV VA_BITS=%d in vmcoreinfo\n", vtop_type);
+ arch.vtop = NULL;
+ break;
+ }
+}
+
+void write_user_registers_formatted(FILE *fp, struct pt_regs *regs)
+{
+ fprintf(fp, "user registers:\n");
+ fprintf(fp, "pc :0x%016llx ra :0x%016llx sp :0x%016llx gp :0x%016llx\n",
+ (unsigned long long)regs->pc, (unsigned long long)regs->ra,
+ (unsigned long long)regs->sp, (unsigned long long)regs->gp);
+ fprintf(fp, "tp :0x%016llx t0 :0x%016llx t1 :0x%016llx t2 :0x%016llx\n",
+ (unsigned long long)regs->tp, (unsigned long long)regs->t0,
+ (unsigned long long)regs->t1, (unsigned long long)regs->t2);
+ fprintf(fp, "s0 :0x%016llx s1 :0x%016llx a0 :0x%016llx a1 :0x%016llx\n",
+ (unsigned long long)regs->s0, (unsigned long long)regs->s1,
+ (unsigned long long)regs->a0, (unsigned long long)regs->a1);
+ fprintf(fp, "a2 :0x%016llx a3 :0x%016llx a4 :0x%016llx a5 :0x%016llx\n",
+ (unsigned long long)regs->a2, (unsigned long long)regs->a3,
+ (unsigned long long)regs->a4, (unsigned long long)regs->a5);
+ fprintf(fp, "a6 :0x%016llx a7 :0x%016llx s2 :0x%016llx s3 :0x%016llx\n",
+ (unsigned long long)regs->a6, (unsigned long long)regs->a7,
+ (unsigned long long)regs->s2, (unsigned long long)regs->s3);
+ fprintf(fp, "s4 :0x%016llx s5 :0x%016llx s6 :0x%016llx s7 :0x%016llx\n",
+ (unsigned long long)regs->s4, (unsigned long long)regs->s5,
+ (unsigned long long)regs->s6, (unsigned long long)regs->s7);
+ fprintf(fp, "s8 :0x%016llx s9 :0x%016llx s10:0x%016llx s11:0x%016llx\n",
+ (unsigned long long)regs->s8, (unsigned long long)regs->s9,
+ (unsigned long long)regs->s10, (unsigned long long)regs->s11);
+ fprintf(fp, "t3 :0x%016llx t4 :0x%016llx t5 :0x%016llx t6 :0x%016llx\n",
+ (unsigned long long)regs->t3, (unsigned long long)regs->t4,
+ (unsigned long long)regs->t5, (unsigned long long)regs->t6);
+}
diff --git a/vmcore_tasks/arch/riscv64/riscv64_defs.h b/vmcore_tasks/arch/riscv64/riscv64_defs.h
new file mode 100644
index 00000000..fbeaec8d
--- /dev/null
+++ b/vmcore_tasks/arch/riscv64/riscv64_defs.h
@@ -0,0 +1,127 @@
+#ifndef RISCV64_DEFS_H
+#define RISCV64_DEFS_H
+
+#include <stdint.h>
+
+#include "vmcore_info.h"
+
+/* Page table definitions */
+/* Page size - read from vmcoreinfo at runtime via sys_cfg */
+#define SYS_PAGE_SIZE (get_page_size())
+#define SYS_PAGE_SHIFT (get_page_shift())
+#define SYS_PAGE_MASK (~(SYS_PAGE_SIZE - 1)) // 0xFFFFFFFFFFFFF000
+
+/* SV39 Page table shifts for 4k pages */
+/* WARNING: These assume 4K pages. For non-4K page sizes, these must be
+ * recalculated based on get_page_shift(). */
+/* NOTE: Sv48/Sv57 require different shifts — see riscv64_sv48_vtop */
+#define PGD_SHIFT 30 // PGD covers bits [38:30]
+#define PMD_SHIFT 21 // PMD covers bits [29:21]
+#define PTE_SHIFT 12 // PTE covers bits [20:12]
+
+/* Entries per level (9 bits each = 512 entries) */
+#define PTRS_PER_PGD 512
+#define PTRS_PER_PMD 512
+#define PTRS_PER_PTE 512
+
+/* PTE format */
+#define _PAGE_PFN_SHIFT 10 // PPN starts at bit 10 in PTE
+#define PTE_PFN_MASK 0x003FFFFFFFFFFC00UL // Bits [53:10] = PPN
+
+/* Page flags (bits [9:0]) */
+#define _PAGE_PRESENT (1 << 0) // V - Valid
+#define _PAGE_READ (1 << 1) // R - Readable
+#define _PAGE_WRITE (1 << 2) // W - Writable
+#define _PAGE_EXEC (1 << 3) // X - Executable
+#define _PAGE_USER (1 << 4) // U - User accessible
+#define _PAGE_GLOBAL (1 << 5) // G - Global mapping
+#define _PAGE_ACCESSED (1 << 6) // A - Accessed
+#define _PAGE_DIRTY (1 << 7) // D - Dirty
+
+/* Mask for PPN + protection bits (bits [53:0]) */
+#define PTE_PFN_PROT_MASK 0x003FFFFFFFFFFFFFULL
+
+
+/* Extract index for each page table level from virtual address */
+
+// PGD index: bits [38:30] - 9 bits
+#define pgd_index(vaddr) (((vaddr) >> PGD_SHIFT) & (PTRS_PER_PGD - 1))
+
+// PMD index: bits [29:21] - 9 bits
+#define pmd_index(vaddr) (((vaddr) >> PMD_SHIFT) & (PTRS_PER_PMD - 1))
+
+// PTE index: bits [20:12] - 9 bits
+#define pte_index(vaddr) (((vaddr) >> PTE_SHIFT) & (PTRS_PER_PTE - 1))
+
+// Page offset: bits [11:0] - 12 bits
+#define rv_page_offset(vaddr) ((vaddr) & (SYS_PAGE_SIZE - 1))
+
+
+/* Extract physical address of next-level page table from PTE value */
+#define pte_to_phys(pte_val) (((pte_val) >> _PAGE_PFN_SHIFT) << SYS_PAGE_SHIFT)
+
+/* Same as above, written differently */
+#define pte_pfn(pte_val) ((pte_val) >> _PAGE_PFN_SHIFT)
+#define pfn_to_phys(pfn) ((pfn) << SYS_PAGE_SHIFT)
+/* unwind related definitions */
+#define GET_OFFSET_IN_PAGE(x) ((x) & (SYS_PAGE_SIZE - 1))
+#define GET_PAGE_NR(x) ((x) & SYS_PAGE_MASK)
+#define MAX_ALLOC_PAGES 3
+
+/* Opcode values (subset we need) */
+#define OPCODE_OP_IMM 0x13 /* addi, slli, ... */
+#define OPCODE_STORE 0x23 /* stores: sb, sh, sw, sd */
+#define OPCODE_OP 0x33 /* register-register ops (not used for these checks) */
+
+/* Registers */
+#define REG_PC 0
+#define REG_RA 1
+#define REG_SP 2
+
+/* RISC-V CPU register state structure */
+struct pt_regs {
+ unsigned long pc; /* program counter */
+ unsigned long ra;
+ unsigned long sp;
+ unsigned long gp;
+ unsigned long tp;
+ unsigned long t0;
+ unsigned long t1;
+ unsigned long t2;
+ unsigned long s0;
+ unsigned long s1;
+ unsigned long a0;
+ unsigned long a1;
+ unsigned long a2;
+ unsigned long a3;
+ unsigned long a4;
+ unsigned long a5;
+ unsigned long a6;
+ unsigned long a7;
+ unsigned long s2;
+ unsigned long s3;
+ unsigned long s4;
+ unsigned long s5;
+ unsigned long s6;
+ unsigned long s7;
+ unsigned long s8;
+ unsigned long s9;
+ unsigned long s10;
+ unsigned long s11;
+ unsigned long t3;
+ unsigned long t4;
+ unsigned long t5;
+ unsigned long t6;
+ /* Supervisor/Machine CSRs */
+ unsigned long status;
+ unsigned long badaddr;
+ unsigned long cause;
+ /* a0 value before the syscall */
+ unsigned long orig_a0;
+};
+
+#define PT_REGS_PC(X) ((X).pc)
+#define PT_REGS_RA(X) ((X).ra)
+#define PT_REGS_SP(X) ((X).sp)
+
+#endif /* RISCV64_DEFS_H */
\ No newline at end of file
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread* [PATCH 06/10] vmcore_tasks: arch: Add MIPS64 architecture support
2026-06-22 20:54 [PATCH 00/10] vmcore-tasks: lightweight task and backtrace extractor for vmcore files Pnina Feder
` (4 preceding siblings ...)
2026-06-22 20:54 ` [PATCH 05/10] vmcore_tasks: arch: Add RISC-V 64-bit architecture support Pnina Feder
@ 2026-06-22 20:54 ` Pnina Feder
2026-06-22 20:54 ` [PATCH 07/10] vmcore_tasks: Add generic user-space stack unwinder Pnina Feder
` (5 subsequent siblings)
11 siblings, 0 replies; 15+ messages in thread
From: Pnina Feder @ 2026-06-22 20:54 UTC (permalink / raw)
To: kexec; +Cc: horms, Pnina Feder
Add MIPS64 page table translation and instruction helpers:
- mips64_defs.h: MIPS64 instruction format helpers, pt_regs
register structure (32 GPRs + CP0), opcode constants.
- 3-level page table walk (PGD -> PMD -> PTE) with parameters
read from vmcoreinfo at runtime: _PFN_SHIFT, _PFN_MASK,
PMD_SHIFT, PGDIR_SHIFT, PTRS_PER_*, _PAGE_PRESENT, _PAGE_VALID.
Falls back to sensible defaults when entries are missing.
Derives max physical address bits from PT_LOAD segments when
vmcoreinfo lacks _MAX_PHYSMEM_BITS.
- Direct kernel virtual-to-physical translation overriding the
weak defaults in memory.c for MIPS64 fixed segments: XKPHYS
(cached CCA=3), KSEG0, and KSEG1.
- Instruction decode helpers for the generic unwinder:
is_sp_move_ins: detects addiu/daddiu sp,sp,imm
is_ra_save_ins: detects sw/sd ra,offset(sp)
is_end_of_backtrace: detects 0xf825 (move ra,zero) sentinel
- Signal trampoline detection: deterministic matching of the
MIPS rt_sigreturn instruction pair (li v0,__NR_rt_sigreturn;
syscall). Validates syscall numbers for o32, n32, and n64
ABIs. Signal frame layout driven by vmcoreinfo offsets.
- Register formatting for MIPS64 pt_regs in tasks output.
Signed-off-by: Pnina Feder <pnina.feder@mobileye.com>
---
vmcore_tasks/arch/mips64/mips64.c | 648 +++++++++++++++++++++++++
vmcore_tasks/arch/mips64/mips64_defs.h | 78 +++
2 files changed, 726 insertions(+)
create mode 100644 vmcore_tasks/arch/mips64/mips64.c
create mode 100644 vmcore_tasks/arch/mips64/mips64_defs.h
diff --git a/vmcore_tasks/arch/mips64/mips64.c b/vmcore_tasks/arch/mips64/mips64.c
new file mode 100644
index 00000000..09ad5497
--- /dev/null
+++ b/vmcore_tasks/arch/mips64/mips64.c
@@ -0,0 +1,648 @@
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "memory.h"
+#include "vmcore_info.h"
+#include "elf_info.h"
+
+#include "vmcore_tasks_defs.h"
+
+/* ========================================================================
+ * MIPS64 Memory Layout Overrides
+ *
+ * MIPS64 uses fixed address segments instead of configurable PAGE_OFFSET:
+ *
+ * XKPHYS: 0x8000000000000000 - 0xBFFFFFFFFFFFFFFF
+ * paddr = kvaddr & 0x07FFFFFFFFFFFFFF
+ * Cached (CCA=3): 0x9800000000000000
+ *
+ * KSEG0: 0xFFFFFFFF80000000 - 0xFFFFFFFF9FFFFFFF
+ * paddr = kvaddr & 0x1FFFFFFF
+ *
+ * KSEG1: 0xFFFFFFFFA0000000 - 0xFFFFFFFFBFFFFFFF
+ * paddr = kvaddr & 0x1FFFFFFF
+ * ======================================================================== */
+
+#define MIPS64_XKPHYS_CACHED 0x9800000000000000ULL
+#define MIPS64_XKPHYS_BASE 0x8000000000000000ULL
+#define MIPS64_XKPHYS_TOP 0xBFFFFFFFFFFFFFFFULL
+#define MIPS64_XKPHYS_PHYS_MASK 0x07FFFFFFFFFFFFFFULL /* bits [58:0] = physical address */
+
+#define MIPS64_KSEG0_BASE 0xFFFFFFFF80000000ULL
+#define MIPS64_KSEG0_END 0xFFFFFFFF9FFFFFFFULL
+#define MIPS64_KSEG1_BASE 0xFFFFFFFFA0000000ULL
+#define MIPS64_KSEG1_END 0xFFFFFFFFBFFFFFFFULL
+#define MIPS64_KSEG_MASK 0x1FFFFFFFULL
+
+#define MIPS_SYSCALL_MASK 0xFC00003F
+#define MIPS_SYSCALL_INS 0x0000000C
+
+#ifndef MIPS_OP_ORI
+#define MIPS_OP_ORI 0x0d
+#endif
+
+#ifndef MIPS_REG_V0
+#define MIPS_REG_V0 2
+#endif
+
+/* __NR_rt_sigreturn values by ABI (Linux MIPS syscall base + table index). */
+#define MIPS_NR_RT_SIGRETURN_O32 4193U /* 4000 + 193 */
+#define MIPS_NR_RT_SIGRETURN_N64 5211U /* 5000 + 211 */
+#define MIPS_NR_RT_SIGRETURN_N32 6211U /* 6000 + 211 */
+
+/* MIPS64 pagetable walk runtime parameters (from vmcoreinfo, with fallbacks) */
+static uint64_t mips64_page_present;
+static uint64_t mips64_page_valid;
+static uint64_t mips64_pfn_mask;
+static unsigned int mips64_pfn_shift;
+static unsigned int mips64_pmd_shift;
+static unsigned int mips64_pgdir_shift;
+static unsigned int mips64_page_shift;
+static uint64_t mips64_pgd_mask;
+static uint64_t mips64_pmd_mask;
+static uint64_t mips64_pte_mask;
+static uint64_t mips64_level_mask;
+static uint64_t mips64_phys_mask;
+
+static inline uint64_t mips64_entry_phys_base(uint64_t entry)
+{
+ uint64_t base;
+
+ if (mips64_pfn_mask) {
+ uint64_t pfn = (entry & mips64_pfn_mask) >> mips64_pfn_shift;
+ base = (pfn << mips64_page_shift);
+ } else {
+ base = entry & SYS_PAGE_MASK;
+ }
+
+ return base & mips64_phys_mask;
+}
+
+static inline bool mips64_pte_is_present_valid(uint64_t pte)
+{
+ if (!(pte & mips64_page_present))
+ return false;
+
+ if (mips64_page_valid && !(pte & mips64_page_valid))
+ return false;
+
+ return true;
+}
+
+static inline uint64_t mips64_pgd_index(uint64_t vaddr)
+{
+ return (vaddr >> mips64_pgdir_shift) & mips64_pgd_mask;
+}
+
+static inline uint64_t mips64_pmd_index(uint64_t vaddr)
+{
+ return (vaddr >> mips64_pmd_shift) & mips64_pmd_mask;
+}
+
+static inline uint64_t mips64_pte_index(uint64_t vaddr)
+{
+ return (vaddr >> mips64_page_shift) & mips64_pte_mask;
+}
+
+static bool mips64_table_entry_to_paddr(uint64_t entry,
+ const char *level,
+ uint64_t *table_paddr)
+{
+ uint64_t aligned = (entry & SYS_PAGE_MASK) & mips64_phys_mask;
+
+ if (!aligned) {
+ pr_trace("mips64_vtop: %s entry 0x%llx aligns to 0\n",
+ level, (unsigned long long)entry);
+ return false;
+ }
+
+ if (is_kvaddr(aligned)) {
+ if (!vtop_direct(aligned, table_paddr)) {
+ pr_trace("mips64_vtop: %s aligned kvaddr 0x%llx failed direct vtop\n",
+ level, (unsigned long long)aligned);
+ return false;
+ }
+ pr_trace("mips64_vtop: %s entry 0x%llx (kvaddr 0x%llx) -> next table paddr 0x%llx\n",
+ level, (unsigned long long)entry,
+ (unsigned long long)aligned,
+ (unsigned long long)*table_paddr);
+ return true;
+ }
+
+ *table_paddr = aligned;
+ pr_trace("mips64_vtop: %s entry 0x%llx -> next table paddr 0x%llx\n",
+ level, (unsigned long long)entry,
+ (unsigned long long)*table_paddr);
+ return true;
+}
+
+void mips64_pagetable_init(void)
+{
+ uint64_t max_phys_bits;
+ uint64_t ptrs_per;
+ unsigned long long phys_start;
+ unsigned long long phys_end;
+ unsigned long long virt_start;
+ unsigned long long virt_end;
+ unsigned long long max_phys_seen = 0;
+ int idx;
+
+ mips64_page_shift = get_page_shift();
+
+ if (NUMBER_EXISTS("_PAGE_PRESENT"))
+ mips64_page_present = NUMBER("_PAGE_PRESENT");
+ else
+ mips64_page_present = MIPS64_PTE_PRESENT;
+
+ if (NUMBER_EXISTS("_PAGE_VALID"))
+ mips64_page_valid = NUMBER("_PAGE_VALID");
+ else
+ mips64_page_valid = 0;
+
+ if (NUMBER_EXISTS("_PFN_SHIFT"))
+ mips64_pfn_shift = (unsigned int)NUMBER("_PFN_SHIFT");
+ else
+ mips64_pfn_shift = 15;
+
+ if (NUMBER_EXISTS("_PFN_MASK"))
+ mips64_pfn_mask = NUMBER("_PFN_MASK");
+ else
+ mips64_pfn_mask = 0;
+
+ if (NUMBER_EXISTS("PMD_SHIFT"))
+ mips64_pmd_shift = (unsigned int)NUMBER("PMD_SHIFT");
+ else
+ mips64_pmd_shift = (mips64_page_shift * 2) - 3;
+
+ if (NUMBER_EXISTS("PGDIR_SHIFT"))
+ mips64_pgdir_shift = (unsigned int)NUMBER("PGDIR_SHIFT");
+ else
+ mips64_pgdir_shift = mips64_pmd_shift + mips64_page_shift - 3;
+
+ if (mips64_page_shift < 4)
+ mips64_level_mask = 0;
+ else
+ mips64_level_mask = (1ULL << (mips64_page_shift - 3)) - 1;
+
+ mips64_pgd_mask = mips64_level_mask;
+ mips64_pmd_mask = mips64_level_mask;
+ mips64_pte_mask = mips64_level_mask;
+
+ if (NUMBER_EXISTS("PTRS_PER_PGD")) {
+ ptrs_per = NUMBER("PTRS_PER_PGD");
+ if (ptrs_per)
+ mips64_pgd_mask = ptrs_per - 1;
+ }
+ if (NUMBER_EXISTS("PTRS_PER_PMD")) {
+ ptrs_per = NUMBER("PTRS_PER_PMD");
+ if (ptrs_per)
+ mips64_pmd_mask = ptrs_per - 1;
+ }
+ if (NUMBER_EXISTS("PTRS_PER_PTE")) {
+ ptrs_per = NUMBER("PTRS_PER_PTE");
+ if (ptrs_per)
+ mips64_pte_mask = ptrs_per - 1;
+ }
+
+ if (NUMBER_EXISTS("_MAX_PHYSMEM_BITS"))
+ max_phys_bits = NUMBER("_MAX_PHYSMEM_BITS");
+ else if (NUMBER_EXISTS("MAX_PHYSMEM_BITS"))
+ max_phys_bits = NUMBER("MAX_PHYSMEM_BITS");
+ else {
+ max_phys_bits = 0;
+ for (idx = 0; get_pt_load(idx, &phys_start, &phys_end,
+ &virt_start, &virt_end); idx++) {
+ (void)virt_start;
+ (void)virt_end;
+ if (phys_end > 0 && (phys_end - 1) > max_phys_seen)
+ max_phys_seen = phys_end - 1;
+ }
+
+ if (max_phys_seen) {
+ max_phys_bits = 64U - (uint64_t)__builtin_clzll(max_phys_seen);
+ pr_trace("mips64 pagetable: derived max phys bits=%llu from PT_LOAD max paddr=0x%llx\n",
+ (unsigned long long)max_phys_bits,
+ (unsigned long long)max_phys_seen);
+ } else {
+ max_phys_bits = 59;
+ }
+ }
+
+ if (max_phys_bits >= 64)
+ mips64_phys_mask = ~0ULL;
+ else
+ mips64_phys_mask = (1ULL << max_phys_bits) - 1;
+
+ pr_trace("mips64 pagetable: page_shift=%u pfn_shift=%u pfn_mask=0x%llx "
+ "pmd_shift=%u pgdir_shift=%u present=0x%llx valid=0x%llx "
+ "masks[pgd=0x%llx pmd=0x%llx pte=0x%llx] phys_mask=0x%llx\n",
+ mips64_page_shift, mips64_pfn_shift,
+ (unsigned long long)mips64_pfn_mask,
+ mips64_pmd_shift, mips64_pgdir_shift,
+ (unsigned long long)mips64_page_present,
+ (unsigned long long)mips64_page_valid,
+ (unsigned long long)mips64_pgd_mask,
+ (unsigned long long)mips64_pmd_mask,
+ (unsigned long long)mips64_pte_mask,
+ (unsigned long long)mips64_phys_mask);
+}
+
+bool mips64_vtop(uint64_t pgd_paddr, uint64_t vaddr, uint64_t *paddr)
+{
+ uint64_t pgd_idx;
+ uint64_t pmd_idx;
+ uint64_t pte_idx;
+ uint64_t pgd_entry;
+ uint64_t pmd_entry;
+ uint64_t pte_entry;
+ uint64_t pmd_paddr;
+ uint64_t pte_paddr;
+ uint64_t raw_paddr;
+
+ pgd_idx = mips64_pgd_index(vaddr);
+ pmd_idx = mips64_pmd_index(vaddr);
+ pte_idx = mips64_pte_index(vaddr);
+
+ pr_trace("mips64_vtop: walk vaddr=0x%llx pgd_paddr=0x%llx idx[pgd=%llu pmd=%llu pte=%llu]\n",
+ (unsigned long long)vaddr,
+ (unsigned long long)pgd_paddr,
+ (unsigned long long)pgd_idx,
+ (unsigned long long)pmd_idx,
+ (unsigned long long)pte_idx);
+
+ uint64_t pgd_entry_paddr = pgd_paddr + (pgd_idx * sizeof(uint64_t));
+ if (readmem(pgd_entry_paddr, &pgd_entry, sizeof(pgd_entry), "pgd_entry", PADDR) < 0) {
+ pr_verbose("mips64_vtop: failed read PGD entry at paddr=0x%llx\n",
+ (unsigned long long)pgd_entry_paddr);
+ return false;
+ }
+
+ pr_trace("mips64_vtop: PGD[%llu] @0x%llx = 0x%llx\n",
+ (unsigned long long)pgd_idx,
+ (unsigned long long)pgd_entry_paddr,
+ (unsigned long long)pgd_entry);
+
+ if (!pgd_entry) {
+ pr_verbose("mips64_vtop: empty PGD entry\n");
+ return false;
+ }
+
+ if (!mips64_table_entry_to_paddr(pgd_entry, "PGD", &pmd_paddr))
+ return false;
+
+ uint64_t pmd_entry_paddr = pmd_paddr + (pmd_idx * sizeof(uint64_t));
+ if (readmem(pmd_entry_paddr, &pmd_entry, sizeof(pmd_entry), "pmd_entry", PADDR) < 0) {
+ pr_verbose("mips64_vtop: failed read PMD entry at paddr=0x%llx\n",
+ (unsigned long long)pmd_entry_paddr);
+ return false;
+ }
+
+ pr_trace("mips64_vtop: PMD[%llu] @0x%llx = 0x%llx\n",
+ (unsigned long long)pmd_idx,
+ (unsigned long long)pmd_entry_paddr,
+ (unsigned long long)pmd_entry);
+
+ if (!pmd_entry) {
+ pr_verbose("mips64_vtop: empty PMD entry\n");
+ return false;
+ }
+
+ if (!mips64_table_entry_to_paddr(pmd_entry, "PMD", &pte_paddr))
+ return false;
+
+ uint64_t pte_entry_paddr = pte_paddr + (pte_idx * sizeof(uint64_t));
+ if (readmem(pte_entry_paddr, &pte_entry, sizeof(pte_entry), "pte_entry", PADDR) < 0) {
+ pr_verbose("mips64_vtop: failed read PTE entry at paddr=0x%llx\n",
+ (unsigned long long)pte_entry_paddr);
+ return false;
+ }
+
+ pr_trace("mips64_vtop: PTE[%llu] @0x%llx = 0x%llx\n",
+ (unsigned long long)pte_idx,
+ (unsigned long long)pte_entry_paddr,
+ (unsigned long long)pte_entry);
+
+ if (!mips64_pte_is_present_valid(pte_entry)) {
+ pr_verbose("mips64_vtop: PTE not valid/present (pte=0x%llx present=0x%llx valid=0x%llx)\n",
+ (unsigned long long)pte_entry,
+ (unsigned long long)mips64_page_present,
+ (unsigned long long)mips64_page_valid);
+ return false;
+ }
+
+ if (mips64_pfn_mask) {
+ uint64_t pfn = (pte_entry & mips64_pfn_mask) >> mips64_pfn_shift;
+ raw_paddr = (pfn << mips64_page_shift) | (vaddr & (SYS_PAGE_SIZE - 1));
+ } else {
+ raw_paddr = ((pte_entry >> mips64_pfn_shift) << mips64_page_shift) |
+ (vaddr & (SYS_PAGE_SIZE - 1));
+ }
+
+ *paddr = raw_paddr & mips64_phys_mask;
+
+ pr_trace("mips64_vtop: success vaddr=0x%llx -> raw_paddr=0x%llx masked_paddr=0x%llx (page_off=0x%llx)\n",
+ (unsigned long long)vaddr,
+ (unsigned long long)raw_paddr,
+ (unsigned long long)*paddr,
+ (unsigned long long)(vaddr & (SYS_PAGE_SIZE - 1)));
+
+ return true;
+}
+
+/**
+ * get_page_offset - MIPS64 uses XKPHYS cached segment as PAGE_OFFSET
+ */
+uint64_t get_page_offset(void)
+{
+ return MIPS64_XKPHYS_CACHED;
+}
+
+/**
+ * is_kvaddr - check if address is in a MIPS64 kernel segment
+ */
+bool is_kvaddr(uint64_t vaddr)
+{
+ /* XKPHYS: 0x8000000000000000 - 0xBFFFFFFFFFFFFFFF (top 2 bits = 10) */
+ if (vaddr >= MIPS64_XKPHYS_BASE && vaddr <= MIPS64_XKPHYS_TOP)
+ return true;
+
+ /* KSEG0 */
+ if (vaddr >= MIPS64_KSEG0_BASE && vaddr <= MIPS64_KSEG0_END)
+ return true;
+
+ /* KSEG1 */
+ if (vaddr >= MIPS64_KSEG1_BASE && vaddr <= MIPS64_KSEG1_END)
+ return true;
+
+ return false;
+}
+
+/**
+ * vtop_direct - MIPS64 kernel virtual to physical translation
+ */
+bool vtop_direct(uint64_t kvaddr, uint64_t *paddr)
+{
+ if (kvaddr >= MIPS64_XKPHYS_BASE && kvaddr <= MIPS64_XKPHYS_TOP) {
+ *paddr = kvaddr & MIPS64_XKPHYS_PHYS_MASK;
+ return true;
+ }
+
+ if (kvaddr >= MIPS64_KSEG0_BASE && kvaddr <= MIPS64_KSEG0_END) {
+ *paddr = kvaddr & MIPS64_KSEG_MASK;
+ return true;
+ }
+
+ if (kvaddr >= MIPS64_KSEG1_BASE && kvaddr <= MIPS64_KSEG1_END) {
+ *paddr = kvaddr & MIPS64_KSEG_MASK;
+ return true;
+ }
+
+ return false;
+}
+
+
+
+/*
+ * MIPS64 instruction helpers for the generic unwinder.
+ */
+
+/**
+ * is_sp_move_ins_mips64 - detect stack pointer adjustment
+ *
+ * Matches:
+ * addiu sp, sp, imm (opcode 0x09, rs=29, rt=29)
+ * daddiu sp, sp, imm (opcode 0x19, rs=29, rt=29)
+ *
+ * Also detects (but ignores) dynamic stack allocation:
+ * dsubu sp, sp, v0 (R-format, func=0x2f, rs=29, rd=29, rt=2)
+ */
+static bool is_sp_move_ins_mips64(uint32_t insn, int *stack_adj)
+{
+ unsigned int opcode = mips_opcode(insn);
+ unsigned int rs = mips_rs(insn);
+ unsigned int rt = mips_rt(insn);
+
+ /* addiu sp, sp, imm or daddiu sp, sp, imm */
+ if (rs == MIPS_REG_SP && rt == MIPS_REG_SP) {
+ if (opcode == MIPS_OP_ADDIU || opcode == MIPS_OP_DADDIU) {
+ *stack_adj = (int)mips_simmediate(insn);
+ return true;
+ }
+ }
+
+ /* dsubu sp, sp, v0 — dynamic stack allocation, log but don't report */
+ if (opcode == MIPS_OP_SPECIAL &&
+ mips_func(insn) == MIPS_FUNC_DSUBU &&
+ rs == MIPS_REG_SP &&
+ mips_rd(insn) == MIPS_REG_SP &&
+ rt == 2) {
+ pr_trace("mips64: dynamic stack allocation (dsubu sp,sp,v0) at insn=0x%08x\n", insn);
+ }
+
+ return false;
+}
+
+/**
+ * is_ra_save_ins_mips64 - detect RA save to stack
+ *
+ * Matches:
+ * sw ra, offset(sp) (opcode 0x2b, rs=29, rt=31)
+ * sd ra, offset(sp) (opcode 0x3f, rs=29, rt=31)
+ */
+static bool is_ra_save_ins_mips64(uint32_t insn, unsigned long *ra_offset)
+{
+ unsigned int opcode = mips_opcode(insn);
+ unsigned int rs = mips_rs(insn);
+ unsigned int rt = mips_rt(insn);
+
+ if ((opcode == MIPS_OP_SW || opcode == MIPS_OP_SD) &&
+ rs == MIPS_REG_SP && rt == MIPS_REG_RA) {
+ int16_t imm = mips_simmediate(insn);
+
+ if (imm < 0)
+ return false;
+
+ *ra_offset = (unsigned long)imm;
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * is_end_of_backtrace_mips64 - detect end-of-backtrace marker
+ *
+ * The kernel uses 0xf825 (move ra, zero / or ra, zero, zero)
+ * as a sentinel indicating no further frames.
+ */
+static bool is_end_of_backtrace_mips64(uint32_t insn)
+{
+ if (insn == MIPS_END_OF_BT_MARKER) {
+ pr_trace("mips64: found end of backtrace marker\n");
+ return true;
+ }
+ return false;
+}
+
+/**
+ * read_task_registers - MIPS64 override
+ */
+bool read_task_registers(uint64_t stack_ptr, struct pt_regs *regs)
+{
+ uint64_t regs_addr;
+ size_t pt_regs_size = SIZE("pt_regs");
+ size_t thread_size = NUMBER("THREAD_SIZE");
+
+ if (!stack_ptr) {
+ fprintf(stderr, "Invalid stack pointer\n");
+ return false;
+ }
+
+ regs_addr = stack_ptr + thread_size - 32 - pt_regs_size;
+
+ pr_verbose("mips64 read_task_registers: stack=0x%llx thread_size=%zu "
+ "pt_regs_size=%zu regs_addr=0x%llx\n",
+ (unsigned long long)stack_ptr, thread_size,
+ pt_regs_size, (unsigned long long)regs_addr);
+
+ if (readmem(regs_addr, regs, pt_regs_size, "read pt_regs", KVADDR) < 0) {
+ fprintf(stderr, "Failed to read pt_regs from 0x%016llx\n",
+ (unsigned long long)regs_addr);
+ return false;
+ }
+
+ return true;
+}
+
+static inline bool mips64_is_rt_sigreturn_nr(uint32_t nr)
+{
+ return nr == MIPS_NR_RT_SIGRETURN_O32 ||
+ nr == MIPS_NR_RT_SIGRETURN_N64 ||
+ nr == MIPS_NR_RT_SIGRETURN_N32;
+}
+
+/*
+ * Match "li v0, __NR_rt_sigreturn" forms used before syscall:
+ * addiu v0, zero, imm
+ * daddiu v0, zero, imm
+ * ori v0, zero, imm
+ */
+static inline bool mips64_extract_li_v0_imm(uint32_t insn, uint32_t *imm_out)
+{
+ unsigned int opcode = mips_opcode(insn);
+ unsigned int rs = mips_rs(insn);
+ unsigned int rt = mips_rt(insn);
+
+ if (!imm_out)
+ return false;
+
+ if (rs != 0 || rt != MIPS_REG_V0)
+ return false;
+
+ if (opcode == MIPS_OP_ADDIU ||
+ opcode == MIPS_OP_DADDIU ||
+ opcode == MIPS_OP_ORI) {
+ *imm_out = (uint32_t)(insn & 0xffffU);
+ return true;
+ }
+
+ return false;
+}
+
+static bool mips64_is_rt_sigreturn_trampoline(uint64_t li_addr, uint64_t syscall_addr)
+{
+ uint32_t li_insn = 0;
+ uint32_t sc_insn = 0;
+ uint32_t nr = 0;
+
+ if (readmem(li_addr, &li_insn, sizeof(li_insn), "mips sigtramp li", UVADDR) < 0)
+ return false;
+
+ if (readmem(syscall_addr, &sc_insn, sizeof(sc_insn),
+ "mips sigtramp syscall", UVADDR) < 0)
+ return false;
+
+ if ((sc_insn & MIPS_SYSCALL_MASK) != MIPS_SYSCALL_INS)
+ return false;
+
+ if (!mips64_extract_li_v0_imm(li_insn, &nr))
+ return false;
+
+ if (!mips64_is_rt_sigreturn_nr(nr))
+ return false;
+
+ pr_trace("mips64: matched rt_sigreturn trampoline li@0x%llx sc@0x%llx nr=%u\n",
+ (unsigned long long)li_addr,
+ (unsigned long long)syscall_addr,
+ nr);
+
+ return true;
+}
+
+static bool mips64_likely_signal_pc(uint64_t pc)
+{
+ /*
+ * Deterministic sigtramp detection:
+ * [li v0, __NR_rt_sigreturn] ; [syscall]
+ * Accept if PC is on syscall or on the preceding li.
+ */
+ if (pc >= 4 && mips64_is_rt_sigreturn_trampoline(pc - 4, pc))
+ return true;
+
+ if (pc <= (UINT64_MAX - 4) &&
+ mips64_is_rt_sigreturn_trampoline(pc, pc + 4))
+ return true;
+
+ return false;
+}
+
+static const struct signal_unwind_layout mips64_sig_layout = {
+ .rt_sigframe_uc_key = "rt_sigframe.rs_uc",
+ .ucontext_mcontext_key = "ucontext.uc_mcontext",
+ .sigcontext_regs_key = "sigcontext.sc_regs",
+ .sigcontext_pc_key = "sigcontext.sc_pc",
+ .reg_width = 8,
+ .sp_reg_index = 29,
+ .ra_reg_index = 31,
+ .pc_reg_index = -1,
+ .pc_reg_alt_index = -1,
+};
+
+/**
+ * mips64_init - initialize arch_deps for MIPS64
+ */
+void mips64_init(void)
+{
+ mips64_pagetable_init();
+ arch.unwind_ops.insn_size = 4;
+ arch.unwind_ops.ra_width = 8;
+ arch.unwind_ops.is_sp_move_ins = is_sp_move_ins_mips64;
+ arch.unwind_ops.is_ra_save_ins = is_ra_save_ins_mips64;
+ arch.unwind_ops.is_end_of_backtrace = is_end_of_backtrace_mips64;
+ arch.unwind_ops.post_unwind_fixup = NULL;
+ arch.unwind_ops.likely_signal_pc = mips64_likely_signal_pc;
+ arch.unwind_ops.signal_layout = &mips64_sig_layout;
+
+ arch.vtop = mips64_vtop;
+}
+
+void write_user_registers_formatted(FILE *fp, struct pt_regs *regs)
+{
+ fprintf(fp, "user registers:\n");
+ for (int i = 0; i < 32; i += 4) {
+ fprintf(fp, "R%02d:0x%016llx R%02d:0x%016llx R%02d:0x%016llx R%02d:0x%016llx\n",
+ i, (unsigned long long)regs->regs[i],
+ i + 1, (unsigned long long)regs->regs[i + 1],
+ i + 2, (unsigned long long)regs->regs[i + 2],
+ i + 3, (unsigned long long)regs->regs[i + 3]);
+ }
+ fprintf(fp, "cp0_status: 0x%016llx hi: 0x%016llx lo: 0x%016llx\n",
+ (unsigned long long)regs->cp0_status,
+ (unsigned long long)regs->hi, (unsigned long long)regs->lo);
+ fprintf(fp, "cp0_badvaddr:0x%016llx cp0_cause:0x%016llx cp0_epc:0x%016llx\n",
+ (unsigned long long)regs->cp0_badvaddr,
+ (unsigned long long)regs->cp0_cause,
+ (unsigned long long)regs->cp0_epc);
+}
\ No newline at end of file
diff --git a/vmcore_tasks/arch/mips64/mips64_defs.h b/vmcore_tasks/arch/mips64/mips64_defs.h
new file mode 100644
index 00000000..97bcdac7
--- /dev/null
+++ b/vmcore_tasks/arch/mips64/mips64_defs.h
@@ -0,0 +1,78 @@
+#ifndef MIPS64_DEFS_H
+#define MIPS64_DEFS_H
+
+#include <stdint.h>
+
+#include "vmcore_info.h"
+
+/* Page table definitions */
+#define SYS_PAGE_SIZE (get_page_size())
+#define SYS_PAGE_SHIFT (get_page_shift())
+#define SYS_PAGE_MASK (~(SYS_PAGE_SIZE - 1))
+
+/*
+ * MIPS64 PTE bit layout (from vmcoreinfo):
+ *
+ * _PFN_SHIFT = 15 (bits 0-14 are flags, PFN starts at bit 15)
+ * PMD_SHIFT = 25
+ * PGDIR_SHIFT = 36
+ * PGTABLE_LEVELS = 3 (PGD → PMD → PTE)
+ *
+ * Physical address from a leaf PTE:
+ * paddr = (pte >> _PFN_SHIFT) << PAGE_SHIFT
+ *
+ * PGD and PMD entries store physical addresses of next-level tables
+ * directly — they do NOT use _PFN_SHIFT encoding.
+ */
+
+/* PTE flag bits (bits below _PFN_SHIFT) */
+#define MIPS64_PTE_PRESENT (1UL << 0)
+
+/* MIPS instruction format helpers */
+
+/* I-format: opcode(6) | rs(5) | rt(5) | immediate(16) */
+static inline unsigned int mips_opcode(uint32_t insn) { return (insn >> 26) & 0x3f; }
+static inline unsigned int mips_rs(uint32_t insn) { return (insn >> 21) & 0x1f; }
+static inline unsigned int mips_rt(uint32_t insn) { return (insn >> 16) & 0x1f; }
+static inline int16_t mips_simmediate(uint32_t insn) { return (int16_t)(insn & 0xffff); }
+
+/* R-format: opcode(6) | rs(5) | rt(5) | rd(5) | sa(5) | func(6) */
+static inline unsigned int mips_rd(uint32_t insn) { return (insn >> 11) & 0x1f; }
+static inline unsigned int mips_func(uint32_t insn) { return insn & 0x3f; }
+
+/* MIPS opcodes */
+#define MIPS_OP_ADDIU 0x09
+#define MIPS_OP_DADDIU 0x19
+#define MIPS_OP_SW 0x2b
+#define MIPS_OP_SD 0x3f
+#define MIPS_OP_SPECIAL 0x00
+
+/* MIPS R-format function codes */
+#define MIPS_FUNC_DSUBU 0x2f
+
+/* MIPS register numbers */
+#define MIPS_REG_SP 29
+#define MIPS_REG_RA 31
+
+struct pt_regs {
+ /* Saved main processor registers. */
+ unsigned long regs[32];
+
+ /* Saved special registers. */
+ unsigned long cp0_status;
+ unsigned long hi;
+ unsigned long lo;
+ unsigned long cp0_badvaddr;
+ unsigned long cp0_cause;
+ unsigned long cp0_epc;
+ unsigned long __last[0];
+};
+
+#define PT_REGS_PC(X) ((X).cp0_epc)
+#define PT_REGS_RA(X) ((X).regs[MIPS_REG_RA])
+#define PT_REGS_SP(X) ((X).regs[MIPS_REG_SP])
+
+/* End of backtrace marker */
+#define MIPS_END_OF_BT_MARKER 0xf825
+
+#endif /* MIPS64_DEFS_H */
\ No newline at end of file
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread* [PATCH 07/10] vmcore_tasks: Add generic user-space stack unwinder
2026-06-22 20:54 [PATCH 00/10] vmcore-tasks: lightweight task and backtrace extractor for vmcore files Pnina Feder
` (5 preceding siblings ...)
2026-06-22 20:54 ` [PATCH 06/10] vmcore_tasks: arch: Add MIPS64 " Pnina Feder
@ 2026-06-22 20:54 ` Pnina Feder
2026-06-22 20:54 ` [PATCH 08/10] vmcore_tasks: Add VMA utilities and task enumeration Pnina Feder
` (4 subsequent siblings)
11 siblings, 0 replies; 15+ messages in thread
From: Pnina Feder @ 2026-06-22 20:54 UTC (permalink / raw)
To: kexec; +Cc: horms, Pnina Feder
Add architecture-independent user-space frame unwinder that uses
arch-provided instruction callbacks to walk the call stack.
The unwinder tries recovery in order:
1. Signal frame: if arch reports PC looks like a signal trampoline,
walks rt_sigframe -> ucontext -> sigcontext using vmcoreinfo
offsets to recover saved PC, SP, and RA.
2. Prologue scan: scans backward from PC looking for stack frame
setup (SP adjustment) and RA save instructions. Uses a buffered
read strategy with page-boundary handling for efficiency.
Architecture-specific behavior is provided via arch.unwind_ops:
- is_sp_move_ins(): detect stack pointer adjustment
- is_ra_save_ins(): detect return address save to stack
- is_end_of_backtrace(): detect end-of-trace markers (optional)
- post_unwind_fixup(): arch-specific post-processing (optional)
- likely_signal_pc(): hint whether PC is in a signal trampoline
- signal_layout: describes signal frame structure offsets
This separation allows adding new architectures by implementing
only the instruction decode callbacks, without duplicating the
unwinding logic.
Signed-off-by: Pnina Feder <pnina.feder@mobileye.com>
---
vmcore_tasks/unwind.c | 267 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 267 insertions(+)
create mode 100644 vmcore_tasks/unwind.c
diff --git a/vmcore_tasks/unwind.c b/vmcore_tasks/unwind.c
new file mode 100644
index 00000000..99d23322
--- /dev/null
+++ b/vmcore_tasks/unwind.c
@@ -0,0 +1,267 @@
+#include <stdio.h>
+#include <string.h>
+#include "memory.h"
+#include "vmcore_tasks_defs.h"
+
+/* Return codes for unwind_one_frame */
+#define UNWIND_OK 0
+#define UNWIND_LEAF -1 /* No prologue found — possible leaf function */
+#define UNWIND_FAIL -2 /* Hard failure (bad memory, etc.) */
+
+static bool sigframe_candidate_ok(uint64_t old_pc, uint64_t old_sp, uint64_t old_ra,
+ uint64_t new_pc, uint64_t new_sp, uint64_t new_ra)
+{
+ if (!new_pc || !new_sp)
+ return false;
+ if (new_pc == old_pc && new_ra == old_ra)
+ return false;
+ /* Signal return should restore caller SP above current sigframe SP. */
+ if (new_sp <= old_sp)
+ return false;
+ /* Reject absurd jumps in SP (false positives). */
+ if ((new_sp - old_sp) > (1ULL << 20))
+ return false;
+ return true;
+}
+
+static int unwind_signal_frame(struct stackframe *frame)
+{
+ const struct signal_unwind_layout *layout = arch.unwind_ops.signal_layout;
+ uint64_t old_pc, old_sp, old_ra;
+ uint64_t uc_addr, mctx_addr, regs_addr;
+ uint64_t new_pc = 0, new_sp = 0, new_ra = 0;
+
+ if (!frame || !layout)
+ return -1;
+
+ /* Required vmcoreinfo keys to locate rt_sigframe -> ucontext -> sigcontext regs. */
+ if (!layout->rt_sigframe_uc_key ||
+ !layout->ucontext_mcontext_key ||
+ !layout->sigcontext_regs_key)
+ return -1;
+
+ if (!OFFSET_EXISTS(layout->rt_sigframe_uc_key) ||
+ !OFFSET_EXISTS(layout->ucontext_mcontext_key) ||
+ !OFFSET_EXISTS(layout->sigcontext_regs_key))
+ return -1;
+
+ /* Walk the layout chain from current SP to saved register block. */
+ uc_addr = frame->sp + OFFSET(layout->rt_sigframe_uc_key);
+ mctx_addr = uc_addr + OFFSET(layout->ucontext_mcontext_key);
+ regs_addr = mctx_addr + OFFSET(layout->sigcontext_regs_key);
+
+ /* Always recover RA and SP from arch provided indices */
+ if (readmem(regs_addr + ((uint64_t)layout->ra_reg_index * layout->reg_width),
+ &new_ra, layout->reg_width, "sigcontext ra", UVADDR) < 0)
+ return -1;
+ if (readmem(regs_addr + ((uint64_t)layout->sp_reg_index * layout->reg_width),
+ &new_sp, layout->reg_width, "sigcontext sp", UVADDR) < 0)
+ return -1;
+
+ old_pc = frame->pc;
+ old_sp = frame->sp;
+ old_ra = frame->ra;
+
+ /* PC can be either a dedicated field or a slot in the regs array. */
+ if (layout->sigcontext_pc_key && OFFSET_EXISTS(layout->sigcontext_pc_key)) {
+ uint64_t pc_addr = mctx_addr + OFFSET(layout->sigcontext_pc_key);
+ if (readmem(pc_addr, &new_pc, layout->reg_width,
+ "sigcontext pc", UVADDR) < 0)
+ return -1;
+ } else {
+ if (readmem(regs_addr + ((uint64_t)layout->pc_reg_index * layout->reg_width),
+ &new_pc, layout->reg_width, "sigcontext pc", UVADDR) < 0)
+ return -1;
+ /* Try alternate PC slot for kernels/ABIs with different ordering. */
+ if (!sigframe_candidate_ok(old_pc, old_sp, old_ra, new_pc, new_sp, new_ra) &&
+ layout->pc_reg_alt_index >= 0) {
+ if (readmem(regs_addr + ((uint64_t)layout->pc_reg_alt_index * layout->reg_width),
+ &new_pc, layout->reg_width, "sigcontext pc(alt)", UVADDR) < 0)
+ return -1;
+ }
+ }
+
+ /* Final sanity gate before committing recovered frame. */
+ if (!sigframe_candidate_ok(old_pc, old_sp, old_ra, new_pc, new_sp, new_ra))
+ return -1;
+
+ pr_verbose("sigframe-candidate: old[pc=0x%016llx sp=0x%016llx ra=0x%016llx] new[pc=0x%016llx sp=0x%016llx ra=0x%016llx] chain[uc=0x%016llx mctx=0x%016llx regs=0x%016llx] src=%s idx[sp=%d ra=%d pc=%d alt=%d] w=%u\n",
+ (unsigned long long)old_pc, (unsigned long long)old_sp, (unsigned long long)old_ra,
+ (unsigned long long)new_pc, (unsigned long long)new_sp, (unsigned long long)new_ra,
+ (unsigned long long)uc_addr, (unsigned long long)mctx_addr, (unsigned long long)regs_addr,
+ (layout->sigcontext_pc_key && OFFSET_EXISTS(layout->sigcontext_pc_key)) ? "sigcontext_pc_key" : "regs[]",
+ layout->sp_reg_index, layout->ra_reg_index, layout->pc_reg_index, layout->pc_reg_alt_index,
+ layout->reg_width);
+
+ frame->pc = (unsigned long)new_pc;
+ frame->sp = (unsigned long)new_sp;
+ frame->ra = (unsigned long)new_ra;
+ return 0;
+}
+
+/*
+ * Try to recover the previous frame by scanning backward for prologue
+ * instructions (SP adjustment + RA save).
+ *
+ * Returns: UNWIND_OK on success, UNWIND_LEAF if no prologue found,
+ * UNWIND_FAIL on hard error.
+ */
+static int unwind_prologue_scan(struct stackframe *frame, unsigned int max_check)
+{
+ off_t ra_offset = 0;
+ size_t stack_size = 0;
+ unsigned long scan_addr, scan_end;
+ uint32_t insn;
+ int sp_adjust = 0;
+ unsigned long ra_slot = 0;
+
+ uint8_t code_buf[CODE_BUF_SIZE];
+ unsigned long buf_base = 0;
+ ssize_t buf_len = 0;
+
+ scan_addr = frame->pc - arch.unwind_ops.insn_size;
+
+ if (max_check >= frame->pc)
+ scan_end = 0;
+ else
+ scan_end = frame->pc - max_check;
+
+ while (scan_addr >= scan_end) {
+ /* Refill code buffer when needed */
+ if (buf_len == 0 || scan_addr < buf_base) {
+ if (scan_addr + arch.unwind_ops.insn_size < CODE_BUF_SIZE)
+ buf_base = 0;
+ else
+ buf_base = scan_addr + arch.unwind_ops.insn_size - CODE_BUF_SIZE;
+
+ if (buf_base < scan_end)
+ buf_base = scan_end & ~((unsigned long)arch.unwind_ops.insn_size - 1);
+
+ size_t to_read = scan_addr + arch.unwind_ops.insn_size - buf_base;
+ buf_len = readmem(buf_base, code_buf, to_read,
+ "read code chunk", UVADDR);
+ if (buf_len <= 0) {
+ unsigned long next_page = (buf_base & SYS_PAGE_MASK) + SYS_PAGE_SIZE;
+ pr_trace("Failed to read at 0x%lx, advancing scan_end to 0x%lx\n",
+ buf_base, next_page);
+ scan_end = next_page;
+ buf_len = 0;
+ continue;
+ }
+ }
+
+ size_t offset = scan_addr - buf_base;
+ if (offset + arch.unwind_ops.insn_size > (size_t)buf_len) {
+ pr_verbose("Offset out of buffer range\n");
+ return UNWIND_FAIL;
+ }
+ memcpy(&insn, code_buf + offset, arch.unwind_ops.insn_size);
+
+ if (arch.unwind_ops.is_sp_move_ins(insn, &sp_adjust)) {
+ if (sp_adjust < 0) {
+ stack_size = (size_t)(-sp_adjust);
+ pr_trace("sp_move: pc=0x%lx insn=0x%08x adjust=%d\n",
+ scan_addr, insn, sp_adjust);
+ break;
+ }
+ } else if (arch.unwind_ops.is_ra_save_ins(insn, &ra_slot)) {
+ pr_trace("ra_save: pc=0x%lx insn=0x%08x slot=%lu\n",
+ scan_addr, insn, ra_slot);
+ ra_offset = (off_t)ra_slot;
+ }
+
+ if (scan_addr < arch.unwind_ops.insn_size)
+ break;
+ scan_addr -= arch.unwind_ops.insn_size;
+ }
+
+ if (!stack_size)
+ return UNWIND_LEAF;
+
+ if (ra_offset) {
+ unsigned long ra_addr = frame->sp + ra_offset;
+ unsigned long new_ra;
+ if (readmem(ra_addr, &new_ra, arch.unwind_ops.ra_width,
+ "read RA from stack", UVADDR) != (ssize_t)arch.unwind_ops.ra_width) {
+ pr_trace("RA slot unreadable at 0x%lx; treating as end of backtrace\n",
+ ra_addr);
+ return UNWIND_FAIL;
+ }
+ frame->ra = new_ra;
+ frame->pc = new_ra;
+ } else if (frame->ra != 0) {
+ /* No RA slot found — use existing RA as fallback (e.g. from registers) */
+ frame->pc = frame->ra;
+ } else {
+ fprintf(stderr, "No RA found and frame->ra is 0\n");
+ return UNWIND_FAIL;
+ }
+
+ frame->sp += stack_size;
+
+ pr_trace("unwound: pc=0x%lx ra=0x%lx sp=0x%lx\n",
+ frame->pc, frame->ra, frame->sp);
+ return UNWIND_OK;
+}
+
+/*
+ * Try signal-frame recovery if the arch hints that PC looks like a
+ * signal trampoline.
+ */
+static int try_signal_frame_unwind(struct stackframe *frame)
+{
+ if (!arch.unwind_ops.signal_layout)
+ return -1;
+
+ bool likely = true;
+ if (arch.unwind_ops.likely_signal_pc)
+ likely = arch.unwind_ops.likely_signal_pc(frame->pc);
+
+ if (!likely)
+ return -1;
+
+ struct stackframe candidate = *frame;
+ if (unwind_signal_frame(&candidate) != 0)
+ return -1;
+
+ pr_verbose("signal-frame: pc 0x%lx->0x%lx sp 0x%lx->0x%lx ra 0x%lx->0x%lx\n",
+ frame->pc, candidate.pc, frame->sp, candidate.sp,
+ frame->ra, candidate.ra);
+ *frame = candidate;
+ return 0;
+}
+
+/*
+ * Public entry point: unwind one user-space frame.
+ *
+ * Tries, in order:
+ * 1. Signal-frame recovery
+ * 2. Prologue-scan unwinding
+ *
+ * Returns UNWIND_OK, UNWIND_LEAF, or UNWIND_FAIL.
+ */
+int unwind_user_frame(struct stackframe *frame, unsigned int max_check)
+{
+ int ret;
+
+ if (!frame->pc || !frame->sp) {
+ fprintf(stderr, "error: pc or sp is zero: pc=0x%lx sp=0x%lx\n",
+ frame->pc, frame->sp);
+ return UNWIND_FAIL;
+ }
+
+ if (try_signal_frame_unwind(frame) == 0)
+ return UNWIND_OK;
+
+ ret = unwind_prologue_scan(frame, max_check);
+ if (ret != UNWIND_OK)
+ return ret;
+
+ /* Optional arch-specific fixup after unwinding. */
+ if (arch.unwind_ops.post_unwind_fixup) {
+ if (arch.unwind_ops.post_unwind_fixup(frame, max_check) != 0)
+ return UNWIND_FAIL;
+ }
+
+ return UNWIND_OK;
+}
\ No newline at end of file
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread* [PATCH 08/10] vmcore_tasks: Add VMA utilities and task enumeration
2026-06-22 20:54 [PATCH 00/10] vmcore-tasks: lightweight task and backtrace extractor for vmcore files Pnina Feder
` (6 preceding siblings ...)
2026-06-22 20:54 ` [PATCH 07/10] vmcore_tasks: Add generic user-space stack unwinder Pnina Feder
@ 2026-06-22 20:54 ` Pnina Feder
2026-06-22 20:54 ` [PATCH 09/10] vmcore_tasks: Add main entry point and backtrace parser Pnina Feder
` (3 subsequent siblings)
11 siblings, 0 replies; 15+ messages in thread
From: Pnina Feder @ 2026-06-22 20:54 UTC (permalink / raw)
To: kexec; +Cc: horms, Pnina Feder
Add VMA handling and task walking infrastructure:
- vma_util: VMA metadata extraction from kernel structures.
Supports two enumeration strategies selected at runtime based
on vmcoreinfo availability:
1. Legacy mmap linked list (kernels < ~6.1): walks
mm_struct->mmap -> vm_area_struct->vm_next chain with
map_count hint, self-loop and max-walk guards.
2. Maple tree (kernels >= ~6.1): walks mm_struct->mm_mt via
do_maple_tree() when mm_struct.mmap offset is absent.
For each VMA, extracts addresses, flags, resolves backing
filename via dentry->d_name chain walk, and classifies type
(stack, heap, text) using mm_struct fields.
- tasks: Walks the kernel task_struct linked list from init_task.
For each user task, collects PID, comm, flags, task state,
mm_struct page table base, and per-task VMA snapshot. Enumerates
threads via signal_struct->thread_head list, reads pt_regs from
each thread's kernel stack, and invokes the user-space unwinder
with leaf-function fallback for backtrace capture. Outputs
formatted text with VMA map, register state, and backtrace
per thread.
Signed-off-by: Pnina Feder <pnina.feder@mobileye.com>
---
vmcore_tasks/tasks.c | 540 ++++++++++++++++++++++++++++++++++++++++
vmcore_tasks/tasks.h | 53 ++++
vmcore_tasks/vma_util.c | 429 +++++++++++++++++++++++++++++++
vmcore_tasks/vma_util.h | 16 ++
4 files changed, 1038 insertions(+)
create mode 100644 vmcore_tasks/tasks.c
create mode 100644 vmcore_tasks/tasks.h
create mode 100644 vmcore_tasks/vma_util.c
create mode 100644 vmcore_tasks/vma_util.h
diff --git a/vmcore_tasks/tasks.c b/vmcore_tasks/tasks.c
new file mode 100644
index 00000000..1bcd2e91
--- /dev/null
+++ b/vmcore_tasks/tasks.c
@@ -0,0 +1,540 @@
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#include "vmcore_info.h"
+#include "memory.h"
+
+#include "tasks.h"
+#include "vma_util.h"
+
+#define DEF_MAX_INSTR_CHECK (1024 * 10)
+
+/* Find last (most significant) bit set - same as Linux fls() */
+static inline int mcd_fls(unsigned int x)
+{
+ if (x == 0)
+ return 0;
+ return 32 - __builtin_clz(x);
+}
+
+static unsigned int read_task_state(const char *task_buf)
+{
+ if (OFFSET_EXISTS("task_struct.__state"))
+ return UINT(task_buf + OFFSET("task_struct.__state"));
+ return UINT(task_buf + OFFSET("task_struct.state"));
+}
+
+/* Compute task state index - mirrors kernel's __task_state_index() */
+static unsigned int get_task_state_index(unsigned int tsk_state, unsigned int tsk_exit_state)
+{
+ unsigned int state = (tsk_state | tsk_exit_state) & TASK_REPORT;
+
+ if ((tsk_state & TASK_IDLE) == TASK_IDLE)
+ return TASK_STATE_IDLE;
+
+ /* Report RTLOCK_WAIT and FROZEN as uninterruptible */
+ if ((tsk_state & TASK_RTLOCK_WAIT) || (tsk_state & TASK_FROZEN))
+ state = TASK_UNINTERRUPTIBLE;
+
+ return mcd_fls(state);
+}
+
+/* Convert state index to string for display */
+static const char *task_state_to_str(unsigned int state_idx)
+{
+ static const char * const state_names[] = {
+ [TASK_STATE_RUNNING] = "R (running)",
+ [TASK_STATE_SLEEPING] = "S (sleeping)",
+ [TASK_STATE_DISK_SLEEP] = "D (disk sleep)",
+ [TASK_STATE_STOPPED] = "T (stopped)",
+ [TASK_STATE_TRACED] = "t (tracing stop)",
+ [TASK_STATE_DEAD] = "X (dead)",
+ [TASK_STATE_ZOMBIE] = "Z (zombie)",
+ [TASK_STATE_PARKED] = "P (parked)",
+ [TASK_STATE_IDLE] = "I (idle)",
+ };
+
+ if (state_idx < sizeof(state_names) / sizeof(state_names[0]) && state_names[state_idx])
+ return state_names[state_idx];
+ return "? (unknown)";
+}
+
+/*
+ * Check whether addr is in a valid executable VMA and fill vma_out.
+ * Returns true if valid.
+ */
+static bool is_valid_text_addr(unsigned long addr, struct task_data *task,
+ struct vma_metadata *vma_out)
+{
+ return get_vma_from_user_addr(addr, task, vma_out) &&
+ (vma_out->flags & VM_EXEC);
+}
+
+static void record_bt_entry(struct thread_data *thread, int slot,
+ unsigned long addr, struct vma_metadata *vma)
+{
+ thread->bt_entry[slot].abs_addr = addr;
+ thread->bt_entry[slot].vm_start = vma->start;
+ thread->bt_entry[slot].vm_end = vma->end;
+ snprintf(thread->bt_entry[slot].filename,
+ sizeof(thread->bt_entry[slot].filename),
+ "%s", vma->filename);
+}
+
+/*
+ * Attempt leaf-function fallback: use the original RA from registers
+ * as the caller PC. Only valid for the very first frame.
+ *
+ * Resets SP to the original value (the failed unwind may have corrupted it)
+ * and clears RA so the next unwind iteration won't reuse stale state.
+ *
+ * Returns true if fallback was applied and a bt_entry was recorded.
+ */
+static bool try_leaf_fallback(struct stackframe *frame, struct task_data *task,
+ struct thread_data *thread, int *slot,
+ unsigned long orig_ra, unsigned long orig_sp)
+{
+ struct vma_metadata text_vma;
+
+ if (orig_ra == 0)
+ return false;
+
+ if (!is_valid_text_addr(orig_ra, task, &text_vma))
+ return false;
+
+ pr_verbose("Backtrace: leaf fallback: using RA from regs 0x%lx\n", orig_ra);
+ frame->pc = orig_ra;
+ frame->sp = orig_sp;
+ frame->ra = 0;
+
+ (*slot)--;
+ record_bt_entry(thread, *slot, orig_ra, &text_vma);
+ return true;
+}
+
+void do_user_backtrace(struct task_data *task, struct thread_data *thread)
+{
+ struct vma_metadata stack_vma = {};
+ struct vma_metadata text_vma = {};
+ struct stackframe frame;
+ int slot = BACKTRACE_DEPTH;
+ unsigned long max_search;
+
+ frame.pc = PT_REGS_PC(thread->regs);
+ frame.sp = PT_REGS_SP(thread->regs);
+ frame.ra = PT_REGS_RA(thread->regs);
+
+ unsigned long orig_ra = frame.ra;
+ unsigned long orig_sp = frame.sp;
+
+ pr_trace("init regs: pc=0x%016llx ra=0x%016llx sp=0x%016llx\n",
+ (unsigned long long)frame.pc,
+ (unsigned long long)frame.ra,
+ (unsigned long long)frame.sp);
+
+ /* Validate initial SP is in a known VMA (used for bounds checking later) */
+ if (!get_vma_from_user_addr(frame.sp, task, &stack_vma)) {
+ fprintf(stderr, "error: invalid initial SP, cannot backtrace\n");
+ if (is_verbose()) print_vma_map_for_task(task);
+ return;
+ }
+
+ /* Record initial PC as first backtrace entry */
+ if (slot > 0) {
+ if (!is_valid_text_addr(frame.pc, task, &text_vma)) {
+ fprintf(stderr, "error: initial PC 0x%016llx not in executable VMA\n",
+ (unsigned long long)frame.pc);
+ if (is_verbose()) print_vma_map_for_task(task);
+ return;
+ }
+ slot--;
+ record_bt_entry(thread, slot, frame.pc, &text_vma);
+ }
+
+ bool is_first_frame = true;
+
+ while (slot > 0) {
+ /* Validate current PC is in an executable VMA to determine scan range */
+ if (!is_valid_text_addr(frame.pc, task, &text_vma)) {
+ pr_trace("Backtrace: PC 0x%lx not in executable VMA, stopping\n", frame.pc);
+ break;
+ }
+
+ max_search = (frame.pc - text_vma.start > DEF_MAX_INSTR_CHECK)
+ ? DEF_MAX_INSTR_CHECK
+ : frame.pc - text_vma.start;
+
+ if (unwind_user_frame(&frame, max_search) != 0) {
+ /*
+ * On the first frame this may be a leaf function —
+ * try falling back to the RA from the original registers.
+ */
+ if (is_first_frame &&
+ try_leaf_fallback(&frame, task, thread, &slot, orig_ra, orig_sp)) {
+ is_first_frame = false;
+ continue;
+ }
+ break;
+ }
+
+ if (frame.ra == 0 || !is_valid_text_addr(frame.ra, task, &text_vma)){
+ /*
+ * RA is zero, unmapped, or not in a text section.
+ * This is likely a false-positive prologue match.
+ * On the first frame, try the leaf fallback.
+ */
+ if (is_first_frame &&
+ try_leaf_fallback(&frame, task, thread, &slot, orig_ra, orig_sp)) {
+ is_first_frame = false;
+ continue;
+ }
+ pr_trace("Backtrace: RA 0x%lx invalid after unwind, stopping\n", frame.ra);
+ break;
+ }
+
+ is_first_frame = false;
+
+ /* Check SP stays within the original stack VMA */
+ if (frame.sp > stack_vma.end || frame.sp < stack_vma.start) {
+ pr_trace("Backtrace: SP 0x%lx out of stack bounds [0x%lx-0x%lx]\n",
+ frame.sp, stack_vma.start, stack_vma.end);
+ break;
+ }
+
+ /* Detect infinite loops (RA same as previous entry) */
+ if (frame.ra == thread->bt_entry[slot].abs_addr) {
+ pr_trace("Backtrace: RA 0x%lx same as previous entry, stopping\n", frame.ra);
+ break;
+ }
+
+ slot--;
+ record_bt_entry(thread, slot, frame.ra, &text_vma);
+
+ pr_verbose("Backtrace: frame #%d: addr=0x%016llx, filename=%s, offset=0x%016llx\n",
+ BACKTRACE_DEPTH - 1 - slot,
+ (unsigned long long)frame.ra,
+ text_vma.filename,
+ (unsigned long long)(frame.ra - text_vma.start));
+ }
+}
+
+__weak bool read_task_registers(uint64_t stack_ptr, struct pt_regs *regs)
+{
+ uint64_t regs_addr;
+ size_t pt_regs_size = SIZE("pt_regs");
+ size_t stack_align = NUMBER("STACK_ALIGN");
+ size_t thread_size = NUMBER("THREAD_SIZE");
+
+ if (!stack_ptr) {
+ fprintf(stderr, "Invalid stack pointer\n");
+ return false;
+ }
+
+ // Calculate pt_regs location: stack + THREAD_SIZE - ALIGN(sizeof(pt_regs), STACK_ALIGN)
+ // ALIGN(x, a) = (((x) + (a) - 1) & ~((a) - 1))
+ size_t aligned_size = ((pt_regs_size + stack_align - 1) & ~(stack_align - 1));
+ regs_addr = stack_ptr + thread_size - aligned_size;
+
+ // Read pt_regs structure
+ if (readmem(regs_addr, regs, pt_regs_size, "read pt_regs", KVADDR) < 0) {
+ fprintf(stderr, "Failed to read pt_regs from 0x%016llx\n",
+ (unsigned long long)regs_addr);
+ return false;
+ }
+
+ return true;
+}
+
+int fill_task_data(char *task_buf, struct task_data *task){
+ uint64_t mm_addr, next_list_head, stack_addr;
+ uint64_t signal_addr, thread_head_addr, thread_task_addr, next_val;
+ unsigned int task_state, exit_state;
+ size_t task_struct_sz = SIZE("task_struct");
+
+ task->pid = UINT(task_buf + OFFSET("task_struct.pid"));
+ memcpy(task->comm, task_buf + OFFSET("task_struct.comm"), MAX_TASK_NAME - 1);
+ task->comm[MAX_TASK_NAME - 1] = '\0';
+
+ pr_trace("Task name: %s [PID %d]\n", task->comm, task->pid);
+
+ mm_addr = UINT64(task_buf + OFFSET("task_struct.mm"));
+ if (!mm_addr) {
+ task->is_user_task = 0;
+ task->vmas.vma_count = 0;
+ task->vmas.vma_meta = NULL;
+ task->flags = UINT(task_buf + OFFSET("task_struct.flags"));
+ task->num_threads = 1;
+ task->threads = NULL;
+
+ task_state = read_task_state(task_buf);
+ exit_state = UINT(task_buf + OFFSET("task_struct.exit_state"));
+ task->state_idx = get_task_state_index(task_state, exit_state);
+ task->state = task_state;
+ return 0;
+ }
+ // user task - collect VMAs (shared by all threads)
+ task->is_user_task = 1;
+ if (readmem(mm_addr + OFFSET("mm_struct.pgd"), &task->mm_pgd, sizeof(uint64_t), "read mm_pgd", KVADDR) < 0) {
+ fprintf(stderr, "Failed to read mm_pgd for task %s [PID %d]\n", task->comm, task->pid);
+ return -1;
+ }
+ set_context(task->mm_pgd); // Set once at start
+
+ pr_verbose("Task name: %s [PID %d]\n", task->comm, task->pid);
+
+ // fill VMAs
+ if (!dump_vma_snapshot(&task->vmas, mm_addr)){
+ fprintf(stderr, "Failed to collect vma list.\n");
+ task->vmas.vma_count = 0;
+ task->vmas.vma_meta = NULL;
+ return -1;
+ }else{
+ if (is_verbose()) print_vma_map_for_task(task);
+ }
+
+ // Collect threads
+ signal_addr = UINT64(task_buf + OFFSET("task_struct.signal"));
+ thread_head_addr = signal_addr + OFFSET("signal_struct.thread_head");
+ if (readmem(signal_addr + OFFSET("signal_struct.nr_threads"), &task->num_threads, sizeof(int),
+ "read threads num from signal", KVADDR) < 0) {
+ fprintf(stderr, "Failed to read number of threads for task %s [PID %d]\n", task->comm, task->pid);
+ return -1;
+ }
+ if (task->num_threads <= 0 || task->num_threads > 10000) {
+ fprintf(stderr, "Suspicious nr_threads=%d for task %s [PID %d]\n",
+ task->num_threads, task->comm, task->pid);
+ return -1;
+ }
+ pr_trace("Total threads found for PID=%d: %d\n", task->pid, task->num_threads);
+
+ task->threads = calloc(task->num_threads, sizeof(struct thread_data));
+ if (!task->threads) {
+ fprintf(stderr, "Failed to allocate threads array\n");
+ return -1;
+ }
+
+ pr_verbose("Generating backtrace\n");
+ // Walk the thread list
+ // Start reading from the .next pointer inside it
+ next_list_head = thread_head_addr + OFFSET("list_head.next");
+ for (int i = 0; i < task->num_threads; i++) {
+ // Read next thread pointer from current thread's list_head .next
+ if (readmem(next_list_head, &next_val, sizeof(next_val),
+ "read next thread pointer", KVADDR) < 0) {
+ fprintf(stderr, "Failed to read next thread pointer from 0x%llx\n",
+ (unsigned long long)next_list_head);
+ break;
+ }
+ if (next_val == thread_head_addr) {
+ // Reached back to head
+ fprintf(stderr, "Reached back to thread list head prematurely\n");
+ break;
+ }
+ // next_val points to thread_node (the list_head struct)
+ thread_task_addr = next_val - OFFSET("task_struct.thread_node");
+
+ // For next iteration, read from next_val + OFFSET("list_head.next")
+ next_list_head = next_val + OFFSET("list_head.next");
+
+ // Read thread task_struct
+ char *thread_task_buf = calloc(1, task_struct_sz);
+ if (!thread_task_buf) {
+ fprintf(stderr, "Failed to allocate thread task buffer\n");
+ return -1;
+ }
+
+ if (readmem(thread_task_addr, thread_task_buf, task_struct_sz,
+ "read thread task_struct", KVADDR) < 0) {
+ fprintf(stderr, "Failed to read thread task_struct @ 0x%llx size = %zu\n",
+ (unsigned long long)thread_task_addr, task_struct_sz);
+ free(thread_task_buf);
+ thread_task_buf = NULL;
+ return -1;
+ }
+ task->threads[i].tid = UINT(thread_task_buf + OFFSET("task_struct.pid"));
+ task->threads[i].flags = UINT(thread_task_buf + OFFSET("task_struct.flags"));
+ memcpy(task->threads[i].comm, thread_task_buf + OFFSET("task_struct.comm"), MAX_TASK_NAME - 1);
+ task->threads[i].comm[MAX_TASK_NAME - 1] = '\0';
+
+ task_state = read_task_state(thread_task_buf);
+ exit_state = UINT(thread_task_buf + OFFSET("task_struct.exit_state"));
+ task->threads[i].state_idx = get_task_state_index(task_state, exit_state);
+ task->threads[i].state = task_state;
+
+ stack_addr = UINT64(thread_task_buf + OFFSET("task_struct.stack"));
+ if (!read_task_registers(stack_addr, &task->threads[i].regs)) {
+ fprintf(stderr, "Failed to read registers for TID=%d\n", task->threads[i].tid);
+ free(thread_task_buf);
+ thread_task_buf = NULL;
+ return -1;
+ }
+ pr_trace("===== Thread PID=[%d], NAME= %s, STATE=%s, is_user_task: %d, thread_num: %d/%d, FLAGS=0x%x =====\n",
+ task->threads[i].tid, task->threads[i].comm, task_state_to_str(task->threads[i].state_idx), task->is_user_task, i+1, task->num_threads, task->threads[i].flags);
+ do_user_backtrace(task, &task->threads[i]);
+
+ free(thread_task_buf);
+ thread_task_buf = NULL;
+ }
+ return 0;
+}
+
+int collect_tasks(struct system_minicore *sys_mc)
+{
+ uint64_t list_head_vaddr, task_vaddr, list_next_task;
+ char *task_buf = NULL;
+ const int max_tasks = 10000;
+ size_t task_struct_sz = SIZE("task_struct");
+
+ list_head_vaddr = SYMBOL("init_task") + OFFSET("task_struct.tasks") + OFFSET("list_head.next");
+ task_vaddr = SYMBOL("init_task");
+
+ // First pass: count tasks
+ list_next_task = list_head_vaddr;
+ do {
+ sys_mc->task_count++;
+ // Read next task pointer from current task's tasks list_head
+ if (readmem(list_next_task, &list_next_task, sizeof(list_next_task),
+ "read next task pointer", KVADDR) < 0) {
+ fprintf(stderr, "Failed to read next task pointer from 0x%llx\n",
+ (unsigned long long)list_head_vaddr);
+ //break;
+ return -1;
+ }
+ } while (list_next_task != list_head_vaddr && sys_mc->task_count < max_tasks);
+
+ // Allocate tasks array
+ sys_mc->tasks = calloc(sys_mc->task_count, sizeof(struct task_data));
+ if (!sys_mc->tasks) {
+ fprintf(stderr, "Failed to allocate tasks array\n");
+ return -1;
+ }
+
+ if (OFFSET_EXISTS("mm_struct.mmap"))
+ pr_info("Using mm_struct->mmap linked list for VMA walk\n");
+ else
+ pr_info("mm_struct->mmap not found, using maple tree for VMA walk\n");
+
+ // Second pass: read each task and collect info
+ for (int i = 0; i < sys_mc->task_count; i++) {
+ task_buf = calloc(1, task_struct_sz);
+ if (!task_buf) {
+ fprintf(stderr, "Failed to allocate task buffer\n");
+ return -1;
+ }
+ // Read task buffer
+ if (readmem(task_vaddr, task_buf, task_struct_sz, "read task struct", KVADDR) < 0) {
+ fprintf(stderr, "Failed to read task_struct @ 0x%llx size = %zu\n",
+ (unsigned long long)task_vaddr, task_struct_sz);
+ free(task_buf);
+ return -1;
+ }
+ if (fill_task_data(task_buf, &sys_mc->tasks[i]) < 0) {
+ fprintf(stderr, "Failed to fill task data for task %d\n", i);
+ // Continue to next task — partial data is better than abort
+ }
+
+ list_next_task = UINT64(task_buf + OFFSET("task_struct.tasks") + OFFSET("list_head.next"));
+
+ free(task_buf);
+ task_buf = NULL;
+
+ task_vaddr = list_next_task - OFFSET("task_struct.tasks");
+ }
+ pr_info("Total tasks collected: %d\n", sys_mc->task_count);
+ return 0;
+}
+
+/* Write formatted output mimicking mini_coredump_buffer_asserter.txt format */
+static void write_vma_table(FILE *fp, struct task_data *task)
+{
+ fprintf(fp, "vm_nr [ vm_start- vm_end] filename name flags \n");
+ for (int i = 0; i < task->vmas.vma_count; i++) {
+ struct vma_metadata *vma = &task->vmas.vma_meta[i];
+
+ fprintf(fp, "%-5d [0x%016llx-0x%016llx] %-48s %-16s 0x%08llx\n",
+ i,
+ (unsigned long long)vma->start,
+ (unsigned long long)vma->end,
+ vma->filename[0] ? vma->filename : "",
+ vma->type,
+ (unsigned long long)vma->flags);
+ }
+}
+
+__weak void write_user_registers_formatted(FILE *fp, struct pt_regs *regs)
+{
+ (void)fp;
+ (void)regs;
+}
+
+static void write_user_backtrace_formatted(FILE *fp, struct thread_data *thread)
+{
+ int bt_idx = 0;
+ fprintf(fp, "user backtrace:\n");
+ /* Iterate from end to start to print in correct order (highest index = first entry) */
+ for (int i = BACKTRACE_DEPTH - 1; i >= 0; i--) {
+ if (thread->bt_entry[i].abs_addr == 0)
+ continue;
+ fprintf(fp, "#%-2d addr=0x%016llx, vm_start=0x%016llx, vm_end=0x%016llx, filename=%-60s\n",
+ bt_idx++,
+ (unsigned long long)thread->bt_entry[i].abs_addr,
+ (unsigned long long)thread->bt_entry[i].vm_start,
+ (unsigned long long)thread->bt_entry[i].vm_end,
+ thread->bt_entry[i].filename);
+ }
+}
+
+void save_minicore_formatted(FILE *fp, struct system_minicore *sys_mc)
+{
+ for (int i = 0; i < sys_mc->task_count; i++) {
+ struct task_data *task = &sys_mc->tasks[i];
+
+ if (!task->is_user_task)
+ continue;
+
+ if (!task->threads)
+ continue;
+
+ for (int t = 0; t < task->num_threads; t++) {
+ struct thread_data *thread = &task->threads[t];
+
+ /* Write task/thread header */
+ fprintf(fp, "name: %s, pid: [%d], state: %d, is_user_task: %c, thread_nr: %d/%d, flags: 0x%x\n",
+ thread->comm,
+ thread->tid,
+ thread->state,
+ task->is_user_task ? 'Y' : 'N',
+ t + 1,
+ task->num_threads,
+ thread->flags);
+
+ /* Write VMA table only for first thread (VMAs are shared by all threads) */
+ if (t == 0 && task->vmas.vma_count > 0) {
+ write_vma_table(fp, task);
+ }
+
+ /* Write registers */
+ write_user_registers_formatted(fp, &thread->regs);
+
+ /* Write backtrace */
+ write_user_backtrace_formatted(fp, thread);
+ }
+ }
+
+ for (int i = 0; i < sys_mc->task_count; i++) {
+ struct task_data *task = &sys_mc->tasks[i];
+
+ if (task->is_user_task)
+ continue;
+
+ fprintf(fp, "name: %s, pid: [%d], state: %d, is_user_task: %c, flags: 0x%x\n",
+ task->comm,
+ task->pid,
+ task->state,
+ task->is_user_task ? 'Y' : 'N',
+ task->flags);
+ }
+}
\ No newline at end of file
diff --git a/vmcore_tasks/tasks.h b/vmcore_tasks/tasks.h
new file mode 100644
index 00000000..d01a7a24
--- /dev/null
+++ b/vmcore_tasks/tasks.h
@@ -0,0 +1,53 @@
+#ifndef TASKS_H
+#define TASKS_H
+
+#include <stdint.h>
+
+#include "vmcore_tasks_defs.h"
+
+/* Task state flags - from include/linux/sched.h */
+#define TASK_RUNNING 0x00000000
+#define TASK_INTERRUPTIBLE 0x00000001
+#define TASK_UNINTERRUPTIBLE 0x00000002
+#define __TASK_STOPPED 0x00000004
+#define __TASK_TRACED 0x00000008
+#define EXIT_DEAD 0x00000010
+#define EXIT_ZOMBIE 0x00000020
+#define TASK_PARKED 0x00000040
+#define TASK_DEAD 0x00000080
+#define TASK_WAKEKILL 0x00000100
+#define TASK_WAKING 0x00000200
+#define TASK_NOLOAD 0x00000400
+#define TASK_NEW 0x00000800
+#define TASK_RTLOCK_WAIT 0x00001000
+#define TASK_FROZEN 0x00002000
+#define TASK_IDLE (TASK_UNINTERRUPTIBLE | TASK_NOLOAD)
+
+#define TASK_REPORT (TASK_RUNNING | TASK_INTERRUPTIBLE | \
+ TASK_UNINTERRUPTIBLE | __TASK_STOPPED | \
+ __TASK_TRACED | EXIT_DEAD | EXIT_ZOMBIE | \
+ TASK_PARKED)
+
+/* Task state index values (result of fls) */
+#define TASK_STATE_RUNNING 0
+#define TASK_STATE_SLEEPING 1 /* TASK_INTERRUPTIBLE */
+#define TASK_STATE_DISK_SLEEP 2 /* TASK_UNINTERRUPTIBLE */
+#define TASK_STATE_STOPPED 3
+#define TASK_STATE_TRACED 4
+#define TASK_STATE_DEAD 5 /* EXIT_DEAD */
+#define TASK_STATE_ZOMBIE 6 /* EXIT_ZOMBIE */
+#define TASK_STATE_PARKED 7
+#define TASK_STATE_IDLE 8 /* special case */
+
+/* Fills out with parsed VMCOREINFO values.
+ * Returns 0 on success, -1 if required base fields missing.
+ */
+int collect_tasks(struct system_minicore *sys_mc);
+
+/* Write tasks formatted output to file */
+void save_minicore_formatted(FILE *fp, struct system_minicore *sys_mc);
+
+/* Arch-overridable: read pt_regs from kernel stack (weak in tasks.c) */
+bool read_task_registers(uint64_t stack_ptr, struct pt_regs *regs);
+
+#endif /* TASKS_H */
\ No newline at end of file
diff --git a/vmcore_tasks/vma_util.c b/vmcore_tasks/vma_util.c
new file mode 100644
index 00000000..25e30fd4
--- /dev/null
+++ b/vmcore_tasks/vma_util.c
@@ -0,0 +1,429 @@
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <sys/param.h>
+#include <stdlib.h>
+
+#include "memory.h"
+#include "maple_tree.h"
+#include "vmcore_info.h"
+
+#include "vma_util.h"
+#include "vmcore_tasks_defs.h"
+
+
+bool get_vma_from_user_addr(unsigned long in_addr,
+ struct task_data *task,
+ struct vma_metadata *out_vma)
+{
+ struct task_vmas task_vmas = task->vmas;
+ int i;
+
+ for (i = 0; i < task_vmas.vma_count; i++) {
+ struct vma_metadata *vma_meta = &task_vmas.vma_meta[i];
+
+ if (in_addr >= vma_meta->start && in_addr < vma_meta->end) {
+ pr_trace("found vma mapping : [0x%016lx-0x%016lx]\n", vma_meta->start, vma_meta->end);
+
+ /* Copy only fields we use elsewhere */
+ out_vma->start = vma_meta->start;
+ out_vma->end = vma_meta->end;
+ out_vma->file = vma_meta->file;
+ out_vma->flags = vma_meta->flags;
+ snprintf(out_vma->filename, sizeof(out_vma->filename), "%s", vma_meta->filename);
+ snprintf(out_vma->type, sizeof(out_vma->type), "%s", vma_meta->type);
+ return true;
+ }
+ }
+ return false;
+}
+
+void print_vma_map_for_task(struct task_data *task)
+{
+ pr_verbose(" VMAs: %d regions\n", task->vmas.vma_count);
+ for (int i = 0; i < task->vmas.vma_count; i++) {
+ pr_verbose(" [%d] 0x%llx-0x%llx flags=0x%llx file=0x%llx type=%s filename=%s\n",
+ i,
+ (unsigned long long)task->vmas.vma_meta[i].start,
+ (unsigned long long)task->vmas.vma_meta[i].end,
+ (unsigned long long)task->vmas.vma_meta[i].flags,
+ (unsigned long long)task->vmas.vma_meta[i].file,
+ task->vmas.vma_meta[i].type,
+ task->vmas.vma_meta[i].filename);
+ }
+}
+
+static bool get_file_path(uint64_t file_ptr, char *path_buf, size_t buf_size)
+{
+ uint64_t f_path_dentry;
+ uint64_t d_name_ptr, d_parent;
+ uint64_t hash_len;
+ uint32_t d_name_len;
+ char *file_buf = NULL;
+ char *dentry_buf = NULL;
+ bool ret = false;
+ size_t file_sz = SIZE("file");
+ size_t dentry_sz = SIZE("dentry");
+
+ /* Stack of path components (max depth 32 should be plenty) */
+ #define MAX_PATH_DEPTH 32
+ char components[MAX_PATH_DEPTH][256];
+ int depth = 0;
+
+ if (!file_ptr) {
+ path_buf[0] = '\0';
+ return true;
+ }
+
+ file_buf = malloc(file_sz);
+ dentry_buf = malloc(dentry_sz);
+ if (!file_buf || !dentry_buf) {
+ fprintf(stderr, "get_file_path: allocation failed\n");
+ snprintf(path_buf, buf_size, "[error]");
+ goto out;
+ }
+
+ // Read struct file
+ if (readmem(file_ptr, file_buf, file_sz, "read file struct", KVADDR) < 0) {
+ snprintf(path_buf, buf_size, "[error]");
+ goto out;
+ }
+
+ // Get f_path.dentry pointer
+ f_path_dentry = UINT64(file_buf + OFFSET("file.f_path") + OFFSET("path.dentry"));
+ if (!f_path_dentry) {
+ snprintf(path_buf, buf_size, "[no dentry]");
+ goto out;
+ }
+
+ /* Walk dentry chain up to root (d_parent == self) */
+ uint64_t cur_dentry = f_path_dentry;
+ while (depth < MAX_PATH_DEPTH) {
+ if (readmem(cur_dentry, dentry_buf, dentry_sz, "read dentry", KVADDR) < 0) {
+ snprintf(path_buf, buf_size, "[error]");
+ goto out;
+ }
+
+ hash_len = UINT64(dentry_buf + OFFSET("dentry.d_name") + OFFSET("qstr.hash_len"));
+ d_name_ptr = UINT64(dentry_buf + OFFSET("dentry.d_name") + OFFSET("qstr.name"));
+ d_name_len = (uint32_t)(hash_len >> 32);
+
+ if (!d_name_ptr || d_name_len == 0 || d_name_len > 255)
+ break;
+
+ if (d_name_len >= sizeof(components[0]))
+ d_name_len = sizeof(components[0]) - 1;
+
+ if (readmem(d_name_ptr, components[depth], d_name_len, "read filename", KVADDR) < 0)
+ break;
+ components[depth][d_name_len] = '\0';
+
+ /* Skip root dentry name "/" */
+ if (d_name_len == 1 && components[depth][0] == '/')
+ break;
+
+ depth++;
+
+ d_parent = UINT64(dentry_buf + OFFSET("dentry.d_parent"));
+ if (d_parent == cur_dentry || d_parent == 0)
+ break;
+ cur_dentry = d_parent;
+ }
+
+ /* Build full path from components (reverse order) */
+ if (depth == 0) {
+ snprintf(path_buf, buf_size, "[unknown]");
+ goto out;
+ }
+
+ path_buf[0] = '\0';
+ for (int i = depth - 1; i >= 0; i--) {
+ size_t cur_len = strlen(path_buf);
+ snprintf(path_buf + cur_len, buf_size - cur_len, "/%s", components[i]);
+ }
+
+ ret = true;
+out:
+ if (file_buf)
+ free(file_buf);
+ if (dentry_buf)
+ free(dentry_buf);
+ return ret;
+ #undef MAX_PATH_DEPTH
+}
+
+static void get_vma_type(struct vma_metadata *vma_meta, unsigned long start_stack,
+ unsigned long start_brk, unsigned long brk)
+{
+
+ /* Check if this is the stack region */
+ if (vma_meta->start <= start_stack && vma_meta->end >= start_stack) {
+ snprintf(vma_meta->type, sizeof(vma_meta->type), "[stack]");
+ return;
+ }
+
+ /* Check if this is the heap region */
+ if (vma_meta->start <= brk && vma_meta->end >= start_brk) {
+ snprintf(vma_meta->type, sizeof(vma_meta->type), "[heap]");
+ return;
+ }
+
+ /* Check if this is a text (code) region: readable + executable but not writable */
+ if ((vma_meta->flags & (VM_READ | VM_EXEC | VM_WRITE)) == (VM_READ | VM_EXEC)) {
+ snprintf(vma_meta->type, sizeof(vma_meta->type), "[text]");
+ return;
+ }
+
+ /* Default: data or other */
+ snprintf(vma_meta->type, sizeof(vma_meta->type), "[---]");
+}
+
+/*
+ * Walk VMAs via the legacy mm_struct->mmap linked list.
+ * Works on kernels before the maple tree migration (< ~6.1).
+ */
+static bool collect_vmas_mmap(struct task_vmas *vmas, uint64_t mm_addr,
+ unsigned long start_stack, unsigned long start_brk,
+ unsigned long brk)
+{
+ uint64_t vma_addr;
+ unsigned long map_count = 0;
+ int count = 0, capacity = 64;
+ int max_walk = 0;
+ size_t vma_struct_sz = SIZE("vm_area_struct");
+ char *vma_struct_tmp = NULL;
+ struct vma_metadata *vma_meta = NULL;
+
+ /* Read the mmap head pointer */
+ if (readmem(mm_addr + OFFSET("mm_struct.mmap"), &vma_addr,
+ sizeof(vma_addr), "read mm->mmap", KVADDR) < 0) {
+ fprintf(stderr, "collect_vmas_mmap: failed to read mmap pointer\n");
+ return false;
+ }
+
+ if (vma_addr == 0) {
+ vmas->vma_count = 0;
+ vmas->vma_meta = NULL;
+ return true;
+ }
+
+ /* Use map_count as an allocation hint when available. */
+ if (OFFSET_EXISTS("mm_struct.map_count") &&
+ readmem(mm_addr + OFFSET("mm_struct.map_count"), &map_count,
+ sizeof(map_count), "read mm->map_count", KVADDR) >= 0) {
+ if (map_count > 0 && map_count < 100000)
+ capacity = (int)map_count;
+ }
+
+ if (capacity < 32)
+ capacity = 32;
+
+ /* Hard walk cap for corrupted lists: 4x expected + slack. */
+ max_walk = (capacity * 4) + 1024;
+
+ vma_struct_tmp = malloc(vma_struct_sz);
+ if (!vma_struct_tmp)
+ return false;
+
+ vma_meta = calloc(capacity, sizeof(struct vma_metadata));
+ if (!vma_meta) {
+ free(vma_struct_tmp);
+ return false;
+ }
+
+ while (vma_addr != 0) {
+ if (count >= max_walk) {
+ fprintf(stderr,
+ "collect_vmas_mmap: aborting VMA walk after %d entries (map_count hint=%lu)\n",
+ count, map_count);
+ break;
+ }
+
+ if (readmem(vma_addr, vma_struct_tmp, vma_struct_sz,
+ "read vm_area_struct", KVADDR) < 0) {
+ fprintf(stderr, "collect_vmas_mmap: failed to read VMA at 0x%lx\n",
+ (unsigned long)vma_addr);
+ break;
+ }
+
+ /* Grow array if needed */
+ if (count >= capacity) {
+ capacity *= 2;
+ struct vma_metadata *tmp = realloc(vma_meta,
+ capacity * sizeof(struct vma_metadata));
+ if (!tmp) {
+ fprintf(stderr, "collect_vmas_mmap: realloc failed\n");
+ free(vma_meta);
+ free(vma_struct_tmp);
+ return false;
+ }
+ vma_meta = tmp;
+ memset(&vma_meta[count], 0,
+ (capacity - count) * sizeof(struct vma_metadata));
+ }
+
+ vma_meta[count].start = ULONG(vma_struct_tmp +
+ OFFSET("vm_area_struct.vm_start"));
+ vma_meta[count].end = ULONG(vma_struct_tmp +
+ OFFSET("vm_area_struct.vm_end"));
+ vma_meta[count].flags = ULONG(vma_struct_tmp +
+ OFFSET("vm_area_struct.vm_flags"));
+ vma_meta[count].file = ULONG(vma_struct_tmp +
+ OFFSET("vm_area_struct.vm_file"));
+
+ get_file_path(vma_meta[count].file,
+ vma_meta[count].filename,
+ sizeof(vma_meta[count].filename));
+
+ get_vma_type(&vma_meta[count], start_stack, start_brk, brk);
+
+ count++;
+
+ /* Follow vm_next pointer */
+ if (OFFSET_EXISTS("vm_area_struct.vm_next"))
+ vma_addr = ULONG(vma_struct_tmp +
+ OFFSET("vm_area_struct.vm_next"));
+ else
+ /* vm_next is right after vm_end (offset 16 on 64-bit) */
+ vma_addr = ULONG(vma_struct_tmp + sizeof(unsigned long) * 2);
+
+ /* Guard self-loop on corrupted vm_next. */
+ if (count > 0 && vma_addr == vma_meta[count - 1].start) {
+ fprintf(stderr, "collect_vmas_mmap: detected self-loop at 0x%lx\n",
+ (unsigned long)vma_addr);
+ break;
+ }
+ }
+
+ vmas->vma_count = count;
+ vmas->vma_meta = vma_meta;
+
+ free(vma_struct_tmp);
+ return true;
+}
+
+/* Walk VMAs via maple tree (kernels >= ~6.1). */
+static bool collect_vmas_mapletree(struct task_vmas *vmas, uint64_t mm_addr,
+ unsigned long start_stack, unsigned long start_brk,
+ unsigned long brk)
+{
+ ulong vma;
+ int count, i, j, entry_num;
+ size_t vma_struct_sz = SIZE("vm_area_struct");
+ char *vma_struct_tmp = NULL;
+ struct vma_metadata *vma_meta = NULL;
+ struct list_pair *entry_list = NULL;
+
+ vma_struct_tmp = malloc(vma_struct_sz);
+ if (!vma_struct_tmp) {
+ fprintf(stderr, "dump_vma_snapshot: allocation failed\n");
+ return false;
+ }
+
+ uint64_t mt_addr = mm_addr + OFFSET("mm_struct.mm_mt");
+
+ /* This code was copied from gcore_coredump.c */
+
+ entry_num = do_maple_tree(mt_addr, MAPLE_TREE_COUNT, NULL);
+ if (entry_num <= 0) {
+ vmas->vma_count = 0;
+ vmas->vma_meta = NULL;
+ free(vma_struct_tmp);
+ return true; // Empty VMA list is valid (kernel threads)
+ }
+
+ entry_list = (struct list_pair *)calloc(entry_num, sizeof(struct list_pair));
+ if (!entry_list) {
+ fprintf(stderr, "dump_vma_snapshot: failed to allocate entry_list\n");
+ free(vma_struct_tmp);
+ return false;
+ }
+ entry_list[0].index = entry_num; /* limit gather to allocated size */
+ do_maple_tree(mt_addr, MAPLE_TREE_GATHER, entry_list);
+
+ /* Calculate the actual number of vmas since each
+ * entry could be empty. */
+ count = 0;
+ for (i = 0; i < entry_num; ++i)
+ if (entry_list[i].value)
+ count++;
+
+ vma_meta = (struct vma_metadata *)calloc(count, sizeof(struct vma_metadata));
+ if (!vma_meta) {
+ fprintf(stderr, "Failed to allocate vma_meta\n");
+ free(entry_list);
+ free(vma_struct_tmp);
+ return false;
+ }
+
+ for (i = 0, j = 0; i < entry_num; ++i) {
+ if (!entry_list[i].value)
+ continue;
+
+ vma = (ulong)entry_list[i].value;
+
+ if (readmem(vma, vma_struct_tmp,
+ SIZE("vm_area_struct"), "read vm_area_struct", KVADDR) < 0 ) {
+ fprintf(stderr, "dump_vma_snapshot: read vm_area_struct failed\n");
+ free(vma_meta);
+ free(entry_list);
+ free(vma_struct_tmp);
+ return false;
+ }
+ vma_meta[j].start = ULONG(vma_struct_tmp + OFFSET("vm_area_struct.vm_start"));
+ vma_meta[j].end = ULONG(vma_struct_tmp + OFFSET("vm_area_struct.vm_end"));
+ vma_meta[j].flags = ULONG(vma_struct_tmp + OFFSET("vm_area_struct.vm_flags"));
+ vma_meta[j].file = ULONG(vma_struct_tmp + OFFSET("vm_area_struct.vm_file"));
+
+ // Get filename if available
+ get_file_path(vma_meta[j].file,
+ vma_meta[j].filename, sizeof(vma_meta[j].filename));
+
+ // Determine VMA type (stack, heap, text, data)
+ get_vma_type(&vma_meta[j], start_stack, start_brk, brk);
+
+ j++;
+ }
+
+ free(entry_list);
+ entry_list = NULL;
+
+ vmas->vma_count = count;
+ vmas->vma_meta = vma_meta;
+
+ free(vma_struct_tmp);
+ return true;
+}
+
+bool dump_vma_snapshot(struct task_vmas *vmas, uint64_t mm_addr)
+{
+ unsigned long start_stack = 0, start_brk = 0, brk = 0;
+
+ /* Read mm_struct fields for VMA type detection */
+ if (readmem(mm_addr + OFFSET("mm_struct.start_stack"), &start_stack,
+ sizeof(start_stack), "read start_stack", KVADDR) < 0) {
+ fprintf(stderr, "dump_vma_snapshot: read start_stack failed\n");
+ }
+ if (readmem(mm_addr + OFFSET("mm_struct.start_brk"), &start_brk,
+ sizeof(start_brk), "read start_brk", KVADDR) < 0) {
+ fprintf(stderr, "dump_vma_snapshot: read start_brk failed\n");
+ }
+ if (readmem(mm_addr + OFFSET("mm_struct.brk"), &brk,
+ sizeof(brk), "read brk", KVADDR) < 0) {
+ fprintf(stderr, "dump_vma_snapshot: read brk failed\n");
+ }
+ /*
+ * Try legacy mmap linked list first (older kernels).
+ * Fall back to maple tree if mmap doesn't exist (kernels >= ~6.1).
+ */
+ if (OFFSET_EXISTS("mm_struct.mmap")) {
+ pr_trace("Using mm_struct->mmap linked list for VMA walk\n");
+ return collect_vmas_mmap(vmas, mm_addr, start_stack, start_brk, brk);
+ }
+
+ pr_trace("mm_struct->mmap not found, using maple tree for VMA walk\n");
+ return collect_vmas_mapletree(vmas, mm_addr, start_stack, start_brk, brk);
+}
diff --git a/vmcore_tasks/vma_util.h b/vmcore_tasks/vma_util.h
new file mode 100644
index 00000000..5030561d
--- /dev/null
+++ b/vmcore_tasks/vma_util.h
@@ -0,0 +1,16 @@
+#ifndef VMA_UTIL_H
+#define VMA_UTIL_H
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdbool.h>
+
+#include "vmcore_tasks_defs.h"
+
+bool get_vma_from_user_addr(unsigned long in_addr,
+ struct task_data *task,
+ struct vma_metadata *out_vma);
+void print_vma_map_for_task(struct task_data *task);
+bool dump_vma_snapshot(struct task_vmas *vmas, uint64_t mm_addr);
+
+#endif /* VMA_UTIL_H */
\ No newline at end of file
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread* [PATCH 09/10] vmcore_tasks: Add main entry point and backtrace parser
2026-06-22 20:54 [PATCH 00/10] vmcore-tasks: lightweight task and backtrace extractor for vmcore files Pnina Feder
` (7 preceding siblings ...)
2026-06-22 20:54 ` [PATCH 08/10] vmcore_tasks: Add VMA utilities and task enumeration Pnina Feder
@ 2026-06-22 20:54 ` Pnina Feder
2026-06-22 20:54 ` [PATCH 10/10] vmcore_tasks: Add README with project documentation Pnina Feder
` (2 subsequent siblings)
11 siblings, 0 replies; 15+ messages in thread
From: Pnina Feder @ 2026-06-22 20:54 UTC (permalink / raw)
To: kexec; +Cc: horms, Pnina Feder
- vmcore_tasks.c: Main entry point that orchestrates vmcore
analysis. Opens vmcore ELF, parses headers and vmcoreinfo
note, initializes architecture and memory subsystems, collects
all tasks with per-thread register state and backtraces,
extracts kernel dmesg, and writes combined output to
tasks_buffer.txt. Supports debug verbosity levels via -d flag.
- vmcore_tasks_backtrace_parser.py: Post-processing Python tool
that parses tasks_buffer.txt and resolves raw addresses to
function names and source locations using addr2line and readelf.
Handles shared library load address calculation, ELF link base
detection for non-PIE binaries, C++ symbol demangling, and
classifies tasks into user/kernel/running/crashed categories.
Signed-off-by: Pnina Feder <pnina.feder@mobileye.com>
---
vmcore_tasks/vmcore_tasks.c | 182 ++++++++
vmcore_tasks/vmcore_tasks_backtrace_parser.py | 393 ++++++++++++++++++
2 files changed, 575 insertions(+)
create mode 100644 vmcore_tasks/vmcore_tasks.c
create mode 100644 vmcore_tasks/vmcore_tasks_backtrace_parser.py
diff --git a/vmcore_tasks/vmcore_tasks.c b/vmcore_tasks/vmcore_tasks.c
new file mode 100644
index 00000000..68785b4e
--- /dev/null
+++ b/vmcore_tasks/vmcore_tasks.c
@@ -0,0 +1,182 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+
+#include "elf_info.h"
+#include "vmcore_info.h"
+#include "memory.h"
+#include "maple_tree.h"
+
+#include "vmcore_tasks_defs.h"
+#include "tasks.h"
+
+extern const char *fname;
+struct arch_deps arch;
+int debug_level = 0;
+
+#define OUT_BUF_SIZE (64 * 1024) // Larger buffer for full dmesg
+
+static char dmesg_buf[OUT_BUF_SIZE];
+static size_t dmesg_len = 0;
+static bool dmesg_truncated = false;
+
+void dmesg_handler(char *buf, unsigned int len)
+{
+ if (dmesg_len + len < OUT_BUF_SIZE) {
+ memcpy(dmesg_buf + dmesg_len, buf, len);
+ dmesg_len += len;
+ dmesg_buf[dmesg_len] = '\0';
+ } else if (!dmesg_truncated) {
+ fprintf(stderr, "Warning: dmesg buffer full, output truncated\n");
+ dmesg_truncated = true;
+ }
+}
+
+/* Write dmesg to file */
+void save_dmesg_formatted(FILE *fp, const char *dmesg)
+{
+ if (dmesg && dmesg[0]) {
+ fprintf(fp, "\ndmesg: (size: %zu)\n%s\n", dmesg_len, dmesg);
+ }
+}
+
+void cleanup_minicore(struct system_minicore *sys_mc)
+{
+ if (!sys_mc || !sys_mc->tasks)
+ return;
+
+ for (int i = 0; i < sys_mc->task_count; i++) {
+ struct task_data *task = &sys_mc->tasks[i];
+
+ /* Free VMA metadata */
+ if (task->vmas.vma_meta) {
+ free(task->vmas.vma_meta);
+ task->vmas.vma_meta = NULL;
+ }
+
+ /* Free threads array */
+ if (task->threads) {
+ free(task->threads);
+ task->threads = NULL;
+ }
+ }
+
+ free(sys_mc->tasks);
+ sys_mc->tasks = NULL;
+ sys_mc->task_count = 0;
+}
+
+int main(int argc, char **argv)
+{
+ int fd = -1, ret = 0;
+ struct system_minicore sys_mc;
+ // Initialize vmcoreinfo tables from raw data
+ const char *vmci_data;
+ size_t vmci_size;
+ const char *corefile = NULL;
+ FILE *out_fp = NULL;
+
+ memset(&sys_mc, 0, sizeof(sys_mc));
+
+ /* Parse command line arguments */
+ for (int i = 1; i < argc; i++) {
+ if (strncmp(argv[i], "-d", 2) == 0) {
+ if (argv[i][2] != '\0') {
+ /* -d<level> form, e.g. -d1, -d3 */
+ debug_level = atoi(&argv[i][2]);
+ } else if (i + 1 < argc && argv[i + 1][0] != '-') {
+ /* -d <level> form with space */
+ debug_level = atoi(argv[++i]);
+ } else {
+ /* -d alone — default to TRACE */
+ debug_level = DBG_TRACE;
+ }
+ if (debug_level < DBG_NONE || debug_level > DBG_MEM) {
+ fprintf(stderr, "Invalid debug level %d (valid: %d-%d)\n",
+ debug_level, DBG_NONE, DBG_MEM);
+ ret = -1;
+ goto out_close;
+ }
+ } else if (argv[i][0] != '-') {
+ corefile = argv[i];
+ } else {
+ fprintf(stderr, "Unknown option: %s\n", argv[i]);
+ fprintf(stderr, "usage: %s [-d[0-4]] <kernel core file>\n", argv[0]);
+ ret = -1;
+ goto out_close;
+ }
+ }
+ if (!corefile) {
+ fprintf(stderr, "usage: %s [-d[0-4]] <kernel core file>\n", argv[0]);
+ ret = -1;
+ goto out_close;
+ }
+
+ fname = corefile;
+
+ fd = open(fname, O_RDONLY);
+ if (fd < 0) {
+ fprintf(stderr, "Cannot open %s: %s\n",
+ fname, strerror(errno));
+ ret = -1;
+ goto out_close;
+ }
+
+ pr_info("Reading vmcore ELF and initializing vmcoreinfo\n");
+ ret = read_elf(fd);
+ if (ret != 0) {
+ fprintf(stderr, "Unable to read ELF information"
+ " from vmcore\n");
+ ret = -1;
+ goto out_close;
+ }
+
+ if (elf_get_raw_vmcoreinfo(&vmci_data, &vmci_size) == 0) {
+ vmcoreinfo_init(vmci_data, vmci_size);
+ vmcore_set_fd(fd);
+ } else {
+ fprintf(stderr, "Warning: no VMCOREINFO note found\n");
+ }
+
+ /* Dump vmcoreinfo for debugging */
+ if (is_trace()) vmcoreinfo_dump();
+
+ pr_info("Initializing architecture specific dependencies\n");
+ arch_init();
+ mem_vtop = arch.vtop;
+ memory_config_init();
+
+ /* Open output file for writing */
+ out_fp = fopen("tasks_buffer.txt", "w");
+ if (!out_fp) {
+ fprintf(stderr, "Failed to open output file: %s\n", strerror(errno));
+ ret = -1;
+ goto out_close;
+ }
+ pr_info("os release: %s\n", vmcoreinfo_osrelease());
+
+ pr_info("Collecting tasks data\n");
+ ret = collect_tasks(&sys_mc);
+ if (ret < 0) {
+ fprintf(stderr, "Failed to collect tasks\n");
+ ret = -1;
+ goto out_close;
+ }
+ save_minicore_formatted(out_fp, &sys_mc);
+
+ pr_info("Dumping dmesg log\n");
+ dump_dmesg(fd, dmesg_handler);
+ save_dmesg_formatted(out_fp, dmesg_buf);
+
+ printf("Tasks data saved to tasks_buffer.txt\n");
+
+out_close:
+ if (out_fp)
+ fclose(out_fp);
+ cleanup_minicore(&sys_mc);
+ if (fd >= 0)
+ close(fd);
+ vmcoreinfo_cleanup();
+ return ret;
+}
\ No newline at end of file
diff --git a/vmcore_tasks/vmcore_tasks_backtrace_parser.py b/vmcore_tasks/vmcore_tasks_backtrace_parser.py
new file mode 100644
index 00000000..5abea4ef
--- /dev/null
+++ b/vmcore_tasks/vmcore_tasks_backtrace_parser.py
@@ -0,0 +1,393 @@
+#!/usr/bin/env python
+
+import argparse
+import subprocess
+import os
+import sys
+import logging
+import re
+
+g_output_file = 'tasks_output.txt'
+g_readelf_file_dict = {'name':'readelf','path':'./'}
+g_addr2line_file_dict = {'name':'addr2line','path':'./'}
+g_syms_default = ""
+g_task_list = []
+g_user_task_list = []
+g_kernel_task_list = []
+g_running_task_list = []
+g_crash_task_list = []
+g_libs_address_so_dict = {}
+g_libs_load_address_dict = {}
+
+
+class MyFormatter(logging.Formatter):
+ err_fmt = "ERR: %(msg)s"
+ dbg_fmt = "DBG: %(module)s: %(lineno)d: %(msg)s"
+ info_fmt = "%(msg)s"
+
+ def __init__(self, fmt="%(levelno)s: %(msg)s"):
+ logging.Formatter.__init__(self, fmt)
+
+ def format(self, record):
+ format_orig = self._fmt
+
+ if record.levelno == logging.DEBUG:
+ self._fmt = MyFormatter.dbg_fmt
+
+ elif record.levelno == logging.INFO:
+ self._fmt = MyFormatter.info_fmt
+
+ elif record.levelno == logging.ERROR:
+ self._fmt = MyFormatter.err_fmt
+
+ result = logging.Formatter.format(self, record)
+
+ self._fmt = format_orig
+
+ return result
+
+# create logger
+fmt = MyFormatter()
+logger = logging.getLogger('mcd_bt_parser_logger')
+logger.setLevel(logging.INFO)
+
+# create console handler
+console_handler = logging.StreamHandler()
+console_handler.setFormatter(fmt)
+logger.addHandler(console_handler)
+
+class ArgumentParser:
+ def __init__(self):
+ self.mandatory_arg = ['buffer']
+ self.mandatory_toolchain_tools = [g_readelf_file_dict, g_addr2line_file_dict]
+ self.parser = argparse.ArgumentParser(description='Mini codedump backtrace parser')
+
+ self.parser.add_argument('--buffer', '-b', metavar='<path to tasks buffer>', required=True, help='tasks buffer')
+ self.parser.add_argument('--syms', '-s', '--path', '-p', metavar='<path to libs/apps syms>', required=True, help='libs/apps path. can be separated by :')
+ self.parser.add_argument('--toolchain', '-t', metavar='<path to toolchain>', help='toolchain bin directory, or full path including prefix (e.g. /path/bin/riscv64-mti-linux-gnu-)')
+ self.parser.add_argument('--toolchain-prefix', metavar='<toolchain prefix>', help='toolchain tool prefix (e.g. mips64el-linux-musl-)')
+
+ self.activate()
+
+ # Update tool names if --toolchain-prefix is given
+ if self.args.toolchain_prefix:
+ g_readelf_file_dict['name'] = self.args.toolchain_prefix + 'readelf'
+ g_addr2line_file_dict['name'] = self.args.toolchain_prefix + 'addr2line'
+ elif self.args.toolchain and not os.path.isdir(self.args.toolchain):
+ # Support combined path+prefix, e.g. /path/bin/riscv64-mti-linux-gnu-
+ toolchain_dir = os.path.dirname(self.args.toolchain)
+ toolchain_prefix = os.path.basename(self.args.toolchain)
+ if toolchain_dir and os.path.isdir(toolchain_dir):
+ self.args.toolchain = toolchain_dir
+ g_readelf_file_dict['name'] = toolchain_prefix + 'readelf'
+ g_addr2line_file_dict['name'] = toolchain_prefix + 'addr2line'
+ logger.info("auto-detected toolchain dir: {} prefix: {}".format(toolchain_dir, toolchain_prefix))
+
+ if g_syms_default:
+ self.args.syms="{}:{}".format(self.args.syms,g_syms_default)
+ else:
+ self.args.syms=self.args.syms
+
+ # Normalize syms paths: if a path is a file, use its parent directory
+ normalized_paths = []
+ for spath in self.args.syms.split(':'):
+ if os.path.isfile(spath):
+ parent = os.path.dirname(spath) or '.'
+ logger.info("syms path '{}' is a file, using parent directory '{}'".format(spath, parent))
+ normalized_paths.append(parent)
+ else:
+ normalized_paths.append(spath)
+ self.args.syms = ':'.join(normalized_paths)
+ self.check_mandatory_args()
+ self.check_dependency_tools()
+
+ def activate(self):
+ self.args = self.parser.parse_args()
+
+ def args(self):
+ return self.args
+
+ def check_mandatory_args(self):
+ logger.info("checking mandatory input args")
+ for arg in vars(self.args):
+ logger.debug("parsing arguments: arg={} value={}".format(arg, getattr(self.args, arg)))
+
+ if arg in self.mandatory_arg and not os.path.isfile(getattr(self.args, arg)):
+ logger.error("missing mandatory input arg: --{}. {} is invalid file path".format(arg, getattr(self.args, arg)))
+ sys.exit()
+
+ logger.info("Done!")
+
+ def check_dependency_tools(self):
+ # got toolchain input flag
+ if (self.args.toolchain) != None and os.path.isdir(self.args.toolchain):
+ logger.debug("got toolchain flag: {}".format(self.args.toolchain))
+ # go over the toolchain dir and verify the mandatory tools are there
+ for file_dict in self.mandatory_toolchain_tools:
+ path = "{}/{}".format(self.args.toolchain, file_dict['name'])
+ if (not os.path.isfile(path)):
+ logger.error("toolchain path is missing tool: {}".format(path))
+ sys.exit()
+ else:
+ # update the toolchain tool path
+ path = "{}/{}".format(self.args.toolchain, file_dict['name'])
+ file_dict['path'] = self.args.toolchain
+ logger.debug("updating toolchain tools path to: {}".format(file_dict['path']))
+
+ # didn't get toolchain flag - tools must be in current working directory of parser
+ else:
+ for file_dict in self.mandatory_toolchain_tools:
+ if (not os.path.isfile("{}/{}".format(file_dict['path'], file_dict['name']))):
+ logger.error("toolchain path is missing tool: {}".format(file_dict))
+ sys.exit()
+
+class BacktraceParser:
+ def __init__(self, user_params):
+ self.addr2line = "{}/{}".format(g_addr2line_file_dict['path'], g_addr2line_file_dict['name'])
+ self.readelf_path = "{}/{}".format(g_readelf_file_dict['path'], g_readelf_file_dict['name'])
+ self.syms = user_params.args.syms
+ self.buffer = user_params.args.buffer
+ self.PF_DUMPCORE = 0x00000200 # Dumped core
+ self.PF_SIGNALED = 0x00000400 # Killed by a signal
+
+ self.parse_buffer_txt()
+
+ self.print_tasks_summary()
+
+ def write_to_file(self, content, write_handle):
+ write_handle.write(content)
+
+ def get_task_flags(self, line):
+ return int(line.split("flags: ")[1].rstrip(), 16)
+
+ def is_crashed_task(self, flag):
+ return flag & self.PF_SIGNALED != 0
+
+ def get_text_address_from_so(self, readelf_path, so_file):
+ logger.debug("input params: readelf_path={} so_file={}".format(readelf_path, so_file))
+
+ if not so_file in g_libs_address_so_dict:
+ logger.debug("{} not found in libs offset dictionary, trying to get .text section from {}".format(so_file, so_file))
+ cmd = '{} -S {} | grep -F .text'.format(readelf_path, so_file)
+ value = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True).stdout.read().split()[3]
+ logger.debug("value={}".format(value))
+ logger.debug("text address in {} is: {}".format(so_file, str(value)))
+ g_libs_address_so_dict[so_file] = value
+
+ return g_libs_address_so_dict.get(so_file)
+
+ def check_func_name(self, func_name):
+ out_func_name = func_name
+
+ if func_name.startswith('_Z'):
+ cmd = '{} {}'.format('c++filt -n -p', func_name)
+ out_func_name = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True).stdout.read().rstrip().decode("utf-8")
+ logger.debug("func name {} mangled to {}".format(func_name, out_func_name))
+
+ return out_func_name
+
+ def print_tasks_summary(self):
+ logger.info("writing tasks summary into {}".format(g_output_file))
+
+ with open(g_output_file, 'a') as writer:
+ # summary of all tasks
+ writer.write("\n{}\nuser tasks:\n{}\n".format(11 * "=", 11 * "="))
+ for task in g_user_task_list:
+ writer.write(task)
+
+ writer.write("\n{}\nkernel tasks:\n{}\n".format(13 * "=", 13 * "="))
+ for task in g_kernel_task_list:
+ writer.write(task)
+
+ writer.write("\n{}\nrunning tasks:\n{}\n".format(14 * "=", 14 * "="))
+ for task in g_running_task_list:
+ writer.write(task)
+
+ writer.write("\n{}\ncrash tasks:\n{}\n".format(12 * "=", 12 * "="))
+ for task in g_crash_task_list:
+ writer.write(task)
+ logger.info("Done!")
+
+ def handle_so_file(self, line, filename, bt_index, addr):
+ func_name = "<unknown function>"
+ file_line = "<.so binary not found>"
+ for path in self.syms.split(':'):
+ full_path = path + "/" + os.path.basename(filename)
+ if os.path.isfile(full_path):
+ logger.debug("found .so file at: {}".format(full_path))
+
+ so_address_offset = int(self.get_text_address_from_so(self.readelf_path, full_path), 16)
+ so_virtual_base = int(g_libs_load_address_dict [filename], 16)
+ text_virtual_addr = so_virtual_base + so_address_offset
+ bt_offset = addr - text_virtual_addr
+
+ cmd = '{} -e {} -f -s -p -i -j .text {}'.format(self.addr2line, full_path, hex(bt_offset))
+ logger.debug("addr: " + str(hex(addr)) + " text_virtual_addr: " + str(hex(text_virtual_addr)) + " so_virtual_base: " + str(hex(so_virtual_base)) + " so_address_offset: " + str(hex(so_address_offset)) + " cmd: " + cmd)
+ bt = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True).communicate()[0].decode("utf-8")
+
+ # in case of inline function
+ if "(inlined by)" in bt:
+ cmd = '{} -e {} -f -s -p -i -j .text {}'.format(self.addr2line, full_path, hex(bt_offset + 4))
+ bt = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True).communicate()[0].decode("utf-8").splitlines()[-1].replace(" (inlined by) ", "")
+
+ logger.debug("backtrace cmd: {}\noutput:{}".format(cmd, bt))
+
+ try:
+ func_name = self.check_func_name(bt.split()[0])
+ file_line = bt.split('at ')[1].rstrip()
+ except:
+ logger.error("in getting function name/line number")
+ logger.error("backtrace cmd: {}\n".format(cmd))
+ func_name = "<error in getting function>"
+ file_line = "<error in getting file line>"
+
+ break
+
+ return "{} {} in {} at {} [{}]\n".format(bt_index, str(hex(addr)),
+ func_name, file_line,filename)
+
+ def find_binary(self, filename):
+ """Find binary in syms paths, preferring _unstripped variant."""
+ basename = os.path.basename(filename)
+ for path in self.syms.split(':'):
+ # Prefer _unstripped variant
+ unstripped = path + "/" + basename + "_unstripped"
+ if os.path.isfile(unstripped):
+ return unstripped
+ regular = path + "/" + basename
+ if os.path.isfile(regular):
+ return regular
+ return None
+
+ def get_elf_load_base(self, readelf_path, elf_file):
+ """Get the ELF link base address (first LOAD segment p_vaddr).
+ For non-PIE executables this is the fixed link address (e.g. 0x120000000).
+ For PIE executables / shared libraries this is typically 0.
+ """
+ cmd = '{} -l {} 2>/dev/null | grep LOAD | head -1'.format(readelf_path, elf_file)
+ try:
+ out = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True).stdout.read().decode("utf-8").strip()
+ if out:
+ # LOAD segment line: " LOAD 0x000000 0x1000000000 0x1000000000 ..."
+ # p_vaddr is the 3rd field (index 2)
+ parts = out.split()
+ return int(parts[2], 16)
+ except Exception as e:
+ logger.warning("Failed to get ELF load base for {}: {}".format(elf_file, e))
+ return 0
+
+ def handle_bin_file(self, line, filename, bt_index, addr, vm_start=0):
+ func_name = "<unknown function>"
+ file_line = "<binary file not found>"
+ full_path = self.find_binary(filename)
+ if full_path:
+ logger.debug("found binary app at: {}".format(full_path))
+ elf_link_base = self.get_elf_load_base(self.readelf_path, full_path)
+ bt_offset = (addr - vm_start) + elf_link_base
+ cmd = '{} -e {} -f -s -p {}'.format(self.addr2line, full_path, str(hex(bt_offset)))
+ bt = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True).communicate()[0].decode("utf-8").rstrip()
+ logger.debug("backtrace cmd: {}\noutput:{}".format(cmd, bt))
+
+ try:
+ func_name = self.check_func_name(bt.split()[0])
+ file_line = bt.split('at ')[1].rstrip()
+ except:
+ logger.error("in getting function name/line number")
+ logger.error("backtrace cmd: {}\n".format(cmd))
+ func_name = "<error in getting function>"
+ file_line = "<error in getting file line>"
+
+ return "{} {} in {} at {} [{}]\n".format(bt_index, str(hex(addr)),
+ func_name, file_line, filename)
+
+ def parse_buffer_txt(self):
+ # open buffer txt for read, open output file for write
+ with open(self.buffer, 'r', errors='replace') as buffer_txt:
+ with open(g_output_file, 'w') as writer:
+ logger.info("reading from {} into {}".format(self.buffer, g_output_file))
+
+ # go over each line
+ line = buffer_txt.readline()
+ while line:
+ line_len = len(line)
+ logger.debug("current line is: {}".format(line))
+
+ # start of task
+ if line.startswith('name:'):
+ task_name = line.split('name:')[1].split(',')[0]
+ logger.debug("current task name is: {}".format(task_name))
+
+ # if the header task is user
+ if "is_user_task: Y" in line:
+ logger.debug("task {} is user task".format(task_name))
+ g_user_task_list.append("{}".format(line))
+
+ # if the header task is kernel
+ elif "is_user_task: N" in line:
+ logger.debug("task {} is kernel task".format(task_name))
+ g_kernel_task_list.append("{}".format(line))
+
+ if "state: 0" in line:
+ logger.debug("task {} is in running mode".format(task_name))
+ g_running_task_list.append("{}".format(line))
+
+ if self.is_crashed_task(self.get_task_flags(line)):
+ logger.debug("task {} is crashed".format(task_name))
+ g_crash_task_list.append("{}".format(line))
+
+ # write the header task to file
+ self.write_to_file("\n{}\n{}{}\n".format('=' * line_len, line, '=' * line_len), writer)
+
+ #/proc/map libs load address parsing
+ elif line.startswith("vm_nr"):
+ g_libs_load_address_dict.clear()
+ self.write_to_file(line, writer)
+
+ elif line.strip() and line.split()[0].isdigit():
+ if len(line.split()) > 4:
+ buffer_libname = os.path.basename(line.split()[2].rstrip())
+ if not buffer_libname in g_libs_load_address_dict:
+ value = line.split()[1].split('-')[0][1:]
+ g_libs_load_address_dict[buffer_libname] = value
+ self.write_to_file(line, writer)
+
+ # backtrace parsing
+ elif re.match(r'#.*?addr=',line):
+ bt_index = line.split(' ')[0]
+ addr = int(line.split(', ')[0].split('=')[1].rstrip(), 16)
+ vm_start = int(line.split(', ')[1].split('=')[1].rstrip(), 16)
+ lib_name = os.path.basename(line.split(', ')[3].split('=')[1].rstrip())
+
+ if addr == 0:
+ self.write_to_file(("{}: {}\n".format(line.split(':')[0], str(hex(addr)))), writer)
+
+ else:
+ # in case of SHARED library
+ if '.so' in lib_name:
+ output = self.handle_so_file(line, lib_name, bt_index, addr)
+ else:
+ output = self.handle_bin_file(line, lib_name, bt_index, addr, vm_start)
+
+ self.write_to_file(output, writer)
+
+ # print the dmesg header
+ elif line.startswith("dmesg:"):
+ self.write_to_file("\n{}\n{}{}\n".format('=' * line_len, line, '=' * line_len), writer)
+
+ # line is not backtrace and not header - just print the line
+ else:
+ self.write_to_file(line, writer)
+
+ # read the next line
+ line = buffer_txt.readline()
+
+ # end of tasks buffer
+ self.write_to_file("\n{} END OF TASKS BUFFER {}\n".format(30 * "@", 30 * "@"), writer)
+
+def main():
+ user_params = ArgumentParser()
+ BacktraceParser(user_params)
+
+if __name__ == '__main__':
+ main()
+
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread* [PATCH 10/10] vmcore_tasks: Add README with project documentation
2026-06-22 20:54 [PATCH 00/10] vmcore-tasks: lightweight task and backtrace extractor for vmcore files Pnina Feder
` (8 preceding siblings ...)
2026-06-22 20:54 ` [PATCH 09/10] vmcore_tasks: Add main entry point and backtrace parser Pnina Feder
@ 2026-06-22 20:54 ` Pnina Feder
2026-06-22 21:41 ` [PATCH] vmcore-tasks: lightweight task and backtrace extractor for vmcore files Pnina Feder
2026-07-08 7:38 ` [PATCH 00/10] " Pnina Feder
11 siblings, 0 replies; 15+ messages in thread
From: Pnina Feder @ 2026-06-22 20:54 UTC (permalink / raw)
To: kexec; +Cc: horms, Pnina Feder
Add comprehensive README covering:
- Project overview and motivation
- Code structure and runtime flow
- Vmcoreinfo requirements for common subsystems (tasks, VMA,
memory, signal frames) and per-architecture (RISC-V 64, MIPS64)
- Key design decisions
- Build and usage instructions
- Testing methodology and platform coverage
- Known limitations
Signed-off-by: Pnina Feder <pnina.feder@mobileye.com>
---
vmcore_tasks/README.md | 331 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 331 insertions(+)
create mode 100644 vmcore_tasks/README.md
diff --git a/vmcore_tasks/README.md b/vmcore_tasks/README.md
new file mode 100644
index 00000000..7abbc3ce
--- /dev/null
+++ b/vmcore_tasks/README.md
@@ -0,0 +1,331 @@
+# vmcore-tasks
+
+## Overview
+
+vmcore-tasks is a userspace tool that extracts per-task mini coredumps with register state and user-space backtraces and dmesg from Linux vmcore (kdump) files. It is designed for post-mortem
+debugging of kernel crashes on embedded targets where full crash-utility support
+may not be available.
+
+Unlike crash-utility, vmcore-tasks does **not** require vmlinux, DWARF data, or
+symbol tables. Instead, it relies on a **kernel-side component** that exports
+the required struct offsets, sizes, and symbol addresses into the standard
+vmcoreinfo note section. This data is embedded in the vmcore by kdump and is
+sufficient to navigate kernel data structures at runtime.
+
+Currently supports RISC-V 64-bit and MIPS64 architectures.
+
+## Motivation
+
+After a kernel panic, the kdump mechanism produces a vmcore file containing a
+snapshot of system memory. Existing tools (crash-utility, makedumpfile) are
+heavyweight. vmcore-tasks provides a lightweight alternative that extracts the most useful debugging artifacts:
+
+- Per-task mini coredumps with register state and user-space backtraces
+- Kernel dmesg log (all ringbuffer formats: legacy, structured, lockless)
+
+## Code Structure
+
+```
+vmcore_tasks.c Main entry point, orchestration
+ |
+ +-- tasks.c Task list walking, per-task data collection,
+ | mini coredump output
+ |
+ +-- vma_util.c VMA enumeration (maple tree or mmap linked list)
+ |
+ +-- unwind.c Generic user-space frame unwinder
+ |
+ +-- vmcore_tasks_defs.h Common types, accessor macros, arch dispatch
+ |
+ +-- arch/
+ | +-- riscv64/ RISC-V 64: SV39 page table walk, instruction decode
+ | | +-- riscv64.c
+ | | +-- riscv64_defs.h
+ | +-- mips64/ MIPS64: 3-level page table walk, instruction decode
+ | +-- mips64.c
+ | +-- mips64_defs.h
+ |
+ +-- util_lib/ Foundation libraries
+ +-- vmcore_info.c Vmcoreinfo parser (SYMBOL/OFFSET/SIZE/NUMBER/LENGTH)
+ +-- memory.c readmem(), virtual-to-physical dispatch
+ +-- maple_tree.c Maple tree walker (from crash-utility)
+ +-- include/
+ +-- vmcore_tasks_util.h Shared macros, typedefs, debug helpers
+ +-- vmcore_info.h Vmcoreinfo API declarations
+ +-- memory.h Memory API declarations
+ +-- maple_tree.h Maple tree API declarations
+```
+
+## Runtime Flow
+
+1. **ELF parsing**: Open vmcore, parse ELF headers, locate PT_LOAD
+ segments and vmcoreinfo note.
+
+2. **Vmcoreinfo init**: Parse all SYMBOL/OFFSET/SIZE/NUMBER/LENGTH
+ entries and plain KEY=VALUE pairs (PAGESIZE, OSRELEASE) into
+ lookup tables.
+
+3. **Architecture init**: Based on compile-time target (RISCV64/MIPS64),
+ register vtop function, unwind callbacks, and read arch-specific
+ parameters from vmcoreinfo.
+
+4. **Memory init**: Read PAGE_OFFSET, PHYS_OFFSET, VMALLOC range from
+ vmcoreinfo. `readmem()` translates virtual addresses to physical via
+ arch vtop, then maps to file offsets via PT_LOAD segments.
+
+5. **Task enumeration**: Walk `init_task` -> `task_struct` linked list.
+ For each task:
+ - Read PID, comm, mm_struct, register state (pt_regs from stack)
+ - Enumerate VMAs via maple tree (kernel >= 6.1) or mmap linked
+ list (older kernels), auto-selected based on vmcoreinfo
+ - Run user-space stack unwinder using arch instruction callbacks
+ - Output mini coredump with NT_PRSTATUS, NT_PRPSINFO, VMA map,
+ and backtrace
+
+6. **Dmesg extraction**: Extract kernel log from the vmcore using the
+ appropriate ringbuffer format (auto-detected from vmcoreinfo symbols).
+
+## Vmcoreinfo Requirements
+
+The kernel-side component must export the following entries into vmcoreinfo.
+Standard kdump entries (marked with \*) are already present in mainline Linux.
+
+### Common (all architectures)
+
+**Plain key=value:**
+
+| Entry | Notes |
+|-------|-------|
+| `PAGESIZE` \* | Runtime page size |
+| `OSRELEASE` \* | Kernel version string |
+
+**Symbols:**
+
+| Entry | Used by |
+|-------|---------|
+| `SYMBOL(swapper_pg_dir)` | Kernel page table base |
+| `SYMBOL(init_task)` | Task list head |
+
+**Numbers:**
+
+| Entry | Notes |
+|-------|-------|
+| `NUMBER(PAGE_OFFSET)` \* | Kernel direct-map base |
+| `NUMBER(phys_ram_base)` | Physical RAM base (fallback: `PHYS_OFFSET`) |
+| `NUMBER(VMALLOC_START)` | Optional - vmalloc range |
+| `NUMBER(VMALLOC_END)` | Optional - vmalloc range |
+| `NUMBER(VMEMMAP_START)` | Optional - vmemmap range |
+| `NUMBER(VMEMMAP_END)` | Optional - vmemmap range |
+| `NUMBER(MODULES_VADDR)` | Optional - module range |
+| `NUMBER(MODULES_END)` | Optional - module range |
+| `NUMBER(KERNEL_LINK_ADDR)` | Optional - kernel link address |
+| `NUMBER(va_kernel_pa_offset)` | Optional - kernel VA-PA offset |
+| `NUMBER(STACK_ALIGN)` | Stack alignment for pt_regs location |
+| `NUMBER(THREAD_SIZE)` | Kernel thread stack size |
+
+**Sizes:**
+
+| Entry | Used by |
+|-------|---------|
+| `SIZE(task_struct)` | Task walking |
+| `SIZE(pt_regs)` | Register extraction |
+| `SIZE(vm_area_struct)` | VMA reading |
+| `SIZE(file)` | File path resolution |
+| `SIZE(dentry)` | File path resolution |
+| `SIZE(maple_tree)` | Maple tree support (optional) |
+| `SIZE(maple_node)` | Maple tree support (optional) |
+
+**Offsets - task_struct:**
+
+| Entry |
+|-------|
+| `OFFSET(task_struct.__state)` (or `.state` for older kernels) |
+| `OFFSET(task_struct.pid)` |
+| `OFFSET(task_struct.comm)` |
+| `OFFSET(task_struct.mm)` |
+| `OFFSET(task_struct.flags)` |
+| `OFFSET(task_struct.exit_state)` |
+| `OFFSET(task_struct.stack)` |
+| `OFFSET(task_struct.tasks)` |
+| `OFFSET(task_struct.signal)` |
+| `OFFSET(task_struct.thread_node)` |
+
+**Offsets - signal_struct:**
+
+| Entry |
+|-------|
+| `OFFSET(signal_struct.thread_head)` |
+| `OFFSET(signal_struct.nr_threads)` |
+
+**Offsets - mm_struct:**
+
+| Entry | Notes |
+|-------|-------|
+| `OFFSET(mm_struct.mmap)` | Legacy VMA path (optional if maple tree used) |
+| `OFFSET(mm_struct.mm_mt)` | Maple tree VMA path (kernel >= 6.1) |
+| `OFFSET(mm_struct.map_count)` | Optional |
+| `OFFSET(mm_struct.pgd)` | User page table base |
+| `OFFSET(mm_struct.start_stack)` | VMA type classification |
+| `OFFSET(mm_struct.start_brk)` | VMA type classification |
+| `OFFSET(mm_struct.brk)` | VMA type classification |
+
+**Offsets - vm_area_struct:**
+
+| Entry | Notes |
+|-------|-------|
+| `OFFSET(vm_area_struct.vm_start)` | |
+| `OFFSET(vm_area_struct.vm_end)` | |
+| `OFFSET(vm_area_struct.vm_flags)` | |
+| `OFFSET(vm_area_struct.vm_file)` | |
+| `OFFSET(vm_area_struct.vm_next)` | Legacy path only |
+
+**Offsets - file/dentry (filename resolution):**
+
+| Entry |
+|-------|
+| `OFFSET(file.f_path)` |
+| `OFFSET(path.dentry)` |
+| `OFFSET(dentry.d_name)` |
+| `OFFSET(dentry.d_parent)` |
+| `OFFSET(qstr.hash_len)` |
+| `OFFSET(qstr.name)` |
+
+**Offsets - list_head:**
+
+| Entry |
+|-------|
+| `OFFSET(list_head.next)` |
+
+**Offsets - maple tree (optional, needed for kernel >= 6.1):**
+
+| Entry |
+|-------|
+| `OFFSET(maple_tree.ma_root)` |
+| `OFFSET(maple_tree.ma_flags)` |
+| `OFFSET(maple_node.parent)` |
+| `OFFSET(maple_node.ma64)` |
+| `OFFSET(maple_node.mr64)` |
+| `OFFSET(maple_node.slot)` |
+| `OFFSET(maple_arange_64.pivot)` |
+| `OFFSET(maple_arange_64.slot)` |
+| `OFFSET(maple_arange_64.gap)` |
+| `OFFSET(maple_arange_64.meta)` |
+| `OFFSET(maple_range_64.pivot)` |
+| `OFFSET(maple_range_64.slot)` |
+| `OFFSET(maple_metadata.end)` |
+| `OFFSET(maple_metadata.gap)` |
+
+### RISC-V 64-bit
+
+RISC-V uses SV39 virtual address translation (3-level page table).
+The unwinder decodes RISC-V standard (32-bit) instructions to trace stack frames and detect signal trampolines.
+
+| Entry | Type | Notes |
+|-------|------|-------|
+| `NUMBER(VA_BITS)` | NUMBER | Selects SV39/SV48/SV57 |
+| `NUMBER(__NR_rt_sigreturn)` | NUMBER | Signal frame detection (optional) |
+| `OFFSET(rt_sigframe.uc)` | OFFSET | Signal frame unwinding |
+| `OFFSET(ucontext.uc_mcontext)` | OFFSET | Signal frame unwinding |
+| `OFFSET(sigcontext.sc_regs)` | OFFSET | Signal frame unwinding |
+
+### MIPS64
+
+MIPS64 uses a 3-level page table (PGD -> PMD -> PTE). Page table bit
+layouts vary between kernel configurations, so the tool reads them from
+vmcoreinfo rather than hardcoding. Address space regions (XKPHYS, KSEG0,
+KSEG1, XKSEG) are identified by fixed address ranges.
+
+| Entry | Type | Notes |
+|-------|------|-------|
+| `NUMBER(_PAGE_PRESENT)` | NUMBER | Page table flags (optional, has defaults) |
+| `NUMBER(_PAGE_VALID)` | NUMBER | Page table flags (optional, has defaults) |
+| `NUMBER(_PFN_SHIFT)` | NUMBER | PTE to PFN extraction |
+| `NUMBER(_PFN_MASK)` | NUMBER | PTE to PFN extraction |
+| `NUMBER(PMD_SHIFT)` | NUMBER | Page table geometry |
+| `NUMBER(PGDIR_SHIFT)` | NUMBER | Page table geometry |
+| `NUMBER(PTRS_PER_PGD)` | NUMBER | Page table geometry |
+| `NUMBER(PTRS_PER_PMD)` | NUMBER | Page table geometry |
+| `NUMBER(PTRS_PER_PTE)` | NUMBER | Page table geometry |
+| `NUMBER(_MAX_PHYSMEM_BITS)` | NUMBER | Physical address width (optional) |
+| `OFFSET(rt_sigframe.rs_uc)` | OFFSET | Signal frame unwinding |
+| `OFFSET(ucontext.uc_mcontext)` | OFFSET | Signal frame unwinding |
+| `OFFSET(sigcontext.sc_regs)` | OFFSET | Signal frame unwinding |
+| `OFFSET(sigcontext.sc_pc)` | OFFSET | Signal frame unwinding |
+
+## Key Design Decisions
+
+- **No vmlinux/DWARF required**: All struct layout information is provided
+ by the kernel-side vmcoreinfo additions. The tool is self-contained.
+
+- **Page size at runtime**: Not hardcoded. Read from vmcoreinfo PAGESIZE
+ entry via cached `get_page_size()`/`get_page_shift()` accessors.
+
+- **VMA enumeration strategy**: Auto-selected based on vmcoreinfo.
+ If `mm_struct.mmap` offset exists, uses legacy linked list walk.
+ Otherwise uses maple tree (kernel >= 6.1).
+
+- **Unwinder separation**: Generic unwinding logic in `unwind.c`,
+ arch-specific instruction decode in `arch/*/`. Adding a new
+ architecture requires only implementing the instruction callbacks
+ (`is_sp_move_ins`, `is_ra_save_ins`, etc.), not the unwinding state
+ machine.
+
+- **elf_info.c and maple_tree.c**: Ported from kexec-tools and
+ crash-utility respectively. Kept close to upstream to ease future
+ syncs, with minimal modifications.
+
+## Build
+
+```bash
+# Auto-detect architecture from cross-compiler:
+make CC=riscv64-linux-gnu-gcc
+
+# Or specify explicitly:
+make CC=mips-linux-gnu-gcc TARGET=MIPS64
+
+# Native build (for development/testing on x86_64):
+make TARGET=RISCV64
+```
+
+## Usage
+
+```bash
+# Extract all artifacts from vmcore:
+vmcore-tasks <vmcore_file>
+
+# With debug output (levels 1-4):
+vmcore-tasks -d1 <vmcore_file>
+
+# Full trace to file:
+vmcore-tasks -d4 <vmcore_file> 2>log.txt
+
+# Parse backtrace output with addr2line:
+python3 vmcore_tasks_backtrace_parser.py -b tasks_buffer.txt -s <symbols_path> -t <toolchain_path>
+```
+
+## Testing
+
+Validated against vmcore files generated from controlled kernel panic
+scenarios on the following platforms:
+
+| Platform | Page Table | Test Scenarios |
+|----------|-----------|----------------|
+| RISC-V 64-bit (SV39) | 3-level | Assert handler, leaf application, signal frame |
+| MIPS64 | 3-level | Assert handler, leaf application, signal frame |
+
+Each test scenario exercises a different unwinding path:
+
+- **Assert handler**: Multi-frame user-space backtrace through libc `abort()`.
+- **Leaf application**: Function without stack frame setup (leaf function at
+ top of stack).
+- **Signal frame**: User-space signal handler active at time of crash,
+ validates `rt_sigframe` unwinding.
+
+Tested with vmcore-tasks running on:
+- Native RISC-V 64-bit and MIPS64 targets
+- Cross-architecture: x86_64 host processing RISC-V/MIPS64 vmcores
+
+## TODO / Known Limitations
+
+- SV48/SV57 page table translation not yet implemented for RISC-V
+- Compressed instruction support (16 bit) for RISCV not yet implemented
\ No newline at end of file
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread* Re: [PATCH] vmcore-tasks: lightweight task and backtrace extractor for vmcore files
2026-06-22 20:54 [PATCH 00/10] vmcore-tasks: lightweight task and backtrace extractor for vmcore files Pnina Feder
` (9 preceding siblings ...)
2026-06-22 20:54 ` [PATCH 10/10] vmcore_tasks: Add README with project documentation Pnina Feder
@ 2026-06-22 21:41 ` Pnina Feder
2026-07-08 7:38 ` [PATCH 00/10] " Pnina Feder
11 siblings, 0 replies; 15+ messages in thread
From: Pnina Feder @ 2026-06-22 21:41 UTC (permalink / raw)
To: pnina.feder; +Cc: horms, kexec
This patch depends on the following linux-kernel patch series
which exports the new vmcoreinfo fields consumed here:
Link: https://lore.kernel.org/all/20260622211430.4008899-1-pnina.feder@mobileye.com/
^ permalink raw reply [flat|nested] 15+ messages in thread* RE: [PATCH 00/10] vmcore-tasks: lightweight task and backtrace extractor for vmcore files
2026-06-22 20:54 [PATCH 00/10] vmcore-tasks: lightweight task and backtrace extractor for vmcore files Pnina Feder
` (10 preceding siblings ...)
2026-06-22 21:41 ` [PATCH] vmcore-tasks: lightweight task and backtrace extractor for vmcore files Pnina Feder
@ 2026-07-08 7:38 ` Pnina Feder
11 siblings, 0 replies; 15+ messages in thread
From: Pnina Feder @ 2026-07-08 7:38 UTC (permalink / raw)
To: Pnina Feder, kexec@lists.infradead.org
Cc: horms@kernel.org, Gregory Greenman, Vladimir Kondratiev
Hi,
Did you have a chance to look at it?
I got a response from Linux kernel saying the kernel patch looks ok and they want to know if you are interested in it.
https://lore.kernel.org/all/20260622211430.4008899-1-pnina.feder@mobileye.com/
Thanks,
Pnina
> This series adds vmcore-tasks, a new userspace tool for extracting per-task debugging information from Linux vmcore (kdump) files. It is designed as a lightweight tool, targeting embedded systems where crash-utility may not be available.
>
> Motivation
> ----------
>
> After a kernel panic, kdump produces a vmcore containing a full memory snapshot. Existing post-mortem tools like crash-utility are powerful but heavyweight — they require vmlinux with DWARF debug info and a matching kernel symbol table. On embedded targets with limited storage, stripped kernels, or unsupported architectures, this is often not practical.
>
> vmcore-tasks takes a different approach: it requires no vmlinux or DWARF data. Instead, a kernel-side component exports the necessary struct offsets, sizes, and symbol addresses into the standard vmcoreinfo note section. This data, already embedded in the vmcore by kdump, is sufficient to navigate kernel data structures, allowing it to run directly from crash-kernel, and only solving bt addresses later with tools like addr2line.
>
> vmcore-tasks extract:
>
> - Per-task register state (pt_regs) and user-space backtraces
> - VMA memory maps with filename resolution via dentry walk
> - Per-thread state for multi-threaded processes
> - Kernel dmesg log (reuses existing vmcore-dmesg infrastructure)
>
> Architecture
> ------------
>
> The tool is structured in layers:
>
> util_lib/ Foundation: vmcoreinfo parser, memory reader,
> maple tree walker (reuses existing elf_info.c
> and adds new shared utilities)
>
> vmcore_tasks/ Core logic: task enumeration, VMA collection,
> generic stack unwinder with arch callbacks
>
> vmcore_tasks/arch/ Architecture backends: page table translation
> and instruction decode (RISC-V 64, MIPS64)
>
> The generic unwinder in unwind.c drives frame recovery using arch-provided callbacks (detect SP adjustment, RA save, signal trampolines), so adding a new architecture requires only implementing the instruction decode — not the unwinding state machine.
>
> VMA enumeration auto-selects between the legacy mm_struct->mmap linked list (kernels < 6.1) and maple tree walk (kernels >= 6.1) based on vmcoreinfo content.
>
> Integration with kexec-tools
> ----------------------------
>
> The new files are placed in vmcore_tasks/ and util_lib/.
> The util_lib additions (vmcore_info.c, memory.c, maple_tree.c) are currently built only by the vmcore_tasks Makefile.
> Integration into the main kexec-tools build system can follow once the tool matures.
>
> The tool reuses elf_info.c from util_lib for ELF parsing and
> paddr_to_offset() for physical-to-file-offset mapping, and reuses
> dump_dmesg() from vmcore-dmesg for kernel log extraction.
>
> Depends on kernel-side patch to export the needed vmcoreinfo values.
> Will update with a link to this patch later.
>
> Testing
> -------
>
> Validated against vmcore files from controlled kernel panic scenarios on RISC-V 64-bit (SV39) and MIPS64 platforms, covering multi-frame backtraces, leaf functions, and signal frame unwinding. Tested both natively and cross-architecture (x86_64 host processing RISC-V/MIPS64 vmcores).
>
> Pnina Feder (10):
> util_lib: add paddr_to_offset function and expose raw vmcoreinfo data
> util_lib: Add vmcoreinfo parser and memory reader
> util_lib: Add maple tree walker for VMA enumeration
> vmcore_tasks: Add common definitions and project infrastructure
> vmcore_tasks: arch: Add RISC-V 64-bit architecture support
> vmcore_tasks: arch: Add MIPS64 architecture support
> vmcore_tasks: Add generic user-space stack unwinder
> vmcore_tasks: Add VMA utilities and task enumeration
> vmcore_tasks: Add main entry point and backtrace parser
> vmcore_tasks: Add README with project documentation
^ permalink raw reply [flat|nested] 15+ messages in thread