gdb调试多进程,多线程

时间:2023-02-01 16:40:47

一:普通命令

1.list命令
list linenum 显示程序第linenum行周围的程序
list function 显示函数名为function的函数的源程序
list 显示当前行后面的源程序
list - 显示当前行前面的源程序

gdb调试多进程,多线程

2.run(r) 运行命令。

3.break(b) 打断点,使用方法:
b linenum 在某行打断点
关闭断点:delete(d) breakpoint-id(标号)
查看断点:info b

gdb调试多进程,多线程

4.单步命令
step count 一次性执行count步,如果有函数会进入函数
next count 一次执行count,不进入函数
finish 运行程序,直到当前函数完成返回,并打印函数返回时的堆栈地址和返回值以及参数信息
until 退出循环体

gdb调试多进程,多线程

5.continue命令
当程序被停住之后,可以使用continue(c)命令,恢复程序的运行直到程序结束,或到达下一个断点。这里要注意如果没有断点程序是会直接结束的。

6.print(p)命令
这个命令比较常用,用来查看我们想看的内容。比如有关数组可以看全部,也可以看从左到右某一部分:

print命令针对变量查看的输出格式有:
x 按十六进制格式显示变量
d 按十进制格式显示变量
u 按十六进制格式显示无符号整型
o 按八进制格式显示变量
t 按二进制格式显示变量t 按二进制格式显示变量
a 按十六进制格式显示变量
c 按字符格式显示变量
f 按浮点数格式显示变量

7.watch命令
这个命令比较有用。watch一般用来观察某个表达式(变量也是一种表达式)的值是否有变化,如果有变化,马上停住程序。我们有一下几种方法设置观察点:
watch expr 为表达式expr设置一个观察点,一旦表达式值有变化,马上停住程序
rwatch expr 当表达式expr被读时,停住程序
awatch expr 当表达式的值被读或被写时,停住程序。
info watchpoints 列出所有观察点(info指令通常可以去套)
举例如下,演示观测*i的值,一旦变化停下来:

在循环中我们也可以使用watch,配合ignore,它是除了until命令之外又一个可以让我们跳出循环的方法,不过watch+ignore更强大,可以任意跳转到第i次循环。它们的意思就是观察一个变量,可以理解为断点,ignore这个断点多少次,然后用continue就可以直接跳过了。

8.examine命令 使用该命令来查看内存地址中的值。
语法是:x/u addr addr表示一个内存地址。
n 是一个正整数,表示显示内存的长度,也就是说从当前地址向后显示几个地址的内容;
f 表示显示的格式,如果地址所指的是字符串,那么格式可以是s,如果地址是指令地址,那么格式可以是i;
u 表示从当前地址往后请求的字节数,如果不指定的话,GDB默认是4字节。

u参数可以被一些字符代替:b表示单字节,h表示双字节,w表示四字节,g表示八字节。
当我们指定了字节长度后,GDB会从指定的内存地址开始,读写指定字节,并把其当作一个值取出来。

n、f、u这3个参数可以一起使用,例如命令“x/3uh 0x54320”表示从内存地址0x54320开始以双字节为1个单位(h)、16进制方式(u)显示3个单位(3)的内存。

9.jump命令 不会改变程序栈的内容,一般只在同一函数内跳转。
jump linespec 指定下一条语句的运行点
jump address 跳到代码行的地址

10.signal命令
使用signal 信号名(如SIGINT)这种方式把信号发送给程序,如果程序注册了signal_handler函数,还可以进行相应的处理,帮助调试程序。

11.set命令
set args 设置命令行参数
set env environmentVarname=value 设置环境变量。如:set env USER=benben

12.call命令
call function 强制调用某函数
强制调用某函数,它会显示函数返回值(如果函数返回值不是void)。print命令也可以完成该功能。

13.disassemble命令
反汇编命令,查看执行时源代码的机器码。

二:多进程调试

1.单独调试子进程
我们可以先运行程序,然后再另一终端使用ps -ef | grep “test”(main此处是可执行文件名)搜索到子进程pid,然后启动gdb,在将其附加(attach)到gdb调试器上。

attach child-pid 使用该命令后,直接run即可,和调试普通程序就没区别了 dettach
脱离进程
gdb调试多进程,多线程

gdb调试多进程,多线程

2.使用调试器选项follow-fork-mode
我们知道如果不设置任何选项,gdb默认调试父进程。调试器选项用法如下:

set follow-fork-mode mode 其中mode的可选值是parent和child,分别表示调试父进程和子进程。
info inferiors 查询正在调试的进程 inferior
processnum 切换进程

