Can't run the C runtime constructors and destructors manually
Posted: Sat Aug 12, 2023 8:50 pm
I'm having major problems running the C runtime constructors/destructors manually in a program compiled using -nostartfiles and loaded via LoadSeg(). For purposes of illustration I have created two little sample programs that show the issue:
This is loader.c which loads the child program and calls a function:
And this is child.c:
The code that manually runs the constructors and destructors is in the InitClib2() and UnInitClib2() functions and has been taken from clib2/stdlib_lib_main.c so it should be correct.
This is how I compile both programs:
When starting "loader", the function from the child program is called correctly because the console window opened in func() appears but then the program crashes while running destructors. Precisely, it's this code in the _fini() function that crashes:
I don't see any problem with the code. I've tried it with different gcc versions (8, 10, 11) from the latest SDK all to no avail. It always crashes when running the destructors so I'm out of ideas here. Can anybody help?
This is what the .ctors and .dtors sections look like in the map file for the child program:
This is loader.c which loads the child program and calls a function:
Code: Select all
#include <ctype.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <exec/exec.h>
#include <exec/types.h>
#include <proto/elf.h>
#include <proto/exec.h>
struct magicwordinfo
{
ULONG magicword;
void (*func)(void);
};
struct Library *ElfBase = NULL;
struct ElfIFace *IElf = NULL;
static int findmagicword(UBYTE *data, int size)
{
int i;
for(i = 0; i <= size - sizeof(struct magicwordinfo); i += 2) {
struct magicwordinfo *tmp = (struct magicwordinfo *) (data + i);
if(tmp->magicword == 0xC0DEFACE) {
tmp->func();
return 1;
}
}
return 0;
}
int main(int argc, char *argv[])
{
APTR obj;
int k, c = 0;
ElfBase = OpenLibrary("elf.library", 0);
IElf = (struct ElfIFace *) GetInterface(ElfBase, "main", 1, NULL);
obj = OpenElfTags(OET_Filename, "child", TAG_DONE);
ElfLoadSegTags(obj, ELS_FreeUnneeded, TRUE, TAG_DONE);
GetElfAttrsTags(obj, EAT_NumSections, &c, TAG_DONE);
for(k = 0; k < c; k++) {
APTR s = GetSectionTags(obj, GST_SectionIndex, k, TAG_DONE);
Elf32_Shdr *h = GetSectionHeaderTags(obj, GST_SectionIndex, k, TAG_DONE);
if(s && h && h->sh_size > sizeof(struct magicwordinfo)) {
if(findmagicword(s, h->sh_size)) break;
}
}
CloseElfTags(obj, CET_UnloadSeg, TRUE, TAG_DONE);
DropInterface((struct Interface *) IElf);
CloseLibrary((struct Library *) ElfBase);
return 0;
}
Code: Select all
#include <ctype.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <dos/dos.h>
#include <exec/exec.h>
#include <exec/types.h>
#include <proto/dos.h>
#include <proto/exec.h>
#include <proto/utility.h>
struct magicwordinfo
{
ULONG magicword;
void (*func)(void);
};
struct Library *DOSBase = NULL;
struct DOSIFace *IDOS = NULL;
// use __UtilityBase because this is required for clib2 initialization --> see clib2/stdlib_lib_main.c
struct Library *__UtilityBase = NULL;
struct UtilityIFace *__IUtility = NULL;
void _start(void)
{
}
static BOOL InitClib2(void);
static void UnInitClib2(void);
int initamigastuff(void)
{
SysBase = *((struct ExecBase **) 4);
IExec = (struct ExecIFace *) ((struct ExecBase *) SysBase)->MainInterface;
DOSBase = OpenLibrary("dos.library", 0);
IDOS = (struct DOSIFace *) GetInterface(DOSBase, "main", 1, NULL);
if(!InitClib2()) return 0;
return 1;
}
void freeamigastuff(void)
{
UnInitClib2();
if(IDOS) DropInterface((struct Interface *) IDOS);
if(DOSBase) CloseLibrary((struct Library *) DOSBase);
}
void func(void)
{
BPTR fh;
initamigastuff();
fh = Open("CON:100/100/320/240/Output/AUTO/CLOSE/WAIT", MODE_NEWFILE);
FPrintf(fh, "Hello from child!\n");
FFlush(fh);
freeamigastuff();
}
const struct magicwordinfo magicword = {
0xC0DEFACE,
func
};
// NB: this has been taken from clib2/stdlib_lib_main.c --> normally, it wouldn't be
// necessary to copy this code here but we could just call __lib_init() and __lib_exit()
// exported by clib2 but we can't do this here because __lib_init() and __lib_exit()
// are only available in clib2-ts and this isn't included in the latest SDK any more
// so we have to work around the problem by copying the code from clib2 directly
#include <setjmp.h>
extern jmp_buf __exit_jmp_buf;
extern BOOL __exit_blocked;
/****************************************************************************/
/*
* Dummy constructor and destructor array. The linker script will put these at the
* very beginning of section ".ctors" and ".dtors". crtend.o contains a similar entry
* with a NULL pointer entry and is put at the end of the sections. This way, the init
* code can find the global constructor/destructor pointers.
*
* WARNING:
* This hack does not work correctly with GCC 5 and higher. The optimizer
* will see a one element array and act appropriately. The current workaround
* is to use -fno-aggressive-loop-optimizations when compiling this file.
*/
static void (*__CTOR_LIST__[1]) (void) __attribute__(( used, section(".ctors"), aligned(sizeof(void (*)(void))) ));
static void (*__DTOR_LIST__[1]) (void) __attribute__(( used, section(".dtors"), aligned(sizeof(void (*)(void))) ));
/****************************************************************************/
/****************************************************************************/
static void
_init(void)
{
int num_ctors,i;
int j;
for(i = 1, num_ctors = 0 ; __CTOR_LIST__[i] != NULL ; i++)
num_ctors++;
for(j = 0 ; j < num_ctors ; j++)
__CTOR_LIST__[num_ctors - j]();
}
/****************************************************************************/
static void
_fini(void)
{
int num_dtors,i;
static int j;
for(i = 1, num_dtors = 0 ; __DTOR_LIST__[i] != NULL ; i++)
num_dtors++;
while(j++ < num_dtors)
__DTOR_LIST__[j]();
}
STATIC BOOL lib_init_successful;
/****************************************************************************/
STATIC BOOL
open_libraries(void)
{
BOOL success = FALSE;
int os_version;
/* Check which minimum operating system version we actually require. */
os_version = 37;
__UtilityBase = OpenLibrary("utility.library",os_version);
if(__UtilityBase == NULL)
goto out;
#if defined(__amigaos4__)
{
__IUtility = (struct UtilityIFace *)GetInterface(__UtilityBase, "main", 1, 0);
if(__IUtility == NULL)
goto out;
}
#endif /* __amigaos4__ */
success = TRUE;
out:
return(success);
}
/****************************************************************************/
STATIC VOID
close_libraries(VOID)
{
#if defined(__amigaos4__)
{
if(__IUtility != NULL)
{
DropInterface((struct Interface *)__IUtility);
__IUtility = NULL;
}
}
#endif /* __amigaos4__ */
if(__UtilityBase != NULL)
{
CloseLibrary(__UtilityBase);
__UtilityBase = NULL;
}
}
/****************************************************************************/
static void UnInitClib2(void)
{
if(lib_init_successful)
{
/* Enable exit() again. */
__exit_blocked = FALSE;
/* If one of the destructors drops into exit(), either directly
or through a failed assert() call, processing will resume with
the next following destructor. */
(void)setjmp(__exit_jmp_buf);
/* Go through the destructor list */
_fini();
close_libraries();
lib_init_successful = FALSE;
}
}
/****************************************************************************/
static BOOL InitClib2(void)
{
int result = FALSE;
/* Open dos.library and utility.library. */
if(!open_libraries())
goto out;
/* This plants the return buffer for _exit(). */
if(setjmp(__exit_jmp_buf) != 0)
{
/* If one of the destructors drops into exit(), either directly
or through a failed assert() call, processing will resume with
the next following destructor. */
(void)setjmp(__exit_jmp_buf);
/* Go through the destructor list */
_fini();
goto out;
}
/* Go through the constructor list */
_init();
/* Disable exit() and its kin. */
__exit_blocked = TRUE;
/* Remember this so that __lib_exit() will know what to do. */
lib_init_successful = TRUE;
result = TRUE;
out:
if(!lib_init_successful)
close_libraries();
return(result);
}
This is how I compile both programs:
Code: Select all
gcc -D__USE_INLINE__ -mcrt=clib2 -o loader loader.c
gcc -D__USE_INLINE__ -mcrt=clib2 -nostartfiles -o child child.c
Code: Select all
while(j++ < num_dtors)
__DTOR_LIST__[j]();
This is what the .ctors and .dtors sections look like in the map file for the child program:
Code: Select all
.ctors 0x01013064 0x8
*crtbegin.o(.ctors)
*crtbegin?.o(.ctors)
*(EXCLUDE_FILE(*crtend?.o *crtend.o) .ctors)
.ctors 0x01013064 0x4 /tmp/cc7dGGOl.o
*(SORT(.ctors.*))
.ctors._9 0x01013068 0x4 /SDK/clib2/lib/libc.a(stdlib_malloc.o)
*(.ctors)
.dtors 0x0101306c 0x8
*crtbegin.o(.dtors)
*crtbegin?.o(.dtors)
*(EXCLUDE_FILE(*crtend?.o *crtend.o) .dtors)
.dtors 0x0101306c 0x4 /tmp/cc7dGGOl.o
*(SORT(.dtors.*))
.dtors._9 0x01013070 0x4 /SDK/clib2/lib/libc.a(stdlib_malloc.o)
*(.dtors)