摘要:欢迎来到用拓展的最后一篇。对于通用语言来说,暴露的接口不过是又一个库而已。这两者间的通讯使用协议。该客户端可以向外界暴露出调试时的信息。用拓展系列到此就结束了。
欢迎来到《用python拓展gdb》的最后一篇。第一篇结尾,我提到了通用语言相对于领域特定语言的一项优势,即在处理数据上更加灵活。其实通用语言还有着另一样优势,领域特定语言只能局限在宿主程序中使用,而通用语言则无此限制。对于通用语言来说,gdb暴露的接口不过是又一个库而已。
在本篇中,我们会把python当作一门“胶水语言”,A面是gdb的接口,B面是一个终端界面的程序。姑且把这个终端界面程序称之为gti(gdb"s terminal interface)吧。我们会实现从gdb到gti的单向数据传输。每当gdb触发断点时,就在gti上自动输出各项相关信息。这两者间的通讯使用UDP协议。换言之,接下来要完成的是一个位于gdb内部UDP客户端,和监听指定端口的带终端界面的UDP服务端。
gdb 端实现gdb端功能如下:
每当断点被触发时,通过gdb接口获取info breakpoints和info args,以及info locals三者的值
把上述三者的值转换成json格式
通过UDP协议发送到端口9876
功能要求看上去很多,不过实现成代码其实也就二三十行:
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的输出中:
$ nc -l 9876 {"locals": "pointers = ...gti 端实现
gti 端功能如下:
监听端口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 " % data["current"]) for key, value in data.items(): if key != "current": self._addstr("%s: " % key) self._addstr(value) self._addstr(" ") 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拓展予以解决。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/38011.html
摘要:在末尾,我提到了也可以用来实现拓展脚本。其中最为常用的是和。接受一个字符串作为表达式,并以的形式返回表达式求值的结果。当触发断点或收到信号时,就会调用事先注册的回调函数。对应的,撤销回调函数的接口是。本教程剩余部分会提及这一点。 之前写的《GDB 自动化操作的技术》一文介绍了可在gdb内部使用的DSL(领域特定语言)来自动化gdb的操作。借助该DSL,我们分别实现了一个名为mv的自定义...
摘要:欢迎来到用拓展的第三篇。它们必须以开头,以此区别于来自于上下文的函数。提供的基类名为。不过有一个区别是,的方法通常会返回一个对象,表示调用该函数后的返回值。它不能像通常意义上的函数独立使用,只能跟某个命令搭配。具体实现参见用拓展第一篇。 欢迎来到《用python拓展gdb》的第三篇。上一篇我们谈到了pretty printer,一个需要python支持的特性。这一篇我们谈论另一个需要p...
摘要:欢迎来到用拓展的第二篇。到目前为止,我们都是在用实现内置领域特定语言也能实现的效果。这就是的全部要求了。构造函数接收一个表示被打印的的必选。在后被调用,可用于打印复杂的成员。能通过来自定义打印方式,无疑为的使用打开新的大门。 欢迎来到《用python拓展gdb》的第二篇。在上一篇,我们学习了gdb提供的常用python接口,并用python实现了自定义命令和调试脚本。 到目前为止,我们...
摘要:背景这几天一直在查一个线上程序住的问题这个程序总是在运行分钟后住通过以下的一些调试手段发现是打日志的时候因为满被了日志是默认打到的无论日志级别而我这个程序是被另一个程序调起的父进程没有接收子进程的导致了被打满在调试的过程中用到了以下几种调试 FROM http://kamushin.github.io/debug/python.html 背景 这几天一直在查一个线上程序 hang 住的...
阅读 1810·2021-08-13 15:06
阅读 3100·2021-08-05 10:02
阅读 3365·2019-08-30 15:55
阅读 2378·2019-08-30 13:46
阅读 2485·2019-08-30 13:01
阅读 1323·2019-08-29 17:17
阅读 2824·2019-08-29 15:27
阅读 1431·2019-08-29 11:12