通过一个基本框架解释各个指令和用处,方便复习。所以我认为最好的学习顺序就是先看一段完整的汇编代码程序,然后给你逐个逐个的解释每一个代码是干嘛用的。然后剩下的还有很多指令或者伪指令会在以后用到的时候再根据具体上下文使用,我认为汇编就是实战出来的,很多指令根本不用记忆,需求出来了,你用了后就知道是怎个事了。

First Program

经典Hello Word!
代码如下:

stack segment
db 10 dup(00)
topSP equ this word ;栈顶指针地址标号
stack ends
datas segment
string db 'Hello Word!', 0Dh, 0AH, '$'
db 10 dup (?)
datas ends
code segment
assume cs:code,ss:stack,ds:datas
start:
mov ax, datas
mov ds, ax
mov dx, offset string
mov ah, 09h
int 21h
mov ax, 4c00h
int 21h
code ends
end start

编译->链接->运行,经过三个步骤就能够使用debug对生成的exe文件调试了
编译命令:masm 名.asm;
链接命令:link 名.obj; (obj文件是在masm后自动生成的)
运行命令:名.exe(exe文件是在link后自动生成的)
debug调试exe命令:debug 名.exe

指令系统

点击复习指令系统

伪指令

伪指令都不占程序空间,因为在编译过程中,你的伪指令全部帮你翻译好了,然后变成机器指令后就只能按照机器指令, 机器指令没有伪指令,伪指令是方便人类阅读代码的,可以通过汇编编译器翻译对应的计算机能够执行的机器指令,比如:fun我把他作为一个函数入口地址,我们编译器翻译的时候不会再fun这一处存fun的地址空间,而是假设你有用到fun这个函数入口地址的话,在你使用该标号的位置处将该标号翻译成fun的入口地址就是fun的下一条指令。

  • 标号(他不是伪指令,是一个不占空间但又不是伪指令的东西)
    可以理解为变量名,变量的名字可以随便起,这个和下面介绍的名字指令差不多。
    标号后一定要接了一个冒号:,表示这个标号可以作为一个地址,地址和下一个指令地址一样。(虽然不是伪指令,但是他也不占程序空间,用来标记程序中某些位置的)

    随便名标号:
    mov ax ,bx
    loop 随便名标号
    ...
  • 名字(就是程序中Stack 或者 datas这些,可以随便命名)
    可以理解为变量名,用来记录当前位置的下一条指令或者数据的地址,所以利用这个特点我们就可以用它来定位是一件十分轻松的事情,因为他不占空间地址,写一条指令上去后就可以直接用这个指令的名字来表示地址了,很方便。(这里我程序里面也用到了我给栈顶指针起了个名字topSP ,最显著的特点就是用来当栈顶指针,妈妈再也不用担心我计算指针位置错误了!又因为他不占空间,有了这个标志就直接用就可以找到栈顶指针,不会影响后面的代码位置关系)

    随便名字段 segment
    随便名字段 ends
    随便名字段 db 'hello'
    ...
  • ASSUME
    翻译就是:假设、设想
    在汇编中是指明段寄存器的段名,格式为:assume 段寄存器:随便名字段
    随便名字段是段地址,是段地址,是段地址!!
    assume可以放在开头也行,不一样放在数据段定义的后面,因为assume是伪指令,汇编编译过程中会帮你翻译好。
    很明显这个是给自己编写的段一个归属,比如我datas段就是专门给ds段的,所以在assume中就是ds:datas,这就将我们的datas段数据连接到了数据段了,说到连接,那其实assume的本质就是为了汇编在link的时候将自己写的段设置到正确的段位置。
    注意事项:assume是 给自己定义的数据块指明回家的路,回到自己的段内,意思是DS冒号后面你的数据段属于DS段,那你要是用你他的时候就需要在DS里面访问(这里是一个坑,下面我接着说很重要的一点)
    还有一点很重要:我们assume定义完成后希望在code代码里面使用这段数据的话,其实代码里面还需要自己将DS段设置为随便名字段对应的段地址,我们的随便名字段就是段地址。
    因此正确使用我们定义的数据段因该是:

    assume cs:随便名字段,ds:datas, ss:随便名字段3
    mov ax, datas
    mov ds, ax

    将datas段地址给到我们的ds段,由于前面设置了ds:datas,所以编译器看得懂你这段datas数据,否则定义在其他段了你又在ds使用的话就会出问题。datas可以随便起名字

  • OFFSET 标号或者名字 / SEG 标号或者名字
    offset是返回标号在他自己的段的偏移地址
    seg是返回标号所在的段地址
    一般用法:

    mov ax, offset string
    mov ax, seg string;这里返回的是string所在的段地址

    通过验证也可以知道seg是真的返回了string的段地址,所以有时候不知道string的段地址的名字叫啥比如忘记了datas的话,我们就可以通过这种方式获取段地址,如下图所示

  • DUP
    格式:重复次数 DUP (要重复的东西)
    DUP一般是用来赋初值的
    要重复的东西:这里可以是问号,可以是数值,也可以是字符串等等,字符串也给你重复好多次。
    ?问号表示不确定是什么,如下图,通过验证后,8086debug中显示是0的状态。

    db 10 dup(00)
    db 10 dup (?)
  • DB/DD/DW
    DB字节 8字节
    DD单字 16字节
    DW双字 32字节
    用处就是当你定义一个数据段的时候可以指明一个元素占多少字节
    好比上一个DUP中用到的就是DB,表示他每一个重复的元素都是占8个字节大小

    db 10 dup(00)
  • $
    有时候表示当前地址
    比如:使用 $ 和相对偏移计算mov bx, $ - 标号
    有时候表示字符串结束符,当我们int 21中 09h的功能就是当遇到$字符的时候停止输出字符。
    比如:

    前面我们定义了string db 'Hello Word!', 0Dh, 0AH, '$'
    所以int 21的09号中断程序会在打印字符中遇到$的时候停止
    mov ah, 09h
    nt 21h
  • EQU / =
    equal相等,=也是相等
    格式:

    随便 equ 12
    随便2 = 13

    那以后代码中出现 了随便,就会在汇编编译过程汇总将随便这个名字换成12同理随便2换成13

  • PTR
    在指令系统讲过了,就是指明类型的意思
    下面介绍几个用法即可

  • THIS
    用法:THIS 类型名
    这种用法搭配equ和等号简直无敌,因为是伪指令所有使用他的时候不占程序空间内存,单单用作给编译器翻译,让编译器去做而已,翻译机器码后是没有这个语句的
    让我感到优雅的一个语句就是:topSP equ this word ;栈顶指针地址标号
    基本一看就懂了,最牛逼的是opSP equ this word伪指令他不占内存。翻译的时候他名字对应的地址是等于下一个指令的地址,并且不会影响你该语句后面的数据段或者代码段位置。我们以后想用栈的时候直接使用topSP这个名字就行,真的是一箭双雕,太优雅了!

    stack segment
    db 10 dup(00)
    topSP equ this word ;栈顶指针地址标号
    stack ends

    下图也可以看出确实topSP没有他的内存空间

    还有一种老师的用法,就是通过地址相同,因为不占空间,但是地址是下一条指令地址,我们直接将f_jump 相等equ与far,因为不指明跳转范围的标号都是段内转移,但是由于f_jump等于下一个指令的地址,但是他的属性类型是far,所以只要用他的名字就可以实现段间转移(确实有点反人类指令)

  • TYPE
    这个就是typeofC语言中的函数一样,
    type 标号或者名字,能够返回该标号或者名字的类型大小,以字节作为单位,
    比如一个标号s是段内转移,那他就是一个byte,返回的就是1,假设像我们f_jump的话,我们type一下他就返回2,因为far是段间

