摘要:在调试程序的时候,一般我们只能通过以下几种方式进行调试程序中已经有的日志在代码中插入但是以上的方法也有不方便的地方,比如对于已经在运行中的程序,就不可能停止程序后加入调试代码和增加新的日志从的项目得到灵感,尝试对正在运行的进程插入代码,在程
在调试 Python 程序的时候,一般我们只能通过以下几种方式进行调试:
程序中已经有的日志
在代码中插入 import pdb; pdb.set_trace()
但是以上的方法也有不方便的地方, 比如对于已经在运行中的程序, 就不可能停止程序后加入 调试代码和增加新的日志.
从 JAVA 的 BTrace(https://kenai.com/projects/btrace) 项目得到灵感,尝试对正在运行的 Python 进程插入代码,在程序运行到指定的函数后,自动连接远程主机进行调试
首先介绍三个开源的项目, 本实验需要用到这三个项目
Pyasite https://github.com/lmacken/pyrasite Tools for injecting code into running Python processes
Byteplay https://github.com/serprex/byteplay 一个字节码维护项目,类似 java的asm/cglib
Rpdb-Shell https://github.com/alex8224/Rpdb-Shell
待注入的代码, 用官方的 tornado hello demo 做例子
import tornado.ioloop import tornado.web import os class MainHandler(tornado.web.RequestHandler): def get(self): self.write("Hello, world") application = tornado.web.Application([ (r"/", MainHandler), ]) if __name__ == "__main__": application.listen(8888) print(os.getpid()) tornado.ioloop.IOLoop.instance().start()
注入以下代码(testinject.py)到 get 中
import sys import dis import inspect from byteplay import * def wearedcode(fcode): c = Code.from_code(fcode) if c.code[1] == (LOAD_CONST, "injected"): return fcode c.code[1:1] = [ (LOAD_CONST, injected"), (STORE_FAST, "name"), (LOAD_FAST, "name"), (PRINT_ITEM, None), (PRINT_NEWLINE, None), (LOAD_CONST, -1), (LOAD_CONST, None), (IMPORT_NAME, "rpdb"), (STORE_FAST, "rpdb"), (LOAD_FAST, "rpdb"), (LOAD_ATTR, "trace_to_remote"), (LOAD_CONST, "192.168.1.1"), (CALL_FUNCTION, 1), (POP_TOP, None) ] return c.to_code() def trace(frame, event, arg): if event != "call": return co = frame.f_code func_name = co.co_name if func_name == "write": return if func_name == "get": import tornado.web args = inspect.getargvalues(frame) if "self" in args.locals: if isinstance(args.locals["self"], tornado.web.RequestHandler): getmethod = args.locals["self"].get code = getmethod.__func__.__code__ getmethod.__func__.__code__ = wearedcode(code) return sys.settrace(trace)环境
ubuntu 14.04 64bit LTS
Python 2.7.6
步骤在机器上安装上面需要用到的三个项目
python server.py
在 192.168.1.1 执行 nc -l 4444
pyrasite $(ps aux |grep server.py |grep -v grep|awk "{print $2}") testinject.py
执行 curl http://localhost:8000 两次, 在第二次请求时替换的 bytecode 才会生效
结果在执行上面的步骤后, 在执行第二次 curl http://127.0.0.1:8000 后, 应该能够看到控制台输入 injected 的字样,并且 nc -l 4444 监听的终端会出现 (pdb)> 的字样, 这样就能够对正在运行中的程序进行调试了.
原理Pyasite 可以注入代码到运行中的 Python 进程,它利用了 Python 的 PyRun_SimpleString 这个API插入代码, 至于进程注入应该是使用了 ptrace
Byteplay 是一个可以维护 Python bytecode的工具, 这部分跟 cglib/asm类似
Pyasite 只能把代码注入到进程中并运行,不能定位到具体的函数并注入 bytecode, 在 testinject.py 中结合 Byteplay 完成了函数定位和替换 get 函数字节码的功能.
函数的定位用到了 sys.settrace 这个API,他提供了 call, line, return, exception事件,在合适的时机调用用户提供的函数, 具体可以参考 https://docs.python.org/2/library/sys.html#sys.settrace 的解释
理论上可以插入任意字节码到程序中的任意位置, 实现对现有进程中代码的任意修改.
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/37537.html
摘要:进程可创建多个线程来执行同一程序的不同部分。就绪等待线程调度。运行线程正常运行阻塞暂停运行,解除阻塞后进入状态重新等待调度。消亡线程方法执行完毕返回或者异常终止。多线程多的情况下,依次执行各线程的方法,前头一个结束了才能执行后面一个。 浅谈Python多线程 作者简介: 姓名:黄志成(小黄)博客: 博客 线程 一.什么是线程? 操作系统原理相关的书,基本都会提到一句很经典的话: 进程...
摘要:其次,解释器的主循环,一个名为的函数,读取字节码并逐个执行其中的指令。所有线程都运行相同的代码,并以相同的方式定期从它们获取锁定。无论如何,其他线程无法并行运行。 概述 如今我也是使用Python写代码好多年了,但是我却很少关心GIL的内部机制,导致在写Python多线程程序的时候。今天我们就来看看CPython的源代码,探索一下GIL的源码,了解为什么Python里要存在这个GIL,...
摘要:倘若该回答是正确的,则立即有如下推论在处理信号的过程中,字节码具有原子性。因此,除了在两个字节码之间,应该还有其他时机唤起了。行的是信号处理函数的最外层包装,由系统调用或注册至内核,并在信号发生时被内核回调,是异常控制流的入口。 写在前面 前几天工作时遇到了一个匪夷所思的问题。经过几次尝试后问题得以解决,但问题产生的原因却仍令人费解。查找 SO 无果,我决定翻看 Python 的源码。...
摘要:软件包存储库正成为供应链攻击的热门目标,和等流行存储库已经受到恶意软件攻击,研究人员称。当应用程序中的第三代码方库不能保持在最新状态时,对企业来说后果可能很严重。 .markdown-body{word-break:break-word;line-height:1.75;font-weight:400;font-size:15px;overflow-x:hidden;color:#333}...
摘要:摘要性能彪悍的引擎。深入浅出系列深入浅出第课箭头函数中的究竟是什么鬼深入浅出第课函数是一等公民是什么意思呢深入浅出第课什么是垃圾回收算法深入浅出第课是如何工作的最近,生态系统又多了个非常硬核的项目。 摘要: 性能彪悍的V8引擎。 《JavaScript深入浅出》系列: JavaScript深入浅出第1课:箭头函数中的this究竟是什么鬼? JavaScript深入浅出第2课:函数是一...
阅读 2754·2019-08-30 15:53
阅读 521·2019-08-29 17:22
阅读 1038·2019-08-29 13:10
阅读 2307·2019-08-26 13:45
阅读 2751·2019-08-26 10:46
阅读 3201·2019-08-26 10:45
阅读 2503·2019-08-26 10:14
阅读 466·2019-08-23 18:23