Linux GCC常用命令和背后的故事
一、Linux GCC 常用命令
1.简单编译
//test.c
编译指令
gcc test.c -o test
上述编译过程是分为四个阶段进行的,即预处理(也称预编译,Preprocessing)、编译 (Compilation)、汇编 (Assembly)和连接(Linking)
2.预处理
gcc -E test.c -o test.i 或 gcc -E test.c
可以输出 test.i 文件中存放着 test.c 经预处理之后的代码
3.编译为汇编代码(Compilation)
预处理之后,可直接对生成的 test.i 文件编译,生成汇编代码:
gcc -S test.i -o test.s
4.汇编(Assembly)
gcc -c test.s -o test.o
5.连接(Linking)
gcc 连接器是 gas 提供的,负责将程序的目标文件与所需的所有附加的目标文件连接起来,最终生 成可执行文件。附加的目标文件包括静态连接库和动态连接库
gcc test.o -o test
6.多个程序文件的编译
通常整个程序是由多个源文件组成的,相应地也就形成了多个编译单元,使用 GCC 能够很好地管理 这些编译单元。假设有一个由 test1.c 和 test2.c 两个源文件组成的程序,为了对它们进行编译,并 最终生成可执行程序 test,可以使用下面这条命令:
gcc test1.c test2.c -o test
7.检错
gcc -pedantic illcode.c -o illcode
-pedantic 编译选项并不能保证被编译程序与 ANSI/ISO C 标准的完全兼容,它仅仅只能用来帮助 Linux 程序员离这个目标越来越近。或者换句话说,-pedantic 选项能够帮助程序员发现一些不符合 ANSI/ISO C 标准的代码,但不是全部,事实上只有 ANSI/ISO C 语言标准中要求进行编译器诊断的 那些情况,才有可能被 GCC 发现并提出警告
8.链接
gcc –L /usr/dev/mysql/lib –lmysqlclient test.o –o test
Linux 下的库文件分为两大类分别是动态链接库(通常以.so 结尾)和静态链接库(通常以.a 结尾), 二者的区别仅在于程序执行时所需的代码是在运行时动态加载的,还是在编译时静态加载的。
二、GCC 背后的故事
1.准备工作
先创建一 个工作目录 test0,然后用文本编辑器生成一个 C 语言编写的简单 Hello.c
hello.c
2.编译过程-预处理
使用 gcc 进行预处理
将源文件 hello.c 文件预处理生成 hello.i
GCC 的选项-E 使 GCC 在进行完预处理后即停止
3.编译
使用 gcc 进行编译
将预处理生成的 hello.i 文件编译生成汇编程序 hello.s
GCC 的选项-S 使 GCC 在执行完编译后停止,生成汇编程序
上述命令生成的汇编程序 hello.s 的代码片段如下所示,其全部为汇编代码。
hello.s
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $.LC0, %edi
call puts
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
4.汇编
使用 gcc 进行汇编
将编译生成的 hello.s 文件汇编生成目标文件 hello.o
GCC 的选项-c 使 GCC 在执行完汇编后停止,生成目标文件
5.链接
使用动态库进行链接,生成的 ELF 可执行文件的大小(使用 Binutils 的 size 命令查看)和链接的动态库 (使用 Binutils 的 ldd 命令查看)
6.分析 ELF 文件
readelf -S hello
ELF 文件的段
There are 29 section headers, starting at offset 0x1930:
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 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
0000000000000012 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),
l (large), p (processor specific)
反汇编 ELF
由于 ELF 文件无法被当做普通文本文件打开,如果希望直接查看一个 ELF 文件包 含的指令和数据,需要使用反汇编的方法。 使用 objdump -D 对其进行反汇编如下:
objdump -D hello
使用 objdump -S
将其反汇编并且将其 C 语言源代码混合显示出来:
6a6: 44 89 ef mov %r13d,%edi
6a9: 41 ff 14 dc callq *(%r12,%rbx,8)
6ad: 48 83 c3 01 add $0x1,%rbx
6b1: 48 39 dd cmp %rbx,%rbp
6b4: 75 ea jne 6a0 <__libc_csu_init+0x40>
6b6: 48 83 c4 08 add $0x8,%rsp
6ba: 5b pop %rbx
6bb: 5d pop %rbp
6bc: 41 5c pop %r12
6be: 41 5d pop %r13
6c0: 41 5e pop %r14
6c2: 41 5f pop %r15
6c4: c3 retq
6c5: 90 nop
6c6: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
6cd: 00 00 00
00000000000006d0 <__libc_csu_fini>:
6d0: f3 c3 repz retq
Disassembly of section .fini:
00000000000006d4 <_fini>:
6d4: 48 83 ec 08 sub $0x8,%rsp
6d8: 48 83 c4 08 add $0x8,%rsp
6dc: c3 retq
三、下载安装nasm并用nasm编译生成程序
1.nasm下载安装
输入命令进行下载
2.“hello.asm”编译生成可执行程序
1.生成文件并写下代码
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 //调用内核功能
2.编译生成程序并运行
ld -s -o hello hello.o
3.查看程序大小
size hello
3.“hello world”C代码的编译生成的程序
1.创建hello.c文件
#include<stdio.h>
int void main()
{
printf("Hello World!\n");
return 0;
}
2.编译并输出
gcc hello.c -o helloo
./helloo
3.程序大小为
4.2种程序大小进行比较
可以看出nasm生成的执行程序要比c代码编译生成的程序小得多
四、借助第三方库函数完成代码设计
1.最常用的光标库(curses)的主要函数功能
(1)cbreak():调用cbreak函数后,除了"Del"和"Ctrl"键外,接受其他所有字符输入。
(2)nl (/nonl ):输出时,换行是否作为回车字符。nl函数将换行作为回车符,而nonl函数相反。
(3)noecho()/echo():关闭/打开输入回显功能。
(4)intrflush(WINDOW *win,bool bf) win为标准输出。当bf为true时输入Break,可以加快中断的响应。但是,有可能会造成屏幕输出信息的混乱。
(5)keypad (WINDOW *win, bool bf) : win为标准输出。调用keypad函数后,将可以使用键盘上的一些特殊字符,如方向键,转化成curses.h中的特殊键。
(6)refresh():重绘屏幕显示内容。在调用initscr函数后,第一次调用refresh函数会清除屏幕显示。
2.在Ubuntu中安装curses库
1.使用下面命令安装
2.查找头文件
输入命令
cd/usr/include
3.查找库文件
输入命令
cd/usr/lib
ls
3.以游客身份体验一下即将绝迹的远古时代的 BBS
4.gcc编译生成一个终端游戏
cc mysnake1.0.c -lcurses -o mysnake1.0
输出
./mysnake1.0