问题描述
我一直在尝试传递一个加载到$t0
中的地址值,其中包含一个!扩展命令I没有写入和一个Windbg脚本I没有写入...通过重新使用别名,我已经取得了一些进展,但我仍然在想,这里是否遗漏了Windbg语法的一些变幻莫测的东西。
!printcols 0x00000000017e1b68
一起工作,但我也知道我可以用该地址值加载$t0
,但我不能使用各种方法将@$t0
成功传递给扩展命令,例如
dx @$t0 = ((foo *) bar)->bar2
后跟:
? @$t0
Evaluate expression: 25041768 = 00000000017e1b68
然后!printcols @$t0
就不起作用了。它提供了扩展用法提示,而不是Windbg错误。这很烦人,因为我知道$t0
=0x00000000017e1b68
,但如果我执行以下操作并引入一个名为lCols
的别名,则!EXTENSION命令可以很好地工作...这是可行的:
dx @$t0 = ((foo *) bar)->bar2; as /x lCols @$t0; !printcols ${lCols}
同样,这与我编写的脚本类似(但不是相同)...我有一个名为get_items.wds
的脚本,它接受一个地址作为它的单个参数...因此$$>a<C:get_items.wds 0x0000000049b50010
工作正常。
但我不能使用0x0000000049b50010
加载$t0
,然后将其传递给get_items.wds
,因此尝试执行如下操作:
0:030> r $t0 = 0x0000000049b50010
0:030> ? @$t0
Evaluate expression: 1236598800 = 0000000049b50010
0:030> $$>a<C:get_items.wds @$t0
将失败。或${@$t0}
或我尝试过的任何其他组合。但别名的把戏也不会以完全相同的方式起作用。如果我在单独的行上执行命令,它们会起作用-那么这与扩展有关吗?-但如果我将它们组合到一行中,它们就不会起作用,所以:
dx @$t0 = ((foo *) bar)->bar2
as /x lItem @$t0
$$>a<H:Downloadsget_ti.wds ${lItem}
这是可行的-我已经通过别名将$t0
的内容传递给了一个脚本(我知道它是dx
中的0x0000000049b50010)。
我可以查看lItem
,当然:
0:030> al
Alias Value
------- -------
lItem 0x49b50010
但如果我在一行上尝试所有这些操作,它将再次失败。温德格喃喃地说"Arg别名已经存在"..。但即使我做了ad
,它也是一样的。正在尝试:
dx @$t0 = ((foo *) bar)->bar2; as /x lItem @$t0; $$>a<C:get_item.wds ${lItem}
不起作用...但完全相同的方法对!扩展名有效。不是吗?
将伪寄存器中保存的值传递给!EXTENSION命令或Windbg脚本是否容易?
推荐答案
TL;DR: 这可能是我写过的最长的一篇帖子,只是为了得出结论
.block{ad /q ${/v:foo}};.block{as /x foo $t0};.block{$$>a<d:debug est.wds foo $t0};.block{ad /q ${/v:foo}}
是您要查找的答案。
但我认为您已经到了一个点,在深入研究脚本之前,您应该知道所有的疯狂之处。 为什么?因为还有CLRMD、PYKD或DotNet-Dump这样的替代方案。
一旦您了解了问题,我将继续,我将找到使您的脚本正常工作的方法。
WinDbg脚本问题
WinDbg脚本编写受到限制且出现故障,WinDbg帮助中的说明不完整,有时甚至具有误导性。 在WinDbg中,似乎没有解析器接受您的命令并构建抽象语法树或任何您所期望的东西,如果您是程序员的话。 可以把它想象成一群口译员接受你的输入,做一些你无法预测的事情。 好了,现在这是一个严酷的声明,不是吗?让我看看...不能简单地使用;
作为分隔符来连接命令
示例1:
0:000> as foo bar
0:000> al
Alias Value
------- -------
foo bar
到目前为止,这是意料之中的。但当您在一行中执行此操作时,输出将丢失:
0:000> as foo bar;al
原因是分号已成为别名的一部分。
0:000> al
Alias Value
------- -------
foo bar;al
您可能会同意,任何使用分号的语言分析器都不会以这种方式处理它。
此特定问题的解决方案:使用aS
或使用.block{}
。
清理:ad *
示例2:
0:000> ad foo
0:000> aS foo bar
0:000> .echo ${foo}
bar
那太好了。但当您在一行中执行此操作时,输出会有所不同:
0:000> ad foo;aS foo bar;.echo ${foo}
${foo}
清理:ad *
我怀疑这是不是真的预料到的,但至少它是有记录的:
请注意,如果分号之后的那部分行需要扩展别名,则必须将该行的第二部分括在新块中。
此问题的解决方案:使用.block{}
。
示例3:
0:000> *
0:000> .echo foo
foo
显然变成了
0:000> *;.echo foo
但是,您能从行评论中期待什么呢?
此问题的解决方案:使用$$
或使用.block{}
。
示例4:
0:000> ~*e .echo hello
hello
hello
hello
hello
0:000> .echo world
world
这突然变成了
0:000> ~*e .echo hello; .echo world
hello
world
hello
world
hello
world
hello
world
此问题的解决方案:使用.block{}
。
示例5:
如果您认为只有内置命令才使用分号,那么您就错了。元命令也会受到影响:
0:000> .extpath somepath
Extension search path is: somepath
0:000> ? 5
Evaluate expression: 5 = 00000000`00000005
相对于
0:000> .extpath somepath;? 5
Extension search path is: somepath;? 5
所以分号神奇地变成了路径分隔符,%PATH%
。
此问题的解决方案:使用.block{}
。
示例6:
到目前为止,您已经看到行首的命令会影响行尾。但也有可能是相反的方向:
2:008> r $t0 = 5
2:008> r $t0 = $t0 -1 ; z($t0)
redo [1] r $t0 = $t0 -1 ; z($t0)
redo [2] r $t0 = $t0 -1 ; z($t0)
redo [3] r $t0 = $t0 -1 ; z($t0)
redo [4] r $t0 = $t0 -1 ; z($t0)
0:000> r $t0 = 5; r $t0 = $t0 -1 ; z($t0)
redo [1] r $t0 = 5; r $t0 = $t0 -1 ; z($t0)
redo [2] r $t0 = 5; r $t0 = $t0 -1 ; z($t0)
redo [3] r $t0 = 5; r $t0 = $t0 -1 ; z($t0)
redo [4] r $t0 = 5; r $t0 = $t0 -1 ; z($t0)
redo [5] r $t0 = 5; r $t0 = $t0 -1 ; z($t0)
redo [6] r $t0 = 5; r $t0 = $t0 -1 ; z($t0)
[...]
清理:重新启动调试器
此问题的解决方案:使用.block{}
。
示例7:
0:000> $<d:debug est.wds
0:000> ? 5
Evaluate expression: 5 = 00000000`00000005
对比
0:000> $<d:debug est.wds;? 5
Command file execution failed, Win32 error 0n123
"The filename, directory name, or volume label syntax is incorrect."
至少,这是有记录的:
因为$<;允许在文件名中使用分号,所以不能将$<;与其他调试器命令连接在一起,因为分号不能同时用作命令分隔符和文件名的一部分。
此问题的解决方案:使用.block{}
。
您不能简单地编写空语句
分号本身不执行任何操作:
0:000> ;
0:000> ;;
0:000> ;;;
但您不能将其与所有内容组合。
示例:
0:000> ;aS foo bar
0:000> ;al
Alias Value
------- -------
foo bar
0:000> ;ad foo
^ No information found error in ';ad bar'
清理:ad *
WinDbg(有时)区分空格
示例1:
通常,命令前面的空格并不重要。
0:000> aS foo bar
0:000> ad foo
0:000> al
No aliases
我喜欢用分号分隔的命令,这些命令有额外的视觉分隔空间,尤其是在使用分号时:
0:000> aS foo bar; ad foo; al
No aliases
现在尝试这样做,在每行的命令前面增加一个空格:
0:000> aS foo bar
0:000> ad foo
^ No information found error in ' ad bar'
清理:ad *
示例2:
您从命令行和编程语言中了解到的是,用空格分隔标记。对于较新的程序,我们拥有<program> <verb> <options> [--] [<files>]
喜欢
git commit -m "commit message" -- helloworld.cpp
其中各个部分由空格隔开。所以,这看起来非常熟悉:
2:008> lm f m ntdll
start end module name
00007fff`3b100000 00007fff`3b2f0000 ntdll ntdll.dll
但您也可以这样做:
2:008> lmfmntdll
Browse full module list
start end module name
00007fff`3b100000 00007fff`3b2f0000 ntdll ntdll.dll
您只能想知道:WinDbg如何区分以lm开头的不同命令?也许它不能。至少没有。
一行并不总是一行
我们已经使用过as
命令,并且我们发现有些命令是基于行的。
另一个例子是COMMENT命令。描述如下:
如果星号(*
)字符位于命令的开头,则该行的其余部分将被视为注释,即使分号出现在它之后也是如此。
我喜欢将*
与.logopen
结合使用来记录我的调查结果。
0:000> * I just found out how to use comments
0:000> * Even ; does not matter here
术语行的含义似乎与CRLF";之前的所有字符都不同:
0:000> .echo before;.block{* surprise};.echo after
before
after
同样适用于as
的文档:
如果不使用任何开关,则as命令将该行的其余部分用作别名等效项。
0:000> ad *
0:000> as foo bar;k
0:000> ad *
0:000> .block{as foo bar};k
# Child-SP RetAddr Call Site
00 00000017`73dbf120 00007fff`24ed455f ntdll!LdrpDoDebuggerBreak+0x30
至少这是一致的。
字符串转义中断
字符串参数通常不需要引号。
0:000> .echo Hello
Hello
有时您可以使用引号而不起作用:
0:000> .echo "Hello"
Hello
有时您必须使用它们:
0:000> .echo Hello;World
Hello
^ Syntax error in '.echo Hello;World'
0:000> .echo "Hello;World"
Hello;World
现在,如何打印引号?嗯,你可以在中间用
0:000> .echo He"lo
He"lo
但不是在开头已经使用的时候:
0:000> .echo "He"lo"
^ Malformed string in '.echo "He"lo"'
每种编程语言都能以某种方式转义引号,但WinDbg不能
0:000> .echo "
"
0:000> .echo """
^ Malformed string in '.echo """'
0:000> .echo """"
^ Malformed string in '.echo """"'
0:000> .echo """
^ Malformed string in '.echo """'
或者可能可以,有时:
0:000> .foreach /s (x "Hello World "Hello") {}
0:000> .printf ""Hello World""
"Hello World"
这似乎只是取决于命令。
批注并不总是批注
我们以前提到过$$
作为*
的替代方案。微软说:
如果命令的开头出现两个美元符号($$),则该行的其余部分将被视为注释,除非注释以分号结尾。
和
不以任何方式处理以*或$$标记为前缀的文本。
总的来说,这似乎是可行的:
0:000> $$ Yippieh!
0:000> $$Yay
当然,除非评论以<
0:000> $$<
^ Non-empty string required in '$$<'
这是因为$$<
是不同的命令。只是$$
的文档忘记了这一点。
使您的脚本工作
$$&>
在我看来,您似乎需要$$>a<
,因为这是唯一接受参数的命令。因此,您必须接受它的其他属性,如下所示:
允许文件名包含分号:否
允许连接以分号分隔的其他命令:是
浓缩为单个命令块:是
尤其是最后一个,这里比较棘手。那浓缩成一个区块到底是什么意思?您可以通过触发错误消息的命令来最好地看到这一点:
文件内容:
.echo before
.echo """
.echo after
结果:
0:000> $$>a<d:debug est.wds
before
^ Malformed string in '.echo before;.echo """;.echo after'
这意味着:所有命令都将由分号连接-我们知道这会导致一大堆命令出现问题。
幸运的是,它们中的大多数可以通过.block{}
修复。您甚至可以在您的脚本中使这些块看起来很漂亮:
.echo before
.block{
.echo ${$arg1}
.echo """
.echo ${$arg2}
}
.echo after
记住
这将在.block{
后面添加一个空分号,通常不做任何操作,但会扰乱ad
。
块内容的缩进在命令前面增加了空格,这通常只会扰乱ad
别名扩展
对于此实验,您需要文件内容
.echo ${$arg1}
.echo ${$arg2}
如我们所见,即使没有${}
语法:
0:000> as foo bar
0:000> r $t0 = 1
0:000> $$>a<d:debug est.wds foo $t0
bar
$t0
仅当别名不用空格分隔时才需要${}
:
0:000> $$>a<d:debug est.wds foobar $t0
foobar
$t0
0:000> $$>a<d:debug est.wds ${foo}bar $t0
barbar
$t0
伪寄存器
伪寄存器不是别名,它们不会以相同的方式展开。并且您不能对它们应用别名解释程序${}
:
0:000> $$>a<d:debug est.wds $t0 ${t0}
$t0
${t0}
0:000> $$>a<d:debug est.wds ${$t0} ${@$t0}
${$t0}
${@$t0}
但基本上,它可以像预期的那样与脚本中的命令一起工作。脚本
.echo ${$arg1}
r ${$arg2}
将按预期输出:
0:000> $$>a<d:debug est.wds foo $t0
bar
$t0=0000000000000001
扩展命令
扩展命令(以!
开头)在DLL中实现。您可以自己构建这样的扩展,它们已经由其他开发人员构建。他们中的一些人确实支持WinDbg的功能,并考虑到了它的特性,而另一些人则不支持。
在实践中,如果其中一些需要地址,则需要传递一个数值。无论您的WinDbg数字格式设置为什么(请参阅n
命令),此数字都可能必须以十六进制指定。如果以0x
作为该十六进制地址的前缀,其中一些操作甚至会失败。
完全支持会是什么样子?
数字,考虑基数 符号名称 寄存器 伪寄存器 表达式、MASM和C++ ...例如,!chkimg
将计算伪寄存器:
0:000> r $t0 = ntdll
0:000> !chkimg $t0
3 errors : $t0 (7fff3b27e000-7fff3b27e002)
Istruggled with this sort of support myself recently,所以我猜您的!printcols
命令可能没有实现所有这些。
在调用扩展之前仍将处理别名,如我们在此实验中所见:
0:000> !chkimg foo
Unable to determine offset from expression: foo
0:000> as foo ntdll
0:000> !chkimg foo
3 errors : ntdll (7fff3b27e000-7fff3b27e002)
清理:ad *
最后,解决方案
假设!printcols
没有那么复杂,您将需要处理它。
解决方案是:
.block{ad /q ${/v:foo}};.block{as /x foo $t0};.block{$$>a<d:debug est.wds foo $t0};.block{ad /q ${/v:foo}}
这里发生了什么?
ad
尝试删除现有别名,以便as /x
不会抱怨该别名已存在。
/q
设置为静默,以防不存在此类别名
我们不能只执行ad /q foo;something
,因为这将搜索名为foo;something
的别名
如果我们放入.block{ad /q foo}
,ad
不再是该行的第一个字符。因此,foo
将被其别名替换,从而查找名为bar
(或任何值)的错误别名。
要转义别名替换,请使用${/v:foo}
如果别名foo
以前存在,它将在任何地方被替换。我们不希望as /x
出现这种情况。因此,引入另一个重新计算别名的块。这次将保留foo
,因为我们之前删除了foo
。
使用as /x
设置别名foo
后,我们需要再次引入一个新块,以便为脚本评估别名。
最后,清除别名foo
,以免它损坏其他东西。
如果您阅读了整个答案,则您现在是WinDbg脚本忍者。