Win32匯編過程與宏調用


匯編語言(assembly language)是一種用於電子計算機、微處理器、微控制器或其他可編程器件的低級語言,亦稱為符號語言.在匯編語言中,用助記符(Mnemonics)代替機器指令的操作碼,用地址符號(Symbol)或標號(Label)代替指令或操作數的地址.在不同的設備中,匯編語言對應着不同的機器語言指令集,通過匯編過程轉換成機器指令,普遍地說,特定的匯編語言和特定的機器語言指令集是相互對應的,不同平台之間不可直接移植.

堆棧操作指令

在計算機領域,堆棧是一個不容忽視的概念,堆棧是一種后進先出(LIFO,Last-In,First-Out)的數據結構,這是因為最后壓入堆棧的值總是最先被取出,而新數值在執行PUSH壓棧時總是被加到堆棧的最頂端,數據也總是從堆棧的最頂端被取出,堆棧是個特殊的存儲區,主要功能是暫時存放數據和地址,通常用來保護斷點和現場.

當程序運行時,棧是由CPU直接管理線性內存數組,它使用兩個寄存器(SS和ESP)來保存堆棧的狀態.在保護模式下,SS寄存器存放段選擇符(Segment Selector)運行在保護模式下的程序不能對其進行修改,而ESP寄存器的值通常是指向特定位置的一個32位偏移值,我們很少需要直接操作ESP寄存器,相反的ESP寄存器總是由CALL,RET,PUSH,POP等這類指令間接性的修改.

接着來簡單介紹下關於堆棧操作的兩個寄存器,CPU系統提供了兩個特殊的寄存器用於標識位於系統棧頂端的棧幀.
ESP 棧指針寄存器: 棧指針寄存器,其內存放着一個指針,該指針永遠指向系統棧最上面一個棧幀的棧頂.
EBP 基址指針寄存器: 基址指針寄存器,其內存放着一個指針,該指針永遠指向系統棧最上面一個棧幀的底部.

◆堆棧參數傳遞◆

在通常情況下ESP是可變的,隨着棧的生產而逐漸變小,而EBP寄存器是固定的,只有當函數的調用后,發生入棧操作而改變.

1.在32位系統中,執行PUSH壓棧時,堆棧指針自動減4,再將壓棧的值復制到堆棧指針所指向的內存地址.
2.在32位系統中,執行POP出棧時,從棧頂移走一個值並將其復制給內存或寄存器,然后再將堆棧指針自動加4.
3.在32位系統中,執行CALL調用時,CPU會用堆棧保存當前被調用過程的返回地址,直到遇到RET指令再將其彈出.

PUSH/POP指令: 在32位環境下,分別將數組中的元素100h-300h壓入堆棧,並且通過POP將元素反彈出來.

.data
	Array DWORD 100h,200h,300h,400h
.code
	main PROC
		xor eax,eax
		push eax                      ; push 0
		push DWORD PTR [Array]        ; push 100
		push DWORD PTR [Array+4]      ; push 200
		push DWORD PTR [Array+8]      ; push 300
		pop eax                       ; pop 300
		pop eax                       ; pop 200
		pop eax                       ; pop 100
		pop eax                       ; pop 0

		push 0
		call ExitProcess
	main ENDP
END main

PUSHFD/POPFD指令: PUSHFD在堆棧上壓入EFLAGS寄存器的值,POPFD將堆棧的值彈出並送至EFLAGS寄存器.

.data
	SaveFlage DWORD ?
.code
	main PROC
		pushfd            ; 標識入棧
		pop SaveFlage     ; 彈出並保存到內存

		push SaveFlage    ; 從內存取出,並入棧
		popfd             ; 恢復到EFLAGS寄存器中

		push 0
		call ExitProcess
	main ENDP
END main

PUSHAD/POPAD指令: 將通用寄存器按照EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDI的順序壓棧保存.

.code
	main PROC
		pushad
		mov eax,1000
		mov ebx,2000
		mov ecx,3000
		mov edx,4000
		popad

		push 0
		call ExitProcess
	main ENDP
END main

◆聲明局部變量◆

高級語言程序中,在單個過程中創建使用和銷毀的變量我們稱它為局部變量(local variable),局部變量是在程序運行時,由系統動態的在棧上開辟的,在內存中通常在基址指針(EBP)之下,盡管在匯編時不能給定默認值,但可以在運行時初始化,如下一段偽代碼:

