MEMORY

MEMORY {
    NAME1 [(ATTR)] : ORIGIN = ORIGIN1, LENGTH = LEN2
    NAME2 [(ATTR)] : ORIGIN = ORIGIN2, LENGTH = LEN2
    ...
}

在默认情况下,链接器可以为 section 分配任意位置的存储区域,也可以使用 MEMORY 命令定义存储区域,并通过输出 section 描述的 [AT > REGION] 属性显示地该输出 section 限定于某块存储区域,当存储区域大小不能满足要求时,链接器会报错。

NAME

存储区域的名字,这个名字可以与符号名,文件名,section 名重复,因为它处于一个独 立的名字空间内。

ATTR

定义该存储区域的属性。在默认情况下,当某个输出 section 没有在 SECTIONS 命令内 被引用是,链接器会把该输出 setion 直接拷贝成输出 section,然后将该输出 section 放入内存区域内。如果内存设置了 ATTR 属性,那么该区域只接收满足该属性的 section。ATTR 属性内可以出现七个字符:

  1. R 只读 section

  2. W 读/写 section

  3. X 可执行 section

  4. A 可分配的 section

  5. I 初始化的 section

  6. L 初始化的 section

  7. !不能满足该字符之后的任何一个属性的 section

ORIGIN

关键字,区域的开始地址,可以简写成 org 或 o

LENGTH

关键字,区域的大小,可简写成 len 或者 l

一个简单的例子:

MEMORY
{
    rom (rx) : ORIGIN = 0, LENGTH = 256K
    ram(!rx) : org = 0x40000000, l = 4M
}

MEMORY 与 SECTIONS 的层级关系:

MEMORY { ... }
SECTIONS { ... }

此例子中,把 SECTIONS 命令内未引用的且具有读属性或者写属性的输入 section 放入 rom 区域内,把其他未引用的输入 section 放入 ram. 如果某些输出 section 要被放入 某内存区域内,而该输出 section 又没有指明 ADDRESS 属性,那么链接器将该输出 section 放在区域内下一个能使用位置。

e.g. 三个源文件 DemoA.c,DemoB.c 和 DemoC.c,其中 DemoA.c 引用 DemoA.c 和 DemoB.c 里面的函数,使用 GCC 生成独立的目标文件 DemoA.o,DemoB.o 和 DemoC.o。 ld 使用链接脚本 Demo.lds, 并且在 Demo.lds 里面通过 MEMORY 关键字,以此来创建一 些内存区域:

DemoA.c

extern void print();
extern void exit();

void nomain()
{
    print();
    exit();
}

DemoB.c

char *str = "Hello World\n";

void print()
{
    __asm__ ("movl $13, %%edx\n\t"
             "movl %0, %%ecx\n\t"
             "movl $0, %%ebx\n\t"
             "movl $4, %%eax\n\r"
             "int $0x80\n\t"
             :: "r" (str) : "edx", "ecx", "ebx");
}

DemoC.c

void exit()
{
    __asm__ ("movl $42, %ebx\n\t"
             "movl $1, %eax\n\t"
             "int $0x80\n\t");
}

Demo.lds

ENTRY(nomain)

INPUT(DemoA.o)
INPUT(DemoB.o)
INPUT(DemoC.o)

MEMORY
{
    rom (r)   : ORIGIN = 0x08060000, LENGTH = 0x100
    ram (rw)  : ORIGIN = 0x08050000, LENGTH = 0x4000
}

SECTIONS
{
    . = 0x08048000 + SIZEOF_HEADERS;

    DemoText  { *(.text) *(.data) }

    DemoRD : { *(.rodata) }

    /DISCARD/ : { *(.comment) }
}

使用如下命令进行编译和链接:

gcc DemoA.c -c
gcc DemoB.c -c
gcc DemoC.c -c -fno-builtin
ld -static -T Demo.lds -o a.out
objdump -xSsdh a.out

LD

通过上面的运行数据可知,在链接脚本里创建了两个内存区域,其中第一个内存区域为只 读区域,起始地址为 0x08060000, 大小为 0x100,根据这个区域的属性,可知输出 DemoRD section 就是只读区域内的,所以输出 DemoRD section 的起始地址为 0x08060000。第二个内存区域为可读可写区域,起始地址为 0x08050000, 大小为 0x4000,根据这个区域的属性,可知输出 DemoText section 就是可读可写的,所以输出 DemoText section 的起始地址为 0x08050000。

上面的形式都是映射的将输出 section 放到符合属性的内存区域内,开发者也可以显示 的将输出 section 放到指定的内存区域内,如下链接脚本

ENTRY(nomain)

INPUT(DemoA.o)
INPUT(DemoB.o)
INPUT(DemoC.o)

MEMORY
{
    rom (r)   : ORIGIN = 0x08060000, LENGTH = 0x100
    ram (rw)  : ORIGIN = 0x08050000, LENGTH = 0x4000
    com (rw)  : ORIGIN = 0x08070000, LENGTH = 0x8000
}

SECTIONS
{
    . = 0x08048000 + SIZEOF_HEADERS;

    DemoText  { *(.text) } > ram

    DemoData : { *(.data) } > com

    DemoRD : { *(.rodata) } > rom

    /DISCARD/ : { *(.comment) }
}

运行下面命令:

gcc DemoA.c -c
gcc DemoB.c -c
gcc DemoC.c -c -fno-builtin
ld -static -T Demo.lds -o a.out
objdump -xSsdh a.out

LD

通过上面的运行数据可知,DemoText 被放到了内存区域 ram 里面,其起始地址也变成了 0x08050000;DemoData 被放到了内存区域 com 里面,其起始地址变成了 0x08070000; DemoRD 被放到了内存区域 rom 里面,其起始地址也变成了 0x080600000。


附录

BiscuitOS Home

BiscuitOS Driver

BiscuitOS Kernel Build

Linux Kernel

Bootlin: Elixir Cross Referencer

赞赏一下吧 🙂

MMU