摘要:反之,如果是使用并通過進(jìn)行調(diào)用,那么就應(yīng)當(dāng)進(jìn)行修改如果一個函數(shù)內(nèi)部有阻塞式的,那么改變這個函數(shù)是沒有用的,顯然要應(yīng)用改變的對象需要對象下一次被調(diào)用,這個不難理解但是容易漏想到
后端開發(fā)中有時會遇到這種情況:進(jìn)程運行中偶現(xiàn),重啟進(jìn)程問題就消失;或者是,進(jìn)程一定要運行一段時間才會出現(xiàn)問題;又或是,極難復(fù)現(xiàn)的問題出現(xiàn)了,然而已有的log不足以定位
對于這些情況,盡管大部分時候,我們可以通過在可能的地方加log,然后重啟進(jìn)程等待問題復(fù)現(xiàn),但這樣相對被動。我們都知道如果要調(diào)試C/C++程序,gdb attach上進(jìn)程就可以,而python雖然有相似的工具pdb,但它無法附加到一個進(jìn)程上,必須要用pdb啟動進(jìn)程,在實際環(huán)境中顯然不管用,那么python是否有類似的辦法來改變運行中進(jìn)程的代碼呢?這樣我們就可以通過實時加log來定位問題,這樣幾乎可以解決python層面的任何問題
可以參考兩篇文章:
https://mozillazg.com/2018/07...
https://mozillazg.com/2017/07...
簡單來說,可以直接用gdb使用類似調(diào)試c程序的方式,但要求python進(jìn)程是使用python-debug這種版本的python,同樣不夠?qū)嵱?。這里介紹博客中提到的“純gdb”的方式,通過github上一個開源python包pyrasite,本質(zhì)上是通過gdb的-eval-command和它的PyRun_SimpleString來向進(jìn)程注入代碼。
這個庫有一些附加功能,可以通過它的文檔去了解。這里只說實現(xiàn)進(jìn)程注入的核心,是其中一個很短的文件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)
這個函數(shù)做的事很簡單,不難看懂,所以,我們需要做的就是調(diào)用這個函數(shù),傳入pid和文件名,文件是一個你要對這個進(jìn)程執(zhí)行的python代碼?,F(xiàn)在我們運行一個很簡單的python進(jìn)程test.py:
import time def b(): print("b") while 1: b() time.sleep(1)
然后創(chuàng)建一個文件patch.py:
print("injecting") def newb(): print("new b") b = newb
在injector.py的末尾加上一段,以便接收命令行調(diào)用:
import sys pid = sys.argv[1] filename = sys.argv[2] inject(int(pid), filename)
通過ps aux|grep test.py查看上面進(jìn)程的pid,然后執(zhí)行python injector.py pid patch.py,為方便反復(fù)測試可以這樣:
pid=`ps aux | grep test.py | grep -v grep | awk "{print $2}"`;python injector.py $pid patch.py;echo $pid injected
輸出如下:
至此就實現(xiàn)了進(jìn)程注入。
注意點:
修改類或類方法和函數(shù)同理,改變類的方法時,直接使用類名classA.method = new_method會將變化應(yīng)用到所有實例,注意對類方法來說在patch.py中定義時也要加上self參數(shù)
在patch.py中,我們可以直接對b賦值,因為我們gdb進(jìn)入一個進(jìn)程后,所在的上下文環(huán)境就是該進(jìn)程的入口模塊,可以通過打印globals()來看到有哪些全局變量,這些就是可以直接訪問的對象。如果是在一個普通的業(yè)務(wù)進(jìn)程中,必然有大量import,這種情況下你需要import相應(yīng)模塊再對該模塊的函數(shù)或類進(jìn)行修改,如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 ..會將對象復(fù)制一份到本地命名空間。反之,如果A是使用import B并通過B.func進(jìn)行調(diào)用,那么就應(yīng)當(dāng)import B進(jìn)行修改
如果一個函數(shù)內(nèi)部有阻塞式的while True,那么改變這個函數(shù)是沒有用的,顯然要應(yīng)用改變的對象需要對象下一次被調(diào)用,這個不難理解但是容易漏想到
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/43988.html
摘要:在調(diào)試程序的時候,一般我們只能通過以下幾種方式進(jìn)行調(diào)試程序中已經(jīng)有的日志在代碼中插入但是以上的方法也有不方便的地方,比如對于已經(jīng)在運行中的程序,就不可能停止程序后加入調(diào)試代碼和增加新的日志從的項目得到靈感,嘗試對正在運行的進(jìn)程插入代碼,在程 在調(diào)試 Python 程序的時候,一般我們只能通過以下幾種方式進(jìn)行調(diào)試: 程序中已經(jīng)有的日志 在代碼中插入 import pdb; pdb.s...
摘要:背景這幾天一直在查一個線上程序住的問題這個程序總是在運行分鐘后住通過以下的一些調(diào)試手段發(fā)現(xiàn)是打日志的時候因為滿被了日志是默認(rèn)打到的無論日志級別而我這個程序是被另一個程序調(diào)起的父進(jìn)程沒有接收子進(jìn)程的導(dǎo)致了被打滿在調(diào)試的過程中用到了以下幾種調(diào)試 FROM http://kamushin.github.io/debug/python.html 背景 這幾天一直在查一個線上程序 hang 住的...
摘要:開源項目起因最近做病毒分析的時候遇到遠(yuǎn)控馬,需要記錄連接的遠(yuǎn)程地址用火絨劍或者可以看到一部分,但是我想要更全面的信息,于是搗鼓了和。使用比較簡單,但是只能看到的流量,雖然能捕獲所有流量,但沒法過濾特定進(jìn)程的包,而且過濾規(guī)則對我來說太復(fù)雜了。 開源項目QPA 起因最近做病毒分析的時候遇到遠(yuǎn)控馬,需要記錄連接的遠(yuǎn)程地址!用火絨劍或者ProcessMonitr可以看到一部分,但是我想要更全面...
閱讀 2712·2021-10-12 10:12
閱讀 2343·2021-09-02 15:41
閱讀 2577·2019-08-30 15:55
閱讀 1409·2019-08-30 13:05
閱讀 2443·2019-08-29 11:21
閱讀 3542·2019-08-28 17:53
閱讀 3034·2019-08-26 13:39
閱讀 808·2019-08-26 11:50