├── .editorconfig ├── .gitignore ├── ChangeLog ├── Makefile ├── NEWS.md ├── README.md ├── __pkginfo__.py ├── admin-tools ├── make-dist-3.2-3.5.sh ├── make-dist-newer.sh ├── merge-for-2.7.sh ├── merge-for-3.2.sh ├── pyenv-versions-newer ├── pyenv-versions-older ├── setup-master.sh ├── setup-python-2.7.sh └── setup-python-3.2.sh ├── screenshots ├── .gitignore ├── README.md ├── trepan-xpy-demo1.cast ├── trepan-xpy-demo1.gif ├── trepan-xpy-demo1.txt └── trepan-xpy-demo2.cast ├── setup.cfg ├── setup.py ├── test └── example │ ├── fib.py │ ├── gcd.py │ └── hanoi.py └── trepanxpy ├── __init__.py ├── __main__.py ├── core.py ├── debugger.py ├── debugger_defaults.py ├── events.py ├── fmt.py ├── processor ├── __init__.py ├── cmd.py ├── command │ ├── __init__.py │ ├── continue.py │ ├── finish.py │ ├── info.py │ ├── info_subcmd │ │ ├── __init__.py │ │ ├── blocks.py │ │ ├── pc.py │ │ └── stack.py │ ├── next.py │ ├── python.py │ ├── quit.py │ ├── return.py │ ├── set.py │ ├── set_subcmd │ │ ├── __init__.py │ │ ├── autostack.py │ │ ├── loglevel.py │ │ └── pc.py │ ├── show.py │ ├── show_subcmd │ │ ├── __init__.py │ │ ├── autostack.py │ │ └── loglevel.py │ ├── step.py │ ├── stepi.py │ ├── vmstack.py │ └── vmstack_subcmd │ │ ├── __init__.py │ │ ├── peek.py │ │ ├── pop.py │ │ └── push.py └── trace.py └── version.py /.editorconfig: -------------------------------------------------------------------------------- 1 | # THis is an EditorConfig file 2 | # https://EditorConfig.org 3 | 4 | root = true 5 | 6 | [*] 7 | end_of_line = lf 8 | insert_final_newline = true 9 | charset = utf-8 10 | indent_style = tab 11 | indent_size = 4 12 | insert_final_newline = true 13 | 14 | [*.yml] 15 | indent_style = space 16 | indent_size = 2 17 | end_of_line = lf 18 | insert_final_newline = true 19 | 20 | [*.py] 21 | indent_style = space 22 | indent_size = 4 23 | end_of_line = lf 24 | insert_final_newline = true 25 | 26 | # Tab indentation (no size specified) 27 | [Makefile] 28 | indent_style = tab 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | .coverage 3 | .mypy_cache 4 | .python-version 5 | /.gdb_history 6 | /build 7 | /dist 8 | /tmp 9 | /trepanpyx.egg-info 10 | /trepanxpy.egg-info 11 | __pycache__ 12 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | 2024-07-22 rocky 2 | 3 | * __pkginfo__.py, trepanxpy/version.py: Get ready for release 1.1.1 4 | 5 | 2024-05-06 rocky 6 | 7 | * Makefile: Administrivia 8 | 9 | 2024-05-06 rocky 10 | 11 | * __pkginfo__.py, setup.py: Administrivia 12 | 13 | 2023-05-24 rocky 14 | 15 | * admin-tools/pyenv-versions-newer: Bump testing to newest Python 16 | versions 17 | 18 | 2022-03-30 rocky 19 | 20 | * admin-tools/pyenv-versions-newer, trepanxpy/fmt.py: Guard agains 21 | RAISE_VARARGS with no arg 22 | 23 | 2021-11-20 rocky 24 | 25 | * __pkginfo__.py, trepanxpy/debugger_defaults.py: Change autostack 26 | and autopc to default to True 27 | 28 | 2021-11-08 rocky 29 | 30 | * README.md: Mention bytecode-specific commands 31 | 32 | 2021-11-07 rocky 33 | 34 | * admin-tools/how-to-make-a-release.md, trepanxpy/version.py: Bump 35 | version 36 | 37 | 2021-11-07 rocky 38 | 39 | * ChangeLog, NEWS.md, __pkginfo__.py, 40 | admin-tools/how-to-make-a-release.md, 41 | admin-tools/make-dist-newer.sh, admin-tools/make-dist-older.sh, 42 | admin-tools/pyenv-versions-newer, setup.cfg, setup.py, 43 | trepanxpy/__init__.py, trepanxpy/__main__.py, trepanxpy/version.py: 44 | Get ready for release 1.1.0 45 | 46 | 2021-09-12 rocky 47 | 48 | * trepanxpy/fmt.py: Formatting tweak 49 | 50 | 2021-08-31 rocky 51 | 52 | * trepanxpy/processor/command/vmstack_subcmd/peek.py: Add vmstack 53 | peek 54 | 55 | 2021-08-30 rocky 56 | 57 | * trepanxpy/processor/command/vmstack_subcmd/__init__.py, 58 | trepanxpy/processor/command/vmstack_subcmd/pop.py, 59 | trepanxpy/processor/command/vmstack_subcmd/push.py: Add vmstack push Show type of object popped 60 | 61 | 2021-08-30 rocky 62 | 63 | * trepanxpy/processor/command/vmstack.py, 64 | trepanxpy/processor/command/vmstack_subcmd/pop.py: Add 1st VMStack 65 | command: vmstack pop 66 | 67 | 2021-08-29 rocky 68 | 69 | * trepanxpy/debugger_defaults.py, 70 | trepanxpy/processor/command/set_subcmd/autostack.py, 71 | trepanxpy/processor/command/show_subcmd/autostack.py: Add set/show 72 | autostack... "set autostack on" shows evaluation stack on debugger entry "show autostack" show current setting of this on/off setting 73 | 74 | 2021-08-28 rocky 75 | 76 | * trepanxpy/debugger.py: vm parameter in format_instruction is 77 | optional 78 | 79 | 2021-08-22 rocky 80 | 81 | * trepanxpy/debugger_defaults.py: Was missing "tempdir" default 82 | setting 83 | 84 | 2021-07-04 rocky 85 | 86 | * .editorconfig: Let editors know what style we use. 87 | 88 | 2021-06-23 rocky 89 | 90 | * setup.cfg: Yet another config file 91 | 92 | 2021-03-30 rocky 93 | 94 | * : commit 3fed185b27d07f64ff3ea15dad0362efe87ac416 Author: rocky 95 | Date: Tue Mar 30 05:03:27 2021 -0400 96 | 97 | 2021-03-24 rocky 98 | 99 | * __pkginfo__.py, setup.py, trepanxpy/debugger_defaults.py: Use 100 | term-background library. Update for 3.9 101 | 102 | 2020-07-22 rocky 103 | 104 | * trepanxpy/__init__.py: fill out __init__.py Add pydoc friendly VERSON Add introductory docstring Note that we'll 105 | use RsT 106 | 107 | 2020-07-03 rocky 108 | 109 | * admin-tools/how-to-make-a-release.md, trepanxpy/core.py, 110 | trepanxpy/processor/command/set_subcmd/loglevel.py: Small tweaks: downcase loglevel value, e.g. `set loglevel DEBUG` is valid 111 | how-to-make-a-release.md: the python module name is trepanxpy, not 112 | trepan-xpy core.py: prepare stub for new breakpoint handling 113 | 114 | 2020-06-28 rocky 115 | 116 | * admin-tools/how-to-make-a-release.md: Administrivia 117 | 118 | 2020-06-28 rocky 119 | 120 | * admin-tools/how-to-make-a-release.md: Administrivia 121 | 122 | 2020-06-28 rocky 123 | 124 | * ChangeLog, Makefile, NEWS.md, __pkginfo__.py, 125 | trepanxpy/debugger.py, trepanxpy/processor/cmd.py: Get ready for 126 | release 1.0.3 127 | 128 | 2020-06-23 rocky 129 | 130 | * trepanxpy/core.py, trepanxpy/debugger_defaults.py: Use new 131 | trepan3k's new asm_format option 132 | 133 | 2020-06-15 rocky 134 | 135 | * trepanxpy/core.py, trepanxpy/debugger.py, 136 | trepanxpy/debugger_defaults.py, trepanxpy/events.py, 137 | trepanxpy/processor/cmd.py, 138 | trepanxpy/processor/command/continue.py, 139 | trepanxpy/processor/command/step.py, trepanxpy/processor/trace.py: 140 | Implemented breakpoints and step counts... Event flags added to core for such purpose. 141 | 142 | 2020-06-15 rocky 143 | 144 | * __pkginfo__.py, trepanxpy/__main__.py, trepanxpy/debugger.py, 145 | trepanxpy/processor/cmd.py, trepanxpy/version.py: Bump min pkg 146 | versions, start breakpoints. And start renamin Debugger TrepanXPy 147 | 148 | 2020-06-03 rocky 149 | 150 | * admin-tools/how-to-make-a-release.md: Administrivia 151 | 152 | 2020-06-03 rocky 153 | 154 | * ChangeLog, NEWS.md, __pkginfo__.py, 155 | admin-tools/how-to-make-a-release.md, trepanxpy/version.py: Get 156 | ready for release 1.0.2 157 | 158 | 2020-06-02 rocky 159 | 160 | * README.md, screenshots/.gitignore, 161 | screenshots/trepan-xpy-demo1.cast, 162 | screenshots/trepan-xpy-demo2.cast, trepanxpy/debugger_defaults.py, 163 | trepanxpy/processor/command/set_subcmd/loglevel.py, 164 | trepanxpy/processor/command/set_subcmd/logtrace.py, 165 | trepanxpy/processor/command/show.py, 166 | trepanxpy/processor/command/show_subcmd/__init__.py, 167 | trepanxpy/processor/command/show_subcmd/loglevel.py: set logtrace -> 168 | set loglevel and ... add `show loglevel`. Bugs the set routine were fixed as well. Additional loglevel names 169 | (e.g. critical, warning) were added README.md and screenshot have been updated. 170 | 171 | 2020-06-01 rocky 172 | 173 | * __pkginfo__.py, trepanxpy/fmt.py: Bump versions, remove debug 174 | stmts 175 | 176 | 2020-05-30 rocky 177 | 178 | * : commit d2aae9b27c79a32768cbecbba20e840af5c9f0af Merge: 2dd2321 179 | a9f60c2 Author: rocky Date: Sat May 30 18:10:39 180 | 2020 -0400 181 | 182 | 2020-05-30 rocky 183 | 184 | * : commit 2dd23218913f8feda80becac2258e23624438097 Author: rocky 185 | Date: Sat May 30 17:54:37 2020 -0400 186 | 187 | 2020-05-30 rocky 188 | 189 | * README.md, README.rst, __pkginfo__.py, setup.py: Try using 190 | Markdown 191 | 192 | 2020-05-30 rocky 193 | 194 | * ChangeLog, Makefile, NEWS.md, __pkginfo__.py, 195 | admin-tools/how-to-make-a-release.md, admin-tools/{make-dist.sh => 196 | make-dist-newer.sh}, admin-tools/make-dist-older.sh, 197 | admin-tools/{pyenv-versions => pyenv-versions-newer}, 198 | admin-tools/pyenv-versions-older, trepanxpy/processor/trace.py, 199 | trepanxpy/version.py: Get ready for release 1.0.1 200 | 201 | 2020-05-29 rocky 202 | 203 | * README.rst, screenshots/README.md: Go over README, again 204 | 205 | 2020-05-29 rocky 206 | 207 | * README.rst, screenshots/trepan-xpy-demo1.cast, 208 | screenshots/trepan-xpy-demo1.txt: Update demo 209 | 210 | 2020-05-29 rocky 211 | 212 | * : commit eeede1162c960a86204ef993feb2552673a04fd3 Author: rocky 213 | Date: Fri May 29 10:58:18 2020 -0400 214 | 215 | 2020-05-29 R. Bernstein 216 | 217 | * README.rst: Update README.rst 218 | 219 | 2020-05-29 rocky 220 | 221 | * README.rst, screenshots/README.md, 222 | screenshots/trepan-xpy-demo1.cast, test/example/gcd.py, 223 | trepanxpy/processor/command/set_subcmd/logtrace.py: Add a screenshot 224 | 225 | 2020-05-28 rocky 226 | 227 | * trepanxpy/debugger_defaults.py, trepanxpy/fmt.py, 228 | trepanxpy/processor/cmd.py: stack_fmt() may need the int arg 229 | parameter... * Use our own repr() to format instructions * Don't show instruction on a return event. 230 | 231 | 2020-05-28 rocky 232 | 233 | * trepanxpy/debugger_defaults.py, 234 | trepanxpy/processor/command/set_subcmd/logtrace.py: WIP - set logger 235 | level... Does not change a setting properly after the set, and there is no 236 | corresponding "show" commaond, but it is still useful. 237 | 238 | 2020-05-28 rocky 239 | 240 | * trepanxpy/debugger.py, trepanxpy/fmt.py, 241 | trepanxpy/processor/cmd.py, trepanxpy/processor/trace.py: Use vm 242 | info in showing instructiontrace info 243 | 244 | 2020-05-28 rocky 245 | 246 | * trepanxpy/debugger.py, trepanxpy/fmt.py, 247 | trepanxpy/processor/command/next.py, 248 | trepanxpy/processor/command/step.py, 249 | trepanxpy/processor/command/stepi.py: Bang on "next" and "step" 250 | commands 251 | 252 | 2020-05-27 rocky 253 | 254 | * : commit 143a00745aa05482f821f491953c4bb79b634122 Author: rocky 255 | Date: Wed May 27 20:02:03 2020 -0400 256 | 257 | 2020-05-27 R. Bernstein 258 | 259 | * README.rst: Update README.rst for
 formatting
260 | 
261 | 2020-05-27  R. Bernstein 
262 | 
263 | 	* README.rst: Update README.rst
264 | 
265 | 2020-05-27  rocky 
266 | 
267 | 	* README.rst: Experiment with raw HTML
268 | 
269 | 2020-05-24  rocky 
270 | 
271 | 	* trepanxpy/debugger.py: lint
272 | 
273 | 2020-05-24  rocky 
274 | 
275 | 	* trepanxpy/debugger_defaults.py: Lint
276 | 
277 | 2020-05-23  rocky 
278 | 
279 | 	* trepanxpy/processor/command/continue.py: "Continue" should
280 | 	continue
281 | 
282 | 2020-05-23  rocky 
283 | 
284 | 	* trepanxpy/processor/command/info_subcmd/pc.py: Wasn't passing
285 | 	co_names as I should have...  There are too many parameters in a code object so it's easy to miss
286 | 	one.
287 | 
288 | 2020-05-22  rocky 
289 | 
290 | 	* README.rst, __pkginfo__.py, trepanxpy/version.py: Bump pyficache
291 | 	version
292 | 
293 | 2020-05-21  rocky 
294 | 
295 | 	* .gitignore, admin-tools/setup-master.sh,
296 | 	admin-tools/setup-python-3.2.sh, trepanxpy/processor/cmd.py,
297 | 	trepanxpy/processor/command/continue.py,
298 | 	trepanxpy/processor/trace.py: Administrivia
299 | 
300 | 2020-05-21  R. Bernstein 
301 | 
302 | 	* README.rst: Update README.rst
303 | 
304 | 2020-05-20  rocky 
305 | 
306 | 	* README.rst, admin-tools/how-to-make-a-release.md: Typos
307 | 
308 | 2020-05-20  rocky 
309 | 
310 | 	* NEWS.md, __pkginfo__.py, admin-tools/how-to-make-a-release.md,
311 | 	admin-tools/make-dist.sh, admin-tools/pyenv-versions: Get ready for
312 | 	release 1.0.0
313 | 
314 | 2020-05-20  rocky 
315 | 
316 | 	* README.rst: Spelling typos
317 | 
318 | 2020-05-20  R. Bernstein 
319 | 
320 | 	* README.rst: Update README.rst
321 | 
322 | 2020-05-20  R. Bernstein 
323 | 
324 | 	* README.rst: Update README.rst
325 | 
326 | 2020-05-20  R. Bernstein 
327 | 
328 | 	* README.rst: Update README.rst
329 | 
330 | 2020-05-20  rocky 
331 | 
332 | 	* .gitignore, README.rst, setup.py,
333 | 	trepanxpy/processor/command/python.py,
334 | 	trepanxpy/processor/command/stepi.py: Add python and step
335 | 	commmands...  go over docs.
336 | 
337 | 2020-05-20  rocky 
338 | 
339 | 	* .gitignore, trepanxpy/core.py, trepanxpy/debugger.py,
340 | 	trepanxpy/fmt.py, trepanxpy/processor/cmd.py,
341 | 	trepanxpy/processor/trace.py: Add instruction formatting.
342 | 
343 | 2020-05-19  rocky 
344 | 
345 | 	* trepanxpy/processor/command/return.py,
346 | 	trepanxpy/processor/command/set_subcmd/pc.py,
347 | 	trepanxpy/processor/command/step.py: Add a gdb immediate "return"
348 | 	statement...  Correct `step` command the more compatable sys.settrace() VMTrace
349 | 	environment.
350 | 
351 | 2020-05-19  rocky 
352 | 
353 | 	* trepanxpy/core.py, trepanxpy/processor/cmd.py,
354 | 	trepanxpy/processor/command/set_subcmd/pc.py,
355 | 	trepanxpy/processor/trace.py: Add "set pc" command; more proper
356 | 	trace hook..  `set pc offset` will set the next instruction to be executed to be
357 | 	`offset` cmd.py: follow sys.settrace() conventions more closely.  trace.py:
358 | 	fix bug in showing instructions.
359 | 
360 | 2020-05-19  rocky 
361 | 
362 | 	* __pkginfo__.py: Bump required x-python & trepan3k versions
363 | 
364 | 2020-05-14  rocky 
365 | 
366 | 	* trepanxpy/processor/command/info_subcmd/pc.py: Set opc for in
367 | 	"info pc" to get x-version right
368 | 
369 | 2020-05-13  rocky 
370 | 
371 | 	* trepanxpy/__main__.py, trepanxpy/debugger.py,
372 | 	trepanxpy/processor/trace.py: Fix up tracing...  * Add --trace option * Correct trace hook for new callbacks * Tidy output
373 | 
374 | 2020-05-13  rocky 
375 | 
376 | 	* trepanxpy/processor/cmd.py,
377 | 	trepanxpy/processor/command/info_subcmd/blocks.py: Add "info block"
378 | 	subcommand
379 | 
380 | 2020-05-13  rocky 
381 | 
382 | 	* README.rst, test/example/gcd.py: Update README.rst
383 | 
384 | 2020-05-13  rocky 
385 | 
386 | 	* trepanxpy/__main__.py, trepanxpy/debugger.py,
387 | 	trepanxpy/processor/cmd.py: Add -c option; revise for better
388 | 	instruction_info
389 | 
390 | 2020-05-13  rocky 
391 | 
392 | 	* trepanxpy/processor/command/info_subcmd/pc.py: Need xdis's
393 | 	findlinestarts not dis's...  If we are to support cross-bytecode interpreting
394 | 
395 | 2020-05-12  rocky 
396 | 
397 | 	* trepanxpy/processor/cmd.py,
398 | 	trepanxpy/processor/command/info_subcmd/pc.py,
399 | 	trepanxpy/processor/command/set_subcmd/logtrace.py: "list" and
400 | 	"logtrace" fixups
401 | 
402 | 2020-05-12  rocky 
403 | 
404 | 	* trepanxpy/processor/cmd.py, trepanxpy/processor/command/info.py,
405 | 	trepanxpy/processor/command/info_subcmd/pc.py,
406 | 	trepanxpy/processor/command/set.py: Add our own exception-tolerant
407 | 	`info pc` ...  set manager fixups
408 | 
409 | 2020-05-12  rocky 
410 | 
411 | 	* trepanxpy/processor/cmd.py,
412 | 	trepanxpy/processor/command/info_subcmd/stack.py,
413 | 	trepanxpy/processor/trace.py: Handle uncaught errors better
414 | 
415 | 2020-05-12  rocky 
416 | 
417 | 	* trepanxpy/debugger_defaults.py,
418 | 	trepanxpy/processor/command/info.py,
419 | 	trepanxpy/processor/command/info_subcmd/__init__.py,
420 | 	trepanxpy/processor/command/info_subcmd/stack.py,
421 | 	trepanxpy/processor/command/set.py: Add "info stack"
422 | 
423 | 2020-05-11  rocky 
424 | 
425 | 	* trepanxpy/debugger.py, trepanxpy/debugger_defaults.py,
426 | 	trepanxpy/processor/cmd.py, trepanxpy/processor/command/set.py,
427 | 	trepanxpy/processor/command/set_subcmd/{logging.py => logtrace.py}: 
428 | 	First "set" command addition
429 | 
430 | 2020-05-11  rocky 
431 | 
432 | 	* trepanxpy/processor/command/set.py,
433 | 	trepanxpy/processor/command/set_subcmd/__init__.py: WIP towards
434 | 	adding set command "logging"
435 | 
436 | 2020-05-11  rocky 
437 | 
438 | 	* : commit 2dcad1a53f23cc60f3b29dff958e176754051439 Author: rocky
439 | 	 Date:   Mon May 11 19:26:57 2020 -0400
440 | 
441 | 2020-05-11  rocky 
442 | 
443 | 	* trepanxpy/processor/command/set_subcmd/logging.py: Start custom
444 | 	set command
445 | 
446 | 2020-05-11  rocky 
447 | 
448 | 	* trepanxpy/debugger.py, trepanxpy/processor/cmd.py,
449 | 	trepanxpy/processor/command/step.py, trepanxpy/processor/trace.py: 
450 | 	Add YIELD events Work on termination
451 | 
452 | 2020-05-10  rocky 
453 | 
454 | 	* trepanxpy/processor/cmd.py,
455 | 	trepanxpy/processor/command/continue.py,
456 | 	trepanxpy/processor/command/quit.py,
457 | 	trepanxpy/processor/command/step.py,
458 | 	trepanxpy/processor/command/stepi.py: Add stepi, step and continue
459 | 	commands..  Other bugs prevent these from really shining though.
460 | 
461 | 2020-05-10  rocky 
462 | 
463 | 	* trepanxpy/debugger.py, trepanxpy/processor/cmd.py,
464 | 	trepanxpy/processor/command/help/README.md,
465 | 	trepanxpy/processor/command/help/arange.rst,
466 | 	trepanxpy/processor/command/help/command.rst,
467 | 	trepanxpy/processor/command/help/examples.rst,
468 | 	trepanxpy/processor/command/help/filename.rst,
469 | 	trepanxpy/processor/command/help/location.rst,
470 | 	trepanxpy/processor/command/help/range.rst,
471 | 	trepanxpy/processor/command/help/suffixes.rst,
472 | 	trepanxpy/processor/trace.py: Some cleanup.
473 | 
474 | 2020-05-10  rocky 
475 | 
476 | 	* trepanxpy/core.py, trepanxpy/debugger.py,
477 | 	trepanxpy/processor/cmd.py,
478 | 	trepanxpy/processor/command/__init__.py,
479 | 	trepanxpy/processor/command/quit.py: Our first revised command:
480 | 	quit!
481 | 
482 | 2020-05-09  rocky 
483 | 
484 | 	* __pkginfo__.py, trepanxpy/__main__.py, trepanxpy/clifns.py,
485 | 	trepanxpy/{lib => }/core.py, trepanxpy/debugger.py,
486 | 	trepanxpy/misc.py, trepanxpy/processor/cmd.py, trepanxpy/{lib =>
487 | 	processor/command}/__init__.py, trepanxpy/processor/trace.py: 
488 | 	Something that has a prompt and cmdline interface
489 | 
490 | 2020-05-09  rocky 
491 | 
492 | 	* test/example/fib.py, test/example/gcd.py, test/example/hanoi.py,
493 | 	trepanxpy/clifns.py, trepanxpy/debugger.py,
494 | 	trepanxpy/lib/__init__.py, trepanxpy/lib/core.py,
495 | 	trepanxpy/misc.py, trepanxpy/processor/__init__.py,
496 | 	trepanxpy/processor/trace.py: Something that looks like tracing
497 | 
498 | 2020-05-08  rocky 
499 | 
500 | 	* Much ado about nothing!
501 | 
502 | 


--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
 1 | # Compatibility for us old-timers.
 2 | 
 3 | # Note: This makefile include remake-style target comments.
 4 | # These comments before the targets start with #:
 5 | # remake --tasks to shows the targets and the comments
 6 | 
 7 | GIT2CL ?= git2cl
 8 | PYTHON ?= python
 9 | PYTHON3 ?= python3
