《深入剖析Nginx》——2.4 获得Nginx程序完整执行流程

    xiaoxiao2023-07-21  135

    本节书摘来自异步社区《深入剖析Nginx》一书中的第2章,第2.4节,作者: 高群凯 更多章节内容可以访问云栖社区“异步社区”公众号查看。

    2.4 获得Nginx程序完整执行流程

    利用strace命令能帮助我们获取到Nginx在运行过程中所发起的所有系统调用,但是不能看到Nginx内部各个函数的调用情况。利用gdb调试Nginx能让我们很清晰地获得Nginx每一步的执行流程,但是单步调试毕竟是非常麻烦的,有没有更为方便的方法一次性获得Nginx程序执行的整个流程呢?答案是肯定的,而且方法还非常多1。虽然相比直接使用某些强大工具(如System Tap2)而言,下面要介绍的方法比较笨,但它的确可行,而且从这个过程中也许能学到一些额外的知识。我们只需利用gcc的一个名为-finstrument- functions3的编译选项,再加上一些我们自己的处理,就可以达到既定目的。关于gcc提供的这个-finstrument-functions选项,这里不做过多介绍,我们只需明白它提供的是一种函数调用记录追踪功能。关于这些,感兴趣的读者请直接参考gcc官网手册,下面来看获得Nginx程序完整执行流程的具体操作。

    首先,我们准备两个文件,文件名和文件内容分别如下。

    00: 代码片段2.4-1,文件名: my_debug.h 01: #ifndef MY_DEBUG_LENKY_H 02: #define MY_DEBUG_LENKY_H 03: #include <stdio.h> 04: 05: void enable_my_debug( void ) __attribute__((no_instrument_function)); 06: void disable_my_debug( void ) __attribute__((no_instrument_function)); 07: int get_my_debug_flag( void ) __attribute__((no_instrument_function)); 08: void set_my_debug_flag( int ) __attribute__((no_instrument_function)); 09: void main_constructor( void ) __attribute__((no_instrument_function, constructor)); 10: void main_destructor( void ) __attribute__((no_instrument_function, destructor)); 11: void __cyg_profile_func_enter( void *,void *) __attribute__((no_instrument_ function)); 12: void __cyg_profile_func_exit( void *, void *) __attribute__((no_instrument_ function)); 13: 14: #ifndef MY_DEBUG_MAIN 15: extern FILE *my_debug_fd; 16: #else 17: FILE *my_debug_fd; 18: #endif 19: #endif 00: 代码片段2.4-2,文件名: my_debug.c 01: #include "my_debug.h" 02: #define MY_DEBUG_FILE_PATH "/usr/local/nginx/sbin/mydebug.log" 03: int _flag = 0; 04: 05: #define open_my_debug_file() \ 06: (my_debug_fd = fopen(MY_DEBUG_FILE_PATH, "a")) 07: 08: #define close_my_debug_file() \ 09: do { \ 10: if (NULL != my_debug_fd) { \ 11: fclose(my_debug_fd); \ 12: } \ 13: }while(0) 14: 15: #define my_debug_print(args, fmt...) \ 16: do{ \ 17: if (0 == _flag) { \ 18: break; \ 19: } \ 20: if (NULL == my_debug_fd && NULL == open_my_debug_file()) { \ 21: printf("Err: Can not open output file.\n"); \ 22: break; \ 23: } \ 24: fprintf(my_debug_fd, args, ##fmt); \ 25: fflush(my_debug_fd); \ 26: }while(0) 27: 28: void enable_my_debug( void ) 29: { 30: _flag = 1; 31: } 32: void disable_my_debug( void ) 33: { 34: _flag = 0; 35: } 36: int get_my_debug_flag( void ) 37: { 38: return _flag; 39: } 40: void set_my_debug_flag( int flag ) 41: { 42: _flag = flag; 43: } 44: void main_constructor( void ) 45: { 46: //Do Nothing 47: } 48: void main_destructor( void ) 49: { 50: close_my_debug_file(); 51: } 52: void __cyg_profile_func_enter( void *this, void *call ) 53: { 54: my_debug_print("Enter\n%p\n%p\n", call, this); 55: } 56: void __cyg_profile_func_exit( void *this, void *call ) 57: { 58: my_debug_print("Exit\n%p\n%p\n", call, this); 59: }

    这两个文件是我2009年写的,比较乱,不过够用且测试无误,所以我这里也就直接先用它。将这两个文件放到/nginx-1.2.0/src/core/目录下,然后编辑/nginx-1.2.0/objs/Makefile文件,给CFLAGS选项增加-finstrument-functions选项。

    02: 代码片段2.4-3,文件名: Makefile 03: CFLAGS = -pipe -O0 -W -Wall -Wpointer-arith -Wno-unused-parameter -Wunused- function -Wunused-va riable -Wunused-value -Werror -g -finstrument-functions

    接着,需要将my_debug.h和my_debug.c引入到Nginx源码里一起编译,所以继续修改/nginx- 1.2.0/objs/Makefile文件,根据Nginx的Makefile文件特点,修改的地方主要有如下几处。

    00: 代码片段2.4-4,文件名: Makefile 01: … 18: CORE_DEPS = src/core/nginx.h \ 19: src/core/my_debug.h \ 20: … 84: HTTP_DEPS = src/http/ngx_http.h \ 85: src/core/my_debug.h \ 86: … 102: objs/nginx: objs/src/core/nginx.o \ 103: objs/src/core/my_debug.o \ 104: … 211: $(LINK) -o objs/nginx \ 212: objs/src/core/my_debug.o \ 213: … 322: objs/src/core/my_debug.o: $(CORE_DEPS) \ 323: src/core/my_debug.c 324: $(CC) -c $(CFLAGS) $(CORE_INCS) \ 325: -o objs/src/core/my_debug.o \ 326: src/core/my_debug.c 327: …

    为了在 Nginx 源码里引入 my_debug,这需要在 Nginx 所有源文件都包含有头文件 my_ debug.h,当然没必要每个源文件都去添加对这个头文件的引入,我们只需要在头文件ngx_core.h内加入对my_debug.h文件的引入即可,这样其他Nginx的源文件就间接地引入了这个文件。

    37: 代码片段2.4-5,文件名: ngx_core.h 38: #include "my_debug.h" 在``` 源文件nginx.c的最前面加上对宏MY_DEBUG_MAIN的定义,以使得Nginx程序有且仅有一个my_debug_fd变量的定义。

    06: 代码片段2.4-6,文件名: nginx.c07: #define MY_DEBUG_MAIN 108: 09: #include 10: #include 11: #include

    最后就是根据我们想要截取的执行流程,在适当的位置调用函数enable_my_debug();和函数disable_my_debug();,这里仅作测试,直接在main函数入口处调用enable_my_debug();,而disable_my_debug();函数就不调用了。

    200: 代码片段2.4-7,文件名: nginx.c201: main(int argc, char const argv)202: {203: …208: enable_my_debug();

    至此,代码增补工作已经完成,重新编译Nginx,如果之前已编译过Nginx,那么需记得先把Nginx源文件的时间戳进行刷新。 以单进程模式运行Nginx,并且在配置文件里将日志功能的记录级别设置低一点,否则将有大量的日志函数调用堆栈信息,经过这样的设置后,我们才能获得更清晰的Nginx执行流程,即配置文件里做如下设置。

    00: 代码片段2.4-8,文件名: nginx.c01: master_process off;02: error_log logs/error.log emerg;

    正常运行后的Nginx将产生一个记录程序执行流程的文件,这个文件会随着Nginx的持续运行迅速增大,所以在恰当的地方调用disable_my_debug();函数是非常有必要的,不过我这里在获取到一定量的信息后就直接kill掉Nginx进程了。mydebug.log的内容如下所示。

    [root@localhost sbin]# head -n 20 mydebug.log Enter0x804a5fc0x806e2b3Exit0x804a5fc0x806e2b3…这记录的是N` ginx执行函数调用关系,不过这里的函数还只是以对应的地址显示而已,利用另外一个工具 addr2line 可以将这些地址转换回可读的函数名。addr2line 工具在大多数Linux发行版上默认有安装,如果没有那么在官网4下载即可,其具体用法也可以参考官网手册5。这里我们直接使用,写个addr2line.sh脚本。

    00: 代码片段2.4-9,文件名: addr2line.sh 01: #!/bin/sh 02: 03: if [ $# != 3 ]; then 04: echo 'Usage: addr2line.sh executefile addressfile functionfile' 05: exit 06: fi; 07: 08: cat $2 | while read line 09: do 10: if [ "$line" = 'Enter' ]; then 11: read line1 12: read line2 13: # echo $line >> $3 14: addr2line -e $1 -f $line1 -s >> $3 15: echo "--->" >> $3 16: addr2line -e $1 -f $line2 -s | sed 's/^/ /' >> $3 17: echo >> $3 18: elif [ "$line" = 'Exit' ]; then 19: read line1 20: read line2 21: addr2line -e $1 -f $line2 -s | sed 's/^/ /' >> $3 22: echo "<---" >> $3 23: addr2line -e $1 -f $line1 -s >> $3 24: # echo $line >> $3 25: echo >> $3 26: fi; 27: done 执行addr2line.sh进行地址与函数名的转换,这个过程挺慢的,因为从上面的Shell脚本可以看到对于每一个函数地址都调用addr2line进行转换,执行效率完全没有考虑,不过够用就好,如果非要追求高效率,直接写个C程序来做这个转换工作当然也是可以的。 [root@localhost sbin]# vi addr2line.sh [root@localhost sbin]# chmod a+x addr2line.sh [root@localhost sbin]# ./addr2line.sh nginx mydebug.log myfun.log [root@localhost sbin]# head -n 12 myfun.log main nginx.c:212 ---> ngx_strerror_init ngx_errno.c:47 ngx_strerror_init ngx_errno.c:47 <--- main nginx.c:212 …

    关于如何获得Nginx程序执行流程的方法大体就是上面描述的这样,不过这里介绍得很粗略,写的代码也仅只是作为示范使用,关于 gcc 以及相关工具的更深入研究已不在本书的讨论范围之内,如感兴趣可查看上文中提供的相关链接。

    相关资源:敏捷开发V1.0.pptx
    最新回复(0)