void MySub()
{
	int var1 = 10;
	int var2 = 20;
}

上面的一段代碼經過C編譯器轉換后,會變成如下的樣子,其中EBP-4必須是4的倍數,因為默認就是4字節存儲.

MySub PROC
	push ebp                  ; 將EBP存儲在棧中
	mov ebp,esp               ; 堆棧框架的基址
	sub esp,8                 ; 創建局部變量空間

	mov DWORD PTR [ebp-4],10  ; var1 = 10
	mov DWORD PTR [ebp-8],20  ; var2 = 20

	mov esp,ebp               ; 從堆棧上刪除局部變量
	pop ebp                   ; 恢復EBP指針
	ret 8                     ; 返回,清理堆棧
MySub ENDP

如果去掉了上面的mov esp,ebp,那么當執行pop ebp時將會得到EBP等於10,執行RET指令會導致控制轉移到內存地址10處執行,從而程序會崩潰.

為了使代碼更加的容易閱讀,可以在上面的代碼的基礎上給每個變量的引用地址都定義一個符號並在代碼中使用這些符號來完成編寫.

var1_local EQU DWORD PTR [ebp-4]
var2_local EQU DWORD PTR [ebp-8]

MySub PROC
	push ebp
	mov ebp,esp
	sub esp,8
	mov var1_local,10
	mov var2_local,20
	mov esp,ebp
	pop ebp
	ret 8
MySub ENDP

◆ENTER/LEAVE 偽指令◆

ENTRE指令自動為被調用過程創建堆棧框架,它為局部變量保留堆棧空間並在堆棧上保存EBP,該指令執行后會執行以下動作.

1.在堆棧上壓入EBP(push ebp)
2.把EBP設為堆棧框架的基指針(mov ebp,esp)
3.為局部變量保留適當的空間(sub esp,numbytes)

ENTER指令有兩個參數,第一個操作數是一個常量,用於指定要為局部變量保留多少堆棧空間(numbytes),第二個參數指定過程的嵌套層數,這兩個操作數都是立即數,numbytes總是向上取整為4的倍數,以使ESP按照雙字邊界地址對其.

比如以下代碼,使用ENTER為局部變量保存8字節的堆棧空間:

MySub PROC
	enter 8,0
MySub ENDP

經過編譯器轉換后,會首先轉換為以下的樣子:

MySub PROC
	push ebp
	mov ebp,esp
	sub esp,8
MySub ENDP

上面的代碼只有開頭沒有結尾,如果要使用ENTER指令分配空間的話,則必須在結尾加上LEAVE指令,這樣程序才完整.

MySub PROC
	enter 8,0
....
	leave
	ret
MySub ENDP

下面代碼和上面代碼作用是相同的,它首先為局部變量保留8字節的堆棧空間然后丟棄.

MySub PROC
	push ebp
	mov ebp,esp
	sub esp,8
....
	mov esp,ebp
	pop ebp
	ret
MySub ENDP

◆USES/LOCAL 偽指令◆

USES操作符: 該操作符用於指定需要壓棧的寄存器,其會自動生成壓棧出棧代碼無需手動添加.

.code
	main PROC
		mov eax,1
		mov ebx,2
		mov ecx,3
		call mycall
		push 0
		call ExitProcess
	main ENDP

	mycall PROC USES eax ebx ecx     ; 生成壓棧代碼,自動壓eax,ebx,ecx
		xor eax,eax              ; 壓棧的寄存器可以隨意修改
		xor ebx,ebx              ; 過程結束后會自動恢復這些寄存器
		ret
	mycall ENDP
END main

LOCAL操作符: 在過程內聲明一個或多個命名局部變量,並賦予相應的尺寸屬性,該語句必須緊跟PROC指令后面.

.code
	main PROC
		LOCAL var1:WORD
		LOCAL var2:DWORD,var3:BYTE

		mov DWORD PTR [var1],1024
		mov eax,DWORD PTR [var1]
		mov [var2],1024            ; DWORD
		mov eax,[var2]
		mov [var3],10              ; BYTE
		mov al,[var3]
		push 0
		call ExitProcess
	main ENDP
END main

局部變量:

