Nginx 导出 ngx_module_t 变量是为了支持静态模块机制下的模块数组构建与运行时查找调用;这些全局变量以 STB_GLOBAL 符号存在于 ELF 的 .data 或 .rodata 段中,被 ngx_modules[] 数组引用并由核心遍历初始化。

在 Nginx 源码中,ngx_module_t 是模块定义的核心结构体,每个模块(如 ngx_http_core_module、ngx_events_module)都以全局变量形式声明一个该类型的实例。这些变量不仅在编译期参与模块注册,在运行时也作为模块元信息被核心调度器访问。
为什么 ngx_module_t 变量会被导出到二进制中
Nginx 采用静态模块机制,所有模块代码与核心一同链接为单个可执行文件(如 nginx)。每个模块的 ngx_module_t 实例是全局符号(global symbol),具有外部链接属性(extern 可见性),因此在最终生成的 ELF 文件中会以 STB_GLOBAL 类型出现在符号表(.symtab)中,并通常保留在 .data 或 .rodata 段内。
这种导出不是为了动态加载(Nginx 默认不依赖 dlopen),而是为了满足两个关键需求:
-
模块数组构建:编译时通过
ngx_modules.c自动生成的ngx_module_t *ngx_modules[]数组,其元素直接引用各模块的全局ngx_module_t变量地址; -
运行时查找与调用:Nginx 核心通过遍历
ngx_modules数组,依据ctx(如ngx_http_module_t)或type字段定位并初始化对应模块,其函数指针(如create_main_conf)均来自这些已导出的结构体实例。
如何验证 ngx_module_t 变量是否导出
可在编译后的 nginx 二进制文件中使用标准 ELF 工具确认:
- 用
nm -D nginx | grep http_core查看动态符号表(实际因静态链接常用nm -g nginx); - 用
readelf -s nginx | grep ngx_http_core_module直接搜索符号名,应看到类型为OBJECT、绑定为GLOBAL、大小符合sizeof(ngx_module_t)(通常 104 或 112 字节,依版本而定)的条目; - 用
readelf -x .data nginx或readelf -x .rodata nginx结合符号地址,可定位该结构体在段内的原始字节布局。
导出行为受编译选项影响的关键点
虽然默认行为是导出,但以下情况会改变结果:
-
启用
--without-*配置选项:被禁用模块的ngx_module_t变量不会被编译进目标文件,自然不出现在最终二进制中; -
启用
--with-debug:可能增加调试符号,但不影响模块变量本身的导出属性; -
使用
-fvisibility=hidden(非 Nginx 默认):若手动修改构建系统强制隐藏全局符号,则需显式用__attribute__((visibility("default")))标记模块变量,否则链接时报undefined reference; -
启用 LTO 或 strip 操作:
strip --strip-unneeded会移除未被引用的全局符号,但 Nginx 的模块变量均被ngx_modules[]引用,故通常保留;strip -s则清除整个符号表,导出信息丢失,但变量实体仍在数据段中。
自定义模块中确保正确导出的方法
开发第三方静态模块时,必须保证其 ngx_module_t 实例满足导出要求:
- 变量声明不能加
static修饰符; - 应在模块源文件顶层作用域定义,而非函数内;
- 模块名建议遵循
ngx_[type]_[name]_module命名惯例,便于调试和工具识别; - 若模块被条件编译(如
#ifdef NGX_HTTP_CACHE),需确保预处理后仍生成有效定义,否则链接阶段可能缺失符号。
本质上,ngx_module_t 的导出是 Nginx 静态架构的设计契约:它让模块成为可被核心感知的“第一类公民”,而非黑盒对象。理解这一点,有助于调试模块加载失败、分析内存布局,或在安全审计中识别潜在的模块注入面。

















