把ELF文件加载到内存后进行重新搬移,并且执行

把ELF文件加载到内存后进行重新搬移,并且执行

ELF文件, Executable and Linkable Format,为可执行可链接的文件格式。我用这个格式的文件是因为想把汇编语言写得程序和C语言写的程序链接起来,然后放到内存里去运行。

ELF文件分为三种,可执行文件、可重定位文件、共享目标库文件。

可执行文件中,程序由段(Segment)构成,每一个段都有一个对应的段头(program header)来描述这个段(包括段在ELF文件中的偏移,大小,类型等)。所有的program header就构成了段表(program header table),一般对于可执行文件,我们只用段表,不用节表。

可重定位文件,程序由节(Section)构成,同段类似,每个节有节头,和节表。

共享目标库文件,既有段表(program header table)也有节表(section header table)。

目前,我只用了可执行文件这种类型的文件,因为我感觉它比较简单。现在说ELF文件的整个结构
ELF 文件结构

最开头是ELF头,它的大小固定,里面包含了对整个ELF文件的信息,例如有多少个段表,段表大小等。再接下去,紧接着的就是程序头表,也就是段表,其中每一个程序头都包含着对应段的一些信息(比较重要的有该段在ELF文件中的偏移,要映射到内存中的虚拟地址,段大小,段的类型)。通过段表,我们把各个段按照它的要求搬移到内存中的对应位置,然后在跳转入ELF入口地址,就可以执行这个ELF文件了。

现在给出ELF头的数据结构 和 程序头的数据结构

/**********************************************************************************
    ----------------------------------------
        |   ELF header    -->  ELF头       |
    ----------------------------------------            
    |     Program header 0 \               |
    |     Program header 1  >-> Program header table 程序头表
    |     Program header 2 /               |
    |     ......                           |
    ----------------------------------------     
    |     Section 0        \               |
    |     Section 1         >-> 节         |
    |     Section 2        /               |
    |     ......                           |
    ----------------------------------------     
    |     Section header 0 \               |
    |     Section header 1  >-> Section header table 节头表
    |     Section header 2 /               |
    |     ......                           |
    ----------------------------------------
**********************************************************************************/


/**********************************************************************************
因为ELF文件想要支持8位到32位的各种架构的机器,所以定义了如下数据类型

数据类型名称  大小(字节)  对齐      用途
Elf32_Addr      4           4           无符号程序地址
Elf32_Half      2           2           无符号中等大小整数
Elf32_Off       4           4           无符号文件偏移
Elf32_Sword     4           4           有符号大整数
Elf32_Word      4           4           无符号大整数
unsigned char   1           1           无符号小整数

**********************************************************************************/

/* ELF header */
#define EI_NIDENT   16
typedef struct {
    unsigned char   e_ident[EI_NIDENT]; /* 0 一共16个字节,其中开头的4个字节固定不变,位7F 45 4C 46 表示ELF文件 */
    Elf32_Half  e_type;         /* 16 文件的类型(可读可写可执行) */
    Elf32_Half  e_machine;      /* 18 运行改程序需要的体系结构,如Intel 80386 */
    Elf32_Word  e_version;      /* 20 该文件的版本 */
    Elf32_Addr  e_entry;        /* 24 程序入口地址 */
    Elf32_Off   e_phoff;        /* 28 Program header table在文件中的偏移量(以字节位单位) */
    Elf32_Off   e_shoff;        /* 32 Section header table在文件中的偏移量(以字节为单位) */
    Elf32_Word  e_flags;        /* 36 IA32中为0 */
    Elf32_Half  e_ehsize;       /* 40 ELF header的大小(以字节位单位) */
    Elf32_Half  e_phentsize;    /* 42 Program header table中每一条目的大小 */
    Elf32_Half  e_phnum;        /* 44 Program header table中有多少个条目 */
    Elf32_Half  e_shentsize;    /* 46 Section header table中每一个条目大小 */
    Elf32_Half  e_shnum;        /* 48 Section header table中有多少个条目 */
    Elf32_Half  e_shstrndx;     /* 50 包含节名称的字符串表示第几个字节 */
}Elf32_Ehdr;


/* Program header */
typedef strcut {
    Elf32_Word  p_type;     /* 0 当前Program header所描述的段的类型 */
    Elf32_Off   p_offset;   /* 4 段的第一个字节在文件中的偏移 */
    Elf32_Addr  p_vaddr;    /* 8 段的第一个字节在内存中的虚拟地址 */
    Elf32_Addr  p_paddr;    /* 12 此项为物理地址保留 */
    Elf32_Word  p_filesz;   /* 16 段在文件中的长度 */
    Elf32_Word  p_memsz;    /* 20 段在内存中的长度 */
    Elf32_Word  p_flags;    /* 24 与段相关的标志 */
    Elf32_Word  p_align;    /* 28 规定该段在文件或者内存中的对其方式 */
}Elf32_Phdr;

然后我们在Linux下用readelf -a elf.bin来查看该ELF文件的信息
这里写图片描述
这是我自己写的一个.asm文件和一个.c文件编译链接后得到的ELF格式文件。其中在ELF头中,我们要注重程序入口地址为0x50000,在我们搬移完ELF文件后就要跳转到这个位置来执行ELF文件;还有就是程序头起点,从这个位置开始保存着我们的段的信息。

接下来是接头,不重要,跳过不看、

最后是程序头,这个可以说是最重要的了。在这里我的程序一共只有一个段,另外一个GNU_STACK大小为零,就不搬移了。这一个段在ELF文件中的偏移为0x001000,在内存中需要的虚拟地址为0x00050000,段的大小为0x000B8,可读可执行,最后一个是对齐。这就是我们要搬移的规则,必须按照这个规则搬移。

则搬移ELF文件的函数为:

test_move_elf:
mov ax, ds
push ax
mov ax, es
push ax
push esi
push edi

    mov ax, SelectorElf
    mov ds, ax
    mov ax, SelectorPhy
    mov es, ax

    mov ecx, 2
    .check_elf:
        cmp ecx, 0
        jz .move_end
        dec ecx

        push ecx

            mov ecx, 0xB8
            mov esi, 0x1000
            mov edi, 0x50000

            .test_move:
                cmp ecx, 0
                jz .test_move_end
                dec ecx
                mov al, byte [ds:esi]
                mov byte [es:edi], al
                inc esi
                inc edi
                jmp .test_move
            .test_move_end:

        pop ecx

.move_end:
pop edi
pop esi
pop ax
mov es, ax
pop ax
mov ds, ax
ret

最后我们再直接跳转到入口地址 jmp SelectorExe:0x50000就可以了

图是运行结果
这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值