美化显示LLDB调试的数据结构

前面的博文美化显示GDB调试的数据结构介绍了如何美化显示GDB中调试的数据结构,本文将还是以mupdf库为例介绍如何美化显示LLDB中调试的数据结构。

先看一下美化后的效果:

在这里插入图片描述

一、加载自定义脚本

与GDB类似,需要添加一个~/.lldbinit文件,可以在其中添加一些LLDB的命令,笔者的内容为:

setting set target.x86-disassembly-flavor intel
command script import C:/Users/admin/lldb/mupdf_printer.py

第一行设置LLDB反汇编格式为intel,第二行则是执行我们自定义的美化显示脚本mupdf_printer.py,如果是在VSCode中使用codelldb插件,则可以不用写第二行,而在.vscode/launch.json中设置调试配置的initCommands来加载脚本:

	{"type": "lldb","request": "launch","name": "(lldb) 启动","program": "${workspaceFolder}/build/t.exe","args": [],"cwd": "${workspaceFolder}","initCommands": ["command script import ${workspaceFolder}/lldbscripts/mupdf_printer.py",]},

二、写测试代码

与博文美化显示GDB调试的数据结构中的测试代码一样。

三、写LLDB的python脚本

1. 向LLDB注册pdf_obj类型

LLDB必须使用__lldb_init_module(debugger : lldb.SBDebugger, internal_dict : dict)签名的函数来处理类型的注册。

注册分为概要summary类型)和混合器synthetic )类型。概要类型用于不需要展开即可显示的信息;而混合器类型用于展开时显示的信息,比如数组、字典等等。

def __lldb_init_module(debugger : lldb.SBDebugger, internal_dict : dict):debugger.HandleCommand(r'type summary add -x "^pdf_obj.*\*" --python-function mupdf_printer.PDFObjAPISummary')debugger.HandleCommand(r'type synthetic add -x "^pdf_obj.*\*" --python-class mupdf_printer.PDFObjAPIPrinter')print("MuPDF pdf_obj summary and synthetic provider (via API) loaded.")

2.写美化输出代码

根据LLDB的Python协议,概要类型的处理只能是一个函数,而混合器类型的处理是一个类。
所以pdf_obj的概要类型处理函数如下:

def PDFObjAPISummary(val : lldb.SBValue, internal_dict : dict):try:addr = val.GetValueAsAddress()if not addr:return "<null>"ref = ""if call_pdf_api("pdf_is_indirect", val):num = call_pdf_api(f"pdf_to_num", val)#gen = call_pdf_api(f"pdf_to_gen", valobj)ref = f"<Ref {num}> => "kind = detect_pdf_obj_kind(val)if kind == "null":return f"{ref}<null>"elif kind == "int":return f"{ref}{call_pdf_api("pdf_to_int", val)}"elif kind == "real":return f"{ref}{call_pdf_api(f"pdf_to_real", val, float)}"elif kind == "bool":v = call_pdf_api(f"pdf_to_bool", val)return f"{ref}{'true' if v else 'false'}"elif kind == "string":return f'{ref}{call_pdf_api("pdf_to_text_string", val, str)}'elif kind == "name":v = call_pdf_api("pdf_to_name", val, str)return f'{ref}/{v.strip('"')}'elif kind == "array":length = call_pdf_api("pdf_array_len", val)return f"{ref}[size]={length}"elif kind == "dict":length = call_pdf_api(f"pdf_dict_len", val)return f"{ref}[pairs]={length}"return f"{ref}{addr}"except Exception as e:return f"<error: {e}>"

混合器类型的处理函数如下:

class PDFObjAPIPrinter:def __init__(self, val : lldb.SBValue, internal_dict : dict):self.val = valself.kind = detect_pdf_obj_kind(val)self.size = self.num_children()def has_children(self):# 只在array/dict类型时允许展开return self.kind in ["array", "dict"]def num_children(self):if self.kind == "array":length = call_pdf_api(f"pdf_array_len", self.val)return int(length) if length else 0elif self.kind == "dict":length = call_pdf_api(f"pdf_dict_len", self.val)return int(length) if length else 0return 0def get_child_at_index(self, index):try:if index < 0 or index >= self.size:return Noneif self.kind == "array":v = call_pdf_api_1(f"pdf_array_get", self.val, index, object)# 根据索引取到pdf_obj对象了,需要获取其地址addr = v.GetValueAsAddress()# 再构造一个表达式,将这个地址强制转为pdf_obj的指针expr = f"(pdf_obj *){addr}"# 最后根据这个表达式创建一个新的值,LLDB会自动重新根据规则显示这个值return self.val.CreateValueFromExpression(f"[{index}]", expr)elif self.kind == "dict":key = call_pdf_api_1("pdf_dict_get_key", self.val, index, object)val = call_pdf_api_1("pdf_dict_get_val", self.val, index, object)# 将pdf_obj中字典的Key一定是一个name,取name的值key_str = call_pdf_api("pdf_to_name", key, str).strip('"')# 将字典的value取地址,构造一个新的表达式addr = val.GetValueAsAddress()expr = f"(pdf_obj *){addr}"# 最后根据这个表达式创建一个新的值,LLDB会自动重新根据规则显示这个值return self.val.CreateValueFromExpression(f"[/{key_str}]", expr)except Exception as e:print(f"Error in get_child_at_index: {e}")return None

代码中同样需要一些辅助函数,就不再一一列举了,直接给出完整代码。

3. 完整代码

mupdf_printer.py

import lldbpdf_ctx : int | None = Nonedef get_mupdf_version_from_symbol(target : lldb.SBTarget):try:version = target.EvaluateExpression('(const char*)mupdf_version')return version.GetSummary()except Exception as e:return f"<symbol not found: {e}>"def call_mupdf_api(func_name : str, val : lldb.SBValue, retType : type, *args):try:target = val.GetTarget()addr = val.GetValueAsAddress()  # 使用GetValueAsAddress获取指针值cast = {int: "(int)",float: "(float)",str: "(const char*)",  # for functions returning const char*object: "(pdf_obj *)",  # for pdf_obj pointers}.get(retType, "(void)")global pdf_ctxif pdf_ctx is None:ver = get_mupdf_version_from_symbol(target)print(f"[LLDB] MuPDF version: {ver}")ctx : lldb.SBValue = target.EvaluateExpression(f"(fz_context*)fz_new_context_imp(0,0,0,{ver})")pdf_ctx = ctx.GetValueAsAddress()  # 保存为整数地址if args:args_str = ', '.join([str(arg) for arg in args])expr = f"{cast}{func_name}((fz_context*){pdf_ctx},{addr}, {args_str})"else:expr = f"{cast}{func_name}((fz_context*){pdf_ctx},(pdf_obj*){addr})"result : lldb.SBValue = target.EvaluateExpression(expr)if retType == int:return int(result.GetValue())elif retType == float:return float(result.GetValue())elif retType == str:return result.GetSummary()else:return resultexcept Exception as e:print(f"<error calling {func_name}: {e}>")return f"<error calling {func_name}: {e}>"def call_pdf_api(func_name : str, val : lldb.SBValue, rettype=int):return call_mupdf_api(func_name, val, rettype)def call_pdf_api_1(func_name : str, val : lldb.SBValue, arg, rettype=int):return call_mupdf_api(func_name, val, rettype, arg)# 检测除间隔引用外的数据类型
def detect_pdf_obj_kind(val : lldb.SBValue):try:if call_pdf_api("pdf_is_null", val):return "null"elif call_pdf_api("pdf_is_int", val):return "int"elif call_pdf_api("pdf_is_real", val):return "real"elif call_pdf_api("pdf_is_bool", val):return "bool"elif call_pdf_api("pdf_is_string", val):return "string"elif call_pdf_api("pdf_is_name", val):return "name"elif call_pdf_api("pdf_is_array", val):return "array"elif call_pdf_api("pdf_is_dict", val):return "dict"return "unknown"except Exception as e:print(f"<error detecting pdf_obj kind: {e}>")return "<error>"def PDFObjAPISummary(val : lldb.SBValue, internal_dict : dict):try:addr = val.GetValueAsAddress()if not addr:return "<null>"ref = ""if call_pdf_api("pdf_is_indirect", val):num = call_pdf_api(f"pdf_to_num", val)#gen = call_pdf_api(f"pdf_to_gen", valobj)ref = f"<Ref {num}> => "kind = detect_pdf_obj_kind(val)if kind == "null":return f"{ref}<null>"elif kind == "int":return f"{ref}{call_pdf_api("pdf_to_int", val)}"elif kind == "real":return f"{ref}{call_pdf_api(f"pdf_to_real", val, float)}"elif kind == "bool":v = call_pdf_api(f"pdf_to_bool", val)return f"{ref}{'true' if v else 'false'}"elif kind == "string":return f'{ref}{call_pdf_api("pdf_to_text_string", val, str)}'elif kind == "name":v = call_pdf_api("pdf_to_name", val, str)return f'{ref}/{v.strip('"')}'elif kind == "array":length = call_pdf_api("pdf_array_len", val)return f"{ref}[size]={length}"elif kind == "dict":length = call_pdf_api(f"pdf_dict_len", val)return f"{ref}[pairs]={length}"return f"{ref}{addr}"except Exception as e:return f"<error: {e}>"
class PDFObjAPIPrinter:def __init__(self, val : lldb.SBValue, internal_dict : dict):self.val = valself.kind = detect_pdf_obj_kind(val)self.size = self.num_children()def has_children(self):# 只在array/dict类型时允许展开return self.kind in ["array", "dict"]def num_children(self):if self.kind == "array":length = call_pdf_api(f"pdf_array_len", self.val)return int(length) if length else 0elif self.kind == "dict":length = call_pdf_api(f"pdf_dict_len", self.val)return int(length) if length else 0return 0def get_child_at_index(self, index):try:if index < 0 or index >= self.size:return Noneif self.kind == "array":v = call_pdf_api_1(f"pdf_array_get", self.val, index, object)# 根据索引取到pdf_obj对象了,需要获取其地址addr = v.GetValueAsAddress()# 再构造一个表达式,将这个地址强制转为pdf_obj的指针expr = f"(pdf_obj *){addr}"# 最后根据这个表达式创建一个新的值,LLDB会自动重新根据规则显示这个值return self.val.CreateValueFromExpression(f"[{index}]", expr)elif self.kind == "dict":key = call_pdf_api_1("pdf_dict_get_key", self.val, index, object)val = call_pdf_api_1("pdf_dict_get_val", self.val, index, object)# 将pdf_obj中字典的Key一定是一个name,取name的值key_str = call_pdf_api("pdf_to_name", key, str).strip('"')# 将字典的value取地址,构造一个新的表达式addr = val.GetValueAsAddress()expr = f"(pdf_obj *){addr}"# 最后根据这个表达式创建一个新的值,LLDB会自动重新根据规则显示这个值return self.val.CreateValueFromExpression(f"[/{key_str}]", expr)except Exception as e:print(f"Error in get_child_at_index: {e}")return Nonedef __lldb_init_module(debugger : lldb.SBDebugger, internal_dict : dict):debugger.HandleCommand(r'type summary add -x "^pdf_obj.*\*" --python-function mupdf_printer.PDFObjAPISummary')debugger.HandleCommand(r'type synthetic add -x "^pdf_obj.*\*" --python-class mupdf_printer.PDFObjAPIPrinter')print("MuPDF pdf_obj summary and synthetic provider (via API) loaded.")

