diff options
-rw-r--r-- | kexec/Makefile | 1 | ||||
-rw-r--r-- | kexec/arch/x86_64/crashdump-x86_64.c | 204 | ||||
-rw-r--r-- | kexec/crashdump.h | 2 | ||||
-rw-r--r-- | kexec/kexec-elf-core.c | 29 | ||||
-rw-r--r-- | kexec/kexec-elf.c | 3 | ||||
-rw-r--r-- | kexec/kexec-elf.h | 2 | ||||
-rw-r--r-- | kexec/kexec.c | 44 | ||||
-rw-r--r-- | kexec/kexec.h | 4 |
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); |