[PATCH 08/10] vmcore_tasks: Add VMA utilities and task enumeration

Pnina Feder <[email protected]>
Newsgroups org.infradead.lists.kexec
Message-ID <[email protected]>
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 <[email protected]>
---
 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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.