Linux学习四(GDB)

Linux学习四 : GDB

在细节上,GDB调试工具更加强大

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
using std::cout;
using std::endl;

int func(int n){
int sum = 0;
for(int i=0;i<n;i++){
sum+=i;
}
return sum;
}

int main(){
long result =0;
for(int i=1;i<=50;i++){
result+=i;
}
cout<<"result[1-50] =" <<result<<endl;
cout<<"result[1-60] =" <<func(60)<<endl;
return 0;
}
  1. 若要调试,在编译的过程中要加上-g。
1
2
3
4
5
6
7
8
9
CC = g++ -std=c++14
CFLAGS = -g -Wall

main:
$(CC) $(CFLAGS) -o main main.cpp

.PHONY:clean
clean:
rm -f main *.o
  1. 启动gdb进程, 指定需要gdb调试的应用程序名称。

    gdb main

    1. set args .. 可以设置参数 show args 查看设置的命令行参数
    2. l l(小写L)命令相当于list,从第一行开始例出原码。
    3. 直接回车表示,重复上一次命令.
    4. 打断点:break 行数,或者函数名,代表函数入口。
    5. info break 查看断点信息。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
liuchang@DESKTOP-LIUCHANG:~/codetest$ gdb main
GNU gdb (Ubuntu 8.1.1-0ubuntu1) 8.1.1
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from main...done.
(gdb) l
101
102 #include <iostream>
103 using std::cout;
104 using std::endl;
105
106 int func(int n){
107 int sum = 0;
108 for(int i=1;i<=n;i++){
109 sum+=i;
110 }
(gdb) l
111 return sum;
112 }
113
114 int main(){
115 long result =0;
116 for(int i=1;i<=50;i++){
117 result+=i;
118 }
119 cout<<"result[1-50] =" <<result<<endl;
120 cout<<"result[1-60] =" <<func(60)<<endl;
(gdb) l
121 return 0;
122 }(gdb)
Line number 123 out of range; main.cpp has 122 lines.
(gdb) break 115
Breakpoint 1 at 0x991: file main.cpp, line 115.
(gdb) break func
Breakpoint 2 at 0x961: file main.cpp, line 107.
(gdb) info break
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000000991 in main() at main.cpp:115
2 breakpoint keep y 0x0000000000000961 in func(int) at main.cpp:107
  1. r 运行程序 r是run的缩写。会在断点处挺住
  2. n单条执行语句,next命令简写。
  3. c 继续执行程序,continue命令简写。
  4. p打印变量的值,print命令简写。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
(gdb) r
Starting program: /home/liuchang/codetest/main

