跳至主要內容

ComputerSystems

LincZero大约 8 分钟

ComputerSystems

目录

信息访问

mov(数据传送指令)

把不同的指令划分成指令类(每一类指令都执行相同的操作,主要区别在与操作数大小不同)

(表格中“效果”一列的前者是源操作数,后者是目的操作数

mov

存储单元之间的数据传输关系(mov)

mov 目的操作数, 源操作数 # 位宽必须一致

操作对象有4种

  • 通用寄存器:AX、BX等
  • 段寄存器:CS、DS等
  • 内存单元
  • 立即数、常数

image-20211110001421862

实战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指令效果描述
movbD < S传送字节(B)
movwD < S传送字(2B)
movlD < S传送双字(4B)
movqD < S传送四字(8B)
movabsqR < I传送绝对的四字

MOVS / MOVZ类(较小源值复制到较大目的)

  • 没有movzlq原因:根据复制时的规则,当复制双字时就已经会自动零扩展,即movl的效果本质上就是movzlq
  • movz和movs指令末两位都是大小指示符,而cltq没有操作数(操作数默认:%eax, %rax所对应的寄存器位置为第一个(返回值))
movz指令效果(零扩展)描述movs指令效果(符号扩展)描述
movzbwR < 零扩展(S)将做了零扩展的字节传送到movsbwR < 符号扩展(S)将做了符号扩展的字节传送到
movzblR < 零扩展(S)将做了零扩展的字节传送到双字movsblR < 符号扩展(S)将做了符号扩展的字节传送到双字
movzwlR < 零扩展(S)将做了零扩展的传送到双字movswlR < 符号扩展(S)将做了符号扩展的传送到双字
movzbqR < 零扩展(S)将做了零扩展的字节传送到四字movsbqR < 符号扩展(S)将做了符号扩展的字节传送到四字
movzwqR < 零扩展(S)将做了零扩展的传送到四字movswqR < 符号扩展(S)将做了符号扩展的传送到四字
(movl)R < 零扩展(S)将做了零扩展的双字传送到四字movslqR < 符号扩展(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位

流程

  • 告诉硬盘要读几个扇区,将该数值写入到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!

实战:读取显卡(独显)