Home
Introduction
Display
Strings
Graphics
Hardware
Interrupts
File Systems
Components
ASM Editors
Real&PMode
Assemblers
Libraries
Downloads
Links
Contact Us
 















Comment! 
And here we have the second tutorial from vulture. This one will discuss
how to create a bootstrap so that you can load an operating system from
a disk drive. Since it is probably easier to write a file in DOS and copy
it to a disk via DOS, the included program will load a binary image in
DOS format and run it. 
This tutorial will also discuss some of the storage methods that DOS uses
for a filesystem. This is by no means the only way of storing files: I will
just explain this since it is one way that everyone has access to and can
easily copy files to and from. 
The first thing you must know about booting from a disk is that DOS is
not loaded and thus you cannot call any DOS interrupts like INT 21h. 
Secondly, memory must be managed. We could setup a memory management system,
but that is for the operating system; not for the bootstrap. all the
bootstrap does is load some system files off the disk (i.e. MSDOS.SYS and
IO.SYS) and allows them to setup interrupts and load the command interpreter. 
But wait... if we don't have DOS, how do we read from a disk? The BIOS gives
us INT 13h which allows us to do some low-level I/O. Rather than files, we
have sectors, sides, and tracks. On a hard drive, sides are heads since there
can be more than 2 sides; and tracks are called cylinders. 
Here are some INT 13h calls: 
INT 13h Read Sector:
------------	
AH=2
AL=sectors to read
CH=cylinder
CL=sector
DH=head
DL=drive
ES:BX -> buffer for read data 
On return, Carry flag set if error and AH contains error code. 
A note here may be made that only the lower 6 bits of CL are used for
sector. The high 2 bits are used as bits 8 and 9 of the cylinder. Also,
only the lower 6 bits of DH are used for the head, so the high 2 bits are
used as bits 10 and 11 of the cylinder. Also, the disk controller on the
PC starts sector numbers at 1, so the sector can be a number from 1 to 63.
This is not always true - especially for a floppy disk. For example, a
high density 3.5" disk generally has 18 sectors. The head has a limit from
0 to 63. Since floppy disks are portable and contain one disk, they have
2 sides. These sides can also be called heads. The number of cylinders can
range from 0 all the way up to 4095 using INT 13h / AH=2 . 
For a method of shortening space, a triplet can be used to specify a certain
sector on disk. The triplet is given as (sector, side, track). For example,
(1,0,0) is the boot sector. 
Another thing about reading sectors from disk: the read should be done about
3 times because of various status messages like a disk change. If the read
fails 3 times, then an error handler should be used. 
INT 13h Write Sector:
------------
AH=3
AL=sectors to write
CH=cylinder
CL=sector
DH=head
DL=drive
ES:BX -> buffer for data to be written 
On return, Carry flag set if error and AH contains error code. 
INT 13h Get Last Operation Status
------------
AH=1
DL=drive 
On return, Carry flag set if last call had an error and AH contains 
the last error code or if no error then 0. 
INT 13h Reset Disk - Seek to (1,0,0)
------------
AH=0
DL=drive 
On return, Carry flag set if error and AH contains error code.

Now, onto the loading procedure. 
When a disk is booted, the boot sector (1,0,0) is loaded at 0:7C00h. No
stack is setup, so one should be created to prevent overwriting something. 
XOR AX,AX
MOV SS,AX
MOV DS,AX
MOV ES,AX
SUB AX,2 ; this is used because older style computers store values
MOV SP,AX ; then subtract sp by whatever size 
Now that a stack is setup, we can PUSH and POP all we like. A more important
thing to do right now is to attempt to load the operating system off of the
disk. As you can see, since sectors are not linear (they are given as a
triplet of (sector, head, cylinder)), it will be hard to load an operating
system from different sizes of disks. Because of this, the boot sector
normally contains a record of the disk that DOS and others can use to
determine the type of disk and its characteristics. Here is the basic boot
sector: 
Offset Size Description

