C++ 运行、编译和链接基础内容
C++ 运行、编译和链接基础内容
任何变成语言最终无非产生:
1. 指令
2. 数据
产生的可执行程序:xxx.exe 存放在磁盘上
但调用时不可能直接加载到物理内存。
int gdata1 = 10; // .data
int gdata2 = 0; // .bss
int gdata3; // .bssstatic int gdata4 = 11; // .data
static int gdata5 = 0; // .bss
static int gdata6; // .bss// 以上均为数据, 创建以后会在符号表中产生符号, 存放在数据段int main() {int a = 12; // stack/*mov dword ptr[a], 0ch // .texta 只是指令, 不是数据, 不在符号表中产生符号函数运行时, 指令运行时, 在 stack 上给 a 开辟 4 byte 空间*/int b = 0; // stackint c; // stackstatic int e = 13; // .datastatic int f = 0; // .bssstatic int g; // .bssreturn 0;
}
程序调用时 linux 系统会给当前进程分配一个 4G 的虚拟地址空间
-------------用户空间--------------0x00000000
| 不可用 |
----------------------------------0x08048000
| .text .rodata |
.text: 代码段; 代码存放处
.rodata: 只读数据段当编写:char* p = "hello, world";*p = 'a';可以编译通过, 但是运行会报错因为字符串你好世界存放在 .rodata 中, 尝试修改了 .rodata 的数据通常使用以下写法:const char *p = "hello, world";可以避免此类问题
----------------------------------
| .data |
存放初始化的, 且初始化数值不为 0 的
----------------------------------
| .bss |
存放未初始化的, 或初始化值为 0 的当写下:int gdata; // 全局cout << gdata << endl;会打印出 0操作系统会自动为 .bss 进行 0 初始化
----------------------------------
| heap |
----------------------------------
| 加载共享库: *.dll *.so |
----------------------------------^^||||
| stak 栈空间 |
----------------------------------
| 命令行参数 和 环境变量 |
-------------内核空间--------------0xC0000000 3G
| ZONE_DMA
| ZONE_NORMAL
| ZONE_HIGHMEM
-----------------------------------0xFFFFFFFF
每个进程的用户区是独有的,内核空间是共享的。
进程之间的通信方式有哪些?
匿名管道通信;在内核空间划分一块内存,在此处写入数据实现通信。
#include <iostream>using namespace std;int sum(int a, int b) {int temp = 0;temp = a + b;return temp;
}int main() {int a = 0, b = 0;int ret = sum(a, b);return 0;
}
- main 函数调用 sum,sum 执行完后怎么知道回到哪个函数中?
- sum 函数执行完,回到 main 后怎么知道从哪一行指令继续执行?
栈帧创建过程(从下往上看;从高地址向低地址生长)
esp-------------------------0xCCCCCCCCrep stos (对新栈帧空间的初始化)formov dword ptr[ebp - 4], 0 给 tmp 开辟空间mov eax, dword ptr[ebp + 0ch] 将 a 的值存到 寄存器add eax, dword ptr[ebp + 8] a + bmov dword ptr[ebp - 4], eax 赋值给 tmpmov eax, dword ptr[ebp - 4]mov esp, ebp '}'产生的行为 => 回退栈空间回退后:pop ebp 出栈并将栈顶赋给 ebpebp 回到 0x0018ff40 main 函数的栈底ret 把出栈的内容放入 pc 寄存器中
ebp-------------------------0x0018ff40psuh ebp '{' 产生的行为mov ebp, espsub esp, 4Ch (给函数开辟4Ch空间)
esp'-------------------------0x08124458
-----------------------------call sum(完成两件事:1. 把此行指令的下一行的地址压栈2.)add esp, 8 ----0x08124458 把形参栈帧清除mov dword ptr[ebp - 0ch], eax 把寄存器中的数值放入 ret 中(形参压栈完成, 进行函数调用)
-----------------------------10 => int a 压栈 (形参 a 的内存)
-----------------------------20 => int b 压栈 (形参 b 的内存)mov eax, dword ptr[ebp - 8]push eaxmov eax, dword ptr[ebp - 4]push eax
esp--------------------------0x----------------------------- ret 以下为 mov 指令
----------------------------- 不涉及压栈b(ebp - 8) 20
-----------------------------a(ebp - 4) 10
ebp--------------------------0x0018ff40main 函数栈帧
因此,对于:
int * func() {int data = 10;return & data;
}
为不安全的写法;当 } 执行后栈帧回退,栈空间已经交还给系统。
但是:
int * p = func();
cout << *p << endl;
可以正常输出,栈帧回退并没有对栈空间进行清理
ps:
- 当返回值 < 4 byte 时,由寄存器 eax 带回返回值。
- 当 4 < 返回值 < 8 byte 时,由寄存器 eax, edx 带回返回值。
- 当返回值 > 8 时,会产生临时量带出返回值。
编译过程:
预编译 | 编译 | 汇编 | 二进制可重定位的目标文件 (*.obj) | |
---|---|---|---|---|
main.cpp sum.cpp | # 开头的命令 除了如 #pragma lib,#pragma link | gcc,g++ | 根据对应架构生成汇编指令 | main.o sum.o |
链接过程:编译完成的所有 .o 文件 + 静态库文件
步骤一:
所有 .o 文件段的合并
符号表合并后,进行符号解析
步骤二:
符号的重定位(重定向)
==> xxx.exe, a.out
假设原函数为
chipen@ubuntu:~/projects/GCCG++/build$ cat ../main.cpp
extern int gdata;
int sum(int, int);int data = 20;int main() {int a = gdata;int b = data;int ret = sum(a, b);return 0;
}chipen@ubuntu:~/projects/GCCG++/build$ cat ../sum.cpp
int gdata = 10;int sum(int a, int b) {return a + b;
检查符号表
chipen@ubuntu:~/projects/GCCG++/build$ objdump -t sum.osum.o: file format elf64-x86-64SYMBOL TABLE:
0000000000000000 l df *ABS* 0000000000000000 sum.cpp
0000000000000000 l d .text 0000000000000000 .text
0000000000000000 g O .data 0000000000000004 gdata
0000000000000000 g F .text 0000000000000018 _Z3sumiichipen@ubuntu:~/projects/GCCG++/build$ objdump -t main.omain.o: file format elf64-x86-64SYMBOL TABLE:
0000000000000000 l df *ABS* 0000000000000000 main.cpp
0000000000000000 l d .text 0000000000000000 .text
0000000000000000 g O .data 0000000000000004 data # 全局变量且不为 0,放在 .data
0000000000000000 g F .text 0000000000000037 main # main 函数存放在代码段
0000000000000000 *UND* 0000000000000000 gdata # 也产生了符号
0000000000000000 *UND* 0000000000000000 _Z3sumii # 符号
# 上面两个虽然产生符号但是不知道放在哪里(UND),只是对符号的引用
注释:
chipen@ubuntu:~/projects/GCCG++/build$ cat ../main.cpp
extern int gdata; # 生成符号 *UND*
int sum(int, int); # *UND*int data = 20; # .dataint main() { # .textint a = gdata;int b = data;int ret = sum(a, b);return 0;
}chipen@ubuntu:~/projects/GCCG++/build$ cat ../sum.cpp
int gdata = 10; # .dataint sum(int a, int b) { # sum_int_int .textreturn a + b;
ps:.o 文件的格式组成
ELF 文件头
.text
.data
.rodata
.bss
.symbal # 符号表
.section_table编译过程中符号是不分配虚拟地址的,分配在链接阶段完成
带上调试信息再观察:
chipen@ubuntu:~/projects/GCCG++/build$ objdump -S main.omain.o: file format elf64-x86-64Disassembly of section .text:0000000000000000 <main>:
int sum(int, int);int data = 20;int main() {0: f3 0f 1e fa endbr644: 55 push %rbp5: 48 89 e5 mov %rsp,%rbp8: 48 83 ec 10 sub $0x10,%rspint a = gdata;c: 8b 05 00 00 00 00 mov 0x0(%rip),%eax # 12 <main+0x12>12: 89 45 f4 mov %eax,-0xc(%rbp)int b = data;15: 8b 05 00 00 00 00 mov 0x0(%rip),%eax # 1b <main+0x1b>1b: 89 45 f8 mov %eax,-0x8(%rbp)int ret = sum(a, b);1e: 8b 55 f8 mov -0x8(%rbp),%edx21: 8b 45 f4 mov -0xc(%rbp),%eax24: 89 d6 mov %edx,%esi26: 89 c7 mov %eax,%edi28: e8 00 00 00 00 call 2d <main+0x2d>2d: 89 45 fc mov %eax,-0x4(%rbp)return 0;30: b8 00 00 00 00 mov $0x0,%eax35: c9 leave36: c3 ret
chipen@ubuntu:~/projects/GCCG++/build$ objdump -S sum.osum.o: file format elf64-x86-64Disassembly of section .text:0000000000000000 <_Z3sumii>:
int gdata = 10;int sum(int a, int b) {0: f3 0f 1e fa endbr644: 55 push %rbp5: 48 89 e5 mov %rsp,%rbp8: 89 7d fc mov %edi,-0x4(%rbp)b: 89 75 f8 mov %esi,-0x8(%rbp)return a + b;e: 8b 55 fc mov -0x4(%rbp),%edx11: 8b 45 f8 mov -0x8(%rbp),%eax14: 01 d0 add %edx,%eax16: 5d pop %rbp17: c3 ret
从目标文件到可执行文件过程中:
合并了所有目标文件各个段:符号表的合并;所有对符号的引用(UND)都要找到该符号定义的地方
给所有符号(UND)分配虚拟地址 => 符号的重定向
不妨对比一下目标文件和可执行文件反汇编后的异同
chipen@ubuntu:~/projects/GCCG++/build$ objdump -t main.outmain.out: file format elf64-x86-64SYMBOL TABLE:
0000000000000000 l df *ABS* 0000000000000000 Scrt1.o
00000000000003fc l O .note.ABI-tag 0000000000000020 __abi_tag
...
0000000000000000 l df *ABS* 0000000000000000 main.cpp
0000000000000000 l df *ABS* 0000000000000000 sum.cpp
0000000000000000 l df *ABS* 0000000000000000 crtstuff.c
...
0000000000004014 g O .data 0000000000000004 gdata
0000000000001160 g F .text 0000000000000018 _Z3sumii
...
0000000000004010 g O .data 0000000000000004 data
0000000000004020 g .bss 0000000000000000 _end
0000000000001040 g F .text 0000000000000026 _start
0000000000004018 g .bss 0000000000000000 __bss_start
0000000000001129 g F .text 0000000000000037 main
...
可见各符号已经找到了对应的位置了
chipen@ubuntu:~/projects/GCCG++/build$ readelf -h main.out
ELF Header:Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00Class: ELF64Data: 2's complement, little endianVersion: 1 (current)OS/ABI: UNIX - System VABI Version: 0Type: DYN (Position-Independent Executable file) # 可执行文件Machine: Advanced Micro Devices X86-64Version: 0x1Entry point address: 0x1040 # 入口地址Start of program headers: 64 (bytes into file)Start of section headers: 14056 (bytes into file)Flags: 0x0Size of this header: 64 (bytes)Size of program headers: 56 (bytes)Number of program headers: 13Size of section headers: 64 (bytes)Number of section headers: 30Section header string table index: 29
程序执行后,是一股脑把数据都加载到内存中吗?
检查程序头中: LOAD 段告诉程序把那些段加载到内存中chipen@ubuntu:~/projects/GCCG++/build$ readelf -l main.outElf file type is DYN (Position-Independent Executable file) Entry point 0x1040 There are 13 program headers, starting at offset 64Program Headers:Type Offset VirtAddr PhysAddrFileSiz MemSiz Flags AlignPHDR 0x0000000000000040 0x0000000000000040 0x00000000000000400x00000000000002d8 0x00000000000002d8 R 0x8INTERP 0x0000000000000318 0x0000000000000318 0x00000000000003180x000000000000001c 0x000000000000001c R 0x1[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]LOAD 0x0000000000000000 0x0000000000000000 0x00000000000000000x0000000000000660 0x0000000000000660 R 0x1000LOAD 0x0000000000001000 0x0000000000001000 0x00000000000010000x0000000000000185 0x0000000000000185 R E 0x1000LOAD 0x0000000000002000 0x0000000000002000 0x00000000000020000x00000000000000ec 0x00000000000000ec R 0x1000LOAD 0x0000000000002df0 0x0000000000003df0 0x0000000000003df00x0000000000000228 0x0000000000000230 RW 0x1000DYNAMIC 0x0000000000002e00 0x0000000000003e00 0x0000000000003e000x00000000000001c0 0x00000000000001c0 RW 0x8 ...