Skip to content

Instantly share code, notes, and snippets.

@ricksladkey
Last active April 25, 2024 12:27
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ricksladkey/bdcd761a5b06e3d670728d8cc96458ba to your computer and use it in GitHub Desktop.
Save ricksladkey/bdcd761a5b06e3d670728d8cc96458ba to your computer and use it in GitHub Desktop.
Example Python script for GDB that reads and displays the text in a ring buffer every time the program stops
from __future__ import print_function
import struct
import gdb
def log():
# Get the inferior.
try:
inferior = gdb.selected_inferior()
except RuntimeError:
return
if not inferior or not inferior.is_valid():
return
# Look up the 'crash_log' symbol.
crash_log_symbol, _ = gdb.lookup_symbol('crash_log')
if not crash_log_symbol:
return
# Dereference the pointer to the crash log.
crash_log = crash_log_symbol.value().dereference()
if crash_log.address == 0:
return
# Check whether there is any new data in the ring buffer.
read = crash_log['read']
write = crash_log['write']
if read == write:
return
# Calculate the relative positions of the new log data.
data = crash_log['data']
mask = crash_log['mask']
length = write - read
size = mask + 1
read_index = read & mask
write_index = write & mask
# Sanity check length.
if length > 16 * 1024:
return
# Read the log data from the inferior.
if write_index <= read_index:
tail_bytes = inferior.read_memory(data + read_index, size - read_index)
head_bytes = inferior.read_memory(data, write_index)
bytes = tail_bytes + head_bytes
else:
bytes = inferior.read_memory(data + read_index, length)
bytes = str(bytes)
# Write the log data back to the user.
bytes = ''.join(['log: ' + line + '\n' for line in bytes.splitlines()])
gdb.write(bytes)
# Update the read pointer to consume the data.
inferior.write_memory(crash_log['read'].address, struct.pack("=I", write), 4)
def log_hook(event):
log()
class Log(gdb.Command):
"""
Inline logging for embeddeded programs
Reads data from an in-memory ring buffer and displays
it to the user. See ring_buffer.c and crash.c for
details.
Use 'log hook' and 'log unhook' to hook the stop event.
"""
def __init__(self):
super(Log, self).__init__(
"log",
gdb.COMMAND_SUPPORT,
gdb.COMPLETE_NONE,
True
)
def invoke(self, arg, from_tty):
# Check for hook.
if arg and arg == 'hook':
gdb.events.stop.connect(log_hook)
return
# Check for unhook.
if arg and arg == 'unhook':
gdb.events.stop.disconnect(log_hook)
return
# Validate argument.
if arg:
gdb.write('usage: log [hook|unhook]\n')
return
log()
Log()
@ricksladkey
Copy link
Author

This was designed for embedded development, but it would also work anywhere you might use remote debugging, e.g. when you are using a GDB server to another machine. In that case, the program being debugged cannot simply print to stdout and have it appear inline in the debugger.

@ricksladkey
Copy link
Author

ricksladkey commented Mar 22, 2018

If it isn't clear, here is the corresponding C struct:

typedef struct ring_buffer
{
    char *data;
    volatile uint32_t read;
    volatile uint32_t write;
    uint32_t mask;
} ring_buffer;

where mask is the size of the array pointed to by data, which is a positive power of two.

@ricksladkey
Copy link
Author

Here is the native GDB scripting version (which is extremely slow, like, 1200 baud slow):

define crash_log

set $data = crash_log->data
set $read = crash_log->read
set $write = crash_log->write
set $mask = crash_log->mask
while $read < $write
    printf "%c", $data[$read & $mask]
    set $read = $read + 1
end
set crash_log->read = $write

end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment