匯編程序中, 對數據訪問時, 通常是這樣的:
_asm{
...
DATA_LABLE:
_emit 0x87
_emit 0xa0
_emit 0x49
_emit 0x90
...
mov ebx, dword ptr [DATA_LABLE]
...
}
其中, 當程序編譯之後, mov指令中的DATA_LABLE標號地址會被轉成一個絕對地址. 而有時絕對地址這一點可能會對這樣一種需求帶來障礙: 我們希望自己寫的匯編代碼不管被放在哪塊地址空間中都是能正常運行的, 就象我們寫高級語言中的那些函數一樣, 函數位置可以被隨意放置, 絲毫不會影響函數本身的功能使用. 當然, 不得不指出的是, 盡管要求的是同樣功能, 但匯編與高級語言之間在這方面的實現卻相去甚遠. 高級語言的函數在最終被編譯之後, 其函數地址也是固定的絕對地址, 而我們所想要用匯編實現的才是真正的"可被任意放置"的二進制執行塊.
借用call指令, 可以實現運行期標號地址的相對尋址, 大致的思路如下:
_asm{
...
call FUNC_START
FUNC_START:
pop ebx
sub ebx, offset FUNC_START
mov [ebp-xx], ebx
...
DATA_LABLE:
_emit 0x87
_emit 0xa0
_emit 0x49
_emit 0x90
...
mov eax, [ebp-xx]
mov ebx, dword ptr [DATA_LABLE+eax]
...
}
步驟是這樣的:
1.首先, 在匯編功能塊或函數首部, 使用以下語句取得運行期地址與編譯地址的修正差值.
call FUNC_START
FUNC_START:
pop ebx
sub ebx, offset FUNC_START
mov [ebp-xx], ebx
略作解釋: call 函數會將eip寄存器壓入堆棧, 之後用"pop ebp"是將eip值賦給ebp, 而eip表示的是"下一條語句的地址", 在這裡, 當程序運行到"call FUNC_START"時, 它表示的是以標號"FUNC_START:"開始的"pop ebx"指令起始地址. 而另一方面, sub指令中的"offset FUNC_START", 在編譯時, offset會被轉成一個絕對地址. 這樣,通過sub操作, 就獲得了此段代碼在編譯期和運行期關於指令地址的修正值. 下面的這句: "mov [ebp-xx], ebx", 實際上只是錦上添花, 它把這個值保存在了某一個自定義的函數局部變量空間內, 以備後續語句方便引用.
2.相應的, 對標號數據的引用就變成這樣的兩句:
mov eax, [ebp-xx]
mov ebx, dword ptr [DATA_LABLE+eax]
對於匯編函數中的此類代碼進行這樣的處理後, 此段二進制執行塊就可以被放置在任意地方而不致因為對DATA_LABLE數據地址的錯誤引用造成程序錯誤.