【笔记整理 - GDB】

gdb 调试程序

linux环境的调试,可以使用printf语句跟踪程序的运行,也可以使用调试工具。

用gcc编译源程序的时候,编译后的可执行文件不会包含源程序代码,如果您打算编译后的程序可以被调试,编译的时候要加-g的参数:

gcc -g -o test test.c

然后就能使用gdb指令调试了

gdb test
命令命令缩写命令说明
set args设置主程序的参数。
breakb设置断点,b 20 表示在第20行设置断点,可以设置多个断点。
runr开始运行程序, 程序运行到断点的位置会停下来,如果没有遇到断点,程序一直运行下去。
nextn执行当前行语句,如果该语句为函数调用,不会进入函数内部执行。
steps执行当前行语句,如果该语句为函数调用,则进入函数执行其中的第一条语句。
printp显示变量值,例如:p name表示显示变量name的值。
continuec继续程序的运行,直到遇到下一个断点。
set var name=value设置变量的值,假设程序有两个变量:int ii; char name[21];set var ii=10 把ii的值设置为10;set var name=“西施” 把name的值设置为"西施",注意,不是strcpy。
quitq退出gdb环境。

set args 的例子

程序正常执行:
./book119 /oracle/c/book1.c /tmp/book1.c

gdb调试:
gdb book119
(gdb) set args /oracle/c/book1.c /tmp/book1.c

!实践记录

在gdb中使用list指令可以在gdb内查看源代码,但还是开另一个窗口更方便。

通常可以打开2个窗口,一个窗口显示代码、:set nu;另一个窗口进行gdb调试。

set var 变量名=值可以将当前的作用域中名为i的变量的值设为i,可以通过这个改变程序的运行状态。

p 变量名中,变量名可以是函数

这个函数必须在当前停止的位置前执行过一次。

(gdb) p strlen("123qe")
$5 = 5

当然这个函数必须在当前程序中有声明、实现。

关于set args

在设置参数时,可以不加双引号。但如果参数中有如空格之类的特殊字符,则必须得有双引号。

(gdb) set args "1" "2" "3"	// 无任何输出反馈
// 运行程序,在断点停止后:
(gdb) r
Starting program: /root/coding/test "1" "2" "3"
...
(gdb) p argc
$1 = 4
(gdb) p argv[0]
$2 = 0x7fffffffe778 "/root/coding/test"

断点

info b可以查看设置的断点

输入delete n(断点序号)可删除相应断点

输出数组

p *arr@len
arr:数组名
len:数组长度
去掉星号(*)输出的就是地址

多文件调试中在特定文件设置断点

例如http.cpp是生成项目的一个源文件。

b http.cpp:45

跳出当前循环

until

似乎至少要运行循环体一次。

core文件调试

core文件的简单介绍
在一个程序崩溃时,它一般会在指定目录下生成一个core文件。core文件仅仅是一个内存映象(同时加上调试信息),主要是用来调试的。

启用core文件生成

// core文件默认禁止生成
[root@localhost coding]# ulimit -a
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
...

// 修改core文件的限制
[root@localhost coding]# ulimit -c unlimited

[root@localhost coding]# ulimit -a
core file size          (blocks, -c) unlimited
...

使用

生成core文件

编写一个会导致运行错误的代码:

void foo()
{
	int* i = 0;
	*i = 1;
}

int main(int argc, char* argv[])
{
	foo();
}
[root@localhost coding]# ./test 
Segmentation fault (core dumped)
[root@localhost coding]# ls
...   core.3301   ...
使用core文件

1、gdb -c core

[root@localhost coding]# gdb -c core
(gdb) file test	// 先载入可执行文件test
Reading symbols from /root/coding/test...done.
(gdb) r	// 运行
Starting program: /root/coding/test 

Program received signal SIGSEGV, Segmentation fault.
0x000000000040065d in foo () at test.cpp:11
11	 *i = 1;
Missing separate debuginfos, use: debuginfo-install glibc-2.17-324.el7_9.x86_64 libgcc-4.8.5-44.el7.x86_64 libstdc++-4.8.5-44.el7.x86_64

输出的信息指出源文件第11行的*i = 1;导致了错误。

2、gdb 可执行文件名 输出的相应core文件

直接得到错误信息。