0003h 8 bytes OEM Name String
000Bh 2 bytes Bytes per Sector
000Dh 1 byte Sectors per Cluster
000Eh 2 bytes Reserved Sectors
0010h 1 byte Number of File Allocation Tables
0011h 2 bytes Number of Root Directory Entries
0013h 2 bytes Total Number of Sectors
0015h 1 byte Media Descriptor Byte
0016h 2 bytes Sectors per File Allocation Table
0018h 2 bytes Sectors per Track
001Ah 2 bytes Number of Heads
001Ch 2 bytes Number of Hidden Sectors
001Eh 9 bytes ???????????? Unused ????????????
0027h 4 bytes Volume Serial Number (Reverse Order)
002Bh 11 bytes Volume Name at Creation (Actual Volume is in Root)
0036h 8 bytes FAT Description String

Space after here is operating system specific. These are the only required
data bytes that should be used if you want a DOS compatible disk. You must
also remember to allocate enough sectors for the File Allocation Table and
the root directory. 
Now wait a minute... if that offset starts at 0003h, where is my program
supposed to go? At offset 0000h you should call a short or near jump to
goto the rest of your loader program. A short jump only takes 2 bytes so
a NOP instruction (opcode 90h) can be used as filler. 
One sector only gives you 512 bytes. The boot sector table above reduces it
a little so remember to keep your loader code small; put all non-needed code
into a system file. 
Media Descriptor Table: 
Byte Disk Type Sectors Heads Tracks Formatted Size

FFh 5 1/4" 8 2 40 320K bytes
FEh 5 1/4" 8 1 40 160K bytes
FDh 5 1/4" 9 2 40 360K bytes
FCh 5 1/4" 9 1 40 180K bytes
FBh both 9 2 80 640K bytes
FAh both 9 1 80 320K bytes
F9h 5 1/4" 15 2 80 1200k bytes
F9h 3 1/2" 9 2 80 720k bytes
F0h 3 1/2" 18 2 80 1440k bytes
F8h hard disk

For the hard disk type, you can have almost any number of sectors, heads,
and tracks. Also note that a hard drive usually has a master boot record
(MBR) which will point to a sector which contains the boot sector. Also
note here that these media descriptor bytes don't mean much anymore since
some of the same byte can mean different things (i.e. F9h) and also all
the needed information is already given in the boot sector. 
By no means is the above boot sector table a table for the master boot
record. If you write over the MBR on your hard drive, you'll have a problem
when you startup your computer next time. 
Continuing from the above loader code... 
XOR AX,AX
MOV SS,AX
MOV DS,AX
MOV ES,AX
SUB AX,2 ; this is used because older style computers store values
MOV SP,AX ; then subtract sp by whatever size 
Okay... since now we know that different types of disks can have different
numbers of sectors, heads, and tracks; it should be understandable that to
load a system file from say (5,1,3) would not be the same from a 1.44M disk
to a 360k disk. With this in mind, a linear sector reader could be very
helpful. Linear would indicate that we could pass a value of 55 to the
procedure and it would read (2,1,1) from a 1.44M disk or (2,0,3) from a 360k
disk. Here is how we establish these numbers: 


Read_Sector Proc ; dl = drive / eax = sector / si = num / es:bx -> buffer
    ; eax = 0 is the first sector for this and eax = 1
    ; is the second, etc.
 PUSH ESI   ; save for whatever reason
 PUSH BX  ; save buffer offset
 PUSH DX   ; save drive
 XOR EDX,EDX   ; prepare for dividing
 MOV ESI,EDX   ; set high 16 bits of esi to 0 for divide
 MOV SI,[Sectors_Per_Track] ; set esi is 32 bit sectors per track
 DIV ESI    ; edx = remainder / eax = quotient
 INC DX    ; dx = sector
 MOV CX,DX    ; cx = sector
 XOR EDX,EDX    ; prepare for another divide
 MOV ESI,EDX    ; esi = 0 again
 MOV SI,Number_Of_Heads  ; for another 32 bit divide
 DIV ESI    ; dx = head / ax = track
 MOV BX,DX    ; save head
 POP DX    ; get drive back (dl)
 MOV DH,BL    ; set dh to head
 MOV CH,AL    ; ch = track
 SHR AX,2    ; ah = bits 10 and 11 of track (cylinder)
 AND AL,11000000b   ; set al for adding to cl
 ADD CL,AL    ; add on bits 8 and 9 of track (cylinder)
 SHL AH,6    ; set ah for adding to dh
 ADD DH,AH    ; add on bits 10 and 11 of track (cylinder)
 POP BX    ; restore offset
 POP ESI    ; restore esi
 MOV AX,SI    ; set al to number to read (1 to xx)
     ; note that xx is not always read because of
     ; various reasons like crossing to the next head
     ; or track (running out of sectors on current head)
     ; this will be returned in al on return
 MOV AH,2    ; function ah=2 / int 13h = read sector
 INT 13h    ; read sector(s) into memory
 RET     ; carry flag set if error, ah = error
     ; al = number of sectors read
 ENDP     ; end procedure 


