Page 1 of 1

.ctors section empty?

Posted: Fri Aug 17, 2012 6:22 am
by KittyFisto
Hello,

I've been following this forums for a week, reading the wiki and other tutorials on how to start working on an OS. I've followed all tutorials so far (compiling a cross compiler, starting with a c++ bare bone, ...) and I've got simple print screen stuff working, both in qemu and VMware.

However I am unable to bring the compiler to create the .ctors table for global objects. It seems to me that the table is empty, although I have one global object. From the looks of it, both start_ctors and end_ctors point to the same address and the content of said address is always 0. From what I've heard about that table, it should simply be a nullterminated array of function pointers, shouldn't it?

kmain.cpp

Code: Select all

#include <stdint.h>
#include "boot/multiboot.hpp"
#include "devices/screen.hpp"
#include "kernel.h"
#include "runtime/globals.h"

extern "C" uint32_t magic;
extern "C" multiboot_info *mbd;

Kernel kernel;

extern "C" void kmain(void)
{
	screen::clear();

	if ( magic != 0x2BADB002 )
	{
		screen::write("There's been an error with the boot sequence: Magic number's dont match");
	}

	__ctors();
	//asm volatile ("int $3");
	__dtors();
}
linker.ld

Code: Select all

ENTRY (start)

SECTIONS
{
    . = 0x00100000;

    .text ALIGN (0x1000) :
    {
        *(.text)
        *(.gnu.linkonce.t*)
    }

    .rodata ALIGN (0x1000) :
    {
        start_ctors = .;
        *(SORT(.ctors.*))  /* Note the "SORT" */
        end_ctors = .;

        start_dtors = .;
        *(SORT(.dtors.*))
        end_dtors = .;

        *(.rodata*)
        *(.gnu.linkonce.r*)
    }

    .data ALIGN (0x1000) :
    {
        *(.data)
        *(.gnu.linkonce.d*)
    }

    .bss :
    {
        sbss = .;
        *(COMMON)
        *(.bss)
        *(.gnu.linkonce.b*)
        ebss = .;
    }

    /DISCARD/ :
    {
        *(.comment)
        *(.eh_frame) /* discard this, unless you are implementing runtime support for C++ exceptions. */
    }
}
globals.cpp

Code: Select all

extern "C"
{

extern uint32_t *start_ctors;
extern uint32_t *end_ctors;

void __ctors()
{
	screen::writeLine("Constructing global objects...");
	screen::write("lower: ");
	screen::writeHex(start_ctors);
	screen::writeLine();
	screen::write("upper: ");
	screen::writeHex(end_ctors);

	auto it = reinterpret_cast<void(**)(void)>(&start_ctors);
	while(*it != nullptr)
	{
		(*it)();
		++it;
	}
}
}
makefile:

Code: Select all

OBJECTS=kernel/boot/loader.o \
	kernel/kmain.o \
	kernel/kernel.o \
	kernel/runtime/pure_virt_call.o \
	kernel/runtime/globals.o \
	kernel/gdt/gdt_flush.o \
	kernel/gdt/gdt.o \
	kernel/idt/idt.o \
	kernel/idt/interrupts.o \
	kernel/idt/idt_flush.o \
	kernel/devices/io.o \
	kernel/devices/screen.o \

TOOLSET=/usr/local/cross/bin

INCLUDES=-I./stdlib -I./

CC=$(TOOLSET)/i586-elf-g++
CFLAGS=-c -Wall -Wextra -Werror -nostdlib -fno-builtin -nostartfiles \
	-nodefaultlibs -fno-exceptions -fno-rtti -fno-stack-protector \
	-ffreestanding \
	-std=c++11 \
	$(INCLUDES)

AS=$(TOOLSET)/i586-elf-as
ASFLAGS=

LD=$(TOOLSET)/i586-elf-ld
LDFLAGS=-Tlinker.ld -v

all: kernel.img

.cpp.o:
	$(CC) $(CFLAGS) -o $@ $<

