Chinaunix首页 | 论坛 | 博客
  • 博客访问: 375503
  • 博文数量: 44
  • 博客积分: 2060
  • 博客等级: 上尉
  • 技术积分: 528
  • 用 户 组: 普通用户
  • 注册时间: 2008-04-17 20:50
文章分类
文章存档

2011年(1)

2010年(28)

2008年(15)

分类:

2010-03-08 13:34:42

3.3 构造Section

 

在构建了基本的segment后,就可以从输入.o文件中获取感兴趣的section以生成新的section并放入相应的segment。在这里,输入的section称为input section,生成的新section称为output section。除此之外,有一个重要的链接脚本符号“.”需要了解。”.”是个位置计数器,记录着当前位置在目标文件中的虚拟地址(VMA)”.”是个自动增加的计数器,当一个output section生成后,”.”的值自动加上该output section的长度。我们也可以显式的给”.”赋值以改变当前位置的地址,这在内核链接脚本中被大量使用。一个例子可以很好的描述”.”的作用:

. = 0x100000;

_start_addr = .;

.text : { *(.text) }

_end_addr = . ;

这里我们首先给”.”赋了一个初值,将地址指定到0x100000处,并将该值赋给变量_start_addr,它是.text section的起始地址;接着我们生成了一个.text section,此时”.”自动加上该section的长度,可描述为 . = . + SIZEOF(.text);最后将”.”赋值给_end_addr,记录下.text的结束地址。此时”.”的值变成了 0x100000 + SIZEOF(.text)。有了”.”的帮助,我们可以灵活的控制目标文件中各个section所在的虚拟地址(VMA)

 

3.3.1 Text Segment的构造

内核首先构造的是text segment,该segment又由若干个.text.*节构成,除此之外,它还包含了note segment的内容以及只读数据section。下面的代码完成了这些工作:

SECTIONS

