суботу, 4 травня 2013 р.

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

Що таке SYS.COM --- якраз розповідалося. Кому цікаво, що являв собою PC-DOS 1.00, просимо сюди, а ми перейдемо до дизасемблювання.


Загальний вступ написано в пості про перші вправи з дизасемблюванням: "Аналіз DATE.COM з PC-DOS 1.00". Далі виходитиму з того, що ви його проглянули. Також знадобиться інформація, що таке FCB, і з чим їх їдять.

Нагадаю хіба, що SYS.COM --- програма типу COM. Такі програми побайтово вантажаться у пам'ять, починаючи із зміщення 100h від початку сегменту (перед нею, цих 0FFh байтах, знаходиться PSP). Коли програмі передається керування, гарантується, що:
  • CS=DS=ES=SS, всі вказують на той же сегмент,
  • SP=0FFFEh -- стек у кінці сегмента, росте вниз,
  • IP=0100h -- починаємо зразу після PSP.
  • AL = 00h, якщо перший FCB в PSP має правильну літеру диску, 0FFh, якщо ні.
  • AH -- аналогічно для другого FCB з PSP.
NASM остаточно задовбав -- напевне, він хороший асемблер (це не іронія!), але ряд його звичок, наприклад говорити "times db" замість звичного "db dup", ISTRUC-збочення і т.д. занадто суперечать моїм дитячим звичка. Тому, нижче наведено код, зроблено сумісним з моїм улюбленим Flat Assembler (fasm). Файл-заголовок, з описом FCB, взято з поста про FCB, але підігнано під конкретні потреби, і виправлено пару косяків.

;  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   : E1B70C19E40A68D90FF9DFC498AB7374
; File Name   : <>\PC-DOS_1.0\SYS.COM
; Format      : MS-DOS COM-file
; Base Address: 1000h Range: 10100h-10480h Loaded length: 380h


 ; CPU 8086
 use16
 org 100h

 include "my_fcb_2a.inc"

start:
 jmp short EntryPoint1

BadDriveLetter:    
 mov dx, aInvalidDriveSp ; "Invalid drive specification\r\n\n$"
 jmp PrnMsgNExit ; MSG -- in DX

AnyArgIsInvalid:   
 mov dx, aInvalidParamet ; "Invalid parameter\r\n\n$"
 jmp PrnMsgNExit ; MSG -- in DX

; ===========================================================================
unused_a15Jul81 db '15-Jul-81'
; ===========================================================================

EntryPoint1:    
 mov sp, Stack_top ; Stack top. 46Fh
 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
 cmp byte [ds:5Dh], ' ' ; In PSP, 5Ch-6Bh (16 bytes) -- Unopened Standard FCB 1
    ; So here will be any symbol, entered after [a-z][:][\]
 jnz short AnyArgIsInvalid

ChkForIBMBIO:    
 mov ah, 0Fh
 mov dx, FCBext_1 ; Check for file IBMBIO.COM on current disk.
 int 21h  ; DOS - OPEN DISK FILE
    ; DS:DX -> FCB
    ; Return: AL = 00h file found, FFh file not found
 or al, al
 jz short IBMBIO_found

InsertSysDisk1:    
 call RequestInsertSysDisk ; If not found IBMBIO.COM -- ask to
    ; insert system disk and try once more
 jmp short ChkForIBMBIO

IBMBIO_found:    
 mov ax, word [FCBext_1.FileDate]
 mov [IBMBIO_date], ax ; Saving the IBMBIO.COM file date
 mov ah, 0Fh
 mov dx, FCBext_2 ; Checking for IBMDOS.COM on current disk
 int 21h  ; DOS - OPEN DISK FILE
    ; DS:DX -> FCB
    ; Return: AL = 00h file found, FFh file not found
 or al, al
 jnz short InsertSysDisk1
