You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

GDB中使用finish和continue时malloc断点被跳过的问题排查

GDB自动continue导致malloc断点被跳过,内存统计数据错误

需求

跟踪已分配(malloc)和释放(free)的总内存,捕获所有malloc与free调用的回溯(暂不考虑日志)。

当前实现

  • malloc处理:等待每个malloc调用执行完成,通过$rax寄存器结合malloc_usable_size($rax)获取实际分配内存大小。
  • free处理:通过malloc_usable_size($rdi)直接获取释放的内存大小。
    手动在GDB提示符输入continue可正常统计,但调试场景包含数千次malloc/free调用,需实现无人干预的自动执行。

问题现象

在hookpost-myfinish中添加continue后,出现malloc断点被跳过的情况,导致malloc_count、free_count、total_malloced、total_freed统计数据错误。推测该问题发生在连续malloc调用场景:从malloc执行finish返回至main()后,continue操作跳过了下一个malloc入口断点。

复现步骤

  • 使用gdb_commands.txt:手动输入continue可正常统计。
  • 使用gdb_commands_continue.txt:交替的malloc调用被跳过,统计结果错误。

相关代码

code.c

#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>  // Required for malloc_usable_size

int main() {
    printf("Starting memory allocation test...\n");

    // Allocate memory blocks of different sizes
    void *ptr1 = malloc(32);
    printf("ptr1 allocated at address: %p\n", ptr1);
 
    free(ptr1);
    printf("ptr1 freed");
    void *ptr2 = malloc(64);
    printf("ptr2 allocated at address: %p\n", ptr2);
    printf("ptr2 allocated at address: %p\n", ptr2);
    printf("ptr2 allocated at address: %p\n", ptr2);
    printf("ptr2 allocated at address: %p\n", ptr2);
    printf("ptr2 allocated at address: %p\n", ptr2);
    printf("ptr2 allocated at address: %p\n", ptr2);
    printf("ptr2 allocated at address: %p\n", ptr2);
    printf("ptr2 allocated at address: %p\n", ptr2);
    printf("ptr2 allocated at address: %p\n", ptr2);
    printf("ptr2 allocated at address: %p\n", ptr2);
    printf("ptr2 allocated at address: %p\n", ptr2);
    printf("ptr2 allocated at address: %p\n", ptr2);

    void *ptr3 = malloc(128);
    printf("ptr3 allocated at address: %p\n", ptr3);

    free(ptr2);
    free(ptr3);

    void *ptr4 = malloc(128);
    printf("ptr4 allocated at address: %p\n", ptr4);

    void *ptr5 = malloc(128);
    printf("ptr5 allocated at address: %p\n", ptr5);

    void *ptr6 = malloc(256);
    printf("ptr6 allocated at address: %p\n", ptr6);
    void *ptr7 = malloc(256);
    void *ptr8 = malloc(256);
    void *ptr9 = malloc(256);
    void *ptr10 = malloc(256);
    void *ptr11 = malloc(256);
    void *ptr12 = malloc(256);
    void *ptr13 = malloc(256);
    void *ptr14 = malloc(256);
    void *ptr15 = malloc(256);
    void *ptr16 = malloc(256);

    free(ptr5);
    free(ptr4);
    free(ptr6);
    free(ptr7);
    free(ptr8);
    free(ptr9);
    free(ptr10);
    free(ptr11);
    free(ptr12);
    free(ptr13);
    free(ptr14);
    free(ptr15);
    free(ptr16);
    
    printf("Memory allocation test completed.\n");
    return 0;
}

gdb_commands.txt(手动continue正常)

set unwindonsignal off
set $mc = 0 
set $fc = 0
set $mallocsize = 0
set $in_malloc = 0
set $total_malloced = 0
set $total_freed = 0
b memory_test.c:8
b malloc
b free
disable 2 3

commands 1
    silent
    enable 2 3
    continue
end

commands 2
  set $mc = $mc + 1
  set $mallocsize = $rdi
  printf "Asked to malloc %d bytes\n", $mallocsize
  set $in_malloc = 1
  myfinish
end

commands 3
  set $fc = $fc + 1
  if ($rdi)
    set $total_freed = $total_freed + (size_t)malloc_usable_size($rdi)
    printf "Freed %d bytes \n ", (size_t)malloc_usable_size($rdi)
  end
  continue
end

define myfinish
    finish
end