.s.o:
	$(AS) $(ASFLAGS) -o $@ $<

kernel.img: kernel.bin
	./patch_floppy_image.sh
	./build_qemu_image.sh

kernel.bin: $(OBJECTS)
	$(LD) $(LDFLAGS) -o kernel.bin $(OBJECTS)

clean:
	-rm kernel.bin $(OBJECTS)
loader.s:

Code: Select all

.global start                          # making entry point visible to linker

# setting up the Multiboot header - see GRUB docs for details
.set ALIGN,    1<<0                     # align loaded modules on page boundaries
.set MEMINFO,  1<<1                     # provide memory map
.set FLAGS,    ALIGN | MEMINFO          # this is the Multiboot 'flag' field
.set MAGIC,    0x1BADB002               # 'magic number' lets bootloader find the header
.set CHECKSUM, -(MAGIC + FLAGS)         # checksum required
 
.align 4
.long MAGIC
.long FLAGS
.long CHECKSUM
 
# reserve initial kernel stack space
.set STACKSIZE, 0x4000                  # that is, 16k.
.lcomm stack, STACKSIZE                 # reserve 16k stack on a doubleword boundary
.comm  mbd, 4                           # we will use this in kmain
.comm  magic, 4                         # we will use this in kmain
 
start:
    mov   $(stack + STACKSIZE), %esp    # set up the stack
    movl  %eax, magic                   # Multiboot magic number
    movl  %ebx, mbd                     # Multiboot data structure
 
    call kmain                          # call kernel proper
 
    cli
hang:
    hlt                                 # halt machine should kernel return
    jmp   hang
I have tried the loader.s content from the tutorial, but it didn't work either (I assume that it executed the loop zero times, but I couldn't verify this, hence I wrote c++ code and added calls to screen::write).

build output:

Code: Select all

/usr/local/cross/bin/i586-elf-as  -o kernel/boot/loader.o kernel/boot/loader.s
/usr/local/cross/bin/i586-elf-g++ -c -Wall -Wextra -Werror -nostdlib -fno-builtin -nostartfiles  -nodefaultlibs -fno-exceptions -fno-rtti -fno-stack-protector  -ffreestanding  -std=c++11  -I./stdlib -I./ -o kernel/kmain.o kernel/kmain.cpp
/usr/local/cross/bin/i586-elf-g++ -c -Wall -Wextra -Werror -nostdlib -fno-builtin -nostartfiles  -nodefaultlibs -fno-exceptions -fno-rtti -fno-stack-protector  -ffreestanding  -std=c++11  -I./stdlib -I./ -o kernel/kernel.o kernel/kernel.cpp
/usr/local/cross/bin/i586-elf-g++ -c -Wall -Wextra -Werror -nostdlib -fno-builtin -nostartfiles  -nodefaultlibs -fno-exceptions -fno-rtti -fno-stack-protector  -ffreestanding  -std=c++11  -I./stdlib -I./ -o kernel/runtime/pure_virt_call.o kernel/runtime/pure_virt_call.cpp
/usr/local/cross/bin/i586-elf-g++ -c -Wall -Wextra -Werror -nostdlib -fno-builtin -nostartfiles  -nodefaultlibs -fno-exceptions -fno-rtti -fno-stack-protector  -ffreestanding  -std=c++11  -I./stdlib -I./ -o kernel/runtime/globals.o kernel/runtime/globals.cpp
/usr/local/cross/bin/i586-elf-as  -o kernel/gdt/gdt_flush.o kernel/gdt/gdt_flush.s
/usr/local/cross/bin/i586-elf-g++ -c -Wall -Wextra -Werror -nostdlib -fno-builtin -nostartfiles  -nodefaultlibs -fno-exceptions -fno-rtti -fno-stack-protector  -ffreestanding  -std=c++11  -I./stdlib -I./ -o kernel/gdt/gdt.o kernel/gdt/gdt.cpp
/usr/local/cross/bin/i586-elf-g++ -c -Wall -Wextra -Werror -nostdlib -fno-builtin -nostartfiles  -nodefaultlibs -fno-exceptions -fno-rtti -fno-stack-protector  -ffreestanding  -std=c++11  -I./stdlib -I./ -o kernel/idt/idt.o kernel/idt/idt.cpp
/usr/local/cross/bin/i586-elf-as  -o kernel/idt/interrupts.o kernel/idt/interrupts.s
/usr/local/cross/bin/i586-elf-as  -o kernel/idt/idt_flush.o kernel/idt/idt_flush.s
/usr/local/cross/bin/i586-elf-g++ -c -Wall -Wextra -Werror -nostdlib -fno-builtin -nostartfiles  -nodefaultlibs -fno-exceptions -fno-rtti -fno-stack-protector  -ffreestanding  -std=c++11  -I./stdlib -I./ -o kernel/devices/io.o kernel/devices/io.cpp
/usr/local/cross/bin/i586-elf-g++ -c -Wall -Wextra -Werror -nostdlib -fno-builtin -nostartfiles  -nodefaultlibs -fno-exceptions -fno-rtti -fno-stack-protector  -ffreestanding  -std=c++11  -I./stdlib -I./ -o kernel/devices/screen.o kernel/devices/screen.cpp
/usr/local/cross/bin/i586-elf-ld -Tlinker.ld -v -o kernel.bin kernel/boot/loader.o  kernel/kmain.o  kernel/kernel.o  kernel/runtime/pure_virt_call.o  kernel/runtime/globals.o  kernel/gdt/gdt_flush.o  kernel/gdt/gdt.o  kernel/idt/idt.o  kernel/idt/interrupts.o  kernel/idt/idt_flush.o  kernel/devices/io.o  kernel/devices/screen.o  
GNU ld (GNU Binutils) 2.22
I don't really know how to tackle this problem. Can you tell me what I can do in order to find what I did wrong?

