具体的配置步骤可以参考:

汇编环境搭建 Windows10 VS2019 MASM32

本文主要是入门向的教程,VS2019中要调用C语言函数需要加上

includelib ucrt.lib
includelib legacy_stdio_definitions.lib

输出

配置好了环境之后,让我们开始第一个汇编程序吧

.686
.MODEL flat, c
.stack 100h includelib ucrt.lib
includelib legacy_stdio_definitions.lib ;Function prototypes
printf PROTO arg1:PTR byte .data
hello byte "hello world !",0Ah, 0 ;声明变量 .code
main proc
invoke printf, ADDR hello ;调用printf函数打印变量
ret ;相当于return 0
main endp
end main

.686是指明使用的指令集,向下兼容,.model flat,c中的flat表示程序使用保护模式,c表示可以和c/c++进行连接。.stack以十六进制的形式声明堆栈大小,这几句先照抄就好。

如果要调用C函数记得把上面说的两个lib加上,printf proto这句话是指明printf函数的原型,它的参数是一个指向字符串的指针。

.data.code就如同他们的英文名字一样直接明了,数据段和代码段。

在汇编中要想使用printf,需要使用INVOKE指令。ADDR你可以理解成给参数赋值,ADDR表明了输出字符串的内存地址。特别注意:该指令会破坏eax,ecx,edx寄存器的值

hello byte "hello world !",0Ah, 0,你可能比较疑惑0Ah是干啥的,它其实就是\n,最后面跟着个0表示字符串到此结束(你肯定在C语言里学到过)。hello是变量名,你可以换成你喜欢的名字。不过汇编里面变量名是不区分大小写的

endp表示过程(procduce)的结束,end表示程序的结束.

ret等同于return 0

整个程序如果用C来写相当于

#include<stdio.h>
int main()
{
printf("hello world !");
return 0;
}

输入

学会了输出自然也得把输入学会,请看下面的代码:

.686
.MODEL flat, c
.stack 100h includelib ucrt.lib
includelib legacy_stdio_definitions.lib printf PROTO arg1:PTR byte, printlist:vararg
scanf PROTO arg2:ptr byte, inputlist:vararg .data
in1fmt byte "%d",0
msg1fmt byte 0Ah,"%s%d",0Ah,0
msg1 byte "the number is ",0
number sdword ? .code
main proc
invoke scanf, ADDR in1fmt, ADDR number ;scanf必须都加addr,类似于&
invoke printf, ADDR msg1fmt, ADDR msg1, number
ret
main endp
end main

看着有点恐怖?对照C语言程序看一下吧

#include<stdio.h>
int main()
{
int number;
scanf("%d",&number);
printf("\n%s%d\n","the number is ",number);
return 0;
}

这段程序大体跟之前的差不多,只不过多了几张新面孔。

.686
.model flat, c
.stack 100h includelib ucrt.lib
includelib legacy_stdio_definitions.lib printf proto arg1:ptr byte, printlist:vararg
scanf proto arg2:ptr byte, inputlist:vararg .data
in1fmt byte "%d",0
msg1fmt byte "%s%d",0Ah,0
msg1 byte "x: ",0
msg2 byte "y: ",0
x sdword ?
y sdword ? .code
main proc
invoke scanf,ADDR in1fmt, ADDR x
invoke printf,ADDR msg1fmt, ADDR msg1, x
mov eax,x
mov y,eax
invoke printf,ADDR msg1fmt, ADDR msg2, y
ret
main endp
end main
#include<stdio.h>
int main()
{
int x,y;
scanf("%d",&x);
printf("x: %d",x );
y=x;
printf("y: %d",y);
return 0;
}

对比上面两段代码你发现了什么吗?在C语言里面,把x赋值给y只需要一句话,但在汇编里面却不能这样做。因为数据不能直接从一个内存单元到另外一个内存单元去,只能是通过寄存器完成相关操作。RAM中的数据先要被装载到CPU中,再由CPU将其存到目的内存单元中。

如果是字符怎么办?方法跟是一样的,只不过这里只需要使用eax的低8位al即可。

	.data
char1 byte ?
char2 byte ?
.code
mov char1,'A'
mov al,char1
mov char2,al

字符串怎么办?其实这玩意就是个数组,让我们来看看如何操作数组吧

循环与数组

它们俩可是好兄弟

.data
numary sdword 2,3,4
zeroary sdword 3 dup(0)
empary sdword 3 dup(?)

要想遍历数组,循环结构是必不可少的。

for(int i=0;i<3;i++)
{
printf("%d\n",numary[i]);
sum += numary[i];
}
printf("%d\n",sum);

这段C语言代码用汇编来写是这样的


