어떻게 디 버 깅 을 통 해 nginx 를 배 웁 니까?

15025 단어
저 는 장 샤 오 팡 입 니 다. 공중 번호 인 '고성능 서버 개발' 입 니 다. 전 여행 망 기술 전문가 팀 전문가 로 고성능 서버 개발 에 뛰 어 납 니 다!글 은 [고성능 서버 개발] 공중 번호 에 수 록 될 것 입 니 다. 이것 은 개발 자의 향상 을 돕 는 실제 적 인 기술 번호 입 니 다.
실제 응용 에서 리 눅 스 함수 fork 를 통 해 새로운 하위 프로 세 스 를 만 드 는 응용 프로그램 이 있 습 니 다.nginx 를 예 로 들 면 nginx 가 클 라 이언 트 에 대한 연결 은 다 중 프로 세 스 모델 을 사용 합 니 다. nginx 가 클 라 이언 트 연결 을 받 아들 인 후에 이 연결 의 정보 왕래 를 처리 하 는 새로운 프로 세 스 를 만 듭 니 다.새로 생 긴 프로 세 스 는 원래 의 프로 세 스 와 서로 부자 관계 이다.그렇다면 어떻게 gdb 로 이러한 부자 프로 세 스 를 디 버 깅 합 니까?일반적으로 두 가지 방법 이 있다.
방법 1
gdb 로 부모 프로 세 스 를 먼저 디 버 깅 하고 하위 프로 세 스 가 fork 에서 나 온 후에 gdb attach 를 사용 하여 하위 프로 세 스 에 연결 합 니 다.물론 디 버 깅 을 위해 셸 창 을 다시 열 어야 합 니 다. gdb attach 의 용법 은 앞에서 소개 되 었 습 니 다.
우 리 는 nginx 서 비 스 를 디 버 깅 하 는 것 을 예 로 들 었 다.
nginx 홈 페이지 에서http://nginx.org/en/download.html 최신 nginx 소스 코드 를 다운로드 한 다음 에 컴 파일 하여 설치 합 니 다 (필자 가 이 책 을 쓸 때 nginx 의 최신 안정 버 전 은 1.18.0).
##    nginx   
[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]

##   nginx
[root@iZbp14iz399acush5e8ok7Z zhangyl]# tar zxvf nginx-1.18.0.tar.gz

##   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"

##   ,  nginx     /usr/local/nginx/   
[root@iZbp14iz399acush5e8ok7Z nginx-1.18.0]make install

메모: make 명령 을 사용 하여 컴 파일 할 때 저 희 는 생 성 된 nginx 가 디 버 깅 기호 정 보 를 가지 고 컴 파일 러 를 최적화 시 키 기 위해 '- g - O 0' 옵션 을 설정 하 였 습 니 다.
시작 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)

위 에서 보 듯 이 nginx 는 기본적으로 두 개의 프로 세 스 를 시작 합 니 다. 제 기계 에서 루트 사용자 로 실행 되 는 nginx 프로 세 스 는 부모 프로 세 스 이 고 프로 세 스 번 호 는 5246 이 며 nobody 사용자 로 실행 되 는 프로 세 스 는 하위 프로 세 스 이 며 프로 세 스 번 호 는 5247 입 니 다.현재 창 에서 gdb 를 nginx 메 인 프로 세 스에 추가 하 는 명령 gdb attach 5246 을 사용 합 니 다.
[root@iZbp14iz399acush5e8ok7Z sbin]# gdb attach 5246
...        ...
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)

이 때 우 리 는 nginx 부모 프로 세 스 를 디 버 깅 할 수 있 습 니 다. 예 를 들 어 bt 명령 으로 현재 호출 스 택 을 볼 수 있 습 니 다.
(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)

f 1 명령 을 사용 하여 현재 호출 스 택 으로 전환 합 니 다 \ # 1. nginx 부모 프로 세 스 의 메 인 스 레 드 가 src/core/nginx.c:382 에 걸 려 있 는 것 을 발견 할 수 있 습 니 다.
이 때 는 c 명령 을 사용 하여 프로그램 을 계속 실행 시 킬 수도 있 고 정지점 을 추가 하거나 다른 디 버 깅 작업 을 할 수도 있 습 니 다.
셸 창 을 하나 더 열 고 gdb 를 nginx 서브 프로 세 스에 추가 합 니 다.
[root@iZbp14iz399acush5e8ok7Z sbin]# gdb attach 5247
...      ...
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)

