• 完整boot引导代码详解(完整无注释代码boot.asm+简单loader.asm)


     代码块一:

    1. org 0x7c00
    2. BaseOfStack equ 0x7c00
    3. BaseOfLoader equ 0x1000
    4. OffsetOfLoader equ 0x00
    5. RootDirSectors equ 14
    6. SectorNumOfRootDirStart equ 19
    7. SectorNumOfFAT1Start equ 1
    8. SectorBalance equ 17
    9. jmp short Label_Start
    10. nop
    11. BS_OEMName db 'MINEboot'
    12. BPB_BytesPerSec dw 512
    13. BPB_SecPerClus db 1
    14. BPB_RsvdSecCnt dw 1
    15. BPB_NumFATs db 2
    16. BPB_RootEntCnt dw 224
    17. BPB_TotSec16 dw 2880
    18. BPB_Media db 0xf0
    19. BPB_FATSz16 dw 9
    20. BPB_SecPerTrk dw 18
    21. BPB_NumHeads dw 2
    22. BPB_HiddSec dd 0
    23. BPB_TotSec32 dd 0
    24. BS_DrvNum db 0
    25. BS_Reserved1 db 0
    26. BS_BootSig db 0x29
    27. BS_VolID dd 0
    28. BS_VolLab db 'boot loader'
    29. BS_FileSysType db 'FAT12 '

            这段程序中的代码BaseOfLoader equ 0x1000和offsetofLoader equ 0x00组合成了Loader程序的起始物理地址,这个组合必须经过实模式的地址变换公式才能生成物理地址,即BaseofLoader<<4 +offsetofLoader = 0x10000。
            代码RootDirSectors equ 14定义了根目录占用的扇区数,这个数值是根据FAT12文件系统提供的信息经过计算而得,即(BPB_RootEntCnt * 32 + BPB_BytesPerSec - 1)/BPB_BytesPersec = ( 224×32 + 512 - 1)/ 512 = 14。
            等价语句sectorNumofRootDirstart equ 19定义了根目录的起始扇区号,这个数值也是通过计算而得,即保留扇区数+FAT表扇区数*FAT表份数=1 + 9 * 2= 19,因为扇区编号的计数值从O开始,故根目录的起始扇区号为19。
            程序SectorNumOfFAT1Start equ 1代表了FAT1表的起始扇区号,在FAT1表前面只有一个保留扇区(引导扇区),而且它的扇区编号是0,那么FAT1表的起始扇区号理应为1。
            汇编代码sectorBalance equ 17用于平衡文件(或者目录)的起始簇号与数据区起始簇号的差值。更通俗点说,因为数据区对应的有效簇号是2(FAT[2]),为了正确计算出FAT表项对应的数据区起始扇区号,则必须将FAT表项值减2,或者将数据区的起始簇号/扇区号减2(仅在每簇由一个扇区组成时可用)。本程序暂时采用一种更取巧的方法是,将根目录起始扇区号减2(19-2=17),进而间接把数据区的起始扇区号(数据区起始扇区号=根目录起始扇区号+根目录所占扇区数)减2。

     代码块二:

    1. ;======= read one sector from floppy
    2. Func_ReadOneSector:
    3. push bp
    4. mov bp, sp
    5. sub esp, 2
    6. mov byte [bp - 2], cl
    7. push bx
    8. mov bl, [BPB_SecPerTrk]
    9. div bl
    10. inc ah
    11. mov cl, ah
    12. mov dh, al
    13. shr al, 1
    14. mov ch, al
    15. and dh, 1
    16. pop bx
    17. mov dl, [BS_DrvNum]
    18. Label_Go_On_Reading:
    19. mov ah, 2
    20. mov al, byte [bp - 2]
    21. int 13h
    22. jc Label_Go_On_Reading
    23. add esp, 2
    24. pop bp
    25. ret

            代码中的Func_ReadOnesector模块负责实现软盘读取功能,它借助BIOS中断服务程序INT 13h的主功能号AH=02h实现软盘扇区的读取操作,该中断服务程序的各寄存器参数说明如下。
            INT 13h,AH=02h 功能:读取磁盘扇区。

    • AL=读入的扇区数(必须非0);
    • CH=磁道号(柱面号)的低8位;
    • CL=扇区号1~63 ( bit 0~5 ),磁道号(柱面号)的高2位( bit 6~7,只对硬盘有效);
    • DH=磁头号;
    • DL=驱动器号(如果操作的是硬盘驱动器,bit 7必须被置位);
    • ES:BX-一>数据缓冲区。

            模块Func_ReadOnesector仅仅是对BIOS中断服务程序的再次封装,以简化读取磁盘扇区的操作过程,进而在调用Func_ReadOnesector模块时,只需传递下列参数到对应的寄存器中,即可实现磁盘扇区的读取操作。模块Func_ReadOnesector详细参数说明如下。
            模块Func_Readonesector功能:读取磁盘扇区。

    • AX=待读取的磁盘起始扇区号;
    • CL=读入的扇区数量;
    • ES:BX=>目标缓冲区起始地址。

            因为Func_ReadOnesector模块传入的磁盘扇区号是LBA (Logical Block Address,逻辑块寻址)格式的,而INT 13h,AH=O2h中断服务程序只能受理CHS ( Cylinder /Head/Sector,柱面/磁头/扇区)格式的磁盘扇区号,那么必须将LBA格式转换为CHS格式,通过下列公式可将LBA格式转换为CHS格式。

             模块Func_ReadOnesector在读取软盘之前,会先保存栈帧寄存器和栈寄存器的数值,从栈中开辟两个字节的存储空间(将栈指针向下移动两个字节),由于此时代码bp - 2与ESP寄存器均指向同一内存地址,所以CL寄存器的值就保存在刚开辟的栈空间里。而后,使用AX寄存器(待读取的磁盘起始扇区号)除以BL寄存器(每磁道扇区数),计算出目标磁道号(商:AL寄存器)和目标磁道内的起始扇区号(余数:AH寄存器),考虑到磁道内的起始扇区号从1开始计数,故此将余数值加1,即inc ah。紧接着,再按照上公式计算出磁道号(也叫柱面号)与磁头号,将计算结果保存在对应寄存器内。最后,执行INT 13h中断服务程序从软盘扇区读取数据到内存中,当数据读取成功(CF标志位被复位)后恢复调用现场。

    代码块三:

    1. ;======= search loader.bin
    2. mov word [SectorNo], SectorNumOfRootDirStart
    3. Lable_Search_In_Root_Dir_Begin:
    4. cmp word [RootDirSizeForLoop], 0
    5. jz Label_No_LoaderBin
    6. dec word [RootDirSizeForLoop]
    7. mov ax, 00h
    8. mov es, ax
    9. mov bx, 8000h
    10. mov ax, [SectorNo]
    11. mov cl, 1
    12. call Func_ReadOneSector
    13. mov si, LoaderFileName
    14. mov di, 8000h
    15. cld
    16. mov dx, 10h
    17. Label_Search_For_LoaderBin:
    18. cmp dx, 0
    19. jz Label_Goto_Next_Sector_In_Root_Dir
    20. dec dx
    21. mov cx, 11
    22. Label_Cmp_FileName:
    23. cmp cx, 0
    24. jz Label_FileName_Found
    25. dec cx
    26. lodsb
    27. cmp al, byte [es:di]
    28. jz Label_Go_On
    29. jmp Label_Different
    30. Label_Go_On:
    31. inc di
    32. jmp Label_Cmp_FileName
    33. Label_Different:
    34. and di, 0ffe0h
    35. add di, 20h
    36. mov si, LoaderFileName
    37. jmp Label_Search_For_LoaderBin
    38. Label_Goto_Next_Sector_In_Root_Dir:
    39. add word [SectorNo], 1
    40. jmp Lable_Search_In_Root_Dir_Begin

            通过这段代码能够从根目录中搜索出引导加载程序(文件名为loader.bin )。在程序执行初期,程序会先保存根目录的起始扇区号,并依据根目录占用磁盘扇区数来确定需要搜索的扇区数,并从根目录中读入一个扇区的数据到缓冲区;接下来,遍历读入缓冲区中的每个目录项,寻找与目标文件名字符串( “LOADER BIN" , 0 )相匹配的目录项,其中DX寄存器记录着每个扇区可容纳的目录项个数(512/32= 16=0x10 ),CX寄存器记录着目录项的文件名长度(文件名长度为11B,包括文件名和扩展名,但不包含分隔符“.”)。在比对每个目录项文件名的过程中,使用了汇编指令LODSB,该命令的加载方向与DF标志位有关,因此在使用此命令时需用CLD指令清DF标志位。
            以下是Intel官方白皮书对LODSB/LODSW/LODSD/LODSQ指令的概括描述。
            1.该命令可从DS:(R|E)SI寄存器指定的内存地址中读取数据到AL/AX/EAX/RAX寄存器。
            2.当数据载入到AL/AX/EAX/RAX寄存器后,(RE)SI寄存器将会依据R|EFLAGs标志寄存器的DF标志位自动增加或减少载人的数据长度(1/2/4/8字节)。当DF=0时,(RE)SI寄存器将会自动增加;反之,(R|E)SI寄存器将会自动减少。
            一旦发现完全匹配的字符串,则跳转到Label_FileName_Found处执行;如果没有找到,那么就执行其后的Label_No_LoaderBin模块,进而在屏幕上显示提示信息,通知用户引导加载程序不存在。

            特别注意,因为FAT12文件系统的文件名是不区分大小写字母的,即使将小写字母命名的文件复制到FAT12文件系统内,文件系统也会为其创建大写字母的文件名和目录项。而小写字母文件名只作为其显示名,真正的数据内容皆保存在大写字母对应的目录项。所以这里应该搜索大写字母的文件名字符串。


      代码块四:

    1. ;======= display on screen : ERROR:No LOADER Found
    2. Label_No_LoaderBin:
    3. mov ax, 1301h
    4. mov bx, 008ch
    5. mov dx, 0100h
    6. mov cx, 21
    7. push ax
    8. mov ax, ds
    9. mov es, ax
    10. pop ax
    11. mov bp, NoLoaderMessage
    12. int 10h
    13. jmp $

            这段代码借助BIOS中断处理程序INT 10h ,将字符串ERROR :No LOADER Found显示到屏幕的第1行第0列上。

    代码块五:

    1. ;======= get FAT Entry
    2. Func_GetFATEntry:
    3. push es
    4. push bx
    5. push ax
    6. mov ax, 00
    7. mov es, ax
    8. pop ax
    9. mov byte [Odd], 0
    10. mov bx, 3
    11. mul bx
    12. mov bx, 2
    13. div bx
    14. cmp dx, 0
    15. jz Label_Even
    16. mov byte [Odd], 1
    17. Label_Even:
    18. xor dx, dx
    19. mov bx, [BPB_BytesPerSec]
    20. div bx
    21. push dx
    22. mov bx, 8000h
    23. add ax, SectorNumOfFAT1Start
    24. mov cl, 2
    25. call Func_ReadOneSector
    26. pop dx
    27. add bx, dx
    28. mov ax, [es:bx]
    29. cmp byte [Odd], 1
    30. jnz Label_Even_2
    31. shr ax, 4
    32. Label_Even_2:
    33. and ax, 0fffh
    34. pop bx
    35. pop es
    36. ret

            此前已经提及FAT12文件系统的每个FAT表项占用12 bit,即每三个字节存储两个FAT表项,由此看来,FAT表项的存储位置是具有奇偶性的。使用Func_GetFATEntry模块可根据当前FAT表项索引出下一个FAT表项,该模块的寄存器参数说明如下。
            模块Func_GetFATEntry 功能:根据当前FAT表项索引出下一个FAT表项。

    •         AH=FAT表项号(输入参数/输出参数)。

            这段程序首先会保存FAT表项号,并将奇偶标志变量(变量[odd])置0。因为每个FAT表项占1.5 B,所以将FAT表项乘以3除以2(扩大1.5倍)来判读余数的奇偶性并保存在[odd]中(奇数为1,偶数为0 ),再将计算结果除以每扇区字节数,商值为FAT表项的偏移扇区号,余数值为FAT表项在扇区中的偏移位置。接着,通过Func_Readonesector模块连续读入两个扇区的数据,此举的目的是为了解决FAT表项横跨两个扇区的问题。最后,根据奇偶标志变量进一步处理奇偶项错位问题,即奇数项向右移动4位。有能力的读者可自行将FAT12文件系统替换为FAT16文件系统,这样可以简化FAT表项的索引过程。
            在完成Func_ReadOnesector和Func_GetFATEntry模块后,就可借助这两个模块把loader.bin文件内的数据从软盘扇区读取到指定地址中。代码清单3-10实现了从FAT12文件系统中加载loader.bin文件到内存的过程。

    代码块六: 

    1. ;======= found loader.bin name in root director struct
    2. Label_FileName_Found:
    3. mov ax, RootDirSectors
    4. and di, 0ffe0h
    5. add di, 01ah
    6. mov cx, word [es:di]
    7. push cx
    8. add cx, ax
    9. add cx, SectorBalance
    10. mov ax, BaseOfLoader
    11. mov es, ax
    12. mov bx, OffsetOfLoader
    13. mov ax, cx
    14. Label_Go_On_Loading_File:
    15. push ax
    16. push bx
    17. mov ah, 0eh
    18. mov al, '.'
    19. mov bl, 0fh
    20. int 10h
    21. pop bx
    22. pop ax
    23. mov cl, 1
    24. call Func_ReadOneSector
    25. pop ax
    26. call Func_GetFATEntry
    27. cmp ax, 0fffh
    28. jz Label_File_Loaded
    29. push ax
    30. mov dx, RootDirSectors
    31. add ax, dx
    32. add ax, SectorBalance
    33. add bx, [BPB_BytesPerSec]
    34. jmp Label_Go_On_Loading_File
    35. Label_File_Loaded:
    36. jmp $

            在Label_FileName_Found模块中,程序会先取得目录项DIR_FstClus字段的数值,并通过配置ES寄存器和BX寄存器来指定loader.bin程序在内存中的起始地址,再根据loader.bin程序的起始簇号计算出其对应的扇区号。为了增强人机交互效果,此处还使用BIOS中断服务程序INT 10h在屏幕上显示一个字符' .'。接着,每读入一个扇区的数据就通过Func_GetFATEntry模块取得下一个FAT表项,并跳转至Label_Go_on_Loading_File处继续读入下一个簇的数据,如此往复,直至Func_GetFATEntry模块返回的FAT表项值是offfh为止。当loader.bin文件的数据全部读取到内存后,跳转至Label_File_Loaded处准备执行loader.bin程序。
            这段代码使用了BIOS中断服务程序INT10h的主功能号AH=0Eh在屏幕上显示一个字符。详细寄存器参数说明如下。
    INT 10h,AH=OEh 功能:在屏幕上显示一个字符。

    • AL=待显示字符;
    • BL=前景色。

    代码块七:

    1. ;======= tmp variable
    2. RootDirSizeForLoop dw RootDirSectors
    3. SectorNo dw 0
    4. Odd db 0
    5. ;======= display messages
    6. StartBootMessage: db "Start Boot"
    7. NoLoaderMessage: db "ERROR:No LOADER Found"
    8. LoaderFileName: db "LOADER BIN",0

            这三个变量用于保存程序运行时的临时数据,上文已经讲解了它们的使用过程,此处不再过多讲述。

            上述字符串均是屏幕上显示的日志信息。值得说明的是,NASM编译器中的单引号与双引号作用相同,并非如标准C语言中规定的:双引号会在字符串结尾处自动添加字符· \0',而在NASM编译器中必须自行添加。不过,本程序使用的BIOS中断服务程序必须明确提供显示的字符串的长度,不需要判读字符串结尾处的字符'\0 '。
            目前,我们还未进入Loader引导加载程序的开发环节,所以在Label_File_Loaded处使用代码jmp s,让程序死循环在此处。

    完整无注释代码

    boot.am

    1. org 0x7c00
    2. BaseOfStack equ 0x7c00
    3. BaseOfLoader equ 0x1000
    4. OffsetOfLoader equ 0x00
    5. RootDirSectors equ 14
    6. SectorNumOfRootDirStart equ 19
    7. SectorNumOfFAT1Start equ 1
    8. SectorBalance equ 17
    9. jmp short Label_Start
    10. nop
    11. BS_OEMName db 'MINEboot'
    12. BPB_BytesPerSec dw 512
    13. BPB_SecPerClus db 1
    14. BPB_RsvdSecCnt dw 1
    15. BPB_NumFATs db 2
    16. BPB_RootEntCnt dw 224
    17. BPB_TotSec16 dw 2880
    18. BPB_Media db 0xf0
    19. BPB_FATSz16 dw 9
    20. BPB_SecPerTrk dw 18
    21. BPB_NumHeads dw 2
    22. BPB_HiddSec dd 0
    23. BPB_TotSec32 dd 0
    24. BS_DrvNum db 0
    25. BS_Reserved1 db 0
    26. BS_BootSig db 0x29
    27. BS_VolID dd 0
    28. BS_VolLab db 'boot loader'
    29. BS_FileSysType db 'FAT12 '
    30. Label_Start:
    31. mov ax, cs
    32. mov ds, ax
    33. mov es, ax
    34. mov ss, ax
    35. mov sp, BaseOfStack
    36. ;======= clear screen
    37. mov ax, 0600h
    38. mov bx, 0700h
    39. mov cx, 0
    40. mov dx, 0184fh
    41. int 10h
    42. ;======= set focus
    43. mov ax, 0200h
    44. mov bx, 0000h
    45. mov dx, 0000h
    46. int 10h
    47. ;======= display on screen : Start Booting......
    48. mov ax, 1301h
    49. mov bx, 000fh
    50. mov dx, 0000h
    51. mov cx, 10
    52. push ax
    53. mov ax, ds
    54. mov es, ax
    55. pop ax
    56. mov bp, StartBootMessage
    57. int 10h
    58. ;======= reset floppy
    59. xor ah, ah
    60. xor dl, dl
    61. int 13h
    62. ;======= search loader.bin
    63. mov word [SectorNo], SectorNumOfRootDirStart
    64. Lable_Search_In_Root_Dir_Begin:
    65. cmp word [RootDirSizeForLoop], 0
    66. jz Label_No_LoaderBin
    67. dec word [RootDirSizeForLoop]
    68. mov ax, 00h
    69. mov es, ax
    70. mov bx, 8000h
    71. mov ax, [SectorNo]
    72. mov cl, 1
    73. call Func_ReadOneSector
    74. mov si, LoaderFileName
    75. mov di, 8000h
    76. cld
    77. mov dx, 10h
    78. Label_Search_For_LoaderBin:
    79. cmp dx, 0
    80. jz Label_Goto_Next_Sector_In_Root_Dir
    81. dec dx
    82. mov cx, 11
    83. Label_Cmp_FileName:
    84. cmp cx, 0
    85. jz Label_FileName_Found
    86. dec cx
    87. lodsb
    88. cmp al, byte [es:di]
    89. jz Label_Go_On
    90. jmp Label_Different
    91. Label_Go_On:
    92. inc di
    93. jmp Label_Cmp_FileName
    94. Label_Different:
    95. and di, 0ffe0h
    96. add di, 20h
    97. mov si, LoaderFileName
    98. jmp Label_Search_For_LoaderBin
    99. Label_Goto_Next_Sector_In_Root_Dir:
    100. add word [SectorNo], 1
    101. jmp Lable_Search_In_Root_Dir_Begin
    102. ;======= display on screen : ERROR:No LOADER Found
    103. Label_No_LoaderBin:
    104. mov ax, 1301h
    105. mov bx, 008ch
    106. mov dx, 0100h
    107. mov cx, 21
    108. push ax
    109. mov ax, ds
    110. mov es, ax
    111. pop ax
    112. mov bp, NoLoaderMessage
    113. int 10h
    114. jmp $
    115. ;======= found loader.bin name in root director struct
    116. Label_FileName_Found:
    117. mov ax, RootDirSectors
    118. and di, 0ffe0h
    119. add di, 01ah
    120. mov cx, word [es:di]
    121. push cx
    122. add cx, ax
    123. add cx, SectorBalance
    124. mov ax, BaseOfLoader
    125. mov es, ax
    126. mov bx, OffsetOfLoader
    127. mov ax, cx
    128. Label_Go_On_Loading_File:
    129. push ax
    130. push bx
    131. mov ah, 0eh
    132. mov al, '.'
    133. mov bl, 0fh
    134. int 10h
    135. pop bx
    136. pop ax
    137. mov cl, 1
    138. call Func_ReadOneSector
    139. pop ax
    140. call Func_GetFATEntry
    141. cmp ax, 0fffh
    142. jz Label_File_Loaded
    143. push ax
    144. mov dx, RootDirSectors
    145. add ax, dx
    146. add ax, SectorBalance
    147. add bx, [BPB_BytesPerSec]
    148. jmp Label_Go_On_Loading_File
    149. ; Label_File_Loaded:
    150. ; jmp $
    151. ;======= read one sector from floppy
    152. Func_ReadOneSector:
    153. push bp
    154. mov bp, sp
    155. sub esp, 2
    156. mov byte [bp - 2], cl
    157. push bx
    158. mov bl, [BPB_SecPerTrk]
    159. div bl
    160. inc ah
    161. mov cl, ah
    162. mov dh, al
    163. shr al, 1
    164. mov ch, al
    165. and dh, 1
    166. pop bx
    167. mov dl, [BS_DrvNum]
    168. Label_Go_On_Reading:
    169. mov ah, 2
    170. mov al, byte [bp - 2]
    171. int 13h
    172. jc Label_Go_On_Reading
    173. add esp, 2
    174. pop bp
    175. ret
    176. ;======= get FAT Entry
    177. Func_GetFATEntry:
    178. push es
    179. push bx
    180. push ax
    181. mov ax, 00
    182. mov es, ax
    183. pop ax
    184. mov byte [Odd], 0
    185. mov bx, 3
    186. mul bx
    187. mov bx, 2
    188. div bx
    189. cmp dx, 0
    190. jz Label_Even
    191. mov byte [Odd], 1
    192. Label_Even:
    193. xor dx, dx
    194. mov bx, [BPB_BytesPerSec]
    195. div bx
    196. push dx
    197. mov bx, 8000h
    198. add ax, SectorNumOfFAT1Start
    199. mov cl, 2
    200. call Func_ReadOneSector
    201. pop dx
    202. add bx, dx
    203. mov ax, [es:bx]
    204. cmp byte [Odd], 1
    205. jnz Label_Even_2
    206. shr ax, 4
    207. Label_Even_2:
    208. and ax, 0fffh
    209. pop bx
    210. pop es
    211. ret
    212. ;======= tmp variable
    213. RootDirSizeForLoop dw RootDirSectors
    214. SectorNo dw 0
    215. Odd db 0
    216. ;======= display messages
    217. StartBootMessage: db "Start Boot"
    218. NoLoaderMessage: db "ERROR:No LOADER Found"
    219. LoaderFileName: db "LOADER BIN",0
    220. ;=========================
    221. Label_File_Loaded:
    222. jmp BaseOfLoader:OffsetOfLoader
    223. ;======= fill zero until whole sector
    224. times 510 - ($ - $$) db 0
    225. dw 0xaa55

    简单的loader.asm代码

    1. org 10000h
    2. mov ax,cs
    3. mov ds,ax
    4. mov es,ax
    5. mov ax,0x00
    6. mov ss,ax
    7. mov sp,0x7c00
    8. ;========== 显示 Start Loader....
    9. mov ax,1301h
    10. mov bx,000fh
    11. mov dx,0200h
    12. mov cx,12
    13. push ax
    14. mov ax,ds
    15. mov es,ax
    16. pop ax
    17. mov bp,StartLoaderMessage
    18. int 10h
    19. jmp $
    20. ;=============
    21. StartLoaderMessage : db "Start Loader"

     摘自:《一个64位操作系统的设计与实现》

  • 相关阅读:
    构造方法与方法调用指令分析 || JVM类加载与字节码技术
    如何使用 React 和 Docusaurus 编写的一些自定义钩子(Hook)
    分库分表实战:竿头日上—千万级数据优化之读写分离
    电巢科技出席第26届西北地区电子技术与线路课程教学改革研讨会,聚焦一流课程建设!
    基于webpack5从0搭建Vue脚手架
    Google Earth Engine ——neighborhoodToBands函数的使用
    Python每日一练(牛客数据分析篇新题库)——第37天:合并
    JVM 的可达性分析法和四种引用
    nodejs配置
    使用策略模式重构审批
  • 原文地址:https://blog.csdn.net/weixin_42492218/article/details/127752246