利用

您所在的位置:网站首页 gcc如何把变量放在固定地址 利用

利用

2024-01-06 22:21| 来源: 网络整理| 查看: 265

 转自:

https://mp.weixin.qq.com/s?__biz=MzAwMDUwNDgxOA==&mid=2652663356&idx=1&sn=779762953029c0e0946c22ef2bb0b754&chksm=810f28a1b678a1b747520ba3ee47c9ed2e8ccb89ac27075e2d069237c13974aa43537bff4fba&mpshare=1&scene=1&srcid=0111Ys4k5rkBto22dLokVT5A&pass_ticket=bGNWMdGEbb0307Tm%2Ba%2FzAKZjWKsImCYqUlDUYPZYkLgU061qPsHFESXlJj%2Fyx3VM#rd

原创 2018-01-11 廖威雄 Linuxer

本文详细讲解了利用__attribute__((section()))构建初始化函数表,以及Linux内核各级初始化的原理。

 

作者简介:

    廖威雄,2016年本科毕业于暨南大学,目前就职于珠海全志科技股份有限公司从事linux嵌入式系统(Tina Linux)的开发,主要负责文件系统和存储的开发和维护,兼顾linux测试系统的设计和持续集成的维护。

    拆书帮珠海百岛分舵的组织长老,二级拆书家,热爱学习,热爱分享。

欢迎投稿:

2018年给Linuxer投稿原创Linux技术文章,一经录取,赠送人民邮电出版社任意在售图书,获得读者红包打赏,和公众号站长宋宝华200元微信红包。

Linuxer-"Linux开发者自己的媒体"第五月稿件和赠书名单

 

欢迎关注Linuxer

问题导入

传统的应用编写时,每添加一个模块,都需要在main中添加新模块的初始化

 

 

使用__attribute__((section()))构建初始化函数表后,由模块告知main:“我要初始化“,添加新模块再也不需要在main代码中显式调用模块初始化接口。

以此实现main与模块之间的隔离,main不再关心有什么模块,模块的删减也不需要修改main。

那么,如何实现这个功能呢?如何实现DECLARE_INIT呢?联想到内核驱动,所有内核驱动的初始化函数表在哪里?为什么添加一个内核驱动不需要修改初始化函数表?

下文会从 构建初始化函数表的原理分析、分析内核module_init实现、演练练习 的3个角度给小伙伴分享。

构建初始化函数表的原理分析 __attribute__((section(”name“)))是gcc编译器支持的一个编译特性(arm编译器也支持此特性),实现在编译时把某个函数/数据放到name的数据段中。因此实现原理就很简单了: 1.模块通过__attribute__((section("name")))的实现,在编译时把初始化的接口放到name数据段中 2.main在执行初始化时并不需要知道有什么模块需要初始化,只需要把name数据段中的所有初始化接口执行一遍即可 首先: gcc -c test.c -o test.o 此时编译过程中处理了__atribute__((section(XXX))),把标记的变量/函数放到了test.o的XXX的数据段,可用 readelf命令查询。 最后:ld -T test.o -otest.bin 链接时,test.o的XXX数据段(输入段),最终保存在test.bin的XXX数据段(输出段),如此在bin中构建了初始化函数表。 由于自定义了一个数据段,而默认链接脚本缺少自定义的数据段的声明,因此并不能使用默认的链接脚本。 ld链接命令有两个关键的选项: ld -T :指定链接时的链接脚本 ld --verbose:打印出默认的链接脚本 在我们下文的演练中,我们首先通过”ld --verbose”获取默认链接脚本,然后修改链接脚本,添加自定义的段,最后在链接应用时通过“-T” 指定我们修改后的链接脚本。 下文,我们首先分析内核module_init的实现,最后进行应用程序的演练练习。 分析内核module_init实现 内核驱动的初始化函数表在哪里?为什么添加一个内核驱动不需要修改初始化函数表?为什么所有驱动都需要module_init? 1.module_init的定义 module_init定义在。代码如下: 代码中使用的“_section_”,是一层层的宏,为了简化,把其等效理解为“section”。 分析上述代码,我们发现module_init由__attribute__((section(“name”)))实现,把初始化函数地址保存到名为".initcall6.init" 的数据段中。 2.链接内核使用自定义的链接脚本 我们看到内核目录最上层的Makefile,存在如下代码: # Rule to link vmlinux - also used during CONFIG_KALLSYMS # May be overridden by arch/$(ARCH)/Makefile quiet_cmd_vmlinux__ ?= LD $@ cmd_vmlinux__ ?= $(LD) $(LDFLAGS) $(LDFLAGS_vmlinux) -o $@ \ -T $(vmlinux-lds) $(vmlinux-init) \ --start-group $(vmlinux-main) --end-group \ $(filter-out $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) vmlinux.o FORCE ,$^) 本文的关注点在于:-T $(vmlinux-lds),通过“ld -T ”使用了定制的链接脚本。定制的链接脚本在哪里呢?在Makefile存在如下代码: vmlinux-lds := arch/$(SRCARCH)/kernel/vmlinux.lds 我们以”ARCH=arm“ 为例,查看链接脚本:arch/arm/kernel/vmlinux.lds: 在上述代码中,我们聚焦于两个地方: __initcall6_start = .; : 由__initcall6_start指向当前地址 *(.initcall6.init) : 所有.o文件的.initcall6.init数据段放到当前位置 如此,“__initcall6_start”指向“.initcall6.init”数据段的开始地址,在应用代码中就可通过“__initcall6_start”访问数据段“.initcall6.init”。 是不是如此呢?我们再聚焦到文件中。 “.initcall.init”数据段的使用 在中,有如下代码: static initcall_t *initcall_levels[] __initdata = { __initcall0_start, __initcall1_start, __initcall2_start, __initcall3_start, __initcall4_start, __initcall5_start, __initcall6_start, __initcall7_start, __initcall_end, }; ...... int __init_or_module do_one_initcall(initcall_t fn) { ...... if (initcall_debug) ret = do_one_initcall_debug(fn); else ret = fn(); ...... } ...... static void __init do_initcall_level(int level) { ...... for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++) do_one_initcall(*fn); } 按0-7的初始化级别,依次调用各个级别的初始化函数表,而驱动module_init的初始化级别为6。在“for (fn = initcall_levels[level]; fn


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3