/* * PROJECT: FreeLoader * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+) * PURPOSE: BTRFS volume boot sector for FreeLoader * COPYRIGHT: Copyright 2018 Victor Perevertkin (victor@perevertkin.ru) */ #include #include .code16 CHUNK_MAP_OFFSET = HEX(8200) // max - 256 chunks (chunk is 24 bytes) DATA_BUFFER_SEGMENT = HEX(9a0) // here we will store FS structures read from disk FIRST_SUPERBLOCK_OFFSET = HEX(80) //in sectors ROOT_TREE_OBJECTID = 1 EXTENT_TREE_OBJECTID = 2 CHUNK_TREE_OBJECTID = 3 DEV_TREE_OBJECTID = 4 FS_TREE_OBJECTID = 5 FIRST_CHUNK_TREE_OBJECTID = 256 DIR_ITEM_KEY = 84 EXTENT_DATA_KEY = 108 ROOT_ITEM_KEY = 132 EXTENT_ITEM_KEY = 168 CHUNK_ITEM_KEY = 228 BTRFS_FT_REG_FILE = 1 KEY_SIZE = 17 MAX_CHUNK_ITEMS = 255 start: jmp short main nop // globals ChunkMapSize: .byte 0 BootDrive: .byte 0 PartitionStartLBA: .quad HEX(3f) // default value. Setup tool have to overwrite it upon installation TreeRootAddress: .quad 0 ChunkRootAddress: .quad 0 FsRootAddress: .quad 0 NodeSize: .long 0 LeafSize: .long 0 StripeSize: .long 0 SysChunkSize: .long 0 TreeRootLevel: .byte 0 ChunkRootLevel: .byte 0 FsRootLevel: .byte 0 main: xor eax, eax // Setup segment registers mov ds, ax // Make DS correct mov es, ax // Make ES correct mov ss, ax // Make SS correct mov sp, HEX(7c00) // Setup a stack mov bp, sp mov byte ptr [BootDrive], dl // Now check if this computer supports extended reads. This boot sector will not work without it CheckInt13hExtensions: mov ah, HEX(41) // AH = 41h mov bx, HEX(55aa) // BX = 55AAh int HEX(13) // IBM/MS INT 13 Extensions - INSTALLATION CHECK jc PrintDiskError // CF set on error (extensions not supported) cmp bx, HEX(aa55) // BX = AA55h if installed jne PrintDiskError test cl, 1 // si = API subset support bitmap jz PrintDiskError // Bit 0, extended disk access functions (AH=42h-44h,47h,48h) supported LoadExtraBootCode: // First we have to load our extra boot code into into memory at [0000:7e00h] xor edx, edx mov eax, 1 // read from the second sector - the first was already read mov cx, 2 mov bx, HEX(7e00) // Read sector to [0000:7e00h] call ReadSectors mov ax, DATA_BUFFER_SEGMENT mov es, ax // reading superblock xor edx, edx mov eax, FIRST_SUPERBLOCK_OFFSET mov cx, 8 // superblock is 0x1000 bytes - 8 sectors xor bx, bx call ReadSectors push es // swapping segments for memory operations with superblock push ds // ds must be DATA_BUFFER_SEGMENT pop es pop ds mov si, HEX(40) lea di, [btrfsSignature] mov cx, 4 // magic string size (words) repe cmpsw je SignatureOk mov si, wrongSignatureError call PrintCustomError SignatureOk: // signature is ok - reading superblock data add si, 8 // logical address of the root tree root lea di, [TreeRootAddress] mov cx, 8 // read both root tree root and chunk tree root rep movsw // read sizes add si, HEX(34) // now pointing to nodesize field lea di, [NodeSize] // read all 4 values mov cx, 8 rep movsw // read both levels add si, 34 movsw // di is on the right place push es push ds pop es pop ds // read sys chunk array mov ax, HEX(32b) // sys_chunk_start ReadSysChunk: mov bx, ax add bx, KEY_SIZE push bx // start of the chunk call InsertChunk pop si mov bx, word ptr es:[si+44] // number of stripes shl bx, 5 // one stripe entry is 32 bytes lea si, [si+bx+48] // 48 - size of the chunk mov ax, si // save for next iteration sub si, HEX(32b) cmp si, word ptr [SysChunkSize] // the size cannot be more than 0xFFFF here so use word ptr jb ReadSysChunk jmp SetTreeRoots // Reads logical sectors from disk // INPUT: // - ES:[BX] address at which the data will be stored // - EDX:EAX logical sector number from which to read // - CX number of sectors to read // OUTPUT: // - 512*CX bytes of memory at ES:[BX] // LOCALS: // - [bp-2] - LBASectorsRead ReadSectors: push bp mov bp, sp sub sp, 2 push es // we will spoil es register here add eax, dword ptr [PartitionStartLBA] adc edx, dword ptr [PartitionStartLBA+4] ReadSectors_loop: pushad // Save logical sector number & sector count cmp cx, 8 // Maximum sectors per call is 0x7F, but VirtualBox correctly works with only 8 jbe ReadSectorsSetupDiskAddressPacket // If we are reading less than 9 sectors then just do the read mov cx, 8 // Otherwise read only 8 sectors on this loop iteration ReadSectorsSetupDiskAddressPacket: mov word ptr [bp-2], cx push edx push eax // Put 64-bit logical block address on stack push es // Put transfer segment on stack push bx // Put transfer offset on stack push cx // Set transfer count push 16 // Set size of packet to 10h mov si, sp // Setup disk address packet on stack mov dl, byte ptr [BootDrive] // Drive number mov ah, HEX(42) // Int 13h, AH = 42h - Extended Read int HEX(13) // Call BIOS jc PrintDiskError // If the read failed then abort add sp, 16 // Remove disk address packet from stack popad // Restore sector count & logical sector number push bx movzx ebx, word ptr [bp-2] add eax, ebx // Increment sector to read adc edx, 0 shl ebx, 5 // Multiplying by 512=2^9 here. // Shifting only by 5, because it goes to segment // (segment will be shifter by another 4 when converted to real addr) mov si, es add si, bx // Setup read buffer for next sector mov es, si pop bx sub cx, word ptr [bp-2] jnz ReadSectors_loop // Read next sector pop es leave ret // Displays a disk error message // And reboots PrintDiskError: mov si, msgDiskError // Bad boot disk message PrintCustomError: call PutChars // Display it Reboot: lea si, [msgAnyKey] // Press any key message call PutChars // Display it xor ax,ax int HEX(16) // Wait for a keypress int HEX(19) // Reboot PutChars: lodsb or al,al jz short Done call PutCharsCallBios jmp short PutChars PutCharsCallBios: mov ah, HEX(0e) mov bx, HEX(07) int HEX(10) ret Done: mov al, HEX(0d) call PutCharsCallBios mov al, HEX(0a) call PutCharsCallBios ret msgDiskError: .asciz "Disk error" msgAnyKey: .asciz "Press any key to restart" .org 510 .word HEX(aa55) // BootSector signature SetTreeRoots: // converting sizes to sectors. We dont need this sizes in bytes shr dword ptr [NodeSize], 9 shr dword ptr [LeafSize], 9 // leafsize is deprecated // finding FS_TREE root // put the key on stack xor eax, eax push eax push eax dec sp mov byte ptr [esp], ROOT_ITEM_KEY push eax push 0 push FS_TREE_OBJECTID mov eax, dword ptr [TreeRootAddress] mov edx, dword ptr [TreeRootAddress+4] mov cl, byte ptr [TreeRootLevel] call SearchTree add sp, 17 // setting stack back // bx - pointer to ROOT_ITEM mov al, byte ptr es:[bx+238] // moving level mov byte ptr [FsRootLevel], al cld mov eax, dword ptr es:[bx+176] mov dword ptr [FsRootAddress], eax mov eax, dword ptr es:[bx+176+4] mov dword ptr [FsRootAddress+4], eax // now we need to find DIR_ITEM_KEY with freeldr.sys hash xor eax, eax push eax push dword ptr [filenameCrc] dec sp mov byte ptr [esp], DIR_ITEM_KEY push eax push 0 push 256 // root dir objectid mov eax, dword ptr [FsRootAddress] mov edx, dword ptr [FsRootAddress+4] mov cl, byte ptr [FsRootLevel] call SearchTree add sp, 17 // setting stack back // parsing DIR_ITEM // bx - item addr // cx - item length mov ax, cx push ds push es pop ds pop es ParseDirItem_loop: cmp byte ptr [bx+29], BTRFS_FT_REG_FILE // checking dir_item type jne ParseDirItem_loop_2 cmp word ptr [bx+27], 11 // checking name length jne ParseDirItem_loop_2 lea si, [bx+30] lea di, [freeldrFilename] mov cx, 11 repe cmpsb je FreeLdrFound ParseDirItem_loop_2: mov dx, 30 add dx, word ptr [bx+27] cmp dx, ax jae FreeLdrNotFound add bx, dx jmp ParseDirItem_loop FreeLdrNotFound: lea si, [notFoundError] call PrintCustomError FreeLdrFound: // freeldr objectid is the first qword of DIR_ITEM structure xor eax, eax push eax push eax dec sp mov byte ptr [esp], EXTENT_DATA_KEY push dword ptr [bx+4] push dword ptr [bx] push es pop ds mov eax, dword ptr [FsRootAddress] mov edx, dword ptr [FsRootAddress+4] mov cl, byte ptr [FsRootLevel] call SearchTree add sp, 17 // here we have an EXTENT_ITEM with freeldr.sys mov eax, dword ptr es:[bx+29] shr eax, 9 // getting the number of clusters mov cx, ax push cx lea di, [bx+21] call ConvertAddress pop cx xor bx, bx mov ds, bx mov es, bx mov bx, FREELDR_BASE call ReadSectors mov dl, byte ptr [BootDrive] // Load boot drive into DL //mov dh, 0 // Load boot partition into DH (not needed, FreeLbr detects it itself) /* Transfer execution to the bootloader */ ljmp16 0, FREELDR_BASE // Insert chunk into chunk map (located at DS:[CHUNK_MAP_OFFSET]) // INPUT: // - ES:[AX] chunk key address // - ES:[BX] chunk item address // TODO: add max items checking InsertChunk: push bx push ax push es xor ecx, ecx // index InsertChunk_loop: std // numbers are little-engian, going right-to-left mov si, CHUNK_MAP_OFFSET lea si, [esi+ecx*8] lea si, [esi+ecx*8] lea si, [esi+ecx*8] // shift by 24 bytes inc cx cmp cl, byte ptr [ChunkMapSize] ja InsertChunk_insert lea si, [si+4] // set to the high dword of the 8-byte number lea di, [eax+KEY_SIZE-4] // set to high dword of key's offset field (offset=logical addr) cmpsd jb InsertChunk_loop cmpsd jb InsertChunk_loop lea si, [si+4] // set back to the beginning of key // numbers are greater - need to insert Here // shifting all to right by one element InsertChunk_insert: push si // here we will store new chunk dec cx // because we increased it before comparison mov dx, ds mov es, dx movzx eax, byte ptr [ChunkMapSize] // number of elements mov si, CHUNK_MAP_OFFSET lea si, [esi+eax*8] lea si, [esi+eax*8] lea si, [esi+eax*8-4] // setting to the high dword of the last element mov di, si add di, 20 // last byte of the last + 1 element (-4 bytes) sub ax, cx mov bx, 6 mul bx // 24/4 because of dwords mov cx, ax // number of elements to shift rep movsd // finally we can write the element cld pop di // here we will store new chunk pop ds // key-to-insert address segment pop si // key-to-insert address add si, 9 // move to 'offset' field movsd movsd // logical address loaded! // time for stripes pop si // chunk item address, length is the first field movsd movsd add si, 48 // move to offset of the first stripe (we read only first one) movsd movsd push es // swapping segments back to original push ds pop es pop ds inc byte ptr [ChunkMapSize] ret // Searches a key in a BTRFS tree // INPUT: // - [bp+4] BTRFS key to find // - EDX:EAX logical address of root header // - CL leaf node level // OUTPUT: // - ES:[SI] pointer to found item's key // - ES:[BX] pointer to found item // - ECX item length // LOCALS: // - [bp-8] - block number/current node offset // - [bp-9] - current level SearchTree: push bp mov bp, sp sub sp, 9 // set stack frame mov dword ptr [bp-4], edx mov dword ptr [bp-8], eax mov byte ptr [bp-9], cl SearchTree_readHeader: push ss pop es lea di, [bp-8] call ConvertAddress // LBA is in edx:eax now mov bx, DATA_BUFFER_SEGMENT mov es, bx xor bx, bx mov cl, byte ptr [bp-9] test cl, cl jz SearchTree_readLeaf // read node mov cx, word ptr [NodeSize] // word btr because we cannot read more than 65536 bytes yet call ReadSectors // reading node to the memory // every node begins with header //mov ax, word ptr [DATA_BUFFER_OFFSET + HEX(60)] // number of items in this leaf mov cx, -1 // index // finding the key SearchTree_findLoop_1: inc cx cmp word ptr es:[HEX(60)], cx je SearchTree_findLoop_1_equal // we are at the end - taking the last element lea si, [bp+4] // key to find mov di, HEX(65) // first key in leaf mov ax, 33 call CompareKeys jb SearchTree_findLoop_1 je SearchTree_findLoop_1_equal sub di, 33 // setting to previous element // we are here if key is equal or greater // (si points to the start of the key) SearchTree_findLoop_1_equal: push ds push es pop ds pop es lea si, [di+17] lea di, [bp-8] movsd movsd push es pop ds dec byte ptr [bp-9] // decrement level jmp SearchTree_readHeader SearchTree_readLeaf: mov cx, word ptr [LeafSize] call ReadSectors // every node begins with header //mov ax, word ptr [DATA_BUFFER_OFFSET + HEX(60)] // number of items in this leaf mov cx, -1 // index // finding the key SearchTree_findLoop_2: inc cx cmp word ptr es:[HEX(60)], cx je SearchTree_foundEqual lea si, [bp+4] // key to find mov di, HEX(65) // first key in leaf mov ax, 25 call CompareKeys jb SearchTree_findLoop_2 je SearchTree_foundEqual // set pointer to previous element if greater sub di, 25 SearchTree_foundEqual: // found equal or greater key mov bx, word ptr es:[di+17] // data offset relative to end of header mov cx, word ptr es:[di+21] // data size add bx, HEX(65) // end of header mov si, di leave ret SearchTree_notFound: xor ecx, ecx // return ecx=0 if nothing found leave ret // Converts logical address into physical LBA addr using chunk map // INPUT: // - ES:[DI] pointer to logical addr // OUTPUT: // - EDX:EAX target LBA // - sets ZF on failure ConvertAddress: // NOTE: SearchTree will overwrite data buffer area and our logical addr will be erased! // so putting it on stack push dword ptr es:[di+4] push dword ptr es:[di] mov bl, 1 // indicates first try. On second try BL must be set to 0 ConvertAddress_secondTry: push ds pop es xor ecx, ecx // set index to 0 ConvertAddress_loop: cmp cl, byte ptr [ChunkMapSize] jae ConvertAddress_checkInclusion // greater chunk is not found in chunk map - checking the last one std // numbers are little-engian, going right-to-left mov si, CHUNK_MAP_OFFSET lea si, [esi+ecx*8] lea si, [esi+ecx*8] lea si, [esi+ecx*8] // shift by 24 bytes lea di, [esp+4] // set to the second dword the 8-byte number lea si, [si+4] inc cl cmpsd jb ConvertAddress_loop cmpsd jb ConvertAddress_loop ConvertAddress_checkInclusion: dec cl cld // found chunk map item, checking inclusion with length mov si, CHUNK_MAP_OFFSET lea si, [esi+ecx*8] lea si, [esi+ecx*8] lea si, [esi+ecx*8] // shift by 24 bytes // logical_addr + length mov eax, dword ptr [si] // low dword of address mov edx, dword ptr [si+4] // high dword of address add eax, dword ptr [si+8] // low dword of length adc edx, dword ptr [si+12] // high dword of length // edx:eax is the end of the chunk // (logical_addr + length) - addr_to_find cmp edx, dword ptr [esp+4] ja ConvertAddress_found cmp eax, dword ptr [esp] ja ConvertAddress_found // address is greater than end of the chunk test bl, bl jnz ConvertAddress_notFound ret 8 ConvertAddress_found: // found chunk. Calculating the address // addr_to_find - logical_addr pop eax // low dword of addr_to_find pop edx // high dword of addr_to_find sub eax, dword ptr [si] sbb edx, dword ptr [si+4] // (addr_to_find - logical_addr) + physical_addr add eax, dword ptr [si+16] adc edx, dword ptr [si+20] // edx:eax is physical address. Converting to LBA (shifting by 9 bits) shrd eax, edx, 9 shr edx, 9 inc bl // clears ZF (bl is 0 or 1 here) ret ConvertAddress_notFound: // offset is alredy on stack //push dword ptr [esp+4] //push dword ptr [esp] dec sp mov byte ptr [esp], CHUNK_ITEM_KEY data32 push 0 push 0 push FIRST_CHUNK_TREE_OBJECTID mov eax, dword ptr [ChunkRootAddress] mov edx, dword ptr [ChunkRootAddress+4] mov cl, byte ptr [ChunkRootLevel] call SearchTree add sp, 9 // setting stack back mov ax, si // ES:[SI] - found key pointer call InsertChunk mov bl, 0 // indicates second try jmp ConvertAddress_secondTry // Compare key (key1) with key in array identified by base and index (key2) // INPUT: // - DS:[SI] key1 pointer // - ES:[DI] start of the array // - AX size of one key in array // - CX key index // OUTPUT: // - DS:[SI] key1 pointer // - ES:[DI] key2 pointer // - sets flags according to comparison CompareKeys: //xchg si, di mul cx add di, ax push ds push si push es push di // must be replaced for proper flags after cmps push ds push es pop ds pop es xchg si, di lea si, [si+4] // key in array lea di, [di+4] // searchable key std // comparing objectid cmpsd jne CompareKeys_end cmpsd jne CompareKeys_end // comparing type lea si, [si+12] lea di, [di+12] cmpsb jne CompareKeys_end // comparing offset lea si, [si+1+1+4] lea di, [di+1+1+4] cmpsd jne CompareKeys_end cmpsd CompareKeys_end: cld pop di pop es pop si pop ds ret btrfsSignature: .ascii "_BHRfS_M" //.org 1022 // .word HEX(aa55) wrongSignatureError: .asciz "BTRFS read error" MaxItemsError: .asciz "Max items error" filenameCrc: .long HEX(68cba33d) // computed hashsum of "freeldr.sys" freeldrFilename: .ascii "freeldr.sys" notFoundError: .asciz "NFE" .org 1534 .word HEX(aa55) .endcode16 END