Issue
I have a project set up where I compile and link a shared library (libexample.so) with a linker script that looks like this:
SECTIONS
{
.modules : {
__MODULES_START = .;
KEEP(*(.mod*));
__MODULES_END = .;
}
...
}
I use these in my code to load modules compiled into the library.
extern uint32_t __MODULES_START;
extern uint32_t __MODULES_END;
unsigned int init_mods (void) {
void (*p)(void) = (void *)&__MODULES_START;
...
}
And when I compile the library in my Makefile
build/%.o: %.c
gcc -o $@ -c $< -fPIC -g -Os -Iinclude
bin/libexample.so: $(OBJS)
gcc -o $@ $^ -shared -fPIC -lc -T$(LINKER_SCRIPT)
It builds and links just fine, and it works when I try to link the library to another project that calls "init_mods".
build/%.o: %.c
gcc -o $@ -c $< -fPIE -g -Os -Iinclude -I../libexample/include
bin/untitled-program: $(OBJS)
gcc -o $@ $^ -fPIE -lc -lexample -Lbin '-Wl,-rpath,$$ORIGIN'
However, when I run the program where it can find the library, I receive the following linking error:
/bin/untitled-program: error while loading shared libraries: /blah/blah/libexample.so: unexpected PLT reloc type 0x08
When I readelf the shared library, I get the two definitions in my symbol table
Symbol table '.symtab' contains 223 entries:
Num: Value Size Type Bind Vis Ndx Name
...
154: 0000000000000050 0 NOTYPE GLOBAL DEFAULT 2 __MODULE_INIT_END
...
222: 0000000000000028 0 NOTYPE GLOBAL DEFAULT 2 __MODULE_INIT_START
So I'm wondering if my issue has to do with the NOTYPE, but I'm having trouble finding documentation on this.
To explain why I think my issue has to do with the linker script variables, when I run my program with linker debugging on, one of them is the last one to show up.
$ LD_DEBUG=all ./untitled-program
...
23856: symbol=__MODULE_END; lookup in file=./bin/untitled-program [0]
23856: symbol=__MODULE_END; lookup in file=/usr/lib/libc.so.6 [0]
23856: symbol=__MODULE_END; lookup in file=./bin/libexample.so [0]
23856: binding file ./bin/libexample.so [0] to ./bin/libexample.so [0]: normal symbol `__MODULE_END'
...
23856: symbol=__MODULE_START; lookup in file=./bin/untitled-program [0]
23856: symbol=__MODULE_START; lookup in file=/usr/lib/libc.so.6 [0]
23856: symbol=__MODULE_START; lookup in file=./bin/libexample.so [0]
23856: binding file ./bin/libexample.so [0] to ./bin/libexample.so [0]: normal symbol `__MODULE_START'
./bin/untitled-program: error while loading shared libraries: ./bin/libexample.so: unexpected PLT reloc type 0x08
But, that's the weird thing because it's able to bind one of the other linker script variables before it fails.
I have been working on this issue for too long, so I'm having trouble seeing the bigger picture. Maybe I'm thinking about this wrong and the issue is with another symbol. Any help or guidance would be appreciated!
Solution
Just mark your module init function with the GCC constructor function attribute (it has nothing to do with C++ constructors!), and it will include its address in the init_array
section; the dynamic linker will then execute it before main()
, or immediately when a dynamic library is loaded.
static void my_mod_init(void) __attribute__((constructor));
static void my_mod_init(void)
{
/* Initialize this module, hook up to the library */
}
This has the benefit that because the dynamic linker executes these automatically, these are also run when you load a dynamic library with such modules with e.g. dlopen(path, RTLD_NOW | RTLD_GLOBAL)
.
If you want to replicate the functionality under your own control, then have each module declare an array of the init function addresses to a special section, say "mod_inits". Define some helper macros:
#define MERGE2_(a, b) a ## b
#define MERGE2(a, b) MERGE2_(a, b)
#define MODULE_INIT(func) \
static void *MERGE2(_init_, __LINE__) \
__attribute__((section ("mod_inits"), used)) = &func
Then, in your module source files, make some functions:
static void hello(void) {
puts("Hello!");
}
MODULE_INIT(hello);
static void also(void) {
puts("Also hello!");
}
MODULE_INIT(also);
In the library file, to scan and execute all functions in any compilation units marked with MODULE_INIT()
:
extern void *__start_mod_inits;
extern void *__stop_mod_inits;
void run_mod_inits(void)
{
for (void **ptr = &__start_mod_inits; ptr < &__stop_mod_inits; ptr++) {
void (*func)(void) = *ptr;
func(); /* Could pass parameters, if they have the same prototype */
}
}
You do not need any linker file for this, as gcc provides the __start_
and __stop_
symbols for all sections whose names are valid C identifiers.
Answered By - None