Introduction
In the previous article, I tried to brief about booting, how to write a bootable code in C and assembly, and how to embed assembly statements inside a
C program as well.
We also tried to write a few programs to examine if our code injected into the boot sector of a device works or not. In this article, I will try to explain about segmentation
and reading data from a floppy disk during booting and displaying it onto the screen. We will try writing
a few programs
and examine if we can read the data and try displaying it onto the screen.
What is the scope of the article?
I will limit the scope of the article onto how to write a program code in assembly, and how to copy it to the boot sector of a 3.5 inches floppy disk image,
and then how to test the floppy disk to boot it with the code we have written using an x86 emulator like bochs. I will take the help of BIOS services to achieve the task
of reading data from a floppy disk. By doing so we can explore more about BIOS routines and feel comfortable playing around in Real Mode.
Breakdown of topics
- Introduction to segmentation
- Programming environment
- Reading data from RAM-Memory
- Introduction to storage devices
- Architecture of a floppy disk
- Interaction with a floppy disk
Introduction to Segmentation
Before I proceed writing some examples on how to read a floppy disk, I wanted to refresh the topic of segmentation and its need as below.
What is
Segmentation?
The main memory is divided into segments that are indexed by few special registers called as segment registers (CS, DS, SS and ES).
What is the use of
Segmentation?
When we specify a 16-bit address, the CPU automatically calculates the start address of the respective segment. However, it is the duty of the programmer
to specify the start address of each segment especially when writing a program like boot loader.
What are the different types of segments?
I will mention only four types for now, as they are important for us to understand.
- Code Segment
- Data Segment
- Stack Segment
- Extended Segment
Code Segment
It is one of the sections of a program in memory that contains the executable instructions.
If you refer to my previous article, you will see the label .text where under which we intend to place the instructions to execute. When the program is loaded into memory,
the instructions under section .text are placed into code segment.
In CPU, we use CS register to refer to the code segment in memory.
Data Segment
It is one of the sections of a program in memory that contains variables both static and global by the programmer. We use DS register to refer to the data segment in memory.
Stack Segment
A programmer can use registers to store, modify and retrieve data during the scope of the program
that he has written. As there are only a few registers available for a programmer to use during the run time of the program, there is always a chance that the program logic
might get complicated, as there are only a few registers available for temporary use. Due to this, the programmer might always feel the need for a bigger place, which is more flexible
in terms of storing, processing and retrieving data. The CPU designers have come up with a special segment called the stack segment.
In order to store and retrieve data on stack segment, the programmer uses push and pop instructions. We use push instructions to pass arguments to functions as well.
We use SS register to refer to the stack segment in memory. Also remember that stack grows downwards.
Extended Segment:
The extended segment is normally used to load data that is much bigger than the size of the data that is stored in data segment. You will further see that I will try to load the data
from the floppy on Extended segment. We use ES register to refer to the Extended Segment in memory.
How to set Segment registers?
The programmer does not have the freedom to directly set any of the segment registers but instead we follow this way.
movw $0x07c0, %ax
movw %ax, %ds
What does the above step means?
- Copy the data to a general purpose register.
- Then assign it to the segment register.
We are setting AX register to 0x07c0
And when we are copying the contents of AX to DS. The absolute address is calculated as below.
DS = 16 * AX
So DS = 0x7c00
We further use offset to traverse from here on. To reach to a location in Data segment, we use offsets.
Programming environment
- Operating system (GNU Linux)
- Assembler (GNU Assembler)
- Compiler (GNU GCC)
- Linker (GNU linker ld)
- An x86 emulator used for our testing purposes(bochs).
Reading data from RAM
Note
Now, if you observe BIOS loads our program at 0x7c00 and starts executing it and then our program starts printing values one by one. We access the data on RAM by directly specifying
the offset and setting the data segment to 0x7c00.
Example1
Once our program is loaded by BIOS at 0x7c00, let us try to read data from offset 3 and 4 and then print them onto the screen.
Program: test.S
.code16 #generate 16-bit code
.text #executable code location
.globl _start
_start: #code entry point
jmp _boot #jump to boot code
data : .byte 'X' #variable
data1: .byte 'Z' #variable
_boot:
movw $0x07c0, %ax #set ax = 0x07c0
movw %ax , %ds #set ds = 16 * 0x07c0 = 0x7c00
#Now we will copy the data at position 3 from 0x7c00:0x0000
# and then print it onto the screen
movb 0x02 , %al #copy the data at 2nd position to %al
movb $0x0e , %ah
int $0x10
#Now we will copy the data at position 4 from 0x7c00:0x0000
# and then print it onto the screen
movb 0x03 , %al #copy the data at 3rd position to %al
movb $0x0e , %ah
int $0x10
#infinite loop
_freeze:
jmp _freeze
. = _start + 510 #mov to 510th byte from 0 pos
.byte 0x55 #append boot signature
.byte 0xaa #append boot signature
Now on the command line, to generate binary and to copy the code to boot sector of a floppy disk, type as below and then hit return.
- as test.S –o test.o
- ld –Ttext=0x7c00 –oformat=binary boot.o –o boot.bin
- dd if=/dev/zero of=floppy.img bs=512 count=2880
- dd if=boot.bin of=floppy.img
Note
If you open the boot.bin file in hexadecimal editor, you will see something similar as below.
You will see that X and Y are in the third and fourth position from 0th offset of 0x7c00.
To test the code, type the following commands as below.
Example:
Once our program is loaded by BIOS at 0x7c00, let us read a null terminated string from offset 2 and then print it.
Program: test2.S
.code16 #generate 16-bit code
.text #executable code location
.globl _start
_start: #code entry point
jmp _boot #jump to boot code
data : .asciz "This is boot loader" #variable
#calls the printString function which
#starts printing string from the position
.macro mprintString start_pos #macro to print string
pushw %si
movw \start_pos, %si
call printString
popw %si
.endm
printString: #function to print string
printStringIn:
lodsb
orb %al , %al
jz printStringOut
movb $0x0e, %ah
int $0x10
jmp printStringIn
printStringOut:
ret
_boot:
movw $0x07c0, %ax #set ax = 0x07c0
movw %ax , %ds #set ds = 16 * 0x07c0 = 0x7c00
mprintString $0x02
_freeze:
jmp _freeze
. = _start + 510 #mov to 510th byte from 0 pos
.byte 0x55 #append boot signature
.byte 0xaa #append boot signature
If you compile the program and open the binary in hexadecimal editor, you may see the string as “This is boot loader” onto output.
Introduction to storage devices
What is a storage device?
It is a device used for information storage, retrieval. It can also be used as a bootable media.
Why does a computer require storage devices?
We use computers mainly to store information, retrieve the information and process it so as part of storing and retrieval
of information, the manufactures came up with a new device called a storage device with various types.
What are the various types of storage devices available?
Depending on the size of the data, I will try to list them as below.
- Floppy disk
- Hard disk
- USB Disk
And more…
What is a floppy disk?
It is a device used for information storage, retrieval. It is also used as a bootable media.
A floppy disk is designed to store small amounts of data whose maximum size
could be limited to few Mega Bytes.
What is a Mega Byte?
In computing, the size of the data is measured in one of the following ways:
- Bit: It can store
a value of either a 1 or 0
- Nibble: 4 bits
- Byte(B): 8 bits
- Kilo Byte(KB): 1024 bytes
- Mega Byte(MB): 1 Kilo Byte * 1 Kilo Byte = 1,048,576 Bytes = 1024 Kilo Bytes = 1024 * 1024 Bytes
- Giga Byte(GB): 1,073,741,824 Bytes= 2^30 Bytes = 1024 Mega Bytes = 1,048,576 Kilo Bytes = 1024 * 1024 * 1024 Bytes
- Tera Byte(TB): 1,099,511,627,776 Bytes= 2^40 Bytes = 1024 Giga Bytes = 1,048,576 Megabytes = 1024 * 1024 * 1024 * 1024 Bytes
And more…but we will limit our selves to the above.
How does a floppy disk look like?
How much data can I store on a floppy disk?
It all depends upon various types of floppy disks available from manufactures and their respective sizes.
I will simply try to list the details of 3.5 inches floppy disk which I have used earlier in my life.
Name Description Size(mm) Volume(mm2) Capicity(MB)
3.5 inches 3.5 inch Floppy Disk 93.7 x 90.0 x 3.3 27,828.9 1.44 MB
Architecture of a typical floppy disk
The above diagram shows you the architecture of a typical floppy disk and let us be concerned about
3.5 inches floppy disk and I will explain more about it as below.
How to describe a 3.5 inches floppy disk?
- It has 2 sides
- It side is called as a head.
- Each Side contains 80 tracks on it.
- Each track contains 18 sectors in it.
- Each sector contains 512 bytes in it.
How to calculate the size of a floppy disk?
- Total Size in Bytes: Total sides * Total tracks * Total Sectors Per Track * Total Bytes Per Sector.
Total Size(Bytes) = 2 * 80 * 18 * 512 = 1474560 Bytes.
- Total Size in Kilo Bytes: (Total sides * Total tracks * Total Sectors Per Track * Total Bytes)/1024.
Total Size(KB) = (2 * 80 * 18 * 512)/1024 = 1474560/1024 = 1440 KB.
- Total Size in Mega Bytes: ((Total sides * Total tracks * Total Sectors Per Track * Total Bytes)/1024)/1024
Total Size(MB) = ((2 * 80 * 18 * 512)/1024)/1024 = (1474560/1024)/1024 = 1440/1024 = 1.4 MB
Where is boot sector located on a floppy disk?
It is located on the first sector of the disk.
Interaction with a floppy disk
How to read data from a floppy disk?
As our mission in this article is to read data from a floppy disk, the only choice left to us as of now is to use BIOS Services in our program as during the boot time we are in Real Mode to interact with the floppy disk. We need to use BIOS Interrupts to achieve our task.
Which interrupts are we going to use?
Interrupt 0x13
Service code 0x02
How to access a floppy disk using the interrupt 0x13?
- To request BIOS to read a sector on a floppy we use below.
AH = 0x02
- To request BIOS to read from the ‘N’th cylinder we use below.
CH = ‘N’
- To request BIOS to read from the ‘N’th head we use below.
DH = ‘N’
- To request BIOS to read ‘N’th sector we use below.
CL = ‘N’
- To request BIOS to read ‘N’ number of sectors we use below.
AL = N
- To interrupt the CPU to perform this activity we use below.
Int 0x13
Reading data from Floppy Disk
Let us write a program to display the labels of few sectors.
Program: test.S
.code16 #generate 16-bit code
.text #executable code location
.globl _start
_start:
jmp _boot #jump to the boot code to start execution
msgFail: .asciz "something has gone wrong..." #message about erroneous operation
#macro to print null terminated string
#this macro calls function PrintString
.macro mPrintString str
leaw \str, %si
call PrintString
.endm
#function to print null terminated string
PrintString:
lodsb
orb %al , %al
jz PrintStringOut
movb $0x0e, %ah
int $0x10
jmp PrintString
PrintStringOut:
ret
#macro to read a sector from a floppy disk
#and load it at extended segment
.macro mReadSectorFromFloppy num
movb $0x02, %ah #read disk function
movb $0x01, %al #total sectors to read
movb $0x00, %ch #select cylinder zero
movb $0x00, %dh #select head zero
movb \num, %cl #start reading from this sector
movb $0x00, %dl #drive number
int $0x13 #interrupt cpu to get this job done now
jc _failure #if fails then throw error
cmpb $0x01, %al #if total sectors read != 1
jne _failure #then throw error
.endm
#display the string that we have inserted as the
#identifier of the sector
DisplayData:
DisplayDataIn:
movb %es:(%bx), %al
orb %al , %al
jz DisplayDataOut
movb $0x0e , %ah
int $0x10
incw %bx
jmp DisplayDataIn
DisplayDataOut:
ret
_boot:
movw $0x07c0, %ax #initialize the data segment
movw %ax , %ds #to 0x7c00 location
movw $0x9000, %ax #set ax = 0x9000
movw %ax , %es #set es = 0x9000 = ax
xorw %bx , %bx #set bx = 0
mReadSectorFromFloppy $2 #read a sector from floppy disk
call DisplayData #display the label of the sector
mReadSectorFromFloppy $3 #read 3rd sector from floppy disk
call DisplayData #display the label of the sector
_freeze: #infinite loop
jmp _freeze #
_failure: #
mPrintString msgFail #write error message and then
jmp _freeze #jump to the freezing point
. = _start + 510 #mov to 510th byte from 0 pos
.byte 0x55 #append first part of the boot signature
.byte 0xAA #append last part of the boot signature
_sector2: #second sector of the floppy disk
.asciz "Sector: 2\n\r" #write data to the begining of the sector
. = _sector2 + 512 #move to the end of the second sector
_sector3: #third sector of the floppy disk
.asciz "Sector: 3\n\r" #write data to the begining of the sector
. = _sector3 + 512 #move to the end of the third sector
Now compile the code as below
- as test.S -o test.o
- ld -Ttext=0x0000 --oformat=binary test.o -o test.bin
- dd if=test.bin of=floppy.img
If you open the test.bin in an hexadecimal editor you will find that I have embedded a label to sector 2 and 3. I have highlighted them in the below snapshot.
If you run the program by typing bochs on command prompt you will see as below.
What is the functionality of the above program?
In the above program we define macros and functions to read and display the contents of the strings that are embedded in each sector.
Let me brief you about each macro and function.
#macro to print null terminated string
#this macro calls function PrintString
.macro mPrintString str
leaw \str, %si
call PrintString
.endm
This macro is defined to take a string as an argument and internally it calls another function called
PrintString
which does the job of displaying character by character onto the screen.
#function to print null terminated string
PrintString:
lodsb
orb %al , %al
jz PrintStringOut
movb $0x0e, %ah
int $0x10
jmp PrintString
PrintStringOut:
Ret
This is the function called by the macro mPrintString
to display each byte of the null terminated string onto the screen.
#macro to read a sector from a floppy disk
#and load it at extended segment
.macro mReadSectorFromFloppy num
movb $0x02, %ah #read disk function
movb $0x01, %al #total sectors to read
movb $0x00, %ch #select cylinder zero
movb $0x00, %dh #select head zero
movb \num, %cl #start reading from this sector
movb $0x00, %dl #drive number
int $0x13 #interrupt cpu to get this job done now
jc _failure #if fails then throw error
cmpb $0x01, %al #if total sectors read != 1
jne _failure #then throw error
.endm
This macro mReadSectorFromFloppy reads a sector into the extended segment and places there for further processing. This macro takes a sector number to read as an argument.
#display the string that we have inserted as the
#identifier of the sector
DisplayData:
DisplayDataIn:
movb %es:(%bx), %al
orb %al , %al
jz DisplayDataOut
movb $0x0e , %ah
int $0x10
incw %bx
jmp DisplayDataIn
DisplayDataOut:
Ret
This function displays each byte of the data from beginning until a null terminated character is encountered.
_boot:
movw $0x07c0, %ax #initialize the data segment
movw %ax , %ds #to 0x7c00 location
movw $0x9000, %ax #set ax = 0x9000
movw %ax , %es #set es = 0x9000 = ax
xorw %bx , %bx #set bx = 0
This is the main boot code for execution. Before we begin with printing the contents of the disk, we set the data segment to 0x7c00 and also set the extended segment to 0x9000.
What is the purpose of setting the extended segment?
First we read a sector into our program memory at 0x9000 and then start displaying the content of the sector.
That is why we set the Extended segment to 0x9000.
mReadSectorFromFloppy $2 #read a sector from floppy disk
call DisplayData #display the label of the sector
mReadSectorFromFloppy $3 #read 3rd sector from floppy disk
call DisplayData #display the label of the sector
We call the macro to read sector2 and then display its content and then again we call the macro to read sector 3 and then display its content.
_freeze: #infinite loop
jmp _freeze #
After displaying the contents of the sectors, we go into one infinite loop to hang our program.
_failure: #
mPrintString msgFail #write error message and then
jmp _freeze #jump to the freezing point
We defined this section to jump to this label incase of any erroneous situations and then hang the program again.
. = _start + 510 #mov to 510th byte from 0 pos
.byte 0x55 #append first part of the boot signature
.byte 0xAA #append last part of the boot signature
We move to the 510th byte of the sector and then append the boot signature which is a mandatory for a floppy disk to be identified as a bootable device,
else the system throws an error as invalid disk.
_sector2: #second sector of the floppy disk
.asciz "Sector: 2\n\r" #write data to the begining of the sector
. = _sector2 + 512 #move to the end of the second sector
_sector3: #third sector of the floppy disk
.asciz "Sector: 3\n\r" #write data to the begining of the sector
. = _sector3 + 512 #move to the end of the third sector
The above step performs appending a string at the beginning of the sector 2 and 3.
That’s all for this article :)
Have fun and try to explore reading floppy disk in real mode and embed the functionality into your bootloader.
In the following articles I will try to brief about File systems and their importance. We will also write a minimal bootloader to parse a fat12 formatted floppy disk,
how to read and write to it and also write a second stage bootloader and its importance.
Bye for now :)