第八章 硬盘和显卡的访问与控制

mbr加载、重定位用户程序

PART 1 >> VirtualBox显示最终效果

===================================================================================================

PART 2 >> 部分说明

1、应用程序的头部

加载器和用户程序之间需要一个协定,比如说,用户程序的一些基本的结构信息必须放在某个固定位置,加载器就可以从这个固定位置读取。经验表明,把这个约定的地方放在用户程序的起始位置,对双方,特别是对加载器来说比较方便,这就是用户程序的头部。

(1)用户程序的大小,以字节为单位。加载器需要根据这个信息来决定读取多少个逻辑扇区。

(所有程序在硬盘上所占用的逻辑扇区不一定是连续的 ?)

(2)应用程序入口点(Entry Point),包括段地址和偏移地址。即,第一条要执行的指令的段地址和偏移地址。

(3)段重定位表。程序通常会包含多个代码段和数据段。加载器要根据程序加载到内存的实际位置,来重新计算每个段的地址,即重定位。

2、内存空间范围

物理地址0x0 FFFF以下,是加载器及其栈的范围;

0xA 0000以上,是BIOS和外围设备的范围,有很多传统的老式设备将自己的存储器和只读存储器映射到这段空间。

===================================================================================================

PART 3 >> 源代码

3.1 c08_mbr.asm

mbr程序