There it is. This particular read sector procedure has support for reading
in multiple sectors at a time as well as reading from a hard drive (12 bit
cylinder support). The reason that EAX is used for sector and not AX is
because on hard drives, sector numbers can reach up VERY high. There is
still some limitation with this procedure as it can only read up to the
63 * 64 * 4096 = 16,515,072th sector limiting it to 8,455,716,864 bytes.
This tutorial will only discuss floppy disk bootstraps though and so this
is irrelevent for the moment. 
Now that our linear sector reader is written, we can attempt to load the
system file from disk. Linear sector 0 is actually the boot sector and
linear sector 1 is actually the sector after the boot sector. 
Anyway... what sector is the system file at on the disk? The file allocation
table and the root directory entry tells us this. The file allocation table
is xxxx sectors which can be found in the boot sector and has a format like
this: 
File Allocation Table: 
Value Meaning

00000h Free Sector
0FFF0h to 0FFF6h Reserved
0FFF7h Bad Sector - data error or other
0FFF8h to 0FFFFh End of File Chain

Any other value points to the next sector. Note that each entry actually
counts as a cluster, not necessarily one sector. This value can be found
in the boot sector. 
Note that ALL floppy disks use a 12-bit FAT and thus 0FF8h to 0FFFh can mean
end of file chain. 
Now, this is probably confusing... it is probably easier to explain the
file allocation table (FAT) by an example: 
F0 FF FF 03 40 00 05 60 00 07 80 00 FF 0F 
This is a 12-bit FAT and thus 3 characters are used for each. 
Here is a conversion in columns for ease of comprehension: 
0000 / FF0
0001 / FFF
0002 / 003
0003 / 004
0004 / 006
0005 / FFF
0006 / 007
0007 / 008
0008 / FFF 
Okay... entry 0000 seems to be reserved. This could probably be the root
directory or something. It would be located at Cluster 0. 
Entry 0001 is an end of file chain. This could be the last 512 bytes or
however many bytes per cluster of a file. This is located at Cluster 1.
Maybe this is the root directory? 
Entry 0002 has the value of 3. Thus, a file on the disk starts at Cluster 2
and continues on to 3. Since now we are at cluster 3, we read the number
there. If it is not an end of file chain marker, then we continue further.
We find 4 : the value is 6. So we look at entry 6 : the value is 7. 7 to 8.
And then at 8 we have and end of file marker. This tells us that there is
information here but it might not be entirely filled with data. To figure
out how much of the data belongs to the file, you must look at the root
directory file size entry. 
Entry 0005 has an end of file chain marker. This is a file here that has from
0 to 512 bytes since no other cluster pointed to it. An exact size can be
found in the root directory entry. Since the previous file that started at
entry 0002 and was "split" in the middle by this entry, we have a
defragmented file. This demonstrates how the file allocation table is used
to allow space to be wasted as little as possible since it is not really
necessary to have a linear block of space for a file. When you defragment
your hard drive or a floppy drive, you are actually moving data from cluster
to cluster in such a way that each entry will point to the next one creating
all linear files. 
Now the root directory... this contains xxxx entries (which can be found in
the boot sector) which are of 32 bytes each. This is found immediately after
the file allocation table(s) whose size and number of FATs can be found in
the boot sector. 
Offset Size Description

0000h 8 bytes Filename (Space (20h) padded if shorter)
0008h 3 bytes Filename extention (padded if needed)
000Bh 1 byte File Attribute
000Ch 10 bytes Reserved - I think Win95 uses this for long names
0016h 2 bytes Time of last write to file
0018h 2 bytes Date of last write to file
001Ah 2 bytes Start Cluster (see FAT)
001Ch 4 bytes Exact file size

If the filename starts with value 00 it generally means that the entry is
not in use. 
If the filename starts with value E5 it generally, but not always means
that the entry is not in use. To find out if it is really deleted, the FAT
value pointed at by the Start Cluster must be 0000. 
The file attribute tells what kind of a file it is, or whether it is hidden
or something. Here is a bit table from bit 0 (rightmost) to bit 7
(leftmost): 
bit 0 : read-only if set
bit 1 : hidden file if set
bit 2 : system file if set
bit 3 : volume label if set
bit 4 : subdirectory if set
bit 5 : archive bit - backup programs use this to see if the file has
changed since last backup
bit 6 : reserved
bit 7 : reserved 
Note: Only the first file with the volume label set is actually the volume
label. 
Back to the bootstrap program... 
After setting up the stack and establishing the linear sector reader, we
must find and load a file off the disk. This is accomplished by: 
(1) Scan through root directory for OUR file to load
(2) Get starting cluster of file and filesize
(3) Load first cluster (starting cluster) into memory and add cluster size
to the memory offset
(4) If value at (starting cluster) in FAT is not end of file chain marker,
set (starting cluster) to value found and goto step 3
(5) End of file. JMP to original memory offset to run the file. Note that
the file loaded must be a raw machine language file - NOT an EXE file.
COM files work nicely here. 
One note here: My bootstrap program fills up most of the boot sector except
for 3 bytes. If you plan to edit it at all for your own use and it turns out
too big, I suggest shortening the "System file(s) not found" string. Also,
this bootstrap is superior to DOS's in that it doesn't HAVE to load a linear
file starting from cluster 1 like when you boot from a DOS disk. My
bootstrap here will load ANY file that is on the disk. All you have to do is
change the systemfilename variable. One more thing - if you plan to make
a bootstrap for your hard drive, remember that this program loads a 12-bit
FAT and your hard drive will usually be 16-bit or 32-bit (or neither if you
don't use a DOS compatible OS). After reading my tutorial, you should be
able to figure out how to modify this, right? :) Anyhow, a 16-bit FAT
should take less code and math - that's why I chose to explain the 12-bit
FAT. 
That is pretty much all there is to a bootstrap. The included set of
programs should help you, if you choose to, make a bootstrap and be able
to load your own operating system or whatever you want it to do. Until next
time... 
- vulture a.k.a. Sean Stanek 

! 

locals @@ 

segment code
assume cs:code,ds:code
org 7C00h
start: 

.386     ; enable 386 stuff - 386 required 

jmp short skipdiskinfo   ; short jump to skip boot sector info
nop     ; nop for filler 

Byte_Per_Sector Equ word ptr ds:[7C0Bh]
Sectors_Per_Cluster Equ byte ptr ds:[7C0Dh]
Reserved_Sectors Equ word ptr ds:[7C0Eh]
Number_Of_FATs Equ byte ptr ds:[7C10h]
Number_Of_Root_Entries Equ word ptr ds:[7C11h]
Number_Of_Sectors Equ word ptr ds:[7C13h]
Media_Descriptor Equ byte ptr ds:[7C15h]
Sectors_Per_FAT Equ word ptr ds:[7C16h]
Sectors_Per_Track Equ word ptr ds:[7C18h]
Number_Of_Heads Equ word ptr ds:[7C1Ah]
Number_Of_Hidden_Sectors Equ word ptr ds:[7C1Ch] 

