如果数据仅在 C 函数的 return 上可用,如何使用 LLDB 自动捕获输出数据?
How to automate capturing output data with LLDB if that data is only available on return of a C function?
我编译了没有任何源代码的二进制文件,但我知道它包含一个具有以下签名的 C 函数
void generateMoreData ( char * destination, long size )
该函数的符号在 LLDB 调试器中可见,我想捕获所有生成的数据。
目前我知道我可以通过以下方式捕获数据:
- 我设置断点
break set -n generateMoreData
- 一旦遇到断点,我检查
$rdi
和 $rsi
的值,因为系统 V 的 x86_64 ABI(被 Linux、BSD、macOS 使用, 和 Solaris) 在这些寄存器中传递前两个参数。
- 然后我继续使用
thread step-out
. 直到函数 returns
- 最后,我可以使用
x -c COUNT ADDRESS
转储数据,其中 COUNT
是 $rsi
的值,ADDRESS
是 $rdi
的值,如步骤中所示(2)
这没问题,但我想将整个过程自动化,运行分为两个问题:
问题A:只有在进入函数$rdi
和$rsi
时才包含我需要的值,但在函数returns时不再包含这些值,因为这些寄存器被使用通过函数,从而失去它们的初始值。
问题 B:我可以使用 break command add
设置在命中断点时执行的命令,但这些命令不能包含 thread step-out
,因为该命令继续执行并且第一个命令继续执行会停止处理断点命令,因此永远不会执行在此命令之后设置的任何命令。
该问题的总体解决方案是实际使用两个断点。
首先我们通过将所需的值存储到 LLDB 变量来解决问题 A:
break set -G true -n generateMoreData
break command add 1
> expr long $destination = $rsi
> expr long $size = $rdi
> DONE
-G true
确保程序在执行所有命令后自动继续,并且使用 expr
可以将寄存器内容存储到我们命名为 $destination
和 [=16 的变量中=].
为了解决问题B,我们需要在generateMoreData
的return指令处设置另一个断点。如果 generateMoreData
有多个 return 指令,我们需要在每个指令处设置一个断点,但我们假设它是一个相当简单的函数,通常只有一个断点。
首先我们需要中断那个函数,所以我们只是在它上面设置一个普通的断点,让调试器命中它。然后我们可以使用 dis
反汇编函数。输出可能类似于下面的输出:
-> 0x7fff76be0bac <+0>: pushq %rbp
0x7fff76be0bad <+1>: movq %rsp, %rbp
0x7fff76be0bb0 <+4>: pushq %r14
0x7fff76be0bb2 <+6>: pushq %rbx
0x7fff76be0bb3 <+7>: subq [=11=]x40, %rsp
:
0x7fff76be0c5f <+179>: popq %r14
0x7fff76be0c61 <+181>: popq %rbp
0x7fff76be0c62 <+182>: retq
0x7fff76be0c63 <+183>: nop
绝对地址通常无用,因为它们可能在程序的两个 运行 之间改变(例如,由于 ASLR,地址 Space 布局随机化),有趣的地址是相对地址( <+...>
)。知道 +182
处有一个 return 允许我们在那里设置断点,这样我们就可以解决问题 B:
break set -G true -n generateMoreData -R 182
break command add 2
> x -o generateMoreData.txt --append-outfile -c `$size` $destination
> DONE
-R
以字节为单位设置相对偏移量。捕获的数据被写入 generateMoreData.txt
供以后检查(-o
设置输出文件,--append-outfile
确保附加新数据而不是覆盖现有数据)。
现在只是 运行 程序,最后您可以在输出文件中检查所有生成的数据。
找出步骤 B 的断点地址的更简单方法是利用父框架中的 pc 值始终是该框架的 return pc 这一事实。所以我们可以让断点 1 的命令弄清楚。由于您在到达第二个断点时未使用当前帧的任何信息,因此您不关心是在 returns 处还是在 returns 之后停止。
你必须记得先清除旧的,但是在 lldb 中使用命名断点很容易做到:
break set -G true -n generateMoreData --skip-prologue false
breakpoint name configure SecondBreakpoint -G true -C "x -o generateMoreData.txt --append-outfile -c \`$size\` $destination" -C "break delete SecondBreakpoint"
break command add 1
> expr long $destination = $arg1
> expr long $size = $arg2
> up
> break set -N SecondBreakpoint -a $pc
> DONE
请注意,我们不必将命令添加到我们创建的断点,因为我已将其添加到名称,因此新断点将从该名称继承它们。
我还使用了 $arg1
和 $arg2
而不是 $rsi
和 $rdi
。这只是一个方便的 lldb 别名,以防你不记得哪个是哪个......
此外,如果可能从多个线程调用此函数,则解决此问题会变得更加困难。然后你需要获取当前线程并设置线程特定的断点。此外,如果这将被递归调用,则必须为每个 returning 帧设置不同的 $size 和 $destination 值。
如果您开始尝试处理此类问题,最好对断点使用 Python 回调。然后,不像 Mecki 聪明的解决方案那样在过程中存储要在 return 上打印的数据,您可以维护一个小的 Python 数据结构,该结构会记住每个 [=27] 的 $size 和 $destination =] 组合,并在每次调用 returns 时进行正确的打印。
我编译了没有任何源代码的二进制文件,但我知道它包含一个具有以下签名的 C 函数
void generateMoreData ( char * destination, long size )
该函数的符号在 LLDB 调试器中可见,我想捕获所有生成的数据。
目前我知道我可以通过以下方式捕获数据:
- 我设置断点
break set -n generateMoreData
- 一旦遇到断点,我检查
$rdi
和$rsi
的值,因为系统 V 的 x86_64 ABI(被 Linux、BSD、macOS 使用, 和 Solaris) 在这些寄存器中传递前两个参数。 - 然后我继续使用
thread step-out
. 直到函数 returns
- 最后,我可以使用
x -c COUNT ADDRESS
转储数据,其中COUNT
是$rsi
的值,ADDRESS
是$rdi
的值,如步骤中所示(2)
这没问题,但我想将整个过程自动化,运行分为两个问题:
问题A:只有在进入函数
$rdi
和$rsi
时才包含我需要的值,但在函数returns时不再包含这些值,因为这些寄存器被使用通过函数,从而失去它们的初始值。问题 B:我可以使用
break command add
设置在命中断点时执行的命令,但这些命令不能包含thread step-out
,因为该命令继续执行并且第一个命令继续执行会停止处理断点命令,因此永远不会执行在此命令之后设置的任何命令。
该问题的总体解决方案是实际使用两个断点。
首先我们通过将所需的值存储到 LLDB 变量来解决问题 A:
break set -G true -n generateMoreData
break command add 1
> expr long $destination = $rsi
> expr long $size = $rdi
> DONE
-G true
确保程序在执行所有命令后自动继续,并且使用 expr
可以将寄存器内容存储到我们命名为 $destination
和 [=16 的变量中=].
为了解决问题B,我们需要在generateMoreData
的return指令处设置另一个断点。如果 generateMoreData
有多个 return 指令,我们需要在每个指令处设置一个断点,但我们假设它是一个相当简单的函数,通常只有一个断点。
首先我们需要中断那个函数,所以我们只是在它上面设置一个普通的断点,让调试器命中它。然后我们可以使用 dis
反汇编函数。输出可能类似于下面的输出:
-> 0x7fff76be0bac <+0>: pushq %rbp
0x7fff76be0bad <+1>: movq %rsp, %rbp
0x7fff76be0bb0 <+4>: pushq %r14
0x7fff76be0bb2 <+6>: pushq %rbx
0x7fff76be0bb3 <+7>: subq [=11=]x40, %rsp
:
0x7fff76be0c5f <+179>: popq %r14
0x7fff76be0c61 <+181>: popq %rbp
0x7fff76be0c62 <+182>: retq
0x7fff76be0c63 <+183>: nop
绝对地址通常无用,因为它们可能在程序的两个 运行 之间改变(例如,由于 ASLR,地址 Space 布局随机化),有趣的地址是相对地址( <+...>
)。知道 +182
处有一个 return 允许我们在那里设置断点,这样我们就可以解决问题 B:
break set -G true -n generateMoreData -R 182
break command add 2
> x -o generateMoreData.txt --append-outfile -c `$size` $destination
> DONE
-R
以字节为单位设置相对偏移量。捕获的数据被写入 generateMoreData.txt
供以后检查(-o
设置输出文件,--append-outfile
确保附加新数据而不是覆盖现有数据)。
现在只是 运行 程序,最后您可以在输出文件中检查所有生成的数据。
找出步骤 B 的断点地址的更简单方法是利用父框架中的 pc 值始终是该框架的 return pc 这一事实。所以我们可以让断点 1 的命令弄清楚。由于您在到达第二个断点时未使用当前帧的任何信息,因此您不关心是在 returns 处还是在 returns 之后停止。
你必须记得先清除旧的,但是在 lldb 中使用命名断点很容易做到:
break set -G true -n generateMoreData --skip-prologue false
breakpoint name configure SecondBreakpoint -G true -C "x -o generateMoreData.txt --append-outfile -c \`$size\` $destination" -C "break delete SecondBreakpoint"
break command add 1
> expr long $destination = $arg1
> expr long $size = $arg2
> up
> break set -N SecondBreakpoint -a $pc
> DONE
请注意,我们不必将命令添加到我们创建的断点,因为我已将其添加到名称,因此新断点将从该名称继承它们。
我还使用了 $arg1
和 $arg2
而不是 $rsi
和 $rdi
。这只是一个方便的 lldb 别名,以防你不记得哪个是哪个......
此外,如果可能从多个线程调用此函数,则解决此问题会变得更加困难。然后你需要获取当前线程并设置线程特定的断点。此外,如果这将被递归调用,则必须为每个 returning 帧设置不同的 $size 和 $destination 值。
如果您开始尝试处理此类问题,最好对断点使用 Python 回调。然后,不像 Mecki 聪明的解决方案那样在过程中存储要在 return 上打印的数据,您可以维护一个小的 Python 数据结构,该结构会记住每个 [=27] 的 $size 和 $destination =] 组合,并在每次调用 returns 时进行正确的打印。