返回列表 发帖

[原创]汇编语言教学

第十六课 事件对象
本课中我们将要学习事件对象以及如何在多线程编程中如何使用同步对象。
理论:
上一课中我们演示了如何用WINDOWS消息在不同的线程之间进行通讯。另外的两种,即:使用全局变量和事件对象,将在本课中讲解。
事件对象就像一个开关:它只有两种状态---开和关。当一个事件处于”开”状态,我们称其为”有信号”否则称为”无信号”。您可以在一个线程的执行函数中创建一个事件对象,然后观察它的状态,如果是”无信号”就让该线程睡眠,这样该线程占用的CPU时间就比较少。
产生事件对象的函数如下:
CreateEvent proto lpEventAttributes:DWORD,\
bManualReset:DWORD,\
bInitialState:DWORD,\
lpName:DWORD
lpEventAttribute--> 如果是NULL值,产生的事件对象有缺省的安全属性。
bManualReset--> 如果想在每次调用WaitForSingleObject 后让WINDOWS为您自动地把事件地状态恢复为”无信号”状态,必须把该参数设为FALSE,否则,您必须每次调用ResetEvent函数来清除事件的信号。
bInitialState--> 刚刚产生事件对象时的状态。如果设为TRUE是”有信号”,否则是”无信号”。
lpName --> 事件对象的名称。您在OpenEvent函数中可能使用。
如果CreateEvent调用成功的话,会返回新生成的对象的句柄,否则返回NULL。
这里有两个API函数用来修改事件对象的信号状态:SetEvent和ResetEvent。前者把事件对象设为”有信号”状态,而后者正好相反。
在事件对象生成后,必须调用WaitForSingleObject来让线程进入等待状态,该函数的语法如下:
WaitForSingleObject proto hObject:DWORD, dwTimeout:DWORD
hObject -->指向同步对象的指针。事件对象其实是同步对象的一种。
dwTimeout --> 等待同步对象变成”有信号”前等待的时间,以毫秒计。当等待的时间超过该值后无信号同步对象仍处于”无信号”状态,线程不再等待,WaitForSingleObject函数会返回。如果想要线程一直等待,请把该参数设为INFINITE(该值等于0xffffffff)。
例子:
下面的例子显示了一个窗口,当用户选择了菜单项”run thread”后,线程开始简单的计数运算。结束后弹出一个对话框通知用户。在整个的计数期间,您可以选择菜单项”stop thread”来随时终止线程。
.386
.model flat,stdcall
option casemap:none
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
.const
IDM_START_THREAD equ 1
IDM_STOP_THREAD equ 2
IDM_EXIT equ 3
WM_FINISH equ WM_USER+100h
.data
ClassName db "Win32ASMEventClass",0
AppName db "Win32 ASM Event Example",0
MenuName db "FirstMenu",0
SuccessString db "The calculation is completed!",0
StopString db "The thread is stopped",0
EventStop BOOL FALSE
.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hwnd HANDLE ?
hMenu HANDLE ?
ThreadID DWORD ?
ExitCode DWORD ?
hEventStart HANDLE ?
.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,OFFSET MenuName
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,\
ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,300,200,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
invoke GetMenu,hwnd
mov hMenu,eax
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_CREATE
invoke CreateEvent,NULL,FALSE,FALSE,NULL
mov hEventStart,eax
mov eax,OFFSET ThreadProc
invoke CreateThread,NULL,NULL,eax,\
NULL,0,\
ADDR ThreadID
invoke CloseHandle,eax
.ELSEIF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.if lParam==0
.if ax==IDM_START_THREAD
invoke SetEvent,hEventStart
invoke EnableMenuItem,hMenu,IDM_START_THREAD,MF_GRAYED
invoke EnableMenuItem,hMenu,IDM_STOP_THREAD,MF_ENABLED
.elseif ax==IDM_STOP_THREAD
mov EventStop,TRUE
invoke EnableMenuItem,hMenu,IDM_START_THREAD,MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_STOP_THREAD,MF_GRAYED
.else
invoke DestroyWindow,hWnd
.endif
.endif
.ELSEIF uMsg==WM_FINISH
invoke MessageBox,NULL,ADDR SuccessString,ADDR AppName,MB_OK
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
ThreadProc PROC USES ecx Param:DWORD
invoke WaitForSingleObject,hEventStart,INFINITE
mov ecx,600000000
.WHILE ecx!=0
.if EventStop!=TRUE
add eax,eax
dec ecx
.else
invoke MessageBox,hwnd,ADDR StopString,ADDR AppName,MB_OK
mov EventStop,FALSE
jmp ThreadProc
.endif
.ENDW
invoke PostMessage,hwnd,WM_FINISH,NULL,NULL
invoke EnableMenuItem,hMenu,IDM_START_THREAD,MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_STOP_THREAD,MF_GRAYED
jmp ThreadProc
ret
ThreadProc ENDP
end start
分析:
本例中,我们演示另一种技巧:
.IF uMsg==WM_CREATE
invoke CreateEvent,NULL,FALSE,FALSE,NULL
mov hEventStart,eax
mov eax,OFFSET ThreadProc
invoke CreateThread,NULL,NULL,eax,\
NULL,0,\
ADDR ThreadID
invoke CloseHandle,eax
在WM_CREATE 消息的处理中我们生成事件同步对象并创建线程。我们设置了相关的值让同步对象生成时处于”无信号”状态而且在调用了WaitForSingleObject后可以自动把事件对象的状态设为”无信号”。然后我们创建线程。 线程的代码开始执行后立即被阻塞:
ThreadProc PROC USES ecx Param:DWORD
invoke WaitForSingleObject,hEventStart,INFINITE
mov ecx,600000000
您可以看到线程的执行体的第一条代码就是调用WaitForSingleObject函数,该函数使得线程阻塞并且一直处于等待事件对象变成”有信号”。这也就是说,我们以开始就让该线程进入了睡眠状态。 当用户选择了菜单项”run thread”后,我们把事件对象得状态变成”有信号”:
.if ax==IDM_START_THREAD
invoke SetEvent,hEventStart
函数SetEvent可以让同步对象变成”有信号”状态,那么下一次线程得到时间片运行时,WaitForSingleObject函数就会返回,线程余下的代码就可以得到执行了。当用户选择了菜单项”stop thread” 时,我们把全局变量EventStop设为TRUE。
.if EventStop==FALSE
add eax,eax
dec ecx
.else
invoke MessageBox,hwnd,ADDR StopString,ADDR AppName,MB_OK
mov EventStop,FALSE
jmp ThreadProc
.endif
这样线程得计数工作结束,然后跳转到重新执行WaitForSingleObject函数的地方。注意:我们不用手动清除事件对象的信号,因为在调用CreateEvent函数时把参数bManualReset的值设为了FALSE。

TOP

[原创]汇编语言教学

第十七课 动态链接库 本课中,我们将学习DLLs,它们到底是什么和如何创建它们。 理论: 如果您编程的时间非常长,就会发现很多的程序之间其实有相当多的重复代码。每编一个程序就重写一遍这些代码既没必要又浪费时间。在DOS时代,一般的做法是把这些重复的代码写成一个个的函数,然后把它们按类别放到不同的库文件中去。当要使用这些函数时,只要把您的目标文件(.obj)文件和先前存放在库文件中的函数进行链接,链接时链接器会从库文件中抽取相关的信息并把它们插入到可执行文件中去。这个过程叫做静态链接。C运行时库就是一个好例子。这样的库的缺点是您在每一个调用库函数的程序中都必须嵌入同一函数的拷贝,这显然很浪费磁盘。在DOS时代毕竟每一时刻仅有一个程序在运行,所以浪费的还只是磁盘而已,在多任务的WINDOWS时代就不仅浪费磁盘,还要浪费宝贵的内存了。 在WINDOWS中,由于有多个程序同时运行,如果您的程序非常大的话,那将消耗相当多的内存。WINDOWS的解决办法是:使用动态链接库。动态链接库从表面上看也是一大堆的通用函数,不过即使有多个程序调用了它,在内存中也仅仅只有动态链接库的唯一一份拷贝。WINDOWS是通过分页机制来作到这一点的。当然,库的代码只有一份,但是每一个应用程序要有自己单独的数据段,要么就会乱掉。 不象旧时的静态链接库,它并不会把这些函数的可执行代码放入到应用程序中去,而是当程序已经在内存中运行时,如果需要调用该函数时才调入内存也即链接。这也就是为什么把它叫做“动态”的原因所在。另外您还可以动态地卸载动态链接库,当然要求这时没有其它的应用程序在使用它,否则就要一直等到最后一个使用它的函数也不再使用该动态链接库时才能去卸载它。 为了正确的调用库和给库函数分配内存空间,在编译和链接应用程序时,必须把重定位等一些消息插入到执行代码中去,以便载入正确的库,并给库函数分配正确的地址。 那么这些信息从哪里得到呢?引入库。引入库包含足够的信息,链接器从中抽取足够的信息(注意区别:静态链接库放入的是可执行代码)把它们放入到可执行文件中去。当WINDOWS的加载器装入应用程序查看到有DLL时,它会查找该库文件,如果没有查到,就报错退出,否则就把它映射进进程的地址空间,并修正函数调用语句的地址。 如果没有引入库呢?当然我们也可以调用动态链接库中的任意函数。只不过您必须知道调用的函数是否在库中而且是否在库的引出名字表中,另外还需要知道该函数的参数个数和参数的类型。 (译者加:说到这里,让我想起了一件很有名的事。<>一书的作者Angel Schudleman 曾经利用此方法来跟踪微软Win3x系统动态链接库中未公开的函数,因为在微软给程序员提供的系统动态链接库的引入库中没有提供这些函数的原型,所以您无法在链接时把这些函数的信息链接到可执行文件中去,而为了某种目的您又要使用这些函数,您就可以在执行时加载动态链接库并得到这些函数的地址,从而和调用其它的库函数一样使用这些未公开的函数。由于这本书的巨大影响,当时许多程序员纷纷在它们的程序中调用未公开函数,甚至在写商业程序时也这么做。这种走偏峰的做法引起了微软的反感,后来微软在它Win3x的改进版中不再把那些未公开函数列入系统动态链接库的引出名字表,这样也就无法再利用这种方法来调用未公开的函数了。) 当您让系统的加载器为您加载动态库时,如果不能找到库文件,它就会提示一条“A required .DLL file, xxxxx.dll is missing”,这样您的应用程序就无法运行,即使该库对您的应用程序来说并不重要。 如果您选择在程序运行时自己加载该库,就没有这种问题了。 如果您知道足够的信息,就可以调用系统未公开的函数。 如果您调用LoadLibrary函数加载库,就必须再调用GetProcAddress函数来得到每一个您想调用的函数的地址,GetProcAddress会在动态链接库中查找函数的入口地址。由于多余的步骤,这样您的程序执行起来会慢一点,但是并不明显。 明白了LoadLibrary函数的优缺点,下面我们就来看看如何产生一个动态链接库。下面的代码是一个动态链接库的框架: ;-------------------------------------------------------------------------------------- ; DLLSkeleton.asm ;-------------------------------------------------------------------------------------- .386 .model flat,stdcall option casemap:none include \masm32\include\windows.inc include \masm32\include\user32.inc include \masm32\include\kernel32.inc includelib \masm32\lib\user32.lib includelib \masm32\lib\kernel32.lib .data .code DllEntry proc hInstDLL:HINSTANCE, reason:DWORD, reserved1:DWORD mov eax,TRUE ret DllEntry Endp ;--------------------------------------------------------------------------------------------------- ;下面是一个空函数,您可以象下面一样插入您的函数。 ;---------------------------------------------------------------------------------------------------- TestFunction proc ret TestFunction endp End DllEntry ;------------------------------------------------------------------------------------- ; DLLSkeleton.def ;------------------------------------------------------------------------------------- LIBRARY DLLSkeleton EXPORTS TestFunction 上面是一个动态链接库的框架,每一个DLL必须有一个入口点函数,WINDOWS每一次在做下面的动作时会调用该入口点函数: 当动态链接库被加载时 当动态链接库卸载时 同一进程的线程生成时 同一进程的线程退出时 DllEntry proc hInstDLL:HINSTANCE, reason:DWORD, reserved1:DWORD mov eax,TRUE ret DllEntry Endp 入口点函数的名称无所谓只要您让语句“END<函数名>”中的函数名和前面的相同就可以了。该函数共有三个参数,只有前面两个是重要的。 hInstDLL是该动态链接库模块的句柄。它和进程的实例句柄不一样。如果您以后要用,可以保存它,因为以后再要获得它不容易。 根据不同的时机,reason传入的值可能是下面的四个值中的一个: DLL_PROCESS_ATTACH 动态链接库第一次插入进程的地址空间时。当传入的参数是该值时,您可以做一些初始化的工作。 DLL_PROCESS_DETACH 动态链接库从进程的地址空间卸出时。您可以在此做一些清理的工作。譬如:释放内存等。 DLL_THREAD_ATTACH 新线程生成。 DLL_THREAD_DETACH 线程销毁。 如果想要库中的代码继续执行,返回TRUE,否则返回FALSE,那样动态链接库就不会加载了。譬如:您想分配一块内存,如果不成功的话就退出,这时您就可以返回FALSE。那样动态链接库就不会加载了。 您可以加入的函数,它们的位置并不重要,把它们放在入口点函数的前面或后面都可以。只是如果您想要它们能被其它的程序调用的话,就必须把它们的名字放到模块定义文件(.def)中去。 动态链接库在它们自己的编译过程就需要,而不只是提供给其它要引用它的程序参考。他们如下: LIBRARY DLLSkeleton EXPORTS TestFunction 第一行是必须的。LIBRARY 定义了DLL的模块名称。它必须和动态链接库的名称相同。 EXPORTS关键字告诉链接器该DLL的引出函数,也就是其它程序可以调用的函数。举个例子:其它的程序想要调用函数TestFunction ,我们就把它放到EXPORTS中。 还有就是,链接器的选项中必须放入开关项:/DLL 和/DEF,就像下面这样: link /DLL /SUBSYSTEM:WINDOWS /DEF:DLLSkeleton.def /LIBPATH:c:\masm32\lib DLLSkeleton.obj 编译器的开关选项是一样的,即:/c /coff /Cp。在您链接好后,链接器会生成.lib 和.dll文件。前者是引入库,当其它的程序要调用您的动态链接库中的函数时就需要该引入库,以便把必要的信息加入到其可执行文件中去。 接下来我们来看看如何使用LoadLibrary函数来加载一个DLL。 ;--------------------------------------------------------------------------------------------- ; UseDLL.asm ;---------------------------------------------------------------------------------------------- .386 .model flat,stdcall option casemap:none include \masm32\include\windows.inc include \masm32\include\user32.inc include \masm32\include\kernel32.inc includelib \masm32\lib\kernel32.lib includelib \masm32\lib\user32.lib .data LibName db "DLLSkeleton.dll",0 FunctionName db "TestHello",0 DllNotFound db "Cannot load library",0 AppName db "Load Library",0 FunctionNotFound db "TestHello function not found",0 .data? hLib dd ? ; 动态链接库的句柄 (DLL) TestHelloAddr dd ? ; TestHello 函数的地址 .code start: invoke LoadLibrary,addr LibName ;--------------------------------------------------------------------------------------------------------- ; 调用LoadLibrary,其参数是欲加载的动态链接库的名称。如果调用成功,将返回该DLL的句柄。 否则返回NULL。该句柄可以传给 :library函数和其它需要动态链接库句柄的函数。 ;----------------------------------------------------------------------------------------------------------- .if eax==NULL invoke MessageBox,NULL,addr DllNotFound,addr AppName,MB_OK .else mov hLib,eax invoke GetProcAddress,hLib,addr FunctionName ;----------------------------------------------------------------------------------------------------------- ; 当您得到了动态链接库的句柄后,把它传给GetProcAddress函数,再把您要调用的函数的名称 也传给该函数。如果成功的话,它:会返回想要的函数的地址,失败的话返回NULL。除非卸载该 动态链接库否则函数的地址是不会改变的,所以您可以把它保存到一个:全局变量中以备后用。 ;----------------------------------------------------------------------------------------------------------- .if eax==NULL invoke MessageBox,NULL,addr FunctionNotFound,addr AppName,MB_OK .else mov TestHelloAddr,eax call [TestHelloAddr] ;----------------------------------------------------------------------------------------------------------- ; 以后您就可以和调用其它函数一样调用该函数了。其中要把包含函数地址信息的变量用方括号括起来。 ;----------------------------------------------------------------------------------------------------------- .endif invoke FreeLibrary,hLib ;----------------------------------------------------------------------------------------------------------- ;调用FreeLibrary卸载动态链接库。 ;----------------------------------------------------------------------------------------------------------- .endif invoke ExitProcess,NULL end start 使用LoadLibrary函数加载动态链接库,可能要自己多做一些工作,但是这种方法确实是提供了许多的灵活性。

TOP

[原创]汇编语言教学

8086/8088指令系统 一、数据传送指令 1.通用数据传送指令 MOV(Move)传送 PUSH(Push onto the stack)进栈 POP(Pop from the stack)出栈 XCHG(Exchange)交换 .MOV指令 格式为: MOV DST,SRC 执行的操作:(DST)<-(SRC) .PUSH进栈指令 格式为:PUSH SRC 执行的操作:(SP)<-(SP)-2 ((SP)+1,(SP))<-(SRC) .POP出栈指令 格式为:POP DST 执行的操作:(DST)<-((SP+1),(SP)) (SP)<-(SP)+2 .XCHG 交换指令 格式为:XCHG OPR1,OPR2 执行的操作:(OPR1)<-->(OPR2) 2.累加器专用传送指令 IN(Input) 输入 OUT(Output) 输出 XLAT(Translate) 换码 这组指令只限于使用累加器AX或AL传送信息. .IN 输入指令 长格式为: IN AL,PORT(字节) IN AX,PORT(字) 执行的操作: (AL)<-(PORT)(字节) (AX)<-(PORT+1,PORT)(字) 短格式为: IN AL,DX(字节) IN AX,DX(字) 执行的操作: AL<-((DX))(字节) AX<-((DX)+1,DX)(字) .OUT 输出指令 长格式为: OUT PORT,AL(字节) OUT PORT,AX(字) 执行的操作: (PORT)<-(AL)(字节) (PORT+1,PORT)<-(AX)(字) 短格式为: OUT DX,AL(字节) OUT DX,AX(字) 执行的操作: ((DX))<-(AL)(字节) ((DX)+1,(DX))<-AX(字) 在IBM-PC机里,外部设备最多可有65536个I/O端口,端口(即外设的端口地址)为0000~FFFFH.其中前256个端口(0~FFH)可以直接在指令中指定,这就是长格式中的PORT,此时机器指令用二个字节表示,第二个字节就是端口号.所以用长格式时可以在指定中直接指定端口号,但只限于前256个端口.当端口号>=256时,只能使用短格式,此时,必须先把端口号放到DX寄存器中(端口号可以从0000到0FFFFH),然后再用IN或OUT指令来 传送信息. .XLAT 换码指令 格式为: XLAT OPR 或: XLAT 执行的操作:(AL)<-((BX)+(AL)) 3.有效地址送寄存器指令 LEA(Load effective address)有效地址送寄存器 LDS(Load DS with Pointer)指针送寄存器和DS LES(Load ES with Pointer)指针送寄存器和ES .LEA 有效地址送寄存器 格式为: LEA REG,SRC 执行的操作:(REG)<-SRC 指令把源操作数的有效地址送到指定的寄存器中. .LDS 指针送寄存器和DS指令 格式为: LDS REG,SRC 执行的操作:(REG)<-(SRC) (DS)<-(SRC+2) 把源操作数指定的4个相继字节送到由指令指定的寄存器及DS寄存器中.该指令常指定SI寄存器. .LES 指针送寄存器和ES指令 格式为: LES REG,SRC 执行的操作: (REG)<-(SRC) (ES)<-(SRC+2) 把源操作数指定的4个相继字节送到由指令指定的寄存器及ES寄存器中.该指令常指定DI寄存器. 4.标志寄存器传送指令 LAHF(Load AH with flags)标志送AH SAHF(store AH into flags)AH送标志寄存器 PUSHF(push the flags) 标志进栈 POPF(pop the flags) 标志出栈 .LAHF 标志送AH 格式为: LAHF 执行的操作:(AH)<-(PWS的低字节) .SAHF AH送标志寄存器 格式为: SAHF 执行的操作:(PWS的低字节)<-(AH) .PUSHF 标志进栈 格式为: PUSHF 执行的操作:(SP)<-(SP)-2 ((SP)+1,(SP))<-(PSW) .POPF 标志出栈 格式为: POPF 执行的操作:(PWS)<-((SP)+1,(SP)) (SP)<-(SP+2) 二、算术指令 1.加法指令 ADD(add)加法 ADC(add with carry)带进位加法 INC(increment)加1 .ADD 加法指令 格式: ADD DST,SRC 执行的操作:(DST)<-(SRC)+(DST) .ADC 带进位加法指令 格式: ADC DST,SRC 执行的操作:(DST)<-(SRC)+(DST)+CF .ADD 加1指令 格式: INC OPR 执行的操作:(OPR)<-(OPR)+1 2.减法指令 SUB(subtract)减法 SBB(subtract with borrow)带借位减法 DEC(Decrement)减1 NEG(Negate)求补 CMP(Compare)比较 .SUB 减法指令 格式: SUB DST,SRC 执行的操作:(DST)<-(DST)-(SRC) .SBB 带借位减法指令 格式: SBB DST,SRC 执行的操作:(DST)<-(DST)-(SRC)-CF .DEC 减1指令 格式: DEC OPR 执行的操作:(OPR)<-(OPR)-1 .NEG 求补指令 格式: NEG OPR 执行的操作:(OPR)<- -(OPR) .CMP 比较指令 格式: CMP OPR1,OPR2 执行的操作:(OPR1)-(OPR2) 该指令与SUB指令一样执行减法操作,但不保存结果,只是根据结果设置条件标志西半球. 3.乘法指令 MUL(Unsigned Multiple)无符号数乘法 IMUL(Signed Multiple)带符号数乘法 .MUL 无符号数乘法指令 格式: MUL SRC 执行的操作: 字节操作数:(AX)<-(AL)*(SRC) 字操作数:(DX,AX)<-(AX)*(SRC) .IMUL 带符号数乘法指令 格式: IMUL SRC 执行的操作:与MUL相同,但必须是带符号数,而MUL是无符号数. 4.除法指令 DIV(Unsigned divide)无符号数除法 IDIV(Signed divide)带符号数除法 CBW(Convert byte to word)字节转换为字 CWD(Contert word to double word)字转换为双字 .DIV 无符号数除法指令 格式: DIV SRC 执行的操作: 字节操作:(AL)<-(AX)/(SRC)的商 (AH)<-(AX)/(SRC)的余数 字操作: (AX)<-(DX,AX)/(SRC)的商 (AX)<-(DX,AX)/(SRC)的余数 .IDIV 带符号数除法指令 格式: DIV SRC 执行的操作:与DIV相同,但操作数必须是带符号数,商和余数也均为带符号数,且余数的符号与被除数的符号相同. .CBW 字节转换为字指令 格式: CBW 执行的操作:AL的内容符号扩展到AH.即如果(AL)的最高有效位为0,则(AH)=00;如(AL)的最高有效位为1,则(AH)=0FFH .CWD 字转换为双字指令 格式: CWD 执行的操作:AX的内容符号扩展到DX.即如(AX)的最高有效位为0,则(DX)=0;否则(DX)=0FFFFH. 这两条指令都不影响条件码. 三、逻辑指令 1.逻辑运算指令 AND(and) 逻辑与 OR(or) 逻辑或 NOT(not) 逻辑非 XOR(exclusive or)异或 TEST(test) 测试 .AND 逻辑与指令 格式: AND DST,SRC 执行的操作:(DST)<-(DST)^(SRC) .OR 逻辑或指令 格式: OR DST,SRC 执行的操作:(DST)<-(DST)V(SRC) .NOT 逻辑非指令 格式: NOT OPR 执行的操作:(OPR)<-(OPR) .XOR 异或指令 格式: XOR DST,SRC 执行的操作:(DST)<-(DST)V(SRC) .TEST 测试指令 格式: TEST OPR1,OPR2 执行的操作:(DST)^(SRC) 两个操作数相与的结果不保存,只根据其特征置条件码 2.移位指令 SHL(shift logical left) 逻辑左移 SAL(shift arithmetic left) 算术左移 SHR(shift logical right) 逻辑右移 SAR(shift arithmetic right) 算术右移 ROL(Rotate left) 循环左移 ROR(Rotate right) 循环右移 RCL(Rotate left through carry) 带进位循环左移 RCR(Rotate right through carry) 带进位循环右移 格式: SHL OPR,CNT(其余的类似) 其中OPR可以是除立即数以外的任何寻址方式.移位次数由CNT决定,CNT可以是1或CL. 循环移位指令可以改变操作数中所有位的位置;移位指令则常常用来做乘以2除以2操作.其中算术移位指令适用于带符号数运算,SAL用来乘2,SAR用来除以2;而逻辑移位指令则用来无符号数运算,SHL用来乘2,SHR用来除以2. 四、串处理指令 1.与REP相配合工作的MOVS,STOS和LODS指令 .REP重复串操作直到(CX)=0为上 格式: REP string primitive 其中String Primitive可为MOVS,LODS或STOS指令 执行的操作: 1)如(CX)=0则退出REP,否则往下执行. 2)(CX)<-(CX)-1 3)执行其中的串操作 4)重复1)~3) .MOVS 串传送指令 格式:可有三种 MOVS DST,SRC MOVSB(字节) MOVSW(字) 其中第二、三种格式明确地注明是传送字节或字,第一种格式则应在操作数中表明是字还是字节操作,例如: MOVS ES:BYTE PTR[DI],DS:[SI] 执行的操作: 1)((DI))<-((SI)) 2)字节操作: (SI)<-(SI)+(或-)1,(DI)<-(DI)+(或-)1 当方向标志DF=0时用+,当方向标志DF=1时用- 3)字操作: (SI)<-(SI)+(或-)2,(DI)<-(DI)+(或-)2 当方向标志DF=0时用+,当方向标志DF=1时用- 该指令不影响条件码. .CLD(Clear direction flag)该指令使DF=0,在执行串操作指令时可使地址自动增量; .STD(Set direction flag)该指令使DF=1,在执行串操作指令时可使地址自动减量. .STOS 存入串指令 格式: STOS DST STOSB(字节) STOSW(字) 执行的操作: 字节操作:((DI))<-(AL),(DI)<-(DI)+-1 字操作: ((DI))<-(AX),(DI)<-(DI)+-2 该指令把AL或AX的内容存入由(DI)指定的附加段的某单元中,并根据DF的值及数据类型修改DI的内容,当它与REP联用时,可把AL或AX的内容存入一个长度为(CX)的缓冲区中. .LODS 从串取指令 格式: LODS SRC LODSB LODSW 执行的操作: 字节操作:(AL)<-((SI)),(SI)<-(SI)+-1 字操作: (AX)<-((SI)),(SI)<-(SI)+-2 该指令把由(SI)指定的数据段中某单元的内容送到AL或AX中,并根据方向标志及数据类型修改SI的内容.指令允许使用段跨越前缀来指定非数据段的存储区.该指令也不影响条件码. 一般说来,该指令不和REP联用.有时缓冲区中的一串字符需要逐次取出来测试时,可使用本指令. 2.与REPE/REPZ和REPNZ/REPNE联合工作的CMPS和SCAS指令 .REPE/REPZ 当相等/为零时重复串操作 格式: REPE(或REPZ) String Primitive 其中String Primitive可为CMPS或SCAS指令. 执行的操作: 1)如(CX)=0或ZF=0(即某次比较的结果两个操作数不等)时退出,否则往下执行 2)(CX)<-(CX)-1 3)执行其后的串指令 4)重复1)~3) .REPNE/REPNZ 当不相等/不为零时重复串操作 格式: REPNE(或REPNZ) String Primitive 其中String Primitive可为CMPS或SCAS指令 执行的操作: 除退出条件(CX=0)或ZF=1外,其他操作与REPE完全相同. .CMPS 串比较指令 格式: CMP SRC,DST CMPSB CMPSW 执行的操作: 1)((SI))-((DI)) 2)字节操作:(SI)<-(SI)+-1,(DI)<-(DI)+-1 字操作: (SI)<-(SI)+-2,(DI)<-(DI)+-2 指令把由(SI)指向的数据段中的一个字(或字节)与由(DI)指向的附加段中的一个字(或字节)相减,但不保存结果,只根据结果设置条件码,指令的其它特性和MOVS指令的规定相同. .SCAS 串扫描指令 格式: SCAS DST SCASB SCASW 执行的操作: 字节操作:(AL)-((DI)),(DI)<-(DI)+-1 字操作: (AL)-((DI)),(DI)<-(DI)+-2 该指令把AL(或AX)的内容与由(DI)指定的在附加段中的一个字节(或字)进行比较,并不保存结果,只根据结果置条件码.指令的其他特性和MOVS的规定相同. 五、控制转移指令 1.无条件转移指令 .JMP(jmp) 跳转指令 1)段内直接短转移 格式:JMP SHORT OPR 执行的操作:(IP)<-(IP)+8位位移量 2)段内直接近转移 格式:JMP NEAR PTR OPR 执行的操作:(IP)<-(IP)+16位位移量 3)段内间接转移 格式:JMP WORD PTR OPR 执行的操作:(IP)<-(EA) 4)段间直接(远)转移 格式:JMP FAR PTR OPR 执行的操作:(IP)<-OPR的段内偏移地址 (CS)<-OPR所在段的段地址 5)段间间接转移 格式:JMP DWORD PTR OPR 执行的操作:(IP)<-(EA) (CS)<-(EA+2) 2.条件转移指令 1)根据单个条件标志的设置情况转移 .JZ(或JE)(Jump if zero,or equal) 结果为零(或相等)则转移 格式:JE(或JZ) OPR 测试条件:ZF=1 .JNZ(或JNE)(Jump if not zero,or not equal) 结果不为零(或不相等)则转移 格式:JNZ(或JNE) OPR 测试条件:ZF=0 .JS(Jump if sign) 结果为负则转移 格式: JS OPR 测试条件:SF=1 .JNS(Jump if not sign) 结果为正则转移 格式:JNS OPR 测试条件:SF=0 .JO(Jump if overflow) 溢出则转移 格式: JO OPR 测试条件:OF=1 .JNO(Jump if not overflow) 不溢出则转移 格式: JNO OPR 测试条件:OF=0 .JP(或JPE)(Jump if parity,or parity even) 奇偶位为1则转移 格式: JP OPR 测试条件:PF=1 .JNP(或JPO)(Jump if not parity,or parity odd) 奇偶位为0则转移 格式: JNP(或JPO) OPR 测试条件:PF=0 .JB(或JNAE,JC)(Jump if below,or not above or equal,or carry) 低于,或者不高于或等于,或进位位为1则转移 格式:JB(或JNAE,JC) OPR 测试条件:CF=1 .JNB(或JAE,JNC)(Jump if not below,or above or equal,or not carry) 不低于,或者高于或者等于,或进位位为0则转移 格式:JNB(或JAE,JNC) OPR 测试条件:CF=0 2)比较两个无符号数,并根据比较的结果转移 .JB(或JNAE,JC) 格式:同上 .JNB(或JAE,JNC) 格式:同上 .JBE(或JNA)(Jump if below or equal,or not above) 低于或等于,或不高于则转移 格式:JBE(或JNA) OPR 测试条件:CFVZF=1 .JNBE(或JA)(Jump if not below or equal,or above) 不低于或等于,或者高于则转移 格式:JNBE(或JA) OPR 测试条件:CFVZF=0 3)比较两个带符号数,并根据比较的结果转移 .JL(或LNGE)(Jump if less,or not greater or equal) 小于,或者不大于或者等于则转移 格式:JL(或JNGE) OPR 测试条件:SFVOF=1 .JNL(或JGE)(Jump if not less,or greater or equal)不小于,或者大于或者等于则转移 格式:JNL(或JGE) OPR 测试条件:SFVOF=0 .JLE(或JNG)(Jump if less or equal,or not greater) 小于或等于,或者不大于则转移 格式:JLE(或JNG) OPR 测试条件:(SFVOF)VZF=1 .JNLE(或JG)(Jump if not less or equal,or greater) 不小于或等于,或者大于则转移 格式:JNLE(或JG) OPR 测试条件:(SFVOF)VZF=0 4)测试CX的值为0则转移指令 .JCXZ(Jump if CX register is zero) CX寄存器的内容为零则转移 格式:JCXZ OPR 测试条件:(CX)=0 注:条件转移全为8位短跳! 3.循环指令 .LOOP 循环指令 格式: LOOP OPR 测试条件:(CX)<>0 .LOOPZ/LOOPE 当为零或相等时循环指令 格式: LOOPZ(或LOOPE) OPR 测试条件:(CX)<>0且ZF=1 .LOOPNZ/LOOPNE 当不为零或不相等时循环指令 格式: LOOPNZ(或LOOPNE) OPR 测试条件:(CX)<>0且ZF=0 这三条指令的步骤是: 1)(CX)<-(CX)-1 2)检查是否满足测试条件,如满足则(IP)<-(IP)+D8的符号扩充. 4.子程序 .CALL调用指令 .RET返回指令 5.中断 .INT指令 格式: INT TYPE 或 INT 执行的操作:(SP)<-(SP)-2 ((SP)+1,(SP))<-(PSW) (SP)<-(SP)-2 ((SP)+1,(SP))<-(CS) (SP)<-(SP)-2 ((SP)+1,(SP))<-(IP) (IP)<-(TYPE*4) (CS)<-(TYPE*4+2) .INTO 若溢出则中断 执行的操作:若OF=1则: (SP)<-(SP)-2 ((SP)+1,(SP))<-(PSW) (SP)<-(SP)-2 ((SP)+1,(SP))<-(CS) (SP)<-(SP)-2 ((SP)+1,(SP))<-(IP) (IP)<-(10H) (CS)<-(12H) .IRET 从中断返回指令 格式: IRET 执行的操作:(IP)<-((SP)+1,(SP)) (SP)<-(SP)+2 (CS)<-((SP)+1,(SP)) (SP)<-(SP)+2 (PSW)<-((SP)+1,(SP)) (SP)<-(SP)+2 六、处理机控制指令 1.标志处理指令 .CLC进位位置0指令(Clear carry)CF<-0 .CMC进位位求反指令(Complement carry)CF<-CF .STC进位位置1指令(Set carry)CF<-1 .CLD方向标志置0指令(Clear direction)DF<-0 .STD方向标志置1指令(Set direction)DF<-1 .CLI中断标志置0指令(Clear interrupt)IF<-0 .STI中断标志置1指令(Set interrupt)IF<-0 2.其他处理机控制指令 NOP(No Opreation) 无操作 HLT(Halt) 停机 WAIT(Wait) 等待 ESC(Escape) 换码 LOCK(Lock) 封锁 这些指令可以控制处理机状态.这们都不影响条件码. .NOP 无操作指令 该指令不执行任何操作,其机器码占有一个字节,在调试程序时往往用这条指令占有一定的存储单元,以便在正式运行时用其他指令取代. .HLT停机指令 该指令可使机器暂停工作,使处理机处于停机状态以便等待一次外部中断到来,中断结束后可继续执行下面的程序. .WAIT等待指令 该指令使处理机处于空转状态,它也可以用来等待外部中断的发生,但中断结束后仍返回WAIT指令继续德行. .ESC换码指令 格式ESC mem 其中mem指出一个存储单元,ESC指令把该存储单元的内容送到数据总线去.当然ESC指令不允许使用立即数和寄存器寻址方式.这条指令在使用协处理机(Coprocessor)执行某些操作时,可从存储器指得指令或操作数.协处理机(如8087)则是为了提高速度而可以选配的硬件. .LOCK封锁指令 该指令是一种前缀,它可与其他指令联合,用来维持总线的锁存信号直到与其联合的指令执行完为止.当CPU与其他处理机协同工作时,该指令可避免破坏有用信息.