I really hope I haven't overlooked something stupid, but I've spent half a day googling for this problem, without success so far.

Re: .ctors section empty?

Posted: Fri Aug 17, 2012 6:25 am
by OSwhatever
If the linker cannot find any reference to the ctor section it will remove it. Have you used KEEP in order to tell the linker to keep the section regardless?

Re: .ctors section empty?

Posted: Fri Aug 17, 2012 6:29 am
by KittyFisto
Thanks for the quick answer.

I've changed the linker script:

Code: Select all

start_ctors = .;
KEEP(*(SORT(.ctors.*)))  /* Note the "SORT" */
end_ctors = .;
However the output is still the same:

Code: Select all

Constructing global objects...
lower: 0x72656854
upper: 0x72656854
*edit*

Just to clarify: Does the linker create a section .ctors that is essentially an array of function pointers and the linker script above embedds that into the .rodata section?

Re: .ctors section empty?

Posted: Fri Aug 17, 2012 6:40 am
by OSwhatever
I know at least my ctor is empty as GCC seems to use init array instead.

__INIT_ARRAY_START__ = .;
KEEP (*(SORT(.init_array.*)))
KEEP (*(.init_array))
__INIT_ARRAY_END__ = .;

add this and recompile and look what happens.

Re: .ctors section empty?

Posted: Fri Aug 17, 2012 6:46 am
by KittyFisto
It didn't change anything unfortunately. Both __INIT_ARRAY_START__ and __INIT_ARRAY_END__ point to the same address, just like start_ctors. I must be missing something fundamental, but I can't say what I'm doing wrong, probably because I don't know enough about low level stuff.

Re: .ctors section empty?

Posted: Fri Aug 17, 2012 7:13 am
by evoex
I see you use the section ".ctors.*". I'm not completely sure, but I think it doesn't have that last period and should be ".ctors*" or even just ".ctors".

Re: .ctors section empty?

Posted: Fri Aug 17, 2012 7:23 am
by Owen
My linker script has

