田雅宁 发表于 2025-5-28 23:48:27

如何实现在Windows上运行Linux程序,附示例代码

微软在去年发布了Bash On Windows, 这项技术允许在Windows上运行Linux程序, 我相信已经有很多文章解释过Bash On Windows的原理,
而今天的这篇文章将会讲解如何自己实现一个简单的原生Linux程序运行器, 这个运行器在用户层实现, 原理和Bash On Windows不完全一样,比较接近Linux上的Wine.
示例程序完整的代码在github上, 地址是 https://github.com/303248153/HelloElfLoader
初步了解ELF格式

首先让我们先了解什么是原生Linux程序, 以下说明摘自维基百科
In computing, the Executable and Linkable Format (ELF, formerly named Extensible Linking Format), is a common standard file format for executable files, object code, shared libraries, and core dumps. First published in the specification for the application binary interface (ABI) of the Unix operating system version named System V Release 4 (SVR4), and later in the Tool Interface Standard, it was quickly accepted among different vendors of Unix systems. In 1999, it was chosen as the standard binary file format for Unix and Unix-like systems on x86 processors by the 86open project.

By design, ELF is flexible, extensible, and cross-platform, not bound to any given central processing unit (CPU) or instruction set architecture. This has allowed it to be adopted by many different operating systems on many different hardware platforms.Linux的可执行文件格式采用了ELF格式, 而Windows采用了PE格式, 也就是我们经常使用的exe文件的格式.
ELF格式的结构如下
https://upload.wikimedia.org/wikipedia/commons/thumb/7/77/Elf-layout--en.svg/260px-Elf-layout--en.svg.png
大致上可以分为这些部分

[*]ELF头,在文件的最开头,储存了类型和版本等信息
[*]程序头, 供程序运行时解释器(interpreter)使用
[*]节头, 供程序编译时链接器(linker)使用, 运行时不需要读节头
[*]节内容, 不同的节作用都不一样

[*].text 代码节,保存了主要的程序代码
[*].rodata 保存了只读的数据,例如字符串(const char*)
[*].data 保存了可读写的数据,例如全局变量
[*]还有其他各种各样的节

让我们来实际看一下Linux可执行程序的样子
以下的编译环境是Ubuntu 16.04 x64 + gcc 5.4.0, 编译环境不一样可能会得出不同的结果
首先创建hello.c,写入以下的代码
#include <stdio.h>

int max(int x, int y) {
        return x > y ? x : y;
}

int main() {
        printf("max is %d\n", max(123, 321));
        printf("test many arguments %d %d %d %s %s %s %s %s %s\n", 1, 2, 3, "a", "b", "c", "d", "e", "f");
        return 100;
}然后使用gcc编译这份代码
gcc hello.c编译完成后你可以看到hello.c旁边多了一个a.out, 这就是linux的可执行文件了, 现在可以在linux上运行它
./a.out你可以看到以下输出
max is 321
test many arguments 1 2 3 a b c d e f我们来看看a.out包含了什么,解析ELF文件可以使用readelf命令
readelf -a ./a.out可以看到输出了以下的信息
ELF 头:
Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
类别:                              ELF64
数据:                              2 补码,小端序 (little endian)
版本:                              1 (current)
OS/ABI:                            UNIX - System V
ABI 版本:                        0
类型:                              EXEC (可执行文件)
系统架构:                        Advanced Micro Devices X86-64
版本:                              0x1
入口点地址:               0x400430
程序头起点:          64 (bytes into file)
Start of section headers:          6648 (bytes into file)
标志:             0x0
本头的大小:       64 (字节)
程序头大小:       56 (字节)
Number of program headers:         9
节头大小:         64 (字节)
节头数量:         31
字符串表索引节头: 28

节头:
[号] 名称            类型             地址            偏移量
       大小            全体大小          旗标   链接   信息   对齐
[ 0]                   NULL             000000000000000000000000
       00000000000000000000000000000000         0   0   0
[ 1] .interp         PROGBITS         000000000040023800000238
       000000000000001c0000000000000000   A       0   0   1
[ 2] .note.ABI-tag   NOTE             000000000040025400000254
       00000000000000200000000000000000   A       0   0   4
[ 3] .note.gnu.build-i NOTE             000000000040027400000274
       00000000000000240000000000000000   A       0   0   4
[ 4] .gnu.hash         GNU_HASH         000000000040029800000298
       000000000000001c0000000000000000   A       5   0   8