;
; We have found IBMBIO.COM and IBMDOS.COM files. (No check for COMMAND.COM)
 mov ax, word FCBext_2.FileDate
 mov [IBMBDOS_date], ax ; Saving IBMBDOS.COM file date
 
 mov ah, 1Ah
 mov dx, IBMBIO_DTA ; Data area contains strange byte...
 int 21h  ; DOS - SET DISK TRANSFER AREA ADDRESS
    ; DS:DX -> disk transfer buffer
 xor ax, ax
 mov word [FCBext_1.DirectRecord], ax
 mov word [FCBext_1.DirectRecord+2], ax
 mov word [FCBext_2.DirectRecord], ax
 mov word [FCBext_2.DirectRecord+2], ax
 mov ax, 1
 mov word [FCBext_1.RecordSize], ax
 mov word [FCBext_2.RecordSize], ax
 mov ah, 27h
 mov dx, FCBext_1
 mov cx, 0C00h ; =3072=6*512; real size=1920=3*512
 int 21h  ; DOS - RANDOM BLOCK READ
    ; DS:DX -> FCB
    ; CX = number of records to be read
 mov [ReadedFromBIO], cx ; Size of file in bytes, if less than 3072

 mov ah, 1Ah  ; DTA for IBMDOS
 mov dx, IBMBIO_DTA+0C00h ; "IBMDOS_DTA", IBMBIO_DTA (46F) +0C00h = 106Fh
 int 21h  ; DOS - SET DISK TRANSFER AREA ADDRESS
    ; DS:DX -> disk transfer buffer
 mov ah, 27h
 mov dx, FCBext_2
 mov cx, 2000h ; 8196=32*512; real size=6400=12*512
 int 21h  ; DOS - RANDOM BLOCK READ
    ; DS:DX -> FCB
    ; CX = number of records to be read
 mov [ReadedFromDOS], cx ; Size of file in bytes, if less than 8196
; 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
; Check if files are present in target disk and
; are compatible in sizes
 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,
    ; Global variable File_Date_ptr contains pointer
    ; to file date
 mov bx, FCBext_2
 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,
    ; Global variable File_Date_ptr contains pointer
    ; to file date
 mov dx, IBMBIO_DTA
 mov ah, 1Ah
 int 21h  ; DOS - SET DISK TRANSFER AREA ADDRESS
    ; DS:DX -> disk transfer buffer
 mov ax, IBMBIO_date
 mov [File_Date_ptr], ax ; Elegant! Almost (ersatz) pointer :-)
    ; Moreover, it is global variable, known by function...
 mov bx, FCBext_1
 call WriteSysFile ; At call DX points to DTA with data to write,
    ; BX -- unopened tagret extended FCB
 mov ax, IBMBDOS_date
 mov [File_Date_ptr], ax
 mov dx, IBMBIO_DTA+0C00h ; "IBMDOS_DTA", IBMBIO_DTA (46F) +0C00h = 106Fh
 mov ah, 1Ah
 int 21h  ; DOS - SET DISK TRANSFER AREA ADDRESS
    ; DS:DX -> disk transfer buffer
 mov bx, FCBext_2
 call WriteSysFile ; At call DX points to DTA with data to write,
    ; BX -- unopened tagret extended FCB
 mov dx, aSystemTransfer ; "System transferred$"
; Single exit point. Just messages differs.

PrnMsgNExit:    
 mov ah, 9  ; MSG -- in DX
 int 21h  ; DOS - PRINT STRING
    ; DS:DX -> string terminated by "$"
 int 20h  ; DOS - PROGRAM TERMINATION
    ; returns to DOS--identical to INT 21/AH=00h

; ============== subroutine =================================================
; 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,
; Global variable File_Date_ptr contains pointer
; to file date

ChkSysFilesOnTARGETdisk:

; FUNCTION CHUNK AT 022B SIZE 0000000A BYTES

 mov ah, 23h
 mov dx, bx  ; BX points to extended FCB
 int 21h  ; DOS - GET FILE SIZE
    ; DS:DX -> unopened FCB with filename and record size
    ; All fields are already initialized
 or al, al
 jnz short noRoomForSys

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

 ;mov ax, word [bx+ExtFCB_t.DirectRecord] ; BX points to extended FCB
 mov ax, word [word bxExtFCB_t.DirectRecord] ; BX points to extended FCB
      ; [word ...] --- to use disp16, not disp8
    ;  +28h -- DirectRecord
 mov dx, [word bx+2Ch] ; After each FCB we have variable with
    ; number of bytes readed from file
 test ax, 1FFh ; 1FFh=512-1
    ; Check if target file has sector, which is not full.
    ; If do have -- add 512 to size
 jz short NoPartialSector
 add ax, 200h ; 512

NoPartialSector:   
 and ax, 0FE00h ; Zero last 9 bits -- offset in sector
 test dx, 1FFh
 jz short NoPartialSector2 ; Same for DX, readed source file size
 add dx, 200h ; 512