mov ax, type f_jump ;这里等于 mov ax, 2
mov ax, type n_jump ;这里等于 mov ax, 1
  • SIZEOF
    这个指令就是量整个变量所占的空间,可以理解为量我们的数据段首地址,然后就会返回你这个数据段的占的字节大小。

  • LENGTHOF
    这个就是量你元素个数,数据段中我们可能有多个相同大小的多个元素,那么这个指令就是用来量长度的,量个数的。

  • SIZEOF = TYPE × LENGTHOF

数值表达式

这个就有点推翻以前说的格式了,我们以前的指令格式都是:
操作指令 目的操作数,源操作数
但当你使用数值表达式的话就不一样了,就跟我们平常的写式子的顺序是一样的。

mov ax,3*4+5 ;等价于 mov ax,17

目前看起来很正常,因为我们的乘除法这样使用的在高级语言中也是可以。

or al,03h AND 45h ;等价于 or al,01h

这种就有点迷惑了,首先我们学的and指令都是:and 操作数1,操作数2
但是这种就是跟你写数学式子一样
也是数值表达式的一种特色吧,直接用and没给你改成其他的指令,直接拿硬指令,所以通过这个式子例子就可以猜到后面的用法了,
也就是说当我们需要一种计算的式子的话,可以放到源操作数中进行,不用手动计算,工作交给编译器,编译完成后会直接形成结果,所以不会影响执行速度。

