[PATCH 08/10] vmcore_tasks: Add VMA utilities and task enumeration
Pnina Feder
pnina.feder at mobileye.com
Mon Jun 22 13:54:46 PDT 2026
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 at 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
More information about the kexec
mailing list