笔者在前面博文 美化显示GDB调试的数据结构中有提到:打算以mupdf库为例,介绍GDBLLDBMS Debugger这三个调试器的美化输出,目前已经写了两篇了: 美化显示GDB调试的数据结构美化显示LLDB调试的数据结构

本文将介绍MS调试器的美化显示,还是以mupdf库为例,先看一下效果:

在这里插入图片描述

MS调试器不像GDB与LLDB支持Python脚本,它使用的是一种扩展名为.natvis的XML文件格式,规则相对简单,但功能也相对较弱。.natvis的文档参见: 使用 Natvis 框架在调试器中创建自定义C++对象的视图

根据规则,.natvis文件可以放在项目的根目录,比如取名为mupdf.natvis。 框架为:

1<?xml version="1.0" encoding="utf-8"?> 
2<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
3  <Type Name="pdf_obj">
4  </Type>
5</AutoVisualizer>

这里没有如GDB和LLDB中的Python脚本需要自己写正则表达式或者其它代码来匹配类型,直接给出原型类型pdf_obj,调试器的.natvis解析引擎会自动匹配类型。

需要注意的是.natvis虽然可以调用函数,但是调试器不会主动执行有副作用的函数,同时不能像Python一样保存临时变量,所以针对mupdf库,必须要有调试信息,即必须知道完整的数据结构,否则无效。

接下来就是针对pdf_obj封装的各种不同类型(整数(int)浮点数(real)字符串(string)名字(name)数组(array)字典(dictinary)间接引用(indirect reference))进行处理。

一、基本类型

先介绍一个基础类型整数(int)的美化显示方法:

1<?xml version="1.0" encoding="utf-8"?> 
2<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
3  <Type Name="pdf_obj">
4  	    <!--整数-->
5    	<DisplayString Condition="(kind=='i')">
6      	{((pdf_obj_num*)this)->u.i}
7    	</DisplayString>
8  </Type>
9</AutoVisualizer>

.natvis中使用this来表示调试查看的变量,该this不是C++的this,纯C语言中的数据结构也是使用this来表示调试查看的变量。由于pdf_obj是一个封装类型,实际类型是根据kind来决定的:

 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;

上述的.natvis表示类型为pdf_obj的变量,如果kindi则使用{((pdf_obj_num*)this)->u.i}的值显示,注意一定要加大括号才会计算表达式的值

二、容器类型

再介绍一个容器类型数组的美化显示方法,容器类是需要展开的,所以需要添加到Expand标签下:

 1<?xml version="1.0" encoding="utf-8"?> 
 2<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
 3  <Type Name="pdf_obj">
 4    <Expand>
 5      <!--数组-->
 6      <Item Name="[cap]" Condition="(kind=='a')">((pdf_obj_array*)this)->cap</Item>
 7      <Item Name="[len]" Condition="(kind=='a')">((pdf_obj_array*)this)->len</Item>
 8      <ArrayItems Condition="(kind=='a')">
 9        <Size>((pdf_obj_array*)this)->len</Size>
10        <!--数组子项不显示地址-->
11        <ValuePointer>((pdf_obj_array*)this)->items, na</ValuePointer>
12      </ArrayItems>
13    </Expand>
14  </Type>
15</AutoVisualizer>

这里就不一一介绍了标签作用了,以可参考文档,这里的数组显示了一个容量cap和使用长度len,然后就是展开的子项数据,子项数据中需要使用na格式化参数来不显示地址,否则会比较难看。

三、间接引用类型

在mupdf中有间接引用(indirect reference)类型,它指向的是另一个pdf_obj对象。

由于mupdf中的绝大部分API都需要一个上下文fz_context的指针,而在.natvis中是无法保存临时变量的,所以不能像GDB与LLDB中的Python脚本那样调用fz_new_context_imp来保存一个上下文。这里笔者采用一个变通的方法,即像在GDB与LLDB中的Python脚本中获取mupdf库的版本号一样,自定义一个函数将间接引用的对象转换出来:

 1
 2#if defined(_MSC_VER) && defined(_DEBUG)
 3// VC调试器需要ref2obj辅助函数来展开查看间接引用的值
 4fz_context* mupdf_ctx = nullptr;
 5
 6pdf_obj* ref2obj(pdf_obj* obj) {
 7  if (mupdf_ctx == nullptr) {
 8    return obj;
 9  }
10  if (pdf_is_indirect(mupdf_ctx, obj)) {
11    return pdf_resolve_indirect_chain(mupdf_ctx, obj);
12  }
13  return obj;
14}
15#endif

这样就可以在.natvis中调用了:

1    <Expand>
2      <!--间接引用展开显示,调用自定义函数ref2obj,调试器会报`此表达式有副作用,将不予计算`,需要手动刷新计算-->
3      <Item Name="value" Condition="(kind=='r')"> ref2obj(this) </Item>
4    </Expand>

四、完整版本

重要的技术点前面已经介绍了,下面直接给出完整的mupdf.natvis配置:

 1<?xml version="1.0" encoding="utf-8"?> 
 2<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
 3  <Type Name="pdf_obj">
 4    <!--整数-->
 5    <DisplayString Condition="(kind=='i')">
 6      {((pdf_obj_num*)this)->u.i}
 7    </DisplayString>
 8    <!--浮点数-->
 9    <DisplayString Condition="(kind=='f')">
10      {((pdf_obj_num*)this)->u.f}
11    </DisplayString>
12    
13    <!--字符串-->
14    <DisplayString Condition="(kind=='s')">
15      <!--以UTF8格式显示字符串-->
16      {{ text = {((pdf_obj_string*)this)->text,s8}, buf = {((pdf_obj_string*)this)->buf,s8} }}
17    </DisplayString>
18
19    <!--布尔的false-->
20    <!--this == ((pdf_obj*)(intptr_t)PDF_ENUM_FALSE))-->
21    <DisplayString Condition="this == ((pdf_obj*)(intptr_t)PDF_ENUM_FALSE)">
22      false
23    </DisplayString>
24
25    <!--布尔的true-->
26    <!--this == ((pdf_obj*)(intptr_t)PDF_ENUM_TRUE))-->
27    <DisplayString Condition="this == ((pdf_obj*)(intptr_t)PDF_ENUM_TRUE)">
28      true
29    </DisplayString>
30
31    <!--名字-->
32    <!--this > ((pdf_obj*)(intptr_t)PDF_ENUM_FALSE) && this < ((pdf_obj*)(intptr_t)PDF_ENUM_LIMIT)-->
33    <DisplayString Condition="this &gt; ((pdf_obj*)(intptr_t)PDF_ENUM_FALSE) &amp;&amp; this &lt; ((pdf_obj*)(intptr_t)PDF_ENUM_LIMIT)">
34      <!--以UTF8格式显示不带引号的字符串-->
35      /{PDF_NAME_LIST[(intptr_t)this],s8b}
36    </DisplayString>
37
38    <!--名字-->
39    <DisplayString Condition="(kind=='n')">
40      <!--以UTF8格式显示不带引号的字符串-->
41      /{((pdf_obj_name*)this)->n,s8b}
42    </DisplayString>
43
44    <!--间接引用-->
45    <DisplayString Condition="(kind=='r')">
46      {{ num = {((pdf_obj_ref*)this)->num}, gen = {((pdf_obj_ref*)this)->gen} }}
47    </DisplayString>
48    
49    <Expand>
50      <!--数组-->
51      <Item Name="[cap]" Condition="(kind=='a')">((pdf_obj_array*)this)->cap</Item>
52      <Item Name="[len]" Condition="(kind=='a')">((pdf_obj_array*)this)->len</Item>
53      <ArrayItems Condition="(kind=='a')">
54        <Size>((pdf_obj_array*)this)->len</Size>
55        <!--数组子项不显示地址-->
56        <ValuePointer>((pdf_obj_array*)this)->items, na</ValuePointer>
57      </ArrayItems>
58
59      <!--字典-->
60      <Item Name="[cap]" Condition="(kind=='d')">((pdf_obj_dict*)this)->cap</Item>
61      <Item Name="[len]" Condition="(kind=='d')">((pdf_obj_dict*)this)->len</Item>
62      <ArrayItems Condition="(kind=='d')">
63        <Size>((pdf_obj_dict*)this)->len</Size>
64        <!--字典子项不显示地址-->
65        <ValuePointer>((pdf_obj_dict*)this)->items, na</ValuePointer>
66      </ArrayItems>
67
68      <!--字符串展开显示-->
69      <Item Name="string" Condition="(kind=='s')">(pdf_obj_string*)this</Item>
70      <!--间接引用展开显示,调用自定义函数ref2obj,调试器会报`此表达式有副作用,将不予计算`,需要手动刷新计算-->
71      <Item Name="value" Condition="(kind=='r')"> ref2obj(this) </Item>
72    </Expand>
73  </Type>
74  <!--keyval结构-->
75  <Type Name="keyval">
76    <!--使用na参数不显示地址-->
77    <DisplayString>{{ {k,na},{v,na} }} </DisplayString>
78    <Expand>
79      <Item Name="Key"> k </Item>
80      <Item Name="Value"> v </Item>
81    </Expand>
82  </Type>
83  <!--fz_buffer结构-->
84  <Type Name="fz_buffer">
85    <DisplayString>{data,[len]s8}</DisplayString>
86    <Expand>
87      <Item Name="[cap]">cap</Item>
88      <Item Name="[len]">len</Item>
89      <!--按指定长度显示文本-->
90      <Item Name="text">data,[len]s8</Item>
91    </Expand>
92  </Type>
93</AutoVisualizer>

