Boot Loader для флешки на FAT32 (результат)

В марте я занялся серией постов о разработке загрузчика для флешки. Было проделано много предварительной работы.

Пришла пора написать, наконец, код. Когда приступил к написанию в понедельник, столкнулся с проблемой долгого отсутствия опыта в ассемблере и с одной элементарной проблемой боролся два вечера. Точнее две ночи. Вчера в два ночи проблему таки поборол. Надобавлял в код отладки и … сидел перед бегущими значениями регистров… Помогло… Сегодня вечером дописал. Так что продолжение ниже.

Ядро загружается по адресу 0x10000. Я использовал такой адрес просто потому, что уже загружал ядро по этому адресу в загрузчике с FAT12.
Нижеприведенный boot-loader загружает ядро из файла ‘KERNEL’ из корневого директория FAT32 флешки.

section .text
	use16
	org  0x7C00       ; Boot Loader starts at 0:0x7c00
start: 
  jmp boot_code
  db 0x90
BS_OEMName:
  times 8 db ' '  
BPB_BytsPerSec:     ; 0x0b Bytes per sector 512,1024,2048 or 4096
  dw 0x200
BPB_SecPerClus:     ; 0x0d Sectors per Cluster
  db 0
BPB_RsvdSecCnt:     ; 0x0e Reserved sectors count
  dw 0
BPB_NumFATs:       ; 0x10 FATs number
  db 0
BPB_RootEntCnt:      ; 0x11 (not used for FAT32)
	dw 0
BPB_TotSec16:        ; 0x13 (not used for FAT32)
	dw 0
BPB_Media:           ; 0x15 (not used)
	db 0
BPB_FATSz16:        ; 0x16 (not used for FAT32)
	dw 0
BPB_SecPerTrk:       ; 0x18 (SPT geometry for CHS)
  dw 0
BPB_NumHeads:       ; 0x1a (HPC geometry for CHS)
  dw 0
BPB_HiddSec:         ; 0x1c (Sectors before this partition)
  dd 0
BPB_TotSec32:        ; 0x20 (Total sectors must be !=0 for FAT32)
	dd 0
BPB_FATSz32:        ; 0x24 Sectors count in one FAT
  dd 0
BPB_ExtFlags:         ; 0x28 See Microsoft document
  dw 0
BPB_FSVer:           ; 0x2a Version of FAT32 filesystem
  dw 0
BPB_RootClus:         ; 0x2c Root Directory cluster number
  dd 0
BPB_FSInfo:           ; 0x30 Reserved = 1 usially see doc...
  dw 0
BPB_BkBootSec:       ; 0x32 Sector in reserve area with Boot Sect Copy
  dw 0
BPB_Reserved: 
  times 12 db 0
BS_DrvNum:           ; 0x40 Drive number for int 0x13
  db 0
BS_Reserved1:         ; 0x41
  db 0
BS_BootSig:            ; 0x42 0x29 means that next fields present
  db 0
BS_VolID:              ; 0x43 Volume ID 32 bit value
  dd 0
BS_VolLab:             ; 0x47
  times 11 db ' '
BS_FilSysType:         ; 0x52 "FAT32   "  
  db 'FAT32   '

; parameter block for read sector
read_sector_data:       ; 0x5a
  dw 0x10
sectors_count:
  dw 0x01
read_offset:
  dw 0x00
read_segment:
  dw 0x80
lba_lo:
  dw 0x00
lba_hi:
  dw 0x00
lba_hihi:
  dw 0x00
  dw 0x00

; kernel file name
kernel:
  db 'KERNEL     '

boot_code:             ; 0x5a boot code start
  cli
  xor ax, ax
  mov ss, ax
  mov sp, start        ; set stack pointer and segments
  mov bp,sp
  mov ds, ax
  mov es, ax
  sub sp,0x80         ; reserve 0x80 byte for local variables
  sti
; get cluster chain starting from specified
  push dx				 ; [ bp - 82 ] BIOS disk number
get_first_data_sector:
  mov eax, [ bp + BPB_FATSz32 - start ]
  movzx ecx, byte [ bp + BPB_NumFATs - start ]
  mul ecx
  movzx ecx, word [ bp + BPB_RsvdSecCnt - start ]
  add ecx, [ bp + BPB_HiddSec - start ]
  push ecx            ; [ bp - 86 ] reserved + hidden
  add eax, ecx
; here 0x2fc0
  push eax            ; [ bp - 8a ] first data sector
