汇编开发(九):MS-Windows 程序
1. Win32 控制台程序
1). 显示一个消息框
MessageBoxA PROTO,
hWnd:DWORD, ; handle to window (can be null)
lpText:PTR BYTE, ; 消息框内部
lpCaption:PTR BYTE, ; 消息框标题
uType:DWORD ; 内容和行为
hWnd
在控制台应用中可以设置为NULL
lpText
为一个以空值终止的字符串指针
lpCaotion
为一个以空值终止的对话框标题字符串指针
uType
为消息框的内容和行为。
-
内容和行为
uType参数包含一个位映射整数,它包含三种类型的选项:要显示的按钮,图标和默认按钮选项。 其中按钮有7个常量值:MB_OK
,MB_OKCANCEL
,MB_YESNO
,MB_YESNOCANCEL
,MB_RETRYCANCEL
,MB_ABORTRETRYIGNORE
,MB_CANCELTRYCONTINUE
。 -
默认的按钮
当按下Enter
键的时候,可以自动选择默认的按钮。 -
图标
四种图片可供选择。- 停止: MB_ICONSTOP, MB_ICONHAND, or MB_ICONERROR
- 问号标记(?) : MB_ICONQUESTION
- 消息标记(i): MB_ICONINFORMATION, MB_ICONASTERISK
- 警告标记(!): MB_ICONEXCLAMATION, MB_ICONWARNING
-
返回值
如果MessageBoxA失败了,则返回0,否则返回用户点击的按钮对应的整型。它们是IDABORT, IDCANCEL,IDCONTINUE, IDIGNORE, IDNO, IDOK, IDRETRY, IDTRYAGAIN, and IDYES。
示例:
; 消息框
INCLUDE Irvine32.inc
.data
captionW BYTE "Warning",0
warningMsg BYTE "The current operation may take years "
BYTE "to complete.",0
captionQ BYTE "Question",0
questionMsg BYTE "A matching user account was not found."
BYTE 0dh,0ah,"Do you wish to continue?",0
captionC BYTE "Information",0
infoMsg BYTE "Select Yes to save a backup file "
BYTE "before continuing,",0dh,0ah
BYTE "or click Cancel to stop the operation",0
captionH BYTE "Cannot View User List",0
haltMsg BYTE "This operation not supported by your "
BYTE "user account.",0
.code
main PROC
; 显示一个带图标的OK按钮
INVOKE MessageBox, NULL, ADDR warningMsg, ADDR captionW, MB_OK + MB_ICONEXCLAMATION
; 显示一个带问号的图标和Yes/No按钮
INVOKE MessageBox, NULL, ADDR questionMsg, ADDR captionQ, MB_YESNO + MB_ICONQUESTION
; 显示一个消息图标和Yes/No/Cancel按钮
INVOKE MessageBox, NULL, ADDR infoMsg, ADDR captionC, MB_YESNOCANCEL + MB_ICONINFORMATION + MB_DEFBUTTON2
; 显示一个停止图标和一个OK按钮
INVOKE MessageBox, NULL, ADDR haltMsg, ADDR captionH, MB_OK + MB_ICONSTOP
exit
main ENDP
END
效果.gif
2). 控制台输入
- ReadConsole
ReadConsole PROTO,
hConsoleInput:HANDLE, ; input handle
lpBuffer:PTR BYTE, ; 缓冲指针
nNumberOfCharsToRead:DWORD, ; 要读取的字符数
lpNumberOfCharsRead:PTR DWORD, ; 要读取的字符指针
lpReserved:DWORD ; (不使用)
hConsoleInput
一个有效的控制台输入句柄,使用GetStdHandle函数获取
lpBuffer
字符数组首地址
nNumberOfCharsToRead
要读取的字符数
lpNumberOfCharsRead
要读取的字符指针
lpReserved
保留参数
示例:
; 从控制台读取数据
INCLUDE Irvine32.inc
BufSize = 80
.data
buffer BYTE BufSize DUP(?), 0, 0
stdInHandle HANDLE ?
bytesRead DWORD ?
.code
main PROC
; 获取标准输入句柄
INVOKE GetStdHandle, STD_INPUT_HANDLE
mov stdInHandle, eax
; 等待用户输入
INVOKE ReadConsole, stdInHandle, ADDR buffer, BufSize, ADDR bytesRead, 0
; 显示缓冲内容
mov esi, OFFSET buffer
mov ecx, bytesRead
mov ebx, TYPE buffer
call DumpMem
call WaitMsg
call Crlf
exit
main ENDP
END
效果.png
- 检查错误
如果Windows API 函数返回一个错误值,我们可以调用GetLastError 函数来获取更多的错误信息。
.data
messageId DWORD ?
.code
call GetLastError
mov messageId,eax
获取到错误码之后,我们可以调用FormatMessage 函数获取具体信息
FormatMessage PROTO, ; format a message
dwFlags:DWORD, ; formatting options
lpSource:DWORD, ; location of message def
dwMsgID:DWORD, ; message identifier
dwLanguageID:DWORD, ; language identifier
lpBuffer:PTR BYTE, ; ptr to buffer receiving string
nSize:DWORD, ; buffer size
va_list:DWORD ; pointer to list of arguments
其中除了lpBuffer是输出参数,其余均为输入参数。
dwFlags
是一个整型的格式选项。
lpSource
是一个消息的地址指针。
dwMsgID
是通过GetLastError获取到的双字整型值。
dwLanguageID
是语言标识。如果设置为0,则消息将是用户默认的语言
lpBuffer
是一个以空值终止的字符串指针。
nSize
是可以使用一个特殊的缓冲来获取消息。
va_list
是格式消息字符串的指针。
示例:
.data
messageId DWORD ?
pErrorMsg DWORD ? ; points to error message
.code
call GetLastError
mov messageId,eax
INVOKE FormatMessage, FORMAT_MESSAGE_ALLOCATE_BUFFER + \
FORMAT_MESSAGE_FROM_SYSTEM, NULL, messageID, 0,
ADDR pErrorMsg, 0, NULL
- WriteWindowsMsg
显示一个最近异常信息的字符串。
;----------------------------------------------------
WriteWindowsMsg PROC USES eax edx
;
; Displays a string containing the most recent error
; generated by MS-Windows.
; Receives: nothing
; Returns: nothing
;----------------------------------------------------
.data
WriteWindowsMsg_1 BYTE "Error ",0
WriteWindowsMsg_2 BYTE ": ",0
pErrorMsg DWORD ? ; points to error message
messageId DWORD ?
.code
call GetLastError
mov messageId,eax
; Display the error number.
mov edx,OFFSET WriteWindowsMsg_1
call WriteString
call WriteDec
mov edx,OFFSET WriteWindowsMsg_2
call WriteString
; Get the corresponding message string.
INVOKE FormatMessage, FORMAT_MESSAGE_ALLOCATE_BUFFER + \
FORMAT_MESSAGE_FROM_SYSTEM, NULL, messageID, NULL,
ADDR pErrorMsg, NULL, NULL
; Display the error message generated by MS-Windows.
mov edx,pErrorMsg
call WriteString
; Free the error message string.
INVOKE LocalFree, pErrorMsg
ret
WriteWindowsMsg ENDP
- 单字符输入
控制台模式下的单字符输入有点棘手。 MS-Windows为当前安装的键盘提供设备驱动程序。 按下某个键时,会将8位扫描码传输到计算机的键盘端口。 当释放密钥时,发送第二扫描代码。 MSWindows使用设备驱动程序将扫描代码转换为16位虚拟密钥代码,这是一种由MS-Windows定义的与设备无关的值,用于标识密钥的用途。 MS-Windows创建一条消息,其中包含扫描代码,虚拟密钥代码和其他相关信息。
在Irvine32库中相关函数有:- ReadChar: 从键盘读取一个字符的ASCII码,返回这个字符到AL中。
- ReadKey: 执行无等待键盘检查。如果控制台输入缓冲区中没有等待键,则设置Zero标志。 如果找到密钥,则清零零标志,AL包含零或ASCII代码。 EAX和EDX的上半部分被覆盖。
- ReadKey测试程序
; 测试ReadKey
INCLUDE Irvine32.inc
INCLUDE Macros.inc
.code
main PROC
L1:
mov eax, 10 ; 延时消息
call Delay
call ReadKey ; 等待输入
jz L1
test ebx, CAPSLOCK_ON
jz L2
mWrite <"CapsLock is ON", 0dh, 0ah>
jmp L3
L2:
mWrite <"CapsLock is OFF", 0dh, 0ah>
L3: exit
main ENDP
main
- 获取键盘状态
可以测试各个键盘键的状态,以找出当前按下的键。
GetKeyState PROTO, nVirtKey:DWORD
示例:
; Keyboard Toggle Keys (Keybd.asm)
INCLUDE Irvine32.inc
INCLUDE Macros.inc
; GetKeyState sets bit 0 in EAX if a toggle key is
; currently on (CapsLock, NumLock, ScrollLock).
; It sets the high bit of EAX if the specified key is
; currently down.
.code
main PROC
INVOKE GetKeyState, VK_NUMLOCK
test al,1
.IF !Zero?
mWrite <"The NumLock key is ON",0dh,0ah>
.ENDIF
INVOKE GetKeyState, VK_LSHIFT
test eax,80000000h
.IF !Zero?
mWrite <"The Left Shift key is currently DOWN",0dh,0ah>
.ENDIF
exit
main ENDP
END main
Testing Keys with GetKeyState.png
3). 控制台输出
-
数据结构
一些Win32控制台功能使用预定义的数据结构,包括COORD和SMALL_RECT。 -
WriteConsole 函数
WriteConsole函数将字符串写入当前光标位置的控制台窗口,并将光标留在最后写入的字符之后。
WriteConsole PROTO,
hConsoleOutput:HANDLE,
lpBuffer:PTR BYTE,
nNumberOfCharsToWrite:DWORD,
lpNumberOfCharsWritten:PTR DWORD,
lpReserved:DWORD
hConsoleOutput
控制台输出流句柄
lpBuffer
用户想要输出的数组指针
nNumberOfCharsToWrite
数组长度
lpNumberOfCharsWritten
指向一个整数,该整数分配了函数返回时实际写入的字节数。
lpReserved
保留参数。
示例:
; 程序调用的Win32 控制台函数: GetStdHandle, ExitProcess, WriteConsole
INCLUDE Irvine32.inc
.data
endl EQU <0dh,0ah> ; end of line sequence
message LABEL BYTE
BYTE "This program is a simple demonstration of"
BYTE "console mode output, using the GetStdHandle"
BYTE "and WriteConsole functions.",endl
messageSize DWORD ($ - message)
consoleHandle HANDLE 0 ; handle to standard output device
bytesWritten DWORD ? ; number of bytes written
.code
main PROC
; 获取控制台输出句柄
INVOKE GetStdHandle, STD_OUTPUT_HANDLE
mov consoleHandle, eax
; 在控制台输出一个字符串
INVOKE WriteConsole,
consoleHandle,
ADDR message,
messageSize,
ADDR bytesWritten,
0
call WaitMsg
INVOKE ExitProcess, 0
main ENDP
END
- WriteConsoleOutputCharacter 函数
WriteConsoleOutputCharacter函数将字符数组复制到控制台屏幕缓冲区的连续单元格,从指定位置开始。
WriteConsoleOutputCharacter PROTO,
hConsoleOutput:HANDLE, ; console output handle
lpCharacter:PTR BYTE, ; pointer to buffer
nLength:DWORD, ; size of buffer
dwWriteCoord:COORD, ; first cell coordinates
lpNumberOfCharsWritten:PTR DWORD ; output count
4). 读写文件
- CreateFile 函数
CreateFile函数可以创建新文件或打开现有文件。 如果成功,则返回打开文件的句柄; 否则,它返回一个名为INVALID_HANDLE_VALUE的特殊常量。
CreateFile PROTO, ; create new file
lpFilename:PTR BYTE, ; ptr to filename
dwDesiredAccess:DWORD, ; access mode
dwShareMode:DWORD, ; share mode
lpSecurityAttributes:DWORD, ; ptr security attrib
dwCreationDisposition:DWORD, ; file creation options
dwFlagsAndAttributes:DWORD, ; file attributes
hTemplateFile:DWORD ; handle to template file
CreateFile Parameters.png
dwDesiredAccess: 允许您指定对文件的读访问,写访问,读/写访问或设备查询访问。
dwCreationDisposition: 指定对存在的文件执行的操作以及文件不存在时要执行的操作。
dwCreationDisposition Parameter Options.pngdwFlagsAndAttributes: 除了所有其他文件属性都覆盖FILE_ATTRIBUTE_NORMAL之外,任何属性组合都是可接受的。
Selected FlagsAndAttributes Values.png示例:
—— 读取一个已存在的文件
INVOKE CreateFile,
ADDR filename, ; ptr to filename
GENERIC_READ, ; read from the file
DO_NOT_SHARE, ; share mode
NULL, ; ptr to security attributes
OPEN_EXISTING, ; open an existing file
FILE_ATTRIBUTE_NORMAL, ; normal file attribute
0 ; not used
—— 为已存在的文件写内容
INVOKE CreateFile,
ADDR filename,
GENERIC_WRITE, ; write to the file
DO_NOT_SHARE,
NULL,
OPEN_EXISTING, ; file must exist
FILE_ATTRIBUTE_NORMAL,
0
—— 创建一个文件,并设置普通属性,擦除已有的文件内容
INVOKE CreateFile,
ADDR filename,
GENERIC_WRITE, ; write to the file
DO_NOT_SHARE,
NULL,
CREATE_ALWAYS, ; overwrite existing file
FILE_ATTRIBUTE_NORMAL,
0
—— 如果文件不存在则创建新文件,如果存在则打开
INVOKE CreateFile,
ADDR filename,
GENERIC_WRITE, ; write to the file
DO_NOT_SHARE,
NULL,
CREATE_NEW, ; don't erase existing file
FILE_ATTRIBUTE_NORMAL,
0
- CloseHandle 函数
CloseHandle函数关闭一个打开的文件句柄。
CloseHandle PROTO,
hObject:HANDLE ; handle to object
- ReadFile 函数
ReadFile函数从一个文件中读取文本。
ReadFile PROTO,
hFile:HANDLE, ; input handle
lpBuffer:PTR BYTE, ; ptr to buffer
nNumberOfBytesToRead:DWORD, ; num bytes to read
lpNumberOfBytesRead:PTR DWORD, ; bytes actually read
lpOverlapped:PTR DWORD ; ptr to asynch info
hFile
是一个CreateFile函数返回的文件句柄
lpBuffer
是从文件中接收到的数据指针
nNumberOfBytesToRead
指定从文件中读取的最大字节数
lpNumberOfBytesRead
指向一个整数,表示函数返回时实际读取的字节数
lpOverlapped
应该设置为NULL(0)进行同步读取(我们使用)。 如果函数失败,则返回值为零。
- WriteFile 函数
WriteFile函数使用输出句柄将数据写入文件。 句柄可以是屏幕缓冲区句柄,也可以是分配给文本文件的句柄。 该函数开始在文件内部位置指针指示的位置将数据写入文件。 写操作完成后,文件的位置指针由实际写入的字节数调整。
WriteFile PROTO,
hFile:HANDLE, ; output handle
lpBuffer:PTR BYTE, ; pointer to buffer
nNumberOfBytesToWrite:DWORD, ; size of buffer
lpNumberOfBytesWritten:PTR DWORD, ; num bytes written
lpOverlapped:PTR DWORD ; ptr to asynch info
hFile
是已打开文件的句柄
lpBuffer
是要写入文件内容指针
nNumberOfBytesToWrite
是指定写入文件的字节数
lpNumberOfBytesWritten
是指向一个整数,该整数指定函数执行后实际写入的字节数
lpOverlapped
对于同步操作,lpOverlapped应设置为NULL。 如果函数失败,则返回值为零
- SetFilePointer 函数
SetFilePointer函数移动打开文件的位置指针.
SetFilePointer PROTO,
hFile:HANDLE, ; file handle
lDistanceToMove:SDWORD, ; bytes to move pointer
lpDistanceToMoveHigh:PTR SDWORD, ; ptr bytes to move, high
dwMoveMethod:DWORD ; starting point
dwMoveMethod
移动指针的起始位置,有三个可选常量FILE_BEGIN, FILE_CURRENT, and FILE_END。
示例:
INVOKE SetFilePointer,
fileHandle, ; file handle
0, ; distance low
0, ; distance high
FILE_END ; move method
5). Irvine32 库中的文件I/O操作
CreateOutputFile, OpenFile, WriteToFile, ReadFromFile, and CloseFile 源码如下:
;------------------------------------------------------
CreateOutputFile PROC
;
; Creates a new file and opens it in output mode.
; Receives: EDX points to the filename.
; Returns: If the file was created successfully, EAX
; contains a valid file handle. Otherwise, EAX
; equals INVALID_HANDLE_VALUE.
;------------------------------------------------------
INVOKE CreateFile,
edx, GENERIC_WRITE, DO_NOT_SHARE, NULL,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0
ret
CreateOutputFile ENDP
;------------------------------------------------------
OpenFile PROC
;
; Opens a new text file and opens for input.
; Receives: EDX points to the filename.
; Returns: If the file was opened successfully, EAX
; contains a valid file handle. Otherwise, EAX equals
; INVALID_HANDLE_VALUE.
;------------------------------------------------------
INVOKE CreateFile,
edx, GENERIC_READ, DO_NOT_SHARE, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0
ret
OpenFile ENDP
;--------------------------------------------------------
WriteToFile PROC
;
; Writes a buffer to an output file.
; Receives: EAX = file handle, EDX = buffer offset,
; ECX = number of bytes to write
; Returns: EAX = number of bytes written to the file.
; If the value returned in EAX is less than the
; argument passed in ECX, an error likely occurred.
;--------------------------------------------------------
.data
WriteToFile_1 DWORD ? ; number of bytes written
.code
INVOKE WriteFile, ; write buffer to file
eax, ; file handle
edx, ; buffer pointer
ecx, ; number of bytes to write
ADDR WriteToFile_1, ; number of bytes written
0 ; overlapped execution flag
mov eax,WriteToFile_1 ; return value
ret
WriteToFile ENDP
;--------------------------------------------------------
ReadFromFile PROC
;
; Reads an input file into a buffer.
; Receives: EAX = file handle, EDX = buffer offset,
; ECX = number of bytes to read
; Returns: If CF = 0, EAX = number of bytes read; if
; CF = 1, EAX contains the system error code returned
; by the GetLastError Win32 API function.
;--------------------------------------------------------
.data
ReadFromFile_1 DWORD ? ; number of bytes read
.code
INVOKE ReadFile,
eax, ; file handle
edx, ; buffer pointer
ecx, ; max bytes to read
ADDR ReadFromFile_1,; number of bytes read
0 ; overlapped execution flag
mov eax,ReadFromFile_1
ret
ReadFromFile ENDP
;--------------------------------------------------------
CloseFile PROC
;
; Closes a file using its handle as an identifier.
; Receives: EAX = file handle
; Returns: EAX = nonzero if the file is successfully
; closed.
;--------------------------------------------------------
INVOKE CloseHandle, eax
ret
CloseFile ENDP
6). 测试文件I/O程序
- 写文件程序
; 创建文件, 生成的文件在项目的根目录下
INCLUDE Irvine32.inc
BUFFER_SIZE = 501
.data
buffer BYTE BUFFER_SIZE DUP(?)
filename BYTE "output.txt", 0
fileHandle HANDLE ?
stringLength DWORD ?
bytesWritten DWORD ?
str1 BYTE "Cannot create file", 0dh, 0ah, 0
str2 BYTE "Bytes written to file [output.txt]: ", 0
str3 BYTE "Enter up to 500 characters and press"
BYTE "[Enter]: ", 0dh, 0ah, 0
.code
main PROC
; 创建一个新的文本文件
mov edx, OFFSET filename
call CreateOutputFile
mov fileHandle, eax
; 检查错误
cmp eax, INVALID_HANDLE_VALUE ; 是否有错误
jne file_ok ; no: skip
mov edx, OFFSET str1 ; 显示错误
call WriteString
jmp quit
file_ok:
; 让用户输入字符串
mov edx, OFFSET str3 ; "Enter up to ..."
call WriteString
mov ecx, BUFFER_SIZE ; 输入是字符串
mov edx, OFFSET buffer
call ReadString
mov stringLength, eax ; 计算字符串的字符数
; 写入文件
mov eax, fileHandle
mov edx, OFFSET buffer
mov ecx, stringLength
call WriteToFile
mov bytesWritten, eax ; 保存返回值
call CloseFile
; 显示返回值
mov edx, OFFSET str2 ; Bytes written
call WriteString
mov eax, bytesWritten
call WriteDec
call Crlf
quit:
call WaitMsg
call Crlf
exit
main ENDP
END
- 读文件程序
; 读取文件内容
INCLUDE Irvine32.inc
INCLUDE macros.inc
BUFFER_SIZE = 5000
.data
buffer BYTE BUFFER_SIZE DUP(?)
filename BYTE 80 DUP(0)
fileHandle HANDLE ?
.code
main PROC
; 让用户输入文件名
mWrite "Enter an input filename: "
mov edx, OFFSET filename
mov ecx, SIZEOF filename
call ReadString
; 打开文件
mov edx,OFFSET filename
call OpenInputFile
mov fileHandle, eax
; 检查错误
cmp eax, INVALID_HANDLE_VALUE
jne file_ok
mWrite <"Cannot open file", 0dh, 0ah>
jmp quit
file_ok:
; 读取文件内容到缓冲区
mov edx, OFFSET buffer
mov ecx, BUFFER_SIZE
call ReadFromFile
jnc check_buffer_size
mWrite "Error reading file."
call WriteWindowsMsg
jmp close_file
check_buffer_size:
cmp eax, BUFFER_SIZE
jb buf_size_ok
mWrite <"Error: Buffer too small for the file", 0dh, 0ah>
jmp quit
buf_size_ok:
mov buffer[eax], 0
mWrite "File size: "
call WriteDec
call Crlf
; 显示缓冲区内容
mWrite <"Buffer: ", 0dh, 0ah, 0dh, 0ah>
mov edx, OFFSET buffer
call WriteString
call Crlf
close_file:
mov eax, fileHandle
call CloseFile
quit:
call WaitMsg
call Crlf
exit
main ENDP
END
7). 控制台窗口操作
Win32 API提供了对控制台窗口及其缓冲区的可控制.
Screen buffer and console window.png有几个函数会影响控制台窗口及其相对于屏幕缓冲区的位置:
—— SetConsoleWindowInfo设置控制台窗口相对于屏幕缓冲区的大小和位置。
—— GetConsoleScreenBufferInfo返回控制台窗口相对于屏幕缓冲区的矩形坐标(以及其他内容)。
—— SetConsoleCursorPosition将光标位置设置为屏幕缓冲区内的任何位置; 如果该区域不可见,则移动控制台窗口以使光标可见。
—— ScrollConsoleScreenBuffer移动屏幕缓冲区中的部分或全部文本,这会影响控制台窗口中显示的文本
- SetConsoleTitle 函数
SetConsoleTitle函数可让我们改变控制他窗口的标题。
.data
titleStr BYTE "Console title",0
.code
INVOKE SetConsoleTitle, ADDR titleStr
- GetConsoleScreenBufferInfo 函数
GetConsoleScreenBufferInfo函数返回控制台窗口的当前状态信息。 它有量参数:一个是控制台窗口句柄,另一个是指向由函数填充的结构的指针。
GetConsoleScreenBufferInfo PROTO,
hConsoleOutput:HANDLE,
lpConsoleScreenBufferInfo:PTR CONSOLE_SCREEN_BUFFER_INFO
其中CONSOLE_SCREEN_BUFFER_INFO结构体定义内容为:
CONSOLE_SCREEN_BUFFER_INFO STRUCT
dwSize COORD <>
dwCursorPosition COORD <>
wAttributes WORD ?
srWindow SMALL_RECT <>
dwMaximumWindowSize COORD <>
CONSOLE_SCREEN_BUFFER_INFO ENDS
示例:
.data
consoleInfo CONSOLE_SCREEN_BUFFER_INFO <>
outHandle HANDLE ?
.code
INVOKE GetConsoleScreenBufferInfo, outHandle, ADDR consoleInfo
CONSOLE_SCREEN_BUFFER_INFO structure.png
- setConsoleWindowInfo 函数
SetConsoleWindowInfo函数允许您设置控制台窗口相对于其屏幕缓冲区的大小和位置。
SetConsoleWindowInfo PROTO,
hConsoleOutput:HANDLE, ; screen buffer handle
bAbsolute:DWORD, ; coordinate type
lpConsoleWindow:PTR SMALL_RECT ; ptr to window rectangle
bAbsolute指示如何使用lpConsoleWindow指向的结构中的坐标。 如果bAbsolute为true,则坐标指定控制台窗口的新左上角和右下角。 如果bAbsolute为false,则坐标将添加到当前窗口坐标。
; 滚动控制台窗口
INCLUDE Irvine32.inc
.data
message BYTE ": This line of text was written "
BYTE "to the screen buffer",0dh,0ah
messageSize DWORD ($-message)
outHandle HANDLE 0 ; standard output handle
bytesWritten DWORD ? ; number of bytes written
lineNum DWORD 0
windowRect SMALL_RECT <0,0,60,11> ; left,top,right,bottom
.code
main PROC
INVOKE GetStdHandle, STD_OUTPUT_HANDLE
mov outHandle,eax
.REPEAT
mov eax,lineNum
call WriteDec ; display each line number
INVOKE WriteConsole, outHandle, ; console output handle
ADDR message, ; string pointer
messageSize, ; string length
ADDR bytesWritten, ; returns num bytes written
0 ; not used
inc lineNum ; next line number
.UNTIL lineNum > 50
; Resize and reposition the console window relative to the
; screen buffer.
INVOKE SetConsoleWindowInfo,
outHandle,
TRUE,
ADDR windowRect ; window rectangle
call Readchar ; wait for a key
call Clrscr ; clear the screen buffer
call Readchar ; wait for a second key
INVOKE ExitProcess,0
main ENDP
END main
main ENDP
END
- SetConsoleScreenBufferSize 函数
SetConsoleScreenBufferSize函数允许您将屏幕缓冲区大小设置为X列Y行。
SetConsoleScreenBufferSize PROTO,
hConsoleOutput:HANDLE, ; handle to screen buffer
dwSize:COORD ; new screen buffer size
8). 控制光标
Win32 API提供了设置光标大小,可见性和屏幕位置的功能。 与这些函数相关的重要数据结构是CONSOLE_CURSOR_INFO,其中包含有关控制台光标大小和可见性的信息。
CONSOLE_CURSOR_INFO STRUCT
dwSize DWORD ?
bVisible DWORD ?
CONSOLE_CURSOR_INFO ENDS
dwSize是光标填充的字符单元格的百分比(1到100)。 如果光标可见,则bVisible等于TRUE(1)。
- GetConsoleCursorInfo 函数
GetConsoleCursorInfo 函数返回光标的可见性和大小。
GetConsoleCursorInfo PROTO,
hConsoleOutput:HANDLE,
lpConsoleCursorInfo:PTR CONSOLE_CURSOR_INFO
- SetConsoleCursorInfo 函数
SetConsoleCursorInfo 函数设置光标的可见与大小。
SetConsoleCursorInfo PROTO,
hConsoleOutput:HANDLE,
lpConsoleCursorInfo:PTR CONSOLE_CURSOR_INFO
- SetConsoleCursorPosition 函数
SetConsoleCursorPosition 函数设置光标的位置。
SetConsoleCursorPosition PROTO,
hConsoleOutput:DWORD, ; input mode handle
dwCursorPosition:COORD ; screen X,Y coordinates
9). 控制字体颜色
- SetConsoleTextAttribute 函数
SetConsoleTextAttribute 函数允许您将所有后续文本输出的前景色和背景色设置到控制台窗口.
SetConsoleTextAttribute PROTO,
hConsoleOutput:HANDLE, ; console output handle
wAttributes:WORD ; color attribute
- WriteConsoleOutputAttribute 函数
WriteConsoleOutputAttribute 函数从指定位置开始,将一组属性值复制到控制台屏幕缓冲区的连续单元格。
WriteConsoleOutputAttribute PROTO,
hConsoleOutput:DWORD, ; output handle
lpAttribute:PTR WORD, ; write attributes
nLength:DWORD, ; number of cells
dwWriteCoord:COORD, ; first cell coordinates
lpNumberOfAttrsWritten:PTR DWORD ; output count
lpAttribute
指向一个属性数组,其中每个属性的低位字节包含颜色
nLength
是数组的长度
dwWriteCoord
是接收属性的起始屏幕单元格
lpNumberOfAttrsWritten
指向一个变量,该变量将保存写入的单元格数
; 更改文本颜色
INCLUDE Irvine32.inc
.data
outHandle HANDLE ?
cellsWritten DWORD ?
xyPos COORD <10,2>
; Array of character codes:
buffer BYTE 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15
BYTE 16,17,18,19,20
BufSize DWORD ($-buffer)
; Array of attributes:
attributes WORD 0Fh,0Eh,0Dh,0Ch,0Bh,0Ah,9,8,7,6
WORD 5,4,3,2,1,0F0h,0E0h,0D0h,0C0h,0B0h
.code
main PROC
; Get the Console standard output handle:
INVOKE GetStdHandle,STD_OUTPUT_HANDLE
mov outHandle,eax
; Set the colors of adjacent cells:
INVOKE WriteConsoleOutputAttribute,
outHandle, ADDR attributes,
BufSize, xyPos, ADDR cellsWritten
; Write character codes 1 through 20:
INVOKE WriteConsoleOutputCharacter,
outHandle, ADDR buffer, BufSize,
xyPos, ADDR cellsWritten
INVOKE ExitProcess,0 ; end program
main ENDP
END
效果.png
10). 时间和日期函数
Win32 API提供了相当多的时间和日期函数选择。
Win32 DateTime Functions.png- SYSTEMTIME 结构体
SYSTEMTIME STRUCT
wYear WORD ? ; year (4 digits)
wMonth WORD ? ; month (1-12)
wDayOfWeek WORD ? ; day of week (0-6)
wDay WORD ? ; day (1-31)
wHour WORD ? ; hours (0-23)
wMinute WORD ? ; minutes (0-59)
wSecond WORD ? ; seconds (0-59)
wMilliseconds WORD ?; milliseconds (0-999)
SYSTEMTIME ENDS
wDayOfWeek 里Sunday为0,然后递加。
- GetLocalTime 和 SetLocalTime 函数
GetLocalTime函数根据系统时钟返回日期和当前时间。SetLocalTime函数设置系统的本地日期和时间。
GetLocalTime PROTO,
lpSystemTime:PTR SYSTEMTIME
SetLocalTime PROTO,
lpSystemTime:PTR SYSTEMTIME
示例:
.data
sysTime SYSTEMTIME <>
.code
INVOKE GetLocalTime, ADDR sysTime
- GetTickCount 函数
GetTickCount函数返回自系统启动以来经过的毫秒数。
GetTickCount PROTO ; return value in EAX
示例: 计算经过时间
; 计算经过时间
INCLUDE Irvine32.inc
INCLUDE macros.inc
.data
startTime DWORD ?
.code
main PROC
INVOKE GetTickCount ; get starting tick count
mov startTime,eax ; save it
; Create a useless calculation loop.
mov ecx,10000100h
L1:
imul ebx
imul ebx
imul ebx
loop L1
INVOKE GetTickCount ; get new tick count
cmp eax,startTime ; lower than starting one?
jb error ; it wrapped around
sub eax,startTime ; get elapsed milliseconds
call WriteDec ; display it
mWrite <" milliseconds have elapsed",0dh,0ah>
jmp quit
error:
mWrite "Error: GetTickCount invalid--system has"
mWrite <"been active for more than 49.7 days",0dh,0ah>
quit:
call WaitMsg
call Crlf
exit
main ENDP
END main
- Sleep 函数
程序有时需要暂停或延迟很短的时间。 虽然可以构建一个计算循环或繁忙循环来保持处理器忙,但循环的执行时间会因处理器而异。 此外,繁忙的循环将不必要地占用处理器,从而减慢同时执行的其他程序的速度。
Sleep PROTO,
dwMilliseconds:DWORD
- GetDateTime 程序
Irvine32库中的GetDateTime过程返回自1601年1月1日以来经过的100纳秒时间间隔的数量。
;--------------------------------------------------
GetDateTime PROC,
pStartTime:PTR QWORD
LOCAL sysTime:SYSTEMTIME, flTime:FILETIME
;
; Gets and saves the current local date/time as a
; 64-bit integer (in the Win32 FILETIME format).
;--------------------------------------------------
; Get the system local time
INVOKE GetLocalTime,
ADDR sysTime
; Convert the SYSTEMTIME to FILETIME
INVOKE SystemTimeToFileTime,
ADDR sysTime,
ADDR flTime
; Copy the FILETIME to a 64-bit integer
mov esi,pStartTime
mov eax,flTime.loDateTime
mov DWORD PTR [esi],eax
mov eax,flTime.hiDateTime
mov DWORD PTR [esi+4],eax
ret
GetDateTime ENDP
2. Windows 图形应用
Win32 图形应用需要的文件如下图, 使用/SUBSYSTEM:WINDOWS
替换/SUBSYSTEM:CONSOLE
(修改方法:在解决方法管理器中,项目名上右键 -> 属性 -> 配置属性 -> 链接器 -> 系统 -> 子系统,属性 -> 配置属性 -> 链接器 -> 高级 -> 入口点main@0 改为WinMain@0)
1). 必要的结构
- POINT 结构体
POINT结构指定屏幕上某点的X和Y坐标,以像素为单位。
POINT STRUCT
ptX DWORD ?
ptY DWORD ?
POINT ENDS
- RECT 结构体
RECT结构定义矩形的边界。 左侧成员包含矩形左侧的X坐标。 顶部成员包含矩形顶部的Y坐标。
RECT STRUCT
left DWORD ?
top DWORD ?
right DWORD ?
bottom DWORD ?
RECT ENDS
- MSGStruct 结构体
MSGStruct结构定义了MS-Windows消息所需的数据。
MSGStruct STRUCT
msgWnd DWORD ?
msgMessage DWORD ?
msgWparam DWORD ?
msgLparam DWORD ?
msgTime DWORD ?
msgPt POINT <>
MSGStruct ENDS
- WNDCLASS结构体
WNDCLASS结构定义了一个窗口类。 程序中的每个窗口都必须属于一个类,每个程序必须为其主窗口定义一个窗口类。
WNDCLASS STRUC
style DWORD ? ; window style options
lpfnWndProc DWORD ? ; pointer to WinProc function
cbClsExtra DWORD ? ; shared memory
cbWndExtra DWORD ? ; number of extra bytes
hInstance DWORD ? ; handle to current program
hIcon DWORD ? ; handle to icon
hCursor DWORD ? ; handle to cursor
hbrBackground DWORD ? ; handle to background brush
lpszMenuName DWORD ? ; pointer to menu name
lpszClassName DWORD ? ; pointer to WinClass name
WNDCLASS ENDS
2). WinMain 程序
每个Windows应用程序都需要一个启动过程,通常名为WinMain,它负责以下任务:
•获取当前程序的句柄。
•加载程序的图标和鼠标光标。
•注册程序的主窗口类,并确定将处理窗口事件消息的过程。
•创建主窗口。
•显示和更新主窗口。
•开始接收和发送消息的循环。 循环继续,直到用户关闭应用程序窗口。
3). WinProc 程序
WinProc过程接收并处理与窗口有关的所有事件消息。 大多数事件由用户通过单击并拖动鼠标,按键盘键等启动。
WinProc PROC,
hWnd:DWORD, ; handle to the window
localMsg:DWORD, ; message ID
wParam:DWORD, ; parameter 1 (varies)
lParam:DWORD ; parameter 2 (varies)
WinProc 中的三种特殊消息:
•WM_LBUTTONDOWN,用户按下鼠标左键时生成
•WM_CREATE,表示刚刚创建了主窗口
•WM_CLOSE,表示应用程序的主窗口即将关闭
4). ErrorHandler 程序
如果系统在注册和创建程序主窗口期间报告错误,则会调用ErrorHandler过程(可选)。
ErrorHandler过程有几个重要的任务要执行:
•调用GetLastError以检索系统错误号。
•调用FormatMessage以检索适当的系统格式错误消息字符串。
•调用MessageBox以显示包含错误消息字符串的弹出消息框。
•调用LocalFree以释放错误消息字符串使用的内存。
5). 程序示例
; Windows Application (WinApp.asm)
; This program displays a resizable application window and
; several popup message boxes. Special thanks to Tom Joyce
; for the first version of this program.
INCLUDE Irvine32.inc
INCLUDE GraphWin.inc
;==================== DATA =======================
.data
AppLoadMsgTitle BYTE "Application Loaded",0
AppLoadMsgText BYTE "This window displays when the WM_CREATE "
BYTE "message is received",0
PopupTitle BYTE "Popup Window",0
PopupText BYTE "This window was activated by a "
BYTE "WM_LBUTTONDOWN message",0
GreetTitle BYTE "Main Window Active",0
GreetText BYTE "This window is shown immediately after "
BYTE "CreateWindow and UpdateWindow are called.",0
CloseMsg BYTE "WM_CLOSE message received",0
ErrorTitle BYTE "Error",0
WindowName BYTE "ASM Windows App",0
className BYTE "ASMWin",0
; Define the Application's Window class structure.
MainWin WNDCLASS <NULL,WinProc,NULL,NULL,NULL,NULL,NULL, \
COLOR_WINDOW,NULL,className>
msg MSGStruct <>
winRect RECT <>
hMainWnd DWORD ?
hInstance DWORD ?
;=================== CODE =========================
.code
WinMain PROC
; Get a handle to the current process.
INVOKE GetModuleHandle, NULL
mov hInstance, eax
mov MainWin.hInstance, eax
; Load the program's icon and cursor.
INVOKE LoadIcon, NULL, IDI_APPLICATION
mov MainWin.hIcon, eax
INVOKE LoadCursor, NULL, IDC_ARROW
mov MainWin.hCursor, eax
; Register the window class.
INVOKE RegisterClass, ADDR MainWin
.IF eax == 0
call ErrorHandler
jmp Exit_Program
.ENDIF
; Create the application's main window.
INVOKE CreateWindowEx, 0, ADDR className,
ADDR WindowName,MAIN_WINDOW_STYLE,
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,
CW_USEDEFAULT,NULL,NULL,hInstance,NULL
; If CreateWindowEx failed, display a message and exit.
.IF eax == 0
call ErrorHandler
jmp Exit_Program
.ENDIF
; Save the window handle, show and draw the window.
mov hMainWnd,eax
INVOKE ShowWindow, hMainWnd, SW_SHOW
INVOKE UpdateWindow, hMainWnd
; Display a greeting message.
INVOKE MessageBox, hMainWnd, ADDR GreetText,
ADDR GreetTitle, MB_OK
; Begin the program's continuous message-handling loop.
Message_Loop:
; Get next message from the queue.
INVOKE GetMessage, ADDR msg, NULL,NULL,NULL
; Quit if no more messages.
.IF eax == 0
jmp Exit_Program
.ENDIF
; Relay the message to the program's WinProc.
INVOKE DispatchMessage, ADDR msg
jmp Message_Loop
Exit_Program:
INVOKE ExitProcess,0
WinMain ENDP
;-----------------------------------------------------
WinProc PROC,
hWnd:DWORD, localMsg:DWORD, wParam:DWORD, lParam:DWORD
;
; The application's message handler, which handles
; application-specific messages. All other messages
; are forwarded to the default Windows message
; handler.
;-----------------------------------------------------
mov eax, localMsg
.IF eax == WM_LBUTTONDOWN ; mouse button?
INVOKE MessageBox, hWnd, ADDR PopupText,
ADDR PopupTitle, MB_OK
jmp WinProcExit
.ELSEIF eax == WM_CREATE ; create window?
INVOKE MessageBox, hWnd, ADDR AppLoadMsgText,
ADDR AppLoadMsgTitle, MB_OK
jmp WinProcExit
.ELSEIF eax == WM_CLOSE ; close window?
INVOKE MessageBox, hWnd, ADDR CloseMsg,
ADDR WindowName, MB_OK
INVOKE PostQuitMessage,0
jmp WinProcExit
.ELSE ; other message?
INVOKE DefWindowProc, hWnd, localMsg, wParam, lParam
jmp WinProcExit
.ENDIF
WinProcExit:
ret
WinProc ENDP
;---------------------------------------------------
ErrorHandler PROC
; Display the appropriate system error message.
;---------------------------------------------------
.data
pErrorMsg DWORD ? ; ptr to error message
messageID DWORD ?
.code
INVOKE GetLastError ; Returns message ID in EAX
mov messageID,eax
; Get the corresponding message string.
INVOKE FormatMessage, FORMAT_MESSAGE_ALLOCATE_BUFFER + \
FORMAT_MESSAGE_FROM_SYSTEM,NULL,messageID,NULL,
ADDR pErrorMsg,NULL,NULL
; Display the error message.
INVOKE MessageBox,NULL, pErrorMsg, ADDR ErrorTitle,
MB_ICONERROR+MB_OK
; Free the error message string.
INVOKE LocalFree, pErrorMsg
ret
ErrorHandler ENDP
END WinMain
3. 动态申请内存
动态内存分配(也称为堆分配)是一种编程语言用于在创建对象,数组和其他结构时保留内存的技术。
C,C ++和Java具有内置的运行时堆管理器,用于处理存储分配和释放的编程请求。 堆启动管理器通常在程序启动时从操作系统分配大块内存。 他们创建了一个指向存储块的免费指针列表。 当收到分配请求时,堆管理器将适当大小的内存块标记为保留,并返回指向该块的指针。 稍后,当收到对同一块的删除请求时,堆会释放该块,并将其返回到空闲列表。 每次收到新的分配请求时,堆管理器都会扫描空闲列表,查找足够大的第一个可用块以授予请求。
- GetProcessHeap
如果您满足于使用当前程序拥有的默认堆,则GetProcessHeap就足够了。
GetProcessHeap PROTO
示例:
.data
hHeap HANDLE ?
.code
INVOKE GetProcessHeap
.IF eax == NULL ; cannot get handle
jmp quit
.ELSE
mov hHeap,eax ; handle is OK
.ENDIF
- HeapCreate
创建私有堆空间
HeapCreate PROTO,
flOptions:DWORD, ; heap allocation options
dwInitialSize:DWORD, ; initial heap size, in bytes
dwMaximumSize:DWORD ; maximum heap size, in bytes
flOptions
设置为NULL
dwInitialSize
设置为初始堆大小(以字节为单位)。 该值向上舍入到下一页边界
HeapAlloc
调用时超过初始堆大小时,它将增大到您在dwMaximumSize参数中指定的值(向上舍入到下一页边界)。 调用它之后,EAX中的空返回值表示未创建堆
示例:
HEAP_START = 2000000 ; 2 MB
HEAP_MAX = 400000000 ; 400 MB
.data
hHeap HANDLE ? ; handle to heap
.code
INVOKE HeapCreate, 0, HEAP_START, HEAP_MAX
.IF eax == NULL ; heap not created
call WriteWindowsMsg ; show error message
jmp quit
.ELSE
mov hHeap,eax ; handle is OK
.ENDIF
- HeapDestroy
HeapDestroy 函数销毁已存在的私有堆空间
HeapDestroy PROTO,
hHeap:DWORD ; heap handle
示例:
.data
hHeap HANDLE ? ; handle to heap
.code
INVOKE HeapDestroy, hHeap
.IF eax == NULL
call WriteWindowsMsg ; show error message
.ENDIF
- HeapAlloc
HeapAlloc 函数在已存在的堆中申请内存块
HeapAlloc PROTO,
hHeap:HANDLE, ; handle to existing heap block
dwFlags:DWORD, ; heap allocation control flags
dwBytes:DWORD ; number of bytes to allocate
hHeap
一个由GetProcessHeap或HeapCreate初始化的堆的32位句柄。
dwFlags
包含一个或多个标志值的双字。 您可以选择将其设置为HEAP_ZERO_MEMORY,它将内存块设置为全零。
dwBytes
,一个双字,表示堆分配的大小,以字节为单位
.data
hHeap HANDLE ? ; heap handle
pArray DWORD ? ; pointer to array
.code
INVOKE HeapAlloc, hHeap, HEAP_ZERO_MEMORY, 1000
.IF eax == NULL
mWrite "HeapAlloc failed"
jmp quit
.ELSE
mov pArray,eax
.ENDIF
- HeapFree
HeapFree 函数在已经存在的内存块中释放私有空间。
HeapFree PROTO,
hHeap:HANDLE,
dwFlags:DWORD,
lpMem:DWORD
示例:
INVOKE HeapFree, hHeap, 0, pArray
- Error Handling
如果在调用HeapCreate,HeapDestroy或GetProcessHeap时遇到错误,可以通过调用GetLastError API函数来获取详细信息。
INVOKE HeapCreate, 0,HEAP_START, HEAP_MAX
.IF eax == NULL ; failed?
call WriteWindowsMsg ; show error message
.ELSE
mov hHeap,eax ; success
.ENDIF
1). 堆测试程序
; 堆测试
; 程序动态申请1000个字节的内存
INCLUDE Irvine32.inc
.data
ARRAY_SIZE = 1000
FILL_VAL EQU 0FFh
hHeap HANDLE ? ; 进程堆句柄
pArray DWORD ? ; 指向内存块
newHeap DWORD ? ; 新堆的句柄
str1 BYTE "Heap size is: ",0
.code
main PROC
INVOKE GetProcessHeap ; 获取堆句柄
.IF eax == NULL ; 如果获取失败,则显示异常信息
call WriteWindowsMsg
jmp quit
.ELSE
mov hHeap,eax ; 获取成功
.ENDIF
call allocate_array
jnc arrayOk ; 失败,设置CF=1
call WriteWindowsMsg
call Crlf
jmp quit
arrayOk: ; 字节填充
call fill_array
call display_array
call Crlf
; free the array
INVOKE HeapFree, hHeap, 0, pArray
quit:
exit
main ENDP
;--------------------------------------------------------
allocate_array PROC USES eax
; 为数组动态申请内存空间
; Receives: EAX = handle to the program heap
; Returns: CF = 0 if the memory allocation succeeds.
;--------------------------------------------------------
INVOKE HeapAlloc, hHeap, HEAP_ZERO_MEMORY, ARRAY_SIZE
.IF eax == NULL
stc ; 返回时CF=1
.ELSE
mov pArray,eax ; 保存指针
clc ; 返回时CF=0
.ENDIF
ret
allocate_array ENDP
;--------------------------------------------------------
fill_array PROC USES ecx edx esi
; 填充单个字符
; Receives: nothing
; Returns: nothing
;--------------------------------------------------------
mov ecx,ARRAY_SIZE ; 循环计数
mov esi,pArray ; 数组指针
L1: mov BYTE PTR [esi],FILL_VAL ; 填充每个字节
inc esi ; 下一个位置
loop L1
ret
fill_array ENDP
;--------------------------------------------------------
display_array PROC USES eax ebx ecx esi
; 显示数组
; Receives: nothing
; Returns: nothing
;--------------------------------------------------------
mov ecx,ARRAY_SIZE ; 循环计数
mov esi,pArray ; 数组指针
L1: mov al,[esi] ; 获取一个字节
mov ebx,TYPE BYTE
call WriteHexB ; 显示
inc esi ; 下一个位置
loop L1
ret
display_array ENDP
END main
4. x86 内存管理
- 将逻辑地址转换为线性地址
- 将线性地址转换为物理地址(分页)
1). 线性地址
- 将逻辑地址转换为线性地址
多任务操作系统允许多个程序(任务)同时在内存中运行。 每个程序都有自己独特的数据区域。 假设三个程序每个都有一个偏移量为200h的变量; 三个变量如何在不共享的情况下彼此分离? 答案是x86处理器使用一步或两步过程将每个变量的偏移量转换为唯一的内存位置.
第一步将段值与变量的偏移量组合在一起以创建线性地址。 该线性地址可以是变量的物理地址。 但是,诸如MS-Windows和Linux之类的操作系统采用称为分页的功能,以允许程序使用比计算机中物理可用的线性内存更多的线性内存。 他们必须使用称为页面转换的第二步将线性地址转换为物理地址。
-
分页
分页是x86处理器的一个重要特性,它使计算机可以运行一些本来不适合内存的程序组合。 处理器通过最初仅将部分程序加载到内存中,同时将剩余部分保留在磁盘上来完成此操作。 程序使用的内存分为称为页面的小单元,每个单元通常为4 KB。 当每个程序运行时,处理器选择性地从内存中卸载非活动页面并加载其他立即需要的页面。 -
描述表
段描述符可以在两种类型的表中找到:全局描述符表和本地描述符表。
全局描述符表——当操作系统在启动期间将处理器切换到保护模式时,会创建单个GDT。 其基址保存在GDTR(全局描述符表寄存器)中。
本地描述符表——在多任务操作系统中,通常为每个任务或程序分配其自己的段描述符表,称为LDT。 LDTR寄存器包含程序LDT的地址。 -
段描述符详细信息
除了段的基址之外,段描述符还包含指定段限制和段类型的位映射字段。
2). 页面翻译
启用分页时,处理器必须将32位线性地址转换为32位物理地址。 该过程中使用了三种结构:
•页面目录:最多1024个32位页面目录条目的数组。
•页表:最多1024个32位页表条目的数组。
•页面:4 KB或4 MB的地址空间。