[ 5] .dynsym         DYNSYM         00000000004002b8000002b8
       00000000000000600000000000000018   A       6   1   8
[ 6] .dynstr         STRTAB         000000000040031800000318
       000000000000003f0000000000000000   A       0   0   1
[ 7] .gnu.version      VERSYM         000000000040035800000358
       00000000000000080000000000000002   A       5   0   2
[ 8] .gnu.version_r    VERNEED          000000000040036000000360
       00000000000000200000000000000000   A       6   1   8
[ 9] .rela.dyn         RELA             000000000040038000000380
       00000000000000180000000000000018   A       5   0   8
.rela.plt         RELA             000000000040039800000398
       00000000000000300000000000000018AI       5    24   8
.init             PROGBITS         00000000004003c8000003c8
       000000000000001a0000000000000000AX       0   0   4
.plt            PROGBITS         00000000004003f0000003f0
       00000000000000300000000000000010AX       0   0   16
.plt.got          PROGBITS         000000000040042000000420
       00000000000000080000000000000000AX       0   0   8
.text             PROGBITS         000000000040043000000430
       00000000000001f20000000000000000AX       0   0   16
.fini             PROGBITS         000000000040062400000624
       00000000000000090000000000000000AX       0   0   4
.rodata         PROGBITS         000000000040063000000630
       00000000000000500000000000000000   A       0   0   8
.eh_frame_hdr   PROGBITS         000000000040068000000680
       000000000000003c0000000000000000   A       0   0   4
.eh_frame         PROGBITS         00000000004006c0000006c0
       00000000000001140000000000000000   A       0   0   8
.init_array       INIT_ARRAY       0000000000600e1000000e10
       00000000000000080000000000000000WA       0   0   8
.fini_array       FINI_ARRAY       0000000000600e1800000e18
       00000000000000080000000000000000WA       0   0   8
.jcr            PROGBITS         0000000000600e2000000e20
       00000000000000080000000000000000WA       0   0   8
.dynamic          DYNAMIC          0000000000600e2800000e28
       00000000000001d00000000000000010WA       6   0   8
.got            PROGBITS         0000000000600ff800000ff8
       00000000000000080000000000000008WA       0   0   8
.got.plt          PROGBITS         000000000060100000001000
       00000000000000280000000000000008WA       0   0   8
.data             PROGBITS         000000000060102800001028
       00000000000000100000000000000000WA       0   0   8
.bss            NOBITS         000000000060103800001038
       00000000000000080000000000000000WA       0   0   1
.comment          PROGBITS         000000000000000000001038
       00000000000000340000000000000001MS       0   0   1
.shstrtab         STRTAB         0000000000000000000018ea
       000000000000010c0000000000000000         0   0   1
.symtab         SYMTAB         000000000000000000001070
       00000000000006600000000000000018          30    47   8
.strtab         STRTAB         0000000000000000000016d0
       000000000000021a0000000000000000         0   0   1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), l (large)
I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)

There are no section groups in this file.

程序头:
Type         Offset             VirtAddr         PhysAddr
               FileSiz            MemSiz            FlagsAlign
PHDR         0x0000000000000040 0x0000000000400040 0x0000000000400040
               0x00000000000001f8 0x00000000000001f8R E    8
INTERP         0x0000000000000238 0x0000000000400238 0x0000000000400238
               0x000000000000001c 0x000000000000001cR      1
      
LOAD         0x0000000000000000 0x0000000000400000 0x0000000000400000
               0x00000000000007d4 0x00000000000007d4R E    200000
LOAD         0x0000000000000e10 0x0000000000600e10 0x0000000000600e10
               0x0000000000000228 0x0000000000000230RW   200000
DYNAMIC      0x0000000000000e28 0x0000000000600e28 0x0000000000600e28
               0x00000000000001d0 0x00000000000001d0RW   8
NOTE         0x0000000000000254 0x0000000000400254 0x0000000000400254
               0x0000000000000044 0x0000000000000044R      4
GNU_EH_FRAME   0x0000000000000680 0x0000000000400680 0x0000000000400680
               0x000000000000003c 0x000000000000003cR      4
GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
               0x0000000000000000 0x0000000000000000RW   10
GNU_RELRO      0x0000000000000e10 0x0000000000600e10 0x0000000000600e10
               0x00000000000001f0 0x00000000000001f0R      1

