Nginx笔记之编译调试

date: 2017.04.21; modification:2017.06.02

目录:

1 背景

Nginx版本: 1.10.3

2 编译

./configure
make
make install

3 跟踪全部函数调用流程

本节几乎是克隆了: <<获得Nginx程序完整执行流程>> 中的代码. 这篇文章写的很不错, 在网上也广为流传, 但是由于严格按照原文的方法(原文作者用的是1.2.0版本), 在本文提到的版本(1.10.3)中, 会一运行就段错误, 所以在此再立一节来重新描述.

本节只是稍微做了一些简化与改进:

具体实施方法如下:

3.1 增加hook代码

利用gcc的一个名为-finstrument-functions的编译选项, 再加上一些我们自己的处理, 就可以达到跟踪进出全部函数调用流程的目的.

首先在src/core/nginx.c中加入如下代码:


    #include <stdio.h>
    
    FILE *my_debug_fd;
    
    #define MY_DEBUG_FILE_PATH "./mydebug.log"
    static int my_debug_flag = 1;
    
    #define open_my_debug_file()  \
        (my_debug_fd = fopen(MY_DEBUG_FILE_PATH, "a"))
    
    #define close_my_debug_file()  \
        do {  \
            if (NULL != my_debug_fd) {  \
                fclose(my_debug_fd);  \
            }  \
        }while(0)
    
    #define my_debug_print(args, fmt...) \
        do{  \
            if (0 == my_debug_flag) {  \
                break;  \
            }  \
            if (NULL == my_debug_fd && NULL == open_my_debug_file()) {  \
                printf("Err: Can not open output file.\n");  \
                break;  \
            }  \
            fprintf(my_debug_fd, args, ##fmt);  \
            fflush(my_debug_fd);  \
        }while(0)
    
    void __attribute__((no_instrument_function))
    enable_my_debug( void )
    {
        my_debug_flag = 1;
    }
    
    void __attribute__((no_instrument_function))
    disable_my_debug( void )
    {
        my_debug_flag = 0;
    }
    
    int __attribute__((no_instrument_function))
    get_my_debug_flag( void )
    {
        return my_debug_flag;
    }
    
    void __attribute__((no_instrument_function))
    set_my_debug_flag( int flag )
    {
        my_debug_flag = flag;
    }
    
    void __attribute__((no_instrument_function, constructor))
    main_constructor( void )
    {
        //Do Nothing
    }
    
    void __attribute__((no_instrument_function, destructor))
    main_destructor( void )
    {
        close_my_debug_file();
    }
    
    void __attribute__((no_instrument_function))
    __cyg_profile_func_enter( void *this, void *call )
    {
        my_debug_print("Enter\n%p\n%p\n", this, call);
    }
    
    void __attribute__((no_instrument_function))
    __cyg_profile_func_exit( void *this, void *call )
    {
        my_debug_print("Exit\n%p\n%p\n", this, call);
    }

由于这里将my_debug_flag全局变量初始就设置为了1, 所以不需要在main中增加任何使能代码, 就可以看到输出, 输出内容在: ./mydebug.log.

3.2 编译运行

只增加上面的代码, 还不行, 还要在编译的时候, 增加-finstrument-functions编译选项: 在objs/Makefile文件中的"CFLAGS = ..."这一行后面, 加入 -finstrument-functions这个选项. 本文作者在测试时, 发现只是这样还不行, 会段错误崩溃(针对1.10.3版本), 在google大婶那也没问到什么有用信息. 后来经过多次尝试, 后来发现 还要将CFLAGS这行中的"-O"这个选项去掉才行.

这样再编译运行的时候, 就会在mydebug.log中输出类似如下内容:

Enter
0x40fcf7
0x7f9a7d4ad830
Enter
0x4474ff
0x40fd35
Exit
0x4474ff
0x40fd35
Enter
0x410e3c
0x40fd5b
...

3.3 提取函数名

进行到这里, 只是得到函数地址信息, 可读性为0, 要想看到函数调用情况, 还要将其转为函数名, 本文也参照了上文所属文章中的方法.

(注意, 如果读者要自己修改, 需要注意一点, 本文中输入this与call的顺序, 与原文中描述的顺序是相反的, 不要弄错了顺序).

转换脚本(a2l.sh)如下:


    #!/bin/bash
    
    if [ $# != 2 ]; then
        echo -e "Usage: $0 nginx_elf addr_file"
        exit
    fi;
    
    elf=$1
    addr_file=$2
    
    indent_num=0
    cat ${addr_file} | while read line
    do
        read line1
        read line2
        this=`addr2line -e ${elf} -f $line1 -s`
        this_func=`echo ${this} | awk '{ print $1 }'`
        this_src=`echo ${this} | awk '{ print $2 }'`
    
        caller=`addr2line -e ${elf} -f $line2 -s`
        caller_func=`echo ${caller} | awk '{ print $1 }'`
        caller_src=`echo ${caller} | awk '{ print $2 }'`
            
        if [ "$line" = 'Enter' ]; then
            for (( i = 0; i < ${indent_num}; i++ )); do
                echo -e " \c"
            done
            #echo "${this_func}    ${this_src}        caller: ${caller_func}    ${caller_src}" # 如果需要全部信息(被调函数&源文件, 调用者函数&源文件), 放开此行, 注释掉下一行
            echo -e "${this_func}  \033[81G${caller_src}" # 调用者函数源文件定位到了81列, 如果发生了函数名覆盖, 可以增大该列数.
    
            indent_num=`expr ${indent_num} + 2`
        elif [ "$line" = 'Exit' ]; then
            indent_num=`expr ${indent_num} - 2`
        fi
    done

使用方法为:

./a2l.sh  usr/sbin/nginx  ./mydebug.log # usr/sbin/nginx 为编译出来的nginx可执行程序

得到输出, 形如:

main                                    ??:0
  ngx_strerror_init                     nginx.c:283
  ngx_get_options                       nginx.c:287
  ngx_time_init                         nginx.c:304
    ngx_time_update                     ngx_times.c:73
      ngx_gmtime                        ngx_times.c:120
      ngx_localtime                     ngx_times.c:135
  ngx_regex_init                        nginx.c:307
  ngx_log_init                          nginx.c:309
  ngx_ssl_init                          nginx.c:324
  ngx_save_argv                         nginx.c:333
  ngx_process_options                   nginx.c:337
    ngx_conf_full_name                  nginx.c:1018
      ngx_get_full_name                 ngx_conf_file.c:857
        ngx_test_full_name              ngx_file.c:27
  ngx_os_init                           nginx.c:341
    ngx_os_specific_init                ngx_posix_init.c:39
    ngx_init_setproctitle               ngx_posix_init.c:44
    ngx_cpuinfo                         ngx_posix_init.c:65
      ngx_cpuid                         ngx_cpuinfo.c:86
      ngx_cpuid                         ngx_cpuinfo.c:94

3.4 进一步精简输出

按照上述方法, 就已经得到树形的函数调用输出. 但是其中有大量琐碎的底层操作, 对于我们理解主流程来说没有太大作用, 可以将其去掉. 方法为: 将之前设置编译选项的 CFLAGS 中, 增加" -finstrument-functions-exclude-file-list=xxx.c"选项, 这样就可以将某些文件排除在外. 例如, 作者的编译配置为:

CFLAGS =  -pipe  -W -Wall -Wpointer-arith -Wno-unused-parameter -Werror -g -finstrument-functions \
    -finstrument-functions-exclude-file-list=ngx_string.c,ngx_alloc.c,ngx_palloc.c,ngx_array.c,ngx_array.h,ngx_hash.c

然后再进行编译运行, 注意需要重新configure, 否则不会重新编译.o. 不过这里要注意, 重新configure的时候, 会覆盖掉已经修改过的Makefile. 所以最好将修改与配置编译写成一个脚本.

4 Nginx log系统使用

4.1 error_log

语法:

error_log file/stderr/syslog:server=address[,parameter=value] [debug/info/notice/warn/error/crit/alert/emerg];

默认值: error_log logs/error.log error;

配置段: main, http, server, location

注意: 在configure中加入--with-debug选项, 才能看到debug内容. 否则即使配置文件中设置了error_log也只能看到很少的信息.

5 参考