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

问题描述 投票:1回答:1

在他关于理解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?

linux ld
1个回答
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_endfunction_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的类型是一种编程语言概念,表示该语言中的程序如何使用其地址由S符号化的字节序列。

只要链接提供了该符号,C程序就可以自由地声明它所使用的外部符号S所需的任何类型。无论可能是什么类型,程序都将获得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)
© www.soinside.com 2019 - 2024. All rights reserved.