Section to Segment mapping:
段节...
   00   
   01   .interp
   02   .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .plt.got .text .fini .rodata .eh_frame_hdr .eh_frame
   03   .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss
   04   .dynamic
   05   .note.ABI-tag .note.gnu.build-id
   06   .eh_frame_hdr
   07   
   08   .init_array .fini_array .jcr .dynamic .got

Dynamic section at offset 0xe28 contains 24 entries:
标记      类型                         名称/值
0x0000000000000001 (NEEDED)             共享库:
0x000000000000000c (INIT)               0x4003c8
0x000000000000000d (FINI)               0x400624
0x0000000000000019 (INIT_ARRAY)         0x600e10
0x000000000000001b (INIT_ARRAYSZ)       8 (bytes)
0x000000000000001a (FINI_ARRAY)         0x600e18
0x000000000000001c (FINI_ARRAYSZ)       8 (bytes)
0x000000006ffffef5 (GNU_HASH)         0x400298
0x0000000000000005 (STRTAB)             0x400318
0x0000000000000006 (SYMTAB)             0x4002b8
0x000000000000000a (STRSZ)            63 (bytes)
0x000000000000000b (SYMENT)             24 (bytes)
0x0000000000000015 (DEBUG)            0x0
0x0000000000000003 (PLTGOT)             0x601000
0x0000000000000002 (PLTRELSZ)         48 (bytes)
0x0000000000000014 (PLTREL)             RELA
0x0000000000000017 (JMPREL)             0x400398
0x0000000000000007 (RELA)               0x400380
0x0000000000000008 (RELASZ)             24 (bytes)
0x0000000000000009 (RELAENT)            24 (bytes)
0x000000006ffffffe (VERNEED)            0x400360
0x000000006fffffff (VERNEEDNUM)         1
0x000000006ffffff0 (VERSYM)             0x400358
0x0000000000000000 (NULL)               0x0

重定位节 '.rela.dyn' 位于偏移量 0x380 含有 1 个条目:
偏移量          信息         类型         符号值      符号名称 + 加数
000000600ff8000300000006 R_X86_64_GLOB_DAT 0000000000000000 __gmon_start__ + 0

重定位节 '.rela.plt' 位于偏移量 0x398 含有 2 个条目:
偏移量          信息         类型         符号值      符号名称 + 加数
000000601018000100000007 R_X86_64_JUMP_SLO 0000000000000000 printf@GLIBC_2.2.5 + 0
000000601020000200000007 R_X86_64_JUMP_SLO 0000000000000000 __libc_start_main@GLIBC_2.2.5 + 0

The decoding of unwind sections for machine type Advanced Micro Devices X86-64 is not currently supported.

Symbol table '.dynsym' contains 4 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
   0: 0000000000000000   0 NOTYPELOCALDEFAULTUND
   1: 0000000000000000   0 FUNC    GLOBAL DEFAULTUND printf@GLIBC_2.2.5 (2)
   2: 0000000000000000   0 FUNC    GLOBAL DEFAULTUND __libc_start_main@GLIBC_2.2.5 (2)
   3: 0000000000000000   0 NOTYPEWEAK   DEFAULTUND __gmon_start__

