Windbg - 将伪寄存器传递给扩展和脚本

Windbg - passing pseudo-registers to extensions and scripts

我一直在尝试使用我 没有 编写的 !extension 命令和我 编写的 Windbg 脚本来传递加载到 $t0 的地址值]did 写...我已经通过返回使用别名取得了一些进展,但我仍然想知道我是否遗漏了这里 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:\Downloads\get_ti.wds ${lItem}

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

我可以查lItem,当然:

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

但是如果我在一行中尝试所有这些,它又会失败。 Windbg 咕哝了一些关于 "Arg alias already exists"... 但即使我做了 ad 也是一样的。所以尝试:

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

不起作用...但完全相同的方法 确实 对 !extension 有效。不是吗?

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

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

.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 *

我怀疑这是否真的是预期的,但至少它被记录在案:

Note that if the portion of the line after the semicolon requires expansion of the alias, you must enclose that second portion of the line in a new block.

这个问题的解决方法:使用.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."

至少,这是有记录的:

Because $< allows semicolons to be used in the file name, you cannot concatenate $< with other debugger commands, because a semicolon cannot be used both as a command separator and as part of a file name.

这个问题的解决方法:使用.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 是白色的space 敏感(有时)

示例 1:

一般情况下,命令前加个space无所谓。

0:000> aS foo bar
0:000> ad foo
0:000> al
No aliases

我喜欢用分号分隔的命令,这些命令有额外的 space 用于视觉分隔,尤其是在使用分号时:

0:000> aS foo bar; ad foo; al
No aliases

现在尝试在每行的命令前添加一个 space:

0:000>  aS foo bar
0:000>  ad foo
             ^ No information found error in ' ad bar'

清理:ad *

示例 2:

您从命令行和编程语言了解到的是 space 分隔标记。我们有更新的程序

<program> <verb> <options> [--] [<files>]

喜欢

git commit -m "commit message" -- helloworld.cpp

其中各个部分由 space 分隔。所以,这看起来非常熟悉:

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 开头的不同命令?恐怕不能。至少还有none.

一行并不总是一行

我们已经玩过 as 命令,我们发现有些命令是基于行的。

另一个例子是评论命令。是这样描述的:

If the asterisk (*) character is at the start of a command, then the rest of the line is treated as a comment, even if a semicolon appears after it.

我喜欢结合使用 *.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的文档:

If you do not use any switches, the as command uses the rest of the line as the alias equivalent.

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"

好像就是要看命令了

评论并不总是评论

我们之前提到过 $$ 作为 * 的替代品。微软说:

If two dollar signs ( $$ ) appear at the start of a command, then the rest of the line is treated as a comment, unless the comment is terminated by a semicolon.

Text prefixed by the * or $$ tokens is not processed in any way.

总的来说,这似乎有效:

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
  • 块内容的缩进在命令前添加白色space,这通常只会弄乱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

当别名没有被space分隔时,你只需要${}:

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
  • 将别名foo设置为as /x后,我们需要再次引入一个新的块,以便为脚本评估别名。
  • 最后,在别名 foo 破坏其他东西之前清理别名。

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