우 리 는 bt 명령 을 사용 하여 프로 세 스 의 주 스 레 드 현재 호출 스 택 을 봅 니 다.
(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 , 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)

하위 프로 세 스 가 걸 려 있 는 epoll 을 발견 할 수 있 습 니 다.wait 함수 부.우 리 는 epollwait 함수 가 돌아 온 후 gdb attach 5247 정지점 을 추가 한 다음 c 명령 을 사용 하여 nginx 서브 프로 세 스 를 계속 실행 합 니 다.
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.

이 어 저 희 는 브 라 우 저 에서 nginx 의 사 이 트 를 방문 합 니 다. 제 ip 주 소 는 제 클 라 우 드 호스트 주소 입 니 다. 독자 가 실제 디 버 깅 할 때 자신의 nginx 서버 가 있 는 주소 로 바 꾸 었 습 니 다. 이 컴퓨터 라면 127.0.0.0.1 입 니 다. 기본 포트 는 80 이기 때문에 포트 번 호 를 지정 하지 않 아 도 됩 니 다.
http://  ip  :80
   
http://  ip  

이 때 우 리 는 nginx 서브 프로 세 스 의 디 버 깅 인터페이스 로 돌아 가 정지점 이 실 행 된 것 을 발견 합 니 다.
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) 

bt 명령 을 사용 하면 현재 호출 스 택 을 얻 을 수 있 습 니 다:
(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 , 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) 

info threads 명령 을 사용 하면 하위 프로 세 스 의 모든 스 레 드 정 보 를 볼 수 있 습 니 다. nginx 하위 프로 세 스 는 하나의 메 인 스 레 드 만 있 습 니 다.
(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) 

nginx 부모 프로 세 스 는 클 라 이언 트 요청 을 처리 하지 않 습 니 다. 클 라 이언 트 요청 을 처리 하 는 논 리 는 하위 프로 세 스 에 있 습 니 다. 단일 키 프로 세 스 클 라 이언 트 요청 수량 이 일정 수량 에 이 르 렀 을 때 부모 프로 세 스 는 새로운 하위 프로 세 스 를 다시 fork 하여 새로운 클 라 이언 트 요청 을 처리 합 니 다. 즉, 하위 프로 세 스 수량 이 여러 개 있 을 수 있 습 니 다. 여러 개의 셸 창 을 열 수 있 습 니 다.gdb attach 를 사용 하여 각 하위 프로 세 스 에 디 버 깅 합 니 다.
요약 하면 우 리 는 이런 방법 으로 각종 단점 디 버 깅 nginx 의 기능 을 추가 할 수 있 고 천천히 우 리 는 nginx 의 각 내부 논 리 를 익 힐 수 있다.
그러나 방법 중 하 나 는 프로그램 이 시작 되 었 다 는 단점 이 있 습 니 다. 우 리 는 gdb 를 사용 하여 프로그램의 행동 을 관찰 할 수 밖 에 없습니다. 만약 에 우리 가 프로그램 이 시작 에서 실행 되 는 사이 의 실행 절 차 를 디 버 깅 하려 면 방법 이 적용 되 지 않 을 수 있 습 니 다.일부 독자 들 은 내 가 gdb 로 프로 세 스 에 추가 한 후에 정지점 을 추가 한 다음 에 run 명령 으로 프로 세 스 를 다시 시작 하면 디 버 깅 프로그램 이 시작 에서 실행 되 는 사이 의 실행 절 차 를 디 버 깅 할 수 있 지 않 겠 느 냐 고 말 할 수 있다.문 제 는 이러한 방법 이 통용 되 는 것 이 아니 라 다 중 프로 세 스 서비스 모델 에 대해 일부 부자 프로 세 스 는 어느 정도 의존 관계 가 있 기 때문에 운영 과정 에서 다시 시작 하기 가 불편 하 다 는 것 이다.이 럴 때 는 방법 2 로 디 버 깅 할 수 있다.
방법 2
gdb 디 버 거 는 follow - fork 라 는 옵션 을 제공 합 니 다. set follow - fork mode 를 통 해 프로 세 스 fork 가 새로운 하위 프로 세 스 를 만 들 때 gdb 는 부모 프로 세 스 (값 은 parent) 를 계속 디 버 깅 하 는 지, 하위 프로 세 스 (값 은 child) 를 계속 디 버 깅 하 는 지, 기본 값 은 부모 프로 세 스 (값 은 parent) 입 니 다.
# fork  gdb attach    
set follow-fork child
# fork  gdb attach    ,     
set follow-fork parent

