Indrekis
Indrekis

Categories

  • retrocomputing
  • ibm_pc_compat

Tags

  • retrocomputing
  • IBM PC та сумісні
  • 86-DOS
  • SYS.COM

Поки возився з 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
}