print · rss · source

< Utiliser un système de fichier Ext2FS | TutoOS | Booter avec Grub sur un disque IDE >


Créer et lancer une application au format ELF à partir du système de fichier

Les sources

Le package contenant les sources est téléchargeable ici : kernel_ELF.tgz
Pour naviguer dans l'arborescence : ELF


Remarques

Dans ce chapitre, nous allons voir comment créer et charger un fichier exécutable au format ELF pour Pépin. Notez que pour le moment :

  • Les fichiers seront chargés à partir de leur numéro d'inode
  • Seuls les fichiers compilés en statique seront supportés
  • Il y a une disquette avec le noyau d'une part et un disque avec des fichiers d'autre part. Patience ! Nous verrons au chapitre suivant comment booter un noyau avec Grub directement depuis un disque IDE.

Anatomie d'un fichier ELF

Un fichier au format ELF est formé notamment par :

  • un premier en-tête qui décrit le type de fichier dont il est question et qui définit l'organisation globale au sein de ce fichier.
  • une table spéciale, la Program Header Table, qui décrit et localise les différents segments de code à charger par le noyau

Le schéma ci-dessous est une illustration d'un fichier ELF comportant deux segments de code :

L'en-tête d'un fichier ELF est défini dans le fichier elf.h par la structure Elf32_Ehdr.

typedef struct {
        unsigned char e_ident[16];      /* ELF identification */
        u16 e_type;             /* 2 (exec file) */
        u16 e_machine;          /* 3 (intel architecture) */
        u32 e_version;          /* 1 */
        u32 e_entry;            /* starting point */
        u32 e_phoff;            /* program header table offset */
        u32 e_shoff;            /* section header table offset */
        u32 e_flags;            /* various flags */
        u16 e_ehsize;           /* ELF header (this) size */

        u16 e_phentsize;        /* program header table entry size */
        u16 e_phnum;            /* number of entries */

        u16 e_shentsize;        /* section header table entry size */
        u16 e_shnum;            /* number of entries */

        u16 e_shstrndx;         /* index of the section name string table */
} Elf32_Ehdr;
  • La variable e_ident[] sert à identifier le type de fichier et doit commencer par les caractères : 0x7f, 'E', 'L', 'F'.
  • La variable e_entry définit le point d'entrée du programme et dans notre cas correspond à l'adresse virtuelle 0x40000000 (USER_OFFSET).
  • La variable e_phoff indique où se situe la Program Header Table décrivant les segments de code.
  • La variable e_phnum indique le nombre d'entrées dans cette table.

Les autres variables ne sont pas importantes pour le moment.

Chaque entrée de la Program Header Table est une structure qui donne les caractéristiques d'un segment dans le fichier et où le charger en mémoire. La structure correspondant à chacune de ces entrées est la suivante : Elf32_Phdr.

typedef struct {
        u32 p_type;             /* type of segment */
        u32 p_offset;
        u32 p_vaddr;
        u32 p_paddr;
        u32 p_filesz;
        u32 p_memsz;
        u32 p_flags;
        u32 p_align;
} Elf32_Phdr;
  • p_offset est l'offset où se situe le segment par rapport au début du fichier
  • p_filesz correspond à sa taille
  • p_vaddr est l'adresse virtuelle où il doit être chargé par le noyau
  • p_memsz correspond à sa taille en mémoire. Notez qu'il est tout à fait possible que la valeur de p_memsz soit supérieure à celle de p_filesz. Dans ce cas, le noyau doit simplement réserver l'espace suffisant et l'initialiser à 0.

Cette description d'un fichier ELF est très sommaire car ce format est complexe et je n'ai voulu indiquer que ce qui est indispensable pour notre noyau. Pour en savoir plus, je vous conseille de parcourir la documentation http://refspecs.freestandards.org/elf/elf.pdf.

Charger un exécutable

L'algorithme utilisé par la fonction load_elf() pour charger un exécutable au format ELF est très simple :

#include "elf.h"
#include "ext2.h"
#include "mm.h"
#include "lib.h"
#include "kmalloc.h"

