Bona Fide OS Developer
View unanswered posts | View active topics It is currently Tue Mar 19, 2024 2:21 am



Post new topic Reply to topic  [ 1 post ] 
 Testing Non-Emulated Boot from CD/DVD 
Author Message

Joined: Sun Sep 19, 2010 9:45 am
Posts: 28
Post Testing Non-Emulated Boot from CD/DVD
I have now what can be the start of investigating and building and easy-to-understand document about how to boot from CD/DVD.

For this I have used Nero (or another burning program of your choice) that can produce a bootable ISO image.

I have used also the code of John Fine's bootf02.zip code, but more heavily documented by me and all the code put in one single file.

This is for a boot record for a floppy disk.

The trick here is to use this floppy sector as the boot record to burn into the ISO image.

This code originally detects the drive we booted from, and thus I have made a slight modification here, in which we set DL to 0:


Code:
 ;>   cs = 0
 ;>>  dl = drive we were booted from
 ;INIT: enable protected mode (time 1 of 2) to load GDT
 ;INIT: enable protected mode (time 1 of 2) to load GDT
 ;INIT: enable protected mode (time 1 of 2) to load GDT

   ;Force floppy A (THIS IS THE TRICK TO BOOT FROM CD/DVD
   ;BUT LOAD THE KERNEL FROM THE FLOPPY):
   ;;
    xor dx,dx


With this, we can boot and execute this code from a CD/DVD, which in turn will parse the floppy drive A looking for the KERNEL.BIN file.

All of the FAT12 fields used are read from the boot record in the CD/DVD, so with this method we can only use 1.44 MB standard floppies, as long as we use the unmodified fields in the code below.

Interesting, isn't it?


Code:
%define resbytes 510-(unused-_00h_jmp)

ORG 7C00h


_00h_jmp: db 0EBh                         ;jmps 003E
          db 03Ch                         ;2                        (2)
_02h_nop: nop                             ;1                        (3)

_03h_OEMid              db    "OEMIdent"  ;8                        (11)
_0Bh_bytesPerSect       dw         0200h  ;2                        (13)
_0Dh_sectsPerClus       db          001h  ;1                        (14)
_0Eh_resrvedSects       dw         0001h  ;2                        (16)
_10h_numOfFATs          db          002h  ;1                        (17)
_11h_numRootDirEntries  dw         00E0h  ;2                        (19)
_13h_numSectors         dw         0B40h  ;2                        (21)
_15h_mediaType          db          0F0h  ;1                        (22)
_16h_numFATsectors      dw         0009h  ;2                        (24)
_18h_sectorsPerTrack    dw         0012h  ;2                        (26)
_1Ah_numHeads           dw         0002h  ;2                        (28)
_1Ch_numHiddenSects     dd     00000000h  ;4                        (32)
_20h_numSectorsHuge     dd     00000000h  ;4                        (36)
_24h_driveNumber        db           00h  ;1                        (37)
_25h_reserved           db           00h  ;1                        (38)
_26h_signature          db           29h  ;1                        (39)
_27h_volumeID           db        "????"  ;4                        (43)
_28h_volumeLabel        db "VolumeLabel"  ;11                       (54)
_36h_FSType             db    "FAT12   "  ;8                        (62)





;INIT: 448 free bytes:
;INIT: 448 free bytes:
;INIT: 448 free bytes:
;   pusha

;    mov al,18   ;640x480x16 mode
;    mov ah,0   ;service
;     int 10h

;   popa

 ;>   cs = 0
 ;>>  dl = drive we were booted from
 ;INIT: enable protected mode (time 1 of 2) to load GDT
 ;INIT: enable protected mode (time 1 of 2) to load GDT
 ;INIT: enable protected mode (time 1 of 2) to load GDT

   ;Force floppy A:
  ;;
    xor dx,dx


  start:  cli                             ;{0}
          lgdt    [cs:GDT]              ;Load GDT. This is how we would
                                          ;exactly access the GDT in
                                          ;real mode
            mov     ecx, CR0                ;Switch to protected mode
            inc     cx            ;set PE bit
            mov     CR0, ecx          ;{5} here we activate protected mode
 ;END:  enable protected mode (time 1 of 2) to load GDT
 ;END:  enable protected mode (time 1 of 2) to load GDT
 ;END:  enable protected mode (time 1 of 2) to load GDT








