1/* 2 * PROJECT: FreeLoader 3 * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+) 4 * PURPOSE: BTRFS volume boot sector for FreeLoader 5 * COPYRIGHT: Copyright 2018 Victor Perevertkin (victor@perevertkin.ru) 6 */ 7 8#include <asm.inc> 9#include <freeldr/include/arch/pc/x86common.h> 10 11.code16 12 13CHUNK_MAP_OFFSET = HEX(8200) // max - 256 chunks (chunk is 24 bytes) 14DATA_BUFFER_SEGMENT = HEX(9a0) // here we will store FS structures read from disk 15 16FIRST_SUPERBLOCK_OFFSET = HEX(80) //in sectors 17 18ROOT_TREE_OBJECTID = 1 19EXTENT_TREE_OBJECTID = 2 20CHUNK_TREE_OBJECTID = 3 21DEV_TREE_OBJECTID = 4 22FS_TREE_OBJECTID = 5 23FIRST_CHUNK_TREE_OBJECTID = 256 24 25DIR_ITEM_KEY = 84 26EXTENT_DATA_KEY = 108 27ROOT_ITEM_KEY = 132 28EXTENT_ITEM_KEY = 168 29CHUNK_ITEM_KEY = 228 30 31BTRFS_FT_REG_FILE = 1 32 33KEY_SIZE = 17 34MAX_CHUNK_ITEMS = 255 35 36start: 37 jmp short main 38 nop 39// globals 40ChunkMapSize: 41 .byte 0 42BootDrive: 43 .byte 0 44PartitionStartLBA: 45 .quad HEX(3f) // default value. Setup tool have to overwrite it upon installation 46 47TreeRootAddress: 48 .quad 0 49ChunkRootAddress: 50 .quad 0 51FsRootAddress: 52 .quad 0 53 54NodeSize: 55 .long 0 56LeafSize: 57 .long 0 58StripeSize: 59 .long 0 60SysChunkSize: 61 .long 0 62 63TreeRootLevel: 64 .byte 0 65ChunkRootLevel: 66 .byte 0 67FsRootLevel: 68 .byte 0 69 70main: 71 xor eax, eax // Setup segment registers 72 mov ds, ax // Make DS correct 73 mov es, ax // Make ES correct 74 mov ss, ax // Make SS correct 75 mov sp, HEX(7c00) // Setup a stack 76 mov bp, sp 77 78 mov byte ptr [BootDrive], dl 79 80// Now check if this computer supports extended reads. This boot sector will not work without it 81CheckInt13hExtensions: 82 mov ah, HEX(41) // AH = 41h 83 mov bx, HEX(55aa) // BX = 55AAh 84 int HEX(13) // IBM/MS INT 13 Extensions - INSTALLATION CHECK 85 jc PrintDiskError // CF set on error (extensions not supported) 86 cmp bx, HEX(aa55) // BX = AA55h if installed 87 jne PrintDiskError 88 test cl, 1 // si = API subset support bitmap 89 jz PrintDiskError // Bit 0, extended disk access functions (AH=42h-44h,47h,48h) supported 90 91LoadExtraBootCode: 92 // First we have to load our extra boot code into into memory at [0000:7e00h] 93 xor edx, edx 94 mov eax, 1 // read from the second sector - the first was already read 95 mov cx, 2 96 mov bx, HEX(7e00) // Read sector to [0000:7e00h] 97 call ReadSectors 98 99 mov ax, DATA_BUFFER_SEGMENT 100 mov es, ax 101 102// reading superblock 103 xor edx, edx 104 mov eax, FIRST_SUPERBLOCK_OFFSET 105 mov cx, 8 // superblock is 0x1000 bytes - 8 sectors 106 xor bx, bx 107 call ReadSectors 108 109 push es // swapping segments for memory operations with superblock 110 push ds // ds must be DATA_BUFFER_SEGMENT 111 pop es 112 pop ds 113 114 mov si, HEX(40) 115 lea di, [btrfsSignature] 116 mov cx, 4 // magic string size (words) 117 repe cmpsw 118 je SignatureOk 119 120 mov si, wrongSignatureError 121 call PrintCustomError 122 123SignatureOk: 124// signature is ok - reading superblock data 125 add si, 8 // logical address of the root tree root 126 lea di, [TreeRootAddress] 127 mov cx, 8 // read both root tree root and chunk tree root 128 rep movsw 129// read sizes 130 add si, HEX(34) // now pointing to nodesize field 131 lea di, [NodeSize] // read all 4 values 132 mov cx, 8 133 rep movsw 134// read both levels 135 add si, 34 136 movsw // di is on the right place 137 138 push es 139 push ds 140 pop es 141 pop ds 142 143// read sys chunk array 144 mov ax, HEX(32b) // sys_chunk_start 145ReadSysChunk: 146 mov bx, ax 147 add bx, KEY_SIZE 148 push bx // start of the chunk 149 call InsertChunk 150 151 pop si 152 mov bx, word ptr es:[si+44] // number of stripes 153 shl bx, 5 // one stripe entry is 32 bytes 154 lea si, [si+bx+48] // 48 - size of the chunk 155 mov ax, si // save for next iteration 156 sub si, HEX(32b) 157 cmp si, word ptr [SysChunkSize] // the size cannot be more than 0xFFFF here so use word ptr 158 jb ReadSysChunk 159 160 jmp SetTreeRoots 161 162 163// Reads logical sectors from disk 164// INPUT: 165// - ES:[BX] address at which the data will be stored 166// - EDX:EAX logical sector number from which to read 167// - CX number of sectors to read 168// OUTPUT: 169// - 512*CX bytes of memory at ES:[BX] 170// LOCALS: 171// - [bp-2] - LBASectorsRead 172ReadSectors: 173 push bp 174 mov bp, sp 175 sub sp, 2 176 push es // we will spoil es register here 177 178 add eax, dword ptr [PartitionStartLBA] 179 adc edx, dword ptr [PartitionStartLBA+4] 180ReadSectors_loop: 181 pushad // Save logical sector number & sector count 182 183 cmp cx, 8 // Maximum sectors per call is 0x7F, but VirtualBox correctly works with only 8 184 jbe ReadSectorsSetupDiskAddressPacket // If we are reading less than 9 sectors then just do the read 185 mov cx, 8 // Otherwise read only 8 sectors on this loop iteration 186 187ReadSectorsSetupDiskAddressPacket: 188 mov word ptr [bp-2], cx 189 push edx 190 push eax // Put 64-bit logical block address on stack 191 push es // Put transfer segment on stack 192 push bx // Put transfer offset on stack 193 push cx // Set transfer count 194 push 16 // Set size of packet to 10h 195 mov si, sp // Setup disk address packet on stack 196 197 mov dl, byte ptr [BootDrive] // Drive number 198 mov ah, HEX(42) // Int 13h, AH = 42h - Extended Read 199 int HEX(13) // Call BIOS 200 jc PrintDiskError // If the read failed then abort 201 202 add sp, 16 // Remove disk address packet from stack 203 204 popad // Restore sector count & logical sector number 205 206 push bx 207 movzx ebx, word ptr [bp-2] 208 add eax, ebx // Increment sector to read 209 adc edx, 0 210 shl ebx, 5 // Multiplying by 512=2^9 here. 211 // Shifting only by 5, because it goes to segment 212 // (segment will be shifter by another 4 when converted to real addr) 213 mov si, es 214 add si, bx // Setup read buffer for next sector 215 mov es, si 216 pop bx 217 218 sub cx, word ptr [bp-2] 219 jnz ReadSectors_loop // Read next sector 220 221 pop es 222 leave 223 ret 224 225// Displays a disk error message 226// And reboots 227PrintDiskError: 228 mov si, msgDiskError // Bad boot disk message 229PrintCustomError: 230 call PutChars // Display it 231 232Reboot: 233 lea si, [msgAnyKey] // Press any key message 234 call PutChars // Display it 235 xor ax,ax 236 int HEX(16) // Wait for a keypress 237 int HEX(19) // Reboot 238 239PutChars: 240 lodsb 241 or al,al 242 jz short Done 243 call PutCharsCallBios 244 jmp short PutChars 245PutCharsCallBios: 246 mov ah, HEX(0e) 247 mov bx, HEX(07) 248 int HEX(10) 249 ret 250Done: 251 mov al, HEX(0d) 252 call PutCharsCallBios 253 mov al, HEX(0a) 254 call PutCharsCallBios 255 ret 256 257 258msgDiskError: 259 .asciz "Disk error" 260msgAnyKey: 261 .asciz "Press any key to restart" 262 263.org 510 264 .word HEX(aa55) // BootSector signature 265 266SetTreeRoots: 267 // converting sizes to sectors. We dont need this sizes in bytes 268 shr dword ptr [NodeSize], 9 269 shr dword ptr [LeafSize], 9 // leafsize is deprecated 270 271 // finding FS_TREE root 272 273 // put the key on stack 274 xor eax, eax 275 push eax 276 push eax 277 dec sp 278 mov byte ptr [esp], ROOT_ITEM_KEY 279 push eax 280 push 0 281 push FS_TREE_OBJECTID 282 283 mov eax, dword ptr [TreeRootAddress] 284 mov edx, dword ptr [TreeRootAddress+4] 285 mov cl, byte ptr [TreeRootLevel] 286 287 call SearchTree 288 add sp, 17 // setting stack back 289 290 // bx - pointer to ROOT_ITEM 291 mov al, byte ptr es:[bx+238] // moving level 292 mov byte ptr [FsRootLevel], al 293 cld 294 295 mov eax, dword ptr es:[bx+176] 296 mov dword ptr [FsRootAddress], eax 297 mov eax, dword ptr es:[bx+176+4] 298 mov dword ptr [FsRootAddress+4], eax 299 300 // now we need to find DIR_ITEM_KEY with freeldr.sys hash 301 xor eax, eax 302 push eax 303 push dword ptr [filenameCrc] 304 dec sp 305 mov byte ptr [esp], DIR_ITEM_KEY 306 push eax 307 push 0 308 push 256 // root dir objectid 309 310 mov eax, dword ptr [FsRootAddress] 311 mov edx, dword ptr [FsRootAddress+4] 312 mov cl, byte ptr [FsRootLevel] 313 314 call SearchTree 315 add sp, 17 // setting stack back 316 317 // parsing DIR_ITEM 318 // bx - item addr 319 // cx - item length 320 mov ax, cx 321 322 push ds 323 push es 324 pop ds 325 pop es 326ParseDirItem_loop: 327 cmp byte ptr [bx+29], BTRFS_FT_REG_FILE // checking dir_item type 328 jne ParseDirItem_loop_2 329 cmp word ptr [bx+27], 11 // checking name length 330 jne ParseDirItem_loop_2 331 lea si, [bx+30] 332 lea di, [freeldrFilename] 333 mov cx, 11 334 repe cmpsb 335 je FreeLdrFound 336 337ParseDirItem_loop_2: 338 mov dx, 30 339 add dx, word ptr [bx+27] 340 cmp dx, ax 341 jae FreeLdrNotFound 342 add bx, dx 343 jmp ParseDirItem_loop 344 345FreeLdrNotFound: 346 lea si, [notFoundError] 347 call PrintCustomError 348 349FreeLdrFound: 350 // freeldr objectid is the first qword of DIR_ITEM structure 351 xor eax, eax 352 push eax 353 push eax 354 dec sp 355 mov byte ptr [esp], EXTENT_DATA_KEY 356 push dword ptr [bx+4] 357 push dword ptr [bx] 358 359 push es 360 pop ds 361 362 mov eax, dword ptr [FsRootAddress] 363 mov edx, dword ptr [FsRootAddress+4] 364 mov cl, byte ptr [FsRootLevel] 365 366 call SearchTree 367 add sp, 17 368 369 // here we have an EXTENT_ITEM with freeldr.sys 370 mov eax, dword ptr es:[bx+29] 371 shr eax, 9 // getting the number of clusters 372 mov cx, ax 373 push cx 374 375 lea di, [bx+21] 376 call ConvertAddress 377 pop cx 378 379 xor bx, bx 380 mov ds, bx 381 mov es, bx 382 mov bx, FREELDR_BASE 383 call ReadSectors 384 385 mov dl, byte ptr [BootDrive] // Load boot drive into DL 386 //mov dh, 0 // Load boot partition into DH (not needed, FreeLbr detects it itself) 387 388 /* Transfer execution to the bootloader */ 389 ljmp16 0, FREELDR_BASE 390 391 392// Insert chunk into chunk map (located at DS:[CHUNK_MAP_OFFSET]) 393// INPUT: 394// - ES:[AX] chunk key address 395// - ES:[BX] chunk item address 396// TODO: add max items checking 397InsertChunk: 398 push bx 399 push ax 400 push es 401 402 xor ecx, ecx // index 403InsertChunk_loop: 404 std // numbers are little-engian, going right-to-left 405 mov si, CHUNK_MAP_OFFSET 406 lea si, [esi+ecx*8] 407 lea si, [esi+ecx*8] 408 lea si, [esi+ecx*8] // shift by 24 bytes 409 410 inc cx 411 cmp cl, byte ptr [ChunkMapSize] 412 ja InsertChunk_insert 413 414 lea si, [si+4] // set to the high dword of the 8-byte number 415 lea di, [eax+KEY_SIZE-4] // set to high dword of key's offset field (offset=logical addr) 416 417 cmpsd 418 jb InsertChunk_loop 419 cmpsd 420 jb InsertChunk_loop 421 lea si, [si+4] // set back to the beginning of key 422 423 // numbers are greater - need to insert Here 424 // shifting all to right by one element 425InsertChunk_insert: 426 push si // here we will store new chunk 427 dec cx // because we increased it before comparison 428 429 mov dx, ds 430 mov es, dx 431 432 movzx eax, byte ptr [ChunkMapSize] // number of elements 433 434 mov si, CHUNK_MAP_OFFSET 435 lea si, [esi+eax*8] 436 lea si, [esi+eax*8] 437 lea si, [esi+eax*8-4] // setting to the high dword of the last element 438 439 mov di, si 440 add di, 20 // last byte of the last + 1 element (-4 bytes) 441 442 sub ax, cx 443 mov bx, 6 444 mul bx // 24/4 because of dwords 445 mov cx, ax // number of elements to shift 446 rep movsd 447 448 // finally we can write the element 449 cld 450 pop di // here we will store new chunk 451 452 pop ds // key-to-insert address segment 453 pop si // key-to-insert address 454 add si, 9 // move to 'offset' field 455 movsd 456 movsd // logical address loaded! 457 458 // time for stripes 459 pop si // chunk item address, length is the first field 460 movsd 461 movsd 462 463 add si, 48 // move to offset of the first stripe (we read only first one) 464 movsd 465 movsd 466 467 push es // swapping segments back to original 468 push ds 469 pop es 470 pop ds 471 472 inc byte ptr [ChunkMapSize] 473 ret 474 475 476// Searches a key in a BTRFS tree 477// INPUT: 478// - [bp+4] BTRFS key to find 479// - EDX:EAX logical address of root header 480// - CL leaf node level 481// OUTPUT: 482// - ES:[SI] pointer to found item's key 483// - ES:[BX] pointer to found item 484// - ECX item length 485// LOCALS: 486// - [bp-8] - block number/current node offset 487// - [bp-9] - current level 488SearchTree: 489 push bp 490 mov bp, sp 491 sub sp, 9 // set stack frame 492 493 mov dword ptr [bp-4], edx 494 mov dword ptr [bp-8], eax 495 mov byte ptr [bp-9], cl 496 497SearchTree_readHeader: 498 push ss 499 pop es 500 501 lea di, [bp-8] 502 call ConvertAddress 503 // LBA is in edx:eax now 504 mov bx, DATA_BUFFER_SEGMENT 505 mov es, bx 506 xor bx, bx 507 508 mov cl, byte ptr [bp-9] 509 test cl, cl 510 jz SearchTree_readLeaf 511// read node 512 mov cx, word ptr [NodeSize] // word btr because we cannot read more than 65536 bytes yet 513 call ReadSectors // reading node to the memory 514 515 // every node begins with header 516 //mov ax, word ptr [DATA_BUFFER_OFFSET + HEX(60)] // number of items in this leaf 517 mov cx, -1 // index 518 // finding the key 519SearchTree_findLoop_1: 520 inc cx 521 cmp word ptr es:[HEX(60)], cx 522 je SearchTree_findLoop_1_equal // we are at the end - taking the last element 523 524 lea si, [bp+4] // key to find 525 mov di, HEX(65) // first key in leaf 526 mov ax, 33 527 call CompareKeys 528 jb SearchTree_findLoop_1 529 je SearchTree_findLoop_1_equal 530 sub di, 33 // setting to previous element 531 532 // we are here if key is equal or greater 533 // (si points to the start of the key) 534SearchTree_findLoop_1_equal: 535 push ds 536 push es 537 pop ds 538 pop es 539 540 lea si, [di+17] 541 lea di, [bp-8] 542 movsd 543 movsd 544 545 push es 546 pop ds 547 548 dec byte ptr [bp-9] // decrement level 549 jmp SearchTree_readHeader 550 551SearchTree_readLeaf: 552 mov cx, word ptr [LeafSize] 553 call ReadSectors 554 555 // every node begins with header 556 //mov ax, word ptr [DATA_BUFFER_OFFSET + HEX(60)] // number of items in this leaf 557 mov cx, -1 // index 558 // finding the key 559SearchTree_findLoop_2: 560 inc cx 561 cmp word ptr es:[HEX(60)], cx 562 je SearchTree_foundEqual 563 564 lea si, [bp+4] // key to find 565 mov di, HEX(65) // first key in leaf 566 mov ax, 25 567 call CompareKeys 568 jb SearchTree_findLoop_2 569 je SearchTree_foundEqual 570 571 // set pointer to previous element if greater 572 sub di, 25 573 574SearchTree_foundEqual: 575 // found equal or greater key 576 mov bx, word ptr es:[di+17] // data offset relative to end of header 577 mov cx, word ptr es:[di+21] // data size 578 add bx, HEX(65) // end of header 579 mov si, di 580 581 leave 582 ret 583 584SearchTree_notFound: 585 xor ecx, ecx // return ecx=0 if nothing found 586 587 leave 588 ret 589 590 591// Converts logical address into physical LBA addr using chunk map 592// INPUT: 593// - ES:[DI] pointer to logical addr 594// OUTPUT: 595// - EDX:EAX target LBA 596// - sets ZF on failure 597ConvertAddress: 598 // NOTE: SearchTree will overwrite data buffer area and our logical addr will be erased! 599 // so putting it on stack 600 push dword ptr es:[di+4] 601 push dword ptr es:[di] 602 603 mov bl, 1 // indicates first try. On second try BL must be set to 0 604ConvertAddress_secondTry: 605 push ds 606 pop es 607 xor ecx, ecx // set index to 0 608ConvertAddress_loop: 609 cmp cl, byte ptr [ChunkMapSize] 610 jae ConvertAddress_checkInclusion // greater chunk is not found in chunk map - checking the last one 611 612 std // numbers are little-engian, going right-to-left 613 mov si, CHUNK_MAP_OFFSET 614 lea si, [esi+ecx*8] 615 lea si, [esi+ecx*8] 616 lea si, [esi+ecx*8] // shift by 24 bytes 617 618 lea di, [esp+4] // set to the second dword the 8-byte number 619 lea si, [si+4] 620 inc cl 621 622 cmpsd 623 jb ConvertAddress_loop 624 cmpsd 625 jb ConvertAddress_loop 626 627ConvertAddress_checkInclusion: 628 dec cl 629 cld 630 // found chunk map item, checking inclusion with length 631 mov si, CHUNK_MAP_OFFSET 632 lea si, [esi+ecx*8] 633 lea si, [esi+ecx*8] 634 lea si, [esi+ecx*8] // shift by 24 bytes 635 636 // logical_addr + length 637 mov eax, dword ptr [si] // low dword of address 638 mov edx, dword ptr [si+4] // high dword of address 639 add eax, dword ptr [si+8] // low dword of length 640 adc edx, dword ptr [si+12] // high dword of length 641 // edx:eax is the end of the chunk 642 643 // (logical_addr + length) - addr_to_find 644 cmp edx, dword ptr [esp+4] 645 ja ConvertAddress_found 646 cmp eax, dword ptr [esp] 647 ja ConvertAddress_found // address is greater than end of the chunk 648 test bl, bl 649 jnz ConvertAddress_notFound 650 ret 8 651 652ConvertAddress_found: 653 // found chunk. Calculating the address 654 // addr_to_find - logical_addr 655 pop eax // low dword of addr_to_find 656 pop edx // high dword of addr_to_find 657 sub eax, dword ptr [si] 658 sbb edx, dword ptr [si+4] 659 // (addr_to_find - logical_addr) + physical_addr 660 add eax, dword ptr [si+16] 661 adc edx, dword ptr [si+20] 662 663 // edx:eax is physical address. Converting to LBA (shifting by 9 bits) 664 shrd eax, edx, 9 665 shr edx, 9 666 inc bl // clears ZF (bl is 0 or 1 here) 667 ret 668 669ConvertAddress_notFound: 670 // offset is alredy on stack 671 //push dword ptr [esp+4] 672 //push dword ptr [esp] 673 dec sp 674 mov byte ptr [esp], CHUNK_ITEM_KEY 675 data32 push 0 676 push 0 677 push FIRST_CHUNK_TREE_OBJECTID 678 679 mov eax, dword ptr [ChunkRootAddress] 680 mov edx, dword ptr [ChunkRootAddress+4] 681 mov cl, byte ptr [ChunkRootLevel] 682 683 call SearchTree 684 add sp, 9 // setting stack back 685 686 mov ax, si // ES:[SI] - found key pointer 687 call InsertChunk 688 689 mov bl, 0 // indicates second try 690 jmp ConvertAddress_secondTry 691 692 693// Compare key (key1) with key in array identified by base and index (key2) 694// INPUT: 695// - DS:[SI] key1 pointer 696// - ES:[DI] start of the array 697// - AX size of one key in array 698// - CX key index 699// OUTPUT: 700// - DS:[SI] key1 pointer 701// - ES:[DI] key2 pointer 702// - sets flags according to comparison 703CompareKeys: 704 //xchg si, di 705 706 mul cx 707 add di, ax 708 push ds 709 push si 710 push es 711 push di 712 713 // must be replaced for proper flags after cmps 714 push ds 715 push es 716 pop ds 717 pop es 718 xchg si, di 719 720 lea si, [si+4] // key in array 721 lea di, [di+4] // searchable key 722 std 723 724 // comparing objectid 725 cmpsd 726 jne CompareKeys_end 727 cmpsd 728 jne CompareKeys_end 729 // comparing type 730 lea si, [si+12] 731 lea di, [di+12] 732 733 cmpsb 734 jne CompareKeys_end 735 // comparing offset 736 lea si, [si+1+1+4] 737 lea di, [di+1+1+4] 738 739 cmpsd 740 jne CompareKeys_end 741 cmpsd 742CompareKeys_end: 743 cld 744 pop di 745 pop es 746 pop si 747 pop ds 748 ret 749 750 751 752btrfsSignature: 753 .ascii "_BHRfS_M" 754 755//.org 1022 756// .word HEX(aa55) 757 758 759wrongSignatureError: 760 .asciz "BTRFS read error" 761MaxItemsError: 762 .asciz "Max items error" 763filenameCrc: 764 .long HEX(68cba33d) // computed hashsum of "freeldr.sys" 765freeldrFilename: 766 .ascii "freeldr.sys" 767notFoundError: 768 .asciz "NFE" 769.org 1534 770 .word HEX(aa55) 771.endcode16 772 773END 774