GDB

GDB

一. GDB简介

1.1

支持的语言

二. 使用

1.0 例子代码

#include <stdio.h>

/* print a given string and a number if a pre-determined format. */
void
print_string(int num, char* string)
{
    printf("String '%d' - '%s'\n", num, string);
}

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

    /* check for command line arguments */
    if (argc < 2) { /* 2 - 1 for program name (argv[0]) and one for a param. */
        printf("Usage: %s [<string> ...]\n", argv[0]);
	exit(1);
    }

    /* loop over all strings, print them one by one */
    for (argc--,argv++,i=1 ; argc > 0; argc--,argv++,i++) {
        print_string(i, argv[0]);  /* function call */
    }

    printf("Total number of strings: %d\n", i);
   
    return 0;
}

1.1 调用gdb调试器

gcc -g debug_me.c -o debug_me
gdb debug_me

1.2 运行

run "hello, world" "goodbye, world"

1.3 设置断点

仅运行代码的问题在于它会一直运行直到程序退出,这通常为时已晚。为此,引入了断点。断点是调试器在执行特定源代码行之前停止程序执行的命令。我们可以使用两种方法设置断点:

1.3.1 指定要停止的特定代码行:

break debug_me.c:9

1.3.2 指定一个函数名,每次调用时都要中断:

break main

1.3.2 断点相关命令

break 6                   //简写成b 6,既在第六行设置断点
info b                    //显示当前程序的断点设置情况
扩展:
	b fn1 if a>b         //条件断点设置
	break func            //在函数func()的入口处设置断点,如:break cb_button
	delete 断点号n         //删除第n个断点
	disable 断点号n        //暂停第n个断点
	enable 断点号n         //开启第n个断点
	clear 行号n            //清除第n行的断点

1.4 一次执行一个命令

break main
run "hello, world" "goodbye, world"
调试器给出如下内容:
Starting program: /usr/home/choo/work/c-on-unix/debug_me
warning: Unable to find dynamic linker breakpoint function.
warning: GDB will be unable to debug shared library initializers
warning: and track explicitly loaded dynamic code.

