четвер, 9 травня 2013 р.

Аналіз SYS.COM з PC-DOS 1.10

Рухаємося далі. Наступив черговий, 1982 рік. В травні вийшла наступна версія PC-DOS, 1.10. Революційною вона не була. Виправлено помилки, додано підтримку двосторонніх дискет (320кб). Для файлів почав зберігатися і час зміни, не тільки дата. З'явився ще один системний виклик, "set/reset verify switch", AH=2Eh/INT 21h (не траплялося мені в документації згадок, що він тільки з 1.10, але в коді їх IBMDOS.COM це видно однозначно). TIME i DATE стали внутрішніми командами, тепер їх виконанням займався COMMAND.COM. Всілякі дрібні зміни там і сям. В Microsoft ця версія мала номер 1.24 (наступна, 1.25, постачалася іншим ОЕМ). (Детальніше про номери версій див. тут, шукайте за стрічкою "1.24").

Подивимося, що змінилося в SYS.COM з виходом цієї версії. Повторюватися не буду, загальну інформацію див. попередній пост ("Аналіз SYS.COM з PC-DOS 1.00") і посилання там.


SYS.COM все ще залишилася COM програмою (і, візуально, продовжувала залишатися ним до самого кінця мікрософтівського DOS, хоча всередині все стало значно складніше -- слідкуйте за наступними постами).

Код -- сумісний з Flat Assembler. 

;
;  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 nasm and commented by Indrekis, indrekis2.blogspot.com
 
; Input MD5   : F4759B60F3A5FB8B8323CED9B2437E5A

; File Name   : PC-DOS_1.10\SYS.COM
; Format      : MS-DOS COM-file
; Base Address: 0h Range: 100h-35Dh Loaded length: 25Dh

IBMDOS_DTA  = 35Eh
IBMBIO_DTA  = 235Eh


  ; CPU.086
  use16
  org 100h

  include "my_fcb_2a.inc"
 
start:  
  jmp short EntryPoint1

AnyArgIsInvalid:   
  mov dx, aInvalidParamet ; "Invalid parameter$"
  jmp PrnMsgNExit

BadDriveLetter:    
  mov dx, aInvalidDriveSp ; "Invalid drive specification$"
  jmp PrnMsgNExit
  
; ===========================================================================
; Now situation when one of system files is absent
; is handled in more structured way --- just in one place.
;
; If asking to insert disk, all drive checks are done once more
;
; Unlike PC-DOS 1.00, do not attempt file closing.
; Also, "anykey" input is done without echo.

AskToInsertSysDisk:   
  mov al, [currentDrive]
  add al, 40h  ; Cbrrent FCB driver number to letter
  mov [drive_letter_char], al
  mov dx, aInsertSystemDi ; "Insert system disk in drive "
  mov ah, 9
  int 21h  ; DOS - PRINT STRING
     ; DS:DX -> string terminated by "$"
  mov ax, 0C08h ; Waiting for any key
  int 21h  ; DOS - CLEAR KEYBOARD BUFFER
     ; AL must be 01h, 06h, 07h, 08h, or 0Ah.
  xor al, al
; ---------------------------------------------------------------------------

