大理寺少卿 发表于 2024-12-22 22:15:26

8086汇编(16位汇编)学习笔记09.宏汇编

宏汇编在文件中是当做关键字的,但是在bug中运行时并没有这些指令,这些关键词被称为伪指令,cpu并不认识他们,需要经过编译器转化成 cpu认识的代码,但是他多我们写代码帮助又很大

表达式

表达式中的求值是在程序链接时完成的,所以表达式中的各值必须是在汇编或链接期就能确定,也就是 说不能将寄存器或者变量运⽤于表达式。

算术表达式


| 运算符 | 意义 | 例⼦          |
| -------- | ------ | --------------- |
| +      | 加   | 65 + 32       |
| -      | 减   | size val - 54 |
| *      | 乘   | 23h * 65h   |
| /      | 除   | 98 / 45       |
| mod    | 取模 | 99 mod 65   |

```
mov ax, 54+65
mov ax, 65-48
mov ax, 65*78
mov ax, type byte * 78
mov ax, 78 mod 5
mov ax, 96 / 5
mov ax, size g_buf / type g_buf

;mov ax,bx+7   ;这个是错的,因为编译器无法得到结果,不知道bx的值
```

!(./notesimg/1652871699132-34b2446a-9e0a-4c15-999d-b4059f2db368.png)

逻辑运算符

逻辑运算即位运算,逻辑运算符与对应的指令助记符单词是相同的,当它们出现在操作码部分时是指 令,出现在操作数时是逻辑运算符


| 运算符 | 意义   |
| -------- | ---------- |
| and    | 位与   |
| or   | 位或   |
| not    | 按位取反 |
| xor    | 异或   |

```
mov ax, 5566h and 6655h
mov ax, 7788h or 8877h
mov ax, not 5566h
mov ax, 5566h xor 7788h
```

!(./notesimg/1652872024974-f92ffe0b-4146-4507-b22e-4b3a16b47269.png)

关系运算符

关系运算符的结果,如果结果为真,则所有位都置为1,即FFFF;否则所有位都置为0,即 .


| 运算符 | 英⽂                  | 意义      |
| -------- | ----------------------- | ------------- |
| EQ   | equal               | 等于        ==   |
| NE   | not equal             | 不等于        !=   |
| GT   | greater than          | ⼤于        >      |
| LT   | less than             | ⼩于        <      |
| GE   | greater than or equal | ⼤于等于 >= |
| LE   | less than or equal    | ⼩于等于 <= |

```
mov bx,88 eq 88    ;bx= 0FFFFh   因为结果为ture
mov bx,88 ne 88    ;bx= 0000h    因为结果为flase
mov bx,88 ge 88    ;bx= 0FFFFh   因为结果为ture
```

!(./notesimg/1652872630303-78c97344-e2c8-44e3-b315-4e1b41b06f2f.png)

### 标号

#### 匿名标号@@

- @@是匿名标号
- @b 向上查找最近的@@, b是back
- @f向下查找最近的@@ ,f是front

一般我们用 jmp 跳转你需要有标号,而且标号名必须唯一 ,但是如果 标号名是   @@ ,那么就可以重复,跳转用@b 或@f, @@的 跳转距离在段内是 没有限制的

!(./notesimg/1652873630277-df26cf73-fb96-4a53-b5f4-73a6eb555989.png)

#### 调整偏移量指令ORG

格式 ORG 偏移值

此指令后的下⼀个变量或指令从 偏移值 开始存放,即把一个变量或者指令放在指定的偏移内,可以提前,也可以延后

```
;这是栈段
stack segment stack
db 512 dup(0)
stackends

;这是数据段
data segment
g_buf db "hello world"

org20
g_buf1 db "hello world";表示 g_buf1 会从段偏移20的地方开始存放

org0
g_buf2 db "111111"   ;表示 g_buf2 会从段偏移0的地方开始存放,回覆盖 g_buf的数据

data ends

;这里是代码
CODE segment

START:
    assume ds:data
    mov ax, data
    mov ds, ax


    org 20h
    mov ax,4C00H; 带参数返回的退出   ;此处代码从20H 处开始存放,后面指令依次后推
    int 21h   ;使用21号中断
   
CODE ends

end START
```

!(./notesimg/1652874628115-dde37c51-74f2-47fd-93af-a113f6fd0952.png)

#### 当前地址指令$

- $伪指令代表当前指令或变量的地址
- 常⽤于计算缓冲区⻓度和获取当前ip值
- 可与 ORG 配合使⽤

```
data_seg segment
g_buf dw 10h dup(0)
g_len dw $ - offset g_buf;获取上⾯的g_buf的⻓度
org $ + 10h                ;下⼀个变量从当前地址+10h的偏移开始存放
g_w dw 65h
   
data_seg ends

CODE segment

START:

org 100h
mov ax, 5566h and 6655h ;此指令从代码段偏移100h开始存放
mov ax, $ ;获取 本条指令的地址
mov ax, $+4;获取 本条指令的地址+4
mov ax, 4c00h
int 21h
   
CODE ends
```

### 结构体   struc

#### 格式

结构体名 struc