Symbol table '.symtab' contains 68 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
   0: 0000000000000000   0 NOTYPELOCALDEFAULTUND
   1: 0000000000400238   0 SECTION LOCALDEFAULT    1
   2: 0000000000400254   0 SECTION LOCALDEFAULT    2
   3: 0000000000400274   0 SECTION LOCALDEFAULT    3
   4: 0000000000400298   0 SECTION LOCALDEFAULT    4
   5: 00000000004002b8   0 SECTION LOCALDEFAULT    5
   6: 0000000000400318   0 SECTION LOCALDEFAULT    6
   7: 0000000000400358   0 SECTION LOCALDEFAULT    7
   8: 0000000000400360   0 SECTION LOCALDEFAULT    8
   9: 0000000000400380   0 SECTION LOCALDEFAULT    9
    10: 0000000000400398   0 SECTION LOCALDEFAULT   10
    11: 00000000004003c8   0 SECTION LOCALDEFAULT   11
    12: 00000000004003f0   0 SECTION LOCALDEFAULT   12
    13: 0000000000400420   0 SECTION LOCALDEFAULT   13
    14: 0000000000400430   0 SECTION LOCALDEFAULT   14
    15: 0000000000400624   0 SECTION LOCALDEFAULT   15
    16: 0000000000400630   0 SECTION LOCALDEFAULT   16
    17: 0000000000400680   0 SECTION LOCALDEFAULT   17
    18: 00000000004006c0   0 SECTION LOCALDEFAULT   18
    19: 0000000000600e10   0 SECTION LOCALDEFAULT   19
    20: 0000000000600e18   0 SECTION LOCALDEFAULT   20
    21: 0000000000600e20   0 SECTION LOCALDEFAULT   21
    22: 0000000000600e28   0 SECTION LOCALDEFAULT   22
    23: 0000000000600ff8   0 SECTION LOCALDEFAULT   23
    24: 0000000000601000   0 SECTION LOCALDEFAULT   24
    25: 0000000000601028   0 SECTION LOCALDEFAULT   25
    26: 0000000000601038   0 SECTION LOCALDEFAULT   26
    27: 0000000000000000   0 SECTION LOCALDEFAULT   27
    28: 0000000000000000   0 FILE    LOCALDEFAULTABS crtstuff.c
    29: 0000000000600e20   0 OBJECTLOCALDEFAULT   21 __JCR_LIST__
    30: 0000000000400460   0 FUNC    LOCALDEFAULT   14 deregister_tm_clones
    31: 00000000004004a0   0 FUNC    LOCALDEFAULT   14 register_tm_clones
    32: 00000000004004e0   0 FUNC    LOCALDEFAULT   14 __do_global_dtors_aux
    33: 0000000000601038   1 OBJECTLOCALDEFAULT   26 completed.7585
    34: 0000000000600e18   0 OBJECTLOCALDEFAULT   20 __do_global_dtors_aux_fin
    35: 0000000000400500   0 FUNC    LOCALDEFAULT   14 frame_dummy
    36: 0000000000600e10   0 OBJECTLOCALDEFAULT   19 __frame_dummy_init_array_
    37: 0000000000000000   0 FILE    LOCALDEFAULTABS hello.c
    38: 0000000000000000   0 FILE    LOCALDEFAULTABS crtstuff.c
    39: 00000000004007d0   0 OBJECTLOCALDEFAULT   18 __FRAME_END__
    40: 0000000000600e20   0 OBJECTLOCALDEFAULT   21 __JCR_END__
    41: 0000000000000000   0 FILE    LOCALDEFAULTABS
    42: 0000000000600e18   0 NOTYPELOCALDEFAULT   19 __init_array_end
    43: 0000000000600e28   0 OBJECTLOCALDEFAULT   22 _DYNAMIC
    44: 0000000000600e10   0 NOTYPELOCALDEFAULT   19 __init_array_start
    45: 0000000000400680   0 NOTYPELOCALDEFAULT   17 __GNU_EH_FRAME_HDR
    46: 0000000000601000   0 OBJECTLOCALDEFAULT   24 _GLOBAL_OFFSET_TABLE_
    47: 0000000000400620   2 FUNC    GLOBAL DEFAULT   14 __libc_csu_fini
    48: 0000000000000000   0 NOTYPEWEAK   DEFAULTUND _ITM_deregisterTMCloneTab
    49: 0000000000601028   0 NOTYPEWEAK   DEFAULT   25 data_start
    50: 0000000000601038   0 NOTYPEGLOBAL DEFAULT   25 _edata
    51: 0000000000400624   0 FUNC    GLOBAL DEFAULT   15 _fini
    52: 0000000000000000   0 FUNC    GLOBAL DEFAULTUND printf@@GLIBC_2.2.5
    53: 0000000000400526    22 FUNC    GLOBAL DEFAULT   14 max
    54: 0000000000000000   0 FUNC    GLOBAL DEFAULTUND __libc_start_main@@GLIBC_
    55: 0000000000601028   0 NOTYPEGLOBAL DEFAULT   25 __data_start
    56: 0000000000000000   0 NOTYPEWEAK   DEFAULTUND __gmon_start__
    57: 0000000000601030   0 OBJECTGLOBAL HIDDEN    25 __dso_handle
    58: 0000000000400630   4 OBJECTGLOBAL DEFAULT   16 _IO_stdin_used
    59: 00000000004005b0   101 FUNC    GLOBAL DEFAULT   14 __libc_csu_init
    60: 0000000000601040   0 NOTYPEGLOBAL DEFAULT   26 _end
    61: 0000000000400430    42 FUNC    GLOBAL DEFAULT   14 _start
    62: 0000000000601038   0 NOTYPEGLOBAL DEFAULT   26 __bss_start
    63: 000000000040053c   109 FUNC    GLOBAL DEFAULT   14 main
    64: 0000000000000000   0 NOTYPEWEAK   DEFAULTUND _Jv_RegisterClasses
    65: 0000000000601038   0 OBJECTGLOBAL HIDDEN    25 __TMC_END__
    66: 0000000000000000   0 NOTYPEWEAK   DEFAULTUND _ITM_registerTMCloneTable
    67: 00000000004003c8   0 FUNC    GLOBAL DEFAULT   11 _init

