一、开发环境
我使用visual studio 2022 preview,其他版本的设置大同小异。
第一步:
打开visual studio,点击“创建新项目”:
第二步:
visual studio并没有专门的汇编项目,所以需要挂羊头卖狗肉,选择C++空项目
第三步:
输入项目名称,点击创建
第四步:
鼠标右键单击项目名称——>生成依赖项——>生成自定义,点击masm,这样才能把masm加入到项目的生成工具中
第五步:
鼠标右键单击源文件——>添加——>新建像,选择C++文件,但文件扩展名要改为.asm,编译器才能自动的使用masm编译
第六步:
因为64位汇编比较简单,初学时把配置改为Release、X64,了解深入后再探索32位汇编
大功告成!
如果用visual studio 2019,可以安装AsmDude插件,辅助编写汇编代码。但它目前不支持visual studio 2022
二、汇编语言的基础知识
1、哲学思考
(1)汇编语言的特点
与硬件相关,每一种处理器都有相应的汇编语言;
与机器码相关,指令通常与机器码一一对应;
家用电脑使用intel/amd处理器,对应x86-x64汇编语言。
(2)汇编语言的作用
深入理解硬件工作原理;
充分利用计算机特性(如SIMD,即单指令多数据);
开发操作系统内核、驱动程序等;
优化程序。
(3)汇编语言无用论
汇编语言代码难以维护;
编译器优化足够先进,无需汇编语言(编译器可实现循环展开、内联展开等,减少跳转,而汇编语言开发难以实现);
高级语言也可以实现同样的功能,汇编语言与高级语言相比并无优势。
(4)汇编语言有害论
汇编语言给黑客、病毒、作弊外挂大开方便之门(反驳一:工具价值中立;反驳二:软件破解为普通人节省大量金钱)。
(5)汇编语言也是高级语言论
在许多CPU中,汇编语言指令并非直接操作硬件,而需通过硬件制造商提供固件内的程序编译成“微指令”再执行,这与高级语言没有说很么不同。
2、数学基础
(1)二进制、十进制、十六进制转换和运算(可借助windows计算器)。
(2)浮点数的存储:
real4:符号1位+指数8位+小数23位(整数部分1省略);
real8:符号1位+指数11位+小数52位(整数部分1省略);
real10:符号1位+指数15位+小数64位(整数部分1省略)。
3、寄存器
汇编语言使用的寄存器实际上并不是物理寄存器,而是在通过一定的操作映射CPU内部特定的物理寄存器。主要包括:
(1)通用寄存器16个,可以通过通常的指令读写:
全部64位 | 0-31位 | 0-15位 | 8-15位 | 0-7位 |
---|---|---|---|---|
rax | eax | ax | ah | al |
rbx | ebx | bx | bh | bl |
rcx | ecx | cx | ch | cl |
rdx | edx | dx | dh | dl |
rsi | esi | si | sil | |
rdi | edi | di | dil | |
rbp | ebp | bp | bpl | |
rsp | esp | sp | spl | |
r8 | r8d | r8w | r8b | |
r9 | r9d | r9w | r9b | |
r10 | r10d | r10w | r10b | |
r11 | r11d | r11w | r11b | |
r12 | r12d | r12w | r12b | |
r13 | r13d | r13w | r13b | |
r14 | r14d | r14w | r14b | |
r15 | r15d | r15w | r15b |
以上16个寄存器,esp专门用于指示栈顶;此外,其他其他寄存器也有一定的功能区别,在某些指令中作为默认操作数,但这些功能区别正日益消失。
(2)专用寄存器有专门用途,并且只能通过专门的指令读写:
用途 | 全部64位 | 0-31位 |
---|---|---|
标志寄存器 | rflags | eflags |
指令寄存器 | rip |
标志寄存器各位含义:
位 | 名称 | 数值含义 | 主要用途 |
---|---|---|---|
0 | CF | 1进位,0无进位 | 无符号数运算是否溢出 |
1 | 置1 | ||
2 | PF | 1偶数,0奇数 | 过去主要用于数据传输校验,目前很少用 |
3 | 置0 | ||
4 | AF | 1半进位,0无半进位 | 过去主要用于BCD码加减法,因BCD码指令已废除,目前很少用 |
5 | 置0 | ||
6 | ZF | 1结果零,0结果非零 | 是否相等 |
7 | SF | 1负数,0正数 | 是否大于或小于 |
8 | TF | 1引发int 1中断,0不引发中断 | 用于调试,用户态置0,不可更改 |
9 | IF | 1允许中断,0不允许中断 | 用于暂时屏蔽中断,用户态置1,不可更改 |
10 | DF | 1递增,0递减 | 确定字符串指令的执行方向,通常置0 |
11 | OF | 1溢出,0无溢出 | 有符号数运算是否溢出 |
12-13 | IOPL | 0-3,表示IN/OUT指令操作需要的特权级 | 操作系统通常置0,即用户态不能使用IN/OUT指令 |
14 | NT | 表示当前任务与其他任务是否关联 | 由操作系统管理 |
15 | 置0 | ||
16 | RF | 确定调试异常的处理方式 | 由操作系统管理 |
17 | VM | 表示是否处于虚拟8086模式 | 由操作系统管理 |
18 | AC | 表示是否进行内存对齐检查 | 由操作系统管理 |
19 | VIF | 虚拟中断标志 | 由操作系统管理 |
20 | VIP | 虚拟中断挂起 | 由操作系统管理 |
21 | ID | 表示是否允许执行CPUID指令 | 由操作系统管理 |
22-63 | 置0 |
不涉及系统编程时,通常只需要注意CF、AF、SF、OF四个标志位,并将其用于有条件跳转指令。
需注意的是,Visual Studio 2019、2022的调试模式下,寄存器窗口的EFLAGS第1位按照0来显示,但将EFLAGS存入堆栈后可以看出EFLAGS第1位仍然是1。Visual Studio 2017及更早版本的寄存器窗口则显示为1。
(3)其他寄存器:
与浮点数或SIMD指令集相关,在学习相应的指令集时再学习。
4、操作数(又称“寻址模式”)
(1)立即数
包括8位、16位、32位、64位立即数。
在masm中不能指定立即数类型,而是由编译器根据情况确定。如果需要指定立即数类型,只能直接书写机器码(对比:nasm可以指定)。
除mov指令外,绝大多数指令不允许使用64位立即数。要对64位数值进行操作,通常需要用mov指令先传入某个寄存器,再指定寄存器位操作数。
(2)寄存器
包括8位、16位、32位、64位寄存器,通过寄存器名称指定。
intel匪夷所思的重要规则:对通用寄存器低32位操作会导致高32位清零,对通用寄存器的低16位、低8位、8-15位操作均不影响其他位。
(3)内存
内存数据可以是8位、16位、32位、64位或128位。
内存地址的指定方式包括:
第一,rip+偏移量,通过变量名指定内存时,编译器自动转换到这种方式
第二,通用寄存器,如[rdx]
第三,通用寄存器+偏移量,如[rdx+0531h]
第四,比例索引字节(SIB),即[存放基址的寄存器+存放索引号的寄存器*比例]
第五,比例索引字节(SIB)+偏移量,即[存放基址的寄存器+存放索引号的寄存器*比例+偏移量];
第六,64位数直接指定内存地址(仅MOV指令支持)。
其中:
比例索引字节的比例可以是1、2、4或8;
偏移量只能是8位或32位,intel不支持64位偏移量。
需要特别指定内存的数据长度时,可以添加“数据类型 ptr”前缀。
5、数据
(1)数据类型
字节数 | 无符号整型 | 有符号整型 | 浮点型 |
---|---|---|---|
1 | db, byte | ||
2 | dw, word | sword | |
4 | dd, dword | sdword | real4 |
6 | df, fword | ||
8 | dq, qword | real8 | |
10 | dt, tbyte | real10 |
SIMD指令使用的数据类型,在SIMD部分再学习。
在汇编语言中,有符号整型和无符号整型实际上没有区别,且均可以用来表示浮点数,因此下列代码含义完全相同:
acon dd 6.7
acon sdword 6.7
acon real4 6.7
但是,浮点型却不能用来表示整数,因此,下列代码无法通过编译:
mydata real8 35
如此修改则可以通过编译:
mydata real8 35.0
(2)结构体
结构体类型名 struct
内容
结构体类型名 ends
(3)常量
十进制数,直接书写,或以d/t结尾,如:
358
4.1
二进制数,用b结尾,如:
01011100b
八进制数,用o/q结尾,如:
401o
十六进制数,用h结尾,如果以字母开头,则前面加0,如:
0ah
45h
浮点数可以用小数或者科学计数法表示,如:
4.1
4.32398E4;(即43239.8)
浮点数只能用在数据初始化部分,不能用在指令中;在指令中使用浮点数,应转换为浮点编码。此外,MASM不支持某些教材上介绍的r后缀表示的“编码实数”类型。
字符和字符串常量:
以单引号或双引号表示,MASM不区分单引号和双引号,但单引号和双引号仍需分别配对,不能混用。引号内一个字符则为字符,多个字符则为字符串。单引号字符串中可以包含双引号,双引号字符串可以包含单引号。如果字符串内单引号、双引号混用,则需要分开写,并以逗号分割,如:
'Hello from Luisa'
"XP"
"No. It isn't"
'app "windows"'
'app "windows"?', "No. It isn't"
为字符串。须以数值表示。
MASM只能用引号定义ansi字符串,如果需要定义unicode字符串,须逐字符书写,例如:
dw 'H', 'e', 'l', 'l', 'o', 0;unicode字符串"Hello\0"
masm32论坛可以找到他人编写的unicode字符串宏,以简化unicode字符串书写。
整型常量表达式:
整型常量之间可以用+-*/MOD以及括号运算符进行计算,计算过程由编译器进行。
符号常量:
=:可以表示包含符号常量的常量表达式,但只能表示整数,并且可以在程序中反复修改,例如:
.data
P EQU 3
count=5
d1 dw count dup(0)
count=10*2
d2 dw count dup(0)
count=10*P
d3 dw count dup(0)
EQU:可以表示包含符号常量的常量表达式,例如:
count=5
PI EQU 3.1416
PP EQU count+2
MY_NAME EQU "Luisa Amalia Ho"
d1 qword PP*PP; d1=49
EQU后面的常量表达式如果加尖括号,表示将括号内的文本原样复制,数值可能存在差异,例如
count=5
PI EQU <3.1416>
PP EQU <count+2>
MY_NAME EQU <"Luisa Amalia Ho">
d1 qword PP*PP; d1=17
(3)标识符
常量名、变量名、标号、过程名等自定义的名称,均为标识符。
MASM的标识符由1-247个字符组成,不区分大小写,只能使用字母、数字、下划线、@、?、$,且数字不能作为标识符开头。
标识符不得与MASM保留字相同。
三、第一个汇编语言程序
如上所述,visual studio并没有专门的汇编语言项目,我们是以挂羊头卖狗肉的方式,借助C++项目的名义书写汇编语言程序。在这样的项目下,要想让汇编语言程序运行,有几种选择:
第一种选择是,在项目的属性中指定入口点,此时,windows系统将直接按照入口点,调用你书写的汇编语言过程,该过程结束后,程序结束,返回windows系统。程序只会执行你书写汇编语言代码,不会执行其他的东西。但是即便是初学者,大家也希望书写一个能够通过输入输出实现某种功能的程序,而不是仅仅在调试窗口里观察寄存器和内存的数值变化。虽然此时我们也可以通过调用windows api实现输入输出,但实现过程比较复杂,对于初学者并不合适。
第二种选择是,我们既然以挂羊头卖狗肉的方式,假借C++的名义,创建了项目,那么我们可以创建C++文件,由C++的main函数调用我们书写的汇编代码。然而,这种C/C++和汇编的混合编程,本应在了解C/C++和汇编语言之后才能学习。对于汇编语言的初学者也不合适。
第三种选择是,我们既然以挂羊头卖狗肉的方式,假借C++的名义,创建了项目,那么我们就仿照C++的方式,假装自己是C++程序,创建main过程让项目执行。然后,我们可以像其他学习C/C++的人一样,直接调用C/C++库函数实现输入输出的目的。
按照上文方式创建的项目,在没有指定入口函数的情况下,visual studio会指定mainCRTStartup为入口,接下来才会调用main。因此,直接生成必然是会产生找不到mainCRTStartup的链接错误;如果你在visual studio项目中创建一个空的,c/.cpp文件,那么vs就会正确的加载相应的库函数,进而调用你用汇编写成的main过程。不过,仅仅这么做,如果你调用printf,链接器依然无法找不到C/C++库函数。
此时,简单易行的做法应该是通过INCLUDELIB,把C/C++库文件包含在汇编代码内,即:
INCLUDELIB libcmt
加入这一个库文件,就不需要另外创建.c/.cpp空文件。vs会把你的代码当作C++项目,正确的编译、链接,并且以main作为入口点执行。
因此,第一个汇编语言程序,露易莎在这里向各位问好了:
INCLUDELIB libcmt;包含“C运行时库”
EXTERN printf:PROC;声明函数printf
.data;数据段
output_format db "Hello from Luisa",0;需要打印的字符串
.code;代码段
main PROC;main过程开始
sub rsp, 28h;预留空间
lea rcx, output_format;传入output_format的地址作为参数
call printf;调用printf
add rsp, 28h;平衡堆栈
ret;从过程返回
main ENDP;main过程结束
END;汇编代码结束
1、MASM64汇编语言源代码文件的书写特点
(1)不区分大小写;
(2)注释以分号开始;
(3)换行通常作为语句结束标志,如一个语句过长,内部需要换行,可以在行尾用'\'表示。
2、内容
(1)声明部分
include包含*.inc文件;
includelib包含*lib库文件;
EXTERN声明外部函数和变量;
(2)段
在64位汇编中,采用平坦模式,不涉及段。但MASM在程序结构上仍然保留了段的概念。在运行时,不同的段会被分配到不同的页面。
64位汇编通常以简易的方式声明段,包括:
.data(可读,可写,不可执行,段名_DATA),本段数据可以初始化,如未初始化,则编译器自动填入0,这些数据会存入.exe文件中
.data?(可读,可写,不可执行,段名_BSS),本段数据不进行初始化,也不会存入.exe,在运行时才分配内存,并全部填入0。该段中的数据若进行初始化,编译不会报错,但运行时仍然填入0。
.code(可读,不可写,可执行,段名_TEXT),本段为代码段,但在代码之中也可以包括数据,如数据未初始化,则编译器自动填入0,这些数据会存入.exe文件中
.const(可读,不可写,不可执行,段名CONST),本段数据可以初始化,如未初始化,则编译器自动填入0,这些数据会存入.exe文件中
64位汇编不能自己定义堆栈段。堆栈段由操作系统提供,堆栈段大小可以在项目属性中设置。
违反段的权限规定,会报异常。
完整方式声明段的方式是:
段名 segment [read|write|execute]
段名 ends
同名的段可以多次定义,但权限不得改变,链接器将把同名的段链接在一起。
通常仅在需要运行时修改代码等有特殊用途的情况下,才会用完整方式声明段
(3)变量定义
变量定义格式为[变量名] 类型 初始化数值;未初始化的数值用?表示。
dup伪指令表示重复同样数值或?,例如:mydata db 300 dup(0),表示定义300个db类型的0,每个占用1字节。
定义大型数组(例如500k以上),无论是否初始化,masm64编译都会显著变慢,这是一个长久的bug,原因不明(GoAsm等其他编译器不存在此类问题)。
一种替代方法使用org伪指令指定下一个数据的地址,之前的内存会全部置0且可以读写。
例如定义会导致编译极慢:
array db 500000 dup(?)
可以考虑改为
org 0
array db ?
org 500000
db ?; 这一行必须有,否则操作系统不会分配这部分内存
此时地址0~500000均可正常读写。
(4)文件结束
END是汇编代码文件结束的标志。END之后无论写什么,编译器一律忽略。文章来源:https://www.toymoban.com/news/detail-450403.html
因此,这样的代码也是可以编译运行的:文章来源地址https://www.toymoban.com/news/detail-450403.html
INCLUDELIB libcmt;包含“C运行时库”
EXTERN printf:PROC;声明函数printf
.data;数据段
output_format db "Hello from Luisa",0;需要打印的字符串
.code;代码段
main PROC;main过程开始
sub rsp, 28h;预留空间
lea rcx, output_format;传入output_format的地址作为参数
call printf;调用printf
add rsp, 28h;平衡堆栈
ret;从过程返回
main ENDP;main过程结束
END;汇编代码结束
Nichts hier ist für immer
Auch wenn es so scheint
Plötzlich wachst Du auf
Und bist allein
Die Straßen sind noch grau
Es ist wie jedes Jahr
Die Uhr sie bleibt nicht stehen
Du bist allein
Eben noch
Warst Du ein Teil der schönen Welt
Der Unendlichkeit
Gerade noch
Warst Du ein Stück der Ewigkeit
Schon Vergangenheit
Es ist vorbei
Es ist vorbei
Es ist vorbei
Wieder stehst Du auf
Und schaust zum Fenster raus
Alles ist noch so
Wies früher war
Der Tag lacht viel zu laut
Und will in Dein Gesicht
Du ziehst den Vorhang zu
Und Du bleibst da
Eben noch
Warst Du ein Teil der schönen Welt
Der Unendlichkeit
Gerade noch
Warst Du ein Stück der Ewigkeit
Schon Vergangenheit
Es ist vorbei
Es ist vorbei
Es ist vorbei
Eben noch
Warst Du ein Teil der schönen Welt
Der Unendlichkeit
Gerade noch
Warst Du ein Stück der Ewigkeit
Schon Vergangenheit
Es ist vorbei
Es ist vorbei
Es ist vorbei
Es ist vorbei
Es ist vorbei
Es ist vorbei
到了这里,关于汇编语言笔记(一)——汇编语言基础的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!