跟我一起写操作系统

    返回首页    发表留言
本文作者:李德强
          第二节 载入可执行文件
 
 

        本节的目的是要编写一个可重定位的程序,通过内核程序将其载入到内存中,再通过对这种可重定位文件进行重定位,将其转换为一个可执行的程序。首先来对上一节开始时的system程序做一些修改start_main.c和system.c分别如下:

//start_main.c
#include <shell/start_main.h>
extern int main(int argc, char *args[]);
void start_main()
{
        main(1, NULL);
        for (;;)
        {
        }
}
//system.c
#include <shell/system.h>
int main(int argc, char **args)
{
        char *str = "Start System Process...";
        __asm__ volatile("int        $0x82" :: "a"(str));
        for (;;)
        {
        }
        return 0;
}

        在编译源代码时使用gcc命令将start_main.c和system.c分别编译为start_main.o和system.o,再使用ld命令将其链接成一个文件,并使用start_main()函数为程序的入口函数,让start_main()函数作为.text段的0地址,也就是使用-Ttext 0x0参数:

gcc -m32 -c start_main.c -o start_main.o
gcc -m32 -c system.c -o system.o
ld  -m elf_i386 -Ttext 0x0 -e start_main -r start_main.o system.o -o system

        我们知道C语言规定程序的入口函数为main(),但是我们需要在程序被执行之前做一些进程的相关操作和记录,在程序执行结束或异常终止后再执行一些清理工作,这些工作就是在start_main()函数中实现的。编写一个start_main.c源代码并生成start_main.o,今后所有程序在链接时都使用这个start_main.o与之链接,start_main函数将在调用main函数前申请进程内存,初始化等工作,然后调用main()函数,在main()函数执行完毕或异常中止后再做相关清理工作。目前start_main()函数中只有调用main()函数的功能,其它功能在后续章节中再做完善。在main()函数中调用了int $0x82中断,也就是说普通程序通过中断的方式切入内核,调用内核程序。0x82号中断将要为普通程序提供标准输入输出功能,它调用了内核程序中的sys_stdio()函数,这函数目前也只实现输出一个字符串功能。其它功能也将在后续章节内完善:

void sys_stdio(void *params)
{
        set_ds(GDT_INDEX_KERNEL_DS);
        set_cr3(PAGE_DIR);
        u32 cr3 = pcb_cur->tss.cr3;
        params = addr_parse(cr3, params);
        printf("%s\n", (char *)(params));
        set_cr3(cr3);
        set_ds(0xf);
}

        在前面章节中我们学习了内存分页,知道程序在运行时所使用的地址均为逻辑地址,而内核程序所使用的都是物理地址。所以当中断程序想要使用普通程序传递进来的地址参数时,必须将其先转化为物理地址:

void* addr_parse(u32 cr3, void *p)
{
        u32 addr = (u32) p;
        u32 *page_dir = (u32 *) cr3;
        //页目录索引
        u32 page_dir_index = (addr >> 22) & 0x3ff;
        //页表索引
        u32 page_table_index = (addr >> 12) & 0x3ff;
        u32 *page_tbl = (u32 *) (page_dir[page_dir_index] & 0xfffff000);
        void *p_addr = (void *) ((page_tbl[page_table_index] & 0xfffff000) | (addr & 0xfff));
        return p_addr;
}

        下面来实现载入可重定位程序并对其进程重定位操作将其转为可执行程序。首先将已经编译链接成功的system文件载入到内存中:

void install_system()
{
        //载入并运行system程序
        load_process("/usr/bin/system", "");
}
/*
 * 载入文件系统中的可执行程序
 */
