编译和调试C/C++程序常用到的一些知识点

做个笔记,记录下编译和调试C/C++程序常用的一些知识点,主要就是gcc、gdb的一些常用使用方法。

gcc

如图所示,先看下C/C++代码生成可执行文件的过程,共4步:预处理、编译、汇编、链接。

一步到位的编译命令为:

1
2
gcc -Wall -g test.c -o test

预处理

命令为:

1
2
gcc -E test.c -o test.i 或 gcc -E test.c

生成的test.i文件,可以通过vim或emacs直接打开。

gcc的-E选项,可以让编译器在预处理后停止,并输出预处理结果。比如预处理结果就是将stdio.h 文件中的内容插入到test.c中了。

编译为汇编代码(Compilation)

预处理之后,可直接对生成的test.i文件编译,生成汇编代码:

1
2
gcc -S test.i -o test.s

gcc的-S选项,表示在程序编译期间,在生成汇编代码后,停止,-o输出汇编代码文件。

汇编(Assembly)

对于上一小节中生成的汇编代码文件test.s,gas汇编器负责将其编译为目标文件,如下:

1
2
gcc -c test.s -o test.o

连接(Linking)

gcc连接器是gas提供的,负责将程序的目标文件与所需的所有附加的目标文件连接起来,最终生成可执行文件。附加的目标文件包括静态连接库和动态连接库。

对于上一小节中生成的test.o,将其与C标准输入输出库进行连接,最终生成程序test

1
2
gcc test.o -o test

一些技巧

生成的汇编代码可以使用vim或emacs直接打开,如下代码文件名为main.c。
x86系统:

1
2
gcc -S -O2 code.cpp

x64系统:

1
2
gcc -m32 -S -O2 code.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>

void f1(int *a)
{
for (int i = 0; i < 3;i++) {
a[i] = a[i] + 2;
}
}

void f2(int *a)
{
a[0] = a[0] + 2;
a[1] = a[1] + 2;
a[2] = a[2] + 2;
}

int main() {
int a[3] = {0,1,2};
f1(a);
f2(a);
return 0;
}

注意使用-O2编译,生成的.s文件可以直接打开,部分代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
.LFB961:
.cfi_startproc
.cfi_personality 0x0,__gxx_personality_v0
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
movl 8(%ebp), %eax
addl $2, (%eax)
addl $2, 4(%eax)
addl $2, 8(%eax)
popl %ebp
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE961:
.size _Z2f1Pi, .-_Z2f1Pi
.p2align 4,,15

objdump

最常用命令为:

1
objdump -S code

即同时显示源代码和汇编代码,部分代码为:

1
2
3
4
5
6
7
8
9
10
11
void f1(int *a)
{
for (int i = 0; i < 3;i++) {
a[i] = a[i] + 2;
400680: 83 07 02 addl $0x2,(%rdi)
400683: 83 47 04 02 addl $0x2,0x4(%rdi) // 取出寄存器的值,然后加上4,得到的值作为地址,间接寻址得到需要的数据
400687: 83 47 08 02 addl $0x2,0x8(%rdi)
}
}
40068b: c3 retq
40068c: 0f 1f 40 00 nopl 0x0(%rax)

二进制文件也可以使用vim或emacs打开,使用十六进制打开即可:

1
vim命令为:%!xxd

emacs命令为:

1
ALT+X hexl-mode

当然也可以使用hexdump查询十六进制(od也可以,只是默认八进制),命令为:

1
2
hexdump -C 二进制文件 | more

gdb

gdb有2种使用方式,一个是直接挂载指定的进程,命令为

1
2
gdb -p <pid>

另一种方式就加载二进制文件或core文件,命令为:

1
2
gdb -c core  得到二进制文件地址

加载二进制文件方式:

1
2
3
4
gdb binary

gdb binary core

运行相关

1
2
3
4
5
6
7
8
9
10
11
run:简记为 r ,其作用是运行程序,当遇到断点后,程序会在断点处停止运行,等待用户输入下一步的命令。
continue (简写c ):继续执行,到下一个断点处(或运行结束)
next:(简写 n),单步跟踪程序,当遇到函数调用时,也不进入此函数体;此命令同 step 的主要区别是,step 遇到用户自定义的函数,将步进到函数中去运行,而 next 则直接调用函数,不会进入到函数体内。
step (简写s):单步调试如果有函数调用,则进入函数;与命令n不同,n是不进入调用的函数的
until:当你厌倦了在一个循环体内单步跟踪时,这个命令可以运行程序直到退出循环体。
until+行号: 运行至某行,不仅仅用来跳出循环
finish: 运行程序,直到当前函数完成返回,并打印函数返回时的堆栈地址和返回值及参数值等信息。
call 函数(参数):调用程序中可见的函数,并传递“参数”,如:call gdb_test(55)
frame/f 帧编号: 选择栈帧
start:开始执行程序,停在main函数第一行语句前面等待命令
quit:简记为 q ,退出gdb

break

1
2
3
4
5
6
7
8
b main           // main函数设置断点
b 8 // 第8行设置断点
b main.cpp:main //main文件的main函数设置断点
b main.cpp:8 //main文件的第8行设置断点
delete 断点号n //删除第n个断点
disable 断点号n //暂停第n个断点
enable 断点号n //开启第n个断点
delete breakpoints //清除所有断点

info

1
2
3
4
5
6
7
查看当前程序栈的信息: info frame----list general info about the frame
查看当前程序栈的参数: info args---lists arguments to the function
查看当前程序栈的局部变量: info locals---list variables stored in the frame
查看当前寄存器的值:info registers(不包括浮点寄存器) info all-registers(包括浮点寄存器)
查看当前栈帧中的异常处理器:info catch(exception handlers)
查看断点信息: info break
查看当前线程: info threads

查看源码

1
2
3
list :简记为 l ,其作用就是列出程序的源代码,默认每次显示10行。
list 行号:将显示当前文件以“行号”为中心的前后10行代码,如:list 12
list 函数名:将显示“函数名”所在函数的源代码,如:list main

打印

1
2
3
p/x var   //以十六进制打印整数var
p/a addr //打印十六进制形式的地址
p/u var //变量var无符号整数打印

分割窗口

1
2
3
4
5
6
7
layout:用于分割窗口,可以一边查看代码,一边测试
layout src:显示源代码窗口
layout asm:显示反汇编窗口
layout regs:显示源代码/反汇编和CPU寄存器窗口
layout split:显示源代码和反汇编窗口 //推荐这个命令,汇编的si/ni对应源码的s/n,si会进入汇编和C函数内部,ni不会
Ctrl + L:刷新窗口

多线程

1
2
3
info threads   //查看当前进程的线程
thread <id> //切换线程
f 3 //选择栈帧

参考链接:
http://www.cnblogs.com/ggjucheng/archive/2011/12/14/2287738.html
http://man.linuxde.net/objdump
http://linuxtools-rst.readthedocs.io/zh_CN/latest/tool/gdb.html

目录

  1. 1. gcc
    1. 1.1. 预处理
    2. 1.2. 编译为汇编代码(Compilation)
    3. 1.3. 汇编(Assembly)
    4. 1.4. 连接(Linking)
    5. 1.5. 一些技巧
  2. 2. objdump
  3. 3. gdb
    1. 3.1. 运行相关
    2. 3.2. break
    3. 3.3. info
    4. 3.4. 查看源码
    5. 3.5. 打印
    6. 3.6. 分割窗口
    7. 3.7. 多线程