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

时间:2016-01-28 13:09:08

标签: c clang llvm llvm-ir abi

我有一个结构类型:

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

现在,对于我的分析,我希望完全从这4个中lplp2,{{1}完整地获取这两个参数lp.coerce0lp.coerce1lp2.coerce0)。在这种情况下,我可能依赖于名称(lp2.coerce1表示第一个字段,.coerce0 - 秒)。

我不喜欢这种做法:

  • 我不确定,Clang将在以后的版本中保留此约定
  • 这当然取决于ABI,因此在另一个平台上可能会有不同的细分。

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

我使用基于LLVM .coerce1的Clang 3.4.2作为目标3.4.2

P.S。这是another example,显示了Clang如何能够搞乱函数参数。

1 个答案:

答案 0 :(得分:1)

我假设您正在编译而不是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
  4. 当然,你可以从函数参数开始反过来做。

    这是未来证明吗?没有明确的否定。 Clang / LLVM不是为了保持向后兼容性而设计的。为了兼容性,ABI很重要。

    缺点:你必须在代码生成之后很早就进入优化器。即使01也会删除boundptr的这些堆栈分配。因此,您必须修改clang以在优化期间执行传递,而不能将其作为独立传递(例如opt使用)。

    更好的解决方案: 由于必须以某种方式修改clang,因此您可以添加标识boundptr类型参数的元数据。因此,您可以将boundptr的片段“打包”在一起,将其标识为boundptr。这将在优化器中存活。