summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVivek Goyal <vgoyal@in.ibm.com>2006-09-21 14:51:44 -0400
committerSimon Horman <horms@verge.net.au>2006-10-06 12:46:16 +0900
commit103c946e39eb9dd57ccbda20ac12ccb01f883c17 (patch)
tree439847d98ca015f0675690fd61cb3a16dd1c7216
parentf11123aa03a7e8bfee165a56f7f1f6d8baa1d163 (diff)
kexec-tools: x86_64 read kernel vaddr and size from /proc/kcore
o With relocatable kernel in picture, the kernel text map offset (__START_KERNEL_map) is no longer constant. It depends on where kernel is loaded. o Now /proc/kcore is read to determine the virtual address the kernel is mapped at and /porc/iomem is read for determining the physical address where kernel is loaded at. This information is enough to create to fill virtual address and phy addr info for elf header mapping kernel text and data. o Virtual address for kernel text are needed by gdb as well as crash to retrieve the meaningful data from core file. o This patch requires "elf note memsz" fix in the kernel. Currently that fix is in -mm tree. It will still work with older kernels. It will display the warning messages (/proc/kcore could not be parsed) and hardcode the kernel virtual address and size. Signed-off-by: Vivek Goyal <vgoyal@in.ibm.com> Signed-off-by: Simon Horman <horms@verge.net.au>
-rw-r--r--kexec/Makefile1
-rw-r--r--kexec/arch/x86_64/crashdump-x86_64.c204
-rw-r--r--kexec/crashdump.h2
-rw-r--r--kexec/kexec-elf-core.c29
-rw-r--r--kexec/kexec-elf.c3
-rw-r--r--kexec/kexec-elf.h2
-rw-r--r--kexec/kexec.c44
-rw-r--r--kexec/kexec.h4
8 files changed, 263 insertions, 26 deletions
diff --git a/kexec/Makefile b/kexec/Makefile
index 7a26106..3d906a6 100644
--- a/kexec/Makefile
+++ b/kexec/Makefile
@@ -13,6 +13,7 @@ KEXEC_C_SRCS:= kexec/kexec.c
KEXEC_C_SRCS+= kexec/ifdown.c
KEXEC_C_SRCS+= kexec/kexec-elf.c
KEXEC_C_SRCS+= kexec/kexec-elf-exec.c
+KEXEC_C_SRCS+= kexec/kexec-elf-core.c
KEXEC_C_SRCS+= kexec/kexec-elf-rel.c
KEXEC_C_SRCS+= kexec/kexec-elf-boot.c
KEXEC_C_SRCS+= kexec/crashdump.c
diff --git a/kexec/arch/x86_64/crashdump-x86_64.c b/kexec/arch/x86_64/crashdump-x86_64.c
index e496485..9d9ab66 100644
--- a/kexec/arch/x86_64/crashdump-x86_64.c
+++ b/kexec/arch/x86_64/crashdump-x86_64.c
@@ -24,8 +24,10 @@
#include <errno.h>
#include <limits.h>
#include <elf.h>
+#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
+#include <sys/mman.h>
#include <unistd.h>
#include "../../kexec.h"
#include "../../kexec-elf.h"
@@ -38,6 +40,137 @@
/* Forward Declaration. */
static int exclude_crash_reserve_region(int *nr_ranges);
+#define KERN_VADDR_ALIGN 0x200000 /* 2MB */
+
+/* Read kernel physical load addr from /proc/iomem (Kernel Code) and
+ * store in kexec_info */
+static int get_kernel_paddr(struct kexec_info *info)
+{
+ const char iomem[]= "/proc/iomem";
+ char line[MAX_LINE];
+ FILE *fp;
+ unsigned long long start, end;
+
+ fp = fopen(iomem, "r");
+ if (!fp) {
+ fprintf(stderr, "Cannot open %s: %s\n", iomem, strerror(errno));
+ return -1;
+ }
+ while(fgets(line, sizeof(line), fp) != 0) {
+ char *str;
+ int consumed, count;
+ count = sscanf(line, "%Lx-%Lx : %n",
+ &start, &end, &consumed);
+ if (count != 2)
+ continue;
+ str = line + consumed;
+#ifdef DEBUG
+ printf("%016Lx-%016Lx : %s",
+ start, end, str);
+#endif
+ if (memcmp(str, "Kernel code\n", 12) == 0) {
+ info->kern_paddr_start = start;
+
+#ifdef DEBUG
+ printf("kernel load physical addr start = 0x%016Lx\n",
+ start);
+#endif
+ fclose(fp);
+ return 0;
+ }
+ }
+ fprintf(stderr, "Cannot determine kernel physical load addr\n");
+ fclose(fp);
+ return -1;
+}
+
+/* Hardcoding kernel virtual address and size. While writting
+ * this patch vanilla kernel is compiled for addr 2MB. Anybody
+ * using kernel older than that which was compiled for 1MB
+ * physical addr, use older version of kexec-tools. This function
+ * is there just for backward compatibility reasons and we should
+ * get rid of it at some point of time.
+ */
+
+static int hardcode_kernel_vaddr_size(struct kexec_info *info)
+{
+ fprintf(stderr, "Warning: Hardcoding kernel virtual addr and size\n");
+ info->kern_vaddr_start = __START_KERNEL_map + 0x200000;
+ info->kern_size = KERNEL_TEXT_SIZE - 0x200000;
+ fprintf(stderr, "Warning: virtual addr = 0x%lx size = 0x%lx\n",
+ info->kern_vaddr_start, info->kern_size);
+ return 0;
+}
+
+/* Retrieve info regarding virtual address kernel has been compiled for and
+ * size of the kernel from /proc/kcore. Current /proc/kcore parsing from
+ * from kexec-tools fails because of malformed elf notes. A kernel patch has
+ * been submitted. For the folks using older kernels, this function
+ * hard codes the values to remain backward compatible. Once things stablize
+ * we should get rid of backward compatible code. */
+
+static int get_kernel_vaddr_and_size(struct kexec_info *info)
+{
+ int result;
+ const char kcore[] = "/proc/kcore";
+ char *buf;
+ struct mem_ehdr ehdr;
+ struct mem_phdr *phdr, *end_phdr;
+ int align;
+ unsigned long size;
+ uint32_t elf_flags = 0;
+
+ align = getpagesize();
+ size = KCORE_ELF_HEADERS_SIZE;
+ buf = slurp_file_len(kcore, size);
+ if (!buf) {
+ fprintf(stderr, "Cannot read %s: %s\n", kcore, strerror(errno));
+ return -1;
+ }
+
+ /* Don't perform checks to make sure stated phdrs and shdrs are
+ * actually present in the core file. It is not practical
+ * to read the GB size file into a user space buffer, Given the
+ * fact that we don't use any info from that.
+ */
+ elf_flags |= ELF_SKIP_FILESZ_CHECK;
+ result = build_elf_core_info(buf, size, &ehdr, elf_flags);
+ if (result < 0) {
+ fprintf(stderr, "ELF core (kcore) parse failed\n");
+ hardcode_kernel_vaddr_size(info);
+ return 0;
+ }
+
+ /* Traverse through the Elf headers and find the region where
+ * kernel is mapped. */
+ end_phdr = &ehdr.e_phdr[ehdr.e_phnum];
+ for(phdr = ehdr.e_phdr; phdr != end_phdr; phdr++) {
+ if (phdr->p_type == PT_LOAD) {
+ unsigned long saddr = phdr->p_vaddr;
+ unsigned long eaddr = phdr->p_vaddr + phdr->p_memsz;
+ unsigned long size;
+
+ /* Look for kernel text mapping header. */
+ if ((saddr >= __START_KERNEL_map) &&
+ (eaddr <= __START_KERNEL_map + KERNEL_TEXT_SIZE)) {
+ saddr = (saddr) & (~(KERN_VADDR_ALIGN - 1));
+ info->kern_vaddr_start = saddr;
+ size = eaddr - saddr;
+ /* Align size to page size boundary. */
+ size = (size + align - 1) & (~(align - 1));
+ info->kern_size = size;
+#ifdef DEBUG
+ printf("kernel vaddr = 0x%lx size = 0x%lx\n",
+ saddr, size);
+#endif
+ return 0;
+ }
+ }
+ }
+ fprintf(stderr, "Can't find kernel text map area from kcore\n");
+ return -1;
+}
+
/* Stores a sorted list of RAM memory ranges for which to create elf headers.
* A separate program header is created for backup region */
static struct memory_range crash_memory_range[CRASH_MAX_MEMORY_RANGES];
@@ -81,6 +214,7 @@ static int get_crash_memory_ranges(struct memory_range **range, int *ranges)
while(fgets(line, sizeof(line), fp) != 0) {
char *str;
int type, consumed, count;
+
if (memory_ranges >= CRASH_MAX_MEMORY_RANGES)
break;
count = sscanf(line, "%Lx-%Lx : %n",
@@ -125,17 +259,6 @@ static int get_crash_memory_ranges(struct memory_range **range, int *ranges)
crash_memory_range[memory_ranges].end = end;
crash_memory_range[memory_ranges].type = type;
memory_ranges++;
-
- /* Segregate linearly mapped region. */
- if ((MAXMEM - 1) >= start && (MAXMEM - 1) <= end) {
- crash_memory_range[memory_ranges-1].end = MAXMEM -1;
-
- /* Add segregated region. */
- crash_memory_range[memory_ranges].start = MAXMEM;
- crash_memory_range[memory_ranges].end = end;
- crash_memory_range[memory_ranges].type = type;
- memory_ranges++;
- }
}
fclose(fp);
if (exclude_crash_reserve_region(&memory_ranges) < 0)
@@ -532,8 +655,35 @@ static int prepare_crash_memory_elf64_headers(struct kexec_info *info,
/* Increment number of program headers. */
(elf->e_phnum)++;
+#ifdef DEBUG
+ printf("Elf header: p_type = %d, p_offset = 0x%lx "
+ "p_paddr = 0x%lx p_vaddr = 0x%lx "
+ "p_filesz = 0x%lx p_memsz = 0x%lx\n",
+ phdr->p_type, phdr->p_offset, phdr->p_paddr,
+ phdr->p_vaddr, phdr->p_filesz, phdr->p_memsz);
+#endif
}
+ /* Setup an PT_LOAD type program header for the region where
+ * Kernel is mapped.
+ */
+ phdr = (Elf64_Phdr *) bufp;
+ bufp += sizeof(Elf64_Phdr);
+ phdr->p_type = PT_LOAD;
+ phdr->p_flags = PF_R|PF_W|PF_X;
+ phdr->p_offset = phdr->p_paddr = info->kern_paddr_start;
+ phdr->p_vaddr = info->kern_vaddr_start;
+ phdr->p_filesz = phdr->p_memsz = info->kern_size;
+ phdr->p_align = 0;
+ (elf->e_phnum)++;
+#ifdef DEBUG
+ printf("Kernel text Elf header: p_type = %d, p_offset = 0x%lx "
+ "p_paddr = 0x%lx p_vaddr = 0x%lx "
+ "p_filesz = 0x%lx p_memsz = 0x%lx\n",
+ phdr->p_type, phdr->p_offset, phdr->p_paddr,
+ phdr->p_vaddr, phdr->p_filesz, phdr->p_memsz);
+#endif
+
/* Setup PT_LOAD type program header for every system RAM chunk.
* A seprate program header for Backup Region*/
for (i = 0; i < CRASH_MAX_MEMORY_RANGES; i++) {
@@ -553,27 +703,25 @@ static int prepare_crash_memory_elf64_headers(struct kexec_info *info,
else
phdr->p_offset = mstart;
- /* Handle linearly mapped region.*/
-
- /* Filling the vaddr conditionally as we have two linearly
- * mapped regions here. One is __START_KERNEL_map 0 to 40 MB
- * other one is PAGE_OFFSET */
-
- if ((mend <= (MAXMEM - 1)) && mstart < KERNEL_TEXT_SIZE)
- phdr->p_vaddr = mstart + __START_KERNEL_map;
- else {
- if (mend <= (MAXMEM - 1))
- phdr->p_vaddr = mstart + PAGE_OFFSET;
- else
- phdr->p_vaddr = -1ULL;
- }
+ /* We already prepared the header for kernel text. Map
+ * rest of the memory segments to kernel linearly mapped
+ * memory region.
+ */
phdr->p_paddr = mstart;
+ phdr->p_vaddr = mstart + PAGE_OFFSET;
phdr->p_filesz = phdr->p_memsz = mend - mstart + 1;
/* Do we need any alignment of segments? */
phdr->p_align = 0;
/* Increment number of program headers. */
(elf->e_phnum)++;
+#ifdef DEBUG
+ printf("Elf header: p_type = %d, p_offset = 0x%lx "
+ "p_paddr = 0x%lx p_vaddr = 0x%lx "
+ "p_filesz = 0x%lx p_memsz = 0x%lx\n",
+ phdr->p_type, phdr->p_offset, phdr->p_paddr,
+ phdr->p_vaddr, phdr->p_filesz, phdr->p_memsz);
+#endif
}
return 0;
}
@@ -591,6 +739,12 @@ int load_crashdump_segments(struct kexec_info *info, char* mod_cmdline,
long int nr_cpus = 0;
struct memory_range *mem_range, *memmap_p;
+ if (get_kernel_paddr(info))
+ return -1;
+
+ if (get_kernel_vaddr_and_size(info))
+ return -1;
+
if (get_crash_memory_ranges(&mem_range, &nr_ranges) < 0)
return -1;
diff --git a/kexec/crashdump.h b/kexec/crashdump.h
index 682dd53..bb3649f 100644
--- a/kexec/crashdump.h
+++ b/kexec/crashdump.h
@@ -5,5 +5,7 @@ extern int get_crash_notes_per_cpu(int cpu, uint64_t *addr);
/* Need to find a better way to determine per cpu notes section size. */
#define MAX_NOTE_BYTES 1024
+/* Expecting ELF headers to fit in 4K. Increase it if you need more. */
+#define KCORE_ELF_HEADERS_SIZE 4096
#endif /* CRASHDUMP_H */
diff --git a/kexec/kexec-elf-core.c b/kexec/kexec-elf-core.c
new file mode 100644
index 0000000..a341fdb
--- /dev/null
+++ b/kexec/kexec-elf-core.c
@@ -0,0 +1,29 @@
+#include <stdio.h>
+#include <stdint.h>
+#include <errno.h>
+#include <stdlib.h>
+#include "elf.h"
+#include "kexec-elf.h"
+
+
+int build_elf_core_info(const char *buf, off_t len, struct mem_ehdr *ehdr,
+ uint32_t flags)
+{
+ int result;
+ result = build_elf_info(buf, len, ehdr, flags);
+ if (result < 0) {
+ return result;
+ }
+ if ((ehdr->e_type != ET_CORE)) {
+ /* not an ELF Core */
+ fprintf(stderr, "Not ELF type ET_CORE\n");
+ return -1;
+ }
+ if (!ehdr->e_phdr) {
+ /* No program header */
+ fprintf(stderr, "No ELF program header\n");
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/kexec/kexec-elf.c b/kexec/kexec-elf.c
index 929c79c..18b857f 100644
--- a/kexec/kexec-elf.c
+++ b/kexec/kexec-elf.c
@@ -715,7 +715,8 @@ static int build_mem_notes(const char *buf, off_t len, struct mem_ehdr *ehdr)
note_size += (hdr.n_descsz + 3) & ~3;
if ((hdr.n_namesz != 0) && (name[hdr.n_namesz -1] != '\0')) {
- die("Note name is not null termiated");
+ fprintf(stderr, "Note name is not null termiated\n");
+ return -1;
}
ehdr->e_note[i].n_type = hdr.n_type;
ehdr->e_note[i].n_name = (char *)name;
diff --git a/kexec/kexec-elf.h b/kexec/kexec-elf.h
index 0f09285..db13001 100644
--- a/kexec/kexec-elf.h
+++ b/kexec/kexec-elf.h
@@ -94,6 +94,8 @@ extern int build_elf_exec_info(const char *buf, off_t len,
extern int build_elf_rel_info(const char *buf, off_t len, struct mem_ehdr *ehdr,
uint32_t flags);
+extern int build_elf_core_info(const char *buf, off_t len,
+ struct mem_ehdr *ehdr, uint32_t flags);
extern int elf_exec_load(struct mem_ehdr *ehdr, struct kexec_info *info);
extern int elf_rel_load(struct mem_ehdr *ehdr, struct kexec_info *info,
unsigned long min, unsigned long max, int end);
diff --git a/kexec/kexec.c b/kexec/kexec.c
index a32cacb..ce0663e 100644
--- a/kexec/kexec.c
+++ b/kexec/kexec.c
@@ -392,6 +392,50 @@ char *slurp_file(const char *filename, off_t *r_size)
return buf;
}
+/* This functions reads either specified number of bytes from the file or
+ lesser if EOF is met. */
+
+char *slurp_file_len(const char *filename, off_t size)
+{
+ int fd;
+ char *buf;
+ off_t progress;
+ ssize_t result;
+
+ if (!filename)
+ return 0;
+ fd = open(filename, O_RDONLY);
+ if (fd < 0) {
+ fprintf(stderr, "Cannot open %s: %s\n", filename,
+ strerror(errno));
+ return 0;
+ }
+ buf = xmalloc(size);
+ progress = 0;
+ while(progress < size) {
+ result = read(fd, buf + progress, size - progress);
+ if (result < 0) {
+ if ((errno == EINTR) || (errno == EAGAIN))
+ continue;
+ fprintf(stderr, "read on %s of %ld bytes failed: %s\n",
+ filename, (size - progress)+ 0UL,
+ strerror(errno));
+ free(buf);
+ return 0;
+ }
+ if (result == 0)
+ /* EOF */
+ break;
+ progress += result;
+ }
+ result = close(fd);
+ if (result < 0) {
+ die("Close of %s failed: %s\n",
+ filename, strerror(errno));
+ }
+ return buf;
+}
+
#if HAVE_ZLIB_H
char *slurp_decompress_file(const char *filename, off_t *r_size)
{
diff --git a/kexec/kexec.h b/kexec/kexec.h
index 57fca7d..1cee900 100644
--- a/kexec/kexec.h
+++ b/kexec/kexec.h
@@ -116,6 +116,9 @@ struct kexec_info {
struct mem_ehdr rhdr;
unsigned long backup_start;
unsigned long kexec_flags;
+ unsigned long kern_vaddr_start;
+ unsigned long kern_paddr_start;
+ unsigned long kern_size;
};
void usage(void);
@@ -177,6 +180,7 @@ extern void die(char *fmt, ...);
extern void *xmalloc(size_t size);
extern void *xrealloc(void *ptr, size_t size);
extern char *slurp_file(const char *filename, off_t *r_size);
+extern char *slurp_file_len(const char *filename, off_t size);
extern char *slurp_decompress_file(const char *filename, off_t *r_size);
extern void add_segment(struct kexec_info *info,
const void *buf, size_t bufsz, unsigned long base, size_t memsz);