Friday, July 27, 2012

Reading Disk Properties in Assembler - INT 13H

This post is a continuation from the previous post [Writting a Bootloader] which was an attempt to build a Bootloader for the x86 architecture.

Although the executable size is limited to 512 Bytes, there are several important tasks that can be accomplished during booting process. One of them is to read the Geometry from several storage devices.

Interrupt 13h [INT 13h] provides sector-based disk access services using the CHS addressing  method (Cylinder/Head/Sectors). This values provide the physical location of the data (although this is not completely true nowadays).

The code below is an example of this interrupt. It reads the CHS from the first Hard Disk using Interrupt 13h.

Briefly, interrupt parameters are set in AX and DX registers. After interrupt is completed, its values can be read from the appropriate registers (CX, DX). The rest of the code includes helper functions to print numbers and strings to present results properly.

After booting our virtual system using the code, we get the following result:

    Hello Again
    Hard Drive Information
    Disk Geometry (C/H/S): 130/16/63

Comparing these results with fdisk on the same drive:

    Disk /dev/sdb: 67 MB, 67108864 bytes
    255 heads, 63 sectors/track, 8 cylinders, total 131072 sectors
    Units = sectors of 1 * 512 = 512 bytes
    Sector size (logical/physical): 512 bytes / 512 bytes
    I/O size (minimum/optimal): 512 bytes / 512 bytes
    Disk identifier: 0x00000000

I am still trying to figure why these values are so different. Anyway, the total size of the drive taking the total oh Cylinders/Heads/Sectors will be close to the drive size described by fdisk.

Finally, notice that 08h service is limited to drives up to 500 MB. If there's required to read properties from bigger drives, service 48h is the appropriate.

As exercise, following improvements can be done:
  • Report an error if reading fails
  • Read properties from all connected drives
  • Determine drive size in LBA format
  • Modify code to read bigger drives

  1. BITS 16                 ;Tell compiler that this is 16 Bit code
  2. org 07C00h              ;Tell compiler code will be loaded in this memory section
  4. mov si, txtStr
  5. call printTxt
  6. call crlf
  7. mov si, txtStr2
  8. call printTxt
  9. call crlf
  11. mov si, txtDisk
  12. call printTxt
  14. ;Read HD1 parameters
  15. mov AH, 08h     ;Read Drive Parameters
  16. mov DL, 80h     ; First Drive
  17. int 13h
  19. push cx
  20. mov ax, cx      ;Cylinders
  21. and ax, 0C0h
  22. shl ax, 2
  23. and cx, 0FF00h
  24. shr cx, 8
  25. or cx, ax
  26. mov ax, cx
  27. inc ax
  28. call printNum
  29. mov si, txtSlash
  30. call printTxt
  31. pop cx
  33. mov al, dh      ;Num Heads
  34. mov ah, 0
  35. inc ax
  36. call printNum
  37. mov si, txtSlash
  38. call printTxt
  40. and cx, 3Fh ;[5..0] Sectors Per Track
  41. mov ax, cx
  42. call printNum
  43. call crlf
  45. JMP $
  47. ;********************* Utility Functions ******************
  49. printNum:       ;Print a number (ax)
  50.         push cx
  51.         push dx
  52.         mov cx,000Ah    ;divide by 10
  53.         mov bx, sp
  54.         getDigit:
  55.                 mov dx,0        ;puting 0 in the high part of the divided number (DX:AX)
  56.                 div cx          ;DX:AX/cx.  ax=dx:ax/cx  and dx=dx:ax%cx(modulus)
  57.                 push dx
  58.                 cmp ax,0
  59.         jne getDigit
  61.         printNmb:
  62.                 pop ax
  63.                 add al, 30h     ;adding the '0' char for printing
  64.                 mov ah,0eh      ;print char interrupt
  65.                 int 10h
  66.                 cmp bx, sp
  67.         jne printNmb
  69.         pop dx
  70.         pop cx
  71.         RET
  73. printTxt:       ;Print a String (si)
  74.   lodsb
  75.   cmp al, 0
  76.   je exitFn
  77.   mov ah, 0Eh
  78.   int 10h
  79.   jmp printTxt
  80. exitFn:
  81.   RET
  83. crlf:   ;Print End of line
  84.         mov ax, 0E0dh
  85.         int 10h
  86.         mov ax, 0E0ah
  87.         int 10h
  88.         RET
  90. ;AppData
  91. txtStr db "Hello Again", 0
  92. txtStr2 db "Hard Drive Information", 0
  93. txtDisk db "Disk Geometry (C/H/S): ", 0
  94. txtSlash db "/", 0
  95. times 510-($-$$) db 0   ;Fill the rest of the Loader with "0"
  96. dw 0AA55h               ;Bootloader Signature

