From: Vivek Goyal <[EMAIL PROTECTED]>
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 <[EMAIL PROTECTED]>
---
kexec/Makefile | 1
kexec/arch/x86_64/crashdump-x86_64.c | 204 ++++++++++++++++++++++++++++++----
kexec/crashdump.h | 2
kexec/kexec-elf-core.c | 28 +++++
kexec/kexec-elf.c | 35 ++++--
kexec/kexec-elf.h | 3 +
kexec/kexec.c | 45 ++++++++
kexec/kexec.h | 4 +
8 files changed, 285 insertions(+), 37 deletions(-)
diff --git a/kexec/Makefile b/kexec/Makefile
index f8c2f18..c059153 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 52cb72e..22647a7 100644
--- a/kexec/arch/x86_64/crashdump-x86_64.c
+++ b/kexec/arch/x86_64/crashdump-x86_64.c
@@ -24,8 +24,10 @@ #include <stdlib.h>
#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 @@ #include <x86/x86-linux.h>
/* 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;
+
+ 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.
+ */
+ ignore_elf_len_check = 1;
+ result = build_elf_core_info(buf, size, &ehdr);
+ ignore_elf_len_check = 0;
+ 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(struc
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 @@ #endif
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 @@ #define MAX_NOTE_BYTES 1024
/* 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 @@ #define MAX_NOTE_BYTES 1024
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
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 c
/* 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..6a61911
--- /dev/null
+++ b/kexec/kexec-elf-core.c
@@ -0,0 +1,28 @@
+#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)
+{
+ int result;
+ result = build_elf_info(buf, len, ehdr);
+ 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 da2c788..d149daa 100644
--- a/kexec/kexec-elf.c
+++ b/kexec/kexec-elf.c
@@ -11,6 +11,17 @@ #include "kexec-elf.h"
static const int probe_debug = 0;
+/* This parameter can be set to force ELF phdr and shdr building routines
+ * to skip the check on file length which make sure that data pointed by
+ * phdrs and shdrs is actually present in the file. This has been introduced
+ * to handle the corner case of reading /proc/kcore. /proc/kcore can be
+ * of huge size (GB, TB), depending on RAM size. It does not make sense
+ * to read that whole core file just to extract some info from kcore ELF
+ * headers.
+ * This is hackish. Please suggest a better way to handle it.
+ */
+int ignore_elf_len_check = 0;
+
uint16_t elf16_to_cpu(const struct mem_ehdr *ehdr, uint16_t value)
{
if (ehdr->ei_data == ELFDATA2LSB) {
@@ -420,11 +431,12 @@ static int build_mem_phdrs(const char *b
* they are safe to use.
*/
phdr = &ehdr->e_phdr[i];
- if ((phdr->p_offset + phdr->p_filesz) > len) {
+ if (!ignore_elf_len_check &&
+ (phdr->p_offset + phdr->p_filesz) > len) {
+
/* The segment does not fit in the buffer */
- if (probe_debug) {
+ if (probe_debug)
fprintf(stderr, "ELF segment not in file\n");
- }
return -1;
}
if ((phdr->p_paddr + phdr->p_memsz) < phdr->p_paddr) {
@@ -630,15 +642,13 @@ static int build_mem_shdrs(const char *b
* they are safe to use.
*/
shdr = &ehdr->e_shdr[i];
- if ((shdr->sh_type != SHT_NOBITS) &&
- ((shdr->sh_offset + shdr->sh_size) > len))
- {
+ if (!ignore_elf_len_check && (shdr->sh_type != SHT_NOBITS)
+ && (shdr->sh_offset + shdr->sh_size) > len) {
/* The section does not fit in the buffer */
- if (probe_debug) {
- fprintf(stderr, "ELF section %d not in file\n",
- i);
- }
- return -1;
+ if (probe_debug)
+ fprintf(stderr, "ELF section %d not in "
+ "file\n", i);
+ return -1;
}
if ((shdr->sh_addr + shdr->sh_size) < shdr->sh_addr) {
/* The memory address wraps */
@@ -710,7 +720,8 @@ static int build_mem_notes(const char *b
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 b7332de..a784855 100644
--- a/kexec/kexec-elf.h
+++ b/kexec/kexec-elf.h
@@ -89,6 +89,7 @@ extern int build_elf_info(const char *bu
extern int build_elf_exec_info(const char *buf, off_t len, struct mem_ehdr
*ehdr);
extern int build_elf_rel_info(const char *buf, off_t len, struct mem_ehdr
*ehdr);
+extern int build_elf_core_info(const char *buf, off_t len, struct mem_ehdr
*ehdr);
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);
@@ -107,6 +108,8 @@ extern void elf_rel_set_symbol(struct me
extern void elf_rel_get_symbol(struct mem_ehdr *ehdr,
const char *name, void *buf, size_t size);
+extern int ignore_elf_len_check;
+
uint16_t elf16_to_cpu(const struct mem_ehdr *ehdr, uint16_t value);
uint32_t elf32_to_cpu(const struct mem_ehdr *ehdr, uint32_t value);
uint64_t elf64_to_cpu(const struct mem_ehdr *ehdr, uint64_t value);
diff --git a/kexec/kexec.c b/kexec/kexec.c
index 0206fa6..ab65ffd 100644
--- a/kexec/kexec.c
+++ b/kexec/kexec.c
@@ -392,6 +392,51 @@ char *slurp_file(const char *filename, o
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);
_______________________________________________
fastboot mailing list
[email protected]
https://lists.osdl.org/mailman/listinfo/fastboot