【汇编优化】之对nasm中GOT的理解
转自:https://blog.csdn.net/listener51/article/details/79686102
参考:https://blog.csdn.net/erazy0/article/details/5452276
##1.Obtaining the Address of the GOT
Each code module in your shared library should define the GOT as an external symbol:
<span style="color:#000000"><code>extern _GLOBAL_OFFSET_TABLE_ ; in ELF
extern __GLOBAL_OFFSET_TABLE_ ; in BSD a.out
</code></span>
At the beginning of any function in your shared library which plans to access your data or BSS sections, you must first calculate the address of the GOT. This is typically done by writing the function in this form:
<span style="color:#000000"><code>func: push ebp
mov ebp,esp
push ebx
call .get_GOT
.get_GOT:
pop ebx
add ebx,_GLOBAL_OFFSET_TABLE_+$$-.get_GOT wrt ..gotpc
; the function body comes here
mov ebx,[ebp-4]
mov esp,ebp
pop ebp
ret
</code></span>
(For BSD, again, the symbol _GLOBAL_OFFSET_TABLE requires a second leading underscore.)
The first two lines of this function are simply the standard C prologue to set up a stack frame, and the last three lines are standard C function epilogue. The third line, and the fourth to last line, save and restore the EBX register, because PIC shared libraries use this register to store the address of the GOT.
这个函数的头两行只是简单的标准的C风格的开头,用于设置栈框架,最后的三行是标准的C风格的结尾,第三行,和倒数第四行,分别保存和恢复'EBS'寄存器,因为PIC共享库使用这个寄存器保存GOT的地址。
The interesting bit is the CALL instruction and the following two lines. The CALL and POP combination obtains the address of the label .get_GOT, without having to know in advance where the program was loaded (since the CALL instruction is encoded relative to the current position). The ADD instruction makes use of one of the special PIC relocation types: GOTPC relocation. With the WRT …gotpc qualifier specified, the symbol referenced (here GLOBAL_OFFSET_TABLE, the special symbol assigned to the GOT) is given as an offset from the beginning of the section. (Actually, ELF encodes it as the offset from the operand field of the ADD instruction, but NASM simplifies this deliberately, so you do things the same way for both ELF and BSD.) So the instruction then adds the beginning of the section, to get the real address of the GOT, and subtracts the value of .get_GOT which it knows is in EBX. Therefore, by the time that instruction has finished, EBX contains the address of the GOT.
最关键的是'CALL'指令和接下来的两行代码。'CALL'和'POP'一起用来获得 .get_GOT的地址,不用进一步知道程序被载入到什么地方(因为call指令是解码 成跟当前的位置相关)。‘ADD’指令使用了一个特殊的PIC重定位类型:GOTPC 重定位。通过使用限定符'WRT ..gotpc',被引用的符号(这里是 `_GLOBAL_OFFSET_TABLE_',一个被赋给GOT的特殊符号)被以从段起始地址开始 的偏移的形式给出。(实际上,‘ELF’把它编码为从‘ADD’的操作数域开始的 一个偏移,但NASM把它简化了,所以你在‘ELF’和‘BSD’中可以用同样的方 式处理。)所以,这条指令然后加上段起始地址,然后得到GOT的真正的地址。 然后减去'.get_GOT'的值,当这条指令执行结束的时候,'EBX'中含有'GOT' 的值。
If you didn’t follow that, don’t worry: it’s never necessary to obtain the address of the GOT by any other means, so you can put those three instructions into a macro and safely ignore them:
<span style="color:#000000"><code>%macro get_GOT 0
call %%getgot
%%getgot:
pop ebx
add ebx,_GLOBAL_OFFSET_TABLE_+$$-%%getgot wrt ..gotpc
%endmacro
</code></span>
###1.1 Obtaining the Address of the GOT的理解
for example: a function(here add_fun) writed by asm, and called by c
add.asm
<span style="color:#000000"><code>extern _GLOBAL_OFFSET_TABLE_
section .data
local_val: db 4
section .text
global add_fun
add_fun:
push ebp
mov ebp, esp
push ebx
call .get_GOT
.get_GOT:
pop ebx
mov ecx, $$
mov ecx,_GLOBAL_OFFSET_TABLE_ wrt ..gotpc
mov ecx,.get_GOT
add ebx,_GLOBAL_OFFSET_TABLE_+$$-.get_GOT wrt ..gotpc
mov eax, [esp + 8]
mov edx, [esp + 12]
add eax, edx;
mov edx, [ebx + local_val wrt ..gotoff]
mov eax,edx
mov esp, ebp
pop ebp
ret
</code></span>
<span style="color:#000000"><code>#include<stdio.h>
#include<stdlib.h>
extern int add_fun(int a, int b);
int main()
{
int sum = add_fun(2,3);
printf("sum = %d\n", sum);
return 0;
}
</code></span>
<span style="color:#000000"><code>#!/bin/sh
gcc -g -m32 -c demo.c -fPIC -I. -o demo.o
yasm -m x86 -f elf -DPIC -I. add.asm -o add.o
gcc -g -m32 -o demo demo.o add.o
</code></span>
after compliled
<span style="color:#000000"><code>Dump of assembler code for function add_fun:
=> 0x56555580 <+0>: push %ebp
0x56555581 <+1>: mov %esp,%ebp
0x56555583 <+3>: push %ebx
0x56555584 <+4>: call 0x56555589 <add_fun+9>
0x56555589 <+9>: pop %ebx
0x5655558a <+10>: mov $0x56555580,%ecx
0x5655558f <+15>: mov $0x1a58,%ecx
0x56555594 <+20>: mov $0x56555589,%ecx
0x56555599 <+25>: add $0x1a4f,%ebx
0x5655559f <+31>: mov 0x8(%esp),%eax
0x565555a3 <+35>: mov 0xc(%esp),%edx
0x565555a7 <+39>: add %edx,%eax
0x565555a9 <+41>: mov 0x30(%ebx),%edx
0x565555af <+47>: mov %edx,%eax
0x565555b1 <+49>: mov %ebp,%esp
0x565555b3 <+51>: pop %ebp
0x565555b4 <+52>: ret
0x565555b5 <+53>: xchg %ax,%ax
0x565555b7 <+55>: xchg %ax,%ax
0x565555b9 <+57>: xchg %ax,%ax
0x565555bb <+59>: xchg %ax,%ax
0x565555bd <+61>: xchg %ax,%ax
0x565555bf <+63>: nop
</code></span>
(1)、0x56555584 <+4>: call 0x56555589 <add_fun+9>
对应add.asm中的call .get_GOT
0x56555589是.get_GOT的地址,另外执行完 0x56555589 <+9>: pop %ebx,ebx中存放的也是.get_GOT的地址(即0x56555589)。
(2)、 0x5655558a <+10>: mov $0x56555580,%ecx
对应add.asm中的mov ecx, $$
0x56555580是当前段的起始地址。
(3)、0x5655558f <+15>: mov $0x1a58,%ecx
对应add.asm中的mov ecx,_GLOBAL_OFFSET_TABLE_ wrt ..gotpc
0x1a58是当前段的偏移。
(4)、0x56555599 <+25>: add $0x1a4f,%ebx
对应add.asm中的add ebx,_GLOBAL_OFFSET_TABLE_+$$-.get_GOT wrt ..gotpc
执行完这条命令后,ebx中存放的是GOT的地址:0x565556fd8 = 0x1a4f + 0x56555589 = 0x1a58 + 0x56555580。
参考网址:http://coding.derkeiler.com/Archive/Assembler/comp.lang.asm.x86/2007-02/msg00041.html
参考网址:http://www.nasm.us/doc/nasmdoc9.html