Code: Select all

        *(SORT(.init_array*))
and it's working (ARM, GCC 4.7).There seems to be a lot of variation with this...

Re: .ctors section empty?

Posted: Fri Aug 17, 2012 7:30 am
by KittyFisto
I changed it to both ".ctors*" and ".ctors"
The former behaves just like ".ctors.*" but the latter behaves differently.
start_ctors now points at 0x0010009D and end_ctors at 0x72656854. This doesn't seem right at all, *if* the structure of the ctor table is as it is described in the tutorial:

[first ptr, second ptr, third ptr, null ptr]

Afaik, start_ctor should point to the first entry in the array (ie. to the function ptr of the first constructor) and end_constructor to the last entry.

Do you know where this stuff is defined? There must be some rule that GCC follows when creating said table.

Re: .ctors section empty?

Posted: Sat Aug 18, 2012 4:02 am
by jnc100
What happens if you dump the section table for your kmain.o file (objdump/readelf)? Does it display a non-zero length .ctors section?

Regards,
John.

Re: .ctors section empty?

Posted: Sat Aug 18, 2012 4:13 am
by KittyFisto
Thanks for that suggestion. That seems to nail down the problem. My kmain.cpp is the same as I posted in the first place: That is one global Kernel object and the kmain entry point.

Code: Select all

$ objdump -h kmain.o

kmain.o:     file format elf32-i386

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .group        00000008  00000000  00000000  00000034  2**2
                  CONTENTS, READONLY, EXCLUDE, GROUP, LINK_ONCE_DISCARD
  1 .text         000000ad  00000000  00000000  0000003c  2**2
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  2 .data         00000000  00000000  00000000  000000ec  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  3 .bss          00000c5c  00000000  00000000  00000100  2**5
                  ALLOC
  4 .text._ZnwmPv 00000008  00000000  00000000  00000100  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  5 .rodata       0000006c  00000000  00000000  00000108  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  6 .ctors        00000004  00000000  00000000  00000174  2**2
                  CONTENTS, ALLOC, LOAD, RELOC, DATA
  7 .comment      00000012  00000000  00000000  00000178  2**0
                  CONTENTS, READONLY
  8 .eh_frame     00000094  00000000  00000000  0000018c  2**2
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA

*edit*

I was too tired and checked the wrong file. Indeed there is a .ctors section that contains one function pointer I guess. Checking the resulting elf-binary results in the following:

Code: Select all

$ objdump -h kernel.bin

kernel.bin:     file format elf32-i386

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         00001cd1  00100000  00100000  00001000  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .text._ZnwmPv 00000008  00101cd1  00101cd1  00002cd1  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  2 .text._ZN11noncopyableC2Ev 00000005  00101cda  00101cda  00002cda  2**1
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  3 .rodata       00000162  00102000  00102000  00003000  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  4 .data         00000010  00103000  00103000  00004000  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  5 .ctors        00000008  00103010  00103010  00004010  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  6 .bss          0000529c  00103020  00103020  00004018  2**5
                  ALLOC
Does this mean there's some error with my calling code?

Re: .ctors section empty?

Posted: Sat Aug 18, 2012 4:37 am
by jnc100
Sorry, didn't look closely at your code. I think the error is in the way you are accessing the addresses. Try something like:

Code: Select all

extern char start_ctors;
extern char end_ctors;

