GDB scheduler locking, function calls and multi-threading
GDB can call functions in the program's address-space. That can be useful in different sitations, for debugging or just informal code testing. Let's say I want to allocate some memory:
(gdb) p/x (char *)malloc(1024) $3 = 0x60c960
I can do what ever I want with this buffer:
(gdb) set $b = $1 (gdb) set $b[0] = 48 (gdb) set $b[1] = 120 (gdb) set $b[2] = 57 (gdb) set $b[3] = 55 (gdb) set $b[4] = 50 (gdb) set $b[5] = 0 (gdb) p $b $3 = 0x60c960 "0x972" (gdb) call puts($b) 0x972
... but that's not the point of this post. What I wanted to highlight here is want happens in a multithreaded execution, if I do something similar:
(gdb) break 350 (gdb) run [... many threads are created here ...] [Switching to Thread ... (LWP 20645)] Breakpoint 1, .... at ...:350 (gdb) p malloc(1024) [Switching to Thread ... (LWP 20592)] Breakpoint 1, .... at ...:350 The program stopped in another thread while making a function call from GDB. Evaluation of the expression containing the function (malloc) will be abandoned. When the function is done executing, GDB will silently stop.
The problem here is that another thread hits the breakpoint during the execution of malloc. This is the default behavior of GDB, and it may be surprising when you're not used to it ...
But in addition to being suprising, it is problematic because the return value of malloc is lost! And the same can happend with next, step, finish, etc.
So if you want to force a single-thread behavior (that is, everytime you're sure that there should't be any deadlock during the function call), set GDB's scheduler-locking.
With the scheduler-locking enabled, GDB only let the current thread run. The execution of the other one is blocked in Linux scheduler.
(In practise, this is implemented in ptrace API, to which GDB either passes the process PID or only the thread TID.)
(gdb) set scheduler-locking on (gdb) p malloc(1024) ... (gdb) set scheduler-locking off
Don't forget to turn it back off again afterwards, or your thread will certainly block soon because of someone else's mutex!
In Python, I have a simple wrapper that does that for me:
def set_parameter(name, value): class temp: def __init__(self): self.old = None def __enter__(self): self.old = gdb.parameter(name) if self.old is True: self.old = "on" if self.old is False: self.old = "off" # otherwise don't change gdb.execute("set {} {}".format(name, value), to_string=True) def __exit__(self, type, value, traceback): gdb.execute("set {} {}".format(name, self.old), to_string=True) return temp()
I use it this way:
with set_parameter("scheduler-locking", True): buffer = gdb.parse_and_eval("malloc")(1024)
David Bluecame :
Hello,
Excelent piece of advice, it solved my problem!!! :-) I was pulling my hair out until I found your post. Thank you VERY much and happy new year 2018!!!
Lemy Danger :
Had the same problem, thanks as well.