One of the neat features of the GNU Debugger is the support for running python scripts in the GDB environment. This is helpful for logging and automating debug sessions.
In the gdb environment we run pi
to launch an interactive python session.
(gdb) pi
>>> gdb.breakpoints()
(<gdb.Breakpoint object at 0x7f78b0c8e418>,)
>>> gdb.execute("info inferiors")
Num Description Executable
* 1 process 3850 /home/hema/C_progs/a.out
>>> gdb.execute("info shared")
From To Syms Read Shared Object Library
0x00007ffff7ddab00 0x00007ffff7df55b0 Yes /lib64/ld-linux-x86-64.so.2
0x00007ffff7bc19f0 0x00007ffff7bce471 Yes /lib/x86_64-linux-gnu/libpthread.so.0
0x00007ffff7812520 0x00007ffff795ae03 Yes /lib/x86_64-linux-gnu/libc.so.6
>>> gdb.solib_name(0x00007ffff78125ff)
'/lib/x86_64-linux-gnu/libc.so.6'
You can read more about it on their online docs. Python-GDB API
Now before diving into the example, I would like to mention about GDB’s powerful utility to set thread-specific breakpoints, which means you can choose which thread is allowed to hit a breakpoint. The syntax is simple
(gdb) b <breakpoint_location> thread <thread_number>
Now let’s consider a simple multi threaded program.
#include <stdio.h>
#include <pthread.h>
int thread_echo(int thread_num)
{
//Nothing to do here
return 100;
}
void* thread_func(void* num)
{
int *echo_arg;
echo_arg=(int*)num;
while(1)
thread_echo(*echo_arg);
}
int main()
{
pthread_t threads[5];
int i,arg[5];
for (i=0;i<5;i++)
{
arg[i]=i;
pthread_create(&threads[i],NULL,thread_func,&arg[i]);
}
pthread_join(threads[0],NULL);
return 0;
}
Now suppose we would like to log the entries to thread_echo
function in a thread-specific manner. We can do this by setting a breakpoint at the function thread-wise and collect the arguments and return value whenever it is hit via a python script.
GDB provides a general event facility so that Python code can be notified of various state changes, particularly changes that occur in the inferior.
In order to be notified of an event, you must register an event handler with an event registry. A stop event indicates that the inferior has stopped. A stop event handler is used to provide the user an option to stop the logging and change the thread selection. Here I have tested this script on a ARM v7 based board
try:
import gdb
except ImportError as e:
raise ImportError("This script must be run in GDB: ", str(e))
logFileName = "./multithread_log.txt"
flag = 0
def stop_handler (event):
global flag
print("thread %s" %(event.inferior_thread))
if isinstance(event,gdb.SignalEvent):
print("event stop signal %s" %(event.stop_signal))
if event.stop_signal == 'SIGINT':
print("process got interrupted")
resp = raw_input("continue logging ? (y/n)")
if resp == 'n':
flag = 0
else:
set_breakpoints()
gdb.execute("c")
gdb.events.stop.connect (stop_handler)
class ConsoleColorCodes:
RED = '\033[91m'
BLUE = '\033[94m'
YELLOW = '\033[93m'
END = '\033[0m'
class Utility:
@staticmethod
def writeColorMessage(message, colorCode):
print(colorCode + message + ConsoleColorCodes.END)
@staticmethod
def writeMessage(message):
Utility.writeColorMessage(message, ConsoleColorCodes.BLUE)
@staticmethod
def writeErrorMessage(message):
Utility.writeColorMessage(message, ConsoleColorCodes.RED)
@staticmethod
def logInfoMessage(message):
global logFileName
with open(str(logFileName), 'a') as logFile:
logFile.write(str(message))
@staticmethod
def writeInfoMessage(message):
Utility.logInfoMessage(message)
Utility.writeColorMessage(message, ConsoleColorCodes.RED)
@staticmethod
def convertToHexString(val):
return hex(int(val))
Functions = ["thread_echo"]
thread_echo_Args = ["thread number"]
max_callstack_depth = 16
def get_callstack():
ret = []
depth = 1
frame = gdb.selected_frame()
while True:
if (frame) and ( depth <= max_callstack_depth ):
current_frame_name = str(frame.name())
ret.append(current_frame_name)
frame = frame.older()
depth += 1
else:
gdb.Frame.select ( gdb.newest_frame() )
return ret
def get_return_value():
ret = ""
gdb.execute("finish")
ret = str(gdb.parse_and_eval("$r0"))
return ret
def get_argument (counter):
index = counter - 1
return str(gdb.parse_and_eval("$r" + str(index)))
def append_callstack(target, callstack):
ret = str(target) + "\ncallstack : "
for callstack_frame in callstack:
ret += "\n\t"
ret += str(callstack_frame)
return ret
def set_breakpoints():
global flag
gdb.execute("info threads")
print("choose thread to log")
thread_num = raw_input()
if thread_num == 'q':
print("stop log")
flag = 0
return
# Remove breakpoints
gdb.execute("delete breakpoints")
# Setup breakpoints for memory functions
for Function in Functions:
gdb.execute("b " + Function + " thread " + thread_num )
print("breakpoints set")
gdb.execute("set confirm off")
gdb.execute("set pagination off")
gdb.execute("set breakpoint pending on")
backtrace_limit_command = "set backtrace limit " + str(max_callstack_depth)
gdb.execute(backtrace_limit_command)
flag = 1
gdb.execute("b main")
gdb.execute("r")
set_breakpoints()
gdb.execute("i b")
gdb.execute("c")
while flag == 1:
print("entered while loop")
try:
frame = gdb.selected_frame()
except:
set_breakpoints()
gdb.execute("c")
print("current frame: " + str(frame.name()))
current_frame_name = str(frame.name())
##########################################################################
#MAIN
if "main" in current_frame_name:
gdb.execute("cont")
##########################################################################
#EXIT
if "exit" in current_frame_name:
Utility.writeInfoMessage("\n")
break
##########################################################################
if "thread_echo" in current_frame_name:
message = "\ntype : thread_echo ,"
counter = 1
for arg in thread_echo_Args:
message += " arg" + str(counter) + " : "
message += get_argument(counter)
message += ","
counter += 1
callstack = get_callstack()
address = Utility.convertToHexString(get_return_value())
message += " return value : " + address
message += ","
# append callstack at the end
message = append_callstack(message, callstack)
# Continue
Utility.writeInfoMessage(message)
gdb.execute("cont")
Utility.writeMessage('DEBUGEE EXITING')
Here is a sample session.
Breakpoint 3, 0x0001050c in thread_echo ()
thread None
entered while loop
current frame: thread_echo
0x00010558 in thread_func ()
thread None
type : thread_echo , arg1 : 1, return value : 0x64,
callstack :
thread_echo
thread_func
start_thread
None
Breakpoint 3, 0x0001050c in thread_echo ()
thread None
entered while loop
current frame: thread_echo
0x00010558 in thread_func ()
thread None
type : thread_echo , arg1 : 1, return value : 0x64,
callstack :
thread_echo
thread_func
start_thread
None
^C
Program received signal SIGINT, Interrupt.
[Switching to Thread 0x75e3d460 (LWP 1367)]
0x0001050c in thread_echo ()
event stop signal SIGINT
process got interrupted
continue logging ? (y/n)y
Id Target Id Frame
6 Thread 0x74e3d460 (LWP 1370) "a.out" 0x0001050c in thread_echo ()
5 Thread 0x7563d460 (LWP 1368) "a.out" 0x00010510 in thread_echo ()
* 4 Thread 0x75e3d460 (LWP 1367) "a.out" 0x0001050c in thread_echo ()
3 Thread 0x7663d460 (LWP 1366) "a.out" 0x0001050c in thread_echo ()
2 Thread 0x76e3d460 (LWP 1364) "a.out" 0x0001050c in thread_echo ()
1 Thread 0x76ff7000 (LWP 1361) "a.out" 0x76f87274 in pthread_join (threadid=<optimized out>, thread_return=0x0) at pthread_join.c:92
choose thread to log
5
Breakpoint 4 at 0x1050c
breakpoints set
[Switching to Thread 0x7563d460 (LWP 1368)]
Breakpoint 4, 0x0001050c in thread_echo ()
thread None
entered while loop
current frame: thread_echo
0x00010558 in thread_func ()
thread None
type : thread_echo , arg1 : 3, return value : 0x64,
callstack :
thread_echo
thread_func
start_thread
None
Breakpoint 4, 0x0001050c in thread_echo ()
thread None
entered while loop
current frame: thread_echo
0x00010558 in thread_func ()
thread None
type : thread_echo , arg1 : 3, return value : 0x64,
callstack :
thread_echo
thread_func
start_thread
None
And finally the multithread_log.txt file
type : thread_echo , arg1 : 1, return value : 0x64,
callstack :
thread_echo
thread_func
start_thread
None
type : thread_echo , arg1 : 1, return value : 0x64,
callstack :
thread_echo
thread_func
start_thread
None
type : thread_echo , arg1 : 3, return value : 0x64,
callstack :
thread_echo
thread_func
start_thread
None
type : thread_echo , arg1 : 3, return value : 0x64,
callstack :
thread_echo
thread_func
start_thread
None