现在的位置: 首页 > 综合 > 正文

Apache Hook 结构分析

2013年01月30日 ⁄ 综合 ⁄ 共 11380字 ⁄ 字号 评论关闭

一、简介... 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.aspxhttp://www.loveopensource.com/?p=18两篇文章将会更容易理解Hook机制实现原理。

Apache启动和运行被分成许多阶段,如果某些阶段允许第三方开发的module进行扩展,在此阶段便会实现一些Hook,通过该Hook便可以挂载用户自定义的Module。计算机程序是数据结构和算法的结合体,Hook核心数据结构是一个array,每一个array项存储一个要执行的函数指针,而hook算法由三个函数来实现:hook, runhook_get(注:这三个名称是对hook函数的简称,在下文将看到,不同hook对应的函数名不相同)。假设我们要实现一个名为examplehook,以下内容将以example为例来探索hook原理。

二、Hook array结构分析

每一个Hook需要一个array存储要执行的函数指针,apache中,每个hook都会定义一个变量:_hooks,以example hook为例,以下是该变量的定义:


其中apr_array_header_t  apacheapr中定义的一个array管理结构,其定义如下:

apr_array_header_t 结构实现array结构并对其进行管理,array的每一项称为element,其中pool记录该array是由哪个pool分配的空间,elt_size记录每一个elementsizenelts表示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结构定义如下:

t

定义中的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 array 结构

<!--[if !vml]--><!--[endif]-->

三、Hook 函数分析

每一个Hook的实现对应三函数:hookrunget_hook,实现一个名字为examplehook,对应的函数分别为:ap_hook_example,ap_run_exampleap_hook_get_example,其中hook函数用于向hook array队列中添加elementrun函数则顺序读取hook array的每一个element,并调用element中指向的函数(即调用ap_LINK_example_t结构中pFunc指向的函数),hook_get函数仅仅简单返回_hooks变量中对应的link_example指针。

3.1 hook函数实现

Ap_hook_example函数用于向Hook array中添加一个element,其定义如下:

该函数中通过apr_array_pushelement添加到了array末尾,apache允许载入用户自己开发的Module,在用户自己开发的模块中,存在一个register_hooks(参见:http://www.loveopensource.com/?p=18)函数,该函数将调用一个HOOKhook函数把用户自己编写的函数注入到系统处理流程中,该例子中的ap_hook_handler(c_handler, NULL, NULL, APR_HOOK_MIDDLE)函数表示把用户自己开发的函数c_handler注入到handler这个HOOK中,当handler对应的run函数被调用时,将会调用c_handler函数。

3.2 Run函数实现

    Apache中每个Hook会对应FIRSTALLVOID三种处理流程中的一个,这三种处理流程都会顺序访问Hook array的每一项,并调用elementpFunc指向的函数,区别仅在于它们的返回条件不一样。顾名思义,在FIRST流程顺序访问array中,只要碰到一个pFunc指向的函数执行完成(即返回结果不等于DECLINED),FIRST流程将会结束处理并returnALL流程将会和FIRST一样顺序访问array,但只有碰到pFunc指向的函数执行异常(即返回结果既不等于DECLINED,也不等于OK)才返回,如果没碰到异常将会调用所有element对应的函数;而VOID将会忽略pFunc指向的函数执行情况,不论其返回何值,都将把array中所有element都处理一遍。每一个Hook都对应一个run函数,run函数的实现将会对应FIRST,ALLVOID中的一个,以下以example HOOK为例,演示FIRST,ALLVOID三种处理流程下run函数的实现。

3.2.1 FIRST处理流程对应的run函数定义

    FIRST流程顺序访问array的每一个element,只要碰到一个element对应的的函数执行完成(即返回结果不等于DECLINED),FIRST流程就结束处理并returnExample HOOKFIRST处理流程run函数定义如下:

从以上代码中可以看到,当rv 不等于 decline时候,将会从该函数中return,不再继续处理。

3.2.2 ALL处理流程对应的run函数定义

ALL流程顺序访问array的每一个element,如果不碰到element对应的函数执行异常(即返回结果既不等于DECLINED也不等与OK),ALL流程将会调用所有element对应的函数,Example HOOKALL 处理流程run函数定义如下:

在该函数中可以看到,只有当(rv != ok && rv != decline)时才会从该函数中return,否则将会把所有的array都顺序处理一遍。

3.2.3 VOID处理流程对应的run函数定义

VOID将会忽略pFunc指向的函数执行情况,不论其返回何值,都将把array中所有element都处理一遍。Example HOOKVOID 处理流程run函数定义如下:

在该函数中可以看到,不论函数执行如何,将会顺序处理一遍hook array中的所有element

以上的ALLFIRSTALL对应的函数名字相同,因此一个HOOK可以从FIRSTALLVOID中选择一种处理方式,定义一个HOOKrun 函数。

3.3 get_hook函数实现

get_hook 函数仅简单返回hook arrayapr_array_header_t 指针,example HOOK对应的get_hook函数实现如下:


四、HOOK宏分析

    通过以上分析可以看出apacheHOOK实现需要编写一个Hook array和三个函数,为了简化实现hook的方法和提高代码开发效率,Apache定义了五个HOOK宏来实现HOOKAPR_HOOK_STRUCTAPR_DECLARE_EXTERNAL_HOOKAPR_IMPLEMENT_EXTERNAL_HOOK_VOIDAPR_IMPLEMENT_EXTERNAL_HOOK_RUN_ALLAPR_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST,为描述方便,以上五个宏按照其名字分别简称为:HOOK_STRUCTEXTERNAL_HOOKVOIDRUN_ALLRUN_FIRST

4.1 HOOK_STRUCT宏分析

    APR_HOOK_STRUCT主要用于定义hook对应的_hooks变量,该宏的代码为:

但在apache源码中,该宏通常以以下形式使用:

    以上使用涉及到APR_HOOK_LINK宏,其定义非常简单:

结合APR_HOOK_LINKHOOK_STRUCT宏,便可以定义_hooks 变量。

4.2 EXTERNAL_HOOK宏分析

       EXTERNAL_HOOK宏主要用于声明Hook对应的hookrunget_hook函数(注意:仅仅是声明而不是实现),声明了hook array中对应的element类型,其源码如下

 

从以上源码中可以看到涉及APR_IMPLEMENT_HOOK_GET_PROTO宏,其功能是用于声明HOOKget_hook函数,其源码如下:

4.3 VOID宏分析

       VOID 宏主要用于实现处理流程为VOID时的run函数,其源码如下:

源码中用到了APR_IMPLEMENT_EXTERNAL_HOOK_BASE宏,该宏用于实现HOOK对应的hookget_hook函数,其源码如下:

4.4 RUN_ALL宏分析

RUN_ALL宏用于实现处理流程为ALL时的run函数,其源码如下:

      以上用到的APR_IMPLEMENT_EXTERNAL_HOOK_BASE宏和在VOID中用到一样功能。

4.5 RUN_FIRST宏分析

RUN_ALL宏用于实现处理流程为FIRST时的run函数,其源码如下:

以上用到的APR_IMPLEMENT_EXTERNAL_HOOK_BASE宏和在VOID中用到一样功能。

4.6 使用宏定义HOOK

       经过以上分析的以后,如果要实现HOOK将会变得比较简单,只需按照顺序依次调用三个宏即可完成HOOK的定义,与上文相同,假设要实现example HOOK,其定义源码如下:

五、HOOK定义扩展

       在以上分析中说过每一个HOOK都对应一个_hooks变量,如果在同一个编译单元里我们两次使用HOOK_STRUCT宏定义example1example2 HOOK,岂不是会存在两个_hooks变量,这将导致编译错误,如果在同一编译单元中需要定义两个HOOK,就需要用到HOOK的扩展定义方式。假如要定义example1example2 HOOK,可以按照以下方式进行定义:

以上宏定义展开后的源码如下:

如此就在一个_hooks变量中定义了两个HOOK array,其中一个用于example1 HOOK,另外一个用于example2,之后需要使用两次APR_DECLARE_EXTERNAL_HOOK宏来分别定义example1examplehook函数和element类型,同样也需要使用两次APR_IMPLEMENT_EXTERNAL_HOOK_RUN_ALL宏来定义run函数,其源码如下:

至此便解决了同一编译单元定义多个HOOK的问题,笔者称此方法为HOOK扩展(也许不恰当,但没有想到更好方法)。虽然如此,疑惑也随之而来,这样定义的HOOK是一个HOOK还是两个HOOK?我想暂且称之为HOOK组吧,虽然他们由同一个HOOK_STRUCT定义,并且通过同一个_hooks 变量管理。

六、小结和思考

       Apache HOOK目的是为了能让用户开发的模块注入到apache处理流程中,其核心在于函数指针的应用,笔者也接触过C++中的多态开发,通过使用多态可以避免函数指针的使用,而且可以使系统结构更加简单,但我想可能是C++多态开销太高,开发人员没有采用吧。

以前只学过C++,没有太多接触过C 语言,虽然二者相通,可在看源代码中发现,二者对程序的组织以及语言的应用完全不一样,apache中的HOOK 宏就耗去了我一个周的时间,而且在VC中宏是没有办法debug调试执行的,只好手工把宏进行替换,之后用自己替换后的函数进行debug才弄懂程序执行流程,不经感慨要是有一个宏替换的小软件该多好啊!

我研究生导师有一句诗:“才别夕阳又见朝阳”,感觉刚刚开了电脑写文档,倏忽已是日别西山,手中方才调整好本文格式,不经感慨古人写书之艰辛与执着,批阅十载,增删五次,方成红楼,而我辈电脑词霸一应俱全,google搜索时刻相随,却提笔踌躇,不知所书。虽如此,我辈阅大牛之源码,亦应有所得,有所得便应有所录,有所录方能河海不择细流故能就其深。

 

抱歉!评论已关闭.