- 技术优劣取决于需求
2. 编译和链接
2.1 编译过程
在linux下,当我门用GCC来编译helloworld.c程序时,只需使用最简单的命令即可
#include <stdio.h>
int main(){
printf("hello world\n");
return 0;
}
$ gcc hello.c
$ ./a.out
上述过程可以分解为4个步骤:
- 预处理Preprecessing
处理#开头的预编译指令,如#include,#define
使用命令:gcc -E hello.c -o hello.i - 编译 Compilation
将预处理完的文件经过一系列的词法分析,语法分析,语义分析,优化后生成对应的汇编代码文件
使用命令:gcc -S hello.i -o hello.s - 汇编 Assembly
将汇编代码转为机器可以执行的二进制文件
使用命令:gcc -hello.s -o hello.o - 链接 Linking
ld -static **** hello.o ***
2.2 编译器做了啥
- 词法分析
- 语法分析
- 语义分析
- 中间语言生成
- 目标代码生成
2.3 链接器
程序设计模块化
2.4 模块拼接–静态链接
一个复杂的软件,人们把每个源代码模块独立的编译,然后按照需要将他们组装起来,这个组装模块的过程就是链接。从原理上来讲,链接就是把一些指令对其他符号地址的引用加以修改
3. 目标文件
3.1 目标文件格式
目标文件格式:
windows:PE(Portable Executable)
Linux:ELF(Executable Linkable Format)
目标文件就是源代码编译后但是未进行链接的那些中间文件(linux下为***.o),他跟可执行文件很相近
不光是可执行文件(win下的.exe和linux下的ELF可执行文件)按照可执行文件格式存储。动态链接库(DLL)(win下的.dll和linux的.so)以及静态链接库(win下的lib和linux下的.a)都按照可执行文件格式存储
具体可以参考下面的表格:
3.2 目标文件什么样的
int printf(const char* format, ...);
int global_init_var = 84;
int global_uninit_var;
void func1(int i)
{
printf("%d\n", i);
}
int main(void)
{
static int static_var = 85;
static int static_var2;
int a =1;
int b;
func1(static_var + static_var2 + a + b);
return a;
}
3.3 挖掘目标文件
可以使用objdump查看目标文件内部的结构:
lighthouse@VM-4-12-ubuntu:~/test$ objdump -h SimpleSection.o
SimpleSection.o: file format elf64-x86-64
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 00000057 0000000000000000 0000000000000000 00000040 2**0
CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
1 .data 00000008 0000000000000000 0000000000000000 00000098 2**2
CONTENTS, ALLOC, LOAD, DATA
2 .bss 00000004 0000000000000000 0000000000000000 000000a0 2**2
ALLOC
3 .rodata 00000004 0000000000000000 0000000000000000 000000a0 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
4 .comment 0000002a 0000000000000000 0000000000000000 000000a4 2**0
CONTENTS, READONLY
5 .note.GNU-stack 00000000 0000000000000000 0000000000000000 000000ce 2**0
CONTENTS, READONLY
6 .eh_frame 00000058 0000000000000000 0000000000000000 000000d0 2**3
CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA
除了objdump之外还有readelf可以读取obj文件。
备注:
.note.GNU-stack: 堆栈提示段
.comment: 注释信息段
.rodata: 只读数据段
可以通过size查看ELF文件的不同段的大小
ighthouse@VM-4-12-ubuntu:~/test$ size SimpleSection.o
text data bss dec hex filename
179 8 4 191 bf SimpleSection.o
3.3.1 代码段
反汇编代码
#-s 将段内容打印出来
#-d 反汇编
objdump -s -d SimpleSection.o
3.3.2 数据段
3.4 ELF文件结构
3.4.1 文件头
可以使用readelf来查看elf文件
lighthouse@VM-4-12-ubuntu:~/test$ readelf -h SimpleSection.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: 1104 (bytes into file)
Flags: 0x0
Size of this header: 64 (by在这里插入代码片tes)
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: 12
3.4.2 段表
段表位置有ELF文件头中的e_shoff成员决定,比如上个elf文件头重显示的“Start of section headers: 1104 (bytes into file)”
我们可以使用readelf工具来查看ELF文件的段,会显示完整的段信息:
lighthouse@VM-4-12-ubuntu:~/test$ readelf -S SimpleSection.o
There are 13 section headers, starting at offset 0x450:
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 00000340
0000000000000078 0000000000000018 I 10 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
000000000000002a 0000000000000001 MS 0 0 1
[ 7] .note.GNU-stack PROGBITS 0000000000000000 000000ce
0000000000000000 0000000000000000 0 0 1
[ 8] .eh_frame PROGBITS 0000000000000000 000000d0
0000000000000058 0000000000000000 A 0 0 8
[ 9] .rela.eh_frame RELA 0000000000000000 000003b8
0000000000000030 0000000000000018 I 10 8 8
[10] .symtab SYMTAB 0000000000000000 00000128
0000000000000198 0000000000000018 11 11 8
[11] .strtab STRTAB 0000000000000000 000002c0
000000000000007c 0000000000000000 0 0 1
[12] .shstrtab STRTAB 0000000000000000 000003e8
0000000000000061 0000000000000000 0 0 1
3.4.3 重定位表
.rel.text
3.4.4 字符串表
3.5 链接的接口–符号
目标文件之间拼合实际上是目标文件之间对地址的饮用,即对函数和变量地址的饮用
3.5.1 符号表
.symtab
3.5.3 符号修饰
函数签名:包含了一个函数的信息,包括函数名、参数类型、所在的类和命名空间。用于识别不同的函数。
3.5.4 extern “c”
c++编译器会在编译c++程序时默认使用extern “C”,但是c语言的时候会直接调用memset
#ifdef __cplusplus
extern "C" {
#endif
void *memset (void *, int, size_t)
#ifdef __cplusplus
}
#endif
3.5.5 弱符号与强符号
3.6 调试信息
4 静态链接
6. 可执行文件的装载和进程
看不下去了,换一个看:)