This program demonstrates how EuroAssembler format BIN can be utilized to compile file
binboot.sec
which can be used as a floppy disk boot sector.
Boot sector is a 512 bytes long image recorded in the first sector of disk volume. When personal computer starts, its BIOS will read boot sector and store its contents at address 0x07C00 in memory. This sample source file compiles boot sector of floppy diskette 3.5" formated by MS-DOS ver.6.
Code in this sector is executed in real CPU mode, no DOS services are available yet. Since the binary format cannot contain any metainformation (entry point, relocations etc), all absolute addresses in its code must be assembled to fixed values.
BIOS loads the sector at agreed linear address 0x07C00 and then it will set register
DL to the drive number (DL=0 for floppy drive A:), CS=0x0000 and IP=0x7C00 (some BIOS vendors may prefer CS=0x07C0 and IP=0x0000).
Other registers are undefined. This transfers control to the first boot-sector instruction
Entry: JMPS Start:
which skips data structures expected near the beginning of the sector.
Origin of the code is elevated to the expected offset by pseudoinstruction $ EQU 0x7C00
, which emits 0x7C00 undefined bytes
to the linked image; those bytes will be omitted thanks to PROGRAM parameter
OutFile="binboot.sec"[0x7C01 .. 0x7E00]
.
Notice the attribute operator OFFSET# used with absolutely addressed symbols, for instance
MOV DI,OFFSET# Dpt:
. This converts the address of symbol Dpt to a plain number, which doesn't need relocation. Without OFFSET# this would work as well (MOV DI,Dpt:
) but €ASM vainly decorates the offset in listing dump column using brackets [ ] as relocatable (which it isn't). So the operator OFFSET# is used from cosmetic reason only.Instruction modifier
CODE=LONG
is unnecessary, too. I wanted the sample code to be binary-identical with real boot sector of floppy disk formated by MS-DOS 6, and the compiler used by Microsoft prefers machine instructions with alternative (long) opcode.
binboot.secto the first disk sector, for instance
dd.exe if=binboot.sec of=\\.\A:
and then boot from the disk A:.EUROASM AutoAlign=Off, DumpAll=Yes, DumpWidth=29 binboot PROGRAM Format=BIN, Model=TINY, Width=16,Entry=Entry:, \ OutFile="binboot.sec"[0x7C01 .. 0x7E00], \ ; Skip 0x7C00 dummy bytes generated at the beginning of output file. ListMap=On, ListGlobals=Off INCLUDE bioss.htm ; Macrolibrary bioss.htm defines layout of structures used by the boot code. %OEM_ID %SET "EUROASM",0 ; This 8 bytes identifies OS which created the boot sector. %VolumeLabel %SET "EUROASMBOOT" ; Diskette label: arbitrary 11 characters, space-padded. %VolumeSerial %SET 0x11223344 ; Serial number: random DWORD generated at disk format-time. ;; [BOOT] SEGMENT PURPOSE=CODE+DATA ; It will be linked at fixed address (ImageBase) = 0x7C00. $ EQU 0x7C00 ; Start the assembly at offset 0x7C00. Entry: JMP Start: ; Skip OEM_ID and Bpb allocated statically in the beginning of boot-sector. NOP DB %OEM_ID ; Fixed address of OEM_ID is 0x7C03. Bpb: DS BPB_FAT16, .VolumeLabel=%VolumeLabel, .VolumeSerialNumber=%VolumeSerial ; Fixed address of Bpb at 0x7C0B. Dpt: DS DPT ; Diskette Parameter Table (11 bytes) will be copied here at 0x7C3E, overwriting the Start: code. Lba: DS LBA ; Area used in conversion of disk geometry CHS/LBA, allocated at 0x7C49. $ EQU Dpt: ; Assemble the following Start code back at 0x7C3E, overwriting Dpt and Lba. Start: ; Entry point jumps to this fixed label Start: at address 0x7C3E, where the boot code actually starts. CLI ; Disable HW interrupts, as the stack is not settled yet. XOR AX,AX,CODE=LONG MOV SS,AX MOV SP,0x7C00 ; Set the stack pointer to memory just below the boot sector. PUSH SS POP ES ; Let ES=SS=0. MOV BX,0x0078 ; Offset of pointer to the default DPT prepared by BIOS (Interrupt 0x1E vector). LDS SI,[SS:BX] ; Let DS:SI point to DPT in BIOS memory. PUSH DS,SI,SS,BX MOV DI,OFFSET# Dpt: ; Let ES:DI point to Dpt in boot-sector memory. MOV CX,SIZE# DPT: ; 11. CLD REP MOVSB ; Copy DPT from BIOS memory to the Dpt at 0x7C3E. PUSH ES POP DS ; DS=ES=SS=0. MOVB [DI-SIZE#DPT+DPT.bHdSettle],15 MOV CX,[OFFSET# Bpb.SectorsPerTrack] MOV [DI-SIZE#DPT+DPT.bLastTrack],CL ; Modify the vector address of local DPT copy (Dpt) in BIOS memory. MOV [BX+2],AX ; PARA# Dpt=0. MOV [BX+0],OFFSET# Dpt, DATA=WORD ; OFFSET# Dpt=0x7C3E updates interrupt 0x1E vector to point at Dpt. STI ; Recalibrate floppy drive number DL=0. INT 13h ; AH=0 Reset disk system. JC Error: XOR AX,AX CMP [OFFSET# Bpb.SmallSectors],AX JZ Large: MOV CX,[OFFSET# Bpb.SmallSectors] ; 2880 sectors = 1.44 MB. MOV [OFFSET# Bpb.LargeSectors],CX ; Use .LargeSectors rather than .SmallSectors. Large: MOV AL,[OFFSET# Bpb.NumberOfFats] ; 2. MULW [OFFSET# Bpb.SectorsPerFat] ; 9. ADD AX,[OFFSET# Bpb.HiddenSectors+0] ; 0 ADC DX,[OFFSET# Bpb.HiddenSectors+2] ; 0. ADD AX,[OFFSET# Bpb.ReservedSectors] ; 1. ADC DX,0 ; DX:AX is now Lba.root of root-directory (19). MOV [Lba.root+0],AX MOV [Lba.root+2],DX MOV [Lba.data+0],AX MOV [Lba.data+2],DX MOV AX,SIZE# DIR_ENTRY ; 32. MULW [OFFSET# Bpb.RootEntries] ; 224. MOV BX,[OFFSET# Bpb.BytesPerSector] ; 512. ADD AX,BX,CODE=LONG DEC AX DIV BX ; Divide root-dir size by sector size (512). ADD [Lba.data+0],AX ; +14. ADC [Lba.data+2],0,DATA=WORD MOV BX,0x0500 ; Memory address where to read disk sectors (DTA). MOV DX,[Lba.root+2] MOV AX,[Lba.root+0] CALL LBAtranslate: JB Error: MOV AL,1 CALL ReadSec: ; Read the directory entry. JB Error: MOV DI,BX,CODE=LONG MOV CX,8+3 ; Filename size. MOV SI,OFFSET# IO.SYS: REPE CMPSB ; The first file in dir must be IO.SYS. JNE Error: LEA DI,[BX+SIZE# DIR_ENTRY] ; The next dir entry should be MSDOS.SYS. MOV CX,8+3 ; Filename size. SI points to MSDOS.SYS. REPE CMPSB ; Check if MSDOS.SYS is at expected position on disk. JE Loader: ; Load the contents of IO.SYS at address 0x00700 and go to its entry 0x0070:0. Error: MOV SI,OFFSET# Message: CALL Display: XOR AX,AX INT 16h ; Wait for any key pressed. POP SI,DS,[SI+0],[SI+2] INT 19h ; Invoke the bootstrap loader. Try to boot again from a better disk. Error2:POP AX,AX,AX JMP Error: Loader: ; IO.SYS file loader reads the first three sectors of IO.SYS to the address 0x00700. MOV AX,[BX+DIR_ENTRY.wClstrNo] ; BX=0x500 points to the directory entry of IO.SYS. DEC AX,AX MOV BL,[OFFSET# Bpb.SectorsPerCluster] ; 1. XOR BH,BH MUL BX ADD AX,[Lba.data+0] ADC DX,[Lba.data+2] MOV BX,0x0700 ; Memory address where to read. MOV CX,3 ; Only read 3 sectors. IO.SYS manages the rest. NextSec:PUSH AX,DX,CX CALL LBAtranslate: ; Convert the cluster number in DX:AX to C/H/S geometry. JC Error2: MOV AL,1 CALL ReadSec: ; Read AL sectors to address BX. POP CX,DX,AX JC Error: ADD AX,1 ; Prepare to read the next cluster. ADC DX,0 ADD BX,[OFFSET# Bpb.BytesPerSector] LOOP NextSec: MOV CH,[OFFSET# Bpb.MediaDescriptor] MOV DL,[OFFSET# Bpb.PhysicalDriveNumber] MOV BX,[Lba.data+0] MOV AX,[Lba.data+2] JMP 0x0070:0 ; Start the code in IO.SYS. Display:PROC ; Subprocedure which displays a zero-terminated string DS:SI. LODSB OR AL,AL,CODE=LONG JZ Return: ; Return when the string is completely displayed. MOV AH,0x0E MOV BX,0x0007 INT 10h ; Output character AL on screen, advance cursor. JMP Display: LBAtranslate:PROC ; Subprocedure which translates LBA in DX:AX (cluster number, 19 for root-dir, 33 for data) to the disk geometry. CMP DX,[OFFSET# Bpb.SectorsPerTrack] ; 18. JNB RetCF: DIVW [OFFSET# Bpb.SectorsPerTrack] ; AX=track number, DX=sector number in the track. INC DL MOV [Lba.track],DL XOR DX,DX,CODE=LONG DIVW [OFFSET# Bpb.NumberOfHeads] ; 2. MOV [OFFSET# Bpb.Reserved],DL ; This BPB member is misused for the head number. MOV [Lba.cylinder],AX CLC RET RetCF: STC ; Signalize return with error. Return: RET ENDPROC LBAtranslate: ENDPROC Display: ReadSec:PROC ; Subprocedure which reads AL sectors to memory at ES:BX from translated disk address. MOV AH,2 MOV DX,[Lba.cylinder] MOV CL,6 SHL DH,CL OR DH,[Lba.track] MOV CX,DX,CODE=LONG XCHG CH,CL MOV DL,[OFFSET# Bpb.PhysicalDriveNumber] MOV DH,[OFFSET# Bpb.Reserved] ; Head number. INT 0x13 ; Read AL sectors starting from CL to ES:BX by BIOS service. RET ENDPROC ReadSec: Message:DB 13,10,"Non-System disk or disk error" DB 13,10,"Replace and press any key when ready" DB 13,10,0 IO.SYS: DB "IO SYS" ; File names of MS DOS boot files. DB "MSDOS SYS" DB 0,0 ; Unused sector space. DB 0x55,0xAA ; Boot sector end signature at 0x7FDE. ENDPROGRAM binboot