汇编指令初步

汇编指令

mov 和 add 指令

image-20250308140124584

转移指令 jmp

JMP 指令是汇编语言中的 无条件转移指令,用于将程序的执行流程跳转到指定的目标地址(代码位置)。当 JMP 指令被执行时,CPU 会将目标地址加载到 指令指针寄存器(IP/EIP/RIP) 中,从而改变程序的执行路径。

下面是 jmp 指令的用法:

  • jmp 指令用于同时修改 CS 和 IP 的内容

    • 语法:jmp 段地址:偏移地址

      • jmp 2AE3:3
      • jmp 3:0B16
    • 功能:用指令给出的段地址修改 CS, 偏移地址修改 IP

  • 仅修改 IP 的内容

    • 语法:jmp 某一合法寄存器
      • jmp ax(类似于 mov IP, ax)
      • jmp bx
    • 功能:直接跳转到 CS:某一寄存器 指定的地址

举例如下:

image-20250308140135916


汇编语言程序设计

首先给出一段完整可执行的汇编语言程序:

assume cs:codesg
codesg segment
    mov ax,0123H
    mov bx,0456H
    add ax,bx
    add ax,ax
    mov ax,4c00h
    int 21h
codesg ends
end

该代码的部分:

  1. 伪指令:编译器执行的指令,没有对应机器码
assume cs:codesg
codesg segment


codesg ends
end
  1. 汇编指令:编译为机器码的指令
mov ax,0123H
mov bx,0456H
add ax,bx
add ax,ax
mov ax,4c00h
int 21h

image-20250308140148885

三种伪指令

image-20250308140200461

汇编语言代码示例

编写一个计算 2^3 的代码:

assume cs:asc
abc segment
	mov ax,2
	add ax,ax
	add ax,ax
	
	mov ax,4c00h
	int 21h
abc ends
end

代码解释:

  1. 伪指令

    • assume cs:asc:指令告诉汇编器当前代码段使用的段寄存器是 CS,且段名为 asc; assume 声明仅用来表明段寄存器的逻辑关联,方便汇编器生成正确的代码。
    • abc segment:声明一个名为 abc 的代码段。
  2. 代码段

    • 一次 mov 和两次 add 实现了 2^3 的计算
  3. 程序终止

    • mov ax,4c00h:用于将 4C00h 加载到寄存器 AX,该值是 DOS 系统调用的 int 21h功能号,用于程序终止
    • int 21h调用中断服务例程,执行功能号 4Ch

[…]和(…)的语法规定

在汇编语言中,方括号通常用于表示内存地址的访问

语法:

MOV AX, [1234h]  ; 将内存地址 1234h 处的值加载到 AX 寄存器
MOV [BX], AL     ; 将 AL 的值存储到由 BX 指定的内存地址

在某些汇编器(如 AT&T 语法)中,圆括号用于内存操作的间接寻址,特别是在 x86 AT&T 汇编语法中。

圆括号(…)不属于汇编语言语法,只是表达汇编指令的一个助记符,它表示一个内存单元或者寄存器中的内容

区分有无[ ]:

  • 有[]时:MOV AX, [BX] 表示将 BX 寄存器中存储的**地址位置的值(即内存中的数据)**加载到 AX 寄存器中
  • 无[]时:直接使用寄存器的值。比如,MOV AX, BX 代表将寄存器 BX 的值复制到寄存器 AX

可以用 C++ 语法来类比理解汇编语言中寄存器有无方括号的区别。

  1. 无方括号:类似于直接使用变量的值。

    cpp复制编辑int a = 5;
    int b = 10;
    

    在汇编中,相当于 MOV AX, BX,直接将 BX 寄存器的值赋给 AX 寄存器。

  2. 有方括号:类似于访问指针所指向的内存中的数据。

    int a = 5;
    int* ptr = &a;  // 指针 ptr 指向 a
    int b = *ptr;   // 通过指针访问 a 的值
    

    在汇编中,相当于 MOV AX, [BX],假设 BX 存储的是某个内存地址,那么 [BX] 表示访问该内存地址上的数据。

Loop指令

语法:loop 标号

功能:实现循环(计数型循环)

CPU执行Loop指令的操作:

  • (cx)=(cx)-1;
  • 判断cx中的值:
    • 不为零则转至标号处执行程序
    • 如果为零则向下执行

示例代码:

assume cs:code        ; 告诉汇编器,代码段寄存器cs指向名为'code'的段
code segment          ; 定义一个代码段,命名为'code'
    mov ax, 2         ; 将立即数2加载到AX寄存器中
    mov cx, 11        ; 将立即数11加载到CX寄存器中,作为循环计数器
    
s:                    ; 标签's',标记循环开始的位置
    add ax, ax        ; 将AX寄存器中的值加上自身(即乘以2)
    loop s            ; CX寄存器减1,并且如果CX不为零,则跳转到标签's'

    mov ax, 4c00h     ; 将立即数4C00h加载到AX寄存器中,准备调用DOS中断来正常退出程序
    int 21h           ; 调用DOS中断21h,功能号在AH寄存器中指定(这里是4Ch,表示程序终止)

code ends             ; 结束代码段定义
end                   ; 汇编结束指令

该程序的主要功能是在AX寄存器中计算 2^11 的值(即2048),并通过DOS中断21h正常退出程序

image-20250308140216339

例:实现123*236的一个汇编程序:

assume cs:code
code segment 
	mov ax,0 ;初始化结果为0,存在ax中
	mov cx,236
	
s:	
	add ax,123
	loop s
code ends
end

结合[…]语法的示例代码:

assume cs:code
code segment
	mov ax,0ffffh
	mov ds,ax
	mov bx,6
	mov al,[bx]
	mov ah,0
	
	mov dx,0
	mov cx,3
s:	add dx,ax
	loop s
	
	mov ax,4c00h
	int 21h
code ends
end

mov al,[0]的异常情况和段前缀的应用

mov al,[0]的功能本应该是将偏移地址为0的存储单元的内容赋值给寄存器al,在编译并链接后,debug程序发现该语句被翻译成了mov al ,00将0赋值给al

image-20250308140223398

我们可以使用段前缀的方式,即在[data]前显式写上段寄存器

语法:

mov ax, DS:[1234h]  ; 使用数据段寄存器 DS
mov ax, ES:[1234h]  ; 使用额外段寄存器 ES

连续内存单元的求和(ffff:0~ffff:b)

code segment                ; 定义代码段
         
         mov  ax, 0ffffh    ; 将最大16位值 (0xFFFF) 加载到 AX 寄存器
         mov  ds, ax        ; 将 AX 中的值赋给 DS (数据段寄存器),设置数据段为 0xFFFF

         mov  ax, 0         ; 初始化 AX 寄存器为 0,用于中间计算
         mov  dx, 0         ; 初始化 DX 寄存器为 0,用于累加结果
         mov  cx, 12        ; 初始化循环计数器 CX 为 12,准备执行12次循环

    s:                      ; 定义标签 s,表示循环的开始位置
         mov  al, [bx]      ; 将 BX 寄存器指向的内存单元值加载到 AL(低8位寄存器)
         mov  ah, 0         ; 清空 AH(高8位寄存器),确保 AX 只包含 AL 的值
         add  dx, ax        ; 将 AX 的值累加到 DX 中
         inc  bx            ; BX 自增 1,指向下一个内存单元
         loop s             ; 循环 CX 次,每次减 1,直到 CX = 0 跳出循环

         mov  ax, 4c00h     ; 终止程序,设置 AX = 4C00h,其中 4Ch 是退出功能号
         int  21h           ; 调用中断 21h,终止程序
code ends                   ; 结束代码段
end                             ; 程序结束

利用段前缀实现连续内存单元的拷贝

注:es附加段寄存器,作为额外的数据段寄存器,通常用于字符串操作或其他需要跨段数据传输的情况

assume cs:code               ; 假设代码段寄存器 CS 指向名为 'code' 的段
code segment                 ; 定义一个代码段,命名为 'code'

    ; 设置数据段寄存器 DS 为 0FFFFh
    mov ax, 0ffffh           ; 将立即数 0FFFFh 加载到 AX 寄存器中
    mov ds, ax               ; 设置 DS 寄存器为 0FFFFh,这意味着我们将访问 0FFFF:0000 开始的内存区域

    ; 设置附加段寄存器 ES 为 0020h
    mov ax, 0020h            ; 将立即数 0020h 加载到 AX 寄存器中
    mov es, ax               ; 设置 ES 寄存器为 0020h,这意味着我们将写入 0020:0000 开始的内存区域

    ; 初始化偏移地址 BX 和循环计数器 CX
    mov bx, 0                ; 将立即数 0 加载到 BX 寄存器中,作为初始偏移地址
    mov cx, 12               ; 设置循环计数器 CX 为 12,表示要复制 12 个字节