Version symbols section '.gnu.version' contains 4 entries:
地址: 0000000000400358Offset: 0x000358Link: 5 (.dynsym)
000:   0 (*本地*)       2 (GLIBC_2.2.5)   2 (GLIBC_2.2.5)   0 (*本地*)   

Version needs section '.gnu.version_r' contains 1 entries:
地址:0x0000000000400360Offset: 0x000360Link: 6 (.dynstr)
000000: 版本: 1文件:libc.so.6计数:1
0x0010:名称:GLIBC_2.2.5标志:无版本:2

Displaying notes found at file offset 0x00000254 with length 0x00000020:
Owner               Data size        Description
GNU                  0x00000010        NT_GNU_ABI_TAG (ABI version tag)
    OS: Linux, ABI: 2.6.32

Displaying notes found at file offset 0x00000274 with length 0x00000024:
Owner               Data size        Description
GNU                  0x00000014        NT_GNU_BUILD_ID (unique build ID bitstring)
    Build ID: debd3d7912be860a432b5c685a6cff7fd9418528从上面的信息中我们可以知道这个文件的类型是ELF64, 也就是64位的可执行程序, 并且有9个程序头和31个节头, 各个节的作用大家可以在网上找到资料, 这篇文章中只涉及到以下的节

[*].init 程序初始化的代码
[*].rela.dyn 需要重定位的变量列表
[*].rela.plt 需要重定位的函数列表
[*].plt 调用动态链接函数的代码
[*].text 保存了主要的程序代码
[*].init 保存了程序的初始化代码, 用于初始化全局变量等
[*].fini 保存了程序的终止代码, 用于析构全局变量等
[*].rodata 保存了只读的数据,例如字符串(const char*)
[*].data 保存了可读写的数据,例如全局变量
[*].dynsym 动态链接的符号表
[*].dynstr 动态链接的符号名称字符串
[*].dynamic 动态链接所需要的信息,供程序运行时使用(不需要访问节头)
什么是动态链接

上面的程序中调用了printf函数, 然而这个函数的实现并不在./a.out中, 那么printf函数在哪里, 又是怎么被调用的?
printf函数的实现在glibc库中, 也就是/lib/x86_64-linux-gnu/libc.so.6中, 在执行./a.out的时候会在glibc库中找到这个函数并进行调用, 我们来看看这段代码
执行以下命令反编译./a.out
objdump -c -S ./a.out我们可以看到以下的代码
00000000004003f0 <printf@plt-0x10>:
4003f0:        ff 35 12 0c 20 00            pushq0x200c12(%rip)      # 601008 <_GLOBAL_OFFSET_TABLE_+0x8>
4003f6:        ff 25 14 0c 20 00            jmpq   *0x200c14(%rip)      # 601010 <_GLOBAL_OFFSET_TABLE_+0x10>
4003fc:        0f 1f 40 00                  nopl   0x0(%rax)

0000000000400400 <printf@plt>:
400400:        ff 25 12 0c 20 00            jmpq   *0x200c12(%rip)      # 601018 <_GLOBAL_OFFSET_TABLE_+0x18>
400406:        68 00 00 00 00               pushq$0x0
40040b:        e9 e0 ff ff ff               jmpq   4003f0 <_init+0x28>

000000000040053c <main>:
40053c:        55                           push   %rbp
40053d:        48 89 e5                     mov    %rsp,%rbp
400540:        be 41 01 00 00               mov    $0x141,%esi
400545:        bf 7b 00 00 00               mov    $0x7b,%edi
40054a:        e8 d7 ff ff ff               callq400526 <max>
40054f:        89 c6                        mov    %eax,%esi
400551:        bf 38 06 40 00               mov    $0x400638,%edi
400556:        b8 00 00 00 00               mov    $0x0,%eax
40055b:        e8 a0 fe ff ff               callq400400 <printf@plt>在这一段代码中,我们可以看到调用printf会首先调用0x400400的printf@plt
printf@plt会负责在运行时找到实际的printf函数并跳转到该函数
在这里实际的printf函数会保存在0x400406 + 0x200c12 = 0x601018中
需要注意的是0x601018一开始并不会指向实际的printf函数,而是会指向0x400406, 为什么会这样? 因为Linux的可执行程序为了考虑性能,不会在一开始就解决所有动态连接的函数,而是选择了延迟解决.
在上面第一次jmpq   *0x200c12(%rip)会跳转到下一条指令0x400406, 又会继续跳转到0x4003f0, 再跳转到0x601010指向的地址, 0x601010指向的地址就是延迟解决的实现, 第一次延迟解决成功后, 0x601018就会指向实际的printf, 以后调用就会直接跳转到实际的printf上.
程序入口点

