提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
目录
前言
一、gcc编译工具集
1.1gcc工具有哪些
1.2gcc编译过程
1.2.1.这里我们是使用一个hello.c文件进行演示:
1.2.2检错
1.2.3库文件连接
二、ELF文件格式
2.1ELF文件
2.2反汇编ELF
三、nasm汇编编译器编译生成可执行程序
3.1Ubuntu安装nasm汇编编译器
3.2编译代码hello.asm
总结
参考文献:
前言
GCC是Linux下的编译工具集,是GNU Compiler oleton的缩写,包含g. g++等编译器。这个工具集不仅包含编译器,还包含其他工具集,例如ar、 nm等。
GCC工具集不仅能编译C++语言,其他例如Objective-C. Paseal. Fortan、Java、Ada等语言均能进行编译SCC在可以根据不同的硬件平台进行编译,即能进行交叉编译,在A平台上编译B平台的程序,支持常见的X86、ARM、PowerPC、mips等,以及Linux、Windows等软件平台。在本书中仅介绍对C语言进行编译,其他语言的编译请读者查阅相关资料。
GCC在各种平台下都被广泛地采用,特别是嵌入式平台下,这得益于它的目标机定义规则。当对目标机的硬件进行了合适的定义后,可以生成目标机能够正确解析的文件格式。
提示:以下是本篇文章正文内容,下面案例可供参考
一、gcc编译工具集
1.1gcc工具有哪些
| 工具 | |
| addr2line | 用来将程序地址转换成其所对应的程序源文件及所对应的代码行,也可以得到所对应的函数。该工具将帮助调试器在调试的过程中定位对应的源代码位置 |
| as | 主要用于汇编 |
| ld | 主要用于链接 |
| ar | 主要用于创建静态库 |
| ldd | 可以用于查看一个可执行程序依赖的共享库 |
| objcopy | 将一种对象文件翻译成另一种格式,譬如将.bin 转换成.elf、或者将.elf 转换成.bin 等 |
| objdump | 主要的作用是反汇编 |
| readelf | 显示有关ELF文件的信息 |
| size | 列出可执行文件每个部分的尺寸和总尺寸,代码段、数据段、总大小等 |
1.2gcc编译过程
源文件、目标文件和可执行文件是编译过程中经常用到的名词。源文件通常指存放可编辑代码的文件,如存放C、C++和汇编语言的文件。目标文件是指经过编译器的编译生成的CPU可识别的二进制代码,但是目标文件- -般不能执行,因为其中的一-些函数过程没有相关的指示和说明。可执行文件就是目标文件与相关的库链接后的文件,它是可以执行的。
预编译过程将程序中引用的头文件包含进源代码中,并对- - 些宏进行替换。
编译过程将用户可识别的语言翻译成- -组处理器可识别的操作码,生成目标文件,通常翻译成汇编语言,而汇编语言通常和机器操作码之间是一-种一对一-的关系。GNU中有C/C++编译器GCC和汇编器as。
所有的目标文件必须用某种方式组合起来才能运行,这就是链接的作用。目标文件中.通常仅解析了文件内部的变量和函数,对于引用的函数和变量还没有解析,这需要将其他已经编写好的目标文件引用进来,将没有解析的变量和函数进行解析,通常引用的目标是库。链接完成后会生成可执行文件。
| 编译阶段 | 编译命令 | 作用 |
| 预处理 | gcc -E test.c -o test.i | 编译器将源代码中包含头文件编译进来 |
| 编译 | gcc -S test.i -o test.s | 检查代码规范性并翻译成汇编语言 |
| 汇编 | gcc -c test.s -o test.o | 将.s文件转换为目标文件 |
| 连接 | gcc test.o -o test | 将目标文件转换为可执行文件 |
1.2.1.这里我们是使用一个hello.c文件进行演示:
(1)先创建一个文件目录,再使用vim文本编辑器编写一个hello world程序,代码如下:
#includeint main(void) { printf("Hello World!n"); return 0; }
简单编译:
gcc test.c -o test
实际编译:
预处理:gcc -E test.c -o test.i,生成test.i文件
在文件中打开test.i文件,部分内容如下:
编译为汇编代码:gcc -S test.i -o test.s
test部分内容如下:
汇编:gcc -c test.s -o test.o
连接:gcc test.o -o test
图为连接之后生成的文件,接着运行,观察运行结果;
1.2.2检错
gcc -pedantic test.c -o test1
-pedantic:帮助发现一些不符合ANSI/ISO C标准的代码,当出现不符合的代码,会发出警告信息
gcc -Wall test.c -o test2
-Wall:帮助发现一些不符合ANSI/ISO C标准的代码,当出现不符合的代码,gcc会发出尽可能多的警告信息
gcc -Werror test.c -o test3
-Werror:gcc会在所有产生警告的地方停止编译,迫使进行代码的修改
import numpy as np import pandas as pd import matplotlib.pyplot as plt import seaborn as sns import warnings warnings.filterwarnings('ignore') import ssl ssl._create_default_https_context = ssl._create_unverified_context
1.2.3库文件连接
库文件:动态链接库(.so),静态链接库(.a)
函数库:头文件(.h),库文件(.so)
①编译
gcc -c -I /usr/include 源文件 -o 生成.o文件
②链接
gcc -L /usr/lib 动态链接库文件名 生成.o文件 -o 生成可执行文件
③强制性使用静态链接库
gcc链接时会优先使用动态链接库,想强制使用静态链接库执行在命令中加-static
gcc -L /usr/lib -static 静态链接库文件名 生成.o文件 -o 生成可执行文件
静态链接时搜索路径顺序:
a、ld会去找gcc命令中的参数-L
b、gcc的环境变量LBRARY_PATH(指定程序静态链接库文件的搜索路径)
c、内定目录/lib /usr/lib /usr/local/lib
动态链接时搜索路径顺序:
a、编译目标代码时指定的动态库搜索路径
b、环境变量LD_LIBRARY_PATH(指定程序动态链接库文件的搜索路径)
c、配置文件/etc/ld.so.conf中指定的动态库搜索路径
d、默认的动态库搜索路径/lib
e、默认的动态库搜索路径/usr/li
二、ELF文件格式
2.1ELF文件
(1)ELF文件由4部分组成,分别是ELF头(ELF header)、程序头表(Program header table)、节(Section)和节头表(Section header table)。实际上,一个文件中不一定包含全部内容,而且它们的位置也未必如同所示这样安排,只有ELF头的位置是固定的,其余各部分的位置、大小等信息由ELF头中的各项值来决定。
(2)ELF文件格式如下图,位于ELF Header和Section Header Table 之间的都是段(Section)。一个典型的ELF文件包含下面几个段:
.text:已编译程序的指令代码段。
.rodata:ro 代表 read only,即只读数据(譬如常数 const)。
.data:已初始化的 C 程序全局变量和静态局部变量。
.bss:未初始化的 C 程序全局变量和静态局部变量。
.debug:调试符号表,调试器用此段的信息帮助调试。
使用readelf -S test查看test可执行文件各个section的信息
zj123@zj123-virtual-machine:~/test2$ readelf -S hello
There are 29 section headers, starting at offset 0x1930:
节头:
[号] 名称 类型 地址 偏移量
大小 全体大小 旗标 链接 信息 对齐
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .interp PROGBITS 0000000000000238 00000238
000000000000001c 0000000000000000 A 0 0 1
[ 2] .note.ABI-tag NOTE 0000000000000254 00000254
0000000000000020 0000000000000000 A 0 0 4
[ 3] .note.gnu.build-i NOTE 0000000000000274 00000274
0000000000000024 0000000000000000 A 0 0 4
[ 4] .gnu.hash GNU_HASH 0000000000000298 00000298
000000000000001c 0000000000000000 A 5 0 8
[ 5] .dynsym DYNSYM 00000000000002b8 000002b8
00000000000000a8 0000000000000018 A 6 1 8
[ 6] .dynstr STRTAB 0000000000000360 00000360
0000000000000082 0000000000000000 A 0 0 1
[ 7] .gnu.version VERSYM 00000000000003e2 000003e2
000000000000000e 0000000000000002 A 5 0 2
[ 8] .gnu.version_r VERNEED 00000000000003f0 000003f0
0000000000000020 0000000000000000 A 6 1 8
[ 9] .rela.dyn RELA 0000000000000410 00000410
00000000000000c0 0000000000000018 A 5 0 8
[10] .rela.plt RELA 00000000000004d0 000004d0
0000000000000018 0000000000000018 AI 5 22 8
[11] .init PROGBITS 00000000000004e8 000004e8
0000000000000017 0000000000000000 AX 0 0 4
[12] .plt PROGBITS 0000000000000500 00000500
0000000000000020 0000000000000010 AX 0 0 16
[13] .plt.got PROGBITS 0000000000000520 00000520
0000000000000008 0000000000000008 AX 0 0 8
[14] .text PROGBITS 0000000000000530 00000530
00000000000001a2 0000000000000000 AX 0 0 16
[15] .fini PROGBITS 00000000000006d4 000006d4
0000000000000009 0000000000000000 AX 0 0 4
[16] .rodata PROGBITS 00000000000006e0 000006e0
0000000000000011 0000000000000000 A 0 0 4
[17] .eh_frame_hdr PROGBITS 00000000000006f4 000006f4
000000000000003c 0000000000000000 A 0 0 4
[18] .eh_frame PROGBITS 0000000000000730 00000730
0000000000000108 0000000000000000 A 0 0 8
[19] .init_array INIT_ARRAY 0000000000200db8 00000db8
0000000000000008 0000000000000008 WA 0 0 8
[20] .fini_array FINI_ARRAY 0000000000200dc0 00000dc0
0000000000000008 0000000000000008 WA 0 0 8
[21] .dynamic DYNAMIC 0000000000200dc8 00000dc8
00000000000001f0 0000000000000010 WA 6 0 8
[22] .got PROGBITS 0000000000200fb8 00000fb8
0000000000000048 0000000000000008 WA 0 0 8
[23] .data PROGBITS 0000000000201000 00001000
0000000000000010 0000000000000000 WA 0 0 8
[24] .bss NOBITS 0000000000201010 00001010
0000000000000008 0000000000000000 WA 0 0 1
[25] .comment PROGBITS 0000000000000000 00001010
0000000000000029 0000000000000001 MS 0 0 1
[26] .symtab SYMTAB 0000000000000000 00001040
00000000000005e8 0000000000000018 27 43 8
[27] .strtab STRTAB 0000000000000000 00001628
0000000000000203 0000000000000000 0 0 1
[28] .shstrtab STRTAB 0000000000000000 0000182b
00000000000000fe 0000000000000000 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),
2.2反汇编ELF
ELF文件无法被当做普通文本文件打开,如果希望直接查看一个 ELF文件包含的指令和数据,需要使用反汇编的方法,命令为objdump -D test
zj123@zj123-virtual-machine:~/test2$ objdump -D hello
hello: 文件格式 elf64-x86-64
Disassembly of section .interp:
0000000000000238 <.interp>:
238: 2f (bad)
239: 6c insb (%dx),%es:(%rdi)
23a: 69 62 36 34 2f 6c 64 imul $0x646c2f34,0x36(%rdx),%esp
241: 2d 6c 69 6e 75 sub $0x756e696c,%eax
246: 78 2d js 275 <_init-0x273>
248: 78 38 js 282 <_init-0x266>
24a: 36 2d 36 34 2e 73 ss sub $0x732e3436,%eax
250: 6f outsl %ds:(%rsi),(%dx)
251: 2e 32 00 xor %cs:(%rax),%al
Disassembly of section .note.ABI-tag:
0000000000000254 <.note.ABI-tag>:
254: 04 00 add $0x0,%al
256: 00 00 add %al,(%rax)
258: 10 00 adc %al,(%rax)
25a: 00 00 add %al,(%rax)
25c: 01 00 add %eax,(%rax)
25e: 00 00 add %al,(%rax)
260: 47 rex.RXB
261: 4e 55 rex.WRX push %rbp
263: 00 00 add %al,(%rax)
265: 00 00 add %al,(%rax)
267: 00 03 add %al,(%rbx)
269: 00 00 add %al,(%rax)
26b: 00 02 add %al,(%rdx)
26d: 00 00 add %al,(%rax)
26f: 00 00 add %al,(%rax)
271: 00 00 add %al,(%rax)
...
使用objdump -S将其反汇编并且将其C语言源代码混合显示出来:
执行命令gcc -o hello -g hello.c再执行objdump -S test
ELF文件无法被当做普通文本文件打开,如果希望直接查看一个 ELF文件包含的指令和数据,需要使用反汇编的方法,命令为objdump -D test
使用objdump -S将其反汇编并且将其C语言源代码混合显示出来:
执行命令gcc -o hello -g hello.c再执行objdump -S test
hello: 文件格式 elf64-x86-64 Disassembly of section .init: 00000000000004e8 <_init>: 4e8: 48 83 ec 08 sub $0x8,%rsp 4ec: 48 8b 05 f5 0a 20 00 mov 0x200af5(%rip),%rax # 200fe8 <__gmon_start__> 4f3: 48 85 c0 test %rax,%rax 4f6: 74 02 je 4fa <_init+0x12> 4f8: ff d0 callq *%rax 4fa: 48 83 c4 08 add $0x8,%rsp 4fe: c3 retq Disassembly of section .plt: 0000000000000500 <.plt>: 500: ff 35 ba 0a 20 00 pushq 0x200aba(%rip) # 200fc0 <_GLOBAL_OFFSET_TABLE_+0x8> 506: ff 25 bc 0a 20 00 jmpq *0x200abc(%rip) # 200fc8 <_GLOBAL_OFFSET_TABLE_+0x10> 50c: 0f 1f 40 00 nopl 0x0(%rax) 0000000000000510: 510: ff 25 ba 0a 20 00 jmpq *0x200aba(%rip) # 200fd0 516: 68 00 00 00 00 pushq $0x0 51b: e9 e0 ff ff ff jmpq 500 <.plt> Disassembly of section .plt.got: 0000000000000520 <__cxa_finalize@plt>: 520: ff 25 d2 0a 20 00 jmpq *0x200ad2(%rip) # 200ff8 <__cxa_finalize@GLIBC_2.2.5> 526: 66 90 xchg %ax,%ax Disassembly of section .text: 0000000000000530 <_start>: 530: 31 ed xor %ebp,%ebp 532: 49 89 d1 mov %rdx,%r9 535: 5e pop %rsi 536: 48 89 e2 mov %rsp,%rdx 539: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp 53d: 50 push %rax 53e: 54 push %rsp 53f: 4c 8d 05 8a 01 00 00 lea 0x18a(%rip),%r8 # 6d0 <__libc_csu_fini> 546: 48 8d 0d 13 01 00 00 lea 0x113(%rip),%rcx # 660 <__libc_csu_init> 54d: 48 8d 3d e6 00 00 00 lea 0xe6(%rip),%rdi # 63a 554: ff 15 86 0a 20 00 callq *0x200a86(%rip) # 200fe0 <__libc_start_main@GLIBC_2.2.5> 55a: f4 hlt 55b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1) 0000000000000560 : 560: 48 8d 3d a9 0a 20 00 lea 0x200aa9(%rip),%rdi # 201010 <__TMC_END__> 567: 55 push %rbp 568: 48 8d 05 a1 0a 20 00 lea 0x200aa1(%rip),%rax # 201010 <__TMC_END__> 56f: 48 39 f8 cmp %rdi,%rax 572: 48 89 e5 mov %rsp,%rbp 575: 74 19 je 590 577: 48 8b 05 5a 0a 20 00 mov 0x200a5a(%rip),%rax # 200fd8 <_ITM_deregisterTMCloneTable> 57e: 48 85 c0 test %rax,%rax 581: 74 0d je 590 583: 5d pop %rbp 584: ff e0 jmpq *%rax 586: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1) 58d: 00 00 00 590: 5d pop %rbp 591: c3 retq 592: 0f 1f 40 00 nopl 0x0(%rax) 596: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1) 59d: 00 00 00 ......
三、nasm汇编编译器编译生成可执行程序
3.1Ubuntu安装nasm汇编编译器
(1)先判断是否安装nams
使用命令whereis nasm,如果显示nasm: /usr/bin/nasm ,则已经安装;如果只显示nasm: ,则未安装。
(2)安装nasm
使用命令sudo apt install nasm安装nasm编译器
3.2编译代码hello.asm
(1)使用vim文本编辑器编译一个hello.asm文件,代码如下:
; hello.asm
section .data ; 数据段声明
msg db "Hello, world!", 0xA ; 要输出的字符串
len equ $ - msg ; 字串长度
section .text ; 代码段声明
global _start ; 指定入口函数
_start: ; 在屏幕上显示一个字符串
mov edx, len ; 参数三:字符串长度
mov ecx, msg ; 参数二:要显示的字符串
mov ebx, 1 ; 参数一:文件描述符(stdout)
mov eax, 4 ; 系统调用号(sys_write)
int 0x80 ; 调用内核功能
; 退出程序
mov ebx, 0 ; 参数一:退出代码
mov eax, 1 ; 系统调用号(sys_exit)
int 0x80 ; 调用内核功能
编译生成hello可执行文件
使用命令nasm -f elf64 hello.asm将hello.asm文件生成hello.o文件
使用命令ld -s -o hello hello.o将hello.o文件生成hello 可执行文件
执行结果如下:
与gcc编译生成的可执行文件对比(功能都是输出Hello,world!):
汇编语言(hello程序)生成的可执行文件:
gcc生成(hello程序)生成的可执行文件:
由文件大小可见使用nasm汇编生成的main可执行文件要比gcc编译生成的可执行文件要更小
总结
通过对gcc工具集的使用,我了解了在一个程序编译过程中是如何一步一步的编译成可执行文件的。除此之外,还了解了ELF文件和汇编文件的格式以及如何用nasm汇编编辑器编译hello.asm文件生成可执行文件hello。以上这些实验,使我对于Linux操作系统上的程序编译的理解更加深入。在这次实验中也出现了许多小错误,如命令中少打了.o或者-o等,导致程序一直运行失败,这告诉我在使用gcc工具的指令时要做到一丝不苟。
参考文献:
GCC编译工具集和nasm编译器的简要介绍_不#曾&轻听的博客-CSDN博客_nasm编译器
使用gcc和gcc的伙伴_Harriet的博客-CSDN博客



