[PATCH 02/10] util_lib: Add vmcoreinfo parser and memory reader

Pnina Feder <[email protected]>
Newsgroups org.infradead.lists.kexec
Message-ID <[email protected]>
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 <[email protected]>
---
 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
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.