mov al,0101b SHL (2*2);等价于 mov al,01010000b

这个指令就真的是推翻了我在指令系统里面的解释,指令系统中提到过shl如果要移位超过1位的话就要将移位个数存进cl中,然后shl ax, cl才可以多次移位,但是在数值表达式中就直接推翻了,但是 前提是你需要按照数值表达式的格式来写才可以,所以我们可以0101b SHL (2*2),但是这种形式是一个结果,所以我们一般都是需要放在源操作数,然然后送进目的操作数中。

程序框架解释

最后会在int中断号给出相关示例代码,这里只给出模版。

  • assume设置自己定义的数据段时属于哪一个段寄存器的,一般都会有assume,因为我们一般情况下是会定义一个代码段,assume一般是放在开头第一段或者code segment下一行。

  • codes segment …
    codes ends

    代码段,段都是用segment,
    开头段都需要 [段名 segment]
    结束段都需要 [段名 ends]

  • start:…
    end start

    start是程序入口,需要在程序最后结尾添上end start
    start是可以随便起名字

  • 代码段和入口程序标号通常是交织在一起的,我们就按照正常的格式写。如下:

codes segment
start:
......
......
......
codes ends
end start

整合一下整个框架就是:
(记住,要用数据段的时候千万不要漏掉将ds或者其他段用对应的标号修改,比如:mov ax, datas mov ds, ax,修改ds段地址)

assume cs:codes, ss:stacks, ds:datas
stacks segment
... ... ...
stacks ends
datas segment
... ... ...
datas ends
codes segment
start:
... ...
... ...
... ...
... ...
codes ends
end start

int 21 中断

这里是最后一个知识点,因为想要入门一个简单的程序需要用到,但是不会讲的很详细,int中断本来就是一个很大的章节需要慢慢学习,很多功能都能解决很多问题。这里就简单介绍几个常用的功能号

我们在程序中经常会看到这么一段

mov ah, 09h
int 21h
mov ax, 4c00h
int 21h

int 21h是一个DOS系统中断,21h里面包含很多功能,但是需要用寄存器来控制。
怎么控制?
最简单的一个控制,就是当ah等于09h的时候就是打印一个ds段里面的dx偏移地址的字符串,遇到$符号就停止,所以这里终于解释明白了我们First程序的字符串是怎么打印出来的了。
因此可以想象一下,我们int 21h中断有很多功能,那我们不仅仅是一个打印功能,还有很多,有可能是al来控制,也有可能是ah来控制,也有可能是其他寄存器,当然我不知有没有其他寄存器i,只是告诉使用int 21h功能的时候,记得设置功能调用号和参数。
09功能号,参数是ds:dx作为字符串入口
所以就是打印出来一个Hello Word!
4c00h就是意味着代码段程序正常退出
但是不是整个程序结束,整个程序结束时end start,当然 start是随便名,在你代码段开始的start:自己定义名字的。

下面介绍的比如09h数字是ax或者al/ah对应的数字所对应21h中断的功能程序是什么。当然09h上面说过了是打印ds:dx首地址的字符串,遇到$停止
注意:下面需要将中断号传到ah而不是ax,除了4c00需要传到ax,因为他是16位。

  • 09h
    功能号:AH=09H
    功能:在显示器输出指定的字符串
    DS:DX=欲显示字符串在主存中的首地址
    字符串应以$(24H)结束
    可以输出回车(0DH)和换行(0AH)字符产生回车和换行的作用


    示例代码如下:

    ;使用09中断号打印出来Hello Word!
    assume cs:code, ds:datas datas segment
    string db 'Hello Word!', 0dh, 0ah, '$'
    datas ends code segment
    start:
    mov ax, datas
    mov ds, ax ;设置datas对应的数据段
    mov dx, offset string ;这里一定要用offset,
    mov ah, 09h ;打印ds:dx字符串,遇到$停止
    int 21h
    mov ax, 4c00h ;代码段结束
    int 21h
    code ends
    end start
  • 01h
    功能:获得按键的ASCII代码值
    功能号:AH=01H
    出口参数:AL=字符的ASCII码
    调用此功能时,若无键按下,则会一直等待,直到按键后才读取该键值

    示例代码如下:

    ;使用01h中断号,循环录入Hello Word!  也就是说重复录入11个字符
    assume cs:codes, ds:datas datas segment
    string db 11 dup (0), 0dh, 0ah, '$'
    endString equ this byte
    datas ends codes segment
    start:
    mov ax, datas
    mov ds, ax
    mov cx, 11
    mov al, 0
    mov ah, 01h
    mov si, 0
    mov bx, offset string s:
    int 21h
    mov [bx][si], al
    inc si
    loop s mov dx, offset string
    mov ah, 09h
    int 21h mov ax, 4c00h
    int 21h codes ends
    end start
  • 02h
    功能:在显示器当前光标位置显示给定的字符,光标右移一个字符位置。如按Ctrl-Break或Ctrl-C则退出
    功能号:AH=02H
    入口参数:DL=字符的ASCII码
    既然他是显示一个字符就右移一个字符,那就是说我们可以利用循环每调用一次显示一个字符也不会覆盖住我们先前显示的字符了。理论存在,开始实现!

    示例代码如下:

