入门masm32编写简单汇编程序并做具体分析

这次来分享的是用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教程)

1.框架

开门见山,有屁快放道理我懂的

.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”语句后相同的标签。

2.条件和循环结构

是的,你可以像在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跳过下面的代码块,重新执行循环。

3.Invoke语句

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 

你不可以在过程外使用这些变量。它们储存在栈中而且当过程返回时移出。

4.一堆封装好的库函数

就很优秀,没啥好说的具体要用什么函数百度一下或者查查手册吧


最后再解释一蛤

最后再针对自己写的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比较好):

入门masm32编写简单汇编程序并做具体分析_第1张图片

什么?你问我怎么生成exe文件,那我就大发慈悲再告诉你一下:
打开masm32 editor,菜单栏点fileopen你的asm文件,然后菜单栏找到project下的console assemble&link,之后就能在asm文件所在目录找到同名exe文件啦

That’s all thank you

你可能感兴趣的:(breadcrumb)