.686
.model flat, c includelib ucrt.lib
includelib legacy_stdio_definitions.lib printf proto arg1:ptr byte, printlist:vararg .data
msg1fmt byte "%d",0ah,0 ;还记得吧?0ah表示换行 numary sdword 2,5,7
sum sdword ? .code
main proc
mov sum,0
mov ecx,3
mov ebx,0
.repeat push eax
push ecx
push edx invoke printf,addr msg1fmt, numary[ebx] pop edx
pop ecx
pop eax mov eax,numary[ebx]
add sum,eax
add ebx,4 ;因为是双字,4个字节 .untilcxz
invoke printf,addr msg1fmt, sum
ret
main endp
end main

.repeat-.untilcxz该指令对做的事情就是每次循环都把ecx的值减一,直到它为0。这里有一个特别坑的地方:只能有126字节的指令包含在.repeat-.untilcxz循环体内,多了会报错。

另外还有注意的是,千万不要让ecx值为0进入.repeat-.untilcxz循环体,因为执行到.untilcxz语句时,ecx的值会先减1再与0比较是否相等。这就出大麻烦了,ecx的值现在为负数,虽然不会死循环,但程序要循环40亿次才能停下来。(一直减到-2147483648,下一次减一得到的结果才是一个正数2137483647)

鉴于上诉情况,还是用.while来写循环结构比较好

;前置检测循环while(i<=3)
mov i,1
.while (i<=3)
inc i ;i+=1
.endw ;循环体结束 ;后置检测循环do while
mov i,1
.repeat
inc
.untile (i>3)

栈的作用

上面那个打印数组的程序中为什么还用到了push指令?*因为invoke指令会破坏eax,ecx,edx寄存器的值,程序还需要ecx控制循环,所以在调用invoke指令之前需要利用栈将被破坏的ecx赋回原来的值,保证循环正确运行。

当然你也不需要一股脑push这么多,上面的例子其实只需要push ecx就可以了,这样别人看你代码时也能更清楚你都做了些什么。

要想偷懒的话可以使用pushadpopad来保存和恢复寄存器(eax,ecx,edx)中的值。

使用堆栈与xchg指令来实现数据交换

交换两数在高级语言之中一般这样写:

temp=num1
num1=num2
num2=temp

对应到咱们汇编,简短点写法是:

mov	eax,num1
mov edx,num2
mov num1,edx
mov num2,eax

不过这里用到了两个寄存器,还有没有别的比较好的办法呢?

当然是有的,可不就是咱们的标题嘛

push	num1;将num1压栈
push num2;将num2压栈
pop num1;将出栈的元素(num2)赋值给num1
pop num2;将出栈的元素(num1)赋值给num2 ;利用echg指令
mov eax,num1
xchg eax,num2
mov num1,eax

搞这么麻烦,直接xchg num1,num2不就好了吗?

如果你这么想就大错特错了!因为:数据不能直接从一个内存单元到另外一个内存单元去,我们必须借助寄存器的帮助。

上诉三种方法中mov指令是最快的,但需要用到两个寄存器;堆栈是最慢的,但无需使用寄存器;使用xchg指令算是一种折中的方法。

字符串

前面铺垫了那么多,终于到字符串了。

它也是数组

先来个朴实无华的hello world


.686
.model flat, c includelib ucrt.lib
includelib legacy_stdio_definitions.lib printf proto arg1:ptr byte, printlist:vararg .data
msg1fmt byte "%s",0Ah,0 string1 byte "Hello World!",0
string2 byte 12 dup(?),0 .code
main proc
mov ecx,12
mov ebx,0
.repeat
mov al,string1[ebx]
mov string2[ebx],al
inc ebx
.untilcxz
invoke printf,addr msg1fmt,addr string2
ret
main endp
end main

使用寄存器esi和edi进行索引


.686
.model flat, c includelib ucrt.lib
includelib legacy_stdio_definitions.lib printf proto arg1:ptr byte, printlist:vararg .data
msg1fmt byte "%s",0Ah,0 string1 byte "Hello World!",0
string2 byte 12 dup(?),0 .code
main proc
mov ecx,12
lea esi,string1 ;将string1的地址装载到esi
lea edi,string2 ;将string2的地址装载到edi
.repeat
mov al,[esi] ;将esi所指向的地址中的内容放入al
mov [edi],al ;将al中的内容放入edi所指向的地址
inc esi ;将esi中的内容加1
inc edi ;将esi中的内容加1
.untilcxz
invoke printf,addr msg1fmt,addr string2
ret
main endp
end main

当循环体中指令第一次执行时,esi和edi分别指向String1和String2的首地址。第二次执行时,esi和edi以及分别递增加1,esi所指00000101地址处的e会被复制到edi所指的0000010D地址中去。之后ecx减1,esi,edi递增,指向下一个字节处。

