4

I'm wring a toy OS for my raspberry pi and trying to setup the MMU. I want to split the virtual memory between 3G:1G, so I think my code should be linked at 0xC0008000, while loaded to 0x8000 on execution. (The 0x8000 is the address the current bootloaders expect to find the kernel - since they are built for linux).

I think everything is setup fine by poking around with objdump, but it doesn't work. After some debugging with qemu, I think the bootloader doesn't find my code at all.

I believe the problem is with my linkscript, since the kernel starts fine if I move the starting code into its own section that's both linked and loaded at 0x8000.

I've extracted out the script and the minimal code. As following,

$ cat kernel.ld 
ENTRY(_start)

SECTIONS
{
    /* must == KERNLINK */
    .  = 0xC0008000;

    .text : AT(0x8000) {
        *(.text)
    }

    .bss : {
        *(.bss)
    }

    .data : {
        *(.data)
    }

    .rodata : {
        *(.rodata)
    }
}

-

$ cat source/entry.S 
#include "mem.h"

.globl _start
_start = V2P(entry)

.globl entry
entry:
    loop$:
    b loop$

(The "b loop$" won't work since it's generated as "b·c0008000" instead of using a relative branch. But never mind that part, the problem is it never reaches entry).

$ cat source/mem.h 
#define KERNLOAD 0x8000

#define KERNBASE 0xC0000000
#define KERNLINK (KERNBASE+KERNLOAD)

#define V2P(va) ((va) - KERNBASE)

Those are the only three source files. There should be nothing interesting in the Makefile, but the output from make is,

$ make
arm-none-eabi-gcc -g -Wall -c -o build/entry.o source/entry.S
arm-none-eabi-ld --no-undefined -T kernel.ld build/entry.o  -Map kernel.map -o build/output.elf
arm-none-eabi-objcopy build/output.elf -O binary kernel.img

And objdump,

$ arm-none-eabi-objdump -h build/output.elf  

build/output.elf:     file format elf32-littlearm

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         00000004  c0008000  00008000  00008000  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .ARM.attributes 00000014  00000000  00000000  00008004  2**0
                  CONTENTS, READONLY
  2 .debug_line   0000003c  00000000  00000000  00008018  2**0
                  CONTENTS, READONLY, DEBUGGING
  3 .debug_info   00000054  00000000  00000000  00008054  2**0
                  CONTENTS, READONLY, DEBUGGING
  4 .debug_abbrev 00000014  00000000  00000000  000080a8  2**0
                  CONTENTS, READONLY, DEBUGGING
  5 .debug_aranges 00000020  00000000  00000000  000080c0  2**3
                  CONTENTS, READONLY, DEBUGGING

I start to believe I overlooked some obvious yet precious details.

==== update ====

As noted in my own answer below, the confusion is caused by debugging in qemu. The breakpoints are set by virtual addresses. "b entry" doesn't work, because gdb is thinking about virtual address while MMU hasn't been enabled and we're running by physical address.

So before MMU is enabled, we have to use "b *0x8000". This sets a breakpoint that's correctly hit. GDB seems still confused though, since it doesn't show any debugging info (no source code, like 0x00008004 in ?? ()). That's not a big issue since I have the listing produced by "objdump -D".

After MMU is enabled and we branch to main, gdb works perfectly. The crux is to jump to a virtual address, using an absolute branch. b/bl would issue relative jumps. So I use ldr pc =main. bx works too.

jsz
  • 1,387
  • 9
  • 17
  • 1
    See: [Gnu ld giving unexpected load](http://stackoverflow.com/questions/14453996/gnu-linker-map-file-giving-unexpected-load-addresses). I think you have use of `load` confused. It is typically that you have ROM and wish to copy the section to RAM. `LOAD` places it in the binary. It doesn't make the code run at that address. You need to write `PC` relative code for you bootstrap portion. – artless noise Mar 30 '13 at 02:40
  • Seems to be qemu's problem.. – jsz Mar 30 '13 at 09:45
  • You should probably put your *bootstrap* non-MMU code in a separate section. For instance, `. = 0x8000; .boot : { boot.o(.text) }`. I would also take advantage of the [`memory` linker script section](http://ftp.gnu.org/old-gnu/Manuals/ld-2.9.1/html_node/ld_16.html) and maybe name your memory sections `phys` and `virt`. Use the `AT` to say the load address is at *0x8000*. – artless noise Mar 30 '13 at 14:57

1 Answers1

1

Finally solved it...

The code and the linkscript don't have problems. It's just that debugging doesn't seem to work on the qemu I used. It does work when VMA==LMA, but in this case the kernel already runs and qemu doesn't know about it. Or, the breakpoint is lost and never caught by gdb.

Anyway, by starting afresh and adding bits one by one, I have it working on my rpi (real hardware), and then it works on qemu too (gdb still won't catch the breakpoint).

So my question should really be how to debug such a scenario using qemu/gdb.

(My original problem is solved by changing the "domain" permission to "manager", instead of using "client". Yeah I know this is probably temporary and I should set a variable elsewhere).

jsz
  • 1,387
  • 9
  • 17