使用pyrasite进行python进程调试,改变运行中进程的代码 - 编程思维

后台开发离不开debug代码, 而有时线上问题, 生产环境等无法debug, 这时候需要进程调试,由于python偏上层的性质, 一般的python开发相比c/c++开发来说,调试进程的需求要小很多,但也会有一些情况,常见的比如:

  • 生产环境问题, 不能轻易重启进程
  • 偶现问题复现了, 然而已有的log不足以定位
  • 进程运行很长时间才可能达到某个状态,需要基于该状态进行调试
    等等

对于这些情况,尽管大部分时候,我们可以通过在可能的地方加log,然后重启进程等待问题复现,但这样相对被动。我们都知道如果要调试C/C++程序,gdb attach上进程就可以,而python虽然有相似的工具pdb,但它无法附加到一个进程上,必须要用pdb启动进程,在实际环境中显然不管用,那么python是否有类似的办法来改变运行中python进程的代码呢?可以调试python进程的话,就几乎可以解决python层面的任何问题。

可以参考两篇文章:

https://mozillazg.com/2018/07...
https://mozillazg.com/2017/07...

简单来说,可以直接用gdb使用类似调试c程序的方式,但要求python进程是使用python-debug这种版本的python,同样不够实用。这里介绍博客中提到的“纯gdb”的方式,通过github上一个开源python包pyrasite,本质上是通过gdb的-eval-command和它的PyRun_SimpleString来向进程注入代码。

这个库有一些附加功能,可以通过它的文档去了解。这里只说实现进程注入的核心,是其中一个很短的文件injector.py,这里去掉了原文件中用于windows平台的一段代码,我们这里只考虑linux,核心代码如下:

import os
import subprocess
import platform