Breakpoint 1, main () at main.cpp:115
115 long result =0;
(gdb) n
116 for(int i=1;i<=50;i++){
(gdb) n
117 result+=i;
(gdb) n
116 for(int i=1;i<=50;i++){
(gdb) n
117 result+=i;
(gdb) c
Continuing.
result[1-50] =1275

Breakpoint 2, func (n=60) at main.cpp:107
107 int sum = 0;
(gdb) n
108 for(int i=1;i<=n;i++){
(gdb) p i
$1 = 0
(gdb) n
109 sum+=i;
(gdb) n
108 for(int i=1;i<=n;i++){
(gdb) p i
$2 = 1
(gdb) n
109 sum+=i;
(gdb) n
108 for(int i=1;i<=n;i++){
(gdb) p i
$3 = 2
(gdb)
  1. bt 查看堆栈
  2. finish退出函数
  3. q退出gdb
1
2
3
4
5
6
7
8
9
10
11
12
13
(gdb) bt 
#0 func (n=60) at main.cpp:109
#1 0x0000000008000a0f in main () at main.cpp:120
(gdb) finish
Run till exit from #0 func (n=60) at main.cpp:109
0x0000000008000a0f in main () at main.cpp:120
120 cout<<"result[1-60] =" <<func(60)<<endl;
Value returned is $5 = 1830
(gdb) c
Continuing.
result[1-60] =1830
[Inferior 1 (process 26871) exited normally]
(gdb) q

使用gdb

  • 如果没有-g,将看不见程序的函数名、变量名,所代替的全是运行时的内存地址。
  • 启动GDB的方法
    • gdb program :程序一般在当前目录下。
    • gdb program.core : 用gdb同时调试一个运行程序和core文件,core是程序非法执行后core dump后产生的文件。
    • gdb program PID:那么你可以指定这个服务程序运行时的进程ID。gdb会自动attach上去,并调试他。program应该在PATH环境变量中搜索得到。

GDB启动时,可以加一些GDB的启动开关,一般比较常用的就是符号表 -s -c coredump的core文件,-d 添加一个源文件的路径。

gdb的常用命令

b break的缩写,加断点。
info break [n] 查看断点
l list 缩写查看源码
c continue缩写,恢复运行。
step命令可以缩写为s, 命令被执行一次代码被向下执行一行,如果这一行是一个函数调用,那么程序会进入到函数体内部。
n next缩写,单步跟踪程序。next命令和step命令功能是相似的,只是在使用next调试程序的时候不会进入到函数体内部。
p print缩写,可以查看变量值。
p file::variable 某文件中的变量
p function::variable 全局变量
p *array@len 查看数组内容。
p/x 十六进制格式显示变量。其他格式的变量也有。
display i 变量自动显示。
whatis para 查看para的类型
finish 运行程序,直到当前程序函数完成,并打印函数返回时的堆栈地址和返回值及参数值等信息。
until 或 u 运行程序直到结束循环结构。
info agrs 查看函数名和参数值
info locals 打印当前函数中所有局部变量及其值
info line查看源代码内存地址
disassemble func 可以查看函数func的汇编代码。
set listsize n 设置一次显示源代码的行数。
q quit的缩写,退出gdb。

set args 可指定运行时参数。如:set args 10 20 30 40 50
show args 命令可以查看设置好的运行参数。
path dir 可设定程序的运行路径。
show paths
cd / pwd
run > outfile重定向输出

tty命令可以指写输入输出的终端设备。如:tty /dev/ttyb

调试已经运行的程序
两种方法:
1、在UNIX下用ps查看正在运行的程序的PID(进程ID),然后用gdb program PID格式挂接正在运行的程序。
2、先用gdb program关联上源代码,并进行gdb,在gdb中用attach命令来挂接进程的PID。并用detach来取消挂接的进程。

暂停/恢复程序
在gdb中,我们可以有以下几种暂停方式:断点(BreakPoint)、观察点(WatchPoint)、捕捉点(CatchPoint)、信号(Signals)、线程停止(Thread Stops)。如果要恢复程序运行,可以使用c或是continue命令。

break设置断点:

  1. break function :在函数入口处断点。
  2. break linenum:在行号处断点。
  3. break +/- offset:在当前偏移量行挺住
  4. break filename:linenum 在源文件filename的linenum行处停住
  5. break filename:function 在源文件filename的function函数的入口处停住。
  6. break *address 在程序运行的内存地址处停住。
  7. break命令没有参数时,表示在下一条指令处停住。
  8. break … if condition:condition表示条件,在条件成立时停住。比如在循环境体中,可以设置break if i=100,表示当i为100时停住程序。

watch 设置观察点,观察表达式是否发生变化
catchpoint 设置捕捉点,如:载入共享库(动态链接库)或是C++的异常。

维护断点

clear delete 删除断点。
disable 停止使用断点 enable 继续使用
condition 设置断点停止条件。
ignore 忽视断点。
在断点处使用 commands :可以在断点处加上命令处理语句。
break function 告诉断点在哪个重载函数处加断点。

设置变量值
在调试程序的时候, 我们需要在某个变量等于某个特殊值的时候查看程序的运行状态, 但是通过程序运行让变量等于这个值又非常困难, 这种情况下就可以在 gdb 中直接对这个变量进行值的设置, 或者是在单步调试的时候通过设置循环因子的值直接跳出某个循环, 值设置的命令格式为: set var 变量名=值

线程
如果程序是多线程的话,可以定义断点是否在所有的线程上,或是在某个特定的线程。

1
2
break <linespec> thread <threadno>
break <linespec> thread <threadno> if ...

linespec指定了断点设置在的源程序的行号。threadno指定了线程的ID,ID是GDB分配的,可以通过“info threads”命令来查看正在运行程序中的线程信息。如果不指定thread < threadno >则表示你的断点设在所有线程上面,还可以为某线程指定断点条件。

1
(gdb) break frik.c:13 thread 28 if bartab > lim

栈信息

bt 查看栈信息
bt n 查看栈顶n层信息
bt -n 表示栈底下n层的栈信息
frame n 切换栈
up n 栈向上移动
down n 栈向下移动
info frame/f 打印栈的层编号,当前的函数名,函数参数值,函数所在文件及行号,函数执行到的语句。

搜索源代码:字符串的正则表达式。
search
forward-search
向前搜索
reverse-search 全部搜索。

改变程序的执行

print x=4 修改被调试程序运行时的变量值,将x的值改为4.
jump line 跳转到第line行执行。

如何使用gdb调试coredump文件?

  1. coredump文件是核心转储文件。进程运行时,突然崩溃的那一瞬间,进程在内存中的一个快照,会把此刻内存,寄存器状态,运行堆栈等信息转储保存到一个文件里面。肯呢个产生,可能不产生。
  2. 开启coredump:默认的coredump功能是关闭的,用ulimit -c 查看,如果输出为0,则表示异常终止的时候不会产生core dump文件。
  3. ulimit -c unlimited 开启coredump功能,但是这个功能是临时的。
  4. 设置完成以后可以用ulimit -a 查看是否设置成功。
1
2
3
4
5
6
7
8
9
10
#include <iostream>
using std::cout;
using std::endl;

int main(){
int* ptr = NULL;
*ptr = 100;
return 0;

}

产生core 文件以后:
在这里插入图片描述
使用core进行调试。

程序崩溃产生不了core dump文件怎么办?
关注core file size大小,如果是0,需要设置一下,不然无法生成core dump文件。一般设置为unlimited。

gdb exe core 的顺序去执行程序调试coredump文件
在这里插入图片描述
调试过程:

  1. 查看调用堆栈,寻找崩溃原因。
  2. 根据崩溃点,查找代码分析原因。
  3. 修复bug。
    这里因为代码比较简单,直接指出了错误的执行函数main函数,指针设置出现段错误。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
#include <cstring>

using std::cout;
using std::endl;

void func(char* ptr){
strcpy(ptr,"test code ...");
}

int main(){
char* ptr = NULL;
func(ptr);
return 0;
}

编译运行,产生coredump文件。
在这里插入图片描述
查看到是func函数中的问题,ptr是一个空指针。

如何使用gdb调试多线程程序?

  1. 首先编写一个多线程简单测试样例。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <iostream>
#include <unistd.h>
#include <pthread.h>

using std::cout;
using std::endl;

void* thread_entry_funcA(void* arg){
for(int i=0;i<500;i++){
cout<<"[thread_entry_funcA]: "<<i<<endl;
sleep(1);
}
return nullptr;
}

void* thread_entry_funcB(void* arg){
for(int i=0;i<500;i++){
cout<<"[thread_entry_funcB]: "<<i<<endl;
sleep(1);
}
return nullptr;
}

int main(){
pthread_t tidA,tidB;
int ret = pthread_create(&tidA,NULL,thread_entry_funcA,NULL);
if(ret<0){
perror("pthread_create");
return 0;
}

ret = pthread_create(&tidB,NULL,thread_entry_funcB,NULL);
if(ret<0){
perror("pthread_create");
return 0;
}

pthread_join(tidA,NULL);
pthread_join(tidB,NULL);
return 0;
}
  1. 在运行的时候,打开另一个终端查看运行线程信息。
1
2
3
4
5
6
7
8
9
10
liuchang@DESKTOP-LIUCHANG:~/codetest$ ps aux | grep main
liuchang 234 0.1 0.0 952340 71772 pts/2 Sl+ 16:49 0:03 /home/liuchang
liuchang 2162 0.0 0.0 98104 1852 pts/6 Tl 17:30 0:00 ./main
liuchang 2225 0.0 0.0 98104 1884 pts/6 Sl+ 17:30 0:00 ./main
liuchang 4682 0.0 0.0 14904 1096 pts/5 S+ 17:37 0:00 grep --color=auto main
liuchang@DESKTOP-LIUCHANG:~/codetest$ pstack 2162

2162: ./main
pstack: Input/output error
failed to read target.

pstack无法查看,命令无法使用,vim一个新的pstack文件出来,将下面代码拷贝进去,之后运行拷贝,sudo cp pstack /usr/bin,之后pstack就可以正常使用了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#!/bin/sh

if test $# -ne 1; then
echo "Usage: `basename $0 .sh` <process-id>" 1>&2
exit 1
fi

if test ! -r /proc/$1; then
echo "Process $1 not found." 1>&2
exit 1
fi

# GDB doesn't allow "thread apply all bt" when the process isn't
# threaded; need to peek at the process to determine if that or the
# simpler "bt" should be used.

backtrace="bt"
if test -d /proc/$1/task ; then
# Newer kernel; has a task/ directory.
if test `/bin/ls /proc/$1/task | /usr/bin/wc -l` -gt 1 2>/dev/null ; then
backtrace="thread apply all bt"
fi
elif test -f /proc/$1/maps ; then
# Older kernel; go by it loading libpthread.
if /bin/grep -e libpthread /proc/$1/maps > /dev/null 2>&1 ; then
backtrace="thread apply all bt"
fi
fi

GDB=${GDB:-/usr/bin/gdb}

if $GDB -nx --quiet --batch --readnever > /dev/null 2>&1; then
readnever=--readnever
else
readnever=
fi

# Run GDB, strip out unwanted noise.
$GDB --quiet $readnever -nx /proc/$1/exe $1 <<EOF 2>&1 |
set width 0
set height 0
set pagination no
$backtrace
EOF
/bin/sed -n \
-e 's/^\((gdb) \)*//' \
-e '/^#/p' \
-e '/^Thread/p'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
liuchang@DESKTOP-LIUCHANG:~/codetest$ ps aux | grep main
liuchang 234 0.1 0.0 953252 73580 pts/2 Sl+ 16:49 0:04 /home
liuchang 2162 0.0 0.0 98104 1852 pts/6 Tl 17:30 0:00 ./main
liuchang 5255 0.0 0.0 98104 1956 pts/6 Tl 17:39 0:00 ./main
liuchang 5892 0.0 0.0 98104 1948 pts/6 Sl+ 17:47 0:00 ./main
liuchang 6055 0.0 0.0 14904 1088 pts/5 S+ 17:47 0:00 grep --color=auto main

liuchang@DESKTOP-LIUCHANG:~/codetest$ pstack 5892
Thread 3 (Thread 0x7fa009552700 (LWP 5894)):
#0 0x00007fa00a3ee680 in nanosleep () from /lib/x86_64-linux-gnu/libc.so.6
#1 0x0000559d9fe02020 in ?? ()
#2 0x00007fa00a80e65b in std::ostream::put(char) () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#3 0x0000000000000001 in ?? ()
#4 0xffffffffffffff60 in ?? ()
#5 0x0000000000000000 in ?? ()
Thread 2 (Thread 0x7fa009d53700 (LWP 5893)):
#0 0x00007fa00a3ee680 in nanosleep () from /lib/x86_64-linux-gnu/libc.so.6
#1 0x0000559d9fe02020 in ?? ()
#2 0x00007fa00a80e65b in std::ostream::put(char) () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#3 0x0000000000000001 in ?? ()
#4 0xffffffffffffff60 in ?? ()
#5 0x0000000000000000 in ?? ()
Thread 1 (Thread 0x7fa00aebd740 (LWP 5892)):
#0 0x00007fa00aa8cd2d in __pthread_timedjoin_ex () from /lib/x86_64-linux-gnu/libpthread.so.0 #主线程pthreadjoin
#1 0x0000000000001000 in ?? ()
#2 0x0000000000000000 in ?? ()

多线程程序的调试:查看线程信息,切换线程,切换到某一层堆栈

  • gdb打上断点,并且执行之后,info threads查看线程信息。
  • t 加线程id,切换线程。
  • bt 查看堆栈
  • f 加堆栈编号,切换到具体堆栈。
  • p 查看想查看的变量信息
  • n 不仅使当前线程执行一步,其他线程也会执行一部分。(怎么让线程A执行的时候,线程B不要执行?调度器锁)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
(gdb) info threads
Id Target Id Frame
1 Thread 0x7ffff7fe8740 (LWP 27863) "main" clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:78
* 2 Thread 0x7ffff6e83700 (LWP 27867) "main" thread_entry_funcA (arg=0x0) at main.cpp:12
3 Thread 0x7ffff6682700 (LWP 27868) "main" clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:78
(gdb) t 1
[Switching to thread 1 (Thread 0x7ffff7fe8740 (LWP 27863))]
#0 clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:78
78 ../sysdeps/unix/sysv/linux/x86_64/clone.S: No such file or directory.
(gdb) t 2t
Invalid thread ID: 2t
(gdb) t 2t'
Invalid thread ID: 2t'
(gdb) t 2
[Switching to thread 2 (Thread 0x7ffff6e83700 (LWP 27867))]
#0 thread_entry_funcA (arg=0x0) at main.cpp:12
12 cout<<"[thread_entry_funcA]: "<<i<<endl;
(gdb) bt
#0 thread_entry_funcA (arg=0x0) at main.cpp:12
#1 0x00007ffff7bbb6db in start_thread (arg=0x7ffff6e83700) at pthread_create.c:463
#2 0x00007ffff755b61f in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:95
(gdb) f 0
#0 thread_entry_funcA (arg=0x0) at main.cpp:12
12 cout<<"[thread_entry_funcA]: "<<i<<endl;
(gdb) p i
$1 = 0
(gdb) n
[Switching to Thread 0x7ffff6682700 (LWP 27868)]

Thread 3 "main" hit Breakpoint 2, thread_entry_funcB (arg=0x0) at main.cpp:20
20 cout<<"[thread_entry_funcB]: "<<i<<endl;
(gdb) info threads
Id Target Id Frame
1 Thread 0x7ffff7fe8740 (LWP 27863) "main" 0x00007ffff7bbcd2d in __GI___pthread_timedjoin_ex (threadid=140737335801600, thread_return=0x0, abstime=0x0, block=<optimized out>) at pthread_join_common.c:89
2 Thread 0x7ffff6e83700 (LWP 27867) "main" 0x0000555555400b04 in thread_entry_funcA (arg=0x0) at main.cpp:12
* 3 Thread 0x7ffff6682700 (LWP 27868) "main" thread_entry_funcB (arg=0x0) at main.cpp:20

调度器锁模式:只有线程创建之后的暂停效果才有效

调试时除了当前线程在运行,想要规定其他线程的运行情况,可以使用set scheduler-locking [mode] 命令来进行设置。

  1. set scheduler-locking off:不锁定任何线程,所有线程都可以继续执行。
  2. set scheduler-locking on:只有当前线程可以执行,其他线程暂停。
  3. set scheduler-locking step:
    * 当单步执行某一线程时,其它线程不会执行,同时保证在调试过程中当前线程不会发生改变。但如果该模式下执行 continue、until、finish 命令,则其它线程也会执行,并且如果某一线程执行过程遇到断点,则 GDB 调试器会将该线程作为当前线程。centos好像是可以执行的,但是ubuntu不会执行
    * 但如果在该模式下执行continue,until,finish命令,其他线程也会执行,并且如果某一线程执行过程中遇到断点,则GDB调试器会将该线程作为当前线程。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
(gdb) b 12
Breakpoint 1 at 0xaf6: file main.cpp, line 12.
(gdb) b 20
Breakpoint 2 at 0xb61: file main.cpp, line 20.
(gdb) info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000000af6 in thread_entry_funcA(void*) at main.cpp:12
2 breakpoint keep y 0x0000000000000b61 in thread_entry_funcB(void*) at main.cpp:20
(gdb) r
Starting program: /home/liuchang/codetest/main
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[New Thread 0x7ffff6e83700 (LWP 835)]
[New Thread 0x7ffff6682700 (LWP 836)]
[Switching to Thread 0x7ffff6e83700 (LWP 835)]

Thread 2 "main" hit Breakpoint 1, thread_entry_funcA (arg=0x0) at main.cpp:12
12 cout<<"[thread_entry_funcA]: "<<i<<endl;
(gdb) info threads
Id Target Id Frame
1 Thread 0x7ffff7fe8740 (LWP 831) "main" clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:78
* 2 Thread 0x7ffff6e83700 (LWP 835) "main" thread_entry_funcA (arg=0x0) at main.cpp:12
3 Thread 0x7ffff6682700 (LWP 836) "main" thread_entry_funcB (arg=0x0) at main.cpp:20
(gdb) bt
#0 thread_entry_funcA (arg=0x0) at main.cpp:12
#1 0x00007ffff7bbb6db in start_thread (arg=0x7ffff6e83700) at pthread_create.c:463
#2 0x00007ffff755b61f in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:95
(gdb) f 0
#0 thread_entry_funcA (arg=0x0) at main.cpp:12
12 cout<<"[thread_entry_funcA]: "<<i<<endl;
(gdb) n #想要执行线程A,但是发现线程切换到了线程B
[Switching to Thread 0x7ffff6682700 (LWP 836)]

Thread 3 "main" hit Breakpoint 2, thread_entry_funcB (arg=0x0) at main.cpp:20
20 cout<<"[thread_entry_funcB]: "<<i<<endl;

参考列表:
https://haoel.blog.csdn.net/article/details/2879
https://subingwen.cn/linux/gdb/
https://www.bilibili.com/video/BV1mp4y1A7Ss
https://blog.csdn.net/u010164190/article/details/111059283


Linux学习四(GDB)
https://cauccliu.github.io/2024/03/26/Linux学习四(GDB)/
Author
Liuchang
Posted on
March 26, 2024
Licensed under