python代码使用dns进行调试_使用pyrasite进行python进程调试,改变运行中进程的代码...

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

偶现问题复现了, 然而已有的log不足以定位

进程运行很长时间才可能达到某个状态,需要基于该状态进行调试

等等

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

可以参考两篇文章:

简单来说,可以直接用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,需要import A; A.func = newfunc,像这样改变B是没有用的:import B; B.func = newfunc,因为from .. import ..会将对象复制一份到本地命名空间。反之,如果A是使用import B并通过B.func进行调用,那么就应当import B进行修改

如果一个函数内部有持续运行的while True,那么修改这个函数本身是不会生效的(但是修改循环体调用到的函数是有用的),因为要应用改变的对象显然需要对象(在这里指的是while True所在的函数)下一次被调用,这个不难理解但是容易漏想到

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值