calc_sector_size:
	movzx ax, byte [ bp + BPB_SecPerClus - start ]
	push ax             ; [ bp - 0x8c ]  sectors per cluster
	mul word [ bp + BPB_BytsPerSec - start ] 
	shr ax, 4
; here 0x400
	push ax             ; [ bp - 0x8e ] segment increment for cluster
;	mov bx,ax
;	shr eax,16
;	call print_regs
;	jmp $
  mov bx, search_kernel_file
  call read_cluster_chain
  jc error
; we have the kernel here
; initialize segment where to write kernel data
; kernel will be loaded to position 0:0x10000
; (just after the bool sector)
  mov word [ bp + read_segment - start ], 0x1000 ; kernel start segment
  mov al, [ bp + BPB_SecPerClus - start ] 
  mov [ bp + sectors_count - start ], al ; number of sectors to read
  ; only one iteration is required ( reading whole cluster by one operation)
  mov byte [ bp - 0x8c ], 0x1 ; number of iterations in read_cluster_chain
  mov bx, read_kernel_file
read_cluster_chain:
   mov eax, [ bp + BPB_RootClus - start ]   ; current cluster to read
   cmp eax, 0xffffff7
   jl rcc_continue
;;;;
;;;; kernel is loaded if bx == read_kernel_file   
   cmp bx, read_kernel_file
   jne error
;   mov ax, 0xCAFE
;   call print_regs
; switch to protected mode
; and jump to kernel

 lgdt  [ gdtr ]
  
  in al, 0x92
  or al, 2
  out 0x92, al

  mov eax, cr0 
  or al, 1	
  mov cr0, eax 
	
  jmp 0x8: _protected  

  use32
_protected:
  ; jump to kernel
  jmp 0x8:0x10000

  use16

   rcc_continue:
	call get_sector_by_cluster
	; number of iterations
	; for reading sectors one by one
	movzx cx, byte [ bp - 0x8c ]     ; sectors to read
   
rcc_read_next:
	call read_sector
; registrers don't restored if error is happen
; we don't need them just show error message
; sorry saving 1 byte 🙂

	pusha
		call bx            ; process sector data
	popa
	jc rcc_to_next_sector
; processing function finished it's work
; actually it happens only for search directory
; this return can be changed later
	ret

error:
  int 0x18
	
rcc_to_next_sector:
	inc eax
	loop rcc_read_next
	
; get next data cluster
; calculate  FAT sector to read
; read FAT
; get cluster from FAT
	mov eax, [ bp + BPB_RootClus - start ]
	shl eax, 2   ; cluster << 2
	mov dx, ax ; lo word
	shr eax, 16 ; hi word
	xchg ax, dx ; lo <-> hi
	mov cx, word [ bp + BPB_BytsPerSec - start ]
	div cx
	add eax,  [ bp - 0x86 ]  ; reserved + hidden
	push word [ bp + sectors_count - start ] ; sectors to read
   push word [ bp + read_segment - start ] ; where to read
   mov word [ bp + read_segment - start ] , 0x80
   mov byte [ bp + sectors_count - start ], 0x1
   call read_sector
   pop word [ bp + read_segment - start ]
   pop word [ bp + sectors_count - start ]
   mov si , dx
   mov eax, [ si + 0x800 ]
   and eax, 0xfffffff
   mov [ bp + BPB_RootClus - start ], eax
   jmp read_cluster_chain
  
; read sector
; input: read_sector_data area
; output: carry flag (if error happen)
read_sector:                             ; read sector lba mode
  pusha
	  mov [ bp + lba_lo - start ] , eax      ; pass LBA address
		mov si, read_sector_data              ; function parameters
		mov ah, 0x42                         ; function number
		mov dl, 0x80                          ; disk number
		int 0x13
	popa
		jc error     ; error happend	
	ret
  
; search file in directory
search_kernel_file:
	mov di, 0x800 ; [ bp + read_offset - start ]
skf_compare_name:
	mov si, kernel
	mov cx, 11
	push di
	repe cmpsb
	pop di
	jne skf_continue
	mov cx, [ di + 26 ]
	mov [ bp + BPB_RootClus - start ], cx
	mov cx, [ di + 20 ]
	mov [ bp + BPB_RootClus + 2 - start ], cx
	clc
	ret
skf_continue:
  add di, 32
  cmp di, 0xa00
  jl skf_compare_name
  stc
  ret
; calculate sector number by cluster number
; input:   eax - cluster number
; output: eax - cluster number
get_sector_by_cluster:
  dec eax
  dec eax
  movzx ecx, byte [ bp + BPB_SecPerClus - start ]  
  mul ecx
  add eax, [ bp - 0x8a ]                ; first data sector
  ret

