PC相对寻址计算与验证

224 阅读1分钟

main.c

#include <stdio.h>
void hello(); 
int main() { 
    hello(); 
}

hello.c

// hello.c
#include <stdio.h>
void hello() { 
    printf("Hello World\n"); 
}
gcc main.c hello.c

Hello World
gcc -c main.c

得到 main.o

objdump -d main.o

0000000000000000 <main>: 
    0: f3 0f 1e fa endbr64 
    4: 48 83 ec 08 sub $0x8,%rsp 
    8: 31 c0 xor %eax,%eax 
    a: e8 00 00 00 00 callq ???????? 
    f: 31 c0 xor %eax,%eax 
    11: 48 83 c4 08 add $0x8,%rsp 
    15: c3 retq

但有些地址编译的时候不知道啊 (比如 hello), 就先填个0吧

filled = *refptr 为运行时被填入 00 00 00 00 的值。 计算公式如下:

S = ADDR(r.symbol)
A = -4
P = ADDR(s) + r.offset

PC = ADDR(s) + r.offset - r.addend
    = P - A
*refptr = S + A - P 
        = S - PC
        
 所以 S = *refptr + PC 成立

可以验证:

#include <stdio.h>
#include <stdint.h>
#include <assert.h>


int main();
void hello() { 
    char *refptr = (char *)main + 0xa + 1;
    int32_t filled = *(int32_t *)refptr;
    // 必须满足以下条件
    assert((char *)main + 0xf + filled == (char *)hello);
}
// 其中 PC = (char *)main + 0xf 为下一条指令的地址。

根据CSAPP, ADDR(s)的值存在GOT里面。如果是静态链接,在生成可执行目标文件后,这个值就确定不变了。如果是动态链接,第一次调用的时候,会由动态链接器算出这个值(之后就不会变了),然后存在GOT里面,第二次以及之后的调用就直接从GOT里面拿了。