Appearance
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记录段内偏移地址和段地址。
转移地址在内存中有两种格式:
- 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
- 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
数组
- 数组定义
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
- 数组访问
类似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
- 数组写入
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