void __ctors()
{
   screen::writeLine("Constructing global objects...");
   screen::write("lower: ");
   screen::writeHex((uint32_t)&start_ctors);
   screen::writeLine();
   screen::write("upper: ");
   screen::writeHex((uint32_t)&end_ctors);

... and repeat the changes for the actual calling code
The issue is that you want the address of the label (not its contents). And including all *(.ctors*) sections should be enough. You can probably ignore the init_array sections for now but at some point gcc _may_ deprecate .ctors as the init_array sections have now been long supported in glibc.

Regards,
John.

Re: .ctors section empty?

Posted: Sat Aug 18, 2012 4:50 am
by KittyFisto
Thanks for clearing that up. My code now looks like the following:

Code: Select all

extern "C"
{
	extern char start_ctors;
	extern char end_ctors;

	void __ctors()
	{
		auto start = reinterpret_cast<uint32_t*>(&start_ctors);
		auto end = reinterpret_cast<uint32_t*>(&end_ctors);

		screen::writeLine("Constructing global objects...");
		screen::write("lower: ");
		screen::writeHex(start);
		screen::writeLine();
		screen::write("upper: ");
		screen::writeHex(end);
		screen::writeLine();
	}
}
I understand now that I want the address of the label: That's because the actual list of ctor function pointers is in between those labels in memory, right?
Now comes the funny part, the following is the output from that code:

Code: Select all

lower: 0x00102000
upper: 0x00102000
How can that be? I already verified with objdump that the .ctors section of the resulting binary has a size of 8 bytes. Since those symbols delimit that section, there should be a difference in address of exactly 8 bytes, right?

Re: .ctors section empty?

Posted: Sat Aug 18, 2012 5:14 am
by jnc100
I've just done a quick test case.

Using the linker script

Code: Select all

...
start_ctors = .;
*(SORT(.ctors.*))
end_ctors = .;
...
(as per C++ Bare Bones) results in start_ctors and end_dtors having the same address according to readelf -s. On the other hand, using

Code: Select all

...
start_ctors = .;
*(SORT(.ctors*))  // note lack of '.'
end_ctors = .;
...
results in them having a 4 byte difference (as expected). I guess this is an error with the wiki article however I've only tested with old g++ (4.2.1) as I haven't had the need to update my c++ cross compiler in a long time as I don't use c++ anymore in my kernel. If you could confirm changing your linker script to the above fixes your error then I'll change the wiki article.
I already verified with objdump that the .ctors section of the resulting binary has a size of 8 bytes. Since those symbols delimit that section, there should be a difference in address of exactly 8 bytes, right?
Not exactly - the symbols delimit the list in the .rodata section, not .ctors. Actually from my test, if you correctly include the relevant .ctors* sections in the .rodata section then ld will not produce a .ctors section in the output.

Regards,
John.

Re: .ctors section empty?

Posted: Sat Aug 18, 2012 12:14 pm
by KittyFisto
Thank you very much. Your linker script, combined with the correct start_ctors and end_ctors symbols finally enabled me to construct all global objects.

Here's the code actually calling the constructors for anyone interested.

Code: Select all

extern "C"
{
	extern char start_ctors;
	extern char end_ctors;

	void __ctors()
	{
		auto start = reinterpret_cast<uint32_t*>(&start_ctors);
		auto end = reinterpret_cast<uint32_t*>(&end_ctors);

		auto it = start;
		while(it != end)
		{
			auto fn = reinterpret_cast<void(*)()>(*it);
			(*fn)();

			++it;
		}
	}
}

Re: .ctors section empty?

Posted: Sat Sep 29, 2012 9:01 am
by emitrax
I found this section very useful while looking for the correct way to call constructor of global object in my project.

Unfortunately I still have a problem. Here is my linker script

Code: Select all

     .init_array :
      {
                _init_array_start = .;
                KEEP (*core*:*(SORT(.init_array*)))
                KEEP (*core*:*(.init_array))
                KEEP (*(SORT(.init_array.*)))
                KEEP (*(.init_array))
                PROVIDE (_init_array_end = .);
      } > flash
where the pattern *core* is supposed to catch libcore.a, as I want object defined in that library to be called first.

Unfortunately, despite the KEEP keyword, one of the destructor of a global object that is not explicitly referenced in the core library
(I only have two global object), won't end up in the final binary.
The weird thing is that, if I move the same global object outside of the libcore, and thus it get
caught by the more generic linker lines, it does get in the final binary (and thus correctly called).

Anyone has already encountered this problem?

Any suggestion is appreciated.

Thanks in advance.