; FILE: c08_mbr.asm
; DATE: 20191211; jmp near mbrapp_lba_begin equ 100       ; 将配套的的用户程序从磁盘lba扇区100开始写入; 若从硬盘启动,BIOS读取主引导扇区并加载到内存0x0000:0x7c00,即0x07c00
; 即,主引导程序mbr的实际加载地址是0x0000:0x7c00
; vstart, 指定段内汇编地址的开始点, 段内所有元素的汇编地址都将从0x7c00开始计算
SECTION mbr align=16 vstart=0x7c00; 设置堆栈段ss和段指针mov ax, 0mov ss, axmov sp, ax; 设置数据段ds和es; 计算得到加载用户程序的逻辑段地址; 本程序中,数据段和代码段是分离的,而且代码段定义部分使用了"vstart=0x7c00"mov ax, [cs:phy_base]   ; 从标号phy_base的位置读取mov dx, [cs:phy_base + 2]mov bx, 16div bx                  ; 右移4位, 即0x1000mov ds, axmov es, ax; 读取硬盘中用户程序所在的第1个扇区; 包含了头部信息:程序大小、入口点、段重定位表xor di, dimov si, app_lba_begin   ; 程序在硬盘上的逻辑扇区号    xor bx, bx              ; 加载到ds:0x0000处call read_hard_disk_0; 根据用户程序的头部信息,判断整个程序的大小mov dx, [2]mov ax, [0]mov bx, 512div bxcmp dx, 0               jnz @1dec ax                  ; 余数dx为0则商ax减1,已读取一个扇区    @1:cmp ax, 0jz direct               ; 实际长度小于512字节,则已读取完; 读取剩余的扇区push ds                 ; 下面要用到并改变dsmov cx, ax              ; 循环次数(剩余扇区数)@2:mov ax, dsadd ax, 0x20            ; 得到下一个以512字节为边界的段地址mov ds, axxor bx, bx              ; 每次读时,起始偏移地址都为0x0000inc si                  ; 下一个扇区call read_hard_disk_0loop @2pop ds; 根据用户程序的头部信息,计算入口点代码段基址direct:mov dx, [0x08]mov ax, [0x06]          ; 用户程序头部内偏移为0x06处的双字,存放着入口点代码段地址call calc_segment_basemov [0x06], ax          ; 回填修正后的入口点代码段地址; 开始处理用户程序的段重定位表mov cx, [0x0a]          ; 用户程序头部中,存放的重定位表项个数mov bx, 0x0c            ; 重定位表首地址realloc:mov dx, [bx+2]mov ax, [bx]call calc_segment_basemov [bx], ax            ; 回填修正后的段基址add bx, 4               ; 下一个重定位表项loop reallocjmp far [0x04]          ; 转移到用户程序; ===============================================================================
; Function: 读取主硬盘的1个扇区
; Input: 1) di:si 起始逻辑扇区号 2) ds:bx 目标缓冲区地址
read_hard_disk_0:push axpush bxpush cxpush dx; 1) 设置要读取的扇区数; ==========================; 向0x1f2端口写入要读取的扇区数。每读取一个扇区,数值会减1;; 若读写过程中发生错误,该端口包含着尚未读取的扇区数mov dx, 0x1f2           ; 0x1f2为8位端口mov al, 1               ; 1个扇区out dx, al; 2) 设置起始扇区号; ===========================; 扇区的读写是连续的。这里采用早期的LBA28逻辑扇区编址方法,; 28个比特表示逻辑扇区号,每个扇区512字节,所以LBA25可管理128G的硬盘; 28位的扇区号分成4段,分别写入端口0x1f3 0x1f4 0x1f5 0x1f6,都是8位端口inc dx                  ; 0x1f3mov ax, siout dx, alinc dx                  ; 0x1f4mov al, ahout dx, al              ; in和out 操作寄存器只能是al或者axinc dx                  ; 0x1f5mov ax, diout dx, al; 8bits端口0x1f6,低4位存放28位逻辑扇区号的24~27位;; 第4位指示硬盘号,0为主盘,1为从盘;高3位,111表示LBA模式inc dx                  ; 0x1f6mov al, 0xe0            ; al 高4位设为 1110or al, ah               ; al 低4位设为 LBA的的高4位out dx, al; 3) 请求读硬盘; ==========================; 向端口写入0x20,请求硬盘读inc dx                  ; 0x1f7mov al, 0x20out dx, al.wait:; 4) 等待硬盘读写操作完成; ===========================; 端口0x1f7既是命令端口,又是状态端口; 通过这个端口发送读写命令之后,硬盘就忙乎开了。; 0x1f7端口第7位,1为忙,0忙完了同时将第3位置1表示准备好了,; 即0x08时,主机可以发送或接收数据in al, dx               ; 0x1f7and al, 0x88            ; 取第8位和第3位cmp al, 0x08            jnz .wait; 5) 连续取出数据; ============================; 0x1f0是硬盘接口的数据端口,16bitsmov cx, 256             ; loop循环次数,每次读取2bytesmov dx, 0x1f0           ; 0x1f0.readw:in ax, dxmov [bx], axadd bx, 2loop .readwpop dxpop cxpop bxpop axret; ===============================================================================
; Function: 计算段地址
; Input: 1) dx:ax 32位物理地址
; Output: 1) ax 16位段地址
calc_segment_base:push dxadd ax, [cs:phy_base]       ; 标号phy_base处存放着的0x1 0000    adc dx, [cs:phy_base + 2]shr ax, 4ror dx, 4and dx, 0xf000or ax, dxpop dxret; ; 直接除以16可以吗 ???; push bx; push dx; add ax, [cs:phy_base]       ; 标号phy_base处存放着的0x1 0000    ; adc dx, [cs:phy_base + 2]; mov bx, 16; div bx; pop dx; pop bx; ret; 物理内存分布
; 0x0 0000 ~ 0x0 FFFF, 加载器及其栈
; 0xA 0000 ~ 0xF FFFF, BIOS和外围设备的映射空间
; 0x1 0000 ~ 0x9 FFFF, 可用用户空间,约500KB
phy_base dd 0x10000         ; 设定一个程序加载的起始内存地址0x1 0000,标号phy_basetimes 510-($-$$) db 0db 0x55, 0xaa

3.2 c08.asm

用户程序

