IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 从代码到可执行文件 -> 正文阅读

[C++知识库]从代码到可执行文件


以代码main.cpp为例:

#define HEADFILE <iostream>
#include HEADFILE

using namespace std;
// using Marco for test
#define ALIGN_UP(x, n) (x + n - 1) / n * n
static int cnt = 1; // num count
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。可以有多个值,并用空格分开:

  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 #但它们向汇编程序声明了 CPU 上的各种选项
.flie #源文件名
.section .data #初始化的数据,是可读可写的
.section .bss #未初始化的数据
.section .text #保存代码,是只读和可执行的,指令都属于.text段
.align #对下一条指令或者数据的存放地址进行对齐,ARM汇编器使用2^n的值
.type	_ZStL19piecewise_construct, %object #XXX的类型是XXX
.size	_ZStL19piecewise_construct, 1 #XXX的size是XXX
.space size , fill # size个字节,值为fill,如果fill省略,就是0
.local #标示一个符号为本地,对外不可见,如果该符号未定义,就定义该符号
.ascii #声明字符串
.word #用来存放地址
.fpu vfp #浮点协处理器类型 
.global main #定义一个全局符号,通常是为ld使用
.ident	"GCC: (Raspbian 8.3.0-6+rpi1) 8.3.0" #编译器标识,无实际意义
.comm #表示目标文件中的 common symbol,表示公共的符号,`.comm symbol,length`
.syntax [unified | divided] #该指令设置指令集语法
.arm #这执行与.code 32相同的操作
.code [16|32] #该指令选择正在生成的指令集。值 16 选择 Thumb,值 32 选择 ARM。
.fnstart #Marks the start of a function with an unwind table entry.
.cantunwind #Prevents unwinding through the current function. 
.fnend #Marks the end of a function with an unwind table entry.

目标代码

g++ -c main.cpp

内容类似下面:
目标代码内容
这就是一般的ELF(Executable and linking format)文件格式(使用16进制打开),主要分为以下4个部分,一个ELF文件不一定包含所有的部分:
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

本文件中没有程序头。

#下一部分中的可执行文件main就有程序头
$ 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 920 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 920 23:34 main
... ...
$ ./main
Hello, World!
10 align up 16 is 1088

内容类似下面:
静态链接可执行文件

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-10-08 11:36:46  更:2021-10-08 11:38:14 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/29 20:15:48-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码
数据统计