10 | RM      ?= rm
11 | LINT    = flake8
12 | SHELL   ?= bash
13 | 
14 | PHONY=ChangeLog clean clean_pyc dist dist-newer dist-older rmChangeLog
15 | 
16 | #: Clean up temporary files and .pyc files
17 | clean: clean_pyc
18 | 	$(PYTHON) ./setup.py $@
19 | 	find . -name __pycache__ -exec rm -fr {} \; || true
20 | 
21 | #: Create source (tarball) and wheel distribution
22 | dist: clean
23 | 	$(PYTHON) ./setup.py sdist bdist_wheel
24 | 
25 | #: Create distribution for older Python versions
26 | dist-older:
27 | 	bash ./admin-tools/make-dist-older.sh
28 | 
29 | #: Create distribution for older Python versions
30 | dist-newer:
31 | 	bash ./admin-tools/make-dist-newer.sh
32 | 
33 | #: Remove .pyc files
34 | clean_pyc:
35 | 	( cd trepanxpy && $(RM) -f *.pyc */*.pyc )
36 | 	( cd test && $(RM) -f *.pyc )
37 | 
38 | #: Create source tarball
39 | sdist:
40 | 	$(PYTHON) ./setup.py sdist
41 | 
42 | #: Style check. Set env var LINT to pyflakes, flake, or flake8
43 | lint: flake8
44 | 
45 | #: Check StructuredText long description formatting
46 | check-rst:
47 | 	$(PYTHON) setup.py --long-description | rst2html.py > x-python.html
48 | 
49 | #: Create binary egg distribution
50 | bdist_egg:
51 | 	$(PYTHON) ./setup.py bdist_egg
52 | 
53 | 
54 | #: Create binary wheel distribution
55 | bdist_wheel:
56 | 	$(PYTHON) ./setup.py bdist_wheel
57 | 
58 | # It is too much work to figure out how to add a new command to distutils
59 | # to do the following. I'm sure distutils will someday get there.
60 | DISTCLEAN_FILES = build dist *.pyc
61 | 
62 | #: Remove ALL derived files
63 | distclean: clean
64 | 	-rm -fvr $(DISTCLEAN_FILES) || true
65 | 	-find . -name \*.pyc -exec rm -v {} \;
66 | 	-find . -name \*.egg-info -exec rm -vr {} \;
67 | 
68 | #: Install package locally
69 | verbose-install:
70 | 	$(PYTHON) ./setup.py install
71 | 
72 | #: Install package locally without the verbiage
73 | install:
74 | 	$(PYTHON) ./setup.py install >/dev/null
75 | 
76 | rmChangeLog:
77 | 	rm ChangeLog || true
78 | 
79 | #: Create a ChangeLog from git via git log and git2cl
80 | ChangeLog: rmChangeLog
81 | 	git log --pretty --numstat --summary | $(GIT2CL) >$@
82 | 
83 | .PHONY: $(PHONY)
84 | 


--------------------------------------------------------------------------------
/NEWS.md:
--------------------------------------------------------------------------------
 1 | 1.1.1 2024-07-22
 2 | ================
 3 | 
 4 | * Adjust for newer xdis, x-python, and trepan3k
 5 | 
 6 | 1.1.0 2021-11-07
 7 | ================
 8 | 
 9 | * Adjust for newer xdis 6.0.3, and x-python
10 | * Add commands:
11 |   - `vmstack {peek|push|pop}`
12 |   - `set autostack`
13 | 
14 | 
15 | 1.0.3 2020-06-28
16 | ================
17 | 
18 | * Add breakpoints! They can be on offsets too!
19 | * step counting now works. continue to breakpoint also works
20 | * xdis/trepan3k's new assembly formatting
21 | * fix "list" start position
22 | * fix saving sys.argv
23 | 
24 | 1.0.2 2020-06-03 Marilyn + 1
25 | ============================
26 | 
27 | Very minor improvements but note that `set logtrace` has been renamed to `set loglevel`.
28 | 
29 | A few bugs in setting the loglevl have been fixed. For example, previously `set loglevel` only had an effect only on the first setting. The corresponding * `show loglevel` has now been added, and this output also appears after setting the loglevel.
30 | 
31 | 1.0.1 2020-05-30 Lady Elaine
32 | ============================
33 | 
34 | There have been a few usuablity improvements here, with the help of updated `trepan3k` and `x-python` and `xdis` releases.
35 | 
36 | Stack operands are now shown for instruciton and we colorize trace output.
37 | 
38 | New commands:
39 | 
40 | * `set logtrace` sets logger level on vm tracing
41 | * `next` (step over)
42 | * `finish` (step out)
43 | 
44 | There are some bugs in `set logtrace`, and `finish`. However, as in the last release, these are minor compared to the improvements. So release again rather than wait.
45 | 
46 | 
47 | 1.0.0 2020-04-20 One-oh!
48 | ========================
49 | 
50 | I gotta say it that the interaction between this and x-python is pretty cool, and the possibiilities for which direction to go in on both projects are numerous and vast.
51 | 
52 | How did I get here? Well, I was pouring over trace logs to find bugs in x-python, and then realized, hey, I've written a debugger already for Python that could help here.
53 | 
54 | So here it is.  I can die now and my life is complete.
55 | 
56 | Although commands like `next`, `finish` and breakpoints, aren't there yet, rather than sit on it this, I thought I'd release. This is very usuable as is - release early and often!
57 | 


--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
  1 | Abstract
  2 | ========
  3 | 
  4 | This is a gdb-like debugger focusing on Python bytecode. So far as I know, this is the *only* debugger available specifically for Python bytecode.
  5 | 
  6 | However to do this, you need to use underneath [x-python](https://pypi.org/project/x-python): a Python Interpreter written in Python.
  7 | 
  8 | This project builds off of a previous Python 3 debugger called [trepan3k](https://pypi.org/project/trepan3k/).
  9 | 
 10 | Example
 11 | =======
 12 | 
 13 | ![demo](https://github.com/rocky/trepan-xpy/blob/master/screenshots/trepan-xpy-demo1.gif)
 14 | 
 15 | Below we'll try to break down what's going on above.
 16 | 
 17 | We'll invoke the a Greatest Common Divisors program `gcd.py` using our debugger. The source is found in
 18 | [test/example/gcd.py](https://github.com/rocky/trepan-xpy/blob/master/test/example/gcd.py).
 19 | 
 20 | In this section we'll these some interesting debugger commands that are not common in Python debuggers:
 21 | 
 22 | - `stepi` to step a bytecode instruction
 23 | - `set loglevel` to show a the x-python log "info"-level log  tracing.
 24 | - `info stack` to show the current stack frame evaluation stack
 25 | 
 26 | 
$ trepan-xpy test/example/gcd.py 3 5
 27 |  Running x-python test/example/gcd.py with ('3', '5')
 28 |  (test/example/gcd.py:10): <module>
 29 |  -> 2 """
 30 |  (trepan-xpy)
 31 |  
32 | 33 | Above we are stopped before we have even run the first instruction. The `->` icon before `2` means we are stopped calling a new frame. 34 | 35 |
(trepan-xpy) step
 36 | (test/example/gcd.py:2): <module>
 37 | -- 2 """Greatest Common Divisor"""
@ 0: LOAD_CONST 'Greatest Common Divisor' 38 |
39 | 40 | Ok, now we are stopped before the first instruction `LOAD_CONST` which will load a constant onto the evaluation stack. The icon changed from `-> 2` to `-- 2` which indicates we are on a line-number boundary at line 2. 41 | 42 | The Python construct we are about to perform is setting the program's docstring. Let's see how that is implemented. 43 | 44 | First we see that the variable `__doc__` which will eventually hold the docstring isn't set: 45 | 46 | We see here that the first part is loading this constant onto an 47 | evaluation stack. 48 | 49 | At this point, to better see the execution progress we'll issue the command `set loglevel` which will show the instructions as we step along. 50 | 51 | Like *trepan3k*, *trepan-xpy* has extensive nicely formatted help right in the debugger. Let's get the help for the `set loglevel` command: 52 | 53 |
(trepan-xpy) help set loglevel
 54 | set loglevel [ on | off | debug | info ]
 55 | 
 56 | Show loglevel PyVM logger messages. Initially logtracing is off.
 57 | 
 58 | However running set loglevel will turn it on and set the log level to debug.
 59 | So it's the same thing as set loglevel debug.
 60 | 
 61 | If you want the less verbose messages, use info. And to turn off, (except
 62 | critical errors), use off.
 63 | 
 64 | Examples:
 65 | 
 66 |      set loglevel         # turns x-python on info logging messages
 67 |      set loglevel info    # same as above
 68 |      set loglevel debug   # turn on info and debug logging messages
 69 |      set loglevel off     # turn off all logging messages except critical ones
 70 | 
 71 | 
 72 | 
73 | 74 | So now lets's set that: 75 | 76 |
(trepan-xpy) set loglevel
 77 | (trepan-xpy)
78 | 79 | A rather unique command that you won\'t find in most Python debuggers 80 | but is in low-level debuggers is `stepi` which steps and instruction. 81 | Let's use that: 82 | 83 |
(trepan-xpy) stepi
 84 | (test/example/gcd.py:2 @2): <module>
 85 | .. 2 """Greatest Common Divisor"""
@ 2: STORE_NAME 'Greatest Common Divisor') __doc__ 86 |
87 | 88 | The `..` at the beginning indicates that we are on an instruction which 89 | is in between lines. 90 | 91 | We\'ve now loaded the docstring onto the evaluation stack with 92 | `LOAD_CONST` Let\'s see the evaluation stack with `info stack`: 93 | 94 |
(trepan-xpy) info stack
0: <class 'str'> 'Greatest Common Divisor' 95 |
96 | 97 | Here we have pushed the docstring for the program but haven\'t yet 98 | stored that in `__doc__`. To see this, can use the auto-eval feature of 99 | `trepan-xpy`: it will automatically evaluate strings it doesn\'t 100 | recognize as a debugger command: 101 | 102 |
(trepan-xpy) __doc__ is None
103 | True
104 | 
105 | 106 | Let's step the remaining instruction, `STORE_NAME` to complete the 107 | instructions making up line 1. 108 | 109 |
trepan-xpy) stepi
110 | INFO:xpython.vm:L. 10  @  4: LOAD_CONST 0
111 | (test/example/gcd.py:10 @4): <module>
112 | -- 10 import sys
@ 4: LOAD_CONST 0 113 |
114 | 115 | The leading `--` before `10 import`... indicates we are on a line 116 | boundary now. Let\'s see the stack now that we have run `STORE_NAME`: 117 | 118 |
(trepan-xpy) info stack
119 | Evaluation stack is empty
120 | 
121 | 122 | And to see that we\'ve stored this in `__doc__` we can run `eval` to see 123 | its value: 124 | 125 |
(trepan-xpy) eval __doc__
126 | "Greatest Common Divisor"
127 | 
128 | 129 | (Entering just `_doc_` is the same thing as `eval __doc__` when 130 | auto-evaluation is on. 131 | 132 | Now let\'s step a statement (not instructions), to see how a module 133 | becomes visable. 134 | 135 |
(trepan-xpy) step
136 | INFO:xpython.vm:       @  6: LOAD_CONST None
137 | INFO:xpython.vm:       @  8: IMPORT_NAME (0, None) sys
138 | INFO:xpython.vm:       @ 10: STORE_NAME (<module 'sys' (built-in)>)
139 | INFO:xpython.vm:L. 12  @ 12: LOAD_CONST <code object check_args at 0x7f2a0a286f60, file "test/example/gcd.py", line 12>
140 | (test/example/gcd.py:12 @12): <module>
141 | -- 12 def check_args():
@ 12: LOAD_CONST <code object check_args at 0...est/example/gcd.py", line 12> 142 |
143 | 144 | The `INFO` are initiated by the VM interpreter. As a result of the `set loglevel` the interpreters `logger` log level was increased. This in turn causes a callback is made to a formatting routine provided by the debugger to nicly colorize the information. And that is why parts of this are colorized in a terminal session. In `x-python` you can get the same information, just not colorized. 145 | 146 | One thing to note is the value after the operand and in parenthesis, like after `STORE NAME`. Compare that line with what you\'ll see from a static disassembly like Python\'s `dis` or `xdis` version of that: 147 | 148 | 10 STORE_NAME 1 (sys) 149 | 150 | In a static disassembler, the \"1\" indicates the name index in the code object. The value in parenthesis is what that name, here at index 1 is, namely `sys`. 151 | 152 | In `trepan-xpy` and `x-python` however we omit the name index, 1, since that isn't of much interest. Instead we show that dynamic stack entries or operands that `STORE_NAME` is going to work on. In particular the object that is going to be stored in variable `sys` is the built-in module `sys`. 153 | 154 | Now let's step another statement to see how a function becomes available: 155 | 156 |
trepan-xpy) step
157 | INFO:xpython.vm:       @ 14: LOAD_CONST 'check_args'
158 | INFO:xpython.vm:       @ 16: MAKE_FUNCTION (check_args) Neither defaults, keyword-only args, annotations, nor closures
159 | INFO:xpython.vm:       @ 18: STORE_NAME (<Function check_args at 0x7fdb1d4d49f0>) check_args
160 | INFO:xpython.vm:L. 25  @ 20: LOAD_CONST <code object gcd at 0x7fdb1d55fed0, file "test/example/gcd.py", line 25>
161 | (test/example/gcd.py:25 @20): <module>
162 | -- 25 def gcd(a,b):
@ 20: LOAD_CONST <code object gcd at 0x7fdb1d...est/example/gcd.py", line 25> 163 |
164 | 165 | A difference between a dynamic language like Python and a statically compiled language like C, or Java is that there is no linking step in the complation; modules and functions are *imported* or created and linked as part of the execution of the code. 166 | 167 | Notice again what's in the parenthesis after the opcode and how that differs from a static disassembly. For comparison here is what 2nd and 3rd instruction look like from `pydisasm`: 168 | 169 | 16 MAKE_FUNCTION 0 (Neither defaults, keyword-only args, annotations, nor closures) 170 | 18 STORE_NAME 2 (check_args) 171 | 172 | Again, indices into a name table are dropped and in their place are the evaluation stack items. For `MAKE_FUNCTION` the name of the function that is created is shown; while for `STORE_NAME`, as before, the item that gets stored (a function object) is shown. 173 | 174 | The rest of the screencast shows that in addition to the `step` (step into) and `stepi` (step instruction) debugger commands there is a `next` or step over debugger command, and a slightly buggy `finish` (step out) command 175 | 176 | I don't have breakpoints hooked in yet. 177 | 178 | But in contrast to any other Python debugger I know about, we can cause an immediate return with a value and that is shown in the screencast. 179 | 180 | We've only show a few of the many debugger features. 181 | 182 | Bytecode-specific commands 183 | ========================== 184 | 185 | Here are some interesting commands not typically found in Python debuggers, like `pdb` 186 | 187 | - `info blocks` lets you see the block stack 188 | - `set pc ` lets you set the Program counter within the frame 189 | - `set autopc` runs `info pc` to show the debugged program's program counter before each time the debugger's command-loop REPL is run. 190 | - `set autostack` runs `info stack` to show the debugged program's evaluation stack before each time the debugger's command-loop REPL is run. 191 | - `vmstack {peek | push, pop}` - inspects or modifies evaluation stack 192 | 193 | See Also 194 | ======== 195 | 196 | - [xpython](https://pypi.org/project/x-python/) : CPython written in Python 197 | - [trepan3k](https://pypi.org/project/trepan3k/) : trepan debugger for Python 3.x and its extensive [documentation](https://python3-trepan.readthedocs.io/en/latest/). 198 | -------------------------------------------------------------------------------- /__pkginfo__.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2020-2021, 2023, 2024 Rocky Bernstein 2 | # 3 | # This program is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | """Debugger packaging information""" 16 | 17 | # To the extent possible we make this file look more like a 18 | # configuration file rather than code like setup.py. I find putting 19 | # configuration stuff in the middle of a function call in setup.py, 20 | # which for example requires commas in between parameters, is a little 21 | # less elegant than having it here with reduced code, albeit there 22 | # still is some room for improvement. 23 | 24 | import os.path as osp 25 | 26 | # Things that change more often go here. 27 | copyright = """Copyright (C) 2020-2021, 2023-2024 Rocky Bernstein .""" 28 | classifiers = [ 29 | "Development Status :: 3 - Alpha", 30 | "Environment :: Console", 31 | "Intended Audience :: Developers", 32 | "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", 33 | "Operating System :: OS Independent", 34 | "Programming Language :: Python", 35 | "Topic :: Software Development :: Debuggers", 36 | "Topic :: Software Development :: Libraries :: Python Modules", 37 | "Programming Language :: Python :: 3.2", 38 | "Programming Language :: Python :: 3.3", 39 | "Programming Language :: Python :: 3.4", 40 | "Programming Language :: Python :: 3.5", 41 | "Programming Language :: Python :: 3.6", 42 | "Programming Language :: Python :: 3.7", 43 | "Programming Language :: Python :: 3.8", 44 | "Programming Language :: Python :: 3.9", 45 | "Programming Language :: Python :: 3.10 ", 46 | "Programming Language :: Python :: 3.12 ", 47 | ] 48 | 49 | # The rest in alphabetic order 50 | author = "Rocky Bernstein" 51 | author_email = "rb@dustyfeet.com" 52 | entry_points = {"console_scripts": ["trepan-xpy = trepanxpy.__main__:main"]} 53 | extras_require = {"dev": ["nose>=1.0.0, <= 1.3.7"]} 54 | 55 | ftp_url = None 56 | install_requires = [ 57 | "decompyle3 >= 3.9.2", 58 | "term-background >= 1.0.2", 59 | "trepan3k >= 1.3.0", 60 | "uncompyle6 >= 3.9.2", 61 | "x-python >= 1.5.2", 62 | ] 63 | license = "GPL3" 64 | mailing_list = "python-debugger@googlegroups.com" 65 | modname = "trepanxpy" 66 | py_modules = None 67 | short_desc = "GDB-like Debugger for x-python in the Trepan family" 68 | 69 | 70 | def get_srcdir(): 71 | filename = osp.normcase(osp.dirname(osp.abspath(__file__))) 72 | return osp.realpath(filename) 73 | 74 | 75 | def read(*rnames): 76 | return open(osp.join(get_srcdir(), *rnames)).read() 77 | 78 | 79 | # version.py sets variable VERSION. 80 | __version__ = None 81 | exec(read("trepanxpy", "version.py")) 82 | web = "http://github.com/rocky/python-xpy/" 83 | 84 | # tracebacks in zip files are funky and not debuggable 85 | zip_safe = False 86 | 87 | long_description = read("README.md") + "\n" 88 | -------------------------------------------------------------------------------- /admin-tools/make-dist-3.2-3.5.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | PACKAGE=trepanxpy 3 | 4 | # FIXME put some of the below in a common routine 5 | function finish { 6 | cd $owd 7 | } 8 | 9 | cd $(dirname ${BASH_SOURCE[0]}) 10 | owd=$(pwd) 11 | trap finish EXIT 12 | 13 | if ! source ./pyenv-versions-older ; then 14 | exit $? 15 | fi 16 | if ! source ./setup-python-3.2.sh ; then 17 | exit $? 18 | fi 19 | 20 | cd .. 21 | source trepanxpy/version.py 22 | echo $__version__ 23 | 24 | for pyversion in $PYVERSIONS; do 25 | echo --- $pyversion --- 26 | if ! pyenv local $pyversion ; then 27 | exit $? 28 | fi 29 | # pip bdist_egg create too-general wheels. So 30 | # we narrow that by moving the generated wheel. 31 | 32 | # Pick out first two number of version, e.g. 3.5.1 -> 35 33 | first_two=$(echo $pyversion | cut -d'.' -f 1-2 | sed -e 's/\.//') 34 | rm -fr build 35 | python setup.py bdist_egg bdist_wheel 36 | mv -v dist/${PACKAGE}-$__version__-py3-none-any.whl dist/${PACKAGE}-$__version__-py${first_two}-none-any.whl 37 | done 38 | 39 | python ./setup.py sdist 40 | tarball=dist/${PACKAGE}-${__version__}.tar.gz 41 | if [[ -f $tarball ]]; then 42 | mv -v $tarball dist/${PACKAGE}_32-${__version__}.tar.gz 43 | fi 44 | -------------------------------------------------------------------------------- /admin-tools/make-dist-newer.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | PACKAGE=trepanxpy 3 | 4 | # FIXME put some of the below in a common routine 5 | function finish { 6 | cd $owd 7 | } 8 | 9 | cd $(dirname ${BASH_SOURCE[0]}) 10 | owd=$(pwd) 11 | trap finish EXIT 12 | 13 | if ! source ./pyenv-versions-newer ; then 14 | exit $? 15 | fi 16 | if ! source ./setup-master.sh ; then 17 | exit $? 18 | fi 19 | 20 | cd .. 21 | source trepanxpy/version.py 22 | echo $__version__ 23 | 24 | for pyversion in $PYVERSIONS; do 25 | if ! pyenv local $pyversion ; then 26 | exit $? 27 | fi 28 | # pip bdist_egg create too-general wheels. So 29 | # we narrow that by moving the generated wheel. 30 | 31 | # Pick out first two number of version, e.g. 3.5.1 -> 35 32 | first_two=$(echo $pyversion | cut -d'.' -f 1-2 | sed -e 's/\.//') 33 | rm -fr build 34 | python setup.py bdist_egg bdist_wheel 35 | mv -v dist/${PACKAGE}-${__version__}-py3-none-any.whl dist/${PACKAGE}-${__version__}-py${first_two}-none-any.whl 36 | done 37 | 38 | python ./setup.py sdist 39 | -------------------------------------------------------------------------------- /admin-tools/merge-for-2.7.sh: -------------------------------------------------------------------------------- 1 | #/bin/bash 2 | cd $(dirname ${BASH_SOURCE[0]}) 3 | if . ./setup-python-2.7.sh; then 4 | git merge python-3.2-to-3.5 5 | fi 6 | -------------------------------------------------------------------------------- /admin-tools/merge-for-3.2.sh: -------------------------------------------------------------------------------- 1 | #/bin/bash 2 | cd $(dirname ${BASH_SOURCE[0]}) 3 | if . ./setup-python-3.2.sh; then 4 | git merge master 5 | fi 6 | -------------------------------------------------------------------------------- /admin-tools/pyenv-versions-newer: -------------------------------------------------------------------------------- 1 | # -*- shell-script -*- 2 | # Sets PYVERSIONS to be pyenv versions that 3 | # we can use in the master branch. 4 | export PYVERSIONS='3.6.15 3.7.16 3.8 3.9 3.10' 5 | if [[ $0 == ${BASH_SOURCE[0]} ]] ; then 6 | typeset -p PYVERSIONS 7 | echo "Note: this script should be *sourced* rather than run directly through bash" 8 | exit 1 9 | fi 10 | -------------------------------------------------------------------------------- /admin-tools/pyenv-versions-older: -------------------------------------------------------------------------------- 1 | # -*- shell-script -*- 2 | # Sets PYVERSIONS to be pyenv versions that 3 | # we can use in the master branch. 4 | export PYVERSIONS='2.7.18 3.2.6 3.3.7 3.4.10' 5 | if [[ $0 == ${BASH_SOURCE[0]} ]] ; then 6 | typeset -p PYVERSIONS 7 | echo "Note: this script should be *sourced* rather than run directly through bash" 8 | exit 1 9 | fi 10 | -------------------------------------------------------------------------------- /admin-tools/setup-master.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | PYTHON_VERSION=3.8 3 | 4 | owd=$(pwd) 5 | bs=${BASH_SOURCE[0]} 6 | if [[ $0 == $bs ]] ; then 7 | echo "This script should be *sourced* rather than run directly through bash" 8 | exit 1 9 | fi 10 | mydir=$(dirname $bs) 11 | fulldir=$(readlink -f $mydir) 12 | cd $fulldir/.. 13 | (cd ../python-spark && git checkout master && pyenv local $PYTHON_VERSION) && git pull && \ 14 | (cd ../python-xdis && git checkout master && pyenv local $PYTHON_VERSION) && \ 15 | (cd ../python3-trepan && git checkout master && pyenv local $PYTHON_VERSION) && git pull && \ 16 | git checkout master && pyenv local $PYTHON_VERSION && git pull 17 | cd $owd 18 | rm -v */.python-version || true 19 | -------------------------------------------------------------------------------- /admin-tools/setup-python-2.7.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | PYTHON_VERSION=2.7 3 | 4 | export PATH=$HOME/.pyenv/bin/pyenv:$PATH 5 | trepan_xpy_owd=$(pwd) 6 | bs=${BASH_SOURCE[0]} 7 | if [[ $0 == $bs ]] ; then 8 | echo "This script should be *sourced* rather than run directly through bash" 9 | exit 1 10 | fi 11 | mydir=$(dirname $bs) 12 | fulldir=$(readlink -f $mydir) 13 | cd $fulldir/.. 14 | (cd ../python3-trepan && ./admin-tools/setup-python-2.4.sh) && \ 15 | (cd ../x-python && ./admin-tools/setup-python-2.7.sh) 16 | git checkout python-2.7 && pyenv local $PYTHON_VERSION && git pull 17 | cd $trepan_xpy_owd 18 | rm -v */.python-version || true 19 | -------------------------------------------------------------------------------- /admin-tools/setup-python-3.2.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | PYTHON_VERSION=3.2 3 | 4 | trepan_xpy_owd=$(pwd) 5 | bs=${BASH_SOURCE[0]} 6 | mydir=$(dirname $bs) 7 | fulldir=$(readlink -f $mydir) 8 | cd $fulldir/.. 9 | (cd ../python3-trepan && ./admin-tools/setup-python-3.2.sh) && \ 10 | (cd ../x-python && ./admin-tools/setup-python-3.1.sh) 11 | git checkout python-3.2-to-3.5 && pyenv local $PYTHON_VERSION && git pull 12 | cd $trepan_xpy_owd 13 | rm -v */.python-version || true 14 | -------------------------------------------------------------------------------- /screenshots/.gitignore: -------------------------------------------------------------------------------- 1 | /trepan-xpy-demo2.gif 2 | -------------------------------------------------------------------------------- /screenshots/README.md: -------------------------------------------------------------------------------- 1 | ![demo](trepan-xpy-demo1.gif) 2 | 3 | The "cast" screenshots were made with asciienema and then running through `asciicast2gif`. 4 | 5 | For example: 6 | 7 | ``` 8 | $ asciinema rec trepan-xpy-demo1.cast 9 | ``` 10 | 11 | You can edit the `.cast` files. The specific commands used after this were: 12 | 13 | ```console 14 | $ asciicast2gif -w 82 -h 24 trepan-xpy-demo1.{cast,gif} 15 | ``` 16 | -------------------------------------------------------------------------------- /screenshots/trepan-xpy-demo1.cast: -------------------------------------------------------------------------------- 1 | {"version": 2, "width": 80, "height": 24, "timestamp": 1590724114, "env": {"SHELL": "/bin/bash", "TERM": "xterm-256color"}} 2 | [0.006224, "o", "\u001b[33m$ \u001b[0m"] 3 | [0.890216, "o", "trepan-xpy"] 4 | [1.642397, "o", " "] 5 | [1.874474, "o", "test/examples/gcd.py"] 6 | [2.387136, "o", " "] 7 | [2.539024, "o", "3 5"] 8 | [2.705286, "o", "\r\n"] 9 | [2.73683, "o", "Running x-python test/example/gcd.py with ('3', '5')\r\n"] 10 | [2.747228, "o", "(test/example/gcd.py:2): \r\n-> 2 \u001b[33m\"\"\"Greatest Common Divisor\"\"\"\u001b[39;49;00m\r\n"] 11 | [2.747312, "o", "(trepan-xpy) "] 12 | [3.515294, "o", "step"] 13 | [3.699343, "o", "\r\n"] 14 | [3.703103, "o", "(test/example/gcd.py:2): \r\n-- 2 \u001b[33m\"\"\"Greatest Common Divisor\"\"\"\u001b[39;49;00m\r\n"] 15 | [3.703171, "o", " @ 0: \u001b[32mLOAD_CONST\u001b[39;49;00m \u001b[33m'Greatest Common Divisor'\u001b[39;49;00m\r\n"] 16 | [3.703209, "o", "(trepan-xpy) "] 17 | [5.355447, "o", "__doc__ is None"] 18 | [5.501584, "o", "\r\n"] 19 | [5.511584, "o", "True\r\n"] 20 | [6.091802, "o", "(trepan-xpy) "] 21 | [8.027831, "o", "help"] 22 | [8.403621, "o", " "] 23 | [8.635755, "o", "set"] 24 | [9.099835, "o", " loglevel\r\n"] 25 | [10.00000, "o", "PyVM log level is info\r\n"] 26 | [10.069855, "o", "\u001b[01m\u001b[01mset \u001b[39;49;00m\u001b[01m\u001b[01mloglevel\u001b[39;49;00m [ \u001b[01m\u001b[01mon\u001b[39;49;00m | \u001b[01m\u001b[01moff\u001b[39;49;00m | \u001b[01m\u001b[01mdebug\u001b[39;49;00m | \u001b[01m\u001b[01minfo\u001b[39;49;00m ] \r\n\r\nShow loglevel PyVM logger messages. Initially the loglevel is \u001b[04m\u001b[01moff\u001b[39;49;00m. \r\n\r\nHowever running \u001b[04m\u001b[01mset \u001b[39;49;00m\u001b[04m\u001b[01mloglevel\u001b[39;49;00m will turn it on and set the log level to \u001b[04m\u001b[01mdebug\u001b[39;49;00m. So \r\nit's the same thing as \u001b[04m\u001b[01mset \u001b[39;49;00m\u001b[04m\u001b[01mloglevel \u001b[39;49;00m\u001b[04m\u001b[01mdebug\u001b[39;49;00m. \r\n\r\nIf you want the less verbose messages, use \u001b[04m\u001b[01minfo\u001b[39;49;00m. And to turn off, (except \r\ncritical errors), use \u001b[04m\u001b[01moff\u001b[39;49;00m. \r\n\r\n\u001b[01mExamples:\u001b[39;49;00m \r\n\r\n\u001b[33m set loglevel # turns x-python on info logging messages\u001b[39;49;00m\u001b[33m\r\n\u001b[39;49;00m\u001b[33m set loglevel info # same as above\u001b[39;49;00m\u001b[33m\r\n\u001b[39;49;00m\u001b[33m set loglevel debug # turn on info and debug logging messages\u001b[39;49;00m\u001b[33m\r\n\u001b[39;49;00m\u001b[33m set loglevel off # turn off all logging "] 27 | [10.069957, "o", "messages except critical ones\u001b[39;49;00m\u001b[33m\r\n\u001b[39;49;00m\u001b[33m \u001b[39;49;00m\u001b[33m\r\n\u001b[39;49;00m\r\n(trepan-xpy) "] 28 | [15.532534, "o", "set "] 29 | [15.534467, "o", "loglevel"] 30 | [15.800678, "o", "\r\n"] 31 | [15.800918, "o", "(trepan-xpy) "] 32 | [15.857065, "o", "stepi"] 33 | [15.909007, "o", "\r\n"] 34 | [15.989461, "o", "INFO:xpython.vm: @ 2: \u001b[32mSTORE_NAME\u001b[39;49;00m ('Greatest Common Divisor') \u001b[36m__doc__\u001b[39;49;00m\r\n"] 35 | [15.992977, "o", "(test/example/gcd.py:2 @2): \r\n.. 2 \u001b[33m\"\"\"Greatest Common Divisor\"\"\"\u001b[39;49;00m\r\n"] 36 | [15.993046, "o", " @ 2: \u001b[32mSTORE_NAME\u001b[39;49;00m ('Greatest Common Divisor') \u001b[36m__doc__\u001b[39;49;00m\r\n"] 37 | [15.993098, "o", "(trepan-xpy) "] 38 | [18.533294, "o", "info"] 39 | [18.989711, "o", " "] 40 | [19.213211, "o", "stack"] 41 | [20.149286, "o", "\r\n"] 42 | [20.149467, "o", " 0: 'Greatest Common Divisor'\r\n"] 43 | [20.149521, "o", "(trepan-xpy) "] 44 | [21.965495, "o", "step"] 45 | [22.805594, "o", "\r\n"] 46 | [22.805949, "o", "INFO:xpython.vm:L. \u001b[34m10 \u001b[39;49;00m@ 4: \u001b[32mLOAD_CONST\u001b[39;49;00m \u001b[34m0\u001b[39;49;00m\r\n"] 47 | [22.809454, "o", "(test/example/gcd.py:10 @4): \r\n-- 10 \u001b[94mimport\u001b[39;49;00m \u001b[04m\u001b[96msys\u001b[39;49;00m\r\n"] 48 | [22.80948, "o", " @ 4: \u001b[32mLOAD_CONST\u001b[39;49;00m \u001b[34m0\u001b[39;49;00m\r\n"] 49 | [22.80957, "o", "(trepan-xpy) "] 50 | [24.061813, "o", "info stack"] 51 | [24.84593, "o", "\r\n"] 52 | [24.846117, "o", "Evaluation stack is empty\r\n"] 53 | [24.846165, "o", "(trepan-xpy) "] 54 | [26.550188, "o", "__doc__"] 55 | [28.086213, "o", "\r\n"] 56 | [28.086385, "o", "'Greatest Common Divisor'\r\n"] 57 | [28.086444, "o", "(trepan-xpy) "] 58 | [28.982485, "o", "step"] 59 | [29.246316, "o", "\r\n"] 60 | [29.246658, "o", "INFO:xpython.vm: @ 6: \u001b[32mLOAD_CONST\u001b[39;49;00m None\r\n"] 61 | [29.246769, "o", "INFO:xpython.vm: @ 8: \u001b[32mIMPORT_NAME\u001b[39;49;00m (0, None) \u001b[36msys\u001b[39;49;00m\r\n"] 62 | [29.246878, "o", "INFO:xpython.vm: @ 10: \u001b[32mSTORE_NAME\u001b[39;49;00m () \u001b[36msys\u001b[39;49;00m\r\n"] 63 | [29.246979, "o", "INFO:xpython.vm:L. \u001b[34m12 \u001b[39;49;00m@ 12: \u001b[32mLOAD_CONST\u001b[39;49;00m \r\n"] 64 | [29.25048, "o", "(test/example/gcd.py:12 @12): \r\n-- 12 \u001b[94mdef\u001b[39;49;00m \u001b[92mcheck_args\u001b[39;49;00m():\r\n"] 65 | [29.250506, "o", " @ 12: \u001b[32mLOAD_CONST\u001b[39;49;00m \r\n"] 66 | [29.250586, "o", "(trepan-xpy) "] 67 | [29.638456, "o", "step"] 68 | [30.262446, "o", "\r\n"] 69 | [30.262768, "o", "INFO:xpython.vm: @ 14: \u001b[32mLOAD_CONST\u001b[39;49;00m \u001b[33m'check_args'\u001b[39;49;00m\r\n"] 70 | [30.262874, "o", "INFO:xpython.vm: @ 16: \u001b[32mMAKE_FUNCTION\u001b[39;49;00m (check_args) Neither defaults, keyword-only args, annotations, nor closures\r\n"] 71 | [30.26302, "o", "INFO:xpython.vm: @ 18: \u001b[32mSTORE_NAME\u001b[39;49;00m () \u001b[36mcheck_args\u001b[39;49;00m\r\n"] 72 | [30.263107, "o", "INFO:xpython.vm:L. \u001b[34m25 \u001b[39;49;00m@ 20: \u001b[32mLOAD_CONST\u001b[39;49;00m \r\n"] 73 | [30.266584, "o", "(test/example/gcd.py:25 @20): \r\n-- 25 \u001b[94mdef\u001b[39;49;00m \u001b[92mgcd\u001b[39;49;00m(a,b):\r\n"] 74 | [30.266618, "o", " @ 20: \u001b[32mLOAD_CONST\u001b[39;49;00m \r\n"] 75 | [30.266688, "o", "(trepan-xpy) "] 76 | [31.710695, "o", "step"] 77 | [31.750805, "o", "\r\n"] 78 | [31.751117, "o", "INFO:xpython.vm: @ 22: \u001b[32mLOAD_CONST\u001b[39;49;00m \u001b[33m'gcd'\u001b[39;49;00m\r\n"] 79 | [31.751229, "o", "INFO:xpython.vm: @ 24: \u001b[32mMAKE_FUNCTION\u001b[39;49;00m (gcd) Neither defaults, keyword-only args, annotations, nor closures\r\n"] 80 | [31.751372, "o", "INFO:xpython.vm: @ 26: \u001b[32mSTORE_NAME\u001b[39;49;00m () \u001b[36mgcd\u001b[39;49;00m\r\n"] 81 | [31.751474, "o", "INFO:xpython.vm:L. \u001b[34m39 \u001b[39;49;00m@ 28: \u001b[32mLOAD_NAME\u001b[39;49;00m \u001b[36m__name__\u001b[39;49;00m\r\n"] 82 | [31.754963, "o", "(test/example/gcd.py:39 @28): \r\n-- 39 \u001b[94mif\u001b[39;49;00m \u001b[04m\u001b[01m__name__\u001b[39;49;00m==\u001b[33m'\u001b[39;49;00m\u001b[33m__main__\u001b[39;49;00m\u001b[33m'\u001b[39;49;00m:\r\n"] 83 | [31.755002, "o", " @ 28: \u001b[32mLOAD_NAME\u001b[39;49;00m \u001b[36m__name__\u001b[39;49;00m\r\n"] 84 | [31.755054, "o", "(trepan-xpy) "] 85 | [32.254715, "o", "eval?"] 86 | [33.559062, "o", "\r\n"] 87 | [33.559603, "o", "eval: __name__=='__main__'\r\n"] 88 | [33.559664, "o", "True\r\n"] 89 | [33.559709, "o", "(trepan-xpy) "] 90 | [34.527228, "o", "step"] 91 | [35.150969, "o", "\r\n"] 92 | [35.151271, "o", "INFO:xpython.vm: @ 30: \u001b[32mLOAD_CONST\u001b[39;49;00m \u001b[33m'__main__'\u001b[39;49;00m\r\n"] 93 | [35.151383, "o", "INFO:xpython.vm: @ 32: \u001b[32mCOMPARE_OP\u001b[39;49;00m ('__main__', '__main__') ==\r\n"] 94 | [35.151462, "o", "INFO:xpython.vm: @ 34: \u001b[32mPOP_JUMP_IF_FALSE\u001b[39;49;00m \u001b[34m84\u001b[39;49;00m\r\n"] 95 | [35.151539, "o", "INFO:xpython.vm:L. \u001b[34m40 \u001b[39;49;00m@ 36: \u001b[32mLOAD_NAME\u001b[39;49;00m \u001b[36mcheck_args\u001b[39;49;00m\r\n"] 96 | [35.155221, "o", "(test/example/gcd.py:40 @36): \r\n-- 40 check_args()\r\n @ 36: \u001b[32mLOAD_NAME\u001b[39;49;00m \u001b[36mcheck_args\u001b[39;49;00m\r\n"] 97 | [35.155298, "o", "(trepan-xpy) "] 98 | [35.775063, "o", "next"] 99 | [36.343204, "o", "\r\n"] 100 | [36.343531, "o", "INFO:xpython.vm: @ 38: \u001b[32mCALL_FUNCTION\u001b[39;49;00m (check_args) 0 positional arguments\r\n"] 101 | [36.343793, "o", "INFO:xpython.vm: L. \u001b[34m13 \u001b[39;49;00m@ 0: \u001b[32mLOAD_GLOBAL\u001b[39;49;00m \u001b[36mlen\u001b[39;49;00m\r\n"] 102 | [36.343867, "o", "INFO:xpython.vm: @ 2: \u001b[32mLOAD_GLOBAL\u001b[39;49;00m \u001b[36msys\u001b[39;49;00m\r\n"] 103 | [36.343954, "o", "INFO:xpython.vm: @ 4: \u001b[32mLOAD_ATTR\u001b[39;49;00m \u001b[36margv\u001b[39;49;00m\r\n"] 104 | [36.34405, "o", "INFO:xpython.vm: @ 6: \u001b[32mCALL_FUNCTION\u001b[39;49;00m (len) 1 positional argument\r\n"] 105 | [36.344169, "o", "INFO:xpython.vm: @ 8: \u001b[32mLOAD_CONST\u001b[39;49;00m \u001b[34m3\u001b[39;49;00m\r\n"] 106 | [36.344246, "o", "INFO:xpython.vm: @ 10: \u001b[32mCOMPARE_OP\u001b[39;49;00m (3, 3) !=\r\n"] 107 | [36.344328, "o", "INFO:xpython.vm: @ 12: \u001b[32mPOP_JUMP_IF_FALSE\u001b[39;49;00m \u001b[34m22\u001b[39;49;00m\r\n"] 108 | [36.3444, "o", "INFO:xpython.vm: L. \u001b[34m16 \u001b[39;49;00m@ 22: \u001b[32mSETUP_LOOP\u001b[39;49;00m \u001b[34m126\u001b[39;49;00m\r\n"] 109 | [36.34448, "o", "INFO:xpython.vm: @ 24: \u001b[32mLOAD_GLOBAL\u001b[39;49;00m \u001b[36mrange\u001b[39;49;00m\r\n"] 110 | [36.344562, "o", "INFO:xpython.vm: @ 26: \u001b[32mLOAD_CONST\u001b[39;49;00m \u001b[34m2\u001b[39;49;00m\r\n"] 111 | [36.344642, "o", "INFO:xpython.vm: @ 28: \u001b[32mCALL_FUNCTION\u001b[39;49;00m (range) 1 positional argument\r\n"] 112 | [36.344722, "o", "INFO:xpython.vm: @ 30: \u001b[32mGET_ITER\u001b[39;49;00m (range(0, 2)) \r\n"] 113 | [36.344806, "o", "INFO:xpython.vm: @ 32: \u001b[32mFOR_ITER\u001b[39;49;00m \u001b[34m124\u001b[39;49;00m\r\n"] 114 | [36.344891, "o", "INFO:xpython.vm: @ 34: \u001b[32mSTORE_FAST\u001b[39;49;00m (0) \u001b[36mi\u001b[39;49;00m\r\n"] 115 | [36.344974, "o", "INFO:xpython.vm: L. \u001b[34m17 \u001b[39;49;00m@ 36: \u001b[32mSETUP_EXCEPT\u001b[39;49;00m \u001b[34m70\u001b[39;49;00m\r\n"] 116 | [36.345058, "o", "INFO:xpython.vm: L. \u001b[34m18 \u001b[39;49;00m@ 38: \u001b[32mLOAD_GLOBAL\u001b[39;49;00m \u001b[36mint\u001b[39;49;00m\r\n"] 117 | [36.345156, "o", "INFO:xpython.vm: @ 40: \u001b[32mLOAD_GLOBAL\u001b[39;49;00m \u001b[36msys\u001b[39;49;00m\r\n"] 118 | [36.345234, "o", "INFO:xpython.vm: @ 42: \u001b[32mLOAD_ATTR\u001b[39;49;00m \u001b[36margv\u001b[39;49;00m\r\n"] 119 | [36.345331, "o", "INFO:xpython.vm: @ 44: \u001b[32mLOAD_FAST\u001b[39;49;00m \u001b[36mi\u001b[39;49;00m\r\n"] 120 | [36.345431, "o", "INFO:xpython.vm: @ 46: \u001b[32mLOAD_CONST\u001b[39;49;00m \u001b[34m1\u001b[39;49;00m\r\n"] 121 | [36.345521, "o", "INFO:xpython.vm: @ 48: \u001b[32mBINARY_ADD\u001b[39;49;00m (0, 1) \r\n"] 122 | [36.345622, "o", "INFO:xpython.vm: @ 50: \u001b[32mBINARY_SUBSCR\u001b[39;49;00m (['test/example/gcd.py', '3', '5'], 1) \r\n"] 123 | [36.3457, "o", "INFO:xpython.vm: @ 52: \u001b[32mCALL_FUNCTION\u001b[39;49;00m (int) 1 positional argument\r\n"] 124 | [36.345795, "o", "INFO:xpython.vm: @ 54: \u001b[32mLOAD_GLOBAL\u001b[39;49;00m \u001b[36msys\u001b[39;49;00m\r\n"] 125 | [36.345882, "o", "INFO:xpython.vm: @ 56: \u001b[32mLOAD_ATTR\u001b[39;49;00m \u001b[36margv\u001b[39;49;00m\r\n"] 126 | [36.345981, "o", "INFO:xpython.vm: @ 58: \u001b[32mLOAD_FAST\u001b[39;49;00m \u001b[36mi\u001b[39;49;00m\r\n"] 127 | [36.346075, "o", "INFO:xpython.vm: @ 60: \u001b[32mLOAD_CONST\u001b[39;49;00m \u001b[34m1\u001b[39;49;00m\r\n"] 128 | [36.346162, "o", "INFO:xpython.vm: @ 62: \u001b[32mBINARY_ADD\u001b[39;49;00m (0, 1) \r\n"] 129 | [36.346252, "o", "INFO:xpython.vm: @ 64: \u001b[32mSTORE_SUBSCR\u001b[39;49;00m (3, ['test/example/gcd.py', '3', '5'], 1) \r\n"] 130 | [36.346325, "o", "INFO:xpython.vm: @ 66: \u001b[32mPOP_BLOCK\u001b[39;49;00m \r\n"] 131 | [36.346405, "o", "INFO:xpython.vm: @ 68: \u001b[32mJUMP_ABSOLUTE\u001b[39;49;00m \u001b[34m32\u001b[39;49;00m\r\n"] 132 | [36.346482, "o", "INFO:xpython.vm: @ 32: \u001b[32mFOR_ITER\u001b[39;49;00m \u001b[34m124\u001b[39;49;00m\r\n"] 133 | [36.346563, "o", "INFO:xpython.vm: @ 34: \u001b[32mSTORE_FAST\u001b[39;49;00m (1) \u001b[36mi\u001b[39;49;00m\r\n"] 134 | [36.346649, "o", "INFO:xpython.vm: L. \u001b[34m17 \u001b[39;49;00m@ 36: \u001b[32mSETUP_EXCEPT\u001b[39;49;00m \u001b[34m70\u001b[39;49;00m\r\n"] 135 | [36.346727, "o", "INFO:xpython.vm: L. \u001b[34m18 \u001b[39;49;00m@ 38: \u001b[32mLOAD_GLOBAL\u001b[39;49;00m \u001b[36mint\u001b[39;49;00m\r\n"] 136 | [36.346808, "o", "INFO:xpython.vm: @ 40: \u001b[32mLOAD_GLOBAL\u001b[39;49;00m \u001b[36msys\u001b[39;49;00m\r\n"] 137 | [36.346895, "o", "INFO:xpython.vm: @ 42: \u001b[32mLOAD_ATTR\u001b[39;49;00m \u001b[36margv\u001b[39;49;00m\r\n"] 138 | [36.346987, "o", "INFO:xpython.vm: @ 44: \u001b[32mLOAD_FAST\u001b[39;49;00m \u001b[36mi\u001b[39;49;00m\r\n"] 139 | [36.347084, "o", "INFO:xpython.vm: @ 46: \u001b[32mLOAD_CONST\u001b[39;49;00m \u001b[34m1\u001b[39;49;00m\r\n"] 140 | [36.347173, "o", "INFO:xpython.vm: @ 48: \u001b[32mBINARY_ADD\u001b[39;49;00m (1, 1) \r\n"] 141 | [36.347263, "o", "INFO:xpython.vm: @ 50: \u001b[32mBINARY_SUBSCR\u001b[39;49;00m (['test/example/gcd.py', 3, '5'], 2) \r\n"] 142 | [36.347347, "o", "INFO:xpython.vm: @ 52: \u001b[32mCALL_FUNCTION\u001b[39;49;00m (int) 1 positional argument\r\n"] 143 | [36.347437, "o", "INFO:xpython.vm: @ 54: \u001b[32mLOAD_GLOBAL\u001b[39;49;00m \u001b[36msys\u001b[39;49;00m\r\n"] 144 | [36.347523, "o", "INFO:xpython.vm: @ 56: \u001b[32mLOAD_ATTR\u001b[39;49;00m \u001b[36margv\u001b[39;49;00m\r\n"] 145 | [36.347614, "o", "INFO:xpython.vm: @ 58: \u001b[32mLOAD_FAST\u001b[39;49;00m \u001b[36mi\u001b[39;49;00m\r\n"] 146 | [36.34771, "o", "INFO:xpython.vm: @ 60: \u001b[32mLOAD_CONST\u001b[39;49;00m \u001b[34m1\u001b[39;49;00m\r\n"] 147 | [36.347799, "o", "INFO:xpython.vm: @ 62: \u001b[32mBINARY_ADD\u001b[39;49;00m (1, 1) \r\n"] 148 | [36.347889, "o", "INFO:xpython.vm: @ 64: \u001b[32mSTORE_SUBSCR\u001b[39;49;00m (5, ['test/example/gcd.py', 3, '5'], 2) \r\n"] 149 | [36.347961, "o", "INFO:xpython.vm: @ 66: \u001b[32mPOP_BLOCK\u001b[39;49;00m \r\n"] 150 | [36.348038, "o", "INFO:xpython.vm: @ 68: \u001b[32mJUMP_ABSOLUTE\u001b[39;49;00m \u001b[34m32\u001b[39;49;00m\r\n"] 151 | [36.348124, "o", "INFO:xpython.vm: @ 32: \u001b[32mFOR_ITER\u001b[39;49;00m \u001b[34m124\u001b[39;49;00m\r\n"] 152 | [36.348192, "o", "INFO:xpython.vm: @124: \u001b[32mPOP_BLOCK\u001b[39;49;00m \r\n"] 153 | [36.348266, "o", "INFO:xpython.vm: @126: \u001b[32mLOAD_CONST\u001b[39;49;00m None\r\n"] 154 | [36.348352, "o", "INFO:xpython.vm: @128: \u001b[32mRETURN_VALUE\u001b[39;49;00m (None) \r\n"] 155 | [36.348425, "o", "INFO:xpython.vm: @ 40: \u001b[32mPOP_TOP\u001b[39;49;00m \r\n"] 156 | [36.348499, "o", "INFO:xpython.vm:L. \u001b[34m42 \u001b[39;49;00m@ 42: \u001b[32mLOAD_NAME\u001b[39;49;00m \u001b[36msys\u001b[39;49;00m\r\n"] 157 | [36.352136, "o", "(test/example/gcd.py:42 @42): \r\n-- 42 (a, b) = sys.argv[\u001b[94m1\u001b[39;49;00m:\u001b[94m3\u001b[39;49;00m]\r\n"] 158 | [36.352164, "o", " @ 42: \u001b[32mLOAD_NAME\u001b[39;49;00m \u001b[36msys\u001b[39;49;00m\r\n"] 159 | [36.352238, "o", "(trepan-xpy) "] 160 | [40.679566, "o", "step"] 161 | [41.703799, "o", "\r\n"] 162 | [41.704137, "o", "INFO:xpython.vm: @ 44: \u001b[32mLOAD_ATTR\u001b[39;49;00m \u001b[36margv\u001b[39;49;00m\r\n"] 163 | [41.704265, "o", "INFO:xpython.vm: @ 46: \u001b[32mLOAD_CONST\u001b[39;49;00m \u001b[34m1\u001b[39;49;00m\r\n"] 164 | [41.704358, "o", "INFO:xpython.vm: @ 48: \u001b[32mLOAD_CONST\u001b[39;49;00m \u001b[34m3\u001b[39;49;00m\r\n"] 165 | [41.704452, "o", "INFO:xpython.vm: @ 50: \u001b[32mBUILD_SLICE\u001b[39;49;00m 2\r\n"] 166 | [41.704558, "o", "INFO:xpython.vm: @ 52: \u001b[32mBINARY_SUBSCR\u001b[39;49;00m (['test/example/gcd.py', 3, 5], slice(1, 3, None)) \r\n"] 167 | [41.704646, "o", "INFO:xpython.vm: @ 54: \u001b[32mUNPACK_SEQUENCE\u001b[39;49;00m 2\r\n"] 168 | [41.704738, "o", "INFO:xpython.vm: @ 56: \u001b[32mSTORE_NAME\u001b[39;49;00m (3) \u001b[36ma\u001b[39;49;00m\r\n"] 169 | [41.704822, "o", "INFO:xpython.vm: @ 58: \u001b[32mSTORE_NAME\u001b[39;49;00m (5) \u001b[36mb\u001b[39;49;00m\r\n"] 170 | [41.704903, "o", "INFO:xpython.vm:L. \u001b[34m43 \u001b[39;49;00m@ 60: \u001b[32mLOAD_NAME\u001b[39;49;00m \u001b[36mprint\u001b[39;49;00m\r\n"] 171 | [41.708507, "o", "(test/example/gcd.py:43 @60): \r\n-- 43 \u001b[96mprint\u001b[39;49;00m(\u001b[33m\"\u001b[39;49;00m\u001b[33mThe GCD of \u001b[39;49;00m\u001b[33m%d\u001b[39;49;00m\u001b[33m and \u001b[39;49;00m\u001b[33m%d\u001b[39;49;00m\u001b[33m is \u001b[39;49;00m\u001b[33m%d\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m % (a, b, gcd(a, b)))\r\n"] 172 | [41.708603, "o", " @ 60: \u001b[32mLOAD_NAME\u001b[39;49;00m \u001b[36mprint\u001b[39;49;00m\r\n"] 173 | [41.708649, "o", "(trepan-xpy) "] 174 | [43.809618, "o", "step"] 175 | [43.823781, "o", "\r\n"] 176 | [43.824088, "o", "INFO:xpython.vm: @ 62: \u001b[32mLOAD_CONST\u001b[39;49;00m \u001b[33m'The GCD of %d and %d is %d'\u001b[39;49;00m\r\n"] 177 | [43.824199, "o", "INFO:xpython.vm: @ 64: \u001b[32mLOAD_NAME\u001b[39;49;00m \u001b[36ma\u001b[39;49;00m\r\n"] 178 | [43.824294, "o", "INFO:xpython.vm: @ 66: \u001b[32mLOAD_NAME\u001b[39;49;00m \u001b[36mb\u001b[39;49;00m\r\n"] 179 | [43.824391, "o", "INFO:xpython.vm: @ 68: \u001b[32mLOAD_NAME\u001b[39;49;00m \u001b[36mgcd\u001b[39;49;00m\r\n"] 180 | [43.824492, "o", "INFO:xpython.vm: @ 70: \u001b[32mLOAD_NAME\u001b[39;49;00m \u001b[36ma\u001b[39;49;00m\r\n"] 181 | [43.824592, "o", "INFO:xpython.vm: @ 72: \u001b[32mLOAD_NAME\u001b[39;49;00m \u001b[36mb\u001b[39;49;00m\r\n"] 182 | [43.82469, "o", "INFO:xpython.vm: @ 74: \u001b[32mCALL_FUNCTION\u001b[39;49;00m (gcd) 2 positional arguments\r\n"] 183 | [43.828295, "o", "(test/example/gcd.py:29): gcd\r\n-> 29 \u001b[94mif\u001b[39;49;00m a > b:\r\n"] 184 | [43.828371, "o", "__locals__ = {}\r\n"] 185 | [43.828422, "o", "a = 3\r\n"] 186 | [43.828468, "o", "b = 5\r\n"] 187 | [43.828512, "o", "(trepan-xpy) "] 188 | [44.589853, "o", "step"] 189 | [44.607864, "o", "\r\n"] 190 | [44.608162, "o", "INFO:xpython.vm: L. \u001b[34m29 \u001b[39;49;00m@ 0: \u001b[32mLOAD_FAST\u001b[39;49;00m \u001b[36ma\u001b[39;49;00m\r\n"] 191 | [44.611708, "o", "(test/example/gcd.py:29): gcd\r\n-- 29 \u001b[94mif\u001b[39;49;00m a > b:\r\n"] 192 | [44.611793, "o", " @ 0: \u001b[32mLOAD_FAST\u001b[39;49;00m \u001b[36ma\u001b[39;49;00m\r\n"] 193 | [44.611862, "o", "(trepan-xpy) "] 194 | [45.35189, "o", "return 1"] 195 | [46.528253, "o", "\r\n"] 196 | [46.531919, "o", "(test/example/gcd.py:43 @74): \r\n<- 43 \u001b[96mprint\u001b[39;49;00m(\u001b[33m\"\u001b[39;49;00m\u001b[33mThe GCD of \u001b[39;49;00m\u001b[33m%d\u001b[39;49;00m\u001b[33m and \u001b[39;49;00m\u001b[33m%d\u001b[39;49;00m\u001b[33m is \u001b[39;49;00m\u001b[33m%d\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m % (a, b, gcd(a, b)))\r\n"] 197 | [46.531996, "o", "R=> 1\r\n"] 198 | [46.532044, "o", "(trepan-xpy) "] 199 | [49.844394, "o", "step"] 200 | [50.008538, "o", "\r\n"] 201 | [50.008842, "o", "INFO:xpython.vm: @ 76: \u001b[32mBUILD_TUPLE\u001b[39;49;00m 3\r\n"] 202 | [50.00895, "o", "INFO:xpython.vm: @ 78: \u001b[32mBINARY_MODULO\u001b[39;49;00m ('The GCD of %d and %d is %d', (3, 5, 1)) \r\n"] 203 | [50.009035, "o", "INFO:xpython.vm: @ 80: \u001b[32mCALL_FUNCTION\u001b[39;49;00m (print) 1 positional argument\r\n"] 204 | [50.009069, "o", "The GCD of 3 and 5 is 1\r\n"] 205 | [50.009129, "o", "INFO:xpython.vm: @ 82: \u001b[32mPOP_TOP\u001b[39;49;00m \r\n"] 206 | [50.009217, "o", "INFO:xpython.vm:L. \u001b[34m44 \u001b[39;49;00m@ 84: \u001b[32mLOAD_CONST\u001b[39;49;00m None\r\n"] 207 | [50.012675, "o", "(test/example/gcd.py:44 @84): \r\n-- 44 \u001b[94mpass\u001b[39;49;00m\r\n"] 208 | [50.012752, "o", " @ 84: \u001b[32mLOAD_CONST\u001b[39;49;00m None\r\n"] 209 | [50.0128, "o", "(trepan-xpy) "] 210 | [52.1367, "o", "quit"] 211 | [52.576775, "o", "\r\n"] 212 | [52.576929, "o", "Really quit? (N or y) "] 213 | [53.440844, "o", "y"] 214 | [53.4873, "o", "\r\n"] 215 | [53.495002, "o", "trepan-xpy: That's all, folks...\r\n"] 216 | [53.605979, "o", "\u001b[33m$ \u001b[0m"] 217 | [60.905979, "o", ""] 218 | -------------------------------------------------------------------------------- /screenshots/trepan-xpy-demo1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Trepan-Debuggers/trepan-xpy/55959967216b7cc270488b96e90df7891ffaaddb/screenshots/trepan-xpy-demo1.gif -------------------------------------------------------------------------------- /screenshots/trepan-xpy-demo1.txt: -------------------------------------------------------------------------------- 1 | trepan-xpy test/example/gcd.py 3 5 2 | step 3 | __doc__ is None 4 | help set logtrace 5 | set logtrace 6 | stepi 7 | info stack 8 | stepi 9 | info stack 10 | __doc__ 11 | step 12 | step 13 | step 14 | eval? 15 | step 16 | next 17 | # scroll a bit 18 | step 19 | step 20 | return 1 21 | step 22 | quit 23 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_rpm] 2 | release = 0 3 | packager = rocky 79 characters) (replaced by B950 from flake8-bugbear, 33 | # https://github.com/PyCQA/flake8-bugbear) 34 | E501 35 | # W503 line break before binary operator (Not PEP8 compliant, Python Black) 36 | W503 37 | # W504 line break after binary operator (Not PEP8 compliant, Python Black) 38 | W504 39 | # C901 function too complex - since many of zz9 functions are too complex with a lot 40 | # of if branching 41 | C901 42 | # module level import not at top of file. This is too restrictive. Can't even have a 43 | # docstring higher. 44 | E402 45 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import sys 3 | 4 | SYS_VERSION = sys.version_info[0:2] 5 | if not ((3, 1) <= SYS_VERSION <= (3, 12)): 6 | mess = "Python Versions 3.1 to 3.12 are supported only in this package." 7 | if (2, 4) <= SYS_VERSION <= (2, 7): 8 | mess += "\nFor your Python, version %s, See trepan2" % sys.version[0:3] 9 | elif SYS_VERSION < (2, 4): 10 | mess += "\nFor your Python, version %s, see pydb" % sys.version[0:3] 11 | print(mess) 12 | raise Exception(mess) 13 | 14 | # Get the package information used in setup(). 15 | from __pkginfo__ import ( 16 | author, 17 | author_email, 18 | classifiers, 19 | entry_points, 20 | extras_require, 21 | install_requires, 22 | license, 23 | long_description, 24 | modname, 25 | py_modules, 26 | short_desc, 27 | __version__, 28 | web, 29 | zip_safe, 30 | ) 31 | 32 | __import__("pkg_resources") 33 | 34 | from setuptools import setup, find_packages 35 | 36 | packages = find_packages() 37 | 38 | setup( 39 | author=author, 40 | author_email=author_email, 41 | classifiers=classifiers, 42 | description=short_desc, 43 | entry_points=entry_points, 44 | extras_require=extras_require, 45 | install_requires=install_requires, 46 | license=license, 47 | long_description=long_description, 48 | long_description_content_type="text/markdown", 49 | name=modname, 50 | packages=packages, 51 | py_modules=py_modules, 52 | test_suite="nose.collector", 53 | url=web, 54 | version=__version__, 55 | zip_safe=zip_safe, 56 | ) 57 | -------------------------------------------------------------------------------- /test/example/fib.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | def fib(x): 4 | if x <= 1: 5 | return 1 6 | return fib(x-1) + fib(x-2) 7 | 8 | 9 | print("fib(2)= %d, fib(3) = %d, fib(4) = %d\n" % (fib(2), fib(3), fib(4))) 10 | -------------------------------------------------------------------------------- /test/example/gcd.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Greatest Common Divisor""" 3 | 4 | # Some characterstics of this program used for testing: 5 | # 6 | # * check_args() does not have a 'return' statement. 7 | # * check_args() raises an uncaught exception when given the wrong number 8 | # of parameters. 9 | 10 | import sys 11 | 12 | def check_args(): 13 | if len(sys.argv) != 3: 14 | # Rather than use sys.exit let's just raise an error 15 | raise Exception("Need to give two numbers") 16 | for i in range(2): 17 | try: 18 | sys.argv[i+1] = int(sys.argv[i+1]) 19 | except ValueError: 20 | print("** Expecting an integer, got: %s" % repr(sys.argv[i])) 21 | sys.exit(2) 22 | pass 23 | pass 24 | 25 | def gcd(a,b): 26 | """ GCD. We assume positive numbers""" 27 | 28 | # Make: a <= b 29 | if a > b: 30 | (a, b) = (b, a) 31 | pass 32 | 33 | if a <= 0: 34 | return None 35 | if a == 1 or b-a == 0: 36 | return a 37 | return gcd(b-a, a) 38 | 39 | if __name__=='__main__': 40 | check_args() 41 | 42 | (a, b) = sys.argv[1:3] 43 | print("The GCD of %d and %d is %d" % (a, b, gcd(a, b))) 44 | pass 45 | -------------------------------------------------------------------------------- /test/example/hanoi.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Towers of Hanoi""" 3 | import sys 4 | 5 | def hanoi(n,a,b,c): 6 | if n-1 > 0: 7 | hanoi(n-1, a, c, b) 8 | print("Move disk %s to %s" % (a, b)) 9 | if n-1 > 0: 10 | hanoi(n-1, c, b, a) 11 | 12 | if __name__=='__main__': 13 | i_args=len(sys.argv) 14 | if i_args != 1 and i_args != 2: 15 | print("*** Need number of disks or no parameter") 16 | sys.exit(1) 17 | 18 | n=3 19 | 20 | if i_args > 1: 21 | try: 22 | n = int(sys.argv[1]) 23 | except ValueError as msg: 24 | print("** Expecting an integer, got: %s" % repr(sys.argv[1])) 25 | sys.exit(2) 26 | 27 | if n < 1 or n > 100: 28 | print("*** number of disks should be between 1 and 100") 29 | sys.exit(2) 30 | 31 | hanoi(n, "a", "b", "c") 32 | -------------------------------------------------------------------------------- /trepanxpy/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | trepan debugger for x-python 3 | 4 | Copyright 2020-2021 Rocky Bernstein 5 | """ 6 | __docformat__ = "restructuredtext" 7 | 8 | from trepanxpy.version import __version__ # noqa 9 | -------------------------------------------------------------------------------- /trepanxpy/__main__.py: -------------------------------------------------------------------------------- 1 | """A main program for trepan-xpy.""" 2 | import sys 3 | import click 4 | from typing import List 5 | 6 | from trepanxpy.version import __version__ 7 | from trepanxpy.debugger import TrepanXPy 8 | 9 | @click.command() 10 | @click.version_option(__version__, "-V", "--version") 11 | @click.option("-x", "--trace", default=False, required=False, flag_value="trace", 12 | help="Run with instruction tracing, no interactive debugging (until post-mortem)") 13 | @click.option("-c", "--command-to-run", 14 | help="program passed in as a string", required=False) 15 | @click.argument("path", nargs=1, type=click.Path(readable=True), required=False) 16 | @click.argument("args", nargs=-1) 17 | def main(trace: bool, path: str, command_to_run: str, args: List[str]): 18 | 19 | # FIXME: This seems to be needed for pyficache to work on relative paths. 20 | # is this a bug? 21 | sys.path.append(".") 22 | 23 | is_file = True 24 | string_or_path = path 25 | if command_to_run: 26 | if path or args: 27 | print("You must pass either a file name or a command string, not both.") 28 | sys.exit(4) 29 | string_or_path = command_to_run 30 | is_file = False 31 | elif not path: 32 | print("You must pass either a file name or a command string, neither found.") 33 | sys.exit(4) 34 | 35 | TrepanXPy(string_or_path, is_file, trace_only=trace, args=args) 36 | 37 | if __name__ == "__main__": 38 | main(auto_envvar_prefix="XPYTHON") 39 | -------------------------------------------------------------------------------- /trepanxpy/core.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (C) 2020 Rocky Bernstein 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | """Debugger core routines. 17 | 18 | This module contains the Debugger core routines for starting and 19 | stopping trace event handling and breakpoint checking. See also 20 | debugger for top-level Debugger class and module routine which 21 | ultimately will call this. An event processor is responsible of 22 | handling what to do when an event is triggered.""" 23 | 24 | from typing import Any, Optional 25 | 26 | from xpython.vmtrace import PyVMEVENT_FLAG_BITS 27 | 28 | import os, sys, threading 29 | 30 | from trepan.misc import option_set 31 | from trepan.clifns import search_file 32 | import trepan.lib.breakpoint as breakpoint 33 | # import trepanxpy.breakpoint as breakpoint 34 | 35 | # Our local modules 36 | from trepanxpy.processor.trace import XPyPrintProcessor 37 | 38 | 39 | class TrepanXPyCore(object): 40 | 41 | DEFAULT_INIT_OPTS = { 42 | "processor": None, 43 | # How many step events to skip before 44 | # entering event processor? Zero (0) means stop at the next one. 45 | # A negative number indicates no eventual stopping. 46 | "step_ignore": 0, 47 | "ignore_filter": None, # But see debugger.py 48 | } 49 | 50 | def __init__(self, debugger, opts=None): 51 | """ Create a debugger object. But depending on the value of 52 | key 'start' inside hash `opts', we may or may not initially 53 | start tracing events (i.e. enter the debugger). 54 | 55 | See also `start' and `stop'. 56 | """ 57 | 58 | # import trepan.bwprocessor as Mbwproc 59 | 60 | get_option = lambda key: option_set(opts, key, self.DEFAULT_INIT_OPTS) 61 | 62 | self.bpmgr = breakpoint.BreakpointManager() 63 | self.current_bp = None 64 | self.debugger = debugger 65 | self.event_flags = self.debugger.settings["events"] 66 | 67 | # Threading lock ensures that we don't have other traced threads 68 | # running when we enter the debugger. Later we may want to have 69 | # a switch to control. 70 | self.debugger_lock = threading.Lock() 71 | 72 | self.filename_cache = {} 73 | 74 | # Initially the event parameter of the event hook. 75 | # We can however modify it, such as for breakpoints 76 | self.event = None 77 | 78 | # Is debugged program currently under execution? 79 | self.execution_status = "Pre-execution" 80 | 81 | # main_dirname is the directory where the script resides. 82 | # Filenames in co_filename are often relative to this. 83 | self.main_dirname = os.curdir 84 | 85 | # # What event processor and processor options do we use? 86 | # self.processor = get_option("processor") 87 | # proc_opts = get_option("proc_opts") 88 | # if not self.processor: 89 | # self.processor = Mcmdproc.CommandProcessor(self, opts=proc_opts) 90 | # elif self.processor == "bullwinkle": 91 | # self.processor = Mbwproc.BWProcessor(self, opts=proc_opts) 92 | # pass 93 | # What events are considered in stepping. Note: 'None' means *all*. 94 | 95 | self.step_events = None 96 | # How many line events to skip before entering event processor? 97 | # If stop_level is None all breaks are counted otherwise just 98 | # those which less than or equal to stop_level. 99 | self.step_ignore = get_option("step_ignore") 100 | 101 | # If stop_level is not None, then we are next'ing or 102 | # finish'ing and will ignore frames greater than stop_level. 103 | # We also will cache the last frame and thread number encountered 104 | # so we don't have to compute the current level all the time. 105 | self.last_frame = None 106 | self.last_level = 10000 107 | self.last_thread = None 108 | self.stop_level = None 109 | self.stop_on_finish = False 110 | 111 | self.last_lineno = None 112 | self.last_filename = None 113 | self.different_line = None 114 | 115 | # The reason we have stopped, e.g. 'breakpoint hit', 'next', 116 | # 'finish', 'step', or 'exception'. 117 | self.stop_reason = "" 118 | 119 | self.trace_processor = XPyPrintProcessor(self, debugger) 120 | 121 | # What routines (keyed by f_code) will we not trace into? 122 | self.ignore_filter = get_option("ignore_filter") 123 | 124 | self.search_path = sys.path # Source filename search path 125 | 126 | # When trace_hook_suspend is set True, we'll suspend 127 | # debugging. 128 | self.trace_hook_suspend = False 129 | 130 | self.until_condition = get_option("until_condition") 131 | 132 | return 133 | 134 | def add_ignore(self, *frames_or_fns): 135 | """Add `frame_or_fn' to the list of functions that are not to 136 | be debugged""" 137 | for frame_or_fn in frames_or_fns: 138 | rc = self.ignore_filter.add_include(frame_or_fn) 139 | pass 140 | return rc 141 | 142 | def canonic(self, filename): 143 | """ Turns `filename' into its canonic representation and returns this 144 | string. This allows a user to refer to a given file in one of several 145 | equivalent ways. 146 | 147 | Relative filenames need to be fully resolved, since the current working 148 | directory might change over the course of execution. 149 | 150 | If filename is enclosed in < ... >, then we assume it is 151 | one of the bogus internal Python names like which is seen 152 | for example when executing "exec cmd". 153 | """ 154 | 155 | if filename == "<" + filename[1:-1] + ">": 156 | return filename 157 | canonic = self.filename_cache.get(filename) 158 | if not canonic: 159 | lead_dir = filename.split(os.sep)[0] 160 | if lead_dir == os.curdir or lead_dir == os.pardir: 161 | # We may have invoked the program from a directory 162 | # other than where the program resides. filename is 163 | # relative to where the program resides. So make sure 164 | # to use that. 165 | canonic = os.path.abspath(os.path.join(self.main_dirname, filename)) 166 | else: 167 | canonic = os.path.abspath(filename) 168 | pass 169 | if not os.path.isfile(canonic): 170 | canonic = search_file(filename, self.search_path, self.main_dirname) 171 | # FIXME: is this is right for utter failure? 172 | if not canonic: 173 | canonic = filename 174 | pass 175 | canonic = os.path.realpath(os.path.normcase(canonic)) 176 | self.filename_cache[filename] = canonic 177 | return canonic 178 | 179 | def canonic_filename(self, frame): 180 | """Picks out the file name from `frame' and returns its 181 | canonic() value, a string.""" 182 | return self.canonic(frame.f_code.co_filename) 183 | 184 | def filename(self, filename=None): 185 | """Return filename or the basename of that depending on the 186 | basename setting""" 187 | if filename is None: 188 | if self.debugger.mainpyfile: 189 | filename = self.debugger.mainpyfile 190 | else: 191 | return None 192 | if self.debugger.settings["basename"]: 193 | return os.path.basename(filename) 194 | return filename 195 | 196 | def is_running(self): 197 | return "Running" == self.execution_status 198 | 199 | # def is_started(self): 200 | # """Return True if debugging is in progress.""" 201 | # return ( 202 | # tracer.is_started() 203 | # and not self.trace_hook_suspend 204 | # and tracer.find_hook(self.trace_dispatch) 205 | # ) 206 | 207 | def remove_ignore(self, frame_or_fn): 208 | """Remove `frame_or_fn' to the list of functions that are not to 209 | be debugged""" 210 | return self.ignore_filter.remove_include(frame_or_fn) 211 | 212 | # def start(self, opts=None): 213 | # """ We've already created a debugger object, but here we start 214 | # debugging in earnest. We can also turn off debugging (but have 215 | # the hooks suspended or not) using 'stop'. 216 | 217 | # 'opts' is a hash of every known value you might want to set when 218 | # starting the debugger. See START_OPTS of module default. 219 | # """ 220 | 221 | # # The below is our fancy equivalent of: 222 | # # sys.settrace(self._trace_dispatch) 223 | # try: 224 | # self.trace_hook_suspend = True 225 | # get_option = lambda key: option_set(opts, key, default.START_OPTS) 226 | 227 | # add_hook_opts = get_option("add_hook_opts") 228 | 229 | # # Has tracer been started? 230 | # if not tracer.is_started() or get_option("force"): 231 | # # FIXME: should filter out opts not for tracer 232 | 233 | # tracer_start_opts = default.START_OPTS.copy() 234 | # if opts: 235 | # tracer_start_opts.update(opts.get("tracer_start", {})) 236 | # tracer_start_opts["trace_func"] = self.trace_dispatch 237 | # tracer_start_opts["add_hook_opts"] = add_hook_opts 238 | # tracer.start(tracer_start_opts) 239 | # elif not tracer.find_hook(self.trace_dispatch): 240 | # tracer.add_hook(self.trace_dispatch, add_hook_opts) 241 | # pass 242 | # self.execution_status = "Running" 243 | # finally: 244 | # self.trace_hook_suspend = False 245 | # return 246 | 247 | def stop(self, options=None): 248 | """Remove trace hooks. In other words, our version of: 249 | sys.settrace(None) 250 | """ 251 | # Nothing needs to be done 252 | return 253 | 254 | def is_break_here(self, frame): 255 | filename = self.canonic(frame.f_code.co_filename) 256 | if "call" == self.event: 257 | find_name = frame.f_code.co_name 258 | # Could check code object or decide not to 259 | # The below could be done as a list comprehension, but 260 | # I'm feeling in Fortran mood right now. 261 | for fn in self.bpmgr.fnlist: 262 | if fn.__name__ == find_name: 263 | self.current_bp = bp = self.bpmgr.fnlist[fn][0] 264 | if bp.temporary: 265 | msg = "temporary " 266 | self.bpmgr.delete_breakpoint(bp) 267 | else: 268 | msg = "" 269 | pass 270 | self.stop_reason = "at %scall breakpoint %d" % (msg, bp.number) 271 | self.event = "brkpt" 272 | return True 273 | pass 274 | pass 275 | if (filename, frame.f_lineno) in list(self.bpmgr.bplist.keys()): 276 | (bp, clear_bp) = self.bpmgr.find_bp(filename, frame.f_lineno, frame) 277 | if bp: 278 | self.current_bp = bp 279 | if clear_bp and bp.temporary: 280 | msg = "temporary " 281 | self.bpmgr.delete_breakpoint(bp) 282 | else: 283 | msg = "" 284 | pass 285 | self.stop_reason = "at %sline breakpoint %d" % (msg, bp.number) 286 | self.event = "brkpt" 287 | return True 288 | else: 289 | return False 290 | pass 291 | return False 292 | 293 | def matches_condition(self, frame): 294 | # Conditional bp. 295 | # Ignore count applies only to those bpt hits where the 296 | # condition evaluates to true. 297 | try: 298 | val = eval(self.until_condition, frame.f_globals, frame.f_locals) 299 | except: 300 | # if eval fails, most conservative thing is to 301 | # stop on breakpoint regardless of ignore count. 302 | # Don't delete temporary, as another hint to user. 303 | return False 304 | return val 305 | 306 | def is_stop_here(self, frame, event): 307 | """ Does the magic to determine if we stop here and run a 308 | command processor or not. If so, return True and set 309 | self.stop_reason; if not, return False. 310 | 311 | Determining factors can be whether a breakpoint was 312 | encountered, whether we are stepping, next'ing, finish'ing, 313 | and, if so, whether there is an ignore counter. 314 | """ 315 | 316 | # Add an generic event filter here? 317 | # FIXME TODO: Check for 318 | # - thread switching (under set option) 319 | 320 | # Check for "next" and "finish" stopping via stop_level 321 | 322 | # Do we want a different line and if so, 323 | # do we have one? 324 | lineno = frame.f_lineno 325 | filename = frame.f_code.co_filename 326 | if self.different_line and event == "line": 327 | if self.last_lineno == lineno and self.last_filename == filename: 328 | return False 329 | pass 330 | self.last_lineno = lineno 331 | self.last_filename = filename 332 | 333 | # if self.stop_level is not None: 334 | # if frame != self.last_frame: 335 | # # Recompute stack_depth 336 | # self.last_level = Mstack.count_frames(frame) 337 | # self.last_frame = frame 338 | # pass 339 | # if self.last_level > self.stop_level: 340 | # return False 341 | # elif ( 342 | # self.last_level == self.stop_level 343 | # and self.stop_on_finish 344 | # and event in ["return", "c_return"] 345 | # ): 346 | # self.stop_level = None 347 | # self.stop_reason = "in return for 'finish' command" 348 | # return True 349 | # pass 350 | 351 | # Check for stepping 352 | if self._is_step_next_stop(event): 353 | self.stop_reason = "at a stepping statement" 354 | return True 355 | 356 | return False 357 | 358 | def _is_step_next_stop(self, event): 359 | if self.step_events and (PyVMEVENT_FLAG_BITS[event] & self.trace_flags): 360 | return False 361 | if self.step_ignore == 0: 362 | return True 363 | elif self.step_ignore > 0: 364 | self.step_ignore -= 1 365 | pass 366 | return False 367 | 368 | # def set_next(self, frame, step_ignore=0, step_events=None): 369 | # "Sets to stop on the next event that happens in frame 'frame'." 370 | # self.step_events = None # Consider all events 371 | # self.stop_level = Mstack.count_frames(frame) 372 | # self.last_level = self.stop_level 373 | # self.last_frame = frame 374 | # self.stop_on_finish = False 375 | # self.step_ignore = step_ignore 376 | # return 377 | 378 | def trace_dispatch( 379 | self, 380 | event: str, 381 | offset: int, 382 | byte_name: str, 383 | byte_code: int, 384 | line_number: int, 385 | intArg: Optional[int], 386 | event_arg: Any, 387 | vm: Any, 388 | prompt="trepan-xpy", 389 | ): 390 | """A trace event occurred. Filter or pass the information to a 391 | specialized event processor. Note that there may be more filtering 392 | that goes on in the command processor (e.g. to force a 393 | different line). We could put that here, but since that seems 394 | processor-specific I think it best to distribute the checks.""" 395 | 396 | frame = vm.frame 397 | 398 | # For now we only allow one instance in a process 399 | # In Python 2.6 and beyond one can use "with threading.Lock():" 400 | try: 401 | self.debugger_lock.acquire() 402 | 403 | if self.trace_hook_suspend: 404 | return None 405 | 406 | self.event = event 407 | # FIXME: Understand what's going on here better. 408 | # When None gets returned, the frame's f_trace seems to get set 409 | # to None. Somehow this is changing other frames when get passed 410 | # to this routine which also have their f_trace set to None. 411 | # This will disallow a command like "jump" from working properly, 412 | # which will give a cryptic the message on setting f_lineno: 413 | # f_lineno can only be set by a trace function 414 | if self.ignore_filter and self.ignore_filter.is_excluded(frame): 415 | return self 416 | 417 | # if self.debugger.settings["trace"]: 418 | # print_event_set = self.debugger.settings["printset"] 419 | # if event in print_event_set: 420 | # self.trace_processor.event_processor( 421 | # event, 422 | # offset, 423 | # byte_name, 424 | # byte_code, 425 | # line_number, 426 | # intArg, 427 | # event_arg, 428 | # vm 429 | # ) 430 | # pass 431 | # pass 432 | 433 | if self.until_condition: 434 | if not self.matches_condition(frame): 435 | return True 436 | pass 437 | 438 | # 439 | if not (PyVMEVENT_FLAG_BITS[event] & self.event_flags): 440 | return True 441 | 442 | # I think we *have* to run is_stop_here() before 443 | # is_break_here() because is_stop_here() sets various 444 | # stepping counts. But it might be more desirable from the 445 | # user's standpoint to test for breaks before steps. In 446 | # this case we will need to factor out the counting 447 | # updates. 448 | if ( 449 | not frame 450 | or self.is_stop_here(frame, event) 451 | or self.is_break_here(frame) 452 | ): 453 | # Run the event hook 454 | return self.processor.event_hook( 455 | event, 456 | offset, 457 | byte_name, 458 | byte_code, 459 | line_number, 460 | intArg, 461 | event_arg, 462 | vm, 463 | ) 464 | return self 465 | finally: 466 | try: 467 | self.debugger_lock.release() 468 | except: 469 | pass 470 | pass 471 | pass 472 | 473 | pass 474 | 475 | 476 | # Demo it 477 | if __name__ == "__main__": 478 | 479 | class MockProcessor: 480 | pass 481 | 482 | opts = {"processor": MockProcessor()} 483 | dc = TrepanXPyCore(None, opts=opts) 484 | dc.step_ignore = 1 485 | print("dc._is_step_next_stop():", dc._is_step_next_stop("line")) 486 | print("dc._is_step_next_stop():", dc._is_step_next_stop("line")) 487 | print("dc.step_ignore:", dc.step_ignore) 488 | # print("dc.is_started:", dc.is_started()) 489 | print(dc.canonic("")) 490 | print(dc.canonic(__file__)) 491 | pass 492 | -------------------------------------------------------------------------------- /trepanxpy/debugger.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (C) 2020 Rocky Bernstein 3 | 4 | from typing import List, Dict, Any 5 | import os 6 | import sys 7 | from xpython.execfile import run_python_file, run_python_string, NoSourceError 8 | 9 | # Default settings used here 10 | from trepanxpy.debugger_defaults import DEBUGGER_SETTINGS 11 | from trepan.interfaces.user import UserInterface 12 | from trepan.misc import wrapped_lines 13 | from trepan.exception import DebuggerQuit, DebuggerRestart 14 | 15 | from trepanxpy.core import TrepanXPyCore 16 | from trepanxpy.fmt import format_instruction_with_highlight 17 | from trepanxpy.processor.cmd import XPyCommandProcessor 18 | from trepanxpy.processor.trace import XPyPrintProcessor 19 | 20 | 21 | class TrepanXPy(object): 22 | def __init__( 23 | self, string_or_path: str, is_file: bool, trace_only: bool, args: List[str] 24 | ): 25 | """Create a debugger object. But depending on the value of 26 | key 'start' inside hash 'opts', we may or may not initially 27 | start debugging. 28 | 29 | See also TrepanXPy.start and TrepanXPy.stop. 30 | """ 31 | 32 | def instruction_fmt_func(frame, opc, byte_name, int_arg, arguments, offset, line_number, 33 | extra_debug, vm=None): 34 | return format_instruction_with_highlight( 35 | frame=frame, 36 | opc=opc, 37 | byte_name=byte_name, 38 | int_arg = int_arg, 39 | arguments = arguments, 40 | offset = offset, 41 | line_number = line_number, 42 | extra_debug = extra_debug, 43 | settings=self.settings, 44 | vm = vm, 45 | show_line = True 46 | ) 47 | 48 | self.mainpyfile = None 49 | self.thread = None 50 | 51 | completer = lambda text, state: self.complete(text, state) 52 | interface_opts = { 53 | "complete": completer, 54 | "debugger_name": "trepan-xpy", 55 | } 56 | interface = UserInterface(opts=interface_opts) 57 | self.intf = [interface] 58 | 59 | # main_dirname is the directory where the script resides. 60 | # Filenames in co_filename are often relative to this. 61 | self.main_dirname = os.curdir 62 | 63 | self.filename_cache: Dict[str, Any] = {} 64 | self.settings = DEBUGGER_SETTINGS 65 | self.core = TrepanXPyCore(self, {}) 66 | if trace_only: 67 | processor = XPyPrintProcessor(self.core) 68 | else: 69 | processor = XPyCommandProcessor(self.core) 70 | self.core.processor = processor 71 | self.callback_hook = self.core.trace_dispatch 72 | 73 | # Save information for restarting 74 | self.program_sys_argv = list(sys.argv) 75 | self.orig_sys_argv = list(sys.argv) 76 | 77 | if is_file: 78 | mainpyfile = self.core.canonic(string_or_path) 79 | run_fn = run_python_file 80 | else: 81 | mainpyfile = string_or_path 82 | run_fn = run_python_string 83 | 84 | while True: 85 | print("Running x-python %s with %s" % (string_or_path, args)) 86 | try: 87 | run_fn( 88 | string_or_path, 89 | args, 90 | callback=self.callback_hook, 91 | format_instruction=instruction_fmt_func, 92 | ) 93 | except DebuggerQuit: 94 | break 95 | except DebuggerRestart: 96 | self.core.execution_status = "Restart requested" 97 | if self.program_sys_argv: 98 | sys.argv = list(self.program_sys_argv) 99 | part1 = "Restarting %s with arguments:" % self.core.filename( 100 | mainpyfile 101 | ) 102 | args = " ".join(self.program_sys_argv[1:]) 103 | self.intf[-1].msg( 104 | wrapped_lines(part1, args, self.settings["width"]) 105 | ) 106 | else: 107 | break 108 | except (FileNotFoundError, NoSourceError) as e: 109 | self.intf[-1].msg(str(e)) 110 | sys.exit(1) 111 | except SystemExit: 112 | # In most cases SystemExit does not warrant a post-mortem session. 113 | break 114 | else: 115 | if trace_only: 116 | break 117 | msg = "The program finished - press enter to restart; anything else terminates. ? " 118 | response = input(msg) 119 | if response != "": 120 | break 121 | pass 122 | 123 | def restart_argv(self): 124 | """Return an array that would be execv-ed to restart the program""" 125 | return self.orig_sys_argv or self.program_sys_argv 126 | -------------------------------------------------------------------------------- /trepanxpy/debugger_defaults.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (C) 2020-2021, 2024 Rocky Bernstein 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | """ A place for the debugger default settings """ 17 | 18 | from trepan.lib.default import default_style, is_dark_bg, width 19 | from xpython.vmtrace import PyVMEVENT_ALL 20 | 21 | # Below are the default debugger settings. The debugger object version 22 | # of this may change. A setting is something a user may want to 23 | # change, in contrast to settings that the debugger decides to set in 24 | # the course of operation. For example, the maximum print width 25 | # (width) is user settable, whereas whether the debugging program is 26 | # running (execution_status), or traceback frame isn't user settable 27 | # so it doesn't appear below. Some settings like the current frame 28 | # (curframe) or the number of steps to skip before entering a command 29 | # processor (step_ignore) are shared between the two. They also don't 30 | # generally appear as settings. 31 | 32 | # fmt: off 33 | DEBUGGER_SETTINGS = { 34 | # Format style to use in showing disssembly 35 | "asmfmt": "extended", 36 | 37 | # Eval as Python the unrecognized debugger commands? 38 | "autoeval": True, 39 | 40 | # Run 'list' command every time we enter the debugger? 41 | "autolist": False, 42 | 43 | # Enter IPython every time we enter the debugger? 44 | # Note: only relevant if we have ipython installed. This takes 45 | # precedence over autopython. 46 | "autoipython": False, 47 | 48 | # Run 'info pc' command every time we enter the debugger? 49 | "autopc": True, 50 | 51 | # Enter Python every time we enter the debugger? 52 | "autopython": False, 53 | 54 | # Run 'info stack' command every time we enter the debugger? 55 | "autostack": True, 56 | 57 | # Show basename only on filename output? 58 | # This opiton is useful in integration testing and 59 | # possibly to prepare example output for publication 60 | "basename": False, 61 | 62 | # Set echoing lines read from debugger? 63 | "cmdtrace": False, 64 | 65 | # confirm potentially dangerous operations? 66 | "confirm": True, 67 | 68 | # Debug macros? 69 | "debugmacro": False, 70 | 71 | # When True, consecutive stops must be on different 72 | # file/line positions. 73 | "different": True, 74 | 75 | # events is a set of events to process line-, call-, or return-like 76 | # tracing. See tracer.ALL_EVENT_NAMES and ALL_EVENTS 77 | # Note this is independent of printset which just prints the event. 78 | # This set controls entering the debugger command processor. 79 | "events": PyVMEVENT_ALL, 80 | 81 | # Use terminal highlight? Acceptable values are 82 | # 'plain' : no highlighting 83 | # 'dark' : terminal highlighting for a dark background 84 | # 'light' : terminal highlighting for a light background 85 | "highlight": is_dark_bg, 86 | 87 | # Where do we save the history? 88 | "histfile": None, 89 | 90 | # Save debugger history? 91 | "hist_save": True, 92 | 93 | # Show function calls/returns? 94 | "fntrace": False, 95 | 96 | # Number of lines to show by default in a 'list' command. 97 | "listsize": 10, 98 | 99 | # PyVM logger messages? 100 | "logtrace": False, 101 | 102 | # max length to show of parameter string 103 | "maxargstrsize": 100, 104 | 105 | # max length to in other strings 106 | "maxstring": 150, 107 | 108 | # If this is set True, debugger startup file, e.g. .trepanrc will 109 | # not be read/run. 110 | "nostartup": False, 111 | 112 | # Reread source file if we determine it has changed? 113 | "reload": False, 114 | 115 | # Skip instructions that make clases, functions, and closures? 116 | # (In the Python they are "class" and "def" statments) 117 | "skip": False, 118 | 119 | # print trace output? 120 | "step_ignore": 0, 121 | 122 | # Pygments style 123 | "style": default_style, 124 | 125 | # If value is None, use Python's defaults 126 | "tempdir": None, 127 | 128 | # print trace output? 129 | "trace": False, 130 | 131 | # The target maximum print length. Used for example in listing 132 | # arrays which are columnized. 133 | "width": width, 134 | } 135 | # fmt: on 136 | 137 | # Show it: 138 | if __name__ == "__main__": 139 | import pprint 140 | 141 | for val in ["DEBUGGER_SETTINGS", "START_OPTS", "STOP_OPTS"]: 142 | print(f"{val}:") 143 | print(pprint.pformat(eval(val))) 144 | print("-" * 10) 145 | pass 146 | pass 147 | -------------------------------------------------------------------------------- /trepanxpy/events.py: -------------------------------------------------------------------------------- 1 | ALL_EVENT_NAMES = ( 2 | "c_call", 3 | "c_exception", 4 | "c_return", 5 | "call", 6 | "exception", 7 | "line", 8 | "return", 9 | "instruction", 10 | "yield", 11 | ) 12 | 13 | # If you want short strings for the above event names 14 | EVENT2SHORT = { 15 | "c_call": "C>", 16 | "c_exception": "C!", 17 | "c_return": "C<", 18 | "call": "->", 19 | "exception": "!!", 20 | "line": "--", 21 | "instruction": "..", 22 | "return": "<-", 23 | "yield": "<>", 24 | "fatal": "XX", 25 | } 26 | 27 | ALL_EVENTS = frozenset(ALL_EVENT_NAMES) 28 | -------------------------------------------------------------------------------- /trepanxpy/fmt.py: -------------------------------------------------------------------------------- 1 | from os.path import basename 2 | from numbers import Number as NumberType 3 | from pygments.token import Number, Text 4 | 5 | from trepan.lib.format import ( 6 | Filename, 7 | Function, 8 | LineNumber, 9 | Name, 10 | Opcode, 11 | Operator, 12 | String, 13 | format_token, 14 | ) 15 | 16 | 17 | LINE_NUMBER_WIDTH = 4 18 | LINE_NUMBER_WIDTH_FMT = "%%-%dd" % LINE_NUMBER_WIDTH 19 | LINE_NUMBER_SPACES = " " * (LINE_NUMBER_WIDTH + len("L. ")) + "@" 20 | 21 | 22 | def format_instruction_with_highlight( 23 | frame, 24 | opc, 25 | byte_name, 26 | int_arg, 27 | arguments, 28 | offset, 29 | line_number, 30 | extra_debug, 31 | settings, 32 | show_line=True, 33 | vm=None, 34 | repr=repr 35 | ): 36 | """A version of x-python's format_instruction() with terminal highlighting""" 37 | 38 | style=settings.get("style", "none") 39 | code = frame.f_code if frame else None 40 | byteCode = opc.opmap.get(byte_name, 0) 41 | if isinstance(arguments, list) and arguments: 42 | arguments = arguments[0] 43 | argrepr = arguments 44 | 45 | fmt_type = Text 46 | 47 | if vm and byte_name in vm.byteop.stack_fmt: 48 | stack_args = vm.byteop.stack_fmt[byte_name](vm, int_arg, repr) 49 | else: 50 | stack_args = "" 51 | 52 | if hasattr(opc, "opcode_arg_fmt") and byte_name in opc.opcode_arg_fmt and int_arg is not None: 53 | argrepr = f"""["{opc.opcode_arg_fmt[byte_name](int_arg)}"] {int_arg}""" 54 | elif int_arg is None: 55 | argrepr = "" 56 | elif byteCode in opc.NAME_OPS | opc.FREE_OPS | opc.LOCAL_OPS: 57 | fmt_type = Name 58 | elif byteCode in opc.JREL_OPS | opc.JABS_OPS: 59 | fmt_type = Number 60 | argrepr = str(argrepr) 61 | elif byteCode in opc.COMPARE_OPS: 62 | fmt_type = Operator 63 | argrepr = opc.cmp_op[int_arg] 64 | elif byteCode == opc.LOAD_CONST: 65 | if isinstance(argrepr, str): 66 | fmt_type = String 67 | elif isinstance(argrepr, NumberType): 68 | fmt_type = Number 69 | argrepr = repr(argrepr) 70 | 71 | if line_number is None or not show_line: 72 | line_str = LINE_NUMBER_SPACES 73 | else: 74 | number_str = format_token( 75 | LineNumber, LINE_NUMBER_WIDTH_FMT % line_number, style=style 76 | ) 77 | line_str = "L. %s@" % number_str 78 | format_token(fmt_type, argrepr, style=style), 79 | 80 | mess = "%s%3d: %s%s %s" % ( 81 | line_str, 82 | offset, 83 | format_token(Opcode, byte_name, style=style), 84 | stack_args, 85 | format_token(fmt_type, str(argrepr), style=style), 86 | ) 87 | if extra_debug and frame: 88 | filename = basename(code.co_filename) if settings["basename"] else code.co_filename 89 | mess += " %s in %s:%s" % ( 90 | format_token(Function, code.co_name, style=style), 91 | format_token(Filename, filename, style=style), 92 | format_token(LineNumber, str(frame.f_lineno), style=style), 93 | ) 94 | return mess 95 | -------------------------------------------------------------------------------- /trepanxpy/processor/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Trepan-Debuggers/trepan-xpy/55959967216b7cc270488b96e90df7891ffaaddb/trepanxpy/processor/__init__.py -------------------------------------------------------------------------------- /trepanxpy/processor/cmd.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is stripped down from the one in trepan3k 3 | # 4 | # Copyright (C) 2008-2010, 2013-2020 Rocky Bernstein 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | import linecache, sys, re, inspect 19 | import importlib 20 | import pyficache 21 | import os.path as osp 22 | 23 | from typing import Any, Optional, Set 24 | 25 | # Note: the module name pre 3.2 is repr 26 | from reprlib import Repr 27 | 28 | from pygments.console import colorize 29 | 30 | import trepan.lib.bytecode as Mbytecode 31 | import trepan.lib.display as Mdisplay 32 | from trepan.misc import option_set 33 | from trepan.lib.thred import current_thread_name 34 | import trepan.processor.complete_rl as Mcomplete 35 | 36 | from trepan.processor.cmdproc import ( 37 | CommandProcessor, 38 | arg_split, 39 | resolve_name, 40 | print_location, 41 | ) 42 | 43 | from trepanxpy.events import EVENT2SHORT 44 | from trepanxpy.fmt import format_instruction_with_highlight 45 | 46 | 47 | warned_file_mismatches: Set[str] = set() 48 | 49 | 50 | def get_srcdir(): 51 | filename = osp.normcase(osp.dirname(osp.abspath(__file__))) 52 | return osp.realpath(filename) 53 | 54 | 55 | # Default settings for command processor method call 56 | DEFAULT_PROC_OPTS = { 57 | # A list of debugger initialization files to read on first command 58 | # loop entry. Often this something like [~/.config/trepan-xp/profile] which the 59 | # front-end sets. 60 | "initfile_list": [] 61 | } 62 | 63 | # FIXME: can't figure out how to delegate specific fns so we'll subclass instead. 64 | class XPyCommandProcessor(CommandProcessor): 65 | def __init__(self, core_obj, opts=None): 66 | super().__init__(core_obj, opts) 67 | self.core = core_obj 68 | self.debugger = core_obj.debugger 69 | 70 | self.continue_running = False # True if we should leave command loop 71 | self.event2short = dict(EVENT2SHORT) 72 | self.event2short["signal"] = "?!" 73 | self.event2short["brkpt"] = "xx" 74 | 75 | self.optional_modules = () 76 | 77 | # command argument string. Is like current_command, but the part 78 | # after cmd_name has been removed. 79 | self.cmd_argstr = "" 80 | 81 | # command name before alias or macro resolution 82 | self.cmd_name = "" 83 | self.cmd_queue = [] # Queued debugger commands 84 | self.completer = lambda text, state: Mcomplete.completer(self, text, state) 85 | self.current_command = "" # Current command getting run 86 | self.debug_nest = 1 87 | self.display_mgr = Mdisplay.DisplayMgr() 88 | self.intf = core_obj.debugger.intf 89 | self.last_command = None # Initially a no-op 90 | self.precmd_hooks = [] 91 | 92 | # FIXME: can we adjust this to also show the instruction? 93 | self.location = lambda: self 94 | 95 | self.preloop_hooks = [] 96 | self.postcmd_hooks = [] 97 | 98 | # Note: prompt_str's value set below isn't used. It is 99 | # computed dynamically. The value is suggestive of what it 100 | # looks like. 101 | self.prompt_str = "(trepan-xpy) " 102 | 103 | # Stop only if line/file is different from last time 104 | self.different_line = None 105 | 106 | # These values updated on entry. Set initial values. 107 | self.curframe = None 108 | self.event = None 109 | self.event_arg = None 110 | self.frame = None 111 | self.list_lineno = 0 # last list number used in "list" 112 | self.list_offset = -1 # last list number used in "disassemble" 113 | self.list_obj = None 114 | self.list_filename = None # last filename used in list 115 | self.list_orig_lineno = 0 # line number of frame or exception on setup 116 | self.list_filename = None # filename of frame or exception on setup 117 | 118 | self.macros = {} # Debugger Macros 119 | 120 | # Create a custom safe Repr instance and increase its maxstring. 121 | # The default of 30 truncates error messages too easily. 122 | self._repr = Repr() 123 | self._repr.maxstring = 100 124 | self._repr.maxother = 60 125 | self._repr.maxset = 10 126 | self._repr.maxfrozen = 10 127 | self._repr.array = 10 128 | self.stack = [] 129 | self.thread_name = None 130 | self.frame_thread_name = None 131 | 132 | get_option = lambda key: option_set(opts, key, DEFAULT_PROC_OPTS) 133 | initfile_list = get_option("initfile_list") 134 | for init_cmdfile in initfile_list: 135 | self.queue_startfile(init_cmdfile) 136 | 137 | # FIXME: This doesn't work 138 | # # Delegate functions here: 139 | # self.cmdproc = CommandProcessor(self) 140 | # for method in ( 141 | # "_saferepr", 142 | # "add_preloop_hook", 143 | # "defaultFile", 144 | # "eval", 145 | # "exec_line", 146 | # "forget", 147 | # "get_an_int", 148 | # "get_int_noerr", 149 | # "getval", 150 | # "ok_for_running", 151 | # "process_commands", 152 | # "queue_startfile", 153 | # "remove_preloop_hook", 154 | # "setup", 155 | # "undefined_cmd", 156 | # "read_history_file", 157 | # "write_history_file", 158 | # ): 159 | # setattr(self, method, getattr(cmdproc, method)) 160 | 161 | # Remove trepan3k commands which aren't valid here, and those specific to trepan-xpy 162 | remove_commands = ( 163 | "continue", 164 | "finish", 165 | "next", 166 | "quit", 167 | "set", 168 | "step", 169 | ) 170 | new_instances = [] 171 | for cmd in self.cmd_instances: 172 | if cmd.name in remove_commands: 173 | del self.commands[cmd.name] 174 | else: 175 | new_instances.append(cmd) 176 | pass 177 | pass 178 | self.cmd_instances = new_instances 179 | 180 | new_commands = self._update_commands() 181 | for new_command in new_commands: 182 | self.commands[new_command.name] = new_command 183 | self.cmd_instances += new_commands 184 | self._populate_cmd_lists() 185 | 186 | if self.debugger.settings["autopc"]: 187 | self.commands["set"].run(["set", "autopc"]) 188 | return 189 | 190 | def update_commands_easy_install(self, Mcommand): 191 | """ 192 | Add files in filesystem to self.commands. 193 | If running from source or from an easy_install'd package, this is used. 194 | """ 195 | cmd_instances = [] 196 | 197 | for mod_name in Mcommand.__modules__: 198 | if mod_name in ("info_sub", "set_sub", "show_sub",): 199 | pass 200 | import_name = "%s.%s" % (Mcommand.__name__, mod_name) 201 | try: 202 | command_mod = importlib.import_module(import_name) 203 | except: 204 | if mod_name not in self.optional_modules: 205 | print("Error importing %s: %s" % (mod_name, sys.exc_info()[0])) 206 | pass 207 | continue 208 | 209 | classnames = [ 210 | tup[0] 211 | for tup in inspect.getmembers(command_mod, inspect.isclass) 212 | if ( 213 | tup[0] != "DebuggerCommand" 214 | and not tup[0].startswith("Trepan3k") 215 | and tup[0].endswith("Command") 216 | ) 217 | ] 218 | for classname in classnames: 219 | if False: 220 | instance = getattr(command_mod, classname)(self) 221 | cmd_instances.append(instance) 222 | else: 223 | try: 224 | instance = getattr(command_mod, classname)(self) 225 | cmd_instances.append(instance) 226 | except: 227 | print( 228 | "Error loading %s from %s: %s" 229 | % (classname, mod_name, sys.exc_info()[0]) 230 | ) 231 | pass 232 | pass 233 | pass 234 | pass 235 | return cmd_instances 236 | 237 | def set_prompt(self, prompt="trepan-xpy"): 238 | if self.thread_name and self.thread_name != "MainThread": 239 | prompt += ":" + self.thread_name 240 | pass 241 | self.prompt_str = "%s%s%s" % ( 242 | "(" * self.debug_nest, 243 | prompt, 244 | ")" * self.debug_nest, 245 | ) 246 | highlight = self.debugger.settings["highlight"] 247 | if highlight and highlight in ("light", "dark"): 248 | self.prompt_str = colorize("underline", self.prompt_str) 249 | self.prompt_str += " " 250 | 251 | def event_hook( 252 | self, 253 | event: str, 254 | offset: int, 255 | byteName: str, 256 | byteCode: int, 257 | line_number: int, 258 | intArg: Optional[int], 259 | event_arg: Any, 260 | vm: Any, 261 | prompt="trepan-xpy", 262 | ): 263 | "command event processor: reading a commands do something with them." 264 | 265 | def frame_setup(frame): 266 | filename = frame.f_code.co_filename 267 | lineno = frame.f_lineno 268 | line = linecache.getline(filename, lineno, frame.f_globals) 269 | if not line: 270 | opts = { 271 | "output": "plain", 272 | "reload_on_change": self.settings("reload"), 273 | "strip_nl": False, 274 | } 275 | m = re.search("^", filename) 276 | if m and m.group(1): 277 | filename = pyficache.unmap_file(m.group(1)) 278 | line = pyficache.getline(filename, lineno, opts) 279 | self.current_source_text = line 280 | return line, filename 281 | 282 | self.vm = vm 283 | self.frame = vm.frame 284 | self.event = event 285 | self.event_arg = event_arg 286 | 287 | # In order to follow Python's sys.settrace()'s convention: 288 | # returning "None" turns off tracing for the scope. 289 | # However we do not need to return a reference to ourself, 290 | # a callable (this may be allowed in the future though). 291 | # Instead for now a string status is returned 292 | # * "skip" for skip next instruction, and 293 | # * "return" for immediate return 294 | # * "finish" for "step out" 295 | 296 | self.return_status = True 297 | 298 | if event == "fatal": 299 | self.core.execution_status = "Terminated" 300 | # One last hurrah! 301 | 302 | tb = vm.last_traceback 303 | if tb: 304 | frame_setup(tb.tb_frame) 305 | self.vm.frames = [] 306 | while tb: 307 | self.vm.frames.insert(0, tb.tb_frame) 308 | tb = tb.tb_next 309 | self.curframe = self.frame = self.vm.frames[0] 310 | self.setup() 311 | self.curindex = len(vm.frames) - 1 312 | print_location(self) 313 | 314 | self.set_prompt("trepan-xpy:pm") 315 | self.process_commands() 316 | return None 317 | 318 | if self.vm.frame: 319 | self.core.execution_status = "Running" 320 | else: 321 | self.core.execution_status = "Terminated" 322 | return 323 | 324 | line, filename = frame_setup(self.frame) 325 | if self.settings("skip"): 326 | # Note that in contrast to skipping intructions 327 | # when return_status is set to "skip", here 328 | # we are execution the instruction but just skipping 329 | # any handling this instruction the debugger. 330 | if Mbytecode.is_def_stmt(line, self.frame): 331 | return self 332 | if Mbytecode.is_class_def(line, self.frame): 333 | return 334 | pass 335 | self.thread_name = current_thread_name() 336 | self.frame_thread_name = self.thread_name 337 | 338 | self.setup() 339 | print_location(self) 340 | if offset >= 0 and event not in ('call', 'return'): 341 | self.msg( 342 | "%s" 343 | % format_instruction_with_highlight( 344 | vm.frame, 345 | vm.opc, 346 | byteName, 347 | intArg, 348 | event_arg, 349 | offset, 350 | line_number, 351 | extra_debug=False, 352 | settings=self.debugger.settings, 353 | show_line=False, # We show the line number in our location reporting 354 | vm=self.vm, 355 | repr=self._repr.repr 356 | ) 357 | ) 358 | 359 | self.set_prompt(prompt) 360 | self.process_commands() 361 | if filename == "": 362 | pyficache.remove_remap_file("") 363 | return self.return_status 364 | 365 | def _update_commands(self): 366 | """ Create an instance of each of the debugger 367 | commands. Commands are found by importing files in the 368 | directory 'command'. Some files are excluded via an array set 369 | in __init__. For each of the remaining files, we import them 370 | and scan for class names inside those files and for each class 371 | name, we will create an instance of that class. The set of 372 | DebuggerCommand class instances form set of possible debugger 373 | commands.""" 374 | from trepanxpy.processor import command as Mcommand 375 | 376 | if hasattr(Mcommand, "__modules__"): 377 | # This is also used when installing from source 378 | return self.update_commands_easy_install(Mcommand) 379 | else: 380 | return self.populate_commands_pip(Mcommand, "trepanxpy") 381 | 382 | def setup(self): 383 | """Initialization done before entering the debugger-command 384 | loop. In particular we set up the call stack used for local 385 | variable lookup and frame/up/down commands. 386 | 387 | We return True if we should NOT enter the debugger-command 388 | loop.""" 389 | self.forget() 390 | self.curindex = 0 391 | if self.vm.frames: 392 | stack = self.vm.frames 393 | if stack == []: 394 | # FIXME: Just starting up - should there be an event for this 395 | # Or do we need to do this for all call events? 396 | stack = [self.frame] 397 | pass 398 | self.stack = [(frame, frame.line_number()) for frame in reversed(stack)] 399 | self.curframe = self.frame 400 | self.thread_name = "MainThread" 401 | else: 402 | self.stack = self.curframe = self.botframe = None 403 | pass 404 | if self.curframe: 405 | self.list_lineno = ( 406 | max( 407 | 1, 408 | self.frame.line_number() 409 | - int(self.settings("listsize") / 2), 410 | ) 411 | - 1 412 | ) 413 | self.list_offset = self.curframe.f_lasti 414 | self.list_filename = self.curframe.f_code.co_filename 415 | self.list_object = self.curframe 416 | else: 417 | self.list_object = None 418 | pass 419 | # if self.execRcLines()==1: return True 420 | 421 | # FIXME: do we want to save self.list_lineno a second place 422 | # so that we can do 'list .' and go back to the first place we listed? 423 | return False 424 | 425 | 426 | # Demo it 427 | if __name__ == "__main__": 428 | from trepan.processor.command import mock as Mmock 429 | 430 | d = Mmock.MockDebugger() 431 | cmdproc = CommandProcessor(d.core) 432 | print("commands:") 433 | commands = list(cmdproc.commands.keys()) 434 | commands.sort() 435 | print(commands) 436 | print("aliases:") 437 | aliases = list(cmdproc.aliases.keys()) 438 | aliases.sort() 439 | print(aliases) 440 | print(resolve_name(cmdproc, "quit")) 441 | print(resolve_name(cmdproc, "q")) 442 | print(resolve_name(cmdproc, "info")) 443 | print(resolve_name(cmdproc, "i")) 444 | # print '-' * 10 445 | # print_source_line(sys.stdout.write, 100, 'source_line_test.py') 446 | # print '-' * 10 447 | cmdproc.frame = sys._getframe() 448 | cmdproc.setup() 449 | print() 450 | print("-" * 10) 451 | cmdproc.location() 452 | print("-" * 10) 453 | print(cmdproc.eval("1+2")) 454 | print(cmdproc.eval("len(aliases)")) 455 | import pprint 456 | 457 | print(pprint.pformat(cmdproc.category)) 458 | print(arg_split("Now is the time")) 459 | print(arg_split("Now is the time ;;")) 460 | print(arg_split("Now is 'the time'")) 461 | print(arg_split("Now is the time ;; for all good men")) 462 | print(arg_split("Now is the time ';;' for all good men")) 463 | 464 | print(cmdproc.commands) 465 | fn = cmdproc.commands["quit"] 466 | 467 | print("Removing non-existing quit hook: %s" % cmdproc.remove_preloop_hook(fn)) 468 | cmdproc.add_preloop_hook(fn) 469 | print(cmdproc.preloop_hooks) 470 | print("Removed existing quit hook: %s" % cmdproc.remove_preloop_hook(fn)) 471 | pass 472 | -------------------------------------------------------------------------------- /trepanxpy/processor/command/__init__.py: -------------------------------------------------------------------------------- 1 | # This program is free software: you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation, either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | # """ Copyright (C) 2008-2009, 2013-2015, 2018 Rocky Bernstein """ 14 | 15 | import glob, os 16 | 17 | # FIXME: Is it really helpful to "privatize" variable names below? 18 | # The below names are not part of the standard pre-defined names like 19 | # __name__ or __file__ are. 20 | 21 | # Get the name of our directory. 22 | __command_dir__ = os.path.dirname(__file__) 23 | 24 | # A glob pattern that will get all *.py files but not __init__.py 25 | __py_files__ = glob.glob(os.path.join(__command_dir__, '[a-z]*.py')) 26 | 27 | # Take the basename of the filename and drop off '.py'. That minus the 28 | # files in exclude_files and that becomes the list of modules that 29 | # commands.py will use to import 30 | exclude_files = ['mock.py'] 31 | __modules__ = [ os.path.basename(filename[0:-3]) for 32 | filename in __py_files__ 33 | if os.path.basename(filename) not in exclude_files] 34 | __all__ = __modules__ + exclude_files 35 | -------------------------------------------------------------------------------- /trepanxpy/processor/command/continue.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (C) 2009, 2013, 2015, 2017, 2020 Rocky Bernstein 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | 17 | from trepan.processor.command.base_cmd import DebuggerCommand 18 | from trepan.processor.cmdbreak import parse_break_cmd, set_break 19 | 20 | from xpython.vmtrace import ( 21 | PyVMEVENT_ALL, 22 | PyVMEVENT_CALL, 23 | PyVMEVENT_FATAL, 24 | PyVMEVENT_INSTRUCTION, 25 | PyVMEVENT_LINE, 26 | ) 27 | 28 | 29 | class ContinueCommand(DebuggerCommand): 30 | """**continue** 31 | 32 | Leave the debugger read-eval print loop and continue 33 | execution. Subsequent entry to the debugger however may occur via 34 | breakpoints or explicit calls, or exceptions. 35 | 36 | Examples: 37 | --------- 38 | 39 | continue # Continue execution 40 | continue 5 # Continue with a one-time breakpoint at line 5 41 | continue basename # Go to os.path.basename if we have basename imported 42 | continue /usr/lib/python3.8/posixpath.py:110 # Possibly the same as 43 | # the above using file 44 | # and line number 45 | 46 | See also: 47 | --------- 48 | 49 | `step` `jump`, `next`, `finish` and `help syntax location` 50 | """ 51 | 52 | aliases = ("c", "continue!") 53 | execution_set = ["Running"] 54 | short_help = "Continue execution of debugged program" 55 | 56 | DebuggerCommand.setup(locals(), category="running", max_args=1, need_stack=True) 57 | 58 | def run(self, args): 59 | proc = self.proc 60 | if len(args) > 1: 61 | # FIXME: DRY this code. Better is to hook into tbreak. 62 | func, filename, lineno, condition = parse_break_cmd(proc, args) 63 | 64 | if not set_break( 65 | self, func, filename, lineno, condition, True, args 66 | ): 67 | return False 68 | elif args[0][-1] == "!": 69 | proc.vm.frame.event_flags = proc.vm.event_flags = 0 # ignore all events 70 | else: 71 | # Until we hook in breakpoints into the debugger proper, we'll 72 | # treat continue like step in the VM interpreter but not 73 | # from our filtering process 74 | proc.vm.frame.event_flags = PyVMEVENT_ALL 75 | proc.core.event_flags = ( 76 | PyVMEVENT_LINE | PyVMEVENT_INSTRUCTION | 77 | PyVMEVENT_CALL | PyVMEVENT_FATAL 78 | ) 79 | 80 | self.core.step_ignore = -1 81 | self.proc.continue_running = True # Break out of command read loop 82 | self.return_status = "continue" # Tell interpreter to continue running 83 | 84 | pass 85 | 86 | 87 | if __name__ == "__main__": 88 | import sys 89 | from trepan import debugger as Mdebugger 90 | 91 | d = Mdebugger.Trepan() 92 | cmd = ContinueCommand(d.core.processor) 93 | cmd.proc.frame = sys._getframe() 94 | cmd.proc.setup() 95 | 96 | for c in ( 97 | ["continue", "wrong", "number", "of", "args"], 98 | ["c", "5"], 99 | ["continue", "1+2"], 100 | ["c", "foo"], 101 | ): 102 | d.core.step_ignore = 0 103 | cmd.continue_running = False 104 | result = cmd.run(c) 105 | print("Run result: %s" % result) 106 | print( 107 | "step_ignore %d, continue_running: %s" 108 | % (d.core.step_ignore, cmd.continue_running,) 109 | ) 110 | pass 111 | pass 112 | -------------------------------------------------------------------------------- /trepanxpy/processor/command/finish.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (C) 2009, 2013, 2015, 2020 Rocky Bernstein 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | import os.path as osp 17 | 18 | from xpython.vmtrace import ( 19 | PyVMEVENT_LINE, 20 | PyVMEVENT_CALL, 21 | PyVMEVENT_RETURN, 22 | PyVMEVENT_EXCEPTION, 23 | PyVMEVENT_YIELD 24 | ) 25 | 26 | from trepan.processor.command.base_cmd import DebuggerCommand 27 | 28 | # Our local modules 29 | 30 | class FinishCommand(DebuggerCommand): 31 | """**finish** [*level*] 32 | 33 | Continue execution until leaving the current function. When *level* is 34 | specified, that many frame levels need to be popped. Note that *yield* 35 | and exceptions raised my reduce the number of stack frames. Also, if a 36 | thread is switched, we stop ignoring levels. 37 | 38 | See the `break` command if you want to stop at a particular point in a 39 | 40 | See also: 41 | --------- 42 | 43 | `continue`, `step`, `next`. 44 | """ 45 | 46 | aliases = ("fin",) 47 | category = "running" 48 | execution_set = ["Running"] 49 | min_args = 0 50 | max_args = 1 51 | name = osp.basename(__file__).split(".")[0] 52 | need_stack = True 53 | short_help = "Execute until selected stack frame returns" 54 | 55 | def run(self, args): 56 | proc = self.proc 57 | core = self.core 58 | if len(args) <= 1: 59 | core.step_ignore = 0 60 | else: 61 | pos = 1 62 | if pos == len(args) - 1: 63 | core.step_ignore = self.proc.get_int(args[pos], default=1, 64 | cmdname='step') 65 | if self.core.step_ignore is None: return False 66 | # 0 means stop now or step 1, so we subtract 1. 67 | core.step_ignore -= 1 68 | pass 69 | elif pos != len(args): 70 | self.errmsg("Invalid additional parameters %s" 71 | % ' '.join(args[pos])) 72 | return False 73 | pass 74 | 75 | core.stop_level = None 76 | core.last_frame = None 77 | core.stop_on_finish = False 78 | proc.continue_running = True # Break out of command read loop 79 | proc.return_status = "finish" # Tell interpreter to step out. 80 | return True 81 | pass 82 | 83 | if __name__ == '__main__': 84 | from trepan.processor.command.mock import MockDebugger 85 | 86 | d = MockDebugger() 87 | cmd = FinishCommand(d.core.processor) 88 | for c in (["n", "5"], ["next", "1+2"], ["n", "foo"]): 89 | d.core.step_ignore = 0 90 | cmd.continue_running = False 91 | result = cmd.run(c) 92 | print("Run result: %s" % result) 93 | print( 94 | "step_ignore %d, continue_running: %s" 95 | % (d.core.step_ignore, cmd.continue_running,) 96 | ) 97 | pass 98 | for c in (["n"], ["next+"], ["n-"]): 99 | d.core.step_ignore = 0 100 | cmd.continue_running = False 101 | result = cmd.run(c) 102 | print(cmd.core.different_line) 103 | pass 104 | pass 105 | -------------------------------------------------------------------------------- /trepanxpy/processor/command/info.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (C) 2020 Rocky Bernstein 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | 17 | import os.path as osp 18 | 19 | from trepan.processor.command.info import InfoCommand as Trepan3kInfoCommand 20 | 21 | 22 | class InfoCommand(Trepan3kInfoCommand): 23 | """Generic command for showing things about the program being debugged. 24 | 25 | You can give unique prefix of the name of a subcommand to get 26 | information about just that subcommand. 27 | 28 | Type `info` for a list of *info* subcommands and what they do. 29 | Type `help info *` for just a list of *info* subcommands. 30 | """ 31 | 32 | aliases = ("i",) 33 | category = "status" 34 | min_args = 0 35 | max_args = None 36 | name = osp.basename(__file__).split(".")[0] 37 | need_stack = False 38 | short_help = "Information about debugged program and its environment" 39 | 40 | def __init__(self, proc, name="info"): 41 | """Initialize info subcommands. Note: instance variable name 42 | has to be setcmds ('set' + 'cmds') for subcommand completion 43 | to work.""" 44 | 45 | super().__init__(proc, name) 46 | new_cmdlist = [] 47 | for subname in self.cmds.cmdlist: 48 | if subname in ("pc"): 49 | del self.cmds.subcmds[subname] 50 | else: 51 | new_cmdlist.append(subname) 52 | self.cmds.cmdlist = new_cmdlist 53 | self._load_debugger_subcommands(name, "trepanxpy") 54 | return 55 | self.cmds.cmdlist = new_cmdlist 56 | 57 | 58 | if __name__ == "__main__": 59 | from trepan.processor.command import mock 60 | 61 | d, cp = mock.dbg_setup() 62 | command = InfoCommand(cp, "info") 63 | command.run(["info"]) 64 | pass 65 | -------------------------------------------------------------------------------- /trepanxpy/processor/command/info_subcmd/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (C) 2020 Rocky Bernstein 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | """ Copyright (C) 2020 Rocky Bernstein """ 17 | 18 | import glob 19 | import os.path as osp 20 | 21 | # FIXME: Is it really helpful to "privatize" variable names below? 22 | # The below names are not part of the standard pre-defined names like 23 | # __name__ or __file__ are. 24 | 25 | # Get the name of our directory. 26 | __command_dir__ = osp.dirname(__file__) 27 | 28 | # A glob pattern that will get all *.py files but not __init__.py 29 | __py_files__ = glob.glob(osp.join(__command_dir__, "[a-z]*.py")) 30 | 31 | # Take the basename of the filename and drop off '.py'. That minus the 32 | # file exclude_file the list of modules that info.py will use to import 33 | exclude_files = [] 34 | __modules__ = [ 35 | osp.basename(filename[0:-3]) 36 | for filename in __py_files__ 37 | if osp.basename(filename) not in exclude_files 38 | ] 39 | -------------------------------------------------------------------------------- /trepanxpy/processor/command/info_subcmd/blocks.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (C) 2020 Rocky Bernstein 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | 17 | # Our local modules 18 | from trepan.processor.command.base_subcmd import DebuggerSubcommand 19 | 20 | 21 | class InfoBlocks(DebuggerSubcommand): 22 | """**info blocks** 23 | 24 | Show the block-unwinding structure of the current frame 25 | 26 | See also: 27 | --------- 28 | 29 | `show stack`, 30 | """ 31 | 32 | min_abbrev = 1 # Need at least info 'b' 33 | max_args = 0 34 | need_stack = True 35 | short_help = "Show the block-unwinding structure of the current frame" 36 | 37 | def run(self, args): 38 | """Block Stack.""" 39 | frame = self.proc.vm.frame 40 | if not hasattr(frame, "block_stack"): 41 | self.errmsg("frame %s doesn't have a block stack" % frame) 42 | else: 43 | block_stack = frame.block_stack 44 | if len(block_stack) == 0: 45 | self.msg("Block stack is empty") 46 | else: 47 | for i, obj in enumerate(reversed(block_stack)): 48 | self.msg("%2d: %s" % (i, obj)) 49 | return False 50 | 51 | pass 52 | 53 | 54 | # if __name__ == "__main__": 55 | # from trepan.processor.command import mock, info as Minfo 56 | 57 | # d, cp = mock.dbg_setup() 58 | # i = Minfo.InfoCommand(cp) 59 | # sub = InfoBlocks(i) 60 | # sub.run([]) 61 | -------------------------------------------------------------------------------- /trepanxpy/processor/command/info_subcmd/pc.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (C) 2017, 2020 Rocky Bernstein 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | 17 | import inspect 18 | from xdis import findlinestarts 19 | 20 | from trepan.processor.command.base_subcmd import DebuggerSubcommand 21 | from trepan.lib.disassemble import disassemble_bytes 22 | 23 | # FIXME: this could be combined with trepan3k's `info pc`, which doesn't 24 | # require a running program but uses use f_lasti. 25 | # What we have here is less desirable the presence of exceptions, 26 | # but this code would have to be looked over to see if it too would 27 | # work. 28 | class InfoPC(DebuggerSubcommand): 29 | """**info pc** 30 | 31 | List the current program counter or bytecode offset, 32 | and disassemble the instructions around that. 33 | 34 | See also: 35 | --------- 36 | 37 | `info line`, `info program` 38 | """ 39 | 40 | min_abbrev = 2 # Need at least info 'pc' 41 | max_args = 0 42 | need_stack = True 43 | short_help = "Show Program Counter or Instruction Offset information" 44 | 45 | def run(self, args): 46 | """Program counter.""" 47 | proc = self.proc 48 | curframe = proc.curframe 49 | if curframe: 50 | line_no = inspect.getlineno(curframe) 51 | offset = curframe.f_lasti 52 | self.msg("PC offset is %d." % offset) 53 | offset = max(offset, 0) 54 | code = curframe.f_code 55 | co_code = code.co_code 56 | disassemble_bytes( 57 | self.msg, 58 | self.msg_nocr, 59 | code = co_code, 60 | lasti = offset, 61 | cur_line = line_no, 62 | start_line = line_no - 1, 63 | end_line = line_no + 1, 64 | varnames=code.co_varnames, 65 | names=code.co_names, 66 | constants=code.co_consts, 67 | cells=code.co_cellvars, 68 | freevars=code.co_freevars, 69 | line_starts=dict(findlinestarts(code)), 70 | style=self.proc.debugger.settings["style"], 71 | end_offset=offset + 10, 72 | opc=proc.vm.opc, 73 | ) 74 | pass 75 | return False 76 | 77 | pass 78 | 79 | 80 | if __name__ == "__main__": 81 | from trepan.processor.command import mock, info as Minfo 82 | 83 | d, cp = mock.dbg_setup() 84 | i = Minfo.InfoCommand(cp) 85 | sub = InfoPC(i) 86 | sub.run([]) 87 | -------------------------------------------------------------------------------- /trepanxpy/processor/command/info_subcmd/stack.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (C) 2020 Rocky Bernstein 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | 17 | import reprlib as Mrepr 18 | 19 | # Our local modules 20 | from trepan.processor.command.base_subcmd import DebuggerSubcommand 21 | 22 | 23 | class InfoStack(DebuggerSubcommand): 24 | """**info stack** 25 | 26 | Show the current frame's evaluation stack 27 | 28 | See also: 29 | --------- 30 | 31 | `info pc`, `info program` 32 | """ 33 | 34 | min_abbrev = 2 # Need at least info 'pc' 35 | max_args = 0 36 | need_stack = True 37 | short_help = "Show Program Counter or Instruction Offset information" 38 | 39 | def run(self, args): 40 | """Evaluation Stack.""" 41 | frame = self.proc.vm.frame 42 | if not hasattr(frame, "stack"): 43 | self.errmsg("frame %s doesn't have an evaluation stack" % frame) 44 | else: 45 | eval_stack = frame.stack 46 | if len(eval_stack) == 0: 47 | self.msg("Evaluation stack is empty") 48 | else: 49 | for i, obj in enumerate(reversed(eval_stack)): 50 | self.msg("%2d: %s %s" % (i, type(obj), Mrepr.repr(obj))) 51 | return False 52 | 53 | pass 54 | 55 | 56 | # if __name__ == "__main__": 57 | # from trepan.processor.command import mock, info as Minfo 58 | 59 | # d, cp = mock.dbg_setup() 60 | # i = Minfo.InfoCommand(cp) 61 | # sub = InfoStack(i) 62 | # sub.run([]) 63 | -------------------------------------------------------------------------------- /trepanxpy/processor/command/next.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (C) 2009, 2013, 2015, 2020 Rocky Bernstein 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | import os.path as osp 17 | 18 | from xpython.vmtrace import ( 19 | PyVMEVENT_ALL, 20 | PyVMEVENT_INSTRUCTION, 21 | PyVMEVENT_STEP_OVER, 22 | ) 23 | 24 | from trepan.processor.command.base_cmd import DebuggerCommand 25 | 26 | # Our local modules 27 | 28 | class NextCommand(DebuggerCommand): 29 | """**next**[**+**|**-**] [*count*] 30 | 31 | Execute the current simple statement stopping at the next event but 32 | ignoring steps into function calls at this level, 33 | 34 | With an integer argument, perform `next` that many times. However if 35 | an exception occurs at this level, or we *return*, *yield* or the 36 | thread changes, we stop regardless of count. 37 | 38 | A suffix of `+` on the command or an alias to the command forces to 39 | move to another line, while a suffix of `-` does the opposite and 40 | disables the requiring a move to a new line. If no suffix is given, 41 | the debugger setting 'different-line' determines this behavior. 42 | 43 | See also: 44 | --------- 45 | 46 | `step`, `skip`, `jump` (there's no `hop` yet), `continue`, and 47 | `finish` for other ways to progress execution. 48 | """ 49 | 50 | aliases = ("next+", "next-", "n", "n-", "n+") 51 | category = "running" 52 | execution_set = ["Running"] 53 | min_args = 0 54 | max_args = 1 55 | name = osp.basename(__file__).split(".")[0] 56 | need_stack = True 57 | short_help = "Step over" 58 | 59 | def run(self, args): 60 | proc = self.proc 61 | core = self.core 62 | if len(args) <= 1: 63 | core.step_ignore = 0 64 | else: 65 | pos = 1 66 | if pos == len(args) - 1: 67 | core.step_ignore = self.proc.get_int(args[pos], default=1, 68 | cmdname='step') 69 | if self.core.step_ignore is None: return False 70 | # 0 means stop now or step 1, so we subtract 1. 71 | core.step_ignore -= 1 72 | pass 73 | elif pos != len(args): 74 | self.errmsg("Invalid additional parameters %s" 75 | % ' '.join(args[pos])) 76 | return False 77 | pass 78 | 79 | # Add "step over" to all event flags, but remove instruction events. 80 | proc.vm.frame.event_flags = (PyVMEVENT_ALL | PyVMEVENT_STEP_OVER) ^ PyVMEVENT_INSTRUCTION 81 | 82 | core.different_line = True # Mcmdfns.want_different_line(args[0], self.settings['different']) 83 | core.stop_level = None 84 | core.last_frame = None 85 | core.stop_on_finish = False 86 | proc.continue_running = True # Break out of command read loop 87 | return True 88 | pass 89 | 90 | if __name__ == '__main__': 91 | from trepan.processor.command.mock import MockDebugger 92 | 93 | d = MockDebugger() 94 | cmd = NextCommand(d.core.processor) 95 | for c in (["n", "5"], ["next", "1+2"], ["n", "foo"]): 96 | d.core.step_ignore = 0 97 | cmd.continue_running = False 98 | result = cmd.run(c) 99 | print("Run result: %s" % result) 100 | print( 101 | "step_ignore %d, continue_running: %s" 102 | % (d.core.step_ignore, cmd.continue_running,) 103 | ) 104 | pass 105 | for c in (["n"], ["next+"], ["n-"]): 106 | d.core.step_ignore = 0 107 | cmd.continue_running = False 108 | result = cmd.run(c) 109 | print(cmd.core.different_line) 110 | pass 111 | pass 112 | -------------------------------------------------------------------------------- /trepanxpy/processor/command/python.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (C) 2009-2010, 2013, 2015, 2017, 2020 Rocky Bernstein 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | import code, os.path as osp, sys 17 | 18 | # Our local modules 19 | from trepan.processor.command.python import ( 20 | PythonCommand as Trepan3kShell, 21 | interact, 22 | runcode, 23 | ) 24 | from trepan.interfaces.server import ServerInterface 25 | 26 | 27 | class PythonCommand(Trepan3kShell): 28 | """**python** 29 | 30 | Run Python as a command subshell. The *sys.ps1* prompt will be set to 31 | `trepan-xpy >>> `. 32 | 33 | To issue a debugger command use function *dbgr()*. For example: 34 | 35 | dbgr('info program') 36 | 37 | The current call frame and Interpreter VM access is available through variables: frame, and vm. 38 | """ 39 | 40 | aliases = ("py", "shell") 41 | category = "support" 42 | min_args = 0 43 | max_args = 1 44 | name = osp.basename(__file__).split(".")[0] 45 | need_stack = False 46 | short_help = "Run a Python interactive shell" 47 | 48 | def run(self, args): 49 | # See if python's code module is around 50 | 51 | # Python does it's own history thing. 52 | # Make sure it doesn't damage ours. 53 | intf = self.debugger.intf[-1] 54 | if isinstance(intf, ServerInterface): 55 | self.errmsg("Can't run an interactive shell on a remote session") 56 | return 57 | 58 | have_line_edit = self.debugger.intf[-1].input.line_edit 59 | if have_line_edit: 60 | try: 61 | self.proc.write_history_file() 62 | except IOError: 63 | pass 64 | pass 65 | 66 | banner_tmpl = """trepan-xpy python shell%s 67 | Use dbgr(*string*) to issue debugger command: *string*""" 68 | 69 | banner_tmpl += "\nVariable 'frame' contains the current frame; 'vm', the current vm object." 70 | 71 | my_locals = {} 72 | my_globals = None 73 | proc = self.proc 74 | if proc.curframe: 75 | my_globals = proc.curframe.f_globals 76 | if self.proc.curframe.f_locals: 77 | my_locals = proc.curframe.f_locals 78 | pass 79 | pass 80 | 81 | # Give the user a way to get access frame, vm and debugger commands 82 | my_locals["vm"] = proc.vm 83 | my_locals["frame"] = proc.frame 84 | my_locals["dbgr"] = self.dbgr 85 | 86 | # Change from debugger completion to python completion 87 | try: 88 | import readline 89 | except ImportError: 90 | pass 91 | else: 92 | readline.parse_and_bind("tab: complete") 93 | 94 | sys.ps1 = "trepan-xpy >>> " 95 | old_sys_excepthook = sys.excepthook 96 | try: 97 | sys.excepthook = None 98 | if len(my_locals): 99 | interact( 100 | banner=(banner_tmpl % " with locals"), 101 | my_locals=my_locals, 102 | my_globals=my_globals, 103 | ) 104 | else: 105 | interact(banner=(banner_tmpl % "")) 106 | pass 107 | finally: 108 | sys.excepthook = old_sys_excepthook 109 | 110 | # restore completion and our history if we can do so. 111 | if hasattr(self.proc.intf[-1], "complete"): 112 | try: 113 | from readline import set_completer, parse_and_bind 114 | 115 | parse_and_bind("tab: complete") 116 | set_completer(self.proc.intf[-1].complete) 117 | except ImportError: 118 | pass 119 | pass 120 | 121 | if have_line_edit: 122 | self.proc.read_history_file() 123 | pass 124 | return 125 | 126 | pass 127 | 128 | 129 | if __name__ == "__main__": 130 | from trepanxpy.debugger import Debugger 131 | 132 | d = Debugger("x + 1", is_file=False, trace_only=False, args=[]) 133 | command = PythonCommand(d.core.processor) 134 | command.proc.frame = sys._getframe() 135 | command.proc.setup() 136 | if len(sys.argv) > 1: 137 | print("Type Python commands and exit to quit.") 138 | print(sys.argv[1]) 139 | if sys.argv[1] == "-d": 140 | print(command.run(["python", "-d"])) 141 | else: 142 | print(command.run(["python"])) 143 | pass 144 | pass 145 | pass 146 | -------------------------------------------------------------------------------- /trepanxpy/processor/command/quit.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (C) 2013, 2015, 2020 Rocky Bernstein 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | import os, threading 17 | import ctypes 18 | 19 | from trepan.processor.command.base_cmd import DebuggerCommand 20 | 21 | def ctype_async_raise(thread_obj, exception): 22 | found = False 23 | target_tid = 0 24 | for tid, tobj in threading._active.items(): 25 | if tobj is thread_obj: 26 | found = True 27 | target_tid = tid 28 | break 29 | 30 | if not found: 31 | raise ValueError("Invalid thread object") 32 | 33 | ret = ctypes.pythonapi.PyThreadState_SetAsyncExc( 34 | target_tid, ctypes.py_object(exception) 35 | ) 36 | # ref: http://docs.python.org/c-api/init.html#PyThreadState_SetAsyncExc 37 | if ret == 0: 38 | raise SystemExit 39 | elif ret > 1: 40 | # Huh? Why would we notify more than one threads? 41 | # Because we punch a hole into C level interpreter. 42 | # So it is better to clean up the mess. 43 | ctypes.pythonapi.PyThreadState_SetAsyncExc(target_tid, 0) 44 | raise SystemError("PyThreadState_SetAsyncExc failed") 45 | 46 | 47 | class QuitCommand(DebuggerCommand): 48 | """**quit** [**unconditionally**] 49 | 50 | Gently terminate the debugged program. 51 | 52 | The program being debugged is aborted via a *DebuggerQuit* 53 | exception. 54 | 55 | When the debugger from the outside (e.g. via a `trepan` command), the 56 | debugged program is contained inside a try block which handles the 57 | *DebuggerQuit* exception. However if you called the debugger was 58 | started in the middle of a program, there might not be such an 59 | exception handler; the debugged program still terminates but generally 60 | with a traceback showing that exception. 61 | 62 | If the debugged program is threaded, we raise an exception in each of 63 | the threads ending with our own. However this might not quit the 64 | program. 65 | 66 | See also: 67 | --------- 68 | 69 | See `exit` or `kill` for more forceful termination commands. 70 | 71 | `run` and `restart` are other ways to restart the debugged program. 72 | """ 73 | 74 | aliases = ("q", "quit!") 75 | category = "support" 76 | min_args = 0 77 | max_args = 0 78 | name = os.path.basename(__file__).split(".")[0] 79 | need_stack = False 80 | short_help = "Terminate the program - gently" 81 | 82 | def nothread_quit(self, arg): 83 | """ quit command when there's just one thread. """ 84 | 85 | self.debugger.core.stop() 86 | self.debugger.core.execution_status = "Quit command" 87 | raise SystemExit 88 | 89 | def threaded_quit(self, arg): 90 | """ quit command when several threads are involved. """ 91 | threading_list = threading.enumerate() 92 | mythread = threading.currentThread() 93 | for t in threading_list: 94 | if t != mythread: 95 | ctype_async_raise(t, SystemExit) 96 | pass 97 | pass 98 | raise SystemExit 99 | 100 | def run(self, args): 101 | confirmed = False 102 | if len(args) <= 1: 103 | if "!" != args[0][-1]: 104 | confirmed = self.confirm("Really quit", False) 105 | else: 106 | confirmed = True 107 | pass 108 | if confirmed: 109 | threading_list = threading.enumerate() 110 | if ( 111 | len(threading_list) == 1 112 | ) and threading_list[0].getName() == "MainThread": 113 | # We are in a main thread and either there is one thread or 114 | # we or are in ipython, so that's safe to quit. 115 | return self.nothread_quit(args) 116 | else: 117 | return self.threaded_quit(args) 118 | pass 119 | return 120 | 121 | 122 | if __name__ == "__main__": 123 | from trepan.processor.command import mock 124 | 125 | d, cp = mock.dbg_setup() 126 | command = QuitCommand(cp) 127 | try: 128 | command.run(["quit"]) 129 | except SystemExit: 130 | print("A got 'quit' a exception. Ok, be that way - I'm going home.") 131 | pass 132 | 133 | class MyThread(threading.Thread): 134 | def run(self): 135 | command.run(["quit"]) 136 | return 137 | 138 | pass 139 | 140 | t = MyThread() 141 | t.start() 142 | t.join() 143 | pass 144 | -------------------------------------------------------------------------------- /trepanxpy/processor/command/return.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (C) 2009, 2013-2015, 2020 Rocky Bernstein 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | import os.path as osp, sys 17 | 18 | from trepan.processor.command.base_cmd import DebuggerCommand 19 | 20 | 21 | class ReturnCommand(DebuggerCommand): 22 | """**return** [*value*] 23 | 24 | Cause an immediate return giving *value* as the return value. 25 | If no value is specified, we'll use `None`. 26 | 27 | See also: 28 | --------- 29 | 30 | `step`, `stepi`. 31 | """ 32 | 33 | aliases = ("ret",) 34 | category = "running" 35 | execution_set = ["Running"] 36 | min_args = 0 37 | max_args = 1 38 | name = osp.basename(__file__).split(".")[0] 39 | need_stack = True 40 | short_help = "Immediate return with value" 41 | 42 | def run(self, args): 43 | proc = self.proc 44 | if proc.stack is None: 45 | return False 46 | 47 | if len(args) == 0: 48 | proc.vm.return_value = None 49 | else: 50 | text = self.proc.current_command[len(self.proc.cmd_name):] 51 | pass 52 | text = text.strip() 53 | try: 54 | proc.vm.return_value = proc.eval(text) 55 | except: 56 | pass 57 | 58 | proc.return_status = "return" 59 | proc.continue_running = True # Break out of command read loop 60 | return True 61 | 62 | pass 63 | 64 | 65 | if __name__ == "__main__": 66 | from trepan.processor.command.mock import MockDebugger 67 | from xpython.vmtrace import PyVMTraced 68 | d = MockDebugger() 69 | proc = d.core.processor 70 | proc.vm = PyVMTraced(None) 71 | cmd = ReturnCommand(d.core.processor) 72 | 73 | def demo_return(cmd): 74 | for c in ( 75 | ["return", "1"], 76 | ["return", "wrong", "number", "of", "args"], 77 | ["return", "5"], 78 | ): 79 | proc.vm.return_status = "Initial" 80 | proc.continue_running = False 81 | proc.stack = [(sys._getframe(0), 14,)] 82 | result = cmd.run(c) 83 | print("Execute result: %s" % result) 84 | print( 85 | "continue_running: %s, return status: %s" 86 | % (proc.continue_running, 87 | proc.vm.return_status) 88 | ) 89 | pass 90 | return 91 | 92 | demo_return(cmd) 93 | pass 94 | -------------------------------------------------------------------------------- /trepanxpy/processor/command/set.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (C) 2020 Rocky Bernstein 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | 17 | import os.path as osp 18 | 19 | from trepan.processor.command.set import SetCommand as Trepan3kSetCommand 20 | 21 | class SetCommand(Trepan3kSetCommand): 22 | """**set** *set subcommand* 23 | 24 | Modifies parts of the debugger environment. 25 | 26 | You can give unique prefix of the name of a subcommand to get 27 | information about just that subcommand. 28 | 29 | Type `set` for a list of *set* subcommands and what they do. 30 | Type `help set *` for just the list of *set* subcommands. 31 | """ 32 | 33 | category = 'data' 34 | min_args = 0 35 | max_args = None 36 | name = osp.basename(__file__).split('.')[0] 37 | need_stack = False 38 | short_help = 'Modify parts of the debugger environment' 39 | 40 | def __init__(self, proc, name="set"): 41 | """Initialize show subcommands. Note: instance variable name 42 | has to be setcmds ('set' + 'cmds') for subcommand completion 43 | to work.""" 44 | 45 | super().__init__(proc, name) 46 | new_cmdlist = [] 47 | for subname in self.cmds.cmdlist: 48 | if subname in ("dbg_trepan", "events"): 49 | del self.cmds.subcmds[subname] 50 | else: 51 | new_cmdlist.append(subname) 52 | self.cmds.cmdlist = new_cmdlist 53 | self._load_debugger_subcommands(name, "trepanxpy") 54 | 55 | if __name__ == '__main__': 56 | from trepan.processor.command import mock 57 | d, cp = mock.dbg_setup() 58 | command = SetCommand(cp, 'set') 59 | command.run(['set']) 60 | pass 61 | -------------------------------------------------------------------------------- /trepanxpy/processor/command/set_subcmd/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (C) 2020 Rocky Bernstein 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | """ Copyright (C) 2020 Rocky Bernstein """ 17 | 18 | import glob 19 | import os.path as osp 20 | 21 | # FIXME: Is it really helpful to "privatize" variable names below? 22 | # The below names are not part of the standard pre-defined names like 23 | # __name__ or __file__ are. 24 | 25 | # Get the name of our directory. 26 | __command_dir__ = osp.dirname(__file__) 27 | 28 | # A glob pattern that will get all *.py files but not __init__.py 29 | __py_files__ = glob.glob(osp.join(__command_dir__, "[a-z]*.py")) 30 | 31 | # Take the basename of the filename and drop off '.py'. That becomes 32 | # the list of modules that commands.py will use to import 33 | exclude_files = [] 34 | __modules__ = [ 35 | osp.basename(filename[0:-3]) 36 | for filename in __py_files__ 37 | if osp.basename(filename) not in exclude_files 38 | ] 39 | -------------------------------------------------------------------------------- /trepanxpy/processor/command/set_subcmd/autostack.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (C) 2021 Rocky Bernstein 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | 17 | # Our local modules 18 | from trepan.processor.command.base_subcmd import DebuggerSetBoolSubcommand 19 | from trepan.processor.cmdfns import run_set_bool, run_show_bool 20 | 21 | 22 | class SetAutoStack(DebuggerSetBoolSubcommand): 23 | """**set autostack** [ **on** | **off** ] 24 | 25 | Run the `info stack` command every time we enter the debugger. 26 | 27 | See also: 28 | --------- 29 | 30 | `show autostackc` 31 | """ 32 | 33 | in_list = True 34 | min_abbrev = len("autos") 35 | 36 | info_stack_cmd = None 37 | 38 | def run(self, args): 39 | run_set_bool(self, args) 40 | if self.settings["autostack"]: 41 | if self.info_stack_cmd is None: 42 | info_cmd = self.proc.commands["info"] 43 | self.info_stack_cmd = info_cmd.cmds.lookup("stack").run 44 | pass 45 | self.proc.add_preloop_hook(self.run_infostack, 0) 46 | 47 | else: 48 | self.proc.remove_preloop_hook(self.run_infostack) 49 | pass 50 | run_show_bool(self, "Run `info stack` on debugger entry") 51 | return 52 | 53 | def run_infostack(self, args): 54 | if self.proc.frame: 55 | self.info_stack_cmd(["info", "stack"]) 56 | return 57 | 58 | pass 59 | 60 | 61 | if __name__ == "__main__": 62 | from trepan.processor.command.show_subcmd.__demo_helper__ import demo_run 63 | 64 | demo_run(SetAutoStack) 65 | pass 66 | -------------------------------------------------------------------------------- /trepanxpy/processor/command/set_subcmd/loglevel.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (C) 2020 Rocky Bernstein 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | 17 | import logging 18 | 19 | from trepan.processor.command.base_subcmd import DebuggerSubcommand 20 | from trepan.lib.complete import complete_token 21 | 22 | 23 | class SetLogLevel(DebuggerSubcommand): 24 | """**set loglevel** [ **on** | **off** | **debug** | **info** | **critical* | *warn* ] 25 | 26 | Show loglevel PyVM logger messages. Initially logtracing is `off`. 27 | 28 | However running `set loglevel` will turn it on and set the log level to `debug`. So it's the 29 | same thing as `set loglevel debug`. 30 | 31 | If you want the less verbose messages, use `info`. And to turn off, 32 | (except critical errors), use `off`. 33 | 34 | Examples: 35 | --------- 36 | 37 | set loglevel # turns x-python on info logging messages 38 | set loglevel info # same as above 39 | set loglevel debug # turn on info and debug logging messages 40 | set loglevel off # turn off all logging messages except critical ones 41 | """ 42 | 43 | in_list = True 44 | min_abbrev = len('logl') 45 | 46 | logger_choices = frozenset(["debug", "info", "warn", "warning", "critical", "off", "on"]) 47 | 48 | def complete(self, prefix): 49 | return complete_token(SetLogLevel.logger_choices, prefix) 50 | 51 | def get_loglevel_level(self, arg): 52 | if not arg: return "info" 53 | arg = arg.lower() 54 | if arg in SetLogLevel.logger_choices: 55 | return arg 56 | else: 57 | self.errmsg('Expecting %s"; got %s' % 58 | (', '.join(SetLogLevel.logger_choices), arg)) 59 | return None 60 | pass 61 | 62 | 63 | def run(self, args): 64 | if len(args) == 0: 65 | loglevel_level = logging.INFO 66 | else: 67 | level_str = self.get_loglevel_level(args[0]) 68 | if not level_str: 69 | return 70 | if level_str in ("off", "critical"): 71 | loglevel_level = logging.CRITICAL 72 | elif level_str in ("info", "on") : 73 | loglevel_level = logging.INFO 74 | elif level_str in ("warn", "warning") : 75 | loglevel_level = logging.WARNING 76 | else: 77 | assert level_str == "debug" 78 | loglevel_level = logging.DEBUG 79 | pass 80 | 81 | # Remove all handlers associated with the root logger object. 82 | for handler in logging.root.handlers[:]: 83 | logging.root.removeHandler(handler) 84 | pass 85 | logging.basicConfig(level=loglevel_level) 86 | self.proc.commands["show"].run(["show", "loglevel"]) 87 | return 88 | 89 | pass 90 | 91 | if __name__ == '__main__': 92 | from trepan.processor.command.set_subcmd.__demo_helper__ import demo_run 93 | demo_run(SetLogLevel, []) 94 | pass 95 | -------------------------------------------------------------------------------- /trepanxpy/processor/command/set_subcmd/pc.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (C) 2020 Rocky Bernstein 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | 17 | from getopt import getopt, GetoptError 18 | 19 | from trepan.processor.command.base_subcmd import DebuggerSubcommand 20 | from trepan.processor.cmdfns import get_an_int 21 | from trepan.misc import wrapped_lines 22 | 23 | 24 | class SetPC(DebuggerSubcommand): 25 | """**set pc** [ *flag* ] *line-or-offset* 26 | 27 | Set the PC to run at *line-or-offset* 28 | 29 | *flag* can be one of: 30 | 31 | * `--offset` | `-o` to indicate an offset 32 | * `--line` | `-l` to indicate a line number 33 | * `=` which is ignored. It is syntactic sugar 34 | 35 | Alternatively, the line-or-offset can be indicated by prefixing the 36 | number with `l` or `@` respectively. 37 | 38 | Examples 39 | -------- 40 | 41 | set pc 0 42 | set pc @0 # same as above 43 | set pc --offset 0 # same as above 44 | set pc -o 0 # same as above 45 | set pc = 0 # same as above 46 | 47 | set pc l2 # same as above 48 | set pc --line 2 # same as above 49 | set pc -l 2 # same as above 50 | set pc = l0 # same as above 51 | 52 | See Also 53 | -------- 54 | 55 | `info pc` 56 | """ 57 | 58 | in_list = True 59 | min_abbrev = len('pc') 60 | max_args = 2 61 | need_stack = True 62 | short_help = "Set Program Counter" 63 | 64 | def run(self, args): 65 | mainfile = self.core.filename(None) 66 | if self.core.is_running(): 67 | proc = self.proc 68 | 69 | is_offset = True 70 | n = len(args) 71 | if n == 2: 72 | if args[0] == "=": 73 | args[0] = args[1] 74 | else: 75 | try: 76 | opts, args = getopt(args, "hlo", 77 | ["help", "line", "offset"]) 78 | except GetoptError as err: 79 | # print help information and exit: 80 | print(str(err)) # will print something like "option -a not recognized" 81 | return 82 | 83 | for o, a in opts: 84 | if o in ("-h", "--help"): 85 | proc.commands["help"].run(["help", "set", "pc"]) 86 | return 87 | elif o in ("-l", "--line", ): 88 | is_offset = False 89 | elif o in ("-o", "--offset"): 90 | is_offset = True 91 | else: 92 | self.errmsg("unhandled option '%s'" % o) 93 | pass 94 | pass 95 | pass 96 | 97 | frame = proc.frame 98 | if frame: 99 | code = frame.f_code 100 | if is_offset: 101 | min_value = 0 102 | max_value = len(code.co_code) - 1 103 | elif len(code.co_lnotab) > 0: 104 | min_value = code.co_firstlineno 105 | # FIXME: put this in a function in xdis somewhere. 106 | # Also: won't need ord() in Python 2.x 107 | # max_value = min_value + sum([ord(i) for i in code.co_lnotab[1::2]]) 108 | max_value = min_value + sum(code.co_lnotab[1::2]) 109 | else: 110 | min_value = max_value = code.co_firstline 111 | 112 | offset = get_an_int( 113 | self.errmsg, 114 | args[0], 115 | "The 'pc' command requires an integer offset or a line.", 116 | min_value, max_value, 117 | ) 118 | if offset is None: 119 | return None 120 | # FIXME: We check that offset points to an 121 | # opcode or a valid line as opposed to an operand 122 | if not is_offset: 123 | self.errmsg("Sorry, line numbers not handled right now.") 124 | return None 125 | 126 | self.list_offset = frame.f_lasti = offset 127 | frame.fallthrough = False 128 | self.list_lineno = frame.f_lineno = frame.line_number() 129 | self.msg("Execution set to resume at offset %d" % offset) 130 | self.return_status = "skip" 131 | return None 132 | else: 133 | self.errmsg("Oddly, a frame is not set - nothing done.") 134 | pass 135 | else: 136 | if mainfile: 137 | part1 = "Python program '%s'" % mainfile 138 | msg = "is not currently running. " 139 | self.msg(wrapped_lines(part1, msg, self.settings["width"])) 140 | else: 141 | self.msg("No Python program is currently running.") 142 | pass 143 | self.msg(self.core.execution_status) 144 | pass 145 | return None 146 | 147 | pass 148 | 149 | if __name__ == '__main__': 150 | from trepan.processor.command.set_subcmd.__demo_helper__ import demo_run 151 | 152 | demo_run(SetPC, [0]) 153 | pass 154 | -------------------------------------------------------------------------------- /trepanxpy/processor/command/show.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (C) 2020 Rocky Bernstein 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | 17 | import os.path as osp 18 | 19 | from trepan.processor.command.show import ShowCommand as Trepan3kShowCommand 20 | 21 | class ShowCommand(Trepan3kShowCommand): 22 | """**show** *subcommand* 23 | 24 | Generic command for showing things about the debugger. You can 25 | give unique prefix of the name of a subcommand to get information 26 | about just that subcommand. 27 | 28 | Type `show` for a list of *show* subcommands and what they do. 29 | Type `help show *` for just a list of *show* subcommands. 30 | """ 31 | 32 | category = "status" 33 | min_args = 0 34 | max_args = None 35 | name = osp.basename(__file__).split(".")[0] 36 | need_stack = False 37 | short_help = "Show parts of the debugger environment" 38 | 39 | def __init__(self, proc, name="show"): 40 | """Initialize show subcommands. Note: instance variable name 41 | has to be setcmds ('set' + 'cmds') for subcommand completion 42 | to work.""" 43 | 44 | super().__init__(proc, name) 45 | new_cmdlist = [] 46 | for subname in self.cmds.cmdlist: 47 | if subname in ("dbg_trepan", "events"): 48 | del self.cmds.subcmds[subname] 49 | else: 50 | new_cmdlist.append(subname) 51 | self.cmds.cmdlist = new_cmdlist 52 | self._load_debugger_subcommands(name, "trepanxpy") 53 | 54 | 55 | if __name__ == "__main__": 56 | from trepan.processor.command import mock 57 | 58 | d, cp = mock.dbg_setup() 59 | command = ShowCommand(cp, "show") 60 | command.run(["show"]) 61 | pass 62 | -------------------------------------------------------------------------------- /trepanxpy/processor/command/show_subcmd/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (C) 2020 Rocky Bernstein 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | """ Copyright (C) 2020 Rocky Bernstein """ 17 | 18 | import glob 19 | import os.path as osp 20 | 21 | # FIXME: Is it really helpful to "privatize" variable names below? 22 | # The below names are not part of the standard pre-defined names like 23 | # __name__ or __file__ are. 24 | 25 | # Get the name of our directory. 26 | __command_dir__ = osp.dirname(__file__) 27 | 28 | # A glob pattern that will get all *.py files but not __init__.py 29 | __py_files__ = glob.glob(osp.join(__command_dir__, "[a-z]*.py")) 30 | 31 | # Take the basename of the filename and drop off '.py'. That becomes 32 | # the list of modules that commands.py will use to import 33 | exclude_files = [] 34 | __modules__ = [ 35 | osp.basename(filename[0:-3]) 36 | for filename in __py_files__ 37 | if osp.basename(filename) not in exclude_files 38 | ] 39 | -------------------------------------------------------------------------------- /trepanxpy/processor/command/show_subcmd/autostack.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (C) 2021 Rocky Bernstein 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | 17 | # Our local modules 18 | from trepan.processor.command.base_subcmd import DebuggerShowBoolSubcommand 19 | 20 | 21 | class ShowAutoStack(DebuggerShowBoolSubcommand): 22 | """**show autostack** 23 | 24 | Show debugger `info stack` command automatically on entry. 25 | 26 | See also: 27 | --------- 28 | 29 | `set autostack`""" 30 | 31 | min_abbrev = len("autos") 32 | short_help = "Show `info stack` on debugger entry" 33 | pass 34 | 35 | 36 | if __name__ == "__main__": 37 | from trepan.processor.command.show_subcmd.__demo_helper__ import demo_run 38 | 39 | demo_run(ShowAutoStack) 40 | pass 41 | -------------------------------------------------------------------------------- /trepanxpy/processor/command/show_subcmd/loglevel.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (C) 2009, 2015, 2020 Rocky Bernstein 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | 17 | from xpython.vm import log 18 | from trepan.processor.command.base_subcmd import DebuggerSubcommand 19 | 20 | LOGLEVEL2NAME = { 21 | 50: "critical", 22 | 40: "error", 23 | 30: "warning", 24 | 20: "info", 25 | 10: "debug", 26 | 00: "notset" 27 | } 28 | 29 | 30 | class ShowLogLevel(DebuggerSubcommand): 31 | """**show loglevel** 32 | 33 | Show VM logger log level setting. 34 | 35 | See also: 36 | --------- 37 | 38 | `set logtrace`""" 39 | 40 | min_abbrev = len("logl") 41 | short_help = "Show VM logger log level setting" 42 | pass 43 | 44 | def run(self, args): 45 | if len(args) != 0: 46 | self.errmsg("Expecting no args") 47 | return 48 | 49 | log_str = LOGLEVEL2NAME.get(log.getEffectiveLevel(), "unknown") 50 | self.msg("PyVM log level is %s" % log_str) 51 | return 52 | pass 53 | 54 | if __name__ == "__main__": 55 | # from trepan.processor.command.show_subcmd.__demo_helper__ import demo_run 56 | 57 | # demo_run(ShowLogLevel) 58 | pass 59 | -------------------------------------------------------------------------------- /trepanxpy/processor/command/step.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (C) 2009, 2013, 2015, 2020 Rocky Bernstein 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | import os.path as osp 17 | 18 | from xpython.vmtrace import ( 19 | PyVMEVENT_ALL, 20 | PyVMEVENT_INSTRUCTION, 21 | ) 22 | 23 | from trepan.processor.command.base_cmd import DebuggerCommand 24 | 25 | # Our local modules 26 | 27 | 28 | class StepCommand(DebuggerCommand): 29 | """**step**[**+**|**-**|**<**|**>**|**!**] [*event*...] [*count*] 30 | 31 | Execute the current simple statement, stopping at the next event. 32 | 33 | With an integer argument, step that many times. 34 | 35 | *event* is list of an event name which is one of: `call`, 36 | `return`, `line`, `exception` `c-call`, `c-return` or `c-exception`. 37 | If specified, only those stepping events will be considered. If no 38 | list of event names is given, then any event triggers a stop when the 39 | count is 0. 40 | 41 | There is however another way to specify a *single* event, by 42 | suffixing one of the symbols `<`, `>`, or `!` after the command or on 43 | an alias of that. A suffix of `+` on a command or an alias forces a 44 | move to another line, while a suffix of `-` disables this requirement. 45 | A suffix of `>` will continue until the next call. (`finish` will run 46 | run until the return for that call.) 47 | 48 | If no suffix is given, the debugger setting `different-line` 49 | determines this behavior. 50 | 51 | Examples: 52 | --------- 53 | 54 | step # step 1 event, *any* event 55 | step 1 # same as above 56 | step 5/5+0 # same as above 57 | step line # step only line events 58 | step call # step only call events 59 | step> # same as above 60 | step call line # Step line *and* call events 61 | 62 | Related and similar is the `next` command. 63 | 64 | See also: 65 | --------- 66 | 67 | `next`, `skip`, `jump` (there's no `hop` yet), `continue`, `return` and 68 | `finish` for other ways to progress execution. 69 | """ 70 | 71 | aliases = ( 72 | "step+", 73 | "step-", 74 | "step>", 75 | "step<", 76 | "step!", 77 | "s", 78 | "s+", 79 | "s-", 80 | "s<", 81 | "s>", 82 | "s!", 83 | ) 84 | category = "running" 85 | execution_set = ["Running"] 86 | short_help = "Step into" 87 | 88 | DebuggerCommand.setup(locals(), category="running", max_args=1, need_stack=True) 89 | 90 | def run(self, args): 91 | # event_flags = [] 92 | # if args[0][-1] == '>': 93 | # event_flags = ['call'] 94 | # elif args[0][-1] == '<': 95 | # event_flags = ['return'] 96 | # elif args[0][-1] == '!': 97 | # event_flags = ["exception"] 98 | # pass 99 | proc = self.proc 100 | core = self.core 101 | if len(args) <= 1: 102 | core.step_ignore = 0 103 | else: 104 | pos = 1 105 | if pos == len(args) - 1: 106 | core.step_ignore = self.proc.get_int( 107 | args[pos], default=1, cmdname="step" 108 | ) 109 | if self.core.step_ignore is None: 110 | return False 111 | # 0 means stop now or step 1, so we subtract 1. 112 | core.step_ignore -= 1 113 | pass 114 | elif pos != len(args): 115 | self.errmsg("Invalid additional parameters %s" % " ".join(args[pos])) 116 | return False 117 | pass 118 | 119 | # All events except instruction tracing which is xor'd out. 120 | core.step_flags = proc.vm.frame.event_flags = ( 121 | PyVMEVENT_ALL ^ PyVMEVENT_INSTRUCTION 122 | ) 123 | 124 | core.different_line = ( 125 | True # Mcmdfns.want_different_line(args[0], self.settings['different']) 126 | ) 127 | core.stop_level = None 128 | core.last_frame = None 129 | core.stop_on_finish = False 130 | proc.continue_running = True # Break out of command read loop 131 | return True 132 | 133 | pass 134 | 135 | 136 | if __name__ == "__main__": 137 | from trepan.processor.command.mock import MockDebugger 138 | 139 | d = MockDebugger() 140 | cmd = StepCommand(d.core.processor) 141 | for c in (["s", "5"], ["step", "1+2"], ["s", "foo"]): 142 | d.core.step_ignore = 0 143 | cmd.proc.continue_running = False 144 | result = cmd.run(c) 145 | print("Execute result: %s" % result) 146 | print("step_ignore %s" % repr(d.core.step_ignore)) 147 | print("continue_running: %s" % cmd.proc.continue_running) 148 | pass 149 | for c in (["s"], ["step+"], ["s-"], ["s!"], ["s>"], ["s<"]): 150 | d.core.step_ignore = 0 151 | cmd.continue_running = False 152 | result = cmd.run(c) 153 | print("different line %s:" % c[0], cmd.core.different_line) 154 | pass 155 | pass 156 | -------------------------------------------------------------------------------- /trepanxpy/processor/command/stepi.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (C) 2009, 2013, 2015, 2020 Rocky Bernstein 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | import os.path as osp 17 | 18 | from xpython.vmtrace import PyVMEVENT_ALL 19 | 20 | from trepan.processor.command.base_cmd import DebuggerCommand 21 | 22 | # Our local modules 23 | 24 | class StepICommand(DebuggerCommand): 25 | """**stepi** [*count*] 26 | 27 | Step an instruction, stopping at the next instruction or event. 28 | 29 | With an integer argument, step that many times. 30 | 31 | 32 | Examples: 33 | --------- 34 | 35 | stepi # step 1 event, *any* event 36 | stepi 1 # same as above 37 | stepi 5/5+0 # same as above 38 | 39 | Related and similar is the `next` command. More general is the `step` command 40 | 41 | See also: 42 | --------- 43 | 44 | `step` `next`, `skip`, `jump` (there's no `hop` yet), `continue`, `return` and 45 | `finish` for other ways to progress execution. 46 | """ 47 | 48 | aliases = ('si',) 49 | category = 'running' 50 | min_args = 0 51 | max_args = None 52 | execution_set = ['Running'] 53 | name = osp.basename(__file__).split('.')[0] 54 | need_stack = True 55 | short_help = 'Step instruction (possibly entering called functions)' 56 | 57 | def run(self, args): 58 | proc = self.proc 59 | core = self.core 60 | if len(args) <= 1: 61 | core.step_ignore = 0 62 | else: 63 | pos = 1 64 | if pos == len(args) - 1: 65 | core.step_ignore = proc.get_int(args[pos], default=1, 66 | cmdname='step') 67 | if core.step_ignore is None: return False 68 | # 0 means stop now or step 1, so we subtract 1. 69 | core.step_ignore -= 1 70 | pass 71 | elif pos != len(args): 72 | self.errmsg("Invalid additional parameters %s" 73 | % ' '.join(args[pos])) 74 | return False 75 | pass 76 | 77 | proc.vm.frame.event_flags = PyVMEVENT_ALL 78 | 79 | core.different_line = True # Mcmdfns.want_different_line(args[0], self.settings['different']) 80 | core.stop_level = None 81 | core.last_frame = None 82 | core.stop_on_finish = False 83 | proc.continue_running = True # Break out of command read loop 84 | return True 85 | pass 86 | 87 | if __name__ == '__main__': 88 | from trepan.processor.command.mock import MockDebugger 89 | d = MockDebugger() 90 | cmd = StepICommand(d.core.processor) 91 | for c in (['si', '5'], 92 | ['stepi', '1+2'], 93 | ['si', 'foo']): 94 | d.core.step_ignore = 0 95 | cmd.proc.continue_running = False 96 | result = cmd.run(c) 97 | print('Execute result: %s' % result) 98 | print('step_ignore %s' % repr(d.core.step_ignore)) 99 | print('continue_running: %s' % cmd.proc.continue_running) 100 | pass 101 | # for c in (['si'], ['stepi']): 102 | # d.core.step_ignore = 0 103 | # cmd.continue_running = False 104 | # result = cmd.run(c) 105 | # print('different line %s:' % c[0], cmd.core.different_line) 106 | # pass 107 | pass 108 | -------------------------------------------------------------------------------- /trepanxpy/processor/command/vmstack.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (C) 2021 Rocky Bernstein 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | 17 | import os.path as osp 18 | 19 | from trepan.processor.command.show import ShowCommand as Trepan3kShowCommand 20 | 21 | class VMStackCommand(Trepan3kShowCommand): 22 | """**vmstack** *subcommand* 23 | 24 | Generic command for working with the VM evaluation stack. You can 25 | give unique prefix of the name of a subcommand to get information 26 | about just that subcommand. 27 | 28 | Type `vmstack` for a list of *vmstack* subcommands and what they do. 29 | Type `help vmstack *` for just a list of *vmstack* subcommands. 30 | """ 31 | 32 | category = "vmstack" 33 | min_args = 0 34 | max_args = None 35 | name = osp.basename(__file__).split(".")[0] 36 | need_stack = False 37 | short_help = "Work with the VM evaluation stack" 38 | 39 | def __init__(self, proc, name="vmstack"): 40 | """Initialize vmstack subcommands. Note: instance variable name 41 | has to be setcmds ('vmstack' + 'cmds') for subcommand completion 42 | to work.""" 43 | 44 | super().__init__(proc, name, "trepanxpy") 45 | new_cmdlist = [] 46 | for subname in self.cmds.cmdlist: 47 | new_cmdlist.append(subname) 48 | self.cmds.cmdlist = new_cmdlist 49 | self._load_debugger_subcommands(name, "trepanxpy") 50 | 51 | 52 | if __name__ == "__main__": 53 | from trepan.processor.command import mock 54 | 55 | d, cp = mock.dbg_setup() 56 | command = VMStackCommand(cp, "vmstack") 57 | command.run(["show"]) 58 | pass 59 | -------------------------------------------------------------------------------- /trepanxpy/processor/command/vmstack_subcmd/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (C) 2021 Rocky Bernstein 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | """ Copyright (C) 2020 Rocky Bernstein """ 17 | 18 | import glob 19 | import os.path as osp 20 | 21 | # FIXME: Is it really helpful to "privatize" variable names below? 22 | # The below names are not part of the standard pre-defined names like 23 | # __name__ or __file__ are. 24 | 25 | # Get the name of our directory. 26 | __command_dir__ = osp.dirname(__file__) 27 | 28 | # A glob pattern that will get all *.py files but not __init__.py 29 | __py_files__ = glob.glob(osp.join(__command_dir__, "[a-z]*.py")) 30 | 31 | # Take the basename of the filename and drop off '.py'. That becomes 32 | # the list of modules that commands.py will use to import 33 | exclude_files = [] 34 | __modules__ = [ 35 | osp.basename(filename[0:-3]) 36 | for filename in __py_files__ 37 | if osp.basename(filename) not in exclude_files 38 | ] 39 | -------------------------------------------------------------------------------- /trepanxpy/processor/command/vmstack_subcmd/peek.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (C) 2021 Rocky Bernstein 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | 17 | from trepan.processor.command.base_subcmd import DebuggerSubcommand 18 | from trepan.processor.cmdfns import get_an_int 19 | 20 | 21 | # Class has to start out with Vmstack not VMStack or anything else, in order 22 | # for subcommand matching to work properly 23 | class VmstackPeek(DebuggerSubcommand): 24 | """**vmstack peek** *i* 25 | 26 | Look at VM evaluation stack entry *i* 27 | 28 | Examples 29 | -------- 30 | 31 | vmstack peek 1 32 | 33 | See Also 34 | -------- 35 | 36 | `vmstack pop`, `vmstack push` 37 | """ 38 | 39 | in_list = True 40 | min_abbrev = len('pe') 41 | min_args = 0 42 | max_args = 1 43 | need_stack = True 44 | short_help = "look at a VM evaluation stack entry" 45 | 46 | def run(self, args): 47 | if self.core.is_running(): 48 | proc = self.proc 49 | 50 | n = len(proc.vm.frame.stack) 51 | if n == 0: 52 | self.errmsg("VM evaluation stack is empty - nothing to peek at at") 53 | return 54 | 55 | if len(args) == 0: 56 | number = 0 57 | else: 58 | print(args) 59 | number = get_an_int( 60 | self.errmsg, 61 | args[0], 62 | "The 'peek' command requires an integer greater than 0.", 63 | 0, n-1 64 | ) 65 | if number is None: 66 | return 67 | # FIXME: vm.peek seems to be 1 origin. It should have been 0 origin I think. 68 | result = proc.vm.peek(number+1) 69 | self.msg(f"VM evaluation stack at {number}: {result} {type(result)}") 70 | return None 71 | 72 | pass 73 | 74 | if __name__ == '__main__': 75 | from trepan.processor.command.set_subcmd.__demo_helper__ import demo_run 76 | 77 | demo_run(VmstackPeek, []) 78 | pass 79 | -------------------------------------------------------------------------------- /trepanxpy/processor/command/vmstack_subcmd/pop.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (C) 2021 Rocky Bernstein 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | 17 | from trepan.processor.command.base_subcmd import DebuggerSubcommand 18 | 19 | 20 | # Class has to start out with Vmstack not VMStack or anything else, in order 21 | # for subcommand matching to work properly 22 | class VmstackPop(DebuggerSubcommand): 23 | """**vmstack pop** 24 | 25 | Pop a VM evaluation stack entry 26 | 27 | Examples 28 | -------- 29 | 30 | vmstack pop 31 | 32 | See Also 33 | -------- 34 | 35 | `vmstack push` 36 | """ 37 | 38 | in_list = True 39 | min_abbrev = len('po') 40 | max_args = 1 41 | need_stack = True 42 | short_help = "Pop a value off the VM evaluation stack" 43 | 44 | def run(self, args): 45 | if self.core.is_running(): 46 | proc = self.proc 47 | 48 | n = len(args) 49 | if n == 1: 50 | # FIXME add popping n entries 51 | count = 1 52 | else: 53 | count = 1 54 | 55 | result = proc.vm.frame.stack.pop() 56 | self.msg(f"VM stack popped {count} items: {result}, type {type(result)}") 57 | 58 | return None 59 | 60 | pass 61 | 62 | if __name__ == '__main__': 63 | from trepan.processor.command.set_subcmd.__demo_helper__ import demo_run 64 | 65 | demo_run(VmstackPop, []) 66 | pass 67 | -------------------------------------------------------------------------------- /trepanxpy/processor/command/vmstack_subcmd/push.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (C) 2021 Rocky Bernstein 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | 17 | from trepan.processor.command.base_subcmd import DebuggerSubcommand 18 | 19 | 20 | # Class has to start out with Vmstack not VMStack or anything else, in order 21 | # for subcommand matching to work properly 22 | class VmstackPush(DebuggerSubcommand): 23 | """**vmstack push** *value* 24 | 25 | Push a value onto the VM evaluation 26 | 27 | Examples 28 | -------- 29 | 30 | vmstack push "foo" 31 | 32 | See Also 33 | -------- 34 | 35 | `vmstack pop` 36 | """ 37 | 38 | in_list = True 39 | min_abbrev = len('pu') 40 | min_args = 1 41 | max_args = 1 42 | need_stack = True 43 | short_help = "Pop a value off the VM evaluation stack" 44 | 45 | def run(self, args): 46 | if self.core.is_running(): 47 | proc = self.proc 48 | 49 | text = args[0] 50 | proc.vm.push(eval(text)) 51 | self.msg(f"VM pushed: {text}") 52 | 53 | return None 54 | 55 | pass 56 | 57 | if __name__ == '__main__': 58 | from trepan.processor.command.set_subcmd.__demo_helper__ import demo_run 59 | 60 | demo_run(VmstackPush, ["abc"]) 61 | pass 62 | -------------------------------------------------------------------------------- /trepanxpy/processor/trace.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (C) 2020 Rocky Bernstein 3 | # 4 | # This program is free software; you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation; either version 2 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program; if not, write to the Free Software 16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 17 | # 02110-1301 USA. 18 | 19 | # Helper function for Processor. Put here so we 20 | # can use this in a couple of processors. 21 | 22 | from typing import Any, Optional, Callable 23 | 24 | from trepanxpy.fmt import format_instruction_with_highlight 25 | 26 | class XPyPrintProcessor(object): 27 | """ 28 | A processor that just prints out events as we see them. This 29 | is suitable for example for line/call tracing. We assume that the 30 | caller is going to filter out which events it wants printed or 31 | whether it wants any printed at all. 32 | """ 33 | 34 | def __init__(self, core_obj: Any, opts=None): 35 | self.core = core_obj 36 | self.debugger = core_obj.debugger 37 | return 38 | 39 | # FIXME: remove byteName and arguments and have the last instruction sent back 40 | def event_hook( 41 | self, 42 | event: str, 43 | offset: int, 44 | byteName: str, 45 | byteCode: int, 46 | line_number: int, 47 | intArg: Optional[int], 48 | event_arg: Any, 49 | vm: Any, 50 | prompt="trepan-xpy-trace", 51 | ) -> Optional[Callable]: 52 | "A simple event processor that prints out events." 53 | if vm.frame: 54 | if offset >= 0: 55 | print("%-12s - %s" % (event, 56 | format_instruction_with_highlight( 57 | vm.frame, 58 | vm.opc, 59 | byteName, 60 | intArg, 61 | event_arg, 62 | offset, 63 | line_number, 64 | extra_debug=False, 65 | settings=self.debugger.settings, 66 | show_line=True, 67 | vm=vm 68 | ))) 69 | else: 70 | frame = vm.frame 71 | lineno = frame.line_number() 72 | filename = self.core.canonic_filename(frame) 73 | filename = self.core.filename(filename) 74 | print("%s - %s:%d" % (event, filename, lineno)) 75 | else: 76 | print("%s" % (event,)) 77 | 78 | return self.event_hook 79 | 80 | pass 81 | -------------------------------------------------------------------------------- /trepanxpy/version.py: -------------------------------------------------------------------------------- 1 | # This file is needs to be multi-lingual in both Python and POSIX 2 | # shell which "exec()" or "source" it respectively. 3 | 4 | # This file should define a variable VERSION which we use as the 5 | # debugger version number. 6 | 7 | # fmt: off 8 | __version__="1.1.1" # noqa 9 | --------------------------------------------------------------------------------