首页 > nginx, 源码分析 > 利用gdb调试nginx

利用gdb调试nginx

2011年9月10日 发表评论 阅读评论 15,094 次浏览

请关注最新修正合订:http://lenky.info/ebook/
这一系列的文章还是在09年写的,存在电脑里很久了,现在贴出来。顺序也不记得了,看到那个就发那个吧,最近都会发上来。欢迎转载,但请保留链接:http://lenky.info/,谢谢。
利用gdb[i]调试nginx[ii]和利用gdb调试其它程序没有两样,不过nginx可以是daemon程序,也可以以多进程运行,因此利用gdb调试和平常会有些许不一样。当然,我们可以选择将nginx设置为非daemon模式并以单进程运行,而这需做如下设置即可:
daemon off;
master_process off;

这是第一种情况:
这种设置下的nginx在gdb下调试很普通,过程可以[iii]是这样:
执行命令:
lenky@lenky-desktop:/usr/local/nginx/sbin$ sudo gdb ./nginx
当前目录是在/usr/local/nginx/sbin,该目录下有执行程序nginx,上条命令也就是用gdb来开始调试nginx,进入gdb命令行,直接输入r即可执行nginx:
(gdb) r
Starting program: /usr/local/nginx/sbin/nginx
src/core/ngx_conf_file.c 1163 : 1000000
===============================
ngx_timer_resolution 1000000
因为nginx以前台形式运行,键盘输入被nginx接管,此时无法输入gdb命令,因此需要按Ctrl+C退到gdb命令行模式,而nginx被中断暂停在__kernel_vsyscall调用里,输入bt命令可以查看到调用堆栈信息:
(gdb) r
Starting program: /usr/local/nginx/sbin/nginx
src/core/ngx_conf_file.c 1163 : 1000000
===============================
ngx_timer_resolution 1000000
^C
Program received signal SIGINT, Interrupt.
0xb7f29430 in __kernel_vsyscall ()
(gdb) bt
#0  0xb7f29430 in __kernel_vsyscall ()
#1  0xb7e081a8 in epoll_wait () from /lib/tls/i686/cmov/libc.so.6
#2  0x08073ea3 in ngx_epoll_process_events (cycle=0x860ad60, timer=4294967295,
flags=0) at src/event/modules/ngx_epoll_module.c:405
#3  0x080668ee in ngx_process_events_and_timers (cycle=0x860ad60)
at src/event/ngx_event.c:253
#4  0x08071618 in ngx_single_process_cycle (cycle=0x860ad60)
at src/os/unix/ngx_process_cycle.c:283
#5  0x0804a926 in main (argc=1, argv=0xbfd2ae44) at src/core/nginx.c:372
(gdb)
好,来给nginx设置断点,这很简单,比如这里给函数ngx_process_events_and_timers设置个断点,再输入命令c继续执行nginx:
#4  0x08071618 in ngx_single_process_cycle (cycle=0x860ad60)
at src/os/unix/ngx_process_cycle.c:283
#5  0x0804a926 in main (argc=1, argv=0xbfd2ae44) at src/core/nginx.c:372
(gdb) b ngx_process_events_and_timers
Breakpoint 1 at 0x80667d6: file src/event/ngx_event.c, line 200.
(gdb) c
Continuing.
Breakpoint 1, ngx_process_events_and_timers (cycle=0x860ad60)
at src/event/ngx_event.c:200
200  {
(gdb)
结果我这里gdb马上提示断点中断,这是因为恰好调到这个函数而已,也许在你那并不会马上中断,从而gdb就一直停在Continuing后面,此时我们就要主动去触发事件(或者等待,时间可能很久,根据你的nginx设置)使得nginx调用这个函数,比如利用浏览器去访问nginx监听的站点,使得nginx有事件发生。
断点中断后,利用s、c等gdb常规命令进行我们的调试,跟踪,这些无需多说:
200  {
(gdb) s
__cyg_profile_func_enter (this=0x80667d0, call=0x8071618)
at src/core/my_debug.c:65
warning: Source file is more recent than executable.
65      my_debug_print(“Enter\n%p\n%p\n”, call, this);
(gdb)
66    }
(gdb) c
Continuing.
ngx_timer_resolution 1000000
第二种情况:
当以daemon模式(即配置选项:daemon on;)单进程运行nginx时,上面那种方法不凑效了,原因很简单,当nginx以daemon模式运行时,这个启动过程中nginx将fork好几次,而那些父进程都早已直接退出了,因此看到的会这样:
lenky@lenky-desktop:/usr/local/nginx/sbin$ sudo gdb ./nginx
GNU gdb 6.8-debian
Copyright (C) 2008 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type “show copying”
and “show warranty” for details.
This GDB was configured as “i486-linux-gnu”…
(gdb) r
Starting program: /usr/local/nginx/sbin/nginx
src/core/ngx_conf_file.c 1163 : 1000000
===============================
Program exited normally.
(gdb)
而事实上nginx并没有退出,退出的是其父(或祖父)进程,而gdb没有跟着fork跟踪下去:
lenky@lenky-desktop:/usr/local/nginx/sbin$ pidof nginx
7908
gdb考虑了这种情况,因此也提供了相应的follow-fork-mode选项:
其用法为:
set follow-fork-mode [parent|child]
说明:
parent: fork之后继续调试父进程,子进程不受影响。
child: fork之后调试子进程,父进程不受影响。
设置了该选项后,其它调式操作和之前介绍的基本一样,相关示例如下:
lenky@lenky-desktop:/usr/local/nginx/sbin$ sudo gdb ./nginx -q
(gdb) r
Starting program: /usr/local/nginx/sbin/nginx
src/core/ngx_conf_file.c 1163 : 1000000
===============================
Program exited normally.
(gdb) shell pidof nginx
9723
(gdb) shell kill -9 9723
(gdb) set follow-fork-mode child
(gdb) b ngx_process_events_and_timers
Breakpoint 1 at 0x80667d6: file src/event/ngx_event.c, line 200.
(gdb) r
Starting program: /usr/local/nginx/sbin/nginx
src/core/ngx_conf_file.c 1163 : 1000000
===============================
[Switching to process 9739]
Breakpoint 1, ngx_process_events_and_timers (cycle=0x8ff0d60)
at src/event/ngx_event.c:200
200  {
(gdb) s
[tcsetpgrp failed in terminal_inferior: No such process]
__cyg_profile_func_enter (this=0x80667d0, call=0x8071618)
at src/core/my_debug.c:65
warning: Source file is more recent than executable.
65      my_debug_print(“Enter\n%p\n%p\n”, call, this);
(gdb) c
Continuing.
上面我们先设置断点,这样执行r命令后nginx将自动被中断返回到gdb命令行,如果不这样做,那么将无法返回gdb命令行,即按Ctrl+C没有效果,因为nginx是以daemon模式运行的,输入输出已经被重定向,nginx没有接收到这个Ctrl+C输入。当然也并不是没有其它办法,比如另外一个shell窗口,利用kill命令给nginx进程发送一个SIGINT信号即可,这和按Ctrl+C是一样的,如下(假定9897为nginx进程id号):
lenky@lenky-desktop:~$ sudo kill -2 9897
第二种情况:
第三种情况就是不管nginx运行模式了,是通用的,即利用gdb的attach、detach命令。
假定当前nginx的相关配置这样:
daemon on;
master_process on;
worker_processes  1;
即daemon模式,多进程运行nginx。
lenky@lenky-desktop:/usr/local/nginx/sbin$ sudo gdb -q
(gdb) shell ./nginx
src/core/ngx_conf_file.c 1163 : 1000000
===============================
(gdb) shell pidof nginx
10246 10245
(gdb)
可以看到一共有两个nginx进程,这里10245为master进程,而10246为worker进程。我们要调试worker 10246可以如下:
(gdb) attach 10246
Attaching to program: /usr/local/nginx/sbin/nginx, process 10246
Reading symbols from /lib/tls/i686/cmov/libcrypt.so.1…done.
Loaded symbols for /lib/tls/i686/cmov/libcrypt.so.1
Reading symbols from /lib/libpcre.so.3…done.
Loaded symbols for /lib/libpcre.so.3
Reading symbols from /usr/lib/libz.so.1…done.
Loaded symbols for /usr/lib/libz.so.1
Reading symbols from /lib/tls/i686/cmov/libc.so.6…done.
Loaded symbols for /lib/tls/i686/cmov/libc.so.6
Reading symbols from /lib/ld-linux.so.2…done.
Loaded symbols for /lib/ld-linux.so.2
Reading symbols from /lib/tls/i686/cmov/libnss_compat.so.2…done.
Loaded symbols for /lib/tls/i686/cmov/libnss_compat.so.2
Reading symbols from /lib/tls/i686/cmov/libnsl.so.1…done.
Loaded symbols for /lib/tls/i686/cmov/libnsl.so.1
Reading symbols from /lib/tls/i686/cmov/libnss_nis.so.2…done.
Loaded symbols for /lib/tls/i686/cmov/libnss_nis.so.2
Reading symbols from /lib/tls/i686/cmov/libnss_files.so.2…done.
Loaded symbols for /lib/tls/i686/cmov/libnss_files.so.2
0xb8016430 in __kernel_vsyscall ()
(gdb) b ngx_process_events_and_timers
Breakpoint 3 at 0x80667d6: file src/event/ngx_event.c, line 200.
(gdb) c
Continuing.
Breakpoint 3, ngx_process_events_and_timers (cycle=0x8f43d68) at src/event/ngx_event.c:200
200  {
(gdb) detach
Detaching from program: /usr/local/nginx/sbin/nginx, process 10246
(gdb)
总的来看,还是第三种方法最顺手了。



[i] http://www.gnu.org/software/gdb/
[ii] nginx是打开了-g选项编译的。
[iii] 意思是说下面的方法只是众多方法之一。

转载请保留地址:http://www.lenky.info/archives/2011/09/58http://lenky.info/?p=58


备注:如无特殊说明,文章内容均出自Lenky个人的真实理解而并非存心妄自揣测来故意愚人耳目。由于个人水平有限,虽力求内容正确无误,但仍然难免出错,请勿见怪,如果可以则请留言告之,并欢迎来讨论。另外值得说明的是,Lenky的部分文章以及部分内容参考借鉴了网络上各位网友的热心分享,特别是一些带有完全参考的文章,其后附带的链接内容也许更直接、更丰富,而我只是做了一下归纳&转述,在此也一并表示感谢。关于本站的所有技术文章,欢迎转载,但请遵从CC创作共享协议,而一些私人性质较强的心情随笔,建议不要转载。

法律:根据最新颁布的《信息网络传播权保护条例》,如果您认为本文章的任何内容侵犯了您的权利,请以Email或书面等方式告知,本站将及时删除相关内容或链接。

分类: nginx, 源码分析 标签: ,
  1. hahaya
    2014年4月22日14:12 | #1

    你好 我在使用第3种方法时gdb出现如下问题:
    Loaded symbols for /lib/x86_64-linux-gnu/libnss_files.so.2
    0x00007fc9aeeba2c6 in do_sigsuspend (set=0x7fffbdec8680) at ../sysdeps/unix/sysv/linux/sigsuspend.c:32
    32 ../sysdeps/unix/sysv/linux/sigsuspend.c: 没有那个文件或目录.

    请问这是为什么呢?

    • lenky
      2014年4月22日19:18 | #2

      不用管,你又不会看到那里面去~,:)

  2. 2014年3月20日11:44 | #3

    csetpgrp failed in terminal_inferior: No such process,这个信息需要关注?

  3. c
    2012年6月4日08:47 | #4

    再问个问题
    HTTP Addition这个模块啥作用
    这里面有用到sub_request这个子请求啥事会用,不太懂

  4. c
    2012年6月1日03:52 | #5

    再问个ngx_epoll_process_events
    这个函数里面有两个地方,是判断是读事件还是写事件的
    if ((revents & EPOLLIN) && rev->active) {… rev->handler(rev);…}
    if ((revents & EPOLLOUT) && wev->active) {…wev->handler(wev);…}
    我单步调试了,那个读事件就把内容给输出了,没用上写事件,这个写事件是不是在大访问量需要等待的时候用上的

    • lenky
      2012年6月1日12:46 | #6

      嘛,具体我没看,不过我想只有在写的时候发生写阻塞或事件中断需要等待下一次可写事件时才有必要用到写事件吧?

  5. c
    2012年6月1日01:23 | #7

    问下啊,有没有http模块的分析,能不能贴出来的,谢了

  6. c
    2012年5月28日13:13 | #9

    博主说的对,谢谢你
    我下午试的时候是在c++下面用的,他就是不行
    博主好人啊,我问的问题都回,再次感谢

  7. lenky
    2012年5月28日10:33 | #10

    @c
    你是想问为什么可以不用强制转换却没有编译警告吧?
    nginx是用c语言实现的,在c代码里这样写并没有问题。在弱类型的c语言里,void指针(一级指针,即是void *)与其它类型指针之间的赋值,转换总是implicit隐性进行的,所以你可以显示进行强制转换,也可以不必进行。
    如果在c++语言里,那么就会有警告了,此时你必须提供显示强制转换,因为c++里已有类的概念,有new、delete这样的内存操作符,而且变量类型也不再那么简单,在强类型的c++语言里,由其它类型指针转为void指针(一级指针,即是void *),c++仍然提供implicit隐性转换,一则是一定程度的对c进行后向兼容,另外也应该是限于程序对void *类型能够进行的操作比较少吧,但从void指针转换为其它类型指针(包括多级void指针,比如void **、void ***等)都需要程序提供显示转换。

  8. c
    2012年5月28日09:45 | #11

    cycle->conf_ctx = ngx_pcalloc(pool, ngx_max_module * sizeof(void *));
    这句代码后面函数返回值是void*它咋能赋值给void****呢

  9. lenky
    2012年5月28日04:06 | #12

    @c
    哈哈,那就多发点时间呗,Rome was not built in a day.

  10. c
    2012年5月28日02:28 | #13

    这nginx感觉太庞大了,
    这源码研究给点建议吧,感谢

  11. c
    2012年5月28日00:35 | #14

    确实是版本问题,我用了个ubuntu 12这个版本,试了下没问题了
    谢谢啊

  12. lenky
    2012年5月27日23:27 | #15

    @c
    呵呵,目前我只能做出这么多判断,到底还是内核版本太低了吧?

  13. c
    2012年5月27日15:07 | #16

    还有编译的时候加的是-g这个选项,别的都没动
    nginx版本是0.7.61

  14. c
    2012年5月27日15:05 | #17

    debug_points没有配置这个,
    nginx.conf这个文件里面就在底部加了
    daemon on;
    master_process on;
    这两句话,别的都是默认的,没有改动

  15. lenky
    2012年5月27日14:59 | #18

    @c
    是否配置了debug_points,这个指令可以让nginx进入到stop状态,但是在遇到错误的情况下,不过照你的操作,仅只是进行gdb attach,应该不会导致nginx出错才对。实在没有办法的话,你可以试试在同一台虚拟机里把内核升级到2.4.21或之后版本如35(这样能保证其它配置与环境都不变,而仅是内核变化一下),再看情况如何?

  16. c
    2012年5月27日14:37 | #19

    (gdb) attach 2880
    Attaching to process 2880
    Could not get registers:
    我执行这个命令附加子进程后,子进程变成了T
    父进程还是S

  17. c
    2012年5月27日14:25 | #20

    一样,都是S

  18. c
    2012年5月27日13:36 | #21

    应该不是内核的问题吧
    我attach父进程的时候是可以的
    我单独用了一个程序,attach也是可以的,没有报上面的错误

    • lenky
      2012年5月27日13:53 | #22

      你看下那个nginx进程的当前进程状态是什么?ps命令看两个进程状态一样么?

  19. lenky
    2012年5月27日13:19 | #23

    @c
    你好,你这个问题估计是内核问题,你用的内核太旧了。如下有类似问题的讨论,希望你能正常访问:http://forum.soft32.com/linux2/GDB-sleeping-processes-ftopict8625.html
    看来你运气比较差,刚好用到这个2.4.20的版本,而2.4.21以及后续版本都已fixed:
    As I said, 2.4.21 and later from kernel.org have almost proper fix
    (module autoloading from ptraced tasks is not very common.)

    But anyway, the following is _TOTALLY_ _UNTESTED_ patch to fix
    that. It may crash. It may reintroduce the hole, It may break
    something else, it may fail to compile etc… The patch is
    based on 2.4.22-rc3, but should work in any kernel with “proper”
    ptrace fix (apply as usual: patch -p1 in main kernel directory):

  20. c
    2012年5月27日05:10 | #24

    问下啊,按照你上面第三种情况
    编译已经加入了-g选项
    虚拟机下的linux 2.4.20
    以root用户登录的

    (gdb) attach 2880
    Attaching to process 2880
    Could not get registers:不允许操作

    出现个不允许操作,是哪的问题

  1. 2012年10月30日07:46 | #1
  2. 2012年11月3日03:46 | #2