首页
论坛
课程
招聘
[原创]linux gdb调试方法
2022-4-13 19:12 14012

[原创]linux gdb调试方法

2022-4-13 19:12
14012

一、gdb常用调试命令一览表


功能

命令

简写命令

例子

备注

查看命令的帮助help 命令名字
help info break
调节代码窗口高度winheight src -5wh src -5
源代码窗口高度-5行
切换窗口

①查看窗口:info win

②切换到src窗口:fs src

③切换到下一个窗口:fs next

①i win

②fs src

③fs n



查看elf文件是否有符号readelf -S appName|grep debug


查看依赖的库readelf -a xxx.so | grep "Shared"


查看静态库有哪些.o文件readelf -d xxx.a


显示程序的源代码路径

readelf appName -p .debug_str|grep /

或者

readelf call_update.out -p .debug_str|grep .cpp




显示程序的符号nm appName


调试程序app,并传递参数

gdb app

set args 10



显示传递给main函数的参数

show args



显示源代码,默认10行

list

l

l 行号        默认生成10行,当指定行号时,会生成以指定行号为中间的共10行代码

设置断点

①break 文件名:行号

②break 文件名:函数名

③break 行号

③break 函数名

b

例1行号下断点

b main.cpp:6

例2 函数名下断点

b   point.cpp:point::print


设置条件断点

break [break-args] if (condition)


①std::string等于某个值命中断点,这个经过本人验证:

b cppName:20 if strcmp(strName.c_str(),"api-ms") == 0

b cppName:20 if i == 160

b main if argc > 1

b 180 if   (pszName == NULL && i < 0)

b test.c:34 if   (x & y) == 1

b myfunc if i % (j + 3) != 0

⑦b 44 if   strlen(mystring) == 0


设置指定线程的断点

①break <linespec> thread <threadno>

②break   <linespec> thread <threadno> if ...


①break frik.c:13 thread 28 if bartab >   lim

linespec指定了断点设置在的源程序的行号。threadno指定了线程的ID,注意,这个ID是GDB分配的,你可以通过"info   threads"命令来查看

设置内存断点

①watch + [变量][表达式]  当变量或表达式值改变时即停住程序。
  ②rwatch + [变量][表达式] 当变量或表达式被读时,停住程序。
  ③awatch + [变量][表达式] 当变量或表达式被读或被写时,停住程序。


①watch *(int*)0x22cbc0


运行

run

r


整个调试过程,只能运行一次run命令,第二次运行run命令,调试器会询问你是否从新开始调试。如果要运行到下一个断点,不能用run,必须使用continue或者其简写c

运行到下一个断点

continue

c


相当于VS里的F5

运行到下一个断点,输入c,不能输入r,输入run的话会重新开始调试,

执行到指定行until

until 行号until一般在小范围内跳动

单步执行,不能入函数

next

n


相当于vs的F10

单步执行,进入函数

step

s


相当于vs的F11,step/finish组合

跳出函数

finish


相当于vs的Shfit +   F11,函数完整执行后返回,step/finish组合,注意不能使用简写f。f是frame的缩写

跳过当前函数后面的语句直接返回,返回值可以自定义

return 

return 119

step/return组合

查看函数调用堆栈

backtrace

bt



查看设置的断点

info break

i b


注意i和b之间有个空格

查看寄存器的值

info register

i r


注意i和r之间有个空格

查看栈帧信息

①info frame

②info frame 栈帧编号

i f


注意i和f之间有个空格

查看当前stack frame局部变量

info locals

i local


注意不能缩写成i l

查看程序中的源代码文件info sources


删除断点

delete [breakpoints num] [range...]

d

例1:删除序号为5的断点

d 5

例2:删除序号1到4的断点

d 1-4


删除断点(删除指定行的断点,作用范围为1行)

①clear function

②clear   filename:function

③clear   line_number

④clear filename:line_number



禁用断点

disable 断点编号




启用断点

enable 断点编号




打印单个变量值

①print /format 变量名

/x 按十六进制格式显示变量。
  /d 按十进制格式显示变量。
  /u 按十六进制格式显示无符号整型。
  /o 按八进制格式显示变量。
  /t 按二进制格式显示变量。
  /a 按十六进制格式显示变量。
  /c 按字符格式显示变量。
  /f 按浮点数格式显示变量。