org 7C3Eh    ; point after boot sector info
skipdiskinfo:    ; the start of the bootstrap 

 cli     ; disable interrupts so no stack is used
 xor ax,ax
 mov ss,ax
 mov ds,ax
 mov es,ax
 dec ax
 dec ax
 mov sp,ax ; ss:sp -> 0000:fffeh 
 sti
 xor eax,eax    ; set eax=0 
 
 mov al,number_of_FATs  ; calculate number of sectors to root dir
 mov dx,sectors_per_FAT
 mul dx
 shl edx,16
 add eax,edx
 xor ebx,ebx
 mov bx,Reserved_Sectors
 add eax,ebx 
 
 mov dl,0    ; a: 
 
 mov si,1    ; 1 sector at a time 
 
 xor bp,bp    ; bp = number of entries read so far 
 
 nextroot:    ; try all root entries until our's is found
 mov cx,3     ; try 3 times
 tryread:
 push cx
 push eax
 mov dl,0     ; a:
 mov si,1     ; 1 sector
 mov bx,60h
 mov es,bx    ; es:bx -> 60h:100h
 mov bx,100h    ; 100h just because
 call Read_Sector
 pop eax
 pop cx
 jnc readokay    ; if okay, jump to okay
 dec cx     ; decrement # of tries
 jnz tryread    ; and try again if we haven't done cx times
 jmp baddisk    ; something went bad... print bad disk string 
 
readokay:
 mov si,100h 
 mov di,offset systemfilename
 mov cx,11    ; 11 characters per file
nextentry:
 push ds
 push es  
 
 push ds
 push es
 pop ds     ; swap ds=0060h / es=0000h
 pop es 
 
 push si
 rep cmpsb    ; compare string
 pop si
 pop es
 pop ds
 jz filefound
 add si,20h
 inc bp
 cmp si,300h
 jae didntfind
 jmp nextentry 
  
didntfind: 
 inc eax     ; next sector
 cmp bp,number_of_root_entries  ; if we've read all 
        ; and found it not, then
 jae baddisk    ; print bad disk message
 jmp nextroot    ; if under then try again 
 
 filefound:     ; at es:si
 xor eax,eax
 mov ax,es:[si+1Ah]    ; start cluster
 mov di,100h    ; start offset for loading file 
  
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;; low level load a file routine ;;
 ;; this will load a file without dos ;;
 ;; and a max size of 65,280 bytes ;;
 ;; via interrupt 13h only ;;
 ;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  
continueLOADFILE: 
 
 push ax     ; save old current cluster
 mov ch,0
 mov cl,Sectors_Per_Cluster
 mul cx     ; convert cluster -> sector
 shl edx,16    ; now edx(16):ax is sector number
 add eax,edx    ; eax is now sector number 
 
 mov ch,0
 mov cl,Number_Of_FATs
 xor edx,edx
 mov dx,Sectors_Per_FAT
AddFATOffset:    ; adding fat offset
 add eax,edx
 loop AddFATOffset    ; by number_of_fats times 
 
 mov dx,Number_Of_Root_Entries  ; add root entry offset
     ; 512 / 32 = 16 entries per sector
 cmp dl,0    ; 16/sector = 2^4 = 10000b
 jnz noadd DH   ; if we had a weird number, inc dh to fix
 inc dh     ; counteract
noaddDH:
 shr dx,4    ; convert # of entries -> # of sectors 
 
 add eax,edx
 dec eax    ; eax is _finally_ actual sector number 
 
 push es
 mov bx,1000h   ; load file at segment 1000h
 mov es,bx
 mov bx,DI    ; current offset is in di
 add DI,512    ; add for later 
 
 mov dl,0     ; drive a:
 mov si,1     ; 1 sector 
 
 call Read_Sector    ; read sector in eax 
 
 pop es     ; restore old es segment 
 
 xor eax,eax
 pop ax     ; get back last cluster number 
 
