I recently added a function of our code whose purpose was to concatenate the contents of two memory regions. The basic scheme was:
- Ensure destination region is large enough
- Work out number of bytes that need copying (
len
) - Work out
offset
to copy bytes to - Do
memcpy(dest + offset, src, len);
It being early on a Monday and the coffee not yet having kicked in I erroneously determined that offset
and len
were the same quantity and ended up typing something like:
memcpy(dest + offset, src, offset);
This is broken as offset
may be much larger than the number of extra bytes reserved at the end of dest
. This defect manifested as seemingly unrelated failures a long time after the broken copy had taken place. These failures were due to corruption of data stored on the heap. I was able to obtain a recording. My normal work flow for debugging a recording of a session that terminated with an assertion failure (as this was) is something like:
ugo end
backtrace
to get an impression of where I amreverse-finish
to the failing assertion- Navigate around a little with
next
/reverse-next
In this case I identified the memory region that was corrupted and set a watchpoint on it. A reverse-continue
led me to the innards of a memset
. At this point I decided the likely cause was some sort of heap allocation issue. I decided to try a debug build, which incorporates extensive heap checking. This build failed much sooner with a heap integrity check. Once again I obtained a recording. I identified the corrupted field in the heap block header, set a watchpoint and did a reverse-continue
. This took me to my new function and it was rapidly apparent what my mistake was.