Last active
April 25, 2024 12:27
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
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.
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
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.