{

  . = LOAD_OFFSET + LOAD_PHYSICAL_ADDR;

  phys_startup_32 = startup_32 - LOAD_OFFSET;

 

  .text.head : AT(ADDR(.text.head) - LOAD_OFFSET) {

      _text = .;               /* Text and read-only data */

       *(.text.head)

  } :text = 0x9090

 

  /* read-only */

  .text : AT(ADDR(.text) - LOAD_OFFSET) {

       . = ALIGN(PAGE_SIZE); /* not really needed, already page aligned */

       *(.text.page_aligned)

       TEXT_TEXT

       SCHED_TEXT

       LOCK_TEXT

       KPROBES_TEXT

       *(.fixup)

       *(.gnu.warning)

      _etext = .;                     /* End of text section */

  } :text = 0x9090

 

  NOTES :text :note

 

  . = ALIGN(16);         /* Exception table */

  __ex_table : AT(ADDR(__ex_table) - LOAD_OFFSET) {

      __start___ex_table = .;

        *(__ex_table)

      __stop___ex_table = .;

  } :text = 0x9090

 

  RODATA

SECTIONS

{

  . = LOAD_OFFSET + LOAD_PHYSICAL_ADDR;

  phys_startup_32 = startup_32 - LOAD_OFFSET;

 

  .text.head : AT(ADDR(.text.head) - LOAD_OFFSET) {

      _text = .;               /* Text and read-only data */

       *(.text.head)

  } :text = 0x9090

 

  /* read-only */

  .text : AT(ADDR(.text) - LOAD_OFFSET) {

       . = ALIGN(PAGE_SIZE); /* not really needed, already page aligned */

       *(.text.page_aligned)

       TEXT_TEXT

       SCHED_TEXT

       LOCK_TEXT

       KPROBES_TEXT

       *(.fixup)

       *(.gnu.warning)

      _etext = .;                     /* End of text section */

  } :text = 0x9090

 

  NOTES :text :note

 

  . = ALIGN(16);         /* Exception table */

  __ex_table : AT(ADDR(__ex_table) - LOAD_OFFSET) {

      __start___ex_table = .;

        *(__ex_table)

      __stop___ex_table = .;

  } :text = 0x9090

 

  RODATA

首先是SECTIONS关键字,官方的解释是“The SECTIONS command tells the linker how to map input sections into output sections, and how to place the output sections in memory.”,实际上可以把它看成一个描述符,所有的工作要在它的内部完成。就像你在C中定义一个结构体要以struct关键字开头一样。

构造的第一步是为”.”指定初值,之后所有section虚拟地址(VMA)都由该值计算得来(前面我们讲过,生成一个section”.”的值会自动加上改section的长度)。这里初始值为

LOAD_OFFSET + LOAD_PHYSICAL_ADDR,前者是我们熟知的内核虚拟地址空间起始地址0xC0000000LOAD_PHYSICAL_ADDR是内核image加载的物理地址,由CONFIG_PHYSICAL_START计算得到。该物理地址是可以指定的,你可以在.config文件中找到它,也可以由make menuconfig得到,具体解释参考arch/x86/Kconfig文件的PHYSICAL_START条目。对于一般的x86架构,内核被加载到物理地址0x100000处,故”.”的初值为0xC0100000。接着

phys_startup_32 = startup_32 - LOAD_OFFSET;

计算了内核image的入口地址,这在前面已经提到。

开始构造section了。由于使用的语法是固定的,我们只需要了解一个例子,其余的就可举一反三。以第一个section为例:

.text.head : AT(ADDR(.text.head) - LOAD_OFFSET) {

      _text = .;               /* Text and read-only data */

       *(.text.head)

  } :text = 0x9090

.text.head : AT(ADDR(.text.head) - LOAD_OFFSET) {

      _text = .;               /* Text and read-only data */

       *(.text.head)

  } :text = 0x9090

 

.text.head指定了生成的section的名字,后面的冒号是固定语法。AT关键字前面介绍过,指定该section的加载地址(LMA),它的完整表达是

AT(expression)

括号中expression表达式指定LMA的值。在此例中该表达式由

ADDR(.text.head) - LOAD_OFFSET

计算得到。这里

ADDR(section)

计算section的虚拟地址,故.text.head的加载地址(LMA)是它的物理地址。在大括号内部,_text = .;                                                                        

定义了一个全局变量,它的值为”.”的当前值,记录了整个text segment的起始地址.。在这里,由于_text变量前还没有任何section被创建,故_text有如下等价关系:

_text = ADDR(.text.head) = . = LOAD_OFFSET + LOAD_PHYSICAL_ADDR;

*(.text.head)完成了具体的section创建工作,”*”代表所有输入的.o文件,括号中的.text.head指定了链接器感兴趣的section名。

*(text.head)

表示从所有输入文件中抽取名为.text.headsection并填充到目标文件的.text.head section中。

: text

指定了新生成section所在的segment,这里冒号后的textsegment名,可见内核的第一个section被放到了text segment

= 0x9090

指定section的填充内容。从输入文件中抽取来的section由于代码对齐的缘故,其二进制的存放可能是不连续的,这里指定对section中的空隙用0x9090进行填充。0x90是汇编指令NOP的机器码,故相当于在不连续代码间填充空操作。至此,内核的第一个section就创建好了,它名为.text.head,由输入文件的.text.head section构成(并非所有文件都有.text.head section,链接器只从具有该section的文件中抽取内容),该section的虚拟地址(VMA)由”.”的值确定,加载地址(LMA)为其物理地址,section中不连续区域产生的间隙由0x9090填充,最后该section被放入了内核的text segment中。

通过objdump内核,我们可以看到关于该section的最终内容:

Sections:

Idx Name          Size      VMA       LMA       File off  Algn

  0 .text.head    00000375  c1000000  01000000  00001000  2**2

                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE

 

  …………………………………………………………………………………………..

Disassembly of section .text.head:

 

c1000000 <_text>:

c1000000:>--f6 86 11 02 00 00 40 >--testb  $0x40,0x211(%esi)

c1000007:>--75 14                >--jne    c100001d <_text+0x1d>

c1000009:>--0f 01 15 1a e1 4d 01 >--lgdtl  0x14de11a

>--->--->---c100000c: R_386_32>-boot_gdt_descr

c1000010:>--b8 18 00 00 00       >--mov    $0x18,%eax

c1000015:>--8e d8                >--mov    %eax,%ds

 

…………………………………………………………………………………………………

c10013d5:>--5b                   >--pop    %ebx

c10013d6:>--5e                   >--pop    %esi

c10013d7:>--c9                   >--leave--

c10013d8:>--c3                   >--ret----

c10013d9:>--90                   >--nop----

c10013da:>--90                   >--nop----

c10013db:>--90                   >--nop----

c10013dc:>--90                   >--nop----

Sections:

Idx Name          Size      VMA       LMA       File off  Algn

  0 .text.head    00000375  c1000000  01000000  00001000  2**2

                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE

 

  …………………………………………………………………………………………..

Disassembly of section .text.head:

 

c1000000 <_text>:

c1000000:>--f6 86 11 02 00 00 40 >--testb  $0x40,0x211(%esi)

c1000007:>--75 14                >--jne    c100001d <_text+0x1d>

c1000009:>--0f 01 15 1a e1 4d 01 >--lgdtl  0x14de11a

>--->--->---c100000c: R_386_32>-boot_gdt_descr

c1000010:>--b8 18 00 00 00       >--mov    $0x18,%eax

c1000015:>--8e d8                >--mov    %eax,%ds

 

…………………………………………………………………………………………………

c10013d5:>--5b                   >--pop    %ebx

c10013d6:>--5e                   >--pop    %esi

c10013d7:>--c9                   >--leave--

c10013d8:>--c3                   >--ret----

c10013d9:>--90                   >--nop----

c10013da:>--90                   >--nop----

c10013db:>--90                   >--nop----

c10013dc:>--90                   >--nop----

c10013dd:>--90                   >--nop----

c10013de:>--90                   >--nop----

c10013df:>--90                   >--nop----

其中最后一部分显示了填充0x9090产生的nop指令。

链接脚本知识

创建一个section的完整格式是:

section [address] [(type)] : [AT(lma)]

  {

    output-section-command

    output-section-command

    ...

  } [>region] [:phdr :phdr ...] [=fillexp]

其中[address]参数在上例中没有提到,它指定了section的虚拟地址(VMA),如果没有指定该参数及region参数,section的虚拟地址由当前”.”的值确定,正如上例我们看到的一样。Region用于将section分配给通过MEMORY关键字创建的内存描述块,内核链接脚本没使用它,本文也不关注,具体内容详见参考文献1MEMORY command一节。

通过这个例子,我们很容易就可以理解text segment中其它section的创建。例如接下来的第二个.text section,它的创建方法和.text.head类似,唯一不同的是这里多了一句:

. = ALIGN(PAGE_SIZE);

ALIGN(exp)关键字计算当前”.”值对齐到exp边界后的地址,即:

ALIGN(exp) = ( . + exp – 1) & ~(exp – 1);

此处在创建.text section前,将”.”对齐到了页边界,从第一个输入section的名字.text.page_aligned就可以看出,输入section的内容是有对齐要求的。内核使用了TEXT_TEXT等宏将不同类型的输入section进行了封装,展开后可以看到它们都是:

*(section_name)

的形式,和我们前面讲的一样,不再多做介绍。

从上面内容可以看出,输入文件中的section有各种各样的名字,如.text.head.text.page_aligned.text.hot等,并不是所有的section名都是标准的,绝大部分是内核使用GCC扩展生成的自定义名。举个例子,我们常见的__init宏,展开后如下:

#define __init __attribute__ ((__section__(“.init.text”)))

这里.init.text是个自定义的section,用__init修饰的函数编译后会被放到名为.init.text section中。

自定义的section极大的发挥了链接脚本的作用,让我们可以对代码中的函数、数据进行归类操作,同时还可以完成一些在程序中不易完成的功能。这很容易理解,如果我们都用GCC内置的section,何必要自定义链接脚本,用默认的不就好了。

链接脚本向我们展示了大量的自定义section,本人水平有限,无法一一弄清每个section的用途,但通过几个常见的典型例子,我们可以了解它们的用法。首先就以text segment中的exception table举例。

 

阅读(4358) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~