LLVM IR转CFG

    技术2022-07-17  84

    文章目录

    概述一、llvm二、clang1、clang与LLVM的关系:2、LLVM编译工具链编译流程3、使用clang编译c代码生成字节码bc4、使用clang编译c生成可执行二进制文件5、clang生成代码的中间文件IR6、IR和bc相互转换 三、llvm+clang5.0.0安装四、控制流图(Control Flow Graph, CFG)原理if else分析whilefordo-whileswitchbreak-continue 五、IR指令分析1、C代码2、对应的IR文件内容3、IR指令 分析 六、IR生成CFG1、利用可视化工具生成CFG图png2、python遍历ll生成对应的CFG

    概述

    环境: Ubuntu18 本文重点内容是:

    1、学习代码的CFG原理2、利用代码的中间表示IR, 生成代码IR的控制流图CFG

    一、llvm

    LLVM是构架编译器(compiler)的框架系统, 以C++编写而成,用于优化以任意程序语言编写的程序的编译时间(compile-time)、链接时间(link-time)、运行时间(run-time)以及空闲时间(idle-time),对开发者保持开放,并兼容已有脚本;

    如下:llvm包含三部分, 前端Frontend:负责检查语法错误,构建抽象语法树AST, 抽象语法树可以进一步优化,最终转化为新的表达方式IR, 然后进入优化器 优化器Optimize: 这部分对IR(Intermediate representation)进行操作优化, 后端Backend:后端负责将优化好的IR解释成为对应平台的机器码

    llvm的有点: 支持全部代码转化为IR, 中间表示IR优秀

    二、clang

    1、clang与LLVM的关系:

    clang类似gcc是c/c++编译器 相比与gcc,clang的优势:高度模块化,轻量级编译器,编译速度快,占用内存小,方便二次开发

    clang与LLVM是C/C++编译器套件。对于LLVM框架来说,clang是非常好的前端,clang能够分析代码,生成ir

    2、LLVM编译工具链编译流程

    从源代码到token的词法分析和从token到AST的语法分析;词法分析的输出是将源代码解析成一个个的token。这些token就是有类型和值的一些小单元,比如是关键字,还是数字,还是标识符,从AST转LLVM开始,LLVM就开始提供一系列的工具帮助我们快速开发。从IR(中间指令代码)到DAG(有向无环图)再到机器指令,针对常用的平台,LLVM有完善的后端。也就是说,我们只要完成了到IR这一步,后面的工作我们就享有和Clang一样的先进生产力了。

    3、使用clang编译c代码生成字节码bc

    clang -c -emit-llvm test1.c -o test1.bc

    4、使用clang编译c生成可执行二进制文件

    clang test.c -o test ./test执行

    5、clang生成代码的中间文件IR

    clang -S -emit-llvm test.c -o a.ll

    6、IR和bc相互转换

    llvm-dis test1.bc test1.ll llvm-as test1.ll test1.bc

    三、llvm+clang5.0.0安装

    免费下载

    不需要积分

    tar -vxf llvm* # 解压 mkdir build && cd build cmake ../llvm-5.0.0.src -DLLVM_TARGETS_TO_BUILD=X86 -DCMAKE_BUILD_TYPE=Release -DLLVM_USE_LINKER=gold make -j4 # 大约需要两个小时 sudo make install # 运行安装完成后

    llvm-as --version# 查看llvm版本

    LLVM (http://llvm.org/): LLVM version 5.0.0 Optimized build. Default target: x86_64-unknown-linux-gnu Host CPU: haswell

    clang -v # 查看clang版本

    zjq@DESKTOP-O28RVV3:DFG$ clang -v clang version 5.0.0 (tags/RELEASE_500/final) Target: x86_64-unknown-linux-gnu Thread model: posix InstalledDir: /usr/local/bin Found candidate GCC installation: /usr/lib/gcc/x86_64-linux-gnu/7 Found candidate GCC installation: /usr/lib/gcc/x86_64-linux-gnu/7.5.0 Found candidate GCC installation: /usr/lib/gcc/x86_64-linux-gnu/8 Selected GCC installation: /usr/lib/gcc/x86_64-linux-gnu/7.5.0 Candidate multilib: .;@m64 Selected multilib: .;@m64

    如果有问题, 可以跳转到另一个博客 双系统安装 win10+Ubuntu18 注意是第六章23

    四、控制流图(Control Flow Graph, CFG)原理

    注意:结构内表示简单代码,如x=z+y等

    if else分析

    if(x<y){ if} else{ else} return

    if (x<y){ if} return;

    if(x<y){ return;} return

    while

    x = 0; while(x<y){ while} return

    for

    i=0 for(x;x<y;x++){ for} return

    do-while

    x=0 do{ do}while(x<y); return

    switch

    switch(c){ case(1):{case1;break;} case(2):{case2;break;} case(3):{case3;break;} } return

    break-continue

    x = 0; while (x < y){ while1; if (y == 0){ break; } else if (y < 0) { y = y*2; continue; } while2; } return

    五、IR指令分析

    1、C代码

    // test.c #include <stdio.h> int func(int x){ return x+2; } int main(int argc, char const *argv[]) { int x=0; int y=0; for(x=0; x<20; x++){ y = func(x); } if(y>10){ y=10; } else{ y=0; } printf("%d\n", y); printf("%s\n", "hello"); return 0; }

    编译运行 clang test.c ./a.out

    输出10

    编译生成ir文件 clang -S -emit-llvm test.c -o a.ll

    2、对应的IR文件内容

    ; ModuleID = 'test.c' source_filename = "test.c" target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" target triple = "x86_64-unknown-linux-gnu" @.str = private unnamed_addr constant [4 x i8] c"%d\0A\00", align 1 @.str.1 = private unnamed_addr constant [4 x i8] c"%s\0A\00", align 1 @.str.2 = private unnamed_addr constant [6 x i8] c"hello\00", align 1 ; Function Attrs: noinline nounwind optnone uwtable define i32 @func(i32) #0 { %2 = alloca i32, align 4 store i32 %0, i32* %2, align 4 %3 = load i32, i32* %2, align 4 %4 = add nsw i32 %3, 2 ret i32 %4 } ; Function Attrs: noinline nounwind optnone uwtable define i32 @main(i32, i8**) #0 { %3 = alloca i32, align 4 %4 = alloca i32, align 4 %5 = alloca i8**, align 8 %6 = alloca i32, align 4 %7 = alloca i32, align 4 store i32 0, i32* %3, align 4 store i32 %0, i32* %4, align 4 store i8** %1, i8*** %5, align 8 store i32 0, i32* %6, align 4 store i32 0, i32* %7, align 4 store i32 0, i32* %6, align 4 br label %8 ; <label>:8: ; preds = , %2 %9 = load i32, i32* %6, align 4  = icmp slt i32 %9, 20 br i1 , label , label  ; <label>:11: ; preds = %8  = load i32, i32* %6, align 4  = call i32 @func(i32 ) store i32 , i32* %7, align 4 br label  ; <label>:14: ; preds =   = load i32, i32* %6, align 4  = add nsw i32 , 1 store i32 , i32* %6, align 4 br label %8 ; <label>:17: ; preds = %8  = load i32, i32* %7, align 4  = icmp sgt i32 , 10 br i1 , label , label ! ; <label>:20: ; preds =  store i32 10, i32* %7, align 4 br label " ; <label>:21: ; preds =  store i32 0, i32* %7, align 4 br label " ; <label>:22: ; preds = !, # = load i32, i32* %7, align 4 $ = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str, i32 0, i32 0), i32 #) % = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str.1, i32 0, i32 0), i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str.2, i32 0, i32 0)) ret i32 0 } declare i32 @printf(i8*, ...) #1 attributes #0 = { noinline nounwind optnone uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+fxsr,+mmx,+sse,+sse2,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" } attributes #1 = { "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+fxsr,+mmx,+sse,+sse2,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" } !llvm.module.flags = !{!0} !llvm.ident = !{!1} !0 = !{i32 1, !"wchar_size", i32 4} !1 = !{!"clang version 5.0.0 (tags/RELEASE_500/final)"}

    3、IR指令 分析

    define i32 @func(i32) #0 { ## i32表示32位, @表函数 func函数名称 %2 = alloca i32, align 4 ## alloca i32 表示新建全局变量%2, 32位, align表示对齐 store i32 %0, i32* %2, align 4 ## store 表示写入,将%0的值给%2 %3 = load i32, i32* %2, align 4 ## load读出%2的值给%3 %4 = add nsw i32 %3, 2 ## add加, 将%3加上2给%4 ret i32 %4 } ; <label>:8: ; preds = , %2 表示有跳转到%8, %2调转到%8(下面的块可验证) ; <label>:14: ; preds =   = load i32, i32* %6, align 4  = add nsw i32 , 1 store i32 , i32* %6, align 4 br label %8 跳转指令 br i1 , label , label ! 如果为真, 跳转到 的块, 为假,跳转到!的块 br label  直接跳转到的块  = icmp sgt i32 , 10 比较icmp, sgt为大于, sge为大于等于, sle小于等于 因此是比较大于10? ->  即if(y>10)

    六、IR生成CFG

    1、利用可视化工具生成CFG图png

    opt -dot-dom a.ll # 使用a.ll解析main和func的CFG, # 生成dom.main.dot和dom.main.dot两个文件 # sudo apt install graphviz # 如果没有可视化工具, 自行安装 dot -Tpng -o test_main.png dom.main.dot # 生成png格式的CFG图->test_main.png 主函数的CFG

    func函数的CFG 上面的命令可能会出现错误 WARNING: You’re attempting to print out a bitcode file. This is inadvisable as it may cause display problems. If you REALLY want to taste LLVM bitcode first-hand, you can force output with the `-f’ option LLVM ERROR: IO failure on output stream. 是因为路径下已经包含dom.main.dot和dom.main.dot 需要将其删除掉;

    2、python遍历ll生成对应的CFG

    import re import os,sys res = [] with open("a.ll","r") as fd: for line in fd.readlines(): # print(line) preds = re.findall(r"<label>.*?\n", line) # 匹配行里面有有用的数据 if preds: node = re.findall(r"\d+", preds[0]) # 匹配结果, 根据preds行, 第一个是有向终点, 后面是起点 for i in range(len(node)-1): res.append((int(node[i+1]),int(node[0]))) print(sorted(res)) 输出[(2, 8), (8, 11), (8, 17), (11, 14), (14, 8), (17, 20), (17, 21), (20, 22), (21, 22)]

    Processed: 0.013, SQL: 9