关于MASM汇编的一点点备忘

2019/05/08 23:19
阅读数 136

起因

前段时间帮 我的小狗 我的女朋友写汇编作业,很多东西没有和她解释清楚 主要是当时我也不怎么清楚 导致验收的时候发生了一些不愉快的事情,所以整理了这篇随笔,梳理了一遍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汇编需要注意的一点是,它是大小写不敏感的,也就是说ENDend是一个意思,并且汇编语言注释是;开始,一行代码没有结束符号。

前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中,再用cdqeax扩展到edx

接着用ebx 存储一个元素所占字节数

最后经过div ebx 操作,eax里存的就是该元素在数组中的位置了(例如数组[88, 5, 1, 443, 21, 310, 4, 75, 27, 0],443在数组中的位置就是3(从0开始))

The END

大致内容就是这些,本人能力有限,如有谬误,欢迎在评论区批评指正

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部