在写OLLVM的时候,经常会遇到各种坑点,这里就专门开一篇来记录一些自己遇到的、比较难处理的坑。本文动态更新。
复制基本块相关
在Obf的时候经常要对块进行split和clone,这个过程的坑特别多
unmatched subprogram between llvm.dbg.value and !dbg
在使用Debug模式编译程序,并对程序的一些基本块进行复制时,会出现此问题,报错来自于LLVM的Verify机制(verify.cpp)。解决这个报错之前首先先要介绍一下 @llvm.dbg.value 是什么
void @llvm.dbg.value(metadata, i64, metadata, metadata)
这个函数当 local variable 被 set new value 时会被调用,其目的是用于源码级调试时变量数据的同步。
参数 1:被 metadata 包装的 new value
参数 2:local value 中的 offset
参数 3:local variable( DILocalVariable类型 表示源码中的局部变量
参数 4:complex expression
DILocalVariable字段:
name: 源码中这个变量的名字
arg:如果不是 0,则表示他是一个子程序 subprogram 的参数,他会在 DISubprogram 的 variables 字段中
line:行号
type.scope.file…
DISubprogram:表示源码中的方法,他会被使用 !dbg attach 在方法的 define 后面
报错原因:LLVM Verify 机制中,要求 llvm.dbg.value 的第三个参数(源码局部变量)和后面的 !dbg 的 subprogram 一致。然而,clone-remap 后两个 subprogram 内容一样,但是编号不一样。
解决方法:
- clone 的时候不去 clone tail call @llvm.dbg.value。即,clone函数内判断指令如果是 call @llvm.dbg.value 则remove。但这种方法会引入一个新的问题,即,原始块和复制块指令数量不一样,在处理 attach loc 的时候会出差错。
- remap 的时候不去修改 orig 中tail call @llvm.dbg.value信息。即,clone函数内判断指令如果是 call @llvm.dbg.value 则skip,不去ReMap其中的变量。也是笔者目前采用的方法。
下面给出修复问题的 createCloneBasicBlock 函数
|
|
寄存器降级:CatchPadInst not the first non-PHI instruction in the block.
这个问题发生在,Windows下,代码中存在C++异常(IR层面存在Invoke和catchpad)时,对其进行寄存器降级时。
问题复现代码
|
|
原始IR
entry:
%retval = alloca i32, align 4
%a = alloca i32, align 4
%b = alloca i32, align 4
%c = alloca i32, align 4
store i32 0, ptr %retval, align 4
call void @llvm.dbg.declare(metadata ptr %a, metadata !1217, metadata !DIExpression()), !dbg !1218
store i32 0, ptr %a, align 4, !dbg !1218
%call = call i32 (ptr, ...) @scanf(ptr noundef @"??_C@_02DPKJAMEF@?$CFd?$AA@", ptr noundef %a) #6, !dbg !1219
call void @llvm.dbg.declare(metadata ptr %b, metadata !1220, metadata !DIExpression()), !dbg !1221
%0 = load i32, ptr %a, align 4, !dbg !1222
%call1 = invoke noundef i32 @"?callF@@YAHP6AHH@ZH@Z"(ptr noundef @"?f@@YAHH@Z", i32 noundef %0)
to label %invoke.cont unwind label %catch.dispatch, !dbg !1222
catch.dispatch: ; preds = %entry
%1 = catchswitch within none [label %catch] unwind to caller, !dbg !1224
catch: ; preds = %catch.dispatch
%2 = catchpad within %1 [ptr @"??_R0H@8", i32 0, ptr %c], !dbg !1224
call void @llvm.dbg.declare(metadata ptr %c, metadata !1225, metadata !DIExpression()), !dbg !1226
%3 = load i32, ptr %c, align 4, !dbg !1227
store i32 %3, ptr %b, align 4, !dbg !1227
%call2 = call i32 (ptr, ...) @printf(ptr noundef @"??_C@_0BB@LLPJFGLF@catch?5exception?6?$AA@") #6 [ "funclet"(token %2) ], !dbg !1229
catchret from %2 to label %catchret.dest, !dbg !1230
对其寄存器降级后就变成了这样
entry:
%retval.reg2mem = alloca ptr, align 8
%a.reg2mem = alloca ptr, align 8
%b.reg2mem = alloca ptr, align 8
%c.reg2mem = alloca ptr, align 8
%.reg2mem = alloca i32, align 4
%rand.ptr = alloca i32, align 4
%0 = load i32, ptr %rand.ptr, align 4
%1 = mul i32 %0, 3
...............
catch.dispatch: ; preds = %endBB
%18 = catchswitch within none [label %catch] unwind to caller, !dbg !1224
catch: ; preds = %catch.dispatch
%c.reload9 = load ptr, ptr %c.reg2mem, align 8
%19 = catchpad within %18 [ptr @"??_R0H@8", i32 0, ptr %c.reload9], !dbg !1224
...............
可以看到,原始IR的catch块中,开头第一条指令是catchpad,其第三个参数是寄存器 %c,这里实际上对应的是C代码 catch (int c),c变量是声明在entry块中,alloc其为IR寄存器,对应IR %c = alloca i32, align 4 。当复制entry时,会对entry块中用到的逃逸变量进行寄存器降级,对应 createCloneBasicBlock 函数
|
|
即,因为catch的这个c变量,在entry块中声明为一个寄存器,对entry块复制的时候,将函数内的该变量降级成了一个变量,并在所有用到该寄存器的指令前插入了一条Load指令,也就是降级后IR的catch块的第一条指令 %c.reload9 = load ptr, ptr %c.reg2mem, align 8,其将被降级的变量load到寄存器以给catchpad指令使用。但是LLVM要求catch块的第一条非PHI指令必须为catchpad,所以出错。
解决方法:观察了catchpad的使用,发现在正常情况下LLVM会事先的将需要用到的catch变量提前load到一个寄存器里去,所以,解决方案是重写寄存器降级函数,如果发现catchpad指令,则将该Load指令移动到entry块上,以避免直接插入到catchpad指令前。
给出重写后的DemoteRegToStack函数
|
|