默认设置下,在调试多进程程序时GDB只会调试主进程。但是GDB(>V7.0)支持多进程的分别以及同时调试,换句话说,GDB可以同时调试多个程序。
我们还可以使用catch fork指令,如果fork异常,会停止程序。
follow-fork-mode detach-on-fork 说明

gdb调试多进程,多线程

parent                     on               只调试主进程(GDB默认)
child on 只调试子进程
parent off 同时调试两个进程,gdb跟主进程,子进程block在fork位置
child off 同时调试两个进程,gdb跟子进程,主进程block在fork位置

设置方法:set follow-fork-mode [parent|child] set detach-on-fork [on|off]

gdb调试多进程,多线程

三:多线程调试

gdb调试一般有两种模式:all-stop模式和no-stop模式(gdb7.0之前不支持no-stop模式)。
1.all-stop模式

在这种模式下,当你的程序在gdb由于任何原因而停止,所有的线程都会停止,而不仅仅是当前的线程。一般来说,gdb不能单步所有的线程。因为线程调度是gdb无法控制的。无论什么时候当gdb停止你的程序,它都会自动切换到触发断点的那个线程

2.no-stop模式(网络编程常用)

顾名思义,启动不关模式。当程序在gdb中停止,只有当前的线程会被停止,而其他线程将会继续运行。这时候step,next这些命令就只对当前线程起作用。

如果需要打开no-stop模式,可以向~/.gdbinit添加配置文件:

#Enable the async interface  
set target-async 1
#If using the CLI, pagination breaks non-stop
set pagination off
#Finall, turn it on
set non-stop on

gdb支持的命里有两种类型:前台的(同步的)和后台(异步 )的。区别很简单,同步的在输出提示符之前会等待程序report一些线程已经终止的信息,异步则是直接返回。所以我们需要set target-async 1。set pagination off不要出现 Type to continue 的提示信息 。最后一步是打开。
下面是常用命令:

info threads             //显示所有线程
thread id //切换到指定线程
break filename:linenum thread all //在所有线程相应行设置断点,注意如果主线程不会执行到该行,并且启动all-stop模式,主线程执行n或s会切换过去
set scheduler-locking off|on\step //默认off,执行s或c其它线程也同步执行。on,只有当前相称执行。step,只有当前线程执行
show scheduler-locking //显示当前模式
thread apply all command //每个线程执行同意命令,如bt。或者thread apply 1 3 bt,即线程1,3执行bt。

gdb调试多进程,多线程

举例:

#include <stdio.h>
#include <pthread.h>

void processA();
void processB();
void * processAworker(void *arg);

int main(int argc, const char *argv[])
{
int pid;

pid = fork();

if(pid != 0)
processA();
else
processB();

return 0;
}

void processA()
{
pid_t pid = getpid();
char prefix[] = "ProcessA: ";
char tprefix[] = "thread ";
int tstatus;
pthread_t pt;

printf("%s%lu %s\n", prefix, pid, "step1");

tstatus = pthread_create(&pt, NULL, processAworker, NULL);
if( tstatus != 0 )
{
printf("ProcessA: Can not create new thread.");
}

processAworker(NULL);
sleep(1);
}

void * processAworker(void *arg)
{
pid_t pid = getpid();
pthread_t tid = pthread_self();
char prefix[] = "ProcessA: ";
char tprefix[] = "thread ";

printf("%s%lu %s%lu %s\n", prefix, pid, tprefix, tid, "step2");
printf("%s%lu %s%lu %s\n", prefix, pid, tprefix, tid, "step3");

return NULL;
}

void processB()
{
pid_t pid = getpid();
char prefix[] = "ProcessB: ";
printf("%s%lu %s\n", prefix, pid, "step1");
printf("%s%lu %s\n", prefix, pid, "step2");
printf("%s%lu %s\n", prefix, pid, "step3");

}

运行结果:

ProcessA: 7121 step1
ProcessA: 7121 thread 3078198976 step2
ProcessA: 7121 thread 3078198976 step3
ProcessA: 7121 thread 3078196080 step2
ProcessA: 7121 thread 3078196080 step3
ProcessB: 7122 step1
ProcessB: 7122 step2
ProcessB: 7122 step3

调试:
1. 调试主进程,block子进程。
gdb调试多进程,多线程

gdb调试多进程,多线程

  1. 切换到子进程:
    gdb调试多进程,多线程

  2. 设断点继续调试主进程,主进程产生两个子线程:
    gdb调试多进程,多线程

  3. 切换到主进程中的子线程,注意:线程2为前面产生的子进程:
    gdb调试多进程,多线程