Home   About me   Contact   Links   What's new?   Downloads   Sign/ View my guestbook   Legal information   Last update: January 10th, 2003 
Programming a boot-manager:  Description - List of interrupts - Download docs & source-code as zip-file (25.7kB)

Programming a boot-manager

Programming a boot-manager is quite easy - if know assembler and have some understanding of how the boot-process works.
Before getting started, however, I have to warn you: If you don't know what you're doing, don't do it. You may easily destroy all data stored on your harddrive(s), with no Disk Doctor being able to help you recover it! So please read the entire text before starting, making sure everything makes sense!
You should be able to program in assembler, or at least understand the examples. If you don't know assembler yet, you should look here or here.
If you don't already have an assembler, I suggest you try:
  • NASM which is free. You should download the program from here, in the section Assembler/Tools. I didn't find the actual program on the main-site of NASM.

  • Of you can try Eric Isaacson's A86 which is shareware and costs $50.



  • You can check out a list with the interrupts needed for this project and their descriptions here. You can download the whole project, including sample code, needed tools (except the assembler) and documentation in one zip-file.

    How does a boot-manager work in detail?
    The Master Boot Record contains 4 entries, describing where each of the four partitions start and end, what kind of OS they belong to and which one of them is the active one. This table starts at 01BEh in the sector, containing the following record four times:
    offset:  type:content:
    00hbytestatus:
    00h = inactive
    80h = active, boot-partition
    01hbytestart-head of the partition
    02hword start sector and cylinder:
    high-byte: bits 0-7 of cylinder-no.
    low-byte: bits 0-5: number of sector
                 bits 6-7: bits 8+9 of cylinder-no.
    04hbytetype of partition:
    00h: not used
    01h: 12-Bit-FAT-DOS
    02h: XENIX
    03h: XENIX
    04h:16-Bit-FAT-DOS
    05h: extended DOS-partition
    06h: DOS 4.0 partition, more than 32MB
    DBh: concurrent DOS
    05hbyteend-head of the partition
    06hwordend sector and cylinder, see 02h
    08hdworddistance between the MBR and the first sector of the partition in sectors   
    0Chdwordnumber of sectors in the partition

    Note: FAT32-systems seem to use other codes than the ones shown above, but I'm not sure about their meaning. Just don't wonder if you find one that's not listed here!

    As you can see, the first byte in that record specifies whether or not a partition is the active one. The boot-code in the MBR just looks for the one containing the 80h and knows which partition to activate. We, however, want to have several partitions that we can choose from. If you want to have for example a WIN98 and a DOS-partition, you have a problem: You're not supposed to have two primary partitions, which is why a boot-manager usually marks the one which is not active with DBh and so the active OS will justs ignore it. So whatever partition the boot-manager wants to load it marks as a primary partition and then hides the other one. I'm not really sure if you're supposed to mark the partition as active (i.e. set the 80h), but I don't think it is the case.
    The BIOS loads the MBR to the address 0000h:7C00h (i.e. segment 0000h, offset 7C00h). But in order to be activated, the last two bytes in the MBR have to have the value 55h AAh. Otherwise, most BIOSes don't activate the code and believe it to be corrupted.
    The boot-manager now has to move it's code to the address 0000h:0600h, because the boot-sector has to be loaded to 0000h:7C00h and that would overwrite the boot-manager's code. Now the boot-manager can determine which partition to load and it may modify the table in order to activate a OS and then write the table back onto the harddisk. Then it loads the boot-sector of the partition that it wants to load to the address 0000h:7C00h. If you want to, you can have your boot-manager check if the sector loaded is really a boot-sector by checking if it ends with 55h AAh and displaying an error-message if this is not the case.
    Now the boot-manager can activate the code that it loaded: It has to provide a pointer to the entry describing the partition in the registers ES:BP or in ES:SI, I am not sure about that. (Try it out, or provide it in both, if you want to be safe. I've seen some boot-sectors ignore the value you give them, and a MBR that gave the value in BP and in SI.) Now you do a jump to the position 0000h:7C00h to activate the boot-sector's code!

    How two set up two partitions
    As I said before, there are sometimes problems with having two primary partitions at the same time - all FDISK versions that I've seen don't allow you to do that. So if you want to create two primary partitions, this is what you have to do:
    Create a primary partition. Then read out the MBR and set the type of that partition to DBh, or 02h. Reboot from a boot-disk and create another primary partition. Your fdisk or whatever program you use will say the existing partition is some unknown type or whatever, but don't bother about that. If you want to, you can do this again to create another one or you can create a secondary partition.
    Now you can read out the MBR again, program your boot-manager, fill in the partition-data into that boot-manager and then put the boot-manager onto the disk. That's it!
    When you have a look at the MBR, you may find that the first partition is not in the first record - that the two partitions you created only fill the entries 3 and 4 - and maybe they are even reversed there. I don't know why fdisk saves them like that, but we have to know that it can do that and we have to keep that in mind. You probably don't want to write extensive code for a boot-manager to find out which partition is stored where in these entries. That's why I suggest this: Just arrange the partition entries the way you like them, and have the boot-manager access them in that order. Only problem: Each time you use fdisk, you'll have to rearrange them, 'cause otherwise you might start booting from a secondary partition or just not at all. But I don't think you're gonna use fdisk that often, so this is easier than writing complicated code. (I'm really lazy with coding in assembler, 'cause everything takes so damn much code ;-))
    So first, you'll have to have find a way to access the MBR on your harddisk. You can't use debug's standard functions, that's why I provide to little programs that do that in a handy way: RS for reading it, WS for writing. Just give a filename as argument for the sector to be put into or to be loaded from. I also have a backup-utility that can help you back up your MBR onto a disk - you can then just boot from that disk and it will restore the MBR automatically. I suggest you do read out the MBR with RS and then copy that file and WS onto a boot-disk, but also make that other backup, 'cause it will restore your MBR without using DOS or Windows.
    Back to arranging the partition-entries: Just load the file with the MBR into debug and copy the entries around.

    Programming the boot-manager
    Okay, you ready? Good, then we'll start on the boot-manager now.

  • 1. Initialising the registers and moving the code to the position 0000h:0600h:


  •   org 0000h         ;this is to be an object file and starts at 0000h
      cli               ;interrupts off
      xor    ax, ax     ;stack in segment 0000h
      mov    ss, ax
      mov    sp, 7C00h  ;move stack right before our position now
      push   ax         ;put all other segments to 0000h as well
      pop    es
      push   ax
      pop    ds
      sti               ;interrupts back on
      cld               ;make sure we copy upwards, not downwards
      mov    di, 0600h  ;destination for the code: 0600h
      mov    si, sp     ;copy from 7C00h on, which is in SP!
      mov    cx, 0100h  ;number of words to move
      repnz
      movsw
      jmp    0000:(0600h+$+5)   ;jump to instruction behind this
    		;i.e. offset of this instruction + 5
    
          ; ... here we are executing on at 06??h, right after that jump
    

  • 2. Determine what partition you're gonna load

  • Just ask for a key, maybe display a message, or whatever. I'm planning to put a switch onto my serial port, so that I can just flip a switch and it will boot Windows or Linux (if I ever get that one), without asking for a key! You can also provide the option of booting from a floppy-disk, I'll show how to do that as well.
    But keep one thing in mind: At the time the boot-manager asks for a key, there is no keyboard-driver loaded, so you'll have an American keyboard layout, no matter what your keyboard says. So if you have a German keyboard and you want to ask for a 'Y', just check if the user typed a 'Z', and your problem's gone. Also, I suggest you convert all letters to small letters, 'cause you'd have a problem if someone typed 'A' and you're checking for an 'a'.
      mov    ah, 00h
      int    16h        ;call interrupt 16h, function 00h: get key
    
      xor    al, 20h    ;convert to small letters
    
      cmp    al, "a"    ;boot from drive a?
      je     load_from_a
    
       ;  ...  ...
    

  • 3. Changing the active partition

  • First, you set the 80h for the partition you want to activate and all other ones to 00h. You don't necessarily have to write this change to disk, but you have to do it in RAM, because the boot-sector might use the 80h in that table, and I think we can make use of it, too. You'll see why lateron.

    entryoffset sectoroffset in memoryoffset type of partition in memory
    101BEh 07BEh 07C2h 
    201CEh 07CEh 07D2h 
    301DEh 07E2h 07DEh 
    401EEh 07F2h 07EEh 

      mov   byte ptr [07BEh], 80h   ;assume we want to activate partition 1
      mov   byte ptr [07CEh], 00h   ;and inactivate partition 2
    

    You have to set the type of partition of the one you want to activate to it's original value, and the other one's to 0DBh. If you have several partitions that you want to hide, you can set them all to 0DBh. If you have partitions that don't affect each other, like a OS/2 and a Windows98, you can just keep their values - you can just skip this step.
      mov   byte ptr [07C2h], 06h   ;mark partition 1 as DOS-partition
      mov   byte ptr [07D2h], 0DBh  ;mark partition 2 as something else
    

    Now we have to change that sector back to the harddisk:
      mov   bp, 0005h		;BP is our counter
    save_loop:			;enter loop for saving:
      mov   ax, 0301h		;function 03h, write 01h sector(s)
      mov   bx, 0600h               ;address of data to save -
    				;hey, that's our own code!!!
      mov   cx, 0001h		;track no. 00h, sector 01h
      mov   dx, 0080h		;head no. 00h, drive no. 80h (i.e. 1st drive)
      int   13h			;call BIOS disk
      jnc   save_success  		;all right, we saved it!
    
      xor   ax, ax         		;error: call reset function 00h
      int   13h
    
      dec   bp			;decrement counter
      cmp   bp, 00h			;five tries over? error!
      jnz   save_loop     		;try up to five times
    
    	;error saving - display message or whatever
    
    save_success:	;okay, saved successfully
    

  • 4. Loading the boot-sector

  • Because of the structure of the partition-entries, loading the boot-sector is quite easy: Just load the first word of the entry into the dx-register and the second word into the cx-register: The dl-register will then contain the number of the drive (80h), the dh-register the number of the head, cx the number of sector and cylinder. That's why you had to mark the partition as active in RAM! I suggest that you write a procedure that you can call to load the boot-sector, then you just load the values for cx and dx after activating the partition and call the function to load the sector. If you want to provide the option to boot from drive a, you just set different values for cx and dx and call your function!
    load_drive_a:  ;boot from drive A:
      mov  cx,  0001h    ;cylinder 00h, sector 01h
      mov  dx,  0000h    ;track 00h, drive 00h
      call load_sector   ;load sector
    
    load_part_1:   ;boot from partition 1
        ; ... code for activating partition, saving sector
      mov  dx,  [0600h+01BEh]      ;drive-nr+head
      mov  cx,  [0600h+01BEh+2]    ;sector+cylinder
      mov  si,  0600h+01BEh        ;address of table to si for boot-sector
      jmp  load_sector             ;load and activate boot-sector
      
    load_part_2:   ;boot from partition 2
        ; ... code for activating partition, saving sector
      mov  dx,  [0600h+01CEh]      ;drive-nr+head
      mov  cx,  [0600h+01CEh+2]    ;sector+cylinder
      mov  si,  0600h+01CEh        ;address of table to si for boot-sector
      jmp  load_sector             ;load and activate boot-sector
    
    load_sector:                   ;load and activate sector
      mov  bp,  0005h              ;set loading-counter
    loading_loop:
      mov  ax,  0201h              ;function 02h, load 01h sector
      mov  bx,  7C00h              ;destination address
      int  13h                     ;call BIOS-function
      jnc  sector_loaded           ;success?
      
      ;error, try again
      mov  ah,  00h
      int  13h                     ;reset drive
    
      dec  bp
      cmp  bp,  0000h
      jnz  loading_loop            ;do this five times max.
    
        ;error loading sector
    
    sector_loaded:                 ;sector loaded successfully
      ;now we check if this is valid boot-sector, if you want to:
      cmp  word ptr [0000h:7D1Eh],  0AA55h
             ;remember: a word is stored low-byte, then high-byte!
      je   sector_okay             ;does sector end with 55AAh?
        ;error: this is not a valid boot-sector
    
    sector_okay:                   ;boot-sector okay
      ;address of partition-table-entry is in si already
      mov  bp,  si  ;well, we'll make sure the bootsector
                    ;gets that value in BP as well
    
      jmp  0000h:7C00h    ;jump to boot-sector!!!
    
           ;we're done!
    


  • 5. Miscellaneous: Printing a string

  • Here's a function to display a zero-terminated string. Give the offset-address (without adding the 0600h to it!) in SI!
    ;print a string:
      mov   si,  offset msg_text
      call stringout
    
    ;subroutine for displaying a zero-terminated string whose
    ;offset-600h we have in SI. That way, we can use normal
    ;offset-functions provided by the assembler
    stringout:              add si, 0600h     ;adjust pointer
                                              ;add base of 0600h
    
    StrLoop:                lodsb             ;load char and increment SI
                            cmp   al,  00     ;print string till 0
                            jnz NotEnd
                            ret               ;finished printing
    NotEnd:                 push  si          ;save SI, BIOS might change it
                            mov   bx,  0007
                            mov   ah,  0Eh
                            int   10h         ;call BIOS
                            pop   si          ;restore SI
                            jmp   StrLoop
    ;end of subroutine
    
    ;here is a demo-text:
    msg_text   db "hi, whats up?", 13, 10, 0
    

  • 6. Putting it all together

  • Well, now you just have to put together all the code - have a look at my sample, if you don't know how to start. When you've got the code set up, you'll need to run it through the assembler and then put the partition entries into it. I have another utility for that, called MIX. You provide a file with the MBR in it (remember to arrange the partitions so that the first one is in the first entry and so on!) and another one with the boot-manager-code in it, and it will put the partition-entries into your file. You can then use WS to write it onto the harddisk.

    < br. 7. Debugging the boot-manager
    Debugging the boot-manager can be kinda tricky, but you can do it with debug: Load the file to offset 7C00h, then run it step by step. You'll have to adjust the segment-registers as the boot-manager will set them to 0000h, expecting himself to be there - just reset AX after it has been set to 0000h to the code-segment-address. Also, you have to do the jump to the moved code manually, 'cause it wants to jump to 0000h:06??h.
    And there is another thing to be careful about: When the boot-manager writes himself to disk, there might still be the break-instruction from debug in there and the code won't work after reboot! I had that same problem, so please write the MBR to disk using WS and not by running it from debug!!!
    Also, have a look at your file before writing it onto the disk: Make sure your code is not too long and being overwritten by the partition-table!!!




    home  -  about me  -  contact  -  links  -  what's new?  -  view my guestbook  -  sign my guestbook
    legal information  -  last update: January 10th, 2003  -  back to top