测试代码t.c

 1#include <mupdf/fitz.h>
 2#include <mupdf/pdf.h>
 3#include <stdbool.h>
 4
 5#ifndef __cplusplus
 6#if __STDC_VERSION__ < 202311L
 7#ifdef nullptr
 8#undef nullptr
 9#endif
10
11#define nullptr NULL
12#endif
13#endif
14
15// 为GDB调试器使用,不能设置为static
16// 这将使得GDB可以在运行时获取mupdf的版本信息
17const char* mupdf_version = FZ_VERSION;
18
19#if defined(_MSC_VER) && defined(_DEBUG)
20// VC调试器需要ref2obj辅助函数来展开查看间接引用的值
21fz_context* mupdf_ctx = nullptr;
22
23pdf_obj* ref2obj(pdf_obj* obj) {
24  if (mupdf_ctx == nullptr) {
25    return obj;
26  }
27  if (pdf_is_indirect(mupdf_ctx, obj)) {
28    return pdf_resolve_indirect_chain(mupdf_ctx, obj);
29  }
30  return obj;
31}
32#endif
33
34int main(int argc, char* argv[]) {
35  const char* p = "hello";
36  bool b = true;
37  pdf_obj* nil = nullptr;
38  fz_context* ctx = fz_new_context(nullptr, nullptr, FZ_STORE_UNLIMITED);
39#if defined(_MSC_VER) && defined(_DEBUG)
40  mupdf_ctx = ctx;
41#endif
42  pdf_document* doc = nullptr;
43  fz_try(ctx) {
44    // 由于目前的VS2022 CMake项目即使配置了"cwd": "${workspaceRoot}"也不会是项目根目录,
45    // 所以文件路径根据情况自行设定。
46    doc = pdf_open_document(ctx, "../../../t.pdf");
47  }
48  fz_catch(ctx) {
49    fz_report_error(ctx);
50    printf("cannot open document\n");
51    fz_drop_context(ctx);
52    return EXIT_FAILURE;
53  }
54
55  pdf_obj* Int = pdf_new_int(ctx, 10);
56  pdf_obj* Real = pdf_new_real(ctx, 3.14F);
57  pdf_obj* Str = pdf_new_text_string(ctx, "hello");
58  pdf_obj* Name = pdf_new_name(ctx, "name");
59  pdf_obj* True = PDF_TRUE;
60  pdf_obj* False = PDF_FALSE;
61
62  pdf_obj* ar = pdf_new_array(ctx, doc, 10);
63  pdf_array_put(ctx, ar, 0, Int);
64  pdf_array_put(ctx, ar, 1, Real);
65  pdf_array_put(ctx, ar, 2, Str);
66  pdf_array_push_bool(ctx, ar, 1);
67  pdf_array_push_bool(ctx, ar, 0);
68  pdf_array_push(ctx, ar, PDF_NULL);
69
70  pdf_obj* dict = pdf_new_dict(ctx, doc, 10);
71  pdf_dict_puts(ctx, dict, "int", Int);
72  pdf_dict_puts(ctx, dict, "real", Real);
73  pdf_dict_puts(ctx, dict, "str", Str);
74  pdf_dict_puts(ctx, dict, "name", Name);
75  pdf_dict_puts(ctx, dict, "array", ar);
76
77  pdf_obj* ref = pdf_new_indirect(ctx, doc, 3633, 0);
78
79  pdf_drop_obj(ctx, Int);
80  pdf_drop_obj(ctx, Real);
81  pdf_drop_obj(ctx, Str);
82  pdf_drop_obj(ctx, Name);
83  pdf_drop_obj(ctx, ar);
84  pdf_drop_obj(ctx, dict);
85  pdf_drop_obj(ctx, ref);
86
87  pdf_drop_document(ctx, doc);
88  fz_drop_context(ctx);
89}

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

整个测试项目地址: https://github.com/WittonBell/demo/blob/main/mupdf

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