ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

TongWeb 启动报错 undefined symbol EVP_aes_128_gcm:OpenSSL 排查与解决

TongWeb 启动报错 undefined symbol EVP_aes_128_gcm:OpenSSL 排查与解决 1. 报错现场一行输出把TongWeb挡在门外1.1 从终端里看到的错误长什么样大约两个星期前我在一台CentOS 7.9服务器上部署TongWeb执行bin/startserver.sh刚跑过初始化日志终端就弹出一行刺眼的错误/opt/TongWeb/lib/tongweb_crypto.so: undefined symbol: EVP_aes_128_gcm。进程随即退出控制台连Java堆栈都没给留下我对着屏幕愣了几秒。这是一个典型的动态库符号解析失败和业务配置、license授权都没有关系问题出在TongWeb的native加密库和系统OpenSSL版本之间。如果你也遇到过同样报错或者正打算把TongWeb装到老版本Linux系统上这篇文章里的排查思路可以直接照抄。同一个报错在不同版本上会有不同马甲。有的机器上输出libcrypto.so.1.1: cannot open shared object file有的输出symbol lookup error: libsetcrypto.so: undefined symbol: EVP_aes_128_gcm还有的把错误写进了hs_err_pidXXXX.log只在JVM崩溃日志里留下一句Failed to load native library。如果你遇到的是这一类错误不要急着去查端口和JAVA_HOME先往动态库方向想。我那次的具体日志大致是[userlocalhost bin]$ ./startserver.sh ... Load native library: /opt/TongWeb/lib/tongweb_crypto.so /opt/TongWeb/lib/tongweb_crypto.so: undefined symbol: EVP_aes_128_gcm这个画面值得记住它和Java应用常见的NoClassDefFoundError、ClassNotFoundException是完全不同的两类问题。Java异常说明的是字节码层面的问题只要JVM还在就能抓到堆栈而这种undefined symbol是操作系统动态链接器抛出来的Java层面根本拦不住进程一遇到直接终止。所以排查手段也要跟着换一套不能再盯着logs/tongweb.log死磕。1.2 先划清故障边界遇到启动失败我的习惯是冷静三分钟先确认这不是配置文件或安装目录的问题。我的检查顺序是先用ps -ef | grep tongweb确认没有残留进程占着端口再用tail -n 100 logs/tongweb.log看日志最后停在哪一步最后检查bin/startserver.sh里有没有被自己改过什么特殊参数比如手滑设置的JAVA_HOME或者临时的LD_LIBRARY_PATH。如果日志停在“加载native库”附近同时终端出现undefined symbol那就不用再怀疑应用层配置了。我当时把tongweb_crypto.so这个文件名记下来然后用find /opt/TongWeb -name *.so把TongWeb自带的动态库都列了一遍果然在lib/security目录下还有好几个类似的加密库。这类模块负责HTTPS证书解析、密码学算法调用、通信安全初始化是TongWeb和OpenSSL之间最直接的桥梁。这里有一个容易被新手忽略的细节TongWeb安装包的lib目录下往往自带libcrypto.so.1.1、libssl.so.1.1这些文件但并不意味着启动时一定会用它们。动态链接器对库的搜索顺序很挑剔一旦某个环境变量里的路径排在前面自带的库就可能被绕过。这也是为什么我建议大家不要仅凭“安装包里看得到文件”来判断问题。1.3 为什么不是一执行就崩还有一件事让人困惑既然动态库有问题为什么TongWeb能先打几行日志再崩因为Linux共享库默认使用延迟绑定lazy binding。所谓延迟绑定就是函数符号不在一加载时就全部解析而是等到真正调用到某个函数的那一刻动态链接器才去全局符号表里找实现。TongWeb启动前期可能只初始化了少量功能还没碰到EVP_aes_128_gcm等到初始化HTTPS或者加密连接模块时才调用它这时候符号一查不到进程直接终止。这带来的排查影响很大你看到的崩溃点不一定是真正的错误源头真正的错误在“这个函数第一次被调用”的地方。因此排错时不能只看崩溃前的最后一行业务日志而要把视线放到动态库搜索上。理解了延迟绑定就不会奇怪为什么startserver.sh有时候能打印出“TongWeb is running”字样结果访问HTTPS端口时又崩一次。这也解释了为什么有些文章里建议“多访问几次管理端页面”才能复现问题——本质上就是还没触发到加密函数。2. undefined symbol 的底层逻辑符号表与OpenSSL版本2.1 动态链接器到底在干什么要解决这个报错必须理解Linux动态链接的基本机制。每个ELF格式的共享库都有一个动态符号表用来记录对外导出的函数、变量和它们的内存位置。程序A在编译时引用了库B的函数不会把B的实现代码拷进A而是在A的动态符号表里留下一个“未定义引用”。真正运行起来后操作系统里的动态链接器会拿着这个未定义引用去已经加载到进程地址空间的各个共享库里找同名导出符号。undefined symbol: EVP_aes_128_gcm的完整含义是某个TongWeb的native模块需要EVP_aes_128_gcm这个函数但进程里所有已加载共享库的导出符号表中没有一个名字完全匹配EVP_aes_128_gcm。注意动态链接器和人类不同它不关心“功能类似的函数”只认字符串名字。名字对不上绝对不妥协宁可整个进程终止。这一点在很多脚本语言里完全无法理解因为Java、Python通常会给出异常对象或null而C/C的底层容错本身就是零。搜索顺序也有讲究大致是ELF文件里记录的DT_RPATH已废弃但还存在→ 环境变量LD_LIBRARY_PATH→/etc/ld.so.cache中的系统缓存 →/lib64、/usr/lib64这些默认目录。TongWeb如果通过启动脚本设置LD_LIBRARY_PATH实际顺序又和安装包自带的RPATH叠加最终结果很难一眼看出来。这也是为什么处理这类问题必须靠命令把实际加载路径打出来而不是靠猜。光看报错信息里的文件名根本判断不了是哪个路径下的库被加载了。2.2 EVP_aes_128_gcm 是什么时代的符号EVP_aes_128_gcm属于OpenSSL EVP接口是AES-128-GCM加密算法的高级封装。OpenSSL从1.1.0开始统一并优化了EVP层把一批原本分散在低级API里的算法功能收敛成EVP_*形式的接口并把符号正式对外导出。如果你机器上的libcrypto.so是OpenSSL 1.0.x甚至更早的0.9.8那里面大概率没有EVP_aes_128_gcm这个函数名。TongWeb的加密模块如果按1.1.x API编译它一调用这个符号老版本库只能两手一摊。用一条命令就能验证某库是否包含该符号nm -D /usr/lib64/libcrypto.so.1.1 | grep EVP_aes_128_gcm如果看到T EVP_aes_128_gcm说明这个库能提供该符号如果没有任何输出那这个库就是导致报错的“嫌疑犯”。可以多检查几个路径比如/usr/local/lib/libcrypto.so、TongWeb自带的lib目录、$JAVA_HOME/jre/lib/amd64下的同类文件对比一下哪个有符号、哪个没有。这一套检查做下来基本能确认“该找谁”而不是“该改谁”。2.3 为什么换一台机器结果就不同很多人被这个错误折磨是因为同样的TongWeb安装包在别的服务器上秒启换到自己的机器上就报错。我见过的几类典型差异有操作系统自带的OpenSSL版本不同CentOS 6/7多为1.0.xCentOS 8/Ubuntu 18.04多为1.1.x或3.x/etc/ld.so.conf.d/里配置的自定义库路径不同服务器上是否安装过其他中间件或数据库它们可能往LD_LIBRARY_PATH里塞了旧版OpenSSL还有就是JDK安装方式不同某些JDK发行版的lib/amd64下也自带了一套OpenSSL库。这几个因素叠加动态链接器最终搜到的libcrypto.so很可能来自完全不同的来源。同样的二进制包在不同环境表现不同就是因为“当前环境里优先加载了哪个库”这件事并不一致。换句话说这不是TongWeb本身的bug而是部署环境和安装包预期的运行环境不一致。理解这一点排查时就会少很多“重装试试”的冲动。后面你会发现这类问题基本都能在几十分钟内定位真正花时间的往往是环境变量里藏了一条你完全没注意到的库路径。3. 把真正加载的 libcrypto 逮出来完整排查链路3.1 用 readelf 看依赖关系正式排错的第一步是确认报错模块希望的OpenSSL版本。你需要读取这个共享库的动态依赖段而不是只看文件名。以tongweb_crypto.so为例执行下面的 readelf 命令输出里会列出它依赖的共享库清单。这一步能直接告诉你TongWeb是按哪个SO名字去找OpenSSL的省去后面瞎猜readelf -d /opt/TongWeb/lib/tongweb_crypto.so | grep NEEDED输出通常会出现0x0000000000000001 (NEEDED) Shared library: [libcrypto.so.1.1] 0x0000000000000001 (NEEDED) Shared library: [libssl.so.1.1]这说明它要的是SO名字为libcrypto.so.1.1的库。注意SO名字和具体文件名不一定完全一样比如libcrypto.so.10是1.0.2系列libcrypto.so.1.1是1.1系列libcrypto.so.3是3.x系列。TongWeb编译时链接了哪个SO名运行时就按这个名字去找。如果第一行的结果是libcrypto.so.10那问题就成了“系统里没有提供1.0.2兼容库”解决方向完全不同。3.2 用 ldd 看当前解析结果接着用ldd查看当前环境的解析结果。这里一定要把TongWeb自己的lib目录也加进去否则系统默认搜索路径里可能直接not found误导你以为是库缺失ldd /opt/TongWeb/lib/tongweb_crypto.so如果某一行显示libcrypto.so.1.1 not found那说明系统路径和TongWeb自带的路径里都没有这个库——这是最简单的场景补一个兼容库即可。如果显示了一个具体路径比如/usr/local/lib/libcrypto.so.1.1那就需要继续判断这个路径里的库是不是真的包含EVP_aes_128_gcm。很多人到这里就停了觉得“反正找到了库”实际上还需要更深入看符不符合要求。3.3 用 LD_DEBUG 看最终加载顺序ldd只能展示主程序视角下的依赖解析不一定等于TongWeb进程最终加载的库集合。尤其当启动脚本内部会重新设置LD_LIBRARY_PATH或者后续通过dlopen加载其他模块时实际加载顺序会动态变化。为了看清最终“真凶”我建议这样跑export LD_DEBUGlibs,bindings /opt/TongWeb/bin/startserver.sh输出会非常多但重点搜索libcrypto和EVP_aes_128_gcm相关的行。我那次就看到了两行关键信息calling init: /usr/lib64/libcrypto.so.10 trying file/opt/TongWeb/lib/libcrypto.so.1.1虽然TongWeb自带的1.1库也在搜索列表里但系统目录下的1.0.2库已经被先加载并初始化。后续TongWeb的加密模块再去解析EVP_aes_128_gcm时进程地址空间里已经有一份libcrypto.so.10的导出表动态链接器并没有去正确的新库继续寻找最终符号解析失败。这种“先到先得”造成的冲突是LD_LIBRARY_PATH里路径顺序排错最常见的形态。3.4 检查环境变量与系统缓存接下来沿着环境变量这条线继续挖。你要看的是当前shell环境值、系统库缓存配置和所有OpenSSL库的实际路径echo $LD_LIBRARY_PATH cat /etc/ld.so.conf.d/*.conf ldconfig -p | grep crypto我当时发现服务器的/etc/profile.d/下有个其他软件留下的初始化脚本里面写了export LD_LIBRARY_PATH/usr/local/openssl-1.0/lib这个目录里的libcrypto.so是旧版本编译产物正好排在TongWeb自带库的前面。动态链接器优先采纳了它TongWeb的命令行手工启动当然也会受影响只是有些目录加载顺序呈现的结果是晚几分钟崩。这里还要提醒一句LD_LIBRARY_PATH的值是“追加”的后写入的路径会拼到已有值后面。但动态链接器是按照字符串从左到右找库的所以排在越前面的目录优先级越高。如果值本身被某个全局脚本设置得很长TongWeb在startserver.sh里再往后面追加自己的目录优先级反而更靠后。这也是很多人在本地怎么调都正常一放到生产环境就出问题的根源。3.5 一条命令快速验证方向如果不想一开始就输出一大堆LD_DEBUG可以用一个低成本的临时方案验证“是否环境变量干扰”env -u LD_LIBRARY_PATH /opt/TongWeb/bin/startserver.sh-u是unset的意思在当前进程里临时清掉这个变量再启动。如果错误消失或者错误信息变了那基本可以确定这个变量就是罪魁祸首。我建议把这种验证作为标准操作之一因为它的副作用最小不用改任何文件哪怕判断错了也不会影响系统。等确认方向后再去仔细调整各种配置会比直接改启动脚本稳妥得多。4. 让符号对得上三个解决方案与验证方法4.1 方案A独立编译一个OpenSSL 1.1库如果TongWeb要的是libcrypto.so.1.1而系统里只有1.0.2最稳妥的办法不是动系统库而是单独编译一份OpenSSL 1.1.1到独立目录。以OpenSSL 1.1.1w为例wget https://www.openssl.org/source/old/1.1.1/openssl-1.1.1w.tar.gz tar xzf openssl-1.1.1w.tar.gz cd openssl-1.1.1w ./config --prefix/usr/local/openssl111 --openssldir/usr/local/openssl111 shared make -j$(nproc) make install编译完成后/usr/local/openssl111/lib目录下会有libcrypto.so和libcrypto.so.1.1。然后在TongWeb启动脚本中把该目录加到LD_LIBRARY_PATH的最前面export LD_LIBRARY_PATH/usr/local/openssl111/lib:$LD_LIBRARY_PATH执行ldconfig -p | grep /usr/local/openssl111确认目录生效再启动TongWeb。个人强烈不建议把这个路径写进/etc/ld.so.conf.d/全局文件也不要在/usr/lib64下做覆盖软链。系统的curl、yum、ssh都可能依赖原有OpenSSL版本全局替换会让一大批组件出问题。独立目录加上启动脚本内设置影响范围只限于TongWeb进程可维护性最好。4.2 方案B修正TongWeb自身库的加载顺序如果TongWeb安装包里已经自带了正确的libcrypto.so.1.1就不必编译新库只要把搜索顺序纠正过来。先看启动脚本怎么设置环境变量grep -n LD_LIBRARY_PATH /opt/TongWeb/bin/startserver.sh常见写法类似LD_LIBRARY_PATH$TONGWEB_HOME/lib:$TONGWEB_HOME/lib/security:$LD_LIBRARY_PATH export LD_LIBRARY_PATH这里TongWeb自己的目录已经写在前面但问题往往出现在$LD_LIBRARY_PATH这个变量被展开之前外部环境早就塞进去了一堆路径。处理办法有两个一是把TongWeb目录再在脚本里最前面写一次二是如果确定当前环境变量里没有其他必须依赖的库可以直接清空再设置LD_LIBRARY_PATH/opt/TongWeb/lib:/opt/TongWeb/lib/security export LD_LIBRARY_PATH如果不想清空也可以用LD_PRELOAD临时强制加载正确的库来验证方向。LD_PRELOAD的优先级比LD_LIBRARY_PATH还高能让动态链接器在所有正常搜索开始之前先把指定的库加载进来export LD_PRELOAD/usr/local/openssl111/lib/libcrypto.so.1.1 /opt/TongWeb/bin/startserver.sh注意这只是验证手段不应该作为长期生产配置因为LD_PRELOAD会影响Java进程里所有模块的库解析副作用较难评估。验证完最终还是要落到启动脚本变量的调整上。如果担心脚本被升级覆盖建议把改动集中放在一个单独的setenv.sh里很多中间件启动脚本都会默认source这个文件。4.3 方案C从TongWeb版本侧解决还有一种情况TongWeb自带的加密模块可能链接的是libcrypto.so.1.0.0而系统上只有1.1.x。此时报错尽管看起来一模一样解决思路却相反——需要给TongWeb一个匹配它编译时约定的1.0.x兼容库或者换用厂商已经适配新OpenSSL的TongWeb补丁版本。我见过有部署环境直接安装compat-openssl10包来解决的这个包会在系统里额外保留一份1.0.2兼容库不影响默认OpenSSL。命令大致是yum install compat-openssl10装完后用ldconfig -p | grep libcrypto.so.10确认路径再调整LD_LIBRARY_PATH。但这取决于TongWeb具体链接的是哪个SO名不同版本可能不同。比较稳妥的做法是联系TongWeb官方支持让他们提供与当前Linux发行版和OpenSSL版本匹配的补丁或者更新TongWeb到新版。商业中间件在这方面的兼容性调整通常已经在补丁里做过了没必要自己从零造轮子。4.4 改完之后的验证清单很多人的操作停在了“能启动”这一步这并不够。我的验证分三步走先重新执行ldd确认依赖库都解析到预期路径不再出现not found再用LD_DEBUGlibs,bindings启动搜索包含EVP_aes_128_gcm的绑定日志确认它最终匹配到的是libcrypto.so.1.1最后真正打开TongWeb管理控制台并从外部发起一次HTTPS请求确认加密链路能跑通。第3步尤其重要。延迟绑定机制意味着启动成功不等于所有符号都解析成功。我遇到过启动脚本已经输出TongWeb is running但访问8443端口时又报SSL错误的情况。只有完整走一遍加密连接才敢说问题真正解决。如果需要写进变更记录也一定把ldd和nm的结果贴进去方便后面回滚时对比。5. 从这次排错看国产中间件部署的通用经验5.1 安装前先看支持矩阵别默认“装好就能跑”TongWeb这类商业中间件对底层库的版本要求和开源软件JVM一样有明确的兼容边界。部署前花十分钟看文档里写的支持操作系统和OpenSSL版本能避免大多数无谓踩坑。特别是CentOS 7、Anolis、openEuler这些不同发行版自带OpenSSL版本有差异预装JDK也五花八门搞清支持矩阵再动手才是最高效的方式。很多人觉得“都是Linux能有什么差别”结果直接踩中OpenSSL版本不兼容就是这个报错的高发原因。5.2 环境变量污染是最大的暗坑LD_LIBRARY_PATH一旦在登录shell里被设置就会沿袭到所有子进程。服务器上装过监控代理、数据库驱动、其他版本JDK都可能在/etc/profile.d/或/etc/environment里塞自己的库路径。排查undefined symbol时第一个该怀疑的对象就是这个变量。正式部署时可以考虑把TongWeb服务做成 systemd unit在unit文件里明确EnvironmentLD_LIBRARY_PATH...从而绕开全局shell环境的不可控因素。这个习惯能让你少经历很多次“上午还好好的下午重启就报错”的诡异事件。5.3 systemd 与开机自启是另一道坎手工命令行启动正常但重启服务器后TongWeb又报同样的错这种事情也经常出现。原因通常是systemd 启动服务时不会加载/etc/profile.d如果你在登录环境里设置的变量能掩盖问题到了开机自启阶段就裸奔了。反过来也一样如果启动脚本依赖某个只有登录shell才有的变量systemd 环境下就失效。正确做法是把TongWeb启动所需的所有变量都写进自己的启动脚本而不是依赖外部全局配置。测试时也一定要用systemctl restart tongweb来验证而不是只在终端里敲一遍startserver.sh。5.4 学会识别这一族符号错误的“亲戚”EVP_aes_128_gcm只是众多OpenSSL符号中的一个。换一个版本还可能出现EVP_CIPHER_CTX_new、SSL_CTX_set_alpn_protos、BIO_new_ssl等找不到。它们的排错思路完全一致先看报错符号属于谁再用nm -D找出哪个库包含它最后调整搜索顺序。不要看到undefined symbol就卸载重装中间件也不要急着怀疑机器本身有问题问题往往只在库版本选择这一层。熟练之后这类报错从定位到解决基本控制在半小时内重点是别慌。5.5 一点亲测后的个人习惯经过这次排错我养成了一个习惯任何依赖native库的中间件安装完成后先在干净环境里跑一遍ldd并把LD_LIBRARY_PATH的最终值打印到启动日志里。如果运维规范允许我还会在启动脚本里临时输出关键库的nm结果。这样等线上真的出了问题一份日志就能定位八成问题不用再靠LD_DEBUG一遍遍重跑。
返回列表