.code
	lyshark PROC var1:WORD,var2:DWORD
		LOCAL @loca1:BYTE,@loca2:DWORD
		LOCAL @local_byte[100]:BYTE
		
		mov ax,var1
		mov ebx,@loca2
		
		lea ecx,@local_byte
		mov @local_byte[0],0
		mov @local_byte[1],1
		mov @local_byte[2],2
		mov @local_byte[3],3
	lyshark ENDP

	main PROC
		invoke lyshark,100,10000
		ret
	main ENDP
END main

LOCAL(申請數組):

.code
	main PROC
		LOCAL var[3]:DWORD
		
		mov var[0],100
		mov var[1],200
		
		mov eax,var[0]
		mov ebx,var[1]
	main ENDP
END main
.code
	main PROC
		LOCAL ArrayDW[10]:DWORD
		LOCAL ArrayB[10]:BYTE

		lea eax,[ArrayDW]
		mov [ArrayDW],10
		mov [ArrayDW + 4],20
		mov [ArrayDW + 8],30
	main ENDP
END main

## 過程調用指令

CALL指令指示處理器在新的內存地址執行指令,當用戶調用CALL指令時,該指令會首先將CALL指令的下一條指令的內存地址壓入堆棧保存,然后將EIP寄存器修改為CALL指令的調用處,等調用結束后返回從堆棧彈出CALL的下一條指令地址.

1.當遇到CALL指令時,程序會經過計算得到CALL指令的下一條指令的地址,並將其壓入堆棧.
2.接着會將EIP寄存器的地址指向被調用過程的地址,被調用過程被執行.
3.最后過程內部通過RET指令返回,將從堆棧中彈出EIP的地址,程序繼續向下執行.
4.CALL相當於push+jmp,RET相當於pop+jmp.

普通參數傳遞:

.code
	sum PROC var1:DWORD,var2:DWORD,var3:DWORD
		mov eax,var1
		mov ebx,var2
		mov ecx,var3
		ret
	sum ENDP

	main PROC
		invoke sum,10,20,30      ; 調用並傳遞參數
		ret
	main ENDP
END main

寄存器傳遞參數:

.code
	sum PROC
		add eax,ebx
		add eax,ecx
		ret
	sum ENDP

	main PROC
		mov eax,10
		mov ebx,20
		mov ecx,30
		call sum
		ret
	main ENDP
END main

使用PROTO聲明: 如果調用的函數在之后實現, 須用 PROTO 提前聲明,否則會報錯

sum PROTO :DWORD,:DWORD,:DWORD ; 函數聲明的主要是參數類型,省略參數名

.code
	main PROC
		invoke sum,10,20,30    ; 現在調用的是之后的函數
		ret
	main ENDP

	sum PROC var1,var2,var3
		mov eax,var1
		add eax,var2
		add eax,var3
		ret
	sum ENDP
END main

CALL/RET指令: 編寫一個過程,實現對整數數組的求和,並將結果保存到EAX寄存器中.

.data
	array DWORD 1000h,2000h,3000h,4000h,5000h
	theSum DWORD ?
.code
	main PROC
		mov esi,offset array          ; ESI指向array
		mov ecx,lengthof array        ; ECX=array元素個數
		call ArraySum                 ; 調用求和指令
		mov theSum,eax                ; 將結果保存到內存
		push 0
		call ExitProcess
	main ENDP

	ArraySum PROC
		push esi           ; 保存ESI,ECX
		push ecx
		mov eax,0          ; 初始化累加寄存器
	L1:
		add eax,[esi]      ; 每個整數都和EAX中的和相加
		add esi,TYPE DWORD ; 遞增指針,繼續遍歷
		loop L1
		pop ecx            ; 恢復寄存器
		pop esi
		ret
	ArraySum ENDP
END main

通過該語句塊配合可以生成自定義過程,下面我們創建一個名為Sum的過程,實現EBX+ECX並將結果保存在EAX寄存器中.

.data
	TheSum DWORD ?
.code
	main PROC
		mov ebx,100     ; 傳遞ebx
		mov ecx,100     ; 傳遞ecx
		call Sum        ; 調用過程
		mov TheSum,eax  ; 保存結果到TheSum

		push 0
		call ExitProcess
	main ENDP
	
	Sum PROC
		xor eax,eax
		add eax,ebx
		add eax,ecx
		ret
	Sum ENDP
END main

INVOKE調用系統API: 默認情況下,會將返回結果保存在eax寄存器中.

.data
	szCaption db "MsgBox",0
	szText db "這是一個提示框,請點擊確定完成交互!",0
