首页 > 解决方案 > Windbg - 将伪寄存器传递给扩展和脚本

问题描述

我一直在尝试传递一个使用我没有$t0编写的 !extension 命令和编写的 Windbg 脚本加载的地址值......我通过回到使用别名取得了一些进展,但我仍然想知道如果我在这里遗漏了 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工作正常。

但我无法加载$t0然后0x0000000049b50010将其传递给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:\Downloads\get_ti.wds ${lItem}

这很有效 - 我已经通过别名将 的内容传递$t0给了一个脚本(我知道它是 0x0000000049b50010 dx)。

lItem当然可以检查:

0:030> al
  Alias            Value  
 -------          ------- 
 lItem            0x49b50010

但是,如果我在一行上尝试所有这些,它会再次失败。Windbg 咕哝着“Arg 别名已经存在”……但即使我这样做也是一样的ad。所以尝试:

dx @$t0 = ((foo *) bar)->bar2; as /x lItem @$t0; $$>a<C:\get_item.wds ${lItem}

不起作用......但完全相同的方法确实适用于!扩展名。不是吗?

我是否应该很容易将伪寄存器中保存的值传递给 !extension 命令或 Windbg 脚本?

标签: parametersscriptingwindbgaliases

解决方案


TL;DR:这可能是我写过的最长的 SO 帖子,只是为了得出结论

.block{ad /q ${/v:foo}};.block{as /x foo $t0};.block{$$>a<d:\debug\test.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{}.

您不知道不同类别的 WinDbg 命令吗?有关命令类的更多奇怪和不一致之处,请参见此答案。

示例 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\test.wds
0:000> ? 5
Evaluate expression: 5 = 00000000`00000005  

0:000> $<d:\debug\test.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命令,并且发现有些命令是基于行的。

另一个例子是注释命令。它是这样描述的:

如果星号 ( *) 字符位于命令的开头,则该行的其余部分将被视为注释,即使其后出现分号也是如此。

我喜欢*结合使用.logopen来记录我的发现。

0:000> * I just found out how to use comments
0:000> * Even ; does not matter here

术语line似乎与“直到 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\test.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\test.wds foo $t0
bar
$t0

${}当别名不以空格分隔时才需要:

0:000> $$>a<d:\debug\test.wds foobar $t0
foobar
$t0
0:000> $$>a<d:\debug\test.wds ${foo}bar $t0
barbar
$t0

伪寄存器

伪寄存器不是别名,它们不会以同样的方式扩展。而且您不能${}对它们应用别名解释器:

0:000> $$>a<d:\debug\test.wds $t0 ${t0}
$t0
${t0}
0:000> $$>a<d:\debug\test.wds ${$t0} ${@$t0}
${$t0}
${@$t0}

但基本上,它将按预期使用脚本中的命令。剧本

.echo ${$arg1}
r ${$arg2}

将按预期输出:

0:000> $$>a<d:\debug\test.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)

最近自己也在为这种支持而苦苦挣扎,所以我的猜测是你的!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\test.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了。
  • 用设置别名fooas /x,我们需要再次引入一个新块,以便为脚本评估别名。
  • foo最后,在它破坏其他东西之前清理别名。

如果您阅读了整个答案,那么您现在是 WinDbg 脚本忍者。


推荐阅读