Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / programming

Writing a boot loader in Assembly and C - Part 2

4.97/5 (49 votes)
16 Oct 2013CPOL11 min read 73.2K  
Reading the contents of a floppy disk using BIOS interrupts and Services.

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.

ASM
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

ASM
.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.

Image 1

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.

  • bochs

Image 2

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

ASM
.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.

Image 3

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?

Image 4

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

Image 5

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

ASM
.code16                       #generate 16-bit code
.text                         #executable code location
.globl _start;                #code entry point
_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.

Image 6

If you run the program by typing bochs on command prompt you will see as below.

Image 7

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.

ASM
#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.

ASM
#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.

MSIL
#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.

ASM
#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.

ASM
_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.

ASM
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.

ASM
_freeze:                      #infinite loop
jmp _freeze              #

After displaying the contents of the sectors, we go into one infinite loop to hang our program.

ASM
_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.

ASM
. = _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.

ASM
_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 :)

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)