├── .gitignore ├── Makefile.cygwin ├── Makefile.linux ├── test ├── beer.py ├── testddd.py └── test-ipa.c ├── setup.py ├── README.rst ├── init.gdb ├── ipa.h ├── rationale.rst ├── LICENSE ├── pyddd.rst ├── ipa.c └── libddd.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | *.ko 4 | *.obj 5 | *.elf 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Libraries 12 | *.lib 13 | *.a 14 | *.la 15 | *.lo 16 | 17 | # Shared objects (inc. Windows DLLs) 18 | *.dll 19 | *.so 20 | *.so.* 21 | *.dylib 22 | 23 | # Executables 24 | *.exe 25 | *.out 26 | *.app 27 | *.i*86 28 | *.x86_64 29 | *.hex 30 | -------------------------------------------------------------------------------- /Makefile.cygwin: -------------------------------------------------------------------------------- 1 | .PHONY: all 2 | 3 | all: test/pyddd-ipa.dll test/test-ipa.exe 4 | 5 | PYTHONINC := /cygdrive/c/Python27/Include 6 | PYTHONLIB := -L/cygdrive/d/projects/platforms/windows/python27 -lpython27 7 | CFLAGS := -g -I$(PYTHONINC) 8 | CC := i686-pc-mingw32-gcc 9 | 10 | test/pyddd-ipa.dll: ipa.c 11 | $(CC) -shared $(CFLAGS) $< -Wl,-lpthread -o $@ 12 | 13 | test/test-ipa.exe: test/test-ipa.c ipa.c 14 | $(CC) -mconsole -o $@ $(CFLAGS) -DTEST_IPA $< ipa.c $(PYTHONLIB) -Wl,-lpthread 15 | 16 | test: test/test-ipa.exe 17 | test/test-ipa.exe 18 | -------------------------------------------------------------------------------- /Makefile.linux: -------------------------------------------------------------------------------- 1 | .PHONY: all 2 | 3 | all: test/pyddd-ipa.so test/test-ipa 4 | 5 | PYTHONROOT := /home/jondy/workspace/pytransform/python/usr/local 6 | PYTHONINC := $(PYTHONROOT)/include/python2.7 7 | PYTHONLIB := -L$(PYTHONROOT)/lib -lpython2.7 8 | CFLAGS := -g -I$(PYTHONINC) -fPIC 9 | CC := gcc 10 | 11 | test/pyddd-ipa.so: ipa.c 12 | $(CC) -shared $(CFLAGS) $< -Wl,-lpthread -o $@ 13 | 14 | test/test-ipa: test/test-ipa.c ipa.c 15 | $(CC) -o $@ $(CFLAGS) -DTEST_IPA $< ipa.c $(PYTHONLIB) -Wl,-lpthread 16 | 17 | test: test/test-ipa 18 | test/test-ipa 19 | -------------------------------------------------------------------------------- /test/beer.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | # By GvR, demystified after a version by Fredrik Lundh. 4 | 5 | import sys 6 | 7 | n = 10 8 | if sys.argv[1:]: 9 | n = int(sys.argv[1]) 10 | 11 | def bottle(n): 12 | if n == 0: return "no more bottles of beer" 13 | if n == 1: return "one bottle of beer" 14 | return str(n) + " bottles of beer" 15 | 16 | for i in range(n, 0, -1): 17 | print bottle(i), "on the wall," 18 | print bottle(i) + "." 19 | print "Take one down, pass it around," 20 | print bottle(i-1), "on the wall." 21 | 22 | print "bye-bye!" -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys 4 | import os 5 | from distutils.core import setup, Extension 6 | 7 | def get_description(): 8 | filename = os.path.abspath(os.path.join(os.path.dirname(__file__), 'README.rst')) 9 | f = open(filename, 'r') 10 | try: 11 | return f.read() 12 | finally: 13 | f.close() 14 | 15 | VERSION = '0.2.3' 16 | 17 | def main(): 18 | setup_args = dict( 19 | name='pyddd', 20 | version=VERSION, 21 | description='Pyddd is a super-GDB debugger ' \ 22 | 'used to debug python scripts line by line in GDB', 23 | long_description=get_description(), 24 | keywords=['debug', 'gdb', 'pdb'], 25 | py_modules=['libddd'], 26 | ext_modules=[Extension('python-ipa', ['ipa.c', 'ipa.h'], 27 | extra_compile_args=['-g',], 28 | libraries=['pthread',]),], 29 | author='Jondy Zhao', 30 | author_email='jondy.zhao@gmail.com', 31 | maintainer='Jondy Zhao', 32 | maintainer_email='jondy.zhao@gmail.com', 33 | url='https://jondy.github.io/pyddd', 34 | platforms=['Windows', 'Linux'], 35 | license='GPLv3', 36 | ) 37 | setup(**setup_args) 38 | 39 | if __name__ == '__main__': 40 | main() 41 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Pyddd 2 | ===== 3 | 4 | Pyddd is a super-GDB debugger which could debug python scripts as the 5 | same way to debug c program line by line in the same inferior. 6 | 7 | Download 8 | ======== 9 | 10 | You can also download a snapshot from the Git repository: 11 | 12 | * as a `.tar.gz `__ 13 | file or 14 | * as a `.zip `__ file 15 | 16 | Installation 17 | ============ 18 | 19 | Since Pyddd is written in the Python language, you need to install 20 | Python (the required version is at least 2.6). 21 | 22 | Linux 23 | ----- 24 | 25 | * Rebuild GDB with Python and reinstall it. 26 | 27 | .. code-block:: bash 28 | 29 | $ tar xzf gdb-7.8.1.tar.gz 30 | $ cd gdb-7.8.1 31 | $ ./configure --with-python=python --with-babeltrace=no \ 32 | --enable-tui=no --enable-host-shared 33 | $ make && make install 34 | 35 | * Extract Pyddd package, copy libddd.py to gdb data directory. 36 | 37 | .. code-block:: bash 38 | 39 | $ cp libddd.py /usr/local/share/gdb/python 40 | 41 | * Build pyddd-ipa. 42 | 43 | .. code-block:: bash 44 | 45 | $ gcc -g -fPIC -I/usr/include/python2.7 -Wl,-lpthread -shared -o \ 46 | python-ipa.so ipa.c 47 | $ cp python-ipa.so /usr/local/lib 48 | 49 | Windows 50 | ------- 51 | 52 | * Install Cygwin. 53 | * Rebuild GDB with Python in Cygwin and reinstall it. 54 | 55 | .. code-block:: bash 56 | 57 | $ tar xzf gdb-7.8.1.tar.gz 58 | $ cd gdb-7.8.1 59 | $ ./configure --with-python=python --with-babeltrace=no \ 60 | --enable-tui=no --enable-host-shared 61 | $ make && make install 62 | 63 | * Unzip Pyddd package, copy libddd.py to gdb data directory. 64 | 65 | .. code-block:: bash 66 | 67 | $ cp libddd.py /usr/local/share/gdb/python 68 | 69 | * Open Cygwin Terminal, build pyddd-ipa. 70 | 71 | .. code-block:: bash 72 | 73 | $ gcc -g -I/usr/include/python2.7 -Wl,-lpthread -shared -o \ 74 | python-ipa.dll ipa.c 75 | $ cp python-ipa.dll /usr/local/lib 76 | 77 | Quick Start 78 | =========== 79 | 80 | After installation, invoke the command prompt, go to the directory 81 | including the debugged script foo.py and run those commands (assume 82 | pyddd is extracted at /usr/local/pyddd): 83 | 84 | .. code-block:: bash 85 | 86 | $ gdb -x /usr/local/pyddd/init.gdb 87 | (gdb) py-exec-file python 88 | (gdb) py-file foo.py 89 | (gdb) py-start 90 | 91 | For more, see pyddd.rst and rationale.rst. 92 | 93 | -------------------------------------------------------------------------------- /test/testddd.py: -------------------------------------------------------------------------------- 1 | """ 2 | This script is used to test gdb user document. 3 | 4 | It read test doc from ../rationale.rst, then replace the line like 5 | 6 | $(gdb) cmd 7 | 8 | with 9 | 10 | >>> pyddd.command ( cmd ) 11 | 12 | so doctest can run the gdb command and compare the output. 13 | 14 | """ 15 | import multiprocessing 16 | import os 17 | import re 18 | import select 19 | import socket 20 | import subprocess 21 | import sys 22 | import threading 23 | import time 24 | 25 | PROMPT = '(gdb) ' 26 | DOCPREFIX = '>>> pyddd.command("' 27 | 28 | args = './gdb.exe', '--data-directory=./', '--quiet', '-nx' 29 | 30 | class PyDDD(object): 31 | 32 | BUFSIZE = 4096 33 | def __init__(self): 34 | super(PyDDD, self).__init__() 35 | self.sockets = socket.socketpair() 36 | self._gdb = subprocess.Popen( 37 | args, 38 | stdout=self.sockets[0], 39 | stderr=subprocess.STDOUT, 40 | stdin=subprocess.PIPE, 41 | ) 42 | 43 | def start(self): 44 | # Clean output 45 | self.read_stdout() 46 | 47 | def stop(self): 48 | self.command('quit') 49 | 50 | def command(self, line): 51 | self._gdb.stdin.write(('%s\n' % line).encode()) 52 | time.sleep(.5) 53 | self.read_stdout() 54 | 55 | def read_stdout(self): 56 | while select.select(self.sockets, [], [], .5)[0]: 57 | buf = self.sockets[1].recv(PyDDD.BUFSIZE) 58 | if buf.endswith(PROMPT): 59 | sys.stdout.write (buf[:-1]) 60 | break 61 | sys.stdout.write (buf) 62 | 63 | def call(self, lines): 64 | p = subprocess.Popen( 65 | args, 66 | bufsize=PyDDD.BUFSIZE, 67 | stdout=subprocess.PIPE, 68 | stderr=subprocess.STDOUT, 69 | stdin=subprocess.PIPE, 70 | ) 71 | outdata, errdata = p.communicate(lines) 72 | print (outdata) 73 | 74 | def make_test_file(src): 75 | filename = '~tmp%s~' % os.path.basename(src) 76 | with open(filename, 'w') as ft: 77 | with open(src, 'r') as fs: 78 | for line in fs.readlines(): 79 | if line.strip().startswith(PROMPT): 80 | ft.write(line[:-1].replace(PROMPT, DOCPREFIX)) 81 | ft.write('")\n') 82 | else: 83 | ft.write(line) 84 | return filename 85 | 86 | if __name__ == "__main__": 87 | import doctest 88 | pyddd = PyDDD() 89 | pyddd.start() 90 | # doctest.testmod() 91 | filename = '../rationale.rst' 92 | if os.path.exists(filename): 93 | tmpfile = make_test_file(filename) 94 | doctest.testfile( 95 | tmpfile, 96 | globs=globals(), 97 | optionflags=doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE, 98 | ) 99 | os.remove(tmpfile) 100 | pyddd.stop() 101 | -------------------------------------------------------------------------------- /init.gdb: -------------------------------------------------------------------------------- 1 | # 2 | # gdb init script for pyddd. 3 | # 4 | dont-repeat 5 | 6 | define python-ipa-initialize 7 | dont-repeat 8 | if $pyddd_ipa_linux_platform 9 | set $python_ipa_handle = dlopen("pyddd-ipa.so", 2) 10 | else 11 | set $python_ipa_handle = LoadLibraryA("pyddd-ipa.dll") 12 | end 13 | set $python_ipa_trace_flag = PyEval_SetTrace(pyddd_ipa_trace_trampoline, 0) 14 | set $python_major_version = *(char*)Py_GetVersion() 15 | if $python_major_version == 0x33 16 | set var pyddd_ipa_pystring_asstring = PyBytes_AsString 17 | set var pyddd_ipa_pystring_size = PyBytes_Size 18 | else 19 | set var pyddd_ipa_pystring_asstring = PyString_AsString 20 | set var pyddd_ipa_pystring_size = PyString_Size 21 | end 22 | set var pyddd_ipa_pyobject_str = PyObject_Str 23 | set var pyddd_ipa_pyobject_repr = PyObject_Repr 24 | set var pyddd_ipa_pyframe_getlinenumber = PyFrame_GetLineNumber 25 | set var pyddd_ipa_py_compilestringflags = Py_CompileStringFlags 26 | set var pyddd_ipa_pyeval_evalcode = PyEval_EvalCode 27 | set var pyddd_ipa_pyeval_settrace = PyEval_SetTrace 28 | set var pyddd_ipa_py_decref = Py_DecRef 29 | set var pyddd_ipa_pyobject_istrue = PyObject_IsTrue 30 | set var pyddd_ipa_pythreadstate_get = PyThreadState_Get 31 | set var pyddd_ipa_pytuple_getitem = PyTuple_GetItem 32 | set var pyddd_ipa_pydict_getitem = PyDict_GetItem 33 | set var pyddd_ipa_pydict_getitemstring = PyDict_GetItemString 34 | set var pyddd_ipa_pydict_setitem = PyDict_SetItem 35 | set var pyddd_ipa_pydict_setitemstring = PyDict_SetItemString 36 | set var pyddd_ipa_pydict_next = PyDict_Next 37 | set var pyddd_ipa_pydict_keys = PyDict_Keys 38 | set var pyddd_ipa_pyobject_type = PyObject_Type 39 | set var pyddd_ipa_pyobject_getiter = PyObject_GetIter 40 | set var pyddd_ipa_pyiter_next = PyIter_Next 41 | set var pyddd_ipa_pyerr_clear = PyErr_Clear 42 | set var pyddd_ipa_pyerr_printex = PyErr_PrintEx 43 | set var pyddd_ipa_pyerr_occurred = PyErr_Occurred 44 | 45 | # upload breakpoints and catchpoints 46 | python-ipa-load-data 47 | 48 | # clear python frame 49 | python-ipa-frame teardown 50 | end 51 | 52 | alias py-exec-file = file 53 | 54 | def py-start 55 | dont-repeat 56 | py-tcatch call 57 | py-run 58 | end 59 | 60 | alias py-continue = continue 61 | 62 | def py-step 63 | if $argc == 0 64 | call pyddd_ipa_step_command (1) 65 | else 66 | call pyddd_ipa_step_command ($arg0) 67 | end 68 | continue 69 | end 70 | 71 | def py-next 72 | if $argc == 0 73 | call pyddd_ipa_next_command (1) 74 | else 75 | call pyddd_ipa_next_command ($arg0) 76 | end 77 | continue 78 | end 79 | 80 | def py-finish 81 | call pyddd_ipa_finish_command() 82 | continue 83 | end 84 | 85 | def py-until 86 | if $argc == 0 87 | call pyddd_ipa_until_command (0) 88 | else 89 | call pyddd_ipa_until_command ($arg0) 90 | end 91 | continue 92 | end 93 | 94 | def py-advance 95 | call pyddd_ipa_until_command ($arg0) 96 | continue 97 | end 98 | 99 | def py-select-frame 100 | if $argc == 1 && $arg0 >= 0 101 | python-ipa-frame select $arg0 102 | end 103 | end 104 | 105 | def py-frame 106 | if $argc == 1 107 | python-ipa-frame select $arg0 108 | end 109 | python-ipa-frame print 110 | end 111 | 112 | def py-up 113 | if $argc == 0 114 | python-ipa-frame select +1 115 | else 116 | python-ipa-frame select $arg0 117 | end 118 | python-ipa-frame print 119 | end 120 | 121 | def py-down 122 | if $argc == 0 123 | python-ipa-frame select -1 124 | else 125 | python-ipa-frame select $arg0 126 | end 127 | python-ipa-frame print 128 | end 129 | 130 | def py-bt 131 | if $argc == 0 132 | python-ipa-frame bt 133 | else 134 | if $argc == 1 135 | python-ipa-frame bt $arg0 136 | else 137 | if $argc == 2 138 | python-ipa-frame bt $arg0 $arg1 139 | else 140 | if $argc == 3 141 | python-ipa-frame bt $arg0 $arg1 $arg2 142 | end 143 | end 144 | end 145 | end 146 | end 147 | 148 | def py-print 149 | print (char*)pyddd_ipa_eval(0, $arg0) 150 | end 151 | 152 | def py-set-var 153 | if $arg0[0] == '/' 154 | if $arg[[1] == 'g' 155 | call pyddd_ipa_alter_variable(0, $arg1, $arg2, 1) 156 | else 157 | call pyddd_ipa_alter_variable(0, $arg1, $arg2, 0) 158 | end 159 | else 160 | call pyddd_ipa_alter_variable(0, $arg0, $arg1, 0) 161 | end 162 | end 163 | 164 | define hook-continue 165 | python-ipa-frame teardown 166 | end 167 | 168 | define py-list 169 | if $argc == 0 170 | python-ipa-frame sources 171 | else 172 | if $argc == 1 173 | python-ipa-frame sources $arg0 174 | else 175 | if $argc == 2 176 | python-ipa-frame sources $arg0 $arg1 177 | end 178 | end 179 | end 180 | end 181 | 182 | python 183 | import libddd 184 | end 185 | 186 | alias -a pr = py-run 187 | 188 | alias -a ps = py-step 189 | alias -a pn = py-next 190 | alias -a pu = py-until 191 | 192 | alias -a pb = py-break 193 | alias -a pc = py-catch 194 | alias -a ptb = py-tbreak 195 | alias -a ptc = py-tcatch 196 | 197 | alias -a pf = py-frame 198 | alias -a pbt = py-bt 199 | alias -a pup = py-up 200 | alias -a pdo = py-down 201 | 202 | alias -a pl = py-list 203 | alias -a pif = py-info 204 | 205 | alias -a pp = py-print 206 | alias -a psv = py-set-var 207 | 208 | alias -a psf = py-symbol-file 209 | 210 | # end of init.gdb 211 | -------------------------------------------------------------------------------- /ipa.h: -------------------------------------------------------------------------------- 1 | #if !defined(__PYDDD_IPA_H__) 2 | #define __PYDDD_IPA_H__ 3 | 4 | #include "Python.h" 5 | #include "frameobject.h" 6 | #include "code.h" 7 | #include "node.h" 8 | 9 | #include 10 | 11 | #define PYDDD_IPA_BREAKPOINT_PAGE 256 12 | #define PYDDD_IPA_MAX_BREAKPOINT 1024 13 | 14 | /* 15 | * Internal used to support step/next/until/advance/finish commands. 16 | * 17 | * ignore_count is in GDB side, not in python-ipa. 18 | */ 19 | struct pyddd_ipa_t_volatile_breakpoint { 20 | int enabled; 21 | long thread_id; 22 | PyFrameObject *f_frame; 23 | PyObject *co_filename; 24 | int lineno; 25 | }; 26 | 27 | struct pyddd_ipa_t_breakpoint { 28 | int bpnum; /* GDB bpnum */ 29 | int locnum; /* Location number */ 30 | long thread_id; /* Thread id, 0 means any thread */ 31 | char *condition; /* Python condition expression */ 32 | int ignore_count; /* Ignore count */ 33 | int hit_count; 34 | int enabled; /* 0 or 1 */ 35 | 36 | int lineno; /* > 0 */ 37 | char *filename; /* NOT NULL */ 38 | int filename_size; /* Size of filename */ 39 | PyObject *co_filename; /* 0 means unresolved address */ 40 | }; 41 | 42 | const char * pyddd_ipa_version(void); 43 | 44 | int 45 | pyddd_ipa_trace_trampoline(PyObject *self, 46 | PyFrameObject *frame, 47 | int what, 48 | PyObject *arg); 49 | int 50 | pyddd_ipa_profile_trampoline(PyObject *self, 51 | PyFrameObject *frame, 52 | int what, 53 | PyObject *arg); 54 | 55 | int 56 | pyddd_ipa_insert_breakpoint(const int bpnum, 57 | const int locnum, 58 | const long thread_id, 59 | const char *condition, 60 | const int ignore_count, 61 | const int enabled, 62 | const int lineno, 63 | const char *filename 64 | ); 65 | void 66 | pyddd_ipa_update_breakpoint(const int rindex, 67 | const int bpnum, 68 | const int locnum, 69 | const long thread_id, 70 | const char *condition, 71 | const int ignore_count, 72 | const int enabled, 73 | const int lineno, 74 | const char *filename 75 | ); 76 | void pyddd_ipa_remove_breakpoint(const int rindex); 77 | 78 | int pyddd_ipa_step_command(int count); 79 | int pyddd_ipa_next_command(int count); 80 | int pyddd_ipa_finish_command(void); 81 | int pyddd_ipa_until_command(int lineno); 82 | int pyddd_ipa_advance_command(int lineno); 83 | 84 | PyFrameObject * pyddd_ipa_frame_older(PyFrameObject *frame); 85 | PyFrameObject * pyddd_ipa_frame_lookup(PyFrameObject *frame, const char *name); 86 | const char * pyddd_ipa_frame_filename(PyFrameObject *frame); 87 | int pyddd_ipa_frame_lineno(PyFrameObject *frame); 88 | const char * pyddd_ipa_frame_name(PyFrameObject *frame); 89 | const char * pyddd_ipa_frame_locals(PyFrameObject *frame); 90 | const char * pyddd_ipa_frame_globals(PyFrameObject *frame); 91 | const char * pyddd_ipa_frame_varnames(PyFrameObject *frame); 92 | const char * pyddd_ipa_frame_cellvars(PyFrameObject *frame); 93 | const char * pyddd_ipa_frame_values(PyFrameObject *frame, 94 | char *varname, 95 | int index); 96 | const char * pyddd_ipa_frame_variable(PyFrameObject *frame, 97 | char *varname, 98 | int global); 99 | 100 | int pyddd_ipa_alter_variable(PyFrameObject *frame, 101 | char *name, 102 | char *expr, 103 | int global); 104 | const char *pyddd_ipa_eval(PyFrameObject *frame, char *expr); 105 | const char *pyddd_ipa_iter_next(PyObject *iter); 106 | const char *pyddd_ipa_format_object(PyObject *o); 107 | 108 | /* In Python3, it is PyBytes_AsString/PyBytes_Size */ 109 | #define FPyString_AsString pyddd_ipa_pystring_asstring 110 | #define FPyString_Size pyddd_ipa_pystring_size 111 | #define FPyFrame_GetLineNumber pyddd_ipa_pyframe_getlinenumber 112 | #define FPy_CompileStringFlags pyddd_ipa_py_compilestringflags 113 | #define FPyEval_EvalCode pyddd_ipa_pyeval_evalcode 114 | #define FPyEval_SetTrace pyddd_ipa_pyeval_settrace 115 | #define FPy_DecRef pyddd_ipa_py_decref 116 | #define FPyObject_IsTrue pyddd_ipa_pyobject_istrue 117 | #define FPyThreadState_Get pyddd_ipa_pythreadstate_get 118 | #define FPyObject_Str pyddd_ipa_pyobject_str 119 | #define FPyObject_Repr pyddd_ipa_pyobject_repr 120 | #define FPyTuple_GetItem pyddd_ipa_pytuple_getitem 121 | #define FPyDict_GetItem pyddd_ipa_pydict_getitem 122 | #define FPyDict_GetItemString pyddd_ipa_pydict_getitemstring 123 | #define FPyDict_SetItem pyddd_ipa_pydict_setitem 124 | #define FPyDict_SetItemString pyddd_ipa_pydict_setitemstring 125 | #define FPyDict_Keys pyddd_ipa_pydict_keys 126 | #define FPyDict_Next pyddd_ipa_pydict_next 127 | #define FPyObject_Type pyddd_ipa_pyobject_type 128 | #define FPyObject_GetIter pyddd_ipa_pyobject_getiter 129 | #define FPyIter_Next pyddd_ipa_pyiter_next 130 | #define FPyErr_Clear pyddd_ipa_pyerr_clear 131 | #define FPyErr_PrintEx pyddd_ipa_pyerr_printex 132 | #define FPyErr_Occurred pyddd_ipa_pyerr_occurred 133 | 134 | extern char* (*FPyString_AsString)(PyObject *o); 135 | extern int (*FPyFrame_GetLineNumber)(PyFrameObject *frame); 136 | extern void (*FPy_DecRef)(PyObject *); 137 | extern int (*FPyObject_IsTrue)(PyObject *); 138 | extern Py_ssize_t (*FPyString_Size)(PyObject *string); 139 | extern void (*FPyEval_SetTrace)(Py_tracefunc func, PyObject *arg); 140 | extern PyThreadState* (*FPyThreadState_Get)(void); 141 | extern PyObject* (*FPyObject_Str)(PyObject *o); 142 | extern PyObject* (*FPyObject_Repr)(PyObject *o); 143 | extern PyObject* (*FPyTuple_GetItem)(PyObject *p, Py_ssize_t pos); 144 | extern PyObject* (*FPyDict_GetItem)(PyObject *p, PyObject *key); 145 | extern PyObject* (*FPyDict_GetItemString)(PyObject *p, const char *key); 146 | extern int (*FPyDict_SetItem)(PyObject *p, PyObject *key, PyObject *val); 147 | extern int (*FPyDict_SetItemString)(PyObject *p, 148 | const char *key, 149 | PyObject *val); 150 | extern PyObject* (*FPyDict_Keys)(PyObject *p); 151 | extern int (*FPyDict_Next)(PyObject *p, 152 | Py_ssize_t *ppos, 153 | PyObject **pkey, 154 | PyObject **pvalue); 155 | 156 | extern PyObject* (*FPyObject_Type)(PyObject *o); 157 | extern PyObject* (*FPyObject_GetIter)(PyObject *o); 158 | extern PyObject* (*FPyIter_Next)(PyObject *o); 159 | 160 | extern void (*FPyErr_Clear)(void); 161 | extern void (*FPyErr_PrintEx)(int set_sys_last_vars); 162 | extern PyObject* (*FPyErr_Occurred)(void); 163 | extern PyObject* (*FPy_CompileStringFlags)(const char *str, 164 | const char *filename, 165 | int start, 166 | PyCompilerFlags *flags); 167 | extern PyObject* (*FPyEval_EvalCode)(PyCodeObject *co, 168 | PyObject *globals, 169 | PyObject *locals); 170 | 171 | #endif /* __PYDDD_IPA_H__ */ 172 | /* end of ipa.h */ 173 | -------------------------------------------------------------------------------- /rationale.rst: -------------------------------------------------------------------------------- 1 | 2 | **Line by Line Debug Python Scripts In GDB** 3 | 4 | Pyddd is a super-GDB debugger which could debug python scripts as the 5 | same way to debug c program line by line in the same inferior. 6 | 7 | Purpose 8 | ======= 9 | 10 | Debug Python scripts line by line as debug C/C++ in GDB, so that we 11 | can easily debug Python scripts and C/C++ extensions within GDB. 12 | 13 | Causation 14 | ========= 15 | 16 | I always uses Python plus C/C++ to develop my most of 17 | applications. Generally, use GDB to debug python C/C++ extensions, and 18 | insert extra print statements in Python scripts. There is a gdb 19 | extension "libpython.py" within Python sources, it could print Python 20 | frame, locals/globals variable, Python sources in GDB. But it couldn't 21 | set breakpoints in Python scripts directly. Finally, I decided to 22 | write a debugger to extend GDB could debug Python scripts line by 23 | line, just like debugging c/c++. 24 | 25 | Proposal 26 | ======== 27 | 28 | * Start to debug Python script:: 29 | 30 | - (gdb) py-exec-file /usr/local/bin/python2.7 31 | - (gdb) py-file beer.py 32 | - (gdb) py-args 10 33 | - (gdb) py-start 34 | 35 | GDB will stop at the first code line of beer.py. If use `py-run` 36 | instead `py-start`:: 37 | 38 | - (gdb) py-run 39 | 40 | GDB will not stop at the begging of beer.py, until hit some 41 | breakpoint. 42 | 43 | Set Python arguments, for example, unbuffered binary stdout and 44 | stderr:: 45 | 46 | - (gdb) py-exec-args -u 47 | 48 | * Set breakpoints in Python script:: 49 | 50 | - (gdb) py-break 10 51 | - (gdb) py-break beer.py:10 52 | - (gdb) py-break bottle 53 | - (gdb) py-break beer.py:bottle 54 | - (gdb) py-break bottle:+3 55 | - (gdb) py-break bottle:-3 56 | 57 | Set condition of breakpoints:: 58 | 59 | - (gdb) py-break location if condition 60 | - (gdb) py-condition bnum condition 61 | 62 | condition may be any valid Python expression. It will not stop if 63 | symbol in condition isn't available in the current context. 64 | 65 | Set temporary breakpoints, arguments are same with `py-break`:: 66 | 67 | - (gdb) py-tbreak ... 68 | 69 | Delete breakpoints:: 70 | 71 | - (gdb) py-clear 72 | - (gdb) py-clear location 73 | - (gdb) py-delete [breakpoints] [range...] 74 | 75 | Disable breakpoints:: 76 | 77 | - (gdb) py-disable [breakpoints] [range...] 78 | - (gdb) py-enable [breakpoints] [range...] 79 | - (gdb) py-enable [breakpoints] once range... 80 | - (gdb) py-enable [breakpoints] count count range... 81 | - (gdb) py-enable [breakpoints] delete range... 82 | 83 | Breakpoint Command Lists:: 84 | 85 | - (gdb) py-commands [range...] 86 | ... command-list ... 87 | end 88 | 89 | Show breakpoints:: 90 | 91 | - (gdb) py-info [breakpoints] [range...] 92 | 93 | * Catch exception and function call:: 94 | 95 | - (gdb) py-catch exception name 96 | - (gdb) py-catch call name 97 | 98 | GDB will stop when exception name is raised or function name is 99 | called. 100 | 101 | Add temporary catchpoint:: 102 | 103 | - (gdb) py-tcatch exception name 104 | - (gdb) py-tcatch call name 105 | 106 | Clear catchpoints:: 107 | 108 | - (gdb) py-catch clear name 109 | 110 | Show catchpoints:: 111 | 112 | - (gdb) py-catch info [ranges...] 113 | 114 | * Continuing and Stepping:: 115 | 116 | - (gdb) py-continue [ignore-count] 117 | - (gdb) py-step [count] 118 | - (gdb) py-next [count] 119 | - (gdb) py-finish 120 | - (gdb) py-until 121 | - (gdb) py-until location 122 | - (gdb) py-advance location 123 | 124 | * Examining Python Scripts:: 125 | 126 | - (gdb) py-list linenum 127 | - (gdb) py-list function 128 | - (gdb) py-list 129 | - (gdb) py-list - 130 | - (gdb) py-list + 131 | - (gdb) py-list first,last 132 | - (gdb) py-list first, 133 | - (gdb) py-list ,last 134 | 135 | * Examining Python frame stack:: 136 | 137 | - (gdb) py-frame 138 | - (gdb) py-frame n 139 | - (gdb) py-frame function 140 | - (gdb) py-up [n] 141 | - (gdb) py-down [n] 142 | - (gdb) py-select-frame framespec 143 | 144 | * Examining Python backtrace:: 145 | 146 | - (gdb) py-bt 147 | - (gdb) py-bt n 148 | - (gdb) py-bt -n 149 | - (gdb) py-bt full 150 | - (gdb) py-bt full n 151 | - (gdb) py-bt full -n 152 | 153 | * Examining Python Data:: 154 | 155 | - (gdb) py-print expr 156 | - (gdb) py-locals 157 | - (gdb) py-locals varname 158 | - (gdb) py-globals 159 | - (gdb) py-globals varname 160 | 161 | * Altering Python local/global variable:: 162 | 163 | - (gdb) py-set-var name expression 164 | - (gdb) py-set-var /global name expression 165 | 166 | Workaround 167 | ========== 168 | 169 | Fortunately, Python has its line-trace mechanism, see "PySys_SetTrace" 170 | in "Python/sysmodule.c" and "PyEval_SetTrace" in "Python/ceval.c". In 171 | order to stop Python Scripts in GDB, we need write a trace function in 172 | c or c++, install the trace function when run python scripts. In trace 173 | function check all the *Python Breakpoints*, and execute a statement 174 | which include a GDB *Breakpoint*. Here is the basic scenario: 175 | 176 | - Write our own trace function in C, and build it as a shared library. 177 | - Manage *Python Breakpoints* in this library. 178 | - In GDB, load this library and install trace function after start 179 | to debug python scripts. 180 | - In GDB, set a *Breakpoint* in trace function. It will execute the 181 | statement in this *Breakpoint* if any *Python Breakpoint* is 182 | hit. By this way, a *Python Breakpint* is transferred a standard 183 | GDB *Breakpoint*. 184 | 185 | In order to get the lineno of each imported class/function in runtime, 186 | The two GDB *Breakpoints* at "PyImport_ExecCodeModuleEx" and 187 | "PyCode_New" are set. 188 | 189 | Here is prototype of "PyImport_ExecCodeModuleEx":: 190 | 191 | PyObject* PyImport_ExecCodeModuleEx(char *name, PyObject *co, char *pathname); 192 | 193 | When GDB stop at "PyImport_ExecCodeModuleEx", "name" and "pathname" 194 | could be got from the current frame:: 195 | 196 | set $name = (char*)($fp + sizeof($fp) + sizeof($pc)) 197 | set $pathname = (char*)($fp + sizeof($fp) + sizeof($pc) + sizeof(char*) + sizeof(PyObject*) 198 | 199 | For the concerned module, enable *Breakpoint* "PyCode_New"; Otherwise 200 | disable. Because there are many python scripts are imported, only a 201 | few are required to debug. 202 | 203 | When GDB stop at "PyCode_New", as the same way, "name" and 204 | "firstlineno" could be got from current frame. When name equals 205 | "", it means last code object in this module, disable this 206 | *Breakpoint* self. 207 | 208 | 209 | Implementation 210 | ============== 211 | 212 | See ipa.c, init.gdb and libddd.py 213 | 214 | Example 215 | ======= 216 | 217 | This example is doc-tested, run the following command to test it:: 218 | 219 | $ python testddd.py -v 220 | 221 | * Load init.gdb of *PYDDD*:: 222 | 223 | (gdb) source init.gdb 224 | No symbol table is loaded. Use the "file" command. 225 | No symbol table is loaded. Use the "file" command. 226 | No symbol table is loaded. Use the "file" command. 227 | No symbol table is loaded. Use the "file" command. 228 | No symbol table is loaded. Use the "file" command. 229 | No symbol table is loaded. Use the "file" command. 230 | No symbol table is loaded. Use the "file" command. 231 | No symbol table is loaded. Use the "file" command. 232 | (gdb) 233 | 234 | * Specify which python is used:: 235 | 236 | (gdb) py-exec-file python 237 | Reading symbols from ...python...(no debugging symbols found)...done. 238 | (gdb) 239 | 240 | * Specify main script:: 241 | 242 | (gdb) py-file beer.py 243 | main script is beer.py 244 | (gdb) 245 | 246 | * Start debug:: 247 | 248 | (gdb) py-start 249 | Add temporary catchpoint #1, catch call: 250 | load symbols from main script 251 | Disabled autoload imported symbol 252 | [New Thread ...] 253 | [New Thread ...] 254 | Enabled autoload imported symbol 255 | Catch function call: 256 | #0 ( ) at beer.py:5 257 | >5 import sys 258 | Remove temporary catchpoint #1 259 | (gdb) 260 | 261 | * Show sources:: 262 | 263 | (gdb) py-list 264 | >5 import sys 265 | 6 266 | 7 n = 10 267 | 8 if sys.argv[1:]: 268 | 9 n = int(sys.argv[1]) 269 | 10 270 | 11 def bottle(n): 271 | 12 if n == 0: return "no more bottles of beer" 272 | 13 if n == 1: return "one bottle of beer" 273 | 14 return str(n) + " bottles of beer" 274 | 15 275 | (gdb) 276 | 277 | * Continue script:: 278 | 279 | (gdb) py-continue 280 | Continuing. 281 | ... 282 | (gdb) 283 | -------------------------------------------------------------------------------- /test/test-ipa.c: -------------------------------------------------------------------------------- 1 | #include "../ipa.h" 2 | 3 | extern int pyddd_ipa_hit_flag; 4 | 5 | extern struct pyddd_ipa_t_volatile_breakpoint 6 | pyddd_ipa_volatile_breakpoint; 7 | extern struct pyddd_ipa_t_breakpoint pyddd_ipa_breakpoint_table[]; 8 | 9 | extern int pyddd_ipa_breakpoint_top; 10 | extern int pyddd_ipa_breakpoint_counter; 11 | 12 | extern char * pyddd_ipa_python_catch_exceptions; 13 | extern char * pyddd_ipa_python_catch_functions; 14 | 15 | static void 16 | init_func(void) 17 | { 18 | #if PY_MAJOR_VERSION ==3 19 | FPyString_AsString = PyBytes_AsString; 20 | FPyString_Size = PyBytes_Size; 21 | #else 22 | FPyString_AsString = PyString_AsString; 23 | FPyString_Size = PyString_Size; 24 | #endif 25 | 26 | FPyFrame_GetLineNumber = PyFrame_GetLineNumber; 27 | FPy_CompileStringFlags = Py_CompileStringFlags; 28 | FPyEval_EvalCode = PyEval_EvalCode; 29 | FPyEval_SetTrace = PyEval_SetTrace; 30 | FPy_DecRef = Py_DecRef; 31 | FPyObject_IsTrue = PyObject_IsTrue; 32 | FPyThreadState_Get = PyThreadState_Get; 33 | FPyObject_Str = PyObject_Str; 34 | FPyObject_Repr = PyObject_Repr; 35 | FPyTuple_GetItem = PyTuple_GetItem; 36 | FPyDict_GetItem = PyDict_GetItem; 37 | FPyDict_GetItemString = PyDict_GetItemString; 38 | FPyDict_SetItem = PyDict_SetItem; 39 | FPyDict_SetItemString = PyDict_SetItemString; 40 | FPyDict_Next = PyDict_Next; 41 | FPyDict_Keys = PyDict_Keys; 42 | FPyObject_GetIter = PyObject_GetIter; 43 | FPyObject_Type = PyObject_Type; 44 | FPyIter_Next = PyIter_Next; 45 | FPyErr_Clear = PyErr_Clear; 46 | FPyErr_PrintEx = PyErr_PrintEx; 47 | FPyErr_Occurred = PyErr_Occurred; 48 | } 49 | 50 | static PyFrameObject * 51 | make_test_frame(char *code, char *filename) 52 | { 53 | PyCodeObject *co_code; 54 | PyObject *f_locals, *f_globals; 55 | PyThreadState *tstate=(*FPyThreadState_Get)(); 56 | PyFrameObject *frame=tstate->frame; 57 | 58 | co_code = (PyCodeObject*)Py_CompileStringFlags(code, 59 | filename, 60 | Py_file_input, 61 | NULL); 62 | if (!co_code) { 63 | if (PyErr_Occurred()) 64 | PyErr_PrintEx(0); 65 | return NULL; 66 | } 67 | 68 | f_locals = Py_BuildValue("{sisss(ii)}", 69 | "i", 2, 70 | "name", "jondy", 71 | "rlist", 3, 5 72 | ); 73 | f_globals = Py_BuildValue("{si}", 74 | "i", 4 75 | ); 76 | assert (f_locals && f_globals); 77 | return PyFrame_New(tstate, co_code, f_globals, f_locals); 78 | } 79 | 80 | /* 81 | * test find_name_in_list 82 | */ 83 | extern char* find_name_in_list(const char *name, const char *namelist); 84 | void test_find_name_in_list(void) 85 | { 86 | assert (!find_name_in_list (NULL, NULL)); 87 | assert (!find_name_in_list ("", NULL)); 88 | assert (!find_name_in_list ("foo", NULL)); 89 | 90 | assert (find_name_in_list ("foo", "foo")); 91 | assert (find_name_in_list ("foo", "f?o")); 92 | assert (find_name_in_list ("foo", "f*")); 93 | assert (find_name_in_list ("foo", "*o")); 94 | assert (find_name_in_list ("foo", "f*o")); 95 | 96 | assert (find_name_in_list ("foo", "hello foo")); 97 | assert (find_name_in_list ("foo", "hello f?o")); 98 | assert (find_name_in_list ("foo", "hello f*")); 99 | assert (find_name_in_list ("foo", "hello *o")); 100 | assert (find_name_in_list ("foo", "hello f*o")); 101 | 102 | assert (find_name_in_list ("foo", "hello foo fight")); 103 | assert (find_name_in_list ("foo", "hello f?o fight")); 104 | assert (find_name_in_list ("foo", "hello f* fight")); 105 | assert (find_name_in_list ("foo", "hello *o fight")); 106 | assert (find_name_in_list ("foo", "hello f*o fight")); 107 | 108 | assert (!find_name_in_list ("foo", "fo")); 109 | assert (!find_name_in_list ("foo", "f?")); 110 | assert (!find_name_in_list ("foo", "f*b")); 111 | assert (!find_name_in_list ("foo", "*ooo")); 112 | assert (!find_name_in_list ("foo", "*koo")); 113 | 114 | assert (!find_name_in_list ("foo", "hello fo")); 115 | assert (!find_name_in_list ("foo", "hello f?")); 116 | assert (!find_name_in_list ("foo", "hello f*b")); 117 | assert (!find_name_in_list ("foo", "hello *ooo")); 118 | assert (!find_name_in_list ("foo", "hello *koo")); 119 | 120 | assert (!find_name_in_list ("foo", "hello fo fight")); 121 | assert (!find_name_in_list ("foo", "hello f? fight")); 122 | assert (!find_name_in_list ("foo", "hello f*b fight")); 123 | assert (!find_name_in_list ("foo", "hello *ooo fight")); 124 | assert (!find_name_in_list ("foo", "hello *koo fight")); 125 | } 126 | 127 | void test_pyddd_ipa_insert_breakpoint(void) 128 | { 129 | #define ft pyddd_ipa_insert_breakpoint 130 | int n=pyddd_ipa_breakpoint_top; 131 | int i=pyddd_ipa_breakpoint_counter; 132 | int k=n/2; 133 | for (; n ; n--, i++) 134 | assert (i == ft(i+1, 0, 0, NULL, 0, 1, 0, "foo.py")); 135 | 136 | pyddd_ipa_remove_breakpoint (k); 137 | assert (k == ft(i+1, 0, 0, NULL, 0, 1, 0, "foo.py")); 138 | 139 | n=pyddd_ipa_breakpoint_top; 140 | assert (n == ft(i+2, 0, 0, NULL, 0, 1, 0, "foo.py")); 141 | #undef ft 142 | } 143 | 144 | void test_pyddd_ipa_frame_variable(void) 145 | { 146 | #define ft pyddd_ipa_frame_variable 147 | PyFrameObject *frame; 148 | frame = make_test_frame("", ""); 149 | assert (frame); 150 | assert (*ft(frame, "i", 0) == 0x32); 151 | assert (*ft(frame, "i", 1) == 0x34); 152 | Py_DECREF((PyObject*)frame); 153 | #undef ft 154 | } 155 | 156 | void test_pyddd_ipa_alter_variable(void) 157 | { 158 | #define ft pyddd_ipa_alter_variable 159 | #define ft2 pyddd_ipa_frame_variable 160 | PyFrameObject *frame; 161 | frame = make_test_frame("", ""); 162 | assert (frame); 163 | assert (!ft(frame, "i", "7*3", 0)); 164 | assert (!strcmp(ft2(frame, "i", 0), "21")); 165 | 166 | assert (!ft(frame, "i", "4*7", 1)); 167 | assert (!strcmp(ft2(frame, "i", 1), "28")); 168 | 169 | Py_DECREF((PyObject*)frame); 170 | #undef ft 171 | #undef ft2 172 | } 173 | 174 | void test_pyddd_ipa_eval(void) 175 | { 176 | #define ft pyddd_ipa_eval 177 | PyFrameObject *frame; 178 | frame = make_test_frame("", ""); 179 | assert (frame); 180 | assert (*ft(frame, "i*3") == 0x36); 181 | assert (*ft(frame, "rlist[1]") == 0x35); 182 | Py_DECREF((PyObject*)frame); 183 | #undef ft 184 | } 185 | 186 | void test_pyddd_ipa_frame_locals(void) 187 | { 188 | #define ft pyddd_ipa_frame_locals 189 | PyFrameObject *frame; 190 | char *s; 191 | frame = make_test_frame("i+=2", "foo.py"); 192 | assert (frame); 193 | s = (char*)ft(frame); 194 | assert(s && strstr(s, "'i'") && strstr(s, "'name'") && strstr(s, "'rlist'")); 195 | Py_DECREF((PyObject*)frame); 196 | #undef ft 197 | } 198 | 199 | void test_pyddd_ipa_frame_globals(void) 200 | { 201 | #define ft pyddd_ipa_frame_globals 202 | PyFrameObject *frame; 203 | char *s; 204 | frame = make_test_frame("i+=2", "foo.py"); 205 | assert (frame); 206 | s = (char*)ft(frame); 207 | assert(s && strstr(s, "'i'")); 208 | Py_DECREF((PyObject*)frame); 209 | #undef ft 210 | } 211 | 212 | void test_pyddd_ipa_format_object(void) 213 | { 214 | #define ft pyddd_ipa_format_object 215 | char *s; 216 | PyObject *p; 217 | 218 | p = Py_BuildValue("i", 5); 219 | s = (char*)ft(p); 220 | assert (s && !strcmp(s, "5")); 221 | Py_DECREF(p); 222 | 223 | p = Py_BuildValue("I", 2L); 224 | s = (char*)ft(p); 225 | assert (s && !strcmp(s, "2")); 226 | Py_DECREF(p); 227 | 228 | p = Py_BuildValue("f", 1.618); 229 | s = (char*)ft(p); 230 | assert (s && !strcmp(s, "1.618")); 231 | Py_DECREF(p); 232 | 233 | p = Py_BuildValue("s", "pyddd"); 234 | s = (char*)ft(p); 235 | assert (s && !strcmp(s, "'pyddd'")); 236 | Py_DECREF(p); 237 | 238 | #undef ft 239 | } 240 | 241 | void test_pyddd_ipa_trace_trampoline(void) 242 | { 243 | int i; 244 | #define ft pyddd_ipa_trace_trampoline 245 | PyFrameObject *frame; 246 | frame = make_test_frame("j=2", "foo.py"); 247 | assert (frame); 248 | 249 | pyddd_ipa_hit_flag = 0; 250 | ft(NULL, frame, PyTrace_LINE, NULL); 251 | assert (!pyddd_ipa_hit_flag); 252 | 253 | pyddd_ipa_step_command(1); 254 | ft(NULL, frame, PyTrace_LINE, NULL); 255 | assert (pyddd_ipa_hit_flag == 1); 256 | 257 | pyddd_ipa_next_command(1); 258 | ft(NULL, frame, PyTrace_LINE, NULL); 259 | assert (pyddd_ipa_hit_flag == 2); 260 | 261 | pyddd_ipa_volatile_breakpoint.enabled = 0; 262 | pyddd_ipa_hit_flag = 0; 263 | pyddd_ipa_insert_breakpoint(1, 0, 0, NULL, 0, 1, 10, "foo.py"); 264 | pyddd_ipa_insert_breakpoint(1, 0, 0, NULL, 0, 0, 1, "foo.py"); 265 | ft(NULL, frame, PyTrace_LINE, NULL); 266 | assert (!pyddd_ipa_hit_flag); 267 | 268 | i = pyddd_ipa_insert_breakpoint(1, 0, 0, NULL, 0, 1, 1, "foo.py"); 269 | pyddd_ipa_hit_flag = 0; 270 | ft(NULL, frame, PyTrace_LINE, NULL); 271 | ft(NULL, frame, PyTrace_LINE, NULL); 272 | assert (pyddd_ipa_hit_flag == 2); 273 | 274 | pyddd_ipa_hit_flag = 0; 275 | pyddd_ipa_update_breakpoint(i, 1, 0, 0, NULL, 3, 1, 1, "foo.py"); 276 | ft(NULL, frame, PyTrace_LINE, NULL); 277 | ft(NULL, frame, PyTrace_LINE, NULL); 278 | ft(NULL, frame, PyTrace_LINE, NULL); 279 | ft(NULL, frame, PyTrace_LINE, NULL); 280 | ft(NULL, frame, PyTrace_LINE, NULL); 281 | assert (pyddd_ipa_hit_flag == 1); 282 | 283 | pyddd_ipa_hit_flag = 0; 284 | pyddd_ipa_update_breakpoint(i, 1, 0, 0, "i==1", 0, 1, 1, "foo.py"); 285 | ft(NULL, frame, PyTrace_LINE, NULL); 286 | assert (!pyddd_ipa_hit_flag); 287 | 288 | pyddd_ipa_hit_flag = 0; 289 | pyddd_ipa_update_breakpoint(i, 1, 0, 0, "i==2", 0, 1, 1, "foo.py"); 290 | ft(NULL, frame, PyTrace_LINE, NULL); 291 | assert (pyddd_ipa_hit_flag == 1); 292 | 293 | 294 | Py_DECREF((PyObject*)frame); 295 | 296 | #undef ft 297 | } 298 | 299 | int 300 | main(int argc, char **argv) 301 | { 302 | init_func(); 303 | test_find_name_in_list(); 304 | test_pyddd_ipa_insert_breakpoint(); 305 | 306 | Py_SetProgramName(argv[0]); 307 | Py_Initialize(); 308 | PySys_SetArgvEx(argc, argv, 0); 309 | 310 | test_pyddd_ipa_trace_trampoline(); 311 | test_pyddd_ipa_frame_variable(); 312 | 313 | test_pyddd_ipa_alter_variable(); 314 | test_pyddd_ipa_eval(); 315 | test_pyddd_ipa_frame_locals(); 316 | test_pyddd_ipa_frame_globals(); 317 | 318 | test_pyddd_ipa_format_object(); 319 | 320 | Py_Exit(0); 321 | return 0; 322 | } 323 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | 341 | -------------------------------------------------------------------------------- /pyddd.rst: -------------------------------------------------------------------------------- 1 | Python Debugger - PYDDD 2 | ======================= 3 | 4 | *PYDDD* is a super-GDB debugger which could debug python scripts as 5 | the same way to debug c program in the same inferior. 6 | 7 | *PYDDD* extends some corresponding GDB commands with prefix "py-" used 8 | to debug python scripts. For examble, `py-run`, `py-break`, `py-next` 9 | etc. Besides, some commands like "continue" work both c and python. 10 | 11 | Additionally, when debugging python extensions (written in c/c++), 12 | *PYDDD* could show extra information of python. For example, print a 13 | variable of PyObject* will show the value of both c pointer and python 14 | type (a list or dict etc.). And frame will show both c frame and 15 | python frame. 16 | 17 | Before debugging, start GDB. 18 | 19 | .. code-block:: bash 20 | 21 | $ cd /opt/pyddd 22 | $ gdb 23 | (gdb) source init.gdb 24 | (gdb) py-exec-file python 25 | 26 | .. note:: 27 | 28 | For Windows user, even GDB is running in the cygwin, you can also 29 | debug python scripts with native windows python by GDB :command: 30 | `file` 31 | 32 | .. code-block:: bat 33 | 34 | (gdb) py-exec-file C:/Python26/python.exe 35 | 36 | After that, see below chapters to debug your python scripts. 37 | 38 | Start Python Script 39 | =================== 40 | 41 | .. _py-file: 42 | 43 | * py-file filename 44 | 45 | Use filename as main python script. All the functions, classes and 46 | method in this script are read into memory. These symbols are used to 47 | specify breakpoint locations. 48 | 49 | * py-file string 50 | 51 | Use string as -c parameter pass to python program to run. 52 | 53 | * py-args arguments 54 | 55 | Set arguments of python scripts. 56 | 57 | * py-run 58 | 59 | Use the py-run command to run your python script. You must first 60 | specify python program by command :command: `py-exec-file`. 61 | 62 | Here is an example:: 63 | 64 | (gdb) py-exec-file C:/Python27/python.exe 65 | (gdb) py-exec-args -i 66 | (gdb) py-file foo.py 67 | (gdb) py-args -k 68 | (gdb) py-run 69 | 70 | gdb will execute the following shell command:: 71 | 72 | $ C:/Python27/python.exe -i foo.py -k 73 | 74 | * py-start 75 | 76 | Start python script and stop at the beginning of the main script. 77 | 78 | => py-tcatch call "" 79 | 80 | * py-exec-args arguments 81 | 82 | Set arguments for run python, not for python scripts. 83 | 84 | 85 | Stop Python Script 86 | ================== 87 | 88 | Breakpoint Location 89 | ------------------- 90 | 91 | * linenum 92 | 93 | Specifies the line number linenum of the current source file. 94 | 95 | * -offset 96 | * +offset 97 | 98 | Specifies the line offset lines before or after the current line. 99 | 100 | * filename:linenum 101 | 102 | Question: if code object in same file are equal in the different 103 | threads? 104 | 105 | * function 106 | 107 | function, or method of class. function is limited in the current 108 | running script or main script if there is no script running. 109 | 110 | Special function "__main__" stands for the start line of main script. 111 | 112 | * function:offset 113 | 114 | Offset must belong to the scope of function, otherwise it's invalid 115 | location. 116 | 117 | * filename:function 118 | 119 | * filename:function:offset 120 | 121 | A breakpoint with multiple locations is displayed in the breakpoint 122 | table using several rows—one header row, followed by one row for each 123 | breakpoint location. The number column for a location is of the form 124 | breakpoint-number.location-number. 125 | 126 | Each location can be individually enabled or disabled by passing 127 | breakpoint-number.location-number as argument to the enable and 128 | disable commands. Note that you cannot delete the individual locations 129 | from the list, you can only delete the entire list of locations that 130 | belong to their parent breakpoint. 131 | 132 | Typically, you would set a breakpoint in a python script at the 133 | beginning of your debugging session, when the symbols are not 134 | available. After python is running, whenever any module is imported, 135 | GDB reevaluates all the breakpoints. When any module is removed, all 136 | breakpoints that refer to its symbols or source lines become pending 137 | again. 138 | 139 | This logic works for breakpoints with multiple locations, too. 140 | 141 | Except for having unresolved address, pending breakpoints do not 142 | differ from regular breakpoints. You can set conditions or commands, 143 | enable and disable them and perform other breakpoint operations. 144 | 145 | Breakpoint Command 146 | ------------------ 147 | 148 | The meaning of syntax symbol: 149 | 150 | [ ] means optional argument 151 | 152 | | means either of list arguments 153 | 154 | * py-break [location] 155 | 156 | Set a breakpoint at the given location, which can specify a function 157 | name, a line number, filename:linenum, filename:function. 158 | 159 | filename:. means to break on every function in this file. 160 | 161 | If filename is a relative file name, then it will match any source 162 | file name with the same trailing components. 163 | 164 | When called without any arguments, break sets a breakpoint at the next 165 | instruction to be executed in the selected stack frame. 166 | 167 | * py-break [location] if cond 168 | 169 | Set a breakpoint with condition cond; evaluate the expression cond 170 | each time the breakpoint is reached, and stop only if the value is 171 | nonzero. 172 | 173 | Argument cond must be python expression, that is to say, no 174 | convenience variables which start with $ could be used here. 175 | 176 | If a breakpoint has a positive ignore count and a condition, the 177 | condition is not checked. Once the ignore count reaches zero, GDB 178 | resumes checking the condition. 179 | 180 | * py-rbreak regex 181 | 182 | Set breakpoints on all functions matching the regular expression 183 | regex. This command sets an unconditional breakpoint on all matches, 184 | printing a list of all breakpoints it set. Once these breakpoints are 185 | set, they are treated just like the breakpoints set with the break 186 | command. You can delete them, disable them, or make them conditional 187 | the same way as any other breakpoint. 188 | 189 | The syntax of the regular expression is the standard one used with 190 | tools like grep. Note that this is different from the syntax used by 191 | shells, so for instance foo* matches all functions that include an fo 192 | followed by zero or more os. There is an implicit .* leading and 193 | trailing the regular expression you supply, so to match only functions 194 | that begin with foo, use ^foo. 195 | 196 | It does nothing when no script is running, or no symbols loaded. 197 | 198 | * py-rbreak file:regex 199 | 200 | If rbreak is called with a filename qualification, it limits the 201 | search for functions matching the given regular expression to the 202 | specified file. 203 | 204 | This can be used, for example, to set breakpoints on every function in 205 | a given file: 206 | 207 | (gdb) py-rbreak file.py:.* 208 | 209 | The colon separating the filename qualifier from the regex may 210 | optionally be surrounded by spaces. 211 | 212 | * py-tbreak args 213 | 214 | Set a breakpoint enabled only for one stop. The args are the same as 215 | for the py-break command, and the breakpoint is set in the same way, 216 | but the breakpoint is automatically deleted after the first time your 217 | python script stops there. 218 | 219 | * py-clear 220 | 221 | Delete any breakpoints at the next instruction to be executed in the 222 | selected stack frame. When the innermost frame is selected, this is a 223 | good way to delete a breakpoint where your program just stopped. 224 | 225 | * py-clear location 226 | 227 | Delete any breakpoints set at the specified location. 228 | 229 | * py-delete [range] 230 | 231 | Delete the breakpoints of the breakpoint ranges specified as 232 | arguments. If no argument is specified, delete all python breakpoints. 233 | 234 | * py-disable [range] 235 | 236 | Disable the specified breakpoints—or all breakpoints, if none are 237 | listed. A disabled breakpoint has no effect but is not forgotten. All 238 | options such as ignore-counts, conditions and commands are remembered 239 | in case the breakpoint is enabled again later. 240 | 241 | * py-enable [range] 242 | 243 | Enable the specified breakpoints (or all defined breakpoints). They 244 | become effective once again in stopping your program. 245 | 246 | * py-enable once range 247 | 248 | Enable the specified breakpoints temporarily. GDB disables any of 249 | these breakpoints immediately after stopping your program. 250 | 251 | * py-enable count n range 252 | 253 | Enable the specified breakpoints temporarily. GDB records count with 254 | each of the specified breakpoints, and decrements a breakpoint’s count 255 | when it is hit. When any count reaches 0, GDB disables that 256 | breakpoint. If a breakpoint has an ignore count, that will be 257 | decremented to 0 before count is affected. 258 | 259 | * py-enable delete range 260 | 261 | Enable the specified breakpoints to work once, then die. GDB deletes 262 | any of these breakpoints as soon as your program stops 263 | there. Breakpoints set by the tbreak command start out in this state. 264 | 265 | * py-condition bnum expression 266 | 267 | Specify python expression as the break condition for python breakpoint 268 | number bnum. After you set a condition, breakpoint bnum stops your 269 | python script only if the value of expression is true. 270 | 271 | GDB does not actually evaluate expression at the time the condition 272 | command is given. 273 | 274 | * py-condition bnum 275 | 276 | Remove the condition from python breakpoint number bnum. It becomes an 277 | ordinary unconditional breakpoint. 278 | 279 | * py-ignore bnum count 280 | 281 | Set the ignore count of python breakpoint number bnum to count. The 282 | next count times the breakpoint is reached, your program’s execution 283 | does not stop; other than to decrement the ignore count, GDB takes no 284 | action. 285 | 286 | To make the breakpoint stop the next time it is reached, specify a 287 | count of zero. 288 | 289 | Python Catchpoint Command 290 | ========================= 291 | 292 | * py-catch exception * 293 | * py-catch exception name 294 | 295 | A python exception being raised. If an exception name is specified at 296 | the end of the command (eg catch exception PyExc_RuntimeError), the 297 | debugger will stop only when this specific exception is 298 | raised. Otherwise, the debugger stops execution when any Python 299 | exception is raised. 300 | 301 | "unhandle" is special exception name which is used to catch the 302 | exception not handled by the python script. 303 | 304 | * py-catch call function 305 | 306 | A function call to or return from python script. The function name is 307 | compared with co_name of code object in python script. 308 | 309 | '?' stands for one any character in argument name, argument name ends 310 | with "*" matches any same prefix. Especially a single asterisk matches any 311 | name. This command could reduce the performance. 312 | 313 | The following command can be used to debug embedded python statements 314 | in python script: 315 | 316 | (gdb) py-catch call 317 | 318 | * py-tcatch event 319 | 320 | Set a catchpoint that is enabled only for one stop. The catchpoint is 321 | automatically deleted after the first time the event is caught. 322 | 323 | * py-catch info 324 | 325 | Running Script Command 326 | ====================== 327 | 328 | * py-continue [ignore-count] 329 | 330 | Resume script execution, at the address where your script last 331 | stopped; any breakpoints set at that address are bypassed. The 332 | optional argument ignore-count allows you to specify a further number 333 | of times to ignore a breakpoint at this location; its effect is like 334 | that of ignore. 335 | 336 | The argument ignore-count is meaningful only when your script stopped 337 | due to a breakpoint. At other times, the argument to continue is 338 | ignored. 339 | 340 | * py-step [count] 341 | 342 | Continue running your script until control reaches a different source 343 | line, then stop it and return control to GDB. 344 | 345 | Also, the step command only enters a function of python extension if 346 | there is line number information for the function. Otherwise it acts 347 | like the next command. 348 | 349 | If specify count, continue running as in step, but do so count 350 | times. If a breakpoint is reached before count steps, stepping stops 351 | right away. 352 | 353 | * py-next [count] 354 | 355 | Continue to the next source line in the current stack frame. This is 356 | similar to step, but function calls that appear within the line of 357 | code are executed without stopping. Execution stops when control 358 | reaches a different line of code at the original stack level that was 359 | executing when you gave the next command. 360 | 361 | An argument count is a repeat count, as for step. 362 | 363 | * py-finish 364 | 365 | Continue running until just after function in the selected stack frame 366 | returns. Print the returned value (if any). 367 | 368 | * py-until 369 | 370 | Continue running until a source line past the current line, in the 371 | current stack frame, is reached. This command is used to avoid single 372 | stepping through a loop more than once. This means that when you reach 373 | the end of a loop after single stepping though it, until makes your 374 | script continue execution until it exits the loop. In contrast, a next 375 | command at the end of a loop simply steps back to the beginning of the 376 | loop, which forces you to step through the next iteration. 377 | 378 | until always stops your program if it attempts to exit the current 379 | stack frame. 380 | 381 | * py-until location 382 | 383 | Continue running your script until either the specified location is 384 | reached, or the current stack frame returns. This form of the command 385 | uses temporary breakpoints, and hence is quicker than until without an 386 | argument. The specified location is actually reached only if it is in 387 | the current frame. This implies that until can be used to skip over 388 | recursive function invocations. 389 | 390 | * py-advance location 391 | 392 | Continue running the script up to the given location. An argument is 393 | required, which should be of one of invalid location forms. Execution 394 | will also stop upon exit from the current stack frame. This command is 395 | similar to until, but advance will not skip over recursive function 396 | calls, and the target location doesn’t have to be in the same frame as 397 | the current one. 398 | 399 | Python Frame Command 400 | ==================== 401 | 402 | * py-frame [framespec] 403 | 404 | The frame command allows you to move from one stack frame to another, 405 | and to print the stack frame you select. The framespec may be either 406 | the function name of the frame or the stack frame number. Recall that 407 | frame zero is the innermost (currently executing) frame, frame one is 408 | the frame that called the innermost one, and so on. The 409 | highest-numbered frame is the one for PyEval_EvalFrameEx (or 410 | PyEval_EvalFrame). 411 | 412 | Without an argument, frame prints the current stack frame. 413 | 414 | * py-select-frame 415 | 416 | The select-frame command allows you to move from one stack frame to 417 | another without printing the frame. This is the silent version of 418 | frame. 419 | 420 | * py-up n 421 | 422 | Move n frames up the stack; n defaults to 1. For positive numbers n, 423 | this advances toward the outermost frame, to higher frame numbers, to 424 | frames that have existed longer. 425 | 426 | * py-down n 427 | 428 | Move n frames down the stack; n defaults to 1. For positive numbers n, 429 | this advances toward the innermost frame, to lower frame numbers, to 430 | frames that were created more recently. You may abbreviate down as do. 431 | 432 | * py-bt 433 | 434 | Print a backtrace of the entire stack: one line per frame for all 435 | frames in the stack. 436 | 437 | You can stop the backtrace at any time by typing the system interrupt 438 | character, normally Ctrl-c. backtrace n 439 | 440 | * py-bt n 441 | 442 | Similar, but print only the innermost n frames. 443 | 444 | * py-bt -n 445 | 446 | Similar, but print only the outermost n frames. 447 | 448 | * py-bt-full 449 | * py-bt-full n 450 | * py-bt-full -n 451 | 452 | Print the values of the local variables also. As described above, n 453 | specifies the number of frames to print. 454 | 455 | Python Data Command 456 | =================== 457 | 458 | * py-print /r expression 459 | 460 | Return str(PyObject*) or repr(PyObject*) if /r specified. If 461 | expression is valid in current frame, print error. 462 | 463 | * py-locals 464 | 465 | Print all locals as str(PyObject*) 466 | 467 | * py-locals varname 468 | 469 | Look up the given local python variable name, and print it 470 | 471 | * py-globals 472 | 473 | Print all globals as str(PyObject*) 474 | 475 | * py-globals varname 476 | 477 | Look up the given global python variable name, and print it 478 | 479 | Alert Python Variable 480 | ===================== 481 | 482 | * py-set-var name expression 483 | * py-set-var /global name expression 484 | 485 | Show Debug Parameters 486 | ===================== 487 | 488 | * py-info args 489 | * py-info exec-args 490 | * py-info main-script 491 | 492 | Example 493 | ======= 494 | 495 | beer.py queens.py life.py 496 | 497 | (gdb) source init.gdb 498 | 499 | (gdb) py-exec-file python 500 | (gdb) py-file beer.py 501 | (gdb) py-start 502 | 503 | 504 | Known Issues 505 | ============ 506 | 507 | * Missing object entry in multi-thread script maybe. 508 | 509 | It's possible we'll miss some code object when debug python 510 | multi-thread scripts, if it matches the following conditios: 511 | 512 | - One thread stop by a breakpoint 513 | - Debug threads in non-stop mode 514 | - The other running thread is about to create new code object 515 | 516 | Because we hook PyCode_New by command list of c breakpoint, in 517 | non-stop mode, that c breakpoint is ignored. So when PyCode_New 518 | called, no object entry is created. 519 | 520 | Appendix 521 | ======== 522 | 523 | How to find address of "trace_trampoline" from python library in gdb :: 524 | 525 | - (gdb) exec C:/Python27/python.exe 526 | - (gdb) set args -i 527 | - (gdb) b PyEval_EvalFrameEx 528 | No symbol table is loaded. Use the "file" command. 529 | Make breakpoint pending on future shared library load? (y or [n]) y 530 | Breakpoint 1 (PyEval_EvalFrameEx) pending. 531 | - (gdb) run 532 | Starting program: /cygdrive/c/Python27/python.exe -i 533 | [New Thread 4084.0xcc8] 534 | Breakpoint 1, 0x1e00f363 in python27!PyEval_EvalFrameEx () 535 | from /cygdrive/c/WINDOWS/system32/python27.dll 536 | - (gdb) call PyCFunction_GetFunction(PyDict_GetItemString(PyModule_GetDict(PyImport_AddModule("sys")), "settrace")) 537 | $1 = 503847580 538 | - (gdb) p /x $1 539 | $2 = 0x1e081a9c 540 | - (gdb) x /15i $2 541 | 0x1e081a9c : 542 | call 0x1e067c6c 543 | 0x1e081aa1 : cmp $0xffffffff,%eax 544 | 0x1e081aa4 : 545 | jne 0x1e081aa9 546 | 0x1e081aa6 : xor %eax,%eax 547 | 0x1e081aa8 : ret 548 | 0x1e081aa9 : push %esi 549 | 0x1e081aaa : mov $0x1e1ed8c4,%esi 550 | 0x1e081aaf : cmp %esi,0xc(%esp) 551 | 0x1e081ab3 : 552 | jne 0x1e081abb 553 | 0x1e081ab5 : push $0x0 554 | 0x1e081ab7 : push $0x0 555 | 0x1e081ab9 : 556 | jmp 0x1e081ac4 557 | 0x1e081abb : pushl 0xc(%esp) 558 | 0x1e081abf : push $0x1e0d6dfe 559 | 0x1e081ac4 : 560 | call 0x1e05a827 561 | 562 | Before PyEval_SetTrace, push $0x1e0d6dfe, this is what I want 563 | 564 | - (gdb) b *0x1e0d6dfe 565 | - (gdb) call PyEval_SetTrace(0x1e0d6dfe, 0) 566 | 567 | * Build gdb with python and python ipa :: 568 | 569 | $ tar xzf gdb-7.8.1.tar.gz 570 | $ cd gdb-7.8.1 571 | 572 | Hack gdb/configure, replace ncurses with ncursesw, after configure, 573 | add -lncursesw in Makefile :: 574 | 575 | $ ./configure --with-python=python --with-babeltrace=no --enable-tui=no --enable-host-shared 576 | $ make 577 | $ i686-pc-mingw32-gcc -shared -g -I/cygdrive/c/Python27/include -Wl,-lpthread -o pyddd-ipa.dll ipa.c 578 | 579 | 580 | * Print PyCodeObject created by PyCode_New :: 581 | 582 | Prototype of PyCode_New :: 583 | 584 | PyCodeObject * PyCode_New( 585 | int argcount, int nlocals, int stacksize, int flags, 586 | PyObject *code, PyObject *consts, PyObject *names, 587 | PyObject *varnames, PyObject *freevars, PyObject *cellvars, 588 | PyObject *filename, PyObject *name, int firstlineno, 589 | PyObject *lnotab); 590 | 591 | So :: 592 | 593 | filename => $ebp + 0x30 594 | name => $ebp + 0x34 595 | 596 | Then :: 597 | 598 | (gdb) break PyCode_New 599 | commands 600 | silent 601 | p (char*)PyString_AsString({PyObject*}($ebp+0x30)) 602 | p (char*)PyString_AsString({PyObject*}($ebp+0x34)) 603 | p (int)({int*}($ebp+0x38)) 604 | # call pyddd_ipa_new_code_object_hook( 605 | # {PyObject*}($ebp+0x30), 606 | # {PyObject*}($ebp+0x34), 607 | # (int)({int*}($ebp+0x38)), 608 | # {PyObject*}($ebp+0x3c) 609 | # ) 610 | continue 611 | end 612 | 613 | * How to start at the begin of running script: 614 | 615 | Add a temporary catch, as the following command: 616 | 617 | (gdb) py-tcatch call "" 618 | 619 | It will stop as soon as the main script to start at first line. 620 | 621 | * The available variables when hit a breakpoint: 622 | 623 | - PyFrameObject *pyddd_ipa_current_frame, 624 | - long pyddd_ipa_current_thread 625 | - char *pyddd_ipa_current_filename 626 | - int pyddd_ipa_current_lineno 627 | 628 | Extra-names for catch point: 629 | 630 | - char *pyddd_ipa_current_funcname when catch a call 631 | - char *pyddd_ipa_current_excname when catch a exception 632 | 633 | Extra-names for normal breakpoint: 634 | 635 | - int pyddd_ipa_current_breakpoint->bpnum 636 | - int pyddd_ipa_current_breakpoint->locnum 637 | 638 | 639 | * pyddd command map list 640 | 641 | Note that not all of gdb commands have mapped python commands. Some 642 | work both c and python, the others aren't no python's. 643 | 644 | - Watchpoint/Catchpoint/Tracepoint aren't supported 645 | 646 | - GDB convenience variables aren't recognized in python breakpoint's 647 | condition. 648 | 649 | Exactly to say, it's invalid to mix python expression and GDB 650 | convenience variables. 651 | -------------------------------------------------------------------------------- /ipa.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Version number will finally following golden mean 1.6180339... 3 | * 4 | * - Start with version information of "0.1.1" ("major.version.release") 5 | * - Increase "release" if source code has changed at all since the 6 | * last update 7 | * - Increase "version" if any interfaces or export variables has been 8 | * added, removed or changed. If only added something, incease the 9 | * "release" consecutively. In any other case, reset it to "1". 10 | * - If version increased to "0.6.?", next version would be "1.6.1". 11 | * 12 | * The "1.6.1" would be final version, all the interfaces and export 13 | * variables of pyddd-ipa will not be changed at all. After that, 14 | * each release will first append a "0" at the end of old version, 15 | * then increase it up to the golden mean again. For example, 16 | * 17 | * 1.6.1.0, 1.6.1.1, ... 1.6.1.8 18 | * 1.6.1.8.0, 19 | * 1.6.1.8.0.0, 1.6.1.0.1, 1.6.1.0.2, 1.6.1.0.3 20 | * 21 | * Change Logs: 22 | * 23 | * - Version 0.1.1 jondy.zhao@gmail.com 2014-12-24 24 | * * First release. 25 | * 26 | */ 27 | #include "ipa.h" 28 | static const char *PYDDD_IPA_VERSION = "0.1.2"; 29 | 30 | #if defined(TEST_IPA) 31 | #define static 32 | #endif 33 | 34 | static char* find_name_in_list(const char *name, const char *namelist); 35 | static void pyddd_ipa_set_volatile_breakpoint(const int enabled, 36 | const long thread_id, 37 | PyFrameObject *f_frame, 38 | PyObject* co_filename, 39 | const int lineno); 40 | 41 | static pthread_mutex_t mutex_hit_count = PTHREAD_MUTEX_INITIALIZER; 42 | static pthread_mutex_t mutex_object_entry = PTHREAD_MUTEX_INITIALIZER; 43 | 44 | struct pyddd_ipa_t_volatile_breakpoint pyddd_ipa_volatile_breakpoint={0}; 45 | struct pyddd_ipa_t_breakpoint 46 | pyddd_ipa_breakpoint_table[PYDDD_IPA_MAX_BREAKPOINT]={0}; 47 | 48 | int pyddd_ipa_breakpoint_top=PYDDD_IPA_BREAKPOINT_PAGE; 49 | /* Only increased to avoid crash in multi-threads */ 50 | int pyddd_ipa_breakpoint_counter=0; 51 | 52 | /* Set when any internal python breakpoint is hit */ 53 | int pyddd_ipa_hit_flag=0; 54 | 55 | char * pyddd_ipa_python_catch_exceptions=NULL; 56 | char * pyddd_ipa_python_catch_functions=NULL; 57 | 58 | /* Use function pointer, so python-ipa.dll need not bind to special 59 | python version */ 60 | char* (*FPyString_AsString)(PyObject *o)=NULL; 61 | int (*FPyFrame_GetLineNumber)(PyFrameObject *frame)=NULL; 62 | void (*FPy_DecRef)(PyObject *)=NULL; 63 | int (*FPyObject_IsTrue)(PyObject *)=NULL; 64 | Py_ssize_t (*FPyString_Size)(PyObject *string)=NULL; 65 | void (*FPyEval_SetTrace)(Py_tracefunc func, PyObject *arg)=NULL; 66 | PyThreadState* (*FPyThreadState_Get)(void)=NULL; 67 | PyObject* (*FPyObject_Str)(PyObject *o)=NULL; 68 | PyObject* (*FPyObject_Repr)(PyObject *o)=NULL; 69 | PyObject* (*FPyTuple_GetItem)(PyObject *p, Py_ssize_t pos)=NULL; 70 | PyObject* (*FPyDict_GetItem)(PyObject *p, PyObject *key)=NULL; 71 | PyObject* (*FPyDict_GetItemString)(PyObject *p, const char *key)=NULL; 72 | int (*FPyDict_SetItem)(PyObject *p, PyObject *key, PyObject *val)=NULL; 73 | int (*FPyDict_SetItemString)(PyObject *p, 74 | const char *key, 75 | PyObject *val)=NULL; 76 | int (*FPyDict_Next)(PyObject *p, 77 | Py_ssize_t *ppos, 78 | PyObject **pkey, 79 | PyObject **pvalue)=NULL; 80 | PyObject* (*FPyDict_Keys)(PyObject *p)=NULL; 81 | 82 | PyObject* (*FPyObject_Type)(PyObject *o)=NULL; 83 | PyObject* (*FPyObject_GetIter)(PyObject *o)=NULL; 84 | PyObject* (*FPyIter_Next)(PyObject *o)=NULL; 85 | 86 | void (*FPyErr_Clear)(void)=NULL; 87 | void (*FPyErr_PrintEx)(int set_sys_last_vars)=NULL; 88 | PyObject* (*FPyErr_Occurred)(void)=NULL; 89 | PyObject* (*FPy_CompileStringFlags)(const char *str, 90 | const char *filename, 91 | int start, 92 | PyCompilerFlags *flags)=NULL; 93 | PyObject* (*FPyEval_EvalCode)(PyCodeObject *co, 94 | PyObject *globals, 95 | PyObject *locals)=NULL; 96 | 97 | const char * 98 | pyddd_ipa_version(void) 99 | { 100 | return PYDDD_IPA_VERSION; 101 | } 102 | 103 | /* Rename varialbe in breakpoint context to avoid name conflict */ 104 | #define frame pyddd_ipa_current_frame 105 | #define thread pyddd_ipa_current_thread 106 | #define _filename pyddd_ipa_current_filename 107 | #define _lineno pyddd_ipa_current_lineno 108 | #define bp pyddd_ipa_current_breakpoint 109 | #define name pyddd_ipa_current_funcname 110 | #define excname pyddd_ipa_current_excname 111 | 112 | int 113 | pyddd_ipa_trace_trampoline(PyObject *self, 114 | PyFrameObject *frame, 115 | int what, 116 | PyObject *arg) 117 | { 118 | register long thread = frame->f_tstate->thread_id; 119 | register int _lineno = (*FPyFrame_GetLineNumber)(frame); 120 | PyObject *co_filename = frame->f_code->co_filename; 121 | register char *_filename = (*FPyString_AsString)(co_filename); 122 | 123 | /* py-catch call: 124 | Search name within pyddd_ipa_python_catch_functions. 125 | */ 126 | if (what == PyTrace_CALL) { 127 | char *name=(*FPyString_AsString)(frame->f_code->co_name); 128 | if (pyddd_ipa_python_catch_functions \ 129 | && find_name_in_list(name, pyddd_ipa_python_catch_functions)) { 130 | pyddd_ipa_volatile_breakpoint.enabled = 0; 131 | asm("pyddd_ipa_catch_call_addr:"); 132 | pyddd_ipa_hit_flag ++; 133 | }; 134 | return 0; 135 | } 136 | 137 | /* py-catch exception: 138 | Search exception name within pyddd_ipa_python_catch_exceptions. 139 | */ 140 | else if (what == PyTrace_EXCEPTION) { 141 | char *excname=(char*)((PyTypeObject*) 142 | ((*FPyTuple_GetItem)(arg, 0)))->tp_name; 143 | if (pyddd_ipa_python_catch_exceptions 144 | && find_name_in_list(excname, 145 | pyddd_ipa_python_catch_exceptions)) { 146 | pyddd_ipa_volatile_breakpoint.enabled = 0; 147 | asm("pyddd_ipa_catch_exception_addr:"); 148 | pyddd_ipa_hit_flag ++; 149 | }; 150 | return 0; 151 | } 152 | 153 | if (what == PyTrace_LINE) { 154 | 155 | /* Check volatile breakpoint */ 156 | { 157 | register struct pyddd_ipa_t_volatile_breakpoint *vp; 158 | vp = &pyddd_ipa_volatile_breakpoint; 159 | if (vp->enabled 160 | && vp->thread_id == thread 161 | && (!vp->lineno 162 | || (vp->lineno>0 && vp->lineno == _lineno) 163 | || (vp->lineno<0 && -vp->lineno<_lineno)) 164 | && (!vp->f_frame || vp->f_frame == frame) 165 | && (!vp->co_filename || vp->co_filename == co_filename)) { 166 | vp->enabled --; 167 | if (!vp->enabled) { 168 | asm("pyddd_ipa_volatile_breakpoint_addr:"); 169 | pyddd_ipa_hit_flag ++; 170 | return 0; 171 | } 172 | } 173 | } 174 | 175 | /* Check normal breakpoints which at filename:lineno exactly. */ 176 | if (_filename) { 177 | register struct pyddd_ipa_t_breakpoint *bp; 178 | int filename_size=0; 179 | int rindex; 180 | 181 | bp = pyddd_ipa_breakpoint_table; 182 | for (rindex = 0; 183 | rindex < pyddd_ipa_breakpoint_counter; 184 | rindex++, bp++) { 185 | 186 | /* Ignore deleted bpnum, disabled bpnum, not lineno, not 187 | thread */ 188 | if (!bp->bpnum 189 | || !bp->enabled 190 | || (bp->thread_id && bp->thread_id != thread) 191 | || _lineno != bp->lineno) { 192 | if (bp->enabled < 0) ++(bp->enabled); 193 | continue; 194 | } 195 | if (bp->enabled < 0) ++(bp->enabled); 196 | 197 | /* Good preformace, but need more complex mechanism */ 198 | if (bp->co_filename && bp->co_filename != co_filename) 199 | continue; 200 | 201 | /* First compare the filename size so that we can quickly 202 | exclude most of files. */ 203 | if (!filename_size) 204 | filename_size = strlen(_filename); 205 | if (filename_size != bp->filename_size 206 | || strcmp(bp->filename, _filename)) 207 | continue; 208 | 209 | pthread_mutex_lock (&mutex_hit_count); 210 | bp->hit_count ++; 211 | /* Take ignore_count into account */ 212 | if (bp->ignore_count) { 213 | if (bp->hit_count == bp->ignore_count) 214 | bp->hit_count = 0; 215 | else { 216 | pthread_mutex_unlock (&mutex_hit_count); 217 | continue; 218 | } 219 | } 220 | pthread_mutex_unlock (&mutex_hit_count); 221 | 222 | /* Eval breakpoint condition */ 223 | if (bp->condition) { 224 | PyObject *exprobj; 225 | PyObject *result; 226 | 227 | /* Use empty filename to avoid obj added to object entry table */ 228 | exprobj = (*FPy_CompileStringFlags)(bp->condition, 229 | "", 230 | Py_eval_input, 231 | NULL 232 | ); 233 | if (!exprobj) { 234 | (*FPyErr_Clear)(); 235 | continue; 236 | } 237 | 238 | /* Clear flag use_tracing in current PyThreadState to avoid 239 | tracing evaluation self, but if the evluation expression 240 | includes some call of c function, and there is some 241 | breakpoint hit, I don't know what will happen. 242 | */ 243 | frame->f_tstate->use_tracing = 0; 244 | result = (*FPyEval_EvalCode)((PyCodeObject*)exprobj, 245 | frame->f_globals, 246 | frame->f_locals); 247 | frame->f_tstate->use_tracing = 1; 248 | (*FPy_DecRef)(exprobj); 249 | 250 | if (result == NULL) { 251 | (*FPyErr_Clear)(); 252 | continue; 253 | } 254 | 255 | if ((*FPyObject_IsTrue)(result) != 1) { 256 | (*FPy_DecRef)(result); 257 | continue; 258 | } 259 | (*FPy_DecRef)(result); 260 | } 261 | 262 | /* Here is c breakpoint in GDB */ 263 | pyddd_ipa_volatile_breakpoint.enabled = 0; 264 | asm("pyddd_ipa_breakpoint_addr:"); 265 | pyddd_ipa_hit_flag ++; 266 | break; 267 | } 268 | } 269 | } 270 | return 0; 271 | } 272 | 273 | int 274 | pyddd_ipa_profile_trampoline(PyObject *self, 275 | PyFrameObject *frame, 276 | int what, 277 | PyObject *arg) 278 | { 279 | return 0; 280 | } 281 | 282 | #undef frame 283 | #undef thread 284 | #undef _filename 285 | #undef _lineno 286 | #undef bp 287 | #undef name 288 | #undef excname 289 | 290 | 291 | /* 292 | * Format of namelist: 293 | * 294 | * - Separating name pattern by one whitespace. 295 | * - '?' stands for one any character in name pattern 296 | * - Name patterns start with '*' match any same prefix. 297 | * Espcially, name pattern "*" matches any name. 298 | * - Name patterns end with '*' match any same suffix. 299 | * 300 | */ 301 | static char * 302 | find_name_in_list(const char *name, const char *namelist) 303 | { 304 | register char *s=(char*)name; 305 | register char *p=(char*)namelist; 306 | if (s && *s && p && *p) 307 | while (*p) { 308 | while (*s && (*p == *s || *p == '?')) 309 | p++, s++; 310 | /* name end */ 311 | if (!*s) { 312 | if (!*p || *p == '*' || *p == ' ') 313 | return (char*)name; 314 | } 315 | /* asterisk match */ 316 | else if (*p == '*') { 317 | p++; 318 | /* suffix match */ 319 | if (*p == 0 || *p == ' ') 320 | return (char*)name; 321 | /* prefix match */ 322 | if (*s) { 323 | register char *t=p; 324 | int i=0; 325 | int j=strlen(s); 326 | while (*t && *t != ' ' && (++i)) 327 | t++; 328 | if (i <= j) { 329 | s += j; 330 | do { 331 | t--, s--; 332 | } while (*t==*s && i--); 333 | if (!i) 334 | return (char*)name; 335 | } 336 | } 337 | } 338 | /* next pattern */ 339 | s = (char*)name; 340 | if ((p=strchr(p, ' ')) == NULL) 341 | break; 342 | p++; 343 | } 344 | return NULL; 345 | } 346 | 347 | /* 348 | * When you insert/update/delete breakpoints in pyddd-ipa, to be 349 | * sure the intefior is suspend, and there is no any running 350 | * thread. Or any other thread runs in non-stop mode. That is to say, 351 | * they can't read/write the breakpoints. 352 | */ 353 | void 354 | pyddd_ipa_update_breakpoint(const int rindex, 355 | const int bpnum, 356 | const int locnum, 357 | const long thread_id, 358 | const char *condition, 359 | const int ignore_count, 360 | const int enabled, 361 | const int lineno, 362 | const char *filename 363 | ) 364 | { 365 | register struct pyddd_ipa_t_breakpoint *p; 366 | assert (rindex >= 0 && rindex < PYDDD_IPA_MAX_BREAKPOINT); 367 | assert (bpnum>0); 368 | assert (filename); 369 | p = pyddd_ipa_breakpoint_table + rindex; 370 | 371 | p->locnum = locnum; 372 | p->thread_id = thread_id; 373 | p->condition = (char*)condition; 374 | p->ignore_count = ignore_count; 375 | p->hit_count = 0; 376 | p->enabled = enabled; 377 | p->lineno = lineno; 378 | p->filename = (char*)filename; 379 | p->filename_size = strlen(filename); 380 | p->co_filename = NULL; 381 | p->bpnum = bpnum; 382 | } 383 | 384 | int 385 | pyddd_ipa_insert_breakpoint(const int bpnum, 386 | const int locnum, 387 | const long thread_id, 388 | const char *condition, 389 | const int ignore_count, 390 | const int enabled, 391 | const int lineno, 392 | const char *filename 393 | ) 394 | { 395 | register struct pyddd_ipa_t_breakpoint *p; 396 | register int rindex = pyddd_ipa_breakpoint_counter; 397 | 398 | /* Find a index in breakpoint table */ 399 | if (rindex < pyddd_ipa_breakpoint_top) 400 | pyddd_ipa_breakpoint_counter ++; 401 | else { 402 | for (rindex = 0, p = pyddd_ipa_breakpoint_table; 403 | rindex < PYDDD_IPA_MAX_BREAKPOINT; 404 | rindex++, p++) { 405 | if (!p->bpnum) 406 | break; 407 | } 408 | if (rindex >= PYDDD_IPA_MAX_BREAKPOINT) 409 | return -1; 410 | if (rindex >= pyddd_ipa_breakpoint_top) { 411 | assert (pyddd_ipa_breakpoint_top < PYDDD_IPA_MAX_BREAKPOINT); 412 | pyddd_ipa_breakpoint_top += PYDDD_IPA_BREAKPOINT_PAGE; 413 | pyddd_ipa_breakpoint_counter ++; 414 | } 415 | } 416 | 417 | /* Insert a breakpoint entry */ 418 | p = pyddd_ipa_breakpoint_table + rindex; 419 | pyddd_ipa_update_breakpoint(rindex, bpnum, locnum, thread_id, 420 | condition, ignore_count, enabled, 421 | lineno, filename); 422 | 423 | return rindex; 424 | } 425 | 426 | void 427 | pyddd_ipa_remove_breakpoint(const int rindex) 428 | { 429 | if (rindex >= 0 430 | && rindex < PYDDD_IPA_MAX_BREAKPOINT 431 | && pyddd_ipa_breakpoint_table[rindex].bpnum > 0) { 432 | /* In order to support multi-threads, don't decrease counter */ 433 | pyddd_ipa_breakpoint_table[rindex].bpnum = 0; 434 | } 435 | } 436 | 437 | static void 438 | pyddd_ipa_set_volatile_breakpoint(const int enabled, 439 | const long thread_id, 440 | PyFrameObject *f_frame, 441 | PyObject* co_filename, 442 | const int lineno) 443 | { 444 | register struct pyddd_ipa_t_volatile_breakpoint *vp; 445 | assert(thread_id); 446 | 447 | vp = &pyddd_ipa_volatile_breakpoint; 448 | vp->enabled = enabled; 449 | vp->thread_id = thread_id; 450 | vp->f_frame = f_frame; 451 | vp->co_filename = co_filename; 452 | vp->lineno = lineno; 453 | } 454 | 455 | /* Running command: step, next, finish, advance, untill, advance */ 456 | int 457 | pyddd_ipa_step_command(int count) 458 | { 459 | register PyThreadState *tstate=(*FPyThreadState_Get)(); 460 | assert (count); 461 | pyddd_ipa_set_volatile_breakpoint(count, 462 | tstate->thread_id, 463 | NULL, 464 | NULL, 465 | 0 466 | ); 467 | return 0; 468 | } 469 | 470 | int 471 | pyddd_ipa_next_command(int count) 472 | { 473 | register PyThreadState *tstate=(*FPyThreadState_Get)(); 474 | assert (count); 475 | pyddd_ipa_set_volatile_breakpoint(count, 476 | tstate->thread_id, 477 | tstate->frame, 478 | NULL, 479 | 0 480 | ); 481 | return 0; 482 | } 483 | 484 | int 485 | pyddd_ipa_finish_command(void) 486 | { 487 | register PyThreadState *tstate=(*FPyThreadState_Get)(); 488 | register PyFrameObject *frame=tstate->frame; 489 | pyddd_ipa_set_volatile_breakpoint(1, 490 | tstate->thread_id, 491 | frame->f_back, 492 | NULL, 493 | 0 494 | ); 495 | return 0; 496 | } 497 | 498 | int 499 | pyddd_ipa_until_command(int lineno) 500 | { 501 | register PyThreadState *tstate=(*FPyThreadState_Get)(); 502 | register PyFrameObject *frame=tstate->frame; 503 | if (!lineno) 504 | lineno = -(*FPyFrame_GetLineNumber)(frame); 505 | pyddd_ipa_set_volatile_breakpoint(1, 506 | tstate->thread_id, 507 | frame->f_back, 508 | NULL, 509 | lineno 510 | ); 511 | return 0; 512 | } 513 | 514 | int 515 | pyddd_ipa_advance_command(int lineno) 516 | { 517 | register PyThreadState *tstate=(*FPyThreadState_Get)(); 518 | PyObject *co_filename = tstate->frame->f_code->co_filename; 519 | pyddd_ipa_set_volatile_breakpoint(1, 520 | tstate->thread_id, 521 | NULL, 522 | co_filename, 523 | lineno 524 | ); 525 | return 0; 526 | } 527 | 528 | /* 529 | * Frame routines 530 | */ 531 | PyFrameObject * 532 | pyddd_ipa_frame_older(PyFrameObject *frame) 533 | { 534 | if (frame) 535 | return frame->f_back; 536 | return NULL; 537 | } 538 | 539 | PyFrameObject * 540 | pyddd_ipa_frame_lookup(PyFrameObject *frame, const char *name) 541 | { 542 | if (frame && name && *name) 543 | while (strcmp(name, (*FPyString_AsString)(frame->f_code->co_name)) 544 | && (frame = frame->f_back)); 545 | return frame; 546 | } 547 | 548 | const char * 549 | pyddd_ipa_frame_filename(PyFrameObject *frame) 550 | { 551 | if (!frame) 552 | frame=(*FPyThreadState_Get)()->frame; 553 | assert (frame); 554 | return (*FPyString_AsString)(frame->f_code->co_filename); 555 | } 556 | 557 | int 558 | pyddd_ipa_frame_lineno(PyFrameObject *frame) 559 | { 560 | if (!frame) 561 | frame=(*FPyThreadState_Get)()->frame; 562 | assert (frame); 563 | return (*FPyFrame_GetLineNumber)(frame); 564 | } 565 | 566 | const char * 567 | pyddd_ipa_frame_name(PyFrameObject *frame) 568 | { 569 | if (!frame) 570 | frame=(*FPyThreadState_Get)()->frame; 571 | assert (frame); 572 | return (*FPyString_AsString)(frame->f_code->co_name); 573 | } 574 | 575 | const char * 576 | pyddd_ipa_frame_locals(PyFrameObject *frame) 577 | { 578 | assert (frame); 579 | if (frame->f_locals) 580 | return (*FPyString_AsString) \ 581 | ((*FPyObject_Repr)((*FPyDict_Keys)(frame->f_locals))); 582 | return pyddd_ipa_frame_varnames(frame); 583 | } 584 | 585 | const char * 586 | pyddd_ipa_frame_globals(PyFrameObject *frame) 587 | { 588 | assert (frame); 589 | assert (frame->f_globals); 590 | return (*FPyString_AsString)\ 591 | ((*FPyObject_Repr)((*FPyDict_Keys)(frame->f_globals))); 592 | } 593 | 594 | const char * 595 | pyddd_ipa_frame_varnames(PyFrameObject *frame) 596 | { 597 | assert (frame); 598 | return (*FPyString_AsString)((*FPyObject_Repr)(frame->f_code->co_varnames)); 599 | } 600 | 601 | const char * 602 | pyddd_ipa_frame_cellvars(PyFrameObject *frame) 603 | { 604 | assert (frame); 605 | return (*FPyString_AsString)((*FPyObject_Repr)(frame->f_code->co_cellvars)); 606 | } 607 | 608 | /* 609 | * Get values from frame, index == -1, means got from globals 610 | */ 611 | const char * 612 | pyddd_ipa_frame_values(PyFrameObject *frame, char *varname, int index) 613 | { 614 | assert (frame); 615 | if (index == -1) { 616 | PyObject *key, *value; 617 | Py_ssize_t pos = 0; 618 | while ((*FPyDict_Next)(frame->f_globals, &pos, &key, &value)) 619 | if (key && value) 620 | if (!strcmp(varname, (*FPyString_AsString)((*FPyObject_Str)(key)))) 621 | return pyddd_ipa_format_object(value); 622 | if ((*FPyErr_Occurred)()) 623 | (*FPyErr_PrintEx)(0); 624 | return NULL; 625 | } 626 | return pyddd_ipa_format_object(frame->f_localsplus[index]); 627 | } 628 | 629 | const char * 630 | pyddd_ipa_frame_variable(PyFrameObject *frame, char *varname, int global) 631 | { 632 | PyObject *key, *value; 633 | Py_ssize_t pos = 0; 634 | assert (frame); 635 | while ((*FPyDict_Next)(global ? frame->f_globals : frame->f_locals, 636 | &pos, 637 | &key, 638 | &value 639 | )) 640 | if (key && value) 641 | if (!strcmp(varname, (*FPyString_AsString)((*FPyObject_Str)(key)))) 642 | return pyddd_ipa_format_object(value); 643 | if ((*FPyErr_Occurred)()) 644 | (*FPyErr_PrintEx)(0); 645 | return NULL; 646 | } 647 | 648 | /* Alter variables in python */ 649 | int 650 | pyddd_ipa_alter_variable(PyFrameObject *frame, 651 | char *name, 652 | char *expr, 653 | int global 654 | ) 655 | { 656 | PyObject *exprobj; 657 | PyObject *result; 658 | int oldvalue; 659 | PyThreadState *tstate=(*FPyThreadState_Get)(); 660 | 661 | /* Use empty filename to avoid obj added to object entry table */ 662 | exprobj = (*FPy_CompileStringFlags)(expr, "", Py_eval_input, NULL); 663 | if (!exprobj) { 664 | if ((*FPyErr_Occurred)()) 665 | (*FPyErr_PrintEx)(0); 666 | return 1; 667 | } 668 | 669 | if (!frame) 670 | frame = tstate->frame; 671 | assert (frame); 672 | 673 | oldvalue = tstate->use_tracing; 674 | tstate->use_tracing = 0; 675 | result = (*FPyEval_EvalCode)((PyCodeObject*)exprobj, 676 | frame->f_globals, 677 | frame->f_locals 678 | ); 679 | tstate->use_tracing = oldvalue; 680 | (*FPy_DecRef)(exprobj); 681 | 682 | if (result){ 683 | PyObject *p, *key, *value; 684 | Py_ssize_t pos = 0; 685 | p = global ? frame->f_globals : frame->f_locals; 686 | while ((*FPyDict_Next)(p, &pos, &key, &value)) 687 | if (key && value) 688 | if (!strcmp(name, (*FPyString_AsString)((*FPyObject_Str)(key)))) 689 | return (*FPyDict_SetItem)(p, key, result); 690 | } 691 | if ((*FPyErr_Occurred)()) 692 | (*FPyErr_PrintEx)(0); 693 | if (result) 694 | (*FPy_DecRef)(result); 695 | return 1; 696 | } 697 | 698 | const char * 699 | pyddd_ipa_eval(PyFrameObject *frame, char *expr) 700 | { 701 | PyObject *exprobj; 702 | PyObject *result; 703 | PyThreadState *tstate = (*FPyThreadState_Get)(); 704 | int oldvalue; 705 | assert (expr); 706 | 707 | /* Use empty filename to avoid obj added to object entry table */ 708 | exprobj = (*FPy_CompileStringFlags)(expr, "", Py_eval_input, NULL); 709 | if (!exprobj) { 710 | if ((*FPyErr_Occurred)()) 711 | (*FPyErr_PrintEx)(0); 712 | return NULL; 713 | } 714 | 715 | if (!frame) 716 | frame = tstate -> frame; 717 | oldvalue = tstate->use_tracing; 718 | tstate->use_tracing = 0; 719 | result = (*FPyEval_EvalCode)((PyCodeObject*)exprobj, 720 | frame->f_globals, 721 | frame->f_locals 722 | ); 723 | tstate->use_tracing = oldvalue; 724 | (*FPy_DecRef)(exprobj); 725 | 726 | if (!result){ 727 | if ((*FPyErr_Occurred)()) 728 | (*FPyErr_PrintEx)(0); 729 | return NULL; 730 | } 731 | 732 | (*FPy_DecRef)(result); 733 | /* use the result before gc works */ 734 | return (*FPyString_AsString)((*FPyObject_Repr)(result)); 735 | } 736 | 737 | const char * 738 | pyddd_ipa_iter_next(PyObject *iter) 739 | { 740 | PyObject *item=NULL; 741 | if (!iter) 742 | if (!(item = (*FPyIter_Next)(iter))) 743 | (*FPy_DecRef)(iter); 744 | if (!item) 745 | return NULL; 746 | (*FPy_DecRef)(item); 747 | return (*FPyString_AsString)(item); 748 | } 749 | 750 | /* 751 | * int, string, float => repr(o) 752 | * Others => ClsName 753 | */ 754 | const char * 755 | pyddd_ipa_format_object(PyObject *o) 756 | { 757 | assert (o); 758 | const char *t=((PyTypeObject*)(*FPyObject_Type)(o))->tp_name; 759 | if (!strcmp(t, "int") \ 760 | && !strcmp(t, "long") 761 | && !strcmp(t, "float") 762 | && !strcmp(t, "str") 763 | && !strcmp(t, "unicode")) 764 | return t; 765 | return (*FPyString_AsString)((*FPyObject_Repr)(o)); 766 | } 767 | 768 | /* end of ipa.c */ 769 | -------------------------------------------------------------------------------- /libddd.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # @File: libddd.py 5 | # 6 | # @Author: Jondy Zhao(jondy.zhao@gmail.com) 7 | # 8 | # @Create Date: 2014/12/18 9 | # 10 | # @Description: 11 | # 12 | # GDB Extension script. 13 | # 14 | from __future__ import with_statement 15 | 16 | import ast 17 | import fnmatch 18 | import locale 19 | import os 20 | import sys 21 | import string 22 | 23 | import gdb 24 | from gdb.FrameDecorator import FrameDecorator 25 | 26 | ################################################################# 27 | # 28 | # Part: Shared Data 29 | # 30 | ################################################################# 31 | 32 | _python_main_script = '' 33 | _python_exec_arguments = '' 34 | _python_script_arguments = '' 35 | 36 | _python_frame_stack = [] 37 | _python_frame_index = -1 38 | _python_breakpoint_table = [] 39 | _python_catchpoint_table = [] 40 | 41 | # Used to filter python imported module by fnmatch 42 | _imported_script_filters = {'includes' : [], 'excludes' : []} 43 | _imported_script_symbol_table = {} 44 | # Save symbol add by command py-symbol-file 45 | _python_script_symbol_table = {} 46 | 47 | ################################################################# 48 | # 49 | # Part: Utils 50 | # 51 | ################################################################# 52 | 53 | gdb_eval = lambda s : gdb.parse_and_eval(s) 54 | gdb_eval_str = lambda s : gdb.parse_and_eval(s).string() 55 | gdb_eval_int = lambda s : int(gdb.parse_and_eval(s)) 56 | gdb_output = lambda s, sep='\n' : sys.stdout.write (s + sep) 57 | target_has_execution = lambda : gdb.selected_inferior ().pid 58 | 59 | def list_pending_python_breakpoints(filename=None): 60 | for bp in _python_breakpoint_table: 61 | if bp.is_valid() and bp.state \ 62 | and (filename is None or bp.filename == filename): 63 | yield bp 64 | 65 | def list_python_breakpoints(bpnums=None): 66 | for bp in _python_breakpoint_table: 67 | if bp.is_valid() and (bpnums is None or bp.bpnum in bpnums): 68 | yield bp 69 | 70 | def find_python_breakpoint(bpnum): 71 | for bp in _python_breakpoint_table: 72 | if bp.is_valid() and bp.bpnum == bpnum: 73 | return bp 74 | 75 | def list_python_catchpoints(name=None, bpnums=None): 76 | for bp in _python_catchpoint_table: 77 | if bp.is_valid() \ 78 | and (name is None or bp.filename == name) \ 79 | and (bpnums is None or bp.bpnum in bpnums): 80 | yield bp 81 | 82 | def get_symbol_table(filename): 83 | return _imported_script_symbol_table.get( 84 | filename, 85 | _python_script_symbol_table.get(filename, {}) 86 | ) 87 | def resolve_filename_breakpoints(filename): 88 | for bp in list_pending_python_breakpoints(filename): 89 | if bp._resolve(get_symbol_table(filename)): 90 | bp._load() 91 | 92 | def python_ipa_load_catchpoint(): 93 | if target_has_execution(): 94 | gdb.execute('set var pyddd_ipa_python_catch_exceptions = %s' % \ 95 | build_catch_patterns('exception')) 96 | gdb.execute('set var pyddd_ipa_python_catch_functions = %s' % \ 97 | build_catch_patterns('call')) 98 | 99 | def build_catch_patterns(name): 100 | return '"%s"' % ' '.join( 101 | [c.lineno for c in list_python_catchpoints(name) if c.enabled] 102 | ) 103 | 104 | def python_breakpoint_hit_command_list(): 105 | '''Do after a python script breakpoint is hit.''' 106 | gdb.execute('python-ipa-frame setup') 107 | gdb.execute('python-ipa-frame print') 108 | i = PythonIPAFrameCommand.current_lineno() 109 | gdb.execute('python-ipa-frame sources %d, %d' % (i, i)) 110 | 111 | ################################################################# 112 | # 113 | # Part: Internal Breakpoint and Normal Python Breakpoint 114 | # 115 | ################################################################# 116 | 117 | class PythonInternalExceptionCatchpoint (gdb.Breakpoint): 118 | '''This is an internal breakpoint to catch exception. ''' 119 | 120 | def __init__(self): 121 | super(PythonInternalExceptionCatchpoint, self).__init__( 122 | "pyddd_ipa_catch_exception_addr", 123 | internal=True, 124 | ) 125 | self.silent = True 126 | 127 | def stop (self): 128 | name = gdb_eval_str('pyddd_ipa_current_excname') 129 | gdb_output ('Catch exception: %s' % name) 130 | python_breakpoint_hit_command_list() 131 | self._clear(excname) 132 | return True 133 | 134 | def _clear(self, name): 135 | '''Clear temporary catchpoint.''' 136 | bplist = [] 137 | for bp in list_python_catchpoints('exception'): 138 | if bp.temporary and bp.lineno == name: 139 | bplist.append(bp) 140 | for bp in bplist: 141 | _python_catchpoint_table.remove(bp) 142 | gdb_output ('Remove temporary catchpoint #%d' % bp.bpnum) 143 | if len(bplist): 144 | python_ipa_load_catchpoint() 145 | 146 | class PythonInternalCallCatchpoint (gdb.Breakpoint): 147 | '''This is an internal breakpoint to catch function call. ''' 148 | 149 | def __init__(self): 150 | super(PythonInternalCallCatchpoint, self).__init__( 151 | "pyddd_ipa_catch_call_addr", 152 | internal=True, 153 | ) 154 | self.silent = True 155 | 156 | def stop (self): 157 | name = gdb_eval_str('pyddd_ipa_current_funcname') 158 | gdb_output ('Catch function call: %s' % name) 159 | python_breakpoint_hit_command_list() 160 | self._clear(name) 161 | return True 162 | 163 | def _clear(self, name): 164 | '''Clear temporary catchpoint.''' 165 | bplist = [] 166 | for bp in list_python_catchpoints('call'): 167 | if bp.temporary and bp.lineno == name: 168 | bplist.append(bp) 169 | for bp in bplist: 170 | _python_catchpoint_table.remove(bp) 171 | gdb_output ('Remove temporary catchpoint #%d' % bp.bpnum) 172 | if len(bplist): 173 | python_ipa_load_catchpoint() 174 | 175 | class PythonInternalLineBreakpoint (gdb.Breakpoint): 176 | '''This is an internal breakpoint. ''' 177 | 178 | def __init__(self): 179 | super(PythonInternalLineBreakpoint, self).__init__( 180 | spec="pyddd_ipa_breakpoint_addr", 181 | internal=True, 182 | ) 183 | self.silent = True 184 | 185 | def stop (self): 186 | # update hit count of all breakpoints from pyddd ipa 187 | rtable = gdb.parse_and_eval('pyddd_ipa_breakpoint_table') 188 | for bp in list_python_breakpoints(): 189 | if bp.rindex != -1: 190 | bp.hit_count = int(rtable[bp.rindex]['hit_count']) 191 | # stop at bpnum 192 | bpnum = gdb_eval_int('pyddd_ipa_current_breakpoint->bpnum') 193 | locnum = gdb_eval_int('pyddd_ipa_current_breakpoint->locnum') 194 | # update all the python breakpoints 195 | bp = find_python_breakpoint(bpnum) 196 | if bp is None: 197 | gdb_output ('Breakpoint #%d (not found)\n' % bpnum) 198 | else: 199 | # print bpinfo 200 | bp._info() 201 | # if bp is temporary, remove it 202 | if bp.temporary: 203 | _python_breakpoint_table.pop(bp) 204 | bp._unload() 205 | python_breakpoint_hit_command_list() 206 | return True 207 | 208 | def __setattr__(self, name, value): 209 | # For this internal breakpoint, ignore_count always equals 0 210 | # 211 | # When run command "py-continue ignore_count", it will set 212 | # ignore_count of this internal breakpoint. It's not excepted, 213 | # so we clear it here. 214 | if name == 'ignore_count': 215 | value = 0 216 | gdb.Breakpoint.__setattr__(self, name, value) 217 | 218 | class PythonInternalVolatileBreakpoint (gdb.Breakpoint): 219 | '''This is an internal breakpoint. ''' 220 | 221 | def __init__(self): 222 | super(PythonInternalVolatileBreakpoint, self).__init__( 223 | spec="pyddd_ipa_volatile_breakpoint_addr", 224 | internal=True, 225 | ) 226 | self.silent = True 227 | 228 | def stop (self): 229 | python_breakpoint_hit_command_list() 230 | return True 231 | 232 | class PythonInternalStartupBreakpoint (gdb.Breakpoint): 233 | '''This is an internal breakpoint. ''' 234 | def __init__(self, spec): 235 | super(PythonInternalStartupBreakpoint, self).__init__( 236 | spec=spec, 237 | internal=True, 238 | ) 239 | self.silent = True 240 | self.enabled = False 241 | 242 | def stop (self): 243 | gdb.execute('python-ipa-initialize') 244 | gdb.execute('py-symbol-file enable autoload') 245 | self.enabled = False 246 | return False 247 | 248 | class PythonInternalImportModuleBreakpoint (gdb.Breakpoint): 249 | '''This is an internal breakpoint. 250 | 251 | PyObject* PyImport_ExecCodeModuleEx( 252 | char *name, 253 | PyObject *co, 254 | char *pathname) 255 | ''' 256 | 257 | def __init__(self, cobp): 258 | super(PythonInternalImportModuleBreakpoint, self).__init__( 259 | spec="PyImport_ExecCodeModuleEx", 260 | internal=True, 261 | ) 262 | self.silent = True 263 | self.enabled = False 264 | self._cobp = cobp 265 | self._argoffset = None 266 | self._exprs = \ 267 | '{char*}($fp+0x%x)', \ 268 | '{PyObject*}($fp+0x%x)', \ 269 | '{char*}($fp+0x%x)' 270 | self._args = \ 271 | 'sizeof($fp)+sizeof($pc)', \ 272 | 'sizeof($fp)+sizeof($pc)+sizeof(char*)', \ 273 | 'sizeof($fp)+sizeof($pc)+sizeof(char*)+sizeof(PyObject*)' 274 | 275 | def stop (self): 276 | '''Filter module''' 277 | pathname = self._argvalue(2) 278 | name = self._argvalue(0) 279 | enabled = True 280 | for s in _imported_script_filters['includes']: 281 | if fnmatch.fnmatch(pathname, s): 282 | enabled = True 283 | break 284 | elif enabled: 285 | enabled = False 286 | if enabled: 287 | for s in _imported_script_filters['excludes']: 288 | if fnmatch.fnmatch(pathname, s): 289 | enabled = False 290 | break 291 | self._cobp.enabled = enabled 292 | return False 293 | 294 | def _argvalue(self, index): 295 | if self._argoffset is None: 296 | self._argoffset = \ 297 | gdb_eval_int(self._args[0]), \ 298 | gdb_eval_int(self._args[1]), \ 299 | gdb_eval_int(self._args[2]) 300 | return gdb_eval_str(self._exprs[index] % self._argoffset[index]) 301 | 302 | class PythonInternalNewCodeObjectBreakpoint (gdb.Breakpoint): 303 | '''This is an internal breakpoint. 304 | PyCodeObject *PyCode_New(int argcount, 305 | int nlocals, 306 | int stacksize, 307 | int flags, 308 | PyObject *code, 309 | PyObject *consts, 310 | PyObject *names, 311 | PyObject *varnames, 312 | PyObject *freevars, 313 | PyObject *cellvars, 314 | PyObject *filename, 315 | PyObject *name, 316 | int firstlineno, 317 | PyObject *lnotab) 318 | ''' 319 | def __init__(self): 320 | super(PythonInternalNewCodeObjectBreakpoint, self).__init__( 321 | spec="PyCode_New", 322 | internal=True, 323 | ) 324 | self.silent = True 325 | self.enabled = False 326 | self._symbol_table = {} 327 | self._argoffset = None 328 | self._exprs = \ 329 | '(char*)(PyString_AsString({PyObject*}($fp+0x%x)))', \ 330 | '(char*)(PyString_AsString({PyObject*}($fp+0x%x)))', \ 331 | '(int)({int*}($fp+0x%x))' 332 | self._args = \ 333 | 'sizeof($fp)+sizeof($pc)+sizeof(int)*4+sizeof(PyObject*)*6', \ 334 | 'sizeof($fp)+sizeof($pc)+sizeof(int)*4+sizeof(PyObject*)*7', \ 335 | 'sizeof($fp)+sizeof($pc)+sizeof(int)*4+sizeof(PyObject*)*8' 336 | 337 | def stop (self): 338 | self._add_symbol( 339 | self._argvalue(0).string(), 340 | self._argvalue(1).string(), 341 | int(self._argvalue(2)), 342 | ) 343 | return False 344 | 345 | def _argvalue(self, index): 346 | if self._argoffset is None: 347 | self._argoffset = \ 348 | gdb_eval_int(self._args[0]), \ 349 | gdb_eval_int(self._args[1]), \ 350 | gdb_eval_int(self._args[2]) 351 | return gdb_eval(self._exprs[index] % self._argoffset[index]) 352 | 353 | def _add_symbol(self, filename, symbol, lineno): 354 | if symbol == '': 355 | self.enabled = False 356 | _imported_script_symbol_table[filename] = self._symbol_table 357 | self._symbol_table = {} 358 | resolve_filename_breakpoints(filename) 359 | else: 360 | self._symbol_table[symbol] = lineno 361 | 362 | ################################################################# 363 | # 364 | # Part: Python Script Breakpoint 365 | # 366 | ################################################################# 367 | 368 | class PythonBreakpoint (object): 369 | '''Python script breakpoint, includes extra fields: 370 | rindex Index in python-ipa 371 | 372 | filename/lineno Address of this breakpoint if not multiple 373 | 374 | state 0 fixed 375 | 1 pending 376 | 2 resolved 377 | In future: 378 | 379 | multiloc A tuple for breakpoint has multiple addresses 380 | (rindex, enabled, hit_count, filename, lineno), ... 381 | ''' 382 | BP_COUNTER = 0 383 | def __init__(self, spec, temporary=0): 384 | super(PythonBreakpoint, self).__init__() 385 | self.rindex = -1 386 | self.location = spec 387 | self.enabled = 1 388 | self.temporary = temporary 389 | self.thread = 0 390 | self.ignore_count = 0 391 | self.hit_count = 0 392 | self.condition = 0 393 | self.visible = 1 394 | 395 | self.filename = None 396 | self.lineno = 0 397 | self.multiloc = None 398 | self.state = 0 399 | self._parse(spec) 400 | 401 | self.BP_COUNTER += 1 402 | self.bpnum = self.BP_COUNTER 403 | 404 | def is_valid (self): 405 | return True 406 | 407 | def _parse(self, spec): 408 | '''Resolve location to filename:lineno.''' 409 | arglist = spec.split(':') 410 | n = len(arglist) 411 | if n == 0: 412 | self._parse0() 413 | elif n == 1: 414 | self._parse1(*arglist) 415 | elif n == 2: 416 | self._parse2(*arglist) 417 | elif n == 3: 418 | self._parse3(*arglist) 419 | else: 420 | raise gdb.GdbError('Invalid breakpoint spec "%s"' % spec) 421 | 422 | def _parse0(self): 423 | filename = PythonIPAFrameCommand.current_filename() 424 | if filename is None: 425 | raise gdb.GdbError('There is no script') 426 | self.filename = filename 427 | self.lineno = PythonIPAFrameCommand.current_lineno() 428 | self.state = 0 429 | 430 | def _parse1(self, arg): 431 | filename = PythonIPAFrameCommand.current_filename() 432 | if filename is None: 433 | raise gdb.GdbError('There is no script') 434 | try: 435 | offset = int(arg) 436 | except ValueError: 437 | offset = None 438 | if offset is None: 439 | self._parsen(filename, arg) 440 | else: 441 | lineno = offset 442 | if arg[0] in '+-': 443 | lineno += PythonIPAFrameCommand.current_lineno() 444 | if lineno <= 0: 445 | raise gdb.GdbError('Invalid breakpoint location') 446 | self.filename = filename 447 | self.lineno = lineno 448 | self.state = 0 449 | 450 | def _parse2(self, arg1, arg2): 451 | if arg1.lower().endswith('.py'): 452 | if arg2.isdigit(): 453 | self.filename = arg1 454 | self.lineno = arg2 455 | self.state = 0 456 | else: 457 | self._parsen(arg1, arg2) 458 | elif arg2.isdigit(): 459 | k = int(arg2) 460 | self._parsen(PythonIPAFrameCommand.current_filename(), arg1) 461 | if self.state == 2: 462 | self.lineno += k 463 | elif arg1 in ('exception', 'call'): 464 | self.filename = arg1 465 | self.lineno = arg2 466 | self.state = 0 467 | else: 468 | raise gdb.GdbError('Invalid breakpoint location') 469 | 470 | def _parse3(self, filename, funcname, offset): 471 | k = int(offset) 472 | self._parsen(filename, funcname) 473 | if self.state == 2: 474 | self.lineno += k 475 | 476 | def _parsen(self, filename, funcname): 477 | if self._resolve(get_symbol_table(filename)): 478 | self.filename = filename 479 | 480 | def _resolve(self, symtable): 481 | '''Only find first location, multi-location is not implemented.''' 482 | self.state = 1 483 | for name in symtable: 484 | if self.location == name: 485 | self.lineno = symtable[name] 486 | self.state = 2 487 | return True 488 | 489 | def _load(self): 490 | if target_has_execution(): 491 | s = string.Template( 492 | '$bpnum, $locnum, $thread, $condition, ' \ 493 | ' $ignore_count, $enabled, $lineno, "$filename"' 494 | ).substitute(self.__dict__, locnum=0) 495 | if self.rindex == -1: 496 | i = gdb_eval_int('pyddd_ipa_insert_breakpoint(%s)' % s) 497 | self.rindex = i 498 | else: 499 | gdb.execute('call pyddd_ipa_update_breakpoint(%d, %s)' \ 500 | % (self.rindex, s)) 501 | 502 | def _unload(self): 503 | if target_has_execution() and self.rindex != -1: 504 | gdb.execute('call pyddd_ipa_remove_breakpoint(%d)' % self.rindex) 505 | 506 | def _info(self): 507 | gdb_output ('bpnum=%d, location=%s, hit_count=%s' % \ 508 | (self.bpnum, self.location, self.hit_count)) 509 | 510 | ################################################################# 511 | # 512 | # Part: Extend Commands 513 | # 514 | ################################################################# 515 | 516 | # Internal commands: 517 | # python-ipa-download-data 518 | # python-ipa-frame 519 | # 520 | # Start commands: 521 | # py-file 522 | # py-args 523 | # py-run 524 | # py-start 525 | # py-exec-args 526 | # 527 | # Breakpoint commands: 528 | # py-break 529 | # py-tbreak 530 | # py-rbreak 531 | # py-clear 532 | # py-catch 533 | # py-tcatch 534 | # 535 | # Other commands: 536 | # py-symbol-file 537 | # py-info 538 | # 539 | class PythonIPALoadDataCommand(gdb.Command): 540 | ''' 541 | Upload python breakpoints and catchpoints to python-ipa 542 | when python library is loaded (internaly used only). 543 | 544 | Usage: python-ipa-load-data 545 | ''' 546 | 547 | def __init__(self): 548 | gdb.Command.__init__ (self, 549 | 'python-ipa-load-data', 550 | gdb.COMMAND_NONE, 551 | gdb.COMPLETE_NONE, 552 | ) 553 | 554 | def invoke(self, args, from_tty): 555 | self.dont_repeat() 556 | # upload catchpoints to python-ipa 557 | python_ipa_load_catchpoint() 558 | # upload breakpoints to python-ipa 559 | for bp in list_python_breakpoints(): 560 | # Reset rindex, force new breakpoint in ipa 561 | bp.rindex = -1 562 | if bp.filename is not None: 563 | bp._load() 564 | elif bp.multiloc is not None: 565 | raise NotImplementedError('multi-locations') 566 | 567 | class PythonFileCommand(gdb.Command): 568 | ''' 569 | Set main python script 570 | 571 | Usage: py-file filename 572 | py-file "string" 573 | 574 | If argument is filename, then set main script as filename. 575 | If argument is string, use -c command to run this string. 576 | ''' 577 | 578 | def __init__(self): 579 | gdb.Command.__init__ (self, 580 | 'py-file', 581 | gdb.COMMAND_FILES, 582 | gdb.COMPLETE_FILENAME, 583 | ) 584 | 585 | def invoke(self, args, from_tty): 586 | global _python_main_script 587 | self.dont_repeat() 588 | _python_main_script = args 589 | gdb_output ('main script is %s' % _python_main_script) 590 | 591 | class PythonSymbolFileCommand(gdb.Command): 592 | ''' 593 | Mange python script's symbol table 594 | 595 | Usage: py-symbol-file FILENAME 596 | py-symbol-file add FILENAME 597 | Parse FILENAME, get lineno of each function/class, add to symbol 598 | table. These information is used to resolve symbol in breakpoint 599 | location to filename:lineno. 600 | 601 | Usage: py-symbol-file clear 602 | Clear the whole symbol table. 603 | 604 | Usage: py-symbol-file clear autoload 605 | Clear only those symbol file imported in running time. 606 | 607 | Usage: py-symbol-file clear FILENAME 608 | Clear all the symbol belong to FILENAME. 609 | 610 | Usage: py-symbol-file info 611 | Show all the information about python symbol files. 612 | 613 | Usage: py-symbol-file filter PATTERN 614 | py-symbol-file filter !PATTERN 615 | Add include/exclude PATTERN (format of fnmatch) to filter 616 | those imported scripts in runtime. Only those match include 617 | PATTERN and not match exclude PATTERN could be added symbol 618 | table. 619 | 620 | Usage: py-symbol-file filter clear 621 | Clear all the filters. 622 | 623 | Usage: py-symbol-file disable autoload 624 | py-symbol-file enable autoload 625 | Disable/enable autoload imported symbol in runtime. 626 | 627 | ''' 628 | def __init__(self): 629 | gdb.Command.__init__ (self, 630 | 'py-symbol-file', 631 | gdb.COMMAND_FILES, 632 | gdb.COMPLETE_FILENAME, 633 | ) 634 | self._cobp = PythonInternalNewCodeObjectBreakpoint() 635 | self._imbp = PythonInternalImportModuleBreakpoint(self._cobp) 636 | 637 | def invoke(self, args, from_tty): 638 | self.dont_repeat() 639 | try: 640 | cmd, args = args.split(' ', 1) 641 | except Exception: 642 | cmd, args = args, '' 643 | if 'disable'.startswith(cmd): 644 | self._imbp.enabled = False 645 | self._cobp.enabled = False 646 | gdb_output('Disabled autoload imported symbol') 647 | elif 'enable'.startswith(cmd): 648 | self._imbp.enabled = True 649 | gdb_output('Enabled autoload imported symbol') 650 | elif 'clear'.startswith(cmd): 651 | self._clear(args) 652 | elif 'add'.startswith(cmd): 653 | self._add(args) 654 | elif 'filter'.startswith(cmd): 655 | self._filter(args) 656 | elif 'info'.startswith(cmd): 657 | self._info(args) 658 | else: 659 | self._add(args) 660 | 661 | def _clear(self, args): 662 | if args == '': 663 | _imported_script_symbol_table.clear() 664 | _python_script_symbol_table.clear() 665 | gdb_output ('All of python symbol tables are cleared') 666 | else: 667 | if 'autoload'.startswith(args): 668 | _imported_script_symbol_table.clear() 669 | gdb_output ('Imported python symbol tables are cleared') 670 | elif args in _python_script_symbol_table: 671 | _python_script_symbol_table.pop(args) 672 | gdb_output ('Remove "%s" from python symbol table' % args) 673 | else: 674 | gdb_output ('No "%s" in python symbol table' % args) 675 | 676 | def _add(self, args): 677 | arglist = gdb.string_to_argv(args) 678 | for filename in arglist: 679 | if os.path.exists(filename): 680 | s = PythonSymbolList(filename) 681 | s.load() 682 | _python_script_symbol_table[filename] = dict(s) 683 | gdb_output ('Add "%s" to python symbol table' % filename) 684 | else: 685 | raise gdb.GdbError('File "%s" not found' % filename) 686 | 687 | def _update(self, args): 688 | raise NotImplementedError('py-symbol-file update') 689 | 690 | def _info(self, args): 691 | gdb_output ('Autoload imported symbol state: %s' % \ 692 | 'enabled' if self._imbp.enabled else 'disabled') 693 | gdb_output ('Include filters: %s' % \ 694 | str(_imported_script_filters['includes'])) 695 | gdb_output ('Exclude filters: %s' % \ 696 | str(_imported_script_filters['excludes'])) 697 | gdb_output ('Python symbol tables:') 698 | gdb_output (' '.join(_python_script_symbol_table.keys())) 699 | gdb_output ('Auto imported symbol tables:') 700 | gdb_output (' '.join(_imported_script_symbol_table.keys())) 701 | 702 | def _filter(self, args): 703 | if 'clear'.startswith(args): 704 | for k in _imported_script_filters: 705 | _imported_script_filters[:] = [] 706 | gdb_output ('Python symbol filters are cleared') 707 | else: 708 | for arg in gdb.string_to_argv(args): 709 | if arg[0] == '!': 710 | _imported_script_filters['excludes'].append(arg[1:]) 711 | gdb_output ('Added exclude filter "%s"' % arg[1:]) 712 | else: 713 | _imported_script_filters['includes'].append(arg) 714 | gdb_output ('Added include filter "%s"' % arg) 715 | 716 | class PythonRunCommand(gdb.Command): 717 | ''' 718 | Run main python script 719 | 720 | Usage: py-run 721 | Wrap gdb command 'run', set args to call python, for example, 722 | (gdb) py-exec-file python 723 | (gdb) py-file main.py 724 | (gdb) py-args --help 725 | (gdb) py-exec-args -i 726 | (gdb) py-run 727 | 728 | py-run will set args before call gdb command 'run' like this: 729 | gdb.execute('set args -i main.py --help') 730 | ''' 731 | def __init__(self): 732 | gdb.Command.__init__ (self, 733 | 'py-run', 734 | gdb.COMMAND_RUNNING, 735 | gdb.COMPLETE_NONE, 736 | ) 737 | self._internal_bplist = \ 738 | PythonInternalStartupBreakpoint('PySys_SetArgv'), \ 739 | PythonInternalStartupBreakpoint('PySys_SetArgvEx') 740 | 741 | def invoke(self, args, from_tty): 742 | self.dont_repeat() 743 | for bp in self._internal_bplist: 744 | bp.enabled = True 745 | if os.path.exists(_python_main_script): 746 | fmt = 'set args %s %s %s' 747 | gdb_output ('load symbols from main script') 748 | s = PythonSymbolList(_python_main_script) 749 | s.load() 750 | _python_script_symbol_table[_python_main_script] = dict(s) 751 | else: 752 | fmt = 'set args %s -c "%s" %s' 753 | gdb.execute(fmt % (_python_exec_arguments, 754 | _python_main_script, 755 | _python_script_arguments)) 756 | gdb.execute('py-symbol-file disable autoload') 757 | gdb.execute('run') 758 | 759 | class PythonExecArgsCommand(gdb.Command): 760 | ''' 761 | Set arguments to be passed to python, not to python scripts. 762 | 763 | Usage: py-exec-args [option] ... 764 | 765 | Same as options of python command line. 766 | 767 | ''' 768 | def __init__(self): 769 | gdb.Command.__init__ (self, 770 | 'py-exec-args', 771 | gdb.COMMAND_NONE, 772 | gdb.COMPLETE_NONE, 773 | ) 774 | 775 | def invoke(self, args, from_tty): 776 | global _python_exec_arguments 777 | self.dont_repeat() 778 | _python_exec_arguments = args 779 | 780 | class PythonCatchpointCommand(gdb.Command): 781 | ''' 782 | Create python script catchpoints. 783 | 784 | Usage: py-catch exception NAME 785 | Stop when exception NAME raised 786 | 787 | Usage: py-catch call NAME 788 | Stop when call function NAME 789 | 790 | Usage: py-catch info 791 | Show all catchpoints. 792 | ''' 793 | def __init__(self, name='py-catch', temporary=False): 794 | gdb.Command.__init__ (self, 795 | name, 796 | gdb.COMMAND_BREAKPOINTS, 797 | gdb.COMPLETE_COMMAND, 798 | ) 799 | self._temporary = temporary 800 | 801 | def invoke(self, args, from_tty): 802 | self.dont_repeat() 803 | if args == '': 804 | self._info() 805 | else: 806 | try: 807 | cmd, subargs = args.split(' ', 1) 808 | except Exception: 809 | cmd = args 810 | subargs = '' 811 | if 'exception'.startswith(cmd): 812 | self._catchpoint(cmd, subargs) 813 | elif 'call'.startswith(cmd): 814 | self._catchpoint(cmd, subargs) 815 | elif 'info'.startswith(cmd): 816 | self._info(subargs) 817 | else: 818 | raise NotImplementedError(cmd) 819 | 820 | def _catchpoint(self, kind, args): 821 | if self._temporary: 822 | prefix = 'Add temporary catchpoint' 823 | else: 824 | prefix = 'Add catchpoint' 825 | for c in args.split(' '): 826 | spec = '%s:%s' % (kind, c) 827 | cp = PythonBreakpoint(spec, temporary=self._temporary) 828 | _python_catchpoint_table.append(cp) 829 | gdb_output ( 830 | '%s #%d, catch %s' % (prefix, cp.bpnum, spec) 831 | ) 832 | if args != '': 833 | python_ipa_load_catchpoint() 834 | 835 | def _info(self, arg=None): 836 | for bp in list_python_catchpoints(arg): 837 | s = 'Temporary catchpoint' if bp.temporary else 'Catchpoint' 838 | gdb_output ('%s #%d, catch %s:%s' % \ 839 | (bp.bpnum, s, bp.filename,bp.lineno,) 840 | ) 841 | 842 | class PythonTempCatchpointCommand(PythonCatchpointCommand): 843 | ''' 844 | Create temporary python script catchpoints. 845 | 846 | Usage: py-tcatch ARGUMENTS 847 | Same as py-catch, but the catchpoint will be deleted once hit. 848 | ''' 849 | def __init__(self): 850 | super(PythonTempCatchpointCommand, self).__init__( 851 | name='py-tcatch', 852 | temporary=True, 853 | ) 854 | 855 | class PythonBreakpointCommand(gdb.Command): 856 | ''' 857 | Create python script breakpoints. 858 | 859 | Usage: py-break 860 | Add breakpoint in the current file and current lineno. 861 | 862 | Usage: py-break LINENO 863 | Add breakpoint in the LINENO of current file. 864 | 865 | Usage: py-break FUNCTION 866 | Add breakpoint in the FUNCTION of current file. 867 | 868 | Usage: py-break FILENAME:LINENO 869 | Add breakpoint in the FILENAME:LINEO 870 | 871 | Usage: py-break FILENAME:FUNCTION 872 | Add breakpoint in the FILENAME:FUNCTION 873 | 874 | Usage: py-break location if cond 875 | Argument cond must be python expression, it means no convenience 876 | variables which start with $ could be used here. 877 | ''' 878 | def __init__(self, name='py-break', temporary=False): 879 | gdb.Command.__init__ (self, 880 | name, 881 | gdb.COMMAND_BREAKPOINTS, 882 | gdb.COMPLETE_LOCATION, 883 | ) 884 | self._temporary = temporary 885 | 886 | def invoke(self, args, from_tty): 887 | self.dont_repeat() 888 | arglist = gdb.string_to_argv(args) 889 | try: 890 | spec = arglist[0] 891 | except IndexError: 892 | spec = '' 893 | bp = PythonBreakpoint(spec, temporary=self._temporary) 894 | try: 895 | bp.condition = arglist[2] 896 | except IndexError: 897 | pass 898 | _python_breakpoint_table.append(bp) 899 | bp._load() 900 | bp._info() 901 | 902 | class PythonTempBreakpointCommand(PythonBreakpointCommand): 903 | ''' 904 | Create temporary python script breakpoints. 905 | 906 | Usage: py-tbreak ARGUMENTS 907 | Same as py-break, but the breakpoint will be deleted once hit. 908 | ''' 909 | def __init__(self): 910 | super(PythonTempBreakpointCommand, self).__init__( 911 | name='py-tbreak', 912 | temporary=True 913 | ) 914 | 915 | class PythonClearCommand(gdb.Command): 916 | ''' 917 | Clear python script breakpoints or catchpoints. 918 | 919 | Usage: py-clear 920 | Clear breakpoints in the current line of current file. 921 | 922 | Usage: py-clear [break] LOCATION 923 | Clear breakpoints in LOCATION 924 | 925 | Usage: py-clear catch NAME 926 | Clear catchpoint NAME 927 | ''' 928 | def __init__(self): 929 | gdb.Command.__init__ (self, 930 | 'py-clear', 931 | gdb.COMMAND_BREAKPOINTS, 932 | gdb.COMPLETE_LOCATION, 933 | ) 934 | 935 | def invoke(self, args, from_tty): 936 | self.dont_repeat() 937 | if args.startswith('break'): 938 | if ' ' in args: 939 | args = args.split(' ', 1)[1] 940 | self._clear_breakpoint(args) 941 | elif args.startswith('catch'): 942 | if ' ' in args: 943 | args = args.split(' ', 1)[1] 944 | self._clear_catchpoint(args) 945 | else: 946 | self._clear_breakpoint(args) 947 | 948 | def _clear_breakpoint(self, location): 949 | bp = PythonBreakpoint(location) 950 | filename = bp.filename 951 | lineno = bp.lineno 952 | bplist = [] 953 | for bp in list_python_breakpoints(): 954 | if bp.lineno == lineno and bp.filename == filename: 955 | bplist.append(bp) 956 | for bp in bplist: 957 | _python_breakpoint_table.remove(bp) 958 | gdb_output('Remove breakpoint #%d' % bp.bpnum) 959 | bp._unload() 960 | 961 | def _clear_catchpoint(self, name): 962 | bplist = [] 963 | for bp in list_python_catchpoints(): 964 | if bp.lineno == name: 965 | bplist.append(bp) 966 | for bp in bplist: 967 | _python_catchpoint_table.remove(bp) 968 | gdb_output('Remove catchpoint #%d' % bp.bpnum) 969 | # upload catchpoint 970 | python_ipa_load_catchpoint() 971 | 972 | class PythonDeleteCommand(gdb.Command): 973 | ''' 974 | Delete python script breakpoints or catchpoints. 975 | 976 | Usage: py-delete 977 | Delete all the breakpoints. 978 | 979 | Usage: py-delete [break] RANGES 980 | Delete the breakpoints list in RANGES, for example 981 | 982 | $(gdb) py-delete 5 6 8 983 | 984 | Usage: py-delete catch 985 | Delete all the catchpoints. 986 | 987 | Usage: py-delete catch RANGES 988 | Delete the catchpoints list in RANGES. 989 | ''' 990 | def __init__(self): 991 | gdb.Command.__init__ (self, 992 | 'py-delete', 993 | gdb.COMMAND_BREAKPOINTS, 994 | gdb.COMPLETE_NONE, 995 | ) 996 | 997 | def invoke(self, args, from_tty): 998 | self.dont_repeat() 999 | if args.startswith('break'): 1000 | if ' ' in args: 1001 | args = args.split(' ', 1)[1] 1002 | self._delete_breakpoints(args) 1003 | elif args.startswith('catch'): 1004 | if ' ' in args: 1005 | args = args.split(' ', 1)[1] 1006 | self._delete_catchpoint(args) 1007 | else: 1008 | self._delete_breakpoints(args) 1009 | 1010 | def _delete_breakpoints(self, args): 1011 | if args == '': 1012 | _python_breakpoint_table[:] = [] 1013 | else: 1014 | arglist = [int(x) for x in args.split()] 1015 | for bp in list_python_breakpoints(arglist): 1016 | _python_breakpoint_table.remove(bp) 1017 | gdb_output ('Remove breakpoint #%d' % bp.bpnum) 1018 | bp._unload() 1019 | 1020 | def _delete_catchpoints(self, args): 1021 | if args == '': 1022 | _python_catchpoint_table[:] = [] 1023 | else: 1024 | arglist = [int(x) for x in args.split()] 1025 | for bp in list_python_catchpoints(None, arglist): 1026 | _python_catchpoint_table.remove(bp) 1027 | gdb_output ('Remove catchpoint #%d' % bp.bpnum) 1028 | # upload catchpoints 1029 | python_ipa_load_catchpoint() 1030 | 1031 | class PythonEnableCommand(gdb.Command): 1032 | ''' 1033 | Enable python script breakpoints. 1034 | 1035 | Usage: py-enable RANGES 1036 | Enable the breakpoints list in RANGES. 1037 | 1038 | Usage: py-enable once RANGES 1039 | Enable the breakpoints list in RANGES once, it will be disabled 1040 | after hit. 1041 | 1042 | Usage: py-enable count N RANGES 1043 | Enable the breakpoints list in RANGES N times, after that it will 1044 | be disabled. 1045 | 1046 | Usage: py-enable delete RANGES 1047 | After those breakpoints are hit, they'll be deleted. 1048 | ''' 1049 | def __init__(self): 1050 | gdb.Command.__init__ (self, 1051 | 'py-enable', 1052 | gdb.COMMAND_BREAKPOINTS, 1053 | gdb.COMPLETE_NONE, 1054 | ) 1055 | 1056 | def invoke(self, args, from_tty): 1057 | self.dont_repeat() 1058 | if args.startswith('once'): 1059 | args = args.split(' ', 1)[1] 1060 | self._enable_breakpoints(args, enabled=-1) 1061 | elif args.startswith('delete'): 1062 | args = args.split(' ', 1)[1] 1063 | self._enable_breakpoints(args, temporary=True) 1064 | elif args.startswith('count'): 1065 | _x, n, args = args.split(' ', 2) 1066 | self._enable_breakpoints(args, enabled=-int(n)) 1067 | else: 1068 | self._enable_breakpoints(args) 1069 | 1070 | def _enable_breakpoints(self, args, enabled=0, temporary=False): 1071 | arglist = [int(x) for x in args.split()] 1072 | for bp in list_python_breakpoints(arglist): 1073 | if enabled: 1074 | bp.enabled = enabled 1075 | if enabled > 0: 1076 | gdb_output ('Enabled breakpoint #%d') 1077 | else: 1078 | gdb_output ('Enabled breakpoint #%d for %d times' % \ 1079 | (bp.bpnum, -enabled) 1080 | ) 1081 | if temporary: 1082 | bp.temporary = temporary 1083 | gdb_output ('Make breakpoint #%d volatile' % bp.bpnum) 1084 | bp._load() 1085 | 1086 | class PythonDisableCommand(gdb.Command): 1087 | ''' 1088 | Disable python script breakpoints. 1089 | 1090 | Usage: py-disable 1091 | Disable all the breakpoints. 1092 | 1093 | Usage: py-disable RANGES 1094 | Disable the breakpoints list in RANGES. 1095 | ''' 1096 | def __init__(self): 1097 | gdb.Command.__init__ (self, 1098 | 'py-disable', 1099 | gdb.COMMAND_BREAKPOINTS, 1100 | gdb.COMPLETE_NONE, 1101 | ) 1102 | 1103 | def invoke(self, args, from_tty): 1104 | self.dont_repeat() 1105 | if args == '': 1106 | arglist = None 1107 | else: 1108 | arglist = [int(x) for x in gdb.string_to_argv(args)] 1109 | for bp in list_python_breakpoints(arglist): 1110 | bp.enabled = 0 1111 | gdb_output ('Disable breakpoint #%d' % bp.bpnum) 1112 | bp._load() 1113 | 1114 | class PythonInfoCommand(gdb.Command): 1115 | ''' 1116 | Show pyddd information. 1117 | 1118 | Usage: py-info args 1119 | Show python script arguments. 1120 | 1121 | Usage: py-info exec-args 1122 | Show arguments used to run python. 1123 | 1124 | Usage: py-info file 1125 | Show python main script. 1126 | 1127 | Usage: py-info locals 1128 | Usage: py-info globals 1129 | Usage: py-info frame 1130 | Show information of current frame. 1131 | 1132 | Usage: py-info catchpoints 1133 | Show all the of catchpoints. 1134 | 1135 | Usage: py-info catchpoints RANGES 1136 | Show the information of catchpoints in the RANGES 1137 | 1138 | Usage: py-info [breakpoints] 1139 | Show all the breakpoints. 1140 | 1141 | Usage: py-info [breakpoints] RANGES 1142 | Show the information of breakpoints in the RANGES 1143 | ''' 1144 | def __init__(self): 1145 | gdb.Command.__init__ (self, 'py-info', gdb.COMMAND_STATUS) 1146 | 1147 | def invoke(self, args, from_tty): 1148 | self.dont_repeat() 1149 | if 'args'.startswith(args): 1150 | gdb_output ('Script arguments: %s' % _python_script_arguments) 1151 | elif 'exec-args'.startswith(args): 1152 | gdb_output ('Python arguments: %s' % _python_exec_arguments) 1153 | elif 'file'.startswith(args): 1154 | gdb_output ('Main script: %s' % _python_main_script) 1155 | elif 'locals'.startswith(args): 1156 | gdb.execute('python-ipa-frame locals') 1157 | elif 'globals'.startswith(args): 1158 | gdb.execute('python-ipa-frame globals') 1159 | elif 'frame'.startswith(args): 1160 | gdb.execute('python-ipa-frame print verbose') 1161 | elif 'catchpoints'.startswith(args): 1162 | if ' ' in args: 1163 | arglist = [int(x) for x in args.split()[1:]] 1164 | else: 1165 | arglist = None 1166 | for bp in list_python_catchpoints(None, arglist): 1167 | bp._info() 1168 | else: 1169 | if ' ' in args: 1170 | arglist = [int(x) for x in args.split() if x.isdigital()] 1171 | else: 1172 | arglist = None 1173 | for bp in list_python_breakpoints(arglist): 1174 | bp._info() 1175 | 1176 | ################################################################# 1177 | # 1178 | # Part: Python Frame (PyFrameObject*) 1179 | # 1180 | ################################################################# 1181 | 1182 | class PythonFrame(object): 1183 | ''' 1184 | Wrapper for PyFrameObject*, adding various methods. 1185 | 1186 | _frame is gdb.Value 1187 | ''' 1188 | def __init__(self, frame): 1189 | self._frame = frame 1190 | self._filename = None 1191 | self._lineno = None 1192 | self._listlineno = None 1193 | self._listsize = gdb.parameter('listsize') 1194 | self._name = None 1195 | self._args = None 1196 | self._locals = None 1197 | self._globals = None 1198 | 1199 | def select(self): 1200 | '''Print frame info.''' 1201 | return True 1202 | 1203 | def _expr(self, name): 1204 | return 'pyddd_ipa_frame_%s((PyFrameObject*)%s)' \ 1205 | % (name, self._frame) 1206 | 1207 | def older(self): 1208 | return self._frame.dereference()['f_back'] 1209 | 1210 | def info_filename(self): 1211 | if self._filename is None: 1212 | self._filename = gdb_eval_str(self._expr('filename')) 1213 | return self._filename 1214 | 1215 | def info_lineno(self): 1216 | if self._lineno is None: 1217 | self._lineno = gdb_eval_int(self._expr('lineno')) 1218 | return self._lineno 1219 | 1220 | def info_listlineno(self): 1221 | if self._listlineno is None: 1222 | self._listlineno = self._lineno 1223 | return self._listlineno 1224 | 1225 | def info_name(self): 1226 | if self._name is None: 1227 | self._name = gdb_eval_str(self._expr('name')) 1228 | return self._name 1229 | 1230 | def info_args(self): 1231 | if self._args is None: 1232 | code = self._frame.dereference()['f_code'] 1233 | argcount = code.dereference()['co_argcount'] 1234 | varnames = eval(gdb_eval_str(self._expr('varnames'))) 1235 | self._args = [] 1236 | for i in range(argcount): 1237 | name = varnames[i] 1238 | expr = self._expr('values')[:-1] + ', 0, %d)' 1239 | value = gdb_eval_str(expr % i) 1240 | self._args.append((name, value)) 1241 | return self._args 1242 | 1243 | def info_locals(self): 1244 | if self._locals is None: 1245 | self._locals = {} 1246 | code = self._frame.dereference()['f_code'] 1247 | nlocals = code.dereference()['co_nlocals'] 1248 | varnames = eval(gdb_eval_str(self._expr('varnames'))) 1249 | cellvars = eval(gdb_eval_str(self._expr('cellvars'))) 1250 | expr = self._expr('values')[:-1] + ', 0, %d)' 1251 | assert nlocals <= len(varnames) 1252 | for i in range(nlocals): 1253 | name = varnames[i] 1254 | expr = self._expr('values')[:-1] + ', 0, %d)' 1255 | self._locals[name] = gdb_eval_str(expr % i) 1256 | j = nlocals 1257 | for name in cellvars: 1258 | expr = self._expr('values')[:-1] + ', 0, %d)' 1259 | self._locals[name] = gdb_eval_str(expr % j) 1260 | j += 1 1261 | return self._locals 1262 | 1263 | def info_globals(self): 1264 | if self._globals is None: 1265 | self._globals = {} 1266 | expr = self._expr('variable')[:-1] + ', "%s", 0)' 1267 | names = eval(gdb_eval_str(self._expr('globals'))) 1268 | for s in names: 1269 | self._globals[s] = gdb_eval_str(expr % s) 1270 | return self._globals 1271 | 1272 | def info_sources(self, args): 1273 | # 1274 | # list lineno 1275 | # list function 1276 | # Print lines centered around line in the current source file. 1277 | # list 1278 | # Print more lines. 1279 | # list + 1280 | # Print lines just after the lines last printed. 1281 | # list - 1282 | # Print lines just before the lines last printed. 1283 | # list first, last 1284 | # list first, 1285 | # list ,last 1286 | # 1287 | listsize = gdb.parameter('listsize') 1288 | offset = listsize / 2 + self._listsize / 2 1289 | if args == '' or args == '+': 1290 | self._listlineno = self.info_listlineno() + offset 1291 | self._listsize = listsize 1292 | start = self._listlineno - self._listsize / 2 1293 | end = self._listlineno + self._listsize / 2 1294 | elif args == '-': 1295 | self._listlineno = self.info_listlineno() - offset 1296 | if self._listlineno < 1: 1297 | self._listlineno = 1 1298 | self._listsize = listsize 1299 | start = self._listlineno - self._listsize / 2 1300 | end = self._listlineno + self._listsize / 2 1301 | elif ',' in args: 1302 | first, last = args.split(',', 1) 1303 | start = self.info_listlineno() if first == '' else int(first) 1304 | end = self.info_listlineno() if last == '' else int(last) 1305 | self._listsize = end - start + 1 1306 | if listsize < 1: 1307 | raise gdb.GdbError('Invalid line spec') 1308 | self._listlineno = (start + end) / 2 1309 | else: 1310 | try: 1311 | self._listlineno = int(args) 1312 | except ValueError: 1313 | self._listlineno = self._function_lineno(args) 1314 | self._listsize = listsize 1315 | if self._listlineno < 1: 1316 | raise gdb.GdbError('Invalid line spec') 1317 | start = self._listlineno - self._listsize / 2 1318 | end = self._listlineno + self._listsize / 2 1319 | if start < 1: 1320 | start = 1 1321 | with open(self.info_filename(), 'r') as f: 1322 | all_lines = f.readlines() 1323 | # start and end are 1-based, all_lines is 0-based; 1324 | # so [start-1:end] as a python slice gives us [start, end] as a 1325 | # closed interval 1326 | for i, line in enumerate(all_lines[start-1:end]): 1327 | linestr = str(i+start) 1328 | # Highlight current line: 1329 | if i + start == self.info_lineno(): 1330 | linestr = '>' + linestr 1331 | sys.stdout.write('%4s %s' % (linestr, line)) 1332 | 1333 | def _function_lineno(self, name): 1334 | for k, v in get_symbol_table(self.info_filename()).items(): 1335 | if k == name: 1336 | return v 1337 | return 0 1338 | 1339 | def _print(self, level=0, verbose=False): 1340 | # 1341 | # print frame infoformation, output format: 1342 | # 1343 | # #0 name (arg1=value1, ...) at filename:lineno 1344 | # 1345 | # Or, 1346 | # 1347 | # name (arg1=value1, 1348 | # arg2=value2, 1349 | # ... 1350 | # ) 1351 | # at filename:lineno 1352 | # 1353 | # verbose mode, 1354 | # 1355 | # Stack level 0, frame in functionname (filename:lineno) 1356 | # called by frame in functionname (filename:lineno) 1357 | # 1358 | # Arglist: 1359 | # name1=type, value 1360 | # name2=type, value 1361 | # 1362 | # Locals: 1363 | # name1=type, value 1364 | # name2=type. value 1365 | # 1366 | result = ['#%d' % level, self.info_name(), '('] 1367 | for k, v in self.info_args(): 1368 | result.append('%s=%s,' % (k, v[:32])) 1369 | result.append(')') 1370 | result.append('at %s:%d' % (self.info_filename(), self.info_lineno())) 1371 | gdb_output (' '.join(result)) 1372 | if verbose: 1373 | for k, v in self.info_locals().items(): 1374 | if k not in self.info_args(): 1375 | gdb_output (' %s=%s' % (k, v)) 1376 | 1377 | class PythonIPAFrameCommand(gdb.Command): 1378 | ''' 1379 | Manage python frame (internal command). 1380 | 1381 | Usage: python-ipa-frame cmd [args] 1382 | 1383 | cmd = setup | teardown | select | print | source | backtrace \ 1384 | locals | globals 1385 | 1386 | for cmd 'select', args is empty or framespec 1387 | n | +n | -n | name 1388 | 1389 | for cmd 'backtrace', args is empty or 1390 | i 1391 | full 1392 | full i 1393 | ''' 1394 | def __init__(self): 1395 | gdb.Command.__init__ ( 1396 | self, 1397 | 'python-ipa-frame', 1398 | gdb.COMMAND_STACK 1399 | ) 1400 | 1401 | @classmethod 1402 | def is_valid(cls): 1403 | return _python_frame_index >= 0 1404 | 1405 | @classmethod 1406 | def current_frame(cls): 1407 | try: 1408 | return _python_frame_stack[_python_frame_index] 1409 | except IndexError: 1410 | return None 1411 | 1412 | @classmethod 1413 | def current_filename(cls): 1414 | try: 1415 | return _python_frame_stack[_python_frame_index].info_filename() 1416 | except IndexError: 1417 | return None 1418 | 1419 | @classmethod 1420 | def current_lineno(cls): 1421 | try: 1422 | return _python_frame_stack[_python_frame_index].info_lineno() 1423 | except IndexError: 1424 | return None 1425 | 1426 | def invoke(self, args, from_tty): 1427 | self.dont_repeat() 1428 | if args == 'setup': 1429 | self._setup() 1430 | elif args == 'teardown': 1431 | self._teardown() 1432 | elif not args == '': 1433 | try: 1434 | cmd, subargs = args.split(' ', 1) 1435 | except ValueError: 1436 | cmd = args 1437 | subargs = '' 1438 | if cmd == 'select': 1439 | self._select(subargs) 1440 | elif cmd == 'print': 1441 | self._print(subargs) 1442 | elif cmd == 'backtrace' or cmd == 'bt': 1443 | self._backtrace(subargs) 1444 | elif cmd == 'sources': 1445 | self._source(subargs) 1446 | elif cmd == 'locals': 1447 | self._locals(subargs) 1448 | elif cmd == 'globals': 1449 | self._globals(subargs) 1450 | else: 1451 | raise NotImplementedError(cmd) 1452 | return True 1453 | 1454 | def _setup(self): 1455 | global _python_frame_index 1456 | _python_frame_stack[:] = [ 1457 | PythonFrame(gdb_eval('pyddd_ipa_current_frame')) 1458 | ] 1459 | _python_frame_index = 0 1460 | 1461 | def _teardown(self): 1462 | global _python_frame_index 1463 | _python_frame_index = -1 1464 | 1465 | def _push(self, n): 1466 | frame = _python_frame_stack[-1] 1467 | while n: 1468 | f = frame.older() 1469 | if f == 0: 1470 | break 1471 | frame = PythonFrame(f) 1472 | _python_frame_stack.append(frame) 1473 | n -= 1 1474 | return n 1475 | 1476 | def _select(self, args): 1477 | global _python_frame_index 1478 | if args != '': 1479 | if args[0] == '+': 1480 | n = int(args[1:]) 1481 | _python_frame_index += n 1482 | try: 1483 | _python_frame_stack[_python_frame_index] 1484 | k = 0 1485 | except IndexError: 1486 | k = len(_python_frame_stack) 1487 | if k: 1488 | d = self._push(k - _python_frame_index + 1) 1489 | _python_frame_index -= d 1490 | 1491 | elif args[0] == '-': 1492 | n = int(args[1:]) 1493 | _python_frame_index -= n 1494 | if _python_frame_index < 0: 1495 | _python_frame_index = 0 1496 | 1497 | elif args[0].isdigital(): 1498 | n = int(args) 1499 | try: 1500 | _python_frame_stack[n] 1501 | _python_frame_index = n 1502 | except IndexError: 1503 | pass 1504 | if _python_frame_index != n: 1505 | d = self._push(len(_python_frame_stack) - n + 1) 1506 | _python_frame_index = n - d 1507 | else: 1508 | _python_frame_index = 0 1509 | for frame in _python_frame_stack: 1510 | if frame._name == args: 1511 | break 1512 | _python_frame_index += 1 1513 | else: 1514 | frame = _python_frame_stack[-1] 1515 | while True: 1516 | f = frame.older() 1517 | if f == 0: 1518 | break 1519 | frame = PythonFrame(f) 1520 | _python_frame_stack.append(frame) 1521 | if frame.info_name() == args: 1522 | break 1523 | _python_frame_index += 1 1524 | 1525 | def _print(self, args): 1526 | verbose = args!='' 1527 | frame = _python_frame_stack[_python_frame_index] 1528 | frame._print(level=_python_frame_index, verbose=verbose) 1529 | 1530 | def _source(self, args): 1531 | frame = _python_frame_stack[_python_frame_index] 1532 | frame.info_sources(args) 1533 | 1534 | def _locals(self, args): 1535 | frame = _python_frame_stack[_python_frame_index] 1536 | gdb_output ('Locals:') 1537 | for k, v in frame.info_locals().items(): 1538 | if k not in self.info_args(): 1539 | gdb_output (' %s=%s' % (k, v)) 1540 | 1541 | def _globals(self, args): 1542 | frame = _python_frame_stack[_python_frame_index] 1543 | gdb_output ('Globals:') 1544 | for k, v in frame.info_globals().items(): 1545 | if k not in self.info_args(): 1546 | gdb_output (' %s=%s' % (k, v)) 1547 | 1548 | def _backtrace(self, args): 1549 | if args[:4] == 'full': 1550 | verbose = True 1551 | args = args[4:].strip() 1552 | else: 1553 | verbose = False 1554 | if args == '': 1555 | start = 0 1556 | n = -1 1557 | else: 1558 | if args[0] == '-': 1559 | n = int(args[1:]) 1560 | start = _python_frame_index + n 1561 | if start < 0: 1562 | start = 0 1563 | n = _python_frame_index + 1 1564 | else: 1565 | n = int(args) 1566 | start = _python_frame_index 1567 | try: 1568 | while n: 1569 | try: 1570 | frame = _python_frame_stack[start] 1571 | except IndexError: 1572 | frame = None 1573 | if frame is None: 1574 | frame = _python_frame_stack[-1] 1575 | while n: 1576 | f = frame.older() 1577 | if f == 0: 1578 | break 1579 | frame = PythonFrame(f) 1580 | _python_frame_stack.append(frame) 1581 | frame._print(level=start, verbose=verbose) 1582 | start += 1 1583 | n -= 1 1584 | break 1585 | frame._print(level=start, verbose=verbose) 1586 | start += 1 1587 | n -= 1 1588 | except KeyboardInterrupt: 1589 | pass 1590 | 1591 | ################################################################# 1592 | # 1593 | # Part: Parse Script 1594 | # 1595 | ################################################################# 1596 | 1597 | class PythonSymbolList(list): 1598 | ''' 1599 | Parse python scripts to a list like 1600 | (name, lineno), ... 1601 | name is any function/class/method in the python script 1602 | ''' 1603 | def __init__(self, filename=None): 1604 | super(PythonSymbolList, self).__init__() 1605 | self.filename = filename 1606 | if filename is not None: 1607 | self.load(filename) 1608 | 1609 | def visit(self, level, node): 1610 | if isinstance(node, ast.mod): 1611 | for child in ast.iter_child_nodes(node): 1612 | self.visit(level, child) 1613 | 1614 | elif isinstance(node, ast.FunctionDef): 1615 | self.append((node.name, node.lineno)) 1616 | for child in node.body: 1617 | self.visit(level+1, child) 1618 | 1619 | elif isinstance(node, ast.ClassDef): 1620 | self.append((node.name, node.lineno)) 1621 | for child in node.body: 1622 | self.visit(level+1, child) 1623 | 1624 | def reload(self): 1625 | del self[:] 1626 | self.load() 1627 | 1628 | def load(self, filename=None): 1629 | if filename is None: 1630 | filename = self.filename 1631 | else: 1632 | self.filename = filename 1633 | if filename is None: 1634 | return 1635 | f = open(filename, 'r') 1636 | try: 1637 | node = ast.parse('\n'.join(f.readlines())) 1638 | self.visit(0, node) 1639 | finally: 1640 | f.close() 1641 | 1642 | ################################################################# 1643 | # 1644 | # Part: GDB Frame Decorator (Not Used) 1645 | # 1646 | ################################################################# 1647 | 1648 | # We divide gdb.Frame into: 1649 | # - "python frames": 1650 | # - "bytecode frames" i.e. PyEval_EvalFrameEx 1651 | # - "other python frames": things that are of interest from a python 1652 | # POV, but aren't bytecode (e.g. GC, GIL) 1653 | # - everything else 1654 | 1655 | class PythonFrameDecorator(FrameDecorator): 1656 | 1657 | def __init__(self, fobj): 1658 | super(PythonFrameDecorator, self).__init__(fobj) 1659 | 1660 | def function(self): 1661 | frame = self.inferior_frame() 1662 | name = str(frame.name()) 1663 | if self.is_python_frame: 1664 | pass 1665 | return name 1666 | 1667 | def is_python_frame(self): 1668 | '''Is this a PyEval_EvalFrameEx frame, or some other important 1669 | frame? (see is_other_python_frame for what "important" means in this 1670 | context)''' 1671 | if self.is_evalframeex(): 1672 | return True 1673 | if self.is_other_python_frame(): 1674 | return True 1675 | return False 1676 | 1677 | def is_evalframeex(self): 1678 | '''Is this a PyEval_EvalFrameEx frame?''' 1679 | if self.inferior_frame().name() == 'PyEval_EvalFrameEx': 1680 | ''' 1681 | I believe we also need to filter on the inline 1682 | struct frame_id.inline_depth, only regarding frames with 1683 | an inline depth of 0 as actually being this function 1684 | 1685 | So we reject those with type gdb.INLINE_FRAME 1686 | ''' 1687 | if self.inferior_frame().type() == gdb.NORMAL_FRAME: 1688 | # We have a PyEval_EvalFrameEx frame: 1689 | return True 1690 | 1691 | return False 1692 | 1693 | def is_other_python_frame(self): 1694 | '''Is this frame worth displaying in python backtraces? 1695 | Examples: 1696 | - waiting on the GIL 1697 | - garbage-collecting 1698 | - within a CFunction 1699 | If it is, return a descriptive string 1700 | For other frames, return False 1701 | ''' 1702 | if self.is_waiting_for_gil(): 1703 | return 'Waiting for the GIL' 1704 | elif self.is_gc_collect(): 1705 | return 'Garbage-collecting' 1706 | else: 1707 | # Detect invocations of PyCFunction instances: 1708 | older = self.older() 1709 | if older and older._gdbframe.name() == 'PyCFunction_Call': 1710 | # Within that frame: 1711 | # "func" is the local containing the PyObject* of the 1712 | # PyCFunctionObject instance 1713 | # "f" is the same value, but cast to (PyCFunctionObject*) 1714 | # "self" is the (PyObject*) of the 'self' 1715 | try: 1716 | # Use the prettyprinter for the func: 1717 | func = older._gdbframe.read_var('func') 1718 | return str(func) 1719 | except RuntimeError: 1720 | return 'PyCFunction invocation (unable to read "func")' 1721 | 1722 | # This frame isn't worth reporting: 1723 | return False 1724 | 1725 | def is_waiting_for_gil(self): 1726 | '''Is this frame waiting on the GIL?''' 1727 | # This assumes the _POSIX_THREADS version of Python/ceval_gil.h: 1728 | name = self.inferior_frame().name() 1729 | if name: 1730 | return 'pthread_cond_timedwait' in name 1731 | 1732 | def is_gc_collect(self): 1733 | '''Is this frame "collect" within the garbage-collector?''' 1734 | return self.inferior_frame().name() == 'collect' 1735 | 1736 | class FrameFilter(): 1737 | 1738 | def __init__(self): 1739 | self.name = "PyshieldPythonFilter" 1740 | self.priority = 100 1741 | self.enabled = True 1742 | 1743 | # Register this frame filter with the global frame_filters 1744 | # dictionary. 1745 | # gdb.frame_filters[self.name] = self 1746 | 1747 | def filter(self, frame_iter): 1748 | import itertools 1749 | frame_iter = itertools.imap(PythonFrameDecorator, 1750 | frame_iter) 1751 | return frame_iter 1752 | 1753 | ################################################################# 1754 | # 1755 | # Part: Main Code 1756 | # 1757 | ################################################################# 1758 | 1759 | # Create internal python breakpoints 1760 | PythonInternalExceptionCatchpoint() 1761 | PythonInternalCallCatchpoint() 1762 | PythonInternalLineBreakpoint() 1763 | PythonInternalVolatileBreakpoint() 1764 | 1765 | # Register commands 1766 | PythonIPALoadDataCommand() 1767 | PythonIPAFrameCommand() 1768 | PythonFileCommand() 1769 | PythonExecArgsCommand() 1770 | PythonSymbolFileCommand() 1771 | PythonRunCommand() 1772 | PythonBreakpointCommand() 1773 | PythonTempBreakpointCommand() 1774 | PythonCatchpointCommand() 1775 | PythonTempCatchpointCommand() 1776 | PythonClearCommand() 1777 | PythonDeleteCommand() 1778 | PythonEnableCommand() 1779 | PythonDisableCommand() 1780 | PythonInfoCommand() 1781 | 1782 | # Clear imported symbol table when the inferior exits 1783 | gdb.events.exited.connect ( 1784 | lambda e : _imported_script_symbol_table.clear() 1785 | ) 1786 | 1787 | gdb.execute('set $pyddd_ipa_linux_platform = %d' % sys.platform.startswith('linux')) 1788 | # End of libddd.py 1789 | --------------------------------------------------------------------------------