在编译程序时,使用 gcc
或者 g++
时一定要加上 -g
选项,如
gcc -g -o hello hello.c
以便调试程序含有调试符号信息,从而能够正常调试程序。否则则会出现如下提示,导致不能调试。
Reading symbols from demo...(no debugging symbols found)...done.
除了不加 -g
选项,也可以使用 Linux
的 strip
命令移除掉某个程序中的调试信息,我们这里对 hello_server 使用 strip
命令试试:
[root@localhost testclient]# strip hello_server
##使用 strip 命令之前
-rwxr-xr-x. 1 root root 12416 Sep 8 09:45 hello_server
##使用 strip 命令之后
-rwxr-xr-x. 1 root root 6312 Sep 8 09:55 hello_server
可以发现,对 hello_server 使用 strip
命令之后,这个程序明显变小了(由 12416 个字节减少为 6312 个字节)。我们通常会在程序测试没问题以后,将其发布到生产环境或者正式环境中,因此生成不带调试符号信息的程序,以减小程序体积或提高程序执行效率。
注意:
在实际生成调试程序时,一般不仅要加上
-g
选项,也建议关闭编译器的程序优化
选项。编译器的程序优化选项一般有五个级别,从O0 ~ O4
( 注意第一个O0
,是字母O
加上数字0
),O0
表示不优化,从O1 ~ O4
优化级别越来越高,O4
最高。这样做的目的是为了调试的时候,符号文件显示的调试变量等能与源代码完全对应起来。
使用 GDB
调试程序一般有三种方式:
gdb filename
直接调试目标程序gdb attach pid
附加进程gdb filename corename
调试core
文件
1. 直接调试目标程序
假设已有二进制文件如下,其中 chapter_3
为可执行文件
$ ls
chapter_3 main.cpp Makefile student.cpp student.h teacher.cpp teacher.h
$
执行 gdb 可执行文件名
即可以启动调试,如下图:
$ gdb chapter_3
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 chapter_3...done.
(gdb)
如上所示,表示启动调试 chapter_3
成功。这时 gdb
已经成功加载 chapter_3
,并且可以使用一些 gdb
命令,比如设置断点、查看代码等。执行命令 list
的结果如下所示。
(gdb) list
1 //main.cpp
2 #include <iostream>
3 #include "student.h"
4 #include "teacher.h"
5 int main(int argc,char ** argv)
6 {
7 Student stu("Mike",20);
8 printf("Student's Name is %s,age is %d\n",stu.Name(),stu.Age());
9 Teacher teacher("John",2);
10 printf("Teacher's Name is %s,class number is %d\n",teacher.Name(),teacher.classNumber());
(gdb)
接着需要输入 run
命令或者 r
,程序才会真正的运行起来。
(gdb) r
Starting program: /home/wohu/cppProject/book_debug/chapter_3.1/chapter_3
Student's Name is Mike,age is 20
Teacher's Name is John,class number is 2
[Inferior 1 (process 24190) exited normally]
(gdb)
输入 q
,退出 gdb
调试。
(gdb) q
wohu@wohu-dev:~/cppProject/book_debug/chapter_3.1$
2. 添加参数
有些程序在启动时需要接收一些参数,如下所示:
#include <iostream>
#include <string>
using namespace std;
int main(int argc, char *argv[])
{
for (int i = 0; i <= argc; i++)
{
cout << "argc[" << i << "] is " << argv[i] << endl;
}
return 0;
}
使用 gdb
调试,则需要在 gdb
命令窗口中输入以下命令:
set args a b c d
完整命令如下所示:
wohu@wohu-dev:~/cppProject/book_debug/chapter_3.1$ gdb demo
....
Reading symbols from demo...(no debugging symbols found)...done.
(gdb) set args a b c d
(gdb) r
Starting program: /home/wohu/cppProject/book_debug/chapter_3.1/demo a b c d
argc[0] is /home/wohu/cppProject/book_debug/chapter_3.1/demo
argc[1] is a
argc[2] is b
argc[3] is c
argc[4] is d
argc[5] is [Inferior 1 (process 24518) exited normally]
(gdb) q
wohu@wohu-dev:~/cppProject/book_debug/chapter_3.1$
可以通过 show args
查看命令行参数是否设置成功。如果单个命令行参数之间含有空格,可以使用引号将参数包裹起来。
(gdb) set args a b c "12 34"
(gdb) show ar
architecture args
(gdb) show args
Argument list to give program being debugged when it is started is "a b c "12 34"".
(gdb) r
Starting program: /home/weirong/project/cpp/demo a b c "12 34"
argc[0] is /home/weirong/project/cpp/demo
argc[1] is a
argc[2] is b
argc[3] is c
argc[4] is 12 34
argc[5] is [Inferior 1 (process 16433) exited normally]
(gdb)
3. 附加到进程
很多情况下,程序出现问题时并不处于调试状态。也就是说,在我们想要调试程序时,程序已经开始运行,或者服务器代码出现故障时,重启会导致现象丢失,那就只能将 GDB
调试器附加到测试程序上。
如下代码demo.cpp
内容:
#include <iostream>
int main()
{
int a, b;
std::cin >> a >> b;
std::cout << a + b << std::endl;
return 0;
}
通过 g++ -o demo demo.cpp
生成可执行文件 demo
,执行可执行文件如下:
$ ./demo
这时我们需要将 gdb
附加到进程的命令如下:
gdb attach pid
这里的 pid
就是我们程序运行的进程 ID
,可以通过命令 ps
来获取。在新会话 Shell
命令行中执行以下命令:
ps aux | grep demo
可以看到目标程序的 pid
为 1361,
$ ps aux | grep demo
wohu 1361 0.0 0.0 13996 1848 pts/0 S+ 11:30 0:00 ./demo
wohu 1373 0.0 0.0 16180 1088 pts/1 S+ 11:30 0:00 grep --color=auto demo
因此,在 Shell
中执行以下命令
gdb attach 1361
如果是普通用户执行可能报如下错误:
$ gdb attach 1361
...
For help, type "help".
Type "apropos word" to search for commands related to "word"...
attach: No such file or directory.
Attaching to process 1361
Could not attach to process. If your uid matches the uid of the target
process, check the setting of /proc/sys/kernel/yama/ptrace_scope, or try
again as the root user. For more details, see /etc/sysctl.d/10-ptrace.conf
ptrace: Operation not permitted.
/home/wohu/cppProject/book_debug/chapter_3.1/1361: No such file or directory.
(gdb) q
$
切换 root
账号,再次执行
# gdb attach 1361
...
Type "apropos word" to search for commands related to "word"...
attach: No such file or directory.
Attaching to process 1361
Reading symbols from /home/wohu/cppProject/book_debug/chapter_3.1/demo...(no debugging symbols found)...done.
Reading symbols from /usr/lib/x86_64-linux-gnu/libstdc++.so.6...(no debugging symbols found)...done.
Reading symbols from /lib/x86_64-linux-gnu/libc.so.6...Reading symbols from /usr/lib/debug//lib/x86_64-linux-gnu/libc-2.27.so...done.
done.
Reading symbols from /lib/x86_64-linux-gnu/libm.so.6...Reading symbols from /usr/lib/debug//lib/x86_64-linux-gnu/libm-2.27.so...done.
done.
Reading symbols from /lib64/ld-linux-x86-64.so.2...Reading symbols from /usr/lib/debug//lib/x86_64-linux-gnu/ld-2.27.so...done.
done.
Reading symbols from /lib/x86_64-linux-gnu/libgcc_s.so.1...(no debugging symbols found)...done.
0x00007f4a0928f031 in __GI___libc_read (fd=0, buf=0x55eedbbede70, nbytes=1024)
at ../sysdeps/unix/sysv/linux/read.c:27
27 ../sysdeps/unix/sysv/linux/read.c: No such file or directory.
(gdb) list
22 in ../sysdeps/unix/sysv/linux/read.c
(gdb) set args 3 2
(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/wohu/cppProject/book_debug/chapter_3.1/demo 3 2
r
32767
[Inferior 1 (process 1624) exited normally]
(gdb) q
当提示 Attaching to process 1361
时就说明我们已经成功地将 GDB
附加到目标进程了。
需要注意的是,程序使用了一些系统库(如 libgcc_s.so),由于这是发行版本的
Linux
系统,这些库是没有调试符号的,因而GDB
会提示找不到这些库的调试符号。因为目的是调试./demo
,对系统API
调用的内部实现并不关注,所以这些提示可以不用关注,只要./demo
这个文件有调试信息即可。
当用 gdb attach
上目标进程后,调试器会暂停下来,此时可以在 gdb
中输入相关的命令,比如设置断点等,再继续运行程序,此时需要在 gdb
中输入命令 c
继续运行,程序才能恢复为正常状态。
当调试完程序想结束此次调试时,而且不对当前进程 ./demo
有任何影响,也就是说想让这个程序继续运行,可以在 GDB
的命令行界面输入 detach
命令让程序与 GDB 调试器分离,这样 ./demo
就可以继续运行了:
(gdb) detach
Detaching from program: /home/wohu/cppProject/book_debug/chapter_3.1/demo, process 1361
4. 调试 core 文件
有时候,程序运行一段时间后会突然崩溃,只要程序在崩溃的时候有 core
文件产生,就可以使用这个 core
文件来定位崩溃的原因。当然,Linux
系统默认是不开启程序崩溃产生 core
文件这一机制的,我们可以使用 ulimit -c
命令来查看系统是否开启了这一机制。
wohu@ubuntu:~$ ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 7856
max locked memory (kbytes, -l) 64
max memory size (kbytes, -m) unlimited
open files (-n) 65535
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 7856
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
wohu@ubuntu:~$
其中 core file size
那一行默认是 0,表示关闭生成 core
文件,可以使用 ulimit 选项名 设置值
来修改。例如,可以将 core
文件生成改成具体某个值(最大允许的字节数),这里我们使用
ulimit -c unlimited
(unlimited
是 -c
选项值)直接修改成不限制大小。
这样修改以后,当我们关闭这个 Linux 会话,设置项的值就会被还原成 0,因此,我们希望这个选项永久生效,永久生效的方式是把 ulimit -c unlimited
这一行加到 /etc/profile
文件中去,放到这个文件最后一行即可。
生成的 core
文件的默认命名方式是 core.pid
,举个例子,比如某个程序当时运行时其进程 ID
是 16663,那么它崩溃产生的 core
文件的名称就是 core.16663
。
通过下面的命令可以调试 core
文件
gdb filename corename
其中,filename
就是程序名,这里就是可执行文件 demo
,corename
是 core.16663
,我们输入 gdb demo core.16663
来启动调试,然后输入 bt
命令可以看到错误信息。
- 自定义 core 文件的名称和目录
/proc/sys/kernel/core_uses_pid
可以控制产生的 core
文件的文件名中是否添加 PID
作为扩展,如果添加则文件内容为 1,否则为 0;/proc/sys/kernel/core_pattern
可以设置格式化的 core
文件保存位置或文件名。修改方式如下:
echo "/corefile/core-%e-%p-%t" > /proc/sys/kernel/core_pattern
各个参数的说明如下:
假设现在的程序叫 test
,我们设置该程序崩溃时的 core
文件名如下:
echo "/root/testcore/core-%e-%p-%t" > /proc/sys/kernel/core_pattern
那么最终会在 /root/testcore/
目录下生成的 test
的 core
文件名格式如下:
-rw-------. 1 root root 409600 Jan 14 13:54 core-test-13154-1547445291
需要注意的是,您使用的用户必须对指定
core
文件目录具有写权限,否则生成时会因为权限不足而导致无法生成core
文件。