вівторок, 11 червня 2013 р.

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




В листопаді того ж, 1983 року, IBM випустила DOS 2.10. Нічого особливо нового в ній не було --- поточний апдейт, але подивимося детальніше до нутрощів (хоч і на тривіальному прикладі). 


Для кращого розуміння --- див. попередні статті серії. Код -- сумісний з Flat Assembler. 

;  This file is generated by The Interactive Disassembler (IDA)     
;  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, indrekis2.blogspot.com
;
; Input MD5   : 60077C698CB4907A2B1C8022BA16F98D
;
; File Name   : PC-DOS_2.10\SYS.COM
; Format      : MS-DOS COM-file
; Base Address: 0h Range: 100h-790h Loaded length: 690h

  ;.8086
  use16
  org 100h

  include "my_fcb_2b.inc"

start: 

  jmp short EntryPoint1 ;  Save AX
; ===========================================================================
  db  8Ah 
  db    5
aVers1_82 db 'Vers 1.82'
; ===========================================================================
; 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 )

EntryPoint1:    
  push ax  ;  Save AX
; Then check for DOS version. Exit if older than 1.54
; or newer than 2.10
  mov ah, 30h
  int 21h  ; DOS - GET DOS VERSION
     ; Return: AL = major version number (00h for DOS 1.x)
  xchg ah, al
  cmp ax, 136h
  jb short WrongDOSVersion
  cmp ax, 20Ah
  jbe short DosVersionOK

WrongDOSVersion:   
  mov dx, aIncorrectDosVe ; "Incorrect DOS version\r\n$"
  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
; ===========================================================================

DosVersionOK:    
  pop ax  ;  Restore initial AX, with default FCBs disk states.
  jmp short EntryPoint2CorrectVer ; PSP: 5Ch-6Bh (16 bytes) -- Unopened Standard FCB 1
     ; So here will be any symbol, entered after [a-z][:][\]
; ===========================================================================

ArgIsInvalid:    
  mov dx, aInvalidParamet ; "Invalid parameter"
  mov cx, [InvalidParametSize]
  jmp ExitWithCode1
; ===========================================================================

BadDriveLetter:    
  mov dx, aInvalidDriveSp ; "Invalid drive specification"
  mov cx, [InvalidDriveSpSize]
  jmp ExitWithCode1
; ===========================================================================

AskToInsertSysDisk:   
     
  mov al, [currentDrive]
  add al, 40h
  mov [aDriverLetter1], al
  mov dx, aInsertSystemDi ; "Insert system disk in drive "
  mov cx, [InsertSystemDiskFullSize]
  mov bx, 2  ; Standard Error Device handler (STDERR)
     ; http://stanislavs.org/helppc/file_handles.html
  mov ah, 40h
  int 21h  ; DOS - 2+ - WRITE TO FILE WITH HANDLE
     ; BX = file handle, CX = number of bytes to write, DS:DX -> buffer
  call WaitForAnyKey ; Clears buffer before and after.
  xor al, al  ; Restore AL -- 00 means correct first FCB (we have
     ; already checked).

EntryPoint2CorrectVer:   
  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 ArgIsInvalid
  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
;
; 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
  jz short BadDriveLetter
; Check FAT on destination disk
  push ax
  mov al, [ds:5Ch] ; ; Target driver number
  dec al  ; Adjust from FCB numbering to DOS
  mov bx, ReadedIBMDOS
  mov dx, 1
  mov cx, dx  ; Read one sector, starting from first sector
  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
  pop ax  ; Pop flags, left by int 25h
  pop ax  ; Restore AX
  jb short CheckSysFiles
  cmp [ReadedIBMDOS], 0F8h ; Media ID for HDD "Designated to be used for any
     ; partitioned fixed or removable media, where the
     ; geometry  is defined in the BPB."
     ; DOS 2.10 do not know smaller ID's
  jnb short CheckSysFiles
  jmp noRoomForSys
; ===========================================================================

CheckSysFiles:    
  add al, 40h  ; Disk number to letter
  mov byte [aAIbmbio_com], al ; "A:\\IBMBIO.COM"
  mov byte [aAIbmdos_com], al ; "A:\\IBMDOS.COM"
  cld
  mov dx, aAIbmbio_com ; "A:\\IBMBIO.COM"
  mov di, IBMBIO_hndlr
; Try to open both system files.
; Determine their size, time and date. Save them
; both with file handlers. In case of error --
; ask to insert system disk
  call GetFileSizeDateTime ; DX -- filename string
     ; DI -- address of file handler variable
     ; CF -- on error,
     ; If OK, Size, Time and Date are saved after the Handler
     ; Strange that size is saved twice -- two times lo word
     ; and two times hi word.
     ;

locContinuerOrError:   
  jb short AskToInsertSysDisk
  mov dx, aAIbmdos_com ; "A:\\IBMDOS.COM"
  mov di, IBMDOS_hndlr
  call GetFileSizeDateTime ; DX -- filename string
     ; DI -- address of file handler variable
     ; CF -- on error,
     ; If OK, Size, Time and Date are saved after the Handler
     ; Strange that size is saved twice -- two times lo word
     ; and two times hi word.
     ;
  jb short AskToInsertSysDisk
; Both files exist and successfully opened
  mov cx, sp
  sub cx, 98Ah ; Approximation of code+stack size?
  mov [PutativeMaxFreeMem], cx ; Possibly contains approximation of free mem,
     ; above code, below stack
  call ReadFilesToMem
  jb short locContinuerOrError ; IS too far for direct jump?
  mov al, [ds:5Ch] ; Get target drive
  mov byte [FCBext_forFind.Driver], al
  push ax
