Skip to content

Instantly share code, notes, and snippets.

@flaneur2020
Created October 28, 2019 06:31
Show Gist options
  • Save flaneur2020/7794f6ca967ae61b74fc5f795147855f to your computer and use it in GitHub Desktop.
Save flaneur2020/7794f6ca967ae61b74fc5f795147855f to your computer and use it in GitHub Desktop.
stack.py
#!/usr/bin/env python
from __future__ import absolute_import, print_function
import os
import sys
import subprocess
import tempfile
import platform
import functools
import codecs
import locale
FILE_OPEN_COMMAND = r'f = open(\"%s\", \"w\")'
FILE_CLOSE_COMMAND = r'f.close()'
UTILITY_COMMANDS = [
r'itervalues = lambda d:getattr(d, \"itervalues\", d.values)()',
]
GREENLET_STACK_COMMANDS = [
r'import gc,greenlet,traceback',
r'objs=[ob for ob in gc.get_objects() if '
r'isinstance(ob,greenlet.greenlet) if ob]',
r'f.write(\"\\nDumping Greenlets....\\n\\n\\n\")',
r'f.write(\"\\n---------------\\n\\n\".join('
r'\"\".join(traceback.format_stack(o.gr_frame)) for o in objs))',
]
THREAD_STACK_COMMANDS = [
r'import gc,traceback,itertools,sys',
r'f.write(\"Dumping Threads....\\n\\n\\n\")',
r'f.write(\"\\n---------------\\n\\n\".join('
r'\"\".join(traceback.format_stack(o)) for o in '
r'itervalues(sys._current_frames())))',
]
def make_gdb_args(pid, command):
statements = [
r'call (void *) PyGILState_Ensure()',
r'call (void) PyRun_SimpleString("exec(r\"\"\"%s\"\"\")")' % command,
r'call (void) PyGILState_Release((void *) $1)',
]
arguments = ['gdb', '-p', str(pid), '-batch']
arguments.extend("-eval-command=%s" % s for s in statements)
return arguments
def make_lldb_args(pid, command):
statements = [
r'expr void * $gil = (void *) PyGILState_Ensure()',
r'expr (void) PyRun_SimpleString("exec(r\"\"\"%s\"\"\")")' % command,
r'expr (void) PyGILState_Release($gil)',
]
arguments = ['lldb', '-p', str(pid), '--batch']
arguments.extend('--one-line=%s' % s for s in statements)
return arguments
def print_stack(pid, include_greenlet=False, debugger=None, verbose=False):
"""Executes a file in a running Python process."""
# TextIOWrapper of Python 3 is so strange.
sys_stdout = getattr(sys.stdout, 'buffer', sys.stdout)
sys_stderr = getattr(sys.stderr, 'buffer', sys.stderr)
make_args = make_gdb_args
environ = dict(os.environ)
if (
debugger == 'lldb' or
(debugger is None and platform.system().lower() == 'darwin')
):
make_args = make_lldb_args
# fix the PATH environment variable for using built-in Python with lldb
environ['PATH'] = '/usr/bin:%s' % environ.get('PATH', '')
tmp_fd, tmp_path = tempfile.mkstemp()
os.chmod(tmp_path, 0o777)
commands = []
commands.append(FILE_OPEN_COMMAND)
commands.extend(UTILITY_COMMANDS)
commands.extend(THREAD_STACK_COMMANDS)
if include_greenlet:
commands.extend(GREENLET_STACK_COMMANDS)
commands.append(FILE_CLOSE_COMMAND)
command = r';'.join(commands)
args = make_args(pid, command % tmp_path)
process = subprocess.Popen(
args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = process.communicate()
if verbose:
sys_stderr.write(b'Standard Output:\n%s\n' % out)
sys_stderr.write(b'Standard Error:\n%s\n' % err)
sys_stderr.flush()
for chunk in iter(functools.partial(os.read, tmp_fd, 1024), b''):
sys_stdout.write(chunk)
sys_stdout.write(b'\n')
sys_stdout.flush()
CONTEXT_SETTINGS = {
'help_option_names': ['-h', '--help'],
}
def tolerate_missing_locale():
if codecs.lookup(locale.getpreferredencoding()).name != 'ascii':
return
# Dear Click, we really don't need any non-ascii output. Please don't
# crash yourself because you don't like the unicode design of Python 3.
# (http://click.pocoo.org/5/python3/#python-3-surrogate-handling)
os.environ.setdefault('LC_ALL', 'C.UTF-8')
os.environ.setdefault('LANG', 'C.UTF-8')
def main():
tolerate_missing_locale()
pid = 10
print_stack(pid, include_greenlet=True, debugger="gdb", verbose=True)
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment