笔者在前面的博文 记一次pdf转Word的技术经历中有使用到mupdf库,该库是使用C语言写的一个操作PDF文件的库,同时提供了Python接口,Java接口和JavaScript接口。

在使用该库时,如果想要更高的性能,使用C语言接口是不二的选择。笔者在使用mupdf库C API时,遇到一个问题:调试它时不能看到数据结构,全部是一个地址,比如mupdf中的pdf_obj对象,它的API中只有一个typedef,参见 mupdf/pdf/object.h

1typedef struct pdf_obj pdf_obj;

实际的结构定义是在库的 pdf-object.c文件中,库使用者并不可见。它把整数(int)浮点数(real)字符串(string)名字(name)数组(array)字典(dictinary)间接引用(indirect reference)都抽象成pdf_obj

 1typedef enum pdf_objkind_e
 2{
 3	PDF_INT = 'i',
 4	PDF_REAL = 'f',
 5	PDF_STRING = 's',
 6	PDF_NAME = 'n',
 7	PDF_ARRAY = 'a',
 8	PDF_DICT = 'd',
 9	PDF_INDIRECT = 'r'
10} pdf_objkind;
11
12struct pdf_obj
13{
14	short refs;
15	unsigned char kind;
16	unsigned char flags;
17};
18
19typedef struct
20{
21	pdf_obj super;
22	union
23	{
24		int64_t i;
25		float f;
26	} u;
27} pdf_obj_num;
28
29typedef struct
30{
31	pdf_obj super;
32	char *text; /* utf8 encoded text string */
33	size_t len;
34	char buf[FZ_FLEXIBLE_ARRAY];
35} pdf_obj_string;
36
37typedef struct
38{
39	pdf_obj super;
40	char n[FZ_FLEXIBLE_ARRAY];
41} pdf_obj_name;
42
43typedef struct
44{
45	pdf_obj super;
46	pdf_document *doc;
47	int parent_num;
48	int len;
49	int cap;
50	pdf_obj **items;
51} pdf_obj_array;
52
53typedef struct
54{
55	pdf_obj super;
56	pdf_document *doc;
57	int parent_num;
58	int len;
59	int cap;
60	struct keyval *items;
61} pdf_obj_dict;
62
63typedef struct
64{
65	pdf_obj super;
66	pdf_document *doc; /* Only needed for arrays, dicts and indirects */
67	int num;
68	int gen;
69} pdf_obj_ref;

然后API中全部使用pdf_obj*来操作上述数据结构:

 1// 创建API
 2pdf_obj *pdf_new_int(fz_context *ctx, int64_t i);
 3pdf_obj *pdf_new_real(fz_context *ctx, float f);
 4pdf_obj *pdf_new_name(fz_context *ctx, const char *str);
 5pdf_obj *pdf_new_string(fz_context *ctx, const char *str, size_t len);
 6pdf_obj *pdf_new_text_string(fz_context *ctx, const char *s);
 7pdf_obj *pdf_new_indirect(fz_context *ctx, pdf_document *doc, int num, int gen);
 8pdf_obj *pdf_new_array(fz_context *ctx, pdf_document *doc, int initialcap);
 9pdf_obj *pdf_new_dict(fz_context *ctx, pdf_document *doc, int initialcap);
10
11// 释放内存的API
12void pdf_drop_obj(fz_context *ctx, pdf_obj *obj);
13
14// 检测API
15int pdf_is_null(fz_context *ctx, pdf_obj *obj);
16int pdf_is_bool(fz_context *ctx, pdf_obj *obj);
17int pdf_is_int(fz_context *ctx, pdf_obj *obj);
18int pdf_is_real(fz_context *ctx, pdf_obj *obj);
19int pdf_is_number(fz_context *ctx, pdf_obj *obj);
20int pdf_is_name(fz_context *ctx, pdf_obj *obj);
21int pdf_is_string(fz_context *ctx, pdf_obj *obj);
22int pdf_is_array(fz_context *ctx, pdf_obj *obj);
23int pdf_is_dict(fz_context *ctx, pdf_obj *obj);
24int pdf_is_indirect(fz_context *ctx, pdf_obj *obj);
25
26// pdf_obj转换C标准数据类型的API
27int pdf_to_bool(fz_context *ctx, pdf_obj *obj);
28int pdf_to_int(fz_context *ctx, pdf_obj *obj);
29int64_t pdf_to_int64(fz_context *ctx, pdf_obj *obj);
30float pdf_to_real(fz_context *ctx, pdf_obj *obj);
31const char *pdf_to_name(fz_context *ctx, pdf_obj *obj);
32const char *pdf_to_text_string(fz_context *ctx, pdf_obj *obj);
33const char *pdf_to_string(fz_context *ctx, pdf_obj *obj, size_t *sizep);
34char *pdf_to_str_buf(fz_context *ctx, pdf_obj *obj);
35size_t pdf_to_str_len(fz_context *ctx, pdf_obj *obj);
36int pdf_to_num(fz_context *ctx, pdf_obj *obj);
37int pdf_to_gen(fz_context *ctx, pdf_obj *obj);

还有很多操作pdf_obj的API,就不一一列出了。这里想要说的是API的封装非常好,只是使用者在使用库时,想要更深入地查看数据,就很不友好了。

我们在C/C++语言中,经常会自定义数据结构,但是调试时调试器都会默认的按最原始的组织方式展示它们,很不直观。只有借助调试器的美化输出才能达到目的。比如C++中的STL库,定义了很多数据结构,如果不美化调试器的输出是很难查看其数据的,只是由于STL是标准库,编译器厂商已经帮我们做了相应的美化输出工作,所以可以直观地看数据。

下面笔者借调试研究mupdf库,介绍一下如何美化显示调试器的输出。目前主流的调试器有GDBLLDB以及MS Debugger,以GDB的使用最广泛,跨LinuxMacOSWindowsIOS安卓等等,LLDB作为后起之秀,使用也越来越广泛,而MS Debugger本地调试器局限于Windows,但Windows使用广泛。所以笔者打算以mupdf库为例,介绍这三个调试器的美化输出。本文先介绍GDB的美化输出(Windows下的MinGW中的GDB)。

一、加载自定义脚本

GDB在启动时,会尝试加载~/.gdbinit文件(如果有就加载)。所以我们可以把一些基本设置放在这个文件中来,比如设置反汇编的格式为intel(GDB默认的反汇编格式为att)、调用自定义的python脚本。

需要注意的是在Windows下,假定用户名为admin,如果是Windows控制台,~/.gdbinit位于C:/Users/admin/.gdbinit;如果是MinGW的终端,则位于MinGW的/home/admin/.gdbinit

比如笔者的~/.gdbinit文件如下:

 1# 设置反汇编格式为intel
 2set disassembly-flavor intel
 3# 允许自动执行本地gdbinit,即允许执行项目级的`.gdbinit`
 4set auto-load local-gdbinit on
 5# 设置自动加载GDB脚本的安全路径,`/`表示所有项目,如果要指定特定项目,给出具体路径即可
 6set auto-load safe-path /
 7add-auto-load-safe-path /
 8# 执行shell指令,Windows下执行chcp 65001修改控制台编码为UTF8
 9shell chcp 65001
10# python脚本
11python
12import sys
13# 添加python搜索模块的路径
14sys.path.insert(0, 'C:/Users/admin/gdbscripts')
15# 导入mupdf_printer模块
16import mupdf_printer
17end

C:/Users/admin/gdbscripts中添加一个mupdf_printer.pdf文件,写一句:

1print("MuPDF GDB pretty printers loaded.")

分别在Windows控制台以及MinGW终端执行GDB,看是否有输出:

在这里插入图片描述

看到输出就说明自定义的脚本加载成功了。

二、写mupdf测试代码

 1#include <mupdf/fitz.h>
 2#include <mupdf/pdf.h>
 3
 4// 为GDB调试器使用,不能设置为static
 5// 这将使得GDB可以在运行时获取mupdf的版本信息
 6const char* mupdf_version = FZ_VERSION;
 7
 8int main(int argc, char* argv[]) {
 9  pdf_obj* nil = nullptr;
10  fz_context* ctx = fz_new_context(nullptr, nullptr, FZ_STORE_UNLIMITED);
11  pdf_document* doc = pdf_open_document(ctx, "t.pdf");
12  pdf_obj* Int = pdf_new_int(ctx, 10);
13  pdf_obj* Real = pdf_new_real(ctx, 3.14);
14  pdf_obj* Str = pdf_new_text_string(ctx, "hello");
15  pdf_obj* Name = pdf_new_name(ctx, "name");
16
17  pdf_obj* ar = pdf_new_array(ctx, doc, 10);
18  pdf_array_put(ctx, ar, 0, Int);
19  pdf_array_put(ctx, ar, 1, Real);
20  pdf_array_put(ctx, ar, 2, Str);
21  pdf_array_push_bool(ctx, ar, 1);
22  pdf_array_push_bool(ctx, ar, 0);
23  pdf_array_push(ctx, ar, PDF_NULL);
24
25  pdf_obj* dict = pdf_new_dict(ctx, doc, 10);
26  pdf_dict_puts(ctx, dict, "int", Int);
27  pdf_dict_puts(ctx, dict, "real", Real);
28  pdf_dict_puts(ctx, dict, "str", Str);
29  pdf_dict_puts(ctx, dict, "name", Name);
30  pdf_dict_puts(ctx, dict, "array", ar);
31  // 这里3633是笔者pdf文件中的Catalog对象
32  pdf_obj* ref = pdf_new_indirect(ctx, doc, 3633, 0);
33  
34  pdf_drop_obj(ctx, Int);
35  pdf_drop_obj(ctx, Real);
36  pdf_drop_obj(ctx, Str);
37  pdf_drop_obj(ctx, Name);
38  pdf_drop_obj(ctx, ar);
39  pdf_drop_obj(ctx, dict);
40  pdf_drop_obj(ctx, ref);
41
42  fz_drop_context(ctx);
43}

目前在VSCode下使用GDB调试时显示的变量值全部是地址:

在这里插入图片描述

下面是笔者添加了美化显示后的效果:

在这里插入图片描述

三、写GDB的Python脚本

GDB的Python扩展可以参见 Extending GDB using Python,我们目前只需要美化输出的API,参见 Pretty Printing API,我主要使用到以下三个函数:

  • pretty_printer.children (self):用于展示子节点,如果有子节点,需要实现此函数,如果没有则不写。
  • pretty_printer.display_hint (self):用于提示该节点的类型,如果是字典返回"map";如果是数组返回"array";如果是字符串,返回"string";其它情况返回None
  • pretty_printer.to_string (self):用于显示节点的值。

1. 向GDB注册pdf_obj类型

要让GDB以自定义的方式显示数据,需要先向GDB注册数据类型,这里需要注册的是pdf_obj类型。

 1import gdb
 2
 3def pdf_obj_lookup(val):
 4    try:
 5        t = val.type.strip_typedefs()
 6        if t.code == gdb.TYPE_CODE_PTR:
 7            t = t.target()
 8        if t.name == "pdf_obj":
 9            print("<pdf_obj>")
10    except:
11        return None
12
13gdb.pretty_printers.append(pdf_obj_lookup)
14print("MuPDF GDB pretty printers loaded.")

由于C语言中很多类型都是使用typedef方式定义过的,所以在解析类型时,可能需要调用strip_typedefs去掉typedef,最重要的是要去掉指针,这样方便只检测pdf_obj类型。

2. 写美化输出代码

在写美化输出的Python代码前,需要了解一下如何取值:

1. 直接使用C/C++中的字段名取字段值

如果是自己写的代码,或者使用库有调试信息,则可以直接使用字段名来取字段值,比如:

1struct foo { int a, b; };
2struct bar { struct foo x, y; };

则可以直接使用字段名"a"、“b”、“x”,“y"来取值:

 1class fooPrinter(gdb.ValuePrinter):
 2    """Print a foo object."""
 3
 4    def __init__(self, val):
 5        self.__val = val
 6
 7    def to_string(self):
 8        return ("a=<" + str(self.__val["a"]) +
 9                "> b=<" + str(self.__val["b"]) + ">")
10
11class barPrinter(gdb.ValuePrinter):
12    """Print a bar object."""
13
14    def __init__(self, val):
15        self.__val = val
16
17    def to_string(self):
18        return ("x=<" + str(self.__val["x"]) +
19                "> y=<" + str(self.__val["y"]) + ">")

2. 调用C/C++代码中的函数

如果是使用的别人编译的库,且没有调试信息,但有提供相应的API,则可以使用GDB调用API来取值。这里使用的mupdf库,在MinGW下就没有调试信息,但是提供了一系列的操作API,所以这里使用gdb.parse_and_eval来调用C/C++中的函数。需要注意的是在调用完成后需要强制转换成API对应的类型。比如,调用pdf_is_int,它的函数原型为:

1int pdf_is_int(fz_context *ctx, pdf_obj *obj);

需要写成:

1result = gdb.parse_and_eval(f"(int)pdf_is_int({ctx_addr}, {obj_addr}")

result中保存的就是调用的结果,如果GDB设置了set print address on,则字符串会返回地址和字符串内容:

在这里插入图片描述

在字典显示中Key的显示会比较难看,所以需要对结果进行处理,GDB提供了string()函数去掉地址,判断到返回值是字符串,则调用result.string()去掉地址。

在这里插入图片描述

为了方便调用API,定义专门的函数来处理:

 1# 由于mupdf中的API都带有一个 fz_context* 参数,
 2# 为了提高性能,我们调用 fz_new_context_imp 来创建一个全局的pdf_ctx。
 3pdf_ctx = None
 4
 5# 由于fz_new_context_imp需要用到mupdf的版本号,但mupdf库并没提供这样的获取途径,
 6# 所以需要在C/C++代码中定义一个全局变量mupdf_version,且不能设置为静态变量
 7# const char* mupdf_version = FZ_VERSION;
 8# 然后在此写一个专门的函数来获取mupdf的版本号
 9def get_mupdf_version_from_symbol():
10    try:
11        version = gdb.parse_and_eval('(const char*)mupdf_version')
12        return version.string()
13    except gdb.error as e:
14        return f"<symbol not found: {e}>"
15
16# 调用mupdf API的专用函数
17# func_name是要调用的mupdf API名
18# val是pdf_obj或者pdf_obj*
19# retType是API的返回类型,支持int, float, str和object
20# *args 是可能需要的参数,比如pdf_obj *pdf_array_get(fz_context *ctx, pdf_obj *array, int i)需要传一个索引i
21def call_mupdf_api(func_name, val, retType, *args):
22    try:
23        # 获取地址,如果是指针,直接转成int
24        # 如果是对象,需要取地址,再转成int
25        if val.type.code == gdb.TYPE_CODE_PTR:
26            addr = int(val)
27        else:
28            addr = int(val.address)
29        cast = {
30            int: "(int)",
31            float: "(float)",
32            str: "(const char*)",
33            object: "(pdf_obj *)",  # 转成pdf_obj指针
34        }.get(retType, "(void)") # 根据Python传的参数,转成C/C++中的类型,默认强转为void
35        # 如果pdf_ctx还没创建,则调用fz_new_context_imp创建
36        global pdf_ctx
37        if pdf_ctx is None:
38            ver = get_mupdf_version_from_symbol()
39            pdf_ctx = gdb.parse_and_eval(f"(fz_context*)fz_new_context_imp(0,0,0,\"{ver}\")")
40        # 有额外参数的情况
41        if args.__len__() > 0:
42            args_str = ', '.join([str(arg) for arg in args])
43            expr = f"{cast}{func_name}({pdf_ctx},{addr}, {args_str})"
44        else: # 没额外参数的情况
45            expr = f"{cast}{func_name}({pdf_ctx},{addr})"
46        result = gdb.parse_and_eval(expr)
47        # 使用全局 pdf_ctx 则此处不需要释放
48        #gdb.parse_and_eval(f"(void)fz_drop_context({ctx})")
49        # 根据返回类型进行处理,为了避免异常,使用cast转为C/C++中的类型
50        if retType == int:
51            return int(result.cast(gdb.lookup_type("int")))
52        elif retType == float:
53            return float(result.cast(gdb.lookup_type("float")))
54        elif retType == str:
55        	# 去掉字符串中的地址
56            return result.string()
57        else:
58            return result
59    except Exception as e:
60        print(f"<error calling {func_name}: {e}>")
61        return f"<error calling {func_name}: {e}>"

由于很多API的返回值都是int,为了方便,封装一下:

1def call_pdf_api(func_name, val, rettype=int):
2    return call_mupdf_api(func_name, val, rettype)
3
4def call_pdf_api_1(func_name, val, args, rettype=int):
5    return call_mupdf_api(func_name, val, rettype, args)

3.检测pdf_obj的类型

pdf_obj可能是布尔(bool)整数(int)浮点数(real)字符串(string)名字(name)数组(array)字典(dictinary)间接引用(indirect reference),在解析前,需要知道具体的类型:

 1def detect_pdf_obj_kind(val):
 2    try:
 3        if call_pdf_api("pdf_is_int", val):
 4            return "int"
 5        elif call_pdf_api("pdf_is_real", val):
 6            return "real"
 7        elif call_pdf_api("pdf_is_string", val):
 8            return "string"
 9        elif call_pdf_api("pdf_is_name", val):
10            return "name"
11        elif call_pdf_api("pdf_is_array", val):
12            return "array"
13        elif call_pdf_api("pdf_is_dict", val):
14            return "dict"
15        elif call_pdf_api("pdf_is_bool", val):
16            return "bool"
17        elif call_pdf_api("pdf_is_null", val):
18            return "null"
19        else:
20            return "unknown"
21    except Exception as e:
22        print(f"<error detecting pdf_obj kind: {e}>")
23        return "error"

4. 写各类型的美化输出

布尔(bool)整数(int)浮点数(real)字符串(string)名字(name)这些都是没有子节点的简单数据结构,所以不需要children函数:

 1class PDFObjIntPrinter:
 2    def __init__(self, val):
 3        self.val = val
 4
 5    def to_string(self):
 6        return call_pdf_api('pdf_to_int', self.val, int)
 7
 8    def display_hint(self):
 9        return None
10
11class PDFObjRealPrinter:
12    def __init__(self, val):
13        self.val = val
14
15    def to_string(self):
16        return call_pdf_api('pdf_to_real', self.val, float)
17
18    def display_hint(self):
19        return None
20
21class PDFObjStringPrinter:
22    def __init__(self, val):
23        self.val = val
24
25    def to_string(self):
26        return call_pdf_api('pdf_to_text_string', self.val, str)
27
28    def display_hint(self):
29        return "string"
30
31class PDFObjNamePrinter:
32    def __init__(self, val):
33        self.val = val
34
35    def to_string(self):
36        return call_pdf_api('pdf_to_name', self.val, str)
37
38    def display_hint(self):
39        return "string"
40
41class PDFObjBoolPrinter:
42    def __init__(self, val):
43        self.val = val
44
45    def to_string(self):
46        ret = call_pdf_api("pdf_to_bool", self.val, int)
47        return 'true' if ret else 'false'
48
49    def display_hint(self):
50        return None
51
52class PDFObjNullPrinter:
53    def __init__(self, val):
54        self.val = val
55
56    def to_string(self):
57        return "<null>"
58
59    def display_hint(self):
60        return None

数组(array)字典(dictinary)是需要展开子节点的数据结构,必须要有children函数,children函数需要返回的是一个Python的迭代器,迭代器中需要返回两个值,第一个值是名字name,第二个值则是显示内容;而字典是键值对,需要yield返回两次:

 1class PDFArrayPrinter:
 2    def __init__(self, val):
 3        self.val = val
 4        self.count = call_pdf_api("pdf_array_len", self.val)
 5
 6    def to_string(self):
 7        return f"<PDF array[{self.count}]>"
 8
 9    class _interator:
10        def __init__(self, val, count):
11            self.val = val
12            self.count = count
13            self.index = 0
14
15        def __iter__(self):
16            return self
17
18        def __next__(self):
19            if self.index >= self.count:
20                raise StopIteration
21            i = self.index
22            self.index += 1
23            try:
24                item = call_pdf_api_1("pdf_array_get", self.val, i, object)
25                return f"{i}", item
26            except Exception:
27                raise StopIteration
28
29    def children(self):
30        return self._interator(self.val, self.count)
31
32    def display_hint(self):
33        return "array"
34
35class PDFDictPrinter:
36    def __init__(self, val):
37        self.val = val
38
39    def to_string(self):
40        count = call_pdf_api("pdf_dict_len", self.val)
41        return f"<PDF dict[{count}]>"
42
43    def children(self):
44        count = call_pdf_api("pdf_dict_len", self.val)
45        for i in range(count):
46            try:
47                key = call_pdf_api_1("pdf_dict_get_key",self.val, i, object)
48                val = call_pdf_api_1("pdf_dict_get_val",self.val, i, object)
49                # 下面两个yield语句中,元组的第一个元素不能相同
50                yield (f"{i}:k", key) # 返回键值对的键
51                yield (f"{i}:v", val) # 返回键值对的值
52            except Exception as e:
53                print(f"PDFDictPrinter: error getting key {i} {e}")
54                yield f"<key:{i}>", "<invalid>"
55
56    def display_hint(self):
57        return "map"

四、完整Python脚本:

  1import gdb
  2
  3pdf_ctx = None
  4
  5def get_mupdf_version_from_symbol():
  6    try:
  7        version = gdb.parse_and_eval('(const char*)mupdf_version')
  8        return version.string()
  9    except gdb.error as e:
 10        return f"<symbol not found: {e}>"
 11
 12def call_mupdf_api(func_name, val, retType, *args):
 13    try:
 14        # 获取地址
 15        if val.type.code == gdb.TYPE_CODE_PTR:
 16            addr = int(val)
 17        else:
 18            addr = int(val.address)
 19        cast = {
 20            int: "(int)",
 21            float: "(float)",
 22            str: "(const char*)",  # for functions returning const char*
 23            object: "(pdf_obj *)",  # for pdf_obj pointers
 24        }.get(retType, "(void)")
 25        global pdf_ctx
 26        if pdf_ctx is None:
 27            ver = get_mupdf_version_from_symbol()
 28            pdf_ctx = gdb.parse_and_eval(f"(fz_context*)fz_new_context_imp(0,0,0,\"{ver}\")")
 29        if args.__len__() > 0:
 30            args_str = ', '.join([str(arg) for arg in args])
 31            expr = f"{cast}{func_name}({pdf_ctx},{addr}, {args_str})"
 32        else:
 33            expr = f"{cast}{func_name}({pdf_ctx},{addr})"
 34        result = gdb.parse_and_eval(expr)
 35        # 使用全局 pdf_ctx 则此处不需要释放
 36        #gdb.parse_and_eval(f"(void)fz_drop_context({ctx})")  # Clean up context
 37        if retType == int:
 38            return int(result.cast(gdb.lookup_type("int")))
 39        elif retType == float:
 40            return float(result.cast(gdb.lookup_type("float")))
 41        elif retType == str:
 42            return result.string()
 43        else:
 44            return result
 45    except Exception as e:
 46        print(f"<error calling {func_name}: {e}>")
 47        return f"<error calling {func_name}: {e}>"
 48
 49def call_pdf_api(func_name, val, rettype=int):
 50    return call_mupdf_api(func_name, val, rettype)
 51
 52def call_pdf_api_1(func_name, val, args, rettype=int):
 53    return call_mupdf_api(func_name, val, rettype, args)
 54
 55def detect_pdf_obj_kind(val):
 56    try:
 57        if call_pdf_api("pdf_is_int", val):
 58            return "int"
 59        elif call_pdf_api("pdf_is_real", val):
 60            return "real"
 61        elif call_pdf_api("pdf_is_string", val):
 62            return "string"
 63        elif call_pdf_api("pdf_is_name", val):
 64            return "name"
 65        elif call_pdf_api("pdf_is_array", val):
 66            return "array"
 67        elif call_pdf_api("pdf_is_dict", val):
 68            return "dict"
 69        elif call_pdf_api("pdf_is_bool", val):
 70            return "bool"
 71        elif call_pdf_api("pdf_is_stream", val):
 72            return "stream"
 73        elif call_pdf_api("pdf_is_null", val):
 74            return "null"
 75        else:
 76            return "unknown"
 77    except Exception as e:
 78        print(f"<error detecting pdf_obj kind: {e}>")
 79        return "error"
 80
 81class PDFObjIntPrinter:
 82    def __init__(self, val, ref):
 83        self.val = val
 84        self.ref = ref
 85
 86    def to_string(self):
 87        return f"{self.ref}{call_pdf_api('pdf_to_int', self.val, int)}"
 88
 89    def display_hint(self):
 90        return None
 91
 92class PDFObjRealPrinter:
 93    def __init__(self, val, ref):
 94        self.val = val
 95        self.ref = ref
 96
 97    def to_string(self):
 98        return f"{self.ref}{call_pdf_api('pdf_to_real', self.val, float)}"
 99
