View of xos/kernel/exec.c


XOS | Parent Directory | View | Download

/* Copyright (C) 2008  Emmanuel Varoquaux
 
   This file is part of XOS.
 
   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 3 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, see <http://www.gnu.org/licenses/>. */
 
#include "elf.h"
#include "exec_reader.h"
#include "auxiliary_vector.h"
#include "sched.h"
#include "proc_struct.h"
 
#include <fd.h>
#include <resolve.h>
#include <node.h>
#include <stat_struct.h>
#include <vm.h>
#include <verify_area.h>
#include <string_table.h>
#include <gdt.h>
#include <segment.h>
#include <it_stack_structs.h>
#include <asm.h>
#include <errno.h>
#include <consts.h>
#include <enums.h>
#include <i386.h>
#include <stddef.h>
#include <limits.h>
#include <auxv.h>
 
static inline int check_for_addition(unsigned long base, unsigned long offset)
{
  return base <= ULONG_MAX - offset;
}
 
static inline int check_for_signed_addition(unsigned long base, long offset)
{
  if (offset < 0)
    return base >= 1UL - offset + 1UL;
  else
    return base <= ULONG_MAX - offset;
}
 
/* Detecte le format d'executable et construit un exec_reader a partir de
 * filename. Retourne un code d'erreur en cas d'erreur. */
static int create_exec_reader(const char *fs_filename, struct exec_reader_struct **exec_reader)
{
  int retval;
  struct file_struct *file;
  struct stat_struct statbuf;
 
  /* resolution du nom et controle du type du fichier */
  if ((retval = resolve(fs_filename, &file)) < 0)
    goto error;
  if ((retval = stat_node(file, &statbuf)) < 0)
    goto error_release;
  if (statbuf.type != FT_REG) {
    retval = -EACCES;
    goto error_release;
  }
  if ((retval = node_deny_write_access(file)) < 0) /* verification que le fichier n'est pas ouvert en ecriture */
    goto error_release;
 
  /* detection du type d'executable */
  if ((retval = is_elf(file)) < 0)
    goto error_release_write_access;
  if (retval) {
    if ((retval = alloc_elf_exec_reader(file, exec_reader)) < 0)
      goto error_release_write_access;
    return 0;
  }
  retval = -ENOEXEC;
 
 error_release_write_access:
  node_release_write_access(file);
 error_release:
  node_release(file);
 error:
  return retval;
}
 
static unsigned int get_str_table_size(const struct string_table_struct *string_table)
{
  return ALIGN(string_table->size);
}
 
static unsigned int get_auxv_entry_size()
{
  return 2 * sizeof (unsigned long);
}
 
static unsigned int get_auxv_size(const struct auxiliary_vector_struct *auxiliary_vector)
{
  unsigned int size;
 
  size = 0;
  size += get_auxv_entry_size();
  if (auxiliary_vector->contents.program_header_table)
    size += get_auxv_entry_size();
  if (auxiliary_vector->contents.program_header_entry_size)
    size += get_auxv_entry_size();
  if (auxiliary_vector->contents.program_header_entry_number)
    size += get_auxv_entry_size();
  if (auxiliary_vector->contents.page_size)
    size += get_auxv_entry_size();
  if (auxiliary_vector->contents.interpreter_base_address)
    size += get_auxv_entry_size();
  if (auxiliary_vector->contents.program_entry_point)
    size += get_auxv_entry_size();
  if (auxiliary_vector->contents.not_elf)
    size += get_auxv_entry_size();
  if (auxiliary_vector->contents.vdso_entry_point)
    size += get_auxv_entry_size();
  if (auxiliary_vector->contents.vdso_elf_header)
    size += get_auxv_entry_size();
  return size;
}
 
static unsigned int get_str_array_size(unsigned int count)
{
  return (count + 1) * sizeof (long);
}
 
static unsigned int get_int_size()
{
  return sizeof (long);
}
 
/* Retourne la taille initiale minimale que devra avoir la pile utilisateur.
 * La valeur retournee n'est jamais nulle. */
static unsigned int get_user_stack_size(const struct string_table_struct *environ, const struct auxiliary_vector_struct *auxiliary_vector)
{
  unsigned int size;
 
  size = 0;
 
  /* environnement */
  size += get_str_table_size(environ);
 
  /* arguments */
  size += get_str_table_size(&current->cmdline);
 
  /* auxv[] */
  size += get_auxv_size(auxiliary_vector);
 
  /* envp[] */
  size += get_str_array_size(environ->count);
 
  /* argv[] */
  size += get_str_array_size(current->cmdline.count);
 
  /* argc */
  size += get_int_size();
 
  return size;
}
 
static void push_str_table(void **fs_user_stack_top, const struct string_table_struct *string_table)
{
  if (string_table->buf) {
    *fs_user_stack_top -= ALIGN(string_table->size);
    memcpy_tofs(*fs_user_stack_top, string_table->buf, string_table->size);
    memset_tofs(*fs_user_stack_top + string_table->size, 0, ALIGN(string_table->size) - string_table->size);
  }
}
 
