- 论坛徽章:
- 0
|
Apache Hook 结构分析(一)
杨学刚
2009-3-18
xuegangyang@eyou.com
一、简介 1
二、Hook array结构分析 2
三、Hook 函数分析 3
3.1 hook函数实现 3
3.2 Run函数实现 4
3.2.1 FIRST处理流程对应的run函数定义 4
3.2.2 ALL处理流程对应的run函数定义 5
3.2.3 VOID处理流程对应的run函数定义 5
3.3 get_hook函数实现 6
四、HOOK宏分析 6
4.1 HOOK_STRUCT宏分析 6
4.2 EXTERNAL_HOOK宏分析 7
4.3 VOID宏分析 7
4.4 RUN_ALL宏分析 8
4.5 RUN_FIRST宏分析 9
4.6 使用宏定义HOOK 10
五、HOOK定义扩展 10
六、小结和思考 11
一、简介
为了让第三方开发的Module可以扩展服务器的默认处理,Apache使用了Hook机制。本文在源码基础上对Hook实现原理和结构进行分析,结合http://blog.csdn.net/ConeZXY/archive/2007/11/22/1898000.aspx和http://www.loveopensource.com/?p=18两篇文章将会更容易理解Hook机制实现原理。
Apache启动和运行被分成许多阶段,如果某些阶段允许第三方开发的module进行扩展,在此阶段便会实现一些Hook,通过该Hook便可以挂载用户自定义的Module。计算机程序是数据结构和算法的结合体,Hook核心数据结构是一个array,每一个array项存储一个要执行的函数指针,而hook算法由三个函数来实现:hook, run和hook_get(注:这三个名称是对hook函数的简称,在下文将看到,不同hook对应的函数名不相同)。假设我们要实现一个名为example的hook,以下内容将以example为例来探索hook原理。
二、Hook array结构分析
每一个Hook需要一个array存储要执行的函数指针,apache中,每个hook都会定义一个变量:_hooks,以example hook为例,以下是该变量的定义:
static struct {apr_array_header_t * link_example} _hooks; |
其中apr_array_header_t 是apache的apr中定义的一个array管理结构,其定义如下:
struct apr_array_header_t {
/** The pool the array is allocated out of */
apr_pool_t *pool;
/** The amount of memory allocated for each element of the array */
int elt_size;
/** The number of active elements in the array */
int nelts;
/** The number of elements allocated in the array */
int nalloc;
/** The elements in the array */
char *elts;
}; |
apr_array_header_t 结构实现array结构并对其进行管理,array的每一项称为element,其中pool记录该array是由哪个pool分配的空间,elt_size记录每一个element的size,nelts表示array中有效的element数,nalloc表示系统已经给该array分配的element数,elts指针指向array的第一个element地址。Apache采用比较灵活的方法管理array,在我们向array中添加一个element时,它会判断array中是否有空闲的位置(即array中已经分配的element数大于有效的element数,nalloc>nelts),如果没有空闲位置(即nelts == nalloc),则array会重新申请大小为:2 * nalloc的空间。
Hook array的每个element如何存储函数指针,这涉及到hook定义的另外一个LINK数据结构,同样假设我们要定义的Hook名字为:example,则对应的LINK结构定义如下:
typedef struct ap_LINK_example_t
{
ap_HOOK_example * pFunc;
const char* const aszPredecessor;
const char* const aszSuccessors;
int nOrder;
}ap_LINK_example; |
定义中的pFunc就是要执行的函数指针,Hook array中每一个element都是ap_LINK_example结构,apr_array_header_t 结构中的elt_size将会等于sizeof(ap_LINK_example),而elts指向第一个ap_LINK_example地址。
通过以上对Hook array的分析,我们便可以画出Hook array的结构图:
三、Hook 函数分析
每一个Hook的实现对应三个函数:hook,run和get_hook,实现一个名字为example的hook,对应的函数分别为:ap_hook_example,ap_run_example和 ap_hook_get_example,其中hook函数用于向hook array队列中添加element,run函数则顺序读取hook array的每一个element,并调用element中指向的函数(即调用ap_LINK_example_t结构中pFunc指向的函数),hook_get函数仅仅简单返回_hooks变量中对应的link_example指针。
3.1 hook函数实现
ap_hook_example函数用于向Hook array中添加一个element,其定义如下:
AP_DECLARE(void) ap_hook_example(ap_HOOK_example_t *pf,const char * const *aszPre, const char * const *aszSucc,int nOrder)
{
ap_LINK_example_t *pHook;
if(!_hooks.link_example)
{
_hooks.link_example=apr_array_make(apr_hook_global_pool,1,sizeof(ap_LINK_example_t)); \
apr_hook_sort_register(example,&_hooks.link_example);
}
pHook=apr_array_push(_hooks.link_example);
pHook->pFunc=pf;
pHook->aszPredecessors=aszPre;
pHook->aszSuccessors=aszSucc;
pHook->nOrder=nOrder;
pHook->szName=apr_hook_debug_current;
if(apr_hook_debug_enabled)
apr_hook_debug_show(#name,aszPre,aszSucc);
} |
该函数中通过apr_array_push把element添加到了array末尾,apache允许载入用户自己开发的Module,在用户自己开发的模块中,存在一个register_hooks(参见:http://www.loveopensource.com/?p=18)函数,该函数将调用一个HOOK的hook函数把用户自己编写的函数注入到系统处理流程中,该例子中的ap_hook_handler(c_handler, NULL, NULL, APR_HOOK_MIDDLE)函数表示把用户自己开发的函数c_handler注入到handler这个HOOK中,当handler对应的run函数被调用时,将会调用c_handler函数。
3.2 Run函数实现
Apache中每个Hook会对应FIRST、ALL和VOID三种处理流程中的一个,这三种处理流程都会顺序访问Hook array的每一项,并调用element中pFunc指向的函数,区别仅在于它们的返回条件不一样。顾名思义,在FIRST流程顺序访问array中,只要碰到一个pFunc指向的函数执行完成(即返回结果不等于DECLINED),FIRST流程将会结束处理并return;ALL流程将会和FIRST一样顺序访问array,但只有碰到pFunc指向的函数执行异常(即返回结果既不等于DECLINED,也不等于OK)才返回,如果没碰到异常将会调用所有element对应的函数;而VOID将会忽略pFunc指向的函数执行情况,不论其返回何值,都将把array中所有element都处理一遍。每一个Hook都对应一个run函数,run函数的实现将会对应FIRST,ALL或VOID中的一个,以下以example HOOK为例,演示FIRST,ALL和VOID三种处理流程下run函数的实现。
3.2.1 FIRST处理流程对应的run函数定义
FIRST流程顺序访问array的每一个element,只要碰到一个element对应的的函数执行完成(即返回结果不等于DECLINED),FIRST流程就结束处理并return,Example HOOK的FIRST处理流程run函数定义如下:
AP_DECLARE(ret) ap_run_example (apr_pool_t *pconf, apr_pool_t *plog,apr_pool_t *ptemp)
{
ap_LINK_example_t *pHook;
int n;
ret rv;
if(!_hooks.link_example)
return decline;
pHook=(ap_LINK_example_t *)_hooks.link_example->elts;
for(n=0 ; n < _hooks.link_example->nelts ; ++n)
{
rv=pHook[n].pFunc (apr_pool_t *pconf, apr_pool_t *plog,apr_pool_t *ptemp);
if(rv != decline)
return rv;
}
return decline;
}
|
从以上代码中可以看到,当rv 不等于 decline时候,将会从该函数中return,不再继续处理。
3.2.2 ALL处理流程对应的run函数定义
ALL流程顺序访问array的每一个element,如果不碰到element对应的函数执行异常(即返回结果既不等于DECLINED也不等与OK),ALL流程将会调用所有element对应的函数,Example HOOK的ALL 处理流程run函数定义如下:
ap_DECLARE(ret) ap_run_example (request_rec *r)
{
ap_LINK_example_t *pHook;
int n;
ret rv;
if(!_hooks.link_example)
return ok;
pHook=(ap_LINK_example_t *)_hooks.link_example->elts;
for(n=0 ; n < _hooks.link_example->nelts ; ++n)
{
rv=pHook[n].pFunc (request_rec *r);
if(rv != ok && rv != decline)
return rv;
}
return ok;
} |
在该函数中可以看到,只有当(rv != ok && rv != decline)时才会从该函数中return,否则将会把所有的array都顺序处理一遍。
3.2.3 VOID处理流程对应的run函数定义
VOID将会忽略pFunc指向的函数执行情况,不论其返回何值,都将把array中所有element都处理一遍。Example HOOK的VOID 处理流程run函数定义如下:
AP_DECLARE(void) ap_run_example (apr_pool_t *pchild, server_rec *s)
{
ap_LINK_example_t *pHook;
int n;
if(!_hooks.link_example)
return;
pHook=(ap_LINK_example_t *)_hooks.link_example->elts;
for(n=0 ; n < _hooks.link_example->nelts ; ++n)
pHook[n].pFunc (apr_pool_t *pchild, server_rec *s);
} |
在该函数中可以看到,不论函数执行如何,将会顺序处理一遍hook array中的所有element。
以上的ALL、FIRST和ALL对应的函数名字相同,因此一个HOOK可以从FIRST,ALL或VOID中选择一种处理方式,定义一个HOOK的run 函数。
3.3 get_hook函数实现
get_hook 函数仅简单返回hook array的apr_array_header_t 指针,example HOOK对应的get_hook函数实现如下:
AP_DECLARE(apr_array_header_t *) ap_hook_get_example(void)
{
return _hooks.link_example;
} |
[ 本帖最后由 ftomorrow 于 2009-3-18 17:15 编辑 ] |
|