您的位置:首页 > 其它

光盘启动 (Boot from CDROM) Part 2- SakiProject

2014-07-08 12:56 423 查看
继续上1P,我们现在要开始读取光驱里面的内容了。以前我描述过BIOS显示服务和磁盘服务(这里),但是我只将了CHS读软盘,没有涉及到硬盘和光驱。由于硬盘和光驱的构造与软盘截然不同,使用读软盘的手段是行不通的,为此,BIOS给我们提供了扩展13h中断。

Continue to the next part, we are going to read the content in the cdrom. I described the bios display service and disk service in previous posts (Here,
in Chinese), but I only introduced the way to read floppy through CHS method, but not harddisk and cdrom. Because the hardware construction of these devices are totally different, we cannot apply the floppy-way to harddisk(ATA) and cdrom(ATAPI) devices.
In order to solve the problem, BIOS provides us extended int 13h.

RegisterValueDescription
AHFunction(0x42)Extended Read from ATA/ATAPI Device
DLDrive Number
DS:SIAddress of Disk Address Packet
参数和读软盘不一样了。我们使用Disk Address Packet(DAP, 磁盘地址包)来间接传递参数,而不是传CHS信息。DAP的构造如下表:

The argument is different from the normal read function. We no longer pass the CHS arguments to the BIOS, but a Disk Address Packet(DAP) instead. The format of DAP is described by the following table:

OffsetSizeNameDescription
01Packet SizeAlways 16 (At least now)
11ReservedShould be zero
22Sector CountNumber of sectors to read
44Buffer AddressFormat is segment:offset, a word-long offset is stored first, then a word-long segment.
88LBAThe physical address of the first sector to read.
有了这两张表,工作就变得轻松了,我们用这段代码来描述DAP:

With the help with these two tables, it's pretty easy to implement it. We use the following code to describe DAP:

; Disk Address Packet(DAP), argument for extended 13h
DAP:
.packetSize  db 16, 0
.sectCount   dw 0
.bufferOff   dw 0
.bufferSeg   dw 0
.lbaLow      dd 0
.lbaHigh     dd 0


readSector函数的实现也会很轻松。我用了Javadoc样式的注释,这样可以让别人读起来易于理解逻辑和寄存器的关系。

And we can implement the readSector function easily. Notice that I use javadoc styled comments, in order to keep the code organized and easy to understand by others.

; read a sector from the boot device
; @param eax    lba address of the first sector to read
; @param cx     number of sectors
; @param edi    buffer to store read sectors
readSector:
mov     [DAP.lbaLow], eax
mov     [DAP.sectCount], cx
mov     eax, edi
shr     eax, 4                  ; Calculate the segment from absolute address
mov     [DAP.bufferSeg] ,ax
and     di, 0xf                 ; Calculate the offset from absolute address
mov     [DAP.bufferOff], di
mov     ah, 0x42
mov     dl, [driveNum]
mov     si, DAP;
int     13h
ret
;end readSector


这段代码从EDI里的物理地址计算了段:偏移格式的地址,然后还用到了上次保存的driveNum。

The code segment uses the physical address stored in edi to calculate segment:offset style address, and make use of the driveNum variable stored last time.

万事具备,接下来就要开始分析光盘了。光盘的文件系统是ISO 9660,这个文件系统的标准可以在ECMA的官网免费下到

Getting everything prepared, we are going to analyze the cdrom. The file system of cdrom is ISO 9660, the specification can be found on the official site of ECMA (Here).

ISO 9660(又名CDFS)开始是32KB的空余空间,然后是一些卷描述符。一般来说,CD的扇区大小是2KB,所以第一个卷描述符应该在第16扇区。这个描述符很特殊,它是主描述符,有了这个描述符我们就可以逐步揭开CD的面纱。

ISO 9660 (Also called CD File System, CDFS) starts with 32KB free space, then a few volume descriptors. Normally, a sector in CD is 2KB-big, so the first volume descriptor is the 16th sector. This descriptor is very special,
it's the primary one, and we can unseal the CD file system.

在主描述符中,我摘出了一些我们感兴趣的字段:

I picked some interesting fields in the primary volume descriptor.

OffsetSizeNameDescription
01Type CodeAlways 1, indicating that the sector is a primary volume descriptor.
1404Path Table(Little endian)The LBA location of path table.
15634Directory Entry of rootA directory structure, which will be mentioned below.
我们有两种方法找到一个文件,我们可以现在Path Table(路径表)中找到这个文件所在的目录,然后到这个目录里去找,我们也可以直接从根目录直接一层一层找下去。反正都需要从目录里面找文件的代码,我们还不如一层一层找,这样可以增加代码的共用。顺带一提,路径表是有大小限制的,所以只能存放最多65536个目录,但是从根目录找是没有任何限制的。Windows是用路径表的,而Linux是层级寻找的,所以有些Linux能用的光盘Windows读不出来,而Windows能用的光盘Linux都能用。(这是不是解释了为什么Windows慢>_<)

We have two ways to locate a file. We can either locate the parent directory of the file and find it in path table, then find the file in that directory, or we can just recursively find the file/directory from the root directory.
In either way we need code to find a file inside a directory, so we can simply use the second method to shorten code and increase efficiency. By the way, the size of path table is limited, and it can only store up to 65536 directories, but the latter method
has no restrictions. Windows use the former method and Linux use the latter method, so every cd readable in Windows can be read by Linux, but some CDs are not readable by Windows. (Maybe it proved why Windows is so slow >_<)

