

Exploring and Dynamically Patching Django/Python Using GDB - ebroder
https://stripe.com/blog/exploring-python-using-gdb

======
robbles
Couldn't you do almost the exact same thing using pdb, without all the C API /
register stuff?

Maybe I misunderstood the post, but it sounds like you're just stopping right
before the request returns, reloading the module, generating a new response,
and allowing execution to continue.

Why is GDB required for any of this?

~~~
ebroder
You're right! You could do all of this with pdb, but only if you have enough
foresight to run the app under pdb to begin with (which I definitely never
do).

Using GDB, you don't have to change the app or remember to run it in a
particular way.

~~~
robbles
Right, I see the use in this now. How difficult would it be to wrap this up in
a reusable script that you could run directly, instead of having to manually
work through the debugger prompt?

~~~
ebroder
Hmm, it'd probably be doable. In particular, you can use the "commands"
command to script what happens when you hit a breakpoint (a friend talked
about this in a Ksplice GDB blog post:
[https://blogs.oracle.com/ksplice/entry/8_gdb_tricks_you_shou...](https://blogs.oracle.com/ksplice/entry/8_gdb_tricks_you_should#command))

Normally, the "finish" command will interrupt any script you're in the middle
of executing, so we have to do a bit of an ugly hack to make sure our script
keeps running:

b PyEval_EvalFrameEx if strcmp(PyString_AsString(f->f_code->co_name),
"handle_uncaught_exception") == 0

commands

disable

frame 3

python

gdb.execute('finish')

gdb.execute('shell git stash pop')

gdb.execute('call
PyImport_ReloadModule(PyImport_AddModule("monospace.views"))')

gdb.execute('call
PyImport_ReloadModule(PyImport_AddModule("monospace.urls"))')

gdb.execute('set $self = PyDict_GetItemString(f->f_locals, "self")')

gdb.execute('set $request = PyDict_GetItemString(f->f_locals, "request")')

gdb.execute('set $get_response = PyObject_GetAttrString($self,
"get_response")')

gdb.execute('set $args = Py_BuildValue("(O)", $request)')

gdb.execute('set $rax PyObject_Call($get_response, $args, 0)')

gdb.execute('enable')

gdb.execute('c')

end

c

end

(Bah. Edited because I couldn't get monospace text working)

You could put those commands into a file and run "gdb -p <my django process>
-x <my commands file>"

Of course, instead of shelling out to git stash pop, you'd probably want to
pause so you could update the code. And you may need to reload more modules
than just monospace.views and monospace.urls, depending on the change.

~~~
Luyt
_> Edited because I couldn't get monospace text working_

If you start each line with a few spaces, HN will format it as source code:

    
    
        b PyEval_EvalFrameEx if strcmp(PyString_AsString(f->f_code->co_name), "handle_uncaught_exception") == 0
        commands
        disable
        frame 3
        python
        gdb.execute('finish')
        gdb.execute('shell git stash pop')
        gdb.execute('call PyImport_ReloadModule(PyImport_AddModule("monospace.views"))')
        gdb.execute('call PyImport_ReloadModule(PyImport_AddModule("monospace.urls"))')
        gdb.execute('set $self = PyDict_GetItemString(f->f_locals, "self")')
        gdb.execute('set $request = PyDict_GetItemString(f->f_locals, "request")')
        gdb.execute('set $get_response = PyObject_GetAttrString($self, "get_response")')
        gdb.execute('set $args = Py_BuildValue("(O)", $request)')
        gdb.execute('set $rax PyObject_Call($get_response, $args, 0)')
        gdb.execute('enable')
        gdb.execute('c')
        end
        c
        end

------
pc
I've always missed this feature from Seaside/Smalltalk. (Not to mention having
a proper language-level debugger at your disposal.)

------
agumonkey
nice use of cpython knowledge. very black magic, but very usefull in time.