Linux程序运行首先会从_start函数开始, 上面readelf中的入口点地址0x400430就是_start函数的地址,
0000000000400430 <_start>:
400430:        31 ed                        xor    %ebp,%ebp
400432:        49 89 d1                     mov    %rdx,%r9
400435:        5e                           pop    %rsi
400436:        48 89 e2                     mov    %rsp,%rdx
400439:        48 83 e4 f0                  and    $0xfffffffffffffff0,%rsp
40043d:        50                           push   %rax
40043e:        54                           push   %rsp
40043f:        49 c7 c0 20 06 40 00         mov    $0x400620,%r8
400446:        48 c7 c1 b0 05 40 00         mov    $0x4005b0,%rcx
40044d:        48 c7 c7 3c 05 40 00         mov    $0x40053c,%rdi
400454:        e8 b7 ff ff ff               callq400410 <__libc_start_main@plt>
400459:        f4                           hlt   
40045a:        66 0f 1f 44 00 00            nopw   0x0(%rax,%rax,1)接下来_start函数会调用__libc_start_main函数, __libc_start_main是libc库中定义的初始化函数, 负责初始化全局变量和调用main函数等工作.
__libc_start_main函数还负责设置返回值和退出进程, 可以看到上面调用__libc_start_main后的指令是hlt, 这个指令永远不会被执行.
实现Linux程序运行器

在拥有以上的知识后我们可以先构想以下的运行器需要做什么.
因为x64的Windows和Linux程序使用的cpu指令集都是一样的,我们可以直接执行汇编而不需要一个指令模拟器,
而且这次我打算在用户层实现, 所以不能像Bash On Windows一样模拟syscall, 这个运行器会像下图一样模拟libc库的函数

这样运行器需要做的事情有:

[*]解析ELF文件
[*]加载程序代码到指定的内存地址
[*]加载数据到指定的内存地址
[*]提供动态链接的函数实现
[*]执行加载的程序代码
这些工作会在以下的示例程序中一一实现, 完整的源代码可以看文章顶部的链接
首先我们需要把ELF文件格式对应的代码从binutils中复制过来, 它包含了ELF头, 程序头和相关的数据结构, 里面用unsigned char[]是为了防止alignment, 这样结构体可以直接从文件内容中转换过来
ELFDefine.h:
#pragma once

namespace HelloElfLoader {
        // 以下内容复制自
        // https://github.com/aeste/binutils/blob/develop/elfcpp/elfcpp.h
        // https://github.com/aeste/binutils/blob/develop/include/elf/external.h

        // e_ident中各项的偏移值
        const int EI_MAG0 = 0;
        const int EI_MAG1 = 1;
        const int EI_MAG2 = 2;
        const int EI_MAG3 = 3;
        const int EI_CLASS = 4;
        const int EI_DATA = 5;
        const int EI_VERSION = 6;
        const int EI_OSABI = 7;
        const int EI_ABIVERSION = 8;
        const int EI_PAD = 9;
        const int EI_NIDENT = 16;

        // ELF文件类型
        enum {
                ELFCLASSNONE = 0,
                ELFCLASS32 = 1,
                ELFCLASS64 = 2
        };

        // ByteOrder
        enum {
                ELFDATANONE = 0,
                ELFDATA2LSB = 1,
                ELFDATA2MSB = 2
        };