PRIM_DESC_LBA   equ 16

BUFFER          equ 0x600
DESC_TYPE_CODE  equ BUFFER
DESC_ROOT_DIR   equ BUFFER+156

mov     eax, PRIM_DESC_LBA
mov     cx, 1
mov     di, BUFFER
call    readSector


我们先加载主卷描述符,然后分析目录项(根目录项在BUFFER+156):

We first load the descriptor into the buffer, then we can analyze the directory record (note that the root record is at BUFFER+156):

OffsetSizeDescription
01Length of Directory Record
24LBA of file contents
104Data length
321Length of file name
33VariableFile identifier
; Load directory table into memory
; @param edi        directory entry
; @destory eax, ecx, edi
loadDirTable:
mov     eax, [edi+2]
mov     cx, 1
mov     di, BUFFER
call    readSector
ret
;end loadDirTable


我写了上面这个函数,然后可以edi=BUFFER+156调用这个函数加载ROOT目录。对于目录来说,文件内容就是连续分布的目录项,所以我们可以比较目录项里的文件名来找到文件的地址。

The function can be called using edi=BUFFER+156 to load the root directory. For directories, file content is several continuous distributed directory records, so we can compare file names in records to find the LBA address of
file.

; Search for file in directory at BUFFER
; @param esi        filename
; @destory eax, ecx, edx, edi
searchForFile:
call    strlen          ; Get length of filename
mov     edi, BUFFER
.loop:
movzx   eax, byte[edi]  ; Get size of record
or      eax, eax        ; If zero, no more record is available
jz      .err

movzx   ecx, byte[edi+32]   ; Get length of actual filename
cmp     ecx, edx            ; If not equal, next record
jnz     .cont

push    esi
push    edi
add     edi, 33         ; Set edi to the actual filename

repe    cmpsb
pop     edi
pop     esi
jz      .ret
.cont:
add     edi, eax        ; Point to the next record
jmp     .loop
.ret:
ret
.err:
call    print
mov     si, fileNotFound
call    print
cli
hlt
;end searchForFile

; Find length of given string
; @param si     target string
; @return edx   length of string
; @destory eax, edx, ecx, edi
strlen:
mov     di, si
or      cx, -1      ; -1 means that the function will never stop
xor     ax, ax      ; Clear eax to 0
repne   scasb       ; Compare until [esi] to 0
neg     cx          ; Since cx=-1-(len+1), we change the cx to 1+(len+1)
dec     cx
dec     cx
movzx   edx, cx
ret
;end strlen

fileNotFound db " does not exist in cdrom.",0


上面的代码就是分析了目录项,如果不好理解的话可以看下面的伪代码:

The above code analysis the directory records, if it's hard to understand, you can see the following pseudo-code:

void searchForFile(char* filename){
int len=strlen(filename);
void* record=BUFFER;
while(1){
int recordLen=*(unsigned char*)record;
assert(recordLen);
int nameLen=*(unsigned char*)(record+32);
if(nameLen==len&&strcmp((char*)(record+33), filename)==0){
return record;
}
record+=recordLen;
}
}


通过调用这些函数,我们可以逐级找文件。要注意的是,非目录文件会以;1结尾,而且文件名都是大写,所以我们要加载的文件其实是"/SAKI/BOOTMGR/BOOT.TXT;1"

By calling these functions, we can code the rest, which is recursively find files. Notice that files (not directories) will look like FILENAME;1, and all file names should be uppercase, so the path is actually "/SAKI/BOOTMGR/BOOT.TXT;1".
After we find the file, we can load the file to memory and display its content.

; Load the "/"
mov     edi, DESC_ROOT_DIR
call    loadDirTable

; Search for "SAKI"
mov     esi, dirNameSaki
call    searchForFile

; Load "/SAKI"
call    loadDirTable

; Search for "BOOTMGR"
mov     esi, dirNameBootmgr
call    searchForFile

; Load "/SAKI/BOOTMGR"
call    loadDirTable

; Search for "BOOT.TXT;1"
mov     esi, dirNameBooter
call    searchForFile

; Load "/SAKI/BOOTMGR/BOOT.TXT;1"
mov     eax, [edi+2]    ; LBA Address
mov     ecx, [edi+10]   ; Size of file
add     ecx, 2048
shr     ecx, 11         ; Change unit from byte to sector
mov     di, BUFFER
call    readSector

; Print information
mov     esi, BUFFER
call    print
cli
hlt

dirNameSaki db "SAKI",0
dirNameBootmgr db "BOOTMGR",0
dirNameBooter db "BOOT.TXT;1",0


好了,现在我们运行一下看看,看看我们能不能成功显示出文件里的内容吧!

Okay, let's run the program to see whether we succeeded or not!



真赞!引导扇区的任务就到此为止了,接下来的任务就交给Bootmgr了。在我的设计中,Bootmgr要负责切换到保护模式(或长模式)并且加载操作系统本体。下次开发Bootmgr应该就不止2P了。

Wonderful! This is the end of a bootsect, when a bootsect loaded bootmgr, it will give control to bootmgr. In my design, bootmgr will switch to protected mode and load the real operating system. The next series (developing bootmgr
will be more than two parts I think)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: