;********************************************************* ; Загрузчик для дискеты ; Версия 1.10 ; (С) Mustitz 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