zoukankan      html  css  js  c++  java
  • Apache coredump 问题发现与解决记录

    Apache coredump 问题发现与解决记录

    背景

    组内的开发机原来是 Nginx + Tomcat 环境拓扑,但线上是 Apache + Tomcat,为了与线上环境保持一致,要求将开发机上的 Nginx 替换为 Apache。目前开发机上基于域名的虚拟机有dk.qq.com和dk.oa.com,需要支持 https 协议。利用线上的 Apache,轻松将其部署到开发机上。

    发现问题

    按照 Nginx 的原有配置,将 Apache 的 http 和 https 相关配置写完之后,使用 apachectl start 成功启动了 httpd 服务。于是在 chrome 浏览器上尝试访问,访问 http 网址一切正常,但是访问 https://dk.qq.com/AmarSCFOnline/login.jsp,网页提示以下错误:

    无法访问此网站
    dk.qq.com 意外终止了连接。
    ERR_CONNECTION_CLOSED
    

    第一件要做的事就是查看 Apache 日志 /usr/local/apache2/log,发现了下面这些日志记录:

    [Sat Aug 19 13:54:40 2017] [notice] child pid 31117 exit signal Segmentation fault (11)
    [Sat Aug 19 13:54:40 2017] [notice] child pid 31118 exit signal Segmentation fault (11)
    [Sat Aug 19 13:54:40 2017] [notice] child pid 31121 exit signal Segmentation fault (11)
    [Sat Aug 19 13:54:40 2017] [notice] child pid 31122 exit signal Segmentation fault (11)
    [Sat Aug 19 13:54:40 2017] [notice] child pid 31123 exit signal Segmentation fault (11)
    [Sat Aug 19 13:54:40 2017] [notice] child pid 31124 exit signal Segmentation fault (11)
    [Sat Aug 19 13:54:40 2017] [notice] child pid 31125 exit signal Segmentation fault (11)
    

    httpd 进程出现段错误,每次访问都这样,于是使用 gdb 进行调试,以获取更加详细的有用信息。

    基本思路:将 gdb 附加到其中一个 httpd 子进程,并重新加载,等待崩溃,然后查看函数调用栈。

    首先选择要附加的 httpd 子进程:

    ps -ef | grep httpd
    
    nobody   31084 31082  0 13:39 ?        00:00:00 /usr/local/httpd-2.2.27/bin/httpd -k start
    nobody   31085 31082  0 13:39 ?        00:00:00 /usr/local/httpd-2.2.27/bin/httpd -k start
    

    现在将 gdb 附加到 PID 为 31084 的 httpd 子进程上:

    [root@dev157 /usr/local/apache2/logs]# gdb 
    (gdb) attach 31084
    Attaching to process 31084
    (gdb) c
    Continuing.
    

    接下来是重现刚刚的错误,这里的做法非常简单,只需要不断地刷新网页直到刚刚指定的进程 core dump 了为止。如果是非常难以重现的错误,可以修改 Apache 配置,让其只使用一个子进程处理请求,添加的配置如下:

    StartServers 1
    MinSpareServers 1
    MaxSpareServers 1
    

    当 gdb 附加的子进程 core dump后:

    (gdb) c
    Continuing.
    
    Program received signal SIGSEGV, Segmentation fault.
    0x00007f04bc4f94cb in SSL_CTX_ctrl () from /lib64/libssl.so.1.0.0
    
    (gdb) bt
    #0  0x00007f04bc4f94cb in SSL_CTX_ctrl () from /lib64/libssl.so.1.0.0
    #1  0x000000000047978b in ssl_find_vhost (servername=<optimized out>, c=<optimized out>, s=0x7c2828) at ssl_engine_kernel.c:2106
    #2  0x00000000004794d2 in ssl_callback_ServerNameIndication (ssl=<optimized out>, al=<optimized out>, mctx=<optimized out>) at ssl_engine_kernel.c:2022
    #3  0x00007f04bc4ea859 in ssl_check_clienthello_tlsext_early () from /lib64/libssl.so.1.0.0
    #4  0x00007f04bc4d4ebb in ssl3_get_client_hello () from /lib64/libssl.so.1.0.0
    #5  0x00007f04bc4d96fd in ssl3_accept () from /lib64/libssl.so.1.0.0
    #6  0x00007f04bc4e73e8 in ssl23_accept () from /lib64/libssl.so.1.0.0
    #7  0x0000000000477719 in ssl_io_filter_connect (filter_ctx=0x7fc620) at ssl_engine_io.c:1154
    #8  0x0000000000478677 in ssl_io_filter_input (f=0x80f738, bb=0x807228, mode=<optimized out>, block=<optimized out>, readbytes=<optimized out>) at ssl_engine_io.c:1407
    #9  0x0000000000435e42 in ap_rgetline_core (s=0x805cb0, n=8192, read=0x7ffd7e933cc0, r=0x805c80, fold=0, bb=0x807228) at protocol.c:231
    #10 0x000000000043687e in read_request_line (bb=0x807228, r=0x805c80) at protocol.c:596
    #11 ap_read_request (conn=0x7fbe20) at protocol.c:921
    #12 0x0000000000485cb0 in ap_process_http_connection (c=0x7fbe20) at http_core.c:183
    #13 0x0000000000449bf0 in ap_run_process_connection (c=0x7fbe20) at connection.c:43
    #14 0x000000000049ca28 in child_main (child_num_arg=<optimized out>) at prefork.c:667
    #15 0x000000000049cd24 in make_child (s=0x734190, slot=0) at prefork.c:768
    #16 0x000000000049d02e in startup_children (number_to_start=50) at prefork.c:786
    #17 ap_mpm_run (_pconf=<optimized out>, plog=<optimized out>, s=<optimized out>) at prefork.c:1007
    #18 0x000000000042eb74 in main (argc=3, argv=0x7ffd7e9341d8) at main.c:753
    

    从上面可以看出 httpd 挂在了握手过程, ssl3_get_client_hello 服务器收到了浏览器的请求,ssl_check_clienthello_tlsext_earlyssl_callback_ServerNameIndicationssl_find_vhost 可以知道服务器在向浏览器发送服务器证书之前,在进行 TLS SNI 协商,目的是在相同地址支持多个基于域名的虚拟主机的前提下,使服务器更早的切换到正确的虚拟域,并且发送给浏览器包含正确名字的数字证书。

    根据 Openssl 官方文档的描述:

    The SSL_*_ctrl() family of functions is used to manipulate settings of the SSL_CTX and SSL objects.

    看来是 httpd 在使用 SSL_CTX_ctrl 切换 SSL 对象到 SSL_CTX 的时候挂了。

    这时问题遇到了难点,SSL_CTX_ctrl ,和 SSL 相关的有很多,SSL 协议版本和包含 SSL_CTX_ctrl 的 libssl.so 版本等等。

    偶然情况下,使用 IE 浏览器访问 https://dk.qq.com/AmarSCFOnline/login.jsp,竟然可以正常访问,觉得是 IE 和 chrome 使用的 SSL 版本不一样,于是使用 fiddler 进行抓包分析,发现两者在握手时没什么区别,唯一的区别就是使用的 SSL 版本不一样:

    抓包数据中发现 IE 使用到的 SSL 版本有很多,

    Version: 3.0 (SSL/3.0)
    Version: 3.1 (TLS/1.0)
    Version: 3.3 (TLS/1.2)
    

    chrome的抓包数据

    Version: 3.3 (TLS/1.2)
    

    过程中还发现 chrome 已经默认禁用 SSLv3 支持,而且无法修改使用的 SSL 版本,只能使用 TLS/1.2。通过修改 IE Internet选项-高级-安全使用的 SSL 版本,发现只要使用 TLS/1.2 协议去访问,后台的 httpd 服务就会挂。于是查看了 Apache 的 SSL 配置,是已经开启支持 TLS/1.2 的了:

    SSLProtocol All -SSLv2 -SSLv3
    

    httpd-2.2.27 支持 TLS/1.2,既然配置已经开启了支持,还是不行,那应该是 openssl 库不支持 TLS/1.2 的问题了。

    查找了 openssl 的 changelog 文档,TLS 1.2 是在 OpenSSL 1.0.1 以后版本加入的,而 apache 使用的 libssl.so 是 1.0.0 版本,所以不支持 TLS 1.2 协议。

    #0 0x00007f04bc4f94cb in SSL_CTX_ctrl () from /lib64/libssl.so.1.0.0

    解决问题

    方法 1

    apache 使用的 libssl.so 是 1.0.0 版本,不支持 TLS 1.2 协议,所以直接暴力一点:

    mv libssl.so.1.0.0 libssl.so.1.0.0.bak
    mv libssl.so.1.0.2 libssl.so.1.0.0
    

    重启 Apache,出现错误:

    error while loading shared libraries: libcrypto.so.1.0.2: cannot open shared object file: No such file or directory

    找不到 libcrypto.so.1.0.2,于是拷贝了一个libcrypto.so.1.0.2 到 /lib64:

    cp libcrypto.so.1.0.2 libcrypto.so.1.0.0
    

    这会导致一个问题,就是原来的 libssl.so.1.0.0 被删除,会导致其他使用 libssl.so.1.0.0 程序的兼容问题,但是问题不是很大,libssl.so.1.0.2 的主版本号和次版本号与原来的一样,只是发行版本号不一样而已,应该可以向下兼容 libssl.so.1.0.0

    方法 2

    重新编译一个 Apache,但是它使用的 ssl.so 的 soname 必须是 libssl.so.1.0.2,这样只要将 libssl.so.1.0.2 拷贝到开发机上即可支持 TLS 1.2;

    这个方法目前是最好,对开发机的影响最小。

    总结

    整个过程发现了很多潜在的坑,同时也学到了很多,这里一一总结一下。

    Linux 程序编译链接动态库版本问题

    ldd 命令

    涉及命令:ldd
    ldd 简介:打印程序或者库文件所依赖的共享库列表

    涉及选项:

    1. --version:打印指令版本号;
    2. -v:详细信息模式,打印所有相关信息;
    3. -u:打印未使用的直接依赖;
    4. -d:执行重定位和报告任何丢失的对象;
    5. --r:执行数据对象和函数的重定位,并且报告任何丢失的对象和函数;
    6. --help:显示帮助信息。
      其他详细说明请参阅 man 说明。

    示例情景:

    ldd httpd
    	linux-vdso.so.1 =>  (0x00007ffeadb35000)
    	/$LIB/libonion.so => /lib64/libonion.so (0x00007fa6b4534000)
    	libssl.so.1.0.0 => /lib64/libssl.so.1.0.0 (0x00007fa6b41ae000)
    	libcrypto.so.1.0.0 => /lib64/libcrypto.so.1.0.0 (0x00007fa6b3cf4000)
    	...
    

    左边是依赖的动态库名字,右边是链接指向的文件。

    动态库的编译和 soname

    根据 ldd 的结果,httpd 运行时总会去查找加载 libssl.so.1.0.0 等动态库文件,这些动态库文件的名字即 soname,是怎么指定的呢?

    动态库在编译的时候会通过 -soname 指定动态库的真正名字,它存在动态库的二进制数据里面。编译命令示例如下,这时生成的libhello.so.0.0.1 动态库的 Library soname 是 libhello.so.0:

    gcc hello.c -fPIC -shared -Wl,-soname,libhello.so.0 -o libhello.so.0.0.1
    

    除了在编译时指定 soname,我们还可以通过 readelf 命令查看指定动态库的 Library soname,命令示例如下:

    readelf -d libssl.so.1.0.0
    
    Dynamic section at offset 0x6b128 contains 27 entries:
      Tag        Type                         Name/Value
     0x000000000000000e (SONAME)             Library soname: [libssl.so.1.0.2]
    

    我们编译一个需要动态库的程序时,需要通过 -l 选项指定动态库,-L 指定动态库所在目录,命令示例如下:

    gcc main.c -L. -lhello -o main  
    

    在当前目录下,需要存在 libhello.so 文件才能编译过去,也就是说在编译的时候,链接器会去找它依赖的 libxxx.so 这样的文件,因此必须保证 libxxx.so 的存在。通过 ldd main 和 readelf -d libhello.so 可以发现, main 依赖的 libhello 名字和 libhello.so soname 是一致的,也就是说,main 依赖的动态库文件名字来自动态库的 soname。

    动态库版本更新,如果只是小改动,则无需修改 soname,但 so 文件名(.so.a.b.c) 可以增大小版本号,然后再将 soname 软链接到真正的 so 文件。

    线上 Apache 坑

    开发机使用的 Apache 是在线上直接打包的,通过 ldd 发现其依赖的 ssl.so 的 soname 是 libssl.so.1.0.0,也就是说,线上版本 Apache 编译时使用的 ssl.so 版本较低,不支持 TLS 1.2,我也不知道线上 Apache 是怎么做到支持 HTTPS 的,ssl.so 版本明明不对。

    为了解决刚刚的问题,有两种方法:

    1. 重新编译一个 Apache,但是它使用的 ssl.so 的 soname 必须是 libssl.so.1.0.2,这样只要将 libssl.so.1.0.2 拷贝到开发机上即可支持 TLS 1.2;
    2. 暴力使用 libssl.so.1.0.2 去替换开发机上的 libssl.so.1.0.0,这会导致一个问题,就是原来的 libssl.so.1.0.0 被删除,会导致其他使用 libssl.so.1.0.0 程序的兼容问题,但是问题不是很大,libssl.so.1.0.2 的主版本号和次版本号与原来的一样,只是发行版本号不一样而已,应该可以向下兼容 libssl.so.1.0.0

    浏览器

    IE 10 浏览器可以修改 HTTPS 使用的 SSL 协议,包括 SSLv2,SSLv3,TLS 1.0,TLS 1.1,TLS 1.2;而 chrome 是不支持修改使用的 SSL 协议版本的,默认支持 TLS 1.2,Chrome 40 已完全禁用 SSLv3。

  • 相关阅读:
    百度地图api
    白岩松视频
    rails3 unicorn部署
    rails3 unicorn部署
    rails3 unicorn部署
    nginx geoip 模块实现地区性负载均衡
    workingwithrails
    rails部署方案
    rails3使用cucumber和rspec进行测试
    rails3 unicorn部署
  • 原文地址:https://www.cnblogs.com/cposture/p/9029020.html
Copyright © 2011-2022 走看看