序言
这本来是用Word写的,但是后来我换了系统所以只能用markdown迁移然后写了......
$\qquad$本文主要投食给那些在Windows下活了很久然后考试时发现需要用命令行来操作时困惑万分以及觉得GDB很好吃的人
$\qquad$以及----
$\qquad$经常眼瞎看不见i++和j++的区别
$\qquad$经常访问a[-1]然而使编译器无可奈何(除非在使用O2的情况下的明显访问越界)的人
...
$\qquad$正式地说,本文介绍GCC&&GDB命令在OI中的应用。
提要
$\qquad$这需要提要吗?本文就讲GCC和GDB。
$\qquad$注意本文介绍的只是在OI中的应用,一些命令可能会有更高级的用法但是并不会介绍,有一些概念可能是作者糊出来的可以帮助你更好的理解。
$\qquad$本文作为一个教程在语文语法方面不会非常严格,例如命令和指令经常混在一起用(其实我也不知道有什么区别),但是保证内容都是可用的。
说明
$\qquad$本文的有些部分操作在Windows x64下完成,有些在Linux x64下完成的。其中会对比赛的操作系统Noi Linux进行说明(其实大部分情况下都是一样的)。
$\qquad$Windows下的编译器:gcc version 7.1.0 (i686-win32-dwarf-rev0, Built by MinGW-W64 project),7.1.0版本支持到c++17的标准。命令行用的是cmder(无关紧要)。
$\qquad$Linux下用的编译器:Thread model: posix
gcc version 6.4.0 20170724 (Debian 6.4.0-2),支持到c++17的标准
$\qquad$Noi Linux请参考NOI Linux的说明。
$\qquad$下面的指令说明部分${…}不是你要输入的部分,它代表参数。指令后面的括号表示简写。
$\qquad$接下来的一份比较简单的演示源文件,名为XiaPi.cpp。
#include <cstdio>
int main(){
int ans = 0;
for(int i = 1; i <= 10; i++)
ans ++;
if(ans) std::puts("Hello World");
return 0;
}
GCC
$\qquad$一般统称GCC。然而一般编译我们用的GCC/G++编译器,其实两者都能“编译”C/C++源代码,主要是因为编译时两者会类似于“互相调用”。唯一不同的是GCC无法链接C++的库,所以gcc链接时需要加-lstdc++。反正G++是万能的,那么就用G++吧。
$\qquad$然后推荐一个好东西:makefile。因为笔者一般用的编译命令比较长,例如:g++ XiaPi.cpp -g -Wall -Wextra -std=c++17
$\qquad$于是你在源代码目录下创建makefile文件,然后里面的语法参照:
all:XiaPi.cpp
g++ XiaPi.cpp -g -Wall -Wextra -std=c++17
$\qquad$也就是说XiaPi.cpp为你的源文件名,后面是你的编译命令。注意第二行的缩进是必须的。
命令
$\qquad$G++在OI中常用命令解析:
g++ -v
$\qquad$就是把你的编译器的信息打印出来。
$\qquad$不需要加文件名。
$\qquad$首先编译文件的话要以g++ XiaPi.cpp开头。
$\qquad$接下来是各种开关。所有开关都可以调换顺序,但是g++后面一定接文件名(除了-v)。
-g2(-g)/-g3
$\qquad$打开调试开关,这样这份代码就可以被调试。
$\qquad$-g3可以调试宏定义的代码
-O0/-O1/-O2/-Os/-O3
$\qquad$编译器优化,一般自己不需要加,因为在O2及以上的等级都会导致不愉快的调试。
$\qquad$然而打开O2编译等以上的优化可以找出一些越界等问题。
$\qquad$Os为O2.5优化,也就是打开O2但不增加代码长度,有时候会有问题
-Wall -Wextra -Werror
$\qquad$三个命令都是指给代码提供警告,其中:第一个为基础警告,第二个为详细的警告,第三个为把所有警告视为错误。
$\qquad$对于比较粗心的人有极佳的辅助效果。
-std=${standard(c++11 / c++14 / c++17)}
$\qquad$指定C++标准。然而一般来说考试都不会加这个选项,所以一般平时加加就差不多了
$\qquad$主要是锻炼一些装逼写高级代码的能力。
$\qquad$注意不是所有的编译器版本都支持各种标准,目前常用标准有C++11,C++14,C++17。
$\qquad$这些标准的区别在于语法不同,例如:
$\qquad$register标识在c++17下会被提示禁用(其实不少标识都会被忽略)
for(auto it : vector){
...
}
$\qquad$在C++11及以上可以使用
-o ${filename}
$\qquad$后面可接文件名,表示输出的可执行文件名。但是千万别写类似于-o XiaPi.cpp的东西......
-E
$\qquad$输出预处理器处理过的东西,一般是需要在指令后面加上 > XiaPi.e才会输出到文件。一般没什么用,但是你可以观察到前人对你的代码做出了多大的努力。
$\qquad$例如下面一张图是-E处理后的XiaPi.cpp。
[预处理][预处理]
$\qquad$这个有几千行,然而源代码只有几行。
-S
$\qquad$出编译后的汇编代码,有时候可以作为分析优化的工具。会输出一个.s的文件,像这样:
.file "XiaPi.cpp"
.def ___main; .scl 2; .type 32; .endef
.section .rdata,"dr"
LC0:
.ascii "Hello World\0"
.text
.globl _main
.def _main; .scl 2; .type 32; .endef
_main:
LFB44:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
andl $-16, %esp
subl $32, %esp
call ___main
movl $0, 28(%esp)
movl $1, 24(%esp)
L3:
cmpl $10, 24(%esp)
jg L2
addl $1, 28(%esp)
addl $1, 24(%esp)
jmp L3
L2:
cmpl $0, 28(%esp)
je L4
movl $LC0, (%esp)
call _puts
L4:
movl $0, %eax
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
LFE44:
.ident "GCC: (i686-win32-dwarf-rev0, Built by MinGW-W64 project) 7.1.0"
.def _puts; .scl 2; .type 32; .endef
GDB
$\qquad$然后就是GDB部分。GDB的安装(如果和编译器分开了)应和编译器配套,可以通过gdb -v查看你的版本信息。同时在Windows下似乎没有Linux下那么流畅。有时候莫名崩溃,例如在GDB内运行程序之后应该等待(至少我的两台Windows都会这样)线程都开出来了才行,即显示了类似于
[New Thread 11064.0x3d2c]
[New Thread 11064.0x3dbc]
[New Thread 11064.0x1f00]
[New Thread 11064.0x3dc4]
的东西再输入数据,否则可能崩溃。
$\qquad$首先我们在命令行内使用 gdb ${可执行文件名}
进入GDB,例如不加-o的情况下,Windows可使用gdb a(可省略后缀名),Linux下可使用gdb a.out进入。如果使用了-o,则后面接你的可执行文件名。
$\qquad$gdb每次接受一条命令,回车则为重复执行上一条命令。
$\qquad$一般如果当前行前面有(gdb)
那么就是在等待用户命令,否则可能是等待输入或者等待程序运行。
$\qquad$(扩展命令)如果不在gdb内的程序进入了一个死循环什么的,然后你想在当前状态迅速调试它,那么可以再开一个终端,然后输入gdb ${可执行文件名} ${进程pid}
去控制它
概念
停止点
$\qquad$可以使正在运行的程序中断在某个点上。有breakpoint(对应操作break)和watchpoint(对应操作watch)之分(还有一个catchpoint,但是OI中不用)。breakpoint接受一个位置,当到达这个位置的时候停下。Watchpoint接受一个表达式,当表达式的值有变化的时候暂停程序。
查看
$\qquad$一直监视一个变量,对应操作display。
编号和范围
$\qquad$有时候gdb接受一个编号,有时候接受一个编号或范围,编号就是一个数字,范围写作i-j
命令
$\qquad$接下来的操作都是在gdb内执行的。
help
help
help ${function_name}
最重要的命令,获取即时帮助
file
file ${command_name}
$\qquad$这个命令是当你gdb后面没有输入文件名或者输入错误的时候可以载入文件,当gdb中文件有变化(不要太大的变化,可执行文件不能变)的时候可以重载文件。
list
list(l)
list(l) ${function_name}
list(l) ${line_number}
$\qquad$打印代码。第一个是自动连续显示,第二个从某行开始打印。list会有个类似于计数器的东西,每次打印一片,Ctrl+C退出打印,回车继续打印。例如:
#include <cstdio>
int main(){
int ans = 0;
for(int i = 1; i <= 10; i++)
ans ++;
if(ans) std::puts("Hello World");
return 0;
}(gdb)
$\qquad$如果打印到了文件尾部,则会有:Line number 9 out of range; XiaPi.cpp has 8 lines.
$\qquad$此时如果继续list会一直提示。所以我们要list 1回到文件首部。
run(r)
run(r)
$\qquad$这个命令会运行程序,直到遇到错误或遇到断点。运行此命令前请保证已经设置了断点,除非你利用这个检查你程序的错误。
$\qquad$注意到我们断点设置在几行(例如第五行),程序会在第五行暂停,但注意程序此时并没有运行第五行,例如:
Thread 1 hit Breakpoint 1, main () at XiaPi.cpp:5
5 for(int i = 1; i <= 10; i++) ans ++;
$\qquad$表示程序并没有运行到第五行。即在以后看到行号n的时候程序还没有运行第n行的。
next(n)
next(n)
nexti(ni)
$\qquad$向下走一步(一行),但不进入函数
$\qquad$后面那个是在汇编中下一行
step(s)
step(s)
stepi(si)
$\qquad$向下走一步(一行),且如果有函数就进去,内联或宏定义不会进去
$\qquad$后面那个是在汇编中下一行,并且进入call
skip
skip ${function_name}
skip ${file_name}
skip delete\disable\enable
$\qquad$在step时跳过这个函数或文件
$\qquad$delete\disable\enable可以参考其他命令的意思
finish
finish
$\qquad$运行直到退出这个函数
until
until
$\qquad$运行直到当前循环结束
continue(c)
continue(c)
$\qquad$继续运行直到下一个断点
jump
jump ${line_number}
$\qquad$直接跳到某行,如果发生了函数栈的逻辑错误,例如这个函数没有执行完然后直接调到另一个函数,但是命令不会改变栈的内容,所以会有一些不可预料的错误
break(b)
break(b)
break(b) ${function_name}
break(b) (${file_name}::) ${line_number}
break(b) ${line_number} ${expression}
$\qquad$断点操作,第一个是查看所有断点,注意断点都有编号。
$\qquad$第二个是在某个函数开头设置断点。
$\qquad$第三个使在某行设置断点,但程序并不会运行该行。
$\qquad$例如Breakpoint 1 at 0x401611: file XiaPi.cpp, line 6.
表示在XiaPi.cpp中运行到了第6行,但还没有执行第六行的代码
$\qquad$如果遇上多文件(例如交互题)怎么办?你可以用break ${file_name}::${line_number}
来设置指定文件的断点
$\qquad$(扩展用法)最后一个是一个高级用法:当expression满足的时候断点生效,$数据结构等比较麻烦的东西特效$
save && source
save breakpoints ${file_name}
source ${file_name}
$\qquad$从某DL那听来的,好像也比较实用,就是在每次调试完之后希望下一次调试使用同样的断点
$\qquad$就可以首先保存到文件,然后下一次打开的时候source一下
condition
condition ${break_number} ${expression}
$\qquad$上面命令的一个附属命令,修改某个断点上的判断表达式
commands
commands ${break_number}
${commands}
end
$\qquad$在遇到某个断点并且被停住的时候执行命令(GDB内的命令,例如打印某个变量)
$\qquad$配合if可以很好地用在一个复杂的程序提取关键信息
$\qquad$例如一个使用情况:
(gdb) commands 1
Type commands for breakpoint(s) 1, one per line.
End with a line saying just "end".
>print ans
>end
ignore
ignore ${break_number} ${times}
$\qquad$忽略某个断点的条件若干次
watch
watch ${expression}
rwatch ${expression}
awatch ${expression}
$\qquad$设置观察点,当一个表达式(变量)发生一下时间的事情时中断程序
$\qquad$watch:表达式值有变化,rwatch:表达式被读,awatch:b表达式被读或被写(后面两个一般都对于表达式),其中awatch比较适用于一些你认为一个变量被玄学地改动的时候可以用的
clear
clear
clear ${line_number}
clear ${function_name}
$\qquad$删除停止点(不只有断点)
$\qquad$后面就是指定了行数或函数名
delete(d)
delete(d)
delete(d) ${break_number}
$\qquad$删除断点,支持范围(这个意思是这个命令可以把编号替换成为范围)
disable\enable
disable ${break_number}
enable ${break_number}
enable ${break_number} once
enable ${break_number} delete
$\qquad$暂时禁止某个断点,启用某个断点,once表示用一次之后disable,delete表示用一次之后delete
$\qquad$支持范围
checkpoint
checkpoint
$\qquad$在当前位置设置一个检查点,以便于重新调试程序的时候快速恢复状态,注意,它的简单原理就是拷贝进程,所以当前checkpoint进程跑完之后就没有checkpoint,如果需要重复使用就在restart之后马上checkpoint
restart
restart ${check_number}
$\qquad$在第几个checkpoint重启程序
print
print
print (${file_name}/${function_name}::)${expression}
print {array_pointer}@${length}
printf ${format},${expression}...
$\qquad$第一个是输出历史查看过的变量
$\qquad$然后是打印表达式的值(开了-g3可以使用部分宏定义),同时可以指定文件或函数(可以跨函数),表达式内允许使用函数,并且可以使用类似于a=1
的表达式来赋值等等
$\qquad$然后是查看某个数组指针开始的多少个值,如果是二维数组,可以用print *a[10]@100
查看
$\qquad$后面那个是按照printf的格式打印变量
$\qquad$注意有些STL函数或结构体函数不能被查看,这个也没有办法总结,自己按照经验吧
display(disp)
display(disp) ${expression}
undisplay(undisp) ${display_number}
delete display ${display_number}
disable ${display_number}
enable ${display_number}
$\qquad$建立查看,一直打印变量,只要有程序停下或者输入命令都会显示,注意不要弄得太多太复杂了,否则会很卡
$\qquad$后面两个都是删除查看
$\qquad$后面两个和前面的一样
call
call ${expression}
$\qquad$执行某个表达式(函数),并且打印返回值,和print不同的是当返回值为void的时候call不会打印值
set
set ${var_name}=${number}
set var ${var_name}=${number}
set args ${args}...
$\qquad$赋值,如果赋值对象是gdb内的关键字的名词,那么需要用var声明
$\qquad$最后一条可以设置程序的参数
ptype
ptype ${struct/union/class/expression}
$\qquad$可以查看某结构体的具体类型(具体到union/int[2]),可以检查一些隐式转换以防止被卡浮点什么的
whatis
whatis ${struct/union/class/expression}
$\qquad$可以查看某结构体的具体类型(具体到const number)
$\qquad$和上一命令的区别举例:
(gdb) ptype d
type = const union {
int i[2];
double d;
}
(gdb) whatis d
type = const number
d是某个库内定义的东西
info
info 查看所有info可用参数
info address ${var_name} -- 打印指定变量在内存中的位置
info all-register -- 打印所有寄存器状态
info args -- 打印当前函数栈的参数
info breakpoints (${break_number}_ -- 答应断点信息
info checkpoints (${check_number}) -- 打印checkpoint信息
info display (${display_number}) -- 打印display的信息
info functions -- 打印函数信息
info handle -- 打印处理signal的信息
info line (${line_number}) -- 打印行信息,默认当前行
info program -- 打印程序状态
info registers (${register_name}) -- 打印寄存器状态,不要"$",例如`info register eax`
info signals -- 打印信号信息
info skip -- 查看skip
info source -- 查看源码信息
info sources -- 查看源码以及依赖库信息
info stack -- 查看栈信息
info symbol -- 查看某个地址上的变量
info types -- 打印所有定义过的类型
info variables -- 打印所有变量
info watchpoints -- 打印观察点信息
就在上面
breacktrace(bt)
breacktrace(bt)
breacktrace(bt) ${number}
$\qquad$查看栈信息,后面的数字表示查看几层,正数从栈顶开始,否则从栈底开始(一次从栈顶/栈底开始)
frame
frame ${stack_numbwe}
down/up ${number}
$\qquad$跳到第几层栈(不会对栈造成影响)
$\qquad$向上/下移动当前栈
handle
handle ${signal_name} ${keyword}
$\qquad$对某个信号进行处理
$\qquad$signal_name有
SIGABRT -- 进程停止运行
SIGALRM -- 警告钟
SIGFPE -- 算述运算例外
SIGHUP -- 系统挂断
SIGILL -- 非法指令
SIGINT -- 终端中断
SIGKILL -- 停止进程(此信号不能被忽略或捕获)
SIGPIPE -- 向没有读的管道写入数据
SIGSEGV -- 无效内存段访问
SIGQOUT -- 终端退出
SIGTERM -- 终止
SIGUSR1 -- 用户定义信号
SIGUSR2 -- 用户定义信号
SIGCHLD -- 子进程已经停止或退出
SIGCONT -- 如果被停止则继续执行
SIGSTOP -- 停止执行
SIGTSTP -- 终端停止信号
SIGTOUT -- 后台进程请求进行写操作
SIGTTIN -- 后台进程请求进行读操作
$\qquad$keyword有
nostop 发出信息,不停止程序
stop 发出信息病停止程序
print 仅发出信息
noprint 不发出信息
pass/noignore 程序处理信号
nopass/ignore 不让程序处理信号
$\qquad$有时候希望可以暂时忽略某个信号而继续程序可以使用,但不保证每次都可以
奇技淫巧以及小命令
print ${var_name}@${length}
$\qquad$查看内存中某个变量后面的值。用这个可以检测你的错误是否和下标越界有关
search
$\qquad$只是查找源文件的文本而已
max-value-size
$\qquad$这个是限制gdb一次能够查看的数组的大小的限制
$\qquad$一般其实够用了,但是如果你的数组内重复数据比较多的话那么就可以把这个开到尽可能大
set max-value-size ${size}
set max-value-size unlimited
$\qquad$第一个可以设定限制大小
$\qquad$可以取消限制
完结
也许是完结了吧
更新
- 更新了save和source命令