3
votes

I've been going through the following tutorial:

http://intermezzos.github.io/book/multiboot-headers.html

Unfortunately, it uses NASM instead of the GNU Assembler and because I typically work with the GNU toolchain I rather use that instead. The tutorial has the following trivial hello world program:

section .multiboot_header
header_start:
    dd 0xe85250d6                ; magic number
    dd 0                         ; protected mode code
    dd header_end - header_start ; header length

    ; checksum
    dd 0x100000000 - (0xe85250d6 + 0 + (header_end - header_start))

    ; required end tag
    dw 0    ; type
    dw 0    ; flags
    dd 8    ; size
header_end:

which provides the multiboot header as well as the following:

global start
section .text
bits 32
start:
    mov word [0xb8000], 0x0248 ; H
    mov word [0xb8002], 0x0265 ; e
    mov word [0xb8004], 0x026c ; l
    mov word [0xb8006], 0x026c ; l
    mov word [0xb8008], 0x026f ; o
    mov word [0xb800a], 0x022c ; ,
    mov word [0xb800c], 0x0220 ;
    mov word [0xb800e], 0x0277 ; w
    mov word [0xb8010], 0x026f ; o
    mov word [0xb8012], 0x0272 ; r
    mov word [0xb8014], 0x026c ; l
    mov word [0xb8016], 0x0264 ; d
    mov word [0xb8018], 0x0221 ; !
    hlt

These are linked together with the the following linker.ld file:

ENTRY(start)

SECTIONS {
    . = 1M;

    .boot :
    {
        /* ensure that the multiboot header is at the beginning */
        *(.multiboot_header)
    }

    .text :
    {
        *(.text)
    }
}

Compiling and running all of this can be done by:

nasm -f elf64 boot_nasm.asm
nasm -f elf64 multiboot_header_nasm.asm
ld --nmagic --output=kernel.bin --script=linker.ld multiboot_header_nasm.o boot_nasm.o

Check the tutorial how to make an iso image with the kernel and how to boot is using qemu. Now I've ported the source code as follows to use the GNU Assembler:

    .section .multiboot_header
header_start:
    .long 0xe85250d6             # magic number
    .long 0                      # protected mode code
    .long header_end - header_start

    # checksum
    .long 0x100000000 - (0xe85250d6 + 0 + (header_end - header_start))

    # required end tag
    .word 0 # type
    .word 0 # flags
    .long 8 # size
header_end:

and

    .text
    .global start
    .code32
start:
    movw $0x0248, (0xb8000)      # H
    movw $0x0265, (0xb8002)      # e
    movw $0x026c, (0xb8004)      # l
    movw $0x026c, (0xb8006)      # l
    movw $0x026f, (0xb8008)      # o
    movw $0x022c, (0xb800a)      # ,
    movw $0x0220, (0xb800c)      #
    movw $0x0277, (0xb800e)      # w
    movw $0x026f, (0xb8010)      # o
    movw $0x0272, (0xb8012)      # r
    movw $0x026c, (0xb8014)      # l
    movw $0x0264, (0xb8016)      # d
    movw $0x0221, (0xb8018)      # !
    hlt

I've compiled and linked these together by:

as boot.S -o boot.o
as multiboot_header.S -o multiboot_header.o
ld --nmagic --output=kernel.bin --script=linker.ld multiboot_header.o boot.o

objdump shows that the kernel.bin that is output by both cases is identical with respect to code in each segment. The only difference is that offsets where the segments are located are slightly different. The main difference is though that the nasm version boots while the GNU assembler version does not.

The GNU assembler version gives me error: no multiboot header found.

Here's the objdump output from the two created binaries:

 % objdump -D -s kernel_nasm.bin 

kernel_nasm.bin:     file format elf64-x86-64

Contents of section .boot:
 100000 d65052e8 00000000 18000000 12afad17  .PR.............
 100010 00000000 08000000                    ........        
Contents of section .text:
 100020 66c70500 800b0048 0266c705 02800b00  f......H.f......
 100030 650266c7 0504800b 006c0266 c7050680  e.f......l.f....
 100040 0b006c02 66c70508 800b006f 0266c705  ..l.f......o.f..
 100050 0a800b00 2c0266c7 050c800b 00200266  ....,.f...... .f
 100060 c7050e80 0b007702 66c70510 800b006f  ......w.f......o
 100070 0266c705 12800b00 720266c7 0514800b  .f......r.f.....
 100080 006c0266 c7051680 0b006402 66c70518  .l.f......d.f...
 100090 800b0021 02f4                        ...!..          

Disassembly of section .boot:

