Весь исходный код загрузочного сектора MS-DOS 6.22 представлен ниже.
; ================================================================== ; Reverse Engineering MS-DOS 6.22 boot sector. NASM source code. ; Final two bytes being the boot signature (AA55h). Note that in FAT12, ; a cluster is the same as a sector: 512 bytes. ; ================================================================== BITS 16 org 7C00h jmp short bootloader_start ; Jump past disk description section - jmp short size 2 bytes nop ; Pad out before disk description - nop size 1 byte ; ------------------------------------------------------------------ ; Bios parameter block BPB ; Disk description table, to make it a valid floppy ; Note: some of these values are hard-coded in the source! ; Values are those used by IBM for 1.44 MB, 3.5" diskette OEMLabel db "MSDOS622" ; seg000:0003 Disk label BytesPerSector dw 512 ; seg000:000B Bytes per sector 200h SectorsPerCluster db 1 ; seg000:000D Sectors per cluster ReservedForBoot dw 1 ; seg000:000E Reserved sectors for boot record NumberOfFats db 2 ; seg000:0010 Number of copies of the FAT RootDirEntries dw 224 ; seg000:0011 Number of entries in root dir e0h ; (224 * 32 = 7168 = 14 sectors to read) LogicalSectors dw 2880 ; seg000:0013 Number of logical sectors b40h MediumByte db 0F0h ; seg000:0015 Medium descriptor byte SectorsPerFat dw 9 ; seg000:0016 Sectors per FAT SectorsPerTrack dw 18 ; seg000:0018 Sectors per track (36/cylinder) 12h Sides dw 2 ; seg000:001A Number of sides/heads HiddenSectors dd 0 ; seg000:001C Number of hidden sectors LargeSectors dd 0 ; seg000:0020 Number of LBA sectors i.e. 32 bit ; version of number of sectors DriveNo db 0 ; seg000:0024 Drive No: 0 for A: floppy, 80h for HDD CurrentHead db 0 ; seg000:0025 Current Head Signature db 41 ; seg000:0026 Drive signature: 41d 29h for floppy VolumeID dd 00000000h ; seg000:0027 Volume ID: any number VolumeLabel db "MSDOS622 "; seg000:002B Volume Label: any 11 chars FileSystem db "FAT12 " ; seg000:0036 File system type: don't change! ; ------------------------------------------------------------------ ; Main bootloader code Sec9 EQU bootloader_start+0 ;11 byte diskette parm. table BiosLow EQU bootloader_start+11 BiosHigh EQU bootloader_start+13 CurTrk EQU bootloader_start+15 CurSec EQU bootloader_start+17 DirLow EQU bootloader_start+18 DirHigh EQU bootloader_start+20 bootloader_start: cli ; Disable interrupts while changing stack xor AX,AX mov SS,AX ;Work in stack just below this routine mov SP,7C00h push ss pop es mov bx, 78h ;POINTER TO DRIVE PARAMETERS 30d * 4d = 120d (78h) i.e. 1EH*4 lds si, ss:[bx] push DS ; save original vector for possible push SI ; restore push SS push BX mov DI, Sec9 mov CX, 11 cld repz movsb push ES pop DS ; DS = ES = code = 0. mov byte [di-2], 0Fh ; Head settle time mov cx, [SectorsPerTrack] mov byte [DI-7], cl ; End of Track mov [bx+2],AX ; Place in new disk parameter mov word [bx], Sec9 ; table vector sti ; Interrupts OK now int 13h ; Reset the disk system just in case jc CkErr ; any thing funny has happened. xor AX, AX cmp [LogicalSectors], AX ; 32 bit calculation? je Dir_Cont mov CX, [LogicalSectors] mov [LargeSectors], CX ; First, we need to load the root directory from the disk. Technical details: ; Start of root = ReservedForBoot + NumberOfFats * SectorsPerFat = logical 19d ; Number of root = RootDirEntries * 32 bytes/entry / 512 bytes/sector = 14d ; Start of user data = (start of root) + (number of root) = logical 33d 21h Dir_Cont: mov AL, [NumberOfFats] ;Determine sector dir starts on mul WORD [SectorsPerFat] add AX,WORD [HiddenSectors] adc DX,WORD [HiddenSectors + 2] add AX,[ReservedForBoot] adc DX,0 ; DX:AX = NumberOfFats * SectorsPerFat + ReservedForBoot + HiddenSectors mov [DirLow],AX mov [DirHigh],DX mov [BiosLow],AX mov [BiosHigh],DX ; Take into account size of directory (only know number ; of directory entries) mov AX, 20h ; bytes per directory entry mul word [RootDirEntries] ; convert to bytes in directory mov BX,[BytesPerSector] ; add in sector size add AX,BX dec AX ; decrement so that we round up div BX ; convert to sector number add [BiosLow],AX ; Start sector # of Data area adc word [BiosHigh],0 ; We load in the first directory sector and examine it to ; make sure the the BIOS and DOS are the first two directory ; entries. If they are not found, the user is prompted to ; insert a new disk. The directory sector is loaded into 00500h mov BX, 500h ; sector to go in at 00500h mov DX,[DirHigh] mov AX,[DirLow] ; logical start sector of directory call DoDiv ; convert to sector, track, head jc CkErr ; Overflow? BPB must be wrong!! mov al, 1 ; disk read 1 sector call DoCall ; do the disk read jb CkErr ; if errors try to recover ; Now we scan for the presence of BIOS file. mov DI,BX mov CX,11 mov SI, Bio ; point to "io.sys" repz cmpsb ; see if the same jnz CkErr ; if not there advise the user ; Found the BIOS. Check the second directory entry. ; SI will already point to "MSDOS SYS" if first compare ; was successful lea DI,[BX+20h] mov SI,Dos mov CX,11 repz cmpsb jz DoLoad ; There has been some recoverable error. Display a message ; and wait for a keystroke. CkErr: mov SI, SysMsg ; point to no system message ErrOut: call Write ; and write on the screen xor AX,AX ; wait for response int 16h ; get character from keyboard pop SI ; reset disk parameter table back to pop DS ; rom pop word [SI] pop word [SI+2] int 19h ; Continue in loop till good disk Load_Failure: pop ax ;adjust the stack pop ax pop ax jmp short CkErr ;display message and reboot. ; We now begin to load the BIOS in. ; All we have to do is just read is multiply the BioStartClus ; by SecsPerClust to find the logical sector for the start ; of the BIOS file. When this value is added to the double ; word BiosHigh:BiosLow we get the absolute sector offset ; for the start of the file and then read the sectors ; contiguously IBM_LOAD_SIZE times. We here assume that ; IBMLOAD module is contiguous. Currently we estimate that ; IBMLOAD module will not be more than 3 sectors. DoLoad: mov AX,[BX + 1Ah] ; AX = BIOS starting cluster dec AX ; Subtract first 2 reserved clusters dec AX mov BL,[SectorsPerCluster] ; BX = Sectors per cluster xor BH,BH mul BX ; DX:AX = first logical sector of bios add AX,[BiosLow] ; Add absolute start sector adc DX,[BiosHigh] ; DX:AX = Absolute bios sector offset mov BX,700h ;offset of ibmbio(IBMLOAD) to be loaded. mov CX,3 ;# of sectors to read. ; Main read-in loop. ; ES:BX points to area to read. ; Count is the number of sectors remaining. ; BIOS$ is the next logical sector number to read ; ; CurrentHead is the head for this next disk request ; CurTrk is the track for this next request ; CurSec is the beginning sector number for this request ; ; AX is the number of sectors that we may read. Do_While: push AX push DX push CX call DoDiv ; DX:AX = sector number. jc Load_Failure ; Adjust stack. Show error message mov al, 1 ; Read 1 sector at a time. ; This is to handle a case of media ; when the first sector of IBMLOAD is the ; the last sector in a track. call DoCall ; Read the sector. pop CX pop DX pop AX jc CkErr ; Read error? add AX,1 ; Next sector number. adc DX,0 add BX,[BytesPerSector] ; Adjust buffer address. loop Do_While ; IBMINIT requires the following input conditions: ; ; DL = INT 13 drive number we booted from ; CH = media byte ; IBMBIO init routine should check if the boot record is the ; extended one by looking at the extended_boot_signature. ; If it is, then should us AX;BX for the starting data sector number. ; PC-DOS (and MS-DOS up to version ; 6.xx behaves similar) expects on entry for io.sys: ; ch = media id byte in the boot sector ; dl = BIOS drive booted from (0x00=A:, 0x80=C:, ...) ; ax:bx = the starting (LBA) sector of cluster 2 (ie the 1st ; data sector, which is 0x0000:0021 for FAT12) ; note: IO.SYS may use ax:bx and cluster # stored ; elsewhere (perhaps dir entry still at 0x50:0) to determine ; starting sector for full loading of kernel file. ; it also expects the boot sector (in particular the BPB) ; to still be at 0x0:7C00, the directory entry for io.sys ; (generally first entry of first sector of the root directory) ; at 0x50:0 (DOS reserved communication area). DISKOK: mov CH,[MediumByte] mov DL,[DriveNo] mov BX,[BiosLow] ; Low Word Get bios sector in bx BiosLow = 21h 33d mov AX,[BiosHigh] ; Hight Word Value is 0h jmp 70h:0 ; CRANK UP THE DOS ; ------------------------------------------------------------------ ; BOOTLOADER SUBROUTINES Write: lodsb ;GET NEXT CHARACTER or AL,AL ;Clear the high bit jz EndWr ;ERROR MESSAGE UP, JUMP TO BASIC mov AH,14 ;WILL WRITE CHARACTER & ATTRIBUTE mov BX,7 ;ATTRIBUTE int 10h ;PRINT THE CHARACTER jmp Write EndWr: ret ;---------------------------- ; Convert a logical sector into Track/sector/head. ; DX;AX has the sector number. Because of not enough space, we ; are going to use Simple 32 bit division here. ; Carry set if DX;AX is too big to handle. DoDiv: cmp DX,[SectorsPerTrack] ; To prevent overflow!!! jae DivOverFlow ; Compare high word with the divisor. div word [SectorsPerTrack] ; AX = Total tracks, DX = sector number inc DL ; We assume SecPerTrack < 255 & DH=0 ; curSec is 1-based. mov [CurSec], DL ; Save it xor DX,DX div word [Sides] mov [CurrentHead],DL ;Also, NumHeads < 255. mov [CurTrk],AX clc ret DivOverFlow: stc EndWR: ret ;------------------------------ ; Issue one read request. ES:BX have the transfer address, ; AL is the number of sectors. DoCall: mov AH, 2h ;=2 mov DX,[CurTrk] mov CL,6 shl DH,CL or DH,[CurSec] mov CX,DX xchg CH,CL mov DL, [DriveNo] mov DH, [CurrentHead] int 13h ret ; ------------------------------------------------------------------ ; STRINGS AND VARIABLES Bio db "IO SYS" Dos db "MSDOS SYS" ;SysMsg db "Non-System disk or disk error",0Dh,0Ah,"Replace and press any key when ;ready",0Dh,0Ah,0 SysMsg db "Non-System disk or disk error",0Dh,0Ah, 0 ; ------------------------------------------------------------------ ; END OF BOOT SECTOR times 510-($-$$) db 0 ; Pad remainder of boot sector with zeros dw 0AA55h ; Boot signature (DO NOT CHANGE!)
Описание исходного кода.
Ниже в коде указывается что это 16 бит двоичный файл, счетчик команд IP установить 7C00h, и прыжок на метку bootloader_start.
BITS 16 org 7C00h jmp short bootloader_start ; Jump past disk description section nop ; Pad out before disk description
Между прыжком на метку bootloader_start и самой меткой bootloader_start расположена таблица параметров BIOS, которая называется Bios Parameter Block (BPB). См. ниже.
jmp short bootloader_start ; Jump past disk description section nop ; Pad out before disk description ;----------------------------- ; Здесь Bios Parameter Block ;----------------------------- Sec9 EQU bootloader_start+0 ;11 byte diskette parm. table BiosLow EQU bootloader_start+11 BiosHigh EQU bootloader_start+13 CurTrk EQU bootloader_start+15 CurSec EQU bootloader_start+17 DirLow EQU bootloader_start+18 DirHigh EQU bootloader_start+20 bootloader_start:
BPB нужен что бы BIOS компьютера располагал как читать данные из раздела с данными из диска, т.е. BPB включает количество FAT, сколько секторов на одну FAT, сколько может быть записей в корневой директории, номер загрузочного диска, сколько секторов на диске (общее количество) и т.п. Вы сами можете ознакомиться с содержанием BPB. По этой инофрмации BIOS знает как обращаться с дисководом. Между BPB и меткой bootloader_start расположены переменные. Когда программа перейдер на метку bootloader_start, и начнет дальше выполняться, то эти переменные будут как раз расположены там где был выполненный код метки bootloader_start. Это делается что бы не выделять дополнительно место под переменные под кодом загрузчика, т.к. это занимает память, и не хватит 512 байт что бы разместить весь код загрузчика. Поэтому переменная Sec9 и все последующие создаются на месте бывшего кода метки bootloader_start - этот код уже выполнен и мы можем его затереть для использования места под переменные далее в коде.
После метки bootloader_start код выполняется следующим образом. См. ниже. Сначала запрещаются прерывания что бы изменить регистр стека SS. Значение SS = 0. Значение SP = 7C00h - т.е. стек расположен прямо под кодом загручика, снизу, под ним. Далее в ES записываем 0, т.е. ES = 0.
bootloader_start: cli ; Disable interrupts while changing stack xor AX,AX mov SS,AX ;Work in stack just below this routine mov SP,7C00h push ss pop es
Для информации и понимания кода далее, необходимо знать что таблица параметров диска храниться по адресу 0h:78h от начала памяти компьютера смещение 78h. Таблица параметров диска для дискет (DPT) задана указателем в памяти по адресу 0:78h, для жестких дисков (HDPT) — 0:104h. Мы начало этой таблицы, т.е. 0:78h записываем в DS:SI. Смещение переменной Sec9 (она будет хранить новые параметры диска) храниться по адресу ES:DI, т.е. ES = 0, DI = offset Sec9. Далее устанавливается направление копирования cld - это значит то при копировании SI и DI регистры увеличиваются (а не уменьшаются). И команда repz movsb копирует в конце концов таблицу параметров диска в новое место (данные из таблицы) - где расположена переменна Sec9, занимает 11 байт. Т.е. копирование 11 байт. Первый 1 килобайт памяти занимают прерывания BIOS. После включения ПК BIOS загружает свои прерывания начиная от 0 адреса памяти до 1024 - первый килобат. В этом списке 256 прерываний, каждое прерывание - это 4 байта, адрес сегмент:смещение на подпрограмму обработки прерывания. Но в этом участке памяти BIOS хранит не только адреса переходов на подпрограммы прерываний, а также хранит необходимые для работы данные- оно из этих данных - таблица параметров диска имеет номер INT 1Eh но это не прерывание как таковое - это место где BIOS хранит параметры дисковода. 1Eh * 4 = 78h смещение 78h это смещение в памяти для INT 1Eh, 4 байта занимает один адрес - сегмент:смещение. Называется просто INT 1Eh но это не прерывание. Таким образом Bios Parameter Block нужен для описания дискеты которая содержит ОС, например дискета 1400 КB. Disk Parameter Table необходим для описания дисковода (физическое устройство) которое будет читать этот диск 1440 KB с ОС. Но для использования образа ISO в программе VirtualBox параметры в Disk Parameter Table менять не обязательно, образ ISO будет загружатся. Можно закоментировать этот код работы с Int 1Eh, сделать образ ISO и этот образ загрузиться в VirtualBox.
Код ниже берет 4 байта по адресу 0:78h - эти 4 байта это адрес таблицы параметров дисковода, таблица параметров дисковода содержит 11 байт данных. Эти 11 байт копируются по адресу переменной Sec9 - это делает код ниже. DS:SI подержат 0:78h, ES:DI содержат 0:Sec9 (смещение переменной Sec9).
mov bx, 78h ;POINTER TO DRIVE PARAMETERS 30d * 4d = 120d (78h) i.e. 1EH*4 lds si, ss:[bx] push DS ; save original vector for possible push SI ; restore push SS push BX mov DI, Sec9 mov CX, 11 cld repz movsb
Далее в DS записываем ноль, т.е. DS = 0, ES = 0, CS = 0. Почему CS = 0 так как мы установили ранее org 7c00h - это устанавливает CS = 0 и IP = 7c00h (т.е. на код загрузчика).
push ES pop DS ; DS = ES = code = 0.
Далее там где расположена переменная Sec9 на нее указывает регистр DI, и мы меняем некоторые значения в таблице параметров диска.
mov byte [di-2], 0Fh ; Head settle time mov cx, [SectorsPerTrack] mov byte [DI-7], cl ; End of Track
Далее мы указываем что новая таблица параметров диска (11 байт) размещена там где переменная Sec9. В коде ниже bx = 78h - это смещение от начала памяти 78h, по этому смещению адрес старой таблицы диска, оригинальной. Мы записываем новый адрес 4 байта сегмент:смещение по адресу 78h, т.е. сегмент в AX = 0 записываем по адресу bx+2 т.е. 7Ah, по адресу bx = 78h записываем смещение переменной Sec9 (Sec9 мы по этому смещению скоопировали старую таблицу диска), так как little endian требует что бы сначала в памяти разместился младший байт (78h = offset Sec9), затем старший байт (7Ah = segment 0).
mov [bx+2],AX ; Place in new disk parameter mov word [bx], Sec9 ; table vector
Далее делаем на всякий случай сброс дисковода, разрешаем прерывания - команда sti, и вызываем int 13h - прерывание для операций с дисками.
sti ; Interrupts OK now int 13h ; Reset the disk system just in case
Далее код, если ошибка в процессе вызова int 13h выводим сообщение на экран, ждем нажатия на любую клавишу, и далее перезагрузка.
jc CkErr ; any thing funny has happened.
Если в процессе вызова int 13h ошибки нет, далее проверяем нужен ли 32 битный расчет.
xor AX, AX cmp [LogicalSectors], AX ; 32 bit calculation? je Dir_Cont mov CX, [LogicalSectors] mov [LargeSectors], CX
Далее начиная с метки Dir_Cont выполняются такие расчеты.
Сектор начала корневого каталога = ReservedForBoot + NumberOfFats * SectorsPerFat = 19d Сколько занимает места корневой каталог = RootDirEntries * 32 bytes/entry / 512 bytes/sector = 14d Начало данных пользователя на диске на чинается в секторе = (start of root) + (number of root) = 33d 21h
В результате выполнения кода ниже в AX будет сектор начала корневого каталога т.е. это сектор 19d.
Dir_Cont: mov AL, [NumberOfFats] ;Determine sector dir starts on mul WORD [SectorsPerFat] add AX,WORD [HiddenSectors] adc DX,WORD [HiddenSectors + 2] add AX,[ReservedForBoot] adc DX,0
Записываем эти значения в переменные, см. код ниже.
mov [DirLow],AX ; AX = 19d mov [DirHigh],DX ; Dx = 0 mov [BiosLow],AX ; AX = 19d mov [BiosHigh],DX ; DX = 0
На данном этапе мы вычислили номер первого сектора корневого каталога. Далее за корневым каталогом лежит область данных диска. Вычисляем первый сектор области данных диска. См. код ниже - теперь AX = BiosLow = 33d и BiosHigh = 0.
mov AX, 20h ; bytes per directory entry mul word [RootDirEntries] ; convert to bytes in directory mov BX,[BytesPerSector] ; add in sector size add AX,BX dec AX ; decrement so that we round up div BX ; convert to sector number add [BiosLow],AX ; Start sector # of Data area adc word [BiosHigh],0
Следующая инструкция устанавливает что мы хотим загрузить корневой каталог по смещению BX = 500h. Мы загружаем не весь корневой каталог а только 1 сектор, загрузка происходит по адресу 0h:500h, ES:BX, здесь ES = 0 и BX = 500h.
mov BX, 500h ; sector to go in at 00500h
См.код ниже - переменная DirHigh = 0, и DirLow = 19d - это сектор начала корневого каталога. Функция DoDiv выполняет преобразование этого значения 19d в вид "сектор : дорожка : сторона" диска. Проверяем на ошибки - функция CkErr.
mov DX,[DirHigh] mov AX,[DirLow] ; logical start sector of directory call DoDiv ; convert to sector, track, head jc CkErr ; Overflow? BPB must be wrong!!
Далее читаем 1 сектор корневого каталога с диска - функция DoCall. Проверяем на ошибки - функция CkErr. Мы читаем сектор корневого каталога что бы проверить есть ли файлы io.sys и msdos.sys на диске. Если они присутствуют в корневом каталоге значит они есть на диске.
mov al, 1 ; disk read 1 sector call DoCall ; do the disk read jb CkErr ; if errors try to recover
Далее проверяем есть ли файлы io.sys и msdos.sys в корневом каталоге. Команда repz cmpsb сравнивает количество байт записанное в CX, у нас CX = 11 это размер для имен файлов DOS 8 символов имя + 3 символа расширение файла = 11 символов. Регистр ES = 0, DI = BX = 500h куда мы загрузили 1 сектор корневого каталога. Регистр DS = 0, SI = offset Bio. Сравнение идет регистры ES:DI с регистрами DS:SI (на что указывают эти регистры). CkErr проверка на ошибки.
mov DI,BX mov CX,11 mov SI, Bio ; point to "io.sys" repz cmpsb ; see if the same jnz CkErr ; if not there advise the user
Если нашли в корневом каталоге io.sys добавляем до DI 32d или 20h - это размер одного элемента корневого каталога, и переходим таким образом ко второму элементу корневого каталога. Таким же образом как мы искали io.sys находим msdos.sys. Если есть совпадение переходим на загрузку DoLoad. Если в результате сравнения символов в корневом каталоге не нашелся файла msdos.sys то идем на функцию вывода ошибки CkErr.
lea DI,[BX+20h] mov SI,Dos mov CX,11 repz cmpsb jz DoLoad
Что в начале функции DoLoad. BX указывает на первый сектор загруженой корневой директории. Первый и все последующие элементы корневой директории имеют размер 32d байта. Первые 11 байт это имя файла. У нас первая запись в корневой директории файл io.sys. По смещению 1Ah или 26d от начала записи лежит номер первого сектора в FAT файла io.sys. Мы этот номер сектора заносим в AX. Наша цель из сектора FAT получить номер сектора в области данных, если мы знаем что область данных начинается в 33 секторе. Вычисления следующие:
Начальный сектор файла в области данных = ((Кластер FAT) - 2) * SectorsPerCluster + (Начало области данных 33d) = (Кластер FAT) + 31d
DoLoad: mov AX,[BX + 1Ah] ; AX = BIOS starting cluster in FAT
Первые два сектора FAT это служебные секторы, поэтому мы значение в регистре AX должны уменьшить на 2.
FAT cluster 0 = media descriptor = 0F0h FAT cluster 1 = filler cluster = 0FFh
dec AX ; Subtract first 2 reserved clusters dec AX
Выполняем вычисления, в результате AX хранит номер первого сектора на диске в области данных файла io.sys, мы к значению AX добавляем BiosLow = 33d и к DX BiosHigh = 0 (см. вычисления ранее). В результате DX:AX хранит старшие 16 байт и младшие 16 байт которые представляют собой первый сектор io.sys в области данных.
mov BL,[SectorsPerCluster] ; BX = Sectors per cluster xor BH,BH mul BX ; DX:AX = first logical sector of bios add AX,[BiosLow] ; Add absolute start sector adc DX,[BiosHigh] ; DX:AX = Absolute bios sector offset
Мы будем загружать io.sys по адресу ES:BX, где ES = 0 и BX = 700h, т.е. адрес 0000h:0700h или по другому 70h:0. Мы по этому адресу загружаем 3 первых сектора io.sys - в них храниться загрузчик остальной части io.sys и далее грузиться msdos.sys. Мы загружаем на адрес 0000h:0700h первые 3 сектора io.sys и передаем на них управление. Эти первые три сектора грузят остальную часть io.sys и msdos.sys. 3 сектора это 1536 байт, хотя на реальный размер io.sys около 40 Килобайтов. Остальная часть io.sys подружается этим загрузчиком IBMLOAD который расположен в первых 3х секторах io.sys.
mov BX,700h ;offset of io.sys(IBMLOAD) to be loaded. mov CX,3 ;# of sectors to read.
Далее в цикле без помощи FAT (мы уже получили первый сектор io.sys в области данных) читаем подряд 3 сектора файла io.sys с диска с области данных. Перед началом цикла Do_While AX:DX хранят номер первого сектора io.sys в области данных. DoDiv выполняет преобразование номера сектора из AX:DX в вид "сектор : дорожка : сторона" диска. В случае ошибки после вызова DoDiv вызывается Load_Failure. В регистр AL заносим 1 - мы читаем 1 сектор за раз. DoCall читает сектор с диска. CkErr выполняется в случае ошибки чтения с диска. После чтения сектора добавляем к значению в AX 1 и это будет номер следующего сектора. К значению в BX т.е. это 700h добавляем 512 что бы сдвинуть буфер для следующего чтения сектора. Переходим на начало цикла loop Do_While.
Do_While: push AX push DX push CX call DoDiv ; DX:AX = sector number. jc Load_Failure ; Adjust stack. Show error message mov al, 1 ; Read 1 sector at a time. ; This is to handle a case of media ; when the first sector of IBMLOAD is the ; the last sector in a track. call DoCall ; Read the sector. pop CX pop DX pop AX jc CkErr ; Read error? add AX,1 ; Next sector number. adc DX,0 add BX,[BytesPerSector] ; Adjust buffer address. loop Do_While
Теперь у нас есть 3 первых сектора io.sys прочитанные с дика в память по адресу 0000h:0700h, или 70h:0. Далее в коде ниже настраиваем регистры что бы передать управление io.sys, так как io.sys необходимо для работы некоторая информация. И передаем управление на 70h:0 куда мы загрузили первые 3 сектора io.sys.
DISKOK: mov CH,[MediumByte] mov DL,[DriveNo] mov BX,[BiosLow] ; Get bios sector in bx BiosLow = 21h 33d mov AX,[BiosHigh] ; Value is 0h jmp 70h:0 ; CRANK UP THE DOS
После этого на экране высвечивается сообщение о загрузке MS-DOS 6.22 и операционная система начинает работу.
Особенности работы кода. Как понятно из работы кода что 1 сектор корневой директории диска должен быть загружен по адресу 0:0500h иначе загрузки MS-DOS не будет, если закоментировать int 13h когда загружается сектор корневой директории, и не загрузить сектор корневой директории загрузка виснет, т.е. по адресу 0:0500h должен быть 1й сектор корневой директории. Нужно строго соблюдать всю адресацию согласно тому как это сделано в загрузчике MS-DOS 6.22 Микрософт.
Итак подведем итоги- схема загрузки MS-DOS 6.22.
Так же есть укороченная версия загрузчика MS-DOS 6.22. В этой версии только загружается корневой каталог по адресу 0:0500h и затем загружается io.sys по адресу 0:0700h. Для лучшего понимания работы загрузчика я создал укороченную версию загрузчика MS-DOS 6.22. Ниже исходный код укороченной версии:
; ================================================================== ; Reverse Engineering MS-DOS 6.22 boot sector. NASM source code. ; Final two bytes being the boot signature (AA55h). Note that in FAT12, ; a cluster is the same as a sector: 512 bytes. ; ================================================================== BITS 16 org 7C00h jmp short bootloader_start ; Jump past disk description section nop ; Pad out before disk description ; ------------------------------------------------------------------ ; Bios parameter block BPB ; Disk description table, to make it a valid floppy ; Note: some of these values are hard-coded in the source! ; Values are those used by IBM for 1.44 MB, 3.5" diskette OEMLabel db "MSDOS622" ; seg000:0003 Disk label BytesPerSector dw 512 ; seg000:000B Bytes per sector 200h SectorsPerCluster db 1 ; seg000:000D Sectors per cluster ReservedForBoot dw 1 ; seg000:000E Reserved sectors for boot record NumberOfFats db 2 ; seg000:0010 Number of copies of the FAT RootDirEntries dw 224 ; seg000:0011 Number of entries in root dir e0h ; (224 * 32 = 7168 = 14 sectors to read) LogicalSectors dw 2880 ; seg000:0013 Number of logical sectors b40h MediumByte db 0F0h ; seg000:0015 Medium descriptor byte SectorsPerFat dw 9 ; seg000:0016 Sectors per FAT SectorsPerTrack dw 18 ; seg000:0018 Sectors per track (36/cylinder) 12h Sides dw 2 ; seg000:001A Number of sides/heads HiddenSectors dd 0 ; seg000:001C Number of hidden sectors LargeSectors dd 0 ; seg000:0020 Number of LBA sectors i.e. 32 bit ; version of number of sectors DriveNo db 0 ; seg000:0024 Drive No: 0 for A: floppy, 80h for HDD CurrentHead db 0 ; seg000:0025 Current Head Signature db 41 ; seg000:0026 Drive signature: 41d 29h for floppy VolumeID dd 00000000h ; seg000:0027 Volume ID: any number VolumeLabel db "MSDOS622 "; seg000:002B Volume Label: any 11 chars FileSystem db "FAT12 " ; seg000:0036 File system type: don't change! ; ------------------------------------------------------------------ ; Main bootloader code CurTrk EQU bootloader_start+ 0 CurSec EQU bootloader_start+ 2 bootloader_start: cli ; Disable interrupts while changing stack xor AX,AX mov SS,AX ;Work in stack just below this routine mov SP,7C00h sti ; Interrupts OK now push ss pop es push ES pop DS ; DS = ES = code = 0. ;------------------------------ ;первый сектор корневой директории диска ;должен быть загружен по адресу 0:0500h (DOS reserved communication area) ;иначе загрузки MS-DOS не будет ;шаг 1 - загружаем 1 сектор корневого каталога mov BX, 500h ; sector to go in at 00500h mov DX, 0 mov AX,19 ; logical start sector of directory call DoDiv ; convert to sector, track, head mov al, 1 ; disk read 1 sector ;[CurTrk] = 0 ;[CurSec] = 2 ;[CurrentHead] = 1 call DoCall ;шаг 2- загружаем 3 сектора io.sys mov ax, 33 ;первый сектор области данных там io.sys - младшее слово ax mov dx, 0 ;сюда ноль это старшее слово dx для сектора mov BX,700h ;offset of io.sys(IBMLOAD) to be loaded ;0h:0700h is DOS interface to ROM I/O routines mov CX,3 ;читаем в память первые 3 сектора io.sys где находится загрузчик io.sys ;на диске io.sys занимает около 40 Килобайт Do_While: push AX push DX push CX call DoDiv ; DX:AX = sector number. mov al, 1 ; Read 1 sector at a time. ; This is to handle a case of media ; when the first sector of IBMLOAD is the ; the last sector in a track. call DoCall ; Read the sector. pop CX pop DX pop AX add AX,1 ; Next sector number. adc DX,0 add BX,[BytesPerSector] ; Adjust buffer address. loop Do_While ; PC-DOS (and MS-DOS up to version ; 6.xx behaves similar) expects on entry for io.sys: ; ch = media id byte in the boot sector ; dl = BIOS drive booted from (0x00=A:, 0x80=C:, ...) ; ax:bx = the starting (LBA) sector of cluster 2 (ie the 1st ; data sector, which is 0x0000:0021 for FAT12) ; note: IO.SYS may use ax:bx and cluster # stored ; elsewhere (perhaps dir entry still at 0x50:0) to determine ; starting sector for full loading of kernel file. ; it also expects the boot sector (in particular the BPB) ; to still be at 0x0:7C00, the directory entry for io.sys ; (generally first entry of first sector of the root directory) ; at 0x50:0 (DOS reserved communication area). ;шаг 3 - передаем управление на io.sys DISKOK: mov CH,[MediumByte] mov DL,[DriveNo] mov BX,33 ; Low Word Get bios sector in bx BiosLow = 21h 33d mov AX,0 ; Hight Word Value is 0h jmp 70h:0 ; CRANK UP THE DOS ; ------------------------------------------------------------------ ; BOOTLOADER SUBROUTINES ; Convert a logical sector into Track/sector/head. ; DX;AX has the sector number. Because of not enough space, we ; are going to use Simple 32 bit division here. ; Carry set if DX;AX is too big to handle. DoDiv: cmp DX,[SectorsPerTrack] ; To prevent overflow!!! jae DivOverFlow ; Compare high word with the divisor. div word [SectorsPerTrack] ; AX = Total tracks, DX = sector number inc DL ; We assume SecPerTrack < 255 & DH=0 ; curSec is 1-based. mov [CurSec], DL ; Save it xor DX,DX div word [Sides] mov [CurrentHead],DL ;Also, NumHeads < 255. mov [CurTrk],AX clc ret DivOverFlow: stc EndWR: ret ;------------------------------ ; Issue one read request. ES:BX have the transfer address, ; AL is the number of sectors. DoCall: mov AH, 2h ;=2 mov DX,[CurTrk] mov CL,6 shl DH,CL or DH,[CurSec] mov CX,DX xchg CH,CL mov DL, [DriveNo] mov DH, [CurrentHead] int 13h ret ; ------------------------------------------------------------------ ; END OF BOOT SECTOR times 510-($-$$) db 0 ; Pad remainder of boot sector with zeros dw 0AA55h ; Boot signature (DO NOT CHANGE!)