Track the total memory allocated (malloc) and freed (free). Capture backtraces of all malloc and free calls (for now, logging can be ignored). Current Approach For malloc:
I need every malloc call to complete execution. After completion, I retrieve $rax and use malloc_usable_size($rax) to determine the actual allocated memory. For free:
This is simpler since malloc_usable_size($rdi) gives the size being freed. Manually continuing execution from the GDB prompt works fine. However, my debugging case involves thousands of malloc and free calls, so I want this to run automatically without user intervention.
When I add continue inside hookpost-myfinish, I observe skipped malloc breakpoints. As a result, malloc_count, free_count, total_malloced, and total_freed become incorrect. I suspect this happens when consecutive malloc calls occur: finish from malloc returns execution to main(), and another continue causes the next malloc entry breakpoint to be skipped. Reproduction Steps Using gdb_commands.txt → Works correctly (manual continuation required). Using gdb_commands_continue.txt → Causes alternate malloc calls to be skipped, leading to incorrect tracking. This seems to be due to how continue interacts with finish, but I’m unsure of the best way to ensure every malloc and free call is properly accounted for without skipping breakpoints.
Could someone help me understand why continue is skipping alternate malloc calls and suggest a reliable way to handle this?
#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;
}
Manually pressing continue works
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
malloc calls gets skipped with 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
Would appreciate any insights or alternative approaches!
Track the total memory allocated (malloc) and freed (free). Capture backtraces of all malloc and free calls (for now, logging can be ignored). Current Approach For malloc:
I need every malloc call to complete execution. After completion, I retrieve $rax and use malloc_usable_size($rax) to determine the actual allocated memory. For free:
This is simpler since malloc_usable_size($rdi) gives the size being freed. Manually continuing execution from the GDB prompt works fine. However, my debugging case involves thousands of malloc and free calls, so I want this to run automatically without user intervention.
When I add continue inside hookpost-myfinish, I observe skipped malloc breakpoints. As a result, malloc_count, free_count, total_malloced, and total_freed become incorrect. I suspect this happens when consecutive malloc calls occur: finish from malloc returns execution to main(), and another continue causes the next malloc entry breakpoint to be skipped. Reproduction Steps Using gdb_commands.txt → Works correctly (manual continuation required). Using gdb_commands_continue.txt → Causes alternate malloc calls to be skipped, leading to incorrect tracking. This seems to be due to how continue interacts with finish, but I’m unsure of the best way to ensure every malloc and free call is properly accounted for without skipping breakpoints.
Could someone help me understand why continue is skipping alternate malloc calls and suggest a reliable way to handle this?
#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;
}
Manually pressing continue works
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
malloc calls gets skipped with 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
Would appreciate any insights or alternative approaches!
It appears that in GDB the finish
command's default behavior is to stop execution after returning from the breakpointed function, so GDB stops and shows the command prompt.
To circumvent this behavior you could try using the GDB Python API. The API gives the GDB user more flexibility and control as compared to command lists. i changed your commands file for the malloc()
breakpoint and finish processing while the free()
breakpoint commands list was left as is:
set unwindonsignal off
#set trace-commands on
set pagination off
set $fc = 0
set $total_freed = 0
b gdb-testing.c:8
b free
enable 1 2
commands 1
silent
continue
end
commands 2
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)
printf "Total freed %d bytes \n", $total_freed
end
continue
end
python
import gdb
class MallocBreakpoint(gdb.Breakpoint):
def __init__(self):
super(MallocBreakpoint,self).__init__("malloc")
self.silent=True
def stop(self):
frame = gdb.selected_frame()
reqsz=gdb.parse_and_eval(f"$rdi")
print (frame.name())
print(f"Asked to malloc {reqsz} bytes")
MallocFinishBreakpoint(frame)
return False # True will stop at breakpoint
class MallocFinishBreakpoint(gdb.FinishBreakpoint):
tot_malloced = 0
def __init__(self,frame):
super(MallocFinishBreakpoint,self).__init__(frame,internal=True)
self.silent=True
def stop(self):
buffer_address = self.return_value
print(f"malloc() returned buffer address: {hex(buffer_address)}")
result = gdb.execute("call malloc_usable_size({})".format(buffer_address), to_string=True)
# Extract the result (it’s returned as a string like "$4 = 24")
size = int(result.split("=")[1].strip())
MallocFinishBreakpoint.tot_malloced = MallocFinishBreakpoint.tot_malloced + size
# Print the result
print("Actual malloced: {} bytes".format(size))
print("and total malloced till now is: {}". format(MallocFinishBreakpoint.tot_malloced))
return False # True will stop at breakpoint
MallocBreakpoint()
end
If there are any glibc internal calls to malloc()
you will have to account for that as well in the output.
i ran your code without the printf
's and was able to see the allocated and freed bytes were matching:
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
__GI___libc_malloc
Asked to malloc 32 bytes
malloc() returned buffer address: 0x5555555592a0
Actual malloced: 40 bytes
and total malloced till now is: 40
Breakpoint 2, __GI___libc_free (mem=0x5555555592a0) at ./malloc/malloc.c:3352
3352 ./malloc/malloc.c: No such file or directory.
Freed 40 bytes
Total freed 40 bytes
...
...
__GI___libc_malloc
Asked to malloc 256 bytes
malloc() returned buffer address: 0x555555559ee0
Actual malloced: 264 bytes
and total malloced till now is: 3424
...
...
Breakpoint 2, __GI___libc_free (mem=0x555555559ee0) at ./malloc/malloc.c:3352
3352 in ./malloc/malloc.c
Freed 264 bytes
Total freed 3424 bytes
[Inferior 1 (process 136) exited normally]
Relevant GDB documentation: https://sourceware./gdb/current/onlinedocs/gdb.html/Breakpoints-In-Python.html#Breakpoints-In-Python https://sourceware./gdb/current/onlinedocs/gdb.html/Finish-Breakpoints-in-Python.html#Finish-Breakpoints-in-Python
There is probably more than one way to do the breakpoint/finish processing using the Python API. One answer that you might want to look at: https://stackoverflow/a/42607055/11001972
finish
at all? You could just doset $total_malloced = $total_malloced + $mallocsize
inside thecommands 2
block. – ssbssa Commented Mar 6 at 6:58$mallocsize = N
(size_t)malloc_usable_size($rax) = N+x
total_freed = N+x
Hence if you keep adding the $mallocsize it won't give the actual memory allocated, eventually leading to mismatch with total memory freed . – Puspaul Halder Commented Mar 6 at 7:33malloc_usable_size
in free, it would be better to keep track of allocations and their size. Your current approach will also give you wrong results for double-free. If you really want to implement your project in gdb, I suggest to look into the python API. This way you can collect your data in maps (dicts). – Joachim Commented Mar 7 at 7:31