sos-code-article10/sos/binfmt_elf32.c

463 lines
13 KiB
C

/* Copyright (C) 2005 David Decotigny
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
USA.
*/
#include <sos/kmalloc.h>
#include <sos/assert.h>
#include <sos/physmem.h>
#include <drivers/bochs.h>
#include <hwcore/paging.h>
#include <drivers/zero.h>
#include "binfmt_elf32.h"
/**
* The "C" structure of a user program image in the kernel. Structures
* like this are created by the Makefile in the userland/ directory
*/
struct userprog_entry
{
const char *name;
sos_vaddr_t bottom_vaddr;
sos_vaddr_t top_vaddr;
};
/**
* Symbol marking the start of the userprogs table, as setup by the
* ld script in the userland/ directory
*/
extern char _userprogs_table;
/**
* Structure of a mapped resource for an ELF32 program (ie a portion
* of the kernel space)
*/
struct elf32_mapped_program
{
sos_vaddr_t vaddr;
sos_size_t size;
int ref_cnt;
struct sos_umem_vmm_mapped_resource mr;
};
/** Called after the virtual region has been inserted inside its
address space */
static void elf32prog_ref(struct sos_umem_vmm_vr * vr)
{
struct elf32_mapped_program * elf32prog_resource;
elf32prog_resource = (struct elf32_mapped_program*) sos_umem_vmm_get_mapped_resource_of_vr(vr)->custom_data;
elf32prog_resource->ref_cnt ++;
}
/** Called when the virtual region is removed from its address
space */
static void elf32prog_unref(struct sos_umem_vmm_vr * vr)
{
struct elf32_mapped_program * elf32prog_resource;
elf32prog_resource
= (struct elf32_mapped_program*)
sos_umem_vmm_get_mapped_resource_of_vr(vr)->custom_data;
elf32prog_resource->ref_cnt --;
SOS_ASSERT_FATAL(elf32prog_resource->ref_cnt >= 0);
/* Free the resource if it becomes unused */
if (elf32prog_resource->ref_cnt == 0)
sos_kfree((sos_vaddr_t)elf32prog_resource);
}
/** Called when a legitimate page fault is occuring in the VR */
static sos_ret_t elf32prog_page_in(struct sos_umem_vmm_vr * vr,
sos_uaddr_t uaddr,
sos_bool_t write_access)
{
struct elf32_mapped_program * elf32prog_resource;
sos_ret_t retval = SOS_OK;
sos_paddr_t ppage_paddr;
sos_uaddr_t upage_uaddr = SOS_PAGE_ALIGN_INF(uaddr);
sos_uoffset_t offset_in_prog;
sos_size_t size_to_copy;
sos_ui32_t access_rights = sos_umem_vmm_get_prot_of_vr(vr);
elf32prog_resource
= (struct elf32_mapped_program*)
sos_umem_vmm_get_mapped_resource_of_vr(vr)->custom_data;
/* Compute the offset in program of the page, and the size to copy
in user space */
offset_in_prog = upage_uaddr - sos_umem_vmm_get_start_of_vr(vr)
+ sos_umem_vmm_get_offset_in_resource(vr);
size_to_copy = SOS_PAGE_SIZE;
if (offset_in_prog + size_to_copy > elf32prog_resource->size)
size_to_copy = elf32prog_resource->size - offset_in_prog;
/* If the source page is also aligned, simply remap the kernel area
into user space */
if (SOS_IS_PAGE_ALIGNED(elf32prog_resource->vaddr + offset_in_prog))
{
sos_vaddr_t kern_vaddr = elf32prog_resource->vaddr + offset_in_prog;
ppage_paddr = sos_paging_get_paddr(kern_vaddr);
/* Remap it in user space, in read-only mode (to force COW) */
retval = sos_paging_map(ppage_paddr,
upage_uaddr,
TRUE,
access_rights & ~SOS_VM_MAP_PROT_WRITE);
SOS_ASSERT_FATAL(SOS_OK == retval);
}
/* Otherwise we need to allocate a new page */
else
{
/* Allocate a new page that contains the code/data of the
program */
ppage_paddr = sos_physmem_ref_physpage_new(FALSE);
if (! ppage_paddr)
return -SOS_ENOMEM;
/* Map it in user space, in read/write mode for the kernel to copy
the data in the page */
retval = sos_paging_map(ppage_paddr,
upage_uaddr,
TRUE,
access_rights | SOS_VM_MAP_PROT_WRITE);
SOS_ASSERT_FATAL(SOS_OK == retval);
sos_physmem_unref_physpage(ppage_paddr);
/* Copy the program in it */
memcpy((void*)upage_uaddr,
(void*)(elf32prog_resource->vaddr + offset_in_prog),
size_to_copy);
if (size_to_copy < SOS_PAGE_SIZE)
memset((void*)(upage_uaddr + size_to_copy), 0x0,
SOS_PAGE_SIZE - size_to_copy);
/* Change it read-only if needed */
if (! (access_rights & SOS_VM_MAP_PROT_WRITE))
return sos_paging_set_prot(upage_uaddr,
access_rights & ~SOS_VM_MAP_PROT_WRITE);
}
return retval;
}
static struct sos_umem_vmm_vr_ops elf32prog_ops = (struct sos_umem_vmm_vr_ops)
{
.ref = elf32prog_ref,
.unref = elf32prog_unref,
.page_in = elf32prog_page_in,
.unmap = NULL /* ignored */
};
static sos_ret_t elf32prog_mmap(struct sos_umem_vmm_vr *vr)
{
return sos_umem_vmm_set_ops_of_vr(vr, &elf32prog_ops);
}
/*
* Local functions
*/
/**
* Function to locate the given user program image in the kernel memory
*/
static struct userprog_entry * lookup_userprog(const char *name);
sos_uaddr_t sos_binfmt_elf32_map(struct sos_umem_vmm_as * dest_as,
const char * progname)
{
int i;
/**
* Typedefs, constants and structure definitions as given by the ELF
* standard specifications.
*/
typedef unsigned long Elf32_Addr;
typedef unsigned long Elf32_Word;
typedef unsigned short Elf32_Half;
typedef unsigned long Elf32_Off;
typedef signed long Elf32_Sword;
/* Elf identification */
#define EI_NIDENT 16
typedef struct {
unsigned char e_ident[EI_NIDENT];
Elf32_Half e_type;
Elf32_Half e_machine;
Elf32_Word e_version;
Elf32_Addr e_entry;
Elf32_Off e_phoff;
Elf32_Off e_shoff;
Elf32_Word e_flags;
Elf32_Half e_ehsize;
Elf32_Half e_phentsize;
Elf32_Half e_phnum;
Elf32_Half e_shentsize;
Elf32_Half e_shnum;
Elf32_Half e_shstrndx;
} __attribute__((packed)) Elf32_Ehdr_t;
/* e_ident value */
#define ELFMAG0 0x7f
#define ELFMAG1 'E'
#define ELFMAG2 'L'
#define ELFMAG3 'F'
/* e_ident offsets */
#define EI_MAG0 0
#define EI_MAG1 1
#define EI_MAG2 2
#define EI_MAG3 3
#define EI_CLASS 4
#define EI_DATA 5
#define EI_VERSION 6
#define EI_PAD 7
/* e_ident[EI_CLASS] */
#define ELFCLASSNONE 0
#define ELFCLASS32 1
#define ELFCLASS64 2
/* e_ident[EI_DATA] */
#define ELFDATANONE 0
#define ELFDATA2LSB 1
#define ELFDATA2MSB 2
/* e_type */
#define ET_NONE 0 /* No file type */
#define ET_REL 1 /* Relocatable file */
#define ET_EXEC 2 /* Executable file */
#define ET_DYN 3 /* Shared object file */
#define ET_CORE 4 /* Core file */
#define ET_LOPROC 0xff00 /* Processor-specific */
#define ET_HIPROC 0xffff /* Processor-specific */
/* e_machine */
#define EM_NONE 0 /* No machine */
#define EM_M32 1 /* AT&T WE 32100 */
#define EM_SPARC 2 /* SPARC */
#define EM_386 3 /* Intel 80386 */
#define EM_68K 4 /* Motorola 68000 */
#define EM_88K 5 /* Motorola 88000 */
#define EM_860 7 /* Intel 80860 */
#define EM_MIPS 8 /* MIPS RS3000 */
/* e_version */
#define EV_NONE 0 /* invalid version */
#define EV_CURRENT 1 /* current version */
typedef struct {
Elf32_Word p_type;
Elf32_Off p_offset;
Elf32_Addr p_vaddr;
Elf32_Addr p_paddr;
Elf32_Word p_filesz;
Elf32_Word p_memsz;
Elf32_Word p_flags;
Elf32_Word p_align;
} __attribute__((packed)) Elf32_Phdr_t;
/* Reserved segment types p_type */
#define PT_NULL 0
#define PT_LOAD 1
#define PT_DYNAMIC 2
#define PT_INTERP 3
#define PT_NOTE 4
#define PT_SHLIB 5
#define PT_PHDR 6
#define PT_LOPROC 0x70000000
#define PT_HIPROC 0x7fffffff
/* p_flags */
#define PF_X 1
#define PF_W 2
#define PF_R 4
Elf32_Ehdr_t *elf_hdr;
Elf32_Phdr_t *elf_phdrs;
struct elf32_mapped_program * mapped_prog;
struct userprog_entry * prog;
sos_uaddr_t prog_top_user_address = 0;
mapped_prog
= (struct elf32_mapped_program*)
sos_kmalloc(sizeof(struct elf32_mapped_program), 0);
if (! mapped_prog)
return -SOS_ENOMEM;
prog = lookup_userprog(progname);
if (! prog)
{
sos_kfree((sos_vaddr_t)mapped_prog);
return 0;
}
/* Initialize mapped resource */
memset(mapped_prog, 0x0, sizeof(*mapped_prog));
mapped_prog->mr.custom_data = mapped_prog;
mapped_prog->mr.mmap = elf32prog_mmap;
mapped_prog->mr.allowed_access_rights
= SOS_VM_MAP_PROT_READ
| SOS_VM_MAP_PROT_WRITE
| SOS_VM_MAP_PROT_EXEC;
mapped_prog->vaddr = prog->bottom_vaddr;
mapped_prog->size = prog->top_vaddr - prog->bottom_vaddr;
elf_hdr = (Elf32_Ehdr_t*) prog->bottom_vaddr;
/* Make sure the image is large enough to contain at least the ELF
header */
if (prog->bottom_vaddr + sizeof(Elf32_Ehdr_t) > prog->top_vaddr)
{
sos_bochs_printf("ELF prog %s: incorrect header\n", prog->name);
return 0;
}
/* Macro to check expected values for some fields in the ELF header */
#define ELF_CHECK(hdr,field,expected_value) \
({ if ((hdr)->field != (expected_value)) \
{ \
sos_bochs_printf("ELF prog %s: for %s, expected %x, got %x\n", \
prog->name, \
#field, \
(unsigned)(expected_value), \
(unsigned)(hdr)->field); \
return 0; \
} \
})
ELF_CHECK(elf_hdr, e_ident[EI_MAG0], ELFMAG0);
ELF_CHECK(elf_hdr, e_ident[EI_MAG1], ELFMAG1);
ELF_CHECK(elf_hdr, e_ident[EI_MAG2], ELFMAG2);
ELF_CHECK(elf_hdr, e_ident[EI_MAG3], ELFMAG3);
ELF_CHECK(elf_hdr, e_ident[EI_CLASS], ELFCLASS32);
ELF_CHECK(elf_hdr, e_ident[EI_DATA], ELFDATA2LSB);
ELF_CHECK(elf_hdr, e_type, ET_EXEC);
ELF_CHECK(elf_hdr, e_version, EV_CURRENT);
/* Get the begining of the program header table */
elf_phdrs = (Elf32_Phdr_t*) (prog->bottom_vaddr + elf_hdr->e_phoff);
/* Map the program segment in R/W mode. To make things clean, we
should iterate over the sections, not the program header */
for (i = 0 ; i < elf_hdr->e_phnum ; i++)
{
sos_ui32_t prot_flags;
sos_uaddr_t uaddr;
/* Ignore the empty program headers that are not marked "LOAD" */
if (elf_phdrs[i].p_type != PT_LOAD)
{
if (elf_phdrs[i].p_memsz != 0)
{
sos_display_fatal_error("ELF: non-empty non-LOAD segments not supported yet");
}
continue;
}
if (! SOS_PAGING_IS_USER_AREA(elf_phdrs[i].p_vaddr,
elf_phdrs[i].p_memsz) )
{
sos_display_fatal_error("User program has an incorrect address");
}
prot_flags = 0;
if (elf_phdrs[i].p_flags & SOS_VM_MAP_PROT_READ)
prot_flags |= SOS_VM_MAP_PROT_READ;
if (elf_phdrs[i].p_flags & SOS_VM_MAP_PROT_WRITE)
prot_flags |= SOS_VM_MAP_PROT_WRITE;
if (elf_phdrs[i].p_flags & SOS_VM_MAP_PROT_EXEC)
prot_flags |= SOS_VM_MAP_PROT_EXEC;
uaddr = elf_phdrs[i].p_vaddr;
SOS_ASSERT_FATAL(SOS_IS_PAGE_ALIGNED(uaddr));
/* First of all: map the region of the phdr which is also
covered by the file */
SOS_ASSERT_FATAL(SOS_OK
== sos_umem_vmm_map(dest_as, &uaddr,
SOS_PAGE_ALIGN_SUP(elf_phdrs[i].p_filesz),
prot_flags,
/* PRIVATE */ SOS_VR_MAP_FIXED,
& mapped_prog->mr,
elf_phdrs[i].p_offset));
/* Then map the remaining by a zero resource */
uaddr += SOS_PAGE_ALIGN_SUP(elf_phdrs[i].p_filesz);
if (SOS_PAGE_ALIGN_SUP(elf_phdrs[i].p_filesz)
< SOS_PAGE_ALIGN_SUP(elf_phdrs[i].p_memsz))
SOS_ASSERT_FATAL(SOS_OK
== sos_dev_zero_map(dest_as, &uaddr,
SOS_PAGE_ALIGN_SUP(elf_phdrs[i].p_memsz)
- SOS_PAGE_ALIGN_SUP(elf_phdrs[i].p_filesz),
prot_flags,
/* PRIVATE */ SOS_VR_MAP_FIXED));
if (prog_top_user_address
< uaddr + SOS_PAGE_ALIGN_SUP(elf_phdrs[i].p_memsz))
prog_top_user_address
= uaddr + SOS_PAGE_ALIGN_SUP(elf_phdrs[i].p_memsz);
}
/* Now prepare the heap */
sos_umem_vmm_init_heap(dest_as, prog_top_user_address);
return elf_hdr->e_entry;
}
/**
* Lookup a user program located inside the kernel's image
*/
static struct userprog_entry * lookup_userprog(const char *name)
{
struct userprog_entry *prog;
if (! name)
return NULL;
/* Walk through the table of user program description structures to
find the user program with the given name */
for (prog = (struct userprog_entry*) & _userprogs_table ;
prog && (prog->name != NULL) ;
prog++)
{
if (0 == strcmp(name, prog->name))
/* Found it ! */
return prog;
}
return NULL;
}