From 25fcd20044aac65d049baa493862f53327783aa9 Mon Sep 17 00:00:00 2001
From: Vinayak Menon <vinayakm.list@gmail.com>
Date: Wed, 23 Jul 2014 11:30:40 +0530
Subject: [PATCH] raw RAM image support

Raw RAM image can be passed to crash utility in the
following format.

> crash [options] image@address... [namelist] [-o out_file]
image@address is an ordered pair where, image is the raw
RAM file representing a memory node, and address is the
physical start of the node. Multiple such ordered pairs can
be provided, which represent different memory nodes.

The raw dump is converted to kdump format.

Optionally -o can be used to specify the ELF output file
to be generated.

Signed-off-by: Vinayak Menon <vinayakm.list@gmail.com>
---
 Makefile  |   9 +-
 defs.h    |  10 ++
 help.c    |  15 ++-
 main.c    |  30 +++++-
 netdump.c |  11 ++
 ramdump.c | 338 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 408 insertions(+), 5 deletions(-)
 create mode 100644 ramdump.c

diff --git a/Makefile b/Makefile
index cf3ae44..64616fc 100644
--- a/Makefile
+++ b/Makefile
@@ -68,7 +68,8 @@ CFILES=main.c tools.c global_data.c memory.c filesys.c help.c task.c \
 	netdump.c diskdump.c makedumpfile.c xendump.c unwind.c unwind_decoder.c \
 	unwind_x86_32_64.c unwind_arm.c \
 	xen_hyper.c xen_hyper_command.c xen_hyper_global_data.c \
-	xen_hyper_dump_tables.c kvmdump.c qemu.c qemu-load.c sadump.c ipcs.c
+	xen_hyper_dump_tables.c kvmdump.c qemu.c qemu-load.c sadump.c ipcs.c \
+	ramdump.c
 
 SOURCE_FILES=${CFILES} ${GENERIC_HFILES} ${MCORE_HFILES} \
 	${REDHAT_CFILES} ${REDHAT_HFILES} ${UNWIND_HFILES} \
@@ -85,7 +86,8 @@ OBJECT_FILES=main.o tools.o global_data.o memory.o filesys.o help.o task.o \
 	lkcd_x86_trace.o unwind_v1.o unwind_v2.o unwind_v3.o \
 	unwind_x86_32_64.o unwind_arm.o \
 	xen_hyper.o xen_hyper_command.o xen_hyper_global_data.o \
-	xen_hyper_dump_tables.o kvmdump.o qemu.o qemu-load.o sadump.o ipcs.o
+	xen_hyper_dump_tables.o kvmdump.o qemu.o qemu-load.o sadump.o ipcs.o \
+	ramdump.o
 
 MEMORY_DRIVER_FILES=memory_driver/Makefile memory_driver/crash.c memory_driver/README
 
@@ -485,6 +487,9 @@ xen_hyper_global_data.o: ${GENERIC_HFILES} xen_hyper_global_data.c
 xen_hyper_dump_tables.o: ${GENERIC_HFILES} xen_hyper_dump_tables.c
 	${CC} -c ${CRASH_CFLAGS} xen_hyper_dump_tables.c ${WARNING_OPTIONS} ${WARNING_ERROR}
 
+ramdump.o: ${GENERIC_HFILES} ${REDHAT_HFILES} ramdump.c
+	${CC} -c ${CRASH_CFLAGS} ramdump.c ${WARNING_OPTIONS} ${WARNING_ERROR}
+
 ${PROGRAM}: force
 	@make --no-print-directory all
 
