C++ global destructors not working

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
8infy
Member
Member
Posts: 185
Joined: Sun Apr 05, 2020 1:01 pm

C++ global destructors not working

Post by 8infy »

Hello everyone,

I'm finally at a point in my OSdev journey where i'm able to run a kernel executable from disk, so far it's been pretty good.
However, today I stumbled upon an issue that i'm not sure how to resolve and I'm also not sure if my approach is even remotely correct.
I used to call global constructors manually (via init_array in the linker script) and it worked fine, however, today I decided to use a less hacky(is it even?) approach,
where you have crt* files that define _init and _fini from this https://wiki.osdev.org/Calling_Global_Constructors tutorial. This worked fine for constructors, however,
when I decided to define a destructor for my class it resulted in an undefined symbol __cxa_atexit and __cxa_finalize, as far as I understand I'm supposed to define them myself,
so I did and it fixed the error, but calling _fini still doesn't call my destructor and for whatever reason __cxa_atexit is getting called in _init... I understand that you don't really need
destructors for a kernel, because it's not supposed to exit ever but I still don't understand why this isn't working and only constructors are getting called. Also please let me know how I
can make this better, and what i'm missing. Also, is this even the right approach?

Here are my files:

Entrypoint.asm

Code: Select all

extern _init
extern _fini
extern run

global __cxa_atexit
global __cxa_finalize
__cxa_atexit:
    mov eax, 0
    ret
__cxa_finalize:
    mov eax, 0
    ret

section .entry

global start
start:
    ; Set up the kernel stack
    mov esp, kernel_stack_begin

    ; Call all global constructors
    call _init

    ; Jump into kernel main
    call run

    ; call global destructors
    call _fini

hang:
    cli
    hlt
    jmp hang

align 16
kernel_stack_end:
    times 16384 db 0
kernel_stack_begin:
Linker.ld

Code: Select all

ENTRY(start)

SECTIONS
{
    /* also defined in UKLoader.asm */
    . = 0x20000;

    .text BLOCK(4K) : ALIGN(4K)
    {
        *(.entry)
        *(.text)
    }

    .rodata BLOCK(4K) : ALIGN(4K)
    {
        *(.rodata)
    }

    .data BLOCK(4K) : ALIGN(4K)
    {
        *(.data)
    }

    .bss BLOCK(4K) : ALIGN(4K)
    {
        *(COMMON)
        *(.bss)
    }
}
Kernel.cpp

Code: Select all

static constexpr unsigned long video_memory = 0xB8000;

void write_string(const char* string, unsigned char color = 0xF);

class Checker
{
public:
    Checker()
    {
        write_string("Ctor called");
    }

    ~Checker()
    {
        write_string("Dtor called");
    }
};

static Checker c;

void write_string(const char* string, unsigned char color)
{
    static unsigned short* memory = reinterpret_cast<unsigned short*>(video_memory);

    while (*string)
    {
        unsigned short colored_char = *(string++);
        colored_char |= color << 8;

        *(memory++) = colored_char;
    }
}

extern "C" void run()
{
    write_string("Hello from the kernel!", 0x4);
}
Makefile

Code: Select all

gpp = i686-elf-g++
gpp_compile_flags = -std=c++17 -ffreestanding -O2 -Wall -Wextra
nasm_compile_flags = -felf32

ld_flags = -Wl,--oformat=binary -ffreestanding -O2 -nostdlib -lgcc

object_files = Kernel.o Entrypoint.o

crti_obj = crti.o
crtbegin_obj:=$(shell $(gpp) $(gpp_compile_flags) -print-file-name=crtbegin.o)

crtend_obj  :=$(shell $(gpp) $(gpp_compile_flags) -print-file-name=crtend.o)
crtn_obj = crtn.o

# Has to be in this order
obj_link_list := $(crti_obj) $(crtbegin_obj) $(object_files) $(crtend_obj) $(crtn_obj)

Kernel.bin: Linker.ld $(obj_link_list)
	$(gpp) -T Linker.ld -o $@ $(ld_flags) $(obj_link_list)