; FILE: c08.asm
; DATE: 20191211; ===============================================================================
SECTION head vstart=0                       ; 定义用户程序头部段 ; 用户程序可能很大,16位可能不够program_length  dd program_end          ; 程序总长度[0x00]; 程序入口点(Entry Point)program_entry   dw beginning            ; 偏移地址[0x04]; 只是编译阶段确定的汇编地址。程序加载到内存后,需要根据加载的实际位置重新计算; 尽管在16位的环境中,一个段最长为64KB,但它却可以起始于任何20位的物理地址处。; 不可能用16位来保存20位的地址,所以需要32位dd section.code_1.start ; 汇编地址[0x06] realloc_tbl_size dw (head_end-segment_code_1)/4     ; 段重定位表项个数[0x0a]segment_code_1  dd section.code_1.start ; [0x0c]    segment_code_2  dd section.code_2.start ; [0x10]segment_data_1  dd section.data_1.start ; [0x14]segment_data_2  dd section.data_2.start ; [0x18]segment_stack   dd section.stack.start  ; [0x1c]    ; 这里section 和 start 不能用大写 ???head_end:; ===============================================================================
SECTION code_1 align=16 vstart=0beginning:; 设置用户程序自己的堆栈段; ds和es依然指向着用户程序头部head段mov ax, [segment_stack]mov ss, axmov sp, stack_end; 设置用户程序自己的数据段; 如果先初始化数据段ds和附加段es,那么头部head段中的数据将无法访问mov ax, [segment_data_1]mov ds, axmov bx, msg0call show_string; 巧用retf跳转到code_2的beginpush word [es:segment_code_2] ; ds指向了用户程序自己的数据段mov ax, beginpush axretf            ; retf相当于"pop ip" "pop cs"continue:mov ax, [es:segment_data_2]mov ds, ax      ; ds指向数据段2    mov bx, msg1call show_string ; 显示第2段文本jmp $; Function: 频幕上显示文本
; Input: ds:bx 字符串起始地址,以0结尾
show_string:mov cl, [bx]or cl, cljz .exitcall show_charinc bxjmp show_string.exit:ret; Function:
; Input: cl 字符
show_char:push axpush bxpush cxpush dxpush dspush es; 读取当前光标位置; 索引寄存器端口0x3d4,其索引值14(0x0e)和15(0x0f)分别用于提供光标位置的高和低8位; 数据端口0x3d5mov dx, 0x3d4   mov al, 0x0e   out dx, almov dx, 0x3d5in al, dxmov ah, almov dx, 0x3d4mov al, 0x0fout dx, almov dx, 0x3d5in al, dxmov bx, ax      ; 此处用bx存放光标位置的16位数; 判断是否为回车符0x0dcmp cl, 0x0d    ; 0x0d 为回车符jnz .show_0a    ; 不是回车符0x0d,再判断是否换行符0x0amov ax, bx      ; 是回车符,则将光标置位到行首mov bl, 80div blmul blmov bx, axjmp .set_cursor; ; 将光标位置移到行首,可以直接减去当前行吗??; mov ax, bx; mov dl, 80; div dl; sub bx, ah; jmp .set_cursor; 判断是否为换行符0x0a.show_0a:cmp cl, 0x0a    ; 0x0a 为换行符    jnz .show_normal; 不是换行符,则正常显示字符add bx, 80      ; 是换行符,再判断是否需要滚屏jmp .roll_screen; 正常显示字符; 在写入其它内容之前,显存里全是黑底白字的空白字符0x0720,所以可以不重写黑底白字的属性.show_normal:mov ax, 0xb800  ; 显存映射在 0xb8000~0xbffffmov es, axshl bx, 1       ; 光标指示字符位置,显存中一个字符占2字节,光标位置乘2得到该字符在显存中得偏移地址    mov [es:bx], clshr bx, 1       ; 恢复bxinc bx          ; 将光标推进到下一个位置; 判断是否需要向上滚动一行屏幕.roll_screen:cmp bx, 2000    ; 25行x80列jl .set_cursormov ax, 0xb800    mov ds, ax      ; movsw的源地址ds:simov es, ax      ; movsw的目的地址es:dimov si, 0xa0mov di, 0cld             ; 传送方向cls stdmov cx, 1920    ; rep次数 24行*每行80个字符*每个字符加显示属性占2字节 / 一个字为2字节rep movsw; 清除屏幕最底一行,即写入黑底白字的空白字符0x0720mov bx, 3840    ; 24行*每行80个字符*每个字符加显示属性占2字节mov cx, 80.cls:mov word [es:bx], 0x0720add bx, 2loop .clsmov bx, 1920    ; 重置光标位置为最底一行行首; 根据bx重置光标位置; 索引寄存器端口0x3d4,其索引值14(0x0e)和15(0x0f)分别用于提供光标位置的高和低8位; 数据端口0x3d5.set_cursor:mov dx, 0x3d4   mov al, 0x0e   out dx, almov dx, 0x3d5mov al, bh      ; in和out 只能用al或者axout dx, almov dx, 0x3d4mov al, 0x0fout dx, almov dx, 0x3d5mov al, blout dx, alpop espop dspop dxpop cxpop bxpop axret;===============================================================================
SECTION code_2 align=16 vstart=0; 巧用retf跳转到code_1的continue
begin:push word [es:segment_code_1]mov ax, continuepush axretf            ; retf相当于"pop ip" "pop cs";===============================================================================
SECTION data_1 align=16 vstart=0msg0 db '  This is NASM - the famous Netwide Assembler. 'db 'Back at SourceForge and in intensive development! 'db 'Get the current versions from http://www.nasm.us/.'db 0x0d,0x0a,0x0d,0x0adb '  Example code for calculate 1+2+...+1000:',0x0d,0x0a,0x0d,0x0adb '     xor dx,dx',0x0d,0x0adb '     xor ax,ax',0x0d,0x0adb '     xor cx,cx',0x0d,0x0adb '  @@:',0x0d,0x0adb '     inc cx',0x0d,0x0adb '     add ax,cx',0x0d,0x0adb '     adc dx,0',0x0d,0x0adb '     inc cx',0x0d,0x0adb '     cmp cx,1000',0x0d,0x0adb '     jle @@',0x0d,0x0adb '     ... ...(Some other codes)',0x0d,0x0a,0x0d,0x0adb 0;===============================================================================
SECTION data_2 align=16 vstart=0msg1 db '  The above contents is written by BigBao. 'db '2019-12-14'db 0; ===============================================================================
SECTION stack align=16 vstart=0resb 256
stack_end:; ===============================================================================
SECTION tail align=16       ; 这里用于计算程序大小,不需要vstart=0
program_end:

[书]x86汇编语言:从实模式到保护模式 -- 第八章 硬盘和显卡的访问与控制,mbr加载并重定位应用程序相关推荐

  1. [书]x86汇编语言:从实模式到保护模式 -- 第17章 中断、任务切换、分页机制、平坦模型

    # 任务切换 内核任务.用户任务1.用户任务2,之前的轮询切换 利用RTC芯片的硬件中断来实现任务切换 计算机主板上有实时时钟芯片RTC,可以设置RTC芯片,使得它每次更新CMOS中的时间信息后,发出 ...

  2. [书]x86汇编语言:从实模式到保护模式 -- 第13章 mbr加载内核、内核加载应用程序

    # mbr加载内核 1.0x7c00,16位实模式 2.进入保护模式前的准备工作:创建段描述符(代码段.数据段.堆栈段.显示缓冲区),构建gdt 3.进入保护模式 ; 开启保护模式 ; CR0的第1位 ...

  3. [书]x86汇编语言:从实模式到保护模式 -- 第16章 分页机制、平坦模型

    # 分页机制 二级页表:页目录.页表 ==> 4KB物理页 32位线性地址中:高10位为页目录中的索引号(乘4得偏移量),该目录项指向页表的基地址:中间10位为页表中的索引号,该页表项指向4KB ...

  4. [书]x86汇编语言:从实模式到保护模式 -- 第15章 任务切换

    # 执行结果 # TODO:字符串显示函数的滚屏部分应该是有bug. # file_02: c15_core.asm ; FILE: c13_core.asm ; DATE: 20200104 ; T ...

  5. [书]x86汇编语言:从实模式到保护模式 -- 第14章 任务和特权级保护,调用门、LDT、TSS、TCB

    # 加载用户程序 Part 1.TCB, Task Control Block, 任务控制块 分配内存作为该任务的TCB,并插入至TCB链表. Part 2.LDT, Locak Descriptor ...

  6. 硬盘和显卡的访问与控制(一)——《x86汇编语言:从实模式到保护模式》读书笔记01

    本文是<x86汇编语言:从实模式到保护模式>(电子工业出版社)的读书实验笔记. 这篇文章我们先不分析代码,而是说一下在Bochs环境下如何看到实验结果. 需要的源码文件 第一个文件是加载程 ...

  7. [书]x86汇编语言:从实模式到保护模式 -- 第11章 进入保护模式,初识全局描述符表GDT; 第12章 别名,冒泡排序

    第11章 进入保护模式:初始化全局描述符表,通过GDT进入代码段.数据段.堆栈段 ; FILE: c11_mbr.asm ; DATE: 20191229 ; TITLE: 硬盘主引导扇区代码; 设置 ...

  8. [书]x86汇编语言:从实模式到保护模式 -- 第九章 硬中断,使用RTC芯片实现实时时间的显示;软中断,使用BIOS中断实现键盘输入的读取和显示

    PART 1 >> 使用BIOS中断实现键盘输入的读取和显示 ; File: c09_2.asm ; Date: 20191222; =========================== ...

  9. 硬盘和显卡的访问与控制(二)——《x86汇编语言:从实模式到保护模式》读书笔记02

    上一篇博文我们讲了如何看到实验结果,这篇博文我们着重分析源代码. 书中作者为了说明原理,约定了一种比较简单地用户程序头部格式,示意图如下(我参考原书图8-15绘制的,左边的数字表示偏移地址): 所以, ...