NoPartialSector2:   
 and dx, 0FE00h
 cmp ax, dx  ; Check if source and target files are equal,
    ; measuring in entire sectors.
 jnz short IncompatibleSysSize
 retn
; ChkSysFilesOnTARGETdisk endp


; ============== subroutine =================================================
; At call DX points to DTA with data to write,
; BX -- unopened tagret extended FCB

WriteSysFile: 
 xor ax, ax
 ;mov word [bx+ExtFCB_t.DirectRecord], ax ; <= FASM do not support that form
 mov word [word bxExtFCB_t.DirectRecord], ax ; BX points to extended FCB
    ;  +28h -- DirectRecord
 ;mov [bx+ExtFCB_t.DirectRecord+2], ax ;  +2Ah -- DirectRecord+2
 mov word [word bxExtFCB_t.DirectRecord+2], ax ;  +2Ah -- DirectRecord+2
 mov dx, bx
 mov ah, 0Fh
 int 21h  ; DOS - OPEN DISK FILE
    ; DS:DX -> FCB
    ; Return: AL = 00h file found, FFh file not found
 ;mov word [bx+ExtFCB_t.RecordSize], 1 ; <= FASM do not support that form
 mov word [word bxExtFCB_t.RecordSize], 1 ; +15h -- Record size in Extended FCB
 mov ah, 28h
 mov cx, [word bx+2Ch] ; After each FCB we have variable with
    ; number of bytes readed from file
 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, File_Date_ptr
 ;mov word [bx+ExtFCB_t.FileDate], ax ; <= FASM do not support that form
 mov word [word bxExtFCB_t.FileDate], ax ; Restore file date, date specified
    ; by pointer
 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

; ===============================================================
; START OF FUNCTION CHUNK FOR ChkSysFilesOnTARGETdisk

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

IncompatibleSysSize:   
 mov dx, aIncompatibleSy ; "Incompatible system size$"
 jmp short PrnMsgNExit ; MSG -- in DX
; END OF FUNCTION CHUNK FOR ChkSysFilesOnTARGETdisk

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

RequestInsertSysDisk: 
 mov ah, 10h  ; Close files in case if we have already opened
    ; at least one of them.
 mov dx, FCBext_1
 int 21h  ; DOS - CLOSE DISK FILE
    ; DS:DX -> FCB
    ; Return: AL = 00h directory update successful
    ; FFh file not found in directory
 mov dx, FCBext_2
 int 21h  ; DOS - CLOSE DISK FILE
    ; DS:DX -> FCB
    ; Return: AL = 00h directory update successful
    ; FFh file not found in directory
 mov dx, aInsertSystemDi ; "Insert system disk and strike any key$"
 mov ah, 9
 int 21h  ; DOS - PRINT STRING
    ; DS:DX -> string terminated by "$"
 mov ax, 0C01h
 int 21h  ; DOS - FLUSH BUFFER AND READ STANDARD INPUT
    ; AL = STDIN input function to execute after
    ; flushing buffer, other registers as appropriate
    ; for the input function.
    ; (If AL is not one of 01h,06h,07h,08h, or 0Ah,
    ; the buffer is flushed but no input is attempted)
    ;
 mov ah, 2
 mov dl, 0Dh
 int 21h  ; DOS - DISPLAY OUTPUT
    ; DL = character to send to standard output
 mov dl, 0Ah
 int 21h  ; DOS - DISPLAY OUTPUT
    ; DL = character to send to standard output
 mov dl, 0Ah
 int 21h  ; DOS - DISPLAY OUTPUT
    ; DL = character to send to standard output
 retn
;RequestInsertSysDisk endp

; ===============================================================

aInvalidDriveSp db 'Invalid drive specification',0Dh,0Ah 
  db 0Ah,'$'
aInvalidParamet db 'Invalid parameter',0Dh,0Ah 
  db 0Ah,'$'
aIncompatibleSy db 'Incompatible system size$'     
  db 0B0h ; °
aNoRoomForSyste db 'No room for system on destination disk$'
     
  db 0B0h ; °
aSystemTransfer db 'System transferred$' 
  db 0B0h ; °
aInsertSystemDi db 'Insert system disk and strike any key$'     
  db 0B0h ; °

