Поки возився з SYS.COM з 86-DOS версій 0.11 та 0.34, випадково дизасемблював цю ж утиліту з 86-DOS 1.10. Тож, вирішив доповнити колекцію статей про неї.
Аналіз
Алгоритм:
- Здається, якоюсь магією (згідно зашитих в ОС таблиць?) визначає кількість зарезервованих секторів.
- Виглядає, що API вміє отримувати цю інформацію лише для поточного диску, або автори чомусь обмежилися лише цим варіантом системного виклику.
- Шукає файл
86DOS.SYS
на диску-джерелі. - Загадково обраховує, ймовірно, його розмір.
- Читає його вміст.
- Читає зарезервовані сектори.
- Записує зарезервовані сектори на цільовий диск.
- Пробує відкрити файл 86DOS.SYS на цільовому диску.
- Якщо не вдалося – пробує знайти такий файл.
- Якщо знайшовся (але не відкрився на запис) – завершується з повідомленням “No place for system”.
- Інакше – створює його.
- Отримавши тим чи іншим способом відкритий на запис 86DOS.SYS, записує його вміст.
- Якщо вдалося – закриває файл та завершується.
- Якщо ні – повідомляє “No room for system”.
Якщо порівняти з версією для 86-DOS 0.11-1.00, тепер турбується ще й про 86DOS.SYS, не тільки про зарезервовані сектори. Траплялася мені згадка, що окремий 86DOS.SYS виділили, коли ОС перестала вміщатися у зарезервовані сектори (доріжки).
Щодо своєрідної наступниці, з PC DOS 1.00, вона:
- не займається зарезервованими секторами,
- природно – в DOS ця ідея з CP/M не використовується,
- зате турбується про час і дату файлів.
- Системних файлів тепер два.
- Також, вона вміє попросити змінити диск за потреби,
- ймовірно, під 86-DOS потрібно було два дисководи…
- Обробка помилок ґрунтовніша, але програма очікує, що файли з потрібним іменем на цільовому диску вже є – ще тоді дуже дивувався цій вимозі…
Ще цікаво, тут файл має в FCB розширення COM
, а в PC DOS 1.00 відобразиться як CO=
, COН
, абощо – залежно від вашого кодування, оскільки в останньому байтів встановлено старший біт, як ознаку кінця стрічки. Здавалося б, дуже архаїчна річ, але, видається, прийшла вона в перші DOS не з 86-DOS.
Тобто, ідейно, SYS.COM з 86-DOS 1.10 справді посередині між старішими версіями і першою версією з PC DOS, але код дуже різний, важко якесь “успадкування” побачити.
Жодна з цих версій не копіює COMMAND.COM – це вже потім з’явилося.
Завантажити код, разом із лістингом, згенерованим IDA, скомпільованим файлом та, для порівняння, оригінальним SYS.COM, можна тут.
Реконструйовані джерельні тексти
Код, сумісний з Flat Assembler. Вибачте за надміру широке форматування, але редагувати потребуватиме більше часу, ніж вартує результат. Отриманий бінарник трішки відрізняється від оригінального – FASM в кількох місцях підставляє інші, еквівалентні опкоди, наприклад, мнемоніка XOR DX, DX
, асемблюється в:
- 0x33, в оригіналі, опкод
XOR r16, r/m16
, - 0x31, FASM-ом, опкод
XOR r/m16, r16
.
Порівняння результату асемблювання FASM (ліворуч) та оригіналу (праворуч) SYS.COM з 86-DOS 1.10. |
; This file is generated by The Interactive Disassembler (IDA)
; Copyright (c) 2010 by Hex-Rays SA, <support@hex-rays.com>
; Licensed to: Freeware version
;
; Modified to compile by fasm and commented by Indrekis, https://indrekis.github.io/
;
; Input MD5 : FC97207A7FDF7F5E33EE8B933CCCC584
; File Name : D:\WorkWWW\Blog\ComputersHistory.S\images_dos\#86DOS\open-simh\simh\BIN\SYS_v11.COM
; Format : MS-DOS COM-file
; Base Address: 0h Range: 100h-2D0h Loaded length: 1D0h
;.8086
use16
org 100h
include "my_fcb_2c.inc"
start:
jmp short EntryPoint1
; ===========================================================================
BadDriveLetter:
mov dx, aInvalidDriveSp ; "Invalid drive specification$"
noSystemOnDiskA:
jmp PrnMsgNExit
; ===========================================================================
EntryPoint1:
cmp al, 1
jz short BadDriveLetter
mov dl, byte [ds:5Ch] ; In PSP, 5Ch-6Bh (16 bytes) -- Unopened Standard FCB 1
; so it should be a targed driver letter
cmp dl, 2 ; В оригіналі перший байт цього опкоду 0x82, FASM генерує 0x80 -- еквівалентний
jb short BadDriveLetter
mov ah, 19h
int 21h ; DOS - GET DEFAULT DISK NUMBER
mov [currentDrive], al
mov ah, 0Eh
dec dl
int 21h ; DOS - SELECT DISK
; DL = new default drive number (0 = A, 1 = B, etc.)
; Return: AL = number of logical drives
mov ah, 1Fh
int 21h ; DOS - GET DEFAULT DRIVE PARAMETER BLOCK
; Return: AL = 00h No Error, FFh Error
; DS:BX -> drive parameter block
mov ax, [bx+5] ; shift count to convert clusters into sectors for DOS: https://www.ctyme.com/intr/rb-2594.htm
; Looks like for the 86DOS it is number of reserved sectors
mov [cs:defDiskClsToSecShiftDef], ax
mov ah, 0Eh
mov dl, 0
int 21h ; DOS - SELECT DISK
; DL = new default drive number (0 = A, 1 = B, etc.)
; Return: AL = number of logical drives
mov ah, 1Fh
int 21h ; DOS - GET DEFAULT DRIVE PARAMETER BLOCK
; Return: AL = 00h No Error, FFh Error
; DS:BX -> drive parameter block
mov ax, [bx+5] ; shift count to convert clusters into sectors: https://www.ctyme.com/intr/rb-2594.htm
; Does it meant something different back then? Number of reserved sectors?
push es
pop ds
mov word ptr defDiskClsToSecShiftTarg, ax
mov ah, 0Eh
mov dl, [currentDrive]
int 21h ; DOS - SELECT DISK
; DL = new default drive number (0 = A, 1 = B, etc.)
; Return: AL = number of logical drives
mov ah, 0Fh
mov dx, FCB_86DOS_SYS
int 21h ; DOS - OPEN DISK FILE
; DS:DX -> FCB
; Return: AL = 00h file found, FFh file not found
or al, al ; В оригіналі 0x0A, FASM генерує еквівалентний 0x08
mov dx, aNo86dos_sysOnD ; "No 86DOS.SYS on drive A$"
jnz short noSystemOnDiskA
mov [FCB_86DOS_SYS.RecordSize], 1
mov ah, 1Ah
mov dx, B86DOS_DTA ; Structure is, in fact, large
int 21h ; DOS - SET DISK TRANSFER AREA ADDRESS
; DS:DX -> disk transfer buffer
mov dx, FCB_86DOS_SYS
mov ah, 27h
mov cx, [ds:6] ; WTF? In DOS PSP contains addres of the "CALL 5" command,
; so, if it is correct for this version, it is the OS code segment moved to CX
sub cx, 6DBh ; Some pervert way to calculate file size?
int 21h ; DOS - RANDOM BLOCK READ
; DS:DX -> FCB
; CX = number of records to be read
cmp al, 1
mov dx, aErrorIn86dos_s ; "Error in 86DOS.SYS on drive A$"
jnz short PrnMsgNExit
mov [records_read], cx
add cx, 2DBh ; CX contains numer of records read by the previous call.
; ("CX = number of records read (return AL = 00h or 03h)")
mov bx, cx ; В оригіналі опкод 8B, FASM використав еквівалентний 89
xor dx, dx ; В оригіналі опкод 33, FASM використав еквівалентний 31
mov cx, word ptr defDiskClsToSecShiftTarg
mov al, 0 ; Read reserved sectors?
push bx
int 25h ; DOS - ABSOLUTE DISK READ (except DOS 4.0/COMPAQ DOS 3.31 >32M partitn)
; AL = drive number (0=A, 1=B, etc), DS:BX = Disk Transfer Address (buffer)
; CX = number of sectors to read, DX = first relative sector to read
; Return: CF set on error
jb short onHardDiskError ; I hope, hard disk error here means hard error of the disk and is unrelated to the HDD
popf
pop bx
mov al, [ds:5Ch] ; targed driver letter(?)
mov [FCB_86DOS_SYS.Driver], al ; Src FCB drive
mov [FCB_TARGET_86DOS_SYS.Driver], al ; Target FCB drive
dec al
xor dx, dx ; В оригіналі опкод 33, FASM використав еквівалентний 31
mov cx, [defDiskClsToSecShiftDef]
int 26h ; DOS - ABSOLUTE DISK WRITE (except DOS 4.0/COMPAQ DOS 3.31 >32M partn)
; AL = drive number (0=A, 1=B, etc), DS:BX = Disk Transfer Address (buffer)
; CX = number of sectors to write, DX = first relative sector to write
; Return: CF set on error
jb short onHardDiskError
popf
mov ah, 0Fh
mov dx, FCB_86DOS_SYS
int 21h ; DOS - OPEN DISK FILE
; DS:DX -> FCB
; Return: AL = 00h file found, FFh file not found
or al, al
jz short file_86DOS_found
mov dx, FCB_TARGET_86DOS_SYS
mov ah, 11h
int 21h ; DOS - SEARCH FIRST USING FCB
; DS:DX -> FCB
or al, al
mov dx, aNoPlaceForSyst ; "No place for system$"
jz short PrnMsgNExit
mov dx, FCB_86DOS_SYS
mov ah, 16h
int 21h ; DOS - CREATE A DISK FILE
; DS:DX -> FCB
file_86DOS_found:
xor ax, ax
; Тут .DirectRecord, використав CurRecord + 1, щоб отримати той самий розмір файлу
mov word [FCB_86DOS_SYS.CurRecord + 1], ax
mov word [FCB_86DOS_SYS.CurRecord + 1], ax
inc ax
mov [FCB_86DOS_SYS.RecordSize], ax
mov cx, [records_read] ; Src FCB record size
mov ah, 28h
mov dx, FCB_86DOS_SYS
int 21h ; DOS - RANDOM BLOCK WRITE
; DS:DX -> FCB
; CX = number of records to be written
; if zero, truncate file to current random file position
or al, al
mov dx, aNoRoomForSyste ; "No room for system$"
jnz short PrnMsgNExit
mov dx, FCB_86DOS_SYS
mov ah, 10h
int 21h ; DOS - CLOSE DISK FILE
; DS:DX -> FCB
; Return: AL = 00h directory update successful
; FFh file not found in directory
int 20h ; DOS - PROGRAM TERMINATION
; returns to DOS--identical to INT 21/AH=00h
; ===========================================================================
onHardDiskError:
mov dx, aHardDiskError ; "HARD DISK ERROR$"
PrnMsgNExit:
mov ah, 9
int 21h ; DOS - PRINT STRING
; DS:DX -> string terminated by "$"
int 20h ; DOS - PROGRAM TERMINATION
; returns to DOS--identical to INT 21/AH=00h
; ===========================================================================
aHardDiskError db 'HARD DISK ERROR$'
aInvalidDriveSp db 'Invalid drive specification$'
aNo86dos_sysOnD db 'No 86DOS.SYS on drive A$'
aErrorIn86dos_s db 'Error in 86DOS.SYS on drive A$'
aNoPlaceForSyst db 'No place for system$'
aNoRoomForSyste db 'No room for system$'
FCB_TARGET_86DOS_SYS ExtFCB_cut_t 0,"????????", "???", 0,6,0
; FCB_TARGET_86DOS_SYS ExtFCB_cut_t <0FFh, 0, 6, <0, '????????', '???', 0, 0, 0, 0, 0, 0, 0>>
FCB_86DOS_SYS ExtFCB_cut_t 0, "86DOS ", "SYS", 0,6,0
; FCB_86DOS_SYS ExtFCB_t <0FFh, 0, 6, <0, '86DOS ', 'SYS', 0, 0, 0, 0, 0, 0, 0, ?>>
; Тут хак -- ExtFCB_cut_t, а не ExtFCB_t, щоб розмір файлу збігався, але потрібно зберегти зміщення далі
paddingForRecord db 4 dup(?)
defDiskClsToSecShiftTarg db ?
db ?
defDiskClsToSecShiftDef dw ?
records_read dw ?
currentDrive db ?
B86DOS_DTA db 35h dup(?)
; my_fcb_2c.inc -- structures definition for 86-DOS 1.10 SYS.COM
struc FCB_t in_drive,in_name,in_ext,in_record_size ; (sizeof=0x25) -- http://indrekis2.blogspot.com/2013/02/dos-fcb.html
{
.Driver db in_drive; Drive specified, Init by User, 0 = default, 1 = A, etc, FFh is not allowed
.FileName db in_name ; Filename, padded by spaces, 20h, Init by User
assert $-.FileName <= 8
if $-.FileName < 8
lbl=$
.name_padding db lbl-.FileName dup 20h
end if
.FileExt db in_ext ; Extension, padded by spaces, 20h, Init by User
assert $-.FileExt <= 3
if $-.FileExt < 3
lbl=$
.ext_padding db lbl-.FileExt dup 20h
end if
.CurBlock db 2 dup 0 ; Current Block, Init by DOS
.RecordSize dw in_record_size+0 ; (2 dup 0) ; Record size, Init by DOS, def. val=80h=128d
.FileSize db 4 dup 0 ; File size, Init by DOS
.FileDate db 2 dup 0 ; File date, Init by DOS
.FileTime db 2 dup 0 ; File time, Init by DOS
.reserv1 db 8 dup 0 ;
.CurRecord db 1 dup 0 ; Current record, Init by User
.DirectRecord db 4 dup 0 ; Random record, Init by User
}
; Attribute byte format
; bit meaning if bit = 1
; --- ---------------------------------------
; 7 unused
; 6 unused
; 5 file has been changed since last backup
; 4 entry represents a subdirectory
; 3 entry represents a volume label
; 2 system file
; 1 hidden file
; 0 read-only
struc ExtFCB_t in_drive,in_name,in_ext,in_record_size,in_attrib, in_curblock ; (sizeof=0x2C)
{
.ExtMarker db 0FFh ; Extended FCB ID, Init by User
.reserv2 db 5 dup 0 ;
.Attribute db in_attrib ; File attribute, Init by User
.Driver db in_drive; Drive specified, Init by User, 0 = default, 1 = A, etc, FFh is not allowed
.FileName db in_name ; Filename, padded by spaces, 20h, Init by User
assert $ - .FileName <= 8
if $-.FileName < 8
lbl=$
.name_padding db lbl-.FileName dup 20h
end if
.FileExt db in_ext ; Extension, padded by spaces, 20h, Init by User
assert $-.FileExt <= 3
if $-.FileExt < 3
lbl=$
.ext_padding db lbl-.FileExt dup 20h
end if
.CurBlock dw in_curblock ; Current Block, Init by DOS
.RecordSize dw in_record_size+0 ; (2 dup 0) ; Record size, Init by DOS, def. val=80h=128d
.FileSize db 4 dup 0 ; File size, Init by DOS
.FileDate db 2 dup 0 ; File date, Init by DOS
.FileTime db 2 dup 0 ; File time, Init by DOS
.reserv1 db 8 dup 0 ;
.CurRecord db 1 dup 0 ; Current record, Init by User
.DirectRecord db 4 dup 0 ; Random record, Init by User
}
struc ExtFCB_cut_t in_drive,in_name,in_ext,in_record_size,in_attrib, in_curblock ; (sizeof=0x28)
{
.ExtMarker db 0FFh ; Extended FCB ID, Init by User
.reserv2 db 5 dup 0 ;
.Attribute db in_attrib ; File attribute, Init by User
.Driver db in_drive; Drive specified, Init by User, 0 = default, 1 = A, etc, FFh is not allowed
.FileName db in_name ; Filename, padded by spaces, 20h, Init by User
assert $ - .FileName <= 8
if $-.FileName < 8
lbl=$
.name_padding db lbl-.FileName dup 20h
end if
.FileExt db in_ext ; Extension, padded by spaces, 20h, Init by User
assert $-.FileExt <= 3
if $-.FileExt < 3
lbl=$
.ext_padding db lbl-.FileExt dup 20h
end if
.CurBlock dw in_curblock ; Current Block, Init by DOS
.RecordSize dw in_record_size+0 ; (2 dup 0) ; Record size, Init by DOS, def. val=80h=128d
.FileSize db 4 dup 0 ; File size, Init by DOS
.FileDate db 2 dup 0 ; File date, Init by DOS
.FileTime db 2 dup 0 ; File time, Init by DOS
.reserv1 db 8 dup 0 ;
.CurRecord db 1 dup 0 ; Current record, Init by User
}