;;INIT: enable A20
;;INIT: enable A20
;;INIT: enable A20
  ;registers modified in this INIT--END portion:
   ;AX (EAX)
     ;registers with values to reuse in the next INIT--END portion:
      ;AH (cleared to 0)





  .5:     in      al, 0x64              ;Enable A20 {4A}. Port 0x64 is the
                                        ;KBC port at the motherboard.

          test    al, 2     ;See if bit 2 at this port is 1, which means
                            ;that he KBC is not ready.

          jnz     .5    ;Repeat until but 2 of port 0x64 is 0, which means
                        ;that the KBC is ready for commands.

        mov     al, 0xD1    ;This command is to write the status byte.
                            ;This is the so-called *WRITE OUTPUT PORT*.
          out     0x64, al    ;Here we send it and it gets executed.

.6:     in      al, 0x64    ;Read the byte at port 0x64.
        and     ax, byte 2   ;See if this bit is 0.
                             ;NOTE: this will leave AL to 0 at once, which
                             ;      will be used in the next
                             ;      INIT--END block.
          jnz     .6           ;Repeat until the KBC is ready
                               ;(bit 2 to 0)


        mov     al, 0xDF    ;Set the configuration bits to send.

            out     0x60, al   ;Send this parameter to the data port
                               ;of the KBC. At this point is where the
                               ;A20 line is enabled.
