网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
未插桩的代码:
void foo() {
char a[8];
...
return;
}
插桩后的代码:
void foo() {
char redzone1[32]; // 32-byte aligned
char a[8]; // 32-byte aligned
char redzone2[24];
char redzone3[32]; // 32-byte aligned
int *shadow_base = MemToShadow(redzone1);
shadow_base[0] = 0xffffffff; // poison redzone1
shadow_base[1] = 0xffffff00; // poison redzone2, unpoison 'a'
shadow_base[2] = 0xffffffff; // poison redzone3
...
shadow_base[0] = shadow_base[1] = shadow_base[2] = 0; // unpoison all
return;
}
动态运行库
在动态运行库中将malloc/free函数进行了替换。在malloc函数中额外的分配了Redzone区域的内存,将与Redzone区域对应的影子内存加锁,主要的内存区域对应的影子内存不加锁。
free函数将所有分配的内存区域加锁,并放到了隔离区域的队列中(保证在一定的时间内不会再被malloc函数分配)。
AddressSanitizer源码分析
AddressSanitizer主要有三种层面的变量:Stack Variable(局部变量),Global Variable, Heap Variable。由于每种变量的生命周期(life time)不同,所以对不同种类的变量处理也是不同的。下面分别从Global Variable,Stack Variable,Heap Variable三个层次来分析AddressSanitizer源码的逻辑结构。
Global Variable
Global Variable存放在程序的数据段。在该算法的实现过程中,处理GlobalVariale的是AddressSanitizerModule类,该类继承自llvm的ModulePass,所以我们先看一下AddressSanitizerModule类的runOnModule(Module &M)方法的处理过程,该过程首先进行一些初始化,然后我们可以看到对Global的插桩方法InstrumentGlobals()方法。
图2: RunOnModule
在InstrumentGlobals()方法中,主要是分成两步:首先,重新声明一个GlobalVariable,这个GlobalVariable包含以前的GlobalVariable和一个RedZone;然后,调用runtime-library将新声明的这个GlobalVariable的RedZone区域加锁。我们先来看第一步的具体实现,如图3所示。
图3: 生成包含RedZone的新的GlobalVariable
下面,我们首先看一下一个Struct结构,该结构记录GlobalVariable存储的首地址,数据的大小,Redzone的大小,Module的名字等信息,便于在Runtime-library中使用。该结构在AddressSanitizerModule和runtime-library中都有相应的定义:
然后我们可以看到对GlobalVariable进行插桩来实现RedZone的Poison和整个GlobalVariable的Poison操作。
具体的Poison RedZone和Poison GlobalVariable的实现在Runtime-library中:
Stack Variable
Stack Variable保存在栈区,在栈中的数据我们需要控制好变量的声明周期(lifetime),当调用一个函数时,会开辟一个栈,栈中的数据会有相应的redzone和shadow memory,并将redzone的shadow memory Poison,当函数结束(正常返回,异常),栈被销毁,需要将数据和redzone清空,其相应的shadow memory也要UnPoison掉。
对于Stack Variable,AddressSanitizer算法中实现了AddressSanitizer类,该类是继承了llvm的FunctionPass,该Pass能够处理每一个函数,在处理每个函数的时候,处理每一个load,store等能够访问内存的指令,在这些指令执行前进行插桩,看其访问的内存是不是被poison。
下面我们主要看一下AddressSanitizer::runOnFunction(Module &M)函数中主要的插桩过程。
Heap Variable
Heap Variable保存在堆区,其分配的函数是malloc函数,该部分的主要代码在runtime-library中,该库中主要是先将malloc的库函数hook住,然后自己定义malloc函数,定义分配策略。
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
//bbs.csdn.net/topics/618631832)**
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!