assume cs:codes, ds:datas

datas segment
string db 'Hello Word!'
beginString equ this byte
datas ends codes segment
start:
mov dx, seg string
mov ds, dx mov ah, 02h
mov cx, offset beginString - offset string
mov si, 0
s:
mov dl, [offset string][si]
inc si
int 21h
loop s mov ax, 4c00h
int 21h codes ends
end start
  • 0ah
    功能:执行该功能调用时,用户按键,最后用回车确认
    功能号:AH=0AH
    入口参数:DS:DX=缓冲区首地址
    本调用可执行全部标准键盘编辑命令;用户按回车键结束输入,如按Ctrl+Break或Ctrl+C则中止

    细节:首先这个其实是有三个参数,
    就拿datas段来举例子

    datas segment
    string db 12 ;这个是用来表示你最大可以输
    ;入多少个字符,这里是包含回车的,不要忘记 db 0
    ;这里是表示你的输入字符个数,
    ;会在你输入的时候自动计算,然后一般是0 db 12 dup(0)
    ;这里是存你输入的字符串,
    ;所以你要输出的话就要记得将string 地址偏移地址
    ;偏移到该为止,这里由于是db大小所以偏移两个byte。

    下图证明了无论你第二个参数设置什么,都是默认帮你计算好最终你输入多少个字符的,我这里是输入了10个字符,但是我设置的是11,最后结果是a=10那就代表是强制性计数器。

    下面是执行结果,输入字符串直到回车结束,但是我们这里设置了12个加上回车符号那就是只能够输入11个字符,所以刚好输入Hello Word!,然后再利用09h中断号再次找到字符串入口输出。

    下面是示例代码:

    assume cs:codes, ds:datas
    
    datas segment
    string db 12
    db 11
    beginString equ this byte
    db 12 dup (0)
    db 0dh, 0ah, '$'
    datas ends codes segment
    start:
    mov dx, seg string
    mov ds, dx
    mov dx, offset string
    mov ah, 0ah
    int 21h mov ah, 09h
    mov dx, offset beginString
    int 21h mov ax, 4c00h
    int 21h codes ends
    end start
  • 0bh
    功能:仅判断当前是否有按下的键,设置AL后退出
    功能号:AH=0BH
    出口参数:AL=0,当前没有按键;
    AL=FFH,当前已经按键。
    注意的是这里讲的是出口参数,是用来判断你该是否按下按键了

    这个就不打算写示例了,因为就是用来测试有没有按键事件发生的一个中断号,可能在写游戏程序用到很多。

