Boot sector is a 512 bytes long data structure recorded in the first sector of disk volume. When 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. As the binary format does not contain any metainformation (entry point, relocations etc), all absolute addresses in its code must be assembled to fixed values.
BIOS loads the sector at fixed agreed address 0x07C00 and then it will set register
DL to the drive number (DL=0 for floppy drive A:), CS=0 and IP=0x7C00.
Other register are undefined. This transfers control to the first boot-sector instruction
Entry: JMPS Start:.
As the code is expected to run on fixed address 0x0000:0x7C00, origin is elevated to this offset by the statement
$ EQU %^ImageBase(%^ImageBase contains
0x7C00). €ASM fills the gap between the start of segment 0 and sector 0x7C00 with NUL bytes, normally emitted to the output file. They will be trimmed off at link time with the option
PROGRAM OutFile="boot16.sec"[%Base+1 .. %Base+512]which will select only those 512 bytes assembled at 0x7C00.
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 marks the offset in listing dump column using brackets [ ] as relocatable (which it isn't). So the OFFSET# operator is used from cosmetic reason only.
CODE=LONGis 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 opcode.
boot16.secto the first disk sector, for instance
dd.exe if=boot16.sec of=\\.\A:and then boot from the disk A:.
%Base %SETA 0x7C00 ; Absolute address where the boot sector will be loaded at. Aliased as %^ImageBase. %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.
EUROASM AutoAlign=Off, DumpAll=Yes, DumpWidth=29 boot16 PROGRAM Format=BIN, Model=TINY, Width=16, \ OutFile="boot16.sec"[%Base+1 .. %Base+512], \ ImageBase=%Base, Entry=Entry:, \ ListMap=Off, ListGlobals=Off INCLUDE bioss.htm ;bioss.htmdefines layout of structures used by boot code. $ EQU %^ImageBase ; Boot sector is assembled at this fixed origin %Base alias %^ImageBase. Entry: JMPS Start: ; Skip structures allocated in the beginning of boot-sector. NOP $ EQU %^ImageBase + 0x0003 ; Fixed address of OEM_ID. DB %OEM_ID $ EQU %^ImageBase + 0x000B ; Fixed address of statically-defined Bios Parameter Block. Bpb: DS BPB_FAT16, .VolumeLabel=%VolumeLabel, .VolumeSerialNumber=%VolumeSerial Dpt: DS 0*DPT ; Diskette Parameter Table (11 bytes) will be copied here, overwriting the Start: code. ; Immediately below the copied Dpt follow four memory variables LBA* for calculation of disk geometry, ; they overwrite the Start: code, too. LBAdata EQU %^ImageBase+0x0049 ; DWORD with the number of 1st sector with data, immediately following the root-directory. LBAcylinder EQU %^ImageBase+0x004D ; WORD with LBA translated for INT 0x13. LBAtrack EQU %^ImageBase+0x004F ; BYTE with LBA translated for INT 0x13.. LBAroot EQU %^ImageBase+0x0050 ; DWORD with the number of 1st sector in root-directory entry. ; Entry point jumps to this fixed label Start: at address %Boot+0x3E, where the boot code actually starts. Start: CLI ; Disable HW interrupts, as the stack is not settled yet. XOR AX,AX,CODE=LONG MOV SS,AX MOV SP,%^ImageBase ; Set machine stack just below the boot sector. PUSH SS POP ES MOV BX,0x0078 ; Address of default DPT prepared by BIOS. LDS SI,[SS:BX] PUSH DS,SI,SS,BX MOV DI,OFFSET# Dpt: MOV CX,SIZE# DPT: CLD REP MOVSB ; Copy DPT from BIOS memory to the Dpt:. 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. MOV [BX+0],OFFSET# Dpt, DATA=WORD 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 LBAroot of root-directory (19). MOV [LBAroot+0],AX MOV [LBAroot+2],DX MOV [LBAdata+0],AX MOV [LBAdata+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 [LBAdata+0],AX ; +14. ADC [LBAdata+2],0,DATA=WORD MOV BX,0x0500 ; Memory address where to read disk sectors. MOV DX,[LBAroot+2] MOV AX,[LBAroot+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 it at expected position on disk. JE Loader: ; Load the contents of IO.SYS at address 0x00700 and start 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 with 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,[LBAdata+0] ADC DX,[LBAdata+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,[LBAdata+0] MOV AX,[LBAdata+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 [LBAtrack],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 [LBAcylinder],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,[LBAcylinder] MOV CL,6 SHL DH,CL OR DH,[LBAtrack] 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. $ EQU %^ImageBase + 0x01FE ; Fixed origin of boot-sector signature 55AAh. DB 0x55,0xAA ; Boot sector end signature. ENDPROGRAM boot16