-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsearch.xml
88 lines (88 loc) · 84 KB
/
search.xml
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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title><![CDATA[使用Ninja编译llvm]]></title>
<url>%2F2018%2F06%2F16%2Fbuild-llvm-with-ninja%2F</url>
<content type="text"><![CDATA[参考:https://blog.csdn.net/dreammeard/article/details/19490449 https://stackoverflow.com/questions/40042533/how-to-enable-debug-only-in-llvm-build-with-cmake https://llvm.org/docs/CMake.html 第一步 安装ninja和cmake1 获取并编译安装 ninja $ git clone git://github.com/martine/ninja.git $ cd ninja $ ./bootstrap.py $ sudo cp ninja /usr/local/bin/ Q: warning: A compatible version of re2c (>= 0.11.3) was not found; changes to src/*.in.cc will not affect your build. A: re2c 是一个用于编写快速灵活的词法分析器的工具. $sudo apt-get install re2c 2 安装 cmake(至少需要 2.8.9):用sudo apt-get install cmake得到的是 2.8.7–2013.3.7可从http://www.cmake.org/cmake/resources/software.html获 取 最 新 的cmake, 再编译.编译 cmake 需要 QT4 和 java:Q:Could NOT find Qt4 (missing: …A:sudo apt-get install qt-sdkQ:fatal error: ext2fs/ext2 fs.h: No such file or directoryA: sudo apt-get install e2fslibs-devQ:fatal error: curses.h: No such file or directoryA: sudo apt-get install libncurses5-dev 第二步下载源代码llvm所有的项目的代码的svn库都在:http://llvm.org/svn/llvm-project/可以使用svn拷贝1 下载llvm3.3$ svn co http://llvm.org/svn/llvm-project/llvm/branches/release_33/ llvm3.32 下载clang3.3到llvk3.3/tools$ cd llvm3.3/tools/$ svn co http://llvm.org/svn/llvm-project/cfe/branches/release_33/ clang 3 下载Compiler RT到llvm3.3/projects$ cd ../projects$ svn co http://llvm.org/svn/llvm-project/compiler-rt/branches/release_33/ compiler-rt 第三步 编译和安装1 建立build目录$ cd llvm3.3/$ mkdir build2 产生ninja编译文件#可以使用-DCMAKE_INSTALL_PREFIX指定安装目录$ cmake -G Ninja -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_INSTALL_PREFIX=/opt/llvm3.3 .. 1cmake -G Ninja -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_BUILD_TYPE=Debug -DLLVM_TARGETS_TO_BUILD="X86" -DCMAKE_INSTALL_PREFIX=/opt/llvm3.4 .. $ ln -s ../build/compile_commands.json .. (其中,compile_commands.json是根据 -DCMAKE_EXPORT_COMPILE_COMMANDS=ON 生成的,-DCMAKE_BUILD_TYPE=Debug 开启了Debug模式,更多参考https://llvm.org/docs/CMake.html) 3 编译 $ ninja 4 安装 $ sudo ninja install 5 把安装目录加入的环境变量PATH中 可以在home目录的.bashrc中加入 export PATH=/opt/llvm3.3/bin/:$PATH 1cmake -G Ninja -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_BUILD_TYPE=Debug -DLLVM_TARGETS_TO_BUILD="X86" -DCMAKE_INSTALL_PREFIX=/opt/llvm3.4 ..]]></content>
<tags>
<tag>技术</tag>
</tags>
</entry>
<entry>
<title><![CDATA[面向SEU的程序可靠性分析技术(PIN、DDG、LLVM)]]></title>
<url>%2F2018%2F04%2F02%2Fdynamicreliabilityanalysis%2F</url>
<content type="text"><![CDATA[面向SEU的程序可靠性分析技术主要涉及到的工具有:基于PIN的程序动态分析工具,利用python实现的动态依赖图(Dynamic Dependency Graph, DDG)生成工具,基于LLVM的软加固工具。 1 基于PIN的程序动态分析工具坑:进行动态profile时,打印出的static instruction中有遗漏,造成DDG绘制不完整 原因:为了聚焦在分析的代码上,在进行动态插桩时需要进行筛选,通常使用filter,通过指定Routine的名字来进行筛选。但我采用了他人开发的pin-tool中的方式,单独实现了筛选功能,不需要指定Routine的名字,直接筛选位于.text段的二进制程序,排除.init / .fini段的影响。但其中有一种筛选方式,筛掉了没有写入寄存器的指令比如store指令(x86中没有store指令,是以move指令实现的): 123REG reg = INS_RegW(ins, 0);if(!REG_valid(reg)) return false; 2 动态依赖图生成及可靠性分析工具主要功能及实现: 根据“基于PIN的程序动态分析工具”得到的分析结果(静态指令序列,动态指令序列,内存地址访问记录),生成DDG。由于在动态指令序列上进行分析,不需要进行控制流分析,仅仅将指令序列前后依赖的寄存器和内存统一定义为var,针对每条动态指令对Value的读和写,形成DDG。DDG中的edge连接两个动态指令(node),箭头为依赖方向(与传播方向相反),edge上的tag存储了形成依赖的var以及能够传播到下一node的基本单元(unit)的位数unmasked_bits。 针对未加固程序,找到关键指令。关键指令是影响程序执行轨迹、影响输出的指令,我们认为关键指令出错将会导致程序传播路径受到影响,不能够通过程序自身的mask effect自行恢复,简言之,一旦关键指令发生错误,将导致program failure。因此,我们的传播分析,就是分析哪些错误会导致关键指令出错,以致于产生program failure。关键指令的范围包括但不限于:跳转指令,函数调用指令。 找出所有的基本错误集合。基本错误(Basic Error)以$E_u^{(m,n)}$表示,$u$是基本单元,$m$和$n$是起止时间,表示的是基本单元$u$在$m$和$n$时间内产生错误的事件,$u$的每个二进制位经过一个时钟周期称为一个bit-cycle,一个bit-cycle也就是一个脆弱点(vulnerable point)。寻找所有的基本错误集合,也就是找到了所有的vulnerable point。每个Basic Error体现在DDG的edge上,找到基本错误集合,就是要遍历DDG的所有edge,因为我们只关心关键指令,所以遍历 以关键指令为起点,按照依赖关系遍历DDG,目前采用广度优先。 为识别复算域,我们对加固算法做出如下限定,这些假定适用的算法包括EDDI,SWIFT:1)复算域中,除检测语句外,不含有副本变量和原变量的 针对软加固程序,除了找出所有的基本错误集合,还要找到所有能够检测到的错误集合。这里涉及到两个关键技术:检测指令识别和复算域的识别,检测指令是指在DDG中识别出具有错误检测能力的detection_node,我们要求软加固时就对检测指令提供相应的标签,这样检测指令的识别就比较显然了。复算域是从副本变量产生到检测指令的区域,也就是说,复算域中的所有变量都是受到检测指令保护的。一个复算域可能包含另一个复算域,因为检测指令的覆盖范围可能有重叠,我们将识别出所有的复算域,并将所有受保护变量的的bit-cycle加入受保护集合(Guarded-set)。我们将复算域中,副本变量从产生到被检测之间的运算称为副本运算,相应的原始运算过程称为原始运算,由于错误传播机制,副本运算中可能涉及到多个副本变量,也可能涉及到一些没有经过复算的变量,每一条副本运算也不一定有完全相同的原始运算(例如:差异性变换,多版本复算,等)。注意:与LLVM中的分析不同,LLVM的IR中,每条指令的写入目标只有一个,采用SSA方式,且每个指令的输出与这条指令之间可以隐式转换(利用了operator重定义)。识别复算域的关键是找到所有副本变量生成指令,即,副本生成指令的识别。副本生成指令与具体采用何种加固算法有关,以SWIFT数据流加固算法为例,副本生成指令在DDG中可能存在如下特征:1)存在两条依赖于同一Value的,并且从检测指令出发的指令节点;2)至少其中一条是mov指令(在SWIFT算法中,因为副本生成通常都是以从内存中读取数据开始,将内存中的数据读取到两个不同的寄存器中,通常两条都是mov指令,但在CallInst、StoreInst等指令也需要复算时,可能只有一条mov指令来生成副本,目前的LLVM自动加固工具中,没有针对CallInst和StoreInst的复算机制,所以所有的副本生成指令都是两条mov指令)。如下图所示,以检测节点5开始,根据特征1),找到了$I_v$和$I_b$两个节点,根据2)筛选出$I_v$是副本变量的生成节点,而$I_b$不是,图中深背景色的点,即为复算域中包含的所有指令。 从DDG中识别某个检测节点i所保护的复算域的具体算法如下,其中,DDG中的两个节点u和v中,如果u读取了v定值的数据,则称u依赖于v,在节点u中保存从u指向v的一条依赖边dEdge,同时,在节点v中保存一条传播边pEdge,每条边都存储了toNode,fromNode,以及建立依赖的VALUE。因为DDG会被多次访问,我们给每条边设置了visisted_time属性,用以区分当前的遍历是否已经访问过该边。 1234567891011121314151617181920212223242526272829303132333435Find-SoR(DDG, i) SoR_instruction_set D <- Make-Set() //Traverse the DDG by dEdges from detection node i, and put the meet nodes into a set T detection_instruction_dependence_set T <- Make-Set() Q <- Make-Queue() Enqueue(Q, i) while not IsEmpty(Q): do u <- Dequeue(Q) for each edge d in u.dEdges do v <- d.toNode do AddToSet(T, v) Enqueue(Q, v) //Traverse the DDG again, and find the Empty(Q) Enqueue(Q, i) while not IsEmpty(Q)//loop 1 do u <- Dequeue(Q) for each edge d in u.dEdges//loop 2 do v <- d.toNode isVInSoR <- True isVReplicaGen = False for each edge p in v.pEdges//loop 3 do if p.value == value and p.toNode != u then w <- p.toNode if w in T then if u is "mov" instruction and w is "mov" instruction then isVReplicaGen = True else then isVInSoR = False if isVInSoR == True then AddToSet(D, v) if isVReplicaGen == False then Enqueue(Q, v) return D 上述算法中,首先从检测节点开始,沿着依赖边进行广度优先遍历,并将所有可以到达的节点存到集合T中;然后,再次从检测节点开始进行广度优先遍历,找到复算域的边界——副本生成节点或引用同一变量的节点。此处看似我们假定了每条指令只定值一个VALUE,但实际并非如此,如果存在某个指令节点定值了两个VALUE,分别为V1和V2,其中V1被复算,V2被复算域中的两条指令直接引用,那么该指令节点仍然会被从V1复算的边进入并纳入复算域,并且不会将V2视为被复算保护。总之,尽管DDG是以指令为节点建立起来的,我们对复算域的选取仍然以VALUE为主要依据。 在性能上,上述算法虽然有3重循环,但loop2中的循环控制变量“指令依赖的VALUE数”通常小于3,loop3中的循环控制变量“依赖于当前指令定值的VALUE的指令数”通常也是常量级别,因此上述算法仍然是$O(n)$的,$n$是DDG中所有指令的条数。 识别出SoR中包含的指令之后,就可以针对这些指令构成的子图DDG’进行遍历,以获取受到检测指令保护的基本错误集合,并根据错误集合计算出错误区间。这里的关键问题是,各个基本错误集合中的错误区间是存在重合的,获取到错误集合后,不直接计算脆弱区间,而是针对不同的VALUE,对区间进行再次合并,最后进行统一计算。如下图中,指令2使用了32位的v,指令3使用了16位的v,基本错误集合为${v{32}^{(1,2)}}$ 和 $v{16}^{(1,3)}$,其中1~2指令周期内的错误区间在两个基本错误中都覆盖到了。在计算错误区间时,需要进行重新整理,将错误集合整理为互不包含的两个基本错误$v{32}^{(1,2)}$和$v{16}^{(2,3)}$,注意,这里两个基本错误的unmasked-bits是不同的,在整理基本错误集合时,要优先按照大的值进行。 利用DDG对加固后程序可靠性进行分析的能力: 传统的对于软加固程序的可靠性分析方法采用理论分析或形式化验证的方法,分析算法层面的漏洞,常见的软加固漏洞有: 软错误发生在该数据的错误检测和引用之间。数据在错误检测时没有发生错误,但在错误检测和下一次引用之间发生了软错误,致使错误检测失败; 软错误发生在基址寄存器等特殊部件,导致某些软错误在检测时不会被发现,但仍然导致错误结果。 我们的分析方法直接从程序最终的可执行代码出发,分析粒度更加细致,分析结果更加精确,除能够达到以往分析方法的全部能力外,该方法还能够检测出由于编译器优化造成的软加固机制的失效,如使用同一寄存器存储副本变量和原始变量,指令顺序调整,寄存器数量不够或函数调用时保存寄存器现场时的错误区间。除此之外,我们可以有选择地分析目标代码的某一指定区域的可靠性,这对于评估软加固效果,确定检测盲区是十分重要的,并且可以区分在只针对部分代码进行加固的情况下,程序整体的可靠性能够达到怎样的状态。 主要存在问题: 时间空间开销问题。由于采用动态分析,程序的动态指令序列的长度,内存读写记录的长度都会随着输入的规模扩大,而变得很大,目前的算法复杂度几乎是O(n),在执行效率上还算过得去,但在空间上,如果所有的数据都要存储到内存中操作,会导致内存不足,无法完成运算。为此,我们采用分块计算的方法,将动态指令序列和内存读取数据按照一定的规则进行分割,分别运算然后再合并到一起。由于分割不能破坏指令序列中的数据依赖关系,因此需要对一段指令序列的活跃数据所依赖的边进行备份保留。注意:如何对动态指令进行分割,决定了SoR的识别准确率。如果一个SoR被分割到了不同的段中,那么此时由于抛弃了上一个段中活跃指令以外的所有指令关联,所以将识别不出准确的SoR起始点(副本生成指令)。同时,这种分批处理的方式还有一个弊端,就是对于跨越批次的依赖关系,不能准确合并其脆弱区间,而仅仅能够以basic_error的形式统计进区间,但这仅仅是在数字上的问题,在找到的EVS区间上,不存在问题,仅仅是没有合并,会在统计的时候得到较大的结果。重 软加固程序的可靠性分析问题,由于采用的是中间代码级的软加固实现,编译后端的优化会破坏掉软加固机制,经过试验,即便不打开优化开关,后端编译仍然会导致软加固机制的严重损坏,为此,我们暂时折中采用将软加固代码封装在函数中,这样才使得软加固机制的错误检测能力得以保留,但因为引入和新的函数调用,使得部分寄存器会由于保留现场而进行入栈出栈操作,反而增大了脆弱区间,因此,尽管软加固机制能够检测大量的错误,软加固后的程序可靠性相较未加固程序仅提高了10%。 由于该问题是由中间代码加固实现导致的,而从根本上解决这个问题,需要从编译后端及编译开关的选取上进行。在没有解决中间代码软加固实现问题之前,如果需要对SWIFT算法的可靠性进行测试,需要一些小trick,即,将由软加固导致的出栈入栈过程纳入到可检测区间。尽管这样的做法与完美解决问题之间有一定出入,但在现阶段已经是最能够接近真实加固后的效果了。如下图所示: 暂不支持对控制流加固算法和ABFT加固算法的可靠性分析。 实现上的细节: 由于之前生成DDG的程序中,在所有指令之前添加了Entry起始点,并且Entry的id是1,而后添加的节点顺次递增,这与实际需求中以id代表动态指令序号的方式不同,因此进行调整。但Entry节点不能删除,其id最小也只能是0,所以暂时有两个id为0的节点,另一个是动态指令序号0。由此带来的问题,是对于将call指令的下一条指令纳入到检测指令,并计算复算域的临时方案,我们之前采用的以id为索引的查找方式,在调整了id编号后,再按照之前的索引会发现添加的是call指令的前一条指令,所以这里要跟着进行调整。 当前分析到的basic_error集合(也就是脆弱点集合)还没有进行合并,仅仅在计算区间大小的时候才进行了合并。现在已经通过将脆弱区间进行适当合并,并生成了一个value_interval_dict,解决了basic_error中脆弱点有重叠的问题。但在从general_vulnerable_points中取出SoR的detected_vulnerable_points时,最初使用的方法,是将二者的basic_error_set之间进行减法运算,这种方法会使得保留下来的脆弱区间过大,因为会存在在DDG中绕过了SoR,但实际上绕过DDG的这条边仍然有部分周期可以被SoR覆盖。因此,需要在general_vulnerable_points和detected_vulnerable_points之间,通过对各个value对应的区间列表进行减法运算,其算法描述如下: 算法名:Interval_Subsection 输入:value_interval_dict1和value_interval_dict2,每个value_interval_dict的数据结构为{value:[(t1,t2)(t3, t4)…(t5,t6)]} 输出:二者的在interval上的差值,仍然为value_interval_dict 1234567891011121314151617181920212223242526272829303132333435363738new_value_interval_dict = defaultdict{lambda: []}for var in value_interval_dict1: intervals1 = value_interval_dict1[var] intervals2 = value_interval_dict2[var] len1 = len(intervals1) len2 = len(intervals2) index1 = 0 index2 = 0 while index1 < len1 and index2 < len2: t1 = intervals1[index1][0] t2 = intervals1[index1][1] unmasked_bits1 = intervals1[index1][2] s1 = intervals2[index2][0] s2 = intervals2[index2][1] unmasked_bits2 = intervals2[index2][2] if t1 >= s2: index2 = index2 + 1 continue if t2 <= s1: new_value_interval_dict[var].append((t1, t2, unmasked_bits1)) index1 = index1 + 1 continue if t1 < s1: new_value_interval_dict[var].append((t1 + 1, s1, unmasked_bits1)) if t2 > s2: intervals1[index1] = (s2, t2, unmasked_bits1) else if t2 < s2: intervals2[index2] = (t2, s2, unmasked_bits2) else: index1 = index1 + 1 index2 = index2 + 1 if len2 == index2: while index1 < len1: new_value_interval_dict[var].append(intervals1[index1]) index1 = index1 + 1return new_value_interval_dict 对于unmasked_bits的分析还不够完善,很多脆弱点选得并不全面 Todo: 尚未实现对脆弱区间的删减工作,目前的删减停留在集合减法上,但这种方式并不准确,应该在脆弱点级别进行运算。 对于脆弱点区间的定义,在根据DDG的分析结果中,(t1, t2)所指代的脆弱点区间是从t1之后到t2之前的区间,而对于故障注入的fi_point而言,(t1, t2)所指代的故障注入区间是t1之前到t2之前,二者是不同的。所以,当(t1, t2)-(s1, s2)时,不需要考虑边界的调整,例如,当t1 < s1, t2 > s2时,差值为(t1, s1) (s2, t2),而不是(t1, s1+1), (s2+1, t2)。]]></content>
<tags>
<tag>学术</tag>
</tags>
</entry>
<entry>
<title><![CDATA[逐步实现LLVM下的自动软加固工具]]></title>
<url>%2F2018%2F03%2F27%2FLLVMlearning%2F</url>
<content type="text"><![CDATA[指令复算是软件容错的基本方法,自动软加固工具和故障注入工具在软加固研究中几乎必不可少,本文随笔者工作进度,逐步完成一个利用LLVM实现的自动软加固工具。将会记录实现面临的关键技术,及开发过程中遇到的细节问题。 1 如何插入一条指令参考:https://stackoverflow.com/questions/35198935/add-an-llvm-instruction 12345678BasicBlock *B = I->getParent();if (auto *op = dyn_cast<BinaryOperator>(&*I)){ auto temp = op->clone(); B->getInstList().insert(op, temp); temp->setName(op->getName()); op->replaceAllUsesWith(temp);} 其中,I是Instruction,BinaryOperator是Instruction的子类。 各种不同的类型的指令,其插入方式各有不同,上面是针对所有指令的直接clone(),该操作除了没有复制指令所在的位置以外,全部copy原始指令的信息,包括use信息,这是实现复算的最便捷的方式,但我们仍然需要插入一些比对、跳转、函数调用等指令,这些指令大多都提供了相应的Create方法,只要有相应的示例,学习起来并不困难。 CmpInst 12Value *newOp = it->second;CmpInst *cmp = CmpInst::Create(Instruction::ICmp, CmpInst::ICMP_NE, it->first, it->second, "eddi_check", inst); CallInst 无参数的call 12345BB->getInstList().insertAfter(BB->begin(),newret);FunctionType *error_handle_func_type = FunctionType::get(Type::getVoidTy(context), false);Constant *error_handling_func = F.getParent()->getOrInsertFunction("error_handling", error_handle_func_type);CallInst::Create(error_handling_func, "", BB->begin()); 有参数的call 123456789101112131415std::vector<Type*> parameterVector(1);parameterVector[0] = Type::getInt32Ty(context); //IDArrayRef<Type*> parameterVector_array_ref(parameterVector);FunctionType *exit_func_type = FunctionType::get(Type::getVoidTy(context),parameterVector_array_ref, false);Constant *exit_func = F.getParent()->getOrInsertFunction("exit", exit_func_type);Value *one = ConstantInt::get(Type::getInt32Ty(context),1);std::vector<Value*> exitArgs;exitArgs.push_back(one);ArrayRef<Value*> exitArgs_array_ref(exitArgs);//Create the FunctionCallInst::Create(exit_func, exitArgs_array_ref, "", BB->end()); 2 如何识别扫描到的指令的类型参考:https://stackoverflow.com/questions/30250289/how-to-check-the-opcode-of-an-instruction 5down voteaccepted isa is used to check for an existing dirived instruction class. class i.getopcode() could help you to get all the operations information. According to the Inheritance diagram for llvm::Instruction,LLVM internally will divide all the instruction into several different classes, like llvm::BinaryOperator, llvm::CallInst, llvm::CmpInst, etc. But there is no exact operation information for these classes. However, for Instruction::getOpcode(), it will directly get the operation from the llvm::Instruction object. You could refer to Instruction.def for an idea about defination of each instruction. Basically, the opcode will be the exact operations the instruction intends to. Say, for an LLVM IR add. You can use isa<llvm::BinaryOperator>, to know that this is a BinaryOperator. But this is only for what the instruction class it is. If you want to know whether it is an ADD or a SUB. i.getopcode() should be used here. 上面给出了几个很好的总结。 Instruction 有多少子类 在http://llvm.org/doxygen/classllvm_1_1Instruction.html 中,有Instruction的继承关系,可以看到Instruction所有的子类 查询每个指令具体属于的操作码,如对于BinaryOperator,具体是ADD还是SUB https://llvm.org/svn/llvm-project/llvm/trunk/include/llvm/IR/Instruction.def 但目前并不知道如何使用这些信息。 使用isa\确定属于哪个子类,使用i.getopcode()确定是子类中的哪个操作 isa(i) and isa(i) without changing to if (i.getopcode()==…) 3. 如何为指令插入Metadata插入字符串类型,借助MDString类,以下为获取每条指令的操作码的名字 12345Value *Elts[] = { MDString::get(context, inst->getOpcodeName(inst->getOpcode()))};MDNode *Node = MDNode::get(context, Elts);temp->setMetadata("mxk", Node); 插入数字类型 1234std::vector<Value*> llfiindex(1);llfiindex[0] = ConstantInt::get(Type::getInt64Ty(context), fi_index++);MDNode *mdnode = MDNode::get(context, llfiindex);inst->setMetadata("mxk", mdnode); 删除一条指令的Metadata 12Instruction *temp = inst->clone();temp->setMetadata("llfi_index", NULL); 4. 如何更新复算指令的引用为实现代码复算,我们需要为复算指令增加引用,例如: 12%mul = mul nsw i32 %8, %10 原始指令%mul10 = mul nsw i32 %9, %11 副本指令,其use的数据都要更新为原始指令中use数据的副本 为此,我们需要获取每个每个副本指令中需要更新的use数据的信息。 在llvm中,由于采用SSA,所有的数据只会被赋值一次,而且,在数据结构上,所有的数据指针(Value )都是一个指向该数据的指令(Instruction ),注意:Instruction继承自Value和Use。 所以,解决更新副本指令引用的关键,就是找到每个指令所use的数据的副本数据。我们在每条指令的复算过程中,都将原始数据指针(也就是原始指令指针)与副本数据指针(也就是副本数据指针)之间的映射关系存放到map中,这样,我们只需要找到每个指令的所有引用就足以解决问题了。 在https://www.zhihu.com/question/41999500中,给出了很清晰的关于use-def在llvm中如何存储的描述。 llvm的每条指令都存储了def-use信息,结构如下: 对于一个Instruction inst,可以获取其operator的iterator,并且通过for循环遍历之。 123for (User::op_iterator opIterator = temp->op_begin(); opIterator != temp->op_end(); opIterator++) { //....} 同时,Use类中,还有一个方法: 123public: /// Normally Use will just implicitly convert to a Value* that it holds. operator Value*() const { return Val; } 也就是说,Use的值会自动转换成一个Value的指针。 所以,我们可以通过opIterator来获取当前指令的所有引用信息。 实现复算和更新副本指令引用的代码如下: 12345678910111213141516171819202122232425BasicBlock *B = inst->getParent();Instruction *temp = inst->clone();temp->setMetadata("llfi_index", NULL);shadow_value_map.insert(std::make_pair<Value*, Value*>(inst, temp));std::vector<Value*> Elts(1);for (User::op_iterator opIterator = temp->op_begin(); opIterator != temp->op_end(); opIterator++) { //Use *newOp = opIterator->getNext();//the operator's next value should be the duplicated instruction Value *curOp = *opIterator; std::map<Value*, Value*>::iterator it = shadow_value_map.find(curOp); if (it != shadow_value_map.end()) { Value *newOp = it->second; *opIterator = newOp; Elts.push_back(MDString::get(context, newOp->getName())); } //*opIterator = *newOp;}MDNode *Node = MDNode::get(context, Elts);temp->setMetadata("mxk1", Node);B->getInstList().insertAfter(inst,temp);// inst->replaceAllUsesWith(temp);temp->setName(inst->getName()); 5.复算的范围最佳实践是除了terminitor以外,全部复算,这样就与EDDI算法的核心思想相吻合,即,对所有的指令、寄存器、内存都进行了复算。然而,由于某些指令的执行后果的不确定性,导致这样做会出现一些问题。 landingpad指令 这是LLVM中的异常处理机制,在复算检验实现过程中,曾出现过如下异常信息 12345678910111213141516171819202122232425LandingPadInst not the first non-PHI instruction in the block. %21 = landingpad { i8*, i32 } personality i8* bitcast (i32 (...)* @__gxx_personality_v0 to i8*) cleanup, !mxk1 !2, !mxk !64, !numuses !4Broken module found, compilation aborted!0 opt 0x00000000018707fa llvm::sys::PrintStackTrace(_IO_FILE*) + 531 opt 0x0000000001870a8a2 opt 0x00000000018704533 libpthread.so.0 0x00007f3b2999f3904 libc.so.6 0x00007f3b28d58428 gsignal + 565 libc.so.6 0x00007f3b28d5a02a abort + 3626 opt 0x000000000175e9eb7 opt 0x000000000175e69f8 opt 0x0000000001734ff8 llvm::FPPassManager::runOnFunction(llvm::Function&) + 3309 opt 0x000000000173516e llvm::FPPassManager::runOnModule(llvm::Module&) + 12010 opt 0x000000000173546e11 opt 0x0000000001735966 llvm::legacy::PassManagerImpl::run(llvm::Module&) + 26212 opt 0x0000000001735b3f llvm::legacy::PassManager::run(llvm::Module&) + 3913 opt 0x00000000008f3828 main + 580414 libc.so.6 0x00007f3b28d43830 __libc_start_main + 24015 opt 0x00000000008e4ef9 _start + 41Stack dump:0. Program arguments: /home/xiaofengwo/llvm/llvm-3.4/build/bin/opt -load /home/xiaofengwo/llvm/ir_sihft_llvm_build/bin/../llvm_passes/llfi-passes.so -insttracepass -maxtrace 250 -o /home/xiaofengwo/llvm/llvm-workspace/sample_programs/kakadu_source_flat/llfi/a-profiling.ll /home/xiaofengwo/llvm/llvm-workspace/sample_programs/kakadu_source_flat/llfi/a-llfi_index.ll -S1. Running pass 'Function Pass Manager' on module '/home/xiaofengwo/llvm/llvm-workspace/sample_programs/kakadu_source_flat/llfi/a-llfi_index.ll'.2. Running pass 'Module Verifier' on function '@_ZN13kdu_synthesisC2E14kdu_resolutionP20kdu_sample_allocatorbf'Aborted (core dumped) landingpad 貌似只能是每个基本块的第一条non-PHI指令。因此,我们把landingpad暂时移出复算域。 call指令 如果对call指令也进行2遍调用,会在执行的时候报段错误,可能是由于每调用一次都产生两遍副本,对于最底层的函数调用次数过多导致的。 如果对call指令仅调用1遍,而后对其define的副本进行同步,则有可能导致call指令引用的变量所指向的内存单元仅在原始变量中是有效的,即如果call指令所引用的变量是指针,那么调用过call指令后,副本指针所指向的位置可能没有值。但由于副本指针和原始指针指向同一位置,如果有对这块地址的修改操作,恐怕会有问题。 在EDDI算法中,也无法解决call指令的问题,无论是调用之后再进行同步,还是调用call指令2遍,都不能自动化实现对于黑盒函数调用之后的数据一致性问题。 Todo:考虑对函数进行分析,确定函数是否可以被调用2遍的角度,实现真正的EDDI算法。 StoreInst和AllocaInst 在EDDI算法中,应该对二者进行复算,但由于call指令的潜在威胁,暂时无法实现完全的EDDI算法,只能实现针对寄存器的SWIFT算法。StoreInst 和 AllocaInst 指令不参与复算。 然而,目前没有对AllocaInst和CallInst之后的返回值进行复算,这样,仍然有部分变量漏掉了,应该在这些指令之后加入一条赋值指令,但目前没有找到这样的指令,考虑增加一条BinaryOperator指令来完成赋值。 Todo:在AllocaInst和CallInst后加入对返回值的复算。 6. 检查校验对于SWIFT算法,校验点插在StoreInst和CallInst,以及branch指令前。 校验点包括比较和跳转,比较指令比较下一条存储或者函数调用将要用到的变量,跳转指令根据比较结果,选择继续执行还是跳转到最后。 比较指令 比较指令分为ICmpInst和FCmpInst,要按照操作数的类型而调整使用的比较指令类型。 12345678if (it->first->getType()->isIntOrIntVectorTy() || it->first->getType()->isPtrOrPtrVectorTy()) { // if (!isa<llvm::IntegerType>(it->first->getType())) { cmp = CmpInst::Create(Instruction::ICmp, CmpInst::ICMP_NE, it->first, it->second, "eddi_check", inst);} else { cmp = CmpInst::Create(Instruction::FCmp, CmpInst::FCMP_UNE, it->first, it->second, "eddi_check", inst);} 其中,it是操作数的迭代器。 然而,按照以上代码,在对br指令之前的副本变量进行校验的时候,会有部分指令报错 123456789101112131415161718192021222324252627/home/xiaofengwo/llvm/llvm-3.4/build/bin/opt -load /home/xiaofengwo/llvm/ir_sihft_llvm_build/bin/../llvm_passes/llfi-passes.so -insttracepass -maxtrace 250 -o /home/xiaofengwo/llvm/llvm-workspace/sample_programs/kakadu_source_flat/llfi/a-profiling.ll /home/xiaofengwo/llvm/llvm-workspace/sample_programs/kakadu_source_flat/llfi/a-llfi_index.ll -SInvalid operand types for FCmp instruction %eddi_check24 = fcmp une { i8*, i32 } %lpad.val3, %lpad.val323Invalid operand types for FCmp instruction %eddi_check25 = fcmp une { i8*, i32 } %lpad.val3, %lpad.val323Broken module found, compilation aborted!0 opt 0x00000000018707fa llvm::sys::PrintStackTrace(_IO_FILE*) + 531 opt 0x0000000001870a8a2 opt 0x00000000018704533 libpthread.so.0 0x00007f1b3f3403904 libc.so.6 0x00007f1b3e6f9428 gsignal + 565 libc.so.6 0x00007f1b3e6fb02a abort + 3626 opt 0x000000000175e9eb7 opt 0x000000000175e69f8 opt 0x0000000001734ff8 llvm::FPPassManager::runOnFunction(llvm::Function&) + 3309 opt 0x000000000173516e llvm::FPPassManager::runOnModule(llvm::Module&) + 12010 opt 0x000000000173546e11 opt 0x0000000001735966 llvm::legacy::PassManagerImpl::run(llvm::Module&) + 26212 opt 0x0000000001735b3f llvm::legacy::PassManager::run(llvm::Module&) + 3913 opt 0x00000000008f3828 main + 580414 libc.so.6 0x00007f1b3e6e4830 __libc_start_main + 24015 opt 0x00000000008e4ef9 _start + 41Stack dump:0. Program arguments: /home/xiaofengwo/llvm/llvm-3.4/build/bin/opt -load /home/xiaofengwo/llvm/ir_sihft_llvm_build/bin/../llvm_passes/llfi-passes.so -insttracepass -maxtrace 250 -o /home/xiaofengwo/llvm/llvm-workspace/sample_programs/kakadu_source_flat/llfi/a-profiling.ll /home/xiaofengwo/llvm/llvm-workspace/sample_programs/kakadu_source_flat/llfi/a-llfi_index.ll -S1. Running pass 'Function Pass Manager' on module '/home/xiaofengwo/llvm/llvm-workspace/sample_programs/kakadu_source_flat/llfi/a-llfi_index.ll'.2. Running pass 'Module Verifier' on function '@_ZN13kdu_synthesisC2E14kdu_resolutionP20kdu_sample_allocatorbf'Aborted (core dumped) 这就奇怪了,原本以为CmpInst只有ICmpInst和FCmpInst,但上面这种两种都不适用,就不知道怎么回事了。 只好暂时不管这些特殊的类型。 12345678910if (it->first->getType()->isIntOrIntVectorTy() || it->first->getType()->isPtrOrPtrVectorTy()) { // if (!isa<llvm::IntegerType>(it->first->getType())) { cmp = CmpInst::Create(Instruction::ICmp, CmpInst::ICMP_NE, it->first, it->second, "eddi_check", inst);} else if (it->first->getType()->isFPOrFPVectorTy()) { cmp = CmpInst::Create(Instruction::FCmp, CmpInst::FCMP_UNE, it->first, it->second, "eddi_check", inst);} else { continue;} 跳转指令 插入跳转指令,会导致基本块结构的调整,在同一个pass里面,遍历时由于跳转,会破坏该结构,因此,暂时采用插入校验函数的方法,比较低效。 Todo:后期将加入重新对基本块进行调整的机制。 每次拆分基本块后,重新从头开始扫描指令可以解决问题,但十分低效,需要一种快速调整iterator并指向当前处理指令的方法。 可选的方法: 可用方法 是否已尝试 结果 分析 比对之后,结果存于cmp变量中,插入具有比对功能的函数调用 已尝试 方法简单,不需要重新划分基本块,不存在iterator失效问题,但执行时性能较差且出错窗口较大 比对之后,结果存于cmp变量中,在当前指令拆分基本块,并插入br error_detection指令 已尝试 报错,“Instruction does not dominate all uses!” 每次拆分基本块,会是iterator失效,只能重新获取iterator,效率很低。且函数尾部的error_detection标签所处基本块,原本为unreachable,被某处br到后,原本的ret语句不知为何,报错。 比对之后,结果存于cmp变量中,在当前指令拆分基本块,并插入if-then-else,在then中插入错误处理函数 已尝试 报错 每次拆分基本块,会是iterator失效,只能重新获取iterator,效率很低。该方法看似不会存在问题,但是仍然报错,怀疑是iterator的问题 比对之后,结果存于cmp变量中,在当前指令拆分基本块,并插入if-then-else,在then中插入到error_detection的无条件跳转 已尝试 报错,“Instruction does not dominate all uses!” 看来问题集中在ret指令要dominate all uses上 经过各种测试,问题集中在两个方面: 如何在iterator失效的情况下,相对高效地完成对每条代码的扫描 如何解决Instruction does not dominate all uses!问题 对于操作数的比对需要针对不同的数据类型,目前采用了Int,Double,Float,还需要考虑对于struct类型的比对,如果类型不匹配,会在正常执行流程中检测到错误,因此,缺少对一种类型的支持,就会损失很多检测点。 7. 后端编译对软加固代码的影响无论是源代码级软加固还是中间代码级软加固,都面临一个难题,就是后端编译对软加固代码的破坏,这些破坏包括但不限于指令重排,寄存器分配等导致检测点位置,检测点语义(检测的对象)发生变化。后端编译对软加固的影响是巨大的,甚至可以使软加固完全失效。 例如,针对原始代码factorial.c 123456789101112131415#include<stdio.h>main(argc, argv)int argc;char *argv[];{ int i,fact, n; n = atoi(argv[1]); fact = 1; for(i=1;i<=n;i++) { fact = fact * i; } printf("%d\n",fact); exit(0);} 其中间代码factorial.ll如下: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172; ModuleID = 'factorial.ll'target datalayout = "e-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-v128:128:128-a0:0:64-s0:64:64-f80:128: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; Function Attrs: nounwind uwtabledefine i32 @main(i32 %argc, i8** %argv) #0 {entry: %retval = alloca i32, align 4 %argc.addr = alloca i32, align 4 %argv.addr = alloca i8**, align 8 %i = alloca i32, align 4 %fact = alloca i32, align 4 %n = alloca i32, align 4 store i32 0, i32* %retval store i32 %argc, i32* %argc.addr, align 4 store i8** %argv, i8*** %argv.addr, align 8 %0 = load i8*** %argv.addr, align 8 %arrayidx = getelementptr inbounds i8** %0, i64 1 %1 = load i8** %arrayidx, align 8 %call = call i32 (i8*, ...)* bitcast (i32 (...)* @atoi to i32 (i8*, ...)*)(i8* %1) store i32 %call, i32* %n, align 4 store i32 1, i32* %fact, align 4 store i32 1, i32* %i, align 4 br label %for.condfor.cond: ; preds = %for.inc, %entry %2 = load i32* %i, align 4 %3 = load i32* %n, align 4 %cmp = icmp sle i32 %2, %3 br i1 %cmp, label %for.body, label %for.endfor.body: ; preds = %for.cond %4 = load i32* %fact, align 4 %5 = load i32* %i, align 4 %mul = mul nsw i32 %4, %5 store i32 %mul, i32* %fact, align 4 br label %for.incfor.inc: ; preds = %for.body %6 = load i32* %i, align 4 %inc = add nsw i32 %6, 1 store i32 %inc, i32* %i, align 4 br label %for.condfor.end: ; preds = %for.cond %7 = load i32* %fact, align 4 %call1 = call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([4 x i8]* @.str, i32 0, i32 0), i32 %7) call void @exit(i32 0) #3 unreachablereturn: ; No predecessors! %8 = load i32* %retval ret i32 %8}declare i32 @atoi(...) #1declare i32 @printf(i8*, ...) #1; Function Attrs: noreturndeclare void @exit(i32) #2attributes #0 = { nounwind uwtable "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" "stack-protector-buffer-size"="8" "unsafe-fp-math"="false" "use-soft-float"="false" }attributes #1 = { "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" "stack-protector-buffer-size"="8" "unsafe-fp-math"="false" "use-soft-float"="false" }attributes #2 = { noreturn "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" "stack-protector-buffer-size"="8" "unsafe-fp-math"="false" "use-soft-float"="false" }attributes #3 = { noreturn }!llvm.ident = !{!0}!0 = metadata !{metadata !"clang version 3.4 (tags/RELEASE_34/final)"} 经过SWIFT算法软加固后的中间代码factorial_swift.ll如下(忽略其中的profiling): 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146; ModuleID = '/home/xiaofengwo/llvm/llvm-workspace/sample_programs/factorial/llfi/a-llfi_index.ll'target datalayout = "e-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-v128:128:128-a0:0:64-s0:64:64-f80:128: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; Function Attrs: nounwind uwtabledefine i32 @main(i32 %argc, i8** %argv) #0 {entry: %retval = alloca i32, align 4 call void @doProfiling(i32 26) %argc.addr = alloca i32, align 4 call void @doProfiling(i32 26) %argv.addr = alloca i8**, align 8 call void @doProfiling(i32 26) %i = alloca i32, align 4 call void @doProfiling(i32 26) %fact = alloca i32, align 4 call void @doProfiling(i32 26) %n = alloca i32, align 4 call void @doProfiling(i32 26) store i32 0, i32* %retval, !storemark !1 store i32 %argc, i32* %argc.addr, align 4, !storemark !1 store i8** %argv, i8*** %argv.addr, align 8, !storemark !1 %0 = load i8*** %argv.addr, align 8 %1 = load i8*** %argv.addr, align 8, !mxk1 !2, !mxk !3, !numuses !4 call void @doProfiling(i32 27) %arrayidx = getelementptr inbounds i8** %0, i64 1 %arrayidx1 = getelementptr inbounds i8** %1, i64 1, !mxk1 !5, !mxk !6, !numuses !4 call void @doProfiling(i32 29) %2 = load i8** %arrayidx, align 8 %3 = load i8** %arrayidx1, align 8, !mxk1 !7, !mxk !3, !numuses !4 call void @doProfiling(i32 27) %eddi_check = icmp ne i8* %2, %3 call void @check_and_error_handling(i1 %eddi_check) %call = call i32 (i8*, ...)* bitcast (i32 (...)* @atoi to i32 (i8*, ...)*)(i8* %2) call void @doProfiling(i32 49) store i32 %call, i32* %n, align 4, !storemark !1 store i32 1, i32* %fact, align 4, !storemark !1 store i32 1, i32* %i, align 4, !storemark !1 br label %for.condfor.cond: ; preds = %for.inc, %entry %4 = load i32* %i, align 4 %5 = load i32* %i, align 4, !mxk1 !2, !mxk !3, !numuses !4 call void @doProfiling(i32 27) %6 = load i32* %n, align 4 %7 = load i32* %n, align 4, !mxk1 !2, !mxk !3, !numuses !4 call void @doProfiling(i32 27) %cmp = icmp sle i32 %4, %6 %cmp2 = icmp sle i32 %5, %7, !mxk1 !8, !mxk !9, !numuses !4 call void @doProfiling(i32 46) %eddi_check3 = icmp ne i1 %cmp, %cmp2 call void @check_and_error_handling(i1 %eddi_check3) br i1 %cmp, label %for.body, label %for.endfor.body: ; preds = %for.cond %8 = load i32* %fact, align 4 %9 = load i32* %fact, align 4, !mxk1 !2, !mxk !3, !numuses !4 call void @doProfiling(i32 27) %10 = load i32* %i, align 4 %11 = load i32* %i, align 4, !mxk1 !2, !mxk !3, !numuses !4 call void @doProfiling(i32 27) %mul = mul nsw i32 %8, %10 %mul4 = mul nsw i32 %9, %11, !mxk1 !8, !mxk !10, !numuses !4 call void @doProfiling(i32 12) %eddi_check5 = icmp ne i32 %mul, %mul4 call void @check_and_error_handling(i1 %eddi_check5) store i32 %mul, i32* %fact, align 4, !storemark !1 br label %for.incfor.inc: ; preds = %for.body %12 = load i32* %i, align 4 %13 = load i32* %i, align 4, !mxk1 !2, !mxk !3, !numuses !4 call void @doProfiling(i32 27) %inc = add nsw i32 %12, 1 %inc6 = add nsw i32 %13, 1, !mxk1 !5, !mxk !11, !numuses !4 call void @doProfiling(i32 8) %eddi_check7 = icmp ne i32 %inc, %inc6 call void @check_and_error_handling(i1 %eddi_check7) store i32 %inc, i32* %i, align 4, !storemark !1 br label %for.condfor.end: ; preds = %for.cond %14 = load i32* %fact, align 4 %15 = load i32* %fact, align 4, !mxk1 !2, !mxk !3, !numuses !4 call void @doProfiling(i32 27) %eddi_check8 = icmp ne i32 %14, %15 call void @check_and_error_handling(i1 %eddi_check8) %call1 = call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([4 x i8]* @.str, i32 0, i32 0), i32 %14) call void @doProfiling(i32 49) call void @endProfiling() call void @exit(i32 0) #3 unreachablereturn: ; No predecessors! %16 = load i32* %retval %17 = load i32* %retval, !mxk1 !2, !mxk !3, !numuses !12 call void @doProfiling(i32 27) call void @endProfiling() %eddi_check9 = icmp ne i32 %16, %17 call void @check_and_error_handling(i1 %eddi_check9) ret i32 %16mxk_error_detection: ; No predecessors! call void @error_handling() %eddi_check10 = icmp ne i32 %16, %17 call void @check_and_error_handling(i1 %eddi_check10) ret i32 %16}declare i32 @atoi(...) #1declare i32 @printf(i8*, ...) #1; Function Attrs: noreturndeclare void @exit(i32) #2declare void @doProfiling(i32)declare void @endProfiling()declare void @error_handling()declare void @check_and_error_handling(i1)attributes #0 = { nounwind uwtable "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" "stack-protector-buffer-size"="8" "unsafe-fp-math"="false" "use-soft-float"="false" }attributes #1 = { "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" "stack-protector-buffer-size"="8" "unsafe-fp-math"="false" "use-soft-float"="false" }attributes #2 = { noreturn "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" "stack-protector-buffer-size"="8" "unsafe-fp-math"="false" "use-soft-float"="false" }attributes #3 = { noreturn }!llvm.ident = !{!0}!0 = metadata !{metadata !"clang version 3.4 (tags/RELEASE_34/final)"}!1 = metadata !{null, metadata !"store"}!2 = metadata !{null}!3 = metadata !{metadata !"load"}!4 = metadata !{i64 1}!5 = metadata !{null, metadata !""}!6 = metadata !{metadata !"getelementptr"}!7 = metadata !{null, metadata !"arrayidx1"}!8 = metadata !{null, metadata !"", metadata !""}!9 = metadata !{metadata !"icmp"}!10 = metadata !{metadata !"mul"}!11 = metadata !{metadata !"add"}!12 = metadata !{i64 2} 但经过后端编译”llc factorial_swift.ll -o factorial_swift.s”后,得到的汇编代码为: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124 .file "a-profiling.ll" .text .globl main .align 16, 0x90 .type main,@functionmain: # @main .cfi_startproc# BB#0: # %entry pushq %rbp.Ltmp3: .cfi_def_cfa_offset 16.Ltmp4: .cfi_offset %rbp, -16 movq %rsp, %rbp.Ltmp5: .cfi_def_cfa_register %rbp pushq %r14 pushq %rbx subq $32, %rsp.Ltmp6: .cfi_offset %rbx, -32.Ltmp7: .cfi_offset %r14, -24 movq %rsi, %rbx movl %edi, %r14d movl $26, %edi callq doProfiling movl $26, %edi callq doProfiling movl $26, %edi callq doProfiling movl $26, %edi callq doProfiling movl $26, %edi callq doProfiling movl $26, %edi callq doProfiling movl $0, -20(%rbp) movl %r14d, -24(%rbp) movq %rbx, -32(%rbp) movl $27, %edi callq doProfiling movl $29, %edi callq doProfiling movq 8(%rbx), %rbx movl $27, %edi callq doProfiling xorl %edi, %edi callq check_and_error_handling xorl %eax, %eax movq %rbx, %rdi callq atoi movl %eax, %ebx movl $49, %edi callq doProfiling movl %ebx, -44(%rbp) movl $1, -40(%rbp) movl $1, -36(%rbp) jmp .LBB0_1 .align 16, 0x90.LBB0_2: # %for.body # in Loop: Header=BB0_1 Depth=1 movl -40(%rbp), %ebx movl $27, %edi callq doProfiling imull -36(%rbp), %ebx movl $27, %edi callq doProfiling movl $12, %edi callq doProfiling xorl %edi, %edi callq check_and_error_handling movl %ebx, -40(%rbp) movl -36(%rbp), %ebx movl $27, %edi callq doProfiling incl %ebx movl $8, %edi callq doProfiling xorl %edi, %edi callq check_and_error_handling movl %ebx, -36(%rbp).LBB0_1: # %for.cond # =>This Inner Loop Header: Depth=1 movl -36(%rbp), %r14d movl $27, %edi callq doProfiling movl -44(%rbp), %ebx movl $27, %edi callq doProfiling movl $46, %edi callq doProfiling xorl %edi, %edi callq check_and_error_handling cmpl %ebx, %r14d jle .LBB0_2# BB#3: # %for.end movl -40(%rbp), %ebx movl $27, %edi callq doProfiling xorl %edi, %edi callq check_and_error_handling movl $.L.str, %edi xorl %eax, %eax movl %ebx, %esi callq printf movl $49, %edi callq doProfiling callq endProfiling xorl %edi, %edi callq exit.Ltmp8: .size main, .Ltmp8-main .cfi_endproc .type .L.str,@object # @.str .section .rodata.str1.1,"aMS",@progbits,1.L.str: .asciz "%d\n" .size .L.str, 4 .ident "clang version 3.4 (tags/RELEASE_34/final)" .section ".note.GNU-stack","",@progbits 其中所有的检测指令,全部变成了毫无意义的比对: 12xorl %edi, %edicallq check_and_error_handling 对上面的代码进行故障注入实验,可以看到,一条错误都检测不出来。 1234567891011121314151617=========================================================Timeout Count: 157Exception Count: 743SDC Count: 481Right Count: 1107Detected Count: 0=========================================================time elapsed: 1.439475=========================================================Real Timeout Count: 770Real Exception Count: 2026Real SDC Count: 2500Real Right Count: 7256Real Detected Count: 0.0=========================================================Total Interval Number: 12552(2488, 8) 该问题普遍存在于软加固领域,以往有研究试图通过差异性变换来减轻后端编译带来的问题,在LLVM中,默认的编译优化会在寄存器分配时会做出如上的改造。不开启编译优化,采用-O0编译选项能够部分解决该问题,但对代码性能会造成较为严重的影响。为解决该问题,需要对后端编译过程,主要是寄存器分配过程进行改造。 采用-O0编译选项编译,经过故障注入后的结果为,可以看到,软加固的效果同样十分不理想: 12345678910111213141516Timeout Count: 91Exception Count: 326SDC Count: 496Right Count: 877Detected Count: 706=========================================================time elapsed: 1.225579=========================================================Real Timeout Count: 418Real Exception Count: 1672Real SDC Count: 1938Real Right Count: 5609Real Detected Count: 1515=========================================================Total Interval Number: 11152(2496, 8) 对于未加固的程序,采用-O0优化(就是无优化)时,动态指令序列为83,故障注入结果为: 123456789101112131415161718192021222324252627282930313233343536=========================================================Timeout Count: 10Exception Count: 199SDC Count: 411Right Count: 236Detected Count: 0=========================================================time elapsed: 0.388901=========================================================Real Timeout Count: 20Real Exception Count: 703Real SDC Count: 652Real Right Count: 1385Real Detected Count: 0.0=========================================================Total Interval Number: 2760(856, 8)=========================================================Timeout Count: 19Exception Count: 208SDC Count: 385Right Count: 244Detected Count: 0=========================================================time elapsed: 0.455671=========================================================Real Timeout Count: 36Real Exception Count: 755Real SDC Count: 562Real Right Count: 1407Real Detected Count: 0.0=========================================================Total Interval Number: 2760(856, 8) 比对汇编代码发现,即便开启-O0,仍然有软加固代码被优化掉的现象: 未加固版的汇编代码: 12345.LBB0_1: # %for.cond # =>This Inner Loop Header: Depth=1 movl -20(%rbp), %eax cmpl -28(%rbp), %eax jg .LBB0_4 加固版汇编代码: 12345678910111213141516.LBB0_1: # %for.cond # =>This Inner Loop Header: Depth=1 movl -20(%rbp), %eax movl -28(%rbp), %ecx subl %ecx, %eax setle %dl xorl %ecx, %ecx movb %cl, %sil movzbl %sil, %edi movl %eax, -44(%rbp) # 4-byte Spill movb %dl, -45(%rbp) # 1-byte Spill callq check_and_error_handling movb -45(%rbp), %dl # 1-byte Reload testb $1, %dl jne .LBB0_2 jmp .LBB0_4 为此,进行了进一步的尝试,将比对 1234567891011121314151617=========================================================Timeout Count: 110Exception Count: 304SDC Count: 483Right Count: 673Detected Count: 998=========================================================time elapsed: 7.668829=========================================================Real Timeout Count: 344Real Exception Count: 1504Real SDC Count: 1793Real Right Count: 4744Real Detected Count: 2447=========================================================Total Interval Number: 10832(2568, 8) Todo: 通过改变寄存器分配方式,减轻后端编译优化对软加固代码的影响。 疑问,是不是通过传参比对就可以避免二者之间的依赖关系了?或者有没有可能解决了前文的基本块拆分问题,就可以阻止此处的寄存器分配导致软加固失效的现象了? 实验一:将比较指令删除,将原始变量和副本变量的一致性校验功能集成到函数中,通过函数调用避免编译器发现二者之间的关联性,从而为原始变量和副本变量分配相同的寄存器,致使软加固失效。 实验结果总结:即便 factorial_swift.ll 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139; ModuleID = '/home/xiaofengwo/llvm/llvm-workspace/sample_programs/factorial/llfi/a-llfi_index.ll'target datalayout = "e-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-v128:128:128-a0:0:64-s0:64:64-f80:128: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; Function Attrs: nounwind uwtabledefine i32 @main(i32 %argc, i8** %argv) #0 {entry: %retval = alloca i32, align 4 call void @doProfiling(i32 26) %argc.addr = alloca i32, align 4 call void @doProfiling(i32 26) %argv.addr = alloca i8**, align 8 call void @doProfiling(i32 26) %i = alloca i32, align 4 call void @doProfiling(i32 26) %fact = alloca i32, align 4 call void @doProfiling(i32 26) %n = alloca i32, align 4 call void @doProfiling(i32 26) store i32 0, i32* %retval, !storemark !1 store i32 %argc, i32* %argc.addr, align 4, !storemark !1 store i8** %argv, i8*** %argv.addr, align 8, !storemark !1 %0 = load i8*** %argv.addr, align 8 %1 = load i8*** %argv.addr, align 8, !mxk1 !2, !mxk !3, !numuses !4 call void @doProfiling(i32 27) %arrayidx = getelementptr inbounds i8** %0, i64 1 %arrayidx1 = getelementptr inbounds i8** %1, i64 1, !mxk1 !5, !mxk !6, !numuses !4 call void @doProfiling(i32 29) %2 = load i8** %arrayidx, align 8 %3 = load i8** %arrayidx1, align 8, !mxk1 !7, !mxk !3, !numuses !4 call void @doProfiling(i32 27) call void @check_and_error_handling2(i8* %2, i8* %3) %call = call i32 (i8*, ...)* bitcast (i32 (...)* @atoi to i32 (i8*, ...)*)(i8* %2) call void @doProfiling(i32 49) store i32 %call, i32* %n, align 4, !storemark !1 store i32 1, i32* %fact, align 4, !storemark !1 store i32 1, i32* %i, align 4, !storemark !1 br label %for.condfor.cond: ; preds = %for.inc, %entry %4 = load i32* %i, align 4 %5 = load i32* %i, align 4, !mxk1 !2, !mxk !3, !numuses !4 call void @doProfiling(i32 27) %6 = load i32* %n, align 4 %7 = load i32* %n, align 4, !mxk1 !2, !mxk !3, !numuses !4 call void @doProfiling(i32 27) %cmp = icmp sle i32 %4, %6 %cmp2 = icmp sle i32 %5, %7, !mxk1 !8, !mxk !9, !numuses !4 call void @doProfiling(i32 46) call void bitcast (void (i8*, i8*)* @check_and_error_handling2 to void (i1, i1)*)(i1 %cmp, i1 %cmp2) br i1 %cmp, label %for.body, label %for.endfor.body: ; preds = %for.cond %8 = load i32* %fact, align 4 %9 = load i32* %fact, align 4, !mxk1 !2, !mxk !3, !numuses !4 call void @doProfiling(i32 27) %10 = load i32* %i, align 4 %11 = load i32* %i, align 4, !mxk1 !2, !mxk !3, !numuses !4 call void @doProfiling(i32 27) %mul = mul nsw i32 %8, %10 %mul3 = mul nsw i32 %9, %11, !mxk1 !8, !mxk !10, !numuses !4 call void @doProfiling(i32 12) call void bitcast (void (i8*, i8*)* @check_and_error_handling2 to void (i32, i32)*)(i32 %mul, i32 %mul3) store i32 %mul, i32* %fact, align 4, !storemark !1 br label %for.incfor.inc: ; preds = %for.body %12 = load i32* %i, align 4 %13 = load i32* %i, align 4, !mxk1 !2, !mxk !3, !numuses !4 call void @doProfiling(i32 27) %inc = add nsw i32 %12, 1 %inc4 = add nsw i32 %13, 1, !mxk1 !5, !mxk !11, !numuses !4 call void @doProfiling(i32 8) call void bitcast (void (i8*, i8*)* @check_and_error_handling2 to void (i32, i32)*)(i32 %inc, i32 %inc4) store i32 %inc, i32* %i, align 4, !storemark !1 br label %for.condfor.end: ; preds = %for.cond %14 = load i32* %fact, align 4 %15 = load i32* %fact, align 4, !mxk1 !2, !mxk !3, !numuses !4 call void @doProfiling(i32 27) call void bitcast (void (i8*, i8*)* @check_and_error_handling2 to void (i32, i32)*)(i32 %14, i32 %15) %call1 = call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([4 x i8]* @.str, i32 0, i32 0), i32 %14) call void @doProfiling(i32 49) call void @endProfiling() call void @exit(i32 0) #3 unreachablereturn: ; No predecessors! %16 = load i32* %retval %17 = load i32* %retval, !mxk1 !2, !mxk !3, !numuses !12 call void @doProfiling(i32 27) call void @endProfiling() call void bitcast (void (i8*, i8*)* @check_and_error_handling2 to void (i32, i32)*)(i32 %16, i32 %17) ret i32 %16mxk_error_detection: ; No predecessors! call void @error_handling() call void bitcast (void (i8*, i8*)* @check_and_error_handling2 to void (i32, i32)*)(i32 %16, i32 %17) ret i32 %16}declare i32 @atoi(...) #1declare i32 @printf(i8*, ...) #1; Function Attrs: noreturndeclare void @exit(i32) #2declare void @doProfiling(i32)declare void @endProfiling()declare void @error_handling()declare void @check_and_error_handling2(i8*, i8*)attributes #0 = { nounwind uwtable "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" "stack-protector-buffer-size"="8" "unsafe-fp-math"="false" "use-soft-float"="false" }attributes #1 = { "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" "stack-protector-buffer-size"="8" "unsafe-fp-math"="false" "use-soft-float"="false" }attributes #2 = { noreturn "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" "stack-protector-buffer-size"="8" "unsafe-fp-math"="false" "use-soft-float"="false" }attributes #3 = { noreturn }!llvm.ident = !{!0}!0 = metadata !{metadata !"clang version 3.4 (tags/RELEASE_34/final)"}!1 = metadata !{null, metadata !"store"}!2 = metadata !{null}!3 = metadata !{metadata !"load"}!4 = metadata !{i64 1}!5 = metadata !{null, metadata !""}!6 = metadata !{metadata !"getelementptr"}!7 = metadata !{null, metadata !"arrayidx1"}!8 = metadata !{null, metadata !"", metadata !""}!9 = metadata !{metadata !"icmp"}!10 = metadata !{metadata !"mul"}!11 = metadata !{metadata !"add"}!12 = metadata !{i64 2} factorial_swift.s 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134 .file "a-profiling.ll" .text .globl main .align 16, 0x90 .type main,@functionmain: # @main .cfi_startproc# BB#0: # %entry pushq %rbp.Ltmp3: .cfi_def_cfa_offset 16.Ltmp4: .cfi_offset %rbp, -16 movq %rsp, %rbp.Ltmp5: .cfi_def_cfa_register %rbp pushq %r15 pushq %r14 pushq %rbx subq $40, %rsp.Ltmp6: .cfi_offset %rbx, -40.Ltmp7: .cfi_offset %r14, -32.Ltmp8: .cfi_offset %r15, -24 movq %rsi, %rbx movl %edi, %r14d movl $26, %edi callq doProfiling movl $26, %edi callq doProfiling movl $26, %edi callq doProfiling movl $26, %edi callq doProfiling movl $26, %edi callq doProfiling movl $26, %edi callq doProfiling movl $0, -28(%rbp) movl %r14d, -32(%rbp) movq %rbx, -40(%rbp) movl $27, %edi callq doProfiling movl $29, %edi callq doProfiling movq 8(%rbx), %rbx movl $27, %edi callq doProfiling movq %rbx, %rdi movq %rbx, %rsi callq check_and_error_handling2 xorl %eax, %eax movq %rbx, %rdi callq atoi movl %eax, %ebx movl $49, %edi callq doProfiling movl %ebx, -52(%rbp) movl $1, -48(%rbp) movl $1, -44(%rbp) jmp .LBB0_1 .align 16, 0x90.LBB0_2: # %for.body # in Loop: Header=BB0_1 Depth=1 movl -48(%rbp), %ebx movl $27, %edi callq doProfiling imull -44(%rbp), %ebx movl $27, %edi callq doProfiling movl $12, %edi callq doProfiling movl %ebx, %edi movl %ebx, %esi callq check_and_error_handling2 movl %ebx, -48(%rbp) movl -44(%rbp), %ebx movl $27, %edi callq doProfiling incl %ebx movl $8, %edi callq doProfiling movl %ebx, %edi movl %ebx, %esi callq check_and_error_handling2 movl %ebx, -44(%rbp).LBB0_1: # %for.cond # =>This Inner Loop Header: Depth=1 movl -44(%rbp), %r14d movl $27, %edi callq doProfiling movl -52(%rbp), %ebx movl $27, %edi callq doProfiling cmpl %ebx, %r14d setle %r15b movl $46, %edi callq doProfiling movzbl %r15b, %edi movl %edi, %esi callq check_and_error_handling2 cmpl %ebx, %r14d jle .LBB0_2# BB#3: # %for.end movl -48(%rbp), %ebx movl $27, %edi callq doProfiling movl %ebx, %edi movl %ebx, %esi callq check_and_error_handling2 movl $.L.str, %edi xorl %eax, %eax movl %ebx, %esi callq printf movl $49, %edi callq doProfiling callq endProfiling xorl %edi, %edi callq exit.Ltmp9: .size main, .Ltmp9-main .cfi_endproc .type .L.str,@object # @.str .section .rodata.str1.1,"aMS",@progbits,1.L.str: .asciz "%d\n" .size .L.str, 4 .ident "clang version 3.4 (tags/RELEASE_34/final)" .section ".note.GNU-stack","",@progbits 可以看出,并没有什么卵用。 偶然一试,发现修改llc编译优化选项似乎有些效果。 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191 .file "a-profiling.ll" .text .globl main .align 16, 0x90 .type main,@functionmain: # @main .cfi_startproc# BB#0: # %entry pushq %rbp.Ltmp2: .cfi_def_cfa_offset 16.Ltmp3: .cfi_offset %rbp, -16 movq %rsp, %rbp.Ltmp4: .cfi_def_cfa_register %rbp subq $176, %rsp movl $26, %eax movl %edi, -32(%rbp) # 4-byte Spill movl %eax, %edi movq %rsi, -40(%rbp) # 8-byte Spill callq doProfiling movl $26, %edi callq doProfiling movl $26, %edi callq doProfiling movl $26, %edi callq doProfiling movl $26, %edi callq doProfiling movl $26, %edi callq doProfiling movl $27, %edi movl $0, -4(%rbp) movl -32(%rbp), %eax # 4-byte Reload movl %eax, -8(%rbp) movq -40(%rbp), %rsi # 8-byte Reload movq %rsi, -16(%rbp) movq -16(%rbp), %rcx movq -16(%rbp), %rdx movq %rdx, -48(%rbp) # 8-byte Spill movq %rcx, -56(%rbp) # 8-byte Spill callq doProfiling movl $29, %edi callq doProfiling movl $27, %edi movq -56(%rbp), %rcx # 8-byte Reload movq 8(%rcx), %rdx movq -48(%rbp), %rsi # 8-byte Reload movq 8(%rsi), %rsi movq %rsi, -64(%rbp) # 8-byte Spill movq %rdx, -72(%rbp) # 8-byte Spill callq doProfiling movq -72(%rbp), %rdi # 8-byte Reload movq -64(%rbp), %rsi # 8-byte Reload callq check_and_error_handling2 movl $49, %edi movq -72(%rbp), %rcx # 8-byte Reload movl %edi, -76(%rbp) # 4-byte Spill movq %rcx, %rdi movb $0, %al callq atoi movl -76(%rbp), %edi # 4-byte Reload movl %eax, -80(%rbp) # 4-byte Spill callq doProfiling movl -80(%rbp), %eax # 4-byte Reload movl %eax, -28(%rbp) movl $1, -24(%rbp) movl $1, -20(%rbp).LBB0_1: # %for.cond # =>This Inner Loop Header: Depth=1 movl $27, %edi movl -20(%rbp), %eax movl -20(%rbp), %ecx movl %ecx, -84(%rbp) # 4-byte Spill movl %eax, -88(%rbp) # 4-byte Spill callq doProfiling movl $27, %edi movl -28(%rbp), %eax movl -28(%rbp), %ecx movl %ecx, -92(%rbp) # 4-byte Spill movl %eax, -96(%rbp) # 4-byte Spill callq doProfiling movl $46, %edi movl -88(%rbp), %eax # 4-byte Reload movl -96(%rbp), %ecx # 4-byte Reload cmpl %ecx, %eax setle %dl movl -84(%rbp), %esi # 4-byte Reload movl -92(%rbp), %r8d # 4-byte Reload cmpl %r8d, %esi setle %r9b movb %r9b, -97(%rbp) # 1-byte Spill movb %dl, -98(%rbp) # 1-byte Spill callq doProfiling movb -98(%rbp), %dl # 1-byte Reload movzbl %dl, %edi movb -97(%rbp), %r9b # 1-byte Reload movzbl %r9b, %esi callq check_and_error_handling2 movb -98(%rbp), %dl # 1-byte Reload testb $1, %dl jne .LBB0_2 jmp .LBB0_4.LBB0_2: # %for.body # in Loop: Header=BB0_1 Depth=1 movl $27, %edi movl -24(%rbp), %eax movl -24(%rbp), %ecx movl %ecx, -104(%rbp) # 4-byte Spill movl %eax, -108(%rbp) # 4-byte Spill callq doProfiling movl $27, %edi movl -20(%rbp), %eax movl -20(%rbp), %ecx movl %ecx, -112(%rbp) # 4-byte Spill movl %eax, -116(%rbp) # 4-byte Spill callq doProfiling movl $12, %edi movl -108(%rbp), %eax # 4-byte Reload movl -116(%rbp), %ecx # 4-byte Reload imull %ecx, %eax movl -104(%rbp), %ecx # 4-byte Reload movl -112(%rbp), %edx # 4-byte Reload imull %edx, %ecx movl %ecx, -120(%rbp) # 4-byte Spill movl %eax, -124(%rbp) # 4-byte Spill callq doProfiling movl -124(%rbp), %edi # 4-byte Reload movl -120(%rbp), %esi # 4-byte Reload callq check_and_error_handling2 movl -124(%rbp), %eax # 4-byte Reload movl %eax, -24(%rbp)# BB#3: # %for.inc # in Loop: Header=BB0_1 Depth=1 movl $27, %edi movl -20(%rbp), %eax movl -20(%rbp), %ecx movl %ecx, -128(%rbp) # 4-byte Spill movl %eax, -132(%rbp) # 4-byte Spill callq doProfiling movl $8, %edi movl -132(%rbp), %eax # 4-byte Reload addl $1, %eax movl -128(%rbp), %ecx # 4-byte Reload addl $1, %ecx movl %ecx, -136(%rbp) # 4-byte Spill movl %eax, -140(%rbp) # 4-byte Spill callq doProfiling movl -140(%rbp), %edi # 4-byte Reload movl -136(%rbp), %esi # 4-byte Reload callq check_and_error_handling2 movl -140(%rbp), %eax # 4-byte Reload movl %eax, -20(%rbp) jmp .LBB0_1.LBB0_4: # %for.end movl $27, %edi movl -24(%rbp), %eax movl -24(%rbp), %esi movl %esi, -144(%rbp) # 4-byte Spill movl %eax, -148(%rbp) # 4-byte Spill callq doProfiling leaq .L.str, %rdi movl -148(%rbp), %eax # 4-byte Reload movq %rdi, -160(%rbp) # 8-byte Spill movl %eax, %edi movl -144(%rbp), %esi # 4-byte Reload callq check_and_error_handling2 movq -160(%rbp), %rdi # 8-byte Reload movl -148(%rbp), %esi # 4-byte Reload movb $0, %al callq printf movl $49, %edi movl %eax, -164(%rbp) # 4-byte Spill callq doProfiling callq endProfiling movl $0, %edi callq exit.Ltmp5: .size main, .Ltmp5-main .cfi_endproc .type .L.str,@object # @.str .section .rodata.str1.1,"aMS",@progbits,1.L.str: .asciz "%d\n" .size .L.str, 4 .ident "clang version 3.4 (tags/RELEASE_34/final)" .section ".note.GNU-stack","",@progbits]]></content>
<tags>
<tag>学术</tag>
</tags>
</entry>
<entry>
<title><![CDATA[如何实现docx文档的版本管理]]></title>
<url>%2F2018%2F03%2F18%2Fhowtotrackdocxfilewithgit%2F</url>
<content type="text"><![CDATA[docx文件提交到git很容易,对docx文件实现版本管理的难点在于如何比较其中的差异,方法简要概述如下: 利用pandoc实现将docx文件转成md文件(只能是docx,而不能是doc) 通过git hook,配置pre-commit和post-commit,在git commit时自动调用生成md文件的过程,并将md文件提交到库中 md文件作为docx的副本,且可以通过diff直接查看 参考:https://github.com/vigente/gerardus/wiki/Integrate-git-diffs-with-word-docx-files 1. 安装pandoc2. Tell git how to handle diffs of .docx files. Create or edit file ~/.gitconfig (linux, Mac) or “c:\Documents and Settings\user.gitconfig” (Windows) to add 12345[diff "pandoc"] textconv=pandoc --to=markdown prompt = false[alias] wdiff = diff --word-diff=color --unified=1 In your paper directory, create or edit file .gitattributes (linux, Windows and Mac) to add 1*.docx diff=pandoc You can commit .gitattributes so that it stays with your paper for use in other computers, but you’ll need to edit ~/.gitconfig in every new computer you want to use. 3. 配置git hookThis is only going to work from linux/Mac or Windows running git from a bash shell. Install pandoc. Pandoc is a program to convert between different file formats. It’s going to allow us to convert Word files (.docx) to Markdown (.md). Set up git hooks to enable automatic generation and tracking of Markdown copies of .docx files. Copy these hook files to your git project’s .git/hooks directory and rename them, or soft-link to them with ln -s, and make them executable (chmod u+x *.sh): pre-commit-git-diff-docx.sh -> .git/hooks/pre-commit post-commit-git-diff-docx.sh -> .git/hooks/post-commit Now every time you run git commit, the pre-commit hook will automatically run before you see the window to enter the log message. The hook is a script that makes a copy in Markdown format (.md) of every .docx file you are committing. The post-commit hook then amends the commit adding the .md files.]]></content>
<categories>
<category>杂项技术</category>
</categories>
<tags>
<tag>杂项技术</tag>
</tags>
</entry>
<entry>
<title><![CDATA[如何利用shadowsocks配置系统代理]]></title>
<url>%2F2018%2F02%2F06%2Finstruction-to-use-shadowsocks-for-system-proxy%2F</url>
<content type="text"><![CDATA[1. 配置AWS服务器如果没有搭建远端shadowsocks服务器的需求,可以跳过这一步。 申请AWS账号 登陆AWS官方网站https://aws.amazon.com/cn/,并注册账号,需要填写邮箱号,信用卡信息和手机号码,注册时都会进行验证,信用卡会通过预扣费1美元验证,手机号码通过电话输入屏幕显示的即时数字验证。 现在新注册的用户,需要24小时才能完全激活。 开通EC2实例,不具体展开,但需要注意几个问题: 1)选择标有“符合条件的免费套餐”的系统映像(AMI),最好选择红框中的选项,其它的有时不能上GoogleScholar 2)建议选择新加坡地区,延迟较小。 3)启动EC2实例后,一定要修改安全策略,按照下图中的安全组策略配置出站和入站策略,允许全部来源的全部端口,否则无法接入。 利用MobaXterm或者其它终端访问软件连接用户,需要配置远程ip地址(通过在aws管理页面查看已启动的EC2实例获取)、username(固定写ec2-user)、端口(22),以及private key(在启动EC2的时候会要求下载) 配置远端ShadowSocks服务器 1)安装shadowsocks,在命令行中依次输入: 12345sudo yum install python-gevent python-pipsudo pip install shadowsockssudo vi ~/config.json 注:config.json可以实现编写好,然后上传到远程ec2主机,放在任何位置都可以,只要在引用的时候找得到,最好放在用户的home目录下,这样命令短一些。 填入配置信息: 12345678{ "server":"xx.xx.xx.xx", //填写私有IP "server_port":8388, "local_port":10808, "password":"bgt56yhn", "timeout":600, "method":"aes-256-cfb" } 如果需要配置多个端口,则按照下面的格式: 1234567891011{ "server":"xx.xx.xx.xx", //填写私有IP "local_port":1080, "port_password": { "8381": "aaa", "8382": "aaa", "8383": "aaa", }, "timeout":600, "method":"aes-256-cfb"} 注意:上面的server属性,需要配置为私有IP,同样在aws页面的ec2 Dashboard下可以查到: 密码和端口自行设置,加密算法注意要与本地ShadowSocks客户端统一。 2)增加开机启动(没有必要,因为远程服务器EC2重启轻易不会关机): 1sudo vim /etc/rc.local 在结尾增加这句话就可以开机启动了: 1/usr/local/bin/ssserver -c ~/config.json 3)在命令行中启动后台Shadowsocks(不随命令行关闭而结束): 1nohup ssserver -c ~/config.json & 2. 配置本地ShadowSocks客户端 下载ShadowSocks Shadowsocks-4.0.1.zip 配置ShadowSocks 下载后,打开ShadowSocks客户端,配置远端服务器信息。 启用ShadowSocks客户端 正确启动ShadowSocks客户端以后,就打开了一个本地的代理端口1723,本地应用如果需要使用这个代理,还需要进行一定的配置。除了Chrome以外,其它很多程序也可以配置代理,我们主要讲解Chrome的代理配置。 3. 配置Chrome代理 配置Chrome系统代理 在Chrome设置中找到系统代理设置 这个Chrome的代理设置实际上和IE浏览器的代理设置是共享的,设置方法也一样。 依次点击“连接”——“局域网设置”,按照下图配置代理服务器 配置好以后,Chrome就可以通过代理上网了 配置Chrome自动代理 配置好Chrome系统代理后,Chrome所有的访问都将通过代理服务器,一方面增加服务器的压力,占用带宽和流量,另一方面,很多国内的站点,还通过代理服务器访问,会影响上网速度。 下面我们通过Proxy SwitchyOmega这个Chrome扩展程序,配置Chrome自动切换代理功能。 下载Proxy SwitchyOmega。 这一步不做展开,但最便捷的方式是通过Chrome网上应用店搜索下载,这个操作本身就需要使用代理上网。 配置Proxy SwichyOmega。 首先,新建一个通过代理上网的情景模式。 然后,配置auto switch情景模式。 在规则列表中,输入规则列表网址,并点击“立即更新情景模式”,SwitchyOmega会自动下载规则列表,并在下方显示更新。 1https://raw.githubusercontent.com/gfwlist/gfwlist/master/gfwlist.txt 选择AutoProxy 全部配置好以后,如下图所示。 记得配置完成后要点击左下方的“应用选项”,使修改生效。 应用auto switch情景模式]]></content>
<categories>
<category>杂项技术</category>
</categories>
<tags>
<tag>杂项技术</tag>
<tag>实用工具</tag>
</tags>
</entry>
<entry>
<title><![CDATA[如何搭建自己的github]]></title>
<url>%2F2018%2F01%2F31%2Fhow%20to%20build%20your%20own%20github%2F</url>
<content type="text"><![CDATA[建立Git环境关联Github操作技巧1. 为本地库添加远程库现在,我们根据GitHub的提示,在本地的learngit仓库下运行命令: 1$ git remote add origin [email protected]:michaelliao/learngit.git 2. 怎样回退已经commit的git参考:https://blog.csdn.net/chichoxian/article/details/80638031https://www.cnblogs.com/lyy-2016/p/6509707.html当不小心commit了过大的文件时,如果push到线上仓库,会造成线上仓库过大,上传和下载的时间会很长。删除的方法如下: git log 查看已经commit的信息 git status 查看当前commit状态,特别是当前的commit相比远端领先了多少 git reset HEAD~n 回退commit, 分为soft, mixed, 和hard,只要不是hard,就不会丢失工作区]]></content>
<categories>
<category>杂项技术</category>
</categories>
<tags>
<tag>杂项技术</tag>
<tag>github</tag>
</tags>
</entry>
<entry>
<title><![CDATA[一些有用的技术、工具和站点]]></title>
<url>%2F2018%2F01%2F31%2Flist%20of%20the%20useful%20tools%20and%20sites%2F</url>
<content type="text"><![CDATA[(持续更新中…) 站点推荐1. Github2. Library Genesis 在这里可以下载到很多计算机类的电子书,而且都是相对清晰的pdf文档。 云服务系列1. AWS2. 七牛云3. 实用技术1. Markdown2. 思维导图 实用工具1. Typora2. ShadowSocks3. FreeMind4. Anaconda5. 010Editor一款集成了python包的软件,不需要考虑包之间的依赖关系,很方便。 Anaconda官方网站]]></content>
<categories>
<category>杂项技术</category>
</categories>
<tags>
<tag>杂项技术</tag>
<tag>github</tag>
<tag>实用工具</tag>
<tag>站点推荐</tag>
</tags>
</entry>
<entry>
<title><![CDATA[如何利用hexo在github上建立自己的博客]]></title>
<url>%2F2018%2F01%2F30%2Fhowtobuildhexoblogingithub%2F</url>
<content type="text"><![CDATA[1 建立hexo本地站点具体参考https://hexo.io/主要步骤:1hexo init 1hexo g 1hexo s 默认端口为4000,如果该端口被占用,可以通过hexo s -p 8080将端口改为8080 2 配置主题在https://hexo.io/themes/ 中选择适合自己的主题,并copy到themes文件夹以下以next主题为例 3 配置github自动部署参考https://linghucong.js.org/2016/04/15/2016-04-15-hexo-github-pages-blog/主要步骤:修改站点根目录下的_config.yml 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293# Hexo Configuration## Docs: https://hexo.io/docs/configuration.html## Source: https://github.com/hexojs/hexo/# Sitetitle: 民科小屋subtitle: 计算机民科description:author: xiaofengwolanguage: zh-Hanstimezone:# URL## If your site is put in a subdirectory, set url as 'http://yoursite.com/child' and root as '/child/'url: http://ifuleyou.github.ioroot: /permalink: :year/:month/:day/:title/permalink_defaults:# Directorysource_dir: sourcepublic_dir: publictag_dir: tagsarchive_dir: archivescategory_dir: categoriescode_dir: downloads/codei18n_dir: :langskip_render:# Writingnew_post_name: :title.md # File name of new postsdefault_layout: posttitlecase: false # Transform title into titlecaseexternal_link: true # Open external links in new tabfilename_case: 0render_drafts: falsepost_asset_folder: falserelative_link: falsefuture: truehighlight: enable: true line_number: true auto_detect: false tab_replace:# Category & Tagdefault_category: uncategorizedcategory_map:tag_map:# Date / Time format## Hexo uses Moment.js to parse and display date## You can customize the date format as defined in## http://momentjs.com/docs/#/displaying/format/date_format: YYYY-MM-DDtime_format: HH:mm:ss# Pagination## Set per_page to 0 to disable paginationper_page: 5pagination_dir: pageindex_generator: per_page: 5archive_generator: per_page: 20 yearly: true monthly: truetag_generator: per_page: 15# Extensions## Plugins: https://hexo.io/plugins/## Themes: https://hexo.io/themes/theme: next# Deployment## Docs: https://hexo.io/docs/deployment.htmldeploy: type: git repo: [email protected]:ifuleyou/ifuleyou.github.io.git branch: mastersearch: path: search.xml field: post format: html limit: 10000 4 配置评论、评分、访问计数在themes/next/_config.yml里修改以下配置并且按照配置文件中给出的站点到相应的网站中注册账号,获取相应信息 1234567891011121314151617181920# Star rating support to each article.# To get your ID visit https://widgetpack.comrating: enable: true id: xxxx color: fc6423# ---------------------------------------------------------------# Show number of visitors to each article.# You can visit https://leancloud.cn get AppID and AppKey.leancloud_visitors: enable: true app_id: xxxxx app_key: xxxxxxxx# Disqusdisqus: enable: true shortname: xiaofengwo count: true 5 如何为next主题添加背景效果next主题有一个十分有趣的js背景效果,可以跟随鼠标的移动,汇集背景中随机浮动的点,并在每两个点之间生成一条线段,生生灭灭,聚聚散散,科技感十足,特别受广大民科喜欢。 6 为hexo博客插入图片 七牛云存储 首先,到https://www.qiniu.com网站注册账号,需要实名认证。 然后,创建自己的存储空间。 利用图床软件(windows下的uwp图床,mac下的U图床等)将图片上传到自己的存储空间后,获得url,直接利用url访问即可。 此方法的好处,是操作简便,无需关心图片的具体存放,且七牛会为图片进行瘦身,访问速度较快。另外,图片资源不需要存放到hexo的source文件夹下,可以减少本地存储以及git同步时的时间空间开销,也较易于进行跨越博客平台的迁移。 利用hexo-asset-image上传图片 直接摘抄https://www.jianshu.com/p/c2ba9533088a中的步骤 1.首先确认_config.yml 中有 post_asset_folder:true。Hexo 提供了一种更方便管理 Asset 的设定:post_asset_folder当您设置post_asset_folder为true参数后,在建立文件时,Hexo会自动建立一个与文章同名的文件夹,您可以把与该文章相关的所有资源都放到那个文件夹,如此一来,您便可以更方便的使用资源。 2.在hexo的目录下执行npm install https://github.com/CodeFalling/hexo-asset-image --save(需要等待一段时间)。 3.完成安装后用hexo新建文章的时候会发现_posts目录下面会多出一个和文章名字一样的文件夹。图片就可以放在文件夹下面。结构如下: 12345本地图片测试├── apppicker.jpg├── logo.jpg└── rules.jpg本地图片测试.md 这样的目录结构(目录名和文章名一致),只要使用 ![logo](本地图片测试/logo.jpg) 就可以插入图片。其中[]里面不写文字则没有图片标题。生成的结构为 12345public/2016/3/9/本地图片测试├── apppicker.jpg├── index.html├── logo.jpg└── rules.jpg 同时,生成的 html 是 <img src="/2016/3/9/本地图片测试/logo.jpg" alt="logo"> 而不是愚蠢的 <img src="本地图片测试/logo.jpg" alt="logo"> 注意:通过常规的 markdown 语法和相对路径来引用图片和其它资源可能会导致它们在存档页或者主页上显示不正确。在Hexo2时代,社区创建了很多插件来解决这个问题。但是,随着Hexo3的发布,许多新的标签插件被加入到了核心代码中。这使得你可以更简单地在文章中引用你的资源。 比如说:当你打开文章资源文件夹功能后,你把一个 example.jpg图片放在了你的资源文件夹中,如果通过使用相对路径的常规 markdown 语法 [](/example.jpg),它将 不会 出现在首页上。(但是它会在文章中按你期待的方式工作) 正确的引用图片方式是使用下列的标签插件而不是markdown 7 hexo的公式支持参考:http://stevenshi.me/2017/06/26/hexo-insert-formula/ 配置 1$ npm install hexo-math --save 在站点配置文件 _config.yml 中添加: 123456math: engine: 'mathjax' # or 'katex' mathjax: # src: custom_mathjax_source config: # MathJax config 在 next 主题配置文件中 themes/next-theme/_config.yml 中将 mathJax 设为 true: 12345# MathJax Supportmathjax: enable: true per_page: false cdn: //cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML 也可以在文章的开始集成插件支持,但不建议这么做: 123<script type="text/javascript" src="http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script> 使用 $数学公式$ 行内 不独占一行$$数学公式$$ 行间 独占一行]]></content>
<categories>
<category>杂项技术</category>
</categories>
<tags>
<tag>杂项技术</tag>
<tag>github</tag>
</tags>
</entry>
</search>