p

例1十进制打印nNum:

(gdb) p nNum
  $2 = 100

例2十六进制打印nNum:

(gdb) p /x nNum
  $2 = 0x64

例3计算常量表达式的值:

(gdb) p /x   0x22cb70+0x50


打印数组

p 数组第一个元素@打印长度

p

例1打印int数组的前10个元素:

(gdb) p   nScore[0]@10

例2打印int数组的前10个元素:

(gdb) p   *nScore@10

例3以16进制打印数组前10个元素。

(gdb) p /x   *nScore@10

①set print array 命令,以便能够在显示数组时更方便查看

②如果打印std::string显示不全,可以设置打印的数组元素个数为0,意味着不限制字符个数

set print elements 0   

打印结构体

p  结构体变量a

p  *结构体变量指针





例1:打印结构体变量jack

(gdb) set print null-stop

(gdb) set print pretty

(gdb) p jack


例2:打印结构体变量指针pKevin指向的结构体的内容


(gdb) p *pKevin

例3:打印结构体成员

(gdb) p jack.nId


①set print null-stop 命令,设置字符串的显示规则,即遇到结束符时停止显示

②set print pretty

美观显示

打印内存

打印内存:x  /nfu   addr (以f格式打印n个u类型存储单元的以addr开头的内存值)

格式f: o(octal)  x(hex) d(decimal) u(unsigned decimal)   t(binary) f(float) a(address) i(instruciton) c(char) s(string)

字节u: b(byte)  h(halfword)  w(word)    g(gaint, 8 bytes)


例1:以二进制显示addr处开始的256个字节

x    /256xb addr

例2:以十六进制显示addr处开始的256个字节

x    /256xw addr


常显示某变量

display 变量如果我们需要在调试中一直显示某个变量的值,那么就需要display命令了
删除常显示


1、info display

2、undisplay 常显示变量编号


修改变量的值

set variable key = value
  set var key = value


例1 :修改char数组

set var   chArr="hello lily"

例2:修改int变量

set var nNum=102

例3:修改int数组的某个元素

set var nScore[2]=998


修改并查看变量的值

print 变量名=值


例1:修改并打印char数组

p   chArr="sal"

例2:修改并打印int变量值

p nNum=666

例3:修改并打印int数组的某个元素值

p nScore[0]=666


查看线程信息,并切换线程

info thread

thread 线程编号

i  thread

t 线程编号



显示调用堆栈并切换栈帧、并查看局部变量

bt

frame 栈帧编号

f

①切换到栈帧1

f 1

②查看栈帧1的局部变量

info local

首先使用bt命令显示调用堆栈,

再使用f命令切换栈帧,切换栈帧仅仅是因为gdb没有ui界面,相当于点击vs调用堆栈的某个栈帧。没有发生线程切换

再使用info local查看函数的局部变量。

显示源代码窗口

wh     显示源代码窗口

Ctrl + L  代码窗口清屏(,ctrl+L后按下回车键)

Ctrl + X+ A   退出代码窗口




显示已加载的模块信息

a)显示所有已经加载的动态库信息

 info shared

b)显示某个已经加载的动态库信息

 info shared libtgcDefender.so




给动态库加载模块sharedlibrary myso


清屏

!clear




查看当前调试位置where



反汇编disassemble




将某段内存 dump 保存到文件中dump binary memory file start_addr end_addr
dump binary memory my_binary_file.bin 0x22fd8a 0x22fd8a+450

(gdb) help dump binary memory

Write contents of memory to a raw binary file.

Arguments are FILE START STOP. Writes the contents of memory

within the range [START .. STOP) to the specified FILE in binary format.



结束调试

quit

q





二、常见调试场景

以下调试源代码:

                                              

2.1普通调试

(1)开始调试,并设置传递给main函数的参数

命令:gdb 要调试的程序名

set args 参数1  参数2 ......

2)设置、查看断点,并运行命中断点。

  

3)查看并修改变量的值

4)显示指定内存地址的内容


5)单步执行命令n(不进入函数)和s(进入子函数)

