如果数据仅在 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 调试器中可见,我想捕获所有生成的数据。

目前我知道我可以通过以下方式捕获数据:

  1. 我设置断点break set -n generateMoreData
  2. 一旦遇到断点,我检查 $rdi$rsi 的值,因为系统 V 的 x86_64 ABI(被 Linux、BSD、macOS 使用, 和 Solaris) 在这些寄存器中传递前两个参数。
  3. 然后我继续使用 thread step-out.
  4. 直到函数 returns
  5. 最后,我可以使用 x -c COUNT ADDRESS 转储数据,其中 COUNT$rsi 的值,ADDRESS$rdi 的值,如步骤中所示(2)

这没问题,但我想将整个过程自动化,运行分为两个问题:

该问题的总体解决方案是实际使用两个断点。

首先我们通过将所需的值存储到 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 时进行正确的打印。