美化顯示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
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/84178.shtml
英文地址,請注明出處:http://en.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…