注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

易拉罐的博客

心静自然凉

 
 
 

日志

 
 

转 基本概念(win32)汇编教程  

2011-11-20 21:47:37|  分类: 计算机 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

理论:

WIN32 程序运行在保护模式下的,保护模式的历史可以追溯到 80286。而今 80286 已成为了历史。所以我们将只把精力集中于 80386 及后续的X86 系列 CPU。Windows 把每一个 Win32 应用程序放到分开的虚拟地址空间中去运行,也就是说每一个应用程序都拥有其相互独立的 4GB 地址空间,当然这倒不是说它们都拥有 4GB 的物理地址空间,而只是说能够在 4GB 的范围内寻址。操作系统将会在应用程序运行时完成 4GB 的虚拟地址和物理内存地址间的转换。这就要求编写应用程序时必须格守 Windows 的规范,否则极易引起内存的保护模式错误。而过去的 Win16 内存模式下,所有的应用程序都运行于同一个 4GB 地址空间,它们可以彼此"看"到别的程序的内容,这极易导致一个应用程序破坏另一个应用程序甚至是操作系统的数据或代码。

和16 位 Windows 下的把代码分成 DATA,CODE 等段的内存模式不同,WIN32 只有一种内存模式,即 FLAT 模式,意思是"平坦"的内存模式,再没有 64K 的段大小限制,所有的 WIN32 的应用程序运行在一个连续、平坦、巨大的 4GB 的空间中。这同时也意味着您无须和段寄存器打交道,您可以用任意的段寄存器寻址任意的地址空间,这对于程序员来说是非常方便的,这也使得用32位汇编语言和用C语言一样方便。 在Win32下编程,有许多重要的规则需要遵守。有一条很重要的是:Windows 在内部频繁使用 ESI,EDI,EBP,EBX 寄存器,而且并不去检测这些寄存器的值是否被更改,这样当您要使用这些寄存器时必须先保存它们的值,待用完后再恢复它们,一个最显著的应用例子就是 Windows 的 CallBack 函数中。

内容:

下面的程序段是一个框架, 若您现在还不知道这些指令的确切意义的话,没关系, 随后我就会给大家详细解释。

.386
.MODEL Flat, STDCALL
.DATA
<Your initialized data>
......
.DATA?
<Your uninitialized data>
......
.CONST
<Your constants>
......
.CODE
<label>
<Your code>
.....
end <label>

框架就这么简单,好,我现在就给您解释:

.386
这是一个汇编语言伪指令,他告诉编译器我们的程序是使用80386指令集编写的。您还可以使用 .486、.586, 但最安全的还是使用.386。对于每一种CPU有两套几乎功能相同伪指令: .386/.386P、 486/.486P、 586/.586P。 带P的指令标明您的程序中可以用特权级指令。特权级指令是保留给操作系统的,如虚拟设备驱动程序。在大多数时间,您的程序都无须运行在RING0层,故用不带后缀P的伪指令已足够了。

.MODEL FLAT,STDCALL
.MODEL 是用来指定内存模式的伪指令,在Win32下,只有一种内存模型,那就是FLAT。 STDCALL 告诉编译器参数的传递约定。参数的传递约定是指参数传达时的顺序(从左到右或从右到左)和由谁恢复堆栈指针(调用者或被调用者)。在Win16下有两种约定:C 和 PASCAL。C 约定规定参数传递顺序是从右到左,即最右边的参数最先压栈,由调用者恢复堆栈指针。

例如:为调用函数 foo ( int first_param, int second_param, int third_param ); 按C约定的汇编代码应该是这样的:

push [third_param]
push [second_param]
push [first_param]
call foo
add esp, 3 * 4 ;调用者自己恢复堆栈指针

PASCAL约定和C约定正好相反,它规定参数是从左向右传递,由被调用者恢复堆栈。Win16采用了PASCAL约定, 因为PASCAL约定产生的代码量要小。当不知道参数的个数时,C约定特别有用。如在函数wsprintf () 中, wsprintf预先并不知道要传递几个参数,所以它不知道如何恢复堆栈。STDCALL是C约定和PASCAL约定的混合体,它规定参数的传递是从右到左,恢复堆栈的工作交由被调用者。Win32只用STDCALL约定,但除了一个特例,即:wsprintf。

.DATA .DATA? .CONST .CODE
上面的四个伪指令是"分段"(SECTION)伪指令。我们上面刚讲过Win32下没有"段"(SEGMENT)的概念,但是您可以把您的程序分成不同的"分段", 一个"分段"的开始即是上一个"分段"的结束。WIN32中只有两种性质的"分段":DATA和CODE。
其中DATA"分段"又分为三种:
.DATA 其中包括已初始化的数据。
.DATA? 其中包括未初始化的数据。比如有时您仅想预先分配一些内存但并不想指定初始值。使用未初始化的数据的优点是它不占据可执行文件的大小,如:若您要在 .DATA? 段中分配10,000字节的空间,您的可执行文件的大小无须增加10,000字节,而仅仅是要告诉编译器在装载可执行文件时分配所需字节。
.CONST 其中包括常量定义。这些常量在程序运行过程中是不能更改的。 应用程序并不需要以上所有的三个"分段", 可以根据需要进行定义。
.CODE 这是代码"分段"。
<译者注:实际上,分段并不是象在 Dos 下一样,为不同的段分别指出不同的段寄存器,因为 Windows 下只有一个 4GB 的段,Windows 程序中的分段表现在当程序装载时,赋予不同的分段不同的属性,比如说当你的程序加载时,对于 Ring3 程序来说,.code 段是不可写的,而 .data 段是可写的,如果你尝试象在 Dos 下一样写自己的代码部分,你会得到一个蓝屏错误>

<label>
end <label>
是用来唯一标识您的代码范围的标签, 两个标签必须相同,应用程序的所有可执行代码必修在两个标签之间。

 

"hello,world!"win32汇编小程序

首先我们看一个“复杂”的Win32汇编程序
程序用来显示一个消息框

--------------------------------------------------
;文件名:3.asm

        .386
        .model flat ,stdcall

NULL    equ 0
MB_OK   equ 0

ExitProcess PROTO :DWORD
MessageBoxA PROTO :DWORD,:DWORD,:DWORD,:DWORD

includelib kernel32.lib
includelib user32.lib

          .data
szText    db "Hello, world!",0
szCaption db "Win32Asm",0

        .code
start:
        push MB_OK
        lea eax,szCaption
        push eax
        lea eax,szText
        push eax
        push NULL
        call messageboxa
        xor eax,eax
        push eax
        call exitprocess
        end start
--------------------------------------------------
编译链接:
    分下面两步进行:
        ml   /c /coff 3.asm  
        link /subsystem:windows /libpath:d:masm7lib 3.obj
        
    第一步编译生成3.obj文件
    /c 表示只编译,不链接
    /coff 表示生成COFF格式的目标文件
    
    第二步链接生成3.exe文件
    /subsystem:windows 表示生成windows文件
    /libpath:d:masm7lib 表示引入库的路径为:d:masm7lib。
    在安装Masm32后,引入库位于Masm32Lib目录下。
    
    也可设置环境变量Lib的值:在dos提示符下键入Set Lib=d:masm7lib,这样“链接”就可简单写成:
    link /subsystem:windows 3.obj,试想一下,在程序调试过程中,修改源程序是常用的事啦,每次编译链接都要带/libpath:...那该有多烦人呢。当然,我们也可在源程序中直接给出引入库的位置,这样,链接时就方便啦,如下:
    
    includelib d:masm7libkernel32.lib
    includelib d:masm7libuser32.lib
--------------------------------------------------
执行:在dos提示符下键入3,回车,出现一个消息框,哈哈,真正的Win32程序!

--------------------------------------------------
深入分析:
    看一下源程序,有这么两行:call messageboxacall exitprocess。大家一看都知道,这是子程序调用,但是我们并没写这样的子程序,事实上,这些是API函数。作为函数,我们在调用时可能需要传送给函数一些参数,程序怎么知道传送的参数有哪些,类型是什么呢?就是通过函数原型定义,如下所示:
    ExitProcess PROTO :DWORD
    MessageBoxA PROTO :DWORD,:DWORD,:DWORD,:DWORD
    可以看出,ExitProcess有一个参数,MessageBoxA有四个参数,这些参数都是DWORD类型。
    
    在Win32中,参数的传递都是通过堆栈来完成的。象MessageBoxA这个函数有四个参数,究竟是左边的先压入堆栈还是右边的先入栈呢?.model flat,stdcall给出了答案。stdcall 指定参数是从右到左压入堆栈的,且调整堆栈是在子程序返回时完成的。在源程序中不需要用“add sp,值”来保持堆栈平衡。对MessageBox,在API手册中是这样定义的:
        int MessageBox(
            HWND hWnd,          // handle of owner window
            LPCTSTR lpText,     // address of text in message box
            LPCTSTR lpCaption,  // address of title of message box
            UINT uType          // style of message box
            )
      ;所以会有我们的程序段:
        push MB_OK
        lea eax,szCaption
        push eax
        lea eax,szText
        push eax
        push NULL
        call messageboxa

    看看上面的程序,不难想到,假如在写程序时,少往堆栈里压入一个数据,那将是一个致命的错误。能不能将这种检查参数个数是否匹配的工作交给计算机来完成呢?这是可以的,INVOKE指令可以帮助我们完成这样的工作。假如你的参数个数不正确,连接器将给出错误提示。所以,极力建议你使用invoke代替call来调用子程序,当然,这不是绝对的。使用invoke上面的指令就可简写成下面的样子,看起来简炼多啦,查错也方便啦!
        invoke messageboxa, NULL,addr szText,addr szCaption,MB_OK

   另外,像NULL,MB_OK都是一些常量,这样的常量有很多,还有很多的结构,如果在我们的程序中一开始都写这么多的东西,可能一下子就把你吓怕啦,也容易出错,更不便于看程序的主要部分。hutch整理的windows.inc包含了WIN32编程所需要的常量和结构体的定义,我们可简单的用一个include指令将这些常量和结构的定义插入到我们的文件中:
        include d:masm32includewindows.inc
  
   但是windows.inc中并不包含函数原型的声明,还要从其他的头文件中得到函数原型的声明,比如:messageboxa的原型声明在user32.inc文件中,exitprocess在kernel32.inc文件中。这些头文件都放在 masm32include文件夹下。
  
   还有,要用windows.inc,必须使用option casemap:none,它的意思是告诉 MASM 要区分符号的大小写,譬如:start和START是不一样的。否则,一个小小的程序,可能会出成百上千的错误呀!
  
   其他的,就不再细说啦,到此,上面的程序可重新修改如下:
-----------------------------------------------------------------
;最终的结果
        .386                    ;表示要用到386指令
        .model flat,stdcall     ;32位程序,要用flat啦!;stadcall,标准调用
        option casemap:none     ;区别大小写

include    windows.inc  ;包括常量及结构定义

include    kernel32.inc ;函数原型声明
include    user32.inc

includelib kernel32.lib ;用到的引入库
includelib user32.lib

          .data;数据区,定义2个字符串
szText    db "Hello, world!",0
szCaption db "Win32Asm",0

        .code ;代码开始执行处
start:
        invoke MessageBox,NULL,addr szText,addr szCaption,MB_OK 

          ;调用MessageBoxAPI函数
        invoke ExitProcess,NULL         ;程序退出
        end start;结束
------------------------------------
编译链接:
        ml   /c /coff /I d:masm7include 3.asm   ;注意开关符识别大小写
        link /subsystem:windows /libpath:d:masm7lib 3.obj
        
        /I d:masm7include 表示*.inc文件的位置,也可设置环境变量Set include=d:masm7include来简化操作,也可在程序中明确指出*.inc的位置。
        
    前面讲的都是用两条指令来完成编译链接,实际上用一条指令也可完成,如下:
    ml /coff /I d:masm7include 3.asm /link /subsystem:windows /libpath:lib
    若*.inc及引入库在源程序中都明确指出其位置,则可简化为:
    ml /coff 3.asm /link /subsystem:windows


 


http://www.kuqin.com/assemble/

  评论这张
 
阅读(350)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017