3.1.2  段的定义

1. 段的概念

把上面的Win32Hello World源程序中的语句归纳精简一下,再列在下面:

.386

.model flat,stdcall

option casemap:none

  <一些include语句>

.data

  <一些字符串、变量定义>

.code

  <代码>

  <开始标号>

     <其他语句>

end 开始标号

上一节讲到的选项、模式等定义并不会在编译好的可执行程序中产生什么东西,它们只是“说明”,而真正的数据和代码是定义在各个段中的,如上面的 .data段和 .code段,考虑到不同的数据类型,还可以有其他种类的数据段,下面是包含全部段的源程序结构:

.386

.model flat,stdcall

option casemap:none

  <一些include语句>

.stack [堆栈段的大小]

.data

  <一些初始化过的变量定义>

.data?

  <一些没有初始化过的变量定义>

.const

  <一些常量定义>

.code

  <代码>

  <开始标号>

     <其他语句>

end 开始标号

.stack.data.data?.const .code是分段伪指令,Win32中实际上只有代码和数据之分,.data.data? .const是数据段,.code是代码段,和DOS汇编不同,Win32汇编不必考虑堆栈,系统会为程序分配一个向下扩展的、足够大的段作为堆栈段,所以 .stack段定义常常被忽略。

2. 数据段

.data.data? .const定义的是数据段,分别对应不同方式的数据定义,在最后生成的可执行文件中也分别放在不同的节区(Section)中。程序中的数据定义一般可以归纳为3类:

第一类是可读可写的已定义变量。这些数据在源程序中已经被定义了初始值,而且在程序的执行中有可能被更改,如一些标志等,这些数据必须定义在 .data段中,.data段是已初始化数据段,其中定义的数据是可读可写的,在程序装入完成的时候,这些值就已经在内存中了,.data段存放在可执行文件的_DATA节区内。

第二类是可读可写的未定义变量。这些变量一般是当做缓冲区或者在程序执行后才开始使用的,这些数据可以定义在 .data段中,也可以定义在 .data?段中,但一般把它放到 .data?段中。虽然定义在这两种段中都可以正常使用,但定义在 .data?段中不会增大 .exe文件的大小。举例说明,如果要用到一个100 KB的缓冲区,可以在数据段中定义:

szBuffer            db      100 * 1024 dup (?)

如果放在 .data段中,编译器认为这些数据在程序装入时就必须有效,所以它在生成可执行文件的时候保留了所有的100 KB的内容,即使它们是全零!如果程序其他部分的大小是50 KB,那么最后的 .exe文件就会是150 KB大小,如果缓冲区定义为1 MB,那么 .exe文件会增大到1 050 KB.data?段则不同,其中的内容编译器会认为程序在开始执行后才会用到,所以在生成可执行文件的时候只保留了大小信息,不会为它浪费磁盘空间。和上面同样的情况下,即使缓冲区定义为1 MB,可执行文件同样只有50 KB!总之,.data?段是未初始化数据段,其中的数据也是可读可写的,但在可执行文件中不占空间,.data?段在可执行文件中存放在_BSS节区中。

第三类数据是一些常量。如一些要显示的字符串信息,它们在程序装入的时候也已经有效,但在整个执行过程中不需要修改,这些数据可以放在 .const段中,.const段是常量段,它是可读不可写的。一般为了方便起见,在小程序中常常把常量一起定义到 .data段中,而不另外定义一个 .const段。在程序中如果不小心写了对 .const段中的数据做写操作的指令,会引起保护错误,Windows会显示一个如图3.2所示的提示框并结束程序。

 


3.2  .const段写操作引起的非法操作

如果不怕程序可读性不佳的话,把 .const段中定义的东西混到 .code段中去也可以正常使用,因为 .code段也是可以读的。

3. 代码段

.code段是代码段,所有的指令都必须写在代码段中,在可执行文件中,代码段是放在_TEXT节区中的。Win32环境中的数据段是不可执行的,只有代码段有可执行的属性。对于工作在特权级3的应用程序来说,.code段是不可写的,在编DOS汇编程序的时候,好事的程序员往往有个习惯,就是靠改动代码段中的代码来做一些反跟踪的事情,如果企图在Win32汇编下做同样的事情,结果就是和上面同样的“非法操作”。

当然事物总有两面性,在Windows 95下,在特权级0下运行的程序对所有的段都有读写的权利,包括代码段。另外,在优先级3下运行的程序也不是一定不能写代码段,代码段的属性是由可执行文件PE头部中的属性位决定的,通过编辑磁盘上的 .exe文件,把代码段属性位改成可写,那么在程序中就允许修改自己的代码段。一个典型的应用就是一些针对可执行文件的压缩软件和加壳软件,如UpxPeCompact等,这些软件靠把代码段进行变换来达到解压缩或解密的目的,被处理过的可执行文件在执行时需要由解压代码来将代码段解压缩,这就需要写代码段,所以这些软件对可执行文件代码段的属性预先做了修改。

3.1.3  程序结束和程序入口

C语言源程序中,程序不必显式地指定程序由哪里开始执行,编译器已经约定好从main()函数开始执行了。而在汇编源程序中,并没有一个main函数,程序员可以指定从代码段的任何一个地方开始执行,这个地方由程序最后一句的end语句来指定:

end     [开始地址]

这句语句同时表示源程序结束,所有的代码必须在end语句之前。

end start

上述语句指定程序从start这个标号开始执行。当然,start标号必须在程序的代码段中有所定义。

但是,一个源程序不必非要指定入口标号,这时候可以把开始地址忽略不写,这种情况发生在编写多模块程序的单个模块的时候。当分开写多个程序模块时,每个模块的源程序中也可以包括 .data.data?.const .code段,结构就和上面的Win32 Hello World一样,只是其他模块最后的end语句必须不带开始地址。当最后把多个模块链接在一起的时候,只能有一个主模块指定入口地址,在多个模块中指定入口地址或者没有一个模块指定了入口地址,链接程序都会报错。

3.1.4  注释和换行

注释是源程序中不可忽略的一部分,汇编源程序的注释以分号(;)开始,注释既可以在一行的头部,也可以在一行的中间,一行中所有在分号之后的字符全部当做注释处理,但在字符串的定义中包含在引号内的分号不当做是注释的开始。

举例如下:

;这里是注释

call        _PrintChar      ;这里是注释

szChar      db 'Hello, world',0dh,0ah   world后面的分号不是注释,后面的才是

当源程序的某一行过长,不利于阅读的时候,可以分行书写,分行的办法是在一行的最后用反斜杠(\)做换行符,如:

        invoke  MessageBox,NULL,offset szText,offset szCaption,MB_OK

可以写为:

    invoke  MessageBox,\

        Null,\              ;父窗口句柄

        offset szText,\     ;消息框中的文字

        offset szCaption,\  ;标题文字

        MB_OK

“一行的最后”指的是最后一个有用的字符,反斜杠后面多几个空格或加上注释并不影响换行符的使用,如上例所示,这一点和makefile文件中换行符的规定有所不同。

 

posted on 2009-04-11 13:42  jasonM  阅读(522)  评论(0编辑  收藏  举报