前言
大学时期有一门课程叫 《微机原理》, 主要讲述 80x86 intel CPU以及 x86汇编, 其中就提到程序的各个段(Segment), 但是当时也只是浅尝辄止,但是在工作中使用C++经常出现一些编译或者链接上的错误问题,因此有必要了解一下编译生成的目标文件的构成, 对解决实际问题提供思路和帮助,笔者也是一边看书一边实践, 其中还有一些不解之处难免出现理解错误。
Require
- VSCode
- GCC >= 7.5.0
- Ubuntu 20.04
测试代码
一段简单的C++代码,采用GCC编译生成.o文件。
/**
* @file SimpleSectipn.cpp
* @author your name (you@domain.com)
* @brief
* @version 0.1
* @date 2021-04-27
*
* @copyright Copyright (c) 2021
*
*/
int printf(const char* format, ... );
int global_init_var = 84;
int global_uninit_var;
void func1(int i){
printf("%d\n", i);
}
int main(){
static int static_var = 85;
static int static_var2;
int a = 1;
int b;
func1(static_var + static_var2 + a + b);
return a;
}
使用GCC进行编译:gcc -c SimpleSection.cpp
, 生成目标文件 SimpleSection.o
使用objdump
工具查看目标文件的信息: objdump -h SimpleSection.o
objdump 用法:
-a, --archive-headers Display archive header information
-f, --file-headers Display the contents of the overall file header
-p, --private-headers Display object format specific file header contents
-P, --private=OPT,OPT... Display object format specific contents
-h, --[section-]headers Display the contents of the section headers
-x, --all-headers Display the contents of all headers
-d, --disassemble Display assembler contents of executable sections
-D, --disassemble-all Display assembler contents of all sections
--disassemble=<sym> Display assembler contents from <sym>
-S, --source Intermix source code with disassembly
--source-comment[=<txt>] Prefix lines of source code with <txt>
-s, --full-contents Display the full contents of all sections requested
-g, --debugging Display debug information in object file
-e, --debugging-tags Display debug information using ctags style
-G, --stabs Display (in raw form) any STABS info in the file
-W[lLiaprmfFsoRtUuTgAckK] or
--dwarf[=rawline,=decodedline,=info,=abbrev,=pubnames,=aranges,=macro,=frames,
=frames-interp,=str,=loc,=Ranges,=pubtypes,
=gdb_index,=trace_info,=trace_abbrev,=trace_aranges,
=addr,=cu_index,=links,=follow-links]
Display DWARF info in the file
--ctf=SECTION Display CTF info from SECTION
-t, --syms Display the contents of the symbol table(s)
-T, --dynamic-syms Display the contents of the dynamic symbol table
-r, --reloc Display the relocation entries in the file
-R, --dynamic-reloc Display the dynamic relocation entries in the file
@<file> Read options from <file>
-v, --version Display this program's version number
-i, --info List object formats and architectures supported
-H, --help Display this information
查看 .o 文件的各个段的基本信息:
.o 文件主要的各个段:
- .text: 代码段
- .data: 数据段
- .bss 段: 存放未初始化的全局变量
- .rodata: 只读数据段, read-only
- .comment: 注释信息
在Linux中, 还可以使用 size
命令查看 ELF文件各个段的字节大小:
size src/SimpleSectipn.o
text data bss dec hex filename
219 8 8 235 eb src/SimpleSectipn.o
使用objdump显示的代码段大小为 0x5f=95 bytes, 但是size 命令确显示text段大小219字节, 这里没有对上,很奇怪~
代码段
采用objdump对各个段的指令进行反汇编
objdump -s -d xxx.o
-d 代表反汇编的意思
下面是 text段反汇编之后的输出, 可以看到有2个函数, func1()和main()函数, 代码段的大小为0x5f (0x5e+1)
Disassembly of section .text:
0000000000000000 <_Z5func1i>:
0: f3 0f 1e fa endbr64
4: 55 push %rbp
5: 48 89 e5 mov %rsp,%rbp
8: 48 83 ec 10 sub $0x10,%rsp
c: 89 7d fc mov %edi,-0x4(%rbp)
f: 8b 45 fc mov -0x4(%rbp),%eax
12: 89 c6 mov %eax,%esi
14: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # 1b <_Z5func1i+0x1b>
1b: b8 00 00 00 00 mov $0x0,%eax
20: e8 00 00 00 00 callq 25 <_Z5func1i+0x25>
25: 90 nop
26: c9 leaveq
27: c3 retq
0000000000000028 <main>:
28: f3 0f 1e fa endbr64
2c: 55 push %rbp
2d: 48 89 e5 mov %rsp,%rbp
30: 48 83 ec 10 sub $0x10,%rsp
34: c7 45 f8 01 00 00 00 movl $0x1,-0x8(%rbp)
3b: 8b 15 00 00 00 00 mov 0x0(%rip),%edx # 41 <main+0x19>
41: 8b 05 00 00 00 00 mov 0x0(%rip),%eax # 47 <main+0x1f>
47: 01 c2 add %eax,%edx
49: 8b 45 f8 mov -0x8(%rbp),%eax
4c: 01 c2 add %eax,%edx
4e: 8b 45 fc mov -0x4(%rbp),%eax
51: 01 d0 add %edx,%eax
53: 89 c7 mov %eax,%edi
55: e8 00 00 00 00 callq 5a <main+0x32>
5a: 8b 45 f8 mov -0x8(%rbp),%eax
5d: c9 leaveq
5e: c3 retq
数据段 + 只读数据段 (.data + .rodata)
.data: 存放已初始化的全局变量 + 初始化的局部静态变量 ( 对应代码中:global_init_var=84, static int static_var=85 )
.rodata: 只读数据段
Contents of section .data:
0000 54000000 55000000 T...U...
Contents of section .rodata:
0000 25640a00 %d..
BSS段
存放未初始化的全局变量, 未初始化的局部静态变量
.comment 注释段
通过.comment段的信息可以看出, 此目标文件是采用 GCC9.3.0生成的
Contents of section .comment:
0000 00474343 3a202855 62756e74 7520392e .GCC: (Ubuntu 9.
0010 332e302d 31377562 756e7475 317e3230 3.0-17ubuntu1~20
0020 2e303429 20392e33 2e3000 .04) 9.3.0.
高级尝试: 自定义段
__attribute__((section("name")))
------- 将函数或者变量指定到自定义的name段。
在前面的C++代码中添加如下代码, 添加2个自定义的段:ABC, DE
// 使用GCC的扩展功能,制定函数到 ABC段
__attribute__((section("ABC"))) void func2(){
printf("Hello,world!\n");
}
__attribute__((section("DEF"))) int g_index = 12345;
objdump 反汇编可以观察到指定的2个段:
Contents of section DEF:
0000 39300000 90..
Disassembly of section ABC:
0000000000000000 <_Z5func2v>:
0: f3 0f 1e fa endbr64
4: 55 push %rbp
5: 48 89 e5 mov %rsp,%rbp
8: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # f <_Z5func2v+0xf>
f: b8 00 00 00 00 mov $0x0,%eax
14: e8 00 00 00 00 callq 19 <_Z5func2v+0x19>
19: 90 nop
1a: 5d pop %rbp
1b: c3 retq