胡琪

为今天工作,为明天投资,为未来孵化一些东西

llvm中间语言IR介绍

使用Clang编译cpp源码得到IR

首先使用编译器前端clang执行如下命令即可得到中间语言IR,

其中的ori_cpp_file表示原始的cpp文件,out_ir_file表示编译后输出的IR文件。执行该命令后即会在out_ir_file路径下生成源cpp文件的llvm的汇编代码。该命令等同于执行如下命令:

如果不加-S参数那么生成的会是源cpp文件的二进制代码。

IR语法介绍

为了了解IR的语法,我们可以写一段简单的cpp源文件,然后使用clang执行前面说的命令来对比查看下生成的IR文件的内容从而来熟悉IR的语法,为了能够尽可能多的覆盖IR的语法,cpp源文件原本需要尽量包含更多变量类型和程序结构,比如字符串,整数,数组,条件分支,for/while循环结构等。但是为了更容易上手,我们可以把这些结构拆分开来每次只放到一个单独的cpp文件中,然后逐个编译,而不推荐一开始就混在一个cpp文件中,因为即使是一个简单的hello word程序其IR的代码量也是很大的,所以对于初学者而言,混在一起反而不容易上手,等基本语法都熟悉了可以在写个复杂的涵盖程序所有变量类型和程序结构的cpp文件一次性编译查看。

IR语法之IR基本组成部分

这里是一个及其简单的c++源码示例,

其对应的IR代码如下:

一个cpp源文件代码中一般会包含源文件(类或者整个源代码),函数声明,函数定义实现部分,注释等,可以看到IR也同样包含对应的结构,比如模块Module(类似源文件的概念)和注释(在IR中使用英文;表示)比如第一行的注释ModuleID表面该IR属于hello_IR.cpp源文件,函数Function(使用define定义或者declare声明,和c语言类似),基本块Basic Block(类似cpp源文件中的函数体代码实现,不过基本块是一个更小的单位,只具备单入口和单出口,必须以终止指令结尾),指令Instruction,所以整个IR的组成结构大概可以看作是如下结构:

Module->Function->Basic Block->Instruction,也就是Module下包含很多函数,函数中包含很多基本块,基本块中包含很多具体的指令,之所以强调这个结构,是因为后面我们使用LLVM框架提供的类和API接口进行相应操作的时候基本上是按照这个结构来的,而我们之所以去了解IR的语法也是为了为基于llvm框架进行开发做准备。

IR语法之变/常量,数组

这里先写一个简单的包含整型/浮点型/字符型和字符串类型,全局/局部变量的示例如下:

然后其对应的IR表示如下:

从上面的示例中可以简单总结出以下几点语法规则

  • 注释使用英文分号:,比如IR文件的第一行一般使用注释表面该IR属于哪个Module
  • 函数的定义使用define,函数在Module内属于全局值(该概念对比与llvm中的GlobleValue,包含全局变量GlobalVariable和函数Function),函数名前以@开头
  • 函数的声明使用declare
  • 全局变量使用@开头,后面紧接着的是变量名称,全局常量使用constant修饰符修饰,全局变量使用global 修饰符修饰,函数内定义的字符串常量会转为全局常量,

比如示例中main函数中定义的字符串:

转化为了全局常量:

  • 局部变量(这里指的是IR中的局部变量而不是源cpp中的局部变量)使用%开头,后面是局部变量的名称,比如源cpp文件中的int i=0;中int i的定义在IR中的表示为 %10 = alloca i32, align 4 ,表示定义一个局部变量%10,为其分配一个i32大小内存,以4字节对齐,即int i
  • 所有的整数数据类型(包括短整型short,整型int,长整型long)和char类型都用ix表示,其中x表示该数据类型的占位数,比如int/long类型是i32表示4字节32位,short是i16表示2字节16位,char类型是i8表示一个字节8位。
  • 浮点数据类型float和double用floate和double表示
  • 数组类型用[count x ix]表示,其中count表示数组的大小,ix表示数组中每一个元素对应的数据类型,参见上面的一条,比如字符串”Hello IR”表示为[9 x i8],9表示该字符串包含9个元素(末尾包含一个\0),每个元素大小为i8即c语言中的char类型大小
  • 使用c++中string和c语言中的char *定义的字符串会以全局常量@.str开头,后面以数字结尾,比如第一个是@.str,第二个是@.str.1,第3个是@str.2,依次类推。而使用char []字符数组定义的字符串也会转为全局常量但是不会以.str.开头

