Skip to content

Instantly share code, notes, and snippets.

@phagenlocher
Last active May 13, 2023 06:55
Show Gist options
  • Save phagenlocher/d529b4052d261575f9d18f425590e6ec to your computer and use it in GitHub Desktop.
Save phagenlocher/d529b4052d261575f9d18f425590e6ec to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3
import code
import ctypes
import inspect
import logging
def hook(
banner_msg: str | None = None,
overwrite_locals: bool = False,
logger: str = "root",
):
"""Hooks into the caller, starting an interactive shell.
Copies globals and locals from the caller into the shell and local
variables from the shell will be copied back to the caller.
Args:
banner_msg:
Additional message to display on shell startup
overwrite_locals:
If `True`, overwrites locals in the caller function with locals
from the interactive shell
logger:
Name of the logger to use for reporting the hook
"""
# Get stack frame for caller
stack = inspect.stack()[1]
frame = stack.frame
# Set up log function
def log(msg: str):
logging.getLogger(logger).handle(
logging.LogRecord(
name=logger,
level=logging.DEBUG,
pathname=stack.filename,
lineno=stack.lineno,
msg=msg,
args=None,
exc_info=None,
func=stack.function,
)
)
# Log start of REPL
log("Starting interactive shell.")
# Copy locals and globals of caller's stack frame
locals_copy = dict(frame.f_locals)
globals_copy = dict(frame.f_globals)
shell_locals = dict()
shell_locals.update(globals_copy)
shell_locals.update(locals_copy)
# Format banner
banner = f"Hooked at {stack.filename}:{stack.lineno} - Exit with CTRL+D"
if banner_msg:
banner = f"{banner_msg}\n{banner}"
# Start interactive shell
code.interact(banner, local=shell_locals, exitmsg="")
# Update caller's locals with modified locals from shell
if overwrite_locals:
locals_to_update = dict()
for key, value in shell_locals.items():
if key in locals_copy:
locals_to_update[key] = value
frame.f_locals.update(locals_to_update)
ctypes.pythonapi.PyFrame_LocalsToFast(ctypes.py_object(frame), ctypes.c_int(0))
# Log exit from REPL
log("Returned from interactive shell. Resuming execution.")
# Delete frame to avoid cyclic references
del stack
del frame
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment