C/C++知识点之C指针原理(23)-win32汇编及.NET调试
小标 2019-02-19 来源 : 阅读 467 评论 0

摘要:本文主要向大家介绍了 C/C++知识点之C指针原理(23)-win32汇编及.NET调试,通过具体的内容向大家展示,希望对大家学习C/C++知识点有所帮助。

本文主要向大家介绍了 C/C++知识点之C指针原理(23)-win32汇编及.NET调试,通过具体的内容向大家展示,希望对大家学习C/C++知识点有所帮助。

C/C++知识点之C指针原理(23)-win32汇编及.NET调试

在WINDOWS系统能用到汇编的机会不多,基本都可以用C或C++代劳,更何况现在MICROSOFT的Visual Studio 系列工具非常强大,WINDOWS下的开发已经在向.NET方面发展,实际调用WINDOWS SDK的机会也不多。 WIN32汇编编写窗口程序需要调用大量的WINDOWS 的API,而且提供了高级语言才有的条件语句和循环语句,难度相对于LINUX下的AT&T汇编要小很多。


汇编可以开发WINDOWS程序?答案是肯定的,用WIN32汇编语言开发出来的WINDOWS程序具有执行效率高、占用空间小等特点。


一、开发工具下载与安装


  在众多WIN32汇编开发工具中,MASM32笔者比较偏爱,它具有很多代码示例和丰富的开发资源,在下列下载地址中选择一个地址下载MASM32。


二、知识预备


  1、寄存器


   为了提高运算速度和数据的存取速度,在计算机的CPU内,有一组硬件装置,第一个装置内存放CPU运算需要的数,这些数值可供CPU直接存取,这组装置叫寄存器。寄存器分为通用寄存器、段寄存器、程序指针寄存器、标志寄存器。


   编程最常用就是通用寄存器,常用的通用寄存器有eax,ebx,ecx,edx,esi,edi等等,现代计算机的CPU一般是以32位为单位进行运算,因此一个寄存器最大能存放32位的二进制数。每个寄存器通常都有它默认的用法,所谓默认只是一种编程的习惯,你可以不遵守这些用法,但是有些情况下必须遵守,因为我们开发的是WINDOWS程序,要遵守WINDOWS操作系统的规定和汇编语法本身的约束,比如调用一个WINDOWSAPI函数,返回值放在EAX,堆栈的栈顶地址在esp,在汇编语言的循环中,ECX内存放循环的次数。具体寄存器的使用会在以后介绍。


  2、堆栈


   堆栈是个非常古老的概念,在DOS时代就有了,也是个非常重要的东西,程序没了它就活不了,堆栈就是在内存里分配一个区域,使用这个区域必须遵守一个规定:后进先出,后进来的先出去,可以把它想像成一个空木箱,首先往里面放棉衣,然后往里面放书,最后放上运动服。如果要取出书,必须得把最后放上的运动服取出,放的顺序是棉衣->书->运动服,取的顺序是运动服->书->棉衣,堆栈也是如此。


   先解释一下什么是地址,内存价格的便宜和内存容量的扩大,WINDOWS虚拟内存早已出现,内存中放着众多的数据,必须要有方法表示内存某个地点,这个地点就用地址来表示。把内存以字节为单位划分,某个地址表示某个字节的地址,如左下图是一个内有4个成员的堆栈,堆栈里的成员以一个字节为单位(数的右边标明了以十六进制表示的地址,如1001)


+--+


|AF|   地址:AF21


+--+


|03|   地址:AF20


+--+


|30|   地址:AF19


+--+


|F1|   地址:AF18


+--+ 


  堆栈是向下增长的,每增加一个成员,栈顶(堆栈顶部的地址)的地址减1,对于这个堆栈,栈顶地址是AF18,如果再往这个堆栈里增加一个成员的话,它的地址是:AF18-1=AF17。如果往这个堆栈里增加一个32位的数,栈顶的地址是多少呢,32位的数占4个成员的位置,栈顶的地址为:AF18-4=AF14。


  在这个堆栈中增加一个32位数DAB0CD90,新的堆栈如下:


+--+


|AF|   地址:AF21


+--+


|03|   地址:AF20


+--+


|30|   地址:AF19


+--+


|F1|   地址:AF18


+--+ 


|DA|   地址:AF17


+--+


|B0|   地址:AF16


+--+


|CD|   地址:AF15


+--+


|90|   地址:AF14


+--+ 


  如果从堆栈里拿走一个32位数,则栈顶的地址为:AF14+4=AF18


三、WIN32汇编语言的语法


   为了方便大家理解和入门,下面尽量使用宏汇编和伪指令地方进行描述,也正因为有了宏汇编和伪指令的帮助,WIN32汇编才具有很多高级语言的特性,很多语法和C差不多。


  1、WIN32汇编程序基本结构


   .386


   .MODEL Flat,STDCALL


   .DATA


     初始化值的全局变量定义


   .DATA?


     未初始化值的全局变量定义


   .CONST


     常量定义


   .CODE


    ..............


    ...............     


   程序入口LABEL


     ............


     ............


    end程序入口LABEL


2、变量定义


   (1)定义全局变量


   全局变量定义在.data和.data?内,


   初始化变量的定义方式如下:


   .data


   变量名  类型  初始值1,初始值2,.......


   变量名  类型  重复次数dup(初始值1,初始值2,....)


    注意:如果用?表示初始值的话,则表示0


   未初始化变量的定义方式是


   .data?


    变量名  类型 ?


   (2)条件测试语句


   (A)基本结构


    .IF条件


       程序代码


    [.ELSEIF]条件比较


      程序代码


      .......


   [.ELSE]


       程序代码


    .ENDIF


   (3)操作符


   (A)比较操作符


    == 相等


   !=  不等于


   >   大于


    >=  大于或等于


   <   小于


    <=  小于或等于


   &   位测试


    !  逻辑非


   &&  逻辑与


   ||  逻辑或


   (B)位操作符


    AND按位与


   OR  按位或


   XOR 异或


    SHL逻辑左移


    SHR逻辑右移


   (C)标志寄存器操作符


    CARRY?是否进位


   OVERFLOW? 是否溢出


   PARITY?  奇偶位是否置位


   SIGN?   符号位标志位是否被置位


   ZERO?   零位标志位是否置位


   (4)循环语句  


(A)while语句 


 while 条件


    ..........


    ..........


   [.break[.if 退出条件]]


   [.contine[.if 退出条件]]]


  .end


(B)repeat语句


.repeat


...........


..........


[.break[.if 退出条件]]


[.contine[.if 退出条件]]]


.until 条件(或.untilcxz [条件])


(5)子程序定义


1、定义


子程序名 proc [距离][语言类型][可视区域][USERS 寄存器列表][,参数:类型]...[VARARG]local 局部变量列表


..............


...............


...............


子程序名 endp


2、如果在未定义前使用,要声明、


函数名 proto [距离][语言][参数1]:数据类型,[参数2]:数据类型,...............


(6)数据结构


(A)声明


wndclass struct


....


.....


......


wndclass ends


(B)定义


mystruct wndclass<1,1,...,1>


mystruct wndclass <>


(C)使用


mov eax,mystruct.lpfnwndproc


mov esi,offset mystruct


assume esi: ptr WNDCLASS


mov eax,[esi].lpfnwndproc


.......


assume esi:nothing


四、在WIN32汇编中的使用WINDOWS API


  WIN32汇编如果没有API的帮助无法实现很多功能,笔者没见过在WIN32汇编程序不调用API的。


  调用API实际上是靠堆栈来完成参数传递的,既然是堆栈,那就要遵守后进先出的原则,这意味API的第一个参数是最后一个放入堆栈的,最后一个参数是第一个放入堆栈的。  


   调用方式如下:


push 参数n


..........  


push 参数2


push 参数1


call API函数名


  为了简化代码,也可以使用以下这种方式调用API


invoke API函数名,参数1,参数2,.....,参数n


我们用WIN32汇编构建第一个WINODWS程序,这个程序完成显示一个带问号的对话框,对话框的内容是现在系统时间。


   首先,打开MASM32Editor(在桌面上可以找到图标),在里面输入以下代码:


     .386
     .model flat, stdcall
     option casemap :none  


;#################################################################


     include windows.inc
     include user32.inc
     include kernel32.inc
     include gdi32.inc
     include masm32.inc
     
     includelib user32.lib
     includelib kernel32.lib
     includelib gdi32.lib
     includelib masm32.lib


;#################################################################
   .data?
      szbuffer db 100 dup(?)
   .data
      szcaptionName db "我的HELLO,WORLD!",0
      szbegin db "现在时间:"
      sztext db 100 dup(?)
;#################################################################  .code


start:
;程序的入口
       call _callgetnow
       invoke MessageBox,NULL,offset szbegin,offsetszcaptionName,MB_ICONQUESTION or MB_OK
       invoke ExitProcess,eax
;#################################################################
_callgetnow proc
    pushad
  invokeGetDateFormat,LOCALE_USER_DEFAULT,DATE_LONGDATE,NULL,NULL,addrsztext,100
    invokeGetTimeFormat,LOCALE_USER_DEFAULT,LOCALE_NOUSEROVERRIDE,NULL,NULL,addrszbuffer,100
   
    invokeszCatStr,addr sztext,addr szbuffer
    popad
    ret
_callgetnow endp
;#################################################################


end start


  将上述代码保存为HELLOWORLD.ASM后,对程序进行编译。


  在编译前参照下图设置好系统的环境变量,path变量加上x:\masm32\bin,lib变量加上x:\masm32\lib,include变量加上x:\masm32\include。


  确环境变量设置好后,进入DOS窗口开始编译。


   首先运行ml,编译成coff文件格式


  然后运行LINK,进行链接,生成EXE文件,


  大功造成,运行一下试试效果吧!


 下面我们接着来做一个有些难度的helloworld,这个程序将系统时间直接显示在桌面上。程序源代码如下:


    .386
     .model flat, stdcall
     option casemap :none  

;#########################################################################

     include windows.inc
     include user32.inc
     include kernel32.inc
     include gdi32.inc
     include masm32.inc
     
     includelib user32.lib
     includelib kernel32.lib
     includelib gdi32.lib
     includelib masm32.lib

;#########################################################################
   .data?
      szbuffer db 100 dup(?)
   .data
      szmssucesscap db "HELLO,WORLD!深入",0
      szmssucesstext db "在桌面的(300,300)处显示了当前时间",0
      szmscap db "错误",0
      szmstext1 db "无法在桌面上显示!",0
      szmstext2 db "无法得到全屏DC!",0
      szbegin db "现在时间:"
      sztext db 100 dup(?)
;#########################################################################
    .code

start:
;程序的入口
       _showtext  proto :DWORD
       call _callgetnow
       invoke _showtext,offset szbegin
       invoke ExitProcess,eax
;#########################################################################
_callgetnow proc
    pushad
  invokeGetDateFormat,LOCALE_USER_DEFAULT,DATE_LONGDATE,NULL,NULL,addrsztext,100
    invokeGetTimeFormat,LOCALE_USER_DEFAULT,LOCALE_NOUSEROVERRIDE,NULL,NULL,addrszbuffer,100
   
    invokeszCatStr,addr sztext,addr szbuffer
    popad
    ret
_callgetnow endp
;#########################################################################
_showtext proc  lpsztext:DWORD
    LOCAL@Desktopdc:HDC
    LOCAL@dwcolor:DWORD
   
    pushad
    mov@dwcolor,00FF0000h
    invokeGetWindowDC,NULL
    cmpeax,0
    jne @f
    invokeMessageBox,NULL,offset szmstext2,offset szmscap,MB_ICONERROR
    @@:
    mov@Desktopdc,eax
    invokelstrlen,lpsztext
    movebx,eax
    invokeSetBkMode,@Desktopdc,TRANSPARENT
    invokeSetTextColor,@Desktopdc,@dwcolor
    invokeTextOut,@Desktopdc,300,300,lpsztext,ebx
    invokeMessageBox,NULL,offset szmssucesstext,offsetszmssucesscap,MB_ICONINFORMATION
    cmpeax,0
    jne @f
    invokeMessageBox,NULL,offset szmstext1,offset szmscap,MB_ICONERROR
    @@:
    popad
    ret
