GDB调试使用详解

Posted 行稳方能走远

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了GDB调试使用详解相关的知识,希望对你有一定的参考价值。

参考:C语言gdb调试之精髓(常用命令、多进程、多线程、程序日志)
作者:C语言技术网
发布时间: 2020-07-15 17:13:30
网址:https://www.bilibili.com/video/BV1ei4y1V758?from=search&seid=4037367131025904962
以及:http://www.freecplus.net/b72113dda88a43b48728e0552fd8a74c.html

参考:Linux的调试器–GDB等
作者:一只青木呀
发布时间: 2020-07-14 15:31:01
网址:https://blog.csdn.net/weixin_45309916/article/details/107338210

Linux的调试器

概念

程序员写在编写程序的时候不可能是一帆风顺的,gcc编译器可以发现程序代码的语法错误,但不能发现程序的业务逻辑错误,调试程序是软件开发的内容之一。调试程序的方法有很多种,例如可以用printf语句跟踪程序的运行步骤和显示变量的值,本章节介绍一个功能强大的调试工具gdb。

  • 基于命令行的调试方法
  • 所有的调试都是可以进行脚本编写的
  • 能够调试所有架构的代码
  • 有三种调试方法供大家选择
  • GDB支持远程调试,支持与IDA进行联调

安装GDB增强工具 (gef)

一般来说,gdb启动起来就是这样的样子

  • gdb的一直都非常强大,但是每一步调试,可能有一些要查看的信息,如果每一步都要手动输入命令,未免有点麻烦,所以就出现了插件,把某一些经常要查看的信息每一步都自动帮你显示出来,方便调试

简单的安装方法(我的Linux是Ubuntu18.04)

1、切换到用户权限
2、切换到用户家目录 比如/home/qingmu
3、保持网络通畅,然后只要输入下面的命令 就行

git clone https://github.com/gatieme/GdbPlugins.git ~/GdbPlugins  从git上面下载一些插件
(没有git 可以自行安装)apt-get install git


克隆完之后,会在你当前目录下面有GdbPlugins 这个文件,并且里面会有这几个文件

  • 这样就算ok了,里面三个插件,当你想要用某一个插件的时候,只要输入对应命令就行
  • 这里我们主要使用gef 我们执行
echo "source ~/GdbPlugins/gef/gef.py" > ~/.gdbinit 

然后我们启动gdb就行了

至此gef插件安装完毕

尝试使用gdb进行小程序的调试

gcc -g的选项
gdb gdb-test
一些GDB的命令

GDB的命令

启动方法

本地普通启动 gdb
本地段错误文件启动 gdb core
attch方式启动 gdb
远程启动 gdbserver 0.0.0.0:1234 /path/to/file

启动选项

–symbols < file >
-s < file >
从指定文件中读取符号表
-se < file >
从指定文件中读取符号表信息,并把它们用在可执行文件中
–core < file >
调试时core dump的core文件
–directory < directory>
-d < directory>
加入一个源文件的搜索路径。默认搜索路径是环境变量中PATH所定义的路径
详细的开关可以使用gdb --help

基本命令

set listsize 设置调试中可以查看的行数
set args 10 20 30 40 50 设置程序所需要的参数
path
show paths
save breakpoint name.bp
gdb elf -x name.bp
print § 查看运行数据
print *array@10 查看数组
print file::variable 查看file文件下的variable
x/n、f、u 查看内存
n 是一个正整数,表示显示内存的长度,也就是说从当前地址向后显示几个地址的内容。
f 表示显示的格式,跟print 的格式参数相同
u 表示从当前地址往后请求的字节数,如果不指定的话,GDB默认是4个bytes。u参数可以用下面的字符来代替,b表示单字节,h表示双字节,w表示四字节,g表示八字节。当我们指定了字节长度后,GDB会从指内存定的内存地址开始,读写指定字节,并把其当作一个值取出来。

GDB的基本命令

Linux程序发布流程
确定程序是否存在符号表
readelf -s test-1
生成符号表
objcopy --only-keep-debug test-1 test-1.symbol
生成发布程序
objcopy --strip-debug test-1 test-release
使用符号表进行程序debug
gdb -q --symbol=test-1.symbol --exec=test-release
GDB中暂停/恢复程序运行
断点
条件断点
break if
info breakpoints
delete
disable
enable

观察点
watch 地址
info watchpoints
rwatch