100    def display_hint(self):
101        return None
102
103class PDFObjStringPrinter:
104    def __init__(self, val, ref):
105        self.val = val
106        self.ref = ref
107
108    def to_string(self):
109        return f"{self.ref}{call_pdf_api('pdf_to_text_string', self.val, str)}"
110
111    def display_hint(self):
112        return "string"
113
114class PDFObjNamePrinter:
115    def __init__(self, val, ref):
116        self.val = val
117        self.ref = ref
118
119    def to_string(self):
120        return f"{self.ref}{call_pdf_api('pdf_to_name', self.val, str)}"
121
122    def display_hint(self):
123        return "string"
124
125class PDFObjBoolPrinter:
126    def __init__(self, val, ref):
127        self.val = val
128        self.ref = ref
129
130    def to_string(self):
131        ret = call_pdf_api("pdf_to_bool", self.val, int)
132        return f"{self.ref}{'true' if ret else 'false'}"
133
134    def display_hint(self):
135        return None
136
137class PDFObjNullPrinter:
138    def __init__(self, val, ref):
139        self.val = val
140        self.ref = ref
141
142    def to_string(self):
143        return f"{self.ref}<null>"
144
145    def display_hint(self):
146        return None
147
148class PDFArrayPrinter:
149    def __init__(self, val, ref):
150        self.val = val
151        self.ref = ref
152        self.count = call_pdf_api("pdf_array_len", self.val)
153
154    def to_string(self):
155        return f"{self.ref}<PDF array[{self.count}]>"
156
157    class _interator:
158        def __init__(self, val, count):
159            self.val = val
160            self.count = count
161            self.index = 0
162
163        def __iter__(self):
164            return self
165
166        def __next__(self):
167            if self.index >= self.count:
168                raise StopIteration
169            i = self.index
170            self.index += 1
171            try:
172                item = call_pdf_api_1("pdf_array_get", self.val, i, object)
173                return f"{i}", item
174            except Exception:
175                raise StopIteration
176
177    def children(self):
178        return self._interator(self.val, self.count)
179
180    def display_hint(self):
181        return "array"
182
183class PDFDictPrinter:
184    def __init__(self, val, ref):
185        self.val = val
186        self.ref = ref
187
188    def to_string(self):
189        count = call_pdf_api("pdf_dict_len", self.val)
190        return f"{self.ref}<PDF dict[{count}]>"
191
192    def children(self):
193        count = call_pdf_api("pdf_dict_len", self.val)
194        for i in range(count):
195            try:
196                key = call_pdf_api_1("pdf_dict_get_key",self.val, i, object)
197                val = call_pdf_api_1("pdf_dict_get_val",self.val, i, object)
198                # 下面两个yield语句中,元组的第一个元素不能相同
199                yield (f"{i}:k", key) # 返回键值对的键
200                yield (f"{i}:v", val) # 返回键值对的值
201            except Exception as e:
202                print(f"PDFDictPrinter: error getting key {i} {e}")
203                yield f"<key:{i}>", "<invalid>"
204
205    def display_hint(self):
206        return "map"
207
208def pdf_obj_lookup(val):
209    try:
210        t = val.type
211        if t.code == gdb.TYPE_CODE_PTR:
212            t = t.target()
213        if t.name == "pdf_obj":
214            ref = ""
215            if call_pdf_api("pdf_is_indirect", val):
216                ref_num = call_pdf_api("pdf_to_num", val, int)
217                ref = f"<PDF indirect ref {ref_num}> => "
218            kind = detect_pdf_obj_kind(val)
219            if kind == "int":
220                return PDFObjIntPrinter(val, ref)
221            elif kind == "real":
222                return PDFObjRealPrinter(val, ref)
223            elif kind == "string":
224                return PDFObjStringPrinter(val, ref)
225            elif kind == "name":
226                return PDFObjNamePrinter(val, ref)
227            elif kind == "array":
228                return PDFArrayPrinter(val, ref)
229            elif kind == "dict":
230                return PDFDictPrinter(val, ref)
231            elif kind == "bool":
232                return PDFObjBoolPrinter(val, ref)
233            elif kind == "null":
234                return PDFObjNullPrinter(val, ref)
235            else:
236                print(f"<unknown pdf_obj kind: {kind}>")
237                return None
238    except:
239        return None
240
241gdb.pretty_printers.append(pdf_obj_lookup)
242print("MuPDF GDB pretty printers loaded.")

不知道细心的读者有没发现,目前还有一点问题就是在VSCode中数组和字典的在展开过程中,如果已经没有可展开的项了,但是还是有一个箭头在前面。有解决办法的读者也可以在评论区讨论!

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

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