LLVM IR 转 SMT公式

为什么需要LLVM IR 转SMT公式

LLVM IR中有两个特殊的值:

  • undef:表示"未定义",可以是任意值,而且每次读取都可能得到不同的值。
  • poison:表示"毒化",是更受限的未定义,通常由算术溢出等操作产生,一旦使用就会导致UB(未定义行为)。

在验证优化时,我们必须精确地处理这些特殊值,否则可能会把正确的优化误判为错误,或者反之。SMT编码就是用数学逻辑来描述这些语义。

编码一个寄存器的值

对于每个虚拟寄存器(比如 %a),我们用一个有序对来表示它的状态:

text 复制代码
R[%a] = (实际值, is_poison)
  • 实际值:如果该寄存器当前是undef,则用一个新创建的SMT变量(如undef_1)表示;否则就是它原本的LLVM值(也可能是poison,但poison单独用标志表示)。
  • is_poison:布尔值,表示该寄存器当前是否处于poison状态。

例子:简单函数

考虑以下LLVM函数

llvm 复制代码
define i32 @test(i32 %a) {
  %t = add i32 %a, %a          ; t = a + a
  %c = icmp eq i32 %t, 0       ; c = (t == 0)
  %q = shl i32 %a, 2           ; q = a << 2
  %r = and i32 %a, 1           ; r = a & 1
  ret i32 %r                   ; 注意这里返回的是%r,但原文有分支
}

改成带分支

llvm 复制代码
define i32 @test(i32 %a) {
  %t = add i32 %a, %a
  %c = icmp eq i32 %t, 0
  br i1 %c, label %then, label %else

then:
  %q = shl i32 %a, 2
  ret i32 %q

else:
  %r = and i32 %a, 1
  ret i32 %r
}

现在按照文本中的方式编码每个寄存器(假设%a不是undef也不是poison,仅用于展示):

%a:R%a = (ite(is_undef_a, undef_1, %a), is_poison_a) 意思是:

  • 如果%a是undef(is_undef_a为真),则实际值取新变量undef_1;否则取原值%a。同时用一个布尔is_poison_a记录它是否是poison。

%t = add %a, %a :加法结果的实际值 = ite(is_undef_t1, undef_2, R%a.val) + ite(is_undef_t2, undef_3, R%a.val) -

  • 即如果某个操作数是undef,就引入新变量
  • 否则用该寄存器的实际值。
  • 结果的is_poison标志根据加法规则传播(例如如果任何操作数是poison或加法溢出则设为poison)。

%c = icmp eq %t, 0:LLVM中布尔用i1表示,所以%c的实际值是一个ite:

  • ite( (R%t.val == 0), 1, 0 ) 同时其is_poison标志取决于%t是否是poison。

%q = shl %a, 2:

  • 原文说"由于非负定义值上分支是UB,我们可以假设%t是负定义的,进而%a也是负定义的"。
  • 这里涉及优化:因为shl要求左移计数必须小于位宽,否则是UB。
  • 但编码中可能忽略undef/poison情况,认为%a已经是非负定义值(即不是undef也不是poison),所以直接编码为shl(%a, 2)且is_poison=false。
  • 这正是文本中"忽略了%a是undef或poison的情况"的含义。

%r = and %a, 1:

  • and操作:实际值 = R[%a].val & 1,is_poison传播。

编码函数的最终状态

函数可以有多个返回点,也可能永不返回(如无限循环或noreturn函数)。因此最终状态需要编码:

  • 返回值(retval):是一个ite树,根据控制流路径选择对应返回指令的值。
  • ub:表示是否存在未定义行为(如使用了poison或执行了UB操作)。
  • noret:布尔值,表示函数是否可能不返回(例如所有路径都陷入无限循环)。

对于上述分支函数,返回值可以写成:

text 复制代码
retval = ite( %t == 0,
              R[%q].val,      // then分支的返回值
              R[%r].val )     // else分支的返回值

同时ub和noret分别根据路径条件组合。

举例说明优化

假设我们分析得知,当%t == 0时,shl %a, 2操作是UB(比如因为%a是poison或者移位计数越界),那么整个then分支的行为就是UB。

因此,在最终的ite表达式中,我们可以直接省略该分支的贡献,甚至将整个返回值简化为else分支的值,因为UB情况下的返回值无意义。

这种简化能大大降低SMT求解器的复杂度,使验证更可行。

相关推荐
Sarvartha12 分钟前
final 关键字
java·开发语言
2601_9520477918 分钟前
单一策略深度教程:用轻易云把集成任务的报错实时推送到钉钉机器人
java·机器人·钉钉
程序员Sunday1 小时前
JavaScript 事件循环面试题,宏任务与微任务怎么执行|Sunday面试指南
开发语言·javascript·面试·校招·事件循环·程序员sunday
数据狐(Datafox)1 小时前
淘宝图片搜索 API 落地实战:基于以图搜货搭建跨境电商选品系统
java·大数据·微服务
zhangzeyuaaa2 小时前
Ruby 多线程、GVL(GIL)与 Mutex 完全指南
开发语言·前端·ruby
谢亮_vipxieliang3 小时前
用 PHP 构建轻量级 REST API:从路由到鉴权的完整实践
开发语言·后端·php
Escalating_xu3 小时前
【C 语言】数据在内存中的存储:补码、大小端、整型陷阱与 IEEE 754 全解析
java·c语言·网络
思无邪663 小时前
用 AI 做 JS 逆向:从抓包到复现的完整方法论
开发语言·javascript·人工智能