There are many function points of Nginx, and the new concepts and design ideas involved are not particularly friendly to novices. I suggest learning the Nginx source code through debugging after understanding some basic knowledge of Nginx.
The following operations require some gdb debugging knowledge. If you are not familiar with gdb debugging, it is recommended to take a few minutes to learn.
gdb is not difficult to learn. You can master common commands. There are not many common commands. It is recommended< Debugging with GDB: link: https://pan.baidu.com/s/1YPAx9CQgQoJmkd38HLZ35Q Extraction code: d03u1, Download Nginx source code
Download the latest nginx source code from the official website of nginx, and then compile and install it (when answering this question, the latest stable version of nginx is 1.18.0).
Note: when compiling with the make command, we set the "- g -O0" option to make the generated Nginx with debugging symbol information and turn off compiler optimization.## Download nginx source code [root@iZbp14iz399acush5e8ok7Z zhangyl]# wget http://nginx.org/download/nginx-1.18.0.tar.gz --2020-07-05 17:22:10-- http://nginx.org/download/nginx-1.18.0.tar.gz Resolving nginx.org (nginx.org)... 95.211.80.227, 62.210.92.35, 2001:1af8:4060:a004:21::e3 Connecting to nginx.org (nginx.org)|95.211.80.227|:80... connected. HTTP request sent, awaiting response... 200 OK Length: 1039530 (1015K) [application/octet-stream] Saving to: 'nginx-1.18.0.tar.gz' nginx-1.18.0.tar.gz 100%[===================================================================================================>] 1015K 666KB/s in 1.5s 2020-07-05 17:22:13 (666 KB/s) - 'nginx-1.18.0.tar.gz' saved [1039530/1039530] ## Unzip nginx [root@iZbp14iz399acush5e8ok7Z zhangyl]# tar zxvf nginx-1.18.0.tar.gz ## Compiling nginx [root@iZbp14iz399acush5e8ok7Z zhangyl]# cd nginx-1.18.0 [root@iZbp14iz399acush5e8ok7Z nginx-1.18.0]# ./configure --prefix=/usr/local/nginx [root@iZbp14iz399acush5e8ok7Z nginx-1.18.0]make CFLAGS="-g -O0" ## Install so that nginx is installed in the / usr/local/nginx / directory [root@iZbp14iz399acush5e8ok7Z nginx-1.18.0]make install
2, Debug Nginx
There are two ways to debug Nginx:
Method 1
Start Nginx:
[root@iZbp14iz399acush5e8ok7Z sbin]# cd /usr/local/nginx/sbin [root@iZbp14iz399acush5e8ok7Z sbin]# ./nginx -c /usr/local/nginx/conf/nginx.conf [root@iZbp14iz399acush5e8ok7Z sbin]# lsof -i -Pn | grep nginx nginx 5246 root 9u IPv4 22252908 0t0 TCP *:80 (LISTEN) nginx 5247 nobody 9u IPv4 22252908 0t0 TCP *:80 (LISTEN)
As shown above, Nginx will start two processes by default. The Nginx process running as root on my machine is the parent process, and the process number is 5246. The process running as nobody user is a child process, and the process number is 5247. In the current window, we use the gdb attach 5246 command to attach gdb to the Nginx main process.
[root@iZbp14iz399acush5e8ok7Z sbin]# gdb attach 5246 ...Omit some output information... 0x00007fd42a103c5d in sigsuspend () from /lib64/libc.so.6 Missing separate debuginfos, use: yum debuginfo-install glibc-2.28-72.el8_1.1.x86_64 libxcrypt-4.1.1-4.el8.x86_64 pcre-8.42-4.el8.x86_64 sssd-client-2.2.0-19.el8.x86_64 zlib-1.2.11-10.el8.x86_64 (gdb)
At this point, we can debug the Nginx parent process, such as using bt Command to view the current call stack:
(gdb) bt #0 0x00007fd42a103c5d in sigsuspend () from /lib64/libc.so.6 #1 0x000000000044ae32 in ngx_master_process_cycle (cycle=0x1703720) at src/os/unix/ngx_process_cycle.c:164 #2 0x000000000040bc05 in main (argc=3, argv=0x7ffe49109d68) at src/core/nginx.c:382 (gdb) f 1 #1 0x000000000044ae32 in ngx_master_process_cycle (cycle=0x1703720) at src/os/unix/ngx_process_cycle.c:164 164 sigsuspend(&set); (gdb) l 159 } 160 } 161 162 ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "sigsuspend"); 163 164 sigsuspend(&set); 165 166 ngx_time_update(); 167 168 ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0, (gdb)
use f 1 When the command switches to the current call stack #1, we can find that the main thread of the Nginx parent process hangs at src/core/nginx.c:382.
You can use c Command to keep the program running. You can also add breakpoints or do other debugging operations.
Open another shell window and use gdb attach 5247 to attach gdb to the Nginx child process:
[root@iZbp14iz399acush5e8ok7Z sbin]# gdb attach 5247 ...Deployment output omitted... 0x00007fd42a1c842b in epoll_wait () from /lib64/libc.so.6 Missing separate debuginfos, use: yum debuginfo-install glibc-2.28-72.el8_1.1.x86_64 libblkid-2.32.1-17.el8.x86_64 libcap-2.26-1.el8.x86_64 libgcc-8.3.1-4.5.el8.x86_64 libmount-2.32.1-17.el8.x86_64 libselinux-2.9-2.1.el8.x86_64 libuuid-2.32.1-17.el8.x86_64 libxcrypt-4.1.1-4.el8.x86_64 pcre-8.42-4.el8.x86_64 pcre2-10.32-1.el8.x86_64 sssd-client-2.2.0-19.el8.x86_64 systemd-libs-239-18.el8_1.2.x86_64 zlib-1.2.11-10.el8.x86_64 (gdb)
We use the bt command to view the current call stack of the main thread of a process.
(gdb) bt #0 0x00007fd42a1c842b in epoll_wait () from /lib64/libc.so.6 #1 0x000000000044e546 in ngx_epoll_process_events (cycle=0x1703720, timer=18446744073709551615, flags=1) at src/event/modules/ngx_epoll_module.c:800 #2 0x000000000043f317 in ngx_process_events_and_timers (cycle=0x1703720) at src/event/ngx_event.c:247 #3 0x000000000044c38f in ngx_worker_process_cycle (cycle=0x1703720, data=0x0) at src/os/unix/ngx_process_cycle.c:750 #4 0x000000000044926f in ngx_spawn_process (cycle=0x1703720, proc=0x44c2e1 <ngx_worker_process_cycle>, data=0x0, name=0x4cfd70 "worker process", respawn=-3) at src/os/unix/ngx_process.c:199 #5 0x000000000044b5a4 in ngx_start_worker_processes (cycle=0x1703720, n=1, type=-3) at src/os/unix/ngx_process_cycle.c:359 #6 0x000000000044acf4 in ngx_master_process_cycle (cycle=0x1703720) at src/os/unix/ngx_process_cycle.c:131 #7 0x000000000040bc05 in main (argc=3, argv=0x7ffe49109d68) at src/core/nginx.c:382 (gdb) f 1 #1 0x000000000044e546 in ngx_epoll_process_events (cycle=0x1703720, timer=18446744073709551615, flags=1) at src/event/modules/ngx_epoll_module.c:800 800 events = epoll_wait(ep, event_list, (int) nevents, timer); (gdb)
It can be found that the child process is suspended in Src / event / modules / NGX_ epoll_ Module. C: epoll of 800_ At the wait function. We're in epoll_ After the wait function returns (src/event/modules/ngx_epoll_module.c:804), add a breakpoint, and then use c Command causes the Nginx child process to continue running.
800 events = epoll_wait(ep, event_list, (int) nevents, timer); (gdb) list 795 /* NGX_TIMER_INFINITE == INFTIM */ 796 797 ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0, 798 "epoll timer: %M", timer); 799 800 events = epoll_wait(ep, event_list, (int) nevents, timer); 801 802 err = (events == -1) ? ngx_errno : 0; 803 804 if (flags & NGX_UPDATE_TIME || ngx_event_timer_alarm) { (gdb) b 804 Breakpoint 1 at 0x44e560: file src/event/modules/ngx_epoll_module.c, line 804. (gdb) c Continuing.
Then we visit the Nginx site in the browser. My IP address here is my virtual machine address. When the reader actually debugs, it is changed to the address of his own Nginx server. If it is local, it is 127.0.0.1. Since the default port is 80, there is no need to specify the port number.
http://Your IP address: 80 Equivalent to http://Your IP address
At this point, we return to the debugging interface of the Nginx sub process and find that the breakpoint is triggered:
Breakpoint 1, ngx_epoll_process_events (cycle=0x1703720, timer=18446744073709551615, flags=1) at src/event/modules/ngx_epoll_module.c:804 804 if (flags & NGX_UPDATE_TIME || ngx_event_timer_alarm) { (gdb)
Use the bt command to get the call stack at this time:
(gdb) bt #0 ngx_epoll_process_events (cycle=0x1703720, timer=18446744073709551615, flags=1) at src/event/modules/ngx_epoll_module.c:804 #1 0x000000000043f317 in ngx_process_events_and_timers (cycle=0x1703720) at src/event/ngx_event.c:247 #2 0x000000000044c38f in ngx_worker_process_cycle (cycle=0x1703720, data=0x0) at src/os/unix/ngx_process_cycle.c:750 #3 0x000000000044926f in ngx_spawn_process (cycle=0x1703720, proc=0x44c2e1 <ngx_worker_process_cycle>, data=0x0, name=0x4cfd70 "worker process", respawn=-3) at src/os/unix/ngx_process.c:199 #4 0x000000000044b5a4 in ngx_start_worker_processes (cycle=0x1703720, n=1, type=-3) at src/os/unix/ngx_process_cycle.c:359 #5 0x000000000044acf4 in ngx_master_process_cycle (cycle=0x1703720) at src/os/unix/ngx_process_cycle.c:131 #6 0x000000000040bc05 in main (argc=3, argv=0x7ffe49109d68) at src/core/nginx.c:382 (gdb)
Use the info threads command to view all thread information of the child process. We find that the Nginx child process has only one main thread:
(gdb) info threads Id Target Id Frame * 1 Thread 0x7fd42b17c740 (LWP 5247) "nginx" ngx_epoll_process_events (cycle=0x1703720, timer=18446744073709551615, flags=1) at src/event/modules/ngx_epoll_module.c:804 (gdb)
The Nginx parent process does not process client requests. The logic for processing client requests is in the child process. When the number of client requests of a single child process reaches a certain number, the parent process will fork a new child process to process new client requests, that is, there can be multiple child processes, and you can open multiple shell windows, Use gdb attach to debug each child process.
However, method 1 has a disadvantage, that is, the program has been started. We can only use gdb to observe the behavior of the program after that. If we want to debug the execution process of the program from start to run, method 1 may not be applicable. Some readers may say: after attaching to the process with gdb, add the breakpoint, and then restart the process with the run command, so that the execution process of the program can be debugged from start to run. The problem is that this method is not universal, because for the multi process service model, some parent-child processes have certain dependencies, which is inconvenient to restart during operation. At this time, method 2 is more appropriate.
Method 2
The gdb debugger provides an option called follow fork set follow-fork mode To set: when a process forks out a new child process, gdb continues to debug the parent process (the value is) Parent) or child process (the value is Child, the default is the parent process (the value is) parent).
# After fork, GDB attaches to the child process set follow-fork child # After fork, GDB attaches to the parent process, which is the default value set follow-fork parent
We can use show follow-fork mode View current value:
(gdb) show follow-fork mode Debugger response to a program call of fork or vfork is "child".
Let's take debugging Nginx as an example, first enter the directory where the Nginx executable file is located, and stop the Nginx service in method 1:
[root@iZbp14iz399acush5e8ok7Z sbin]# cd /usr/local/nginx/sbin/ [root@iZbp14iz399acush5e8ok7Z sbin]# ./nginx -s stop
There is such logic in the Nginx source code, which will be called at the main function of the program:
//src/os/unix/ngx_ Daemon. C: Line 13 ngx_int_t ngx_daemon(ngx_log_t *log) { int fd; switch (fork()) { case -1: ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "fork() failed"); return NGX_ERROR; //The child process from fork takes this case case 0: break; //The return value of fork in the parent process is the PID of the child process, which is greater than 0, so go to this case //Therefore, the main process exits default: exit(0); } //... omit some code }
As shown in the comments in the above code, in order not to let the main process exit, we add a line in the configuration file of Nginx:
daemon off;
In this way, Nginx will not call NGX_ The daemon function.
Next, we execute gdb nginx, and then pass the configuration file nginx.conf to the Nginx process to be debugged by setting parameters:
Quit anyway? (y or n) y [root@iZbp14iz399acush5e8ok7Z sbin]# gdb nginx ...Omit partial output... Reading symbols from nginx...done. (gdb) set args -c /usr/local/nginx/conf/nginx.conf (gdb)
Then enter the run command to try to run Nginx:
(gdb) run Starting program: /usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf [Thread debugging using libthread_db enabled] ...Omit some output information... [Detaching after fork from child process 7509]
As mentioned earlier, when gdb encounters the fork instruction, it will attach to the parent process by default. Therefore, there is a line prompt "Detaching after fork from child process 7509" in the above output "We interrupt the program according to Ctrl + c, then enter the bt command to view the current call stack, and the output stack information is the same as the calling stack we saw in the parent process, which means that gdb does attach after the program fork:
^C Program received signal SIGINT, Interrupt. 0x00007ffff6f73c5d in sigsuspend () from /lib64/libc.so.6 (gdb) bt #0 0x00007ffff6f73c5d in sigsuspend () from /lib64/libc.so.6 #1 0x000000000044ae32 in ngx_master_process_cycle (cycle=0x71f720) at src/os/unix/ngx_process_cycle.c:164 #2 0x000000000040bc05 in main (argc=3, argv=0x7fffffffe4e8) at src/core/nginx.c:382 (gdb)
If you want gdb to attach subprocesses after fork, you can set set follow fork child before the program runs, and then run the program again with the run command.
(gdb) set follow-fork child (gdb) run The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib64/libthread_db.so.1". [Attaching after Thread 0x7ffff7fe7740 (LWP 7664) fork to child process 7667] [New inferior 2 (process 7667)] [Detaching after fork from parent process 7664] [Inferior 1 (process 7664) detached] [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib64/libthread_db.so.1". ^C Thread 2.1 "nginx" received signal SIGINT, Interrupt. [Switching to Thread 0x7ffff7fe7740 (LWP 7667)] 0x00007ffff703842b in epoll_wait () from /lib64/libc.so.6 (gdb) bt #0 0x00007ffff703842b in epoll_wait () from /lib64/libc.so.6 #1 0x000000000044e546 in ngx_epoll_process_events (cycle=0x71f720, timer=18446744073709551615, flags=1) at src/event/modules/ngx_epoll_module.c:800 #2 0x000000000043f317 in ngx_process_events_and_timers (cycle=0x71f720) at src/event/ngx_event.c:247 #3 0x000000000044c38f in ngx_worker_process_cycle (cycle=0x71f720, data=0x0) at src/os/unix/ngx_process_cycle.c:750 #4 0x000000000044926f in ngx_spawn_process (cycle=0x71f720, proc=0x44c2e1 <ngx_worker_process_cycle>, data=0x0, name=0x4cfd70 "worker process", respawn=-3) at src/os/unix/ngx_process.c:199 #5 0x000000000044b5a4 in ngx_start_worker_processes (cycle=0x71f720, n=1, type=-3) at src/os/unix/ngx_process_cycle.c:359 #6 0x000000000044acf4 in ngx_master_process_cycle (cycle=0x71f720) at src/os/unix/ngx_process_cycle.c:131 #7 0x000000000040bc05 in main (argc=3, argv=0x7fffffffe4e8) at src/core/nginx.c:382 (gdb)
We then press Ctrl + C to interrupt the program, and then use the bt command to view the call stack of the current thread. The results show that it is indeed the call stack of the main thread of the sub process in method 1, which shows that gdb does attach to the sub process.
We can use method 2 to debug any logic before and after fork. It is a general multi process debugging method, which is recommended for readers to master.
To sum up, we can comprehensively use method 1 and method 2 to add various breakpoints to debug the function of Nginx, and gradually become familiar with the internal logic of Nginx.
3, Recommend some Nginx learning lists
- In depth understanding of Nginx module development and architecture analysis version 2
- Practical nginx
- Detailed explanation of Nginx high performance Web server
- Nginx module development guide uses C++11 and Boost library
Get link: