Jack's第一個Win32彙編程式HelloWorld

magus_yang發表於2020-04-04

本文已被更新,請看新版文章
Jack整理的Win32彙編基礎知識
http://blog.csdn.net/magus_yang/archive/2007/04/05/1552930.aspx

標 題: Jack's第一個Win32彙編程式HelloWorld

作 者: Jack Yang
時 間: 2007-02-26 1:02
Hello.asm檔案的內容如下:
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 第一部分:模式和源程式格式的定義語句
              .386                                    ; 指令集
              .model flat,stdcall                 ; 工作模式
              option casemap:none             ; 格式
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Include 檔案定義
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include           windows.inc
include           user32.inc
includelib        user32.lib
include           kernel32.inc
includelib        kernel32.lib
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 資料段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
              .data
szCaption       db    'A MessageBox !',0
szText            db    'Hello, World !',0
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 程式碼段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
              .code
start:
              invoke     MessageBox,NULL,offset szText,offset szCaption,MB_OK
              invoke     ExitProcess,NULL
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
              end start        ; 指定程式的入口
 
1.         第一部分模式和源程式格式的定義語句
第一行 指定使用的指令集(編譯器使用)
Win32環境工作在80386及以上的處理器中,所以必須定義.386。如果程式(VxD等驅動程式)中要用到特權指令,那麼必須定義.386p。
第二行 定義程式工作的模式(包括記憶體模式、語言模式、其它模式)
對Win32程式來說,只有一種記憶體模式,即flat(平坦)模式。
Win32 API呼叫使用的是stdcall格式,所以Win32彙編中必須在.model中加上stdcall引數。
第三行 option語句
       由於Win32 API中的API名稱區分大小寫,所以必須定義option casemap:none,來表明程式中的變數和子程式名對大小寫敏感。
 
2.         包含全部段的源程式結構:
.386
.model flat,stdcall
option casemap:none
<一些include語句>
.stack [堆疊段的大小]
.data
<一些初始化過的變數定義>
.data?
<一些沒有初始化過的變數定義>
.const
<一些常量定義>
.code
<程式碼>
<開始標記>
<其他語句>
end 開始標記
 
3.         段的定義
資料段
       .data
              已初始化資料段,可讀可寫的已定義變數;
當程式裝入完成時,這些值就已經在記憶體中;
              資料定義在.data段中會增加可執行檔案的大小;
              .data段一般存放在可執行檔案的_DATA節區(Section)內;
       .data?
              未初始化資料段,可讀可寫的未定義變數,在可執行檔案中不佔空間;
              這些變數一般作為緩衝區或者在程式執行後才開始使用。
              資料定義在.data?資料段中不會增加可執行檔案的大小;
              .data?段一般存放在可執行檔案的_BSS節區內;
       .const
              常量,可讀不可寫的變數;
 
程式碼段
       .code
              所有的指令都必須寫在程式碼段中;
              Win32中,資料段是不可執行的,只有程式碼段有可執行的屬性;
              對於執行在特權級3的應用程式,.code段不可寫。除非把可執行檔案PE頭部中的屬性位改成可寫;
              對於執行在特權級0的程式,所有的段都有讀寫許可權,包括程式碼段;
              .code程式碼段一般存放在可執行檔案的_TEXT節區內;
 
堆疊段
       .stack
              與DOS彙編不同,Win32彙編不必考慮堆疊。系統會自動分配堆疊空間;
              堆疊段的記憶體屬性是可讀寫並且可執行;
              靠動態修改程式碼的反跟蹤模組可以拷貝到堆疊中去邊修改邊執行;
              緩衝區溢位技術也會用到這個特性;
 
4.         呼叫作業系統功能的方法:
DOS下
作業系統的功能通過各種軟中斷來實現。
       應用程式呼叫作業系統功能將經歷如下三個過程:
              把相應的引數放在各個暫存器中再呼叫相應的中斷;
              程式控制權轉到中斷中去執行;
              完成以後通過iret中斷返回指令回到應用程式中;
       DOS下呼叫系統功能方法的缺點:
              所有的功能號定義是難以記憶的數字;
              80x86系列處理器能處理的中斷最多隻能有256個;
              通過暫存器來傳遞引數,對於引數較多的函式很不方便;
Win32下
       系統功能模組放在Windows的動態連結庫(DLL)中
       作為Win32 API核心的3個DLL:
              KERNEL32.DLL    系統服務功能。
              GDI32.DLL           圖形裝置介面。
              USER32.DLL         使用者介面服務。
 
常用API的引數和函式宣告,檢視文件《Microsoft Win32 Programmer's Reference》
 
5.         Win32 API的函式原型宣告
函式原型宣告的彙編格式如下:
函式名 proto [距離] [語言] [引數1]:資料型別, [引數2]:資料型別,......
proto是函式宣告的偽指令
距離可以設定為NEAR、FAR、NEAR16、NEAR32、FAR16或FAR32,由於Win32中只有一個平坦的段,無所謂距離,所以在定義時可以忽略距離。
語言型別可是使用.model所定義的預設值。
 
以訊息對話方塊函式MessageBox為例
C格式如下:
int MessageBox(
       HWND hWnd,              // Handle to owner window
       LPCTSTR lpText,         // text in message box
       LPCTSTR lpCaption,     // message box title
       UINT uType                 // message box style
       );
 
彙編格式如下:
MessageBox Proto hWnd:dword,lpText:dword,lpCaption:dword,uType:dword
或者寫為
MessageBox Proto :dword,:dword,:dword,:dword
編譯器只對引數的數量和型別感興趣,引數的名稱只是增加可讀性,所以可以省略。
對於組合語言來說,Win32環境中的引數實際上只有一種型別,就是一個32位的整數(dword,double word),雙字,四位元組。
 
 
6.         呼叫Win32 API
呼叫API有如下兩種方法:
1)        invoke
              MASM提供的偽指令;
              invoke偽指令的好處就是能夠提高程式碼的可讀性,減少錯誤;
              invoke做了下面三件事:
                     在編譯的時候,由編譯器把invoke偽指令展開成相應的push指令和call指令;
                     進行引數數量的檢查工作;
                     如果帶的引數數量和宣告時的數量不符,編譯器會報錯;
2)        push和call的組合
80386處理器的指令
 
invoke     MessageBox,NULL,offset szText,offset szCaption,MB_OK
也可寫為
push NULL
push offset szText
push offset szCaption
push MB_OK
call MessageBox
 
7.         Win32 API函式返回值的處理方法
對於組合語言來說,Win32 API函式返回值的型別只有dword一種型別,它永遠放在eax中。
如果要返回的內容在一個eax中放不下,Win32 API採用如下方法來解決:
a)         一般是eax中返回一個指向返回資料的指標;
b)        在呼叫引數中提供一個緩衝區地址,資料直接返回到這個緩衝區中去。類似變參的概念;
 
8.         與字串相關Win32 API的分類
在Win32環境中,根據兩個不同的字符集(ANSI字符集和Unicode字符集),可以把和字串相關的API分成兩類:
a)         處理ANSI字符集的Win32 API函式
函式名稱的尾部帶一個“A”字元;
ANSI字串是以NULL結尾的一串字元陣列,每一個ANSI字元佔一個位元組的寬度;
MessageBoxA Proto hWnd:dword,lpText:dword,lpCaption:dword,uType:dword
b)        處理Unicode字符集的Win32 API函式
函式名稱的尾部帶一個“W”字元;
              每一個Unicode字元佔兩個位元組的寬度,所以可以同時定義65536個不同的字元;
              MessageBoxW Proto hWnd:dword,lpText:dword,lpCaption:dword,uType:dword
 
Windows 9x系列不支援Unicode版本的API,絕大多數的API只有ANSI版本。
只有Windows NT系列才完全支援Unicode版本的API。
為了編寫在幾個平臺中都能通用的程式,一般應用程式都使用ANSI版本的API函式集。
 
提高程式可移植性的一個方法:
一般在源程式中不直接指明使用Unicode還是ANSI版本,而是使用巨集彙編中的條件彙編功能來統一替換。
比如,在標頭檔案中做如下定義:
if UNICODE
       MessageBox equ <MessageBoxW>
else
       MessageBox equ <MessageBoxA>
endif
然後在源程式的頭部指定UNICODE=1或UNICODE=0,重新編譯後就能產生不同的版本。
 
未完,待續。。。
 
 
參考資料:
羅雲彬的《Windows環境下32位組合語言程式設計》(第二版)第三章
 

相關文章