diff --git a/defs.h b/defs.h
index 2737b10..b1c3ca8 100644
--- a/defs.h
+++ b/defs.h
@@ -503,6 +503,7 @@ struct program_context {
 #define VMCOREINFO    (0x400ULL)
 #define ALLOW_FP      (0x800ULL)
 #define REM_PAUSED_F (0x1000ULL)
+#define RAMDUMP	(0x2000ULL)
 #define REMOTE_PAUSED() (pc->flags2 & REM_PAUSED_F)
 	char *cleanup;
 	char *namelist_orig;
@@ -5524,6 +5525,15 @@ void dump_registers_for_qemu_mem_dump(void);
 void kdump_backup_region_init(void);
 
 /*
+ * ramdump.c
+ */
+int is_ramdump(char *pattern);
+char *ramdump_to_elf(void);
+void ramdump_elf_output_file(char *opt);
+void ramdump_cleanup(void);
+int read_ramdump(int fd, void *bufptr, int cnt, ulong addr, physaddr_t paddr);
+
+/*
  *  diskdump.c
  */
 int is_diskdump(char *);
diff --git a/help.c b/help.c
index e1f265a..a35ba9c 100644
--- a/help.c
+++ b/help.c
@@ -47,8 +47,9 @@ char *program_usage_info[] = {
     "",
     "USAGE:",
     "",
-    "  crash [OPTION]... NAMELIST MEMORY-IMAGE  (dumpfile form)",
-    "  crash [OPTION]... [NAMELIST]             (live system form)",
+    "  crash [OPTION]... NAMELIST MEMORY-IMAGE  		(dumpfile form)",
+    "  crash [OPTION]... NAMELIST MEMORY-IMAGE@ADDRESS...	(dumpfile form)",
+    "  crash [OPTION]... [NAMELIST]             		(live system form)",
     "",
     "OPTIONS:",
     "",
@@ -69,6 +70,11 @@ char *program_usage_info[] = {
     "    /dev/mem will be used; but if the kernel has been configured with ",
     "    CONFIG_STRICT_DEVMEM, then /proc/kcore will be used.  It is permissible",
     "    to explicitly enter /dev/crash, /dev/mem or /proc/kcore.",
+    "    @ADDRESS is used along with MEMORY-IMAGE when the dumpfile is a raw dump",
+    "    which needs a conversion to kernel core dump format. If -o option is not",
+    "    used, the kdump ELF file is created in /var/tmp/ and is unlinked on crash",
+    "    exit. Multiple MEMORY-IMAGE@ADDRESS ordered pairs can be used, each",
+    "    representing a memory node. ADDRESS is the physical start address of the node",
     "",
     "  mapfile",
     "    If the NAMELIST file is not the same kernel that is running",
@@ -139,6 +145,11 @@ char *program_usage_info[] = {
     "    architecture-specific option/pairs should only be required in",
     "    very rare circumstances:",
     "",
+    "  -o file",
+    "  Can be used with the MEMORY-IMAGE@ADDRESS format, to specify",
+    "  the path and name of the kdump ELF generated. The file thus generated",
+    "  will not be unlinked on crash exit, and can used as kdump input to crash.",
+    "",
     "    X86_64:",
     "      phys_base=<physical-address>",
     "      irq_eframe_link=<value>",
diff --git a/main.c b/main.c
index b0524d2..6d47548 100644
--- a/main.c
+++ b/main.c
@@ -86,7 +86,7 @@ main(int argc, char **argv)
 	 */
 	opterr = 0;
 	optind = 0;
-	while((c = getopt_long(argc, argv, "Lkgh::e:i:sSvc:d:tfp:m:x",
+	while((c = getopt_long(argc, argv, "Lkgh::e:i:sSvc:d:tfp:m:xo:",
        		long_options, &option_index)) != -1) {
 		switch (c)
 		{
@@ -387,6 +387,10 @@ main(int argc, char **argv)
 			pc->flags |= PRELOAD_EXTENSIONS;
 			break;
 
+		case 'o':
+			ramdump_elf_output_file(optarg);
+			break;
+
 		default:
 			error(INFO, "invalid option: %s\n",
 				argv[optind-1]);
@@ -402,6 +406,29 @@ main(int argc, char **argv)
 	 */
 	while (argv[optind]) {
 
+		if (is_ramdump(argv[optind])) {
+			if (pc->flags & MEMORY_SOURCES) {
+				error(INFO,
+					"too many dumpfile arguments\n");
+					program_usage(SHORT_FORM);
+			}
+			pc->dumpfile = ramdump_to_elf();
+			if (is_kdump(pc->dumpfile, KDUMP_LOCAL)) {
+				pc->flags |= KDUMP;
+				if (pc->flags2 & RAMDUMP)
+					pc->readmem = read_ramdump;
+				else
+					pc->readmem = read_kdump;
+				pc->writemem = NULL;
+			} else {
+				error(INFO, "malformed ELF file: %s\n",
+					pc->dumpfile);
+				program_usage(SHORT_FORM);
+			}
+			optind++;
+			continue;
+		}
+
 		if (is_remote_daemon(argv[optind])) {
                 	if (pc->flags & DUMPFILE_TYPES) {
 				error(INFO, 
@@ -1699,6 +1726,7 @@ clean_exit(int status)
 	if (pc->cleanup && file_exists(pc->cleanup, NULL))
 		unlink(pc->cleanup);
 
+	ramdump_cleanup();
 	exit(status);
 }
 
diff --git a/netdump.c b/netdump.c
index c86a992..043c7f7 100644
--- a/netdump.c
+++ b/netdump.c
@@ -249,6 +249,12 @@ is_netdump(char *file, ulong source_query)
 				goto bailout;
 			break;
 
+		case EM_ARM:
+			if (machine_type_mismatch(file, "ARM", NULL,
+			    source_query))
+				goto bailout;
+			break;
+
 		case EM_AARCH64:
 			if (machine_type_mismatch(file, "ARM64", NULL,
 			    source_query))
@@ -476,6 +482,11 @@ check_dumpfile_size(char *file)
 	struct pt_load_segment *pls;
 	uint64_t segment_end;
 
+	if (pc->flags2 & RAMDUMP) {
+		error(NOTE, "Skipping dumpfile size check for ramdump\n");
+		return;
+	}
+
 	if (stat64(file, &stat) < 0)
 		return;
 
diff --git a/ramdump.c b/ramdump.c
new file mode 100644
index 0000000..2d5824a
--- /dev/null
+++ b/ramdump.c
@@ -0,0 +1,338 @@
+/*
+ * ramdump.c - core analysis suite
+ *
+ * Copyright (c) 2014 Vinayak Menon <vinayakm.list@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * Author: Vinayak Menon <vinayakm.list@gmail.com>
+ */
+
+#define _LARGEFILE64_SOURCE 1  /* stat64() */
+#include "defs.h"
+#include <libelf.h>
+
+struct ramdump_def {
+	char *path;
+	int rfd;
+	ulong start_paddr;
+	ulong end_paddr;
+};
+
+static struct ramdump_def *ramdump;
+static int nodes;
+static char *user_elf;
+static char elf_default[] = "/var/tmp/ramdump_elfXXXXXX";
+
+static void alloc_elf_header(Elf64_Ehdr *ehdr, ushort e_machine)
+{
+	memcpy(ehdr->e_ident, ELFMAG, SELFMAG);
+	ehdr->e_ident[EI_CLASS] = ELFCLASS64;
+	ehdr->e_ident[EI_DATA] = ELFDATA2LSB;
+	ehdr->e_ident[EI_VERSION] = EV_CURRENT;
+	ehdr->e_ident[EI_OSABI] = ELFOSABI_LINUX;
+	ehdr->e_ident[EI_ABIVERSION] = 0;
+	memset(ehdr->e_ident+EI_PAD, 0,
+		EI_NIDENT-EI_PAD);
+	ehdr->e_type = ET_CORE;
+	ehdr->e_machine = e_machine;
+	ehdr->e_version = EV_CURRENT;
+	ehdr->e_entry = 0;
+	ehdr->e_phoff = sizeof(Elf64_Ehdr);
+	ehdr->e_shoff = 0;
+	ehdr->e_flags = 0;
+	ehdr->e_ehsize = sizeof(Elf64_Ehdr);
+	ehdr->e_phentsize = sizeof(Elf64_Phdr);
+	ehdr->e_phnum = 1 + nodes;
+	ehdr->e_shentsize = 0;
+	ehdr->e_shnum = 0;
+	ehdr->e_shstrndx = 0;
+}
+
+static int alloc_program_headers(Elf64_Phdr *phdr)
+{
+	unsigned int i;
+	struct stat64 st;
+
+	for (i = 0; i < nodes; i++, phdr++) {
+		phdr[i].p_type = PT_LOAD;
+
+		if (0 > stat64(ramdump[i].path, &st)) {
+			error(INFO, "ramdump stat failed\n");
+			return -1;
+		}
+
+		phdr[i].p_filesz = st.st_size;
+		phdr[i].p_memsz = phdr[i].p_filesz;
+		phdr[i].p_vaddr = 0;
+		phdr[i].p_paddr = ramdump[i].start_paddr;
+		ramdump[i].end_paddr = ramdump[i].start_paddr + st.st_size - 1;
+		phdr[i].p_flags = PF_R | PF_W | PF_X;
+		phdr[i].p_align = 0;
+	}
+
+	return 0;
+}
+
+static char *write_elf(Elf64_Phdr *load, Elf64_Ehdr *e_head, size_t data_offset)
+{
+#define CPY_BUF_SZ 4096
+	int fd1, fd2, i, err = 1;
+	char *buf;
+	char *out_elf;
+	size_t rd, len, offset;
+
+	buf = (char *)GETBUF(CPY_BUF_SZ);
+
+	offset = data_offset;
+
+	if (user_elf) {
+		fd2 = open(user_elf, O_CREAT|O_RDWR, S_IRUSR|S_IWUSR);
+		if (fd2 < 0) {
+			error(INFO, "%s open error, %s\n",
+				user_elf, strerror(errno));
+			goto end1;
+		}
+		out_elf = user_elf;
+	} else {
+		fd2 = mkstemp(elf_default);
+		if (fd2 < 0) {
+			error(INFO, "%s open error, %s\n",
+				elf_default, strerror(errno));
+			goto end1;
+		}
+		out_elf = elf_default;
+		pc->flags2 |= RAMDUMP;
+	}
+
+	error(NOTE, "Creating kdump ELF %s\n", out_elf);
+
+	while (offset > 0) {
+		len = write(fd2, e_head + (data_offset - offset), offset);
+		if (len < 0) {
+			error(INFO, "ramdump write error, %s\n",
+				strerror(errno));
+			goto end;
+		}
+		offset -= len;
+	}
+
+	if (user_elf) {
+		for (i = 0; i < nodes; i++) {
+			offset = load[i].p_offset;
+
+			fd1 = open(ramdump[i].path, O_RDONLY, S_IRUSR);
+			if (fd1 < 0) {
+				error(INFO, "%s open error, %s\n",
+					ramdump[i].path, strerror(errno));
+				goto end;
+			}
+
+			lseek(fd2, (off_t)offset, SEEK_SET);
+			while ((rd = read(fd1, buf, CPY_BUF_SZ)) > 0) {
+				if (write(fd2, buf, rd) != rd) {
+					error(INFO, "%s write error, %s\n",
+						ramdump[i].path,
+						strerror(errno));
+					close(fd1);
+					goto end;
+				}
+			}
+			close(fd1);
+		}
+	}
+
+	err = 0;
+end:
+	close(fd2);
+end1:
+	FREEBUF(buf);
+	return err ? NULL : out_elf;
+}
+
+static void alloc_notes(Elf64_Phdr *notes)
+{
+	/* Nothing filled in as of now */
+	notes->p_type = PT_NOTE;
+	notes->p_offset = 0;
+	notes->p_vaddr = 0;
+	notes->p_paddr = 0;
+	notes->p_filesz = 0;
+	notes->p_memsz = 0;
+	notes->p_flags = 0;
+	notes->p_align = 0;
+}
+
+char *ramdump_to_elf(void)
+{
+	int i;
+	char *ptr, *e_file = NULL;
+	ushort e_machine = 0;
+	size_t offset, data_offset;
+	size_t l_offset;
+	Elf64_Phdr *notes, *load;
+	Elf64_Ehdr *e_head;
+
+	if (machine_type("ARM")) {
+		e_machine = EM_ARM;
+	} else if (machine_type("ARM64")) {
+		e_machine = EM_AARCH64;
+	} else {
+		error(INFO, "unknown machine type\n");
+		return NULL;
+	}
+
+	e_head = (Elf64_Ehdr *)GETBUF(sizeof(Elf64_Ehdr) +
+		nodes * sizeof(Elf64_Phdr) + PAGESIZE() * 2);
+	ptr = (char *)e_head;
+	offset = 0;
+
+	error(NOTE, "Converting ramdump to kdump\n");
+	alloc_elf_header(e_head, e_machine);
+
+	ptr += sizeof(Elf64_Ehdr);
+	offset += sizeof(Elf64_Ehdr);
+
+	notes = (Elf64_Phdr *)ptr;
+
+	alloc_notes(notes);
+
+	offset += sizeof(Elf64_Phdr);
+	ptr += sizeof(Elf64_Phdr);
+
+	load = (Elf64_Phdr *)ptr;
+
+	if (alloc_program_headers(load))
+		goto end;
+
+	offset += sizeof(Elf64_Phdr) * nodes;
+	ptr += sizeof(Elf64_Phdr) * nodes;
+
+	/* Empty note */
+	notes->p_offset = offset;
+
+	l_offset = offset;
+
+	data_offset = offset;
+
+	for (i = 0; i < nodes; i++) {
+		load[i].p_offset = l_offset;
+		l_offset += load[i].p_filesz;
+	}
+
+	e_file = write_elf(load, e_head, data_offset);
+end:
+	FREEBUF(e_head);
+	return e_file;
+}
+
+int is_ramdump(char *p)
+{
+	char *x = NULL, *y = NULL, *pat;
+	size_t len;
+	char *pattern;
+	int err = 0;
+
+	if (nodes || !strchr(p, '@'))
+		return 0;
+
+	len = strlen(p);
+	pattern = (char *)GETBUF(len + 1);
+	strlcpy(pattern, p, len + 1);
+
+	pat = pattern;
+	while ((pat = strtok_r(pat, ",", &x))) {
+		if ((pat = strtok_r(pat, "@", &y))) {
+			nodes++;
+			ramdump = realloc(ramdump,
+				sizeof(struct ramdump_def) * nodes);
+			if (!ramdump)
+				error(FATAL, "realloc failure\n");
+			ramdump[nodes - 1].path = pat;
+			pat = strtok_r(NULL, "@", &y);
+			ramdump[nodes - 1].start_paddr =
+				htol(pat, RETURN_ON_ERROR, &err);
+			if (err == TRUE)
+				error(FATAL, "Invalid ramdump address\n");
+			if ((ramdump[nodes - 1].rfd =
+				open(ramdump[nodes - 1].path, O_RDONLY)) < 0)
+					error(FATAL,
+						"ramdump %s open failed:%s\n",
+						ramdump[nodes - 1].path,
+						strerror(errno));
+		}
+
+		pat = NULL;
+	}
+
+	return nodes;
+}
+
+void ramdump_elf_output_file(char *opt)
+{
+	user_elf = opt;
+}
+
+void ramdump_cleanup(void)
+{
+	if (!user_elf)
+		unlink(elf_default);
+}
+
+int
+read_ramdump(int fd, void *bufptr, int cnt, ulong addr, physaddr_t paddr)
+{
+	off_t offset;
+	int i;
+	struct ramdump_def *r = &ramdump[0];
+
+	offset = 0;
+
+	if (nodes == 1) {
+		offset = (off_t)paddr - (off_t)ramdump[0].start_paddr;
+	} else {
+
+		for (i = offset = 0; i < nodes; i++) {
+			r = &ramdump[i];
+			if ((paddr >= r->start_paddr) &&
+			    (paddr < r->end_paddr)) {
+				offset = (off_t)paddr - (off_t)r->start_paddr;
+				break;
+			}
+		}
+		if (CRASHDEBUG(8))
+			fprintf(fp, "read_ramdump: READ_ERROR: "
+				    "offset not found for paddr: %llx\n",
+					(ulonglong)paddr);
+		return READ_ERROR;
+	}
+
+	if (CRASHDEBUG(8))
+		fprintf(fp,
+		"read_ramdump: addr: %lx paddr: %llx cnt: %d offset: %llx\n",
+			addr, (ulonglong)paddr, cnt, (ulonglong)offset);
+
+	if (lseek(r->rfd, offset, SEEK_SET) == -1) {
+		if (CRASHDEBUG(8))
+			fprintf(fp, "read_ramdump: SEEK_ERROR: "
+				"offset: %llx\n", (ulonglong)offset);
+		return SEEK_ERROR;
+	}
+
+	if (read(r->rfd, bufptr, cnt) != cnt) {
+		if (CRASHDEBUG(8))
+			fprintf(fp, "read_ramdump: READ_ERROR: "
+				"offset: %llx\n", (ulonglong)offset);
+		return READ_ERROR;
+	}
+
+        return cnt;
+}
-- 
1.9.1