        // 程序头类型
        enum PT
        {
                PT_NULL = 0,
                PT_LOAD = 1,
                PT_DYNAMIC = 2,
                PT_INTERP = 3,
                PT_NOTE = 4,
                PT_SHLIB = 5,
                PT_PHDR = 6,
                PT_TLS = 7,
                PT_LOOS = 0x60000000,
                PT_HIOS = 0x6fffffff,
                PT_LOPROC = 0x70000000,
                PT_HIPROC = 0x7fffffff,
                // The remaining values are not in the standard.
                // Frame unwind information.
                PT_GNU_EH_FRAME = 0x6474e550,
                PT_SUNW_EH_FRAME = 0x6474e550,
                // Stack flags.
                PT_GNU_STACK = 0x6474e551,
                // Read only after relocation.
                PT_GNU_RELRO = 0x6474e552,
                // Platform architecture compatibility information
                PT_ARM_ARCHEXT = 0x70000000,
                // Exception unwind tables
                PT_ARM_EXIDX = 0x70000001
        };

        // 动态节类型
        enum DT
        {
                DT_NULL = 0,
                DT_NEEDED = 1,
                DT_PLTRELSZ = 2,
                DT_PLTGOT = 3,
                DT_HASH = 4,
                DT_STRTAB = 5,
                DT_SYMTAB = 6,
                DT_RELA = 7,
                DT_RELASZ = 8,
                DT_RELAENT = 9,
                DT_STRSZ = 10,
                DT_SYMENT = 11,
                DT_INIT = 12,
                DT_FINI = 13,
                DT_SONAME = 14,
                DT_RPATH = 15,
                DT_SYMBOLIC = 16,
                DT_REL = 17,
                DT_RELSZ = 18,
                DT_RELENT = 19,
                DT_PLTREL = 20,
                DT_DEBUG = 21,
                DT_TEXTREL = 22,
                DT_JMPREL = 23,
                DT_BIND_NOW = 24,
                DT_INIT_ARRAY = 25,
                DT_FINI_ARRAY = 26,
                DT_INIT_ARRAYSZ = 27,
                DT_FINI_ARRAYSZ = 28,
                DT_RUNPATH = 29,
                DT_FLAGS = 30,

                // This is used to mark a range of dynamic tags.It is not really
                // a tag value.
                DT_ENCODING = 32,

                DT_PREINIT_ARRAY = 32,
                DT_PREINIT_ARRAYSZ = 33,
                DT_LOOS = 0x6000000d,
                DT_HIOS = 0x6ffff000,
                DT_LOPROC = 0x70000000,
                DT_HIPROC = 0x7fffffff,

                // The remaining values are extensions used by GNU or Solaris.
                DT_VALRNGLO = 0x6ffffd00,
                DT_GNU_PRELINKED = 0x6ffffdf5,
                DT_GNU_CONFLICTSZ = 0x6ffffdf6,
                DT_GNU_LIBLISTSZ = 0x6ffffdf7,
                DT_CHECKSUM = 0x6ffffdf8,
                DT_PLTPADSZ = 0x6ffffdf9,
                DT_MOVEENT = 0x6ffffdfa,
                DT_MOVESZ = 0x6ffffdfb,
                DT_FEATURE = 0x6ffffdfc,
                DT_POSFLAG_1 = 0x6ffffdfd,
                DT_SYMINSZ = 0x6ffffdfe,
                DT_SYMINENT = 0x6ffffdff,
                DT_VALRNGHI = 0x6ffffdff,

                DT_ADDRRNGLO = 0x6ffffe00,
                DT_GNU_HASH = 0x6ffffef5,
                DT_TLSDESC_PLT = 0x6ffffef6,
                DT_TLSDESC_GOT = 0x6ffffef7,
                DT_GNU_CONFLICT = 0x6ffffef8,
                DT_GNU_LIBLIST = 0x6ffffef9,
                DT_CONFIG = 0x6ffffefa,
                DT_DEPAUDIT = 0x6ffffefb,
                DT_AUDIT = 0x6ffffefc,
                DT_PLTPAD = 0x6ffffefd,
                DT_MOVETAB = 0x6ffffefe,
                DT_SYMINFO = 0x6ffffeff,
                DT_ADDRRNGHI = 0x6ffffeff,

                DT_RELACOUNT = 0x6ffffff9,
                DT_RELCOUNT = 0x6ffffffa,
                DT_FLAGS_1 = 0x6ffffffb,
                DT_VERDEF = 0x6ffffffc,
                DT_VERDEFNUM = 0x6ffffffd,
                DT_VERNEED = 0x6ffffffe,
                DT_VERNEEDNUM = 0x6fffffff,

                DT_VERSYM = 0x6ffffff0,

                // Specify the value of _GLOBAL_OFFSET_TABLE_.
                DT_PPC_GOT = 0x70000000,

                // Specify the start of the .glink section.
                DT_PPC64_GLINK = 0x70000000,