define hookpost-myfinish
    set $total_malloced = $total_malloced + (size_t)malloc_usable_size($rax)
    printf "Actually malloced %d bytes and total malloced till now is %d \n", (size_t)malloc_usable_size($rax), $total_malloced
    set $in_malloc = 0
end

gdb_commands_continue.txt(自动continue导致断点跳过)

set unwindonsignal off
set $mc = 0 
set $fc = 0
set $mallocsize = 0
set $in_malloc = 0
set $total_malloced = 0
set $total_freed = 0
b memory_test.c:8
b malloc
b free
disable 2 3

commands 1
    silent
    enable 2 3
    continue
end

commands 2
  set $mc = $mc + 1
  set $mallocsize = $rdi
  printf "Asked to malloc %d bytes\n", $mallocsize
  set $in_malloc = 1
  myfinish
end

commands 3
  set $fc = $fc + 1
  if ($rdi)
    set $total_freed = $total_freed + (size_t)malloc_usable_size($rdi)
    printf "Freed %d bytes \n ", (size_t)malloc_usable_size($rdi)
  end
  continue
end

define myfinish
    finish
end

define hookpost-myfinish
    set $total_malloced = $total_malloced + (size_t)malloc_usable_size($rax)
    printf "Actually malloced %d bytes and total malloced till now is %d \n", (size_t)malloc_usable_size($rax), $total_malloced
    set $in_malloc = 0
    continue
end

原因分析

问题出在finish命令与hookpost-myfinish中continue的交互逻辑:

  1. finish命令会让程序运行到malloc函数返回后暂停,此时GDB处于断点暂停状态。
  2. 在hookpost-myfinish中执行continue,GDB会直接从当前暂停点继续运行程序。但对于连续的malloc调用,GDB的断点触发机制可能因为刚完成finish的上下文处理,没有及时重新监控后续的malloc入口断点,导致跳过下一次malloc的断点触发。
  3. 手动输入continue时,GDB处于正常的用户交互暂停状态,所有断点监控逻辑已经正确初始化,因此能正常捕获所有malloc断点。

解决方案

放弃依赖finish和hookpost-myfinish的组合,改为在malloc入口断点处手动设置临时返回地址断点,确保每次malloc返回时都能触发统计逻辑,且自动continue不会跳过后续断点。修改后的GDB命令脚本如下:

修正后的gdb_commands_auto.txt

set unwindonsignal off
set $mc = 0 
set $fc = 0
set $mallocsize = 0
set $in_malloc = 0
set $total_malloced = 0
set $total_freed = 0
b memory_test.c:8
b malloc
b free
disable 2 3

commands 1
    silent
    enable 2 3
    continue
end

commands 2
  set $mc = $mc + 1
  set $mallocsize = $rdi
  printf "Asked to malloc %d bytes\n", $mallocsize
  set $in_malloc = 1
  # 获取malloc的返回地址(x86_64架构下,返回地址存在栈顶)
  set $ret_addr = *(void**)$rsp
  # 设置临时断点在返回地址,触发后自动删除
  break *$ret_addr
  commands
    silent
    # 统计实际分配内存
    set $total_malloced = $total_malloced + (size_t)malloc_usable_size($rax)
    printf "Actually malloced %d bytes and total malloced till now is %d \n", (size_t)malloc_usable_size($rax), $total_malloced
    set $in_malloc = 0
    # 删除当前临时断点($bpnum是刚创建的断点编号)
    delete $bpnum
    # 继续执行
    continue
  end
  # 从malloc入口继续执行到返回地址断点
  continue
end

commands 3
  set $fc = $fc + 1
  if ($rdi)
    set $total_freed = $total_freed + (size_t)malloc_usable_size($rdi)
    printf "Freed %d bytes \n ", (size_t)malloc_usable_size($rdi)
  end
  continue
end

方案说明

  1. 每次触发malloc入口断点时,获取栈顶的返回地址(x86_64架构下,函数调用后返回地址会被压入栈顶)。
  2. 在返回地址处设置临时断点,并绑定统计逻辑:触发时计算实际分配内存、更新统计值,然后删除临时断点并continue。
  3. 这种方式避免了finish命令的上下文问题,确保每个malloc的返回和后续的malloc调用都能被正确捕获,实现完全自动的内存统计。

内容的提问来源于stack exchange,提问作者Puspaul Halder

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 23:30:54