;;END:  enable A20
;;END:  enable A20
;;END:  enable A20














 ;;INIT: configuration of memory and register parameters
 ;;INIT: configuration of memory and register parameters
 ;;INIT: configuration of memory and register parameters
  ;registers that are modified in this INIT--END portion:
   ;AX (EAX) 0
   ;DS 0
   ;ES 0x800
   ;SS 0
   ;SP 0x800

     ;registers with values to reuse in the next INIT--END portion:
      ;apparently ALL of the previous ones

 ;>   ah = 0
 ;>   dl = drive we were booted from

        mov     al, SELDat32            ;Selector for 4Gb data seg. What we do here
                                        ;is to take the address of the second selector
                                        ;of the GDT.
                                        ;We are using the 8-bit register AL to hold the
                                        ;16-bit data selector number because AH is
                                        ;already set to 0, and with this we save 1 byte
                                        ;or so of boot space.


        mov     ds, ax                  ;{2} Extend limit for ds. Typically
                                        ;would get the value 0010h. (16)
        mov     es, ax                  ;Extend limit for es. ES would also
                                        ;be set to 16.

        dec     cx                      ;Switch back to real mode. From the start, ECX
                                        ;contained the value of CR0 with PE
                                        ;bit set. Here we disable the PE bit again,
                                        ;and we put it into CR0 in the next
                                        ;instruction.
   mov   CR0, ecx      ;{5}
        sti     ;v2012-05-31


  ;Here we are in Real Mode:
  ;Here we are in Real Mode:
  ;Here we are in Real Mode:
        mov     [_00h_jmp], dl         ;Save drive number we came from. Since
                                       ;we won't use again the first byte of code from
                                       ;the boot, the byte of *jmp 03Eh* instruction,
                                       ;we will use that byte as the variable space for
                                       ;the disk number. We must recognize that this is
                                       ;pretty clever.

        mov     sp, 0x800              ;{1B}. Configure the end of stack at 0x800

        xor     eax, eax                ;Segment. 32 bits set to 0
        mov     ds, ax                  ;DS (Data Segment) set to 0
        mov     ss, ax                  ;SS (Stack Segment) set to 0
        mov     es, sp                  ;Read directory at 800:0 {1C}. ES
                                        ;(Extra Segment) 0x800. Apparently we use
                                        ;ES:DS or ES:SS to access the disk.
                                        ;By now, the conflict that seems to be here
                                        ;with the sack doesn't matter much, since we
                                        ;won't save (much) data nor make calls to
                                        ;routines.
 ;;END:  configuration of memory and register parameters
 ;;END:  configuration of memory and register parameters
 ;;END:  configuration of memory and register parameters











 ;;INIT: Configure disk information
 ;;INIT: Configure disk information
 ;;INIT: Configure disk information
  ;>   eax = 00000000

  ;Here we are still in Real Mode since the previous INIT--END:
  ;Here we are still in Real Mode since the previous INIT--END:
  ;Here we are still in Real Mode since the previous INIT--END:
        mov     al, [_10h_numOfFATs]      ;Number of FATs
        mul     byte [_16h_numFATsectors] ;Times size of FAT |(in sectors?)
        add     ax, [_0Eh_resrvedSects]   ;Plus Sectors before first FAT
                 ;(_10h_numOfFATs*_16h_numFATsectors)+_0Eh_resrvedSects
               ;eax = LBN of Root directory

        movzx   edi,word [_11h_numRootDirEntries] ;Root directory entries
   push   di         ; used again later
   dec   di         ;Convert to number of sectors
                                          ;Then, this means that
                                          ;_11h_numRootDirEntries-1 is equals to
                                          ;the number of sectors. But exactly why?
                                          ;Maybe every sector represents an entry.

        shr     di, 4               ;16 directory entries per sector
   inc   di

        call    read_sectors    ;Call to ahead address
 ;;END:  Configure disk information
 ;;END:  Configure disk information
 ;;END:  Configure disk information












 ;;INIT: Search file name in the root directory
 ;;INIT: Search file name in the root directory
 ;;INIT: Search file name in the root directory
  ;>  eax  = LBN of root directory
  ;>  edi  = length of root directory in sectors
  ;>  [sp] = length of root directory in entries
  ;>  esi  = 00000000

  ;Here we are still in Real Mode since the past 2 INIT--END blocks:
  ;Here we are still in Real Mode since the past 2 INIT--END blocks:
  ;Here we are still in Real Mode since the past 2 INIT--END blocks:
   lea   ebp, [eax+edi]      ;ebp = LBN of cluster 2

        pop     bx                      ;Root directory entries. Recover the value
                                        ;of DI stored in BX
        xor     di, di               ;Point at directory {1C}
  .20:  mov     si, kernelFile       ;Name of file we want. We take the 16-bit
                                     ;address of the string.
   xor   ecx, ecx
        mov     cl, 11       ;Number of bytes to read (length of the string
                             ;"KERNEL  BIN")
        a32 rep cmpsb                   ;Found the file?. Here is where we use
                                        ;the 11 in CL
   je   found         ;Yes
   add   cl, 21         ;Offset to next directory entry
   add   edi, ecx      ;Advance to next entry
   dec   bx         ;Loop through all entries
        jnz     .20   ;According to the result of a bit in FLAGS, produced by
                      ;*dec bx*. Concretely the bit that indicates that the operation
                      ;has set the value of the operand down to 0.

   ;Couldn't find file in directory
  boot_error:
  disk_error:
   mov   ax, 0xE07      ;{3}
        int     10h          ;Video service. Here we will "ring" the
                             ;bell.
        jmp short $     ;Since this in an error, we create a loop that stops
                        ;the operation of the machine.
 ;;END:  Search file name in the root directory
 ;;END:  Search file name in the root directory
 ;;END:  Search file name in the root directory









 ;;INIT: Read the file according to what the previous INIT--END found
 ;;INIT: Read the file according to what the previous INIT--END found
 ;;INIT: Read the file according to what the previous INIT--END found
  ;>> ecx   = 00000000
  ;> es     = 800
  ;> es:edi = Directory entry of file
  ;> ebp    = LBN of cluster 2
  ;> eax    = 0000????

  found:  push    word [es:edi+0xF]        ;Starting cluster of file
          mov     di, [_16h_numFATsectors] ;Size of FAT (in sectors)
          mov     ax, [_0Eh_resrvedSects]  ;LBN of FAT
            call    read_sectors    ;Call to ahead address

   mov   bx, 0x4000
   mov   es, bx         ;es = 0x4000
   mov   edi, 0x100000-0x40000   ;{1D}{4B} One megabyte minus ES base
 ;;END:  Read the file according to what the previous INIT--END found
 ;;END:  Read the file according to what the previous INIT--END found
 ;;END:  Read the file according to what the previous INIT--END found


















 ;;INIT: Final processes, configuration of paging and pass control to the kernel
 ;;INIT: Final processes, configuration of paging and pass control to the kernel
 ;;INIT: Final processes, configuration of paging and pass control to the kernel
  .10:

  ;>>    ecx = 0000????
  ;>    [sp] = Next cluster of file
  ;>     esi = 0000????
  ;>>    edx = 0000????
  ;>  es:edi = Destination address
  ;>     ebp = LBN of cluster 2
  ;>      ds = 0

   xor   eax, eax
   pop   si         ;Next cluster of file
   mov   bx, si
   cmp   si, 0xFF8      ;Valid cluster?
   jae   eof         ;No: assume end of file
               ;Yes: (c-bit set)
   rcr   bx, 1         ;bx = 0x8000 + cluster/2
   mov   bx, [bx+si]      ;Get word containing FAT entry
   jnc   .11         ;Entry is low 12 bits
   shr   bx, 4         ;Entry was high 12 bits
  .11:  and     bh, 0xF                 ;Mask to just 12 bits
   push   bx         ;Save cluster after next
   push   di         ;Save destination address {7}
        mov     al, [_0Dh_sectsPerClus] ;Size of each cluster
   mov   di, ax         ;  (in sectors)
   dec   si
   dec   si
   mul   esi         ;Times cluster number minus 2
   add   eax, ebp      ;Plus LBN of cluster 2   
   call   read_sectors      ;Read that cluster

  ;>     ecx = 0000????
  ;>>    edx = 0000????
  ;>      di = Clustersize in sectors
  ;>     esi = 0
  ;>>    ebp = LBN of cluster 2
  ;>    [sp] = Bottom 16-bits of destination address {7}
  ;>> [sp+2] = Following cluster
  ;>      ds = 0
  ;>      es = 4000

   mov   cx, di         ;Cluster size in sectors
   xchg   ch, cl         ;Cluster size in words
   pop   di         ;Restore destination address {7}
   es a32 rep movsw
   jmp short .10         ;Loop until end of file



 ;;;INIT: paging
 ;;;INIT: paging
 ;;;INIT: paging
  ;>     eax = 0
  ;>     edx = 0000????
  ;>      bx = 0FF?

  eof:
   mov   dx, 0x9C00
   mov   es, dx         ;es = 9C00
   xor   di, di         ;{1E} Address of page tables WRT es
   mov   dh, 4096/256      ;edx = 4096
  .10:  mov     cx, 1024
        mov     al, 00000111b ;7
  .20:  stosd
   add   eax, edx
   int   8         ;{8}
   loop   .20
   shr   eax, 2         ;{4C} (first time only) 4Mb / 4 = 1Mb
   neg   bl         ;Done just one page?
   jns   .10         ;Yes: do one more

   cli            ;{6}

   mov   eax, 0x9C007      ;First page tbl pointer in page dir
   stosd            ;{1H}
   mov   ax, (1024-3)*2
   xchg   ax, cx
   rep stosw
   mov   ax, 0xD007      ;0FF800000 page tbl pointer
   stosd            ;{1F}
   mov   ah, 0xE0      ;Page directory self pointer
   stosd            ;{1G}
   mov   al, 0
   mov   CR3, eax      ;Set up page directory
   mov   eax, CR0      ;Turn on paging and protected mode