IR语法之比较跳转

如下是一个简单的包含if分支大于等于和小于判断的源cpp文件:

其对应的IR代码如下:

可以很清晰的看到if语句在IR中是使用比较指令icmp和跳转指令br来实现的,根据比较指令icmp的比较结果跳转至相应的基本块执行。

其中比较指令icmp的语法格式如下:

cond表示比较条件,比如大于还是小于,ty表示比较的操作数的类型,op1,op2表示比较指令的操作数,以上面示例为例:

比较条件是slt表示有符号数小于,i32表示比较的操作数是32位,%2,0表示将局部变量%2和数值0进行小于比较,也就是if(i<0)这条语句。其中比较指令参数包括如下一些情况:

  1. eq: equal ,即==
  2. ne: not equal,即!=
  3. ugt: unsigned greater than ,即无符号数>
  4. uge: unsigned greater or equal,即无符号数>=
  5. ult: unsigned less than,即无符号数<
  6. ule: unsigned less or equal,即无符号数<=
  7. sgt: signed greater than ,即有符号数>
  8. sge: signed greater or equal,即有符号数>=
  9. slt: signed less than,即有符号数<
  10. sle: signed less or equal,即有符号数<=

除了整形比较指令icmp外还有浮点型比较指令fcmp,和icmp参数几乎完全一样,关于这2个指令的详细介绍,大家可以参考官方文档进行查阅:http://llvm.org/docs/LangRef.html#icmp-instruction

跳转指令br语法如下:

可以看到br跳转包括无条件跳转和有条件跳转,即br il和br。cond表示跳转条件,第一个lable表示如果条件位true要跳转到哪一个基本块的标签(用来标记该基本块的入口),第二个label表示如果比较条件为false要跳转的基本块。还是以上面的示例为例:

表示如果局部变量%cmp4的值为真,则跳转到标签为if.then5的基本块执行,否则跳转到标签为if.end的基本块执行。

至于无条件跳转br指令就很容易理解了,直接跳转至标签为dest的基本块执行。

IR语法之循环

同样的写个简单的包含for/while循环的示例代码:

其对应的IR代码如下:

可以看到for/while循环仍然时借助比较指令icmp和跳转指令br来完成的,只不过增加了一个对计数器的加法指令add,加法指令add语法如下:

其中的nuw和nsw表示“No Unsigned Wrap” and “No Signed Wrap”,ty表示操作数类型,op1和op2表示操作数,还是以上面示例为例,在for混淆中存在对计数器i的i++语法,这部分IR代码如下:

add nsw i32 %1,1表示将局部变量1的值加1之后赋给局部变量inc。除了整型加法指令add外,还包括浮点型加法指令fadd,和add指令参数几乎完全一样,关于这2个指令的详细介绍,大家可以参考官方文档进行查阅:http://llvm.org/docs/LangRef.html#add-instruction

所以现在来看for/while循环的实现逻辑就是这样的:

首先跳转至for/while循环的条件比较基本块for.cond:/while.cond: 中进行判断是否满足循环条件,如果满足跳转值for/while循环逻辑执行基本块for.body:/while.body: 也就是对应源cpp文件中的大花括号{}中的代码块,在该基本块中会对循环计数器进行+1操作,然后跳转至条件比较基本块for.cond:/while.cond:,重复上述过程直至条件比较基本块比较结果为false,则跳转至混淆结束基本块for.end: /while.end:结束循环。

IR指令对应的llvm开发框架下的类

 

打赏

点赞

发表评论

电子邮件地址不会被公开。 必填项已用*标注