读书-程序员的自我修养-链接、封装与库(7: 第三章:目标文件里有什么(2)readelf,链接的本质,符号/表,extern "C",强/弱符号)
6: 第三章:目标文件里有什么(1) 参考上个博客
https://blog.csdn.net/lqy971966/article/details/100068205
1. ELF文件结构描述
1.1 readelf -h simple.o
root@ubuntu-admin-a1:/home# readelf -h simple.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
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: REL (Relocatable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x0
Start of program headers: 0 (bytes into file)
Start of section headers: 1056 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 0 (bytes)
Number of program headers: 0
Size of section headers: 64 (bytes)
Number of section headers: 13
Section header string table index: 10
root@ubuntu-admin-a1:/home#
1.1.1 ELF头文件说明
ELF的文件头中定义了ELF魔数,文件机器字节长度,数据存储方式,版本,平台,ABI版本,硬件版本/平台,入口地址,程序头入口和长度,段表的位置/长度/数量等。
1.2 readelf 命令
- 功能
用于显示elf格式文件的信息 - 参数-h
–file-header 显示elf文件开始的文件头信息.
//查看文件头信息
readelf -h [file] - 参数-d
readelf -d [file]
//查看文件依赖的动态库 - 参数-S
//查看文件中的符号
readelf -s [file] - -S
–section-headers ;
–sections 显示节头信息
1.3 readelf -S simple.o 查看各个段
root@ubuntu-admin-a1:/home# readelf -S simple.o
There are 13 section headers, starting at offset 0x420:
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .text PROGBITS 0000000000000000 00000040
0000000000000057 0000000000000000 AX 0 0 1
[ 2] .rela.text RELA 0000000000000000 00000310
0000000000000078 0000000000000018 I 11 1 8
[ 3] .data PROGBITS 0000000000000000 00000098
0000000000000008 0000000000000000 WA 0 0 4
[ 4] .bss NOBITS 0000000000000000 000000a0
0000000000000004 0000000000000000 WA 0 0 4
[ 5] .rodata PROGBITS 0000000000000000 000000a0
0000000000000004 0000000000000000 A 0 0 1
[ 6] .comment PROGBITS 0000000000000000 000000a4
0000000000000035 0000000000000001 MS 0 0 1
[ 7] .note.GNU-stack PROGBITS 0000000000000000 000000d9
0000000000000000 0000000000000000 0 0 1
[ 8] .eh_frame PROGBITS 0000000000000000 000000e0
0000000000000058 0000000000000000 A 0 0 8
[ 9] .rela.eh_frame RELA 0000000000000000 00000388
0000000000000030 0000000000000018 I 11 8 8
[10] .shstrtab STRTAB 0000000000000000 000003b8
0000000000000061 0000000000000000 0 0 1
[11] .symtab SYMTAB 0000000000000000 00000138
0000000000000180 0000000000000018 12 11 8
[12] .strtab STRTAB 0000000000000000 000002b8
0000000000000054 0000000000000000 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)
root@ubuntu-admin-a1:/home#
1.3.1 simple.o 各个段位置图
1.3.2 .rel.text 重定位表
代码中需要重定位的代码段和数据段,存放在这里。
1.3.3 .strtab/.shstrtab 字符串表
把ELF文件中的很多字符串,比如段名,变量名存放在这里
2. 链接的接口-符号
2.1 链接的本质
- 链接的本质:
把多个不同的目标文件之间相互粘到一起。 - 目标文件直接相互拼合实际上是目标文件之间对地址的引用,即对函数和变量的地址的引用。
2.2 符号
- 符号(Symbol):我们将函数和变量统称为符号。
表示一个地址,这个地址可以是一段子程序/函数,也可以是一个变量的起始地址。 - 符号表(Symbol Table):一个目标文件中所有符号的集合。
- 符号名(Symbol Name):函数名或变量名就是符号名。
- 符号值(Symbol Value): 符号(函数名或变量名)的地址
链接的过程:有一部分是符号的管理,每一个目标文件都会有一个相应的符号表(Symbol Table),这个表里面记录了目标文件中所用到的所有符号。
2.3 .symtab 符号表
它是ELF文件中的一个段。
它的结构很简单,是一个Elf32_Sym结构的数组。
2.4 readelf -s simple.o 查看符号表
root@ubuntu-admin-a1:/home# readelf -s simple.o
Symbol table '.symtab' contains 16 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS simple.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 3
4: 0000000000000000 0 SECTION LOCAL DEFAULT 4
5: 0000000000000000 0 SECTION LOCAL DEFAULT 5
6: 0000000000000004 4 OBJECT LOCAL DEFAULT 3 iStaticVar.2290
7: 0000000000000000 4 OBJECT LOCAL DEFAULT 4 iStaticVar2.2291
8: 0000000000000000 0 SECTION LOCAL DEFAULT 7
9: 0000000000000000 0 SECTION LOCAL DEFAULT 8
10: 0000000000000000 0 SECTION LOCAL DEFAULT 6
11: 0000000000000000 4 OBJECT GLOBAL DEFAULT 3 g_init_var
12: 0000000000000004 4 OBJECT GLOBAL DEFAULT COM g_uinit_var
13: 0000000000000000 34 FUNC GLOBAL DEFAULT 1 func1
14: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND printf
15: 0000000000000022 53 FUNC GLOBAL DEFAULT 1 main
root@ubuntu-admin-a1:/home#
2.5 符号修饰
2.5.1 UNIX规范:
为了防止符号名重名冲突,UNIX规定C语言下的所有全局变量和函数经过编译后,符号钱加上_下划线,如 _foo
2.5.2. C++符号修饰和符号改编
2.5.3. 函数签名(Function Signature):
函数签名包含一个函数的信息,包括函数名,参数类型,名称空间等信息。
如: int C::func(int) -> _ZN1C4funcEi
2.5.4. extern “C”
C++代码调用C代码
C++编译器会将extern "C"的大括号内部的代码当作C语言代码处理。
所以C++代码的名称修饰将不起作用。
2.5.5. __cplusplus
判断当前编译单元是不是c++代码
C++编译器会在编译C++的程序的时候默认定义这个宏。
这个宏判断当前编译器是不是C++。
2.5.6 强符号和弱符号
编译器默认函数和初始化了的全局变量为强符号。
未初始化的全局变量为弱符号。
5. 调试信息 objdump -h debug_simple.o
root@ubuntu-admin-a1:/home# objdump -h debug_simple.o
debug_simple.o: file format elf64-x86-64
Sections:
Idx Name Size VMA LMA File off Algn
……
……
27 .debug_aranges 00000030 0000000000000000 0000000000000000 00001074 2**0
CONTENTS, READONLY, DEBUGGING
28 .debug_info 0000012c 0000000000000000 0000000000000000 000010a4 2**0
CONTENTS, READONLY, DEBUGGING
29 .debug_abbrev 0000009c 0000000000000000 0000000000000000 000011d0 2**0
CONTENTS, READONLY, DEBUGGING
30 .debug_line 00000043 0000000000000000 0000000000000000 0000126c 2**0
CONTENTS, READONLY, DEBUGGING
31 .debug_str 000000e7 0000000000000000 0000000000000000 000012af 2**0
CONTENTS, READONLY, DEBUGGING
root@ubuntu-admin-a1:/home#
6. 相关的linux命令
6.1 file
file 命令 显示文件类型
6.2 objdump
objdump 查看目标文件内部结构,-h-s-d\
6.3 size
size用来查看ELF文件的代码段、数据段和BSS
7 句子
- 很多问题在表面上看似很简单,其实深入内部会发现很多鲜为人知的秘密,或者发现以前自己认为理所当然的 居然是错误的,或者是有偏差的。
- 真正了不起的程序员对自己的程序的每一个字节都了如指掌。
- 马屁股决定航天飞机