Linux mod_sysfs_setup模块sysfs符号表暴露
Linux mod_sysfs_setup模块sysfs符号表暴露
mod_sysfs_setup()是Linux内核模块加载过程中负责在sysfs中创建模块相关目录和文件的关键函数。它在load_module()完成ELF重定位和符号解析之后被调用,向用户空间暴露模块的元数据、参数信息和导出的符号表。
mod_sysfs_setup()位于kernel/module/sysfs.c,其调用链为load_module() -> mod_sysfs_setup() -> add_module_usage()等辅助函数:
static int mod_sysfs_setup(struct module *mod,
const struct load_info *info,
struct kernel_param *kparam,
unsigned int num_params)
{
int err;
/* 第一步:创建模块在/sys/module/下的kobject */
mod->mkobj.kobj.kset = module_kset;
err = kobject_init_and_add(&mod->mkobj.kobj, &module_ktype,
NULL, "%s", mod->name);
if (err)
goto out;
/* 第二步:创建version info文件 */
err = mod_sysfs_setup_vermagic(mod, info);
if (err)
goto out_unreg;
/* 第三步:创建模块参数sysfs条目 */
err = module_param_sysfs_setup(mod, kparam, num_params);
if (err)
goto out_unreg;
/* 第四步:创建模块符号表的sysfs文件 */
err = module_sysfs_setup_holders(mod, info);
if (err)
goto out_unreg;
/* 第五步:创建notes section文件 */
err = module_notes_init(mod, info);
if (err)
goto out_unreg;
/* 第六步:创建模块依赖关系符号链接 */
add_module_usage(mod, info);
/* 第七步:暴露导出的内核符号到/sys/module//sections/ */
add_sect_attrs(mod, info);
add_notes_attrs(mod, info);
return 0;
out_unreg:
kobject_put(&mod->mkobj.kobj);
mod->mkobj.kobj.parent = NULL;
out:
return err;
}
module_kset是模块subsystem的全局kset,其父kobject为module_kobj(即/sys/module)。所有模块的kobject都在此之下形成/sys/module/目录。
符号表暴露的核心在add_sect_attrs()函数中,该函数遍历ELF节区(section),对其中包含符号的节创建sysfs属性文件:
static void add_sect_attrs(struct module *mod, const struct load_info *info)
{
struct module_sect_attrs *sattrs;
struct module_sect_attr *sattr;
unsigned int nsect;
int i;
/* 计算需要暴露的节区数量 */
nsect = 0;
for (i = 0; i < info->hdr->e_shnum; i++)
if (info->secstrings[info->sechdrs[i].sh_name][0] != '.')
nsect++;
sattrs = kzalloc(struct_size(sattrs, attrs, nsect), GFP_KERNEL);
sattr = &sattrs->attrs[0];
/* 为每个节区创建属性 */
for (i = 0; i < info->hdr->e_shnum; i++) {
if (info->secstrings[info->sechdrs[i].sh_name][0] == '.')
continue;
sattr->name = info->secstrings + info->sechdrs[i].sh_name;
sattr->address = info->sechdrs[i].sh_addr;
sysfs_attr_init(&sattr->attr);
sattr->attr.name = sattr->name;
sattr->attr.mode = 0400;
sattr->attr.show = module_sect_show;
sattr++;
}
/* 创建sysfs目录/sys/module//sections/ */
sattrs->kobj.kset = &mod->mkobj.kobj;
err = kobject_init_and_add(&sattrs->kobj, &module_sect_ktype, &mod->mkobj.kobj, "sections");
/* 为每个节区创建属性文件 */
for (i = 0; i < nsect; i++)
sysfs_create_file(&sattrs->kobj, &sattrs->attrs[i].attr);
}
module_sect_show()是属性文件的read回调,它返回对应节区的基地址:
static ssize_t module_sect_show(struct module_attribute *mattr,
struct module_kobject *mk, char *buf)
{
struct module_sect_attr *sattr = container_of(mattr, struct module_sect_attr, mattr);
return sprintf(buf, "0x%px\n", (void *)sattr->address);
}
通过/sys/module//sections/.text,用户空间可以读取模块代码段的加载地址,这对于调试和崩溃分析至关重要。
module_param_sysfs_setup()创建/sys/module//parameters/目录,并在其中为每个module_param()定义的参数创建属性文件。属性的读写回调指向param_ops中对应的get/set函数,使得用户空间可以通过写sysfs文件在运行时修改模块参数:
static int module_param_sysfs_setup(struct module *mod,
struct kernel_param *kparam,
unsigned int num_params)
{
int i;
for (i = 0; i < num_params; i++) {
struct param_attribute *param_attr = kzalloc(sizeof(*param_attr), GFP_KERNEL);
sysfs_attr_init(¶m_attr->mattr.attr);
param_attr->mattr.attr.name = kparam[i].name;
param_attr->mattr.attr.mode = kparam[i].perm;
param_attr->mattr.show = param_attr_show;
param_attr->mattr.store = param_attr_store;
param_attr->mattr.attr.owner = mod;
param_attr->param = &kparam[i];
sysfs_create_file(&mod->mkobj.kobj, ¶m_attr->mattr.attr);
}
return 0;
}
add_module_usage()分析模块的符号依赖关系,在/sys/module//holders/目录下创建指向被依赖模块的符号链接。当模块B使用了模块A导出的符号时,/sys/module/A/holders/B符号链接出现,反映了模块间的依赖关系。
mod_sysfs_teardown()是逆操作,在模块卸载时被调用,依次移除所有sysfs节点并释放相关内存。如果某个模块的sysfs节点在用户空间仍有打开的文件描述符,kobject的引用计数机制会延迟实际的内存释放直到所有引用关闭。
通过mod_sysfs_setup()暴露的sysfs接口为系统管理员和调试工具提供了无需专用工具的模块信息查看能力。用户可以通过cat /sys/module//version、cat /sys/module//sections/.text等命令获取模块的加载信息。