;; in fat12, we can get 1536 / 1.5 = 1024 entries in 3 sectors
;; calculate sectors to load in fat - ax contains cluster number 
 

 push es     ; save for program segment
 push ax     ; save again
 shr ax,10     ; divide by 1024 = 2^10
 mov bx,3     ; every 3 sectors
 mul bx     ; multiply
 add ax,Reserved_Sectors   ; add boot sector size, etc. 
 
 mov bx,2000h
 mov es,bx
 mov bx,0    ; 2000h:0000 (just some place not used)
 mov si,3    ; 1 sector
 mov dl,0    ; drive a:
 call Read_Sector 
 
 pop ax
 and ax,3ffh    ; ax mod 1024 
 
 mov bx,3
 mul bx    ; ax*3/2 = offset of number of cluster
 shr ax,1   ; divide by 2
 jc midway 
 
   
 
 mov bx,ax
 mov ax,es:[bx]
 and ax,0fffh
 jmp gotcluster 
 
midway: 
;;; if we are here it means that our 12 bit cluster 
;;; should appear this way:
;;; xx cx ab
;;; where abc is the cluster number
;;; read in cx ab which reads into a register as abcx
;;; shift abcx right by 4 to get 0abc 
 
 
 mov bx,ax 
 mov ax,es:[bx]
 shr ax,4 
 
gotcluster:
 pop es 
 
 cmp ax,0fffh
 jz runprogram
 jmp continueLOADFILE 

runprogram:
 db 0eah
 dw 100h,1000h    ; jmp far 1000h:100h
   ; 1000h:100h -> place where system file was loaded 
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 

Read_Sector Proc
  ; dl = drive / eax = sector / si = num / es:bx -> buffer
 ; eax = 0 is the first sector for this and eax = 1
      ; is the second, etc.
 PUSH ESI    ; save for whatever reason
 PUSH BX     ; save buffer offset
 PUSH DX     ; save drive
 XOR EDX,EDX   ; prepare for dividing
 MOV ESI,EDX  ; set high 16 bits of esi to 0 for divide
 MOV SI,Sectors_Per_Track ; set esi is 32 bit sectors per track
 DIV ESI     ; edx = remainder / eax = quotient
 INC DX     ; dx = sector
 MOV CX,DX    ; cx = sector
 XOR EDX,EDX    ; prepare for another divide
 MOV ESI,EDX    ; esi = 0 again
 MOV SI,Number_Of_Heads   ; for another 32 bit divide
 DIV ESI     ; dx = head / ax = track
 MOV BX,DX    ; save head
 POP DX     ; get drive back (dl)
 MOV DH,BL    ; set dh to head
 MOV CH,AL    ; ch = track
 SHR AX,2    ; ah = bits 10 and 11 of track (cylinder)
 AND AL,11000000b ; set al for adding to cl
 ADD CL,AL    ; add on bits 8 and 9 of track (cylinder)
 SHL AH,6    ; set ah for adding to dh
 ADD DH,AH    ; add on bits 10 and 11 of track (cylinder)
 POP BX       ; restore offset
 POP ESI    ; restore esi
 MOV AX,SI    ; set al to number to read (1 to xx)
   ; note that xx is not always read because of
   ; various reasons like crossing to the next head
   ; or track (running out of sectors on current head)
   ; this will be returned in al on return
 MOV AH,2    ; function ah=2 / int 13h = read sector
 INT 13h    ; read sector(s) into memory
 RET     ; carry flag set if error, ah = error
     ; al = number of sectors read
 ENDP     ; end procedure 
 
 BadDisk:
 mov si,offset BadDiskString ; offset of asciz string to print
 WriteString:
 mov al,[si]    ; get character
 cmp al,0     ; if z then stop
 jz endString
 mov ah,0eh    ; int 10h / ah=0eh - print character
 int 10h     ; print character
 inc si     ; offset of next character
 jmp WriteString    ; loop until z found
 endString:
 xor ax,ax    ; int 16h / ah=0 - wait for keypress
 int 16h     ; getkey
 int 19h     ; int 19h - reload bootstrap 
 
 BadDiskString db 0dh,0ah
 db 'System file(s) not found.',0dh,0ah
 db 'Press any key to softboot.',0dh,0ah,0 

 systemfilename db 'LOADER COM' ; 8 char / 3 char - all space padded 
 
org 7DFEh
 db 055h,0AAh  ; it's customary to end stuff this way, but
     ; not required 

 ends
 end start
   

New Articles










 
Copyright © 2000 eFront Media, Inc.
Hosted by www.Geocities.ws

1