汇编语言笔记(一)——汇编语言基础

这篇具有很好参考价值的文章主要介绍了汇编语言笔记(一)——汇编语言基础。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、开发环境

我使用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

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模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Keil MDK配置ARM汇编/C语言混合开发环境

    1. 安装Keil MDK 安装方法这里不再说明,懂得都懂!🐶 2. 安装ARM开发包 在Keil MDK5之后,不再原生支持ARM7/9,需要自行安装软件包,链接在这,注意安装与自己MDK版本对应的那个包 我的版本是5.18,下载之后傻瓜式安装,因为它会自动检测你MDK所在的位置。 3. 新建工程 注意:选

    2024年02月02日
    浏览(60)
  • 汇编语言学习笔记六

    CF:进位标志位,产生进位CF=1,否则为0 PF:奇偶位,如010101b,则该数的1有3个,则PF=0,如果该数的1的个数为偶数,则PF=1。 0也是偶数 ZF:在相关指令执行后(运算和逻辑指令,传送指令不影响ZF的值),其结果为0,则ZF=1,否则为0。 SF:符号标志位,如果结果为负,则SF=1,否则为

    2024年02月03日
    浏览(44)
  • 汇编语言学习笔记四

    字符是以ASCII码的形式存储的,一个字符对应着8为二进制数,2位16进制数。 所以可以得到对应的字符地址。 根据ASCII码,字符的大写和小写相差一个0010 0000,比如a对应的ASCII码是0110 0001,那么A则对应的是0100 0001,对比可以发现他们只是第5位不同,第5位为1,则是小写,否则

    2024年02月03日
    浏览(63)
  • 从零学习开发一个RISC-V操作系统(四)丨RISC-V汇编语言编程

       本系列是博主参考B站课程学习开发一个RISC-V的操作系统的学习笔记,计划从RISC-V的底层汇编指令学起,结合C语言,在Ubuntu 20.04上开发一个简易的操作系统。一个目的是通过实践操作学习和了解什么是操作系统,第二个目的是为之后学习RISC-V的集成电路设计打下一定基础

    2024年01月25日
    浏览(61)
  • 【C语言基础】01环境安装 Windows下的CLion开发环境的安装

    资源:放在评论区中 把压缩包拖拽到C盘根目录,一键解压压缩包,得到文件夹mingw64 点击CLion.exe,运行安装程序 路径为默认安装,如需更改,注意路径中不要带有中文. Installation Options 全部勾选 选择我想要之后重启电脑 点击CLion,进入内部设置 点击下方continue 继续 存放在D盘,不要有中

    2024年02月01日
    浏览(53)
  • 【Go】Go语言开发0基础7天入门 - 笔记

    课程来源:【路飞学城】-黑金年卡VIP课程 课程名称:GO语言开发0基础7天入门 讲师:【 前汽车之家架构师 】Wusir-银角大王 官网:点击进入 集python简洁 + C语言性能 详情点击 编程语言 实战经验 源码 并发架构 新语言触类旁通 1.1 开篇介绍(必看) 1.2 环境搭建前戏 1.3 mac系统G

    2024年02月16日
    浏览(49)
  • 汇编语言—常见汇编指令汇总

    mov    寄存器 ,数据                如:mov ax ,8 mov   寄存器,寄存器              如:mov ax,bx mov   寄存器,内存单元          如:mov ax,[0] mov   内存单元,寄存器          如:mov [0],ax mov   段寄存器,寄存器          如:mov ds,ax add   寄存器,数据

    2024年02月10日
    浏览(44)
  • 5.8 汇编语言:汇编高效除法运算

    通常情况下计算除法会使用 div/idiv 这两条指令,该指令分别用于计算无符号和有符号除法运算,但除法运算所需要耗费的时间非常多,大概需要比乘法运算多消耗10倍的CPU时钟,在Debug模式下,除法运算不会被优化,但Release模式下,除法运算指令会被特定的算法经过优化后转

    2024年02月11日
    浏览(44)
  • 5.7 汇编语言:汇编高效乘法运算

    乘法指令是一种在CPU中实现的基本算术操作,用于计算两个数的乘积。在汇编语言中,乘法指令通常是通过 mul(无符号乘法) 和 imul(有符号乘法) 这两个指令实现的。由于乘法指令在执行时所消耗的时钟周期较多,所以编译器在优化代码时通常会尝试将乘法操作转换为更

    2024年02月11日
    浏览(38)
  • 5.6 汇编语言:汇编高效数组寻址

    数组和指针都是用来处理内存地址的操作,二者在C语言中可以互换使用。数组是相同数据类型的一组集合,这些数据在内存中是连续存储的,在C语言中可以定义一维、二维、甚至多维数组。多维数组在内存中也是连续存储的,只是数据的组织方式不同。在汇编语言中,实现

    2024年02月11日
    浏览(48)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包