[root@localhost coding]# gdb test core.3301
...
[New LWP 3301]
Core was generated by `./test'.
Program terminated with signal 11, Segmentation fault.
#0  0x000000000040065d in foo () at test.cpp:11
11	 *i = 1;
Missing separate debuginfos, use: debuginfo-install glibc-2.17-324.el7_9.x86_64 libgcc-4.8.5-44.el7.x86_64 libstdc++-4.8.5-44.el7.x86_64

bt

通过bt指令查看函数的调用栈。

在每个函数中都指出了错误位置。

(gdb) bt
#0  0x000000000040065d in foo () at test.cpp:11
#1  0x0000000000400679 in main (argc=1, argv=0x7ffc5ccd84c8) at test.cpp:18

调试运行中的文件

gdb 程序文件名 -p 进程编号

源文件

void foo()
{
	for(int i = 0; i < 1000; ++i)
	{
 		sleep(1);
		printf("i=%d\n", i);
	}
}

int main(int argc, char* argv[])
{
	foo();
}

在另一个窗口运行./test程序。

[root@localhost coding]# ps -ef|grep test
root       3675   2447  0 21:57 pts/1    00:00:00 ./test
root       3677   2106  0 21:57 pts/0    00:00:00 grep --color=auto test
[root@localhost coding]# gdb test -p 3675

程序停止。

之后就能向调试普通程序一样调试运行的程序了。

例如

// 修改循环条件i
(gdb) set var i=1
...
i=9
i=10
i=11
i=2

使用bt看一下程序的调用栈。

(gdb) bt
#0  0x00007fc44b58f8d0 in __nanosleep_nocancel () from /lib64/libc.so.6
#1  0x00007fc44b58f784 in sleep () from /lib64/libc.so.6
#2  0x00000000004006f8 in foo () at test.cpp:12
#3  0x000000000040072f in main (argc=1, argv=0x7ffe5ff59858) at test.cpp:22

多进程调试

设置要调试那个进程

(gdb) set follow-fork-mode parent(默认)
(gdb) set follow-fork-mode child

设置调试模式

设置调试一个进程的时候,其它进程时继续执行还是挂起。(默认执行)

(gdb) set detach-on-fork on/off

查看当前调试的进程

(gdb) info inferiors

(gdb) info inferiors
  Num  Description       Executable        
  2    process 4443      /root/coding/test 
* 1    process 4439      /root/coding/test 

切换调试的进程

通常要配合detach-on-fork off使用,否则未被调试的进程会一直在运行,可能会影响使用。(例如不断地向屏幕输出信息干扰使用)

inferior 进程序号

(gdb) inferior 2
[Switching to inferior 2 [process 4443] (/root/coding/test)]
[Switching to thread 2 (process 4443)] 
#0  0x00007ffff72b2a02 in fork () from /lib64/libc.so.6
(gdb) info inferiors
  Num  Description       Executable        
* 2    process 4443      /root/coding/test 
  1    process 4439      /root/coding/test 

例子

int main(int argc, char* argv[])
{
    if(fork() != 0)
    {
        printf("父进程\n");
        for(int i = 0; i < 10; ++i)
        {
            sleep(1);
            printf("i = %d \n", i);
        }
    }
    else
    {
        printf("子进程\n");
        for(int i = 0; i < 10; ++i)
        {
            sleep(1);
            printf("j = %d \n", i);
        }
    }
}

默认会调试父进程,在else处设置断点没有效果。

多线程调试

查看当前调试线程

(gdb) info threads

切换调试线程

(gdb) thread 线程编号

线程运行设置

只运行当前线程
set scheduler-locking on
运行全部线程
set scheduler-locking off

指定线程执行命令

指定某线程执行某gdb命令
thread apply 线程编号 gdb命令
全部线程执行某gdb命令
thread apply all gdb命令

例子

void* foo(void* thd)
{
    int tid = (long)thd;
    for(int i = 0; i < 10; ++i)
    {
        sleep(1);
        printf("thread %d = %d\n", tid, i);
    }
}

int main(int argc, char* argv[])
{
    pthread_t tid[2];

    pthread_create(&tid[0], NULL, foo, (void*)0);
    pthread_create(&tid[1], NULL, foo, (void*)1);

    for(int i = 0; i < 10; ++i)
    {
        sleep(1);
        printf("main thread = %d\n", i);
    }
    
    pthread_join(tid[0], NULL);
	pthread_join(tid[1], NULL);
}

线程的停止比较奇怪,一个线程触发断点时,其它线程不会像多进程那样继续运行,不断地向屏幕输出信息。而是会一起被中断。

但是在执行n指令,让当前被调试的线程前进一步后,其余的线程也都会随之执行,且执行的速度快于当前被调试的线程:

(gdb) n
---------- thread 0 = 4 ----------
---------- thread 1 = 1 ----------
---------- main thread = 5 ----------
12	    for(int i = 0; i < 10; ++i)
(gdb) n
14	        sleep(1);
(gdb) n
---------- main thread = 6 ----------
---------- main thread = 7 ----------
15	        printf("---------- thread %d = %d ----------\n", tid, i);
(gdb) n
---------- main thread = 8 ----------
---------- thread 1 = 2 ----------

当前被调试的是“thread 1”,能看到“thread 1”的进度比另外2个线程慢得多。

设置多个断点后,可能会在多个线程间切换:

(gdb) info threads
[New Thread 0x7ffff67cf700 (LWP 5039)]
  Id   Target Id         Frame 
  3    Thread 0x7ffff67cf700 (LWP 5039) "test" 0x00007ffff70cf9c1 in clone () from /lib64/libc.so.6
* 2    Thread 0x7ffff6fd0700 (LWP 5038) "test" foo (thd=0x0) at test.cpp:12
  1    Thread 0x7ffff7fe0740 (LWP 5037) "test" 0x00007ffff70cf9c1 in clone () from /lib64/libc.so.6
(gdb) c
Continuing.
[Switching to Thread 0x7ffff7fe0740 (LWP 5037)]

Breakpoint 2, main (argc=1, argv=0x7fffffffe518) at test.cpp:28
28	    for(int i = 0; i < 10; ++i)
(gdb) info threads
  Id   Target Id         Frame 
  3    Thread 0x7ffff67cf700 (LWP 5039) "test" foo (thd=0x1) at test.cpp:12
  2    Thread 0x7ffff6fd0700 (LWP 5038) "test" 0x00007ffff70968ed in nanosleep () from /lib64/libc.so.6
* 1    Thread 0x7ffff7fe0740 (LWP 5037) "test" main (argc=1, argv=0x7fffffffe518) at test.cpp:28

通过日志进行调试

使用gdb设置断点可能会破坏多线程的并发状态,通过调试器只能看到一个和谐的假象。

解决的方法就是输出log日志。

作者设计了一个框架,将头文件包含后,可以通过一个类对象,将程序的输出保存到一个文本文件中,之后通过查看文本文件分析多线程的运行状态。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值