5

I am developping a small kernel for Raspberry Pi as a school project. We are encountering an issue with static variable initialization: it seems that those are not initialized at all. I've found a few related topics, yet none have brought a solution so far, though this helped me understand the issue (at least, I think).

All the code can be found on this repository, but I will try to sum up the related code here.

Code extracted from the project showing the problem: (kernel/src/kernel.cpp)

static int staticVal = 42;

void doStuff() { // Prevent the compiler from optimizing the value of staticVal
    staticVal++;
}

__attribute__((section(".init")))
int main(void) {
    //...
    gpio::blinkValue(staticVal); // Displays the value through LEDs
    //...
}

The code is then compiled using (eg)

arm-none-eabi-g++ -O2 -std=c++11 -mfpu=vfp -mfloat-abi=hard -march=armv6zk -mtune=arm1176jzf-s -nostartfiles -Wall -Wextra -pedantic -I src/ -I uspi/include -DHW_PI_1B -c src/[file].cpp -o _build/[file].o

and built into a binary file using the same options, and finally assembled into a kernel.img using

arm-none-eabi-ld --no-undefined _build/master.bin -Map _build/kernel.map -o _build/output.elf -T kernel.ld
arm-none-eabi-objcopy _build/output.elf -O binary kernel.img

(you can read the Makefile directly for more details: github.com/tobast/sysres-pikern/blob/staticNotWorking/kernel/Makefile).

The linker script used might as well be the problem, as we have tried to figure out how to write a working one without really knowing what we were doing (we use a modified linker script from another project): github.com/tobast/sysres-pikern/blob/staticNotWorking/kernel/kernel.ld

_start = 0x8000;
ENTRY(_start)

SECTIONS {
    .init 0x8000 : {
        *(.init)
    }

    .text : {
        *(.text)
    }

    .data : {
        *(.data)
    }

    .bss : {
        *(.bss)
    }
}

The program generated this way displays an apparently random value (which has, anyway, nothing to do with the expected value -- 42).

What I've understood so far is that the operating system is supposed to be responsible for initializing static variables before actually starting the program, but as we are writing an operating system, no one is doing it for us. Of course, we could manually initialize those variables (by calling a startup function for this purpose), but it would be an ugly and painful solution...

Is there any g++/linker options we are missing, or a problem in our linker script?

Thanks!

tobast
  • 101
  • 4
  • I'm only wildly guessing, but IIRC a C++ runtime typically doesn't use `main` as an entrypoint but actually some internal "main" function that performs static initialisation _then_ invokes your `main`. And it looks to me like you're using `main` as the actual entrypoint. I suppose it's possible that you need to do that static initialisation yourself, then, but I'd look into whether you really need to write `__attribute__((section(".init")))` on `main`, even on bare metal. Maybe there is no C++ runtime here mind you. – Lightness Races in Orbit May 10 '16 at 11:25
  • http://stackoverflow.com/q/6343348/560648 – Lightness Races in Orbit May 10 '16 at 11:29
  • @LightnessRacesinOrbit actually, C++ DOES use main() as the entry point to the C++ program. HOWEVER, there is a C++ runtime startup entry point that the OS actually enters at and that does the `.bss` clearing, `.data` pre-initializing, and then calls constructors for object in the global scope. I think the real problem is that he's compiling with `-nostartfiles`, which means this crt startup stuff isn't linked in, and the entry point jumps straight to main – Russ Schultz May 10 '16 at 21:01
  • @RussSchultz: Isn't that what I said? – Lightness Races in Orbit May 10 '16 at 21:01
  • oh, duh. I missed 'runtime' in your first sentence and just read "C++ doesn't typically". Mia culpa. – Russ Schultz May 10 '16 at 22:52

2 Answers2

2

I see a couple of problems:

  • Your sections are not page aligned, i.e with ALIGN(0x1000) in the linker script

  • You have no "start up" ASM script that sets up the stack and zeros the bss section.

I have no idea how such a script would look like on ARM (I'm used to x86), but here's an example:

        .section "vectors"
reset:  b     start
undef:  b     undef
swi:    b     swi
pabt:   b     pabt
dabt:   b     dabt
        nop
irq:    b     irq
fiq:    b     fiq

        .text
start:
        @@ Copy data to RAM.
        ldr   r0, =flash_sdata
        ldr   r1, =ram_sdata
        ldr   r2, =data_size

        @@ Handle data_size == 0
        cmp   r2, #0
        beq   init_bss
copy:
        ldrb   r4, [r0], #1
        strb   r4, [r1], #1
        subs   r2, r2, #1
        bne    copy

init_bss:
        @@ Initialize .bss
        ldr   r0, =sbss
        ldr   r1, =ebss
        ldr   r2, =bss_size

        @@ Handle bss_size == 0
        cmp   r2, #0
        beq   init_stack

        mov   r4, #0
zero:
        strb  r4, [r0], #1
        subs  r2, r2, #1
        bne   zero

init_stack:
        @@ Initialize the stack pointer
        ldr   sp, =0xA4000000

        bl    main

stop:   b     stop

Of course you would need to modify this to match the semantics of your kernel.

1

You probably also experience currently that the .bss section is not cleared either. Before main is started, you should have a special crafted (assembler) chunk that initializes the runtime data with the initial data from rom, clearing the bss and set the stack pointer.

This startup blob normally comes into place when you link with libc (and embedded compilers like Keil, just hides this process when using their IDE).

If not using libc, there should be some files named crt*.o available with the compiler that can be used (and you might need some few more details in the linker script, in order to obtain target addresses for where memory actually will be etc)

Stian Skjelstad
  • 2,277
  • 1
  • 9
  • 19
  • http://stackoverflow.com/questions/18091463/why-does-an-assembly-program-only-work-when-linked-with-crt1-o-crti-o-and-crtn-o – Stian Skjelstad May 10 '16 at 11:45
  • Your first part addresses the problem, but the second part only applies to GCC/x86_64 ABI/global constructors (i.e, doesn't concern static initialization of `int`). It may be the same on ARM but I'm not really sure. – uh oh somebody needs a pupper May 10 '16 at 11:51