FCBext_1 ExtFCB_t 0,"IBMBIO  ", "COН", 0,6,4
;FCBext_1 db 0FFh   ; ExtMarker      
;     ; Extended FCB structure
;  db 5 dup(0)  ; Reserved1
;  db 6   ; Attribute
;  db 0   ; Driver
;  db 'I', 'B', 'M', 'B', 'I', 'O', 2 dup(' '); FileName
;  db 'C', 'O', 'Н'        ; FileExt
;  db 4, 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
  
ReadedFromBIO dw 0   ; 

FCBext_2 ExtFCB_t 0,"IBMDOS  ", "COН", 0,6,4
;FCBext_2 db 0FFh   ; ExtMarker      
;     ; Extended FCB structure
;  db 5 dup(0)  ; Reserved1
;  db 6   ; Attribute
;  db 0   ; Driver
;  db 'I', 'B', 'M', 'D', 'O', 'S', 2 dup(' '); FileName
;  db 'C', 'O', 'Н'        ; FileExt
;  db 4, 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
  
ReadedFromDOS dw 0   ; 

File_Date_ptr dw 0   ; 

IBMBIO_date dw 0   ; 
IBMBDOS_date dw 0   ; 

STACK_1  db 100h dup(0)
Stack_top:    ; Initial stack top at the top of stack region

IBMBIO_DTA db 0C9h ; Й  ; 
  db 10h dup(0)

my_fcb_2a.inc

; This file is generated by The Interactive Disassembler (IDA)     
; Copyright (c) 2010 by Hex-Rays SA, <support@hex-rays.com>     
; Licensed to: Freeware version       
;
; Structures definition for SYS.COM and others

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
}

Код цей успішно компілюється. Запускати результат під оригінальним PC-DOS 1.00 не пробував, але, за винятком різниці у кількох байтах, що відповідають командам, для яких сучасний асемблер вибрав інше кодування-синонім, ніж тогочасний, він тотожній оригінальній програмі. (Теоретично, строго по списку не всі відмінності перевірив, тому і тут 100% гарантії не дам, що якусь не прогледів, але :).

Довелося, окрім звичного з попередніх разів "near", також використати конструкцію типу "mov  ax, word [word...]", щоб попросити асемблер вибрати інструкцію з disp16, а не на байт ефективнішу, з disp8. (Ще натяк на недопрацювання тодішнього асемблера). Для того, щоб сказати FASM, що в BX буде адреса структури FCB, використано його директиву "virtual at bx".

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

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

На початку програми стоїть типовий для багатьох COM-файлів перехід на точку входу, EntryPoint1. Після нього --- два шматки коду, які поміщають в DX адресу того чи іншого повідомлення і здійснюють стрибок на кінець програми. Ці мітки я так і назвав: BadDriveLetter і AnyArgIsInvalid. Остання назва "кодує" той факт, що зустрівши хоч щось в переданому, окрім літери диску, програма повідомляє про "Invalid parameter".

Процедура завершення програми, за міткою PrnMsgNExit, виводить стрічку, на яку вказує DX, і завершує програму, викликавши int 20h. Якщо завершення успішне, то виводиться повідомлення "System transferred".

Після цих двох блоків йде стрічка '15-Jul-81', очевидно дата "копірайту", яка ніде не використовується. Далі --- точка входу (мітка EntryPoint1).

Цікаво, ще два таких фрагменти коду трапляються далі, ближче до кінця програми, між підпрограмами. Одна,  noRoomForSys, повідомляє що "No room for system on destination disk", (хоча й обманює трохи при цьому, див. далі), друга --- IncompatibleSysSize, повідомляє, що системні файли на цільовому диску несумісного розміру. Що під цим має на увазі програма, побачимо нижче. Ймовірно, вони так розкидані, щоб дотягувалися короткі умовні переходи.

Фактично виконання починається в EntryPoint1. Спочатку встановлюється стек на виділену в тілі програми  256-байтову (100h) область пам'яті. Потім відбувається перевірка, чи AL не дорівнює 0FFh. (При виклику COM-програми в AL буде 0, якщо диск в першому FCB з PSP коректний, тобто коректний диск було передано першим аргументом програми, 0FFh в іншому випадку.)  Якщо диск не коректний, відбудеться перехід на мітку BadDriveLetter і завершення програми.

