PHDRS

PHDRS {
    NAME TYPE [FILEHDR][PHDRS][AT(ADDRESS)]
    [FLAGS(FLAGS)]
}

PHDRS 命令仅仅在生成 ELF 目标文件时有效。ELF 目标文件格式用 program headers 程 序头 (程序头内包含一个或多个 segment 程序段描述) 来描述程序如何被载入内存。可 以使用 objdump -p 命令查看。

当操作系统执行 ELF 目标文件格式的程序时,系统加载器通过读取程序头信息以通知如 何将程序加载到内存。在链接脚本内不指定 PHDRS 命令时,链接器能够很好的创建程序 头,但是有时需要更精确的描述程序头,那么 PHDRS 命令就可以派上用场。注意,一但 在链接脚本内使用了 PHDRS 命令,那么链接器仅仅会创建 PHDRS 命令指定的信息,所以 使用时须谨慎。

FILEHDR, PHDRS, AT, FLAGS 为关键字

NAME

程序段名,此名字可以与符号名,section 名,文件名重复,因此它在一个独立的名字空 间内。此名字只能在 SECTIONS 命令内使用。一个程序段可以由多个可加载的 section 组成。通过输出 section 描述段的属性 PHDRS 可以将输出 section 加入一个程序段这 里指的是 PHDRS 中的 PHDRS, PHDRS 为段名。在一个输出 section 描述内可以多次使 用 PHDRS 命令,也既可以将一个 section 加入到多个程序段。如果在一个输出 section 描述内指定了 PHDRS 属性,那么其后的输出 section 描述将默认使用该属性, 除非它也定义了 PHDRS 属性。当然当多个输出 section 属于同一程序段时可以简化书写。

TYPE

在 TYPE 属性后存在 FILEHDR 关键字,表示该段包含 ELF 头信息。TYPE 可以使一下八 种形式:

  1. PT_NULL 0

    表示未被使用的程序段。

  2. PT_LOAD 1

    表示该程序段在程序运行时应该被加载

  3. PT_DYNAMIC 2

    表示该程序段包含动态链接信息

  4. PT_INTERP 3

    表示该程序段内包含程序加载器的名字,在 linux 下常见的程序加载器是 ld-linux.so.2

  5. PT_NOTE 4

    表示该程序段内包含了程序的说明信息

  6. PT_SHLIB 5

    一个保留的程序头类型,没有在 ELF ABI 文档内定义。

  7. PT_PHDR 6

    表示该程序段包含程序头信息

EXPRESSION 表达式值

以上每个类型对应一个数字,该表达式定义一个用户自定义的程序头。

AT(ADDRESS)

AT(ADDRESS) 属性定义该程序段的加载位置 (LMA). 该属性将覆盖该程序段内的 section 的 AT() 属性。

FLAGS

默认情况下,链接器会根据该程序段包含的 section 的属性设置 FLAGS 标志,该标志 用于设置程序段描述的 p_flags 域。

一个简单的例子:

PHDRS
{
    headers PT_PHDR PHDRS;
    interp PT_INTERP;
    text PT_LOAD FILEHDR PHDRS;
    data PT_LOAD;
    dynamic PT_DYNAMIC;
}

SECTIONS
{
    . = 0x8048000 + SIZEOF_HEADERS;
    .interp : { *(.interp) } : text : interp
    .text { *(.text) } :text
   .rodata : { *(.rodata) } /* Defaults to .text */

    ....

    . = . + 0x1000;  /* mov to a new page in memory */
    .data : { *(.data) } :data
    .dynamic : { *(.dynamic) } :data : dynamic

    ....
}

PHDRS 与 SECTIONS 之间的层级关系:

PHDRS
{
    NAME TYPE [FILEHDR][PHDRS][AT(ADDRESS)][FLAGS(FLAGS)]
}

SECTIONS
{
    SECTION [ADDRESS][(TYPE)]:[AT(LMA)] {
        OUTPUT-SECTION-COMMAND
        OUTPUT-SECTION-COMMAND
    } [>REGION][AT>LMA_REGION][:PHDR HDR ...][=FILEEXP]
}

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 里面通过 PHDRS 关键字,以此来创建输 出文件的 ELF HEADERS:

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)

PHDRS
{
    headers PT_PHDR PHDRS;
    interp PT_INTERP;
    text PT_LOAD FILEHDR PHDRS;
    data PT_LOAD;
    dynamic PT_DYNAMIC;
}

SECTIONS
{
    . = 0x08048000 + SIZEOF_HEADERS;

    DemoIntR : { *(.interp) } :text :interp
    DemoText : { *(.text) } :text
    DemoRD : { *(.rodata) }

    . = . + 0x1000;

    DemoData : { *(.data) } :data
    DemoDyna : { *(.dynamic) } :data :dynamic

    /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

通过上面的运行数据可知,输出 ELF 文件的 Program Header 区域已经添加了指定的 HEADER 信息。


附录

BiscuitOS Home

BiscuitOS Driver

BiscuitOS Kernel Build

Linux Kernel

Bootlin: Elixir Cross Referencer

赞赏一下吧 🙂

MMU