80x86汇编—汇编程序基本框架的更多相关文章

  1. 80x86汇编小站站长简单介绍-2014年08月23日

    [序言] 旧版的"80x86汇编小站站长简单介绍"已经过时了, 因此于2013年10月01日花费1个小时又一次更新和排版一次. [人生格言]  1] 一生都用头脑而不是情绪解决这个 ...

  2. 80x86汇编小站站长简单介绍

    [人生格言] 1] 一生都用头脑而不是情绪解决这个问题 2] 仅仅有偏执狂才会成功 3] 在最困难时都要保持一份幽默感 4] 吾生也有涯,而知也无涯,以有涯随无涯,殆已 [简历] 我的生日: 1981 ...

  3. 清华操作系统实验--80x86汇编基础

    前言 80x86架构里,因为历史原因字是16位的,因此在汇编指令中用后缀-b,-w,-l来表示操作数是字节 字 或是双字 C声明 Intel数据类型 汇编代码后缀 大小(字节) char 字节 b 1 ...

  4. 学习linux内核时常碰到的汇编指令(1)

     转载:http://blog.sina.com.cn/s/blog_4be6adec01007xvg.html 80X86 汇编指令符号大全 +.-.*./∶算术运算符. &∶宏处理操作符. ...

  5. C51汇编伪指令

    1.DS ---预留存储区命令格式: [标号:] DS   表达式值其功能是从指定地址开始,定义一个存储区,以备源程序使用.存储区预留的存储单元数由表达式的值决定. ;从标号TEP地址处开始保留1个存 ...

  6. GNU C 内联汇编介绍

    GNU C 内联汇编介绍 简介 1.很早之前就听说 C 语言能够直接内嵌汇编指令.但是之前始终没有去详细了解过.最近由于某种需求,看到了相关的 C 语言代码.也就自然去简单的学习了一下如何在 C 代码 ...

  7. GNU ARM 汇编基础

    ARM GNU汇编基础 0 前言 全文补充提醒: 笔者在阅读ARM官方文档及查阅实际的u-boot源码中的汇编代码后,发现了一些不同于ARM官方文档中的汇编语法,查阅相关资料后,才发现主要由于汇编器的 ...

  8. 2013-6-2 [转载自CSDN]如何入门Windows系统下驱动开发

    [序言]很多人都对驱动开发有兴趣,但往往找不到正确的学习方式.当然这跟驱动开发的本土化资料少有关系.大多学的驱动开发资料都以英文为主,这样让很多驱动初学者很头疼.本人从事驱动开发时间不长也不短,大概 ...

  9. windows驱动开发推荐书籍

    [作者] 猪头三 个人网站 :http://www.x86asm.com/ [序言] 很多人都对驱动开发有兴趣,但往往找不到正确的学习方式.当然这跟驱动开发的本土化资料少有关系.大多学的驱动开发资料都 ...

  10. 函数call相关[ASM]

    前言:   __cdecl:C/C++函数默认调用约定,参数依次从右向左传递,并压入堆栈,最后由调用函数清空堆栈,这种方式适用于传递参数个数可变的被调用函数,只有被调用函数才知道它传递了多少个参数给被 ...

随机推荐

  1. ubuntu环境下安装perf工具

    检查当前环境内核的版本,执行如下命令: uname -a 输出信息如下: Linux jackie-ubuntu 5.4.0-26-generic #30-Ubuntu SMP Mon Apr 20 ...

  2. RabbitMQ 01 概述

    什么是消息队列 进行大量的远程调用时,传统的Http方式容易造成阻塞,所以引入了消息队列的概念,即让消息排队,按照队列进行消费. 它能够将发送方发送的信息放入队列中,当新的消息入队时,会通知接收方进行 ...

  3. Qt数据结构-QString一:常用方法

    一.拼接字符串 拼接字符串有两种方法: +=  .  append QString s; s = "hello"; s = s + " "; s += &quo ...

  4. Minio架构简介

    简介 Minio是一个go编写基于Apache License v2.0开源协议的对象存储系统,是为海量数据存储.人工智能.大数据分析而设计,它完全兼容Amazon S3接口,十分符合存储大容量的非结 ...

  5. CentOS 安装openssh-6.XX

    安装openssh-6.0p1 1.安装依赖包 有遇到 报ZLIB有问题的,要安装以下包 rpm -ivh zlib-devel-1.2.3-3.* rpm -ivh libsepol-devel-1 ...

  6. 重新点亮linux 命令树————网络故障排除[十一五]

    前言 简单整理一下网络故障不可达命令. 正文 ping 是否能ping traceroute 追踪路由跳转 mtr 检查数据包是否丢失 nslookup telnet 端口是否可达 tcpdump 能 ...

  7. Linux基础-01:Linux命令的基本格式

    2.1.1 命令提示符 在CentOS 7操作系统中,Linux命令提示符就像是你与电脑交流的一个小标志,告诉你系统已经准备好接受你的指令了. 它通常会显示在你打开的终端窗口或控制台的最前面. 让我们 ...

  8. 3.CSS三种基本选择器

    三种选择器的优先级: id选择器 > class选择器 > 标签选择器 1.标签选择器:会选择到页面上所有的该类标签的元素 格式: 标签{} 1 <!DOCTYPE html> ...

  9. 37 手游基于 Flink CDC + Hudi 湖仓一体方案实践

    ​简介: 介绍了 37 手游为何选择 Flink 作为计算引擎,并如何基于 Flink CDC + Hudi 构建新的湖仓一体方案. 本文作者是 37 手游大数据开发徐润柏,介绍了 37 手游为何选择 ...

  10. Roslyn 通过 EmbedAllSources 将源代码嵌入到 PDB 符号文件中方便开发者调试

    本文来告诉大家如何在项目文件里面添加上 EmbedAllSources 属性,将自己的代码嵌入到 PDB 符号文件里面,让开发者们在调试的时候,可以看到库的源代码 是否记得 PDB 符号文件的作用?符 ...