|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有帐号?FreeOZ用户注册
x
一般而言,只有动态类型的编程语言可以在运行时更改程序自身的逻辑, 或者至少是像Java, C#这样的具有完整元数据,可以在运行时完成自省和反射能力的静态类型语言。一旦拥有了可以在运行时修改程序行为了魔力,就可以完成很多有趣的功能,以Java/C#举例,尽管都是静态类型的编程语言,但是凭借JDK/.NET CLR中的完备的元数据和反射API使得很多功能强大的框架,类库和设计模式的出现成为可能。比如Hibernate 的自动OR Mapping, Spring 等框架的IoC实现, JBoss 中的AOP 实现等等,而同样属于静态类型语言的 C++却缺少对应的实现,或者至少实现起来不是那么的自然和优雅。比如C++中也有个著名的AOP实现Aspect C++ ,但是却只能够对关注点进行静态织入,使得可用性大打折扣,接受度自然不如Java AOP高, 广大的C++程序员也只能够看着Hibernate, Spring之类的让人眼花缭乱的技术咽咽口水,然后埋头苦干地去和各种丑陋不堪的API战斗。
总有些人是不相信宿命的, 比如DynInst 库的作者们,硬是让人匪夷所思地实现了C/C++程序的运行时代码注入功能,下面一个小例子 ,可以牛刀小试下DynInst的威力:
上图右边表示一个运行中的进程(姑且称为FOO进程),这个进程中有个叫foo()的函数和另一个sendMsg函数。左边红色的3个代码块是我们企图在不中断程序运行的前提下动态插入的代码逻辑片段,箭头表示了动态插入的位置。简单地说就是希望在执行foo()时,fooCnt变量加1,退出foo()时fooCnt变量减1,进入sendMsg函数时根据fooCnt值的不同决定是否运行addCounter函数。
要达到上图的目的,需要以下一种能力:
1. 在FOO进程中动态生成int型的变量fooCnt和bytes,并初始化为零
2. 找到foo()和sendMsg函数的位置,并确定3个代码插入点:foo()入口, foo()出口,sendMsg()入口
3. 动态产生3行待插入的代码
4. 讲代码依次插入到相应位置
下面我们来看看DynInst如何做到上面4个步骤:
1. 生成fooCnt和bytes并初始化,用的是BPatch_variableExpr,初始化用writeValue方法
BPatch_variableExpr *fooCnt = appThread->malloc(appImage->findType("int")); BPatch_variableExpr *bytes = appThread->malloc(appImage->findType("int"));int zero = 0;fooCnt.writeValue(&zero);bytes.writeValue(&zero);
2. 用BPatch_arithExpr生成addCounter代码
BPatch_arithExpr fooCntPlusOne(BPatch_plus, *fooCnt, BPatch_constExpr(1));BPatch_arithExpr addCounter(BPatch_assign, *fooCnt, fooCntPlusOne);
相似的, 生成subCounter
BPatch_arithExpr fooCntMinusOne(BPatch_minus, *fooCnt, BPatch_constExpr(1));BPatch_assign subCounter(BPatch_assign, *fooCnt, fooCntMinusOne);
3. 插入到sendMsg的代码比较复杂一点
BPatch_paramExpr cntParam(2), sizeParam(3);BPatch_arithExpr calcBytes(BPatch_times, cntParam, sizeParam);BPatch_arithExpr addBytes(BPatch_plus, *bytes, calcBytes);BPatch_arithExpr addCounter(BPatch_assign, *bytes, addBytes);
对 "if (fooCnt)"的实现
BPatch_boolExpr fooCntCheck(BPatch_gt, *fooCnt, BPatch_constExpr(0));
BPatch_ifExpr addCounterIfInFoo(fooCntCheck, addCounter);
4. 找到插入第一行"addCounter(fooCnt, 1)"的插入点位置BPatch_Vector<BPatch_point*> *pointsFooEntry;BPatch_Vector<BPatch_point*> *pointsFooExit;BPatch_Vector<BPatch_point*> *pointsSendmsgEntry;pointsFooEntry = appImage->findProcedurePoint("foo", BPatch_entry);pointsFooExit = appImage->findProcedurePoint("foo", BPatch_exit);pointsSendmsgEntry = appImage->findProcedurePoint("sendMsg", BPatch_entry);
终于可以动真格的插入了 ^_^
appThread->insertBlock(addCounter, *pointsFooEntry);appThread->insertBlock(subCounter, *pointsFooExit);appThread->insertBlock(addCounterIfInFoo, *pointsFooExit);
完整代码如下:
/*
Program:foo_patch
File:foo_patch.cpp
Usage:./foo_patch
*/
#include
#include
#include
#include
#include "BPatch.h"
#include "BPatch_Vector.h"
#include "BPatch_thread.h"
#include "BPatch_snippet.h"
int main(int argc, char **argv)
{
BPatch * bpatch = new BPatch;
BPatch_thread *appThread = bpatch->attachProcess(argv[1], atoi(argv[2]));
BPatch_image *appImage = appThread->getImage();
BPatch_Vector *pointsFooEntry;
BPatch_Vector *pointsFooExit;
BPatch_Vector *pointsSendmsgEntry;
pointsFooEntry = appImage->findProcedurePoint("foo", BPatch_entry);
pointsFooExit = appImage->findProcedurePoint("foo", BPatch_exit);
pointsSendmsgEntry = appImage->findProcedurePoint("sendMsg", BPatch_entry);
BPatch_variableExpr *fooCnt = appThread->malloc(appImage->findType("int"));
BPatch_variableExpr *bytes = appThread->malloc(appImage->findType("int"));
int zero = 0;
fooCnt.writeValue(&zero);
bytes.writeValue(&zero);
BPatch_arithExpr fooCntPlusOne(BPatch_plus, *fooCnt, BPatch_constExpr(1));
BPatch_arithExpr addCounter(BPatch_assign, *fooCnt, fooCntPlusOne);
BPatch_arithExpr fooCntMinusOne(BPatch_minus, *fooCnt, BPatch_constExpr(1));
BPatch_assign subCounter(BPatch_assign, *fooCnt, fooCntMinusOne);
BPatch_paramExpr cntParam(2), sizeParam(3);
BPatch_arithExpr calcBytes(BPatch_times, cntParam, sizeParam);
BPatch_arithExpr addBytes(BPatch_plus, *bytes, calcBytes);
BPatch_arithExpr addCounter(BPatch_assign, *bytes, addBytes);
BPatch_boolExpr fooCntCheck(BPatch_gt, *fooCnt, BPatch_constExpr(0));
BPatch_ifExpr addCounterIfInFoo(fooCntCheck, addCounter);
appThread->insertBlock(addCounter, *pointsFooEntry);
appThread->insertBlock(subCounter, *pointsFooExit);
appThread->insertBlock(addCounterIfInFoo, *pointsFooExit);
appThread->detach(1);
}
从代码中可以看出,所有DynInst API类型或函数都以BPatch_开头, 其详细参考 可以从DynInst.org下载,大体上所有DynInst API分为以下几类:
1. BPatch
DynInst全局管理类, 同一时间只能实例化一次
2. BPatch_thread
代表一个执行线程,对单线程进程就代表整个进程,对多线程程序则代表其中一个线程。可以通过这个接口对相应线程进行启动,停止,退出等操作,如果在多线程环境中patch其中一个线程,其它线程将不受影响。
3. BPatch_image
代表正在执行中的代码.
4. BPatch_module
代表代码中的一个模块,或者是一个源代码文件,或者是一个动态库。
5. BPatch_function
函数,通过这个接口我们可以得到函数的入口,出口点
6. BPatch_point
代码注入点,注入点可以是函数的入口,出口,也可以是代码的片断位置,比如一个for循环之前/后,或者是一个纯粹的虚地址。
7. BPatch_type
类型,可以是内建类型,也可以是自定义类型,对于自定义类型,必须是存在于目标进程内的类型。
8. BPatch_snippet
代表各种可被用来注入的代码片段,大致上一个BPatch_snippet是有以下各种snippet组合而成:
8.1 BPatch_variableExpr
用于构造变量
8.2 BPatch_arithExpr
算术表达式, +-*/等
8.3 BPatch_boolExpr
布尔表达式, ><>=, and, or等
8.4 BPatch_gotoExpr
goto表达式,用来控制流程,可以用于构造循环代码,对于复杂的代码,最好先写好函数,否则你会被弄晕的 :-)
DynInst的实现原理
在上图中, 我们准备控制的进程是Application, 我们编写的patch程序是Mutator。在底层实现上Mutator依赖ptrace, /proc这样的系统级API用于实现进程的信息获取和控制权转移, 同时对于不同的系统,DynInst会把Mutator中编写的代码动态编译为相应格式的机器码,然后写入正在运行的目标进程所在的内存空间。对DynInst实现的细节感兴趣的朋友可以读一读这些Paper。
有了DynInst可以做什么?
DynInst把最难的部分解决了,一下子给出了巨大的想象空间,似乎C、C++这样的静态语言面对的所有障碍都消失了,剩下的事情就是充分发挥你的想像力了,下面是一坨坨已经用DynInst实现了的功能 (software/paper):
和DynInst类似的项目
微软研究院有一个Detours 的项目,在Windows上实现了类似DynInst的功能,不过主要用于拦截WINDOWS API,实现类似Hooks的功能,同时能够直接永久修改DLL,EXE对应的磁盘文件,对商业使用收费。
DynInst的现状
这里 和这里 有很多2009年DynInst年会的presentations,感兴趣的可以进一步了解。 |
评分
-
查看全部评分
|