6)下断点,并查看断点列表,运行命中断点,切换栈帧并查看局部变量


(7)删除断点

2.2附加调试

1)查看要调试的程序pid

命令:ps -aux | grep attachDemo


2)附加进程调试

执行gdb attach pid即可调试正在运行的程序,

执行:$ gdb attach 29231,若执行gdb attach时提示:” ptrace: Operation not permitted”,则执行:$ sudo gdb attach 29231

3)在工作者线程中下断点

4)运行命中断点

2.3多线程死锁调试

1)编写以下死锁代码,文件名字deadLock.cpp

// deadLock.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
 
#include <thread>
#include <iostream>
#include <mutex>
using namespace std;
mutex _mutex1;
mutex _mutex2;
int date1;
int date2;
 
int do_work_1()
{
    cout << "thread_1 start" << endl;
    lock_guard<mutex> locker1(_mutex1);
    date1++;
    this_thread::sleep_for(chrono::seconds(1));
     lock_guard<mutex> locker2(_mutex2);
    date2++;
    cout << "thread_1 end" << endl;
   return 0;
}
 
 
int do_work_2()
{
    cout << "thread_2 start" << endl;
    lock_guard<mutex> locker2(_mutex2);
    date2++;
    this_thread::sleep_for(chrono::seconds(1));
    lock_guard<mutex> locker1(_mutex1);
     date1++;
     cout << "thread_2 end" << endl;
    return 0;
}
 
int main()
{
    thread t1(do_work_1);
    thread t2(do_work_2);
    t1.join();
    t2.join();
    cout << "end" << endl;
    return 0;
}

2)编译文件

g++ deadLock.cpp -g -o deadlock -std=c++11 -pthread

3)调试

gdb deadlock

4)执行r,产生死锁

5 按下ctrl + C 使程序中断。

查看当前主线程的栈帧(bt)

查看栈帧3停的位置

初步判断是产生了死锁,因为42行的join没有执行。

查看线程信息,命令info thread,简写i thread*号打头的为当前线程。

查看一下当前的线程,切换到2号线程,切换线程指令thread tid t tid)并查看对应的栈帧。死锁在19

查看锁的拥有者命令:p mutex名字

切换到3#线程,切换线程指令thread tid t tid)并查看对应的栈帧。死锁在32

2.4 coredump调试

1 设置core dump文件生成的方法

a)在终端中输入ulimit -c 如果结果为0,说明当程序崩溃时,系统并不能生成core dump
b
使用ulimit -c unlimited命令,开启core dump功能,使用ulimit -c filesize命令,可以限制core文件的大小(filesize的单位为kbyte)。若ulimit -c unlimited,则表示core文件的大小不受 限制。如果生成的信息超过此大小,将会被裁剪,最终生成一个不完整的core文件。在调试此core文件的时候,gdb会提示错误。

c/proc/sys/kernel/core_uses_pid可以控制core文件的文件名中是否添加pid作为扩展。文件内容为1,表示添加pid作为扩展名,生成的core文件格式为core.xxxx;为0则表示生成的core文件同一命名为core

2)编写以下死锁代码,文件名字main.cpp

// main.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
 
#include <iostream>
using namespace std;
 
int Div(int a, int b)
{
    int nDiv = a / b;
    return nDiv;
}
 
int main()
{
    int a = 100;
    int b = 0;
    int nDiv = 0;
 
    std::cout << "enter main" << std::endl;
    nDiv = Div(a, b);
    std::cout << "nDiv=" << nDiv << std::endl;
}

3)运行程序,生成dump

4)调试core dump文件,定位崩溃点。

agdb 程序名  coredump文件名

b)输入bt命令,查看崩溃的调用堆栈

2.5内存断点

1)编写以下测试代码

#include <stdio.h>
#include <string.h>
 
int main()
{
    int i, j;
    char buf[256] = { 0 };
    char* pp = buf;
 
    printf("buf addr= 0x%x\n", buf);
 
    for (i = 0; i < 16; i++)
    {
        printf("addr = 0x%x~0x%x\n", pp + i * 16, pp + i * 16 + 15);
        for (j = 0; j < 16; j++)
            *(pp + i * 16 + j) = i * 16 + j;
    }
    printf("ASCII table:\n");
 
    for (i = 0; i < 16; i++)
    {
      for (j = 0; j < 16; j++)
         printf("%c  ", *(pp + i * 16 + j));
       printf("\n");
    }
    return 0;
}

