Last active
May 13, 2023 06:55
-
-
Save phagenlocher/d529b4052d261575f9d18f425590e6ec to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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