如何检测特定的汇编指令并获取它们的参数

How to instrument specific assembly instructions and get their arguments

给定任何用 gcc

编译的 C/C++ source.c
int func()
{
    // bunch of code
    ...
}

将生成一些程序集(示例)。 . .

func():
  str fp, [sp, #-4]!
  add fp, sp, #0
  sub sp, sp, #12
  mov r3, #0
  str r3, [fp, #-8]
  mov r3, #55
  sub sp, fp, #0
  ldr fp, [sp], #4
  bx lr

。 . .最终变成二进制 source.obj

我想要的是能够指定:在每个汇编指令 X 之前,调用我的自定义函数并将指令的参数作为参数传递 X

我真的只对给定的汇编指令是否执行感兴趣。如果我说我关心 mult,我不一定是说我关心原始来源中是否发生乘法。我知道乘以 2^N 会产生移位指令。我明白了。

假设我指定 mov 作为感兴趣的 asm。

生成的程序集将更改为以下内容

func():
  str fp, [sp, #-4]!
  add fp, sp, #0
  sub sp, sp, #12
  // custom code inserted here:
  // I want to call another function with the arguments of **mov**
  mov r3, #0
  str r3, [fp, #-8]
  // custom code inserted here:
  // I want to call another function with the arguments of **mov**
  mov r3, #55 
  sub sp, fp, #0
  ldr fp, [sp], #4
  bx lr

我知道自定义代码可能必须 push/pop 它使用的任何寄存器,具体取决于它使用的寄存器的数量 gcc "knows"。自定义函数可以是 naked function

为什么

每次执行指令 X 时切换引脚以进行实时分析。
每次 X 的参数满足特定条件时记录。

您的问题不清楚(即使进行了额外的编辑;-finstrument-functions 而不是 转换汇编代码,它正在改变编译器的工作方式,在优化和代码生成;它适用于 中级 编译器表示 - 可能在 GIMPLE level, not at the assembler or RTL 级别)。

也许您可以编写一些 GCC plugin which would work at the GIMPLE 级别的代码(通过添加一个优化通道来转换适当的 GIMPLE;顺便说一句,-finstrument-functions 选项正在添加更多通道)。这可能需要几个月的工作(您需要了解 GCC 的内部结构),并且您将在编译器中添加您自己的检测生成过程。

也许您在代码中 using some asm。然后你可以使用一些预处理器宏在它周围插入一些代码。

也许您想更改 ABI or calling conventions(或 GCC 生成汇编代码的方式)。然后你需要修补编译器本身(并在其中实现一个新目标)。这可能需要一年多的工作。

注意 GCC 所做的各种优化。有时您可能需要 volatile asm 而不仅仅是 asm.

我的 documentation page of GCC MELT 提供了许多幻灯片和链接,应该对您有所帮助。

Is it possible to do this with any compiler?

两个GCC and Clang are free software, so you can study their source code and improve it for your needs. But both are very complex (many millions of lines of source code), and you'll need several years of work to fork他们。到你这样做的时候,它们会发生重大变化。

what I’d like to do is choose a set of assembly instructions - like { add, jump } - and tell the compiler to insert a snippet of my own custom assembly code just before any instruction in that set

你应该读一些关于 compilers (e.g. the Dragon Book) and read another book on Instruction Set Architecture and Computer Architecture. You can't just insert arbitrarily some instructions in the assembler code generated by the compiler (because what you insert requires some processor resources that the compiler did manage, e.g. thru register allocation 等的书......)

编辑后

// I want to call another function with the arguments of mov

 mov r3, #0

这在一般情况下是不可能的(或非常困难的)。因为调用其他函数将使用 r3 并破坏其内容。

gcc -c source.c -o source.obj

是GCC的错误使用方式。您需要 optimization(特别是对于生产二进制文件)。如果您关心汇编代码,请使用 gcc -O -Wall -fverbose-asm -S source.c (也许 -O2 -march=native 而不是 -O ...)然后查看 source.s

Let's say I specify mul as the asm of interest.

同样,这是错误的方法。您关心源代码或某些中间表示中的乘法。也许 mul 可能会在没有 -O 的情况下为 x*3 发出,但可能不会与 -O2

在 GIMPLE 级别思考和工作 而不是在汇编程序级别。

示例

首先,查看GCC的源代码。它是免费软件。如果您想了解 -finstrument-functions 的真正工作原理,请花几个月时间阅读 GCC 内部结构(我提供了链接和参考资料),研究 GCC 的实际源代码,然后在 gcc@gcc.gnu.org 上提问。

现在,想象一下您想要计算和检测完成了多少次乘法IMUL 指令,例如因为 8*x 可能被优化为移位机器代码指令)。当然,这取决于启用的优化,并且您将在 GIMPLE 级别工作。您可能会在每个 GCC 基本块的末尾增加一些计数器。因此,在每次 BB 退出后,您将插入一个额外的 GIMPLE 语句。如此简单的仪器可能需要几个月的工作。

或者假设您想要仪器加载 以尽可能检测未定义的行为或解决问题。这就是 address sanitizer 正在做的事情。花了好几年的功夫。

事情比你想象的要复杂

(GCC源代码行数千万左右,不是白白的;C compilers need to be complex今天。)

如果你不关心 C 源代码,你就不应该关心 GCC。汇编代码可以由 Bones, by Clang、JVM 实现、ocamlopt 等生成(所有这些甚至不使用 GCC)。或者可以由其他版本的 GCC(不是您正在检测的版本)生成。

所以花几周时间阅读更多关于编译器的内容,然后再问另一个问题。这个问题应该提到你想要检测哪种二进制文件或汇编程序。检测汇编程序代码(或二进制可执行文件)比检测 GCC(并且根本不使用文本技术)要困难得多。它首先提取 control flow graph 抽象 形式,并对其进行提炼和推理。

顺便说一句,您会发现很多关于源检测和二进制检测的教科书和会议(这些主题是不同的,即使是相关的)。花几个月时间阅读它们。您天真的文本方法有一些 1960 年代的味道,无法扩展,也不适用于今天的软件。

另请参阅此演讲(和视频):Matt Godbolt “What Has My Compiler Done for Me Lately? Unbolting the Compiler's Lid” CppCon 2017