在他关于理解Linux Kernel Initcall Mechanism的文章中,Trevor创建了一个用户空间程序,模拟调用linux驱动程序的init_module()的机制。
#include <stdio.h>
typedef int (*initcall_t)(void);
extern initcall_t __initcall_start, __initcall_end;
#define __initcall(fn) \
static initcall_t __initcall_##fn __init_call = fn
#define __init_call __attribute__ ((unused,__section__ ("function_ptrs")))
#define module_init(x) __initcall(x);
#define __init __attribute__ ((__section__ ("code_segment")))
static int __init
my_init1 (void)
{
printf ("my_init () #1\n");
return 0;
}
static int __init
my_init2 (void)
{
printf ("my_init () #2\n");
return 0;
}
module_init (my_init1);
module_init (my_init2);
void
do_initcalls (void)
{
initcall_t *call_p;
call_p = &__initcall_start;
do {
fprintf (stderr, "call_p: %p\n", call_p);
(*call_p)();
++call_p;
} while (call_p < &__initcall_end);
}
int
main (void)
{
fprintf (stderr, "in main()\n");
do_initcalls ();
return 0;
}
如您所见,__initcall_start和__initcall_end未定义,因此链接器会抱怨并且不会生成可执行文件。解决方案是通过在文本部分之前添加以下行来自定义默认链接描述文件(由ld --verbose生成):
__initcall_start = .;
function_ptrs : { *(function_ptrs) }
__initcall_end = .;
code_segment : { *(code_segment) }
以下是objdump -t:
输出的片段0000000000000618 g function_ptrs 0000000000000000 __initcall_end<br>
0000000000000608 g .plt.got 0000000000000000 __initcall_start<br>
0000000000000608 l O function_ptrs 0000000000000008 __initcall_my_init1<br>
0000000000000610 O function_ptrs 0000000000000008 __initcall_my_init2<br>
0000000000000618 l F code_segment 0000000000000017 my_init1<br>
我理解机制,我只是不知道链接器如何理解__initcall_start应指向function_ptrs部分或者__initcall_end如何指向code_segment部分。
我看到它的方式,__initcall_start被赋值当前输出位置的值,然后定义了一个section_ptrs部分,它将指向输入文件中的function_ptrs部分,但是我看不到__initcall_start和funtction_ptrs部分之间的链接。
我的问题是:链接器如何理解__initcall_start应该指向funtion_ptrs?
答案 0 :(得分:1)
__initcall_start = .;
function_ptrs : { *(function_ptrs) }
__initcall_end = .;
code_segment : { *(code_segment) }
此位链接描述文件指示链接器如何编写 输出文件的某些部分。这意味着: -
__initcall_start
来寻址位置计数器(即.
)function_ptrs
的部分,该部分由连接组成
所有输入部分都称为function_ptrs
(即function_ptrs
来自所有输入文件的段。)__initcall_end
寻址位置计数器。code_segment
的部分,该部分由连接组成
所有输入部分都称为code_seqment
) function_ptrs
部分是该位置的第一个存储空间
由__initcall_start
处理。所以__initcall_start
是链接器的地址
启动function_ptrs
段。 __initcall_end
解决了该位置问题
在function_ptrs
段之后。同样地,它是地址
链接器启动code_segment
段。
我看到它的方式,为__initcall_start分配了当前输出位置的值,......
你在想:
__initcall_start = .;
使链接器创建一个在某种意义上是指针的符号 并将当前位置指定为该指针的值。有一点像 这个C代码:
void * ptr = &ptr;
这里有相同的想法(强调我的):
我只是看不到链接器如何理解__initcall_start应该如何 指向指向function_ptrs部分或__initcall_end将如何指向指向 code_segment部分。
链接器没有指针的概念。它处理符号符号化地址。
在链接器手册中,Assignment: Defining Symbols 你看:
您可以使用任何C赋值运算符创建全局符号,并将值(地址)分配给全局符号:
symbol = expression;
...
这意味着symbol
被定义为由expression
计算的地址的符号。
同样地:
__initcall_start = .;
表示__initcall_start
被定义为当前地址的符号
地点柜台。这意味着该符号没有类型 - 甚至没有
它是数据符号或函数符号。符号S
的类型是一个编程 -
表达该语言中的程序如何使用其字节序列的语言概念
地址由{/ em> S
符号化。
C程序可以自由地声明它喜欢的任何类型
只要链接提供该符号,它使用的外部符号S
。
无论可能是什么类型,程序都将获得 所表示的地址
S
表达式&S
。
您的C程序选择同时声明__initcall_start
和__initcall_end
截至:
int (*initcall_t)(void);
在程序告诉链接器执行的操作的上下文中很有意义。它
告诉链接器在地址之间布局function_ptrs
部分
由{/ em> __initcall_start
和__initcall_end
表示。本节包括
类型为int ()(void)
的函数数组。所以输入int (*initcall_t)(void)
完全适合遍历该数组,如:
call_p = &__initcall_start;
do {
fprintf (stderr, "call_p: %p\n", call_p);
(*call_p)();
++call_p;
} while (call_p < &__initcall_end)