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

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

gcc

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

一步到位的编译命令为:

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

预处理

命令为:

1
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
gcc -S test.i -o test.s

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

汇编(Assembly)

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

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

连接(Linking)

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

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

1
gcc test.o -o test

一些技巧

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

1
gcc -S -O2 code.cpp

x64系统:

1
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
.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
hexdump -C 二进制文件 | more

gdb

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

1
gdb -p <pid>

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

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

加载二进制文件方式:

1
2
3
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
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

工欲善其事必先利其器

Emacs开发环境配置

Vim开发环境配置

首先,我们先看下自己的VIM都安装了什么插件,命令::scriptnames

我们先配置下vimrc文件,vim ~/.vimrc ,我们先设置让其显示行号和高亮代码,添加如下代码:

set nu! “显示行号
syntax enable “语法高亮
syntax on

TagList

功能:有点像VC里面的工作区,里面列出了当前文件的所有的宏,全局变量,函数名等。CTRL+W 连续2下可以左右切换。

下载taglist压缩包, 下载地址:http://www.vim.org/scripts/script.php?script_id=273,然后把解压的两个文件taglist.vim 和 taglist.txt 分别放到$HOME/.vim/plugin 和 $HOME/.vim/doc 目录中。

之后在~/.vimrc中添加如下几条命令:

let Tlist_Auto_Open = 1
let Tlist_Ctags_Cmd = ‘/usr/bin/ctags’
let Tlist_Show_One_File = 1
let Tlist_Exit_OnlyWindow = 1

此时,我们打开一个.c文件查看,发现左边多出一个workspace,当不想出现此工作区时,使用:Tlist可以关闭和打开。

Ctags

功能:ctags的作用是为系统头文件及自己的程序头文件建立索引,有了这个索引后,就可以使用其它VIM插件来实现相应的功能,比如我需要的功能就是代码提示,那就需要用omnicppcomplete插件,但该插件是依赖于ctags的。VIM默认已安装此插件。

sudo apt-get install exuberant-ctags

我们在源代码的最上层目录下使用此命令:

ctags -R –c++-kinds=+p 或者ctags -R –c-types=+p+x

再在VIM中运行此命令:

:set tags=/home/linuxer/source/tags 该命令将tags文件加入到vim中,也可以加入到~/.vimrc中。

使用方法:

我们把光标移动到函数上,按下CTRL+],VIM会自动切换到意义的函数处。返回时,我们输入CTRL+t。

vim“找到 tag: 1/? 或更多” 其他定义的查看方法:

:tselect 显示列表

:tn和:tp 显示后一个tag和前一个tag

或者g] 就可以了。

WinManager

功能:作用是一个文件管理器,能列出当前目标中的文件,可以通过这个浏览工程中的源文件。当光标停在某个文件或文件夹的时候,回车可以打开该文件或文件夹。

在说这个插件之前,我们先说下netrw.vim插件,这个插件在安装VIM时候就已经安装到系统里了,我们打开VIM输入:e /home/linuxer/source 就可以显示出该文件夹里面的文件,我们的插件其实原理就是由这个插件实现的。

使用方法:

http://www.vim.org/scripts/script.php?script_id=95 ,将对应的plugin和doc放入 ~/.vim 文件夹下对应的plugin和doc文件夹下。

在~/.vimrc下添加以下两行:

let g:winManagerWindowLayout=’FileExplorer|TagList’

或者 let g:winManagerWindowLayout=’FileExplorer’ “这2个显示方式不一样,读者选择自己喜欢的吧,一个是左右两列,一个是上下2列

nmap wm :WMToggle<cr>

在正常情况下输入wm(无:号)可以开启和打开,注意:第一种会把Taglist也关闭,此时用:Tlist可以重新打开。本人倾向第二种,使用时候用wm开启就可已了。

C/C++自动补全插件:clang complete

这个插件需要clang编译器的支持,我们先安装下:

sudo apt-get install clang

之后下载clang complete:http://www.vim.org/scripts/script.php?script_id=3302

方法:vim clang_complete.vmb -c ‘so %’ -c ‘q’

之后在~/.vimrc里添加 set completeopt=longest

配合CTRL+N函数、变量补全基本就差不多了。

上传了一份目前我使用的Vimrc配置到github,主要是为了方便你我使用,点击下面链接进入,我的Vimrc设置

人生大病,只是一“傲”字

先生曰:“人生大病,只是一傲字。为子而傲必不孝,为臣而傲必不忠,为父而傲必不慈,为友而傲必不信。故象与丹朱俱不肖,亦只一傲字,便结果了此生。诸君常要体此人心本是天然之理,精精明明,无致介染着,只是一无我而已:胸中切不可有,有即傲也。古先圣人许多好处,也只是无我而已,无我自能谦。谦者众善之基,傲者众恶之魁。”

引用王阳明先生的这段话来表示下我今后的学习和生活态度,与君共勉。