Thursday, July 26, 2012

Writing a Bootloader

I am starting to think of any reason to Write a Bootloader ...

I've been always interested not only how to write applications but understanding how the whole system works, from the moment the machine boots.

The first steps in computer booting can be described in general as follows:
  • Power Up
  • Start Processing ROM BIOS
  • Execute POS (Power-on self Test)
  • Check hardware devices
  • Look for OS to load

During the last step, BIOS will search for Storage Devices (described in the device Boot Order). These are usually CD-Rom, Hard Drives, USB Drives, Disk Drives, etc.

For each Device (In order), BIOS will look for MBR (Master Boot Record) which corresponds to the first 512 bytes of the Device Image. These 512 Bytes are loaded then into the memory and executed. It's a very small space in memory to be able to achieve many tasks. 

Bootloaders are commonly used to access the kernel loader located in the boot sector of the drive. Other tasks includes initialize critical hardware devices and initialize other resources such as memory, interrupts, clocks and timers.

Hmm looks like I found a reason to create a bootloader ... Just for Fun! (Anyway, I got sick note from Doctor so I have to stay home ... It looks like I am getting Bored!)

Let's get our hands Dirty. Which tools from the Hardware store do we need?
Good news is we don't need any (If you want to go farther, then get some old USB Flash Drive)

Software of course is a must:
  • Any Linux flavor (I used fedora)
  • A Virtualization Application (Virtual Box)
I will run the Bootloader in a Virtual Machine because it will allow me to test easier every change I do to the source code and performing a real test will require me to have a spare machine for tests. (My wife would kill me if She finds her computer broken when She arrives Home)

As my computer is running Windows 7, I have trouble finding appropriate tools to develop a bootloader (It must be compiled as 16Bit Binary File). If you are interested, there's nasm for windows to compile your code, and partcopy to write the MBR of a Device.

Instead a Virtual Environment is also possible to use Bochs which is a x86 (PC) emulator.

First, create the Linux virtual machine in VirtualBox (It's pretty straightforward). After you spent some time installing it, let's create the next Virtual Machine.

I've created a Virtual Machine with 64MB of memory and "Other" as operating system. For the storage I've created a virtual Drive with 64MB of space and type "Shareable". (It will be used inside Linux and our new OS).

Then, Include this Drive in your linux configuration as IDE Primary slave. I will allow linux to recognize it as a valid drive.

Now it's time to code. Run your Linux Virtual Machine and open your favorite text editor.

There are four important requirements for the bootloader:

  • Must be compiled in 16Bit mode
  • Define Memory offset when code is loaded into the physical address.
  • Boot Memory space not used must be cleared.
  • Boot Signature: This is required by the BIOS to determine if the Device is Bootable. Failing to find this signature will cause BIOS to ignore the device.
The following code will generate a basic bootloader:

  1. BITS 16                 ;Tell compiler that this is 16 Bit code
  2. org 07C00h              ;Tell compiler code will be loaded in this memory section
  3. mov si, txtStr
  4. call printTxt
  5. JMP $
  6. printTxt:
  7.   lodsb
  8.   cmp al, 0
  9.   je exitFn
  10.   mov ah, 0Eh
  11.   int 10h
  12.   jmp printTxt
  13. exitFn:
  14.   RET
  15. ;AppData
  16. txtStr db "Hello You!", 0
  17. times 510-($-$$) db 0   ;Fill the rest of the Loader with "0"
  18. dw 0AA55h               ;Bootloader Signature

I will not go into the details of each call. Briefly, it defines code as 16 Bits, tells compiler where in physical memory code will be loaded, prints the string defined in "txtStr", fills the rest of bytes with "0" and finally writes the bootloader signature.

Now I will compile code above using nasm:

     nasm bootload.asm -f bin -o bootload.bin

In the final step I will use the linux "dd" command to copy the generated binary file to the device:

     dd if= bootload.bin bs=512 of=/dev/sdb

Notice that /dev/fd0 is the additional virtual drive we added to the Linux machine. Do not mount the device!

If command above succeeded, go to to Virtual Box Manager and launch the OS where you just copied the Bootloader. Hopefully you will get a screen like this:

If you get this screen, then you can continue your research and go beyond the typical "Hello World" example. Low level programming and OS development is a very interesting topic that is worth exploring.