記得剛學多線程的時候, 碰到一個結構:
//Delphi 的語法描述
PContext = ^TContext;
_CONTEXT = record
ContextFlags: DWord;
Dr0: DWord;
Dr1: DWord;
Dr2: DWord;
Dr3: DWord;
Dr6: DWord;
Dr7: DWord;
FloatSave: TFloatingSaveArea;
SegGs: DWord;
SegFs: DWord;
SegEs: DWord;
SegDs: DWord;
Edi: DWord;
Esi: DWord;
Ebx: DWord;
Edx: DWord;
Ecx: DWord;
Eax: DWord;
Ebp: DWord;
Eip: DWord;
SegCs: DWord;
EFlags: DWord;
Esp: DWord;
SegSs: DWord;
end;
從這個結構中可以基本洞察多線程的基本原理:
1、在切換到另一個線程之前, 先把當前線程在寄存器中的數據保存在這個結構;
2、重新切回線程時, 再才這個結構中讀出相關數據到寄存器, 從而繼續運行...
壓棧、出棧也是類似的道理.
一個程序包含若干子程序, 子程序中一般會有自己的參數或局部變量.
在執行這個子程序前, 應該先把寄存器中的相關數據暫存一下(子程序也要使用寄存器), 這就是所謂的壓棧(PUSH);
等子程序執行完畢, 再把之前壓到棧中的數據取回(而讓程序繼續執行), 這就是所謂的出棧(POP).
什麼是 "棧"?
程序把內存劃分了若干區域, 其中有 "全局數據區" 和 "局部數據區".
全局數據所在的位置叫 "堆";
局部數據(局部變量、局部常量、子程序參數)所在的位置叫 "棧", 也叫 "堆棧".
對 "堆" 和 "棧", 前人給出了不同的使用規則:
"堆" 中的數據一般是由下到上排列;
"棧" 的數據則完全相反, 是由下到上排列.
驗證 "堆" 與 "棧" 不同的數據排列方式:
; Test17_1.asm
.386
.model flat, stdcall
include Windows.inc
include kernel32.inc
include masm32.inc
include debug.inc
includelib kernel32.lib
includelib masm32.lib
includelib debug.lib
.data?
GlobalVal1 dd ?
GlobalVal2 dd ?
GlobalVal3 dd ?
.code
main proc
LOCAL LocalVal1:dword, LocalVal2:dword, LocalVal3:dWord
;獲取全局變量地址(地址是順序遞增的):
PrintHex offset GlobalVal1 ;00403054
PrintHex offset GlobalVal2 ;00403058
PrintHex offset GlobalVal3 ;0040305C
;獲取局部變量地址(地址是順序遞減的):
lea eax, LocalVal1
PrintHex eax ;0012FFBC
lea eax, LocalVal2
PrintHex eax ;0012FFB8
lea eax, LocalVal3
PrintHex eax ;0012FFB4
ret
main endp
end main
壓棧與出棧的順序:
.386
.model flat, stdcall
include Windows.inc
include kernel32.inc
include masm32.inc
include debug.inc
includelib kernel32.lib
includelib masm32.lib
includelib debug.lib
.data
val1 dd 111
val2 dd 222
val3 dd 333
.code
main proc
push val1
push val2
push val3
;壓棧完畢, 接著出棧
pop val1
pop val2
pop val3
;查看取回的數據:
PrintDec val1 ;333
PrintDec val2 ;222
PrintDec val3 ;111
;怎麼反了? 這就是常說的 "棧中的數據是先進後出"! 讓後進的先出就好了.
ret
main endp
end main
根據 "棧" 先進後出的特點, 寫一個變量換值的程序:
; Test17_3.asm
.386
.model flat, stdcall
include Windows.inc
include kernel32.inc
include masm32.inc
include debug.inc
includelib kernel32.lib
includelib masm32.lib
includelib debug.lib
.data
val1 dd 111
val2 dd 999
.code
main proc
push val1
push val2
pop val1
pop val2
;現在 val1 和 val2 的值已經交換
PrintDec val1 ;999
PrintDec val2 ;111
ret
main endp
end main
如果僅是交換變量的值, 可以使用 XCHG 指令:
; Test17_4.asm
.386
.model flat, stdcall
include Windows.inc
include kernel32.inc
include masm32.inc
include debug.inc
includelib kernel32.lib
includelib masm32.lib
includelib debug.lib
.data
val1 dd 111
val2 dd 999
.code
main proc
;xchg va1, val2 ;指令都不支持對兩個變量直接操作, 需要用個寄存器中轉下
mov eax, val1
xchg eax, val2
mov val1, eax
PrintDec val1 ;999
PrintDec val2 ;111
ret
main endp
end main
根據上面的原理, 也可以方便寫出一個翻轉字符串的函數:
; Test17_5.asm
.386
.model flat, stdcall
include Windows.inc
include kernel32.inc
include masm32.inc
include debug.inc
includelib kernel32.lib
includelib masm32.lib
includelib debug.lib
.data
szText db 'Hello World!', 0
.code
main proc
;把字符串中的字符逐個壓入棧中
mov ecx, sizeof szText - 1 ;把字符串長度(將要反復的次數)給 ecx, 沒包括結束記號
xor esi, esi ;清空 esi, 准備用作數組索引
@@: movzx eax, szText[esi] ;循環讀出並壓棧
push eax
inc esi
loop @B
;從棧中逐個取出並寫入字符串
mov ecx, sizeof szText - 1
xor esi, esi
@@: pop eax
mov szText[esi], al
inc esi
loop @B
PrintString szText ;!dlroW olleH
ret
main endp
end main
;做這個程序也有更好的方案, 譬如用 movs