s_pcb* load_process(char *file_name, char *params)
{
        //从文件系统读入程序
        s_file *fp = f_open(file_name, FS_MODE_READ, 0, 0);
        //计算程序占用的页面数量
        int run_pages = fp->fs.size / MM_PAGE_SIZE;
        if (fp->fs.size % MM_PAGE_SIZE != 0)
        {
                run_pages++;
        }
        //申请页面用于存放程序代码
        void *run = alloc_page(process_id, run_pages, 0, 0);
        //读入程序
        f_read(fp, fp->fs.size, (u8 *) run);
        //程序大小
        u32 run_size = fp->fs.size;
        //关闭文件
        f_close(fp);
        //对elf可重定位文件进行重定位
        u32 entry_point = relocation_elf(run);
        
        //初始化进程
        ……
}

        对elf可重定位文件进行分析并重新定位:

/*
 * elf可执行文件重定位
 *  - void *addr : 可执行程序地址
 * return : u32 程序入口地址
 */
u32 relocation_elf(void *addr)
{
        //elf文件头
        Elf32_Ehdr ehdr;
        mmcopy_with((char *) addr, (char *) &ehdr, 0, sizeof(Elf32_Ehdr));

        //程序头
        Elf32_Phdr phdr;
        mmcopy_with((char *) addr, (char *) &phdr, ehdr.e_phoff, sizeof(Elf32_Phdr));
        //段头
        Elf32_Shdr shdrs[ehdr.e_shnum];
        //循环程序头
        u32 shstrtab_size = 0;
        u32 strtab_size = 0;
        u32 symtab_size = 0;
        char *shstrtab = NULL;
        char *symtab = NULL;
        char *strtab = NULL;
        for (u32 i = 0; i < ehdr.e_shnum; ++i)
        {
                mmcopy_with((char *) addr, (char *) &shdrs[i], ehdr.e_shoff + (i * sizeof(Elf32_Shdr)), sizeof(Elf32_Shdr));
                //符号段
                if (shdrs[i].sh_type == 2)
                {
                        symtab_size = shdrs[i].sh_size;
                        symtab = alloc_mm(symtab_size);
                        mmcopy_with((char *) addr, symtab, shdrs[i].sh_offset, symtab_size);
                }
                //符号段
                else if (shdrs[i].sh_type == 3)
                {
                        char *buff = alloc_mm(shdrs[i].sh_size);
                        mmcopy_with((char *) addr, buff, shdrs[i].sh_offset, shdrs[i].sh_size);
                        //取得段符号
                        if (str_compare(".shstrtab", (char *) &buff[shdrs[i].sh_name]) == 0)
                        {
                                shstrtab_size = shdrs[i].sh_size;
                                shstrtab = alloc_mm(shstrtab_size);
                                mmcopy_with(buff, shstrtab, 0, shstrtab_size);
                        }
                        //取得普通符号
                        else
                        {
                                strtab_size = shdrs[i].sh_size;
                                strtab = alloc_mm(strtab_size);
                                mmcopy_with(buff, strtab, 0, strtab_size);
                        }
                        free_mm(buff, shdrs[i].sh_size);
                }
        }
        u32 symtab_num = symtab_size / sizeof(Elf32_Sym);
        Elf32_Sym syms[symtab_num];
        for (u32 i = 0; i < symtab_num; i++)
        {
                mmcopy_with(symtab, &syms[i], i * sizeof(Elf32_Sym), sizeof(Elf32_Sym));
        }
        //重定位.rel.text
        Elf32_Shdr sh_rel_text;
        //重定位.rel.data
        Elf32_Shdr sh_rel_data;
        //重定位.rel.text偏移地址
        u32 sh_text_offset = 0;
        //重定位.rel.data偏移地址
        u32 sh_data_offset = 0;
        //取得重定位段
        for (u32 i = 0; i < ehdr.e_shnum; ++i)
        {
                //如果需要重定位
                if (str_compare(".text", (char *) &shstrtab[shdrs[i].sh_name]) == 0)
                {
                        sh_text_offset = shdrs[i].sh_offset;
                }
                //如果需要重定位
                if (str_compare(".data", (char *) &shstrtab[shdrs[i].sh_name]) == 0)
                {
                        sh_data_offset = shdrs[i].sh_offset;
                }
                //如果需要重定位text段
                if (str_compare(".rel.text", (char *) &shstrtab[shdrs[i].sh_name]) == 0)
                {
                        mmcopy_with(&shdrs[i], &sh_rel_text, 0, sizeof(Elf32_Shdr));
                }
                //如果需要重定位data段
                if (str_compare(".rel.data", (char *) &shstrtab[shdrs[i].sh_name]) == 0)
                {
                        mmcopy_with(&shdrs[i], &sh_rel_data, 0, sizeof(Elf32_Shdr));
                }
        }
        //取得重定位项个数
        u32 rel_text_num = sh_rel_text.sh_size / sizeof(Elf32_Rel);
        u32 rel_data_num = sh_rel_data.sh_size / sizeof(Elf32_Rel);

        //对.text段重定位
        relocation_elf_text_data(addr, sh_rel_text, rel_text_num, sh_text_offset, syms, symtab_num, shdrs);
        //对.data段重定位
        relocation_elf_text_data(addr, sh_rel_data, rel_data_num, sh_data_offset, syms, symtab_num, shdrs);
        if (shstrtab != NULL)
        {
                free_mm(shstrtab, shstrtab_size);
        }
        if (strtab != NULL)
        {
                free_mm(strtab, strtab_size);
        }
        if (symtab != NULL)
        {
                free_mm(symtab, symtab_size);
        }
        //返回程序入口地址
        return sh_text_offset;
}
/*
 * 对.text段或.data段进程重定位
 */
