间接寻址

间接寻址是一种代码生成技术的名字,它允许被定义在文件中的symbols能够被另外一个文件引用,并且不需要引用文件知道任何有关定义这些symbol文件的布局。因此,定义文件能够被独立地修改。间接寻址最小化了动态链接器需要修改的地址,这会提高代码分享和提高效率。
当一个文件使用了定义在另一个文件的数据时,它会创建symbol引用,一个symbol reference标志了symbol是属于哪个文件的和它引用的symbol,有两种类型的symbol reference:non lazy和lazy.

  1. Non-lazy symbol references在一个module被加载的时候被动态链接器resolve(把symbol转化为对应data或function的内存地址)。
    一个non-lazy symbol引用的本质是一个symbol 指针,编译器会产生non-lazy symbol的引用给数据symbols或者函数地址。

  2. Lazy symbol reference是module第一次使用时被动态链接器加载resolved,随后调用被引用的symbol直接跳到symbol 的定义中(即函数或数据中)

Lazy symbol引用是由一个symbol指针和一个symbol stub组成,一小量没有直接引用和符号指针的代码。编译器当它遇到调用另外一个文件的函数时会产生lazy symbol引用

接下来的段落描述了symbol引用时在PowePc和IA-32架构上如何被实现的,如果想知道PowerPC和IA-32的详细信息,seeOS X Assembler Reference

PowePC 符号引用

在PowerPC架构里,当你产生被定义在其它文件的函数调用时,编译器会产生symbol stub和一个lazy symbol指针。Lazy symbol指针是一个地址,它最初设置是为了调用粘合函数dyld_stub_binding_helper中的粘合代码。这个粘合函数调用了动态链接器的功能函数,这些函数会调用用于绑定stub的实际工作。在dyld_stub_binding_helper返回之后,lazy 指针指向了外部函数的实际地址中。
下列这些在Listing 1里的简单的代码样本产生了两个不同类型的symbol stubs,根据它是否使用position-indenpendent代码生成技术。Listing 2展示了间接寻址而不用position-independent代码。Listing 3展示了间接寻址和position-indenpendent的代码。

Listing 1 C code example for indirect function calls

1
2
3
4
5
extern void bar(void);
void foo(void)
{
bar();
}

Listing 2 Example of an indirect function call

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
.text
; The function foo
.align 2
.globl _foo
_foo:
mflr r0 ; move the link register into r0
stw r0,8(r1) ; save the link register value on the stack
stwu r1,-64(r1) ; set up the frame on the stack
bl L_bar$stub ; branch and link to the symbol stub for _bar
lwz r0,72(r1) ; load the link register value from the stack
addi r1,r1,64 ; removed the frame from the stack
mtlr r0 ; restore the link register
blr ; branch to the link register to return

.symbol_stub ; the standard symbol stub section
L_bar$stub:
.indirect_symbol _bar ; identify this symbol stub for the
; symbol _bar
lis r11,ha16(L_bar$lazy_ptr) ; load r11 with the high 16 bits of the
; address of bar’s lazy pointer
lwz r12,lo16(L_bar$lazy_ptr)(r11) ; load the value of bar’s lazy pointer
; into r12
mtctr r12 ; move r12 to the count register
addi r11,r11,lo16(L_bar$lazy_ptr) ; load r11 with the address of bars lazy
; pointer
bctr ; jump to the value in bar’s lazy pointer

.lazy_symbol_pointer ; the lazy pointer section
L_bar$lazy_ptr:
.indirect_symbol _bar ; identify this lazy pointer for symbol
; _bar
.long dyld_stub_binding_helper ; initialize the lazy pointer to the stub
; binding helper address

Listing 3 Example of a position-independent, indirect function call

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
.text
; The function foo
.align 2
.globl _foo
_foo:
mflr r0 ; move the link register into r0
stw r0,8(r1) ; save the link register value on the stack
stwu r1,-80(r1) ; set up the frame on the stack
bl L_bar$stub ; branch and link to the symbol stub for _bar
lwz r0,88(r1) ; load the link register value from the stack
addi r1,r1,80 ; removed the frame from the stack
mtlr r0 ; restore the link register
blr ; branch to the link register to return