static void push_auxv_entry(void **fs_user_stack_top, int id, unsigned long val)
{
  *fs_user_stack_top -= sizeof (long);
  put_fs_long(val, *fs_user_stack_top);
  *fs_user_stack_top -= sizeof (long);
  put_fs_long(id, *fs_user_stack_top);
}
 
static void push_auxv(void **fs_user_stack_top, const struct auxiliary_vector_struct *auxiliary_vector)
{
  push_auxv_entry(fs_user_stack_top, AT_NULL, 0);
  if (auxiliary_vector->contents.program_header_table)
    push_auxv_entry(fs_user_stack_top, AT_PHDR, auxiliary_vector->program_header_table);
  if (auxiliary_vector->contents.program_header_entry_size)
    push_auxv_entry(fs_user_stack_top, AT_PHENT, auxiliary_vector->program_header_entry_size);
  if (auxiliary_vector->contents.program_header_entry_number)
    push_auxv_entry(fs_user_stack_top, AT_PHNUM, auxiliary_vector->program_header_entry_number);
  if (auxiliary_vector->contents.page_size)
    push_auxv_entry(fs_user_stack_top, AT_PAGESZ, auxiliary_vector->page_size);
  if (auxiliary_vector->contents.interpreter_base_address)
    push_auxv_entry(fs_user_stack_top, AT_BASE, auxiliary_vector->interpreter_base_address);
  if (auxiliary_vector->contents.program_entry_point)
    push_auxv_entry(fs_user_stack_top, AT_ENTRY, auxiliary_vector->program_entry_point);
  if (auxiliary_vector->contents.not_elf)
    push_auxv_entry(fs_user_stack_top, AT_NOTELF, 0);
  if (auxiliary_vector->contents.vdso_entry_point)
    push_auxv_entry(fs_user_stack_top, AT_SYSINFO, auxiliary_vector->vdso_entry_point);
  if (auxiliary_vector->contents.vdso_elf_header)
    push_auxv_entry(fs_user_stack_top, AT_SYSINFO_EHDR, auxiliary_vector->vdso_elf_header);
}
 
static void push_str_array(void **fs_user_stack_top, const char *str_table, unsigned int count)
{
  char **fs_str_array;
  const char *s;
  unsigned int i;
 
  *fs_user_stack_top -= (count + 1) * sizeof (long);
  fs_str_array = *fs_user_stack_top;
  s = str_table;
  for (i = 0; i < count; i++) {
    put_fs_long((long)s, (long *)&fs_str_array[i]);
    while (get_fs_byte(s++));
  }
  put_fs_long((long)NULL, (long *)&fs_str_array[count]);
}
 
static void push_int(void **fs_user_stack_top, int value)
{
  *fs_user_stack_top -= sizeof (long);
  put_fs_long(value, *fs_user_stack_top);
}
 
/* Ecrit la pile utilisateur initiale. */
static void fill_in_user_stack(void **fs_user_stack_top, const struct string_table_struct *environ, const struct auxiliary_vector_struct *auxiliary_vector)
{
  const char *environ_table, *argument_table;
 
  /* environnement */
  push_str_table(fs_user_stack_top, environ);
  environ_table = *fs_user_stack_top;
 
  /* arguments */
  push_str_table(fs_user_stack_top, &current->cmdline);
  argument_table = *fs_user_stack_top;
 
  /* auxv[] */
  push_auxv(fs_user_stack_top, auxiliary_vector);
 
  /* envp[] */
  push_str_array(fs_user_stack_top, environ_table, environ->count);
 
  /* argv[] */
  push_str_array(fs_user_stack_top, argument_table, current->cmdline.count);
 
  /* argc */
  push_int(fs_user_stack_top, current->cmdline.count);
}
 
/* Detruit et reinitialise le programme execute par le processus courant. Cette
 * fonction ne retourne pas de code d'erreur. */
static void reset_exec(struct string_table_struct *cmdline)
{
  int intr;
 
  disable_intr(intr);
  current->did_exec = 1;
  string_table_destroy(&current->cmdline);
  current->cmdline = *cmdline;
  vm_free(&current->vm);
  current->brk = 0;
  restore_intr(intr);
}
 
static void close_file_descrs_on_exec(struct file_descr_struct *file_descr_table[])
{
  struct file_descr_struct **file_descr;
 
  for (file_descr = file_descr_table; file_descr < file_descr_table + OPEN_MAX; file_descr++)
    if (*file_descr && fd_get_close_on_exec(*file_descr)) {
      fd_close(*file_descr);
      *file_descr = NULL;
    }
}
 
static void init_auxiliary_vector(struct auxiliary_vector_struct *auxiliary_vector)
{
  auxiliary_vector->contents.program_header_table = 0;
  auxiliary_vector->contents.program_header_entry_size = 0;
  auxiliary_vector->contents.program_header_entry_number = 0;
  auxiliary_vector->contents.page_size = 0;
  auxiliary_vector->contents.interpreter_base_address = 0;
  auxiliary_vector->contents.program_entry_point = 0;
  auxiliary_vector->contents.not_elf = 0;
  auxiliary_vector->contents.vdso_entry_point = 0;
  auxiliary_vector->contents.vdso_elf_header = 0;
}
 