void relocation_elf_text_data(void *addr, Elf32_Shdr sh_rel, u32 rel_num, u32 sh_offset, Elf32_Sym *syms, u32 syms_num, Elf32_Shdr *shdrs)
{
        //取得text段重定位项
        for (u32 i = 0; i < rel_num; i++)
        {
                //重定位项
                Elf32_Rel e_rel;
                //取得重定位项
                mmcopy_with((char *) addr, &e_rel, sh_rel.sh_offset + (i * sizeof(Elf32_Rel)), sizeof(Elf32_Rel));
                //取得重定位符号
                u32 sym = (e_rel.r_info >> 8) & 0xff;
                //计算重定位地址
                u32 relocation_val = relocation_elf_sym(sym, syms, syms_num, shdrs);
                //取得需要重定位内容的地址
                u32 *rel = (u32 *) (addr + sh_offset + e_rel.r_offset);
                //如果重定位类型为1,则按R_386_32方式重定位: S + A
                if ((e_rel.r_info & 0xff) == 1)
                {
                        *rel += (u32) addr + relocation_val;
                }
                //如果重定位类型为2,则按R_386_PC32方式重定位: S + A - P
                else if ((e_rel.r_info & 0xff) == 2)
                {
                        //减4的原因是要跳过当前4字节的数据项从下一个命令的开始算起
                        *rel = (u32) addr + relocation_val - (u32) rel - 4;
                }
        }
}
/*
 * 计算符号重定位地址
 */
u32 relocation_elf_sym(u32 sym, Elf32_Sym *syms, u32 syms_num, Elf32_Shdr *shdrs)
{
        //循环符号表
        for (int i = 0; i < syms_num; i++)
        {
                //找到需要重定位的符号项
                if (sym == i)
                {
                        //计算重定位地址
                        return shdrs[syms[i].st_shndx].sh_offset + syms[i].st_value;
                }
        }
        return 0;
}

        将程序需要重定位的地址都重新定位成实际的运行地址,这样一个可重定位文件就被修改成为一个可执行程序,在下一节中将介绍将可执行程序创建为一个进程。

    返回首页    返回顶部
  看不清?点击刷新

 

  Copyright © 2015-2018 问渠网 辽ICP备15013245号