捕捉点
catch event
throw 抛出一个C++的异常 catch throw
catch 捕捉一个C++的异常 catch catch
exec 调用系统调用exev时停止 catch exec
fork 调用系统调用fork时停止 catch fork
load/load libname 载入动态链接库时 catch load / catch load libname

暂停命令
commands bnum
.
.
.
.
end

GDB的简单使用

0.编译hello.c

gcc -g hello.c -o hello

注意:编译的时候一定要加上 -g 选项,使之加入符号表,否贼调试的时候看不到程序的源代码。为了更方便的使用gdb 一定要安装gdb插件gef

以hello.c为例

1.启动GDB

gdb hello  //hello 是hello.c的可执行文件

2.下断点

也就是程序运行到哪,我们以man函数为例
也可以指定在哪一行下断点。

b

info breakpoints 可查看下的断点

3.让程序运行起来

r


此时程序就运行起来了

4.此时需要往下运行

" n " :执行一条语句,碰到函数会直接运行函数
" s ":执行下一条语句,碰到函数会进入到函数中

5.查看运行中程序的变量

" p "

6.运行时程序的打印会在最上面显示

7.退出gdb

q

8.补充一点(设置参数)

如果程序需要传入参数那么 “set args” 在启动gdb之后就可以设置参数
例如我需要传递两个参数:

中间用空格隔开就可以了

GDB多进程调试

下面以process.c为例

#include<stdio.h>
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
 
int main()
{
    pid_t pid = fork();//创建子进程

    if(pid == -1)
    {
        perror("fork error");
        return -1;
    }
    else if(pid == 0)//child
    {
        printf("i am a child:my pid is %d,my father is %d\\n",getpid(),getppid());
    }
    else//father
    {
        printf("i am a father:my pid is %d\\n",getpid());
        wait(NULL);//等待子进程
    }

    return 0;

}

1.编译

gcc -g process.c -o process  //一定要加-g 把符号表载入代码

2.启动gdb

gdb process

3.下断点(我们以man函数为例)

b main

4.运行

r

5.选择是走子进程还是父进程

fork()函数
返回值为0:子进程
返回值大于0:父进程
返回值小于0:创建失败
函数运行是他自己是走父进程还是子进程是不确定的,所以我们需要让他按照自己的想法运行,这是我们就按自己的需要是走父进程还是子进程

跟着父进程去运行

set follow-fork-mode parent  

跟着子进程运行

set follow-fork-mode child

设置之后我们就n往下运行就可以进入子进程

GDB多线程调试

下面以thread.c为例


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

void* thread1(void* arg)
{
    printf("i am thread1,my tid is %u\\n",pthread_self());
	int i=0;
	int result=0;
	for(;i<100;i++)
	{
		result += i;
	}
	printf("thread1 result = %d\\n",result);
    return NULL;
}

void* thread2(void* arg)
{
    printf("i am thread2,my tid is %u\\n",pthread_self());
	int i=0;
	int result=0;
	for(;i<100;i++)
	{
		result ^= i;
	}
	printf("thread2 result = %d\\n",result);
    return NULL;
}

int main()
{
    pthread_t tid1,tid2;
    pthread_create(&tid1,NULL,thread1,NULL);//创建线程1
    pthread_create(&tid2,NULL,thread2,NULL);//创建线程2

    pthread_join(tid1,NULL);//等待线程1
    pthread_join(tid2,NULL);//等待线程2 

    return 0;
}

1.编译

gcc -g thread.c  -o thread -lpthread  //线程库不是内核自带的 要自己加上

2.开始调试

gdb thread

3.选择线程

线程比较简单 想看哪个线程就把断点下到哪个线程的函数上

跟线程1运行

b thread1

跟线程2运行

b thread2

这里我们跟线程1运行

这里我们就跟进了线程1了

可以通过:"info threads"命令来查看正在运行程序中线程信息
其余的就是基本操作了
p 打印想看的数据
x查看对应的内存等等

以上是关于GDB调试使用详解的主要内容,如果未能解决你的问题,请参考以下文章

LinuxLinux调试器--gdb详解

Linux GDB 程序调试工具使用详解

GDB调试使用详解

『Linux从入门到精通』第 ⑪ 期 - Linux调试器——gdb使用详解

linux c/c++ GDB教程详解(转载)

GDB代码调试与使用