如果输出数据仅在返回C函数时可用,如何使用LLDB自动捕获该数据?

如果输出数据仅在返回C函数时可用,如何使用LLDB自动捕获该数据?,c,debugging,lldb,C,Debugging,Lldb,我编译的二进制代码没有任何源代码,但我知道它包含一个带有以下签名的C函数 void generateMoreData ( char * destination, long size ) 该函数的符号在LLDB调试器中可见,我希望捕获所有生成的数据 目前我知道我可以通过以下方式捕获数据: 我设置了一个断点断点集-n generateMoreData 一旦到达断点,我将检查$rdi和$rsi的值,因为System V的x86_64 ABI(由Linux、BSD、macOS和Solaris使用)在这

我编译的二进制代码没有任何源代码,但我知道它包含一个带有以下签名的C函数

void generateMoreData ( char * destination, long size )
该函数的符号在LLDB调试器中可见,我希望捕获所有生成的数据

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

  • 我设置了一个断点
    断点集-n generateMoreData
  • 一旦到达断点,我将检查
    $rdi
    $rsi
    的值,因为System V的x86_64 ABI(由Linux、BSD、macOS和Solaris使用)在这些寄存器中传递前两个参数
  • 然后继续,直到函数使用
    线程步出返回
  • 最后,我可以使用
    x-c COUNT ADDRESS
    转储数据,其中
    COUNT
    $rsi
    的值,
    ADDRESS
    $rdi
    的值,如步骤(2)所示
  • 这可以正常工作,但我想自动化整个过程,并遇到两个问题:

    • 问题A:只有在输入函数时,
      $rdi
      $rsi
      才包含我需要的值,但当函数返回时,这些寄存器被函数使用,因此丢失了它们的初始值,这些值就不再存在了

    • 问题B:我可以使用
      break command add
      设置在命中断点时要执行的命令,但这些命令不能包含
      线程跳出
      ,因为此命令将继续执行,而继续执行的第一个命令将停止处理断点命令,因此,在此命令之后设置的任何命令都不会执行


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

        首先,我们通过将所需值存储到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
        $size
        的变量中

        为了解决问题B,我们需要在
        generateMoreData
        的返回指令处设置另一个断点。如果
        generateMoreData
        有多条返回指令,我们需要在每条返回指令上都有一个断点,但我们假设这是一个相当简单的函数,通常只有一个断点

        首先,我们需要进入该函数,所以我们只需在其上设置一个普通断点,然后让调试器点击它。然后我们可以使用
        dis
        反汇编函数。输出可能类似于下面的输出:

        ->  0x7fff76be0bac <+0>:   pushq  %rbp
            0x7fff76be0bad <+1>:   movq   %rsp, %rbp
            0x7fff76be0bb0 <+4>:   pushq  %r14
            0x7fff76be0bb2 <+6>:   pushq  %rbx
            0x7fff76be0bb3 <+7>:   subq   $0x40, %rsp
            :
            0x7fff76be0c5f <+179>: popq   %r14
            0x7fff76be0c61 <+181>: popq   %rbp
            0x7fff76be0c62 <+182>: retq   
            0x7fff76be0c63 <+183>: nop
        
        -R
        以字节为单位设置相对偏移量。捕获的数据将写入
        generateMoreData.txt
        以供以后检查(
        -o
        设置输出文件,并且
        --append outfile
        确保追加新数据而不是覆盖现有数据)


        现在只需运行程序,最后可以检查输出文件中生成的所有数据。

        计算步骤B断点地址的更简单方法是利用父帧中的pc值始终是该帧的返回pc这一事实。所以我们可以让断点1的命令找出它。由于在到达第二个断点时没有使用来自当前帧的任何信息,因此您不在乎是在返回时停止还是在返回后停止

        您必须记住首先清除旧的断点,但在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别名,以防您记不起哪个是哪个

        此外,如果可能从多个线程调用此函数,则解决此问题会变得更加困难。然后您需要获取当前线程并设置一个特定于线程的断点。此外,如果要递归调用,则必须为每个返回帧生成不同的$size和$destination值


        如果您开始尝试处理这类问题,那么最好对断点使用Python回调。然后,不必像Mecki聪明的解决方案那样存储返回时要打印的数据,您可以维护一个小Python数据结构,它可以记住每个帧/线程组合的$size和$destination,并在每次调用返回时正确打印。

        非常有趣的解决方案。在一个断点内设置一个断点是我迄今为止从未想到过的。我还考虑了Python解决方案,但我的Python技能非常有限。
        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