llvm生成rdrand指令

问题

在做一个project的时候需要使用llvm的pass对函数进行插桩,在每一个函数头之前插入一条指令rdrand %rax,在寻找llvm基本指令之后发现并没有生成随机数的指令,这时就想到了llvm中intrinsic函数中是否有关于rdrand指令的函数,在对llvm整个源码进行扫描之后,发现有x86的rdrand的intrinsic函数:

1
2
3
4

X86_INTRINSIC_DATA(rdrand_16, RDRAND, X86ISD::RDRAND, 0),
X86_INTRINSIC_DATA(rdrand_32, RDRAND, X86ISD::RDRAND, 0),
X86_INTRINSIC_DATA(rdrand_64, RDRAND, X86ISD::RDRAND, 0),

既然有对它们的定义,那是否有关于它们的使用呢,又经过一番查找,终于在一个测试文件中(/test/CodeGen/X86/rdrand.ll)找到相应的使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
declare {i16, i32} @llvm.x86.rdrand.16()
declare {i32, i32} @llvm.x86.rdrand.32()
declare {i64, i32} @llvm.x86.rdrand.64()

define i32 @_rdrand16_step(i16* %random_val) {
%call = call {i16, i32} @llvm.x86.rdrand.16()
%randval = extractvalue {i16, i32} %call, 0
store i16 %randval, i16* %random_val
%isvalid = extractvalue {i16, i32} %call, 1
ret i32 %isvalid
; CHECK-LABEL: _rdrand16_step:
; CHECK: rdrandw %ax
; CHECK: movzwl %ax, %ecx
; CHECK: movl $1, %eax
; CHECK: cmovael %ecx, %eax
; CHECK: movw %cx, (%r[[A0:di|cx]])
; CHECK: ret
}

define i32 @_rdrand32_step(i32* %random_val) {
%call = call {i32, i32} @llvm.x86.rdrand.32()
%randval = extractvalue {i32, i32} %call, 0
store i32 %randval, i32* %random_val
%isvalid = extractvalue {i32, i32} %call, 1
ret i32 %isvalid
; CHECK-LABEL: _rdrand32_step:
; CHECK: rdrandl %e[[T0:[a-z]+]]
; CHECK: movl $1, %eax
; CHECK: cmovael %e[[T0]], %eax
; CHECK: movl %e[[T0]], (%r[[A0]])
; CHECK: ret
}

define i32 @_rdrand64_step(i64* %random_val) {
%call = call {i64, i32} @llvm.x86.rdrand.64()
%randval = extractvalue {i64, i32} %call, 0
store i64 %randval, i64* %random_val
%isvalid = extractvalue {i64, i32} %call, 1
ret i32 %isvalid
; CHECK-LABEL: _rdrand64_step:
; CHECK: rdrandq %r[[T1:[a-z]+]]
; CHECK: movl $1, %eax
; CHECK: cmovael %e[[T1]], %eax
; CHECK: movq %r[[T1]], (%r[[A0]])
; CHECK: ret
}

这个系列函数是对rdrandx_step(address)系列函数的包装,在这些函数中是生成随机数,并将该随机数保存到address参数中。发现在该系列函数中对@llvm.x86.rdrand.xx()系列函数的调用,可以发现,在该函数第一个语句是对@llvm.x86.rdrand.xx() intrinsic函数的调用,该函数返回一个StructType类型的结果,该结果一共有两个成员组成,第一个成员是@llvm.x86.rdrand.xx()函数产生的随机数,第二个参数表示该函数是否调用成功。第二个语句就是从结果中取出随机数。弄清了该函数的逻辑后就可以通过llvm C API产生相应的IR指令。

llvm C API

下面是我通过llvm的C API产生的相应的IR指令的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

AllocaInst* AI3 = B.CreateAlloca(PtrTy, nullptr, "RandomValue");

std::vector<Type *> arg_type;

Function *fun = Intrinsic::getDeclaration(F->getParent(), Intrinsic::x86_rdrand_64, arg_type);
CallInst* result = B.CreateCall(fun, {});
//B.CreateRet(result);
//result->dump();
if (dyn_cast<StructType>(result->getType())) {
//errs() << "Hello\n";
Value* randomValue = B.CreateExtractValue(result, (uint64_t)0);
Value* randomValuePtr = B.CreateIntToPtr(randomValue, Type::getInt8PtrTy(B.getContext()));
B.CreateStore(randomValuePtr, AI3, true);
}

注意事项

在生成了.ll文件后,需要用llc工具生成二进制文件,此时需要添加-mattr=rdrnd的属性:

1
llc -filetype=obj input.ll -mattr=+rdrnd -o output.o

否则会出现LLVM ERROR: Cannot select: t74: i64,i32,ch = X86ISD::RDRAND t0错误