def inject(pid, filename, verbose=False, gdb_prefix=''):
    """Executes a file in a running Python process."""
    filename = os.path.abspath(filename)
    gdb_cmds = [
        'PyGILState_Ensure()',
        'PyRun_SimpleString("'
            'import sys; sys.path.insert(0, \\"%s\\"); '
            'sys.path.insert(0, \\"%s\\"); '
            'exec(open(\\"%s\\").read())")' %
                (os.path.dirname(filename),
                os.path.abspath(os.path.join(os.path.dirname(__file__), '..')),
                filename),
        'PyGILState_Release($1)',
        ]
    p = subprocess.Popen('%sgdb -p %d -batch %s' % (gdb_prefix, pid,
        ' '.join(["-eval-command='call %s'" % cmd for cmd in gdb_cmds])),
        shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    out, err = p.communicate()
    if verbose:
        print(out)
        print(err)
        

这个函数做的事很简单,不难看懂,所以,我们需要做的就是调用这个函数,传入pid和文件名,文件是一个你要对这个进程执行的python代码。现在我们运行一个很简单的python进程test.py

import time
def b():
  print('b')

while 1:
  b()
  time.sleep(1)
  

然后创建一个文件patch.py

print('injecting')
def newb():
  print('new b')
b = newb

injector.py的末尾加上一段,以便接收命令行调用:

import sys
pid = sys.argv[1]
filename = sys.argv[2]

inject(int(pid), filename)

通过ps aux|grep test.py查看上面进程的pid,然后执行python injector.py pid patch.py,为方便反复测试可以这样:

pid=`ps aux | grep test.py | grep -v grep | awk '{print $2}'`;python injector.py $pid patch.py;echo $pid injected

输出如下:

至此就实现了进程注入。

注意点:

  • 修改类或类方法和函数同理,改变类的方法时,直接使用类名classA.method = new_method会将变化应用到所有实例,注意对于实例方法, 在patch.py中定义时也要加上self参数
  • patch.py中,我们可以直接对b赋值,因为我们gdb进入一个进程后,所在的上下文环境就是该进程的入口模块,可以通过打印globals()来看到有哪些全局变量,这些就是可以直接访问的对象。如果是在一个普通的业务进程中,必然有大量import,这种情况下你需要import相应模块再对该模块的函数或类进行修改,如import x.y.z as z; z.b = newb
  • 特别需要注意的是,如果一个模块A使用了from B import func,那么如果你想改变A中运行的func,需要引入模块A并修改模块A中的func,修改B是没有用的,因为from .. import ..会将对象复制一份到本地命名空间。反之,如果A是使用import B并通过B.func进行调用,那么就应当修改模块B中的func
  • 如果一个函数内部有阻塞式的操作, 如socket监听, 或持续运行的while True循环等.那么修改这个函数本身是不会生效的(但是修改循环体内部调用到的函数是有用的),因为要应用改变的对象显然需要对象(在这里指的是while True所在的函数)下一次被调用,这个不难理解但是容易漏想到

版权声明:本文版权归作者所有,遵循 CC 4.0 BY-SA 许可协议, 转载请注明原文链接
https://segmentfault.com/a/1190000019447389

python里那些深不见底的“坑” - 编程思维

Python是一门清晰简洁的语言,如果你对一些细节不了解的话,就会掉入到那些深不见底的“坑”里,下面,我就来总结一些Python里常见的坑。 列表创建和引用 嵌套列表的创建 使用*号来创建一个嵌套的list: li = [[]] * 3 print(li) # Out: [[], [], [

同样是python,python3和python2怎么区别这么大? - 编程思维

上周,我的测试同事告诉我,你的用户名怎么还允许中文啊?当时我心里就想,你们测试肯定又搞错接口了,我用的是正则w过滤了参数,怎么可能出错,除非Python正则系统出错了,那是不可能的。本着严谨的作风,我自己先测试一下,没问题看我怎么怼回去。可是当我测试,我就懵逼了,中文真TM都验证通过,不对啊,我以前也是这么过滤参数的,

为什么要学习python编程语言?哪些人适合学习python? - 编程思维

先回答第一个被初学编程的朋友问到最多的问题,为什么要学习Python编程语言? 答:现在信息更新的非常快速,又迎来了大数据的时代, 各行各业如果不与时俱进,都将面临优胜劣汰,知识是不断的更新的,只有一技之长,才能立于不败之地。 学习Python编程语言,是大家走入编程世界的最理想选择,在初期入门阶段就可以自己动手做一些

mongodb指南---5、创建、删除文档 - 编程思维

上一篇文章:MongoDB指南---4、MongoDB基础知识-使用MongoDB Shell下一篇文章:MongoDB指南---6、更新文档 本章会介绍对数据库移入/移出数据的基本操作,具体包含如下操作: 向集合添加新文档; 从集合里删除文档; 更新现有文档; 为这些操作选择合适的安全级别和速度。 3.1 插入

mongodb指南---4、mongodb基础知识-使用mongodb shell - 编程思维

上一篇文章:MongoDB指南---3、MongoDB基础知识-数据类型下一篇文章:MongoDB指南---5、创建、删除文档 本节将介绍如何将shell作为命令行工具的一部分来使用,如何对shell进行定制,以及shell的一些高级功能。在上面的例子中,我们只是连接到了一个本地的mongod实例。事实上,可以将sh

mongodb指南---3、mongodb基础知识-数据类型 - 编程思维

上一篇文章:MongoDB指南---2、MongoDB基础知识-文档、集合、数据库、客户端下一篇文章:MongoDB指南---4、MongoDB基础知识-使用MongoDB Shell 本章开始部分介绍了文档的基本概念,现在你已经会启动、运行MongoDB,也会在shell中进行一些操作了。这一节的内容会更加深入。M

node程序debug小记 - 编程思维

有时候,所见并不是所得,有些包,你需要去翻他的源码才知道为什么会这样。 背景 今天调试一个程序,用到了一个很久之前的NPM包,名为formstream,用来将form表单数据转换为流的形式进行接口调用时的数据传递。 这是一个几年前的项目,所以使用的是Generator+co实现的异步流程。 其中有这样一个功能,

如何提高后台服务应用问题的排查效率?日志 vs 远程调试 - 编程思维

转眼间,距离Jerry最近一篇文章推送已经过去了一个多月的时间了。 公众号更新的频率降低,不是因为Jerry偷懒,而是由于从春节过后,我所在的SAP成都研究院数字创新空间整个团队,一直在忙一个5月份需要交付的项目上。 Jerry每天的工作量像下面这张图这样: 这个项目里Jerry负责的是后台开发工作,我用nodej

使用mtr排查服务器网络连通性 - 编程思维

问题ar414这两天ffmpeg推流的同事反应服务器推送视频流到A直播云的直播播放某个时间段经常卡顿排查mtr的使用在末尾同一台服务器将视频流分别推送到B云直播、A直播云对比播放流畅度,B直播云播放流畅,判断:问题不在推流服务器将问题反馈给A直播云,A直播云答复:推送不稳定排查对比推流服务器与A直播云、B直播云的网络联

linux-cppcheck静态代码检查.md - 编程思维

简介 cppcheck是一个C/C++的静态代码检查工具。它不仅可以检查代码中的语法错误,还可以检查出编译器检查不出来的缺陷,从而辅助提升代码质量。cppcheck能够发现很多错误,但不能发现所有的错误。 简单说明 首先使用如下命令安装: sudo apt install cppcheck cppcheck支持的检测功