Page 1 of 1

Global constructors not called

Posted: Wed Feb 28, 2024 6:07 am
by dj1999
Hello, I am writing toy OS using MSVC and C++. Until now, I bypassed the issue I have with global constructors using function with static local variable but it's getting annoying now. This is how relevant parts are set up. crt.cpp is inside CoreLib project which is built as static library and later linked with my OS executable. I did check that the library gets linked. This is the code of crt.cpp:

Code: Select all

#define _CRTALLOC(x) __declspec(allocate(x))

#pragma section(".CRT$XIA",long,read)
#pragma section(".CRT$XIZ",long,read)
#pragma section(".CRT$XCA",long,read)
#pragma section(".CRT$XCZ",long,read)
#pragma section(".CRT$XPA",long,read)
#pragma section(".CRT$XPZ",long,read)
#pragma section(".CRT$XTA",long,read)
#pragma section(".CRT$XTZ",long,read)

typedef void (*_PVFV)(void);
typedef int (*_PIFV)(void);

extern "C" _CRTALLOC(".CRT$XIA") _PIFV __xi_a[] = { 0 }; // C initializers (first)
extern "C" _CRTALLOC(".CRT$XIZ") _PIFV __xi_z[] = { 0 }; // C initializers (last)
extern "C" _CRTALLOC(".CRT$XCA") _PVFV __xc_a[] = { 0 }; // C++ initializers (first)
extern "C" _CRTALLOC(".CRT$XCZ") _PVFV __xc_z[] = { 0 }; // C++ initializers (last)
extern "C" _CRTALLOC(".CRT$XPA") _PVFV __xp_a[] = { 0 }; // C pre-terminators (first)
extern "C" _CRTALLOC(".CRT$XPZ") _PVFV __xp_z[] = { 0 }; // C pre-terminators (last)
extern "C" _CRTALLOC(".CRT$XTA") _PVFV __xt_a[] = { 0 }; // C terminators (first)
extern "C" _CRTALLOC(".CRT$XTZ") _PVFV __xt_z[] = { 0 }; // C terminators (last)
#pragma comment(linker, "/merge:.CRT=.rdata")

extern "C" int crt_call_c(_PIFV * a, _PIFV * b)
{
	while (a != b)
	{
		if (*a)
		{
			int r = (**a)();
			if (r) return r;
		}
		++a;
	}
	return 0;
}
extern "C" void crt_call(_PVFV * a, _PVFV * b)
{
	while (a != b)
	{
		if (*a)
		{
			(**a)();
		}
		++a;
	}
}

static _PVFV ExitList[32];
static unsigned MaxExitListEntries;
static unsigned CurrentExitListIndex;

static void _crt_init_atexit_tables(void)
{
	MaxExitListEntries = 32;
	CurrentExitListIndex = 0;
}
static void _crt_clean_atexit_tables(void)
{
	MaxExitListEntries = 0;
	CurrentExitListIndex = 0;
}

extern "C" int
__cdecl atexit(_PVFV Func)
{
	if (CurrentExitListIndex < MaxExitListEntries)
	{
		ExitList[CurrentExitListIndex++] = Func;
		return 0;
	}
	return -1;
}

void _cdecl crt_init()
{
	_crt_init_atexit_tables();
	crt_call_c(__xi_a, __xi_z);
	crt_call(__xc_a, __xc_z);
}

void _cdecl crt_done()
{
	_crt_clean_atexit_tables();
	crt_call(__xp_a, __xp_z);
	crt_call(__xt_a, __xt_z);
}


int __cdecl _purecall_handler()
{
	for (;;);
}
I did try all possible versions of this I found online.

In my Multiboot.cpp file I declare init/done functions and call them like this:

Code: Select all

extern void _cdecl crt_init();
extern void _cdecl crt_done();

void Start(uint32 eax, uint32 ebx)
{
	crt_init();
	Kernel(eax, ebx);
	crt_done();
}

NOPROLOGUE void MultibootEntry(void)
{
	__asm {
		align 4
		multiboot_header:
		dd(MULTIBOOT_HEADER_MAGIC);
		dd(MULTIBOOT_HEADER_FLAGS);
		dd(CHECKSUM);
		dd(HEADER_ADDRESS);
		dd(KERNEL_LOAD_ADDRESS);
		dd(00);
		dd(00);
		dd(HEADER_ADDRESS + 0x20);
	kernel_entry:
		cli
		mov esp, KERNEL_STACK;
		mov ebp, esp
		push    0;
		popf;
		push    ebx;
		push    eax;
		call    Start;
	halt:
		jmp halt;
	}
}
I went to see the binaries output using dumpbin, and using /headers this is part containing CRT$XCA and similiar:

Code: Select all

SECTION HEADER #6
.CRT$XCA name
       0 physical address
       0 virtual address
       4 size of raw data
     E50 file pointer to raw data (00000E50 to 00000E53)
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
40300040 flags
         Initialized Data
         4 byte align
         Read Only

SECTION HEADER #7
.CRT$XCZ name
       0 physical address
       0 virtual address
       4 size of raw data
     E54 file pointer to raw data (00000E54 to 00000E57)
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
40300040 flags
         Initialized Data
         4 byte align
         Read Only
But opening it with /all /symbols I cant find anything crt related.

Just to show an example, this works fine:

Code: Select all

struct S
{
	int a = 10;
};
static S s{}; //I expected that atleast C initializers need to be called but I guess not since it works even without calling crt_init
But this crashes:

Code: Select all

struct S
{
	S() : a() {}
	int a = 10;
};
static S s{};
I am running out of ideas, any suggestions?