有没有一种标准的方法来重建降低的结构函数参数?

Is there a standard way to reconstruct lowered struct function arguments?

我有一个结构类型:

typedef struct boundptr {
  uint8_t *ptr;
  size_t size;
} boundptr;

并且我想捕获该类型函数的所有参数。例如。在这个函数中:

boundptr sample_function_stub(boundptr lp, boundptr lp2);

在我的 64 位机器上,Clang 将该签名翻译成:

define { i8*, i64 } @sample_function_stub(i8* %lp.coerce0, i64 %lp.coerce1, i8* %lp2.coerce0, i64 %lp2.coerce1) #0 {

问题:

有没有更好的方法来重构这些论点?

是否可以禁止此类参数降低,同时为外部调用保持相同的 ABI?

更多上下文:

所以在 LLVM IR 中,我猜,根据平台 ABI,编译器将结构分解为单独的字段(这不是最坏的情况,请参阅 1)。顺便说一句,它在函数体后面重构了原来的两个参数lplp2

现在对于我的分析,我想得到这两个参数 lplp2 的全部,在这 4(lp.coerce0, lp.coerce1, lp2.coerce0lp2.coerce1)。在这种情况下,我可能可以依赖名称(.coerce0 表示第一个字段,.coerce1 - 第二个)。

我不喜欢这种方法:

另一方面,我不能在函数的开头使用重构代码,因为我可能会将它与某些局部变量的用户代码混淆。


我使用基于 LLVM 3.4.2 的 Clang 3.4.2 作为目标 x86_64-pc-linux-gnu

P.S。这是 another example,展示了 Clang 可以多么疯狂地混淆函数参数。

我假设您没有使用 O0 进行编译。 AFAIK,当您不优化代码时,clang 会重新组装原始类型。 Clang 分解您的结构以通过寄存器(至少在 x86 上)将它们传递给被调用的函数。正如您所说,这取决于所使用的 ABI。

这是您的用例中的一个虚拟示例:

#include <cstddef>

typedef struct boundptr {
  void *ptr;
  size_t size;
} boundptr;

boundptr foo(boundptr ptr1, boundptr ptr2) { return {ptr1.ptr, ptr2.size}; }

int main() {
  boundptr p1, p2;
  boundptr p3 = foo(p1, p2);
  return 0;
}

clang -O0 -std=c++11 -emit-llvm -S -c test.cpp 编译生成 foo:

define { i8*, i64 } @_Z3foo8boundptrS_(i8* %ptr1.coerce0, i64 %ptr1.coerce1, i8* %ptr2.coerce0, i64 %ptr2.coerce1) #0 {
  %1 = alloca %struct.boundptr, align 8
  %ptr1 = alloca %struct.boundptr, align 8
  %ptr2 = alloca %struct.boundptr, align 8
  %2 = bitcast %struct.boundptr* %ptr1 to { i8*, i64 }*
  %3 = getelementptr { i8*, i64 }, { i8*, i64 }* %2, i32 0, i32 0
  store i8** %ptr1.coerce0, i8** %3
  %4 = getelementptr { i8*, i64 }, { i8*, i64 }* %2, i32 0, i32 1
  store i64 %ptr1.coerce1, i64* %4
  %5 = bitcast %struct.boundptr* %ptr2 to { i8*, i64 }*
  %6 = getelementptr { i8*, i64 }, { i8*, i64 }* %5, i32 0, i32 0
  store i8** %ptr2.coerce0, i8** %6
  %7 = getelementptr { i8**, i64 }, { i8**, i64 }* %5, i32 0, i32 1
  store i64 %ptr2.coerce1, i64* %7
  %8 = getelementptr inbounds %struct.boundptr, %struct.boundptr* %1, i32 0, i32 0
  %9 = getelementptr inbounds %struct.boundptr, %struct.boundptr* %ptr1, i32 0, i32 0
  %10 = load i8*, i8** %9, align 8
  store i8* %10, i8** %8, align 8
  %11 = getelementptr inbounds %struct.boundptr, %struct.boundptr* %1, i32 0, i32 1
  %12 = getelementptr inbounds %struct.boundptr, %struct.boundptr* %ptr2, i32 0, i32 1
  %13 = load i64, i64* %12, align 8
  store i64 %13, i64* %11, align 8
  %14 = bitcast %struct.boundptr* %1 to { i8*, i64 }*
  %15 = load { i8*, i64 }, { i8*, i64 }* %14, align 8
  ret { i8*, i64 } %15
}

boundptr在被调用函数栈上重构(这也取决于使用的调用约定)。

现在要找出 boundptr 中的哪些参数是您的参数,您可以执行以下操作:

  1. 访问您通行证中的每个 alloca 机构并关注其用户。
  2. 按照 alloca 的转换以及 GEP 说明找到 在 boundptr 上存储说明。
  3. 检查要存储的值。如果它们是您的函数参数并且匹配类型和名称,那么您已找到重新组装的 boundptr.

当然,您也可以从函数参数开始,反过来做。

这是未来的证明吗?不,绝对不是。 Clang/LLVM 并非旨在保持向后兼容性。对于兼容性,ABI 很重要。

缺点:您必须在代码生成后尽早进入优化器。即使 01 也会删除 boundptr 的这些堆栈分配。所以你必须修改你的 clang 以在优化期间执行你的传递并且你不能使它成为一个独立的传递(例如,由 opt 使用)。

更好的解决方案: 由于必须以某种方式修改 clang,因此您可以添加元数据来标识 boundptr 类型的参数。因此,您可以 "pack" 将您 boundptr 的片段放在一起,以将它们识别为 boundptr。这将在优化器中存活下来。