Якщо диск коректний, перевіряється чи в полі імені файлу стоїть пробіл. Якщо ні, тобто крім диску було введено ще якесь ім'я файлу, переходимо до AnyArgIsInvalid. (Однак, аргументи, передані після літери диску, але відділені хоча б пробілом, повністю проігноруються.)

Наступний крок --- на поточному диску пробуємо відкрити IBMBIO.COM. Робимо це за допомогою підготовленого Extended FCB, який збережено в тілі програми (мітка FCBext_1, диск -- 0, поточний). Якщо файл відкрився, зберігаємо його дату та, за допомогою другого розширеного FCB (FCBext_2), пробуємо відкрити IBMDOS.COM (теж на поточному диску). Якщо файл успішно відкрився, зберігається і його дата.

Якщо хоча б один із цих файлів не відкрився, викликається підпрограма, "RequestInsertSysDisk", яка просить вставити системний диск і знову стрибаємо на спробу відкрити IBMBIO.COM. Зверніть увагу на реалізацію цього блоку в коді -- істинне спагеті, раз стрибаємо, раз не стрибаємо і т.д. і т.п. :-)

RequestInsertSysDisk закриває обидва FCB -- раптом хоча б один із файлів було відкрито. (Якщо б хто дивився в OAK, код реалізації FCB, він би побачив, що там матюгають тупі програми, які FCB по декілька разів закривають. Чого б це? :). Потім виводиться текст: "Insert system disk and strike any key", очищаємо буфер і чекаємо натискання клавіші. Коли клавішу натиснуто, двічі переводиться рядок (ефективно це дає один порожній рядок перед наступним повідомленням) і підпрограма завершується.

Якщо всі файли знайдено, DTA встановлюється на мітку IBMBIO_DTA, яка знаходиться в кінці програми. Нагадую, при роботі з FCB, читання-запис відбувається в області DTA. Зрозуміло, що DTA знаходиться після тіла програми. (COM-файл вважає, що вся пам'ять вище нього -- його.) Але, після мітки IBMBIO_DTA, файл програми містив ще 11 байт, 10 нулів і загадковий 0C9h, який ніде не використовується. Якесь сміття потрапило? 

Згадуючи про загадкові символи. Якщо подивитися на поле розширення обох FCB, там пише: "CO=" або "COН"  -- залежно від вашого кодування. Справа в тому, що після звичайних англійських "CO" йде символ з кодом 0CDh. В кодуванні CP1251 це літера еН, в розширеному ASCII -- символ псевдографіки. Звідки він взявся, що означає і як після такого COM-файли успішно відкриваються? 0CDh = 1100 1101b. Якщо відкинути старший біт, отримаємо 0100 1101b = 4Dh = 'M'. Деталей не знаю, але траплялися згадки, що, десь в ту епоху, існувала традиція кінець стрічки помічати відмінним від нуля старшим бітом. Тобто, з точки зору такого підходу, це звичайна 'M'+ознака кінця рядка. Очевидно, код обробки FCB якось враховував таку можливість, бо успішно працював як варіант із старшим бітом, так і без нього. Поки його ще не проаналізував детально, але в "дизасемблі" від Don Jindra для PC DOS 1.10, при обробці імені, для кожної літери старший біт просто обнуляється.

Отож, встановивши DTA, для обох FCB (вже відкритих -- наперед немає сенсу) встановлюється розмір запису 1, початковий запис -- нульовий.

З IBMBIO.COM пробується прочитати 0C00h=3072 байти (6 секторів). Реальний розмір файлу -- 1920, не знаю, чи випадково, але рівно три сектори. Тобто пробується читати з запасом. Як відомо, в такій ситуації читання через FCB читає, скільки може, потім повертає кількість прочитаних записів у CX. Ця величина зберігається у змінні ReadedFromBIO.

Далі DTA переставляється на IBMBIO_DTA+0C00h --- зразу після області, яка була б використана, якщо б прочитали всіх 0C00h байт. Це потрібно зробити, так як DTA єдиний для всіх операцій, а наступний крок --- прочитати IBMDOS.COM. Читається 2000h  = 8196 байт (32 сектори), за реального розміру файлу -- 6400 байт (рівно 12 секторів). Скільки реально прочитано, зберігається в ReadedFromDOS. Ніякого явного контролю помилок не проводиться. Важливо, що змінні з кількістю прочитаних байт знаходяться зразу після відповідних FCB.

Після того, як файли прочитані, їх дати збережені, в обидва FCB записується цільовий диск. Файли не закриваються --- може собі дозволити, у цій версії DOS вся інформація про відкритий файл зберігається безпосередньо в FCB. (Та й відбувалося тільки читання -- спорожнювати буфери, навіть якщо такі існують, немає сенсу).  

Далі стає цікавіше. Адреса кожного з FCB (і розташованої зразу після нього кількості прочитаних байт) поміщається в BX і викликається підпрограма, умовно мною названа: ChkSysFilesOnTARGETdisk.

Ця підпрограма визначає розмір файлу, на який вказує переданий FCB (засобами FCB, AH=23h/INT 21h). Тобто, увага, програма визначає розмір файлу, на диску, куди вона має перенести системний файл із тим же іменем. Якщо розмір визначити не вдається, тобто, наприклад, файл не знайдено -- повідомляється, що "No room for system on destination disk". Нормально. :-) Щоб SYS.COM могла перенести систему, відповідні системні файли вже мають бути на цільовому диску. Звичайно, вміст їх може відрізнятися. Однак, як показує подальший код, розмір, у сектора, теж має співпадати. (Алгоритм порівняння розмірів кумедний -- див. код.) Якщо розмір співпав -- процедура повертає керування, якщо ні -- переходить до виведення повідомлення "Incompatible system size" і програма завершується.

