
常用软件类: |
|杀毒安全 | |联络聊天 | |网络软件 | |多媒体类 | |系统工具 | |图形图像 | |系统工具 | |应用软件 | |行业软件 |
开发设计类: |
|动画制作 | |图像处理 | |3D设计 | |操作系统 | |站长学院 | |网络相关 | |WEB设计 | |数据库类 | |程序开发 |
在前面的章节中,我们已经了解了寄存器的基本使用方法。而正如结尾提到的那样,仅仅使用寄存器做一点运算是没有什么太大意义的,毕竟它们不能保存太多的数据,因此,对编程人员而言,他肯定迫切地希望访问内存,以保存更多的数据。
我将分别介绍如何在保护模式和实模式操作内存,然而在此之前,我们先熟悉一下这两种模式中内存的结构。
事实上,在实模式中,内存比保护模式中的结构更令人困惑。内存被分割成段,并且,操作内存时,需要指定段和偏移量。不过,理解这些概念是非常容易的事情。请看下面的图:

段-寄存器这种格局是早期硬件电路限制留下的一个伤疤。地址总线在当时有20-bit。
然而20-bit的地址不能放到16-bit的寄存器里,这意味着有4-bit必须放到别的地方。因此,为了访问所有的内存,必须使用两个16-bit寄存器。
这一设计上的折衷方案导致了今天的段-偏移量格局。最初的设计中,其中一个寄存器只有4-bit有效,然而为了简化程序,两个寄存器都是16-bit有效,并在执行时求出加权和来标识20-bit地址。
偏移量是16-bit的,因此,一个段是64KB。下面的图可以帮助你理解20-bit地址是如何形成的:

段-偏移量标识的地址通常记做 段:偏移量 的形式。
由于这样的结构,一个内存有多个对应的地址。例如,0000:0010和0001:0000指的是同一内存地址。又如,
0000:1234 = 0123:0004 = 0120:0034 = 0100:0234
0001:1234 = 0124:0004 = 0120:0044 = 0100:0244
作为负面影响之一,在段上加1相当于在偏移量上加16,而不是一个“全新”的段。反之,在偏移量上加16也和在段上加1等价。某些时候,据此认为段的“粒度”是16字节。
练习题
尝试一下将下面的地址转化为20bit的地址:
| 2EA8:D678 26CF:8D5F 453A:CFAD 2933:31A6 5924:DCCF 694E:175A 2B3C:D218 728F:6578 68E1:A7DC 57EC:AEEA |
稍高一些的要求是,写一个程序将段为AX、偏移量为BX的地址转换为20bit的地址,并保存于EAX中。
[ 上面习题的答案 ]
我们现在可以写一个真正的程序了。
经典程序:Hello, world
| ;;; 应该得到一个29字节的.com文件 .MODEL TINY .CODE CR equ 13 LF equ 10 TERMINATOR equ '$' ORG 100h Main PROC mov dx,offset sMessage mov ah,9 int 21h mov ax,4c00h int 21h Main ENDP sMessage: DB 'Hello, World!' DB CR,LF,TERMINATOR END Main |
; .COM文件的内存模型是‘TINY' ; 代码段开始 ; 回车 ; 换行 ; DOS字符串结束符 ; 代码起始地址为CS:0100h ; 令DS:DX指向Message ; int 21h(DOS中断)功能9 - ; 显示字符串到标准输出设备 ; int 21h功能4ch - ; 终止程序并返回AL的错误代码
|
那么,我们需要解释很多东西。
首先,作为汇编语言的抽象,C语言拥有“指针”这个数据类型。在汇编语言中,几乎所有对内存的操作都是由对给定地址的内存进行访问来完成的。这样,在汇编语言中,绝大多数操作都要和指针产生或多或少的联系。
这里我想强调的是,由于这一特性,汇编语言中同样会出现C程序中常见的缓冲区溢出问题。如果你正在设计一个与安全有关的系统,那么最好是仔细检查你用到的每一个串,例如,它们是否一定能够以你预期的方式结束,以及(如果使用的话)你的缓冲区是否能保证实际可能输入的数据不被写入到它以外的地方。作为一个汇编语言程序员,你有义务检查每一行代码的可用性。
程序中的equ伪指令是宏汇编特有的,它的意思接近于C或Pascal中的const(常量)。多数情况下,equ伪指令并不为符号分配空间。
此外,汇编程序执行一项操作是非常繁琐的,通常,在对与效率要求不高的地方,我们习惯使用系统提供的中断服务来完成任务。例如本例中的中断21h,它是DOS时代的中断服务,在Windows中,它也被认为是Windows API的一部分(这一点可以在Microsoft的文档中查到)。中断可以被理解为高级语言中的子程序,但又不完全一样——中断使用系统栈来保存当前的机器状态,可以由硬件发起,通过修改机器状态字来反馈信息,等等。
那么,最后一段通过DB存放的数据到底保存在哪里了呢?答案是紧挨着代码存放。在汇编语言中,DB和普通的指令的地位是相同的。如果你的汇编程序并不知道新的助记符(例如,新的处理器上的CPUID指令),而你很清楚,那么可以用DB 机器码的方式强行写下指令。这意味着,你可以超越汇编器的能力撰写汇编程序,然而,直接用机器码编程是几乎肯定是一件费力不讨好的事——汇编器厂商会经常更新它所支持的指令集以适应市场需要,而且,你可以期待你的汇编其能够产生正确的代码,因为机器查表是不会出错的。既然机器能够帮我们做将程序转换为代码这件事情,那么为什么不让它来做呢?
细心的读者不难发现,在程序中我们没有对DS进行赋值。那么,这是否意味着程序的结果将是不可预测的呢?答案是否定的。DOS(或Windows中的MS-DOS VM)在加载.com文件的时候,会对寄存器进行很多初始化。.com文件被限制为小于64KB,这样,它的代码段、数据段都被装入同样的数值(即,初始状态下DS=CS)。
也许会有人说,“嘿,这听起来不太好,一个64KB的程序能做得了什么呢?还有,你吹得天花乱坠的堆栈段在什么地方?”那么,我们来看看下面这个新的Hello world程序,它是一个EXE文件,在DOS实模式下运行。
| ;;; 应该得到一个561 字节的EXE文件 .MODEL SMALL .STACK 200h CR equ 13 LF equ 10 TERMINATOR equ '$' .DATA Message DB 'Hello, World !' DB CR,LF,TERMINATOR .CODE Main PROC mov ax, DGROUP mov ds, ax mov dx, offset Message mov ah, 9 int 21h mov ax, 4c00h int 21h Main ENDP END main |
; 采用“SMALL”内存模型 ; 回车 ; 定义数据段 ; 定义显示串 ; 定义代码段
; 设置DX
|
561字节?实现相同功能的程序大了这么多!为什么呢?我们看到,程序拥有了完整的堆栈段、数据段、代码段,其中堆栈段足足占掉了512字节,其余的基本上没什么变化。