.code
	main PROC
		.WHILE (1)
			invoke MessageBox,NULL,offset szText,offset szCaption,MB_YESNO
			.break .if(eax == IDYES)
		.ENDW
		ret
	main ENDP
END main

模塊化調用: 首先創建一個sum.asm然后在main.asm中引用sum這個文件中的函數.

; sum.asm 首先編譯這個文件,並將其放入指定目錄下
	.386
	.model flat, stdcall
.code
	sum PROC v1, v2, v3
	    mov eax, v1
	    add eax, v2
	    add eax, v3
	    ret
	sum ENDP
end
; main.asm 直接引用編譯后的lib文件即可
;這里的引入路徑可以是全路徑, 這里是相對路徑
includelib /masm32/lib/sum.lib

;子程序聲明
sum proto :dword, :dword, :dword
.code
	main PROC
		invoke sum,10,20,30    ;調用過程
		ret
	main ENDP
END main

## 結構與聯合

結構(struct)時邏輯上互相關聯的一組變量的模板或模式,結構中的單個變量稱為域(field),程序的語句可以把結構作為一個實體進行訪問,也可以對結構的單個域進行訪問,結構通常包括不同類型的域,而聯合(union)同樣也是把多個標識符組合在一起,不過與結構不同的是,聯合體共用用一塊內存區域,內存的大小取決於聯合體中最大的元素.

引用結構變量: 通過使用<>,{}均可聲明結構體,同時可以初始化,對結構體賦初值.

;定義結構
MyPoint struct
	pos_x DWORD ?
	pos_y DWORD ?
MyPoint ends

.data
	;聲明結構, 使用 <>、{} 均可
	ptr1 MyPoint <10,20>
	ptr2 MyPoint {30,40}

.code
	main PROC
		lea edx, ptr1
		mov eax, (MyPoint ptr [edx]).pos_x   ; 此時eax=10
		mov ebx, (MyPoint ptr [edx]).pos_y   ; 此時ebx=20
		mov (MyPoint PTR [edx]).pos_x,100    ; 將100寫入MyPoint.pos_x結構中存儲
		ret
	main ENDP
END main

結構初始化: 以下定義了MyStruct結構,並將user2初始化,FName=lyshark,FAge=25.

MyStruct struct
	FName db 20 dup(0)
	FAge db 100
MyStruct ends

.data
	user1 MyStruct <>
	user2 MyStruct <'lyshark',25>

.code
	main PROC
		;lea edx, user1
		;mov eax,DWORD PTR (MyStruct ptr[edx]).FName
		;mov ebx,DWORD PTR (MyStruct ptr[edx]).FAge

		mov eax,DWORD PTR [user2.FName]   ; eax=lyshark
		mov ebx,DWORD PTR [user2.FAge]    ; ebx=25
		ret
	main ENDP
END main

使用系統結構: 通過調用GetLocalTime獲取系統時間,並存儲到SYSTEMTIM結構體中.

.data
	sysTime SYSTEMTIME <>           ; 聲明結構體

.code
	main PROC
		invoke GetLocalTime,addr sysTime    ; 獲取系統時間並放入sysTime
		mov eax,DWORD PTR sysTime.wYear     ; 獲取年份
		mov ebx,DWORD PTR sysTime.wMonth    ; 獲取月份
		mov ecx,DWORD PTR sysTime.wDay      ; 獲取天數
		ret
	main ENDP
END main

結構體的嵌套定義:

MyPT struct
	pt_x DWORD ?
	pt_y DWORD ?
MyPT ends
Rect struct
	Left MyPT <>
	Right MyPT <>
Rect ends

.data
	LyShark1 Rect <>
	LyShark2 Rect {<10,20>,<100,200>}
.code
	main PROC
		mov [LyShark1.Left.pt_x],100
		mov [LyShark1.Left.pt_y],200
		
		mov [LyShark1.Right.pt_x],1000
		mov [LyShark1.Right.pt_y],2000
		mov eax,[LyShark1.Left.pt_x]
		ret
	main ENDP
END main

聯合體的聲明:

; 定義聯合體
MyUnion union
	My_Dword DWORD ?
	My_Word WORD ?
	My_Byte BYTE ?
MyUnion ends

.data
	test1 MyUnion {1122h}; ;只能存放初始值