Коли всі перевірки успішно відбулися, програма переходить до запису. Для обох системних файлів DTA встановлюється на образ в пам'яті файлу, який буде записуватися (спершу --- IBMBIO_DTA), в спеціальну змінну (File_Date_ptr) поміщається дата файлу, який записуватиметься, в BX --- адреса його FCB, і викликається підпрограма запису (WriteSysFile). File_Date_ptr --- така собі глобальна змінна-вказівник, щоб єдина процедура працювала з обома файлами. (Ще одна ілюстрація бардака --- читаються файли кожен окремо, хоч і однаково, розмір їх зберігаємо після FCB, дату в іншому місці. Ну ніякої одноманітності у вирішенні однакових задач. :)

Якщо WriteSysFile обидва рази відпрацювала успішно, (хоча, перевірки помилок у ній і так немає), виводиться повідомлення "System transferred" і програма закінчується.

На завершення подивимося на WriteSysFile. Вона відкриває файл, встановлює розмір запису в 1, запис з початку, кількість байт --- розмір прочитаного файлу, збережений раніше (у змінній зразу після FCB), здійснює запис. Після цього в FCB поміщається збережена раніше дата вихідного файлу і файл закривається. Жодних перевірок на помилки не здійснюється.


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

  • Програмі слід передавати коректне ім'я диску. Що таке коректні диски, строго не скажу -- слід завершити аналіз коду обробки FCB, але так виглядає, що такі, які реально існують.
  • Після імені диску не повинно бути імені файлу ("b:" припустиме, а "b:somefile.ext" чи "b" -- ні). Однак, відділене пробілом може бути все, що завгодно.
  • Програма читає системні файли IBMIBIO.COM/IBMDOS.COM (напевне, в MS-DOS це були б IO.SYS i DOS.SYS) з поточного диску. Якщо не може їх прочитати --- просить вставити диск із системою.
  • Після цього перевіряється, чи на цільовому диску є файли з такими ж іменами. Якщо немає --- повідомляє, що на диску недостатньо місця. Якщо є, але іншого розміру (з точністю до цілих секторів), повідомляється про несумісний розмір системи. (Яка чітка і точна діагностика, слів бракує...).
  • Якщо файли є, їх розмір сумісний, то прочитані з поточного диску файли перезаписують своїх тезок на цільовому диску. 
Тобто, реально переносити систему вона не вміла. Вміла відновити системні файли, які вже існували, у випадку, коли ті були пошкоджені, але все ще займали правильні сектори --- ніякої спроби гарантувати, що під час запису буде вибрано послідовні сектори на початку диску, не робилося.

Про код

Як на конкретних прикладах згадувалося вище --- код, місцями, нахабне спагеті. Переходи туди-сюди, виходи з підпрограм коли як, дані розкидано і т.д. Розбиратися для розваги -- цікаво, але працювати з ним -- жах. Подивимося, як ситуація мінялася з часом.


 Дякую за увагу!

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

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