@@ -440,3 +440,44 @@ def open(cls, path_bytes: bytes, *args):
440
440
PythonSubstitutePath ()
441
441
# Define a customised open implementation in the libpython module to substitute filename paths.
442
442
setattr (libpython , "open" , PythonSubstitutePath .open )
443
+
444
+
445
+ class PyEval (gdb .Command ):
446
+ """
447
+ Evaluate a Python expression in the context of the inferior Python process at the currently
448
+ selected Python frame.
449
+ """
450
+
451
+ def __init__ (self ):
452
+ super ().__init__ ("py-eval" , gdb .COMMAND_RUNNING )
453
+
454
+ def invoke (self , arg , from_tty ):
455
+ eval_expression = arg .replace ("\" " , "\\ \" " )
456
+ try :
457
+ # First, find the frame of the currently selected Python frame. We need this to find the
458
+ # correct locals and globals. The PyEval_GetLocals and PyEval_GetGlobals functions are
459
+ # available but these provide the environment of the currently executing frame, not the
460
+ # selected frame.
461
+ frame_pointer = int (libpython .Frame .get_selected_python_frame ().get_pyop ()._gdbval )
462
+ except AttributeError :
463
+ print ("Unable to locate python frame" )
464
+ return
465
+ frame_var = "$_python_frame"
466
+ frame_expr = f"((struct _frame *){ frame_var } )"
467
+ # Set the frame
468
+ gdb .execute (f"set { frame_var } ={ frame_pointer } " )
469
+ run_arguments = [
470
+ f'"{ eval_expression } "' ,
471
+ # Py_eval_input
472
+ "258" ,
473
+ f"{ frame_expr } ->f_globals" ,
474
+ # PyFrame_FastToLocalsWithError must be called to populate the locals object in the
475
+ # frame. We use the comma operator to let us do this in the same inferior call as
476
+ # PyRun_String.
477
+ f"(PyFrame_FastToLocalsWithError({ frame_var } ), { frame_expr } ->f_locals)" ,
478
+ ]
479
+ run_command = f"p PyRun_String({ ', ' .join (run_arguments )} )"
480
+ gdb .execute (run_command , from_tty = True )
481
+
482
+ PyEval ()
483
+ gdb .execute ("alias -a pe = py-eval" )
0 commit comments