add by zhj: 雖然本文是以Ubuntu Python2.7爲例,但基本也適用於Ubuntu Python3,但有兩點不同
1. apt intall python<3.x>-dbg,這裏的3.x是你要調試的Python程序使用的Python版本。如果你有兩個Python程序需要調試,一個使用Python3.7,另一個使用Python3.9,那需要安裝python3.7-dbg和python3.9-dbg
2. 不需要設置/proc/sys/kernel/yama/ptrace_scope,而是修改~/.gdbinit,如果該文件不存在,那創建即可。寫入內容如下。對於多個python版本,路徑之間用冒號:分隔,親測
add-auto-load-safe-path <你的Python程序使用的可執行文件"python"的全路徑>
原文:https://mozillazg.com/2017/07/debug-running-python-process-with-gdb.html
作者:mozillazg
假設一個服務器上運行了下面這樣的 test.py 程序,我們怎樣才能知道程序是否在正常運行,運行到哪一步了呢?
import time def do(x): time.sleep(10) def main(): for x in range(10000): do(x) if __name__ == '__main__': main()
這個程序既沒有日誌也沒有 print 輸出,通過查看日誌文件/標準輸出/標準錯誤是沒有辦法確認程序狀況的。 一種可行的辦法就是使用 gdb 來查看程序當前的運行狀況。
測試環境
- 系統: Ubuntu 16.04.1 LTS
- Python: 2.7.12
準備工作
安裝 gdb 和 python2.7-dbg:
sudo apt-get install gdb python2.7-dbg
設置 /proc/sys/kernel/yama/ptrace_scope:
echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
運行 test.py:
$ python test.py & [1] 6489
通過 gdb python PID 來調試運行中的進程:
$ gdb python 6489 GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.04) 7.11.1 ... For help, type "help". Type "apropos word" to search for commands related to "word"... Reading symbols from python...Reading symbols from /usr/lib/debug/.build-id/90/d1300febaeb0a626baa2540d19df2416cd3361.debug...done. done. ... Reading symbols from /lib/ld-linux.so.2...Reading symbols from /usr/lib/debug//lib/i386-linux-gnu/ld-2.23.so...done. done. 0xb778fc31 in __kernel_vsyscall () (gdb)
生成 core file
爲了不影響運行中的進程,可以通過生成 core file 的方式來保存進程的當前信息:
(gdb) generate-core-file warning: target file /proc/6489/cmdline contained unexpected null characters Saved corefile core.6489 (gdb) quit A debugging session is active. Inferior 1 [process 6489] will be detached. Quit anyway? (y or n) y
可以通過 gdb python core.PID 的方式來讀取 core file:
$ gdb python core.6489 GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.04) 7.11.1 ... Type "apropos word" to search for commands related to "word"... Reading symbols from python...Reading symbols from /usr/lib/debug/.build-id/90/d1300febaeb0a626baa2540d19df2416cd3361.debug...done. done. warning: core file may not match specified executable file. [New LWP 6489] [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/i386-linux-gnu/libthread_db.so.1". Core was generated by `python'. #0 0xb778fc31 in __kernel_vsyscall () (gdb)
可用的 python 相關的命令
可以通過輸入 py 然後加 tab 鍵的方式來查看可用的命令:
(gdb) py py-bt py-down py-locals py-up python-interactive py-bt-full py-list py-print python
可以通過 help cmd 查看各個命令的說明:
(gdb) help py-bt Display the current python frame and all the frames within its call stack (if any)
當前執行位置的源碼
(gdb) py-list 1 # -*- coding: utf-8 -*- 2 import time 3 4 5 def do(x): >6 time.sleep(10) 7 8 9 def main(): 10 for x in range(10000): 11 do(x) (gdb)
可以看到當前正在執行 time.sleep(10)
當前位置的調用棧
(gdb) py-bt Traceback (most recent call first): <built-in function sleep> File "test.py", line 6, in do time.sleep(10) File "test.py", line 11, in main do(x) File "test.py", line 15, in <module> main() (gdb)
可以看出來是 main() -> do(x) -> time.sleep(10)
查看變量的值
(gdb) py-list 1 # -*- coding: utf-8 -*- 2 import time 3 4 5 def do(x): >6 time.sleep(10) 7 8 9 def main(): 10 for x in range(10000): 11 do(x) (gdb) py-print x local 'x' = 12 (gdb) (gdb) py-locals x = 12 (gdb)
查看上層調用方的信息
(gdb) py-up #9 Frame 0xb74c0994, for file test.py, line 11, in main (x=12) do(x) (gdb) py-list 6 time.sleep(10) 7 8 9 def main(): 10 for x in range(10000): >11 do(x) 12 13 14 if __name__ == '__main__': 15 main() (gdb) py-print x local 'x' = 12 (gdb)
可以通過 py-down 回去:
(gdb) py-down #6 Frame 0xb74926e4, for file test.py, line 6, in do (x=12) time.sleep(10) (gdb) py-list 1 # -*- coding: utf-8 -*- 2 import time 3 4 5 def do(x): >6 time.sleep(10) 7 8 9 def main(): 10 for x in range(10000): 11 do(x) (gdb)
調試多線程程序
測試程序 test2.py:
# -*- coding: utf-8 -*- from threading import Thread import time def do(x): x = x * 3 time.sleep(x * 60) def main(): threads = [] for x in range(1, 3): t = Thread(target=do, args=(x,)) t.start() for x in threads: x.join() if __name__ == '__main__': main()
$ python test2.py &
[2] 12281
查看所有線程
info threads
如下
$ gdb python core.12281 (gdb) info threads Id Target Id Frame * 1 Thread 0xb74b9700 (LWP 11039) 0xb7711c31 in __kernel_vsyscall () 2 Thread 0xb73b8b40 (LWP 11040) 0xb7711c31 in __kernel_vsyscall () 3 Thread 0xb69ffb40 (LWP 11041) 0xb7711c31 in __kernel_vsyscall () (gdb)
可以看到這個程序當前有 3 個線程, 當前進入的是 1 號線程。
切換線程
# 線程序號即info threads的Id列
thread <線程序號>
如下
(gdb) thread 3 [Switching to thread 3 (Thread 0xb69ffb40 (LWP 11041))] #0 0xb7711c31 in __kernel_vsyscall () (gdb) info threads Id Target Id Frame 1 Thread 0xb74b9700 (LWP 11039) 0xb7711c31 in __kernel_vsyscall () 2 Thread 0xb73b8b40 (LWP 11040) 0xb7711c31 in __kernel_vsyscall () * 3 Thread 0xb69ffb40 (LWP 11041) 0xb7711c31 in __kernel_vsyscall () (gdb)
現在切換到了 3 號線程。
可以通過前面所說的 py- 命令來查看當前線程的其他信息:
[Current thread is 1 (Thread 0xb74b9700 (LWP 11039))] (gdb) py-list 335 waiter.acquire() 336 self.__waiters.append(waiter) 337 saved_state = self._release_save() 338 try: # restore state no matter what (e.g., KeyboardInterrupt) 339 if timeout is None: >340 waiter.acquire() 341 if __debug__: 342 self._note("%s.wait(): got it", self) 343 else: 344 # Balancing act: We can't afford a pure busy loop, so we 345 # have to sleep; but if we sleep the whole timeout time, (gdb) thread 2 [Switching to thread 2 (Thread 0xb73b8b40 (LWP 11040))] #0 0xb7711c31 in __kernel_vsyscall () (gdb) py-list 3 import time 4 5 6 def do(x): 7 x = x * 3 >8 time.sleep(x * 60) 9 10 11 def main(): 12 threads = [] 13 for x in range(1, 3): (gdb)
同時操作所有線程
thread apply all CMD 或 t a a CMD
如下
(gdb) thread apply all py-list Thread 3 (Thread 0xb69ffb40 (LWP 11041)): 3 import time 4 5 6 def do(x): 7 x = x * 3 >8 time.sleep(x * 60) 9 10 11 def main(): 12 threads = [] 13 for x in range(1, 3): Thread 2 (Thread 0xb73b8b40 (LWP 11040)): 3 import time 4 5 6 def do(x): 7 x = x * 3 >8 time.sleep(x * 60) 9 10 11 def main(): 12 threads = [] 13 for x in range(1, 3): ---Type <return> to continue, or q <return> to quit--- Thread 1 (Thread 0xb74b9700 (LWP 11039)): 335 waiter.acquire() 336 self.__waiters.append(waiter) 337 saved_state = self._release_save() 338 try: # restore state no matter what (e.g., KeyboardInterrupt) 339 if timeout is None: >340 waiter.acquire() 341 if __debug__: 342 self._note("%s.wait(): got it", self) 343 else: 344 # Balancing act: We can't afford a pure busy loop, so we 345 # have to sleep; but if we sleep the whole timeout time, (gdb)
常用的 gdb python 相關的操作就是這些, 同時也不要忘記原來的 gdb 命令都是可以使用的哦。