gdb调试(四)函数调用栈之Backtraces

本文深入解析GDB命令用于查看栈信息和选择帧进行详细分析的方法,包括bt、btn、btfull等命令的使用,以及如何通过framen、fn、f等命令选择并查看特定帧的信息。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


通过产看栈信息,我们可以了解栈内帧的编号或地址,通过选择帧我们可以移动到指定的帧内去产看信息。



1、查看栈信息


产看函数调用栈的几个函数

bt
显示所有的函数调用栈帧的信息,每个帧一行。
bt n
显示栈定的n个帧信息。
bt -n
显示栈底的n个帧信息。

bt full
显示栈中所有帧的完全信息如:函数参数,本地变量
bt full n
用发同上。
bt full -n

例如:

(gdb) bt

#0 get_net_ipaddr (s=5, ifr=0xbfffe7ec, ipaddr=0xbfffe88c "192.168.1.200") at netif.c:67

#1 0x080491e2 in netinf_get (dev=0x8049a60 "eth0", ipaddr=0xbfffe88c "192.168.1.200", isup=0xbfffe844,

netmask=0xbfffe86c "\325I\024", hwaddr=0xbfffe84c "\001") at netif.c:296

#2 0x08048813 in main () at netif.c:34

(gdb) bt full

#0 get_net_ipaddr (s=5, ifr=0xbfffe7ec, ipaddr=0xbfffe88c "192.168.1.200") at netif.c:67

err = 0

#1 0x080491e2 in netinf_get (dev=0x8049a60 "eth0", ipaddr=0xbfffe88c "192.168.1.200", isup=0xbfffe844,

netmask=0xbfffe86c "\325I\024", hwaddr=0xbfffe84c "\001") at netif.c:296

s = 5

err = 0

