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. 阿里云消息队列(MQ)服务

    A.首先在阿里云上申请消息队列MQ服务: B.然后创建一个Topic(主题,一级主题):然后创建生产者与消费者: C.不过此时还没有结束 ,还需要创建一个AccessKey和AccessSecret( ...

  2. PHP CURL获取页面内容输出例子

    使用PHP curl获取页面内容或提交数据,有时候希望返回的内容作为变量储存,而不是直接输出.这个时候就必需设置curl的CURLOPT_RETURNTRANSFER选项为1或true. 1.curl ...

  3. 邮件服务器 postfix

    背景介绍 邮件服务器普遍需要一个主机名来使得mail from 以"账号@主机名"方式显示.由于外网上垃圾邮件太多,现在已不使用ip发邮件,很多网络供应商都会对来源不明的邮件进行限 ...

  4. 菜鸡谈OO 第二单元总结

    “欢迎来到(玄学)多线程的新世界” Homework1 单部傻瓜电梯调度 Part1 多线程设计策略 第一次学到了线程这个概念,与之前的编程体验大有不同.最大的区别在于从原本的线性发生程序变成了多个行 ...

  5. Idea集成maven插件

    学习目标 1.正确在idea上安装maven 2.安装后使用的基本操作 3.回顾安装步骤 安装过程 设置安装后自动下载功能 maven一键构建概念 我们的项目,往往都要经历编译. 测试. 运行. 打包 ...

  6. framework7 入门(数据获取和传递)

    数据获取 framework7自带request方法 , var app = new Framework7({...});app.request(parameters) 或者 Framework7.r ...

  7. f12 headers 变字典快捷方式

  8. 如何安装并且使用jmeter进行简单的性能测试

    Jmeter  介绍 Jmeter  是一款使用Java开发的,开源免费的,测试工具, 主要用来做功能测试和性能测试(压力测试/负载测试). 而且用Jmeter 来测试 Restful API, 非常 ...

  9. sublime text3 在 14.04.1-Ubuntu 下的中文输入

    1.安装 fcitx sudo add-apt-repository ppa:fcitx-team/nightly // 添加FCITX仓库. sudo apt-get update // 更新仓库. ...

  10. Win10系统下在国内访问Tensorflow官网

    1.修改hosts文件 目录:     C:\Windows\System32\drivers\etc 添加: #TensorFlow start64.233.188.121 www.tensorfl ...