Resposta curta: pyrasite
pip install pyrasite
echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
pyrasite $PID dump_stacks.py
pyrasite-shell <pid>

Utilizando pyrasite para injetar código num console Python
Contexto Link to heading
Esses dias estava com um bug intermitente que só ocorria no servidor de desenvolvimento, então não estava conseguindo reproduzir localmente.
Tentando descobrir o problema, dei SSH para o servidor e vi que num processo estava justamente acontecendo o que eu queria corrigir.
Então comecei a tentar debugar o código que já estava em execução, sem ter que recomeçar o processo, pois se fizesse isso provavelmente o bug não iria ocorrer.
Foi assim que descobri o pyrasite e como debugar um processo Python que já está em execução.
Pyrasite Link to heading
Com o pyrasite conseguimos injetar código em um processo Python e debugar em tempo de execução.
Fonte: http://pyrasite.com/
Instalação Link to heading
pip install pyrasite
Habilitar o trace Link to heading
No Linux, por uma questão de segurança, o trace é desabilitado por padrão. Então temos que habilitar para conseguir debugar com o pyrasite.
echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
Recomendo voltar o ptrace_scope para o valor padrão (1) após o debug.
Mais informações sobre o ptrace_scope na documentação do Kernel.
Utilização Link to heading
Para utilizar o pyrasite chamamos com o ID do Processo (PID) e o script que queremos executar:
pyrasite $PID my_script.py
A ferramenta já vem com alguns scripts prontos para injetar no processo, são eles:
- dump_stacks.py: printa o stacktrace do código executado
- dump_modules.py: printa os módulos carregados (sys.modules)
- force_garbage_collection.py: força a execução do garbage collector
- helloworld.py: printa Hello World
dump_memory.py: depende de uma lib que não suporta Python 3
Além desses, também temos reverse_python_shell.py, reverse_shell.py, start_callgraph.py, stop_callgraph.py, mas esses eu não consegui usar e não fui muito a fundo.
Para, por exemplo, utilizar o dump_stacks.py, rodamos:
pyrasite $PID dump_stacks.py
Shell Link to heading
Além de executar scripts, também podemos habilitar um shell Python para depurar o que está acontecendo na aplicação. Com o shell conseguimos rodar comandos, acessar variáveis e tudo mais que fazemos num console Python
pyrasite-shell $PID
Troubleshooting Link to heading
- Rodei o pyrasite e não apareceu nenhum resultado
- O output do código injetado aparecerá no stdout do processo principal que está recebendo a injeção. Se o seu processo redireciona o stdout para um arquivo, a saída do código injetado também irá para o mesmo arquivo.