在《第一个汇编语言程序》一节中给出的 AddTwo 程序,并添加必要的声明使其成为完全能运行的程序。
; AddTwo.asm -两个 32 位整数相加
.386
.model flat,stdcall
.stack 4096
ExitProcess PROTO, dwExitCode:DWORD
.code
main PROC
mov eax,5 ;将数字5送入eax寄存器
add eax,6 ;eax寄存器加6
INVOKE ExitProcess,0
main ENDP
END main
第 3 行是 .386 伪指令,它表示这是一个 32 位程序,能访问 32 位寄存器和地址。第 4 行选择了程序的内存模式(flat),并确定了子程序的调用规范(称为 stdcall)。其原因是 32 位 Windows 服务要求使用 stdcall 规范。第 5 行为运行时堆栈保留了 4096 字节的存储空间,每个程序都必须有。
第 6 行声明了 ExitProcess 函数的原型,它是一个标准的 Windows 服务。原型包含了函数名、PROTO 关键字、一个逗号,以及一个输入参数列表。ExitProcess 的输入参数名称为 dwExitCode。
可以将其看作为给 Windows 操作系统的返回值,返回值为零,则表示程序执行成功;而任何其他的整数值都表示了一个错误代码。因此,程序员可以将自己的汇编程序看作是被操作系统调用的子程序或过程。当程序准备结束时,它就调用 ExitProcess,并向操作系统返回一个整数以表示该程序运行良好。
大家可能会好奇,为什么操作系统想要知道程序是否成功完成。理由如下:与按序执行一些程序相比,系统管理员常常会创建脚本文件。在脚本文件中的每一个点上,系统管理员都需要知道刚执行的程序是否失败,这样就可以在必要时退出该脚本。
脚本通常如下例所示,其中,ErrorLevel1 表示前一步的过程返回码大于或等于 1 :
call program_1
if ErrorLevel 1 goto FailedLabel
call program_2
if ErrorLevel 1 goto FailedLabel
:SuccessLabel.
Echo Great, everything worked!
现在回到 AddTwo 程序清单。第 15 行用 end 伪指令来标记汇编的最后一行,同时它也标识了程序的入口(main)。标号 main 在第 9 行进行了声明,它标记了程序开始执行的地址。
现在回顾一些在示例程序中使用过的最重要的汇编伪指令。
首先是 .MODEL 伪指令,它告诉汇编程序用的是哪一种存储模式:
.model flat,stdcall
32 位程序总是使用平面(flat)存储模式,它与处理器的保护模式相关联。关键字 stdcall 在调用程序时告诉汇编器,怎样管理运行时堆栈。然后是 .STACK 伪指令,它告诉汇编器应该为程序运行时堆栈保留多少内存字节:
数值 4096 可能比将要用的字节数多,但是对处理器的内存管理而言,它正好对应了一个内存页的大小。所有的现代程序在调用子程序时都会用到堆栈。首先,用来保存传递的参数;其次,用来保存调用函数的代码的地址。
函数调用结束后,CPU 利用这个地址返回到函数被调用的程序点。此外,运行时堆栈还可以保存局部变量,也就是,在函数内定义的变量。
.CODE 伪指令标记一个程序代码区的起点,代码区包含了可执行指令。通常,.CODE 的下一行声明程序的入口,按照惯例,一般会是一个名为 main 的过程。程序的入口是指程序要执行的第一条指令的位置。用下面两行来传递这个信息:
.codemain PROC
ENDP 伪指令标记一个过程的结束。如果程序有名为 main 的过程,则 endp 就必须使用同样的名称:
main ENDP
最后,END 伪指令标记一个程序的结束,并要引用程序入口:
END main
如果在 END 伪指令后面还有更多代码行,它们都会被汇编程序忽略。程序员可以在这里放各种内容一一程序注释,代码副本等等,都无关紧要。
使用 Visual Studio 可以很方便地编辑、构建和运行汇编语言程序。下面的步骤,按照 Visual Studio 2012,说明了怎样打开示例项目,并创建 AddTwo 程序:
1) 启动计算机上安装的最新版本的 Visual Studio。
2) 打开 Visual Studio 中 Solution Explorer 窗口。它应该已经是可见的,但是程序员也可以在 View 菜单中选择 Solution Explorer 使其可见。
3) 在 Solution Explorer 窗口右键点击项目名称,在文本菜单中选择 Add,再在弹出菜单中选择 New Item。
4) 在 Add New File 对话窗口中(如下图所示 ) ,将文件命名为 AddTwo.asm,填写 Location 项为该文件选择一个合适的磁盘文件夹。
5) 单击 Add 按钮保存文件。
6) 键入程序源代码,如下所示。这里大写关键字不是必需的:
; AddTwo.asm - adds two 32-bit integers.
.386
.model flat,stdcall
.stack 4096
ExitProcess PROTO,dwExitCode:DWORD
.code
main PROC
mov eax, 5
add eax, 6
INVOKE ExitProcess,0
main ENDP
END main
7) 在 Project 菜单中选择 Build Project,查看 Visual Studio 工作区底部的错误消息。这被称为错误列表窗口。注意,当没有错误时,窗口底部的状态栏会显示 Build succeeded。
下面将展示 AddTwo 程序的一个示例调试会话。演示使用的是 Visual Studio 2012,不过,自 2008 年起的任何版本的 Visual Studio 都可以使用。
运行调试程序的一个方法是在 Debug 菜单中选择 Step Over。按照 Visual Studio 的配置,F10 功能键或 Shift+F8 组合键将执行 Step Over 命令。
开始调试会话的另一种方法是在程序语句上设置断点,方法是在代码窗口左侧灰色垂直条中直接单击。断点处由一个红色大圆点标识出来。然后就可以从 Debug 菜单中选择 Start Debugging 开始运行程序。
如果试图在非执行代码行设置断点,那么在运行程序时,Visual Studio 会直接将断点前移到下一条可执行代码行。
当调试器被激活时,Visual Studio 窗口底部的状态栏变为橙色。当调试器停止并返回编辑模式时,状态栏变为蓝色。可视提示是有用的,因为在调试器运行时,程序员无法对程序进行编辑或保存。
在调试时可以自定义调试接口。例如,如果想要显示 CPU 寄存器,实现方法是,在 Debug 菜单中选择 Windows,然后再选择 Registerso, 其中 Registers 窗口可见,同时还关闭了一些不重要的窗口。EAX 数值显示为 0000000B,是十进制数 11 的十六进制表示。
Registers 窗口中,EFL 寄存器包含了所有的状态标志位(零标志、进位标志、溢出标志等)。如果在 Registers 窗口中 右键单击,并在弹出菜单中选择Flags,则窗口将显示单个的标志位值。
Registers 窗口的一个重要特点是,在单步执行程序时,任何寄存器,只要当前指令修改了它的数值,就会变为红色。尽管无法在打印页面(它只有黑白两色)上表示出来,这种红色高亮确实显示给程序员,使之了解其程序是怎样影响寄存器的。
在 Visual Studio 中运行一个汇编语言程序时,它是在控制台窗口中启动的。这个窗口与从 Windows 的 Start 菜单运行名为 cmd.exe 程序的窗口是相同的。或者,还可以打开项目 Debug/Bin 文件夹中的命令提示符,直接从命令行运行应用程序。如果采用的是这个方法,程序员就只能看见程序的输出,其中包括了写入控制台窗口的文本。查找具有相同名称的可执行文件作为 Visual Studio 项目。