TOP

[原创]汇编语言教学

VxD 程序设计入门 我们在上一节学会了如何编写一个什么事也不做的VxD程序。在这一节里,我们要给它增加处理控制消息的功能。 VxD的初始化和结束 VxD程序分为两种:静态的和动态的。每种的加载方法都不同,接受到的初始化和结束的控制消息也不同。 静态VxD: 下列情况下,VMM加载一个静态VxD: 一个实模式常驻程序通过调用中断2FH,1605H,来调用此VxD。 此VxD在注册表中的如下位置有定义: HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\VxD\key\StaticVxD=VxD带路径文件名 此VxD在system.ini中的[386enh]行下有定义:[386enh] section: device=VxD带路径文件名 在开发的时候,我建议你从system.ini载入VxD程序,因为这样如果你的VxD程序有错而导致Windows不能启动的话,你可以在Dos下修改system.ini,而如果你使用的注册表载入的办法,就无法修改了。 当VMM加载你的静态VxD程序时,你的VxD程序会按以下顺序接收到三个系统控制消息: Sys_Critical_Init VMM在转入到保护模式后,开放中断前发出这个控制消息。大多数VxD程序到不要用到这个消息,除非: 你的VxD程序要接管一些其他VxD程序或者保护模式程序要用到的中断。既然你处理这个消息的时候这个中断还没有打开,你就可以确定在你接管这个中断的时候此中断不会被调用。 你的VxD程序为其他的VxD程序提供了一些VxD服务。例如,一些在你的VxD程序后加载的VxD程序在处理Device_Init控制消息时需要调用一些你的VxD服务,既然Sys_Critical_Init 控制消息在Device_Init消息之前被发送,所以你应该在Sys_Critical_Init 消息发送时初始化你的程序。 如果你要对这消息进行处理,你应该尽可能快的做完初始化工作,以免太长的执行时间导致的硬中断丢失。(记住:中断还没打开) Device_Init VMM在开放中断后发送此信息。大多数VxD程序都在得到这个消息时初始化。因为中断都开放了,所以耗时的操作也可以在这里执行而不必怕会导致硬中断的丢失。你可以在这时进行初始化(如果你需要的话)。 Init_Complete 在所有的VxD程序处理完Device_Init 消息之后,VMM释放初始化段(ICODE和RCODE段类)之前,VMM发出这个控制消息。只有少数几个VxD要处理这个消息。 你的VxD程序在成功地初始化后,必须将返回标志清零,反之,必须在返回之前把返回标志设为出错信息。如果你的VxD不需要初始化,你就不必对这些消息进行处理。 当要结束静态VxD的时候,VMM发送如下的控制消息: System_Exit2 当你的VxD程序收到这个消息,Windows95正在关闭系统,除了系统虚拟机所有其他虚拟机都已经退出了。尽管如此,CPU仍然处于保护模式下,在系统虚拟机上执行实模式编码也是安全的。在这时Kernel32.dll也已经被卸载了。 Sys_Critical_Exit2 当所有的VxD完成对System_Exit2的响应处理并且中断都被关闭后,你的VxD收到到这个消息。 许多VxD程序并不要响应这两个消息,除非你要为系统做转换到实模式的准备。要知道,当Window95关闭时,它进入到实模式。所以如果你的VxD程序对实模式影像做了一些会导致它不稳定的操作,它就需要在这时进行恢复。 你也许会感到奇怪:为什么这两个消息后面都跟着个“2" ”。这是因为:在VMM加载VxD程序的时候,它是按照初始化顺序值小的VxD先加载的顺序加载的,这样VxD程序就可以使用那些在它们之前加载的VxD程序提供的服务。例如,VxD2要用到VxD1中的服务,它就必须把它的初始化顺序值定义的比VxD小。加载的顺序是: ..... VxD1 ===> VxD2 ===> VxD3 ..... 那么卸载的时候,理所当然的是初始化顺序值大的VxD程序先被卸载,这样他们仍然可以使用比它们后加载的那些VxD程序提供的服务。如上面的例子,次序是: .... VxD3 ===> VxD2 ===> VxD1..... 在上边的例子中,如果VxD2在初始化时调用了VxD1中的某些服务,那么卸载时它可能也要再次用到一些VxD1中的服务。System_Exit2和Sys_Critical_Exit2是反初始化顺序发送的。这表示,当VxD2接受到这些消息时,VxD1还没有被卸载,它仍可以调用VxD1的服务,而System_Exit和Sys_Critical_Exit消息不是按照反初始化顺序发送的。这意味着,你不能肯定你是否仍能调用在你之前加载的VxD提供的VxD服务。新一代的VxD程序不应该使用这些消息。 还有两种退出消息: Device_Reboot_Notify2 告诉VxD程序VMM正在准备重新启动系统。这时候中断还是开放的。 Crit_Reboot_Notify2 告诉VxD程序VMM正在准备重新启动系统。这时候中断已经被关闭了。 到这里,你可以猜到还有Device_Reboot_Notify和Crit_Reboot_Notify 消息,但它们并不是像“2”版本的消息一样按反初始化顺序发送的。 动态VxD: 动态VxD在Windows9x里可以动态的被加载和卸载。这个特点在Window3.x下是没有的。动态VxD程序的主要作用是用来支持某些动态的硬件设备的重装,比如:即插即用设备。尽管如此,你可以从你的Win32程序中加载/卸载它,也可以把它看作是你的程序的一个到ring-0的扩展。 上一节我们提到的例子是一个静态的VxD,你可以把它转换成一个动态的VxD,只要在.def文件中VxD标记的后面加上关键字DYNAMIC。 VxD FIRSTVxD DYNAMIC 这就是你把一个静态VxD转换成一个动态的VxD所要做的一切。 一个动态的VxD可以按以下的方法被加载: 把它放到你的Windows目录下的\SYSTEM\IOSUBSYS目录中。在这个目录里的VxD会被输入输出监视器(ios)加载。这些VxD必须支持层设备驱动。所以用这种方法加载你的动态VxD并不是一个好办法。 用VxD加载服务。 VxDLDR是一个可以加载动态VxD的静态VxD。你可以在其他VxD里面或者在16位代码里面调用它的服务。 用Win32应用程序里的 CreateFile API。你在调用CreateFile时,你的动态VxD要以下面的格式填写: \\.\VxD完整路径名 例如,如果你要加载一个在当前目录下名为FirstVxD的动态VxD,你需要做如下的工作: .data VxDName db "\\.\FirstVxD.VxD",0 ...... .data? hDevice dd ? ..... .code ..... invoke CreateFile, addr VxDName,0,0,0,0, FILE_FLAG_DELETE_ON_CLOSE,0 mov hDevice,eax ...... invoke CloseHandle,hDevice ...... FILE_FLAG_DELETE_ON_CLOSE 这个标志用来说明该VxD在CreateFile返回的句柄关闭时被卸载。 如果你用CreateFile来加载一个动态VxD,那么这个动态VxD必须处理w32_DeviceIoControl 消息。当你的动态VxD第一次被CreateFile函数加载的时候,VWIN32 向你的VxD发出这个消息。你的VxD响应这个消息,返回时eax中的值必须为零。当应用程序调用DeviceIoControl API来与一个动态VxD通讯时,w32_DeviceIoControl消息也被发送。我们会在下一章讲到DeviceIoControl接口。 一个动态VxD在初始化时收到一个消息: Sys_Dynamic_Device_Init 在结束时也收到一个控制消息: Sys_Dynamic_Device_Exit 动态VxD不会收到Sys_Critical_Init, Device_Init和Init_Complete控制消息,因为这些消息是在系统虚拟机初始化时发送的。除了这三个消息,动态VxD能收到所有的控制消息,只要它还在内存里。它可以做静态VxD可以做的所有事情。简单的说,动态VxD除了加载机制和接收到的初始化/结束消息跟静态VxD不同以外,它能做静态VxD所能做的一切。 其它系统控制消息 当VxD在内存里的时候,除了接收和初始化及结束相关的消息外,它还要收到许多别的控制消息。有些消息是关于虚拟机管理器的,有的是关于各种事件的。例如,关于虚拟机的消息如下: Create_VM VM_Critical_Init VM_Suspend VM_Resume Close_VM_Notify Destroy_VM 选择地响应你所感兴趣的消息是你自己的责任。 在VxD内创建函数 你要在一个段里面定义你的函数。你应该首先定义一个段,然后把你的函数放进去。例如,如果你要把你的函数放到一个可调页段中。你应该先定义一个可调页段,像这样: VxD_PAGEABLE_CODE_SEG (你的函数写在这里) VxD_PAGEABLE_CODE_ENDS 你可以在一个段里面插入多个的函数。作为一个VxD编写者,你必须决定每一个函数应该放到哪个段里面去。如果你的函数必须时刻存在于内存中,如某些硬件中断处理程序,就把它们放到锁定页面段里面,否则,你应该把它们放到可调页段。 你要用BeginProc和EndProc 宏来定义你的函数: BeginProc 函数名 EndProc 函数名 使用BeginProc 宏还可以加上一些参数,想了解这些细节,你可以看看Win95 DDK的文档。大多数时候,你只用填写函数的名字就够了。 因为BeginProc-EndProc 宏比proc-endp 指令的功能要强,所以你应该用BeginProc-EndProc宏来代替proc-endp指令 VxD编程约定 寄存器的使用 你的VxD程序可以使用所有的寄存器,FS和GS。但是在改动段寄存器的时候一定要小心。尤其是,一定不要改动CS和SS的内容,除非你对将发生的事情有绝对的把握。你可以使用DS和ES,但一定要记住在返回时恢复它们初值。有两个特征位尤其重要:方向和中断特征位。不要长时间的屏蔽中断。还有如果你要改动方向特征位,不要忘了在返回之前恢复它的初值。 参数传递约定 VxD服务函数有两种调用约定:寄存器法和堆栈法。调用寄存器法服务函数时,你通过各种寄存器来传递服务函数的参数。并且,在调用完成后检查寄存器的值来看操作是否成功。不要总是以为在调用服务函数后主要寄存器的值还和以前一样。当调用堆栈法服务函数时,你把要传递的参数压栈,在eax得到返回值。堆栈调用法的服务函数保存ebx,esi,edi和ebp的值。许多寄存器调用法服务函数都源于Windows3.x的时代。在大多数时候,你可以通过名字来区分这两种服务函数,如果一个函数的名字一下划线开头,如_HeapAllocate,它就是一个堆栈法的服务函数(除了少数从VWIN32.VxD导出的函数)。如果函数名不是一下划线开头,它就是一个寄存器法的服务函数。 调用VxD服务函数 你可以通过VMMCall和VxDCall 宏来调用VMM和VxD服务。这两个宏的语法是一样的。当你要调用VMM导出的VxD服务函数时,用VMMCall。当你要用其它VxD程序导出的VxD服务函数时,用VxDCall。 VMMCall service ; 调用寄存器法服务函数e VMMCall _service, ; 调用堆栈法服务函数 正如我在前面所讲的,VMMCall和VxDCall分解出一个跟着一个双字的20h中断,这样用起来很方便。当你调用堆栈法服务时,你必须用角括号把你的参数列括起来。 VMMCall _HeapAllocate, <, HeapLockedIfDP> _HeapAllocate是一个堆栈法服务函数。它有两个参数,我们必须用角括号把它们括起来。由于第一个参数是一个这个宏不能正确解释的表达式,所以我们又要用一个角括号把它括起来。 Flat地址 在老的编译工具里,当你使用offset 操作符时,编译器和联接器会生成错误地址,所以VxD编写者用offset flat:来代替offset。imm.inc包括了一个使这更简单的宏:OFFSET32 来代替offset flat:。所以如果你要用地址操作时,用OFFSET32 来代替offset操作符。 注意: 当我写这篇教程的时候,我试了一下用offset 操作符。它可以生成正确的地址。所以我想MASM6.14修正了这个bug。但是为了安全起见,你还是应该用OFFSET32宏来代替offset。

TOP

[原创]汇编语言教学

真的很需要这样的教程,多谢楼主了,决定收藏!!!!!

TOP

[原创]汇编语言教学

呵呵
图书馆一直借不到汇编书
最近正郁闷呢
谢谢了啊~~~

TOP

[原创]汇编语言教学

OK!我正在找类似的资料,太谢谢啦!

TOP

[原创]汇编语言教学

感谢楼主!!收了

TOP

返回列表 回复 发帖