; This function is called each time when kernel file cluster 
; has been read. This function just need to increment the
; memory position (where to read next cluster)
read_kernel_file:
  mov ax, [ bp + read_segment - start ]
  add ax, [ bp - 0x8e ]  ; [ bp - 0x8e ] segment increment for cluster
  mov [ bp + read_segment - start ], ax
  stc
  ret

  ; alignment
  times 3 db 0x90
  
  gdt:                    ; Address for the GDT
gdt_null:               ; Null Segment
  dd 0
  dd 0
	
gdt_code:               ; Code segment, read/execute, nonconforming
  dw 0FFFFh
  dw 0
  db 0
  db 0x9a
  db 0xcf
  db 0

gdt_data:               ; Data segment, read/write, expand down
  dw 0FFFFh
  dw 0
  db 0
  db 0x92
  db 0xcf
  db 0
  
gdt_code16:
  dw 0FFFFh
  dw 0
  db 0
  db 0x9e
  db 0
  db 0

gdt_data16:
  dw 0FFFFh
  dw 0
  db 0
  db 0x92
  db 0
  db 0	  
		
gdtr:
  dw gdtr-gdt-1
  dd gdt
 
finish:
  times 0x1FE-finish+start db 0
  db   0x55, 0xAA         ; Boot Sector signature	

Протестировался… Работает… Позволю себе совет, тем, кто берётся за такую штуку как загрузчик. Не пишите сразу много кода. Напишите какой нибудь отладочный фрагмент. Например вывод значений регистров (одного регистра) и вставляйте его везде, где можно что-то проверить. Пишите по несколько строк и постоянно тестируйтесь. Я потратил два вечера на отладку элементарной проблемы. Сегодня, написал весь загрузочный сектор часа за 2,5 — 3. Всего сделал 30 последовательных версий. Каждую предыдущую тестировал и продолжал писать от достигнутого. В итоге один вечер и оно работает.

Я использую режим LBA для чтения. Алгоритм использован уже отлаженный на C. Т.е. у меня есть код, читающий цепочку кластеров. В регистре bx я передаю адрес функции, которая вызывается для обработки прочитанного сектора (или секторов). Алгоритм вкратце такой:

  • Сначала вычисляются ряд параметров и сохраняются в стеке, чтобы несколько раз не считать.
  • Потом читается цепочка кластеров Root Директория. Для прочитанных кластеров вызывается функция поиска ядра в оглавлении
  • Потом та же функция чтения цепочки кластеров вызывается для чтения файла ядра, начиная с кластера полученного из элемента оглавления.
  • Когда достигается конец цепочки (внутри FAT значение кластера > 0xffffff7), ядро считано.
  • Производится переключение в защищённый режим и переход на код ядра уже в защищённом режиме.

О сборке ядра я уже писал ранее. Все ссылки завтра… Сегодня поздно уже…

P.S.
Все ссылки смотрите здесь: Обработка прерываний в ядре ОС на базе IA32

P.P.S.
Позже, я обнаружил, что в инсталляции Операционной Системы Kolibri, большой комплект разных вариантов загрузчиков разработан Евгением Гречниковым (aka Diamond). См. форум http://board.kolibrios.org, а также статью здесь в блоге: Загрузка Kolibri OS с флешки