/*
 * Teste si le fichier dont l'adresse est passee en argument
 * est au format ELF
 */

int is_elf(char *file)
{
        Elf32_Ehdr *hdr;

        hdr = (Elf32_Ehdr *) file;
        if (hdr->e_ident[0] == 0x7f && hdr->e_ident[1] == 'E'
            && hdr->e_ident[2] == 'L' && hdr->e_ident[3] == 'F')
                return 1;
        else
                return 0;
}

u32 load_elf(char *file, struct page_directory * pd, struct page_list * mmap)
{
        char *p;
        u32 v_begin, v_end, v_addr;
        Elf32_Ehdr *hdr;
        Elf32_Phdr *p_entry;
        int i, pe;

        hdr = (Elf32_Ehdr *) file;
        p_entry = (Elf32_Phdr *) (file + hdr->e_phoff)/* Program header table offset */

        if (!is_elf(file)) {
                printk("INFO: load_elf(): file not in ELF format !\n");
                return 0;
        }

        for (pe = 0; pe < hdr->e_phnum; pe++, p_entry++) {      /* Read each entry */

                if (p_entry->p_type == PT_LOAD) {
                        v_begin = p_entry->p_vaddr;
                        v_end = p_entry->p_vaddr + p_entry->p_memsz;

                        /*
                         * Allocation memoire mappee sur l'espace d'adressage
                         * utilisateur
                         */

                        for (v_addr = v_begin; v_addr < v_end; v_addr += PAGESIZE) {

                                if (v_addr < USER_OFFSET) {
                                        printk ("File can't load exec below %p\n", USER_OFFSET);
                                        return 0;
                                }

                                if (v_addr > USER_STACK) {
                                        printk ("File can't load exec above %p\n", USER_STACK);
                                        return 0;
                                }

                                if (get_p_addr((char *) v_addr) == 0) {
                                        if (mmap->page) {
                                                mmap->next = (struct page_list *) kmalloc(sizeof (struct page_list));
                                                mmap->next->next = 0;
                                                mmap->next->prev = mmap;
                                                mmap = mmap->next;
                                        }
                                        mmap->page = (struct page *) kmalloc(sizeof(struct page));
                                        mmap->page->p_addr = get_page_frame();
                                        mmap->page->v_addr = (char *) (v_addr & 0xFFFFF000);
                                        pd_add_page((char *) (v_addr & 0xFFFFF000), mmap->page->p_addr, PG_USER, pd);
                                }
                        }

                        memcpy((char *) v_begin, (char *) (file + p_entry->p_offset), p_entry->p_filesz);
                        if (p_entry->p_memsz > p_entry->p_filesz)
                                for (i = p_entry->p_filesz, p = (char *) p_entry->p_vaddr; i < p_entry->p_memsz; i++)
                                        p[i] = 0;
                }
        }

        /* Return program entry point */
        return hdr->e_entry;
}

Algorithme : load_elf
Paramètres : buffer contenant le fichier, adresse du répertoire de page
Début

Pour chaque entrée dans la table des segments
Si le segment est de type PT_LOAD alors
On charge le segment :
/* p_vaddr et p_memsz permettent de savoir où le segment sera logé en mémoire */
On contrôle que p_vaddr et p_vaddr + p_memsz sont valides
Les pages nécessaires sont réservées en mémoire physique
Le répertoire de page du processus courant est mis à jour
Le segment est recopié à l'endroit défini par p_vaddr
Si p_memsz > p_filesz alors
Les octets en plus sont mis à 0

Fin

En pratique, le fichier kernel.c contient le code permettant de charger un fichier exécutable. Pour le moment, on ne peut charger un exécutable que par son numéro d'inode. Celle-ci est chargée via la fonction ext2_read_inode(). Ensuite, la fonction load_task() charge l'exécutable :

inode = ext2_read_inode(hd, gd, 14);
load_task(hd, inode);

Ensuite, la fonction load_task() fait appel à la fonction load_elf() :

file = ext2_read_file(hd, inode);
load_elf(file, pd, mmap);

Un nouvel appel système : exit()

Jusqu'à présent, notre noyau disposait d'un appel système permettant à une application d'afficher un message sur la console. Cette partie présente un nouvel appel système permettant à une tâche utilisateur de se terminer proprement. Cet appel est implémenté dans le fichier syscalls.c. L'algorithme est très simple :

#include "types.h"
#include "lib.h"
#include "io.h"
#include "process.h"
#include "kmalloc.h"
#include "mm.h"
#include "schedule.h"

void do_syscalls(int sys_num)
{
        /* print console */
        if (sys_num == 1) {
                char *u_str;
                int i;

                asm("mov %%ebx, %0": "=m"(u_str):);
                for (i = 0; i < 100000; i++);   /* temporisation */

                cli;
                printk(u_str);
                sti;
        }

        /* exit */
        else if (sys_num == 2) {
                u16 kss;
                u32 kesp;
                struct page_list *pl, *oldpl;

                cli;

                n_proc--;
                current->state = 0;

                /*
                 * Liberation des ressources memoire allouees au processus :
                 *   - les pages utilisees par le code executable
                 *   - la pile utilisateur
                 *   - la pile noyau
                 *   - le repertoire et les tables de pages
                 */


                /* Libere la memoire occupee par l'executable */
                pl = current->pglist;
                while (pl) {
                        release_page_frame(pl->page->p_addr);
                        kfree(pl->page);
                        oldpl = pl;
                        pl = pl->next;
                        kfree(oldpl);
                }

                /* Libere la pile utilisateur */
                release_page_frame((u32) get_p_addr((char *) USER_STACK));     

                /* Libere la pile noyau */
                kss = p_list[0].regs.ss;
                kesp = p_list[0].regs.esp;
                asm("mov %0, %%ss; mov %1, %%esp;"::"m"(kss), "m"(kesp));
                release_page_from_heap((char *) ((u32) current->kstack.esp0 & 0xFFFFF000));

                /* Libere le repertoire et les tables de pages */
                asm("mov %0, %%eax; mov %%eax, %%cr3"::"m"(pd0));
                pd_destroy(current->pd);

                switch_to_task(0, KERNELMODE);
        }

        /* dump regs */
        else if (sys_num == 3) {
                u32 *pa;

                asm("mov %%ebp, %0": "=m"(pa):);

                printk("eax: %p ecx: %p edx: %p ebx: %p\n", pa[12], pa[11], pa[10], pa[9]);
                printk("ds: %p esi: %p edi: %p\n", pa[4], pa[6], pa[5]);
                printk("ss: %p ebp: %p esp: %p\n", pa[17], pa[7], pa[16]);
                printk("cs: %p eip: %p\n", pa[14], pa[13]);
        }

        else
                printk("unknown syscall %d\n", sys_num);

        return;
}

Algorithme : sys_exit()
Synopsis : termine le processus courant
Début

Désactive les interruptions
Décrémente le nombre de processus
Libère l'entrée dans la table des processus : current->state = 0
Libère les pages mémoires occupées par le code (current->mmap)
Libère la pile utilisateur (get_p_addr((char *) USER_STACK))
Libère la pile noyau (current->kstack)
Libère le répertoire et les tables de pages (current->pd)
Choix d'une nouvelle tâche (ici la "tâche" noyau)
Bascule sur la nouvelle tâche : switch_to_task()

Fin

Pour utiliser ce nouvel appel système, l'application devra simplement insérer l'instruction suivante :

asm("mov $0x02, %eax; int $0x30");

Créer et lancer une application au format ELF à partir du système de fichier

Pour compiler une application au format ELF fonctionnant sous Pépin :

$ gcc -c task1.c
$ ld -Ttext=40000000 --entry=main task1.o 

< Utiliser un système de fichier Ext2FS | TutoOS | Booter avec Grub sur un disque IDE >

print · rss · source
Page last modified on May 06, 2011, at 10:54 AM