四、调试LLDB的python代码

gdb中的python美化显示脚本不能直接使用普通的python调试器进行调试,那是因为GDB中不是使用的常规Python。常规Python不能import gdb包:

在这里插入图片描述

而常规Python是可以import lldb包的:

在这里插入图片描述

所以lldb中的python脚本是可以使用常规的python调试器调试的,下面就介绍一下在VSCode中如何调试它。

首先,需要在.vscode/launch.json中添加lldb调试配置和python调试配置,需要注意的是python的调试配置是附加到进程的类型:

{"version": "0.2.0","configurations": [{"name": "Python 调试程序: 使用进程 ID 附加","type": "debugpy","request": "attach","processId": "${command:pickProcess}"},{"type": "lldb","request": "launch","name": "(lldb) 启动","program": "${workspaceFolder}/build/t.exe","args": [],"cwd": "${workspaceFolder}","initCommands": ["command script import ${workspaceFolder}/lldbscripts/mupdf_printer.py",]}]
}

先在C/C++代码中打好断点:

在这里插入图片描述
然后启动LLDB调试器,此时会在断点处中断。
再启动Python调试,附加到codelldb进程:

在这里插入图片描述

附加成功后,在LLDB的python脚本中打的断点就生效了:

在这里插入图片描述

此时在LLDB调试器中展开还未获取到值的变量,比如数组、字典需要展开的但从未展开过的变量(展开后有缓存,再次展开不会再触发Python脚本,如果要想再次触发,可以如后面介绍的方法,在调试控制台直接使用p命令显示)