0000000000100000 <header_start>:
  100000:   d6                      (bad)  
  100001:   50                      push   %rax
  100002:   52                      push   %rdx
  100003:   e8 00 00 00 00          callq  100008 <header_start+0x8>
  100008:   18 00                   sbb    %al,(%rax)
  10000a:   00 00                   add    %al,(%rax)
  10000c:   12 af ad 17 00 00       adc    0x17ad(%rdi),%ch
  100012:   00 00                   add    %al,(%rax)
  100014:   08 00                   or     %al,(%rax)
    ...

Disassembly of section .text:

0000000000100020 <start>:
  100020:   66 c7 05 00 80 0b 00    movw   $0x248,0xb8000(%rip)        # 1b8029 <start+0xb8009>
  100027:   48 02 
  100029:   66 c7 05 02 80 0b 00    movw   $0x265,0xb8002(%rip)        # 1b8034 <start+0xb8014>
  100030:   65 02 
  100032:   66 c7 05 04 80 0b 00    movw   $0x26c,0xb8004(%rip)        # 1b803f <start+0xb801f>
  100039:   6c 02 
  10003b:   66 c7 05 06 80 0b 00    movw   $0x26c,0xb8006(%rip)        # 1b804a <start+0xb802a>
  100042:   6c 02 
  100044:   66 c7 05 08 80 0b 00    movw   $0x26f,0xb8008(%rip)        # 1b8055 <start+0xb8035>
  10004b:   6f 02 
  10004d:   66 c7 05 0a 80 0b 00    movw   $0x22c,0xb800a(%rip)        # 1b8060 <start+0xb8040>
  100054:   2c 02 
  100056:   66 c7 05 0c 80 0b 00    movw   $0x220,0xb800c(%rip)        # 1b806b <start+0xb804b>
  10005d:   20 02 
  10005f:   66 c7 05 0e 80 0b 00    movw   $0x277,0xb800e(%rip)        # 1b8076 <start+0xb8056>
  100066:   77 02 
  100068:   66 c7 05 10 80 0b 00    movw   $0x26f,0xb8010(%rip)        # 1b8081 <start+0xb8061>
  10006f:   6f 02 
  100071:   66 c7 05 12 80 0b 00    movw   $0x272,0xb8012(%rip)        # 1b808c <start+0xb806c>
  100078:   72 02 
  10007a:   66 c7 05 14 80 0b 00    movw   $0x26c,0xb8014(%rip)        # 1b8097 <start+0xb8077>
  100081:   6c 02 
  100083:   66 c7 05 16 80 0b 00    movw   $0x264,0xb8016(%rip)        # 1b80a2 <start+0xb8082>
  10008a:   64 02 
  10008c:   66 c7 05 18 80 0b 00    movw   $0x221,0xb8018(%rip)        # 1b80ad <start+0xb808d>
  100093:   21 02 
  100095:   f4                      hlt    

Here's the version created by using the GNU assembler:

 % objdump -D -s kernel.bin     

kernel.bin:     file format elf64-x86-64

Contents of section .boot:
 100000 d65052e8 00000000 18000000 12afad17  .PR.............
 100010 00000000 08000000                    ........        
Contents of section .text:
 100000 66c70500 800b0048 0266c705 02800b00  f......H.f......
 100010 650266c7 0504800b 006c0266 c7050680  e.f......l.f....
 100020 0b006c02 66c70508 800b006f 0266c705  ..l.f......o.f..
 100030 0a800b00 2c0266c7 050c800b 00200266  ....,.f...... .f
 100040 c7050e80 0b007702 66c70510 800b006f  ......w.f......o
 100050 0266c705 12800b00 720266c7 0514800b  .f......r.f.....
 100060 006c0266 c7051680 0b006402 66c70518  .l.f......d.f...
 100070 800b0021 02f4                        ...!..          

Disassembly of section .boot:

0000000000100000 <header_start>:
  100000:   d6                      (bad)  
  100001:   50                      push   %rax
  100002:   52                      push   %rdx
  100003:   e8 00 00 00 00          callq  100008 <header_start+0x8>
  100008:   18 00                   sbb    %al,(%rax)
  10000a:   00 00                   add    %al,(%rax)
  10000c:   12 af ad 17 00 00       adc    0x17ad(%rdi),%ch
  100012:   00 00                   add    %al,(%rax)
  100014:   08 00                   or     %al,(%rax)
    ...

Disassembly of section .text:

0000000000100000 <start>:
  100000:   66 c7 05 00 80 0b 00    movw   $0x248,0xb8000(%rip)        # 1b8009 <header_end+0xb7ff1>
  100007:   48 02 
  100009:   66 c7 05 02 80 0b 00    movw   $0x265,0xb8002(%rip)        # 1b8014 <header_end+0xb7ffc>
  100010:   65 02 
  100012:   66 c7 05 04 80 0b 00    movw   $0x26c,0xb8004(%rip)        # 1b801f <header_end+0xb8007>
  100019:   6c 02 
  10001b:   66 c7 05 06 80 0b 00    movw   $0x26c,0xb8006(%rip)        # 1b802a <header_end+0xb8012>
  100022:   6c 02 
  100024:   66 c7 05 08 80 0b 00    movw   $0x26f,0xb8008(%rip)        # 1b8035 <header_end+0xb801d>
  10002b:   6f 02 
  10002d:   66 c7 05 0a 80 0b 00    movw   $0x22c,0xb800a(%rip)        # 1b8040 <header_end+0xb8028>
  100034:   2c 02 
  100036:   66 c7 05 0c 80 0b 00    movw   $0x220,0xb800c(%rip)        # 1b804b <header_end+0xb8033>
  10003d:   20 02 
  10003f:   66 c7 05 0e 80 0b 00    movw   $0x277,0xb800e(%rip)        # 1b8056 <header_end+0xb803e>
  100046:   77 02 
  100048:   66 c7 05 10 80 0b 00    movw   $0x26f,0xb8010(%rip)        # 1b8061 <header_end+0xb8049>
  10004f:   6f 02 
  100051:   66 c7 05 12 80 0b 00    movw   $0x272,0xb8012(%rip)        # 1b806c <header_end+0xb8054>
  100058:   72 02 
  10005a:   66 c7 05 14 80 0b 00    movw   $0x26c,0xb8014(%rip)        # 1b8077 <header_end+0xb805f>
  100061:   6c 02 
  100063:   66 c7 05 16 80 0b 00    movw   $0x264,0xb8016(%rip)        # 1b8082 <header_end+0xb806a>
  10006a:   64 02 
  10006c:   66 c7 05 18 80 0b 00    movw   $0x221,0xb8018(%rip)        # 1b808d <header_end+0xb8075>
  100073:   21 02 
  100075:   f4                      hlt    

Now that I'm looking at it, it looks like the GNU Assembler version has an overlapping .boot and .text segment, so grub while loading the file probably overwrites the .boot section with the data of the .text section. Anyone have an idea how to fix this?

2
Hopefully your boot loader tells you why it doesn't like your kernel. Or maybe it does like it, and it boots and the problem is elsewhere in which case use a debugger to see how far you code gets. Also specify what boot loader you use. - Jester
"The only difference is that offsets where the segments are located are slightly different" - if that's really the only difference, then this must be fundamental to the reason why the GAS version doesn't boot. What precisely are the differences? - davmac
Check the tutorial for the grub.cfg file and how the iso image that is booted by qemu is generated. I've updated my question with the error message that I get. This particular error message comes from grub, so it gets past the BIOS in both cases. - del
I've added some extra data with the objdump output. I think the key is that in the GNU Assembler version, the two sections overlap, so the latter probably gets overwritten by the former in memory while the kernel is loaded. - del
Peter Cordes must be sleeping because he'd probably point out to you that movw $0x0248, (0xb8000) will cause an LCP stall in 32/64 bit code. Using 16-bit immediates is a problem because it needs a length changing prefix that is slower to decode. It is best to move 4 bytes at a time to video memory, and if you have to move 2 bytes, move a 32-bit immediate (representing your 16-bit value) into a 32 bit register and mov the lower half of the 32-bit register to video memory. mov $0x0248, %eax mov %ax, (0xb8000) is better than movw $0x0248, (0xb8000) . - Michael Petch

2 Answers

2
votes

The multiboot specification says:

The Multiboot header must be contained completely within the first 8192 bytes of the OS image, and must be longword (32-bit) aligned.

objdump gives us:

Idx Name              Size      File off  Algn
  3 .multiboot_header 00000018  00000040  2**0 CONTENTS, READONLY

Notice the Algn=2**0.

Solution: specify alignment:

.section .multiboot_header
.balign 4
header_start:

This should be done for the nasm version too, it's just accidental that it works there due to file layout.

1
votes

Other than the alignment issue pointed out by Jester, I think the linker script is suspect. It puts the two different input sections into two different output sections, which might not be what you want, and explains to some degree the differences you see via objdump. Try something like the following instead:

ENTRY(start)

SECTIONS {
    . = 1M;

    .text :
    {
        /* ensure that the multiboot header is at the beginning */
        *(.multiboot_header)
        *(.text)
    }
}

Alternatively, force gas to make ".multiboot_header" an allocatable section in multiboot_header.s:

.section .multiboot_header, "a"