这里假设读者都是用Visual Studio和msbuild的用户,为了安装这个调试器,你需要打开VS的安装程序,切换到Individual Components,勾上Windows Driver Kit,然后把他装上去。
接着就是到Download the Windows Driver Kit (WDK)把WDK再装一遍。
VS自带的只是WDK的库,第二个就是WDK的所有UI工具了。
装完后就可以在C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\cdb.exe找到,命令行就可以用起来了。
我们用CDB还是想要传统的按代码行的方式来调试。默认情况下VS会帮你把exe和pdb都生成在一起,那你什么都不需要做就可以加载符号。一般来说我们可以:
cdb -c ".lines; l+t; l+s" -o 你的exe 你的参数
启动CDB。这个时候程序时处于暂停状态的,你可以趁这个机会打断点,然后按g运行。如果你想直接运行,可以在-c的命令最后加上; g。
lm:列出所有加载的模块。如果你的程序很简单就只有一个UnitTest.exe,那你就不用列了,模块名就是UnitTest。bl:列出所有断点be 数字:enable断点bd 数字:disable断点bc 数字:删掉断点下断点用的是bp命令,他需要你给出指令的地址。但是我们想直接按照行号来也可以:
bp `file.cpp:line`
bp `UnitTest!file.cpp:line`
有了断点之后就可以g(也就是VS的F5)然后停下来,这个时候:
p:运行到下一行,也就是VS的F10t:进函数里面,也就是VS的F11pt:退出函数不管是程序炸了还是命中断点,这个时候就要开始看变凉了。你可以用kn查看完整的callstack,如果太长的话你也可以用kn 10只显示10行。callstack的左边是从0开始排列的数字,看看你想进哪个函数里面看,然后.frame 数字。
CDB牛逼的地方是他可以和VS用相同的.natvis文件。你可以.nvload它。
首先用dv列出当前函数的所有变量,接着就可以用dx去看。调试一个C++程序的话,dx甚至可以用C语言的语法来看一个表达式的值。如果是.net程序或者用dx查看内置的变量,你甚至可以用C#的linq来进行快速筛选。
最后用q就可以退出了,顺便把被调试的程序杀了。
在这里我简单的演示一遍。首先我们要启动UnitTest.exe然后在这里下断点:

& cdb -c ".lines; l+t; l+s" -o "UnitTest.exe"
进去之后依次输入
bp `TestApplication_Dialog_File.cpp:90`
g
我们就可以看程序开始跑,跑到我们的断点就会停下来:

我们跳3行到TEST_ASSERT,这个时候他还不会运行,所以还没炸。然后看看现在的堆栈,跳进去,打印z
p
p
p
kn 5
.frame 0
dv
dx z

这个时候我们可以看到dx z显示的是Variant<vint, WString>的裸成员。我们都知道C++的Variant实现是比较扭曲的,如果要我们在dx里面自己做casting也是麻烦的一笔。当然我们还是可以做:
dx ((vl::ObjectString<wchar_t>*)z.buffer)
as str (((vl::ObjectString<wchar_t>*)z.buffer))
dx (${str}->buffer+${str}->start),[${str}->length]

在这里as和${str}就是宏了,用的时候记得加上括号,道理和C语言的宏一样。
这个时候就是.natvis发挥作用的时候了。我们直接.nvload vlpp.natvix然后再dx z一下:

爽!
到了结束的时候了,我们g让他把TEST_ASSERT执行,然后就炸了。炸完我们还是可以看变量,看完就走:
g
kn 5
.frame 2
dv
dx z

最后一个q优雅退出。
VSCode里面跑copilot还要让LLM在命令行里面交互那是不可能的,所以我们只能把CDB包装成一个一个的命令,执行完退出看结果。所幸CDB是支持远程调试功能的,那么我们可以把server起起来。需要注意的是CDB server一样会等待我们的输入,所以不能占用当前的窗口。单机调试不用管任何安全或者是防火墙问题,简单直接:
cdb -server npipe:pipe=随便给个名字 ...
如果你只是想简单的连接他然后正常使用:
cdb -remote npipe:pipe=上面那个名字
不过让LLM来就得让cdb -remote跑完一个命令直接闪退。我们要做的事情就比较扭曲。比如说我们要跑kn 5命令。首先要创建一个文件,比如说input.txt
".cls`r`nkn 5`r`n.remote_exit" | Out-File -Encoding ASCII -FilePath input.txt
这个时候会得到:
.cls
kn 5
.remote_exit
然后一口气连接执行跑路:
cdb -remote npipe:pipe=上面那个名字 -cf input.txt | Tee-Object -FilePath output.txt
于是你就可以打开input.txt,找到最后的一行xxx> .cls,前面的全部删掉,就得到这个命令的运行结果了。这样做的原因是cdb -remote每次都会把所有的历史记录全部扒下来,用这个方法可以打个标记,就知道我们现在给的命令是从哪里开始输出的。
这一节的内容可以封装成脚本,这样就可以让LLM调试你的程序的同时不会被调试器打断命令行窗口了。
我发现cdb还有-clines 0可以用,不用做这个.cls了。
Powershell 5.0有个问题,就是echo ".remote_exit" | cdb ...会带BOM,这就是为什么需要有个input.txt来装.remote_exit命令用于退出客户端。如果是更高版本的Powershell或者是CMD就没有这个问题。