; Current dir of target disk is saved.
; No cd of source disk, as version from 2.00,
; just use X:\<filename> with correct disk letter.
; (2.00 were using FCB's, so had no choice.)
  mov dl, al
  mov si, CurDirBuffer ; ""
  mov ah, 47h
  int 21h  ; DOS - 2+ - GET CURRENT DIRECTORY
     ; DL = drive (0=default, 1=A, etc.)
     ; DS:SI points to 64-byte buffer area
     ;
     ; http://www.ctyme.com/intr/rb-2933.htm
     ; returned path does not include a drive
     ; or the initial backslash
  pop ax
; Set target disk current dir to root
  add al, 40h
  mov byte [DiskRootDir], al ; "X:\\"
  mov [CurDirDisk], al ; "X:\" -- beginning for next Dir Buffer
  push ax
  mov dx, DiskRootDir ; "X:\\"
  mov ah, 3Bh
  int 21h  ; DOS - 2+ - CHANGE THE CURRENT DIRECTORY (CHDIR)
     ; DS:DX -> ASCIZ directory name (may include drive)
  pop ax
  mov byte [aAIbmbio_com], al ; "A:\\IBMBIO.COM"
  mov byte [aAIbmdos_com], al ; "A:\\IBMDOS.COM"
  mov byte [anyFileMask], al ; "A:\\*.*"
; Search for any files on target disk
; Attributes dir, sys, hid are inclusive --- search
; for ordinary files + for files with any of that attributes.
  mov ah, 11h
  mov dx, FCBext_forFind
  int 21h  ; DOS - SEARCH FIRST USING FCB
     ; DS:DX -> FCB
  push ax
; Restore current disk current dir
  mov dx, CurDirDisk ; "X:\" -- beginning for next Dir Buffer
  mov ah, 3Bh
  int 21h  ; DOS - 2+ - CHANGE THE CURRENT DIRECTORY (CHDIR)
     ; DS:DX -> ASCIZ directory name (may include drive)
; Restore search files results and continue
  pop ax
  or al, al
  jz short SomeFilesFoundOnTrg
  mov ah, 11h
  mov [FCBext_forFind.Attribute], 8 ; ; So we searching for volume lable.
     ; ; (AFAIK, vol. cannot be accessed without
     ; ; FCB in DOS 2)
  mov dx, FCBext_forFind
  int 21h  ; DOS - SEARCH FIRST USING FCB
     ; DS:DX -> FCB
     ;
     ; For extended FCBs with search attribute 08h,
     ; the volume label (if any) will be returned
     ; even if the current directory is not the root dir.
  or al, al
  jnz short no_any_files ; Check for other file types?
  jmp noRoomForSys
; ===========================================================================

SomeFilesFoundOnTrg:   
  mov dx, aAIbmbio_com ; "A:\\IBMBIO.COM"
  mov cx, 7
  mov ah, 4Eh
  int 21h  ; DOS - 2+ - FIND FIRST ASCIZ (FINDFIRST)
     ; CX = search attributes
     ; DS:DX -> ASCIZ filespec
     ; (drive, path, and wildcards allowed)
     ; http://www.ctyme.com/intr/rb-2977.htm
     ;
     ; Under DOS 2.x, searching for attribute 08h
     ; (volume label) will also return normal files
  jnb short CheckForIBMDOS

loc224_noRoomForSys:   
  jmp noRoomForSys
; ===========================================================================

CheckForIBMDOS:    
  mov dx, aAIbmdos_com ; "A:\\IBMDOS.COM"
  mov ah, 4Eh
  int 21h  ; DOS - 2+ - FIND FIRST ASCIZ (FINDFIRST)
     ; CX = search attributes
     ; DS:DX -> ASCIZ filespec
     ; (drive, path, and wildcards allowed)
  jb short loc224_noRoomForSys

no_any_files:    
  mov dx, aAIbmbio_com ; "A:\\IBMBIO.COM"
  mov cx, 0
  mov ax, 4301h
  int 21h  ; DOS - 2+ - SET FILE ATTRIBUTES
     ; DS:DX -> ASCIZ file name
     ; CX = file attribute bits
  mov dx, aAIbmdos_com ; "A:\\IBMDOS.COM"
  mov cx, 0
  mov ax, 4301h
  int 21h  ; DOS - 2+ - SET FILE ATTRIBUTES
     ; DS:DX -> ASCIZ file name
     ; CX = file attribute bits
  mov dx, aAIbmbio_com ; "A:\\IBMBIO.COM"
  mov cx, 7
  mov ah, 3Ch
  int 21h  ; DOS - 2+ - CREATE A FILE WITH HANDLE (CREAT)
     ; CX = attributes for file
     ; DS:DX -> ASCIZ filename (may include drive and path)
  mov [TargetIBMBIO_hndlr], ax
  mov dx, aAIbmdos_com ; "A:\\IBMDOS.COM"
  mov ah, 3Ch
  int 21h  ; DOS - 2+ - CREATE A FILE WITH HANDLE (CREAT)
     ; CX = attributes for file
     ; DS:DX -> ASCIZ filename (may include drive and path)
  mov [TargetIBMDOS_hndlr], ax

ReadNWriteNextPart:   
  call WriteSysFilesFromMem
  jb short loc27b_noRoomForSys
  mov ax, [IBMDOS_size_hi_1]
  or ax, [IBMDOS_size_lo_1]
  or ax, [IBMBIO_size_hi_1]
  or ax, [IBMBIO_size_lo_1]
  jz short RestoreFilesDateNTime
  call ReadFilesToMem
  jnb short ReadNWriteNextPart
  jmp AskToInsertSysDisk
; ===========================================================================

loc27b_noRoomForSys:   
  jmp noRoomForSys
; ===========================================================================
; Unused code!
  mov dx, aIncompatibleSy ; "Incompatible system size"
  mov cx, [IncompatibleSySize]
  jmp short ExitWithCode1
; ===========================================================================
  nop

RestoreFilesDateNTime:   
  mov cx, [IBMBIO_time]
  mov dx, [IBMBIO_date]
  mov bx, [TargetIBMBIO_hndlr]
  mov ax, 5701h
  int 21h  ; DOS - 2+ - SET FILE'S DATE/TIME
     ; BX = file handle, CX = time to be set
     ; DX = date to be set
  mov ah, 3Eh
  int 21h  ; DOS - 2+ - CLOSE A FILE WITH HANDLE
     ; BX = file handle
  mov cx, [IBMDOS_time]
  mov dx, [IBMDOS_date]
  mov bx, [TargetIBMDOS_hndlr]
  mov ax, 5701h
  int 21h  ; DOS - 2+ - SET FILE'S DATE/TIME
     ; BX = file handle, CX = time to be set
     ; DX = date to be set
  mov ah, 3Eh
  int 21h  ; DOS - 2+ - CLOSE A FILE WITH HANDLE
     ; BX = file handle
  call WriteBoot ; Writes boot with correct BPB for 8-track FDD's
     ; Possibly attempt to automatically fix boot for old, (1.XX),
     ; incompatible floppies.
  mov dx, aSystemTransfer ; "System transferred"
  mov cx, [SystemTransferSize]
  xor al, al

PrnMsgNExitWithCode:   
  push ax
  mov bx, 2  ; Standard Error Device handler - can be redirected (STDERR)
     ; http://stanislavs.org/helppc/file_handles.html
  mov ah, 40h
  int 21h  ; DOS - 2+ - WRITE TO FILE WITH HANDLE
     ; BX = file handle, CX = number of bytes to write, DS:DX -> buffer
  pop ax
  mov ah, 4Ch
  int 21h  ; DOS - 2+ - QUIT WITH EXIT CODE (EXIT)
     ; AL = exit code
; ===========================================================================

ExitWithCode1:    
  mov al, 1  ;  Exit code -- 1 (error)
  jmp short PrnMsgNExitWithCode
;start  endp

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

RestoreStackNExit:   
     
  pop cx

Set_CF_NExit:    
  stc

Retn_2D1:    
  retn


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

; Tries to read IBMBIO and IBMDOS to memory.
; Read as much as fits. Sets CF in case of errors,
; clears otherwise.

ReadFilesToMem:

  mov cx, [PutativeMaxFreeMem] ; Possibly contains approximation of free mem,
     ; above code, below stack
  mov bx, [IBMBIO_hndlr]
  mov dx, ReadedIBMDOS
  push cx
  cmp [IBMBIO_size_hi_1], 0 ; If size above 64k -- read max size, which can be
     ; disposed in free mem.
  ja short PRoceedToRead
  cmp [IBMBIO_size_lo_1], cx ; If size is large than can feet in memory --
     ; read max size, which can be disposed in free mem.
  ja short PRoceedToRead
  mov cx, [IBMBIO_size_lo_1] ; Else read exactly file size bytes

PRoceedToRead:    
     
  mov ah, 3Fh
  int 21h  ; DOS - 2+ - READ FROM FILE WITH HANDLE
     ; BX = file handle, CX = number of bytes to read
     ; DS:DX -> buffer
     ;
     ; http://www.ctyme.com/intr/rb-2783.htm
     ; Returns AX = number of bytes actually read
  jb short RestoreStackNExit ; Error -- exit
  cmp ax, cx  ; Readed less than file size
  jnz short RestoreStackNExit
  add dx, ax  ; Calculate first free byte address
  mov [IBMDOS_buffer_addr], dx
  sub [IBMBIO_size_lo_1], ax ; Calculate, how much left to read
  sbb [IBMBIO_size_hi_1], 0
  pop cx
  sub cx, ax  ; Calculate free memory left
  mov bx, [IBMDOS_hndlr]
  cmp [IBMDOS_size_hi_1], 0 ; If size above 64k -- read max size, which can be
     ; disposed in free mem.
  ja short PRoceedToRead2
  cmp [IBMDOS_size_lo_1], cx ; If size is large than can feet in memory --
     ; read max size, which can be disposed in free mem.
  ja short PRoceedToRead2
  mov cx, [IBMDOS_size_lo_1]; Else read exactly file size bytes

PRoceedToRead2:    
     
  mov ah, 3Fh
  int 21h  ; DOS - 2+ - READ FROM FILE WITH HANDLE
     ; BX = file handle, CX = number of bytes to read
     ; DS:DX -> buffer
     ;
     ; Returns AX = number of bytes actually read
  jb short Retn_2D1 ; Error -- exit
  cmp ax, cx
  jnz short Set_CF_NExit ;  Readed less than file size -- exit
  add dx, ax  ; Calculate first free byte address
  mov [After_IBMDOS_buffer], dx
  sub [IBMDOS_size_lo_1], ax ; Calculate, how much left to read
  sbb [IBMDOS_size_hi_1], 0
  clc   ; Clear CF -- no error.

Retn_33A:    
  retn
;ReadFilesToMem endp ; 


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

; DX -- filename string
; DI -- address of file handler variable
; CF -- on error,
; If OK, Size, Time and Date are saved after the Handler
; Strange that size is saved twice -- two times lo word
; and two times hi word.
;

GetFileSizeDateTime:
  mov ax, 3D00h
  int 21h  ; DOS - 2+ - OPEN DISK FILE WITH HANDLE
     ; DS:DX -> ASCIZ filename
     ; AL = access mode
     ; 0 - read
     ; Returns AX = file handle (or err code, if CF=1)
  jb short Retn_33A
  stosw   ; Save file handler
; Determine and save file size
  mov bx, ax
  mov ax, 4202h
  xor cx, cx
  xor dx, dx
  int 21h  ; DOS - 2+ - MOVE FILE READ/WRITE POINTER (LSEEK)
     ; AL = method: offset from end of file
     ; Returns: DX:AX = new file position in bytes from
     ; start of file
  stosw   ; Save lower size word. Twice...
  stosw
  mov ax, dx
  stosw   ; Save higher size word. Twice...
  stosw
  xor dx, dx
  mov ax, 4200h ; Restore file pointer
  int 21h  ; DOS - 2+ - MOVE FILE READ/WRITE POINTER (LSEEK)
     ; AL = method: offset from beginning of file
  mov ax, 5700h
  int 21h  ; DOS - 2+ - GET FILE'S DATE/TIME
     ; BX = file handle
     ; http://www.ctyme.com/intr/rb-2992.htm
     ; CX = file's time (see #01665)
     ; DX = file's date
  mov ax, cx
  stosw   ; Store time
  mov ax, dx
  stosw   ; Store date

retGetFileSizeDateTime:   
     
  retn
;GetFileSizeDateTime endp

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

noRoomForSys:    
  mov dx, aNoRoomForSyste ; "No room for system on destination disk"
  mov cx, [NoRoomForSysteSize]
  jmp ExitWithCode1

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


WriteSysFilesFromMem:
  mov dx, ReadedIBMDOS
  mov cx, [IBMDOS_buffer_addr]
  sub cx, dx
  jz short ProceedToIBMDOS
  mov bx, [TargetIBMBIO_hndlr]
  mov ah, 40h
  int 21h  ; DOS - 2+ - WRITE TO FILE WITH HANDLE
     ; BX = file handle, CX = number of bytes to write, DS:DX -> buffer
  jb short retGetFileSizeDateTime
  cmp ax, cx  ; Written less than should
  jnz short WriteError

ProceedToIBMDOS:   
  mov dx, [IBMDOS_buffer_addr]
  mov cx, [After_IBMDOS_buffer]
  sub cx, dx
  jz short retGetFileSizeDateTime
  mov bx, [TargetIBMDOS_hndlr]
  mov ah, 40h
  int 21h  ; DOS - 2+ - WRITE TO FILE WITH HANDLE
     ; BX = file handle, CX = number of bytes to write, DS:DX -> buffer
  jb short retGetFileSizeDateTime
  cmp ax, cx  ; Written less than should?
  jz short retGetFileSizeDateTime

WriteError:    
  stc
  retn
;WriteSysFilesFromMem endp


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

; Writes boot with correct BPB for 8-track FDD's
; Possibly attempt to automatically fix boot for old, (1.XX),
; incompatible floppies.

WriteBoot:
  mov ah, 32h
  mov dl, [ds:5Ch] ; Target driver
  int 21h  ; DOS - 2+ internal - GET DRIVE PARAMETER BLOCK
     ; DL = drive number, 0 = default, 1 = A, etc.
     ; DS:BX -> Drive Parameter Block (DPB)
     ;
     ; http://www.ctyme.com/intr/rb-2724.htm
; Format of DOS Drive Parameter Block:

; Offset  Size   Description   (Table 01395)
; 00h  BYTE  drive number (00h = A:, 01h = B:, etc)
; 01h  BYTE  unit number within device driver
; 02h  WORD  bytes per sector
; 04h  BYTE  highest sector number within a cluster
; 05h  BYTE  shift count to convert clusters into sectors
; 06h  WORD  number of reserved sectors at beginning of drive
; 08h  BYTE  number of FATs
; 09h  WORD  number of root directory entries
; 0Bh  WORD  number of first sector containing user data
; 0Dh  WORD  highest cluster number (number of data clusters + 1)
; 16-bit FAT if greater than 0FF6h, else 12-bit FAT
; 0Fh  BYTE  number of sectors per FAT
; 10h  WORD  sector number of first directory sector
; 12h  DWORD  address of device driver header (see #01646)
; 16h  BYTE  media ID byte (see #01356)
; 17h  BYTE  00h if disk accessed, FFh if not
; 18h  DWORD  pointer to next DPB
; ---DOS 2.x---
; 1Ch  WORD  cluster containing start of current directory, 0000h=root,
; FFFFh = unknown
; 1Eh 64 BYTEs  ASCIZ pathname of current directory for drive
  mov al, [bx+16h] ; media ID byte
     ;
     ; http://www.ctyme.com/intr/rb-2590.htm#Table1356
     ; FFh  floppy, double-sided, 8 sectors per track (320K)
     ; FEh  floppy, single-sided, 8 sectors per track (160K)
     ; FDh  floppy, double-sided, 9 sectors per track (360K)
     ; FCh  floppy, single-sided, 9 sectors per track (180K)
     ; See also: http://en.wikipedia.org/wiki/File_Allocation_Table#FATID
  push cs  ; Restore spoiled DS
  pop ds
  cmp al, 0FEh ; If less -- exit from proc.
     ; Known ID's, less than FEh are that for 9-track FDD's
  jb short WriteBootExit
  test al, 1  ; For known codes, last bit=1 for double-sided
  jz short DoWriteBoot
; Update boot image for double-sided disk
  mov bx, NewBoot
  mov word [bx+11h], 70h ; BPB -- root entries
  mov word [bx+13h], 280h ; BPB -- NumSectors
  inc byte [bx+15h] ; BPB -- MediaType
  inc word [bx+1Ah] ; NumberOfHeads

DoWriteBoot:    
  mov al, [ds:5Ch] ; Target driver
  dec al  ; FCB disk number to DOS disk number
  mov bx, NewBoot
  xor dx, dx
  mov cx, dx
  inc cx  ; One sector, starting from sector 0
  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
  pop ax  ; Original flags are left on stack by int 26h,
     ; must be popped by caller.

WriteBootExit:    
  retn
;WriteBoot endp 


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

; Clears buffer before and after.

WaitForAnyKey:
  mov ax, 0C08h
  int 21h  ; DOS - CLEAR KEYBOARD BUFFER
     ; AL must be 01h, 06h, 07h, 08h, or 0Ah.
  mov ax, 0C00h
  int 21h  ; DOS - CLEAR KEYBOARD BUFFER
     ; AL must be 01h, 06h, 07h, 08h, or 0Ah.
     ;
     ; (AL=00h, or any
     ; other not from the list above
     ;  -- "the buffer is flushed but no input is attempted")
  retn
;WaitForAnyKey endp

; ===========================================================================
aIncorrectDosVe db 'Incorrect DOS version',0Dh,0Ah,'$' 
aInvalidDriveSp db 'Invalid drive specification' 
InvalidDriveSpSize dw 1Bh  
aInvalidParamet db 'Invalid parameter'  
InvalidParametSize dw 11h  
aNoRoomForSyste db 'No room for system on destination disk' 
NoRoomForSysteSize dw 26h  
; Unused
aIncompatibleSy db 'Incompatible system size' 
IncompatibleSySize dw 18h  
aSystemTransfer db 'System transferred' 
SystemTransferSize dw 12h  
aInsertSystemDi db 'Insert system disk in drive ' 
aDriverLetter1 db 41h   
aAndStrikeAnyKe db 0Dh,0Ah
  db 'and strike any key when ready',0Dh,0Ah
InsertSystemDiskFullSize dw 3Eh  
currentDrive db 0   
     
aAIbmbio_com db 'A:\IBMBIO.COM',0    
aAIbmdos_com db 'A:\IBMDOS.COM',0    
;
; IBMBIO.COM data
IBMBIO_hndlr dw 0   
     
IBMBIO_size_lo_1 dw 0   
     
IBMBIO_size_lo_2 dw 0   ; Just copy of IBMBIO_size_lo_1...
IBMBIO_size_hi_1 dw 0   
     
IBMBIO_size_hi_2 dw 0   ; Just copy of IBMBIO_size_hi_1...
IBMBIO_time dw 0   
IBMBIO_date dw 0   
TargetIBMBIO_hndlr dw 0   
;
; IBMDOS.COM data
IBMDOS_hndlr dw 0   
     
IBMDOS_size_lo_1 dw 0   
     
IBMDOS_size_lo_2 dw 0   ; Just copy of IBMDOS_size_lo_1...
IBMDOS_size_hi_1 dw 0   
     
IBMDOS_size_hi_2 dw 0   ; Just copy of IBMDOS_size_hi_1...
IBMDOS_time dw 0   
IBMDOS_date dw 0   
TargetIBMDOS_hndlr dw 0   
;
;
anyFileMask db 'A:\*.*',0           
DiskRootDir db 'X:\',0              
CurDirDisk db 58h, 3Ah, 5Ch 
     ; "X:\" -- beginning for next Dir Buffer
; 64 bytes
CurDirBuffer db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
     

; 0x01 file is read only
; 0x02 hidden file
; 0x04 system file
; 0x08 a special entry containing the disk's volume label
; 0x10 The entry describes a subdirectory.
; 0x20 This is the archive flag.
; 0x40 Not used; must be set to 0.
; 0x80 Not used; must be set to 0.
;
; 16h -- hidden + system + dir(?)

FCBext_forFind ExtFCB_t 0,"????????", "???", 0,16h,0
;FCBext_forFind db 0FFh   ; ExtMarker ;
;     
;  db 5 dup(0)  ; Reserved1
;  db 16h   ; Attribute
;  db 0   ; Driver
;  db 8 dup('?')           ; FileName
;  db 3 dup('?')           ; 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
  db    0
  db    0
  db    0
  db    0
  db    0
PutativeMaxFreeMem dw 0   
     ; Possibly contains approximation of free mem,
     ; above code, below stack
IBMDOS_buffer_addr dw 0   
     
After_IBMDOS_buffer dw 0  
     
NewBoot  db 0EBh, 2Ch, 90h, 49h, 42h, 4Dh, 20h, 20h, 32h, 2Eh, 30h, 0, 2, 2, 1, 0; 0
     
     
  db 2, 40h, 0, 40h, 1, 0FEh, 1, 0, 8, 0, 1, 0, 0, 0, 0, 0; 16
  db 0Ah, 0DFh, 2, 25h, 2, 9, 2Ah, 0FFh, 50h, 0F6h, 0Fh, 2, 0CDh, 19h, 0FAh, 33h; 32
  db 0C0h, 8Eh, 0D0h, 0BCh, 0, 7Ch, 8Eh, 0D8h, 0A3h, 7Ah, 0, 0C7h, 6, 78h, 0, 21h; 48
  db 7Ch, 0FBh, 0CDh, 13h, 73h, 3, 0E9h, 95h, 0, 0Eh, 1Fh, 0A0h, 10h, 7Ch, 98h, 0F7h; 64
  db 26h, 16h, 7Ch, 3, 6, 1Ch, 7Ch, 3, 6, 0Eh, 7Ch, 0A3h, 3, 7Ch, 0A3h, 13h; 80
  db 7Ch, 0B8h, 20h, 0, 0F7h, 26h, 11h, 7Ch, 5, 0FFh, 1, 0BBh, 0, 2, 0F7h, 0F3h; 96
  db 1, 6, 13h, 7Ch, 0E8h, 7Eh, 0, 72h, 0B3h, 0A1h, 13h, 7Ch, 0A3h, 7Eh, 7Dh, 0B8h; 112
  db 70h, 0, 8Eh, 0C0h, 8Eh, 0D8h, 0BBh, 0, 0, 2Eh, 0A1h, 13h, 7Ch, 0E8h, 0B6h, 0; 128
  db 2Eh, 0A0h, 18h, 7Ch, 2Eh, 2Ah, 6, 15h, 7Ch, 0FEh, 0C0h, 32h, 0E4h, 50h, 0B4h, 2; 144
  db 0E8h, 0C1h, 0, 58h, 72h, 38h, 2Eh, 28h, 6, 20h, 7Ch, 76h, 0Eh, 2Eh, 1, 6; 160
  db 13h, 7Ch, 2Eh, 0F7h, 26h, 0Bh, 7Ch, 3, 0D8h, 0EBh, 0CEh, 0Eh, 1Fh, 0CDh, 11h, 0D0h; 176
  db 0C0h, 0D0h, 0C0h, 25h, 3, 0, 75h, 1, 40h, 40h, 8Bh, 0C8h, 0F6h, 6, 1Eh, 7Ch; 192
  db 80h, 75h, 2, 33h, 0C0h, 8Bh, 1Eh, 7Eh, 7Dh, 0EAh, 0, 0, 70h, 0, 0BEh, 0C9h; 208
  db 7Dh, 0E8h, 2, 0, 0EBh, 0FEh, 2Eh, 0ACh, 24h, 7Fh, 74h, 4Dh, 0B4h, 0Eh, 0BBh, 7; 224
  db 0, 0CDh, 10h, 0EBh, 0F1h, 0B8h, 50h, 0, 8Eh, 0C0h, 0Eh, 1Fh, 2Eh, 0A1h, 3, 7Ch; 240
  db 0E8h, 43h, 0, 0BBh, 0, 0, 0B8h, 1, 2, 0E8h, 58h, 0, 72h, 2Ch, 33h, 0FFh; 256
  db 0B9h, 0Bh, 0, 26h, 80h, 0Dh, 20h, 26h, 80h, 4Dh, 20h, 20h, 47h, 0E2h, 0F4h, 33h; 272
  db 0FFh, 0BEh, 0DFh, 7Dh, 0B9h, 0Bh, 0, 0FCh, 0F3h, 0A6h, 75h, 0Eh, 0BFh, 20h, 0, 0BEh; 288
  db 0EBh, 7Dh, 0B9h, 0Bh, 0, 0F3h, 0A6h, 75h, 1, 0C3h, 0BEh, 80h, 7Dh, 0E8h, 0A6h, 0FFh; 304
  db 0B4h, 0, 0CDh, 16h, 0F9h, 0C3h, 1Eh, 0Eh, 1Fh, 33h, 0D2h, 0F7h, 36h, 18h, 7Ch, 0FEh; 320
  db 0C2h, 88h, 16h, 15h, 7Ch, 33h, 0D2h, 0F7h, 36h, 1Ah, 7Ch, 88h, 16h, 1Fh, 7Ch, 0A3h; 336
  db 8, 7Ch, 1Fh, 0C3h, 2Eh, 8Bh, 16h, 8, 7Ch, 0B1h, 6, 0D2h, 0E6h, 2Eh, 0Ah, 36h; 352
  db 15h, 7Ch, 8Bh, 0CAh, 86h, 0E9h, 2Eh, 8Bh, 16h, 1Eh, 7Ch, 0CDh, 13h, 0C3h, 0, 0; 368
  db 0Dh, 0Ah, 4Eh, 6Fh, 6Eh, 2Dh, 53h, 79h, 73h, 74h, 65h, 6Dh, 20h, 64h, 69h, 73h; 384
  db 6Bh, 20h, 6Fh, 72h, 20h, 64h, 69h, 73h, 6Bh, 20h, 65h, 72h, 72h, 6Fh, 72h, 0Dh; 400
  db 0Ah, 52h, 65h, 70h, 6Ch, 61h, 63h, 65h, 20h, 61h, 6Eh, 64h, 20h, 73h, 74h, 72h; 416
  db 69h, 6Bh, 65h, 20h, 61h, 6Eh, 79h, 20h, 6Bh, 65h, 79h, 20h, 77h, 68h, 65h, 6Eh; 432
  db 20h, 72h, 65h, 61h, 64h, 79h, 0Dh, 0Ah, 0, 0Dh, 0Ah, 44h, 69h, 73h, 6Bh, 20h; 448
  db 42h, 6Fh, 6Fh, 74h, 20h, 66h, 61h, 69h, 6Ch, 75h, 72h, 65h, 0Dh, 0Ah, 0, 69h; 464
  db 62h, 6Dh, 62h, 69h, 6Fh, 20h, 20h, 63h, 6Fh, 6Dh, 30h, 69h, 62h, 6Dh, 64h, 6Fh; 480
  db 73h, 20h, 20h, 63h, 6Fh, 6Dh, 30h, 0, 0, 0, 0, 0, 0, 0, 55h, 0AAh; 496
; Also --- starting address for IBMDOS.COM
ReadedIBMDOS db 1Ah   
  db  1Ah
  db  1Ah
  db  1Ah
  db  1Ah
  db  1Ah


Розмір --- 1 680 байт, росте далі.

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


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

Хоча оновлення від 2.00 до 2.10 було скромним, однак внутрішньо SYS.COM змінився дуже сильно. Більшість викликів до FCB-сервісів замінені на аналогічні нові виклики, що працюють з номерами (handles) файлів.

На початку --- традиційний перехід до перевірки версії, після якої знаходиться два байти --- адреса копії бут-сектора в пам'яті та стрічка "Vers 1.82", очевидно, внутрішня версія SYS.COM. Після неї --- перевірка версії DOS. Мінімальна припустима -- та ж, що і раніше, 1.54 (1.36h), максимальна -- 2.10 ( 2.0Ah). Якщо версія невірна, виводиться "Incorrect DOS version", програма завершується. Ця стрічка все ще виводиться за допомогою старого AH=09h/INT 21h. Очевидно, щоб показати коректне повідомлення, навіть якщо запущено з-під старого DOS.

Далі йде звична перевірка коректності цільового диску та спроби запису на той же, з якого читаємо. Однак, після неї, читається перший сектор диску. Очікується, що в ньому знаходиться FAT! В цьому (гіпотетичному) FAT перевіряється перший байт, який мав би відповідати Media ID. Якщо він менший, ніж  0F8h --- "Media ID for HDD "Designated to be used for any partitioned fixed or removable media, where the geometry  is defined in the BPB.", найменший відомий DOS 2.10, виводиться дещо загадкове "No room for system on destination disk". Очевидно, це спроба не перезаписувати системні файли на не-FAT дисках.

І повідомлення про відсутність місця, і згадане "Invalid drive specification", виводяться тепер за допомогою файлового виклику AH=40h/INT 21h, "WRITE TO FILE WITH HANDLE", з BX=2, "Standard Error Device handler". Програма завершується з кодом завершення, рівним 1, за допомогою виклику  AH=4Ch/INT 21h. Взагалі, це єдиний код повернення для всіх помилок. Забігаючи наперед, якщо програма завершилася успішно, повертається нуль. 

Якщо Media ID влаштовує, програма кладе літеру поточного диску замість "A" в дві стрічки, "A:\IBMBIO.COM" і "A:\IBMBIO.COM", що закінчуються нулем, та намагається відкрити їх і визначити розмір, дату та час файлу за допомогою функції GetFileSizeDateTime, яка приймає в DX адресу стрічки з іменем, в DI -- адресу, куди покласти номер відкритого файлу. Розмір, час і дата зберігаються зразу після нього. Що дивно, розмір зберігається хитро -- спочатку дві копії молодшого слова, потім дві копії старшого... Для повідомлення про помилку функція встановлює прапорець переносу -- CF.

Працює GetFileSizeDateTime так. Намагається відкрити файл, (за допомогою AX=3D00h/INT 21h, " OPEN DISK FILE WITH HANDLE", доступ на читання). Якщо не вдалося -- завершується (передаючи вже встановлений викликом INT 21h прапорець CF). Інакше --- зберігає номер файлу, викликом AH=4202h/INT 21h, "MOVE FILE READ/WRITE POINTER (LSEEK)", переміщає файловий вказівник в кінець. Отримане в DX:AX значення відповідає положенню вказівника відносно початку, і буде рівним розміру файлу. Його зберігає після номера (чомусь двічі старше і двічі молодше слово!). (Нагадаю, stosw автоматично пересуватиме DI). Далі файловий вказівник переміщається на початок, і за допомогою AH=57/INT 21h, "GET FILE'S DATE/TIME" читається та зберігається дата і час файлу. Явної перевірки помилок цих викликів не робиться, однак якщо котрийсь із них поверне CF, код, що викликав GetFileSizeDateTime, прореагує, як і на інші помилки читання системних файлів з поточного диску, проханням вставити системний диск: "Insert system disk in drive ".

Ймовірно, двічі зберігати розмір планувалося для того, щоб один із них залишався незмінним, а інший -- змінювався, вказуючи, скільки ще залишилося записати (див. нижче). Спочатку двічі молодше слово, потім двічі -- старше, ймовірно, зроблено для простоти -- stosw так зручніше.

Процедура обробки цього прохання, "Insert system disk in drive", знаходиться перед точкою входу, тому після неї програма ефективно почнеться з початку (ну, крім перевірки версії DOS --- немає сенсу її повторювати). Для виводу повідомлення використовується те ж AH=40h/INT 21h, "WRITE TO FILE WITH HANDLE", з BX=2, "Standard Error Device handler". Очікування "any key" здійснюється спеціальною підпрограмкою, WaitForAnyKey, що чекає на натискання, потім очищає буфер (чого не робили попередні версії).

Далі програма поводиться хитріше, ніж її попередники. Зроблено так, що якщо файли IBMBIO.COM i IBMDOS.COM не вміщаються в пам'ять, то читання і запис здійснюються шматками, які поміщаються. Для оцінки доступної пам'яті від вершини стеку, SP, віднімається 98Ah. (Цілком раціональна оцінка.). Далі читається, скільки влізло, функцією ReadFilesToMem, яку детальніше розглянемо пізніше. Якщо були помилки --- просимо вставити системний диск. Інакше зберігає поточну директорію цільового диску. Директорія поточного не змінюється --- звертається до файлів з абсолютним шляхом. (Версія 2.00, працюючи за допомогою FCB, особливого вибору не мала, мусила змінювати і там і там.) На цільовому диску здійснюється перехід в корінь та пошук з допомогою FCB за маскою "*.*", після чого поточний шлях відновлюється.

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

Якщо якісь  файли таки знайдені, перевіряємо, чи є поміж них IBMBIO.COM і IBMDOS.COM, якщо є, вважається, що вони правильно розташовані, і переходиться до  переносу системи. Якщо ж немає --- все те ж "немає місця для системи".

Перенос системи починається із того, що обом системним файлам (є там вони, чи ні :), очищаються атрибути, помилки не перевіряються --- якщо файлів немає, ось і добре, а якщо є, вони перестануть бути захищеними від запису.  Далі обидва файли створюються (або обрізаються, якщо вже існували), викликом AH=3Ch/INT 21h ("CREATE OR TRUNCATE FILE WITH HANDLER"), з атрибутами "системні", "приховані", "тільки для читання". Перевірка помилок не здійснюється.

Далі, власне, головний цикл програми. Прочитана раніше порція записується. Звіряється, чи є ще не прочитані байти системних файлів. Якщо є --- читається та записується наступна порція. Якщо в процесі читання помилка, просимо вставити системний диск. Якщо помилка під час запису -- повідомляємо, що недостатньо місця і завершуємо. Запис здійснюється WriteSysFilesFromMem, читання все тією ж ReadFilesToMem. Розглянемо їх детальніше.

ReadFilesToMem перевіряє, чи є ще непрочитані байти в IBMBIO, якщо є -- читає, але не більше, ніж є вільної пам'яті. Для підрахунку використовується визначений раніше розмір. Прочитана кількість кожного разу від нього віднімається. Якщо прочитано менше, ніж є пам'яті, тобто ми прочитали весь залишок файлу (навіть якщо нуль байт, весь файл прочитано ще раніше) --- перемістити буфер для IBMDOS на початок вільної області. (Поки читається IBMBIO, цей буфер буде вказувати на кінець вільної пам'яті. Зроблено це елегантним трюком --- він вказує на адресу, що рівна початку буферів + розмір прочитаного.) Далі повторити аналогічну процедуру для  IBMDOS. Ефективно, на кожному циклі читання буде читатися спершу IBMBIO, потім IBMDOS, шматками, які поміщаються в пам'ять.

WriteSysFilesFromMem працює дзеркально. Подивившись початок буферу для IBMDOS, (який знаходиться після буферу для IBMBIO), визначає, чи залишилося ще щось записувати в IBMBIO. Якщо залишилося --- записуємо. Далі переходимо до запису IBMDOS, якщо є ще що записувати. Якщо на котромусь етапі записалося не все, що планували --- повідомляємо про помилку прапорцем CF. (Вихід, якщо помилки не було, здійснюємо "чужим" retn... І чого автори так люблять цей трюк? Невже вся причина в дальнобійності, точніше в не-дальнобійності, коротких умовних переходів?).

Після цього відновлюємо дату та час системних файлів.

Наступний етап -- відновлення boot-сектора для старих, 8-секторних, дискет, така ж, як і раніше. Аж після цього друкується "System transferred".  На цьому роботу програми закінчено.

Все ще присутній шматок коду, що виводить "Incompatible system size", який теж не використовується.

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

На відміну від попередньої версії, навіть не змінює поточну директорію вихідного диску, але зате відновлює поточну для цільового.

Не хитрує із стиранням-не стиранням системних файлів на цільовому. Якщо вони є, очищає їх атрибути, після чого просто обрізає. Якщо ж немає --- створює.

Дві найбільш важливі зміни:
  • Програма працюватиме на системах із малим об'ємом пам'яті, читаючи порціями, рівними доступному об'єму. Всі попередні в таких умовах вмирали б в тяжких муках.
  • Додано обробку багатьох типових помилкових ситуацій, хоч повідомлення часто будуть загадковими.


Про код


Помітну частину програми переписано, все, крім пошуку Volume Label, реалізовано з використанням сучасних викликів, які працюють із номерами файлів. (З міткою диску альтернатив не було, DOS 2.XX не вмів шукати її без використання FCB. )

Нарешті додано менш-більш систематичну обробку помилок. В файлі менше сміття. Прибрано перекриття FCB для пошуку і нового boot-сектора.

Наступним кроком мав би бути SYS.COM з DOS 2.11. Однак, з ним "все складно". Слідкуйте за новинами ;-)

А поки:

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

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

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