用 Python 拓展 GDB(四)

歡迎來到《用python拓展gdb》的最後一篇。第一篇結尾,我提到了通用語言相對於領域特定語言的一項優點,即在處理數據上更加靈活。其實通用語言還有着另外一樣優點,領域特定語言只能侷限在宿主程序中使用,而通用語言則無此限制。對於通用語言來講,gdb暴露的接口不過是又一個庫而已。python

在本篇中,咱們會把python看成一門「膠水語言」,A面是gdb的接口,B面是一個終端界面的程序。姑且把這個終端界面程序稱之爲gti(gdb's terminal interface)吧。咱們會實現從gdb到gti的單向數據傳輸。每當gdb觸發斷點時,就在gti上自動輸出各項相關信息。這二者間的通信使用UDP協議。換言之,接下來要完成的是一個位於gdb內部UDP客戶端,和監聽指定端口的帶終端界面的UDP服務端。json

gdb 端實現

gdb端功能以下:socket

  1. 每當斷點被觸發時,經過gdb接口獲取info breakpointsinfo args,以及info locals三者的值async

  2. 把上述三者的值轉換成json格式工具

  3. 經過UDP協議發送到端口9876oop

功能要求看上去不少,不過實現成代碼其實也就二三十行:ui

import json
import socket
import gdb


HOST = 'localhost'
PORT = 9876
SOCK = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
SOCK.connect((HOST, PORT))

def send_data(event):
    cur = event.breakpoints[0].location
    if cur is None:
        cur = event.breakpoints[0].expr
    local_vars = gdb.execute('info locals', to_string=True)
    args = gdb.execute('info args', to_string=True)
    bps = gdb.execute('info breakpoints', to_string=True)
    data = {
        'current': cur,
        'locals': local_vars,
        'args': args,
        'breakpoints': bps
    }
    data = json.dumps(data)
    SOCK.send(bytes(data, 'utf-8'))


gdb.events.stop.connect(send_data)

在此以前,須要設置一個監聽9876端口的服務端,否則客戶端這邊就創建不了鏈接。運行nc -l 9876做爲服務端的mock,暫時只需觀察下發送過來的數據是否正確。調試

寫一個自動化腳本,讓gdb設置若干斷點並運行,連續執行屢次continue。你應該能夠觀察到接連有數據顯示在nc的輸出中:code

$ nc -l 9876
{"locals": "pointers = ...

gti 端實現

gti 端功能以下:server

  1. 監聽端口9876

  2. 每當收到數據包時,提取出json格式的數據

  3. 根據收到的數據,重繪當前界面

在繪製終端界面時,我用的是自帶的curses模塊。在監聽端口方面,我用的是python3.4以後纔有的async模塊。固然蘿蔔白菜,各有所愛,大可改用你本身喜歡的庫。

#!/usr/bin/env python3
import asyncio
import curses
import json

def main():
    loop = asyncio.get_event_loop()
    # 1. 監聽端口9876
    server = loop.create_datagram_endpoint(
        GtiProtocol, local_addr=('127.0.0.1', 9876))
    try:
        loop.run_until_complete(server)
        loop.run_forever()
    except KeyboardInterrupt:
        pass
    finally:
        curses.endwin()


class GtiProtocol(asyncio.Protocol):
    def __init__(self):
        self.ui = TextPad()

    def datagram_received(self, byte, _):
        "2. 將收到的數據從byte轉成json"
        data = byte.decode()
        data = json.loads(data)
        self.ui.display(data)


class TextPad:
    def __init__(self):
        self.pad = curses.initscr()
        curses.start_color()

    def _addstr(self, text):
        self.pad.addstr(text, curses.A_BOLD)

    def display(self, data):
        "3. 根據給定的數據重繪界面"
        try:
            self.pad.erase()
            self._addstr('current: %s\n\n' % data['current'])
            for key, value in data.items():
                if key != 'current':
                    self._addstr('%s:\n' % key)
                    self._addstr(value)
                    self._addstr('\n')
            self.pad.refresh()
        except curses.error:
            pass


main()

如今能夠用./gti.py來替換掉nc -l 9876,再從新運行gdb。你應該能看到,每當有新的斷點觸發時,./gti.py就會應用新的數據繪製界面。

順便一提,使用curses模塊純粹是爲了方便示範。curses提供的接口過於底層,許多細節方面都須要本身去摳。若是真的要開發實際可用的終端界面程序,建議使用諸如urwid這樣的第三方包。

小結

如上面的例子所示,咱們成功地用python實現了內嵌於gdb的客戶端。該客戶端能夠向外界暴露出gdb調試時的信息。依據一樣的思路,咱們也能夠在gdb內實現內嵌的服務端,這樣外界就能動態修改gdb調試的方式。固然,這一切離不開python這把「瑞士軍刀」。

《用python拓展gdb》系列到此就結束了。若是你正準備編寫一個拓展,但願本教程能夠教會相關的知識。若是你是一位C/C++開發者,但願本教程可以讓你的工具箱增添新道具。若是你是想了解更多關於gdb調試的信息,但願從此遇到相關問題時能想起編寫python拓展予以解決。

相關文章
相關標籤/搜索