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

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变量的定义。

```javascript
06: 代码片段2.4-6,文件名: nginx.c
07: #define MY_DEBUG_MAIN 1
08:
09: #include <ngx_config.h>
10: #include <ngx_core.h>
11: #include <nginx.h>

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

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

至此,代码增补工作已经完成,重新编译Nginx,如果之前已编译过Nginx,那么需记得先把Nginx源文件的时间戳进行刷新。

以单进程模式运行Nginx,并且在配置文件里将日志功能的记录级别设置低一点,否则将有大量的日志函数调用堆栈信息,经过这样的设置后,我们才能获得更清晰的Nginx执行流程,即配置文件里做如下设置。

00: 代码片段2.4-8,文件名: nginx.c
01: 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
Enter
0x804a5fc
0x806e2b3
Exit
0x804a5fc
0x806e2b3
…
这记录的是
N```
ginx执行函数调用关系,不过这里的函数还只是以对应的地址显示而已,利用另外一个工具 addr2line 可以将这些地址转换回可读的函数名。addr2line 工具在大多数Linux发行版上默认有安装,如果没有那么在官网4下载即可,其具体用法也可以参考官网手册5。这里我们直接使用,写个addr2line.sh脚本。

```javascript
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 以及相关工具的更深入研究已不在本书的讨论范围之内,如感兴趣可查看上文中提供的相关链接。

时间: 2024-08-04 01:54:46

《深入剖析Nginx》——2.4 获得Nginx程序完整执行流程的相关文章

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

2.4 获得Nginx程序完整执行流程 深入剖析Nginx 利用strace命令能帮助我们获取到Nginx在运行过程中所发起的所有系统调用,但是不能看到Nginx内部各个函数的调用情况.利用gdb调试Nginx能让我们很清晰地获得Nginx每一步的执行流程,但是单步调试毕竟是非常麻烦的,有没有更为方便的方法一次性获得Nginx程序执行的整个流程呢?答案是肯定的,而且方法还非常多1.虽然相比直接使用某些强大工具(如System Tap2)而言,下面要介绍的方法比较笨,但它的确可行,而且从这个过程中

vc++深入跟踪MFC程序的执行流程

在MFC程序设计的学习过程中最令人感到难受,甚至于有时会动摇学习者信心的就是一种对于程序的一切细节都没有控制权的感觉.这种感觉来源于学习者不知道一个MFC程序是如何运行起来的(即一个MFC程序的执行流程)和MFC程序的设计思想和机制,即使是写过Windows程序的学习者,也会感到非常迷惘并且无从下手.而这种感觉的出现会使大家认为自己离开了书本上的例子就无法设计编制程序.下面我就来说一说一个MFC具体是如何被执行的.在阅读本文之前,你要有一定的Windows程序设计基础,知道Windows程序的运

java程序的执行流程?

问题描述 具体的从代码写完到工具选择,到运行结果?exe格式?还是什么? 解决方案 解决方案二:写完代码生成.java文件编译得class文件,此时可运行也可将文件打包成jar包运行解决方案三:该回复于2009-04-13 08:34:35被版主删除解决方案四:引用1楼frankwoods123的回复: 写完代码生成.java文件编译得class文件,此时可运行也可将文件打包成jar包运行 补充以下,java也可以做应用程序的,就是生成exe文件,呵呵解决方案五:当然这里生成exe其实也是一种打

《深入剖析Nginx》一1.6 编译与执行

1.6 编译与执行 深入剖析Nginx Nginx的编译安装很简单,使用Linux下通用的三板斧即可:./configure.make.make install.当然,这样做的话,那么一切都是使用的默认配置,如果要做修改,则必须在执行configure时指定,比如对Nginx加上调试功能. [root@localhost nginx-1.2.0]# ./configure --with-debug 修改默认安装路径. [root@localhost nginx-1.2.0]# ./configu

Linux+Nginx+MySQL下配置论坛程序Discuz的基本教程_php实例

Crossday Discuz! Board(简称 Discuz!)是北京康盛新创科技有限责任公司推出的一套通用的社区论坛软件系统.自2001年6月面世以来,Discuz!已拥有14年以上的应用历史和200多万网站用户案例,是全球成熟度最高.覆盖率最大的论坛软件系统之一.目前最新版本Discuz! X3.2正式版于2015年6月9日发布,首次引入应用中心的开发模式.2010年8月23日,康盛创想与腾讯达成收购协议,成为腾讯的全资子公司. Crossday Discuz! Board(以下简称 D

Nginx学习笔记六Nginx的模块开发

1.Nginx配置文件主要组成:main(全局配置)这部分的指令将影响其他所有部分.server(虚拟主机配置)这部分指令主要用于指定虚拟主机域名,IP和端口.upstream(主要为反向代理,负载均衡相关配置)这部分指令用于设置反向代理及后端服务 器的负载均衡.location(目录匹配配置)这部分指令用于匹配网页位置(例如,根目录"/","/images",等 等). location部分会继承server部分的指令,而server部分会继承main部分的指令.

PHP连接Nginx服务器并解析Nginx日志的方法_php技巧

php与nginx整合 PHP-FPM也是一个第三方的FastCGI进程管理器,它是作为PHP的一个补丁来开发的,在安装的时候也需要和PHP源码一起编译,也就是说PHP-FPM被编译到PHP内核中,因此在处理性能方面更加优秀:同时它在处理高并发方面也比spawn-fcgi引擎好很多,因此,推荐Nginx+PHP/PHP-FPM这个组合对PHP进行解析. FastCGI 的主要优点是把动态语言和HTTP Server分离开来,所以Nginx与PHP/PHP-FPM经常被部署在不同的服务器上,以分担

tomcat与nginx的整合&amp;amp;nginx 配置https

一.安装Tomcat和jdk   1.安装jdk # tar xvf jdk1.6.0_11.tar # mv jdk1.6.0_11 /usr/local/ 配置环境变量 # vim /etc/profile 添加 JAVA_HOME=/usr/local/jdk1.6.0_11 export JAVA_HOME CLASSPATH=.:$JAVA_HOME/lib/tools.jar:$JAVA_HOME/lib/dt.jar export CLASSPATH PATH=$JAVA_HOME

lvs集群-lvs +nginx集群、nginx自身集群和 lvs自身集群,三种集群的优缺点

问题描述 lvs +nginx集群.nginx自身集群和 lvs自身集群,三种集群的优缺点 lvs +nginx集群.nginx+keepalive集群.lvs+keepalive集群 三者之间有什么优缺点?尤其nginx+keepalive自身可以实现集群,为什么还有人采用lvs +nginx集群呢,多加一个lvs呢?