Breakpoint 1, main (argc=1, argv=0xbffffba4) at debug_me.c:9
9           if (argc < 2) { /* 2 - 1 for program name (argv[0]) and one for a param. */
(gdb)
1 "next": 使调试器执行当前命令,然后再次停止,显示要执行的代码中的下一个命令。
2 "step": 使调试器执行当前命令,如果它是函数调用 - 在该函数的开头中断。这对于调试嵌套代码很有用。

1.5 打印变量和表达式

print i

您也可以尝试打印更复杂的表达式,例如“i*2”、“argv[3]”或“argv[argc]”等。你也可以使用类型转换,调用程序中的函数。

1.6 检查函数调用堆栈

现在正在执行什么函数,哪个函数调用了它(目前在那个位置上)

where
#0  print_string (num=1, string=0xbffffc9a "hello") at debug_me.c:7
#1  0x80484e3 in main (argc=1, argv=0xbffffba4) at debug_me.c:23

该列表也称未:堆栈跟踪

frame 1
print i

“frame”命令告诉调试器切换到给定的堆栈帧('0' 是当前执行函数的帧)。在那个阶段,调用的任何打印命令都将使用该堆栈帧的上下文。
当然,如果我们发出“step”或“next”命令,程序将在顶部帧继续,而不是在我们要求查看的帧。毕竟,调试器无法“撤消”所有调用并从那里继续。

1.7 附加到已经运行的进程

可能想要调试一个无法从命令行启动的程序。这可能是因为程序是从某个系统守护进程启动的(比如网络上的 CGI 程序),
我们懒得让它直接从命令行运行。或者程序运行它的初始化代码可能需要很长时间,并且使用附加的调试器启动它会导致这个启动时间更长。
还有其他原因。为此,将以这种方式启动调试器:

gdb debug_me 9561

这里我们假设“debug_me”是所执行程序的名称,而 9561 是我们要调试的进程的进程 ID (PID)。

gdb 首先尝试寻找一个名为“9561”的“核心”文件(我们将在下一节中看到核心文件是什么),当它找不到时,它会假设提供的数字是进程 ID,并尝试附加到它。如果有进程执行与我们提供给 gdb 的路径完全相同的程序(不是文件的副本。它必须与进程运行的文件完全相同),它将附加到程序,暂停执行,并让我们继续调试它,就好像我们从调试器内部启动程序一样。当我们得到 gdb 的提示时,做一个“where”将向我们显示进程的堆栈跟踪,我们可以从那里继续。一旦我们退出调试器,它将从进程中分离出来,并且进程将从我们离开它的地方继续执行。

1.8 调试崩溃的程序

调试程序的问题之一,与墨菲定律有关: 程序会在最意想不到的时候崩溃。这句话只是意味着当你把程序作为生产代码取出后,它会崩溃。而且这些错误不一定很容易重现。幸运的是,在“核心文件”的形象中,我们有一些帮助。

核心文件包含进程的内存映像,以及(假设进程中的程序包含调试信息)其堆栈跟踪、变量内容等。程序通常设置为在由于 SEGV 或 BUS 等信号崩溃时生成包含其内存映像的核心文件。如果调用程序的 shell 没有设置限制这个核心文件的大小,我们会在进程的工作目录中找到这个文件(或者是它启动的目录,或者它最后切换到的目录,使用chdir系统调用)。

一旦我们得到这样一个核心文件,我们可以通过发出以下命令来查看它:

gdb /path/to/program/debug_me core

这假设程序是使用这个路径启动的,并且核心文件在当前目录中。如果不是,我们可以给出核心文件的路径。当我们得到调试器的提示时(假设核心文件被成功读取),我们可以发出诸如“print”、“where”或“frame X”之类的命令。我们不能发出暗示执行的命令(例如“下一步”,或函数调用的调用)。在某些情况下,我们将能够看到导致崩溃的原因。

需要注意的是,如果程序由于无效的内存地址访问而崩溃,这将意味着程序的内存已损坏,因此核心文件也已损坏,因此包含错误的内存内容,无效的堆栈帧等. 因此,我们应该将核心文件的内容视为一个可能的过去,在许多可能的过去中(这使得核心文件分析与量子理论非常相似。几乎)。

1.9 一些命令

list 1     // 将显示当前文件以“行号”为中心的前后10行代码,list 10
info frame // 查看堆栈列表

info registers    //查看所有寄存器
info function     //查询函数
x/4x $esp         //查看4个字节的栈顶寄存器的内容
x/4x $ebp         //查看4个字节的栈底寄存器的内容
扩展知识
	(r是64位,e是32位)
	//eax     累加寄存器,它是很多加法乘法指令的缺省寄存器
	//ebx     基地址寄存器,在内存寻址时存放基地址
	//esi     来源索引暂存器     edi     目的索引暂存器  
	//esp     栈顶寄存器        ebp      栈底寄存器   
	//eflags  状态位寄存器
	//ss      堆栈段寄存器  
	//cs      代码段寄存器  
	//ds      数据段寄存器

三. 获取有关调试的更多信息

现在可能是时候使用您的程序和调试器了。建议在 gdb 中尝试“帮助”,以了解有关其命令的更多信息。尤其是“dir”命令,它可以调试源代码被拆分到多个目录的程序。

一旦你觉得 gdb 过于局限,你可以尝试各种图形调试器。尝试检查您是否安装了“xxgdb”——这是一个在 gdb 之上运行的图形界面。如果觉得太难看,可以试试“ddd”。与 xxgdb 相比,它的主要优势在于它允许您以图形方式查看指针、链表和其他复杂数据结构的内容。它可能没有安装在您的系统上,因此您需要从网络上下载它。

如果您在 SunOs 或 Solaris 环境中运行,则有一个名为“gcore”的程序,它允许获取正在运行的进程的核心,而无需停止它。如果进程在无限循环中运行,并且您想将核心文件放在一边,或者您想调试正在运行的进程而不中断它太长时间,这很有用。

http://cn-sec.com/archives/707818.html

https://web.archive.org/web/20071017044810/http://users.actcom.co.il/~choo/lupg/tutorials/debugging/debugging-with-gdb.html#debugger_core

posted @ 2022-09-06 14:41  tlblog  阅读(109)  评论(0编辑  收藏  举报