_showtext endp
;#########################################################################

end start


以上程序中有几个重要的GDI相关的API,下面简要介绍一下


(1)


invoke SetBkMode,@Desktopdc,TRANSPARENT


设置背景方式为透明


(2)


invoke SetTextColor,@Desktopdc,@dwcolor


设置字体颜色为蓝色


(3)


invoke TextOut,@Desktopdc,300,300,lpsztext,ebx


在300,300处显示文本


(4)


invoke GetWindowDC,NULL


取得桌面DC


(5)


mov @dwcolor,00FF0000h


设置颜色为蓝色。@dwcolor是一个DWORD型的变量,


  可以在相关头文件中找到这样的定义:


typedef DWORD COLORREF;
因此COLORREF类型的变量就是DWORD型变量。


 COLORREF变量如何表示颜色呢,只有一个双字大小,它的表示格式是(16进制):


0x00bbggrr


bb表示蓝色,gg表示绿色,rr表示红色


00FF0000h:蓝


0000ff00h:绿


000000FFh:红


程序运行效果如下:


因此,在此只简单介绍一下,首先来看一段简单的窗口程序。注意";"表示注释



;加上注释和个人理解  
  
  
.386  
.model flat,stdcall  
option casemap:none  
  
;以下定义INCLUDE文件  
include winows.inc  
include gdi32.inc  
includelib gdi32.lib  
include user32.inc  
includelib user32.lib  
include kernel32.inc  
include kernel32.lib  
;以下定义数据段  
.data ? ;定义变量  
hinstance dd ?  
hwinmain dd ?  
.const ;定义常量,字符串全部要以0结尾,因为在内存中0是字符串的结束符  
szclassname db 'billclass',0  
szcaptionmain db 'bill's firt program',0  
sztext db 'WIN32汇编,BILL!!!!',0  
;以下是代码段  
.code  
;定义窗口过程  
_procwinmain proc uses ebx edi esi,hwnd,umsg,wparam,lparam  
;定义局部变量用关键字local  
local @stps:PAINTSTRUCT  
local @strect:PAINTSTRUCT  
local @hdc  
mov eax,umsg ;取得传入过程的消息变量值  
;-----------下面开始根据消息类型的不同作出不同的处理  
.if eax == WM_PAINT ;如果消息是窗口绘制  
invoke BeginPaint,hwnd,addr@stRect;WIN32汇编调用API程序后,API程序将返回值放在EAX中,  
;客户区准备  
mov @hdc,eax;取得设备句柄  
  
invoke GetclientRect,hwnd,addr @stRect;addr是取变量的地址但只能用在INVOKE语句中且  
;不能同时使用  
;EAX寄存器传参数,因为ADDR会用到EAX。  
;此API的含义是取得描述客户区的结构放在@stRect  
invoke drawText,@hdc,addr sztext,-1,addr @stRect,\  
DT_SINGLELINE or DT_CENTER or DT_VCENTER ;语句换行符是\,显示'WIN32汇编,BIL  
;L!!!!',并设置其为单行DT_SINGLE  
;等等LINE  
invoke EndPaint,hwnd,addr @stPs  
  .elseifmeax==WM_CLOSE  
invoke DestroyWindow,hwinmain ;销毁窗口  
invoke PostQuitMessage,Null ;向消息循环中发出退出消息  
.else  
invoke DefWindowProc,hwnd,uMsg,wPara,lParam;如果不是上述消息,则执行WINDOWS标准的默认消息处  
;理,如键盘等消息  
ret;返回  
.endif  
  
xor eax,eax ;eax清0  
ret  
_ProcWinMain endp  
;以上这个子程序处理窗口消息的,是窗口的回调函数,该项函数不是我们调用,是由WINDOWS调用用来处理  
;窗口消息的,我们调用的是DispatchMessage,DispatchMessage再回过头来调用窗口过程。  
_WinMain Proc ;主程序  
local @stWndClass:WNDCLASSEX  
local @stmsg:MSG  
  
invoke GetModuleHandle,Null ;得到应用程序句柄  
mov hInstance,eax ;将应用程序的句柄放入hInstance变量  
invoke RtlZeroMemory,addr @stWndClass,sizeof WndClassEX ;msdn的解释TheRtlZeroMemory routine  
;fills a block of memory with zeros,即  
;0填充stWndClass结构变量所占的内存,也就是初始化  
;-----下面注册窗口类  
invoke loadcursor,0,IDC_ARROW ;加载箭头形指针句柄  
mov @stWndClass.hCursor,eax ;鼠标指针赋值  
push hInstance  
pop @stWndClass.hInstance ;窗口句柄赋值  
mov @stWndClass.cbsize,sizeof WNDCLASSEX ;结构大小  
mov @stWndClass.style,CS_HREDRAW or CS_VREDRAW ;设置窗口样式  
mov @stWndClass.lpfnwndproc,offset _procwinmain;设置回调函数,也就是窗口消息处理过程  
mov @stwndclass.hbrbackground,COLOR_WINDOW+1  
mov @stwndclass.lpszclassname,offset szclassname ;设置窗口类的名称  
invoke RegisterClassEx,addr @stwndclass ;传上述设置好的结构以注册窗口类  
 ;建立显示窗口  
invoke CreateWindowEx,WS_EX_CLIENTEDGE,\  
offset szclassname,offsetszcaptionmain,\  
WS_OVERLAPPEDWINDOW,100,100,600,400,NULL,\  
NULL,hinstance,NULL ;建立窗口并返回句柄在EAX中  
mov hwinMain,eax ;刚创建的窗口句柄赋值  
invoke showwindow,hwinmain,SW_SHOWNORMAL ;显示窗口  
invoke updatewindow,hwinmain ;刷新窗口客户区,导致客户区窗口paint  
;消息循环,win32汇编得自行建立WINDOWS消息循环,不过这样更自由,可以彻底地控制程序  
.while true  
 invoke GetMessage,addr @stMsg,null,0,0 ;WINDOWS在系统内部有个系统消息队列,  
            ;并为每个应用程序还维护了一个消息队列,将这些属于这些程序窗口范围内的  
            ;系统消息发到该应用程序消息队伍中,这个API的作用就是从自己的应用程序  
            ;消息队伍中接收消息。  
  .break .if eax==0 ;If the function retrieves the WM_QUIT message, the return value is zero.  
            ;invoke Translate(msdn),也就是说,当程序退出里,消息队伍里会有WM_QUIT消息,            ;就退出循环,意味着退出程序。  
  invoke translatemessage,addr @stmsg;由应用程序对消息进行预处理,如把基于键盘扫描码的按键消息黑心                   ;换成ASCII码的键盘消息等  
  invoke dispatchmessage,addr@stmsg ;将预处理好的消息发给WINDOWS,WINDOWS将其分派给该程序的相应窗;口处理过程处理,那么WINDOWS怎么知道窗口处理过程在哪呢,刚才不是已经注册过窗口类了,这就是为什么窗口;类要注册的原因了,那么为什么不能由程序自己处理消息,非得发给WINDOWS呢,其一、一个应有程序的窗口很多,如果自己处理的话,得建立一个窗口列表,上面记录每个窗口的窗口处理过程。其二、WINDOWS对于一些实时性很;强的信息采用直接调用窗口处理过程的方法。  
.endw  
ret  
_winmain endp  
;没有下面的代码程序无法执行,因为START语句指定程序启动的入口点  
start:  
call _winmain  
invoke ExitProcess,NULL;退出  
end start   


 可以看到上面代码和用C编写的WIN SDK程序很相似。我们接着继续看2个例子:


例1:用WIN32汇编构建第一个WINODWS程序,这个程序完成显示一个带问号的对话框,对话框的内容是现在系统时间。


   首先,打开MASM32Editor(在桌面上可以找到图标),在里面输入以下代码:


     .386  
     .model flat, stdcall  
     option casemap :none    
