这次来分享的是用masm32编写一个很简单的汇编程序来入门一下masm32,打算使用斐波拉切数列这个简单的小程序来作为例子讲述。自己学习masm32原因是,编译原理告诉我们编译器通过一系列骚操作把我们的代码翻译成机器识别的机器码,这一波操作中有一步是翻译成了汇编代码,为了了解编译器的骚操作,我们当然得先懂得手工翻译,所以来回忆一下大一暑假学的汇编,讲道理masm32写起来还是很方便的。吐槽一下,感觉写masm32遇到的问题在度娘上很难找到很切合你所需要的答案,更多的要靠自己去翻官方的教程以及安装的masm32包里给你的一些demo来探索。
可能很多人开始学汇编用的是在dos虚拟机编写的,然而我们选用masm32就是为了借助MASM32包对很多动态链接库支持的特点,使用的时候可以对库函数直接调用,免去自己与硬件/操作系统底层打交道实现的麻烦。我第一次看到masm32给的示例那一堆include、while、invoke的反应是:这TMD什么鬼的汇编程序,你觉得我会信吗!后来看了教程才慢慢入门,可以说如果你对汇编语言和高级语言都有所了解的话,使用起来至少思路上还是很畅通的,至于说遇到问题很难在各大论坛找到针对性的答案,还希望有经验的大佬们分享一些好的方式或资源。
言归正传,fibonacci的高级语言的程序我想大家闭着眼睛倒叙着都能写出来,以C++的为例:
main()
{
int a, b, i, t, n;
a = 0;
b = 1;
i = 1;
cin >> n;
cout << a << endl;
cout << b << endl;
while (i < n)
{
t = b;
b = a + b;
cout << b << endl;
a = t;
i = i + 1;
}
}
它的masm32下的汇编代码是什么样的呢,我是这样写的(文章在会最后给出详细的注释版本):
.486
.model flat, stdcall
option casemap:none
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\gdi32.lib
includelib \masm32\lib\msvcrt.lib
includelib \masm32\lib\masm32.lib
include \masm32\include\kernel32.inc
include \masm32\include\user32.inc
include \masm32\include\gdi32.inc
include \masm32\include\windows.inc
include \masm32\include\msvcrt.inc
include \masm32\include\masm32.inc
include \masm32\macros\macros.asm
.data
n dd 0
a dd 0
b dd 1
i dd 1
t dd 0
.code
start:
mov eax, sval(input("Enter a number : "))
mov n,eax
print chr$("Fibonacci_number")
print str$(1)
print chr$(" is ")
print str$(b)
print chr$(" ",13,10)
mov ecx,n
mov i,ecx
dec i
.while i
mov eax,b
mov t,eax
mov eax,b
add eax,a
mov b,eax
print chr$("Fibonacci_number")
mov ecx,n
sub ecx,i
inc ecx
print str$(ecx)
print chr$(" is ")
print str$(b)
print chr$(" ",13,10)
mov eax,t
mov a,eax
dec i
.endw
ret
end start
这是使用input和print输入输出的汇编代码,一开始没找到他两,所以我先使用了百度查到的crt_scanf和crt_printf写出的代码(用crt_scanf输入数字还有一些问题没有解决,暂时用输入字符串然后减去0的ascii码的方式完成):
.486
.model flat, stdcall
option casemap:none
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\gdi32.lib
includelib \masm32\lib\msvcrt.lib
includelib \masm32\lib\masm32.lib
include \masm32\include\kernel32.inc
include \masm32\include\user32.inc
include \masm32\include\gdi32.inc
include \masm32\include\windows.inc
include \masm32\include\msvcrt.inc
include \masm32\include\masm32.inc
.data
szFmt db 'fibonacci_number=%d',13,10
n dw 5
a dw 0
b dw 1
i dw 1
t dw 0
szFmtIn db '%s',0
szText db ' ',0
.code
start:
invoke crt_scanf, addr szFmtIn,addr szText
mov al,szText
sub al,48
mov ah,0
mov n,ax
invoke crt_printf, addr szFmt,b
mov cx,n
mov i,cx
dec i
.while i
mov ax,b
mov t,ax
mov ax,b
add ax,a
mov b,ax
invoke crt_printf, addr szFmt,b
mov ax,t
mov a,ax
dec i
.endw
invoke ExitProcess, 0
end start
可以看到这段代码和以前我们编写的汇编程序主要区别在于
1.框架
2. Masm有一些伪高阶的语法来简便地创建条件和循环结构
3. Invoke简化了过程和call的使用
4.这里是输入输出调用现成的,可以预见很多在写汇编时常用的操作都被封装好了
当然还有一些像宏方面的优点我暂时还没用到就不说了。
现在具体来说说(参考masm32教程)
开门见山,有屁快放道理我懂的
.486
.model flat, stdcall
option casemap:none
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\gdi32.lib
include \masm32\include\kernel32.inc
include \masm32\include\user32.inc
include \masm32\include\gdi32.inc
include \masm32\include\windows.inc
.data
yaoyaoyaoyao
.code
start:
zongzongzongzonghaihaihaihai
end start
这是Windows汇编源文件(.asm)的基本框架,逐个解释一下
.486
告诉汇编器应该生成486处理器(或更高)的伪代码。你可以使用.386,但大多数情况下用.486
.model flat, stdcall
使用平坦内存模式并使用stdcall调用习惯(stdcall的意思是函数的参数从右往左压入,即最后的参数最先压入,而且函数在结束时自己清栈),这对于几乎所有的Windows API函数和dll是标准
插嘴解释一下平坦内存模式,知道汇编语言的人一定知道段的概念,在16位编程中,段是必不可少的,但对段的各自操作都很乏味。这个问题已经在32位Windows(95及以上)中得到解决。你仍然有段,但不用管他们了,因为它们不再是64kb,而是4GB。你如果尝试着改变段寄存器中的一个,windows甚至会崩溃。这称为平坦(flat)内存模式。只有offset,而且是32位的,因而范围从0到4,294,967,295。内存中的每一个地址都是用offset表示的。这真是32位胜于16位的最大优点。所以,你现在可以忘了段寄存器并把精神集中在其他的寄存器上。
option casemap:none
控制字符的映射为大写。为了Windows.inc文件能正常工作,这个应该为”none”
includelib include
为了使用来自WindowsAPI的函数,你需要导入dll。这是由导入库(.lib)来完成的。这些库是必需的。因为它们使系统(Windows)能在内存的动态基地址处动态的载入dll。在Win32asm包中(win32asm.cjb.net)提供了大多数标准dll的库。你可以用masm的includelib语句装载一个库:
Includelib C:\masm32\lib\kernel32.lib
但你不只是需要包含库。包含文件(.inc)也是必须的。这些可以用l2inc工具由库文件自动生成。包含文件这样装载:
include \masm32\include\kernel32.inc
这里有一个特别的包含文件。大多数的时候统称为Windows.inc,其中包含了用于Windows API的所有常量和结构的定义。
.data
定义各种变量
.code
写代码的地方
start:
end start
表示一个程序的开始的标签。它不是非得叫“start”。你可以使用任何和“end”语句后相同的标签。
是的,你可以像在C语言里if while的语句一样来在masm里用条件和循环语句,这会使你的汇编代码更加可读易懂。Masm为我们提供了许多的伪高阶的语法来简便地创建条件和循环结构:
.IF, .ELSE, .ELSEIF, .ENDIF
.REPEAT, .UNTIL
.WHILE, .ENDW, .BREAK
.CONTINUE
If语句
你不需要和一对跳转搅在一起了,只要一个.IF语句
.IF eax==1
;eax等于1
.ELSEIF eax==3
; eax等于3
.ELSE
; eax既不是1也不是3
.ENDIF
你一定发现了美丽的==符号,是的在masm下一堆美丽的操作符也能使用了,==,!=,>,<,>=,<=,&,!,&&,||,而他们的作用想必大家也都知道。
Repeat语句
这个语句执行直到条件为真为止:
.REPEAT
;代码在此
.UNTIL eax==1
这块代码反复执行repeat和until之间的代码,直到eax=1。
While语句
While是repeat语句的反转。它在条件为真时执行代码块:
.WHILE eax==1
;代码在此
.ENDW
你可以使用.BREAK语句来跳出循环
.WHILE edx==1
inc eax
.IF eax==7
.BREAK
.ENDIF
.ENDW
如果Eax==7,while循环将停止
continue指令使repeat或While跳过下面的代码块,重新执行循环。
Invoke简化了过程和call的使用。
在汇编语言中一般的格式:
push parameter3
push parameter2
push parameter1
call procedure
Invoke 格式:
invoke procedure, parameter1, parameter2, parameter3
汇编后的代码是一模一样的,但invoke格式更简单而且更可靠。对一个过程使用invoke,你要这样定义prototype:
PROTO STDCALL testproc:DWORD, :DWORD, :DWORD
声明了名为testproc,需三个DWORD大小的参数的过程。现在,如果你这么做
invoke testproc, 1, 2, 3, 4 ;这是错误的
masm会给你一个testproc过程需要三个参数而不是四个的错误。Masm还会做类型检查。它检查参数是否为正确的类型(即大小)
在一个invoke语句中,你可以用ADDR代替offset。这会使地址在汇编时是正确的。
过程这样定义:
testproc PROTO STDCALL :DWORD, :DWORD, :DWORD
.code
testproc proc param1:DWORD, param2:DWORD, param3:DWORD
ret
testproc endp
这会创建一个名为testproc,带三个参数的过程。Prototype是用来调用过程的。
testproc PROTO STDCALL :DWORD, :DWORD, :DWORD
.code
testproc proc param1:DWORD, param2:DWORD, param3:DWORD
mov ecx, param1
mov edx, param2
mov eax, param3
add edx, eax
mul eax, ecx
ret
testproc endp
现在,过程做了一下计算,(param1, param2, param3) = param1 * (param2 + param3).结果(返回值)存放在eax中,局部变量这样定义:
testproc proc param1:DWORD, param2:DWORD, param3:DWORD
LOCAL var1:DWORD
LOCAL var2:BYTE
mov ecx, param1
mov var2, cl
mov edx, param2
mov eax, param3
mov var1, eax
add edx, eax
mul eax, ecx
mov ebx, var1
.IF bl==var2
xor eax, eax
.ENDIF
ret
testproc endp
你不可以在过程外使用这些变量。它们储存在栈中而且当过程返回时移出。
就很优秀,没啥好说的具体要用什么函数百度一下或者查查手册吧
最后再针对自己写的fibonacci代码解释一波吧
data段没啥好说的,简洁明了赋初值,dd即dword是为了和后边eax的大小匹配
code段我们一句句分析
mov eax, sval(input("Enter a number : "))
应该很好看出格式,控制台输出引号中的话等待你输入,然后你键入的值被赋给eax寄存器
mov n,eax
从寄存器再给n(注意汇编里面不存在mov memory,memory这一操作,所以两变量赋值要经过寄存器)
print chr$("Fibonacci_number1")
print chr$(" is ")
print str$(b)
print chr$(" ",13,10)
输出第一个斐波拉切数,写的麻烦了点但大家应该都能读懂输出Fibonacci_number1 is 1换行(最后那个print是换行,相信知道汇编的都知道13,10的含义)
mov ecx,n
mov i,ecx
dec i
经过寄存器给循环计数的参数赋值
.while i
mov eax,b
mov t,eax
mov eax,b
add eax,a
mov b,eax
print chr$("Fibonacci_number")
mov ecx,n
sub ecx,i
inc ecx
print str$(ecx)
print chr$(" is ")
print str$(b)
print chr$(" ",13,10)
mov eax,t
mov a,eax
dec i
.endw
这段循环语句也很简单,参照C++的代码翻译的,那么多print只是print用的不熟,不知道按这种格式输出的print怎么写在一句里,其他的mov add操作大家对照C++的代码就能明白了
Ret
没有这句话会让让你调试的
所以最后结果也很完美,用cmd运行masm生成的exe文件(如果直接双击exe文件输入后就会闪退,所以大家用cmd进入文件夹运行exe比较好):
什么?你问我怎么生成exe文件,那我就大发慈悲再告诉你一下:
打开masm32 editor,菜单栏点file,open你的asm文件,然后菜单栏找到project下的console assemble&link,之后就能在asm文件所在目录找到同名exe文件啦