%.o: %.cpp
	$(gpp) $(gpp_compile_flags) -c $< -o $@

%.o: %.asm
	nasm $(nasm_compile_flags) $< -o $@

.PHONY: clean
clean:
	rm $(obj_link_list) Kernel.bin
crti.asm

Code: Select all

section .init
global _init

_init:
    push ebp
    mov ebp, esp

; ctors ^v dtors

section .fini
global _fini

_fini:
    push ebp
    mov ebp, esp
crtn.asm

Code: Select all

section .init
    pop ebp
    ret

section .fini
    pop ebp
    ret
Here's a screenshot, everything works correctly aside from the _fini function, I call it successfully and go to the next instructions but the dtor is not getting called.
https://prnt.sc/s5pq5u

Thank you!
nullplan
Member
Member
Posts: 1792
Joined: Wed Aug 30, 2017 8:24 am

Re: C++ global destructors not working

Post by nullplan »

May I ask a stupid question? Why do you care about destructors for global objects in a kernel? The kernel is supposed to run until the machine shuts down, so I don't know about the usefulness of those.
Carpe diem!
8infy
Member
Member
Posts: 185
Joined: Sun Apr 05, 2020 1:01 pm

Re: C++ global destructors not working

Post by 8infy »

nullplan wrote:May I ask a stupid question? Why do you care about destructors for global objects in a kernel? The kernel is supposed to run until the machine shuts down, so I don't know about the usefulness of those.
Did you read my question? I literally answered it right there...
8infy
Member
Member
Posts: 185
Joined: Sun Apr 05, 2020 1:01 pm

Re: C++ global destructors not working

Post by 8infy »

Alright, so I fixed the program by using -fno-use-cxa-atexit. I have 0 clue as to why this worked, and what even is that cxa_atexit stuff. Would really appreciate an explanaition.
kzinti
Member
Member
Posts: 898
Joined: Mon Feb 02, 2015 7:11 pm

Re: C++ global destructors not working

Post by kzinti »

The C++ compiler needs a way to execute your destructors at exit time. cxa_atexit() / cxa_finalize() are part of that mechanism. Since you don't care about calling them in this case, omitting (part of) that machinery is probably fine.
8infy
Member
Member
Posts: 185
Joined: Sun Apr 05, 2020 1:01 pm

Re: C++ global destructors not working

Post by 8infy »

kzinti wrote:The C++ compiler needs a way to execute your destructors at exit time. cxa_atexit() / cxa_finalize() are part of that mechanism. Since you don't care about calling them in this case, omitting (part of) that machinery is probably fine.
As far as I understand when I do -fno-use-cxa-atexit it proceeds to use _fini for destructors but the order in which it's gonna call destructors is not specified, is this how that works?
kzinti
Member
Member
Posts: 898
Joined: Mon Feb 02, 2015 7:11 pm

Re: C++ global destructors not working

Post by kzinti »

Sorry it's been a long time since I looked at this stuff and I don't remember the details. I simply don't use the crtX files for my kernel, so I don't run into these issues. It's much easier to not use any of it since you don't really need it.

That said, I do want constructors. This is what I did:

1) I compile with -ffreestanding and link with -nostdlib
2) My kernel linker script has an ".init_array" section to hold the constructors: https://github.com/kiznit/rainbow-os/bl ... ds#L63-L72
3) Then I manually walk that array and call constructors when entering the kernel: https://github.com/kiznit/rainbow-os/bl ... pp#L37-L47
nullplan
Member
Member
Posts: 1792
Joined: Wed Aug 30, 2017 8:24 am

Re: C++ global destructors not working

Post by nullplan »

init_array and fini_array are the newer mechanism. They have the benefit of being independent of link order. With _init and _fini, it happened that the linker reordered the input sections (perhaps being told to sort them by some criteria) and then the _init symbol was linked in after the section that contains the return.

Note that you should walk the fini_array backwards.
Carpe diem!
8infy
Member
Member
Posts: 185
Joined: Sun Apr 05, 2020 1:01 pm

Re: C++ global destructors not working

Post by 8infy »

Thanks for the replies!
Post Reply