GDB Tips for C Code

These tips are demonstrated on the starter code for Lab 5, but could apply to any C code you may be debugging.

I've just added a single call to requestMoreSpace in mm_malloc:

// Implement mm_malloc.  You can change or remove any of the above
// code.  It is included as a suggestion of where to start.
// You will want to replace this return statement...
requestMoreSpace(size);
# Launch driver inside gdb, in Verbose mode, with file
$ gdb --args ./mdriver -V -f traces/short1-bal.rep
# Create some breakpoints, I suspect that the problem may be in removeFreeBlock
# Note that if you are getting a segmentation fault, you could also just wait for the segfault and then just debug from there.
(gdb) break mm.c:382
(gdb) break removeFreeBlock
# Start running
(gdb) run

Breakpoint 1, mm_malloc (size=2048) at mm.c:382
382   requestMoreSpace(size);
# From here, you can actually run some C code
# For example, we can run the provided `examine_heap()` helper function.
(gdb) p examine_heap()
FREE_LIST_HEAD: 0x7ffff661a018
0x7ffff661a018: 32 2 0  FREE    next: (nil), prev: (nil)
END OF HEAP

$1 = void
# Cool, huh? That shows us the initial heap, before we've done anything.
# Now continue executing to next breakpoint.
(gdb) continue
Continuing.

Breakpoint 2, removeFreeBlock (freeBlock=0x7ffff661a018) at mm.c:187
187   nextFree = freeBlock->next;
# We've hit our breakpoint, now let's see where we're at by exploring some of the variables.
# Let's see what `freeBlock` is. We can just print the pointer:
(gdb) print freeBlock
$4 = (BlockInfo *) 0x7ffff661a018
# But that's not very interesting. Better: we can *dereference* it!
(gdb) print *freeBlock
$5 = {sizeAndTags = 34, next = 0x0, prev = 0x7ffff661a038}
# Now we see that this is the original block we started with. But the size was 32, wasn't it? How do we get the size? Remember that we're treating the lower bits of the size as a bitvector.
# GDB can print values in binary, which makes bitvectors easier to understand. /t tells it to print a value in binary:
(gdb) print /t freeBlock->sizeAndTags
$6 = 100010
# Ah, right, we had a TAG_PRECEDING_USED flag that we set. `TAG_PRECEDING_USED` is a preprocessor macro, so GDB doesn't know about it. But we can check if the tag is set by using the actual value the macro is defined to (2):
(gdb) print freeBlock->sizeAndTags & 2
$7 = 2
# or we can mask off that tag to get the size:
(gdb) print freeBlock->sizeAndTags & ~2
$8 = 32
# We can also examine the whole heap again:
(gdb) p examine_heap()
FREE_LIST_HEAD: 0x7ffff661a038
0x7ffff661a018: 32 2 0  FREE    next: (nil), prev: 0x7ffff661a038
0x7ffff661a038: 4096 0 0    FREE    next: 0x7ffff661a018, prev: (nil)
END OF HEAP

$2 = void
# Aha, we've added another block of 4 kB. Wonder why we're executing removeFreeBlock...
# Let's print the call stack. The `backtrace` command (or `bt`), shows the chain of function calls that led to where we are now, and where each was called from.
(gdb) backtrace
#0  removeFreeBlock (freeBlock=0x7ffff661a018) at mm.c:187
#1  0x0000000000402bca in coalesceFreeBlock (oldBlock=0x7ffff661a038) at mm.c:225
#2  0x0000000000402d63 in requestMoreSpace (reqSize=2048) at mm.c:302
#3  0x0000000000402e8b in mm_malloc (size=2048) at mm.c:382
#4  0x0000000000401dea in eval_mm_valid (trace=0x607080, tracenum=0,
    ranges=0x7fffffffe190) at mdriver.c:567
#5  0x0000000000401244 in main (argc=4, argv=0x7fffffffe318) at mdriver.c:266

# Currently, our context is in #0, or in removeFreeBlock, so we can examine any variables in scope in removeFreeBlock.
# But we can also go and check out what's going on further up in the call stack. The `up` command changes our context so that we're *up* a stack frame. This just changes what variables GDB sees, it doesn't change the actual execution (it doesn't call `return` or anything)
(gdb) up
#1  0x0000000000402bca in coalesceFreeBlock (oldBlock=0x7ffff661a038) at mm.c:225
225     removeFreeBlock(freeBlock);
# So, now we're "at" the next level up in the stack, where coalesceFreeBlock is calling removeFreeBlock.
# Now we can print things like the value of `blockCursor` and `size` which were used to compute the `freeBlock` pointer that was passed to the `removeFreeBlock`, and try to figure out what's wrong.
(gdb) print blockCursor
$9 = (BlockInfo *) 0x7ffff661a038
(gdb) print size
$10 = 32
(gdb) print *blockCursor
$11 = {sizeAndTags = 4096, next = 0x7ffff661a018, prev = 0x0}

Hopefully this helps you when you're debugging C code in the future, especially in Lab 5!