关于MASM汇编的一点点备忘
起因
前段时间帮 我的小狗 我的女朋友写汇编作业,很多东西没有和她解释清楚 主要是当时我也不怎么清楚 导致验收的时候发生了一些不愉快的事情,所以整理了这篇随笔,梳理了一遍x86汇编的流程和基础用法,于我而言也作备忘之用。
题目要求
编写一个程序,在无符号数组中查找从键盘输入的无符号数,若存在则输出该数在数组中的位置和该数,若不存在则输出错误信息。
例:数组array=[1,23,4,5,6,7,8,9]
-
输入:4
-
输出:3 : 4
先上源代码吧
.386
.model flat, stdcall
.stack 4096
ExitProcess PROTO, dwexitcode:Dword
INCLUDE Irvine32.inc
INCLUDE macros.inc
includelib Irvine32.lib
.data
array dword 88, 5, 1, 443, 21, 310, 4, 75, 27, 0
inputNum dword ?
msg byte "The input number is not in the array!", 0
promptBad byte "Invalid input, please enter again", 0
split byte " : ", 0
.code
main PROC
mov edi, 0
mov ecx, lengthof array ;lengthof 得到数组元素个数
read:
call ReadDec
jnc goodInput
mov edx, OFFSET promptBad
call WriteString;
jmp read
goodInput:
mov inputNum, eax ;store input value
L1 :
mov ebx, [array + edi] ;[]意为解析操作,取出该地址里的值
cmp ebx, inputNum
je Find
add edi, type array ;type得到元素长度(字节),加进edi,得到下一个元素地址
loop L1
jmp notFind
Find:
mov eax, edi
cdq
mov ebx, type array
div ebx
inc eax
call WriteDec ;打印eax寄存器里的值(数字位置) (数字 : 数字)
mov eax, [array + edi]
mov edx, offset split
call WriteString
call WriteDec
jmp ed
notFind:
mov edx, offset msg
call WriteString
jmp ed
ed:
exit
main ENDP
END main
masm汇编需要注意的一点是,它是大小写不敏感的,也就是说END
和end
是一个意思,并且汇编语言注释是;
开始,一行代码没有结束符号。
前7行是32位汇编语言起手式:
.386
表示这是个32位程序,能访问32位寄存器和地址,.model flat, stdcall
选择了程序的运行模式(flat),并确定了子程序的调用规范(stdcall),.stack 4096
声明了该程序运行时堆栈的存储空间,ExitProcess PROTO, dwexitcode:Dword
声明了ExitProcess函数原型,当程序运行结束时,程序就调用该函数,向操作系统返回一个整数表示该程序运行良好(相当于C语言 return 0
),后面三行引入了Irvine32库,里面封装了一些很方便的操作,如下文的WriteString
(相当于C语言中 #include <stdio.h>
)
.data
:
表示接下来是变量声明区,汇编的变量声明格式为:变量 数据类型 初始值,在本程序中,只用到了dword,byte两个数据类型,dword的d为double之意,意为“双字”,word代表无符号16位整数,那么dword就是无符号32位整数,byte意为字节,即是无符号8位整数,在汇编里,数据类型的长度都是确定的,不会出现如C语言中int在32位,64位机器上是32位,16位机器上是16位的情况。
当暂时不需要设置初始值时,可以将初始值设为?
,初始值可以一个,也可以若干个(构成数组),像上文程序里的array
就声明了若干个初始值,也就是说,变量名仅仅表示第一个初始值的地址,比如在上文程序里,array
表示数字88的地址,88后面的数字的地址紧随88地址之后,一个接一个,构成了数组。既然这样,那么字符串也可以像这样表示,例如msg byte "The input number is not in the array!", 0
因为一个英文字符占1个字节,所以用byte类型即可,msg就表示后面的字符串中第一个字符"T"的地址,为什么最后有个0呢,记得C语言里字符串以'\0'结尾吗,这里的0,就是字符串的结束标志。
.code
:
表示接下来是代码区,main PROC
表示接下来是main函数,一直到main ENDP
结束(类似C语言里的main函数),接下来程序一行一行向下运行。
解释一下几个关键寄存器在代码段里的作用:
edi
:表示遍历时,遍历到的数组元素的地址
ecx
:记录数组元素的个数(循环时控制循环次数用)
PS. 不同名字寄存器在计算机中有不同的位置和擅长领域,如eax是“累加器”,ebx是“基地址寄存器”等等,但在汇编代码编写时,我们也可平等视之,像本文代码的edi
寄存器的选择,并没有特殊的考虑。
解释一下几个Irvine库里的函数:
ReadDec
读取一个10进制数进eax
WriteDec
将eax里的数以10进制形式输出到控制台
WriteString
输出字符串到控制台,此字符串的偏移量(有效地址)需要事先存进edx寄存器中
接下来需要着重说明一下的是汇编语言的条件跳转操作和除法操作
x86指令集中没有明确的高级逻辑结构,但是可以用 比较 和 跳转 的组合实现他们:
第一步:用cmp,add,sub等操作修改CPU状态标志位;
第二步:使用合适的条件跳转指令来测试标志位,然后产生一个新地址的分支
例如,在上面的代码中,有cmp ebx, inputNum
je Find
这两句指令,je为相等跳转,也叫为零跳转(CPU零标志位置1),当cmp的两个操作数相等时(也就是ebx与inputNum相等时)发生跳转,跳转到下文的的标志Find
除法操作:在32位模式下,div指令执行8,16,32位无符号除法,idiv执行有符号除法
下面是被除数,除数,商和余数之间的关系:
被除数 | 除数 | 商 | 余数 |
---|---|---|---|
ax | reg/mem8 | al | ah |
dx:ax | reg/mem16 | ax | dx |
edx:eax | reg/mem32 | eax | edx |
例如在上文的代码中,需要用 元素在数组中的偏移量 / 每个元素所占字节数 得到该元素在数组中的位置
mov eax, edi
mov eax, edi
cdq
mov ebx, type array
div ebx
先将edi
里的总字节数存入eax中,再用cdq
将eax
扩展到edx
接着用ebx
存储一个元素所占字节数
最后经过div ebx
操作,eax
里存的就是该元素在数组中的位置了(例如数组[88, 5, 1, 443, 21, 310, 4, 75, 27, 0],443在数组中的位置就是3(从0开始))
The END
大致内容就是这些,本人能力有限,如有谬误,欢迎在评论区批评指正