;;   or   eax, 0x80000001
   or   eax, 0x00000001    ;v2012-05-31
        cli     ;v2012-05-31
   mov   CR0, eax
        mov     cl, SELDat32            ;Setup ds and es
   push   cx         ;{5}
   pop   ds
   mov   es, cx
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;        jmp dword SELCod32:0xFF800000   ;Go
     a32   jmp dword SELCod32:0x100000   ;Go
 ;;;END:  paging
 ;;;END:  paging
 ;;;END:  paging


 ;;END:  Final processes, configuration of paging and pass control to the kernel
 ;;END:  Final processes, configuration of paging and pass control to the kernel
 ;;END:  Final processes, configuration of paging and pass control to the kernel






















 ;;INIT: read_sectors
 ;;INIT: read_sectors
 ;;INIT: read_sectors
  read_sectors:
  ; Input:
  ;       EAX = LBN
  ;       DI  = sector count
  ;       ES = segment
  ; Output:
  ;       EBX high half cleared
  ;       DL = drive #
  ;       EDX high half cleared
  ;       ESI = 0
  ; Clobbered:
  ;       BX, CX, DH

          push    eax
          push    di
          push    es

  .10:    push    eax             ;LBN

          cdq                     ;edx = 0
          movzx   ebx, byte [_18h_sectorsPerTrack]
          div     ebx             ;EAX=track ;EDX=sector-1
          mov     cx, dx          ;CL=sector-1 ;CH=0
          sub     bl, dl          ;BX = max transfer before end of track
          cmp     di, bx          ;Do we want more than that?
          ja      .20             ;Yes, do just this much now
          mov     bx, di          ;No, do it all now
  .20:    mov     esi, ebx        ;Save count for this transfer.

          inc     cx              ;CL=Sector number
          xor     dx, dx
          mov     bl, [_1Ah_numHeads]
          div     ebx             ;EAX=cylinder ;EDX=head

          mov     dh, dl          ;Head
          mov     dl, [_00h_jmp]  ;Drive number
          xchg    ch, al          ;CH=Low 8 bits of cylinder number; AL=0
          shr     ax, 2           ;AL[6:7]=High two bits of cylinder
          or      cl, al          ;CX = Cylinder and sector

          mov     ax, si          ;Sector count
          mov     ah, 2           ;Read
          xor     bx, bx
          push    ax
          int     13h
          pop     ax
          jnc     .30

          int     13h             ;If at second you don't succeed, give up
          jc near disk_error

  .30:    pop     eax
          add     eax, esi        ;Advance LBN

          push    si
          shl     si, 5
          mov     bx, es
          add     bx, si          ;Advance segment
          mov     es, bx
          pop     si

          sub     di, si
          ja      .10

          pop     es
          pop     di
          pop     eax
          xor     si, si
          ret
 ;;END:  read_sectors
 ;;END:  read_sectors
 ;;END:  read_sectors

























 kernelFile db 'KERNEL  BIN'





 ;INIT: GDT
 ;INIT: GDT
 ;INIT: GDT
    GDT:
   _SELNull equ 0   ;WARNING: This selector, besides being the pointer to the GDT
        GDT_size:   ;         is the null selector.
        dw GDTsize
        GDT_actualptr:
        dd GDT
       dw 0x0000

   _SELCod32 equ 8
     dw 0FFFFh       ; bits 0-15 length
     dw 00000h       ; bits 0-15 base address
     db 0            ; bits 16-23 base address
     db 10011010b    ; bits P,DPL,DT and type
     db 11001111b    ; bits G,D and bits 16-19 length
     db 0            ; bits 24-31 base address

   _SELDat32 equ 16  ;this is the "plain data selector"
     dw 0FFFFh       ; bits 0-15 length
     dw 00000h       ; bits 0-15 base address
     db 0            ; bits 16-23 base address
     db 10010010b    ; bits P,DPL,DT and type
     db 11001111b    ; bits G,D and bits 16-19 length
     db 0            ; bits 24-31 base address
   GDT_end:

   GDTsize equ (GDT_end-GDT)-1
 ;END:  GDT
 ;END:  GDT
 ;END:  GDT

  unused: times resbytes db 0x55

;END:  448 free bytes
;END:  448 free bytes
;END:  448 free bytes


_1F_55AA_signature      dw 0xAA55         ;2                        (512)



SELNull  equ 0
SELCod32 equ 8
SELDat32 equ 16



The next step now is to study the ISO file format/file system, analyze the existing image we produced, reconstruct it manually using pure assembly, and then produce documentation explaining how one can build a proper ISO image manually.

Finally, we could use it to load a kernel from the CD/DVD itself, or load a kernel choosing from floppy or FAT hard disks to begin with (and maybe also USB storage).

_________________
Live Development (click image links for full size):
PC 1: ImagePC 2: Image


Tue Jul 10, 2012 12:41 am
Profile
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 1 post ] 


Who is online

Users browsing this forum: No registered users and 1 guest


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
cron
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group.
Designed by Vjacheslav Trushkin and tweaked by the BF Team.