Skip to content

x86汇编学习

loop指令

  loop指令的作用是将cx寄存器减1,并判断是否为0,如果不为0则跳转到指定地址,用于循环。

asm
; 例:计算2的5次方
assume cs:code
code segment
    start:
        mov cx,5 ; 循环次数
        mov ax,1 ; 乘数
        mov bx,2 ; 结果
        loop1:
            mul bx ; ax = ax * bx
            loop loop1 ; cx--,如果不为0则跳转到loop1
        ; 循环结束,ax中存放2的5次方
        ; 结果为32,ax=20h
code ends
end start

代码复制

  适用情况: 将当前段的代码转移到另一个段中执行。

asm
assume cs:code 
code segment
    s: 
        mov ax,bx ; 要被转移的代码,长度为2字节
        mov si,offset s ; 代码段的偏移地址
        mov di,offset s0 ; 要转移到的段的偏移地址
        mov dx,cs:[si] 
        mov cs:[di],dx 
    s0:
        nop
        nop
code ends
end s

jmp指令

asm
    s:
        nop
    ; 可以直接在debug中编写
    jmp 100
    jmp 0760h:100
    jmp ax

    ; 无法直接在debug中编写
    jmp short s ; 短跳转,跳转到s段的偏移地址,ip被修改

  jmp short执行的是段内转移,对IP修改范围为-128~127,并非直接跳转到相应的地址,而是通过偏移量先查询到相应偏移地址再实现跳转。

asm
    jmp near ptr s ; 段内近转移,会报错
    jmp far ptr s ; 段间远转移,不会报错
    db 256 dup(0) 
    s:
        nop

   jmp near ptr范围和jmp short相同,只记录段内偏移地址,jmp far ptr记录段内偏移地址和段地址。

  转移地址在内存中有两种格式:

  1. jmp word ptr 内存单元地址(段内转移)
asm
mov ax,012H
mov ds:[0],ax
jmp word ptr ds:[0] ; IP=0123H

; 或者
mov ax,0123H
mov [bx],ax
jmp word ptr [bx] ; IP=0123H
  1. jmp dword ptr 内存单元地址(段间转移)
asm
mov ax,0123H
mov ds:[0],ax ; 偏移地址
mov word ptr ds:[2],0 ; 段地址
jmp dword ptr ds:[0] ; CS=0,IP=0123H

数组

  1. 数组定义
asm
assume cs:code,ds:data

; 可以用变量名命名数组
; 注意不能带冒号,带冒号则视为代码
data segment
    arr dw 12,34 
    arr2 db 'hello'
data ends

; 可以用变量名访问数组,type查询数组类型
code segment
    start:
        mov ax,type arr ; ax=0002h
        mov ax,type arr2 ; ax=0001h
code ends
end start
  1. 数组访问

  类似C语言。

asm
assume cs:code
code segment
    arr dw 12,34,56,78h
    arr2 db 'hello'
    start:
        mov ax,arr[0] ; ax = 01H
        ; 或者
        mov si, offset arr
        mov ax, cs:[si] ; ax = 01H
        ; 强制类型转换
        mov ax,word ptr arr2[0] ; byte转化为word
code ends
end start

  如果数据存储在数据段中的情况:

asm
assume cs:code,ds:data
data segment
    arr dw 12,34,56,78h
    arr2 db 'hello'
data ends
code segment
    start:
        mov ax,data ; 先将数据段地址存入ax中
        mov ds,ax ; 再将数据段地址赋值给ds
        mov ax,arr[0] ; ax = 01H
code ends
end start
  1. 数组写入
asm
assume cs:codev
code segment
    arr db 12,34,56,78h
    start:
        mov al,arr[3]
        mov arr[0],al
        mov al,arr[0] ; al = 78h
code ends
end start

第一个程序: 输出hello world

asm
assume cs:code,ds:data
data segment
    msg db 'hello world',10,'$' ; 10为换行符,字符串以$结束
data ends
code segment
    start:
        mov ax,data ; 先将数据段地址存入ax中
        mov ds,ax ; 再将数据段地址赋值给ds
        mov dx, offset msg ; dx指向字符串的首地址
        mov ah,9
        int 21h ; 调用DOS的9号功能,输出字符串

        ; 退出程序
        mov ah, 4Ch
        int 21h ; 调用DOS的4Ch功能,退出程序
code ends
end start

实战:数组求和

asm
assume cs:code, ds:data

data segment
    arr db 1,2,3,4,10,20,30,40
    result_msg db 'The sum is: $'
    buffer db 6 dup('$')  ; 足够存储一个16位整数 (最大65535)
data ends

code segment
start:
    ; 初始化段寄存器
    mov ax, data
    mov ds, ax

    ; 数组求和
    mov ax, 0
    mov bx, 0
    mov cx, 8       ; 循环计数
for:
    add al, arr[bx] ; 或者 add al, ds:arr[bx]
    adc ah, 0       ; 处理进位
    inc bx
    loop for

    ; 将 AX 转换为字符串并存储在 buffer 中
    push ax      ; 保存 AX 的值
    mov si, offset buffer
    call convert_to_string
    pop ax       ; 恢复 AX 的值

    ; 输出 "The sum is: "
    mov dx, offset result_msg
    mov ah, 09h
    int 21h

    ; 使用 DOS 中断输出字符串
    mov dx, offset buffer
    mov ah, 09h  ; DOS 功能号,输出字符串
    int 21h

    ; 程序终止
    mov ah, 4Ch
    int 21h

; 子程序:将 AX 寄存器的值转换为字符串
convert_to_string proc near
    push bx
    push cx
    push dx
    push si

    mov cx, 0      ; 用于记录数字的位数
    mov bx, 10     ; 除数,用于十进制转换

convert_loop:
    mov dx, 0      ; DX:AX 存储被除数
    div bx         ; AX = AX / 10, DX = AX % 10
    add dl, '0'    ; 将余数转换为 ASCII 码
    push dx        ; 将 ASCII 码压入栈中
    inc cx         ; 增加数字位数计数器
    cmp ax, 0      ; 检查商是否为 0
    jnz convert_loop ; 如果商不为 0,继续循环

    ; 从栈中弹出数字并存储到 buffer 中
output_loop:
    pop dx         ; 弹出栈顶的 ASCII 码
    mov [si], dl   ; 存储到 buffer 中
    inc si         ; 增加 buffer 指针
    loop output_loop

    mov byte ptr [si], '$' ; 字符串结束符

    pop si
    pop dx
    pop cx
    pop bx
    ret
convert_to_string endp

code ends
end start

实战: 数组拷贝

asm
assume cs:code, ds:data

data segment
    arr db 1, 2, 3, 4, 5
    res db 5 dup(0)  
data ends

code segment
start:
    mov ax, data
    mov ds, ax

    mov bx, 0
    mov cx, 5

loop_start:
    mov al, arr[bx]
    mov res[bx], al
    inc bx
    loop loop_start

    mov ah, 4ch
    int 21h

code ends
end start

实战: 使用栈进行数组反转

asm
comment* 

*
assume cs:code,ds:data,ss:stack

data segment    
    arr dw 1111h,2222h,3333h,4444h,5555h ; push 默认操作 1 word 数据
    res dw 5 dup(0)
data ends   

stack segment
    db 100 dup(0)
stack ends

code segment
start:
    mov ax, data
    mov ds, ax
    mov ax, stack
    mov ss, ax
    mov sp, 100h

    mov bx, 0
    mov cx, 5
for:
    push arr[bx]
    add bx, 2 ; 每次加 2 是因为 arr 是 word 类型
loop for

    mov bx, 0
    mov cx, 5

for2:
    pop res[bx]
    add bx, 2
loop for2

    mov ax, 4c00h
    int 21h

code ends
end start

实战:斐波那契那数列求和

tips

这里用的是RV32I

asm
; 斐波那契数列
.global __start

.section .data

.section .text

__start:
    # 读取输入参数 n 到x10
    li x10, 5
    ecall

    # 初始化
    li x13, 0          ; 前前一项
    li x14, 1          ; 前一项
    li x15, 1          ; 循环计数器,从1开始

    # 特殊情况处理
    li x11, 0
    beq x10, x11, fib_end   # n==0,结果为0
    li x11, 1              # 默认结果为1
    beq x10, x11, fib_end  # n==1,结果为1

# 循环判断
loop_judege:
    blt x15, x10, fib_loop
    j fib_end

fib_loop:
    add x16, x13, x14      # x16 = f(n-2) + f(n-1)
    mv x13, x14            # f(n-2) = f(n-1)
    mv x14, x16            # f(n-1) = f(n)
    addi x15, x15, 1       # i++
    j loop_judege

fib_end:
    # x14 存储第 n 项
    mv x11, x14

    # 打印结果
    li x10, 1            # ecall 打印整数
    ecall # 输出x11

    # 结束
    li x10, 10
    ecall