有没有一种标准的方法来重建降低的结构函数参数?
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)。顺便说一句,它在函数体后面重构了原来的两个参数lp
和lp2
。
现在对于我的分析,我想得到这两个参数 lp
和 lp2
的全部,在这 4(lp.coerce0
, lp.coerce1
, lp2.coerce0
和 lp2.coerce1
)。在这种情况下,我可能可以依赖名称(.coerce0
表示第一个字段,.coerce1
- 第二个)。
我不喜欢这种方法:
- 我不确定,Clang 会在以后的版本中保留此约定
- 这当然取决于 ABI,因此在另一个平台上可能会有不同的细分。
另一方面,我不能在函数的开头使用重构代码,因为我可能会将它与某些局部变量的用户代码混淆。
我使用基于 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
中的哪些参数是您的参数,您可以执行以下操作:
- 访问您通行证中的每个
alloca
机构并关注其用户。
- 按照
alloca
的转换以及 GEP 说明找到
在 boundptr
上存储说明。
- 检查要存储的值。如果它们是您的函数参数并且匹配类型和名称,那么您已找到重新组装的
boundptr
.
当然,您也可以从函数参数开始,反过来做。
这是未来的证明吗?不,绝对不是。 Clang/LLVM 并非旨在保持向后兼容性。对于兼容性,ABI 很重要。
缺点:您必须在代码生成后尽早进入优化器。即使 01
也会删除 boundptr
的这些堆栈分配。所以你必须修改你的 clang
以在优化期间执行你的传递并且你不能使它成为一个独立的传递(例如,由 opt
使用)。
更好的解决方案:
由于必须以某种方式修改 clang,因此您可以添加元数据来标识 boundptr
类型的参数。因此,您可以 "pack" 将您 boundptr
的片段放在一起,以将它们识别为 boundptr
。这将在优化器中存活下来。
我有一个结构类型:
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)。顺便说一句,它在函数体后面重构了原来的两个参数lp
和lp2
。
现在对于我的分析,我想得到这两个参数 lp
和 lp2
的全部,在这 4(lp.coerce0
, lp.coerce1
, lp2.coerce0
和 lp2.coerce1
)。在这种情况下,我可能可以依赖名称(.coerce0
表示第一个字段,.coerce1
- 第二个)。
我不喜欢这种方法:
- 我不确定,Clang 会在以后的版本中保留此约定
- 这当然取决于 ABI,因此在另一个平台上可能会有不同的细分。
另一方面,我不能在函数的开头使用重构代码,因为我可能会将它与某些局部变量的用户代码混淆。
我使用基于 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
中的哪些参数是您的参数,您可以执行以下操作:
- 访问您通行证中的每个
alloca
机构并关注其用户。 - 按照
alloca
的转换以及 GEP 说明找到 在boundptr
上存储说明。 - 检查要存储的值。如果它们是您的函数参数并且匹配类型和名称,那么您已找到重新组装的
boundptr
.
当然,您也可以从函数参数开始,反过来做。
这是未来的证明吗?不,绝对不是。 Clang/LLVM 并非旨在保持向后兼容性。对于兼容性,ABI 很重要。
缺点:您必须在代码生成后尽早进入优化器。即使 01
也会删除 boundptr
的这些堆栈分配。所以你必须修改你的 clang
以在优化期间执行你的传递并且你不能使它成为一个独立的传递(例如,由 opt
使用)。
更好的解决方案:
由于必须以某种方式修改 clang,因此您可以添加元数据来标识 boundptr
类型的参数。因此,您可以 "pack" 将您 boundptr
的片段放在一起,以将它们识别为 boundptr
。这将在优化器中存活下来。