最新文章

  1. oracle中有类似split的方法么,Oracle 实现拆分列数据的split()方法
  2. redhat系统双网卡绑定
  3. 个人计算机的防毒软件无法防御,在win10系统中无法启动defender防御软件的解决方法...
  4. libpng warning: iCCP: known incorrect sRGB profile
  5. orm2 中文文档 3.2 模型验证器
  6. 罗永浩站台微商大会出场费5万?主办方:5万出场费是谣传
  7. 模板题——图论相关(2)
  8. 阶段3 1.Mybatis_12.Mybatis注解开发_2 mybatis注解开发测试和使用注意事项
  9. c语言银行排队系统链表,银行预约排队系统(数据结构问题)
  10. 读文献--《机器学习隐私保护研究综述》
  11. 升级在谷歌电子市场上传的应用
  12. 微型计算机8086工作原理,8086到80486微型计算机系统原理与接口
  13. 网络安全篇 防火墙的静态路由-04
  14. 华为裁员1100人!任正非痛批管理层:这种领导鼠目寸光
  15. 关于中国教育出来的学生
  16. 什么是VOLTE(2)
  17. 循环报错: 远程主机强迫关闭了一个现有的连接
  18. NDCG评价指标讲解
  19. ocp跟oce的区别 oracle_六种角度看OCA与OCP、OCM区别
  20. grpc+gateway使用说明

热门文章

  1. 安卓期末项目:星座App
  2. 联想服务器安装2003系统,联想服务器2003SERVER安装.doc
  3. XV6源代码阅读-进程线程
  4. Project2007软件安装教程
  5. 集群服务器session共享
  6. 字幕剪切视频神器AutoCut的安装和使用
  7. android 淘宝天猫支付宝浏览器打开本地app传递参数打开应用内页
  8. miui主题风格_一种android系统换肤功能的设计,董红光:MIUI主题风格.pdf
  9. Unity UnityWebRequest: InvalidOperationException: Insecure connection not allowed
  10. 怎么把cda文件转换成mp3