在这里插入图片描述

将调试器切换到Python调试器:

在这里插入图片描述

就可以看到触发了Python中的断点了,可以调试Python代码了:

在这里插入图片描述
由于调试了Python代码,LLDB可能会获取超时,出现后面的值显示不了的情况:

在这里插入图片描述

可以在VSCode的调试控制台中直接输入LLDB命令:p ar,刷新一下就显示出来了,此时会再次触发Python脚本。

在这里插入图片描述

五、总结

细心的读者可能会发现,所有的pdf_obj变量前面都有一个展开箭头,不管是基本的数据类型,还是数组与字典,展开后都有一个[raw]项,这是因为pdf_obj注册了混合器。

在这里插入图片描述

如果不注册混合器就不会有展开箭头,但数组与字典也无法展开查看内容,同时pdf_obj的bool值也会有问题:

在这里插入图片描述

有解决办法的读者也可以在评论区留言讨论!

笔者可能会持续改进与补充,欲知后续版本,请移步:
https://github.com/WittonBell/demo/tree/main/mupdf/lldbscripts

如果本文对你有帮助,欢迎点赞收藏!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/bicheng/84178.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Java学习笔记】日期类

日期类 第一代日期类&#xff1a;Date 引入包 import java.text.ParseException&#xff1a;日期转换可能会抛出转换异常 import java.text.SimpleDateFormat import java.util.Date 1. 基本介绍 Date&#xff1a;精确到毫秒&#xff0c;代表特定的瞬间 SimpleDateForma…

C++基础进阶:函数、内联函数与Lambda函数详解

引言 在C编程的旅程中&#xff0c;函数是构建复杂程序的基本单元。它们像乐高积木一样&#xff0c;允许我们将代码分解成更小、更易于管理的部分。今天&#xff0c;我们将深入探讨C中的三种重要函数类型&#xff1a;普通函数、内联函数以及Lambda函数。掌握它们&#xff0c;将…

从Node.js到React/Vue3:流式输出技术的全栈实现指南

本文将从底层原理到工程实践&#xff0c;完整解析如何使用Node.js后端结合React和Vue3前端实现流式输出功能&#xff0c;涵盖协议选择、性能优化、错误处理等关键细节&#xff0c;并通过真实场景案例演示完整开发流程。 一、流式输出的核心原理与协议选择 1.1 流式传输的底层机…

AT2401C中科微2.4g芯片PA

作为无线通信系统的核心模块&#xff0c;射频前端芯片通过整合功率放大器&#xff08;PA&#xff09;、滤波器、开关和低噪声放大器&#xff08;LNA&#xff09;等关键组件&#xff0c;成为保障通信质量、降低功耗及维持信号稳定的决定性因素。 AT2401C是一款面向2.4GHz无线通信…

Linux安装jdk、tomcat

1、安装jdk sudo yum install -y java-1.8.0-openjdk-devel碰到的问题&#xff1a;/var/run/yum.pid 已被锁定 Another app is currently holding the yum lock&#xff1b; waiting for it to exit… https://blog.csdn.net/u013669912/article/details/131259156 参考&#…

在本地电脑中部署阿里 Qwen3 大模型及连接到 Elasticsearch

在今天的文章中&#xff0c;我将参考文章 “使用 Elastic 和 LM Studio 的 Herding Llama 3.1” 来部署 Qwen3 大模型。据测评&#xff0c;这是一个非常不错的大模型。我们今天尝试使用 LM Studio 来对它进行部署&#xff0c;并详细描述如何结合 Elasticsearch 来对它进行使用。…

【设计模式】2.策略模式

every blog every motto: You can do more than you think. https://blog.csdn.net/weixin_39190382?typeblog 0. 前言 商场收银软件为例 1. 基础版 total 0def click_ok(price,num):tot price * numtotal totprint(合计&#xff1a;, total)增加打折 total 0def cli…

c++中的输入输出流(标准IO,文件IO,字符串IO)

目录 &#xff08;1&#xff09;I/O概述 I/O分类 不同I/O的继承关系 不同I/O对应的头文件 &#xff08;2&#xff09;iostream 标准I/O流 iostream头文件中的IO流对象 iostream头文件中重载了<<和>> 缓冲区示意图 标准输入流 cin用法 cin&#xff1a;按空…

人工智能学习06-循环

人工智能学习概述—快手视频 人工智能学习06-循环—快手视频

【电路】阻抗匹配

&#x1f4dd; 阻抗匹配 一、什么是阻抗匹配&#xff1f; 阻抗匹配&#xff08;Impedance Matching&#xff09;是指在电子系统中&#xff0c;为了实现最大功率传输或最小信号反射&#xff0c;使信号源、传输线与负载之间的阻抗达到一种“匹配”状态的技术。 研究对象&#x…

【vue】Uniapp 打包Android 文件选择上传问题详解~

需求 uniapp兼容android app&#xff0c;pc&#xff0c;h5的文件选择并上传功能。 需要支持拍照和相册选择&#xff0c;以及选择其他类型文件上传~ 实践过程和问题 开始使用uni-file-picker组件 以为很顺利&#xff0c;android模拟器测试…… 忽略了平台兼容性提示~&#…

Python:操作 Excel 格式化

🔧Python 操作 Excel 格式化完整指南(openpyxl 与 xlsxwriter 双方案) 在数据处理和报表自动化中,Python 是一把利器,尤其是配合 Excel 文件的读写与格式化处理。本篇将详细介绍两大主流库: openpyxl:适合读取与修改现有 Excel 文件xlsxwriter:适合创建新文件并进行复…

Prompt Enginering(提示工程)先进技术

前沿 CoT&#xff08;Chain-of-Thought&#xff09;和 ReACT&#xff08;Reasoning and Acting&#xff09;是两种先进的 Prompt Engineering&#xff08;提示工程&#xff09; 技术&#xff0c;旨在提升大语言模型&#xff08;LLM&#xff09;的推理、规划和执行能力。 CoT&a…

【C++系列】模板类型特例化

1. C模板类型特例化介绍 ​​定义​​&#xff1a;模板类型特例化&#xff08;Template Specialization&#xff09;是C中为模板的特定类型提供定制实现的机制&#xff0c;允许开发者对通用模板无法处理的特殊类型进行优化或特殊处理。 ​​产生标准​​&#xff1a; C98/03…

AI数据分析在体育中的应用:技术与实践

在现代体育竞技领域&#xff0c;"数据驱动"已不再是一个遥远的概念。尤其随着人工智能&#xff08;AI&#xff09;和大数据分析的不断成熟&#xff0c;从职业俱乐部到赛事直播平台&#xff0c;从运动员训练到球迷观赛体验&#xff0c;AI正以前所未有的方式渗透并改变…

计数思想-众数

11203-众数 题目描述(Description) 众数是指在一组数据中&#xff0c;出现次数最多的数。例如&#xff1a;1, 1, 3 中出现次数最多的数为 1&#xff0c;则众数为 1。 给定一组数&#xff0c;你能求出众数吗&#xff1f; 输入格式(Format Input) 第 1 行输入一个整数 n (1 &…

【Go语言基础【20】】Go的包与工程

文章目录 零、概述一、包基础1、包的核心作用2、包的声明与结构2.1、 包声明&#xff08;Package Declaration&#xff09;2.2、 包的目录结构&#xff08;工程视角&#xff09; 3、包的导入与调用3.1、导入包&#xff08;Import Packages&#xff09;3.2、 调用包成员3.3、 导…

《C++初阶之入门基础》【命名空间 + 输入输出 + 缺省参数 + 函数重载】

【命名空间 输入&输出 缺省参数 函数重载】目录 前言&#xff1a;---------------hello world---------------比较C语言和C的第一个程序&#xff1a;hello word ---------------命名空间---------------什么是命名空间&#xff1f;怎么使用命名空间&#xff1f;怎么定义…

java综合项目开发一课一得

文章目录 Java 综合项目课程学习&#xff1a;探索与成长之路一、课程初体验&#xff1a;从理论走向实践&#xff08;一&#xff09;系统学习 Java 核心理论知识&#xff08;二&#xff09;开启首个实践项目 —— 图书管理系统 二、项目攻坚&#xff1a;挑战与突破&#xff08;一…

JuiceFS v1.3-Beta2:集成 Apache Ranger,实现更精细化的权限控制

在大数据场景中&#xff0c;文件系统和应用组件的权限管理至关重要。在最新发布的 JuiceFS 社区版 v1.3-Beta 2 中&#xff0c;JuiceFS 引入了与 Apache Ranger 的集成&#xff0c;提供了更为灵活和细粒度的权限控制解决方案。 本文将介绍 JuiceFS 社区版如何与 Apache Ranger…