ComputerSystems
大约 8 分钟
ComputerSystems
目录
信息访问
mov(数据传送指令)
把不同的指令划分成指令类
(每一类指令都执行相同的操作,主要区别在与操作数大小不同)
(表格中“效果”一列的前者是源操作数
,后者是目的操作数
)
mov
存储单元之间的数据传输关系(mov)
mov 目的操作数, 源操作数 # 位宽必须一致
操作对象有4种
- 通用寄存器:AX、BX等
- 段寄存器:CS、DS等
- 内存单元
- 立即数、常数
实战demo
mov.asm
# 立即数相关
mov 0xb700, 0xb800 ; mov 立即数, 立即数 error: invalid combination of opcode and operands
mov [0x01], 0xb ; mov 内存单元, 立即数 error: operation size not specified
mov byte [0x01], 0xb800 ; mov 内存单元, 立即数 warning: byte data exceeds bounds [-w+number-overflow]
mov word [0x01], 0xb800 ; mov 内存单元, 立即数 成功
# 内存单元相关
mov [0x01], [0x02] ; mov 内存单元, 内存单元 error: invalid combination of opcode and operands
mov ax, [0x02] ; mov 寄存器, 内存单元 成功
mov [0x03], ax ; mov 内存单元, 寄存器 成功
mov ds, [0x04] ; mov 段寄存器, 内存单元 成功
mov [0x05], ds ; mov 寄存器, 段寄存器 成功
# 寄存器相关
mov ax, bx
mov cx, dl ; mov 16位寄存器, 8位寄存器 error: invalid combination of opcode and operands
mov cs, ds ; mov 段寄存器, 段寄存器 error: invalid combination of opcode and operands
mov衍生指令
- 四条指令都执行相同的操作,主要区别在与操作的数据大小不同
movq
是复制双字再符号扩展为四字,而movabsq
才是真正的复制四字- x86-64限制从内存直接复制到内存(源和目的操作数不能同时为存储器类型)
mov指令 | 效果 | 描述 |
---|---|---|
movb | D < S | 传送字节(B) |
movw | D < S | 传送字(2B) |
movl | D < S | 传送双字(4B) |
movq | D < S | 传送四字(8B) |
movabsq | R < I | 传送绝对的四字 |
MOVS / MOVZ类(较小源值复制到较大目的)
- 没有
movzlq
原因:根据复制时的规则,当复制双字时就已经会自动零扩展,即movl
的效果本质上就是movzlq
- movz和movs指令末两位都是
大小指示符
,而cltq没有操作数(操作数默认:%eax, %rax所对应的寄存器位置为第一个(返回值))
movz指令 | 效果(零扩展) | 描述 | movs指令 | 效果(符号扩展) | 描述 |
---|---|---|---|---|---|
movzbw | R < 零扩展(S) | 将做了零扩展的字节传送到字 | movsbw | R < 符号扩展(S) | 将做了符号扩展的字节传送到字 |
movzbl | R < 零扩展(S) | 将做了零扩展的字节传送到双字 | movsbl | R < 符号扩展(S) | 将做了符号扩展的字节传送到双字 |
movzwl | R < 零扩展(S) | 将做了零扩展的字传送到双字 | movswl | R < 符号扩展(S) | 将做了符号扩展的字传送到双字 |
movzbq | R < 零扩展(S) | 将做了零扩展的字节传送到四字 | movsbq | R < 符号扩展(S) | 将做了符号扩展的字节传送到四字 |
movzwq | R < 零扩展(S) | 将做了零扩展的字传送到四字 | movswq | R < 符号扩展(S) | 将做了符号扩展的字传送到四字 |
(movl) | R < 零扩展(S) | 将做了零扩展的双字传送到四字 | movslq | R < 符号扩展(S) | 将做了符号扩展的双字传送到四字 |
—— | —— | —— | cltq | %rax < 符号扩展(%eax) | 把==%eax符号扩展到%rax== |
数据传送示例
C语言的所谓指针
就是地址,比如long *xp
作第一个参数,那么:
xp
,汇编操作数为%rdi
,保存在寄存器中。其值是一个地址*xp
,汇编操作数为(%rdi)
,保存在内存中。(调用*xp的过程即为间接寻址的过程)&*xp
即取得保存在内存中的*xp
的地址,其值等于xp
&xp
,汇编操作为leaq %rdi,?
(不确定能不能读寄存器地址),其值是%rdi
(这是一个文本而不是操作数)- 访问寄存器比访问内存要快得多
内存转内存的过程*dp = (类型) *sp
,分(1)*sp传送到寄存器、(2)寄存器传送到*dp两步
- *sp类型小于*dp时:(1)进行扩展传送,(2)普通传送
- *sp为T:movs(符号扩展)
- *sp为U:movz(零扩展)
- *sp类型大于*dp时:(1)进行普通传送,(2)取寄存器低位传送
- T2U和U2T
- 这里不用管T2U还是U2T,汇编级不区分TU,区分两种扩展的本质是使数据不改变而已
- 即这一步只有大小转换没有类型转换!也解释了为什么C语言类型转换+大小变换时,先改变大小再改变类型
push/pop(数据传送指令)
压入和弹出栈数据
后进先出,栈指针%rsp
保存着栈顶元素的地址
指令 | 描述 | 效果 | 等价汇编指令 |
---|---|---|---|
pushq S | 将四字压入栈 | R[%rsp] < R[%rsp]-8 M[R[%rsp]] < S | subq $8,%rsp movq %rbq, (%rsp) |
popq D | 将四字弹出栈 | D < M[R[%rsp]] R[%rsp] < R[%rsp]+8 | movq (%rsp), %rax addq $8, %rsp |
画图时是倒过来画的(原因:进程的虚拟地址空间设计中(地址从下往上递增),顶层是内核虚拟内存,往下是用户栈,该栈往下的区域是可扩展区域)
- 栈的走向跟数据段或代码段不一样
- 数据段和代码段:从内存低处向高处进行。例如代码从开始0x7c00,下条可能为0x7c02
- 栈段:从内存高处向低处进行。push操作让sp减少,pop操作让sp增大
in/out
in dest目的(al/ax) source源(dx/imm8) ; 读入指令。若使用imm8只能访问0~255号端口,若使用dx则可以访问全部65536个端口
out dest目的(dx/imm8) source源(al/ax) ; 写入指令。参数相反!
实战:读取硬盘 - 原理
原理:LBA (Logical Block Addressing,逻辑块寻址)
LBA参数
- LBA28:表示有2^28个扇区,每个扇区512字节,共128GB
- LBA48:共256T,也是现在的接口
- 为了方便学习,该demo使用LBA28接口,接口如下:除了0x1F016位,其余每个端口8位
- 0x1F7,告诉硬盘你要读还是写
- 0x20:读硬盘,0x30:写硬盘
- index-3:DRQ,0:未就绪,1:已就绪
- index-7:BSY,0:硬盘闲,1:硬盘忙
- 0x1F6,从哪个逻辑扇区开始读 24~27,其中空余出的4位要标识硬盘号和读写模式
- index-4选择硬盘号,0:主硬盘,1:从硬盘
- index-6选择读写模式,0:CHS (Cylinders Heads Sectors,柱面磁头扇区),1:LBA (Logical Block Addressing,逻辑块寻址)
- 0x1F5,从哪个逻辑扇区开始读 16~23
- 0x1F4,从哪个逻辑扇区开始读 8~15
- 0x1F3,从哪个逻辑扇区开始读 0~7
- 0x1F2,要读几个扇区
- 0x1F1
- 0x1F0,数据端口,16位
- 0x1F7,告诉硬盘你要读还是写
流程
- 告诉硬盘要读几个扇区,将该数值写入到0x1f2端口
- 告诉硬盘从哪个逻辑扇区开始读,LBA28模式下,要写入28位的逻辑扇区号,分成4份写入0x1f3~1f6这4个端口
- 告诉硬盘你要读还是写,写到0x1f7端口。然后硬盘会检查0x1f7端口看是否已经就绪
- 最后读取硬盘
实战:读取硬盘 - 代码
readhdd.asm
HDDPORT equ 0x1f0 ; 硬盘端口号
NUL equ 0x00 ; 空
SETCHAR equ 0x07 ; 设置字符属性
VIDEOMEM equ 0xb800 ; 显卡内存
STRINGLEN equ 0xffff ; 字符串长度(循环次数)
sectioon code align=16 vstart=0x7c00
mov si, [READSTART] ; 高位。扇区号28位,需要两个16位寄存器来装
mov cx, [READSTART+0x02] ; 低位
mov al, [SECTORNUM] ; 读取的扇区数
push ax
mov ax, [DESTMEN]
mov dx, [DESTMEN+0x02]
mov bx, 16
div bx
mov ds, ax
xor di, di
pop ax
call ReadHDD
xor si, si
call PrintString
jmp End
ReadHDD:
push ax
push bx
push cx
push dx
mov dx, HDDPORT+2 ; 即0x1f2
out dx, al
mov dx, HDDPORT+3
mov ax, si
out dx, al
mov dx, HDDPORT+4
mov al, ah
out dx, al
mov dx, HDDPORT+5
mov ax, cx
out dx, al
mov dx, HDDPORT+6
mov al, sh
out dh, 0xe0
or al, ah
out dx, al
mov dx, HDDPORT+7
mov al, 0x20
out dx, al
.waits: ; 标志,等待
in al, dx
and al, 0x88
cmp al, 0x08
jnz .waits
mov dx, HDDPORT
mov cx, 256
.readwrod:
in ax, dx
mov [ds:di], ax
add di, 2
or ah, 0x00
jnz .readword
.return:
pop dx
pop cx
pop bx
pop ax
ret
PrintString: ; 函数
.setup: ; 标志位
mov ax, VIDEOMEM; 显卡内存,要输出的位置
mov es, ax ; 扩展段寄存器
mov bh, SETCHAR ; 设置字符属性
mov cx, STRINGLEN; 字符串长度(循环次数)
.printchar: ; 循环体
mov bl, [ds:si] ; 取第一个字符到bl寄存器,并移动指针
inc si ; -
mov [es:di], bl ; 写入到显存,并移动指针
inc di ; -
mov [es:di], bh ; -
inc di ; -
or bl, NUL ; 判断是否循环结束
jz .return ; 若是则跳出循环
loop .printchar
.return: ; 标志位
ret ; 函数结束
READSTART dd 10
SECTORNUM db 1
DESTMEN dd 0x10000
End: jmp End
times 510-($-$$) db 0
db 0x55, 0xaa
验证
将下面这段汇编代码编译成bin文件并使用fixVhdw将其写入到第10扇区(fixVhdw后面有个参数是 “起始LBA扇区号”)
Data db 'Hi, I come from hard disk drive!'
db 0x00
最后运行输出
Hi, I come from hard disk drive!