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。

Apache coredump 问题发现与解决记录的更多相关文章

  1. ie6,ie7,ie8 css bug兼容解决记录

    ie6,ie7,ie8 css bug兼容解决记录 转载自:ie6,ie7,ie8 css bug兼容解决记录 - 前端开发 断断续续的在开发过程中收集了好多的bug以及其解决的办法,都在这个文章里面 ...

  2. Apache服务无法启动的解决方法

    apache服务无法启动的解决方法 在配置apache的时候,把apache安装为服务myweb,用apacheMonitor启动myweb发现无法启动,提示:the requested operat ...

  3. kylin_异常_02_java.lang.NoClassDefFoundError: org/apache/hadoop/hive/conf/HiveConf 解决办法

    一.异常现象 在kylin的web管理界面,设置hive数据源时,报错: 查找kylin的日志时发现,弹出提示框的原因是因为出现错误: ERROR [http-bio-7070-exec-10] co ...

  4. CentOS 8.2远程连接vncserver升级后1.10.1无法启动解决记录

    CentOS 8.2远程连接vncserver升级后1.10.1无法启动解决记录   问题起源:手贱yum upgrade,重启服务器后无法使用vnc viewer远程连接 查看状态 # system ...

  5. 我是怎么发现并解决项目页面渲染效率问题的(IE调试工具探查器的使用)

    #我是怎么发现并解决项目页面渲染效率问题的(IE调试工具探查器的使用) ##背景 之前的项目中,有很多的登记页面,一般都有100-200甚至更加多的字段,而且还涉及到字典.日期及其他效果的显示,载入时 ...

  6. 解决记录:win10 无法安装VS2017,visual studio installer下载进度始终为0

    问题描述:win10 下无法安装VS2017,visual studio installer下载进度始终为0,点击取消按钮后,也没有反应,visual studio installer也关闭不掉: 具 ...

  7. 错误解决记录------------rhel安装Mysql软件包依赖 mariadb组件

    错误解决记录------------软件包依赖 mariadb组件 错误信息: 错误:软件包:akonadi-mysql-1.9.2-4.el7.x86_64 (@anaconda) 需要:maria ...

  8. Android开发-Android Studio问题以及解决记录

    [Android开发] Android Studio问题以及解决记录   http://blog.csdn.net/niubitianping/article/details/51400721 1.真 ...

  9. java.lang.NoClassDefFoundError: org/apache/juli/logging/LogFactory的解决

    java.lang.NoClassDefFoundError: org/apache/juli/logging/LogFactory的解决          博客分类: 问题 ApacheJavaTo ...

随机推荐

  1. socket error:10053

    系统提示:10053,由于超时或其它失败,连接中止 服务端和客户端并没有出现连接错误或主动关闭连接 发生这个错误的原因往往是连接上了,但是长时间没有通信,所以连接被挂起了 防止的办法就是自己设计心跳包 ...

  2. jquery获取一组文本框的值

    $("#btn5").click(function()  {    var str="";$("[name='checkbox'][checked]& ...

  3. Jquery weui picker 支持label和value

    万年没更新了. 最近用jquery weui. 在使用picker时需要一些问题. 就是让picker 显示label, 但是取值的时候取value用于存储. 官网例子如下 Jquery-weui 官 ...

  4. [AtCoder 2702]Fountain Walk - LIS

    Problem Statement In the city of Nevermore, there are 108 streets and 108 avenues, both numbered fro ...

  5. dc的博客翻修计划启动

    紫书大部分学习完毕,等待一轮补完计划: 白书正在攻略中,进度百分之30: 博客翻修计划启动,我会在本学期内逐渐写系统性的学习笔记,我学习时遇到的困难的地方会有说明: 每个部分一定会放上经过验证的实现: ...

  6. DCOS实践分享(1):基于图形化模型设计的应用容器化实践

    2015年11月29日,Mesos Meetup 第三期 - 北京技术沙龙成功举行.本次活动由数人科技CTO 肖德时 和 Linker Networks 的 Sam Chen 一起组织发起. 在这次m ...

  7. [Swift]LeetCode171. Excel表列序号 | Excel Sheet Column Number

    Given a column title as appear in an Excel sheet, return its corresponding column number. For exampl ...

  8. [Swift]LeetCode719. 找出第 k 小的距离对 | Find K-th Smallest Pair Distance

    Given an integer array, return the k-th smallest distance among all the pairs. The distance of a pai ...

  9. [Swift]LeetCode839. 相似字符串组 | Similar String Groups

    Two strings X and Y are similar if we can swap two letters (in different positions) of X, so that it ...

  10. Redux源码学习笔记

    https://github.com/reduxjs/redux 版本 4.0.0 先了解一下redux是怎么用的,此处摘抄自阮一峰老师的<Redux 入门教程> // Web 应用是一个 ...