```
;这⾥定义结构体成员
```


结构体名 ends

结构体使⽤<>来初始化

结构体可以通过变量名和寄存器来访问成员

结构体可以在定义时候初始化,也可以在后面初始化

结构作为局部变量无法赋初值

```
;定义结构体
Point struc
    m_wX dw 0
    m_wY dw 0
Point ends

;关键字struc,可以在定义时候赋初值
TagStu struc
    m_byte db 0
    m_w    dw 5656h
    m_arydb 8 dup(0)
    m_sz   db "hello"
    m_pt   Point <>      ;结构体里面嵌套结构体
TagStu ends

;堆栈
stack_seg segment stack
    db 512 dup(0)
stack_seg ends

;数据段
data_seg segment
    g_stu TagStu <66h, 7777h, "testtest", "66", <11h, 22h>>   ;初始化结构体
    g_ary Point 12 dup(<11h, 22h>)                            ;定义结构体数组
data_seg ends

;代码
code_seg segment

;结构体传参,直接传对象,它会将结构提对象入栈,每次入栈2字节(推荐用指针)
Foo0PROC far c stu:TagStu
    lea bx, stu

    mov stu.m_byte, 99h   ;修改结构体成员的值
    mov stu.m_w, 6666h      ;修改结构体成员的值

    ret
Foo0ENDP

;结构体传参,传对象指针,通过指针访问必须借助寄存器,不能直接用指针
Foo1 PROC far c pStu:ptr TagStu

    ;mov ax, m_w   ;指针,不能直接点使用,必须通过寄存器

    mov bx, pStu         ;将结构体指针赋值给bx

    ;通过结构体指针访问结构体成员
    assume bx:ptr TagStu   ;将bx解释为 TagStu 结构体指针,类似强转
    mov ax, .m_w         ;通过结构体指针访问成员
    assume bx:nothing      ;将bx 的 解释方式转回默认

    ret
Foo1 ENDP

;结构体作为局部变量,局部变量无法赋初值
foo PROC far c
    local @stu:TagStu   ;变量类型直接是结构体类型

    lea bx, @stu         ;获取变量的偏移地址
    mov al, @stu.m_byte;访问变量成员
    mov ax, @stu.m_w   ;访问变量成员

   ret
foo ENDP

START:
    assume ds:data_seg
    mov ax, data_seg
    mov ds, ax

    mov ax, g_stu.m_pt.m_wX   ;访问结构体内 结构体的的成员

    invoke Foo1, offset g_stu;结构体指针作为函数参数

    invoke Foo0, g_stu         ;结构体对象作为函数参数

    invoke foo               ;结构体作为局部变量

    mov al, g_stu.m_byte      ;访问结构体成员
    mov ax, g_stu.m_w         ;访问结构体成员
    lea bx, g_stu.m_ary       ;获取结构体成员的偏移地址
    mov byte ptr , 'a'    ;修改结构体成员的值

    mov ax, 4c00h;
    int 21h
code_seg ends
end START
```

!(./notesimg/1652878569539-52dfee2a-c7d8-4fd0-9146-cab4463410dc.png)

!(./notesimg/1652879157411-5d8c9bda-1f5a-4d52-a821-48bce6362bed.png)

### 宏

#### equ语句

作用:用来定义宏

不可以重命名

可⽤于常量和表达式

可⽤于字符串

可⽤于指令名,给指令取别名

可⽤于类型,给类型取别名

可⽤于操作数

```
;是无参宏,没有参数

;堆栈
stack_seg segment stack
    db 512 dup(0)
stack_seg ends

PI equ 314                  ;常量
SZNAME equ "hello world"    ;字符串
MYMOV equ mov               ;指令名
CHAR equ db               ;类型
INT16 equ dw                ;类型

;PI equ 6677h ;不允许再次定义

;数据段
data_seg segment
    g_sz CHAR SZNAME
    g_wINT16 PI
    g_w2 INT16 44h
data_seg ends

;代码
code_seg segment

START:
    assume ds:data_seg
    mov ax, data_seg
    mov ds, ax

    MYMOV ax, g_w

    mov ax, 4c00h;
    int 21h
code_seg ends
end START
```

!(./notesimg/1652880751415-1447cb63-abe8-4e01-8647-fc36cd1ccadb.png)

#### =语句

可以被修改

只能⽤于常数

```
COUNT2 = 100h   ;后跟数值
COUNT2 = 200h   ;可以再次赋值

;szTest = "hello world";错误,不能用于字符串,只能用于立即数(常量)

mov ax, COUNT2


;可以用来提高可读性

MYFUNC:
nX = 2
nY = 4
    push bp
    mov bp, sp
    sub sp, 10

    mov , ax
    mov , ax

    mov sp, bp
    pop bp
    ret
```

#### macro语句

###### 格式

宏名 macro [参数1][,参数2]...

```
宏体
```


endm

宏会在使⽤的地⽅展开

宏可以带参数

字符串拼接使⽤&