movsb指令可以帮助我们简化程序,它可用于完成单字节字符串的移动工作:首先将esi所指的字节内容复制到edi所指向的地址,接着将ecx的值减1,同时对esi和edi指向递增或递减操作。

虽然它是单字节移动指令,但与循环结构配合能够发挥出强大的作用。之前的代码我们可以改写成


.686
.model flat, c includelib ucrt.lib
includelib legacy_stdio_definitions.lib printf proto arg1:ptr byte, printlist:vararg .data
msg1fmt byte "%s",0Ah,0 string1 byte "Hello World!",0
string2 byte 12 dup(?),0 .code
main proc
mov ecx,12
mov esi,offset string1+0 ;将string1地址的值加0放入esi中
mov edi,offset string2+0 ;将string2地址的值加0放入edi中
cld ;方向标志值清零
.repeat
movsb
.untilcxz
invoke printf,addr msg1fmt,addr string2
ret
main endp
end main

如果想要将esi和edi中的值都递减,那么需要将cld指令换成std指令。

字符串数组

如何复制一个字符串数组?可以将其看成一个大字符串,这样使用两个循环:一个用于控制字符串数组,另一个用于处理字符串中的每一个数组,即可复制该字符串数组。


.686
.model flat, c includelib ucrt.lib
includelib legacy_stdio_definitions.lib printf proto arg1:ptr byte, printlist:vararg .data
msg1fmt byte "%s",0Ah,0 names1 byte "Abby","Fred","John","Kent","Mary"
names2 byte 20 dup(?) .code
main proc
mov ecx,5
lea esi,names1
lea edi,names2
cld
.repeat
push ecx ;保存寄存器ecx的值
mov ecx,4
rep movsb ;重复执行movsb直到ecx为0
pop ecx ;恢复寄存器ecx的值
.untilcxz
invoke printf,addr msg1fmt,addr names2
ret
main endp
end main
前缀 意义
rep 重复操作
repe 如果相等,则重复操作
repne 如果不相等,则重复操作

前缀rep指令会对寄存器ecx的值进行递减直到它为0,所以程序中使用了堆栈来保护用于控制循环的ecx的值。

过程

过程又被称为子程序,函数。

call指令可以用于调用过程:

call pname

之前程序里的main就是一个过程,过程的具体格式如下

pname	proc
;过程体
ret
pname endp

虽然过程的调用与返回要比直接在主程序中编写代码效率低,但因为相关的代码只需要写一次,所以节省了内存空间。

编写过程时,最好对eax,ecx,edx进行保存恢复工作,这样能方便需要用到这些寄存器的程序调用该过程。

宏的声明需要放在.code之后main过程之前

mname	macro
;宏体
endm

宏的调用不需要call指令,你可以就把它当成一条指令来使用。

使用堆栈与xchg指令来实现数据交换这一标题下提到的程序可以用宏改写为

.code
swap macro p1:REQ,p2:REQ ;; :REG表示参数是必须的
mov ebx,p1 ;;使用双分号进行注释,这段注释不会在后续的宏扩展中出现
xchg ebx,p2
mov p1,ebx
endm
main proc
swap eax,ebx
main endp
end main

判断与条件汇编

在汇编中,if语句与C语言中的没太大区别

.if (判断条件)
.else (判断条件)
.endif

也支持嵌套if,只要记得用完if之后要在后面有个.endif对应即可

那条件汇编又是什么东西呢,它与if这类的选择结构有什么区别?

.if语句用于控制程序执行流从哪一条路径执行下去,条件汇编告诉程序是否将一条指令或一段代码包含到程序中去。

addacc	macro	parm
ifb <parm> ;ifb if blank
inc eax ;如果缺少参数就把eax的值加1
else
add eax,parm;相当于eax+=parm
endif
endm

如果调用宏addacc时缺少了参数,eax默认为1,否则将参数与eax的值相加。

汇编指令 含义
if 如果(可以使用EQ,NE,LT,GT,OR...)
ifb 如果为空
ifnb 如果不为空
ifidn 如果相同
ifidni 不区分大小写时,如果相同
ifdif 如果不同
ifdifi 不区分大小写时,如果不相同