6 thoughts on “Boot Loader для флешки на FAT32 (результат)

  1. Очень интересно, столько работы а готовый образ MBR который грузит ядро из файла kernel есть? Готовое решение есть?
    Столкнулся что на старых ноутбуках и компах bios глючной и не понимает флешку если она не в zip стандарте, для этого нужно протестировать биос и если он не поддерживает lba эмулировать chs, так вот только один нормально работающий такой MBR нашёл но он грузит только syslinux 3.71 а хотелось бы иметь выбор что грузить. В программировании ноль так что сам исправить MBR не смогу на то чтоб грузил то что хочется. Если есть возможность использовать ваше решение, был бы признателен.

  2. В других статьях есть примеры готовых MBR загрузчиков в CHS режиме и в LBA режиме:

    В мои планы не входило делать бинарные инсталляции. Я не ставил целью сделать поддержку всех версий BIOS, проверку работоспособности везде и т.п. Всмысле загрузчики рабочие, но их нужно компилировать, инсталлировать и т.д. Всё описано и выложено, но в исходниках. Моей целью было продемонстрировать внутреннее устройство и сам процесс разработки.

    Вам, судя по-всему, нужен готовый продукт. Чтобы ни о чем не думать с инсталлятором и т.д.

    Поищите здесь: готовый загрузчик с флешек, автор Сергей Гончаров aka Yoda: http://www.osdev.ru/viewtopic.php?f=4&t=485

    Или MBR-загрузчик Alter.
    http://forum.osdev.org/viewtopic.php?f=2&t=23227&start=0

  3. Спасибо за ответ обязательно по ссылкам посмотрю, но думаю что там нет того что мне надо. Мне не нужен полностью готовый вариант с инсталляторами и т.п. могу и ручками MBR загрузить вот только компилировать а точней дорабатывать код не смогу. Дело в том что буквально все даже GRUB и SYSLINUX и PLOP и прямой FreeDOS не грузятся именно с USB флешки когда BIOS кривой и не поддерживает на USB LBA адресацию. Вообще очень часто реализация USB в BIOS очень кривая потому как сам интерфейс появился уже не в эпоху DOS а в эпоху форточек, и вся поддержка только через дрова в мастдае.
    Я только один MBR нашел который проверяет на наличие поддержки в BIOS LBA и если его нет эмулирует CHS, но он как то странно подгружает ядро syslinux. По идее должен грузить любое ядро записанное в 1 сектор первого партишена с тем же именем , но нет он грузит исключительно версию 3.71 . А она к сожалению глюковатая и не имеет многих более поздних доработок. У меня вообщем то просто не чисто теоретический вопрос или изыскание как вышло у вас, если честно никогда не видел смысла в чистой теории всегда стремлюсь применить в жизни иначе зачем оно? Я собрал пару лет назад флеш на freedos для восстановления и т.п. Но друзья которым я ее дал все время жаловались что и загрузка не 10 сек как у меня и вообще бывает не грузиться, вот дошли руки решил разобраться и понял что виной кривые BIOS, а решение — эмуляция, вот и стоит задача подружить приличный загрузчик ака syslinux или GRUB с эмуляцией CHS для кривых биосов. К моему удивлению такие монстры и не имеют эмуляции и не грузятся на многих компах особенно дешевых мамках ))) В инете полно можно встретить описаний что то проверенный образ Linux то еще какой именитый образ не грузиться все из за этой проблемы USB. Если писать на CD то все как правило ок, там нет такого разнообразия геометрий диска, там стандарт, а на USB черт знает что.

  4. MBR Alter — на сколько я понял обычный загрузчик только с возможностью загрузки по горячей клавиши другого партишена. Не понятно зачем это надо , для скрытой системы и загрузки с нее? Чтобы по горячей клавише загружалась система скрытая от общих глаз? Вобщем про эмуляцию и трансляцию из LBA в CHS ни слова.
    А вот набор от Сергея Гончарова очень интересный, и если там нет еще эмуляции я ему намекну что там ей самое место )))

  5. Судя по вашему описанию, у вас есть работающий загрузчик, грузящий версию SYSLINUX 3.71. Другие версии он может не грузить по нескольким причинам. Загрузчик состоит из MBR части и собственно Boot Loader в начале загружаемого раздела. Оба этих компонента должны поддерживать работу в CHS режиме. С версией 3.71 у вас с этим все в порядке. Но есть еще один возможный источник проблем. Собственно само ядро операционной системы. Оно после загрузки само начинает обращаться к файловым системам. Версия 3.71 содержит необходимые драйверы, в более поздних версиях ядра может просто не оказаться нужных драйверов. Кроме того, у более поздних версий может быть ещё одна прозаическая проблема — размер загружаемого ядра. Загрузчик 2-ой стадии может к примеру поддерживать CHS, но не поддерживать больших файлов. Поскольку работает в реальном режиме. Т.е. возможных проблем много.

  6. Мне удалось загрузить на моем ноуте с багом в BIOS (не поддерживает нормально LBA) при помощи MBR Сергея Гончарова GRUB4DOS последней версии по цепочке
    MBR Сергея Гончарова -> MBR GRUB -> ядро GRLDR
    и после grub получил нормальную геометрию диска и нормально читал и мог грузить что угодно. Сам grub при помощи своего MBR не грузиться на бажном ноуте, только на нормальном ноуте грузиться без проблем. Вот только я все время меняю разбивку флешки и уже не помню как именно она была разбита а на FAT32 4k cluster что то MBR Сергея Гончарова не грузится, вобщем у него хорошая вещь но еще не доработана, не с любой FAT32 будет работать.
    Спасибо вам за ссылки и ответы глядишь общими усилиями и будет хорошее решения и не только для прямых BIOS ))

Оставьте комментарий