s:                           ; 标签 's',标记循环开始的位置
    ; 从 DS:BX 处读取一个字节的数据到 DL 寄存器
    mov dl, [bx]             ; 从内存地址 DS:BX 处读取一个字节的数据到 DL 寄存器

    ; 将 DL 寄存器中的数据写入 ES:BX 处
    mov es:[bx], dl          ; 将 DL 寄存器中的数据写入内存地址 ES:BX 处

    ; 增加偏移地址 BX
    inc bx                   ; BX = BX + 1

    ; 循环控制
    loop s                   ; CX 寄存器减 1,并且如果 CX 不为零,则跳转到标签 's'

    ; 程序终止
    mov ax, 4c00h            ; 设置 AX 寄存器的值为 4C00h,准备调用 DOS 中断来正常退出程序
    int 21h                  ; 调用 DOS 中断 21h,功能号 4Ch 表示程序终止

code ends                    ; 结束代码段定义
end                          ; 汇编结束指令

在代码段中使用数据

实际上,在汇编语言中直接指明地址是非常危险的行为,现代操作系统都会为每个进程动态分配内存空间,如果手动修改了关键地址(如内存空间)的值,可能会导致访问冲突,甚至是程序崩溃

此外,硬编码的地址难以理解,在后续维护中不易看出该地址数据的意义

例题如下:

image-20250308140234493

  • dw关键字可以定义数据,每一个数据为一个字,占两个字节,所以存储偏移地址的寄存器bx每次要+=2

这里如果直接运行程序,代码段的起始地址在0010,而不是起始地址,如果想运行程序需要在Debug下将IP设置为10h,令CS:IP指向程序中的第一条指令,才能正常运行

我们可以如下改进:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

利用start标号来指定程序运行的第一条指令,后面end start除了启到终止程序的作用以外,还向编译器告知程序运行的入口

基于此,我们可以这样安排一段基本程序的框架:

assume cs:code
code segment
	
	;定义数据
	
	
start:
	
	;定义代码
	
	
	
code ends
end start

在代码段中使用栈

之前我们使用栈段和栈顶寄存器sssp将一段内存定义为栈,由于用到了真实的物理地址,而不是操作系统分配的内存空间,这种做法也应该抛弃

我们可以在代码段中定义栈,示例代码如下:

assume cs:codesg
codesg segment
    dw 0123H, 0456H, 0789H, 0abcH, 0defH, 0fedH, 0cbaH, 0987H
    dw 0,0,0,0,0,0,0,0,0,0,0,0

start:
    mov ax, cs
    mov ss, ax
    mov sp, 30h
    
    mov bx,0
    mov cx,8
s:	push cs:[bx]
	add bx,2
	loop s
	
	mov bx,0
	mov cx,8
s0:	pop cs:[bx]
	add bx,2
	loop s0
	
	mov ax,4c00h
	int 21h
	
codesg ends
end start

代码解释:

  • 数据段定义

    • 前8个单元存储数据:0123H0987H
    • 后12个单元初始化为 0,用于存储处理后的数据。
  • 初始化寄存器

    • 设置栈段 (SS) 为代码段 (CS),栈顶指针 (SP) 为 30h
  • 入栈过程

    • 从偏移 0-15H 的8个数据依次压入栈。
    • 每次入栈后,BX 加 2 指向下一个数据。
  • 出栈过程

    • 从栈中依次弹出数据,覆盖原来的偏移 0-15H 数据。
  • 程序结束

    • 使用中断 int 21h 正常退出程序。

      add bx,2
      loop s0

      mov ax,4c00h
      int 21h

codesg ends
end start


代码解释:

- **数据段定义**
  - 前8个单元存储数据:`0123H` 到 `0987H`。
  - 后12个单元初始化为 `0`,用于存储处理后的数据。
- **初始化寄存器**
  - 设置栈段 (`SS`) 为代码段 (`CS`),栈顶指针 (`SP`) 为 `30h`。
- **入栈过程**
  - 从偏移 `0-15H` 的8个数据依次压入栈。
  - 每次入栈后,`BX` 加 2 指向下一个数据。

- **出栈过程**
  - 从栈中依次弹出数据,覆盖原来的偏移 `0-15H` 数据。

- **程序结束**
  - 使用中断 `int 21h` 正常退出程序。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值