2)调试程序,在第11行下断点(buf数组初始化为0),命中11行断点。

3)查看下内存断点之前的变量buf的值,然后在buf的第80个元素buf[80]处下内存断点,并运行。

2.6源码调试在别的机器上编译的程序、方法1(在B机器上编译,在A机器上调试)。

    场景:动态库文件libadd.so在B机器上编译的。要在A机器上源码调试libadd.so

1)gdb调试testSO.out,并递归指定源码路径

        命令(注意扩住find命令的不是单引号.....,,就是ESC下面那个,和~同一个键)

                gdb `find /root/projects/sosrc -type d -printf '-d %p '` testSO.out

2)显示gdb搜索的源码路径

   命令:show dir

3)在动态库中下断点,并命中动态库中的断点

2.7源码调试在别的机器上编译的程序,方法2(在B机器上编译,在A机器上调试)。

1)查看程序的源代码路径

   命令:readelf appName -p .debug_str|grep /或者readelf appName -p .debug_str|grep cpp

                        


2)替换源代码路径

      set substitute-path from_path  to_path

      比如 list显示的源码是   /home/aaa/1.cpp

      那么设置了  set substitute-path /home/aaa/   /home/bbb/

                (把源代码拷贝到调试机器上)


                (替换源代码路径,源码调试成功)

      

2.8 调试汇编代码

1)进入汇编调试界面

      layout src:显示源代码窗口

      layout asm:显示反汇编窗口


2单步执行命令

      si   //step into 进入函数内部

      ni  //next  不进入函数内部

3)查看函数对应的汇编代码

     打印内存:x  /nfu   addr (以f格式打印n个u类型存储单元的以addr开头的内存值)

                格式f: 

                        o(octal)  

                        x(hex) d(decimal) 

                        u(unsigned decimal)   

                        t(binary) f(float) 

                        a(address) 

                        i(instruciton指令,即汇编代码) 

                        c(char) 

                        s(string)

               类型:

                        字节u: b(byte)  

                        h(halfword)  

                        w(word)    

                        g(gaint, 8 bytes


        例子1:x/ni 函数名

        例子2:x/ni 地址

                            (查看main函数的前5条汇编指令

                                                 查看某个地址处的汇编代码

                                                       (查看当前要执行的汇编代码

2.9 gdb使用符号表调试release程序

(1)生成debug版本,strip出release版本发给客户

        命令:

                strip -g program_debug -o program_release

         然后通过Debug版本进行调试release版本程序:

              gdb --symbol=program_debug -exec=program_release

(2)使用符号文件调试release版本程序

        生成符号文件:objcopy --only-keep-debug program_debug debug_symbol

       通过符号文件调试release程序:

                   gdb ./app_release  -s  app.symbol

       通过符号文件调试coreDmp文件:    

                   gdb ./app core.xxxxx -s app.symbol

2.11 如何查看一个elf文件是否有调试信息(-g选项)

      通过命名readelf -S 看看有没有.debug段就可以确定elf文件是否具有调试信息:

      命令格式:readelf -S appName

      或者:readelf -S appName |grep debug

   (1).debug :  存放调试信息,如果有该段说明编译时,添加了-g选项

   (2).symtab:  存放符号表,保存着所有的符号,如果有此段,说明可用strip分离



[招生]科锐逆向工程师培训46期预科班将于 2023年02月09日 正式开班

最后于 2022-12-13 19:04 被sanganlei编辑 ,原因:
收藏
点赞3
打赏
分享
最新回复 (2)
雪    币: 1926
活跃值: 活跃值 (1443)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
huangjw 活跃值 2022-4-13 21:57
2
0
verygood
雪    币: 365
活跃值: 活跃值 (1643)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
kakasasa 活跃值 2022-4-13 23:42
3
0
感谢分享,mark
游客
登录 | 注册 方可回帖
返回