.picsymbol_stub ; the standard pic symbol stub section
L_bar$stub:
.indirect_symbol _bar ; identify this symbol stub for the symbol _bar
mflr r0 ; save the link register (LR)
bcl 20,31,L0$_bar ; Use the branch-always instruction that does not
; affect the link register stack to get the
; address of L0$_bar into the LR.
L0$_bar:
mflr r11 ; then move LR to r11
; bar’s lazy pointer is located at
; L0$_bar + distance
addis r11,r11,ha16(L_bar$lazy_ptr-L0$_bar); L0$_bar plus high 16 bits of
; distance
mtlr r0 ; restore the previous LR
lwz r12,lo16(L_bar$lazy_ptr-L0$_bar)(r11); ...plus low 16 of distance
mtctr r12 ; move r12 to the count register
addi r11,r11,lo16(L_bar$lazy_ptr-L0$_bar); load r11 with the address of bar’s
; lazy pointer
bctr ; jump to the value in bar’s lazy
; pointer

.lazy_symbol_pointer ; the lazy pointer section
L_bar$lazy_ptr:
.indirect_symbol _bar ; identify this lazy pointer for symbol bar
.long dyld_stub_binding_helper ; initialize the lazy pointer to the stub
; binding helper address.

正如图所示,在listing 3中__picsymbol_stub代码与在listing 2中生成的position-indenpendent代码相似。对于任何的position-independent Mach-O文件,symbol stubs也必须显而易见的是positon independent。
静态链接器在写输出文件时展示了两种优化:

  1. 它移除了symbol stubs中用于引用到那些被定义在相同module中的symbol,修改了branch指令中那些直接通过stubs调用branch的调用指令。
  2. 它会移除冗余的相同的symbol stub,在必要的时候更新branch的指令。

注意一个路径不直接地branch到另一个路径的路径必须保存在名为GPR11或者GPR12的目标调用中。标准化被编译器用于保存目标地址的注册器会让它更有可能产生优化的动态代码。因为在任何情况目标地址需要被保存在注册器,这个规则标准化了需要使用哪个注册器。那些可能被直接调用的路径不需要依赖于GR12的值,因为如果是直接调用的话,它的值时没有被定义的。

IA-32 Symbol References

在IA-32架构中,symbol引用被作为一个symbol stub和一个lazy symbol指针结合进一个JMP指令来实现,这样的指令指向了动态链接器。当动态链接器遇到这样的指令时,它定位了被引用的symbol并且修改JMP指令去直接指向这个symbol。因此,接下来的JMP执行指令直接跳转到被应用的symbol。
Listing 4 C program using an imported symbol

1
2
3
4
5
#include <stdio.h>
main( int arc, char *argv[])
{
fprintf(stdout, "hello, world!\n") ;
}

Listing 5 IA-32 symbol reference in assembly

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
.cstring
LC0:
.ascii "hello, world!\12\0"
.text
.globl _main
_main:
pushl %ebp
movl %esp, %ebp
subl $24, %esp
movl L___sF$non_lazy_ptr, %eax
addl $88, %eax
movl %eax, 12(%esp)
movl $14, 8(%esp)
movl $1, 4(%esp)
movl $LC0, (%esp)
call L_fwrite$stub ; call to imported symbol
leave
ret
.section __IMPORT,__jump_table,symbol_stubs,self_modifying_code+pure_instructions,5
L_fwrite$stub: ; symbol stub
.indirect_symbol _fwrite
hlt ; hlt ; hlt ; hlt ; hlt
.section __IMPORT,__pointers,non_lazy_symbol_pointers
L___sF$non_lazy_ptr: ; nonlazy pointer
.indirect_symbol ___sF
.long 0
.subsections_via_symbols