ifr = {ifr_ifrn = {ifrn_name = "eth0", '\000' <repeats 11 times>}, ifr_ifru = {ifru_addr = {sa_family = 2,

sa_data = "\000\000\300\250\001\310\000\000\000\000\000\000\000"}, ifru_dstaddr = {sa_family = 2,

sa_data = "\000\000\300\250\001\310\000\000\000\000\000\000\000"}, ifru_broadaddr = {sa_family = 2,

ifru_newname = "\002\000\000\000\300\250\001\310\000\000\000\000\000\000\000",

ifru_data = 0x2 <Address 0x2 out of bounds>}}

#2 0x08048813 in main () at netif.c:34

err = 1

isup = 0

ipaddr = "192.168.1.200\000\004\b\270\350\377\277\245\324\025\000\060\340\021\000\273\231\004\b"



2、选择帧


只有查看所选择帧内的信息是合法的,如果要查看指定帧内的信息,首先要移动到指定帧。

frame n
f n
通过帧编号来选择帧,帧编号可以通过bt来查看。

f addr
通过帧地址来选择帧,帧编号可以通过bt来查看。

up n
在栈中向上移动n个帧。即向着最外层移动n个帧。

down n
与 up 反方向移动n个帧。

up-silently n
down-silently n
在栈中移动n个帧,但是不打印信息。



3、查看帧内信息

frame
f
打印帧内函数的信息。

info frame
info f
打印帧的信息。

例如:

(gdb) info frame

Stack level 1, frame at 0xbfffe820: //被选帧的地址

eip = 0x80491e2 in netinf_get (netif.c:296); saved eip 0x8048813

called by frame at 0xbfffe8c0, caller of frame at 0xbfffe7b0 //调用帧和被调用帧的地址

source language c.

Arglist at 0xbfffe818, args: dev=0x8049a60 "eth0", ipaddr=0xbfffe88c "192.168.1.200", isup=0xbfffe844,

netmask=0xbfffe86c "\325I\024", hwaddr=0xbfffe84c "\001" //变量地址

Locals at 0xbfffe818, Previous frame's sp is 0xbfffe820

Saved registers:

ebp at 0xbfffe818, eip at 0xbfffe81c //保存的寄存器值地址

info frame addr
info f addr

打印通过addr指定帧的信息。
例如: info f 0x08048813

info args
打印函数变量的值。

info locals
打印本地变量的信息。

info catch


<think>好的,我现在要解决用户的问题:如何在GDB调试时区分调用中的内部函数与外部库函数。首先,我需要理解用户的需求。用户在调试程序时,调用可能包含自己编写的内部函数和外部库函数(如libc中的函数),用户希望能够在查看调用时区分这两者,便于调试和分析。 接下来,我需要回忆GDB的相关命令和功能。GDB的`backtrace`或`bt`命令用于显示调用,但默认情况下不会区分内部和外部函数。不过,GDB提供了一些选项和设置,可能帮助用户实现区分。 首先想到的是使用`info sharedlibrary`命令,这个命令可以显示当前加载的所有共享库的信息,包括库的地址范围。如果调用中的函数地址落在这些库的地址范围内,就可以判断为外部库函数。但这个方法需要用户手动对比地址,可能不够直观。 接下来,GDB的`bt`命令可以结合参数,比如`bt full`显示局部变量,但不确定是否能区分内外函数。可能需要其他方法。 然后,考虑GDB的Python脚本扩展功能。用户可以编写Python脚本,在调用遍历时,通过检查函数所在的模块是否为应用程序本身或系统库,从而进行区分。这需要一定的脚本编写能力,但灵活性较高。 另外,GDB的`info proc mappings`可以显示进程的内存映射,包括各模块的起始和结束地址。结合`info symbol`命令,可以确定某个地址属于哪个模块。例如,对每个帧的地址使用`info symbol $pc`,然后判断模块路径是否包含系统库路径(如`/usr/lib`或`/lib`),从而区分内外函数。 可能还可以使用`frame`命令逐个查看帧,并检查函数所在的源文件是否存在。内部函数通常有可用的源文件信息,而外部库函数可能没有,或者源文件路径指向系统库的位置。不过,这依赖于调试信息的安装情况,如果外部库没有调试信息,可能无法获取源文件路径。 另外,用户可能希望有一个自动化的方法,在显示调用时自动标记内外函数。这可能需要自定义GDB命令或使用现有的插件。例如,使用`backtrace`命令后,解析每一帧的信息,判断函数是否属于内部模块。 还需要考虑用户的环境,比如是否在调试时有符号信息。如果外部库没有调试符号,可能只能看到函数名,而无法确定来源。但动态链接库的函数通常会有明确的命名,如`printf`来自libc,可以通过名称识别。 总结可能的步骤: 1. 使用`bt`查看调用。 2. 对每个帧使用`info symbol`或`info address`确定所属模块。 3. 检查模块路径是否属于系统库路径。 4. 编写脚本自动化这一过程。 同时,需要参考用户提供的引用内容。引用[1]提到系统库如libc.so.6位于`/usr/lib`,而引用[2]提到内部函数的符号名可能在text段中。这可能帮助判断函数来源。 可能的实现方法: - 在GDB中使用`info shared`列出已加载的共享库,记录它们的地址范围。 - 遍历调用的每个帧,获取其程序计数器(PC)地址。 - 检查该地址是否在系统库的地址范围内,如果是,则为外部函数;否则为内部函数。 或者,利用GDB的Python API编写脚本,自动处理这些步骤并高亮显示内外函数。 最后,需要将这些方法整理成用户能够理解的步骤,并提供具体的命令示例,确保用户能够操作。</think>在GDB调试中区分调用中的内部函数与外部库函数,可以通过以下方法实现: --- ### **1. 使用`backtrace`命令结合符号信息** GDB默认的`bt`命令会显示完整的调用,但需要结合符号信息识别来源: - **查看调用**: ```bash (gdb) bt #0 func_internal (arg=5) at main.c:3 #1 0x00007ffff7e45d0a in printf (format=0x555555556004 "Result: %d\n") at printf.c:28 ``` 若函数显示为`main.c:3`等源代码位置,则为内部函数;若显示为`printf.c:28`或仅显示地址(如`0x00007ffff7e45d0a`)且无源码路径,则可能为外部库函数[^1]。 --- ### **2. 检查函数所属模块** 通过`info symbol`命令判断函数所属模块: ```bash (gdb) info symbol 0x00007ffff7e45d0a printf in section .text of /usr/lib/libc.so.6 ``` - 若输出包含`/usr/lib`或`/lib`等系统库路径,则为外部库函数[^1]。 - 若输出为内部目标文件名(如`main`),则为内部函数。 --- ### **3. 使用`info sharedlibrary`定位外部库** 列出所有已加载的共享库及其内存范围: ```bash (gdb) info sharedlibrary 0x00007ffff7e3d000 0x00007ffff7fdb000 /usr/lib/libc.so.6 ``` 随后对比调用栈地址是否落在这些库的地址范围内。 --- ### **4. Python脚本自动化区分(进阶)** 编写GDB Python脚本,自动标记内外函数: ```python import gdb class InternalExternalBacktrace(gdb.Command): def __init__(self): super().__init__("ieb", gdb.COMMAND_USER) def invoke(self, arg, from_tty): sys_lib_paths = ["/usr/lib", "/lib"] # 系统库路径 frame = gdb.newest_frame() while frame is not None: pc = frame.pc() symtab = frame.find_sal().symtab if symtab: filename = symtab.fullname() if any(path in filename for path in sys_lib_paths): print(f"[External] {frame.name()} ({filename})") else: print(f"[Internal] {frame.name()} ({filename})") else: # 无符号信息时检查内存映射 proc_map = gdb.execute("info proc mappings", to_string=True) if "libc.so.6" in proc_map: # 示例检查 print(f"[External] {frame.name()} (unknown)") else: print(f"[Internal] {frame.name()} (unknown)") frame = frame.older() InternalExternalBacktrace() ``` 加载脚本后使用`ieb`命令,输出会标注内外函数[^2]。 --- ### **5. 结合调试信息与内存映射** 若外部库无调试符号,可通过`info proc mappings`查看内存映射: ```bash (gdb) info proc mappings 0x7ffff7e3d000 0x7ffff7fdb000 0x17e000 0x0 /usr/lib/libc.so.6 ``` 若调用栈地址落在`libc.so.6`等库的范围内,则为外部函数。 --- ### **示例流程** 1. 运行程序并触发断点: ```bash (gdb) b main.c:3 (gdb) run ``` 2. 查看调用: ```bash (gdb) bt #0 internal_func() at main.c:3 #1 0x7ffff7e45d0a in printf () from /usr/lib/libc.so.6 ``` 3. 验证外部函数地址: ```bash (gdb) info symbol 0x7ffff7e45d0a printf in section .text of /usr/lib/libc.so.6 ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值