Раз зашел разговор про MBR, выложу свои мытарства. Загрузчик для дискеты, заточеный на то, чтобы передать управление программе на Turbo Pascal. Единственное, что мне непонятно, так это то, что необходимо настроить значения вектора 1E, но... раз все так поступают, то и я не был оригинальным.
Когда-то даже
документацию по этому делу решил написать, но не сильно преуспел.
;*********************************************************
; Загрузчик для дискеты
; Версия 1.10
; (С) Mystic 22.11.2002
;*********************************************************
;
; Описание:
; Выполняет загрузку EXE-файла, который должен распола-
; гаться в корневой директории (но не обязательно пер-
; вым). PSP не поддерживается.
;
; Символы препроцессора:
; DOS: предполагает, что значение CS <> 0. Если этот
; символ задан, то возможна отлака файла из DOS.
;
; Константы:
; ExeName: имя EXE-файла в формате 8.3 так, как оно
; прописано в FAT.
; SkipIP: задает число, которое будет добавлено к точке
; входа. При помощи этого параметра можно про-
; пустить код загрузки. Для программ, скомпили-
; рованных в TP 7.0 необходимо установить этот
; параметр в 15.
;
; История:
; версия 1.10
; [*] оптимизирован код
; [+] добавлен символ препроцессора DOS
; [+] добавлен вывод сообщений об ошибках
; [+] установка собственного вектора 1E
; [+] расширены комментарии
;*********************************************************
IDEAL
P8086
MODEL TINY
CODESEG
STARTUPCODE
IFDEF DOS
; Для отладки из-по DOS
ORG 100h
JMP NEAR START
ENDIF
ORG 7C00h
START:
;======================================================
; Параметры FAT
;======================================================
JMP NEAR EntryPoint
OemName DB ' MWOS '
BytePerSector DW 512d
SectorPerCluster DB 1d
ReservedSector DW 1d
FatCount DB 2d
RootEntries DW 224d
SectorCount DW 2880d
MediaDescriptor DB 0F0h
SectorPerFat DW 9d
SectorPerTrack DW 18d
HeadPerTrack DW 2d
HiddenSector DD 0
BigTotalSector DD 0
Drive DB 0
DB 0
ExtendedBootSignature DB ')'
SerialNumber DD 0FACE0000h
VolumeLabel DB ' '
FileSystemID DB 'FAT12 '
;======================================================
; Константы и переменные
;======================================================
ExeName DB 'LOADER EXE' ; Файл, который будем загружать
SkipIP DW 15d ; Загрузчик
Int1E DW 0FFF0h, 0 ; Указатель стека/сохраненный вектор 1Eh
;======================================================
; Сообщения об ошибках...
;======================================================
IOErrorMsg DB 'Disk I/O error', 10, 13
IOErrorLen DW $ - IOErrorMsg
FileNotFoundMsg DB 'File not found', 10, 13
FileNotFoundLen DW $ - FileNotFoundMsg
;======================================================
; Чтение ОДНОГО сектора
; AX -> номер сектора (от 0 до SectorCount-1)
; ES:BX -> буфер, в который записываются прочитанные данные
; AX, CX, DX <- ?
;======================================================
PROC ReadSector NEAR
CWD
DIV [SectorPerTrack]
MOV CL, DL
INC CL
CWD
DIV [HeadPerTrack]
MOV CH, AL
MOV DH, DL
MOV DL, [Drive]
MOV AX, 0201h
INT 13h
JC IOError
RET
ENDP ReadSector
;======================================================
; Чтение ОДНОГО кластера
; AX -> номер кластера
; ES:BX -> буфер, в который записываются прочитанные данные
; ES:BX <- указатель на конец буфера для чтения
; CX <- 0
; AX, DX <- ?
;======================================================
PROC ReadCluster NEAR
DEC AX
DEC AX
MOV CL, [SectorPerCluster]
XOR CH, CH
MUL CX
ADD AX, BP
; JMP ReadSectors --- это следующий адрес :)
ENDP ReadCluster
;======================================================
; Чтение произвольного количества секторов
; AX -> номер сектора (от 0 до SectorCount-1)
; CX -> количество читаемых секторов
; ES:BX -> буфер, в который записываются прочитанные данные
; ES:BX <- указатель на конец буфера для чтения
; CX <- 0
; AX, DX <- ?
;======================================================
PROC ReadSectors NEAR
; JCXZ @@2 Assert(CX<>0)
@@1:
PUSH CX
PUSH AX
CALL ReadSector
POP AX
POP CX
ADD BX, [BytePerSector]
INC AX
LOOP @@1
@@2:
RET
ENDP ReadSectors
;======================================================
; Завершение загрузки в результате ошибки
; Выводит сообщение, ждет нажатия клавиши и
; завершает загрузку
; ES:BP -> Выводимое на экран сообщение
; CX -> Количество выводимых символов
;======================================================
PROC Abort NEAR
MOV AX, 03h
XOR BH, BH
INT 10h
MOV AX, 1301h
MOV BL, 07h
INT 10h
XOR AH, AH
INT 16h
IFDEF DOS ; Данный фрагмент заносит
XOR AX, AX ; в ES значение 0. Под DOS
MOV ES, AX ; мы заносим значение ES явно
ELSE ; а при загрузке с дискеты мы
PUSH CS ; экономим пару байт, учитывая,
POP ES ; что CS = 0.
ENDIF
MOV DI, 1Eh * 4
MOV SI, OFFSET Int1E
MOVSW
MOVSW
IFDEF DOS
MOV AX, 4C00h
INT 21h
ELSE
INT 19h
ENDIF
ENDP Abort
;======================================================
; Завершение загрузки в результате ошибки ввода/вывода
;======================================================
PROC IOError NEAR
MOV CX, [IOErrorLen]
MOV BP, OFFSET IOErrorMsg
JMP Abort
ENDP IOError
;======================================================
; Завершение загрузки (не найден загружаемый файл)
;======================================================
PROC FileNotFound NEAR
MOV CX, [FileNotFoundLen]
MOV BP, OFFSET FileNotFoundMsg
JMP Abort
ENDP FileNotFound
PROC EntryPoint NEAR
; ===========================================================
; Инициализация:
; Установка стека
; Сохранение старого значения Int 1E
; Установка параметров дискеты (Int 1E)
; Установка сегментных регистров
; ===========================================================
CLD
LES DI, [DWORD Int1E]
MOV SP, DI
LDS SI, [ES:1Eh * 4]
MOV [ES:1Eh * 4], DI
MOV [ES:1Eh * 4 + 2], CS
IFDEF DOS ; Для отладки COM файла!!!
PUSH CS ; Для случая DOS необязятельно CS=0
POP ES ; Поэтому надо переустановить ES
ENDIF ; Для корректроно обращения к Int1E
MOV [ES:Int1E], SI
MOV [ES:Int1E+2], DS
MOV CX, 11
REP MOVSB
MOV [BYTE ES:DI-2], 15
PUSH CS
POP DS
; На текущий момент:
; ES=DS=CS [=0]
; SP = 7BE0
; CX = 0
; AX, BX, DX, SI, DI, BP = ?
; ===========================================================
; ===========================================================
; Чтение FAT
; ===========================================================
MOV BX, OFFSET FAT_TABLE
MOV AX, [ReservedSector]
MOV CX, [SectorPerFat]
CALL ReadSectors
; На текущий момент:
; ES=DS=CS [=0]
; ES:BX = Конец прочитанной первой копии FAT
; CX = 0
; AX, DX, SI, DI, BP = ?
; ===========================================================
; ===========================================================
; Чтение корневого каталога
; ===========================================================
MOV AX, [RootEntries]
MOV CL, 5
SHL AX, CL
CWD
MOV CX, [BytePerSector]
DIV CX
MOV CX, AX ; CX --- количество секторов, занимаемых корневым каталогом
MOV AL, [BYTE SectorPerFat]
MUL [BYTE FatCount]
ADD AX, [ReservedSector] ; AX --- начальный сектор корневого каталога
MOV BP, AX
ADD BP, CX ; BP --- первый сектор второго! кластера
PUSH BX
CALL ReadSectors
POP DI
; На текущий момент:
; ES=DS=CS [=0]
; ES:DI = Начало корневого каталога
; ES:BX = Конец корневого каталога
; BP = первой сектор второго кластера
; CX = 0
; BP
; AX, DX, SI = ?
; ===========================================================
; ===========================================================
; Ищем наш файлик...
; ===========================================================
@@1:
CMP DI, BX
JGE FileNotFound
MOV SI, OFFSET ExeName
MOV CL, 11
REPE CMPSB
JZ @@Found
ADD DI, CX
ADD DI, 21
JMP @@1
; На текущий момент:
; ES=DS=CS [=0]
; ES:DI = Конец имени файла в элементе корневого каталога
; ES:BX = Конец корневого каталога
; DS:SI = SkipIP (поскольку идет сразу за ExeName)
; BP = первой сектор второго кластера
; CX = 0
; AX, DX = ?
; ===========================================================
; ===========================================================
; Выделяем память под найденный файл...
; ===========================================================
@@Found:
MOV AX, [WORD DS:DI+15]
PUSH AX
PUSH BX
CALL ReadCluster
POP BX
IFDEF DOS ; Для случая DOS пользуемся тем,
MOV ES, CX ; что после вызова ReadCluster,
MOV AX, [WORD ES:0413h] ; значение CX = 0, настраиваем ES и
ELSE ; обращаемся к области данных BIOS.
MOV AX, [WORD DS:0413h] ; При загрузке CS=DS=0, поэтому
ENDIF ; настройка не требуется.
MOV CL, 6
SHL AX, CL
MOV DX, [WORD DS:BX+0004h] ; Размер EXE в 512-байтовых страницах
DEC CL
SHL DX, CL
SUB AX, DX
SUB AX, [WORD DS:BX+000Ah] ; Минимальный HEAP
MOV ES, AX
XOR BX, BX
POP AX
IFDEF DOS ; Для случая DOS писать куда нам хочеться
PUSH CS ; нельзя, поэтому переустанавличаем ES
POP ES ; на начало сегмента. Результат предыдущих
ENDIF ; нужен только для проверки правильности кода.
PUSH ES
; На текущий момент:
; DS=CS [=0]
; ES:BX = Место, куда будет скопирован EXE-файл
; DS:DI = Конец имени файла в элементе корневого каталога
; DS:SI = SkipIP (поскольку идет сразу за ExeName)
; AX = Первый кластер EXE-файла
; CX = 5
; DX = Чему-то из заголовка EXE-файла
; BP = первой сектор второго кластера
; Stack[0] = Сегмент, начиная с которого читается EXE-файл
; ===========================================================
; ===========================================================
; Чтение EXE-файла
; ===========================================================
@@ReadLoop:
; Читаем кластер
PUSH AX
CALL ReadCluster
; Коррекция на переполнение
MOV AX, ES
MOV CL, 4
SHR BX, CL
ADD AX, BX
MOV ES, AX
XOR BX, BX
POP AX
; Находим следующий кластер
MOV DI, AX
SHR DI, 1
PUSHF
ADD DI, AX
ADD DI, OFFSET FAT_TABLE
MOV AX, [WORD DS:DI]
POPF
JC @@OddCluster
AND AH, 0Fh
JMP @@FindComplete
@@OddCluster:
MOV CL, 4
SHR AX, CL
@@FindComplete:
CMP AX, 0FF8h
JL @@ReadLoop
POP DX
MOV DS, DX
; На текущий момент:
; CS [=0]
; ES:BX = Конец прочитанного EXE-файла
; CS:SI = SkipIP (поскольку идет сразу за ExeName)
; DS, DX = Сегмент, начало заголовка EXE-файл
; AX, CX, DI = ?
; BP = первой сектор второго кластера
; ===========================================================
; ===========================================================
; Настройка адресов
; ===========================================================
ADD DX, [WORD DS:0008h] ; Размер заголовка в 16-байтовых параграфах
MOV SI, [WORD DS:0018h] ; Первый элемент таблицы перемещения
MOV CX, [WORD DS:0006h] ; Число элементов аблицы перемещения
JCXZ @@RelocationComplete
@@RelocationNext:
LODSW
MOV BP, AX
LODSW
ADD AX, DX
MOV ES, AX
MOV AX, [WORD ES:BP]
ADD AX, DX
MOV [WORD ES:BP], AX
LOOP @@RelocationNext
; На текущий момент:
; CS [=0]
; DS = Сегмент, начало заголовка EXE-файла
; DX = Сегмент, начало образа EXE-файла
; CX = 0
; AX, BX, SI, DI, BP, ES = ?
; ===========================================================
; ===========================================================
; GO!
; ===========================================================
@@RelocationComplete:
MOV SP, [WORD DS:0010h] ; Значение SP
MOV AX, [WORD DS:000Eh] ; Значение SS
ADD AX, DX
MOV SS, AX
MOV AX, [WORD DS:0016h] ; Значение CS
ADD AX, DX
PUSH AX
MOV AX, [WORD DS:0014h]
ADD AX, [WORD CS:SkipIP]
PUSH AX
RETF
; На текущий момент:
; CS:IP Точка входа в EXE-файл + SkipIP
; SS:SP Стек EXE-файла
; DS = Сегмент, начало заголовка EXE-файла
; DX = Сегмент, начало образа EXE-файла
; CX = 0
; AX, BX, SI, DI, BP, ES = ?
; ===========================================================
ENDP
ORG 7DFEh
DB 055h, 0AAh
FAT_TABLE:
END
... << RSDN@Home 1.1 beta 2 >>