ld:这个ld脚本是如何工作的?

巴鲁迪·萨夫温(Baroudi Safwen)

在关于理解Linux内核Initcall机制的文章中,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分配了当前输出位置的值,然后定义了一个function_ptrs部分,该部分将指向输入文件中的function_ptrs部分,但是我看不到__initcall_start和funtction_ptrs部分之间的链接。

我的问题是:链接程序如何理解__initcall_start应该指向funtion_ptrs?

迈克·金汉
__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部分。

链接器没有指针的概念它处理表示地址的符号

在链接器手册“分配:定义符号”中,您将看到:

您可以使用任何C赋值运算符创建全局符号,并将值(地址)分配给全局符号:

符号=表达式;

...

这意味着将其简单地symbol定义为由计算的地址的符号expression同样地:

__initcall_start = .;

表示__initcall_start被定义为当前位置计数器上地址的符号它不暗示该符号具有任何类型-甚至不表示它是数据符号还是功能符号。符号类型S是一种编程语言概念,表达了使用该语言的程序如何消耗其地址用表示 的字节序列S

S只要链接提供该符号,AC程序就可以自由声明其喜欢的外部符号的任何类型无论是哪种类型,程序都将获得用表达式符号表示的地址 S&S

您的C程序选择声明__initcall_start__initcall_end类型均为

int (*initcall_t)(void);

在程序告诉链接器执行操作的上下文中,这很有意义。它告诉链接器function_ptrs __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)

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章