├── .gdbinit ├── INSTALL ├── LICENSE ├── README.md ├── pygdb ├── __init__.py ├── _breakpoint.c ├── breakpoint.py └── console │ ├── __init__.py │ ├── commands.py │ ├── core.py │ └── extensions.py ├── setup.cfg ├── setup.py └── test └── pygdbtest.py /.gdbinit: -------------------------------------------------------------------------------- 1 | # --- BEGIN_HEADER --- 2 | # 3 | # .gdbinit - GDB console init file for Python GDB debugger 4 | # Copyright (C) 2019-2020 The pygdb Project lead by Brian Vinter 5 | # 6 | # This file is part of pygdb. 7 | # 8 | # pygdb is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation; either version 2 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # pygdb is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program; if not, write to the Free Software 20 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 21 | # 22 | # -- END_HEADER --- 23 | # 24 | 25 | # TODO: Look at color filters: https://github.com/daskol/gdb-colour-filter 26 | 27 | # Disable prompt for console output pages 28 | set pagination off 29 | 30 | # Load pygdb commands 31 | # https://sourceware.org/gdb/onlinedocs/gdb/Python-API.html 32 | 33 | define py-init 34 | python import sys 35 | python if 'pygdb.console.commands' in sys.modules: del sys.modules['pygdb.console.commands'] 36 | python if 'pygdb.console.core' in sys.modules: del sys.modules['pygdb.console.core'] 37 | python if 'pygdb.console.extensions' in sys.modules: del sys.modules['pygdb.console.extensions'] 38 | python if 'pygdb.console' in sys.modules: del sys.modules['pygdb.console'] 39 | python if 'pygdb' in sys.modules: del sys.modules['pygdb'] 40 | python if 'pyext' in globals(): del globals()['pyext'] 41 | python import pygdb.console.commands 42 | python import pygdb.console.extensions as pyext 43 | python pygdb.console.commands.register() 44 | end -------------------------------------------------------------------------------- /INSTALL: -------------------------------------------------------------------------------- 1 | Package providing a set of tools for debugging python code with GDB 2 | 3 | Tested with Python 2.7.5 4 | 5 | Dependencies: 6 | ============= 7 | gdb python: https://wiki.python.org/moin/DebuggingWithGdb 8 | C compiler (e.g. gcc), Python development headers, 9 | 10 | Install: 11 | ================= 12 | pip install . 13 | ln -sf .gdbinit ~/ 14 | 15 | Uninstall: 16 | ================= 17 | pip uninstall pygdb 18 | rm -f ~/.gdbinit 19 | -------------------------------------------------------------------------------- /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 | 294 | Copyright (C) 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 | , 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pygdb 2 | Python extension for the [GNU project debugger (GDB)](https://www.gnu.org/software/gdb/) 3 | 4 | ## Introduction 5 | 6 | This package provides a set of tools for debugging python code with GDB, including python code breakpoints. 7 | GDB breakpoints operate on native shared libraries (C/assembler code). Since python is an interpreted language it's not possible to set python code breakpoints directly from the GDB console. 8 | In this package python code breakpoints are supported by utilizing a python-c-extension breakpoint-mark function which is accessible from the GDB console. This method requires 9 | that python code breakpoints are set explictly in the python code. 10 | 11 | ## Dependencies 12 | GDB python: https://wiki.python.org/moin/DebuggingWithGdb 13 | 14 | ## Install 15 | From source: 16 | `pip install .` 17 | 18 | Latest release: 19 | `pip install pygdb` 20 | 21 | Copy [.gdbinit](https://github.com/ucphhpc/pygdb/blob/master/.gdbinit) to `~/.gdbinit` 22 | 23 | ## Literature 24 | https://devguide.python.org/gdb/ 25 | 26 | https://sourceware.org/gdb/onlinedocs/gdb/Python-API.html 27 | 28 | ## Usage 29 | ### Python code breakpoints 30 | Breakpoints are set explicitly in the python code: 31 | ``` 32 | import pygdb.breakpoint 33 | pygdb.breakpoint.enable() 34 | 35 | def Test(): 36 | print "Before breakpoint" 37 | pygdb.breakpoint.set() 38 | print "After breakpoint" 39 | ``` 40 | 41 | __NOTE__: pygdb.breakpoint.set() busy-waits until the python process is attached to the GDB console using the `py-attach` command (see below) 42 | 43 | Pygdb breakpoint messages are written to stderr unless a [Python logger](https://docs.python.org/2/library/logging.html) is provided: 44 | ``` 45 | import logging 46 | import pygdb.breakpoint 47 | 48 | logging.basicConfig(level=logging.INFO) 49 | pygdb.breakpoint.enable(logger=logging.getLogger()) 50 | ``` 51 | 52 | ### GDB console 53 | 54 | Launch GDB in python-mode from the shell 55 | 56 | `$> gdb python` 57 | 58 | Add source-path if not current shell directory 59 | 60 | `gdb> dir PATH` 61 | 62 | Init the pygdb framework 63 | 64 | `gdb> py-init` 65 | 66 | Attach GDB console to the python process with PID, notify pygdb.breakpoint that the console is connected 67 | and continue until the native GDB breakpoint 68 | 69 | `gdb> py-attach PID` 70 | 71 | #### command list 72 | 73 | __NOTE__: The GDB console supports tab-completion. 74 | 75 | All GDB console commands added by this packages are prefixed with `py-` 76 | 77 | For each command use --help for detailed help. 78 | ``` 79 | py-init: Initializes pygdb commands and extensions 80 | py-info-procs: Display list of attached processes 81 | py-info-threads: Display list of attached process threads 82 | py-attach: Attach python process to gdb console 83 | py-detach: Detach python process from gdb console 84 | py-thread: Change python thread in attached process 85 | py-breakpoint: Find and display code for python breakpoint 86 | py-step: Continue until control reaches a different python source line 87 | py-next: Continue until control reaches a different python source line in current frame 88 | py-continue: Continue until next python breakpoint and display code 89 | py-list: Display code for current python frame 90 | py-backtrace: Display current python frame and all the frames within its call stack (if any) 91 | py-inspect-frame: Display information about the current python frame 92 | py-builtins: Display builtin varialbes for current python frame 93 | py-globals: Display global variables for current python frame 94 | py-locals: Display local variables for current python frame 95 | py-print: Display python frame variable (if it exists) 96 | py-set-local: Set local variable in current python frame 97 | py-up: Select and display code for the python stack frame that called this one (if any) 98 | py-down: Select and display code for the python stack frame called by this one (if any) 99 | py-inject: Inject python code into current python frame 100 | ``` 101 | -------------------------------------------------------------------------------- /pygdb/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # --- BEGIN_HEADER --- 5 | # 6 | # __init__ - gdb python extensions 7 | # Copyright (C) 2019-2020 The pygdb Project lead by Brian Vinter 8 | # 9 | # This file is part of pygdb. 10 | # 11 | # pygdb is free software: you can redistribute it and/or modify 12 | # it under the terms of the GNU General Public License as published by 13 | # the Free Software Foundation; either version 2 of the License, or 14 | # (at your option) any later version. 15 | # 16 | # pygdb is distributed in the hope that it will be useful, 17 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | # GNU General Public License for more details. 20 | # 21 | # You should have received a copy of the GNU General Public License 22 | # along with this program; if not, write to the Free Software 23 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 24 | # 25 | # -- END_HEADER --- 26 | # 27 | 28 | """This package provide python extensions for gdb. 29 | This file is needed to tell python that this dir is a package 30 | so that other modules can call, say, import pygdb.breakpoint. 31 | Please refer to http://www.network-theory.co.uk/docs/pytut/tut_51.html 32 | for details 33 | """ 34 | 35 | __dummy = True 36 | 37 | # above line is only to make python tidy behave and not 38 | # move module doc string inside header 39 | 40 | # All sub modules to load in case of 'from X import *' 41 | 42 | __all__ = [ 43 | 'breakpoint', 44 | 'console', 45 | ] 46 | 47 | # Collect all package information here for easy use from scripts and helpers 48 | 49 | package_name = 'GDB Python extension' 50 | short_name = 'pygdb' 51 | 52 | # IMPORTANT: Please keep version in sync with doc-src/README.t2t 53 | 54 | version_tuple = (0, 1, 0) 55 | version_suffix = 'post2' 56 | version_string = '.'.join([str(i) for i in version_tuple]) + version_suffix 57 | package_version = '%s %s' % (package_name, version_string) 58 | project_team = 'The pygdb Project lead by Brian Vinter' 59 | project_email = 'pygdb@erda.dk' 60 | maintainer_team = 'The pygdb maintainers' 61 | maintainer_email = 'pygdb@erda.dk' 62 | project_url = 'https://github.com/ucphhpc/pygdb' 63 | download_url = 'https://github.com/ucphhpc/pygdb/releases' 64 | license_name = 'GNU GPL v2' 65 | short_desc = \ 66 | 'Python extension for the GNU project debugger (GDB)' 67 | long_desc = \ 68 | """Python extension for the GNU project debugger (GDB): 69 | https://www.gnu.org/software/gdb/ 70 | 71 | Documentation: https://github.com/ucphhpc/pygdb 72 | """ 73 | project_class = [ 74 | 'Development Status :: 4 - Beta', 75 | 'Environment :: Console', 76 | 'Intended Audience :: Developers', 77 | 'License :: OSI Approved :: GNU General Public License v2 (GPLv2)', 78 | 'Operating System :: OS Independent', 79 | 'Programming Language :: Python :: 2.7', 80 | 'Topic :: Software Development :: Debuggers', 81 | ] 82 | project_keywords = [ 83 | 'Python', 84 | 'Python C extensions', 85 | 'Debugger', 86 | ] 87 | 88 | # Requirements 89 | 90 | full_requires = [] 91 | versioned_requires = [] 92 | project_requires = [] 93 | 94 | # Optional packages required for additional functionality (for extras_require) 95 | 96 | project_extras = {} 97 | package_provides = short_name 98 | project_platforms = ['All'] 99 | -------------------------------------------------------------------------------- /pygdb/_breakpoint.c: -------------------------------------------------------------------------------- 1 | /* --- BEGIN_HEADER --- 2 | 3 | _breakpoint - Shared library functions for Python GDB breakpoint 4 | Copyright (C) 2019-2020 The pygdb Project lead by Brian Vinter 5 | 6 | This file is part of pygdb. 7 | 8 | pygdb is free software: you can redistribute it and/or modify 9 | it under the terms of the GNU General Public License as published by 10 | the Free Software Foundation; either version 2 of the License, or 11 | (at your option) any later version. 12 | 13 | pygdb is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with this program; if not, write to the Free Software 20 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 21 | 22 | -- END_HEADER --- */ 23 | 24 | #include 25 | 26 | // NOTE: We need a non-static function for the GDB breakpoint 27 | PyObject* _pygdb_breakpoint_mark() { 28 | return Py_BuildValue(""); 29 | } 30 | 31 | static PyObject* breakpoint_mark(PyObject* self) { 32 | return _pygdb_breakpoint_mark(); 33 | } 34 | 35 | static char breakpoint_mark_docs[] = \ 36 | "Used for python GDB breakpoints.\n"; 37 | 38 | static PyMethodDef breakpoint_mark_funcs[] = { 39 | {"breakpoint_mark", 40 | (PyCFunction)breakpoint_mark, 41 | METH_NOARGS, 42 | breakpoint_mark_docs}, 43 | {NULL} 44 | }; 45 | 46 | void init_pygdb(void) { 47 | Py_InitModule3("_pygdb", breakpoint_mark_funcs, breakpoint_mark_docs); 48 | } 49 | -------------------------------------------------------------------------------- /pygdb/breakpoint.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # --- BEGIN_HEADER --- 5 | # 6 | # breakpoint - Python GDB breakpoint 7 | # Copyright (C) 2019-2020 The pygdb Project lead by Brian Vinter 8 | # 9 | # This file is part of pygdb. 10 | # 11 | # pygdb is free software: you can redistribute it and/or modify 12 | # it under the terms of the GNU General Public License as published by 13 | # the Free Software Foundation; either version 2 of the License, or 14 | # (at your option) any later version. 15 | # 16 | # pygdb is distributed in the hope that it will be useful, 17 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | # GNU General Public License for more details. 20 | # 21 | # You should have received a copy of the GNU General Public License 22 | # along with this program; if not, write to the Free Software 23 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 24 | # 25 | # -- END_HEADER --- 26 | # 27 | 28 | """Python GNU debugger (GDB) breakpoint 29 | https://devguide.python.org/gdb/ 30 | https://wiki.python.org/moin/DebuggingWithGdb 31 | https://sourceware.org/gdb/onlinedocs/gdb/Python-API.html 32 | 33 | USAGE: 34 | import pygdb.breakpoint 35 | pygdb.breakpoint.enable() 36 | pygdb.breakpoint.set() 37 | 38 | This will block the executing thread (at the pygdb.breakpoint.set() call) 39 | until 'pygdb.console.extension.console_connected' is called 40 | from the GDB console. 41 | """ 42 | 43 | import os 44 | import sys 45 | import logging 46 | import signal 47 | import time 48 | import threading 49 | import _pygdb 50 | 51 | enabled = False 52 | gdb_logger = None 53 | console_connected = None 54 | breakpoint_lock = threading.Lock() 55 | 56 | 57 | def handle_sigcont(signalNumber, frame): 58 | """ NOTE: We do not forward SIGCONT because: 59 | You can set a handler, but SIGCONT always makes the process continue regardless: 60 | https://www.gnu.org/software/libc/manual/html_node/Job-Control-Signals.html 61 | """ 62 | global console_connected 63 | breakpoint_lock.acquire() 64 | if not console_connected: 65 | console_connected = True 66 | log("GDB console attached") 67 | breakpoint_lock.release() 68 | 69 | 70 | def enable(logger=None): 71 | """Enable breakpoint""" 72 | global enabled 73 | global console_connected 74 | global breakpoint_lock 75 | 76 | breakpoint_lock.acquire() 77 | if not enabled: 78 | enabled = True 79 | console_connected = False 80 | set_logger(logger) 81 | signal.signal(signal.SIGCONT, handle_sigcont) 82 | log("enabled") 83 | breakpoint_lock.release() 84 | 85 | 86 | def log(msg): 87 | """log info messages""" 88 | if not enabled: 89 | return 90 | 91 | pid = os.getpid() 92 | tid = threading.current_thread().ident 93 | log_msg = "pygdb: (PID: %d, TID: 0x%0.x): %s" \ 94 | % (pid, tid, msg) 95 | 96 | if isinstance(gdb_logger, logging.Logger): 97 | gdb_logger.info(log_msg) 98 | else: 99 | sys.stderr.write("pygdb: %s\n" % log_msg) 100 | 101 | 102 | def set_logger(logger): 103 | """set logger to used by the log function""" 104 | if not enabled: 105 | return False 106 | 107 | global gdb_logger 108 | 109 | result = False 110 | if isinstance(logger, logging.Logger): 111 | gdb_logger = logger 112 | result = True 113 | 114 | return result 115 | 116 | 117 | def set(logger=None): 118 | """Used to set breakpoint, busy-wait until gdb console is connected""" 119 | if not enabled: 120 | return 121 | 122 | global console_connected 123 | 124 | # Wait for gdb console 125 | breakpoint_lock.acquire() 126 | if logger is not None: 127 | set_logger(logger) 128 | while not console_connected: 129 | log("breakpoint.set: waiting for gdb console") 130 | breakpoint_lock.release() 131 | time.sleep(1) 132 | breakpoint_lock.acquire() 133 | breakpoint_lock.release() 134 | _pygdb.breakpoint_mark() 135 | -------------------------------------------------------------------------------- /pygdb/console/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # --- BEGIN_HEADER --- 5 | # 6 | # __init__ - gdb python console extensions 7 | # Copyright (C) 2019-2020 The pygdb Project lead by Brian Vinter 8 | # 9 | # This file is part of pygdb. 10 | # 11 | # pygdb is free software: you can redistribute it and/or modify 12 | # it under the terms of the GNU General Public License as published by 13 | # the Free Software Foundation; either version 2 of the License, or 14 | # (at your option) any later version. 15 | # 16 | # pygdb is distributed in the hope that it will be useful, 17 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | # GNU General Public License for more details. 20 | # 21 | # You should have received a copy of the GNU General Public License 22 | # along with this program; if not, write to the Free Software 23 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 24 | # 25 | # -- END_HEADER --- 26 | # 27 | 28 | """This package provide python extensions for gdb console. 29 | This file is needed to tell python that this dir is a package 30 | so that other modules can call, say, import pygdb.breakpoint. 31 | Please refer to http://www.network-theory.co.uk/docs/pytut/tut_51.html 32 | for details 33 | """ 34 | 35 | __dummy = True 36 | 37 | # above line is only to make python tidy behave and not 38 | # move module doc string inside header 39 | -------------------------------------------------------------------------------- /pygdb/console/commands.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # --- BEGIN_HEADER --- 5 | # 6 | # pygdb.console.commands - python gdb console commands 7 | # Copyright (C) 2019-2020 The pygdb Project lead by Brian Vinter 8 | # 9 | # This file is part of pygdb. 10 | # 11 | # pygdb is free software: you can redistribute it and/or modify 12 | # it under the terms of the GNU General Public License as published by 13 | # the Free Software Foundation; either version 2 of the License, or 14 | # (at your option) any later version. 15 | # 16 | # pygdb is distributed in the hope that it will be useful, 17 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | # GNU General Public License for more details. 20 | # 21 | # You should have received a copy of the GNU General Public License 22 | # along with this program; if not, write to the Free Software 23 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 24 | # 25 | # -- END_HEADER --- 26 | # 27 | 28 | """Python gdb console commands""" 29 | 30 | import re 31 | import gdb 32 | from pygdb.console.core import Frame, move_in_stack, \ 33 | PyObjectPtr, PyDictObjectPtr, PyInstanceObjectPtr, HeapTypeObjectPtr, \ 34 | MAX_OUTPUT_LEN 35 | 36 | from pygdb.console.extensions import attach, \ 37 | breakpoint_continue, breakpoint_list, \ 38 | get_pyobject_value, inject_pyframe, inspect_pyframe, \ 39 | list_pyframe, pystep, pynext, set_pyframe_local, switch_thread 40 | 41 | commands = None 42 | 43 | 44 | def help(): 45 | """Display help for all commands""" 46 | global commands 47 | if not commands: 48 | register() 49 | for cmd in commands: 50 | print "-------------------------------------------------------------------------------" 51 | cmd.help() 52 | print "-------------------------------------------------------------------------------" 53 | 54 | 55 | def register(): 56 | """Register new commands to the GDB console""" 57 | global commands 58 | commands = [ 59 | cmd_py_info_procs('py-info-procs'), 60 | cmd_py_info_threads('py-info-threads'), 61 | cmd_py_attach('py-attach'), 62 | cmd_py_detach('py-detach'), 63 | cmd_py_thread('py-thread'), 64 | cmd_py_breakpoint('py-breakpoint'), 65 | cmd_py_step('py-step'), 66 | cmd_py_next('py-next'), 67 | cmd_py_continue('py-continue'), 68 | cmd_py_list('py-list'), 69 | cmd_py_backtrace('py-backtrace'), 70 | cmd_py_inspect_frame('py-inspect-frame'), 71 | cmd_py_builtins('py-builtins'), 72 | cmd_py_globals('py-globals'), 73 | cmd_py_locals('py-locals'), 74 | cmd_py_print('py-print'), 75 | cmd_py_set_local('py-set-local'), 76 | cmd_py_up('py-up'), 77 | cmd_py_down('py-down'), 78 | cmd_py_inject("py-inject") 79 | ] 80 | 81 | 82 | class cmd_py_info_procs(gdb.Command): 83 | """Display list of attached processes""" 84 | 85 | name = None 86 | 87 | def __init__(self, name): 88 | self.name = name 89 | gdb.Command.__init__(self, 90 | self.name, 91 | gdb.COMMAND_DATA, 92 | gdb.COMPLETE_COMMAND) 93 | 94 | def help(self): 95 | print "USAGE: %s" % self.name 96 | print "" 97 | doc_arr = self.__doc__.split('\n') 98 | for ent in doc_arr: 99 | print ent.strip() 100 | 101 | def invoke(self, args, from_tty): 102 | if str(args) == "--help": 103 | self.help() 104 | return 105 | gdb.execute('info inferior') 106 | 107 | 108 | class cmd_py_info_threads(gdb.Command): 109 | """Display list of attached process threads""" 110 | 111 | name = None 112 | 113 | def __init__(self, name): 114 | self.name = name 115 | gdb.Command.__init__(self, 116 | self.name, 117 | gdb.COMMAND_DATA, 118 | gdb.COMPLETE_COMMAND) 119 | 120 | def help(self): 121 | print "USAGE: %s" % self.name 122 | print "" 123 | doc_arr = self.__doc__.split('\n') 124 | for ent in doc_arr: 125 | print ent.strip() 126 | 127 | def invoke(self, args, from_tty): 128 | if str(args) == "--help": 129 | self.help() 130 | return 131 | gdb.execute('info threads') 132 | 133 | 134 | class cmd_py_attach(gdb.Command): 135 | """Attach python process (PID) to gdb console""" 136 | 137 | name = None 138 | 139 | def __init__(self, name): 140 | self.name = name 141 | gdb.Command.__init__(self, 142 | self.name, 143 | gdb.COMMAND_DATA, 144 | gdb.COMPLETE_COMMAND) 145 | 146 | def help(self): 147 | print "USAGE: %s PID" % self.name 148 | print "" 149 | doc_arr = self.__doc__.split('\n') 150 | for ent in doc_arr: 151 | print ent.strip() 152 | 153 | def invoke(self, args, from_tty): 154 | if str(args) == "--help": 155 | self.help() 156 | return 157 | try: 158 | pid = int(args) 159 | except: 160 | self.help() 161 | return 162 | 163 | # Reset GDB environment including old breakpoints 164 | 165 | try: 166 | gdb.execute('delete') 167 | except Exception: 168 | pass 169 | 170 | # Attach pid 171 | 172 | attach(pid) 173 | 174 | # Re-register pygdb commands 175 | 176 | register() 177 | 178 | 179 | class cmd_py_detach(gdb.Command): 180 | """Detach python process (GDB-PROCESS-NUM) from gdb console""" 181 | 182 | name = None 183 | 184 | def __init__(self, name): 185 | self.name = name 186 | gdb.Command.__init__(self, 187 | self.name, 188 | gdb.COMMAND_DATA, 189 | gdb.COMPLETE_COMMAND) 190 | 191 | def help(self): 192 | print "USAGE: %s GDB-PROCESS-NUM" % self.name 193 | print "" 194 | doc_arr = self.__doc__.split('\n') 195 | for ent in doc_arr: 196 | print ent.strip() 197 | 198 | def invoke(self, args, from_tty): 199 | if str(args) == "--help": 200 | self.help() 201 | return 202 | try: 203 | num = int(args) 204 | except: 205 | self.help() 206 | return 207 | gdb.execute("detach inferior %d" % num) 208 | 209 | 210 | class cmd_py_thread(gdb.Command): 211 | """Switch to python thread id (TID)""" 212 | 213 | name = None 214 | 215 | def __init__(self, name): 216 | self.name = name 217 | gdb.Command.__init__(self, 218 | self.name, 219 | gdb.COMMAND_DATA, 220 | gdb.COMPLETE_COMMAND) 221 | 222 | def help(self): 223 | print "USAGE: %s TID" % self.name 224 | print "" 225 | doc_arr = self.__doc__.split('\n') 226 | for ent in doc_arr: 227 | print ent.strip() 228 | 229 | def invoke(self, args, from_tty): 230 | if str(args) == "--help": 231 | self.help() 232 | return 233 | try: 234 | threadid = int(args, 16) 235 | except: 236 | self.help() 237 | return 238 | switch_thread(threadid) 239 | 240 | 241 | class cmd_py_breakpoint(gdb.Command): 242 | """Find and display code for python breakpoint 243 | Switch to optional python thread id (TID) if provided""" 244 | 245 | name = None 246 | 247 | def __init__(self, name): 248 | self.name = name 249 | gdb.Command.__init__(self, 250 | self.name, 251 | gdb.COMMAND_DATA, 252 | gdb.COMPLETE_COMMAND) 253 | 254 | def help(self): 255 | print "USAGE: %s [TID]" % self.name 256 | print "" 257 | doc_arr = self.__doc__.split('\n') 258 | for ent in doc_arr: 259 | print ent.strip() 260 | 261 | def invoke(self, args, from_tty): 262 | if str(args) == "--help": 263 | self.help() 264 | return 265 | threadid = None 266 | try: 267 | if args: 268 | threadid = int(args, 16) 269 | except: 270 | self.help() 271 | return 272 | breakpoint_list(threadid=threadid) 273 | 274 | 275 | class cmd_py_step(gdb.Command): 276 | """Continue until control reaches a different python source line""" 277 | 278 | name = None 279 | 280 | def __init__(self, name): 281 | self.name = name 282 | gdb.Command.__init__(self, 283 | self.name, 284 | gdb.COMMAND_DATA, 285 | gdb.COMPLETE_COMMAND) 286 | 287 | def help(self): 288 | print "USAGE: %s" % self.name 289 | print "" 290 | doc_arr = self.__doc__.split('\n') 291 | for ent in doc_arr: 292 | print ent.strip() 293 | 294 | def invoke(self, args, from_tty): 295 | if str(args) == "--help": 296 | self.help() 297 | return 298 | 299 | file_prefix = None 300 | if args: 301 | file_prefix = args 302 | pystep(file_prefix=file_prefix) 303 | 304 | 305 | class cmd_py_next(gdb.Command): 306 | """Continue until control reaches a different python source line in current frame""" 307 | 308 | name = None 309 | 310 | def __init__(self, name): 311 | self.name = name 312 | gdb.Command.__init__(self, 313 | self.name, 314 | gdb.COMMAND_DATA, 315 | gdb.COMPLETE_COMMAND) 316 | 317 | def help(self): 318 | print "USAGE: %s" % self.name 319 | print "" 320 | doc_arr = self.__doc__.split('\n') 321 | for ent in doc_arr: 322 | print ent.strip() 323 | 324 | def invoke(self, args, from_tty): 325 | if str(args) == "--help": 326 | self.help() 327 | return 328 | 329 | file_prefix = None 330 | if args: 331 | file_prefix = args 332 | pynext(file_prefix=file_prefix) 333 | 334 | 335 | class cmd_py_continue(gdb.Command): 336 | """Continue until next python breakpoint and display code""" 337 | 338 | name = None 339 | 340 | def __init__(self, name): 341 | self.name = name 342 | gdb.Command.__init__(self, 343 | self.name, 344 | gdb.COMMAND_DATA, 345 | gdb.COMPLETE_COMMAND) 346 | 347 | def help(self): 348 | print "USAGE: %s" % self.name 349 | print "" 350 | doc_arr = self.__doc__.split('\n') 351 | for ent in doc_arr: 352 | print ent.strip() 353 | 354 | def invoke(self, args, from_tty): 355 | if str(args) == "--help": 356 | self.help() 357 | return 358 | breakpoint_continue() 359 | 360 | 361 | class cmd_py_list(gdb.Command): 362 | """Display code for current python frame 363 | 364 | Use argument: 365 | START 366 | to list at a different line number within the python source. 367 | 368 | Use argument: 369 | START, END 370 | to list a specific range of lines within the python source. 371 | """ 372 | 373 | name = None 374 | 375 | def __init__(self, name): 376 | self.name = name 377 | gdb.Command.__init__(self, 378 | self.name, 379 | gdb.COMMAND_FILES, 380 | gdb.COMPLETE_COMMAND) 381 | 382 | def help(self): 383 | print "USAGE: %s [start] [end]" % self.name 384 | print "" 385 | doc_arr = self.__doc__.split('\n') 386 | for ent in doc_arr: 387 | print ent.strip() 388 | 389 | def invoke(self, args, from_tty): 390 | if str(args) == "--help": 391 | self.help() 392 | return 393 | 394 | start = None 395 | end = None 396 | 397 | m = re.match(r'\s*(\d+)\s*', args) 398 | if m: 399 | start = int(m.group(0)) 400 | 401 | m = re.match(r'\s*(\d+)\s*,\s*(\d+)\s*', args) 402 | if m: 403 | start, end = map(int, m.groups()) 404 | 405 | list_pyframe(start=start, end=end) 406 | 407 | 408 | class cmd_py_backtrace(gdb.Command): 409 | """Display current python frame and all the frames within its call stack (if any)""" 410 | 411 | name = None 412 | 413 | def __init__(self, name): 414 | self.name = name 415 | gdb.Command.__init__(self, 416 | self.name, 417 | gdb.COMMAND_STACK, 418 | gdb.COMPLETE_COMMAND) 419 | 420 | def help(self): 421 | print "USAGE: %s" % self.name 422 | print "" 423 | doc_arr = self.__doc__.split('\n') 424 | for ent in doc_arr: 425 | print ent.strip() 426 | 427 | def invoke(self, args, from_tty): 428 | if str(args) == "--help": 429 | self.help() 430 | return 431 | 432 | frame = Frame.get_selected_python_frame() 433 | while frame: 434 | if frame.is_evalframeex(): 435 | frame.print_summary() 436 | frame = frame.older() 437 | 438 | 439 | class cmd_py_inspect_frame(gdb.Command): 440 | """Display information about the current python frame""" 441 | 442 | name = None 443 | 444 | def __init__(self, name): 445 | self.name = name 446 | gdb.Command.__init__(self, 447 | self.name, 448 | gdb.COMMAND_DATA, 449 | gdb.COMPLETE_COMMAND) 450 | 451 | def help(self): 452 | print "USAGE: %s [start] [end]" % self.name 453 | print "" 454 | doc_arr = self.__doc__.split('\n') 455 | for ent in doc_arr: 456 | print ent.strip() 457 | 458 | def invoke(self, args, from_tty): 459 | if str(args) == "--help": 460 | self.help() 461 | return 462 | 463 | inspect_pyframe() 464 | 465 | 466 | class cmd_py_builtins(gdb.Command): 467 | """Display builtin varialbes for current python frame""" 468 | 469 | name = None 470 | 471 | def __init__(self, name): 472 | self.name = name 473 | gdb.Command.__init__(self, 474 | self.name, 475 | gdb.COMMAND_DATA, 476 | gdb.COMPLETE_COMMAND) 477 | 478 | def help(self): 479 | print "USAGE: %s" % self.name 480 | print "" 481 | doc_arr = self.__doc__.split('\n') 482 | for ent in doc_arr: 483 | print ent.strip() 484 | 485 | def invoke(self, args, from_tty): 486 | if str(args) == "--help": 487 | self.help() 488 | return 489 | 490 | frame = Frame.get_selected_python_frame() 491 | if not frame: 492 | print "Unable to locate python frame" 493 | return 494 | 495 | pyop_frame = frame.get_pyop() 496 | if not pyop_frame: 497 | print "Unable to read information on python frame" 498 | return 499 | 500 | for pyop_name, pyop_value in pyop_frame.iter_builtins(): 501 | print('%s = %s' 502 | % (pyop_name.proxyval(set()), 503 | pyop_value.get_truncated_repr(MAX_OUTPUT_LEN))) 504 | 505 | 506 | class cmd_py_globals(gdb.Command): 507 | """Display global variables for current python frame""" 508 | 509 | name = None 510 | 511 | def __init__(self, name): 512 | self.name = name 513 | gdb.Command.__init__(self, 514 | self.name, 515 | gdb.COMMAND_DATA, 516 | gdb.COMPLETE_COMMAND) 517 | 518 | def help(self): 519 | print "USAGE: %s" % self.name 520 | print "" 521 | doc_arr = self.__doc__.split('\n') 522 | for ent in doc_arr: 523 | print ent.strip() 524 | 525 | def invoke(self, args, from_tty): 526 | if str(args) == "--help": 527 | self.help() 528 | return 529 | 530 | frame = Frame.get_selected_python_frame() 531 | if not frame: 532 | print "Unable to locate python frame" 533 | return 534 | 535 | pyop_frame = frame.get_pyop() 536 | if not pyop_frame: 537 | print "Unable to read information on python frame" 538 | return 539 | 540 | for pyop_name, pyop_value in pyop_frame.iter_globals(): 541 | print('%s = %s' 542 | % (pyop_name.proxyval(set()), 543 | pyop_value.get_truncated_repr(MAX_OUTPUT_LEN))) 544 | 545 | 546 | class cmd_py_locals(gdb.Command): 547 | """Display local variables for current python frame""" 548 | 549 | name = None 550 | 551 | def __init__(self, name): 552 | self.name = name 553 | gdb.Command.__init__(self, 554 | self.name, 555 | gdb.COMMAND_DATA, 556 | gdb.COMPLETE_COMMAND) 557 | 558 | def help(self): 559 | print "USAGE: %s" % self.name 560 | print "" 561 | doc_arr = self.__doc__.split('\n') 562 | for ent in doc_arr: 563 | print ent.strip() 564 | 565 | def invoke(self, args, from_tty): 566 | if str(args) == "--help": 567 | self.help() 568 | return 569 | 570 | frame = Frame.get_selected_python_frame() 571 | if not frame: 572 | print "Unable to locate python frame" 573 | return 574 | 575 | pyop_frame = frame.get_pyop() 576 | if not pyop_frame: 577 | print "Unable to read information on python frame" 578 | return 579 | 580 | for pyop_name, pyop_value in pyop_frame.iter_locals(): 581 | print('%s = %s' 582 | % (pyop_name.proxyval(set()), 583 | pyop_value.get_truncated_repr(MAX_OUTPUT_LEN))) 584 | 585 | 586 | class cmd_py_print(gdb.Command): 587 | """Display python frame variable (if it exists) 588 | Nested dictionary and object values are displayed using dots 589 | 590 | Use argument: 591 | x 592 | to display value for variable x 593 | 594 | Use argument: 595 | x.y 596 | to display value y for dictionary/object variable x 597 | """ 598 | 599 | name = None 600 | 601 | def __init__(self, name): 602 | self.name = name 603 | gdb.Command.__init__(self, 604 | self.name, 605 | gdb.COMPLETE_COMMAND) 606 | 607 | def help(self): 608 | print "USAGE: %s" % self.name 609 | print "" 610 | doc_arr = self.__doc__.split('\n') 611 | for ent in doc_arr: 612 | print ent.strip() 613 | 614 | def invoke(self, args, from_tty): 615 | name = str(args) 616 | if name == "--help": 617 | self.help() 618 | return 619 | 620 | (scope, value) = get_pyobject_value(name) 621 | if value: 622 | value_dict = {} 623 | if isinstance(value, PyDictObjectPtr): 624 | value_dict = value 625 | elif isinstance(value, PyInstanceObjectPtr): 626 | value_dict = value.pyop_field('in_dict') 627 | elif isinstance(value, HeapTypeObjectPtr): 628 | value_dict = value.get_attr_dict() 629 | if value_dict: 630 | print "------------------------------------------------------------------------------" 631 | print "(%s) %s:" % (scope, name) 632 | print "------------------------------------------------------------------------------" 633 | for key, val in value_dict.iteritems(): 634 | if isinstance(key, PyObjectPtr): 635 | key = key.get_truncated_repr(MAX_OUTPUT_LEN) 636 | print "%s = %s" % (key, val.get_truncated_repr(MAX_OUTPUT_LEN)) 637 | print "------------------------------------------------------------------------------" 638 | else: 639 | print "(%s) %s = %s" \ 640 | % (scope, 641 | name, 642 | value.get_truncated_repr(MAX_OUTPUT_LEN)) 643 | else: 644 | print "%r not found" % name 645 | 646 | 647 | class cmd_py_set_local(gdb.Command): 648 | """Set local variable in current python frame""" 649 | 650 | name = None 651 | 652 | def __init__(self, name): 653 | self.name = name 654 | gdb.Command.__init__(self, 655 | self.name, 656 | gdb.COMMAND_DATA, 657 | gdb.COMPLETE_COMMAND) 658 | 659 | def help(self): 660 | print "USAGE: %s" % self.name 661 | print "" 662 | doc_arr = self.__doc__.split('\n') 663 | for ent in doc_arr: 664 | print ent.strip() 665 | 666 | def invoke(self, args, from_tty): 667 | if str(args) == "--help": 668 | self.help() 669 | return 670 | 671 | key, value = args.split(" ") 672 | set_pyframe_local(key, value) 673 | 674 | 675 | class cmd_py_up(gdb.Command): 676 | """Select and display code for the python stack frame that called this one (if any)""" 677 | 678 | name = None 679 | 680 | def __init__(self, name): 681 | self.name = name 682 | gdb.Command.__init__(self, 683 | self.name, 684 | gdb.COMMAND_STACK, 685 | gdb.COMPLETE_COMMAND) 686 | 687 | def help(self): 688 | print "USAGE: %s" % self.name 689 | print "" 690 | doc_arr = self.__doc__.split('\n') 691 | for ent in doc_arr: 692 | print ent.strip() 693 | 694 | def invoke(self, args, from_tty): 695 | if str(args) == "--help": 696 | self.help() 697 | return 698 | 699 | result = move_in_stack(move_up=True, silently=True) 700 | if result: 701 | list_pyframe() 702 | else: 703 | print "Unable to find an older python frame" 704 | 705 | 706 | class cmd_py_down(gdb.Command): 707 | """Select and display code for the python stack frame called by this one (if any)""" 708 | 709 | name = None 710 | 711 | def __init__(self, name): 712 | self.name = name 713 | gdb.Command.__init__(self, 714 | self.name, 715 | gdb.COMMAND_STACK, 716 | gdb.COMPLETE_COMMAND) 717 | 718 | def help(self): 719 | print "USAGE: %s" % self.name 720 | print "" 721 | doc_arr = self.__doc__.split('\n') 722 | for ent in doc_arr: 723 | print ent.strip() 724 | 725 | def invoke(self, args, from_tty): 726 | if str(args) == "--help": 727 | self.help() 728 | return 729 | 730 | result = move_in_stack(move_up=False, silently=True) 731 | if result: 732 | list_pyframe() 733 | else: 734 | print "Unable to find an newer python frame" 735 | 736 | 737 | class cmd_py_inject(gdb.Command): 738 | """Inject python code into current python frame""" 739 | 740 | name = None 741 | 742 | def __init__(self, name): 743 | self.name = name 744 | gdb.Command.__init__(self, 745 | self.name, 746 | gdb.COMMAND_DATA, 747 | gdb.COMPLETE_COMMAND) 748 | 749 | def help(self): 750 | print "USAGE: %s" % self.name 751 | print "" 752 | doc_arr = self.__doc__.split('\n') 753 | for ent in doc_arr: 754 | print ent.strip() 755 | 756 | def invoke(self, args, from_tty): 757 | if str(args) == "--help": 758 | self.help() 759 | return 760 | 761 | cmd = str(args) 762 | inject_pyframe(cmd) 763 | -------------------------------------------------------------------------------- /pygdb/console/core.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # --- BEGIN_HEADER --- 5 | # 6 | # pygdb.console.core - python gdb console core functions 7 | # Copyright (C) 2019-2020 The pygdb Project lead by Brian Vinter 8 | # 9 | # This file is part of pygdb. 10 | # 11 | # pygdb is free software: you can redistribute it and/or modify 12 | # it under the terms of the GNU General Public License as published by 13 | # the Free Software Foundation; either version 2 of the License, or 14 | # (at your option) any later version. 15 | # 16 | # pygdb is distributed in the hope that it will be useful, 17 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | # GNU General Public License for more details. 20 | # 21 | # You should have received a copy of the GNU General Public License 22 | # along with this program; if not, write to the Free Software 23 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 24 | # 25 | # -- END_HEADER --- 26 | # 27 | 28 | # 29 | # This file is a modified version of python-gdb.py from python 2.7.5: 30 | # https://devguide.python.org/gdb/#gdb-7-and-later 31 | # 32 | # You can redistribute it and/or modify it under the terms of the 33 | # PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2: 34 | # https://docs.python.org/2.7/license.html 35 | # 36 | # From gdb 7 onwards, gdb's build can be configured --with-python, allowing gdb 37 | # to be extended with Python code e.g. for library-specific data visualizations, 38 | # such as for the C++ STL types. Documentation on this API can be seen at: 39 | # http://sourceware.org/gdb/current/onlinedocs/gdb/Python-API.html 40 | # 41 | # 42 | # This python module deals with the case when the process being debugged (the 43 | # "inferior process" in gdb parlance) is itself python, or more specifically, 44 | # linked against libpython. In this situation, almost every item of data is a 45 | # (PyObject*), and having the debugger merely print their addresses is not very 46 | # enlightening. 47 | # 48 | # This module embeds knowledge about the implementation details of libpython so 49 | # that we can emit useful visualizations e.g. a string, a list, a dict, a frame 50 | # giving file/line information and the state of local variables 51 | # 52 | # In particular, given a gdb.Value corresponding to a PyObject* in the inferior 53 | # process, we can generate a "proxy value" within the gdb process. For example, 54 | # given a PyObject* in the inferior process that is in fact a PyListObject* 55 | # holding three PyObject* that turn out to be PyStringObject* instances, we can 56 | # generate a proxy value within the gdb process that is a list of strings: 57 | # ["foo", "bar", "baz"] 58 | 59 | # Doing so can be expensive for complicated graphs of objects, and could take 60 | # some time, so we also have a "write_repr" method that writes a representation 61 | # of the data to a file-like object. This allows us to stop the traversal by 62 | # having the file-like object raise an exception if it gets too much data. 63 | # 64 | # With both "proxyval" and "write_repr" we keep track of the set of all addresses 65 | # visited so far in the traversal, to avoid infinite recursion due to cycles in 66 | # the graph of object references. 67 | # 68 | # We try to defer gdb.lookup_type() invocations for python types until as late as 69 | # possible: for a dynamically linked python binary, when the process starts in 70 | # the debugger, the libpython.so hasn't been dynamically loaded yet, so none of 71 | # the type names are known to the debugger 72 | # 73 | # The module also extends gdb with some python-specific commands. 74 | # 75 | 76 | """GDB console python core functions""" 77 | 78 | import sys 79 | import gdb 80 | 81 | # Look up the gdb.Type for some standard types: 82 | _type_char_ptr = gdb.lookup_type('char').pointer() # char* 83 | _type_unsigned_char_ptr = gdb.lookup_type( 84 | 'unsigned char').pointer() # unsigned char* 85 | _type_void_ptr = gdb.lookup_type('void').pointer() # void* 86 | _type_size_t = gdb.lookup_type('size_t') 87 | 88 | SIZEOF_VOID_P = _type_void_ptr.sizeof 89 | 90 | 91 | Py_TPFLAGS_HEAPTYPE = (1L << 9) 92 | 93 | Py_TPFLAGS_INT_SUBCLASS = (1L << 23) 94 | Py_TPFLAGS_LONG_SUBCLASS = (1L << 24) 95 | Py_TPFLAGS_LIST_SUBCLASS = (1L << 25) 96 | Py_TPFLAGS_TUPLE_SUBCLASS = (1L << 26) 97 | Py_TPFLAGS_STRING_SUBCLASS = (1L << 27) 98 | Py_TPFLAGS_UNICODE_SUBCLASS = (1L << 28) 99 | Py_TPFLAGS_DICT_SUBCLASS = (1L << 29) 100 | Py_TPFLAGS_BASE_EXC_SUBCLASS = (1L << 30) 101 | Py_TPFLAGS_TYPE_SUBCLASS = (1L << 31) 102 | 103 | 104 | MAX_OUTPUT_LEN = 1024 105 | 106 | 107 | class NullPyObjectPtr(RuntimeError): 108 | pass 109 | 110 | 111 | def safety_limit(val): 112 | # Given a integer value from the process being debugged, limit it to some 113 | # safety threshold so that arbitrary breakage within said process doesn't 114 | # break the gdb process too much (e.g. sizes of iterations, sizes of lists) 115 | return min(val, 1000) 116 | 117 | 118 | def safe_range(val): 119 | # As per range, but don't trust the value too much: cap it to a safety 120 | # threshold in case the data was corrupted 121 | return xrange(safety_limit(val)) 122 | 123 | 124 | class StringTruncated(RuntimeError): 125 | pass 126 | 127 | 128 | class TruncatedStringIO(object): 129 | """Similar to cStringIO, but can truncate the output by raising a 130 | StringTruncated exception""" 131 | 132 | def __init__(self, maxlen=None): 133 | self._val = '' 134 | self.maxlen = maxlen 135 | 136 | def write(self, data): 137 | if self.maxlen: 138 | if len(data) + len(self._val) > self.maxlen: 139 | # Truncation: 140 | self._val += data[0:self.maxlen - len(self._val)] 141 | raise StringTruncated() 142 | 143 | self._val += data 144 | 145 | def getvalue(self): 146 | return self._val 147 | 148 | 149 | class PyObjectPtr(object): 150 | """ 151 | Class wrapping a gdb.Value that's a either a (PyObject*) within the 152 | inferior process, or some subclass pointer e.g. (PyStringObject*) 153 | 154 | There will be a subclass for every refined PyObject type that we care 155 | about. 156 | 157 | Note that at every stage the underlying pointer could be NULL, point 158 | to corrupt data, etc; this is the debugger, after all. 159 | """ 160 | _typename = 'PyObject' 161 | 162 | def __init__(self, gdbval, cast_to=None): 163 | if cast_to: 164 | self._gdbval = gdbval.cast(cast_to) 165 | else: 166 | self._gdbval = gdbval 167 | 168 | def field(self, name): 169 | """ 170 | Get the gdb.Value for the given field within the PyObject, coping with 171 | some python 2 versus python 3 differences. 172 | 173 | Various libpython types are defined using the "PyObject_HEAD" and 174 | "PyObject_VAR_HEAD" macros. 175 | 176 | In Python 2, this these are defined so that "ob_type" and (for a var 177 | object) "ob_size" are fields of the type in question. 178 | 179 | In Python 3, this is defined as an embedded PyVarObject type thus: 180 | PyVarObject ob_base; 181 | so that the "ob_size" field is located insize the "ob_base" field, and 182 | the "ob_type" is most easily accessed by casting back to a (PyObject*). 183 | """ 184 | if self.is_null(): 185 | raise NullPyObjectPtr(self) 186 | 187 | if name == 'ob_type': 188 | pyo_ptr = self._gdbval.cast(PyObjectPtr.get_gdb_type()) 189 | return pyo_ptr.dereference()[name] 190 | 191 | if name == 'ob_size': 192 | try: 193 | # Python 2: 194 | return self._gdbval.dereference()[name] 195 | except RuntimeError: 196 | # Python 3: 197 | return self._gdbval.dereference()['ob_base'][name] 198 | 199 | # General case: look it up inside the object: 200 | return self._gdbval.dereference()[name] 201 | 202 | def pyop_field(self, name): 203 | """ 204 | Get a PyObjectPtr for the given PyObject* field within this PyObject, 205 | coping with some python 2 versus python 3 differences. 206 | """ 207 | return PyObjectPtr.from_pyobject_ptr(self.field(name)) 208 | 209 | def write_field_repr(self, name, out, visited): 210 | """ 211 | Extract the PyObject* field named "name", and write its representation 212 | to file-like object "out" 213 | """ 214 | field_obj = self.pyop_field(name) 215 | field_obj.write_repr(out, visited) 216 | 217 | def get_truncated_repr(self, maxlen): 218 | """ 219 | Get a repr-like string for the data, but truncate it at "maxlen" bytes 220 | (ending the object graph traversal as soon as you do) 221 | """ 222 | out = TruncatedStringIO(maxlen) 223 | try: 224 | self.write_repr(out, set()) 225 | except StringTruncated: 226 | # Truncation occurred: 227 | return out.getvalue() + '...(truncated)' 228 | 229 | # No truncation occurred: 230 | return out.getvalue() 231 | 232 | def type(self): 233 | return PyTypeObjectPtr(self.field('ob_type')) 234 | 235 | def is_null(self): 236 | return 0 == long(self._gdbval) 237 | 238 | def is_optimized_out(self): 239 | """ 240 | Is the value of the underlying PyObject* visible to the debugger? 241 | 242 | This can vary with the precise version of the compiler used to build 243 | Python, and the precise version of gdb. 244 | 245 | See e.g. https://bugzilla.redhat.com/show_bug.cgi?id=556975 with 246 | PyEval_EvalFrameEx's "f" 247 | """ 248 | return self._gdbval.is_optimized_out 249 | 250 | def safe_tp_name(self): 251 | try: 252 | return self.type().field('tp_name').string() 253 | except NullPyObjectPtr: 254 | # NULL tp_name? 255 | return 'unknown' 256 | except RuntimeError: 257 | # Can't even read the object at all? 258 | return 'unknown' 259 | 260 | def proxyval(self, visited): 261 | """ 262 | Scrape a value from the inferior process, and try to represent it 263 | within the gdb process, whilst (hopefully) avoiding crashes when 264 | the remote data is corrupt. 265 | 266 | Derived classes will override this. 267 | 268 | For example, a PyIntObject* with ob_ival 42 in the inferior process 269 | should result in an int(42) in this process. 270 | 271 | visited: a set of all gdb.Value pyobject pointers already visited 272 | whilst generating this value (to guard against infinite recursion when 273 | visiting object graphs with loops). Analogous to Py_ReprEnter and 274 | Py_ReprLeave 275 | """ 276 | 277 | class FakeRepr(object): 278 | """ 279 | Class representing a non-descript PyObject* value in the inferior 280 | process for when we don't have a custom scraper, intended to have 281 | a sane repr(). 282 | """ 283 | 284 | def __init__(self, tp_name, address): 285 | self.tp_name = tp_name 286 | self.address = address 287 | 288 | def __repr__(self): 289 | # For the NULL pointer, we have no way of knowing a type, so 290 | # special-case it as per 291 | # http://bugs.python.org/issue8032#msg100882 292 | if self.address == 0: 293 | return '0x0' 294 | return '<%s at remote 0x%x>' % (self.tp_name, self.address) 295 | 296 | return FakeRepr(self.safe_tp_name(), 297 | long(self._gdbval)) 298 | 299 | def write_repr(self, out, visited): 300 | """ 301 | Write a string representation of the value scraped from the inferior 302 | process to "out", a file-like object. 303 | """ 304 | # Default implementation: generate a proxy value and write its repr 305 | # However, this could involve a lot of work for complicated objects, 306 | # so for derived classes we specialize this 307 | return out.write(repr(self.proxyval(visited))) 308 | 309 | @classmethod 310 | def subclass_from_type(cls, t): 311 | """ 312 | Given a PyTypeObjectPtr instance wrapping a gdb.Value that's a 313 | (PyTypeObject*), determine the corresponding subclass of PyObjectPtr 314 | to use 315 | 316 | Ideally, we would look up the symbols for the global types, but that 317 | isn't working yet: 318 | (gdb) python print gdb.lookup_symbol('PyList_Type')[0].value 319 | Traceback (most recent call last): 320 | File "", line 1, in 321 | NotImplementedError: Symbol type not yet supported in Python scripts. 322 | Error while executing Python code. 323 | 324 | For now, we use tp_flags, after doing some string comparisons on the 325 | tp_name for some special-cases that don't seem to be visible through 326 | flags 327 | """ 328 | try: 329 | tp_name = t.field('tp_name').string() 330 | tp_flags = int(t.field('tp_flags')) 331 | except RuntimeError: 332 | # Handle any kind of error e.g. NULL ptrs by simply using the base 333 | # class 334 | return cls 335 | 336 | # print 'tp_flags = 0x%08x' % tp_flags 337 | # print 'tp_name = %r' % tp_name 338 | 339 | name_map = {'bool': PyBoolObjectPtr, 340 | 'classobj': PyClassObjectPtr, 341 | 'instance': PyInstanceObjectPtr, 342 | 'NoneType': PyNoneStructPtr, 343 | 'frame': PyFrameObjectPtr, 344 | 'set': PySetObjectPtr, 345 | 'frozenset': PySetObjectPtr, 346 | 'builtin_function_or_method': PyCFunctionObjectPtr, 347 | } 348 | if tp_name in name_map: 349 | return name_map[tp_name] 350 | 351 | if tp_flags & Py_TPFLAGS_HEAPTYPE: 352 | return HeapTypeObjectPtr 353 | 354 | if tp_flags & Py_TPFLAGS_INT_SUBCLASS: 355 | return PyIntObjectPtr 356 | if tp_flags & Py_TPFLAGS_LONG_SUBCLASS: 357 | return PyLongObjectPtr 358 | if tp_flags & Py_TPFLAGS_LIST_SUBCLASS: 359 | return PyListObjectPtr 360 | if tp_flags & Py_TPFLAGS_TUPLE_SUBCLASS: 361 | return PyTupleObjectPtr 362 | if tp_flags & Py_TPFLAGS_STRING_SUBCLASS: 363 | return PyStringObjectPtr 364 | if tp_flags & Py_TPFLAGS_UNICODE_SUBCLASS: 365 | return PyUnicodeObjectPtr 366 | if tp_flags & Py_TPFLAGS_DICT_SUBCLASS: 367 | return PyDictObjectPtr 368 | if tp_flags & Py_TPFLAGS_BASE_EXC_SUBCLASS: 369 | return PyBaseExceptionObjectPtr 370 | # if tp_flags & Py_TPFLAGS_TYPE_SUBCLASS: 371 | # return PyTypeObjectPtr 372 | 373 | # Use the base class: 374 | return cls 375 | 376 | @classmethod 377 | def from_pyobject_ptr(cls, gdbval): 378 | """ 379 | Try to locate the appropriate derived class dynamically, and cast 380 | the pointer accordingly. 381 | """ 382 | try: 383 | p = PyObjectPtr(gdbval) 384 | cls = cls.subclass_from_type(p.type()) 385 | return cls(gdbval, cast_to=cls.get_gdb_type()) 386 | except RuntimeError: 387 | # Handle any kind of error e.g. NULL ptrs by simply using the base 388 | # class 389 | pass 390 | return cls(gdbval) 391 | 392 | @classmethod 393 | def get_gdb_type(cls): 394 | return gdb.lookup_type(cls._typename).pointer() 395 | 396 | def as_address(self): 397 | return long(self._gdbval) 398 | 399 | 400 | class ProxyAlreadyVisited(object): 401 | """ 402 | Placeholder proxy to use when protecting against infinite recursion due to 403 | loops in the object graph. 404 | 405 | Analogous to the values emitted by the users of Py_ReprEnter and Py_ReprLeave 406 | """ 407 | 408 | def __init__(self, rep): 409 | self._rep = rep 410 | 411 | def __repr__(self): 412 | return self._rep 413 | 414 | 415 | def _write_instance_repr(out, visited, name, pyop_attrdict, address): 416 | """Shared code for use by old-style and new-style classes: 417 | write a representation to file-like object 'out'""" 418 | out.write('<') 419 | out.write(name) 420 | 421 | # Write dictionary of instance attributes: 422 | if isinstance(pyop_attrdict, PyDictObjectPtr): 423 | out.write('(') 424 | first = True 425 | for pyop_arg, pyop_val in pyop_attrdict.iteritems(): 426 | if not first: 427 | out.write(', ') 428 | first = False 429 | out.write(pyop_arg.proxyval(visited)) 430 | out.write('=') 431 | pyop_val.write_repr(out, visited) 432 | out.write(')') 433 | out.write(' at remote 0x%x>' % address) 434 | 435 | 436 | class InstanceProxy(object): 437 | 438 | def __init__(self, cl_name, attrdict, address): 439 | self.cl_name = cl_name 440 | self.attrdict = attrdict 441 | self.address = address 442 | 443 | def __repr__(self): 444 | if isinstance(self.attrdict, dict): 445 | kwargs = ', '.join(["%s=%r" % (arg, val) 446 | for arg, val in self.attrdict.iteritems()]) 447 | return '<%s(%s) at remote 0x%x>' % (self.cl_name, 448 | kwargs, self.address) 449 | else: 450 | return '<%s at remote 0x%x>' % (self.cl_name, 451 | self.address) 452 | 453 | 454 | def _PyObject_VAR_SIZE(typeobj, nitems): 455 | return ((typeobj.field('tp_basicsize') + 456 | nitems * typeobj.field('tp_itemsize') + 457 | (SIZEOF_VOID_P - 1) 458 | ) & ~(SIZEOF_VOID_P - 1) 459 | ).cast(_type_size_t) 460 | 461 | 462 | class HeapTypeObjectPtr(PyObjectPtr): 463 | _typename = 'PyObject' 464 | 465 | def get_attr_dict(self): 466 | """ 467 | Get the PyDictObject ptr representing the attribute dictionary 468 | (or None if there's a problem) 469 | """ 470 | try: 471 | typeobj = self.type() 472 | dictoffset = int_from_int(typeobj.field('tp_dictoffset')) 473 | if dictoffset != 0: 474 | if dictoffset < 0: 475 | type_PyVarObject_ptr = gdb.lookup_type( 476 | 'PyVarObject').pointer() 477 | tsize = int_from_int(self._gdbval.cast( 478 | type_PyVarObject_ptr)['ob_size']) 479 | if tsize < 0: 480 | tsize = -tsize 481 | size = _PyObject_VAR_SIZE(typeobj, tsize) 482 | dictoffset += size 483 | assert dictoffset > 0 484 | assert dictoffset % SIZEOF_VOID_P == 0 485 | 486 | dictptr = self._gdbval.cast(_type_char_ptr) + dictoffset 487 | PyObjectPtrPtr = PyObjectPtr.get_gdb_type().pointer() 488 | dictptr = dictptr.cast(PyObjectPtrPtr) 489 | return PyObjectPtr.from_pyobject_ptr(dictptr.dereference()) 490 | except RuntimeError: 491 | # Corrupt data somewhere; fail safe 492 | pass 493 | 494 | # Not found, or some kind of error: 495 | return None 496 | 497 | def proxyval(self, visited): 498 | """ 499 | Support for new-style classes. 500 | 501 | Currently we just locate the dictionary using a transliteration to 502 | python of _PyObject_GetDictPtr, ignoring descriptors 503 | """ 504 | # Guard against infinite loops: 505 | if self.as_address() in visited: 506 | return ProxyAlreadyVisited('<...>') 507 | visited.add(self.as_address()) 508 | 509 | pyop_attr_dict = self.get_attr_dict() 510 | if pyop_attr_dict: 511 | attr_dict = pyop_attr_dict.proxyval(visited) 512 | else: 513 | attr_dict = {} 514 | tp_name = self.safe_tp_name() 515 | 516 | # New-style class: 517 | return InstanceProxy(tp_name, attr_dict, long(self._gdbval)) 518 | 519 | def write_repr(self, out, visited): 520 | # Guard against infinite loops: 521 | if self.as_address() in visited: 522 | out.write('<...>') 523 | return 524 | visited.add(self.as_address()) 525 | 526 | pyop_attrdict = self.get_attr_dict() 527 | _write_instance_repr(out, visited, 528 | self.safe_tp_name(), 529 | pyop_attrdict, 530 | self.as_address()) 531 | 532 | 533 | class ProxyException(Exception): 534 | def __init__(self, tp_name, args): 535 | self.tp_name = tp_name 536 | self.args = args 537 | 538 | def __repr__(self): 539 | return '%s%r' % (self.tp_name, self.args) 540 | 541 | 542 | class PyBaseExceptionObjectPtr(PyObjectPtr): 543 | """ 544 | Class wrapping a gdb.Value that's a PyBaseExceptionObject* i.e. an exception 545 | within the process being debugged. 546 | """ 547 | _typename = 'PyBaseExceptionObject' 548 | 549 | def proxyval(self, visited): 550 | # Guard against infinite loops: 551 | if self.as_address() in visited: 552 | return ProxyAlreadyVisited('(...)') 553 | visited.add(self.as_address()) 554 | arg_proxy = self.pyop_field('args').proxyval(visited) 555 | return ProxyException(self.safe_tp_name(), 556 | arg_proxy) 557 | 558 | def write_repr(self, out, visited): 559 | # Guard against infinite loops: 560 | if self.as_address() in visited: 561 | out.write('(...)') 562 | return 563 | visited.add(self.as_address()) 564 | 565 | out.write(self.safe_tp_name()) 566 | self.write_field_repr('args', out, visited) 567 | 568 | 569 | class PyBoolObjectPtr(PyObjectPtr): 570 | """ 571 | Class wrapping a gdb.Value that's a PyBoolObject* i.e. one of the two 572 | instances (Py_True/Py_False) within the process being debugged. 573 | """ 574 | _typename = 'PyBoolObject' 575 | 576 | def proxyval(self, visited): 577 | if int_from_int(self.field('ob_ival')): 578 | return True 579 | else: 580 | return False 581 | 582 | 583 | class PyClassObjectPtr(PyObjectPtr): 584 | """ 585 | Class wrapping a gdb.Value that's a PyClassObject* i.e. a 586 | instance within the process being debugged. 587 | """ 588 | _typename = 'PyClassObject' 589 | 590 | 591 | class BuiltInFunctionProxy(object): 592 | def __init__(self, ml_name): 593 | self.ml_name = ml_name 594 | 595 | def __repr__(self): 596 | return "" % self.ml_name 597 | 598 | 599 | class BuiltInMethodProxy(object): 600 | def __init__(self, ml_name, pyop_m_self): 601 | self.ml_name = ml_name 602 | self.pyop_m_self = pyop_m_self 603 | 604 | def __repr__(self): 605 | return ('' 606 | % (self.ml_name, 607 | self.pyop_m_self.safe_tp_name(), 608 | self.pyop_m_self.as_address()) 609 | ) 610 | 611 | 612 | class PyCFunctionObjectPtr(PyObjectPtr): 613 | """ 614 | Class wrapping a gdb.Value that's a PyCFunctionObject* 615 | (see Include/methodobject.h and Objects/methodobject.c) 616 | """ 617 | _typename = 'PyCFunctionObject' 618 | 619 | def proxyval(self, visited): 620 | m_ml = self.field('m_ml') # m_ml is a (PyMethodDef*) 621 | ml_name = m_ml['ml_name'].string() 622 | 623 | pyop_m_self = self.pyop_field('m_self') 624 | if pyop_m_self.is_null(): 625 | return BuiltInFunctionProxy(ml_name) 626 | else: 627 | return BuiltInMethodProxy(ml_name, pyop_m_self) 628 | 629 | 630 | class PyCodeObjectPtr(PyObjectPtr): 631 | """ 632 | Class wrapping a gdb.Value that's a PyCodeObject* i.e. a instance 633 | within the process being debugged. 634 | """ 635 | _typename = 'PyCodeObject' 636 | 637 | def addr2line(self, addrq): 638 | """ 639 | Get the line number for a given bytecode offset 640 | 641 | Analogous to PyCode_Addr2Line; translated from pseudocode in 642 | Objects/lnotab_notes.txt 643 | """ 644 | co_lnotab = self.pyop_field('co_lnotab').proxyval(set()) 645 | 646 | # Initialize lineno to co_firstlineno as per PyCode_Addr2Line 647 | # not 0, as lnotab_notes.txt has it: 648 | lineno = int_from_int(self.field('co_firstlineno')) 649 | 650 | addr = 0 651 | for addr_incr, line_incr in zip(co_lnotab[::2], co_lnotab[1::2]): 652 | addr += ord(addr_incr) 653 | if addr > addrq: 654 | return lineno 655 | lineno += ord(line_incr) 656 | return lineno 657 | 658 | 659 | class PyDictObjectPtr(PyObjectPtr): 660 | """ 661 | Class wrapping a gdb.Value that's a PyDictObject* i.e. a dict instance 662 | within the process being debugged. 663 | """ 664 | _typename = 'PyDictObject' 665 | 666 | def iteritems(self): 667 | """ 668 | Yields a sequence of (PyObjectPtr key, PyObjectPtr value) pairs, 669 | analagous to dict.iteritems() 670 | """ 671 | for i in safe_range(self.field('ma_mask') + 1): 672 | ep = self.field('ma_table') + i 673 | pyop_value = PyObjectPtr.from_pyobject_ptr(ep['me_value']) 674 | if not pyop_value.is_null(): 675 | pyop_key = PyObjectPtr.from_pyobject_ptr(ep['me_key']) 676 | yield (pyop_key, pyop_value) 677 | 678 | def proxyval(self, visited): 679 | # Guard against infinite loops: 680 | if self.as_address() in visited: 681 | return ProxyAlreadyVisited('{...}') 682 | visited.add(self.as_address()) 683 | 684 | result = {} 685 | for pyop_key, pyop_value in self.iteritems(): 686 | proxy_key = pyop_key.proxyval(visited) 687 | proxy_value = pyop_value.proxyval(visited) 688 | result[proxy_key] = proxy_value 689 | return result 690 | 691 | def write_repr(self, out, visited): 692 | # Guard against infinite loops: 693 | if self.as_address() in visited: 694 | out.write('{...}') 695 | return 696 | visited.add(self.as_address()) 697 | 698 | out.write('{') 699 | first = True 700 | for pyop_key, pyop_value in self.iteritems(): 701 | if not first: 702 | out.write(', ') 703 | first = False 704 | pyop_key.write_repr(out, visited) 705 | out.write(': ') 706 | pyop_value.write_repr(out, visited) 707 | out.write('}') 708 | 709 | 710 | class PyInstanceObjectPtr(PyObjectPtr): 711 | _typename = 'PyInstanceObject' 712 | 713 | def proxyval(self, visited): 714 | # Guard against infinite loops: 715 | if self.as_address() in visited: 716 | return ProxyAlreadyVisited('<...>') 717 | visited.add(self.as_address()) 718 | 719 | # Get name of class: 720 | in_class = self.pyop_field('in_class') 721 | cl_name = in_class.pyop_field('cl_name').proxyval(visited) 722 | 723 | # Get dictionary of instance attributes: 724 | in_dict = self.pyop_field('in_dict').proxyval(visited) 725 | 726 | # Old-style class: 727 | return InstanceProxy(cl_name, in_dict, long(self._gdbval)) 728 | 729 | def write_repr(self, out, visited): 730 | # Guard against infinite loops: 731 | if self.as_address() in visited: 732 | out.write('<...>') 733 | return 734 | visited.add(self.as_address()) 735 | 736 | # Old-style class: 737 | 738 | # Get name of class: 739 | in_class = self.pyop_field('in_class') 740 | cl_name = in_class.pyop_field('cl_name').proxyval(visited) 741 | 742 | # Get dictionary of instance attributes: 743 | pyop_in_dict = self.pyop_field('in_dict') 744 | 745 | _write_instance_repr(out, visited, 746 | cl_name, pyop_in_dict, self.as_address()) 747 | 748 | 749 | class PyIntObjectPtr(PyObjectPtr): 750 | _typename = 'PyIntObject' 751 | 752 | def proxyval(self, visited): 753 | result = int_from_int(self.field('ob_ival')) 754 | return result 755 | 756 | 757 | class PyListObjectPtr(PyObjectPtr): 758 | _typename = 'PyListObject' 759 | 760 | def __getitem__(self, i): 761 | # Get the gdb.Value for the (PyObject*) with the given index: 762 | field_ob_item = self.field('ob_item') 763 | return field_ob_item[i] 764 | 765 | def proxyval(self, visited): 766 | # Guard against infinite loops: 767 | if self.as_address() in visited: 768 | return ProxyAlreadyVisited('[...]') 769 | visited.add(self.as_address()) 770 | 771 | result = [PyObjectPtr.from_pyobject_ptr(self[i]).proxyval(visited) 772 | for i in safe_range(int_from_int(self.field('ob_size')))] 773 | return result 774 | 775 | def write_repr(self, out, visited): 776 | # Guard against infinite loops: 777 | if self.as_address() in visited: 778 | out.write('[...]') 779 | return 780 | visited.add(self.as_address()) 781 | 782 | out.write('[') 783 | for i in safe_range(int_from_int(self.field('ob_size'))): 784 | if i > 0: 785 | out.write(', ') 786 | element = PyObjectPtr.from_pyobject_ptr(self[i]) 787 | element.write_repr(out, visited) 788 | out.write(']') 789 | 790 | 791 | class PyLongObjectPtr(PyObjectPtr): 792 | _typename = 'PyLongObject' 793 | 794 | def proxyval(self, visited): 795 | """ 796 | Python's Include/longobjrep.h has this declaration: 797 | struct _longobject { 798 | PyObject_VAR_HEAD 799 | digit ob_digit[1]; 800 | }; 801 | 802 | with this description: 803 | The absolute value of a number is equal to 804 | SUM(for i=0 through abs(ob_size)-1) ob_digit[i] * 2**(SHIFT*i) 805 | Negative numbers are represented with ob_size < 0; 806 | zero is represented by ob_size == 0. 807 | 808 | where SHIFT can be either: 809 | #define PyLong_SHIFT 30 810 | #define PyLong_SHIFT 15 811 | """ 812 | ob_size = long(self.field('ob_size')) 813 | if ob_size == 0: 814 | return 0L 815 | 816 | ob_digit = self.field('ob_digit') 817 | 818 | if gdb.lookup_type('digit').sizeof == 2: 819 | SHIFT = 15L 820 | else: 821 | SHIFT = 30L 822 | 823 | digits = [long(ob_digit[i]) * 2**(SHIFT*i) 824 | for i in safe_range(abs(ob_size))] 825 | result = sum(digits) 826 | if ob_size < 0: 827 | result = -result 828 | return result 829 | 830 | 831 | class PyNoneStructPtr(PyObjectPtr): 832 | """ 833 | Class wrapping a gdb.Value that's a PyObject* pointing to the 834 | singleton (we hope) _Py_NoneStruct with ob_type PyNone_Type 835 | """ 836 | _typename = 'PyObject' 837 | 838 | def proxyval(self, visited): 839 | return None 840 | 841 | 842 | class PyFrameObjectPtr(PyObjectPtr): 843 | _typename = 'PyFrameObject' 844 | 845 | def __init__(self, gdbval, cast_to): 846 | PyObjectPtr.__init__(self, gdbval, cast_to) 847 | 848 | if not self.is_optimized_out(): 849 | self.co = PyCodeObjectPtr.from_pyobject_ptr(self.field('f_code')) 850 | self.co_name = self.co.pyop_field('co_name') 851 | self.co_filename = self.co.pyop_field('co_filename') 852 | self.f_lineno = int_from_int(self.field('f_lineno')) 853 | self.f_lasti = int_from_int(self.field('f_lasti')) 854 | self.co_nlocals = int_from_int(self.co.field('co_nlocals')) 855 | self.co_varnames = PyTupleObjectPtr.from_pyobject_ptr( 856 | self.co.field('co_varnames')) 857 | self.f_back = self.field('f_back') 858 | 859 | def iter_locals(self): 860 | """ 861 | Yield a sequence of (name,value) pairs of PyObjectPtr instances, for 862 | the local variables of this frame 863 | """ 864 | if self.is_optimized_out(): 865 | return 866 | 867 | f_localsplus = self.field('f_localsplus') 868 | for i in safe_range(self.co_nlocals): 869 | pyop_value = PyObjectPtr.from_pyobject_ptr(f_localsplus[i]) 870 | if not pyop_value.is_null(): 871 | pyop_name = PyObjectPtr.from_pyobject_ptr(self.co_varnames[i]) 872 | yield (pyop_name, pyop_value) 873 | 874 | def iter_globals(self): 875 | """ 876 | Yield a sequence of (name,value) pairs of PyObjectPtr instances, for 877 | the global variables of this frame 878 | """ 879 | if self.is_optimized_out(): 880 | return 881 | 882 | pyop_globals = self.pyop_field('f_globals') 883 | return pyop_globals.iteritems() 884 | 885 | def iter_builtins(self): 886 | """ 887 | Yield a sequence of (name,value) pairs of PyObjectPtr instances, for 888 | the builtin variables 889 | """ 890 | if self.is_optimized_out(): 891 | return 892 | 893 | pyop_builtins = self.pyop_field('f_builtins') 894 | return pyop_builtins.iteritems() 895 | 896 | def get_var_by_name(self, name): 897 | """ 898 | Look for the named local variable, returning a (PyObjectPtr, scope) pair 899 | where scope is a string 'local', 'global', 'builtin' 900 | 901 | If not found, return (None, None) 902 | """ 903 | for pyop_name, pyop_value in self.iter_locals(): 904 | if name == pyop_name.proxyval(set()): 905 | return pyop_value, 'local' 906 | for pyop_name, pyop_value in self.iter_globals(): 907 | if name == pyop_name.proxyval(set()): 908 | return pyop_value, 'global' 909 | for pyop_name, pyop_value in self.iter_builtins(): 910 | if name == pyop_name.proxyval(set()): 911 | return pyop_value, 'builtin' 912 | return None, None 913 | 914 | def filename(self): 915 | """Get the path of the current Python source file, as a string""" 916 | if self.is_optimized_out(): 917 | return '(frame information optimized out)' 918 | return self.co_filename.proxyval(set()) 919 | 920 | def current_line_num(self): 921 | """Get current line number as an integer (1-based) 922 | 923 | Translated from PyFrame_GetLineNumber and PyCode_Addr2Line 924 | 925 | See Objects/lnotab_notes.txt 926 | """ 927 | if self.is_optimized_out(): 928 | return None 929 | f_trace = self.field('f_trace') 930 | if long(f_trace) != 0: 931 | # we have a non-NULL f_trace: 932 | return self.f_lineno 933 | else: 934 | # try: 935 | return self.co.addr2line(self.f_lasti) 936 | # except ValueError: 937 | # return self.f_lineno 938 | 939 | def current_line(self): 940 | """Get the text of the current source line as a string, with a trailing 941 | newline character""" 942 | if self.is_optimized_out(): 943 | return '(frame information optimized out)' 944 | with open(self.filename(), 'r') as f: 945 | all_lines = f.readlines() 946 | # Convert from 1-based current_line_num to 0-based list offset: 947 | return all_lines[self.current_line_num()-1] 948 | 949 | def write_repr(self, out, visited): 950 | if self.is_optimized_out(): 951 | out.write('(frame information optimized out)') 952 | return 953 | out.write('Frame 0x%x, for file %s, line %i, in %s (' 954 | % (self.as_address(), 955 | self.co_filename, 956 | self.current_line_num(), 957 | self.co_name)) 958 | first = True 959 | for pyop_name, pyop_value in self.iter_locals(): 960 | if not first: 961 | out.write(', ') 962 | first = False 963 | 964 | out.write(pyop_name.proxyval(visited)) 965 | out.write('=') 966 | pyop_value.write_repr(out, visited) 967 | 968 | out.write(')') 969 | 970 | 971 | class PySetObjectPtr(PyObjectPtr): 972 | _typename = 'PySetObject' 973 | 974 | def proxyval(self, visited): 975 | # Guard against infinite loops: 976 | if self.as_address() in visited: 977 | return ProxyAlreadyVisited('%s(...)' % self.safe_tp_name()) 978 | visited.add(self.as_address()) 979 | 980 | members = [] 981 | table = self.field('table') 982 | for i in safe_range(self.field('mask')+1): 983 | setentry = table[i] 984 | key = setentry['key'] 985 | if key != 0: 986 | key_proxy = PyObjectPtr.from_pyobject_ptr( 987 | key).proxyval(visited) 988 | if key_proxy != '': 989 | members.append(key_proxy) 990 | if self.safe_tp_name() == 'frozenset': 991 | return frozenset(members) 992 | else: 993 | return set(members) 994 | 995 | def write_repr(self, out, visited): 996 | out.write(self.safe_tp_name()) 997 | 998 | # Guard against infinite loops: 999 | if self.as_address() in visited: 1000 | out.write('(...)') 1001 | return 1002 | visited.add(self.as_address()) 1003 | 1004 | out.write('([') 1005 | first = True 1006 | table = self.field('table') 1007 | for i in safe_range(self.field('mask')+1): 1008 | setentry = table[i] 1009 | key = setentry['key'] 1010 | if key != 0: 1011 | pyop_key = PyObjectPtr.from_pyobject_ptr(key) 1012 | key_proxy = pyop_key.proxyval(visited) # FIXME! 1013 | if key_proxy != '': 1014 | if not first: 1015 | out.write(', ') 1016 | first = False 1017 | pyop_key.write_repr(out, visited) 1018 | out.write('])') 1019 | 1020 | 1021 | class PyStringObjectPtr(PyObjectPtr): 1022 | _typename = 'PyStringObject' 1023 | 1024 | def __str__(self): 1025 | field_ob_size = self.field('ob_size') 1026 | field_ob_sval = self.field('ob_sval') 1027 | char_ptr = field_ob_sval.address.cast(_type_unsigned_char_ptr) 1028 | return ''.join([chr(char_ptr[i]) for i in safe_range(field_ob_size)]) 1029 | 1030 | def proxyval(self, visited): 1031 | return str(self) 1032 | 1033 | 1034 | class PyTupleObjectPtr(PyObjectPtr): 1035 | _typename = 'PyTupleObject' 1036 | 1037 | def __getitem__(self, i): 1038 | # Get the gdb.Value for the (PyObject*) with the given index: 1039 | field_ob_item = self.field('ob_item') 1040 | return field_ob_item[i] 1041 | 1042 | def proxyval(self, visited): 1043 | # Guard against infinite loops: 1044 | if self.as_address() in visited: 1045 | return ProxyAlreadyVisited('(...)') 1046 | visited.add(self.as_address()) 1047 | 1048 | result = tuple([PyObjectPtr.from_pyobject_ptr( 1049 | self[i]).proxyval(visited) 1050 | for i in safe_range(int_from_int(self.field('ob_size')))]) 1051 | return result 1052 | 1053 | def write_repr(self, out, visited): 1054 | # Guard against infinite loops: 1055 | if self.as_address() in visited: 1056 | out.write('(...)') 1057 | return 1058 | visited.add(self.as_address()) 1059 | 1060 | out.write('(') 1061 | for i in safe_range(int_from_int(self.field('ob_size'))): 1062 | if i > 0: 1063 | out.write(', ') 1064 | element = PyObjectPtr.from_pyobject_ptr(self[i]) 1065 | element.write_repr(out, visited) 1066 | if self.field('ob_size') == 1: 1067 | out.write(',)') 1068 | else: 1069 | out.write(')') 1070 | 1071 | 1072 | class PyTypeObjectPtr(PyObjectPtr): 1073 | _typename = 'PyTypeObject' 1074 | 1075 | 1076 | class PyUnicodeObjectPtr(PyObjectPtr): 1077 | _typename = 'PyUnicodeObject' 1078 | 1079 | def proxyval(self, visited): 1080 | # From unicodeobject.h: 1081 | # Py_ssize_t length; /* Length of raw Unicode data in buffer */ 1082 | # Py_UNICODE *str; /* Raw Unicode buffer */ 1083 | field_length = long(self.field('length')) 1084 | field_str = self.field('str') 1085 | 1086 | # Gather a list of ints from the Py_UNICODE array; these are either 1087 | # UCS-2 or UCS-4 code points: 1088 | Py_UNICODEs = [int(field_str[i]) for i in safe_range(field_length)] 1089 | 1090 | # Convert the int code points to unicode characters, and generate a 1091 | # local unicode instance: 1092 | result = u''.join([unichr(ucs) for ucs in Py_UNICODEs]) 1093 | return result 1094 | 1095 | 1096 | def int_from_int(gdbval): 1097 | return int(str(gdbval)) 1098 | 1099 | 1100 | class Frame(object): 1101 | """ 1102 | Wrapper for gdb.Frame, adding various methods 1103 | """ 1104 | 1105 | def __init__(self, gdbframe): 1106 | self._gdbframe = gdbframe 1107 | 1108 | def older(self): 1109 | older = self._gdbframe.older() 1110 | if older: 1111 | return Frame(older) 1112 | else: 1113 | return None 1114 | 1115 | def newer(self): 1116 | newer = self._gdbframe.newer() 1117 | if newer: 1118 | return Frame(newer) 1119 | else: 1120 | return None 1121 | 1122 | def select(self): 1123 | self._gdbframe.select() 1124 | 1125 | def get_index(self): 1126 | """Calculate index of frame, starting at 0 for the newest frame within 1127 | this thread""" 1128 | index = 0 1129 | # Go down until you reach the newest frame: 1130 | iter_frame = self 1131 | while iter_frame.newer(): 1132 | index += 1 1133 | iter_frame = iter_frame.newer() 1134 | return index 1135 | 1136 | def is_evalframeex(self): 1137 | if self._gdbframe.function(): 1138 | if self._gdbframe.function().name == 'PyEval_EvalFrameEx': 1139 | """ 1140 | I believe we also need to filter on the inline 1141 | struct frame_id.inline_depth, only regarding frames with 1142 | an inline depth of 0 as actually being this function 1143 | 1144 | So we reject those with type gdb.INLINE_FRAME 1145 | """ 1146 | if self._gdbframe.type() == gdb.NORMAL_FRAME: 1147 | # We have a PyEval_EvalFrameEx frame: 1148 | return True 1149 | 1150 | return False 1151 | 1152 | def get_pyop(self): 1153 | try: 1154 | f = self._gdbframe.read_var('f') 1155 | return PyFrameObjectPtr.from_pyobject_ptr(f) 1156 | except ValueError: 1157 | return None 1158 | 1159 | @classmethod 1160 | def get_selected_frame(cls): 1161 | _gdbframe = gdb.selected_frame() 1162 | if _gdbframe: 1163 | return Frame(_gdbframe) 1164 | return None 1165 | 1166 | @classmethod 1167 | def get_selected_python_frame(cls): 1168 | """Try to obtain the Frame for the python code in the selected frame, 1169 | or None""" 1170 | frame = cls.get_selected_frame() 1171 | 1172 | while frame: 1173 | if frame.is_evalframeex(): 1174 | return frame 1175 | frame = frame.older() 1176 | 1177 | # Not found: 1178 | return None 1179 | 1180 | def print_summary(self): 1181 | if self.is_evalframeex(): 1182 | pyop = self.get_pyop() 1183 | if pyop: 1184 | sys.stdout.write('#%i %s\n' % ( 1185 | self.get_index(), pyop.get_truncated_repr(MAX_OUTPUT_LEN))) 1186 | sys.stdout.write(pyop.current_line()) 1187 | else: 1188 | sys.stdout.write( 1189 | '#%i (unable to read python frame information)\n' 1190 | % self.get_index()) 1191 | else: 1192 | sys.stdout.write('#%i\n' % self.get_index()) 1193 | 1194 | 1195 | def move_in_stack(move_up, silently=False): 1196 | """Move up or down the stack (for the py-up/py-down command)""" 1197 | frame = Frame.get_selected_python_frame() 1198 | while frame: 1199 | if move_up: 1200 | iter_frame = frame.older() 1201 | else: 1202 | iter_frame = frame.newer() 1203 | 1204 | if not iter_frame: 1205 | break 1206 | 1207 | if iter_frame.is_evalframeex(): 1208 | # Result: 1209 | iter_frame.select() 1210 | if not silently: 1211 | iter_frame.print_summary() 1212 | return True 1213 | 1214 | frame = iter_frame 1215 | 1216 | if not silently: 1217 | if move_up: 1218 | print 'Unable to find an older python frame' 1219 | else: 1220 | print 'Unable to find a newer python frame' 1221 | 1222 | return False 1223 | -------------------------------------------------------------------------------- /pygdb/console/extensions.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # --- BEGIN_HEADER --- 5 | # 6 | # pygdb.console.extension - python gdb console extension functions 7 | # Copyright (C) 2019-2020 The pygdb Project lead by Brian Vinter 8 | # 9 | # This file is part of pygdb. 10 | # 11 | # pygdb is free software: you can redistribute it and/or modify 12 | # it under the terms of the GNU General Public License as published by 13 | # the Free Software Foundation; either version 2 of the License, or 14 | # (at your option) any later version. 15 | # 16 | # pygdb is distributed in the hope that it will be useful, 17 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | # GNU General Public License for more details. 20 | # 21 | # You should have received a copy of the GNU General Public License 22 | # along with this program; if not, write to the Free Software 23 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 24 | # 25 | # -- END_HEADER --- 26 | # 27 | 28 | """GDB console python extension functions""" 29 | 30 | import sys 31 | import gdb 32 | from pygdb.console.core import Frame, move_in_stack, \ 33 | PyDictObjectPtr, PyInstanceObjectPtr, HeapTypeObjectPtr 34 | 35 | __breakpoint_identifier = '_pygdb_breakpoint_mark' 36 | __breakpoint_func = '_pygdb.breakpoint_mark()' 37 | 38 | 39 | def term_color(*v): 40 | """ 41 | set_term_color(text-type, text-color, background-color) 42 | text-type: 1: bold 43 | 2: faded 44 | 3: italic 45 | 4: underlined 46 | text-color: 30: black 47 | 31: red 48 | 32: green 49 | 33: yellow 50 | 34: blue 51 | 35: magneta 52 | 36: cyan 53 | back-ground-color 40: black 54 | 41: red 55 | 42: green 56 | 43: yellow 57 | 44: blue 58 | 45: magneta 59 | 46: cyan 60 | set_term_color(0): #reset 61 | """ 62 | 63 | return '\x1B['+';'.join(map(str, v))+'m' 64 | 65 | 66 | def inject_pyframe(cmd, silently=False): 67 | """Inject python code *cmd* into selected frame""" 68 | 69 | result = gdb.execute("call PyGILState_Ensure()", to_string=True) 70 | gstate = result.split('=')[1].strip() 71 | if not silently: 72 | print "PyGILState_Ensure: %s" % gstate 73 | gdb.execute("call PyRun_SimpleString(\"%s\")" % cmd) 74 | gdb.execute("call PyGILState_Release(%s)" % gstate) 75 | 76 | 77 | def get_pyframe_f_back(silently=False): 78 | """Get id of parent pyframe""" 79 | 80 | pyop_frame = get_selected_pyop() 81 | if not pyop_frame: 82 | return None 83 | 84 | return pyop_frame.f_back 85 | 86 | 87 | def get_selected_pyop(silently=False): 88 | """Returns pyop for selected frame""" 89 | 90 | frame = Frame.get_selected_python_frame() 91 | if not frame: 92 | if not silently: 93 | print 'Unable to locate python frame' 94 | return None 95 | 96 | pyop = frame.get_pyop() 97 | if not pyop: 98 | if not silently: 99 | print 'Unable to read information on python frame' 100 | return None 101 | 102 | return pyop 103 | 104 | 105 | def get_pyobject_value(name): 106 | """Returns pyobject value for *name*, 107 | supports nested pyobject values are supported""" 108 | 109 | name_arr = name.split('.') 110 | name_arr_len = len(name_arr) 111 | first_name = name_arr[0] 112 | 113 | pyop_frame = get_selected_pyop() 114 | if not pyop_frame: 115 | return (None, None) 116 | 117 | (pyop_var, scope) = pyop_frame.get_var_by_name(first_name) 118 | if not pyop_var: 119 | return (scope, None) 120 | 121 | cur_value = pyop_var 122 | for i in xrange(1, name_arr_len): 123 | next_name = name_arr[i] 124 | pyop_var_dict = None 125 | if isinstance(cur_value, PyDictObjectPtr): 126 | pyop_var_dict = pyop_var 127 | elif isinstance(cur_value, PyInstanceObjectPtr): 128 | pyop_var_dict = pyop_var.pyop_field('in_dict') 129 | elif isinstance(cur_value, HeapTypeObjectPtr): 130 | pyop_var_dict = cur_value.get_attr_dict() 131 | else: 132 | return (scope, None) 133 | cur_value = None 134 | if pyop_var_dict: 135 | # NOTE: pyop_var_dict does not support __getitem__ 136 | for key, value in pyop_var_dict.iteritems(): 137 | if str(key) == next_name: 138 | cur_value = value 139 | if cur_value is None: 140 | break 141 | 142 | return (scope, cur_value) 143 | 144 | 145 | def breakpoint_frame(silently=False): 146 | """Move to python frame with active breakpoint""" 147 | 148 | org_frame = gdb.selected_frame() 149 | gdb.newest_frame().select() 150 | pyop = get_selected_pyop() 151 | if not pyop: 152 | org_frame.select() 153 | return False 154 | line = pyop.current_line().strip() 155 | if line != __breakpoint_func: 156 | if not silently: 157 | print "No breakpoint found for current thread" 158 | org_frame.select() 159 | return False 160 | elif not silently: 161 | frame = Frame.get_selected_python_frame() 162 | frame.print_summary() 163 | 164 | return True 165 | 166 | 167 | def breakpoint_caller_frame(silently=False): 168 | """Move to the frame that called the breakpoint frame""" 169 | status = breakpoint_frame(silently=silently) 170 | if status: 171 | more_frames = move_in_stack(move_up=True, silently=silently) 172 | if not more_frames: 173 | status = False 174 | 175 | return status 176 | 177 | 178 | def list_pyframe(start=None, end=None): 179 | """List current python frame with active code line highlighted""" 180 | show_lines = 30 181 | pyop = get_selected_pyop() 182 | if not pyop: 183 | return 184 | 185 | filename = pyop.filename() 186 | lineno = pyop.current_line_num() 187 | 188 | if start is None: 189 | start = lineno - (show_lines/2) 190 | end = lineno + (show_lines/2) 191 | if end is None: 192 | end = start + show_lines 193 | if start < 1: 194 | start = 1 195 | 196 | thread = gdb.execute('thread', to_string=True) 197 | sys.stdout.write("%s%s:%s\n" % (thread, filename, lineno)) 198 | with open(filename, 'r') as fh: 199 | all_lines = fh.readlines() 200 | # start and end are 1-based, all_lines is 0-based; 201 | for i, line in enumerate(all_lines[start-1:end]): 202 | 203 | display_lineno = int(i+start) 204 | # Highlight current line: 205 | # print color + sys.stdout.write(line[:-1]) 206 | # is a hack to color the full line 207 | if display_lineno == lineno: 208 | color = term_color(0, 34, 47) 209 | else: 210 | color = term_color(0) 211 | print color 212 | sys.stdout.write("%d\t%s" % (display_lineno, line[:-1])) 213 | color = term_color(0) 214 | print color 215 | print "" 216 | fh.close() 217 | 218 | 219 | def attach(pid): 220 | """Delete old breakpoints, add python breakpoint, 221 | attach process and signal that GDB console is attached""" 222 | 223 | # Delete old breakpoints 224 | gdb.execute('delete breakpoints') 225 | 226 | # Attach process 227 | gdb.execute('attach %d' % pid) 228 | 229 | # Add python breakpoint to breakpoints 230 | gdb.execute('break %s' % __breakpoint_identifier) 231 | 232 | # Signal that GDB console is connected (handled by pygdb.breakpoint) 233 | gdb.execute('signal SIGCONT') 234 | 235 | # Show breakpoint 236 | breakpoint_list() 237 | 238 | 239 | def list_threads(): 240 | """List all threads for attached process""" 241 | # NOTE: threads[-1] is main thread 242 | threads = gdb.selected_inferior().threads() 243 | for thread in threads: 244 | print thread 245 | 246 | 247 | def switch_thread(threadid, silently=False): 248 | """Change active thread to *threadid*""" 249 | cmd = "thread find Thread 0x%.x" % threadid 250 | thread_info = gdb.execute(cmd, to_string=True) 251 | thread_info_arr = thread_info.split(" ") 252 | if thread_info_arr[0] == 'No': 253 | if not silently: 254 | print "No Thread found with thread id: 0x%.x" % threadid 255 | return False 256 | else: 257 | cmd = "thread %s" % thread_info_arr[1] 258 | thread_switch = gdb.execute(cmd, to_string=True) 259 | if not silently: 260 | print thread_switch 261 | return True 262 | 263 | 264 | def breakpoint_list(threadid=None, silently=False): 265 | """Display python code for breakpoint frame""" 266 | 267 | status = breakpoint_caller_frame(silently=True) 268 | if status: 269 | list_pyframe() 270 | elif not silently: 271 | if threadid: 272 | msg = "Unable to find breakpoint for thread: 0x%.x" % threadid 273 | else: 274 | msg = "Unable to find breakpoint for current thread" 275 | print msg 276 | 277 | 278 | def breakpoint_continue(): 279 | """Continue until next breakpoint and display it""" 280 | gdb.execute('continue') 281 | breakpoint_list() 282 | 283 | 284 | def inspect_pyframe(show_globals=True, show_locals=True): 285 | """Display details about the current python frame""" 286 | 287 | pyframe_index = 0 288 | pyframe_f_back = get_pyframe_f_back() 289 | 290 | arguments = {'pyframe_index': pyframe_index, 291 | 'pyframe_f_back': pyframe_f_back, 292 | 'show_globals': show_globals, 293 | 'show_locals': show_locals} 294 | 295 | print "====================================================================" 296 | print "Output is written to gdb_logger if found otherwise to process stdout" 297 | print "====================================================================" 298 | 299 | cmd = \ 300 | "from __future__ import print_function as __GDB_DEBUG_print_function; \ 301 | import inspect as __GDB_DEBUG_inspect; \ 302 | __GDB_DEBUG_logger = \ 303 | print if not 'pygdb' in globals() \ 304 | else pygdb.breakpoint.gdb_logger_debug; \ 305 | __GDB_DEBUG_stack = __GDB_DEBUG_inspect.stack(); \ 306 | __GDB_DEBUG_stack_len = len(__GDB_DEBUG_stack); \ 307 | __GDB_DEBUG_frame = __GDB_DEBUG_stack[%(pyframe_index)d][0]; \ 308 | __GDB_DEBUG_frameidx_func = lambda frame: \ 309 | 0 if not frame or id(frame.f_back) == %(pyframe_f_back)d \ 310 | else 1 + __GDB_DEBUG_frameidx_func(frame.f_back); \ 311 | __GDB_DEBUG_frameidx = __GDB_DEBUG_frameidx_func(__GDB_DEBUG_frame); \ 312 | __GDB_DEBUG_frameidx = __GDB_DEBUG_frameidx \ 313 | if __GDB_DEBUG_frameidx < __GDB_DEBUG_stack_len \ 314 | else 0; \ 315 | __GDB_DEBUG_frame, \ 316 | __GDB_DEBUG_filename, \ 317 | __GDB_DEBUG_line_number, \ 318 | __GDB_DEBUG_function_name, \ 319 | __GDB_DEBUG_lines, \ 320 | __GDB_DEBUG_index = __GDB_DEBUG_stack[__GDB_DEBUG_frameidx]; \ 321 | __GDB_DEBUG_logger(chr(10) \ 322 | + '========================== py_inspect_frame ==========================' + chr(10) \ 323 | + 'frame No.: ' + str(__GDB_DEBUG_frameidx) + chr(10) \ 324 | + 'frame: ' + str(__GDB_DEBUG_frame) + chr(10) \ 325 | + 'frame.f_back: ' + str(__GDB_DEBUG_frame.f_back) + chr(10) \ 326 | + 'filename: ' + str(__GDB_DEBUG_filename) + chr(10) \ 327 | + 'line_number: ' + str(__GDB_DEBUG_line_number) + chr(10) \ 328 | + 'function_name: ' + str(__GDB_DEBUG_function_name) + chr(10) \ 329 | + 'lines: ' + str(__GDB_DEBUG_lines) + chr(10) \ 330 | + 'index: ' + str(__GDB_DEBUG_index) + chr(10) \ 331 | + '----------------------------------------------------------------------' + chr(10) \ 332 | + 'f_globals: ' + chr(10) \ 333 | + '----------------------------------------------------------------------' + chr(10) \ 334 | + chr(10).join(' = '.join((str(k),str(v))) for k,v in __GDB_DEBUG_frame.f_globals.items()\ 335 | if %(show_globals)s and not k.startswith('__GDB_DEBUG')) + chr(10) \ 336 | + '----------------------------------------------------------------------' + chr(10) \ 337 | + 'f_locals: ' + chr(10) \ 338 | + '----------------------------------------------------------------------' + chr(10) \ 339 | + chr(10).join(' = '.join((str(k),str(v))) for k,v in __GDB_DEBUG_frame.f_locals.items() \ 340 | if %(show_locals)s and not k.startswith('__GDB_DEBUG')) + chr(10) \ 341 | + '======================== End py_inspect_frame ========================' + chr(10)); \ 342 | [globals().pop(key) \ 343 | for key in globals().keys() \ 344 | if key.startswith('__GDB_DEBUG_')]" % arguments 345 | 346 | inject_pyframe(cmd) 347 | 348 | 349 | def set_pyframe_local(key, value): 350 | """Set local variable *key* to *value* in current python frame""" 351 | arguments = {'pyframe_index': 0, 352 | 'pyframe_f_back': get_pyframe_f_back(), 353 | 'key': key, 354 | 'value': value} 355 | cmd = \ 356 | "from __future__ import print_function as __GDB_DEBUG_print_function; \ 357 | import inspect as __GDB_DEBUG_inspect; \ 358 | import ctypes as __GDB_DEBUG_ctypes; \ 359 | __GDB_DEBUG_logger = \ 360 | print if not 'pygdb' in globals() \ 361 | else pygdb.breakpoint.gdb_logger_debug; \ 362 | __GDB_DEBUG_stack = __GDB_DEBUG_inspect.stack(); \ 363 | __GDB_DEBUG_stack_len = len(__GDB_DEBUG_stack); \ 364 | __GDB_DEBUG_frame = __GDB_DEBUG_stack[%(pyframe_index)d][0]; \ 365 | __GDB_DEBUG_frameidx_func = lambda frame: \ 366 | 0 if not frame or id(frame.f_back) == %(pyframe_f_back)d \ 367 | else 1 + __GDB_DEBUG_frameidx_func(frame.f_back); \ 368 | __GDB_DEBUG_frameidx = __GDB_DEBUG_frameidx_func(__GDB_DEBUG_frame); \ 369 | __GDB_DEBUG_frameidx = __GDB_DEBUG_frameidx \ 370 | if __GDB_DEBUG_frameidx < __GDB_DEBUG_stack_len \ 371 | else 0; \ 372 | __GDB_DEBUG_frame = __GDB_DEBUG_stack[__GDB_DEBUG_frameidx][0]; \ 373 | __GDB_DEBUG_frame.f_locals['%(key)s'] = %(value)s; \ 374 | __GDB_DEBUG_ctypes.pythonapi.PyFrame_LocalsToFast(\ 375 | __GDB_DEBUG_ctypes.py_object(__GDB_DEBUG_frame),\ 376 | __GDB_DEBUG_ctypes.c_int(0)); \ 377 | [globals().pop(key) \ 378 | for key in globals().keys() \ 379 | if key.startswith('__GDB_DEBUG_')]" % arguments 380 | 381 | inject_pyframe(cmd) 382 | 383 | 384 | def pystep(skip_breakpoint_mark=True, 385 | file_prefix=None, 386 | silently=False, 387 | scheduler_locking=False): 388 | """Continue until control reaches a different python source line""" 389 | 390 | if scheduler_locking: 391 | status = gdb.execute('set scheduler-locking step', to_string=True) 392 | else: 393 | status = gdb.execute('set scheduler-locking off', to_string=True) 394 | if not silently: 395 | print status 396 | 397 | step_count = 0 398 | max_step_count = 10000 399 | pyop = get_selected_pyop(silently=silently) 400 | if not pyop: 401 | return 402 | start_filepath = filepath = pyop.filename() 403 | start_lineno = lineno = pyop.current_line_num() 404 | line = pyop.current_line().strip() 405 | while (start_filepath == filepath and start_lineno == lineno) \ 406 | or (skip_breakpoint_mark and line == __breakpoint_func) \ 407 | or (file_prefix and not filepath.startswith(file_prefix)): 408 | step_msg = gdb.execute('step', to_string=True) 409 | gdb.newest_frame().select() 410 | pyop = get_selected_pyop(silently=silently) 411 | if not pyop: 412 | break 413 | filepath = pyop.filename() 414 | lineno = pyop.current_line_num() 415 | line = pyop.current_line().strip() 416 | step_count += 1 417 | if step_count > max_step_count: 418 | break 419 | 420 | if not silently: 421 | print "#steps: %s" % step_count 422 | list_pyframe() 423 | 424 | if step_count > max_step_count: 425 | sys.stdout.write(term_color(0, 31)) 426 | print "WARNING: Stopped after max steps: %s" % max_step_count 427 | sys.stdout.write(term_color(0)) 428 | 429 | 430 | def pynext(skip_breakpoint_mark=True, 431 | file_prefix=None, 432 | scheduler_locking=False, 433 | silently=False): 434 | """Continue until control reaches a different python source line in current frame""" 435 | 436 | if scheduler_locking: 437 | status = gdb.execute('set scheduler-locking on', to_string=True) 438 | else: 439 | status = gdb.execute('set scheduler-locking off', to_string=True) 440 | if not silently: 441 | print status 442 | 443 | next_count = 0 444 | max_next_count = 10000 445 | pyop = get_selected_pyop(silently=silently) 446 | if not pyop: 447 | return 448 | start_filepath = filepath = pyop.filename() 449 | start_lineno = lineno = pyop.current_line_num() 450 | line = pyop.current_line().strip() 451 | while (start_filepath == filepath and start_lineno == lineno) \ 452 | or (skip_breakpoint_mark and line == __breakpoint_func) \ 453 | or (file_prefix and not filepath.startswith(file_prefix)): 454 | next_msg = gdb.execute('next', to_string=True) 455 | gdb.newest_frame().select() 456 | pyop = get_selected_pyop(silently=silently) 457 | if not pyop: 458 | break 459 | filepath = pyop.filename() 460 | lineno = pyop.current_line_num() 461 | line = pyop.current_line().strip() 462 | next_count += 1 463 | 464 | if next_count > max_next_count: 465 | break 466 | 467 | if not silently: 468 | print "#next: %s" % next_count 469 | list_pyframe() 470 | 471 | if next_count > max_next_count: 472 | sys.stdout.write(term_color(0, 31)) 473 | print "WARNING: Stopped after max next's: %s" % max_next_count 474 | sys.stdout.write(term_color(0)) 475 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | # --- BEGIN_HEADER --- 2 | # 3 | # setup.cfg - setup configuration file for Python GDB debugger 4 | # Copyright (C) 2019-2020 The pygdb Project lead by Brian Vinter 5 | # 6 | # This file is part of pygdb. 7 | # 8 | # pygdb is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation; either version 2 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # pygdb is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program; if not, write to the Free Software 20 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 21 | # 22 | # -- END_HEADER --- 23 | # 24 | 25 | [metadata] 26 | description-file = README.md 27 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # --- BEGIN_HEADER --- 5 | # 6 | # setup.py - Setup for Python GDB debugger 7 | # Copyright (C) 2019-2020 The pygdb Project lead by Brian Vinter 8 | # 9 | # This file is part of pygdb. 10 | # 11 | # pygdb is free software: you can redistribute it and/or modify 12 | # it under the terms of the GNU General Public License as published by 13 | # the Free Software Foundation; either version 2 of the License, or 14 | # (at your option) any later version. 15 | # 16 | # pygdb is distributed in the hope that it will be useful, 17 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | # GNU General Public License for more details. 20 | # 21 | # You should have received a copy of the GNU General Public License 22 | # along with this program; if not, write to the Free Software 23 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 24 | # 25 | # -- END_HEADER --- 26 | # 27 | from setuptools import setup, Extension 28 | 29 | from pygdb import version_string, short_name, project_team, \ 30 | project_email, short_desc, long_desc, project_url, download_url, \ 31 | license_name, project_class, project_keywords, versioned_requires, \ 32 | project_requires, project_extras, project_platforms, maintainer_team, \ 33 | maintainer_email 34 | 35 | setup( 36 | name=short_name, 37 | version=version_string, 38 | description=short_desc, 39 | long_description=long_desc, 40 | author=project_team, 41 | author_email=project_email, 42 | maintainer=maintainer_team, 43 | maintainer_email=maintainer_email, 44 | url=project_url, 45 | download_url=download_url, 46 | license=license_name, 47 | classifiers=project_class, 48 | keywords=project_keywords, 49 | platforms=project_platforms, 50 | install_requires=versioned_requires, 51 | requires=project_requires, 52 | extras_require=project_extras, 53 | packages=['pygdb', 'pygdb.console'], 54 | package_dir={'pygdb': 'pygdb', 55 | 'pygdb.console': 'pygdb/console'}, 56 | ext_modules = [ 57 | Extension('_pygdb', ['pygdb/_breakpoint.c'], 58 | define_macros=[('NDEBUG', '0')],), 59 | ] 60 | ) -------------------------------------------------------------------------------- /test/pygdbtest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # --- BEGIN_HEADER --- 5 | # 6 | # pygdbtest.py - Test program for python GDB debugger 7 | # Copyright (C) 2019-2020 The pygdb Project lead by Brian Vinter 8 | # 9 | # This file is part of pygdb. 10 | # 11 | # pygdb is free software: you can redistribute it and/or modify 12 | # it under the terms of the GNU General Public License as published by 13 | # the Free Software Foundation; either version 2 of the License, or 14 | # (at your option) any later version. 15 | # 16 | # pygdb is distributed in the hope that it will be useful, 17 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | # GNU General Public License for more details. 20 | # 21 | # You should have received a copy of the GNU General Public License 22 | # along with this program; if not, write to the Free Software 23 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 24 | # 25 | # -- END_HEADER --- 26 | # 27 | 28 | """Test program for python GDB debugger""" 29 | 30 | import sys 31 | import threading 32 | import time 33 | import traceback 34 | import logging 35 | import pygdb.breakpoint 36 | 37 | logging.basicConfig(level=logging.INFO, 38 | format='%(asctime)s: %(levelname)s: pygdb: %(message)s') 39 | pygdb.breakpoint.enable(logger=logging.getLogger()) 40 | 41 | 42 | def test(threadid, counter): 43 | """Test function""" 44 | print "0x%0.X: Called test with counter: %d" % (threadid, counter) 45 | print "0x%0.X: test breakpoint" % threadid 46 | pygdb.breakpoint.set() 47 | print "0x%0.X: Counter value is %d" % (threadid, counter) 48 | print "0x%0.X: All done!" % threadid 49 | 50 | 51 | class TestThread(threading.Thread): 52 | """Python Test Thread""" 53 | 54 | def __init__(self): 55 | """Init thread""" 56 | threading.Thread.__init__(self) 57 | self.shutdown = threading.Event() 58 | self.counter = 0 59 | 60 | def run(self): 61 | """Start thread""" 62 | print "Starting TestThread: 0x%0.X" % self.ident 63 | sleeptime = 1 64 | 65 | while not self.shutdown.is_set(): 66 | time.sleep(sleeptime) 67 | print "0x%0.X: Thread woke up" % self.ident 68 | test(self.ident, self.counter) 69 | self.counter += 1 70 | # print "shutdown" 71 | # self.shutdown.set() 72 | # print "shutdown: %s" % self.shutdown.is_set() 73 | 74 | def stop(self): 75 | """Stop thread""" 76 | print "Stopping TestThread: 0x%0.X" % self.ident 77 | self.shutdown.set() 78 | print "0x%0.X: stop: Shutdown set" % self.ident 79 | self.join() 80 | print "0x%0.X: stop: joined" % self.ident 81 | 82 | 83 | def gdbtest(threadcount): 84 | """Start gdbtest""" 85 | print "Initializing using #threads: %d" % threadcount 86 | main_threadid = threading.current_thread().ident 87 | 88 | if threadcount == 0: 89 | counter = 0 90 | while True: 91 | test(main_threadid, counter) 92 | counter += 1 93 | else: 94 | threadlist = [] 95 | for _ in xrange(threadcount): 96 | threadlist.append(TestThread()) 97 | try: 98 | active_count = 0 99 | for thread in threadlist: 100 | thread.start() 101 | active_count += 1 102 | while active_count > 0: 103 | time.sleep(1) 104 | for thread in threadlist: 105 | if thread.shutdown.is_set(): 106 | print "run: shutdown.is_set for thread 0x%0.X" \ 107 | % thread.ident 108 | thread.join() 109 | print "run: joined thread 0x%0.X" % thread.ident 110 | active_count -= 1 111 | print "run: Active threads: %d" % active_count 112 | except KeyboardInterrupt: 113 | for thread in threadlist: 114 | thread.stop() 115 | # forward KeyboardInterrupt to main thread 116 | raise 117 | except Exception: 118 | for thread in threadlist: 119 | thread.stop() 120 | raise 121 | 122 | 123 | if __name__ == "__main__": 124 | argc = len(sys.argv)-1 125 | if argc != 1: 126 | print "USAGE: %s #threads" % sys.argv[0] 127 | sys.exit(1) 128 | threadcount = int(sys.argv[1]) 129 | try: 130 | gdbtest(threadcount) 131 | except KeyboardInterrupt: 132 | info_msg = "Received user interrupt" 133 | print info_msg 134 | except Exception, exc: 135 | print "exiting on unexpected exception: %s" % exc 136 | print traceback.format_exc() 137 | --------------------------------------------------------------------------------