恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
httpd编译报错 undefined reference to XML_GetErrorCode 的排查与修复
首页
资讯中心
/
httpd编译报错 undefined reference to XML_GetErrorCode 的排查与修复
httpd编译报错 undefined reference to XML_GetErrorCode 的排查与修复
发布时间:2026/10/19 13:59:42
源码编译 httpd 时遇到 undefined reference toXML_GetErrorCode这个报错我前前后后碰见过三四次每次背后的原因还不完全一样。这篇文章把完整的排查思路和修复方案写下来包括为什么链接阶段会报这个错、expat 在整个编译链条里到底扮演什么角色、Debian/Ubuntu 和 RHEL/CentOS 分别该怎么补依赖以及几个特别容易踩的静态库链接顺序坑。如果你也是头一回自己编译 Apache httpd正好卡在 make 一步那这篇就是照着操作就能过关的那种实战记录。1. 错误现场这个 undefined reference 到底在指什么1.1 编译四步与链接器报错源码编译并不是一步到位而是分成预处理、编译、汇编、链接四个阶段。很多同学把./configure通过等同于万事大吉其实 configure 只是检查环境、生成 Makefile真正的“符号决战”发生在最后的链接阶段。链接器做的事听起来很机械把一堆目标文件合并成可执行文件同时拿着每个目标文件里标记为“未定义”的符号清单去你提供的所有库里逐个查找。如果某个符号翻遍了所有库都找不到对应定义它就报undefined reference to。你可以理解为拼图少了一块链接器拿着缺口的形状对着整盒拼图找没找到就直接摊手。XML_GetErrorCode就是一个典型的未定义符号。它不在 httpd 源码里定义而是来自外部 XML 解析库 expat。问题不在“源码写错”而在链接命令里没有让这个符号的定义参与进来。1.2 为什么偏偏是 XML_GetErrorCode先看一眼最常见的报错样子modules/dav/main/mod_dav.lo: In function dav_xml_parse: modules/dav/main/mod_dav.lo: undefined reference to XML_GetErrorCode collect2: error: ld returned 1 exit statusXML_GetErrorCode是 expat 库暴露的 API作用是在 XML 解析出错后返回错误码调用方能根据错误码判断究竟是格式问题还是内存问题。httpd 里跟 WebDAV 相关的mod_dav、mod_dav_fs等模块要解析客户端提交的 XML 文本比如 PROPFIND 请求的 body就必须依赖这套 XML 解析能力。链接到mod_dav.lo这个目标文件时编译器已经知道函数长什么样、怎么调用但“函数实体住在哪”它不知道。正常情况下链接命令里会包含-lexpat或者通过 apr-util 联动传入 expat 库路径把定义带进来。如果这一步没发生就会得到这一行让人摸不着头脑的报错。1.3 触发条件与代码路径这个错误不是每次都出现它跟你开启的模块强相关。如果你只是编译最基础的 httpd不带任何额外模块可能从头到尾都很顺利。可一旦你用--enable-dav、--enable-dav-fs、--enable-dav-lock或者干脆--enable-modulesall想体验完整版mod_dav相关代码就会被编译进来XML 解析依赖也随之被拉入链接环节。所以排查的时候别光盯着最后一行报错要往前看这次 configure 时到底开了哪些模块开模块之后 configure 有没有明确提示 expat 检测失败。这里有个隐蔽点configure 对 expat 的检测往往不是“找不到就立即终止”而只是记录一个no等编译到相关模块时才爆炸。这就是为什么很多新手会困惑—— configure 明明过了 make 却挂了。2. 根源剖析httpd、apr-util 与 expat 的三者关系2.1 expat 在 httpd 生态里的位置expat 是一个用 C 语言写的 XML 解析库以事件驱动方式工作很多知名软件都拿它做底层解析比如版本管理工具 git 就依赖它的文件格式解析能力。httpd 本身并不直接包含 expat 源码而是把 XML 解析这项职责外包给 expat再通过 apr-util 这一层做适配。依赖链条大概是这样mod_dav在处理请求时会调用到 XML 解析相关的接口而 apr-util 的 xml 模块负责包装底层解析器最终落到 expat 的函数包括XML_ParserCreate、XML_Parse、XML_GetErrorCode等。任何一个环节没对上号链接器就会报出undefined reference to XML_GetErrorCode。这个链条决定了光装 httpd 依赖是不够的你还要保证 expat 的头文件、库文件、pkg-config 信息三者都在而且版本兼容。2.2 configure 为什么没能提前发现很多人不理解configure 不是会检查依赖吗为什么 expat 缺失它不叫停一方面httpd 的 configure 对 expat 的检测结果并不总是 fatal。它可能只是把结果写进 config.log在生成 Makefile 时决定某个模块是否启用而不是中止整个配置流程。另一方面如果你没有显式启用 WebDAV 相关模块configure 甚至不会深入检查 expat因为默认构建路径里根本不需要它。我遇到过一种更气人的情况configure 明明输出了checking for XML_ParserCreate in -lexpat... yes但 make 还是报 undefined reference。这种通常是链接顺序问题而不是缺库问题。make 生成的链接命令里虽然有-lexpat但位置放错了导致链接器在扫描mod_dav.lo的未定义符号之前已经扫完了 expat 库白白错过了解析机会。2.3 链接顺序与符号解析机制链接器处理文件时是按从左到右的顺序扫描的。对于静态库它只会把其中“解决了当前未定义符号”的目标文件抽取出来如果扫描完整个库文件发现当下没有任何未定义符号需要它那这个库就被跳过了之后再遇到需要它的符号也来不及补救。我在实际排查时看过很多 Makefile发现有人习惯在 configure 时加LDFLAGS-lexpat。这个做法很危险因为 configure 通常会把这些 LDFLAGS 放在链接命令最前面而不是列表末尾。等到链接器处理mod_dav.lo时expat 库已经被扫过一次、被标记为“没有需要的东西”自然不会回头再扫第二遍。正确的做法是优先使用LIBS环境变量或者直接在 Makefile 中确认-lexpat出现在源文件之后。举个例子更容易理解# 错误示范-lexpat 放在了目标文件之前 gcc -lexpat mod_dav.lo -o mod_dav.so # 正确姿势先有目标文件再给库 gcc mod_dav.lo -lexpat -o mod_dav.so如果 expat 是动态库顺序问题有时不致命但一旦系统里只装了静态库libexpat.a顺序反了几乎必报错。这就是为什么很多人明明装了 expat却还是卡在同一个错误上的真相。3. 分平台对症下药依赖安装与参数透传3.1 Debian/Ubuntu 系快速修复在 Debian/Ubuntu 系系统上问题十有八九是缺少开发包。运行库和开发库是分开的libexpat1只提供运行时需要的.so.1而编译期需要的头文件和.so软链接在libexpat1-dev包里忘了装后者就会碰到“能找到运行库却找不到给链接器用的文件”的尴尬。sudo apt update sudo apt install libexpat1-dev pkg-config --modversion expat安装完不要急着 make先检查config.log里 expat 相关的检测结果。如果 configure 已经跑过一轮Makefile 里可能缓存了旧的依赖结论最好先清理再重新配置make clean ./configure --prefix/opt/httpd \ --enable-dav \ --enable-dav-fs \ --enable-dav-lock make -j4为什么必须make clean因为 configure 生成 Makefile 时会把当时的检测结果写死进去外部库变了Makefile 不会自动感知。不清理直接 make十次里有八次会得到跟之前一模一样的报错。3.2 RHEL/CentOS 系快速修复Red Hat 系系统用的包名不一样开发包叫expat-develsudo yum install expat-devel # 新版系统用 dnf 也可以 sudo dnf install expat-devel -y pkg-config --modversion expatRed Hat 系还有一个常见的坑64 位系统里 expat 运行库位于/usr/lib64/libexpat.so.1但expat-devel安装的软链接同样在/usr/lib64下。如果你之前手动装过去别的地方的 expat比如/usr/local/lib链接器找库时可能优先命中错误的路径导致版本不匹配随后报出各种匪夷所思的符号错误。判断系统里到底有哪几份 expat 可以用这条命令ldconfig -p | grep expat输出里如果同时出现/usr/lib64/libexpat.so.1和/usr/local/lib/libexpat.so.1就要格外留神后面第 4 节细说怎么处理多版本冲突。3.3 编译参数与 pkg-config 的优先级httpd 的部分版本 configure 直接提供--with-expat选项可以指定builtin、system或具体路径。不过不同小版本的行为不完全一样最稳的方案是把 pkg-config 的路搞对让 configure 自己找到 expat./configure --prefix/opt/httpd \ --with-expatsystem \ --enable-dav \ --enable-dav-fs \ --enable-dav-lock如果你的 expat 安装在非标准前缀比如手动编译装到了/opt/expat那需要把 pkg-config 的搜索路径指过去export PKG_CONFIG_PATH/opt/expat/lib/pkgconfig:$PKG_CONFIG_PATH ./configure ...这里有个重要提醒不要图省事直接LDFLAGS-lexpat ./configure。前面讲过了LDFLAGS 的位置太靠前静态库场景下救不了急。如果确实需要通过环境变量补库用LIBS更靠谱LIBS-lexpat ./configure ...LIBS通常会被追加到链接命令末尾正好命中“依赖库放在依赖者之后”的规则。4. 更隐蔽的坑多版本冲突与 apr-util 重编4.1 本机存在多份 expat 时怎么办这个坑我踩得很深。某次编译 httpd系统已经装了libexpat1-dev运行库也在pkg-config也能输出版本号但 make 依然报 undefined reference。后来发现/usr/local/lib/libexpat.a是某个软件手动编译时留下的旧版本configure在搜库的时候优先匹配到了它而旧版本里没有需要的符号定义。排查命令ls -l /usr/local/lib/libexpat* ls -l /usr/lib/x86_64-linux-gnu/libexpat*再配合grep -i expat config.log看看 configure 实际用的是哪个路径。如果确认有冲突优先清理手动安装的旧版本或者在 configure 时通过--with-expat指定明确路径。清理时要谨慎不要直接删除/usr/local/lib下文件万一其他软件也在用就麻烦了。更稳妥的做法是让 configure 只搜指定目录把/usr/local/lib从库搜索路径里摘出去用--with-expat/usr把路径锁死。4.2 apr-util 才是关键角色httpd 的依赖不只是自身源码还包括 apr 和 apr-util。而 mod_dav 对 expat 的实际调用很大程度要经过 apr-util 的 XML 支持。如果你以前单独装过一套 apr-util当时没有启用 expat或者系统的 apr-util 版本很老那么就算你接下来给 httpd 补了全套 expat 开发包make 时照样可能找不到符号。判断方式很简单重新 configure httpd 时注意看输出里有没有类似checking for XML support in APR-UTIL... no的提示。一旦出现no就别指望 httpd 本身能解决得回头先把 apr-util 编好。如果 httpd 源码包内带了srclib/apr和srclib/apr-util可以这样关联编译./configure \ --prefix/opt/httpd \ --enable-dav \ --enable-dav-fs \ --enable-dav-lock \ --with-apr/usr/local/apr \ --with-apr-util/usr/local/apr如果仍然报 APR-Util 没有 XML 支持就需要单独重编cd httpd-2.4.xx/srclib/apr-util make clean ./configure --prefix/usr/local/apr \ --with-apr/usr/local/apr \ --with-expat/usr make make install重编完 apr-util再回到 httpd 目录清理并重新 configure。整个过程并不复杂但顺序很重要先 apr再 apr-util最后 httpd别倒过来。4.3 动态库运行时路径与期望落空的情况有一种情况是链接阶段确实成功了make 也通过了但启动 httpd 时立刻报错error while loading shared libraries: libexpat.so.1: cannot open shared object file这是动态链接器在运行时找不到库跟编译期的 undefined reference 是两个不同阶段的问题。如果你把 expat 手动装到了/usr/local/lib但这个路径不在系统默认搜索范围就会出现这种“编译时好好的运行时就翻车”的荒诞局面。解决办法是把目录加进动态链接器配置echo /usr/local/lib | sudo tee /etc/ld.so.conf.d/local.conf sudo ldconfig如果不想动系统配置也可以用临时环境变量顶一下export LD_LIBRARY_PATH/usr/local/lib:$LD_LIBRARY_PATH /opt/httpd/bin/httpd -t4.4 静态链接场景下的额外依赖如果系统里只有 expat 静态库链接器在解析XML_GetErrorCode时还会连带把 expat 的依赖项也拉进来。expat 静态库通常依赖线程库于是你会看到一连串跟 XML 完全无关的报错undefined reference to pthread_create undefined reference to pthread_once这种报错给人的迷惑性极强因为你的源码压根没碰线程。其实原因就是静态库的依赖需要手动补齐LIBS-lexpat -lpthread ./configure ...补上-lpthread之后链接器才能把线程相关的符号一并解析。这里也提醒我们undefined reference 报错里的符号名只是“最后的导火索”真正的问题可能在符号背后的依赖链里。5. 完整实操从源码下载到 make 通过5.1 环境与目标某开发者在 Linux 服务器上准备编译一份 httpd功能上需要支持 WebDAV也就是让目录具备文件同步能力。服务器从可视化界面看只是一个普通干净的系统看起来什么开发工具都齐全实际编译时才发现缺东少西。目标很明确获取 httpd 源码包配置启动 dav 相关模块最终能让 httpd 启动并通过配置检查。5.2 失败过程还原操作路径很常规tar xf httpd-2.4.xx.tar.bz2 cd httpd-2.4.xx ./configure --prefix/opt/httpd \ --enable-dav \ --enable-dav-fs \ --enable-dav-lock make -j4configure 阶段一切正常没有报错。make 跑了一小会儿突然刷出一段红色输出modules/dav/main/mod_dav.lo: In function dav_xml_parse: modules/dav/main/mod_dav.lo: undefined reference to XML_GetErrorCode collect2: error: ld returned 1 exit status make[2]: *** [modules/dav/main/mod_dav.lo] Error 1这里有个细节make -j4是并发编译错误信息可能夹在一大堆编译输出中间甚至偶尔会触发因并发导致的次要告警。为了看清真实错误我建议先回退成单线程复现一次make clean make -j1 21 | tail -50单线程下错误定位更干净不会误判成“多线程编译的偶发问题”。5.3 诊断过程记录先看系统里有没有 expat 开发情报pkg-config --modversion expat这条命令如果没有任何输出大概率连expat.pc都没安装意味着开发包没装。再验证头文件ls /usr/include/expat.h这一步直接暴露了问题系统里有 expat 运行库但缺少编译需要的头文件。所以 configure 阶段没有致命报错因为它只检查了一部分信息真正需要编译时才发现缺失。接着看 config.log 里的相关部分grep -i expat\|XML_GetErrorCode config.log | tail -30日志里记录的检查结果大多是no或者直接缺失基本坐实了“依赖不完整”的判断。5.4 修复与验证在 Debian 系系统上安装开发包sudo apt install libexpat1-dev -y安装成功后删掉旧生成物重新 configuremake clean ./configure --prefix/opt/httpd \ --enable-dav \ --enable-dav-fs \ --enable-dav-lock这次 configure 输出里多了类似checking for XML_GetErrorCode in -lexpat... yes的提示心里就有底了。继续 makemake -j4 make install启动前先做配置检查/opt/httpd/bin/httpd -t输出Syntax OK说明模块加载没问题。再用ldd确认模块对 expat 的动态依赖ldd /opt/httpd/modules/mod_dav.so | grep expat能看到libexpat.so.1 ...这类输出证明链接信息已经正确写入。5.5 如果仍然失败的备用路径万一系统 libexpat-dev 装好、configure 也显示 yes可 make 还是报错那就得直接看链接命令行生成的样子。在 Makefile 里搜mod_dav对应的链接规则重点看-lexpat的位置。如果-lexpat出现在mod_dav.lo之前就说明是顺序问题。解决方式不是手动改 Makefile而是重置 configure 参数改用LIBS传递依赖库make clean LIBS-lexpat ./configure ... make -j4这一招对静态库场景尤其好使。我在多个版本上都验证过确认是“急救型”通用解。6. 错误变体与排查速查表实际项目里XML_GetErrorCode经常不是唯一一个未定义符号它往往带着一串朋友一起出现。把常见变体整理成一张速查表排查时可以对照参考错误信息最常见原因处理方式undefined reference to XML_GetErrorCodeexpat 开发包未安装或未参与链接安装libexpat1-dev/expat-devel重新 configureundefined reference to XML_ParserCreateXML_ParserFreeexpat 库缺失或.so软链接不存在确认开发包完整检查/usr/lib下有无libexpat.soAPU has no XML supportapr-util 编译时未启用 expat重编 apr-util加--with-expat/usrundefined reference to pthread_createexpat 静态库依赖线程库configure 时补LIBS-lexpat -lpthreadlibexpat.so.1: cannot open shared object file库被装在非系统默认搜索路径写/etc/ld.so.conf.d后ldconfig或临时LD_LIBRARY_PATHconfigure: error: expat.h not found头文件路径没被 configure 找到安装开发包或CPPFLAGS-I/opt/expat/include表格里第一行就是最常见的通报但真正让我觉得值得留意的其实是表格第三行。apr-util 的 XML 支持状态很多时候决定了 mod_dav 能不能编译出来而它又最容易被人忽略。如果你已经花了很久折腾 expat 本身却毫无进展回头检查一遍 apr-util 的检测输出往往能豁然开朗。另一个容易被忽略的细节是改完依赖之后务必make clean。不是所有情况下都需要但只要你希望新安装的开发包发挥作用就老老实实清一遍。我见过有人连续make了七八次一直报错最后发现 Makefile 还是上一轮 configure 的旧文件记忆和现实严重脱节。排查顺序建议从简单到复杂走一遍先看pkg-config --modversion expat和ls /usr/include/expat.h确认开发包。再看grep -i expat config.log确认 configure 到底认不认识 expat。最后看 make 的链接命令行里-lexpat的排位确认顺序没有倒挂。三步走完九成问题能定位。剩下的一成多半集中在多版本库冲突和动态库运行时路径上那就用ldd和ldconfig -p一点点抠。写在最后的一点经验编译服务器软件这件事依赖问题至少要占一半弯路。我后来养成的习惯是先确认 expat 开发包和 pkg-config 都正常再跑 configure看到 XML 相关的 checking 结果输出 ok 才继续。如果哪次还是撞上来历不明的 undefined reference第一反应不再是怀疑源码而是翻链接命令行——看-lexpat到底有没有出现、出现在哪个位置。最后再分享一个小技巧报错后先执行一下./configure --help | grep -i expat看看当前版本认哪种配置方式是支持--with-expatsystem还是只接受内置方式。确认了这一点至少能省下半小时瞎试的时间。这种排查习惯不仅能解决 httpd 的问题处理其他依赖 expat 的软件同样适用。