.code
	main PROC
		mov eax, [test1.My_Dword]
		mov ax, [test1.My_Word]
		mov al, [test1.My_Byte]
		ret
	main ENDP
END main

## 關於宏匯編

宏過程(Macro Procedure)是一個命名的語匯編語句塊,一旦定義后,宏過程就可以在程序中被調用任意多次,調用宏過程的時候,宏內的語句塊將替換到調用的位置,宏的本質是替換,但像極了子過程,宏可定義在源程序的任意位置,但一般放在.data前面.

一個簡單的宏:

MyCode macro
	xor eax,eax
	xor ebx,ebx
	xor ecx,ecx
	xor edx,edx
endm

.code
	main PROC
		MyCode   ; 將被替換為上面兩行代碼
		ret    
	main ENDP
END main

一個代替求和函數的宏

MySum macro  var1, var2, var3
	mov eax,var1
	add eax,var2
	add eax,var3
endm

.code
	main PROC
		MySum 10,20,30
		MySum 10,20,30,40   ; 多余的參數40會被忽略
		ret    
	main ENDP
END main

宏參數的默認值: 通過定義默認值,可以不給默認的變量傳遞參數.

; 參數 var1、var2 通過 REQ 標識說明是必備參數
MySum macro  var1:req, var2:req, var3:=<30>    ; var3默認值是30
	mov eax,var1
	add eax,var2
	add eax,var3
endm

.code
	main PROC
		MySum 10,20
		ret    
	main ENDP
END main

使用EXITM終止宏執行: 可使用關鍵字exitm 終止宏代碼的后面內容.

MySum macro
	xor eax,eax
	xor ebx,ebx
	xor ecx,ecx
	exitm        ; 只會清空前三個寄存器,后面的跳過了
	xor edx,edx
	xor esi,esi
endm

.code
	main PROC
		MySum
		ret    
	main ENDP
END main

使用PURGE取消指定宏的展開:

MySum macro
	xor eax,eax
	xor ebx,ebx
endm

.code
	main PROC
		MySum           ; 這個會被展開
		purge MySum     ; 這個不會展開
		MySum           ; 這個宏也不會展開了
		ret
	main ENDP
END main

在宏內使用局部標號:

MyMax macro var1,var2
	LOCAL jump
	
	mov eax,var1
	cmp eax,var2
	jge jump
	xor eax,eax
jump:	ret
endm

.code
	main PROC
		MyMax 20,10
	main ENDP
END main

特殊操作符: &、<>、%、!

&  ;替換操作符
<> ;字符串傳遞操作符
%  ;表達式操作符, 也用於得到一個變量或常量的值
!  ;轉義操作符
;自定義的宏
mPrint macro Text
    PrintText '* &Text& *'
endm

.code
main proc
    ;該宏會把參數直接替換過去
    mPrint 1234    ;* 1234 *
    
    ;要保證參數的完整應該使用 <>
    mPrint 12,34   ;* 12 *
    mPrint <12,34> ;* 12,34 *
    
    ;需要計算結果應該使用 %()
    mPrint 34+12   ;* 34+12 *
    mPrint %(34+12)   ;* 46 *
    
    ;用到 &、<、>、%、! 應該使用 ! 轉義
    mPrint 10 !% 2 = %(10/2)!! ;* 10 % 2 = 5! *
    ret
main endp
end main

## 過程小例子

整數求和: 通過使用匯編語言實現一個整數求和的小例子.

.data
	String WORD 100h,200h,300h,400h,500h
.code
	main PROC
		;lea edi,String           ; 取String數組的基址
		mov edi,offset String     ; 同上,兩種方式均可
		mov ecx,lengthof String   ; 取數組中的數據個數
		mov ax,0                  ; 累加器清零
	L1:
		add ax,[edi]              ; 加上一個整數
		add edi,TYPE String       ; 指向下一個數組元素,type(2byte)
		loop L1

		push 0
		call ExitProcess
	main ENDP
END main

正向復制字符串: 使用匯編語言實現字符串的復制,將數據從source復制到target內存中.

.data
	source BYTE "hello lyshark welcome",0h
	target BYTE SIZEOF source DUP(0),0h       ; 取源地址數據大小
.code
	main PROC
		mov esi,0                  ; 使用變址寄存器
		mov ecx,sizeof source      ; 循環計數器
	L1:
		mov al,source[esi]         ; 從源地址中取一個字符
		mov target[esi],al         ; 將該字符存儲在目標地址中
		inc esi                    ; 遞增,將指針移動到下一個字符
		loop L1

		push 0
		call ExitProcess
	main ENDP
END main

反向復制字符串: 使用匯編語言實現字符串的復制,將數據從source復制到target內存中且反向存儲數據.

.data
	source BYTE "hello lyshark welcome",0h
	target BYTE SIZEOF source DUP(0),0h
.code
	main PROC
		mov esi,sizeof source
		mov ecx,sizeof source
		mov ebx,0
	L1:
		mov al,source[esi]
		mov target[ebx],al
		dec esi
		inc ebx
		loop L1
		push 0
		call ExitProcess
	main ENDP
END main

查看內存與寄存器: 通過調用DumpMem/DumpRegs顯示內存與寄存器的快照.

.data
	array DWORD 1,2,3,4,5,6,7,8,9,0ah,0bh
.code
	main PROC
		mov esi,offset array       ; 設置內存起始地址
		mov ecx,lengthof array     ; 設置元素數據,偏移
		mov ebx,type array         ; 設置元素尺寸(1=byte,2=word,4=dword)
		call DumpMem               ; 調用內存查詢子過程
		call DumpRegs              ; 調用查詢寄存器子過程

		push 0
		call ExitProcess
	main ENDP
END main

匯編實現性能度量: 通過調用庫函數,實現對指定代碼執行的性能度量.

.data
	StartTime DWORD ?
.code
	main PROC

		call GetMseconds       ; 調用區本地時間過程
		mov StartTime,eax      ; 將返回值賦值給StartTime

		mov ecx,10             ; 通過調用延時過程,模擬程序的執行
	L1:
		mov eax,1000           ; 指定延時1s=1000ms
		call Delay             ; 調用延時過程
		loop L1

		call GetMseconds       ; 再次調用本地時間過程
		sub eax,StartTime      ; 結束時間減去開始時間
		call WriteDec          ; 以十進制形式輸出eax寄存器的值

		push 0
		call ExitProcess
	main ENDP
END main

字符輸出: WriteString(字符串),WriteInt(整數),WriteHex(16進制),WriteChar(字符),WriteDec(10進制).

.data
	Message BYTE "Input String:",0h
	String DWORD ?

.code
	main PROC
		; 設置控制台背景顏色
		mov eax,yellow +(blue*16)     ; 設置為藍底黃字
		call SetTextColor             ; 調用設置過程
		call Clrscr                   ; 清除屏幕,clear

		; 提示用戶一段話
		mov edx,offset Message        ; 指定輸出的文字
		call WriteString              ; 調用回寫過程
		call Crlf                     ; 調用回車

		push 0
		call ExitProcess
	main ENDP
END main

字符輸入: ReadString(字符串),ReadInt(整數),ReadHex(16進制),ReadChar(字符),ReadDec(10進制).

.data
	Buffer BYTE 21 DUP(0)          ; 輸入緩沖區
	ByteCount DWORD ?              ; 存放計數器      
.code
	main PROC
		mov edx,offset Buffer      ; 指向緩沖區指針
		mov ecx,sizeof Buffer      ; 指定最多讀取的字符數
		call ReadString            ; 讀取輸入字符串
		mov ByteCount,eax          ; 保存讀取的字符數

		push 0
		call ExitProcess
	main ENDP
END main

生成偽隨機數:

.code
	main PROC
		mov ecx,5           ; 循環生成5個隨機數
	L1:
		call Random32       ; 生成隨機數
		call WriteDec       ; 以十進制顯示
		mov al,TAB          ; 水平制表符
		call WriteChar      ; 顯示水平制表符
		loop L1
		call Crlf           ; 回車

		push 0
		call ExitProcess
	main ENDP
END main

生成自定義隨機數:

.code
	main PROC
		mov ecx,5           ; 循環生成5個隨機數
	L1:
		mov eax,100         ; 0-99之間
		call RandomRange    ; 生成隨機數
		sub eax,50          ; 范圍在-50-49
		call WriteInt       ; 十進制輸出
		mov al,TAB
		call WriteChar      ; 輸出制表符
		loop L1
		call Crlf           ; 回車

		push 0
		call ExitProcess
	main ENDP
END main

參考文獻:《Intel 匯編語言程序設計》,《琢石成器-Win32匯編語言程序設計》,《匯編語言-王爽》


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM