《深入剖析Nginx》一2.6 特殊应用逻辑的调试

2.6 特殊应用逻辑的调试

深入剖析Nginx
前面所讲的调试方法都是针对Nginx本身很容易跑到的逻辑,而对于某些只有在特定情况下才会被执行到的代码,又该怎样去调试呢?举个例子,我们知道Nginx里有大量的超时处理,比如,如果读取客户端请求头部数据超时,Nginx就将执行对应的超时处理函数,假设我想通过单步执行的方式来了解这部分相关逻辑,无疑就得让Nginx的执行逻辑走到这条路径上来。由于此时影响Nginx行为的决定因素是客户端所发送的请求头部数据,我们就必须在客户端做动作来构造出这种场景。一般的浏览器,如IE、Firefox等发出请求的行为基本已经固定,而常用的命令行工具,比如curl、wget的源代码又略显复杂,定制它们的请求动作和改变环境来构造所需的场景相对较为麻烦,所以一种更便利的方法就是我们自己写个socket通信的客户端即可,而这并不需要多少代码。

下面给出一个测试示例用代码,为了简单,所以服务器IP和端口都是固定在代码里的,用于发送数据的函数write()调用也未做返回值判断等(后续还有其他类似测试代码也是如此,这点请注意)。

00: 代码片段2.6-1,文件名: request_timeout.c
01: /**
02:  * gcc -Wall -g -o request_timeout request_timeout.c
03:  */
04: #include <sys/types.h>
05: #include <stdio.h>
06: #include <stdlib.h>
07: #include <string.h>
08: #include <errno.h>
09: #include <sys/socket.h>
10: #include <netinet/in.h>
11: #include <arpa/inet.h>
12: #include <unistd.h>
13:
14: //char req_header[] = "GET / HTTP/1.1\r\nUser-Agent: curl/7.19.7\r\nHost: 127.0.0.1\ r\nAccept: */*\r\n\r\n";
15: char req_header[] = "GET / HTTP/1.1\r\nUser-Agent: curl/7.19.7\r\n";
16:
17: int main(int argc, char *const *argv)
18: {
19:       int sockfd;
20:       struct sockaddr_in server_addr;
21:
22:       if ((sockfd = socket (AF_INET, SOCK_STREAM, 0)) == -1) {
23:              fprintf (stderr, "Socket error,%s\r\n", strerror (errno));
24:              return -1;
25:       }
26:
27:       bzero (&server_addr, sizeof (server_addr));
28:       server_addr.sin_family = AF_INET;
29:       server_addr.sin_port = htons (80);
30:
31:       if(!inet_aton("192.168.1.1", &server_addr.sin_addr)) {
32:              fprintf (stderr, "Bad address:%s\r\n", strerror (errno));
33:              close (sockfd);
34:              return -1;
35:       }
36:
37:       if (connect (sockfd, (struct sockaddr *) (&server_addr),
38:              sizeof (struct sockaddr)) == -1) {
39:              fprintf (stderr, "Connect Error:%s\r\n", strerror (errno));
40:              close (sockfd);
41:              return -1;
42:       }
43:
44:       write (sockfd, req_header, strlen(req_header));
45:
46:       close (sockfd);
47:       return 0;
48: }

该程序的代码比较简单,变量req_header存储的是http请求头部数据,被注释掉的是正常的请求头,而我这里使用的请求头是不完整的(正常请求头可以用wget、curl或wireshark1等工具获得,异常请求头必须根据自己所预期场景来进行构造,比如在这里,其他异常情况的请求头可能导致Nginx以其他错误方式返回而不是进行超时监控),所以这会使得Nginx在接收到该请求后,持续等待进一步的头部数据,直到超时。编译这个源代码得到应用程序request_timeout。

将接受http请求的Nginx工作进程绑定到gdb,然后在超时函数ngx_event_expire_timers()内的第149行下断点并按c继续。