쇼 follow - fork 모드 를 사용 하여 현재 값 을 볼 수 있 습 니 다:
(gdb) show follow-fork mode
Debugger response to a program call of fork or vfork is "child".

저 희 는 nginx 를 디 버 깅 하 는 것 을 예 로 들 어 nginx 에서 실행 가능 한 파일 이 있 는 디 렉 터 리 에 먼저 들 어가 방법 1 중의 nginx 서 비 스 를 중단 합 니 다.
[root@iZbp14iz399acush5e8ok7Z sbin]# cd /usr/local/nginx/sbin/
[root@iZbp14iz399acush5e8ok7Z sbin]# ./nginx -s stop

nginx 소스 코드 에 이러한 논리 가 존재 합 니 다. 이 논 리 는 프로그램 main 함수 에서 호출 됩 니 다.
//src/os/unix/ngx_daemon.c: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;
	
	//fork         case
    case 0:
        break;
	
	//    fork        PID,  0,     case
	//        
    default:
        exit(0);
    }

    //...      ...
}

상기 코드 에서 설명 한 바 와 같이 메 인 프로 세 스 가 종료 되 지 않도록 nginx 설정 파일 에 한 줄 을 추가 합 니 다.
daemon off;

이렇게 nginx 는 ngx 를 호출 하지 않 습 니 다.daemon 함수 입 니 다.
다음 에 저 희 는 src/event/modules/ngx_epoll_module.c:800 을 실행 한 다음 에 설정 파 라 메 터 를 통 해 디 버 깅 을 기다 리 는 nginx 프로 세 스 에 설정 파일 nginx. conf 를 전달 합 니 다.
Quit anyway? (y or n) y
[root@iZbp14iz399acush5e8ok7Z sbin]# gdb nginx 
...      ...
Reading symbols from nginx...done.
(gdb) set args -c /usr/local/nginx/conf/nginx.conf
(gdb) 

다음 run 명령 을 입력 하여 nginx 를 실행 하려 고 시도 합 니 다.
(gdb) run
Starting program: /usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf
[Thread debugging using libthread_db enabled]
...        ...
[Detaching after fork from child process 7509]

앞에서 말 한 바 와 같이 gdb 가 fork 명령 을 만 났 을 때 기본적으로 부모 프로 세 스 에 첨부 됩 니 다. 따라서 상기 출력 에는 "Detaching after fork from child process 7509" 라 는 힌트 가 있 습 니 다.", 우 리 는 Ctrl + C 를 누 르 면 프로그램 을 중단 한 다음 bt 명령 을 입력 하여 현재 호출 된 스 택 을 봅 니 다. 출력 된 스 택 정 보 는 우리 가 방법 1 에서 본 부모 프로 세 스 의 호출 스 택 과 마찬가지 로 gdb 가 프로그램 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) 

fork 이후 gdb 에서 attach 하위 프로 세 스 를 가 려 면 프로그램 이 실행 되 기 전에 gdb 에 설정 src/event/modules/ngx_epoll_module.c:804 한 다음 run 명령 을 사용 하여 프로그램 을 다시 실행 할 수 있 습 니 다.
(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 , 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) 

이 어 Ctrl + C 를 누 르 면 프로그램 을 중단 시 킨 다음 bt 명령 을 사용 하여 현재 스 레 드 호출 스 택 을 확인 합 니 다. 이것 은 gdb 가 하위 프로 세 스 에 확실히 연결 되 었 음 을 설명 합 니 다.
우 리 는 방법 2 디 버 깅 프로그램 fork 이전 과 그 후의 모든 논 리 를 이용 하여 비교적 통용 되 는 다 중 프로 세 스 디 버 깅 방법 으로 독자 에 게 파악 하도록 건의 할 수 있다.
만약 당신 이 gdb 에 익숙 하지 않다 면, 여 기 는 gdb 강좌 입 니 다.

좋은 웹페이지 즐겨찾기