| /* Copyright (c) 2017 Google Inc. |
| * See LICENSE for details. |
| * |
| * ELF loading. */ |
| |
| #include <stdio.h> |
| #include <fcntl.h> |
| #include <unistd.h> |
| #include <libelf.h> |
| |
| /* load_elf loads and ELF file. This is almost always a kernel. |
| * We assume that memory is set up correctly, and it will go hard |
| * with you if it is not. The reference parameter records the highest |
| * address we wrote. The initrd can go there.*/ |
| uintptr_t load_elf(char *filename, uint64_t offset, uint64_t *highest, |
| Elf64_Ehdr *ehdr_out) |
| { |
| Elf64_Ehdr *ehdr; |
| Elf *elf; |
| size_t phnum = 0; |
| Elf64_Phdr *hdrs; |
| int fd; |
| uintptr_t ret; |
| uintptr_t kern_end = 0; |
| |
| elf_version(EV_CURRENT); |
| fd = open(filename, O_RDONLY); |
| if (fd < 0) { |
| fprintf(stderr, "Can't open %s: %r\n", filename); |
| return 0; |
| } |
| |
| elf = elf_begin(fd, ELF_C_READ, NULL); |
| if (elf == NULL) { |
| fprintf(stderr, "%s: cannot read %s ELF file.\n", __func__, |
| filename); |
| close(fd); |
| return 0; |
| } |
| |
| ehdr = elf64_getehdr(elf); |
| if (ehdr == NULL) { |
| fprintf(stderr, "%s: cannot get exec header of %s.\n", |
| __func__, filename); |
| goto fail; |
| } |
| fprintf(stderr, "%s ELF entry point is %p\n", filename, |
| (void *)ehdr->e_entry); |
| |
| if (elf_getphdrnum(elf, &phnum) < 0) { |
| fprintf(stderr, "%s: cannot get program header num of %s.\n", |
| __func__, filename); |
| goto fail; |
| } |
| fprintf(stderr, "%s has %p program headers\n", filename, phnum); |
| |
| hdrs = elf64_getphdr(elf); |
| if (hdrs == NULL) { |
| fprintf(stderr, "%s: cannot get program headers of %s.\n", |
| __func__, filename); |
| goto fail; |
| } |
| |
| for (int i = 0; i < phnum; i++) { |
| size_t tot; |
| Elf64_Phdr *h = &hdrs[i]; |
| uintptr_t pa; |
| |
| fprintf(stderr, |
| "%d: type 0x%lx flags 0x%lx offset 0x%lx vaddr 0x%lx\npaddr 0x%lx size 0x%lx memsz 0x%lx align 0x%lx\n", |
| i, |
| h->p_type, /* Segment type */ |
| h->p_flags, /* Segment flags */ |
| h->p_offset, /* Segment file offset */ |
| h->p_vaddr, /* Segment virtual address */ |
| h->p_paddr, /* Segment physical address */ |
| h->p_filesz, /* Segment size in file */ |
| h->p_memsz, /* Segment size in memory */ |
| h->p_align /* Segment alignment */); |
| if (h->p_type != PT_LOAD) |
| continue; |
| if ((h->p_flags & (PF_R | PF_W | PF_X)) == 0) |
| continue; |
| |
| pa = h->p_paddr; |
| fprintf(stderr, |
| "Read header %d @offset %p to %p (elf PA is %p) %d bytes:", |
| i, h->p_offset, pa, h->p_paddr, h->p_filesz); |
| tot = 0; |
| while (tot < h->p_filesz) { |
| int amt = pread(fd, (void *)(pa + tot + offset), |
| h->p_filesz - tot, h->p_offset + tot); |
| |
| if (amt < 1) |
| break; |
| tot += amt; |
| } |
| fprintf(stderr, "read a total of %d bytes\n", tot); |
| if (tot < h->p_filesz) { |
| fprintf(stderr, "%s: got %d bytes, wanted %d bytes\n", |
| filename, tot, h->p_filesz); |
| goto fail; |
| } |
| if ((h->p_paddr + h->p_memsz) > kern_end) |
| kern_end = h->p_paddr + h->p_memsz; |
| } |
| |
| close(fd); |
| ret = ehdr->e_entry + offset; |
| |
| // Save the values in the header, if the caller wanted them |
| if (ehdr_out) |
| *ehdr_out = *ehdr; |
| |
| elf_end(elf); |
| if (highest) |
| *highest = kern_end; |
| return ret; |
| fail: |
| close(fd); |
| elf_end(elf); |
| return 0; |
| } |