以代码main.cpp为例:
#define HEADFILE <iostream>
#include HEADFILE
using namespace std;
#define ALIGN_UP(x, n) (x + n - 1) / n * n
static int cnt = 1;
int num;
int main() {
static int n = 0;
char str[] = "10 align up 16 is ";
cout << "Hello, World!" << endl;
cout << str << ALIGN_UP(1080, 16) << endl;
return 0;
}
gcc/g++在编译程序的时候,主要分为以下四个阶段
预处理
g++ -E main.cpp > out.i
主要是进行宏替换,将#include文件的内容展开,注释的删除,打开后内容类似下面:
# 1 "main.cpp"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 1 "<command-line>" 2
# 1 "main.cpp"
# 1 "/usr/include/c++/8/iostream" 1 3
# 36 "/usr/include/c++/8/iostream" 3
# 37 "/usr/include/c++/8/iostream" 3
# 1 "/usr/include/arm-linux-gnueabihf/c++/8/bits/c++config.h" 1 3
# 236 "/usr/include/arm-linux-gnueabihf/c++/8/bits/c++config.h" 3
# 236 "/usr/include/arm-linux-gnueabihf/c++/8/bits/c++config.h" 3
namespace std
{
typedef unsigned int size_t;
typedef int ptrdiff_t;
typedef decltype(nullptr) nullptr_t;
}
... ...
... ...
namespace std __attribute__ ((__visibility__ ("default")))
{
# 60 "/usr/include/c++/8/iostream" 3
extern istream cin;
extern ostream cout;
extern ostream cerr;
extern ostream clog;
extern wistream wcin;
extern wostream wcout;
extern wostream wcerr;
extern wostream wclog;
static ios_base::Init __ioinit;
}
# 2 "main.cpp" 2
# 3 "main.cpp"
using namespace std;
static int cnt = 1;
int num;
int main() {
static int n = 0;
char str[] = "10 align up 16 is ";
cout << "Hello, World!" << endl;
cout << str << (1080 + 16 - 1) / 16 * 16 << endl;
return 0;
}
输出文件中含有所包含的头文件的信息,头文件展开用行标记描述,格式如下:
# linenum filename flags
表示此后内容是从文件名为filename的文件中第 linenum行展开的。 flags取值范围是1,2,3,4。可以有多个值,并用空格分开:
- 展开一个新文件。
- 一个文件展开完毕。
- 展开一个系统头文件,因此其中的警告信息应被屏蔽不输出。
- 展开的内容应被视为以extern “C” 引入。
汇编
g++ -S main.cpp
main.s的内容类似下面:
.arch armv6
.eabi_attribute 28, 1
.eabi_attribute 20, 1
.eabi_attribute 21, 1
.eabi_attribute 23, 3
.eabi_attribute 24, 1
.eabi_attribute 25, 1
.eabi_attribute 26, 2
.eabi_attribute 30, 6
.eabi_attribute 34, 1
.eabi_attribute 18, 4
.file "main.cpp"
.text
.section .rodata
.align 2
.type _ZStL19piecewise_construct, %object
.size _ZStL19piecewise_construct, 1
_ZStL19piecewise_construct:
.space 1
.local _ZStL8__ioinit
.comm _ZStL8__ioinit,1,4
.data
.align 2
... ...
... ...
push {fp, lr}
add fp, sp, #4
ldr r1, .L11
mov r0, #1
bl _Z41__static_initialization_and_destruction_0ii
pop {fp, pc}
.L12:
.align 2
.L11:
.word 65535
.cantunwind
.fnend
.size _GLOBAL__sub_I_num, .-_GLOBAL__sub_I_num
.section .init_array,"aw"
.align 2
.word _GLOBAL__sub_I_num
.hidden __dso_handle
.ident "GCC: (Raspbian 8.3.0-6+rpi1) 8.3.0"
.section .note.GNU-stack,"",%progbits
输出文件就是汇编语言的代码,其中:
汇编程序中以 . 开头的名称并不是指令的助记符,不会被翻译成机器指令,而是给汇编器一些特殊指示,称为汇编指示(Assembler Directive)或伪操作(Pseudo-operation),由于它不是真正的指令所以加个“伪”字。.section指示把代码划分成若干个段(Section),程序被操作系统加载执行时,每个段被加载到不同的地址,操作系统对不同的页面设置不同的读、写、执行权限。
.arch
.eabi_attributes
.flie
.section .data
.section .bss
.section .text
.align
.type _ZStL19piecewise_construct, %object
.size _ZStL19piecewise_construct, 1
.space size , fill
.local
.ascii
.word
.fpu vfp
.global main
.ident "GCC: (Raspbian 8.3.0-6+rpi1) 8.3.0"
.comm
.syntax [unified | divided]
.arm
.code [16|32]
.fnstart
.cantunwind
.fnend
目标代码
g++ -c main.cpp
内容类似下面: 这就是一般的ELF(Executable and linking format)文件格式(使用16进制打开),主要分为以下4个部分,一个ELF文件不一定包含所有的部分:
ELF头(ELF header)
在elf.h文件中
/* The ELF file header. This appears at the start of every ELF file. */
#define EI_NIDENT (16)
typedef struct
{
unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
Elf32_Half e_type; /* Object file type */
Elf32_Half e_machine; /* Architecture */
Elf32_Word e_version; /* Object file version */
Elf32_Addr e_entry; /* Entry point virtual address */
Elf32_Off e_phoff; /* Program header table file offset */
Elf32_Off e_shoff; /* Section header table file offset */
Elf32_Word e_flags; /* Processor-specific flags */
Elf32_Half e_ehsize; /* ELF header size in bytes */
Elf32_Half e_phentsize; /* Program header table entry size */
Elf32_Half e_phnum; /* Program header table entry count */
Elf32_Half e_shentsize; /* Section header table entry size */
Elf32_Half e_shnum; /* Section header table entry count */
Elf32_Half e_shstrndx; /* Section header string table index */
} Elf32_Ehdr;
$ readelf -h main.o
ELF 头:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
类别: ELF32
数据: 2 补码,小端序 (little endian)
版本: 1 (current)
OS/ABI: UNIX - System V
ABI 版本: 0
类型: REL (可重定位文件)
系统架构: ARM
版本: 0x1
入口点地址: 0x0
程序头起点: 0 (bytes into file)
Start of section headers: 1884 (bytes into file)
标志: 0x5000000, Version5 EABI
本头的大小: 52 (字节)
程序头大小: 0 (字节)
Number of program headers: 0
节头大小: 40 (字节)
节头数量: 17
字符串表索引节头: 16
程序头表(Program header table)
描述文件中的各种segments,用来告诉系统如何创建进程映像的。
$ readelf -l main.o
本文件中没有程序头。
$ readelf -l main
Elf 文件类型为 EXEC (可执行文件)
Entry point 0x10624
There are 9 program headers, starting at offset 52
程序头:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
EXIDX 0x0008d8 0x000108d8 0x000108d8 0x00018 0x00018 R 0x4
PHDR 0x000034 0x00010034 0x00010034 0x00120 0x00120 R 0x4
INTERP 0x000154 0x00010154 0x00010154 0x00019 0x00019 R 0x1
[Requesting program interpreter: /lib/ld-linux-armhf.so.3]
LOAD 0x000000 0x00010000 0x00010000 0x008f4 0x008f4 R E 0x10000
LOAD 0x000ef4 0x00020ef4 0x00020ef4 0x00150 0x001f0 RW 0x10000
DYNAMIC 0x000f00 0x00020f00 0x00020f00 0x00100 0x00100 RW 0x4
NOTE 0x000170 0x00010170 0x00010170 0x00044 0x00044 R 0x4
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x10
GNU_RELRO 0x000ef4 0x00020ef4 0x00020ef4 0x0010c 0x0010c R 0x1
Section to Segment mapping:
段节...
00 .ARM.exidx
01
02 .interp
03 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .rodata .ARM.extab .ARM.exidx .eh_frame
04 .init_array .fini_array .dynamic .got .data .bss
05 .dynamic
06 .note.ABI-tag .note.gnu.build-id
07
08 .init_array .fini_array .dynamic
节(Section, Segments)
segments是从运行的角度来描述elf文件,sections是从链接的角度来描述elf文件。 在链接阶段,我们可以忽略program header table来处理此文件。 在运行阶段,我们可以忽略section header table来处理此程序(所以很多加固手段删除了section header table)。 segments与sections是包含的关系,一个segment包含若干个section。
节头表(Section header table)。
包含了文件各个section的属性信息。
$ readelf -S main.o
There are 17 section headers, starting at offset 0x75c:
节头:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .text PROGBITS 00000000 000034 000120 00 AX 0 0 4
[ 2] .rel.text REL 00000000 000630 000070 08 I 14 1 4
[ 3] .data PROGBITS 00000000 000154 000004 00 WA 0 0 4
[ 4] .bss NOBITS 00000000 000158 00000c 00 WA 0 0 4
[ 5] .rodata PROGBITS 00000000 000158 000027 00 A 0 0 4
[ 6] .ARM.extab PROGBITS 00000000 000180 00000c 00 A 0 0 4
[ 7] .ARM.exidx ARM_EXIDX 00000000 00018c 000018 00 AL 1 0 4
[ 8] .rel.ARM.exidx REL 00000000 0006a0 000028 08 I 14 7 4
[ 9] .init_array INIT_ARRAY 00000000 0001a4 000004 04 WA 0 0 4
[10] .rel.init_array REL 00000000 0006c8 000008 08 I 14 9 4
[11] .comment PROGBITS 00000000 0001a8 000024 01 MS 0 0 1
[12] .note.GNU-stack PROGBITS 00000000 0001cc 000000 00 0 0 1
[13] .ARM.attributes ARM_ATTRIBUTES 00000000 0001cc 00002f 00 0 0 1
[14] .symtab SYMTAB 00000000 0001fc 0002a0 10 15 30 4
[15] .strtab STRTAB 00000000 00049c 000191 00 0 0 1
[16] .shstrtab STRTAB 00000000 0006d0 00008b 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
y (purecode), p (processor specific)
链接生成可执行文件
$ g++ main.o -o main
$ ll
总用量 688K
... ...
-rwxr-xr-x 1 pi pi 8.9K 9月 20 23:34 main
... ...
$ ./main
Hello, World!
10 align up 16 is 1088
也是一个ELF文件格式,内容类似下面: 也可以静态链接编译,可以看到可执行文件的大小有明显的区别:
$ g++ main.cpp -static -o main
$ ll
总用量 2.4M
... ...
-rwxr-xr-x 1 pi pi 1.7M 9月 20 23:34 main
... ...
$ ./main
Hello, World!
10 align up 16 is 1088
内容类似下面:
|