static int setup_user_stack(unsigned long *user_stack_top, const struct string_table_struct *environ, const struct auxiliary_vector_struct *auxiliary_vector)
{
  unsigned long length;
  int rv;
 
  length = PAGE_ALIGN(get_user_stack_size(environ, auxiliary_vector)); /* jamais nul */
  if ((rv = vm_map_anon(&current->vm, USER_STACK_TOP - length, length, PROT_RDWR, 1, "stack")) < 0) /* pile utilisateur */
    return rv;
  if (!verify_area((void *)(USER_STACK_TOP - length), length, PF_WRITE))
    return -EFAULT;
  *user_stack_top = USER_STACK_TOP;
  fill_in_user_stack((void **)user_stack_top, environ, auxiliary_vector);
  return 0;
}
 
int exec(const char *fs_filename, char *const fs_argv[], char *const fs_envp[], struct registers_struct *registers, struct interrupted_instruction_struct *interrupted_instruction, int *status)
{
  int retval;
  struct exec_reader_struct *exec_reader;
  struct string_table_struct cmdline, environ;
  unsigned long user_stack_top, entry_point;
  struct auxiliary_vector_struct auxiliary_vector;
 
  /* detection du format d'executable et des erreurs */
  if ((retval = create_exec_reader(fs_filename, &exec_reader)) < 0)
    goto error;
  if ((retval = string_table_init_from_array(&cmdline, fs_argv)) < 0)
    goto error_free_exec_reader;
  if ((retval = string_table_init_from_array(&environ, fs_envp)) < 0)
    goto error_destroy_cmdline;
  if (cmdline.size + environ.size > ARG_MAX) {
    retval = -E2BIG;
    goto error_destroy_environ;
  }
 
  /* destruction et reinitialisation de certains champs de proc_struct */
  /* A partir de ce point, exec() ne peut plus retourner de code d'erreur dans
     le processus courant. Apres la reinitialisation, en cas d'erreur, un
     abort() sera genere a partir du code mis dans status. */
  reset_exec(&cmdline);
  close_file_descrs_on_exec(current->file_descr_table);
 
  /* mise en place du nouveau programme */
  init_auxiliary_vector(&auxiliary_vector);
  if ((*status = exec_reader->vptr->load(exec_reader, &entry_point, &auxiliary_vector)) < 0)
    goto fatal_error_destroy_environ;
  if ((*status = setup_user_stack(&user_stack_top, &environ, &auxiliary_vector)) < 0)
    goto fatal_error_destroy_environ;
  if (!current->brk) {
    *status = -EINVAL;
    goto fatal_error_destroy_environ;
  }
 
  string_table_destroy(&environ);
  exec_reader->vptr->free(exec_reader);
  registers->eax = 0;
  registers->ebx = 0;
  registers->ecx = 0;
  registers->edx = 0;
  registers->esi = 0;
  registers->edi = 0;
  registers->ebp = 0;
  registers->ds = USER_DATA_SEL;
  registers->es = USER_DATA_SEL;
  registers->fs = USER_DATA_SEL;
  registers->gs = USER_DATA_SEL;
  interrupted_instruction->eip = entry_point;
  interrupted_instruction->cs = USER_CODE_SEL;
  interrupted_instruction->eflags = 0x292; /* AF, SF, IF */
  interrupted_instruction->esp = user_stack_top;
  interrupted_instruction->ss = USER_DATA_SEL;
  *status = 0;
  return 0;
 
 error_destroy_environ:
  string_table_destroy(&environ);
 error_destroy_cmdline:
  string_table_destroy(&cmdline);
 error_free_exec_reader:
  exec_reader->vptr->free(exec_reader);
 error:
  return retval;
 
 fatal_error_destroy_environ:
  string_table_destroy(&environ);
  exec_reader->vptr->free(exec_reader);
  return 0;
}
 
int sbrk(int increment, unsigned long *brk)
{
  unsigned long new_brk;
  unsigned long old_end_data, new_end_data;
  int rv;
 
  if (increment) {
    if (!check_for_signed_addition(current->brk, increment))
      return -EINVAL;
    new_brk = current->brk + increment;
    old_end_data = PAGE_ALIGN(current->brk);
    if (!check_for_addition(new_brk, PAGE_SIZE - 1))
      return -EINVAL;
    new_end_data = PAGE_ALIGN(new_brk);
    if (new_end_data < old_end_data) {
      if ((rv = vm_unmap(&current->vm, new_end_data, old_end_data - new_end_data)) < 0)
        return rv;
    }
    else if (new_end_data > old_end_data)
      if ((rv = vm_map_anon(&current->vm, old_end_data, new_end_data - old_end_data, PROT_RDWR, 0, "heap")) < 0)
        return rv;
    current->brk = new_brk;
  }
  *brk = current->brk;
  return 0;
}