MASM汇编常用基础知识
☆学习及编译环境:
刚开始学习Masm汇编,有些东西好像跟教材不怎么一样,有可能是Masm版本问题,我选择的是MASM5.00,用Vim编辑代码(有语法加亮),网上下载好像要分,VMware Workstation 16虚拟机链接如下:
链接:https://pan.baidu.com/s/1pLR0YKzh7HGCpM0LZRw2Xw
提取码:yp6e
2024年4月30日更新
目前上述编译环境已经优化,dos下的vim还是不怎么方便。而且vmware的虚拟环境并不是100%模拟旧的计算机硬件,部分bios调用都存在问题。因此我将虚拟环境换成PCem(模拟pentium 90,ASUS P55tvp4主板),通过安装MS Client访问真实pc的共享文件夹,映射一个驱动盘,在Dos下编译,在Windows下通过VsCode编写代码(还可以用Ai插件),无论是代码提示还是编码查看,都方便很多。
☆以下是我在学习Masm过程中曾经遇到的问题,记录如下:
1、代码中assume ss:stacksg,并且定义了stacksg,为什么还提示:Link:warning L4021:no stack segment没有堆栈段呢,这里跟教材稍微不同,堆栈段需要这样定义stacksg segment stack。
segment的详细语法:
name SEGMENT align combine 'class"
;align的值有:BYTE,WORD,PARA,PAGE
;combine的值有:PUBLIC,COMMAN,STACK,MEMORY,AT address
;定义在相同class中的segment会被连续加载,在内存中处于相邻的位置,名字必须用单引号
name ENDS
★堆栈段最大的作用是指定堆栈的大小,所以一般都是定义类似如256 dup(?);如果没有定义堆栈段,也可以在link时通过/stack:265指定也可以。
2、数值常量进制后缀(不区分大小写),B表示二进制,Q或O表示八进制,D表示十进制,H表示十六进制。不写后缀用.radix设置。十六进制数如果第一个是字母,应该写成数字0开头,如B800H,必须写成0B800H,否则会提示错误error A2006:undefined symbol xxx,编译器把数字xxx做符号变量处理了。
3、汇编代码不区分大小写,并通过分号(;)来表示代码注释。
4、常用教材都是通过segName segment与segName ends来定义段,但在Masm5之后,如果没有特殊的要求,可通过简单的方式定义段:.model、.data、.code、.stack、.startup、.exit等,如:
.model tiny
.data
.stack
.code
.startup
.exit 0
end
.model tiny指的是建立com文件,通常用small,编译成exe可执行文件。
.stack xxx,如果不设置,则stack默认为1024Byte,即1K。如.stack 100,即栈空间为100Byte。
.exit后跟返回码,如.exit xxx,与mov ax,4cxxh相同。.exit后跟end,且与.startup成对出现。
5、group(组)指的是一组segment(段)的集合,语法是:name GROUP segment [[, segment]]...,在.MODEL语法下,默认将stack和data段定义在DGROUP下,方便进行访问,只要在相应的模式下,Group的总大小不超过64K或4G,可在任何时候向组添加段成员,且添加的段成员顺序并不影响最终在内存中的排列顺序。
6、ret除了可以返回以外,还可以控制SP的出栈数量,ret n的语法含义是:
1 pop ip
2 add sp,n
即返回调用出IP后,在弹出需丢弃的栈元素,通常用于子程序参数的栈传递。
7、Lea和Offset作用都是获取偏移地址,前者是Cpu指令,后者是伪指令(在编译的时候计算,对CPU来说相当于立即数)。Offset只能对变量或标号进行计算,当不能对一般操作数进行计算,如:Lea ax,[bx+si]。但伪指令在编译时计算,且后者是3字节内存寻址指令,所以用Offset程序运行速度较快。
8、如果在WMware中安装TRW2000进行汇编调试,如果无法弹出调试窗口,请修改相应的vmx文件,在文件中加入:
vmmouse.present="FALSE"
svga.maxFullscreenRefreshTick="5"
9、在Bochs中使用Dos 6.22进行汇编调试时,不要在config.sys中加载emm386.exe,否则会卡死。
10、MASM中常数的定义不能以字母开头,如FFFFH要写成0FFFFH。字符串是以ASCII码存储(就是说看成数字),如:mov ax,'he',定义的时候引号可以是单引号或双引号,效果相同。
11、系统装载EXE文件时,会构建一个PSP(256字节)段,紧接着装载程序,并把DS和ES初始化为PSP地址,所以,DS和ES需要手动写代码进行初始化。CS和SS通过“END 标号”和“Assume 标号 Stack”的方式分别在DOS头中进行了初始化。当然,如果通过.Data的方式,Masm会替你生成一段代码Mov DS,DGroup。当然,如果在.Model中如果Stack Distance定义为Nearstack的话,Masm会根据DOS头(PE结构)重新定位SS和SP的初始化值:
1 mov dx, DGROUP
2 mov ds, dx
3 mov bx, ss
4 sub bx, dx
5 shl bx, 1 ; If .286 or higher, this is
6 shl bx, 1 ; shortened to shl bx, 4
7 shl bx, 1
8 shl bx, 1
9 cli ; Not necessary in .286 or higher
10 mov ss, dx
11 add sp, bx
12 sti ; Not necessary in .286 or higher
12
12、在定义变量值为字符串时,db定义'abcd'和'a','b','c','d'存储的方式相同;dw,dq,dt定义'ab'和'a','b'的存储方式不同,且dw,dq,dt定义字符串'ab'的长度不能超过2个字符,'ab'的存储方式是第一字节存储'b',第二字节存储'a',剩余部分用0补齐(如图把ab看成一个数字)。
13、MASM源码中,如果进行直接寻址,必须加上段前缀,如mov ax,[3],这是不允许的,会被编译成mov ax,0003,必须写成:mov ax,ds:[3]。因为带寄存器、标号或变量都可以确定偏移地址所在的段,但直接给地址无法确定是哪个段。
14、masm5中如果要编写com格式代码,必须满足2个条件,程序只包含1个segment,代码中不含有段地址的引用,如mov ax,datasg;程序从0100h处开始执行。通过masm和link生成exe文件后(此文件并不能正常执行,因为没有初始化ds的准确地址)。通过bin2exe(bin2exe hello.exe,hello.com)生成com文件后可正常执行,bin2exe在标配的系统中是没有的,可安装MS-DOS 6.22 Supplemental Kit(可在winpcworld下载)。
示例代码如下:
assume cs:_text,ds:_text,ss:_text
_text segment
org 100h
begin:
jmp start
msg db "Hello,world!",13,10
lmsg equ $ - msg
start:
mov ah,40h
mov bx,1
mov dx,offset msg
mov cx,lmsg
int 21h
mov ax,4c00h
int 21h
_text ends
end begin
15、在simplified segment模式下,如果使用.model,则段寄存器通常与DGroup关联,包括.stack;而不是像full segment模式下与DS关联,stack与SS关联。
16、.bss和.data的区别:
1)都属于静态内存分配,存放全局变量,bss(Block Started by Symbol)用于存放未初始化的变量。
2)bss不占用exe文件空间,只存放占位符,有系统初始化。
17、既然可以通过label:+ret的方式定义子程序,为什么还要proc+endp这样的结构呢,因为前者需要手动定义label和ret的距离,如label是near,那么后面就是retn;如果label是far(通过label伪指令定义),则后面就是retf。如果通过proc的方式,只要定义时在proc后指明near或far,则ret指令会被自动替换成retn或retf。
18、如果想使用386之后的新增指令,如果添加.386指令,masm 5默认寄存器为32位,解决办法:
如果用简化段的方式,则在.model之后加入.386,而不是先写.386然后写.model;
如果是完整段方式,则在segment 声明后加use16。