欢迎来到《用python拓展gdb》的最后一篇。第一篇结尾,我提到了通用语言相对于领域特定语言的一项优点,即在处理数据上更加灵活。其实通用语言还有着另外一样优点,领域特定语言只能局限在宿主程序中使用,而通用语言则无此限制。对于通用语言来讲,gdb暴露的接口不过是又一个库而已。python
在本篇中,咱们会把python看成一门“胶水语言”,A面是gdb的接口,B面是一个终端界面的程序。姑且把这个终端界面程序称之为gti(gdb's terminal interface)吧。咱们会实现从gdb到gti的单向数据传输。每当gdb触发断点时,就在gti上自动输出各项相关信息。这二者间的通信使用UDP协议。换言之,接下来要完成的是一个位于gdb内部UDP客户端,和监听指定端口的带终端界面的UDP服务端。json
gdb端功能以下:socket
每当断点被触发时,经过gdb接口获取info breakpoints
和info args
,以及info locals
三者的值async
把上述三者的值转换成json格式工具
经过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 端功能以下:server
监听端口9876
每当收到数据包时,提取出json格式的数据
根据收到的数据,重绘当前界面
在绘制终端界面时,我用的是自带的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拓展予以解决。