乐于分享
好东西不私藏

ARM架构调试必备:用frame命令追溯函数调用栈中的错误根源

本文最后更新于2026-03-16,某些文章具有时效性,若有错误或已失效,请在下方留言或联系老夜

ARM架构调试必备:用frame命令追溯函数调用栈中的错误根源

大家好,我是一个工作20多年的老程序员,把自己工作中积累的经验慢慢给大家分享。

-begin-

今日命令:ARM架构gdb中的frame(简写f)——栈帧切换与上下文分析

命令作用frame命令用于在函数调用栈的不同栈帧间切换,查看崩溃时各个函数的局部变量、参数值及代码位置。在ARM架构中,栈帧由fp(帧指针,r11)和sp(栈指针,r13)界定,每个栈帧对应一次函数调用,存储着函数的参数、局部变量和返回地址。通过切换栈帧,可逐级追溯错误参数的传递路径,定位问题根源。

适用场景

追踪函数参数的传递过程(如上游函数传入的指针是否有效)。
查看多层调用中局部变量的异常值(如数组越界导致的栈数据污染)。
确认函数返回地址是否被篡改(如缓冲区溢出攻击导致的栈破坏)。

详细使用步骤(ARM程序案例)

1. 调试背景

某ARM Cortex-A53处理器的网关程序崩溃,coredump显示SIGSEGV,崩溃在parse_packet函数。程序为gateway_app(编译命令:aarch64-linux-gnu-gcc -g -o gateway_app gateway.c),coredump文件为core.seg

2. 初步定位调用栈

加载coredump后,用bt(backtrace)查看完整调用栈:

(gdb) bt

#0  0x0000aaaab0018c0 in parse_packet (buf=0xfffffffffffffff0, len=1024) at gateway.c:512

#1  0x0000aaaab0021d4 in process_client (sock=5) at gateway.c:728

#2  0x0000aaaab0027f0 in main_loop () at gateway.c:896

#3  0x0000aaaab0029e8 in main (argc=1, argv=0xffffe5d0a248) at gateway.c:980

调用链为main -> main_loop -> process_client -> parse_packet,崩溃在最底层的parse_packet

3. 用frame命令切换栈帧分析

(1)查看崩溃函数(#0栈帧)

(gdb) frame 0  # 切换到崩溃的parse_packet函数栈帧

#0  0x0000aaaab0018c0 in parse_packet (buf=0xfffffffffffffff0, len=1024) at gateway.c:512

512         if (buf[0] == 0x01) {  // 崩溃行:访问buf[0]

(gdb) p buf

$1 = (unsigned char *) 0xfffffffffffffff0  # 指针值异常,接近64位系统的地址上限

(gdb) info locals  # 查看局部变量

checksum = 0

header = 0x0  # 未初始化的局部指针

buf指针为0xfffffffffffffff0(无效地址),访问其首字节导致段错误,但需确认该指针来自哪个上游函数。

(2)切换到调用者栈帧(#1栈帧)

(gdb) frame 1  # 切换到process_client函数栈帧

#1  0x0000aaaab0021d4 in process_client (sock=5) at gateway.c:728

728         parse_packet(packet_buf, recv_len);

(gdb) list  # 查看调用处代码

723     ssize_t recv_len = recv(sock, packet_buf, 2048, 0);

724     if (recv_len <= 0) {

725         log_error(“recv failed”);

726         return -1;

727     }

728     parse_packet(packet_buf, recv_len);

729     return 0;

(gdb) p packet_buf  # 查看传给parse_packet的buf来源

$2 = (unsigned char *) 0xfffffffffffffff0  # 与parse_packet的buf值一致,说明在此处已异常

(gdb) p recv_len

$3 = 1024  # 接收长度正常,但buffer地址异常

发现process_client中的packet_buf已为无效地址,需进一步查看其赋值过程。

(3)追溯上游栈帧(#2栈帧)

(gdb) frame 2  # 切换到main_loop函数栈帧

#2  0x0000aaaab0027f0 in main_loop () at gateway.c:896

896         process_client(client_sock);

(gdb) p client_sock

$4 = 5  # 套接字描述符正常

(gdb) list  # 查看client_sock的来源

891     int client_sock = accept(listen_sock, NULL, NULL);

892     if (client_sock < 0) {

893         log_error(“accept failed”);

894         continue;

895     }

896     process_client(client_sock);

client_sockaccept返回,值为5(有效),问题不在套接字,而在process_client内部对packet_buf的分配。

(4)验证栈帧完整性

通过info frame查看当前栈帧的fpsp,确认栈未被破坏:

(gdb) info frame 1  # 查看process_client的栈帧信息

Stack frame at 0xffffe5d0a140:

pc = 0x0000aaaab0021d4 in process_client; saved pc = 0x0000aaaab0027f0

fp = 0xffffe5d0a150

sp = 0xffffe5d0a140

called by frame at 0xffffe5d0a180

source language c.

Arglist at 0xffffe5d0a140, args: sock=5

Locals at 0xffffe5d0a140, Previous frame’s sp is 0xffffe5d0a150

Saved registers:

 fp at 0xffffe5d0a148, lr at 0xffffe5d0a14c

fp0xffffe5d0a150)大于sp0xffffe5d0a140),栈帧边界正常,排除栈溢出导致的地址异常。结合process_client代码,发现packet_buf是局部数组,因之前的memcpy操作越界,覆盖了数组地址(这也是packet_buf变为0xfffffffffffffff0的原因)。

4. 实战价值

参数追溯:通过frame逐级向上切换,可清晰看到错误参数(如无效指针)的传递路径,快速定位是哪个函数生成了异常值。
栈帧校验:结合info frame查看fpsp的差值,可判断栈帧是否被破坏(如差值过大可能是局部数组越界)。
多线程场景:在多线程程序中,先用info threads切换到崩溃线程,再用frame分析该线程的调用栈,避免混淆其他线程的上下文。

注意事项

ARM64(AArch64)架构中,栈帧可能因编译器优化省略fpx29),此时需依赖sp和调试符号定位局部变量,建议编译时加入-fno-omit-frame-pointer保留帧指针。
若栈帧被严重破坏(如返回地址被篡改),bt可能显示不完整的调用栈,此时可用x命令直接查看栈内存(x/20xw $sp),手动寻找可能的返回地址。
-end-

如果文章对你有提升,帮忙点赞,分享,关注。非常感谢

本站文章均为手工撰写未经允许谢绝转载:夜雨聆风 » ARM架构调试必备:用frame命令追溯函数调用栈中的错误根源

猜你喜欢

  • 暂无文章