```
;堆栈
stack_seg segment stack
    db 512 dup(0)
stack_seg ends


CHAR equ db
INT16 equ dw

;内存到内存赋值宏
movm macro val1, val2
    push val1
    pop val2
endm

;字符串拼接
shift macro d, opt, count
    push cx
    mov cl, count
    sa&d opt, cl
    pop cx
endm


;数据段
data_seg segment
    g_wINT16 55H
    g_w2 INT16 44h
data_seg ends

;代码
code_seg segment


START:
    assume ds:data_seg
    mov ax, data_seg
    mov ds, ax


    ;内存到内存赋值
    movm g_w, g_w2
    movm g_w2, g_w

    shift r, ax, 2
    shift r, ax, 5
    shift l, bx, 4
    shift l, dx, 2


    mov ax, 4c00h;
    int 21h
code_seg ends

end START
```

!(./notesimg/1652881928524-a25f6836-2120-4d09-8f82-ec2b2bb193ae.png)

!(./notesimg/1652882072991-f33eb32e-9d3d-4fcc-9a16-f3575787937e.png)

### 多文件编译

#### 1.源文件

- 源文件后缀名为asm
- 每个源文件末尾需要有end

#### 2.头文件

- 汇编头文件后缀名为inc
- 头文件包含include xxx.inc
- 头文件防重复包含

```
ifndef SECOND_1
SECOND_1 equ 1
        Func1 proto far stdcall arg1 : word, arg2 : word
      extern g_dw : word
endif
```

#### 3.函数使用

函数在源文件定义,在头文件中声明即可。

#### 4.全局变量

全局变量在定义文件中必须使用public指明此变量为全局public变量名

全局变量在使用文件中必须使用extern 指明此变量来自外部文件 extern 变量:类型

```
ifndef HEADER_INC    ;防止重复包含
HEADER_INC equ 1

   extern g_w:word;声明明 g_w 是来自外部的全局变量

endif
```

```
public g_w    ;指名 g_w 是全局变量 ,不然默认本文件内才能使用(文件作用域)

data_seg segment
    g_w dw 0
data_seg ends

end
```

#### 5.编译

```
ml /c xx.asm yy.asm
link xx.obj yy.obj

ml *.asm
```

### 伪指定实现分支,循环

!(./notesimg/1652882979464-f89aaf33-46e9-46bc-b334-306a3d6686bf.png)

#### 分支

格式

格式1:

.IF condition    ;以英文“句号”开头  ;条件"condition"成立时所执行的指令序列

  指令序列   

.ENDIF

格式2:

.IF condition     ;条件"condition"不成立时所执行的指令序列

  指令序列1

.ELSE

  指令序列2   

.ENDIF

格式3:

.IF condition1

  指令序列1

.ELSEIF condition2      ;条件"condition2"成立时所执行的指令序列

  指令序列2    

.ENDIF

其中:条件表达式“condition”的书写方式与C语言中条件表达式的书写方式相似,也可用括号来组成复杂的条件表达式。

条件表达式中可用的操作符有:==(等于)、!=(不等)、>(大于)、>=(大于等于)、<(小于)、<=(小于等于)、&(位操作与)、!(逻辑非)、&&(逻辑与)、||(逻辑或)等。

```
;堆栈
stack_seg segment stack
    db 512 dup(0)
stack_seg ends

;数据段
data_seg segment

data_seg ends

;代码
code_seg segment

START:
    assume ds:data_seg
    mov ax, data_seg
    mov ds, ax

    mov g_w, 8

    .if ax >= bx
      xor ax, ax
    .elseif ax <= bx
      xor bx, bx
    .else
      xor cx, cx
    .endif

    mov ax, 4c00h;
    int 21h
code_seg ends

end START
```

1、WHILE型循环伪指令

.WHILEcondition    ;条件"condition”成立时所执行的指令序列
  循环体的指令序列    
.ENDW

其中:.ENDW与前面的.WHILE相匹配,它标志着其循环体到此结束。

如果条件表达式“condition”在循环开始时,就为“假”(false),那么,该循环体一次也不会被执行。

2、REPEAT型循环伪指令

.REPEAT
  循环体的指令序列
.UNTIL condition.REPEAT
  循环体的指令序列
.UNTILCXZ

REPEAT型循环在执行完循环体后,才判定逻辑表达式condition的值。若该表达式的值为真,则终止该循环,并将执行伪指令.UNTIL后面的指令,否则,将向上跳转到伪指令.REPEAT之后的指令,为继续执行其循环体作准备

循环终止 break 和继续 continue

(1)、终止循环伪指令
.BREAK

.BREAK.IF condition

该伪指令用来终止包含它的最内层循环。前者是无条件终止循环,后者是仅当逻辑表达式condition为真时,才终止循环。
.WHILE1 .REPEAT

.BREAK .IF condition


.BREAK .IF condition


ENDW .UNTIL 0

对于以上二个循环,如果没有指令来终止循环的话,它们都将进入死循环状态,但如果在该层循环体内,存在伪指令“.BREAK .IF condition”的话,那么,当逻辑表达式condition为真时,该循环就会被终止了。

(2)、循环继续伪指令

.CONTINUE
.CONTINUE .IF condition
页: [1]
查看完整版本: 8086汇编(16位汇编)学习笔记09.宏汇编