;#################################################################  
     include windows.inc  
     include user32.inc  
     include kernel32.inc  
     include gdi32.inc  
     include masm32.inc  
       
     includelib user32.lib  
     includelib kernel32.lib  
     includelib gdi32.lib  
     includelib masm32.lib  
;#################################################################  
   .data?  
      szbuffer db 100 dup(?)  
   .data  
      szcaptionName db "我的HELLO,WORLD!",0  
      szbegin db "现在时间:"  
      sztext db 100 dup(?)  
;#################################################################  .code  
start:  
;程序的入口  
       call _callgetnow  
       invoke MessageBox,NULL,offset szbegin,offsetszcaptionName,MB_ICONQUESTION or MB_OK  
       invoke ExitProcess,eax  
;#################################################################  
_callgetnow proc  
    pushad  
  invokeGetDateFormat,LOCALE_USER_DEFAULT,DATE_LONGDATE,NULL,NULL,addrsztext,100  
    invokeGetTimeFormat,LOCALE_USER_DEFAULT,LOCALE_NOUSEROVERRIDE,NULL,NULL,addrszbuffer,100  
     
    invokeszCatStr,addr sztext,addr szbuffer  
    popad  
    ret  
_callgetnow endp  
;#################################################################  
end start  


  将上述代码保存为HELLOWORLD.ASM后,对程序进行编译。


  在编译前参照下图设置好系统的环境变量,path变量加上x:\masm32\bin,lib变量加上x:\masm32\lib,include变量加上x:\masm32\include。


  确环境变量设置好后,进入DOS窗口开始编译。


   首先运行ml,编译成coff文件格式


  然后运行LINK,进行链接,生成EXE文件,


  大功造成,运行一下试试效果吧!


例2:系统时间直接显示在桌面上。程序源代码如下:


Asm代码  

.386  
     .model flat, stdcall  
     option casemap :none    
;#########################################################################  
     include windows.inc  
     include user32.inc  
     include kernel32.inc  
     include gdi32.inc  
     include masm32.inc  
       
     includelib user32.lib  
     includelib kernel32.lib  
     includelib gdi32.lib  
     includelib masm32.lib  
;#########################################################################  
   .data?  
      szbuffer db 100 dup(?)  
   .data  
      szmssucesscap db "HELLO,WORLD!深入",0  
      szmssucesstext db "在桌面的(300,300)处显示了当前时间",0  
      szmscap db "错误",0  
      szmstext1 db "无法在桌面上显示!",0  
      szmstext2 db "无法得到全屏DC!",0  
      szbegin db "现在时间:"  
      sztext db 100 dup(?)  
;#########################################################################  
    .code  
start:  
;程序的入口  
       _showtext  proto :DWORD  
       call _callgetnow  
       invoke _showtext,offset szbegin  
       invoke ExitProcess,eax  
;################################################<span class="hljs-comment    

本文由职坐标整理并发布,希望对同学们有所帮助。了解更多详情请关注职坐标编程语言C/C+频道!

本文由 @小标 发布于职坐标。未经许可,禁止转载。
喜欢 | 1 不喜欢 | 0
看完这篇文章有何感觉?已经有1人表态,100%的人喜欢 快给朋友分享吧~
评论(0)
后参与评论

您输入的评论内容中包含违禁敏感词

我知道了

助您圆梦职场 匹配合适岗位
验证码手机号,获得海同独家IT培训资料
选择就业方向:
人工智能物联网
大数据开发/分析
人工智能Python
Java全栈开发
WEB前端+H5

请输入正确的手机号码

请输入正确的验证码

获取验证码

您今天的短信下发次数太多了,明天再试试吧!

提交

我们会在第一时间安排职业规划师联系您!

您也可以联系我们的职业规划师咨询:

小职老师的微信号:z_zhizuobiao
小职老师的微信号:z_zhizuobiao

版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
 沪公网安备 31011502005948号    

©2015 www.zhizuobiao.com All Rights Reserved

208小时内训课程