EntryPoint1:    
  cmp byte [ds:5Dh], 20h ; PSP: 5Ch-6Bh (16 bytes) -- Unopened Standard FCB 1
     ; So here will be any symbol, entered after [a-z][:][\]
  jnz short AnyArgIsInvalid
  cmp al, 0FFh ; COM-format executables begin running with the following register values:
     ;   AL = 00h if first FCB has valid drive letter, FFh if not
     ;   AH = 00h if second FCB has valid drive letter, FFh if not
     ; CS,DS,ES,SS = PSP segment SP = offset of last word available in first 64K segment
     ; ( http://www.ctyme.com/intr/rb-2939.htm )
  jz short BadDriveLetter
; Change from PC-DOS-1.00: Swapped order of checks.
; Now first check if there are any spurious letters (filenames, switches, etc)
; and if none, check if driver letter is correct.
; May be -- attempt to have more adequate error messages

; Change from PC-DOS-1.00: added check for attempt
; to write system files into disk, we are running (and reading them from)
  cmp byte [ds:5Ch], 0 ; Check for current drive,
     ; disabling "sys" to self
  jz short BadDriveLetter
  mov ah, 19h
  int 21h  ; DOS - GET DEFAULT DISK NUMBER
  inc al
  mov [currentDrive], al
  cmp [ds:5Ch], al ; One more check to not "sys" to self
  jz short BadDriveLetter 

; Open system files
  mov ah, 0Fh
  mov dx, FCBext_1
  int 21h  ; DOS - OPEN DISK FILE
     ; DS:DX -> FCB
     ; Return: AL = 00h file found, FFh file not found
  or al, al
  jnz short AskToInsertSysDisk
  
  mov dx, FCBext_2
  mov ah, 0Fh
  int 21h  ; DOS - OPEN DISK FILE
     ; DS:DX -> FCB
     ; Return: AL = 00h file found, FFh file not found
  or al, al
  jnz short AskToInsertSysDisk
;
; We have found IBMBIO.COM and IBMDOS.COM files. (No check for COMMAND.COM)
  mov ah, 1Ah  ; Set DTA somewhere high in the our segment
  mov dx, IBMBIO_DTA ; 235Eh, above our code 
  int 21h  ; DOS - SET DISK TRANSFER AREA ADDRESS
     ; DS:DX -> disk transfer buffer
; Both FCBs are opened, so we can set record size
  mov ax, 1
  mov word [FCBext_1.RecordSize], ax
  mov word [FCBext_2.RecordSize], ax
; Read IBMBIO
  mov bx, FCBext_1
  mov cx, 8000h ; Reading at most 8000h bytes=32768=64*512
     ;   Real size =1920=3*512, hasn't changed from DOS 1.00
  call ReadFile ; On entry BX -- FCB, DTA is set
     ; Saves after the FCB file date, file time and number
     ; of readed bytes (recors, but recors size=1)
; IBMBIO readed, proceeding to IMBDOS
  mov dx, IBMDOS_DTA ; Code in file ends at 34Fh. Above it we have several
     ; variables, and 35Eh is the next free byte address.
     ; So set DTA just above it.
  mov ah, 1Ah
  int 21h  ; DOS - SET DISK TRANSFER AREA ADDRESS
     ; DS:DX -> disk transfer buffer
  mov bx, FCBext_2
  mov cx, 2000h ; Read at most 2000h bytes = 8192 = 16*512
     ;   Real size =6400=12*512, hasn't changed from DOS 1.00
  call ReadFile ; On entry BX -- FCB, DTA is set
     ; Saves after the FCB file date, file time and number
     ; of readed bytes (recors, but recors size=1)
; IBMDOS readed.
;
; Both files readed, their date and size saved.
;
  mov al, [ds:5Ch] ; Target disk from PSP FCB1
  mov [FCBext_2.Driver], al ; Set for FCB2
  mov [FCBext_1.Driver], al ; Set for FCB1
  
; DOS 1.10 knows two types of disk -- single side, 160 kb,
; and two-sided, 320kb,(instead of just one for DOS 1.00)
; So SYS.COM need to check.
  mov dl, [ds:5Ch] ; Get FAT info for target drive
  mov ah, 1Ch
  int 21h  ; DOS - GET ALLOCATION TABLE INFORMATION FOR SPECIFIC DRIVE
     ; DL = drive number to check (0=default, 1=A, etc)
     ;
     ; Return (http://www.ctyme.com/intr/rb-2591.htm):
     ; AL = sectors per cluster (allocation unit), or FFh if invalid drive
     ; CX = bytes per sector
     ; DX = total number of clusters
     ; DS:BX -> media ID byte
; Just ignore media byte
  push cs  ; Previous call "spoiled" DS (DS:BX -> media ID byte)
  pop ds  ;  Restoring
  mov ah, 0
  mul cx  ; AX=sectors per cluster, CX=bytes per sector
     ; DX:AX=CX*AX -- bytes in cluster
     ; DX will be 0 -- cluster size is, of course, less than 64K
  xchg ax, cx  ; Cluster size to CX
  
; Check if files are present in target disk and
; are compatible in sizes

; If any of files fails to open, (because it do not exists
; or for any other reason), print "No room for system on destination disk" and exit.
; If file exists, but uses different number of clusters
; print "Incompatible system size" and exit.
  mov bx, FCBext_1
  call ChkSysFilesOnTARGETdisk ; Checks if system file with same name already
     ; exists on target disk and is compatible
     ; by size. Strange behaviour, as for system transfering...
     ; BX -- points to file FCB and filse size, stored after the FCB.
     ;
     ; Opens file during it. If opening failed, jumps to
     ; output of error "No room for system".
     ;
     ; At exit sets flags by comparison  of number in
     ; cluster for source and target files (cmp <src>,<dst>).
  jnz short IncompatibleSysSize ; Error, if files size in clusters is not equal.
  mov bx, FCBext_2
  call ChkSysFilesOnTARGETdisk 
  
  ja short IncompatibleSysSize ; Error, if source file size (in clusters) is large
     ; than destination file size.
; It is interesting -- difference of decision algorithms
; for IBMBIO and IBMDOS is intetional? Equality of sizes
; is requested only for IBMBIO, other file can be of same
; or large size.
; PC-DOS 1.00 checked both files for equal sizes.
;
; All checks successfully completed. Proceeding to write.
  mov dx, IBMBIO_DTA ; DTA to readed IBMBIO
  mov ah, 1Ah
  int 21h  ; DOS - SET DISK TRANSFER AREA ADDRESS
     ; DS:DX -> disk transfer buffer
  mov bx, FCBext_1
  call WriteSysFile ; At call BX -- unopened tagret extended FCB, size,
     ; date and time is saved after the FCB, DTA is set
     ; to memory with source file contents.
  mov dx, IBMDOS_DTA ; DTA to readed IBMDOS
  mov ah, 1Ah
  int 21h  ; DOS - SET DISK TRANSFER AREA ADDRESS
     ; DS:DX -> disk transfer buffer
  mov bx, FCBext_2
  call WriteSysFile ; At call BX -- unopened tagret extended FCB, size,
     ; date and time is saved after the FCB, DTA is set
     ; to memory with source file contents.
  mov dx, aSystemTransfer ; "System transferred$"
; Single exit point. Just messages differs.

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
; ===========================================================================     

noRoomForSys:    
  mov dx, aNoRoomForSyste ; "No room for system on destination disk$"
  jmp short PrnMsgNExit
; ===========================================================================

IncompatibleSysSize:   
  mov dx, aIncompatibleSy ; "Incompatible system size$"
  jmp short PrnMsgNExit


; Reading of files is much more structured than in
; PC-DOS 1.00. Even separate procedure used, instead
; of code mess before.
;
; Though, still no error checking -- we hope, that
; files was read correctly.

virtual at bx
        bxExtFCB_t ExtFCB_t 0,"        ","   ",0,0,0 ; To cheat fasm, needs exactly 8/3 spaces
       ; In fact, values are ignored.
end virtual

; ============== subroutine =================================================

; On entry BX -- FCB, DTA is set
; Saves after the FCB file date, file time and number
; of readed bytes (recors, but recors size=1)

ReadFile:
  mov ah, 27h
  mov dx, bx
  int 21h  ; DOS - RANDOM BLOCK READ
     ; DS:DX -> FCB
     ; CX = number of records to be read

  ; After the FCB we have variables IBM???_date, IBM???_time, IBM???_readed_size
  mov [bx+30h], cx 
  mov ax, word [bxExtFCB_t.FileDate]
  mov [bx+2Ch], ax ; 
  mov ax, word [bxExtFCB_t.FileTime]
  mov [bx+2Eh], ax ; 
  retn
;ReadFile endp


; ============== subroutine =================================================

; That function had changet it role (from DOS 1.00). Now it also
; opens the file. So, different int 21h function is used.
; Also, partially filled clusters handling code simplified.

; Checks if system file with same name already
; exists on target disk and is compatible
; by size. Strange behaviour, as for system transfering...
; BX -- points to file FCB and filse size, stored after the FCB.
;
; Opens file during it. If opening failed, jumps to
; output of error "No room for system".
;
; At exit sets flags by comparison  of number in
; cluster for source and target files (cmp <src>,<dst>).

ChkSysFilesOnTARGETdisk:
  mov ah, 0Fh  ; On entry BX -- FCB, DTA is set
     ; CX -- bytes in cluster
  mov dx, bx
  int 21h  ; DOS - OPEN DISK FILE
     ; DS:DX -> FCB
     ; Return: AL = 00h file found, FFh file not found
     ;
     ; We have not closed file, read before, because for
     ; DOS 1.XX all info about opened file is in FCB, so
     ; no internal structures used.
  or al, al
  jnz short noRoomForSys ; At any error prints "No room"...
  mov ax, word [bxExtFCB_t.FileSize] ; Opened in target disk file size
  xor dx, dx
  add ax, cx
  dec ax  ; AX - file size+cluster size-1
  div cx  ; AX -- clusters in opened file, rounded to upper.
  push ax
  mov ax, [bx+30h] ; Saved file size
  add ax, cx
  dec ax
  xor dx, dx
  div cx  ; AX -- clusters in source file, rounded to upper.
  pop dx  ; DX (saved later AX) -- clusters in opened file
  cmp ax, dx  ; Check if source and target files are equal, measuring in entire clusters.
  retn
;ChkSysFilesOnTARGETdisk endp


; ============== subroutine =================================================

; At call BX -- unopened tagret extended FCB, size,
; date and time is saved after the FCB, DTA is set
; to memory with source file contents.

; Unlike for PC-DOS 1.00, at call of this subroutine file is already opened.
; Additionally it restores file time.
; Still no error checking.


WriteSysFile: 
  mov dx, bx
  xor ax, ax
  mov word [bxExtFCB_t.DirectRecord], ax
  mov word [bxExtFCB_t.DirectRecord+2], ax
  inc ax
  mov word [bxExtFCB_t.RecordSize], ax ; Record size = 1
  mov ah, 28h
  mov cx, [bx+30h] ; Readed file size
  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
  mov ax, [bx+2Ch] ; Set target file date and time, saved from source file
  mov word [bxExtFCB_t.FileDate], ax
  mov ax, [bx+2Eh]
  mov word [bxExtFCB_t.FileTime], ax
  mov ah, 10h
  int 21h  ; DOS - CLOSE DISK FILE
     ; DS:DX -> FCB
     ; Return: AL = 00h directory update successful
     ; FFh file not found in directory
  retn
;WriteSysFile endp

; ===========================================================================
aInvalidDriveSp db 'Invalid drive specification$' 
aInvalidParamet db 'Invalid parameter$' 
aInsertSystemDi db 'Insert system disk in drive ' 
drive_letter_char db 41h  
aAndStrikeAnyKe db 0Dh,0Ah
  db 'and strike any key when ready',0Dh,0Ah,'$'
aNoRoomForSyste db 'No room for system on destination disk$'
     
aIncompatibleSy db 'Incompatible system size$' 
aSystemTransfer db 'System transferred$' 
currentDrive db 0   

FCBext_1 ExtFCB_t 0,"IBMBIO  ", "COM", 0,6,0
;FCBext_1 db 0FFh   ; ExtMarker      
;  db 5 dup(0)  ; Reserved1
;  db 6   ; Attribute
;  db 0   ; Driver
;  db 'I', 'B', 'M', 'B', 'I', 'O', 2 dup(' '); FileName
;  db 'C', 'O', 'M'        ; FileExt
;  db 2 dup(0)  ; CurBlock
;  db 2 dup(0)  ; RecordSize
;  db 4 dup(0)  ; FileSize
;  db 2 dup(0)  ; FileDate
;  db 2 dup(0)  ; FileTime
;  db 8 dup(0)  ; Reserved2
;  db 0   ; CurRecord
;  db 4 dup(0)  ; DirectRecord
IBMBIO_date dw 0
IBMBIO_time dw 0
IBMBIO_readed_size dw 0

FCBext_2 ExtFCB_t 0,"IBMDOS  ", "COM", 0,6,0
;FCBext_2 db 0FFh   ; ExtMarker 
;  db 5 dup(0)  ; Reserved1
;  db 6   ; Attribute
;  db 0   ; Driver
;  db 'I', 'B', 'M', 'D', 'O', 'S', 2 dup(' '); FileName
;  db 'C', 'O', 'M'        ; FileExt
;  db 2 dup(0)  ; CurBlock
;  db 2 dup(0)  ; RecordSize
;  db 4 dup(0)  ; FileSize
;  db 2 dup(0)  ; FileDate
;  db 2 dup(0)  ; FileTime
;  db 8 dup(0)  ; Reserved2
;  db 0   ; CurRecord
;  db 4 dup(0)  ; DirectRecord
IBMDOS_date dw 0
IBMDOS_time dw 0
IBMDOS_readed_size dw 0

Розмір, як не смішно, 605 байт. При тому, програма ще й дещо мудріша від своєї попередниці (розміром 896 байт).

Скачати код, разом із лістингом, згенерованим IDA, скомпільованим файлом та, для порівняння, оригінальним SYS.COM, можна тут. Скомпільований код, з точністю до опкодів-синонімів, тотожній оригінальному.

Алгоритм роботи

На початку --- стрибок до точки входу. Між початком і нею знаходиться коди виведення повідомлень "Invalid parameter" і "Invalid drive specification" та код прохання вставити системний диск. Код цей, на відміну від попередника, не закриває ніякі файли, і не виводить еха натиснутої "any key". При тому, розташований він так, що зразу по завершенню програма перейде до точки входу. 

Аналогічно, після коду основного тіла програми, знаходиться ще два обробника помилок, "No room for system on destination disk" і "Incompatible system size".

На відміну від всіх досліджених системних програм з версії 1.00, стек не переставляється. Тому, якщо пам'яті замало, буде ОЙ. Хоча, ОЙ був би і в старішій версії, але більш контрольований, тоді як зараз затиратиметься стек.  

Першим кроком йде перевірка, чи немає у імені файлу з першого FCB в PSP чогось, окрім пробіла. Якщо є -- стрибає на виведення "Invalid parameter" і завершується. Далі перевіряє, чи коректний диск у цьому ж FCB. Якщо ні --- повідомляє, про це, "Invalid drive specification". Порядок перевірок змінено, в порівнянні з 1.00, ймовірно, для більш адекватного повідомлення про помилки. 

Далі перевіряється, чи ми не пробуємо записати файли на той же диск, з якого їх читаємо. Робиться дві перевірки, спершу на код диску 0, -- чи не є "поточним", а потім на співпадіння "абсолютного" коду поточного диску із тим, що передано (враховується відмінність нумерації дисків для FCB і взагалі в DOS/BIOS). Якщо диски співпадають, пише "Invalid drive specification" і завершується. 1.00 такого не робила.

Наступним кроком відкриваємо, користуючись розташованим в коді FCB, IBMBIO.COM і IBMDOS.COM. Якщо не вдалося відкрити котрийсь з них, просимо вставити системний диск і починаємо з початку. Цей код став значно прямолінійнішим, ніж був раніше. 

Якщо відкрили успішно, кожен із файлів читаємо в пам'ять, підпрограмою ReadFile, якій в BX передається FCB, в CX -- скільки прочитати (як і раніше, "з перельотом"). Перед викликом ReadFile також встановлюється DTA. ReadFile зберігає реальний розмір, дату і час вихідного файлу у змінні, що знаходяться зразу після кожного з FCB. (І тут порядок навели.) Контролю помилок не здійснюється.

DOS 1.10 вже знав про двосторонні дискети, тому, перш ніж порівнювати розмір файлів, програма отримуває інформацію про FAT (AH=1Ch/INT 21h), множить кількість секторів у кластері на кількість байт у секторі, щоб знайти розмір кластера в байта, зберігає його в CX. 

Далі, для кожного файлу, змінивши в його FCB диск на цільовий і передавши адресу цього FCB в BX, викликається ChkSysFilesOnTARGETdisk.  Функція хитріша, ніж її аналог раніше. Спершу вона пробує відкрити відповідний файл (але вже на цільовому диску), якщо їй не вдалося (його немає) --- повідомляє "No room for system on destination disk" і виходить. Якщо відкрився -- порівнює розмір вихідного та цільового файлів у кластерах (cmp <src-size>, <dest-size>), відповідним чином встановивши прапорці. (Розмір у кластерах рахується трішки елегантніше, ніж раніше --- (file size+cluster size-1)/cluster size).

Після повернення з  ChkSysFilesOnTARGETdisk код робить дещо дивне (для мене, принаймні). Для "IBMBIO.COM" перевіряє, чи розміри, в кластерах, рівні. Для "IBMDOS.COM" менш строго, лише чи цільовий файл є не меншим за вихідний. Якщо ці умови не виконуються -- "No room for system on destination disk", інакше переходить до запису.

Запис відбувається практично так само, як і раніше, єдине що відновлюється і час файлу. Перевірки на помилки все ще немає.

Ніяких стрічок із копірайтом чи датою в коді немає.

"Документація"

Основна відмінність від попередньої версії -- здійснюється перевірка запису на диск, з якого запускається програма, розмір файлів порівнюється в кластерах а не секторах і трішки адекватнішими стали повідомлення про помилки.

Про код

Код став акуратнішим --- менше хаотичних переходів туди-сюди, програма стала структурованішою. Цікаво, що хоча опкоди-синоніми відрізняються, на відміну від програмок з DOS 1.00, жодного разу не довелося асемблеру казати вибрати менш ефективний код. Очевидно, їхній тогочасний асемблер теж покращився.

Наступний крок --- PC-DOS 2.00.  Зовсім інша ОС, насправді: з'явилася підтримка директорій, жорстких дисків, кількох форматів дискет. Подивимося.


А поки:
 Дякую за увагу!

Немає коментарів:

Дописати коментар