elf.c - vx32 - Local 9vx git repository for patches.
 (HTM) git clone git://r-36.net/vx32
 (DIR) Log
 (DIR) Files
 (DIR) Refs
       ---
       elf.c (7711B)
       ---
            1 // ELF program loader
            2 
            3 #define _XOPEN_SOURCE 500
            4 
            5 #include <unistd.h>
            6 #include <string.h>
            7 #include <fcntl.h>
            8 #include <errno.h>
            9 #include <stdio.h>
           10 #include <stdlib.h>
           11 #include <assert.h>
           12 
           13 #include "vx32.h"
           14 #include "vx32impl.h"
           15 #include "elf.h"
           16 #include "words.h"
           17 
           18 #define VX32_ARG_MAX 10*1024
           19 #define VX32_STACK 64*1024
           20 #define VXMEMSIZE (1<<30)
           21 
           22 int vx_elfbigmem;
           23 
           24 static int elfloader(vxproc *p,
           25         ssize_t (*readcb)(void*, off_t, void*, size_t), void*,
           26         const char *const *argv, const char *const *envp);
           27 
           28 // From a file.
           29 
           30 struct filedesc
           31 {
           32         int fd;
           33 };
           34 
           35 static ssize_t loadfilecb(void *cbdata, off_t offset, void *buf, size_t size)
           36 {
           37         ssize_t rc;
           38         struct filedesc *desc;
           39         
           40         desc = cbdata;
           41         rc = pread(desc->fd, buf, size, offset);
           42         return rc;
           43 }
           44 
           45 int vxproc_loadelffile(vxproc *p, const char *file,
           46         const char *const *argv, const char *const *envp)
           47 {
           48         int fd, rc;
           49         struct filedesc desc;
           50         
           51         if ((fd = open(file, O_RDONLY)) < 0)
           52                 return -1;
           53         desc.fd = fd;
           54         rc = elfloader(p, loadfilecb, &desc, argv, envp);
           55         close(fd);
           56         return rc;
           57 }
           58 
           59 // From memory.
           60 
           61 struct memdesc
           62 {
           63         const void *buf;
           64         size_t size;
           65 };
           66 
           67 static ssize_t loadmemcb(void *cbdata, off_t offset, void *buf, size_t size)
           68 {
           69         struct memdesc *desc;
           70         
           71         desc = cbdata;
           72         if (offset >= desc->size || offset + size >= desc->size)
           73                 return 0;
           74         memmove(buf, desc->buf + offset, size);
           75         return size;
           76 }
           77 
           78 int vxproc_loadelfmem(vxproc *p, const void *exe, size_t size,
           79         const char *const *argv, const char *const *envp)
           80 {
           81         struct memdesc desc;
           82         
           83         desc.buf = exe;
           84         desc.size = size;
           85         return elfloader(p, loadmemcb, &desc, argv, envp);
           86 }
           87 
           88 
           89 // In general.
           90 
           91 // Count number of args in array.
           92 static int countargs(const char *const *argv)
           93 {
           94         int i;
           95         
           96         for(i=0; argv[i]; i++)
           97                 ;
           98         return i;
           99 }
          100 
          101 // Copy the strings from argv onto the stack, recording
          102 // their guest address in gargv.
          103 static int copystrings(uint8_t *base, uint32_t *espp, int argc, const char *const argv[], uint32_t *gargv)
          104 {
          105         int i;
          106         uint32_t esp = *espp;
          107         for (i = argc-1; i >= 0; i--) {
          108                 int len = strlen(argv[i]);
          109                 if (len + 4096 > esp)
          110                         return -1;
          111                 esp -= len+1;
          112                 memmove(base+esp, argv[i], len+1);
          113                 gargv[i] = esp;
          114         }
          115         *espp = esp;
          116         return 0;
          117 }
          118 
          119 // Copy pre-translated pointer array into guest address space
          120 static int copyptrs(uint8_t *base, uint32_t *espp, int argc, uint32_t *gargv)
          121 {
          122         uint32_t esp = *espp;
          123         esp &= ~3;  // align
          124         if (argc * 4 + 4096 > esp)
          125                 return -1;
          126         esp -= argc*4;
          127         memmove(base+esp, gargv, argc*4);
          128         *espp = esp;
          129         return 0;
          130 }
          131 
          132 #define ELF_MAX_PH 32
          133 
          134 static int elfloader(vxproc *proc,
          135               ssize_t (*readcb)(void*, off_t, void*, size_t),
          136               void *cbdata,
          137               const char *const *argv, const char *const *envp)
          138 {
          139         vxmem *mem;
          140         int i;
          141         size_t size;
          142         ssize_t act;
          143         struct Proghdr ph[ELF_MAX_PH];
          144         vxmmap *mm;
          145         static const char *null;
          146 
          147         if (argv == NULL)
          148                 argv = &null;
          149         if (envp == NULL)
          150                 envp = &null;
          151 
          152         size = 4096;
          153         // For "big mem" we need more than 1/2 GB, but 64-bit Linux
          154         // has only about 1 GB of address space to give out with MAP_32BIT,
          155         // and we've used up some of it for the vxemu structure.  
          156         // Used to ask for (1<<30) - (1<<24), which should be close enough to 1GB
          157         // to run the SPEC programs but leave enough for things like vxemu.
          158         // On Ubuntu 8.10, I get intermittent ouf of memory errors from this
          159         // mmap, so back off to 1<<29.
          160         if (vx_elfbigmem)
          161                 size = (1<<29);
          162 
          163         mm = NULL;
          164 
          165         if ((mem = vxmem_chunk_new(size)) == NULL)
          166                 return -1;
          167 
          168         // Read and check the ELF header
          169         struct Elf32 h;
          170         act = readcb(cbdata, 0, &h, sizeof(h));
          171         if (act < 0)
          172                 goto error;
          173         if (act < sizeof(h)) {
          174         noexec:
          175                 errno = ENOEXEC;
          176                 goto error;
          177         }
          178         if (ltoh32(h.e_magic) != ELF_MAGIC)
          179                 goto noexec;
          180 
          181         // Read the program header table
          182         off_t phoff = ltoh32(h.e_phoff);
          183         size_t phnum = ltoh16(h.e_phnum);
          184         if (phnum <= 0 || phnum > ELF_MAX_PH)        // arbitrary limit for security
          185                 goto noexec;
          186 
          187         act = readcb(cbdata, phoff, ph, phnum * sizeof ph[0]);
          188         if (act < 0)
          189                 goto error;
          190         if (act < phnum * sizeof ph[0])
          191                 goto noexec;
          192         
          193         // Load each program segment
          194         size_t stackhi = VXMEMSIZE;
          195         size_t addrhi = 0;
          196         for (i = 0; i < phnum; i++) {
          197                 const struct Proghdr *p = &ph[i];
          198                 if (ltoh32(p->p_type) != ELF_PROG_LOAD)
          199                         continue;
          200 
          201                 // Validate the program segment
          202                 off_t offset = ltoh32(p->p_offset);
          203                 size_t filesz = ltoh32(p->p_filesz);
          204                 size_t va = ltoh32(p->p_va);
          205                 size_t memsz = ltoh32(p->p_memsz);
          206                 if (filesz > memsz)
          207                         goto noexec;
          208 
          209                 // Validate the memory page region the segment loads into
          210                 size_t memlo = VXPAGETRUNC(va);
          211                 size_t memhi = VXPAGEROUND(va + memsz);
          212                 if (memlo > VXMEMSIZE || memhi > VXMEMSIZE || memhi < memlo)
          213                         goto noexec;
          214 
          215                 // Make sure the VX process is big enough, and mapped
          216                 if (size < memhi) {
          217                         if (mm) {
          218                                 vxmem_unmap(mem, mm);
          219                                 mm = NULL;
          220                         }
          221                         if (vxmem_resize(mem, memhi) < 0)
          222                                 goto error;
          223                         size = memhi;
          224                 }
          225 
          226                 if (mm == NULL) {
          227                         mm = vxmem_map(mem, 0);
          228                         if (mm == NULL)
          229                                 goto error;
          230                 }
          231 
          232                 // Temporarily give ourselves write permissions
          233                 // on the segment in order to load it.
          234                 if (vxmem_setperm(mem, memlo, memhi - memlo,
          235                                 VXPERM_READ | VXPERM_WRITE) < 0)
          236                         return -1;
          237 
          238                 // Load the segment.
          239                 // Any bss portion is already cleared automatically
          240                 // by virtue of the vxproc_clear() above.
          241                 act = readcb(cbdata, offset, mm->base + va, filesz);
          242                 if (act < 0)
          243                         return -1;
          244                 if (act < filesz)
          245                         goto noexec;
          246 
          247                 // Set permissions appropriately on the segment's pages
          248                 int flags = ltoh32(p->p_flags);
          249                 int perm = 0;
          250                 switch (flags & (ELF_PROG_FLAG_READ | ELF_PROG_FLAG_WRITE |
          251                                 ELF_PROG_FLAG_EXEC)) {
          252                 case ELF_PROG_FLAG_READ:
          253                         perm = VXPERM_READ;
          254                         break;
          255                 case ELF_PROG_FLAG_READ | ELF_PROG_FLAG_WRITE:
          256                         perm = VXPERM_READ | VXPERM_WRITE;
          257                         break;
          258                 case ELF_PROG_FLAG_READ | ELF_PROG_FLAG_EXEC:
          259                         perm = VXPERM_READ | VXPERM_EXEC;
          260                         break;
          261                 default:
          262                         goto noexec;        // invalid perms
          263                 }
          264                 if (vxmem_setperm(mem, memlo, memhi - memlo, perm) < 0)
          265                         goto error;
          266 
          267                 // Find the lowest-used va, for locating the stack
          268                 if (va < stackhi)
          269                         stackhi = va;
          270 
          271                 // Find the highest va too
          272                 if (memhi > addrhi)
          273                         addrhi = memhi;
          274         }
          275 
          276         if (size > addrhi)
          277                 vxmem_setperm(mem, addrhi, size - addrhi, VXPERM_READ|VXPERM_WRITE);
          278 
          279         if (mm == NULL) {
          280                 mm = vxmem_map(mem, 0);
          281                 if (mm == NULL)
          282                         goto error;
          283         }
          284 
          285         // Set up the process's stack,
          286         // growing downward from the executable's base load address.
          287         // Initially we enable read/write access on 64K of stack,
          288         // but the process is free to change that if it wants a bigger stack.
          289         stackhi = VXPAGETRUNC(stackhi);
          290         if (stackhi < VX32_STACK)
          291                 goto noexec;
          292         if (vxmem_setperm(mem, stackhi - VX32_STACK, VX32_STACK,
          293                         VXPERM_READ | VXPERM_WRITE) < 0)
          294                 goto error;
          295         proc->cpu->reg[ESP] = stackhi;
          296 
          297         // Push the argument and environment arrays on the stack.
          298         uint32_t esp = stackhi;
          299         uint32_t argc;
          300         uint32_t envc;
          301         argc = countargs(argv);
          302         envc = countargs(envp);
          303         uint32_t *argvenv = malloc((argc+1+envc+1)*sizeof argvenv[0]);
          304         if (argv == NULL)
          305                 goto error;
          306         if (copystrings(mm->base, &esp, envc, envp, argvenv+argc+1) < 0 ||
          307             copystrings(mm->base, &esp, argc, argv, argvenv) < 0) {
          308                 free(argvenv);
          309                 goto error;
          310         }
          311         if (copyptrs(mm->base, &esp, argc+1+envc+1, argvenv) < 0) {
          312                 free(argvenv);
          313                 goto error;
          314         }
          315         
          316         // Set up stack just like Linux: argc, then argv pointers begin, then env pointers.
          317         esp -= 4;
          318         *(uint32_t*)(mm->base+esp) = argc;
          319 
          320         if (proc->mem)
          321                 vxmem_free(proc->mem);
          322         proc->mem = mem;
          323         
          324         // Set up the process's initial register state
          325         for (i = 0; i < 8; i++)
          326                 proc->cpu->reg[i] = 0;
          327         proc->cpu->reg[ESP] = esp;
          328         proc->cpu->eflags = 0;
          329         proc->cpu->eip = ltoh32(h.e_entry);
          330 
          331         return 0;
          332 
          333 error:
          334         if (mm)
          335                 vxmem_unmap(mem, mm);
          336         vxmem_free(mem);
          337         return -1;
          338 }
          339