Administrator
发布于 2024-04-19 / 14 阅读
0
0

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

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

前言

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

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

目标文件的格式

我们先用 file 命令查看


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 命令可观测到


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 区? \\为什么需要bss 区? \\

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

!image-20240405122207974

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

  • 区分读写权限: 数据是可读写的,而代码段是只读的,防止代码段被恶意修改
  • 代码段的复用: 内存中加载的同一程序的多个实例,或者不同程序中引用相同库的情况下,它们可以共享相同的代码段。这种共享可以节省系统内存,并提高程序的执行效率

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


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 标志可执行,也就说明这个内存区间是存放代码段,且运行两个程序,都是相同的内存地址,所以是 同一程序的多个实例共享 同一个代码段的!


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 内容)


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 )


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

//待更新


评论