OSDev.org

The Place to Start for Operating System Developers
It is currently Sat Apr 27, 2024 11:21 am

All times are UTC - 6 hours




Post new topic Reply to topic  [ 3 posts ] 
Author Message
 Post subject: Calling Global Constructors in UEFI + Clang
PostPosted: Wed Jan 31, 2024 5:31 pm 
Offline
Member
Member

Joined: Tue Nov 20, 2012 4:45 pm
Posts: 77
Hi,

Consider the following simple example, "main.cpp":
Code:
#include <Uefi.h>
#include <Library/UefiLib.h>

struct GlobalType {
   int var;
   GlobalType () { var=1; }
};
GlobalType foo, bar, baz;

EFI_STATUS EFIAPI efi_main( IN EFI_HANDLE /*image_handle*/, IN EFI_SYSTEM_TABLE* sys_table ) {
   sys_table->ConOut->OutputString( sys_table->ConOut, (CHAR16*)L"Var is: \"" );
   CHAR16 str[3+1] = { (CHAR16)(foo.var+'0'), (CHAR16)(bar.var+'0'), (CHAR16)(baz.var+'0'), '\0' };
   sys_table->ConOut->OutputString( sys_table->ConOut, str );
   sys_table->ConOut->OutputString( sys_table->ConOut, (CHAR16*)L"\"" );
   while (1) asm("hlt");
   return 0;
}
For simplicity, it can be directly compiled with:
Quote:
#Path to EDK II (https://github.com/tianocore/edk2) root "edk2/" directory, for UEFI headers
UEFI_ROOT=/path/to/edk2/
clang -I${UEFI_ROOT}MdePkg/Include/ -I${UEFI_ROOT}MdePkg/Include/X64 -ffreestanding -fshort-wchar -target x86_64-pc-win32-coff -c main.cpp
lld-link -subsystem:efi_application -dll -entry:efi_main -nodefaultlib -filealign:16 -out:bootx64.efi main.o
When you boot with the resulting "bootx64.efi", you'll get a screen that cheerfully says
Quote:
Var is: "000"
I.e., the global constructors for `foo`, `bar`, and `baz`did not run. That's not particularly surprising, since we didn't call them. I want to know how.

The wiki has a page Calling Global Constructors which is pretty out-of-date, and GCC-centric anyway. I also found a blog post and more practically a previous thread where it was explained that one should walk through symbols `__init_array_start` to `__init_array_end`, but all of this seems to be for "-target i386-elf", which is not usable for a UEFI application. At any rate, I tried various things in these directions without really knowing what I was doing, and either failed to get it to link or it didn't work. How is it supposed to work?

_________________
Github of my OS


Top
 Profile  
 
 Post subject: Re: Calling Global Constructors in UEFI + Clang
PostPosted: Wed Jan 31, 2024 10:12 pm 
Offline
Member
Member

Joined: Mon Mar 25, 2013 7:01 pm
Posts: 5146
Geometrian wrote:
The wiki has a page Calling Global Constructors which is pretty out-of-date, and GCC-centric anyway.

GCC and Clang both call global constructors the same way, it's just that a lot of targets have switched from using .init/.fini/.ctors/.dtors to using .init_array/.fini_array, and different parts of the page were written at different times.

Geometrian wrote:
I also found a blog post and more practically a previous thread where it was explained that one should walk through symbols `__init_array_start` to `__init_array_end`, but all of this seems to be for "-target i386-elf", which is not usable for a UEFI application.

It could be usable for a UEFI application if you choose a target that uses .init_array for constructors. (GNU-EFI does this, but GNU-EFI is an ugly hack and I wouldn't recommend using it as an example.)

Geometrian wrote:
How is it supposed to work?

Good question! It depends on which runtime Clang thinks you're using. For Microsoft's runtime, it's basically the same as .init_array, except you might need to define symbols to find the start and end of the list For MinGW, it works more like the backwards .ctors, and I think the linker defines all the symbols you need. You can use llvm-objdump to examine main.o and see which runtime Clang expects.


Top
 Profile  
 
 Post subject: Re: Calling Global Constructors in UEFI + Clang
PostPosted: Wed Jan 31, 2024 10:39 pm 
Offline
Member
Member

Joined: Wed Aug 30, 2017 8:24 am
Posts: 1605
I'm also no longer sure if the code I had provided back then isn't technically undefined behavior, since I am doing pointer arithmetic between two different arrays. Although I am comparing with !=, so that may work out. But it still might be better to do something like
Code:
typedef void initfunc_t(void);
extern initfunc_t *__init_array_start[], *__init_array_end[];

static void handle_init_array(void) {
  size_t nfunc = ((uintptr_t)__init_array_end - (uintptr_t)__init_array_start)/sizeof (initfunc_t *);
  for (initfunc_t **p = __init_array_start; p < __init_array_start + nfunc; p++)
    (*p)();
}
This way, nfunc is calculated using integer arithmetic, and the loop is definitely defined behavior, and I still avoid the casting orgy the musl initialization code has.

But yeah, this code is not i386 specific, but it might be ELF specific. Which might work out for you if ELF is an intermediate target in building your PE UEFI application.

_________________
Carpe diem!


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 3 posts ] 

All times are UTC - 6 hours


Who is online

Users browsing this forum: No registered users and 9 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group