在linux内核源码中,随处都可以看见类似于__init这样的宏,如下代码片段:
# define __section(S) __attribute__ ((__section__(#S))) #define __init __section(.init.text)
从上述代码片段可见,当使用__init宏修饰一个函数后,那么在进行编译构建过程中,会出现一些不一样的现象。
在比如说__setup宏,该宏定义如下代码片段所示【出自linux dir/include/init.h】:
#define __setup_param(str, unique_id, fn, early)
static const char __setup_str_##unique_id[] __initconst
__aligned(1) = str;
static struct obs_kernel_param __setup_##unique_id
__used __section(.init.setup)
__attribute__((aligned((sizeof(long)))))
= { __setup_str_##unique_id, fn, early }
#define __setup(str, fn)
__setup_param(str, fn, fn, 0)
综上所述
在代码中出现__attribute__和__section__这两个关键字,这两个关键字是实现该机制的基石,在分析了linux内核源码的实现机制后,本文就使用这两个宏来亲自实践该机制,以便加深理解。
实践过程准备: (1)创建一个公用头文件:common_define.h (2)创建一个模块文件:module_1.c (3)创建第二个模块文件:module_2.c (4)创建一个主应用文件:app_demo.c (5)创建一个makefile文件
【2-1】各文件中内容如下:
#ifndef COMMON_DEFINE_H
#define COMMON_DEFINE_H
typedef int (*init_call_fn)(void);
#define __init __attribute__((unused,__section__(".my_init")))
#define MY_DECLAER_INIT(func)
static init_call_fn init_##func __init = func
#endif
#include "stdio.h"
#include "string.h"
#include "module_1.h"
#include "common_define.h"
static int module_1_init(void)
{
printf("module_1_init===============started!!rn");
return 0;
}
MY_DECLAER_INIT(module_1_init);
#include "stdio.h"
#include "module_2.h"
#include "common_define.h"
static int module_2_init(void)
{
printf("module_2_init===============started!!rn");
return 0;
}
MY_DECLAER_INIT(module_2_init);
#include "stdio.h"
#include "string.h"
#include "app_demo.h"
#include "common_define.h"
extern init_call_fn my_init_start;
extern init_call_fn my_init_end;
int main(int argc,char *argv[])
{
init_call_fn *init_call_ptr = &my_init_start;
printf("start load module...rn");
for(;init_call_ptr < &my_init_end; init_call_ptr++)
{
(*init_call_ptr)();
}
printf("load module successfulrn");
return 0;
}
/makefile/
################################################################################# # driver test makefile file #datetime 2021/10/24 #author iriczhao ################################################################################## CC=cc CC_FLAG=-Wall PRG=app_demo OBJ=module_1.o module_2.o app_demo.o $(PRG):$(OBJ) gcc -o $@ $(OBJ) .PRONY:clean clean: @echo "Removing linked and compiled files......" rm -f $(OBJ) $(PRG)
【2-2】编译调试
在项目路径下打开终端,输入make进行编译构建,出现以下报错信息:
在app_demo.c中定义了两个函数指针变量,在链接时没有找到从而出现报错,原因是在开发环境下链接时使用的默认链接脚本。
为了实现类似于linux内核的调用解析机制,这里需要修改链接脚本文件并指定gcc编译器使用该脚本。
【2-3】生成链接脚本文件
使用ld --verbose > ldsModule.lds生成默认的链接脚本文件
打开生成的ldsModule.lds文件,保留=======之间的内容,删除其他内容,然后在.bss节的上方加入自己在common_define.h文件中定义的【.my_init】节段和在app_demo.c文件中定义的函数指针变量的节位置【my_init_start】和【my_init_end】
添加内容和顺序如下:
my_init_start = .;
.my_init :{*(.my_init)}
my_init_end = .;
最后得到文件内容如下:
OUTPUT_FORMAT("elf64-x86-64", "elf64-x86-64",
"elf64-x86-64")
OUTPUT_ARCH(i386:x86-64)
ENTRY(_start)
SEARCH_DIR("=/usr/local/lib/x86_64-linux-gnu"); SEARCH_DIR("=/lib/x86_64-linux-gnu"); SEARCH_DIR("=/usr/lib/x86_64-linux-gnu"); SEARCH_DIR("=/usr/lib/x86_64-linux-gnu64"); SEARCH_DIR("=/usr/local/lib64"); SEARCH_DIR("=/lib64"); SEARCH_DIR("=/usr/lib64"); SEARCH_DIR("=/usr/local/lib"); SEARCH_DIR("=/lib"); SEARCH_DIR("=/usr/lib"); SEARCH_DIR("=/usr/x86_64-linux-gnu/lib64"); SEARCH_DIR("=/usr/x86_64-linux-gnu/lib");
SECTIONS
{
PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x400000)); . = SEGMENT_START("text-segment", 0x400000) + SIZEOF_HEADERS;
.interp : { *(.interp) }
.note.gnu.build-id : { *(.note.gnu.build-id) }
.hash : { *(.hash) }
.gnu.hash : { *(.gnu.hash) }
.dynsym : { *(.dynsym) }
.dynstr : { *(.dynstr) }
.gnu.version : { *(.gnu.version) }
.gnu.version_d : { *(.gnu.version_d) }
.gnu.version_r : { *(.gnu.version_r) }
.rela.dyn :
{
*(.rela.init)
*(.rela.text .rela.text.* .rela.gnu.linkonce.t.*)
*(.rela.fini)
*(.rela.rodata .rela.rodata.* .rela.gnu.linkonce.r.*)
*(.rela.data .rela.data.* .rela.gnu.linkonce.d.*)
*(.rela.tdata .rela.tdata.* .rela.gnu.linkonce.td.*)
*(.rela.tbss .rela.tbss.* .rela.gnu.linkonce.tb.*)
*(.rela.ctors)
*(.rela.dtors)
*(.rela.got)
*(.rela.bss .rela.bss.* .rela.gnu.linkonce.b.*)
*(.rela.ldata .rela.ldata.* .rela.gnu.linkonce.l.*)
*(.rela.lbss .rela.lbss.* .rela.gnu.linkonce.lb.*)
*(.rela.lrodata .rela.lrodata.* .rela.gnu.linkonce.lr.*)
*(.rela.ifunc)
}
.rela.plt :
{
*(.rela.plt)
PROVIDE_HIDDEN (__rela_iplt_start = .);
*(.rela.iplt)
PROVIDE_HIDDEN (__rela_iplt_end = .);
}
.init :
{
KEEP (*(SORT_NONE(.init)))
}
.plt : { *(.plt) *(.iplt) }
.plt.got : { *(.plt.got) }
.plt.sec : { *(.plt.sec) }
.text :
{
*(.text.unlikely .text.*_unlikely .text.unlikely.*)
*(.text.exit .text.exit.*)
*(.text.startup .text.startup.*)
*(.text.hot .text.hot.*)
*(.text .stub .text.* .gnu.linkonce.t.*)
*(.gnu.warning)
}
.fini :
{
KEEP (*(SORT_NONE(.fini)))
}
PROVIDE (__etext = .);
PROVIDE (_etext = .);
PROVIDE (etext = .);
.rodata: { *(.rodata .rodata.* .gnu.linkonce.r.*) }
.rodata1 : { *(.rodata1) }
.eh_frame_hdr : { *(.eh_frame_hdr) *(.eh_frame_entry .eh_frame_entry.*) }
.eh_frame : ONLY_IF_RO { KEEP (*(.eh_frame)) *(.eh_frame.*) }
.gcc_except_table : ONLY_IF_RO { *(.gcc_except_table
.gcc_except_table.*) }
.gnu_extab : ONLY_IF_RO { *(.gnu_extab*) }
.exception_ranges : ONLY_IF_RO { *(.exception_ranges
.exception_ranges*) }
. = DATA_SEGMENT_ALIGN (ConSTANT (MAXPAGESIZE), ConSTANT (COMMONPAGESIZE));
.eh_frame : ONLY_IF_RW { KEEP (*(.eh_frame)) *(.eh_frame.*) }
.gnu_extab : ONLY_IF_RW { *(.gnu_extab) }
.gcc_except_table : ONLY_IF_RW { *(.gcc_except_table .gcc_except_table.*) }
.exception_ranges : ONLY_IF_RW { *(.exception_ranges .exception_ranges*) }
.tdata: { *(.tdata .tdata.* .gnu.linkonce.td.*) }
.tbss : { *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) }
.preinit_array :
{
PROVIDE_HIDDEN (__preinit_array_start = .);
KEEP (*(.preinit_array))
PROVIDE_HIDDEN (__preinit_array_end = .);
}
.init_array :
{
PROVIDE_HIDDEN (__init_array_start = .);
KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*)))
KEEP (*(.init_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .ctors))
PROVIDE_HIDDEN (__init_array_end = .);
}
.fini_array :
{
PROVIDE_HIDDEN (__fini_array_start = .);
KEEP (*(SORT_BY_INIT_PRIORITY(.fini_array.*) SORT_BY_INIT_PRIORITY(.dtors.*)))
KEEP (*(.fini_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .dtors))
PROVIDE_HIDDEN (__fini_array_end = .);
}
.ctors :
{
KEEP (*crtbegin.o(.ctors))
KEEP (*crtbegin?.o(.ctors))
KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .ctors))
KEEP (*(SORT(.ctors.*)))
KEEP (*(.ctors))
}
.dtors :
{
KEEP (*crtbegin.o(.dtors))
KEEP (*crtbegin?.o(.dtors))
KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .dtors))
KEEP (*(SORT(.dtors.*)))
KEEP (*(.dtors))
}
.jcr : { KEEP (*(.jcr)) }
.data.rel.ro : { *(.data.rel.ro.local* .gnu.linkonce.d.rel.ro.local.*) *(.data.rel.ro .data.rel.ro.* .gnu.linkonce.d.rel.ro.*) }
.dynamic : { *(.dynamic) }
.got : { *(.got) *(.igot) }
. = DATA_SEGMENT_RELRO_END (SIZEOF (.got.plt) >= 24 ? 24 : 0, .);
.got.plt : { *(.got.plt) *(.igot.plt) }
.data:
{
*(.data .data.* .gnu.linkonce.d.*)
SORT(CONSTRUCTORS)
}
.data1 : { *(.data1) }
_edata = .; PROVIDE (edata = .);
. = .;
__bss_start = .;
my_init_start = .;
.my_init :{*(.my_init)}
my_init_end = .;
.bss :
{
*(.dynbss)
*(.bss .bss.* .gnu.linkonce.b.*)
*(COMMON)
. = ALIGN(. != 0 ? 64 / 8 : 1);
}
.lbss :
{
*(.dynlbss)
*(.lbss .lbss.* .gnu.linkonce.lb.*)
*(LARGE_COMMON)
}
. = ALIGN(64 / 8);
. = SEGMENT_START("ldata-segment", .);
.lrodata ALIGN(ConSTANT (MAXPAGESIZE)) + (. & (ConSTANT (MAXPAGESIZE) - 1)) :
{
*(.lrodata .lrodata.* .gnu.linkonce.lr.*)
}
.ldata ALIGN(ConSTANT (MAXPAGESIZE)) + (. & (ConSTANT (MAXPAGESIZE) - 1)) :
{
*(.ldata .ldata.* .gnu.linkonce.l.*)
. = ALIGN(. != 0 ? 64 / 8 : 1);
}
. = ALIGN(64 / 8);
_end = .; PROVIDE (end = .);
. = DATA_SEGMENT_END (.);
.stab 0 : { *(.stab) }
.stabstr 0 : { *(.stabstr) }
.stab.excl 0 : { *(.stab.excl) }
.stab.exclstr 0 : { *(.stab.exclstr) }
.stab.index 0 : { *(.stab.index) }
.stab.indexstr 0 : { *(.stab.indexstr) }
.comment 0 : { *(.comment) }
.debug 0 : { *(.debug) }
.line 0 : { *(.line) }
.debug_srcinfo 0 : { *(.debug_srcinfo) }
.debug_sfnames 0 : { *(.debug_sfnames) }
.debug_aranges 0 : { *(.debug_aranges) }
.debug_pubnames 0 : { *(.debug_pubnames) }
.debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) }
.debug_abbrev 0 : { *(.debug_abbrev) }
.debug_line 0 : { *(.debug_line .debug_line.* .debug_line_end ) }
.debug_frame 0 : { *(.debug_frame) }
.debug_str 0 : { *(.debug_str) }
.debug_loc 0 : { *(.debug_loc) }
.debug_macinfo 0 : { *(.debug_macinfo) }
.debug_weaknames 0 : { *(.debug_weaknames) }
.debug_funcnames 0 : { *(.debug_funcnames) }
.debug_typenames 0 : { *(.debug_typenames) }
.debug_varnames 0 : { *(.debug_varnames) }
.debug_pubtypes 0 : { *(.debug_pubtypes) }
.debug_ranges 0 : { *(.debug_ranges) }
.debug_macro 0 : { *(.debug_macro) }
.debug_addr 0 : { *(.debug_addr) }
.gnu.attributes 0 : { KEEP (*(.gnu.attributes)) }
/DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) }
}
(注意)以上文件内容和语法的更多详情需要参考:GNU链接器脚本和语法
【重新修改makefile,指定gcc链接脚本:gcc -T ./ldsModule.lds】
修改后文件如下:
################################################################################# # driver test makefile file #datetime 2021/10/24 #author iriczhao ################################################################################## CC=cc CC_FLAG=-Wall PRG=app_demo OBJ=module_1.o module_2.o app_demo.o $(PRG):$(OBJ) gcc -T ./ldsModule.lds -o $@ $(OBJ) .PRONY:clean clean: @echo "Removing linked and compiled files......" rm -f $(OBJ) $(PRG)
【再次编译链接】运行程序结果如下图所示:
从上图中可见,module1和module2中使用MY_DECLAER_INIT宏声明的函数都被编译执行了,以此方式和思路实现了类似于linux内核中__init宏的机制。
利用__attribute__和__section__可以完成一些高级和复杂的操作。将一个函数或变量数据放入一个ELF的特殊段中,然后在程序中对该段中内容进行操作和调用,或者类似于linux一样当在内核启动和再将其释放掉。
本文所讨论的机制和技术,总而言之,就是链接器首先构造一个函数指针的列表,其中的每一个指针指向一个初始化函数,然后在代码中使用一个循环,依次执行这些函数。



