利用section构建初始化函数表

一. 链接

 

 

    1.1. 每一个链接过程都由链接脚本(linker script, 一般以lds作为文件的后缀名)控制. 链接脚本主要用于规定如何把输入文件内的section放入输出文件内, 并控制输出文件内各部分在程序地址空间内的布局. 但你也可以用连接命令做一些其他事情.连接器有个默认的内置连接脚本, 可用ld –verbose查看. 连接选项-r和-N可以影响默认的连接脚本(如何影响?).-T选项用以指定自己的链接脚本, 它将代替默认的连接脚本

    1.2. 示例:

        以下脚本将输出文件的text section定位在0×10000, data section定位在0×8000000:
 SECTIONS
 {
   .= 0×10000;
   .text : { *(.text) }
   .= 0×8000000;
   .data : { *(.data) }
   .bss : { *(.bss) }
 }
解释一下上述的例子:
 . = 0×10000 : 把定位器符号置为0×10000 (若不指定, 则该符号的初始值为0).
 .text : { *(.text) } : 将所有(*符号代表任意输入文件)输入文件的.text section合并成一个.text section, 该section的地址由定位器符号的值指定, 即0×10000.
 . = 0×8000000 :把定位器符号置为0×8000000
 .data : { *(.data) } : 将所有输入文件的.data section合并成一个.data section, 该section的地址被置为0×8000000.
 .bss : { *(.bss) } : 将所有输入文件的.bss section合并成一个.bss section,该section的地址被置为0×8000000+.data section的大小.
连接器每读完一个section描述后, 将定位器符号的值*增加*该section的大小. 注意: 此处没有考虑对齐约束.

二. section构建初始化函数表

    2.1. 和传统初始化对比

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

 

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

 

         2.1.3. 内核module_init实现是构建初始化函数表

    2.2. 链接文件

          2.2.1. gcc默认链接文件

               1. ld --verbose:打印出默认的链接脚本

          2.2.2. 自定义链接文件

   2.3.  readelf

     2.3.1. 查看section headers

 

 

 

         2.3.2. 查看符合(-s(小写))

 

 

三. 实战

     3.1. 源码

#include <stdio.h>
#include <string.h>
typedef struct static_cmd_function_struct
{
    const char *cmd;
    void (*fp)(void);
    char *description;
}__attribute__ ((aligned (16))) static_cmd_st;
 
#define NR_USED __attribute__((used))
#define NR_SECTION(x) __attribute__((section(".rodata_nr_shell_cmd" x)))
#define NR_SHELL_CMD_EXPORT_START(cmd, func) \
    NR_USED const static_cmd_st _nr_cmd_start_ NR_SECTION("_start") = {#cmd, NULL}
#define NR_SHELL_CMD_EXPORT(cmd, func) \
    NR_USED const static_cmd_st _nr_cmd_##cmd NR_SECTION("") = {#cmd, func}
#define NR_SHELL_CMD_EXPORT_END(cmd, func) \
    NR_USED const static_cmd_st _nr_cmd_end_ NR_SECTION("_end") = {#cmd, NULL}
    


NR_SHELL_CMD_EXPORT_START(start,NULL);
NR_SHELL_CMD_EXPORT_END(end,NULL);


static void func1(void)
{
            printf("call %s\n", __FUNCTION__);

}

NR_SHELL_CMD_EXPORT(ls, func1);
 
static void func2(void)
{
            printf("call %s\n", __FUNCTION__);
                
}

NR_SHELL_CMD_EXPORT(ls2, func2);
 /*main.c*/
int main(int argc, char **argv)
{
         
const static_cmd_st *p0=&_nr_cmd_start_, *p1=&_nr_cmd_ls, *p2=&_nr_cmd_ls2,*p3=&_nr_cmd_end_;

printf("sizeof=0x%lx\r\n",sizeof(static_cmd_st));
printf("addr:p0 %p\r\n",p0);
printf("addr:p1 %p\r\n",p1);
printf("addr:p2 %p\r\n",p2);
printf("addr:p3 %p\r\n",p3);

for(const static_cmd_st *p=&_nr_cmd_start_+1; p<&_nr_cmd_end_; p++)
    p->fp();
return 0;
}
View Code
/*script.lds*/
SECTIONS
{
    . = ALIGN(16);
    .rodata_nr_shell_cmd_start : {
      *(.rodata_nr_shell_cmd_start) 
      }
    .rodata_nr_shell_cmd : {
      *(.rodata_nr_shell_cmd) 
      }
    .rodata_nr_shell_cmd_end : {
      *(.rodata_nr_shell_cmd_end) 
      }
}
INSERT AFTER .rodata
View Code
CC := gcc
DGBFLAG := -g -T script.lds 

SRC :=     main.c
TARGET_I := main.i
TARGET := main
.PHONY: all
all: 

    $(CC) $(DGBFLAG) $(SRC) -E -o $(TARGET_I)
    $(CC) $(DGBFLAG) $(SRC) -o $(TARGET)

clean:
        @rm -f $(TARGET) $(TARGET_I)
View Code

     3.2. 测试

 

 

 

PS: 如果结构体不是16字节对齐的话,运行结果会出错,结果如下

 

 

 

 

    所以需要16/32/6字节对齐即可。

 

 

 

 

参考文档:https://blog.csdn.net/qq_41822235/article/details/81272999

参考文档:https://www.cnblogs.com/sky-heaven/p/8275303.html

参考文档:https://blog.csdn.net/weixin_37571125/article/details/78665184

posted @ 2020-08-23 00:31  三七鸽  阅读(635)  评论(0编辑  收藏  举报