做个笔记,记录下编译和调试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
一些技巧 生成的汇编代码可以使用vim或emacs直接打开,如下代码文件名为main.c。 x86系统:
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 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) 400687 : 83 47 08 02 addl $0x2,0x8 (%rdi) } } 40068 b: c3 retq 40068 c: 0 f 1 f 40 00 nopl 0x0 (%rax)
二进制文件也可以使用vim或emacs打开,使用十六进制打开即可:
emacs命令为:
当然也可以使用hexdump查询十六进制(od也可以,只是默认八进制),命令为:
1 2 hexdump -C 二进制文件 | more
gdb gdb有2种使用方式,一个是直接挂载指定的进程,命令为
另一种方式就加载二进制文件或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 b 8 b main .cpp :main b main .cpp :8 delete 断点号n disable 断点号n enable 断点号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:显示源代码和反汇编窗口 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