编译过程<2>(目标文件)

Last updated on a month ago

前言

从源码到可执行文件,中间的过程中会生成 .o 文件,也就是目标文件,目标文件放的是什么呢?前面经常用objdump 反汇编过,似乎是以莫种格式存放的,什么格式呢? 可执行文件又是什么格式呢?

本文只讨论Linux下的目标文件

目标文件的格式

我们先用 file 命令查看

1
2
3
4
5
6
7
8
hrp@ubuntu:~$ file main
main: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0


hrp@ubuntu:~$ file hello.o
hello.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
hrp@ubuntu:~$

  • 可执行文件 main 是ELF 64-bit LSB shared object ,表示它已经经过链接,可以直接在系统上执行
  • 目标文件 hello.o 是 ELF 64-bit LSB relocatable,可重定位的目标文件,包含了编译后的程序的机器码和未解析的重定位信息

两者都有 ELF ,那ELF是什么?

ELF(Executable and Linkable Format)是一种常见的可执行文件和目标文件格式,用于在类Unix操作系统上存储程序的二进制代码以及与之相关的元数据。对于windows ,称之为 PE(Portable Executable)

从file 命令看目标文件和可执行文件 都是elf 格式,所以再Linux 下,统称为 ELF文件; 想动态库,静态库也是 elf 格式的文件

目标文件是怎样的?

源码编译成目标文件后,可以分为两个部分:

  • 代码段(.text): 存放汇编指令
  • 数据段(.bss / .data ):数据又分为两部分
    • 初始化的全局变量和静态变量存放再 data
    • 未初始化的存放再 .bss(Block Started by Symbol)

用size 命令可观测到

1
2
3
4
5
6
7
8
9
10
11
12
13
hrp@ubuntu:~$ cat hello.c 
#include <stdio.h>
int a = 10;
static int b ;
void hello() {
printf("Hello, world!\n");
}
# data 区4个字节,变量a的; bss 4个字节 ,变量b 未初始化的
hrp@ubuntu:~$ gcc -c hello.c && size hello.o
text data bss dec hex filename
89 4 4 97 61 hello.o
hrp@ubuntu:~$

**为什么需要bss 区? **

本质上都可以存放,但 未初始化的全局变量都为0,如果放date区扩展空间,并存放数据为0,反而会占用空间,为了节省空间,未初始化的全局变量和声明为static的局部变量不占有任何空间,只是保存了在运行时它们要占的空间的大小,所以bss 也被戏称为“Better Save Space”

image-20240405122207974

为什么要区分代码段和数据段?

  • 区分读写权限: 数据是可读写的,而代码段是只读的,防止代码段被恶意修改

  • 代码段的复用: 内存中加载的同一程序的多个实例,或者不同程序中引用相同库的情况下,它们可以共享相同的代码段。这种共享可以节省系统内存,并提高程序的执行效率

    对于第二点,我们可以举个例子看看, 编译并生成两个相同的可执行文件, 然后分别gdb 进去,看两者内存的映射

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    hrp@ubuntu:~$ cat test.c 
    // test.c
    #include <stdio.h>
    void foo() {
    printf("Hello from foo()\n");
    }
    int main() {
    foo();
    return 0;
    }
    hrp@ubuntu:~$ gcc -g test.c -o test2
    hrp@ubuntu:~$ gcc -g test.c -o test1

    可以发现两个运行者的程序共用一段内存地址

    image-20240405124101916

    但这理由证据还不够充分,怎么知道 这就是代码段的映射的内存呢?

    于是我去 /proc/pid/maps 看这个程序的内存映射; 地址和gdb 看的时一样的, 但多了个信息 r-xp ,其中 x 标志可执行,也就说明这个内存区间是存放代码段,且运行两个程序,都是相同的内存地址,所以是 同一程序的多个实例共享 同一个代码段的!

    1
    2
    3
    4
    5
    hrp@ubuntu:~$ cat /proc/3915/maps 
    555555554000-555555555000 r-xp 00000000 08:01 1102662 /home/hrp/test1
    555555754000-555555755000 r--p 00000000 08:01 1102662 /home/hrp/test1
    555555755000-555555756000 rw-p 00001000 08:01 1102662 /home/hrp/test1
    ...

深入 .o 文件组成

都有那些组成

从objdump 反汇编看( -h 选项是 打印出这个目标文件的section 内容)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
hrp@ubuntu:~$ objdump -h hello.o 
hello.o: file format elf64-x86-64
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 00000013 0000000000000000 0000000000000000 00000040 2**0
CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
1 .data 00000004 0000000000000000 0000000000000000 00000054 2**2
CONTENTS, ALLOC, LOAD, DATA
2 .bss 00000004 0000000000000000 0000000000000000 00000058 2**2
ALLOC
3 .rodata 0000000e 0000000000000000 0000000000000000 00000058 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
4 .comment 0000002a 0000000000000000 0000000000000000 00000066 2**0
CONTENTS, READONLY
5 .note.GNU-stack 00000000 0000000000000000 0000000000000000 00000090 2**0
CONTENTS, READONLY
6 .eh_frame 00000038 0000000000000000 0000000000000000 00000090 2**3
CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA
hrp@ubuntu:~$

除了刚才说的 三个 section,还有多了好几个section 名字

  • .rodata:包含只读数据的段
  • .comment:包含注释的节
  • .note.GNU-stack:用于控制栈的属性的节
  • .eh_frame`:包含异常处理信息的节

这里偏移量是从 0x40开始的,那0~0x40是什么呢?
对于elf 格式的文件,都有个elf 头,用 描述这个elf 的基本信息,elf 头大小是固定的, 也可以通过 readelf -h 查看(后面再解释elf )

1
2
3
4
5
6
7
8
9
10
11
12
13
hrp@ubuntu:~$ readelf -h hello.o 
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
...
Size of this header: 64 (bytes)
Size of program headers: 0 (bytes)
Number of program headers: 0
//64字节
Size of section headers: 64 (bytes)
Number of section headers: 13
Section header string table index: 12

所以现在的elf 结构是

image-20240405135104836

接下来以 “看得见” 的方式介绍 各个 段:

.text

//待更新