75: 代码片段2.6-2,文件名: ngx_event_timer.c
76: void
77: ngx_event_expire_timers(void)
78: {
79: …
147:                ev->timedout = 1;
148:
149:                ev->handler(ev);

这个断点是Nginx已经捕获到超时事件,设置其超时旗标并调用对应的回调函数进行处理。在另一个gdb内执行request_timeout,当然,我们需要让它停止在第47行2,避免程序退出,导致它与Nginx工作进程之间的连接断开。等待约60秒(Nginx读取请求头部数据的默认超时时间为60秒,可通过配置指令client_header_timeout修改)后,attach到Nginx工作进程的gdb就会断下来,按s跟进函数,再顺着执行路径而下就会发现此时Nginx将执行到这个逻辑里。

955: 代码片段2.6-3,文件名: ngx_event_timer.c
956: static void
957: ngx_http_process_request_headers(ngx_event_t *rev)
958: {
959: …
976:      if (rev->timedout) {
977:             ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
978:             c->timedout = 1;
979:             ngx_http_close_request(r, NGX_HTTP_REQUEST_TIME_OUT);
980:             return;
981:      }

将执行到第976行的if判断内部,即连接超时,我们看到对于在读取请求头部数据超时的情况下,Nginx工作进程最后所做的几步主要工作,即日志记录、关闭请求并返回。通过这样一个实例,我们也就了解了如何去调试这样的特殊应用逻辑,不仅仅只是针对客户端,对于后端应用服务器也能如此进行模拟构造。

上面演示的环境构造步骤,虽然比较简单且能真实模拟,但毕竟需要我们了解它的细节,也就是需知道触发这种情况的前提条件,如果前提条件比较多,那么模拟起来可能还是比较麻烦,其实,如果我们只是了解一下Nginx如果这样执行会怎么样,那么完全可以通过利用gdb的p命令或set命令修改对应条件变量的值来达到目的。比如在前面的例子里,在一般情况下,rev->timedout为0,即不超时而无法执行第977-980行代码,但我又想看一下执行这几条语句的情况会怎么样,那么就可以像下面这样做。

Breakpoint 1, ngx_http_process_request_headers (rev=0x94a6bfc) at src/http/ ngx_http_ request.c:976
976     if (rev->timedout) {
(gdb) p rev->timedout
$1 = 0
(gdb) p rev->timedout=1
$2 = 1
(gdb) n
977            ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
(gdb) set rev->timedout=0
(gdb) p rev->timedout
$3 = 0
(gdb)

通过执行“p rev->timedout=1”把变量rev->timedout的值改为1,这样就执行到第977行了,当然,如上所示,set命令也可以改变Nginx执行变量的值。值得特别注意的是,这样做仅仅只是因为改变了条件判断的变量值而使得Nginx程序执行路径发生变化,但是其在新的路径上,可能由于使用的某些变量值不是原本所期望的情况而导致执行异常。

时间: 2024-12-26 11:13:24

《深入剖析Nginx》一2.6 特殊应用逻辑的调试的相关文章

《深入剖析Nginx》——2.6 特殊应用逻辑的调试

2.6 特殊应用逻辑的调试 前面所讲的调试方法都是针对Nginx本身很容易跑到的逻辑,而对于某些只有在特定情况下才会被执行到的代码,又该怎样去调试呢?举个例子,我们知道Nginx里有大量的超时处理,比如,如果读取客户端请求头部数据超时,Nginx就将执行对应的超时处理函数,假设我想通过单步执行的方式来了解这部分相关逻辑,无疑就得让Nginx的执行逻辑走到这条路径上来.由于此时影响Nginx行为的决定因素是客户端所发送的请求头部数据,我们就必须在客户端做动作来构造出这种场景.一般的浏览器,如IE.

《深入剖析Nginx》一2.1 利用gdb调试

2.1 利用gdb调试 深入剖析Nginx gdb是Linux下调试程序的常用工具,任何Linux开发工程师初学程序调试时第一个接触到的工具应该就是gdb.关于gdb本身的详细用法,我们不多详述,读者可以参考gdb官网手册1,而在这里,我们将重点介绍一些与Nginx相关的注意点与调试技巧. 2.1.1 绑定Nginx到gdb 利用gdb调式Nginx,首先得在生成Nginx程序时把-g编译选项打开.当然,这并不是说不打开-g选项就无法用gdb调试它,只是会因为缺少相应的符号信息导致调试不便,而此

《深入剖析Nginx》——2.3 利用strace/pstack调试Nginx

2.3 利用strace/pstack调试Nginx Linux下有两个命令strace1和ltrace2可以分别用来查看一个应用程序在运行过程中所发起的系统函数调用和动态库函数调用,这对作为标准应用程序的Nginx自然同样可用.由于这两个命令大同小异,下面就仅以strace为例做简单介绍,大致了解一些它能帮助我们获取哪些有用的调试信息.关于strace/ltrace以及后面介绍的pstack更多的用法请参考对应的Man手册. 从strace的Man手册可以看到几个有用的选项. p pid:通过

《深入剖析Nginx》一导读

前 言 深入剖析Nginx慕名对Nginx源码进行学习与研究是早在2008年的事情.当时正在为职业规划与未来发展困惑不已,一筹莫展之际不知从哪里得知高性能服务器是一个很有"前途"的努力方向,几经搜索又机缘偶合地得识Lighttpd与Nginx.在逐步了解和熟悉它们的源码后,我开始感到自己的无知与浅薄,发现原来代码也可以写得如此优雅. 我已编著过一本<Lighttpd源码分析>.先解析Lighttpd源码并没有什么特别的原因,只是因为在当时Lighttpd比Nginx要火,应

《深入剖析Nginx》一2.2 利用日志信息跟踪Nginx

2.2 利用日志信息跟踪Nginx 深入剖析Nginx 优秀的程序都会带有自己的日志输出接口,并且一般还会给出不同等级的输出级别,以便于重次信息的过滤,比如 Linux 内核的日志输出标准接口为 printk,并且给出了KERN_EMERG.KERN_ALERT.KERN_DEBUG等这样的输出等级.Nginx 与此类似,下面具体来看. 为了获取最丰富的日志信息,我们在进行configure配置时,需要把--with-debug选项加上,这样能生成一个名为NGX_DEBUG的宏,而在Nginx源

《深入剖析Nginx》——1.2 源码下载

1.2 源码下载 深入剖析Nginx Nginx的源码可通过官网提供的下载地址1找到,截止当前的最新版本是Nginx 1.2.0,也就是本书所针对的版本.虽然官网下载页没有提供Nginx旧版源码的下载链接,但Nginx的所有版本源码包都是放在目录 http://nginx.org/download/下的,所以包括Nginx 0.1.0版本在内的Nginx源码都能下载到. 由于Nginx背后有公司运作,所以其更新速度比较快,相关资料也比较齐全,下面是一些有用的网址. 官方主页:http://ngi

《深入剖析Nginx》一第1章 源码分析的准备工作

第1章 源码分析的准备工作 深入剖析Nginx 从Nginx(读作engine x)的官方网站1,我们可以看到如下介绍:Nginx是Igor Sysoev2编写的一款HTTP和反向代理服务器,另外它也可以当作邮件代理服务器.它一直被众多流量巨大的俄罗斯网站所使用,例如Yandex3.Mail.Ru4.VKontakte5以及Rambler6等.据Netcraft统计,截止到2012年8月份,世界上最繁忙的网站中有11.48%7在使用Nginx作为其服务器或者代理服务器.部分典型成功案例有:Net

《深入剖析Nginx》一1.2 源码下载

1.2 源码下载 深入剖析Nginx Nginx的源码可通过官网提供的下载地址1找到,截止当前的最新版本是Nginx 1.2.0,也就是本书所针对的版本.虽然官网下载页没有提供Nginx旧版源码的下载链接,但Nginx的所有版本源码包都是放在目录http://nginx.org/download/下的,所以包括Nginx 0.1.0版本在内的Nginx源码都能下载到. 由于Nginx背后有公司运作,所以其更新速度比较快,相关资料也比较齐全,下面是一些有用的网址. 官方主页:http://ngin

《深入剖析Nginx》一1.3 源码目录结构

1.3 源码目录结构 深入剖析Nginx 将Nginx源码包解压后,目录文件如下所示. [root@localhost nginx-1.2.0]# ls -F auto/ CHANGES CHANGES.ru conf/ configure* contrib/ html/ LICENSE man/ README src/ 其中 auto/:包含了很多会在执行configure进行编译配置时调用的检测代码. CHANGES:Nginx的版本更新细节记录.英文版. CHANGES.ru:Nginx的