在VS2019使用MASM编写汇编程序的更多相关文章

  1. 用Visual Studio 2015 编写 MASM 汇编程序(二)从头开发一个Win32汇编程序

    一,建立一个VC的控制台类型的空工程: 1,从VS菜单中选择“文件”->“新建”->“项目”. 2,在新建项目中选择:“Visual c++”->"Win32"- ...

  2. win10编写8086汇编程序(dosbox)

    有部分同学反馈.在使用edit命令来编写汇编程序时遇到问题,由于模拟器没有edit程序,所以要换一种方式编写源程序.下面是完整的演示. 视频链接:http://www.bilibili.com/vid ...

  3. 汇编:采用址表的方法编写程序实现C程序的switch功能

    //待实现的C程序 1 void main() { ; -) { : printf("excellence"); break; : printf("good") ...

  4. 32位汇编第三讲,RadAsm,IDE的配置和使用,以及汇编代码注入方式

    32位汇编第三讲,RadAsm,IDE的配置和使用,以及汇编代码注入方式 一丶RadAsm的配置和使用 用了怎么长时间的命令行方式,我们发现了几个问题 1.没有代码提醒功能 2.编写代码很慢,记不住各 ...

  5. 用户手册是Yasm汇编

    本文档的用户手册是Yasm汇编. 它是介绍和通用所有Yasm用户参考. 英文的参考:http://www.cnblogs.com/coryxie/p/3959888.html 1 .介绍 Yasm b ...

  6. [抓紧小长假的尾巴] 分析一个KeyFileMe

    系统 : Windows xp 程序 : keyfileme 程序下载地址 :http://pan.baidu.com/s/1qYVfvu0 要求 : 编写KeyFile 使用工具 : OD 可在看雪 ...

  7. HLA高级汇编语言基础

    HLA高级汇编语言环境的搭建与设置 我的操作系统:WINDOWS7 需要下载的东西:MASM32:http://www.masm32.com/masmdl.htm  HLA:http://webste ...

  8. WinHex分析PE格式(1)

    最近在一直努力学习破解,但是发现我的基础太差了,就想学习一下PE结构.可是PE结构里的结构关系太复杂,看这老罗的WiN32汇编最后一章 翻两页又合上了..把自己的信心都搞没了.感觉自己的理解能力不行, ...

  9. 《深入浅出嵌入式底层软件开发》—1. ARM汇编编程基础

    1.1 ARM CPU寄存器 ARM的汇编编程,本质上就是针对CPU寄存器的编程,所以要搞清楚ARM有哪些寄存器:ARM寄存器分为两类:普通寄存器和状态寄存器:普通寄存器一共有16个,分别为R0——R ...

随机推荐

  1. springMVC入门(八)------拦截器

    简介 springMVC拦截器针对处理器映射器进行拦截配置 如果在某个处理器映射器中配置拦截,经过该处理器映射器映射成功的Handler最终使用该拦截器 由于springMVC支持配置多个处理器映射器 ...

  2. kubeadm安装kubernetes(v18.8.8)

    1. 前言 kubernetes版本更新迭代非常快,上一篇写kubernetes搭建时,版本还是v1.15.0,现在已经更新到v1.18.看kubernetes在github的官方仓库,8月14日小版 ...

  3. Windows & Linux 安装使用 Vim 编辑器 3分钟入门 - 精简归纳

    Windows & Linux 安装使用 Vim 编辑器 3分钟入门 - 精简归纳 JERRY_Z. ~ 2020 / 8 / 25 转载请注明出处! 目录 Windows & Lin ...

  4. [CSP-S2019]树上的数 题解

    CSP-S2 2019 D1T3 考场上写了2h还是爆零……思维题还是写不来啊 思路分析 最开始可以想到最简单的贪心,从小到大枚举每个数字将其移动到最小的节点.但是通过分析样例后可以发现,一个数字在移 ...

  5. update 字符串拼接

    UPDATE store SET food_ordering =1,self_pickup_remark = CONCAT('self pick up notes:',store_code,short ...

  6. 使用Apache的反向代理会影响搜索引擎的收录和排名吗

    http://www.wocaoseo.com/thread-292-1-1.html 百度官方观点:Baiduspider对站点的抓取方式和普通用户访问一样,只要普通用户能访问到的内容,我们就能抓取 ...

  7. laravel发送邮件配置

    1.设置发送方,即邮件服务器,可以使用163邮箱,设置smtp,开启后获取授权码 2.在env文件配置 MAIL_DRIVER=smtpMAIL_HOST=smtp.163.com  //邮箱服务器M ...

  8. 集成react-native-image-picker时,报错Couldn't get file path for photo

    1. 版本环境: "react": "16.13.1", "react-native": "0.63.2", " ...

  9. Web测试和前端技术

    Html Form表单 用户需要输入内容的地方一般有一个表单元素 method:GET/POST action:要打开/提交的目文件 Table表格 检查表格数据和数据库的一致性 表格的布局检测:填满 ...

  10. 如何利用 docker 快速部署 Mysql 服务

    docker 基础教程不再多说,这里只着重讲如何使用 docker 部署 mysql 服务 docker 拉取 访问 dockerhub,搜索关键词 mysql,我这里选择 mysql-server, ...