                // Specify the start and size of the .opd section.
                DT_PPC64_OPD = 0x70000001,
                DT_PPC64_OPDSZ = 0x70000002,

                // The index of an STT_SPARC_REGISTER symbol within the DT_SYMTAB
                // symbol table.One dynamic entry exists for every STT_SPARC_REGISTER
                // symbol in the symbol table.
                DT_SPARC_REGISTER = 0x70000001,

                DT_AUXILIARY = 0x7ffffffd,
                DT_USED = 0x7ffffffe,
                DT_FILTER = 0x7fffffff
        };;

        // ELF头的定义
        typedef struct {
                unsigned char        e_ident;                /* ELF "magic number" */
                unsigned char        e_type;                /* Identifies object file type */
                unsigned char        e_machine;                /* Specifies required architecture */
                unsigned char        e_version;                /* Identifies object file version */
                unsigned char        e_entry;                /* Entry point virtual address */
                unsigned char        e_phoff;                /* Program header table file offset */
                unsigned char        e_shoff;                /* Section header table file offset */
                unsigned char        e_flags;                /* Processor-specific flags */
                unsigned char        e_ehsize;                /* ELF header size in bytes */
                unsigned char        e_phentsize;                /* Program header table entry size */
                unsigned char        e_phnum;                /* Program header table entry count */
                unsigned char        e_shentsize;                /* Section header table entry size */
                unsigned char        e_shnum;                /* Section header table entry count */
                unsigned char        e_shstrndx;                /* Section header string table index */
        } Elf64_External_Ehdr;

        // 程序头的定义
        typedef struct {
                unsigned char        p_type;                /* Identifies program segment type */
                unsigned char        p_flags;                /* Segment flags */
                unsigned char        p_offset;                /* Segment file offset */
                unsigned char        p_vaddr;                /* Segment virtual address */
                unsigned char        p_paddr;                /* Segment physical address */
                unsigned char        p_filesz;                /* Segment size in file */
                unsigned char        p_memsz;                /* Segment size in memory */
                unsigned char        p_align;                /* Segment alignment, file & memory */
        } Elf64_External_Phdr;

        // DYNAMIC类型的程序头的内容定义
        typedef struct {
                unsigned char        d_tag;                /* entry tag value */
                union {
                        unsigned char        d_val;
                        unsigned char        d_ptr;
                } d_un;
        } Elf64_External_Dyn;

        // 动态链接的重定位记录,部分系统会用Elf64_External_Rel
        typedef struct {
                unsigned char r_offset;        /* Location at which to apply the action */
                unsigned char        r_info;        /* index and type of relocation */
                unsigned char        r_addend;        /* Constant addend used to compute value */
        } Elf64_External_Rela;

        // 动态链接的符号信息
        typedef struct {
                unsigned char        st_name;                /* Symbol name, index in string tbl */
                unsigned char        st_info;                /* Type and binding attributes */
                unsigned char        st_other;                /* No defined meaning, 0 */
                unsigned char        st_shndx;                /* Associated section index */
                unsigned char        st_value;                /* Value of the symbol */
                unsigned char        st_size;                /* Associated symbol size */
        } Elf64_External_Sym;
}接下来我们定义一个读取和执行ELF文件的类, 这个类会在初始化时把文件加载到fileStream_, execute函数会负责执行
HelloElfLoader.h:
#pragma once
#include <string>
#include <fstream>

namespace HelloElfLoader {
        class Loader {
                std::ifstream fileStream_;

        public:
                Loader(const std::string& path);
                Loader(std::ifstream&& fileStream);
                void execute();
        };
}构造函数如下, 也就是标准的c++打开文件的代码
HelloElfLoader.cpp:
Loader::Loader(const std::string& path) :
    Loader(std::ifstream(path, std::ios::in | std::ios::binary)) {}

Loader::Loader(std::ifstream&& fileStream) :
    fileStream_(std::move(fileStream)) {
    if (!fileStream_) {
      throw std::runtime_error("open file failed");
    }
}接下来将实现上面所说的步骤, 首先是解析ELF文件
void Loader::execute() {    std::cout

眺愤 发表于 2025-10-26 01:15:38

这个有用。

乳杂丫 发表于 6 天前

感谢分享

左丘平莹 发表于 3 小时前

感谢发布原创作品,程序园因你更精彩
页: [1]
查看完整版本: 如何实现在Windows上运行Linux程序,附示例代码