Skip to content

Odin shared libraries call the main program's _startup_runtime on Linux #5169

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Feoramund opened this issue May 15, 2025 · 6 comments · Fixed by #5173
Closed

Odin shared libraries call the main program's _startup_runtime on Linux #5169

Feoramund opened this issue May 15, 2025 · 6 comments · Fixed by #5173
Labels
bug replicated We were able to replicate the bug.

Comments

@Feoramund
Copy link
Contributor

Given the following main program:

main.odin

package main

import "base:runtime"

foreign import alpha "alpha.so"
foreign import beta "beta.so"

foreign alpha {
	get_alpha :: proc() -> int ---
}

foreign beta {
	get_beta :: proc() -> int ---
}

@(init)
program_init :: proc() {
	runtime.println_any("Init from the main program.")
}

@(init)
program_init_2 :: proc() {
	runtime.println_any("Another init from the main program.")
}

main :: proc() {
	alpha := get_alpha()
	beta := get_beta()

	runtime.println_any(alpha)
	runtime.println_any(beta)
}

And the two following libraries:

alpha/lib.odin

package alpha

import "base:runtime"

alpha: int

@(init)
init_alpha :: proc() {
	runtime.println_any("Init from inside alpha.")
	alpha = 32
}

@(export)
get_alpha :: proc() -> int {
	return alpha
}

beta/lib.odin

package beta

import "base:runtime"

beta: int

@(init)
init_beta :: proc() {
	runtime.println_any("Init from inside beta.")
	beta = 16
}

@(export)
get_beta :: proc() -> int {
	return beta
}

Compiled with:

odin build alpha -build-mode:dll -debug
odin build beta -build-mode:dll -debug
odin build . -debug

Running the main program produces this output:

Init from the main program.
Another init from the main program.
Init from the main program.
Another init from the main program.
Init from the main program.
Another init from the main program.
0
0

I'm curious if this also happens on Windows, or if this is another SysV problem.

@Kelimion
Copy link
Member

Kelimion commented May 15, 2025

If foreign importing the .lib files:

Init from inside alpha.
Init from inside beta.
Init from the main program.
Another init from the main program.
32
16

@Kelimion
Copy link
Member

Kelimion commented May 15, 2025

Almost the same if loaded this way:

package main

import "base:runtime"
import "core:dynlib"

Procs :: struct {
	get_alpha: proc() -> int,
	get_beta:  proc() -> int,
}

@(init)
program_init :: proc() {
	runtime.println_any("Init from the main program.")
}

@(init)
program_init_2 :: proc() {
	runtime.println_any("Another init from the main program.")
}

main :: proc() {
	procs: Procs
	count, _ := dynlib.initialize_symbols(&procs, "alpha.dll" when ODIN_OS == .Windows else "alpha.so"); assert(count > 0)
	count, _  = dynlib.initialize_symbols(&procs, "beta.dll"  when ODIN_OS == .Windows else "beta.so");  assert(count > 0)

	runtime.println_any(procs.get_alpha())
	runtime.println_any(procs.get_beta())
}

Except the prints have a different order:

Init from the main program.
Another init from the main program.
Init from inside alpha.
Init from inside beta.
32
16

@Kelimion
Copy link
Member

Kelimion commented May 15, 2025

So I can't replicate it on Linux when loaded through core:dynlib. I get the same output in the same order:

Init from the main program.
Another init from the main program.
Init from inside alpha.
Init from inside beta.
32
16

But I do get the same output as you do when we foreign import the .so files using your original snippet, and it doesn't do that on Windows when we do so with the .lib files.

Init from the main program.
Another init from the main program.
Init from the main program.
Another init from the main program.
Init from the main program.
Another init from the main program.
0
0

@Kelimion Kelimion added bug replicated We were able to replicate the bug. labels May 15, 2025
@Feoramund
Copy link
Contributor Author

Thank you for the investigation and reproduction. I hadn't even known about core:dynlib before, so that's interesting.

@Kelimion
Copy link
Member

Kelimion commented May 15, 2025

It should also be able to import alpha: int if you slap an @(export) on it and put it in the struct. No need for the getter. See https://github.com/odin-lang/Odin/blob/master/core/dynlib/example/example.odin.

@Feoramund
Copy link
Contributor Author

I've only just started reading about the PLT, GOT, and linkers to try to figure this one out, as well as reading through the SysV ABI documentation.

We call _startup_runtime, which is defined as a foreign procedure in runtime, the link name of which is __$startup_runtime, and this procedure is generated by lb_create_startup_runtime.

I tried setting it to be forced inline and changing its linkage with LLVMSetLinkage to no avail.

As I was looking through the disassembly of alpha.so, I noticed most of the calls are in the form of <...>@plt. To my understanding, this means a call to a dynamically-linked procedure that has its address adjusted at runtime. What interested me is that almost every call I could find was one of these @plt calls, even runtime.println_any is called in this manner. I did some experimenting with C (just writing a library that called some of its own functions), and that seems to be the norm, but I did find that interesting. I would think such runtime code would be packaged in the alpha.so binary itself, right?

I'm still new to all this, as I've really never had to deal with the innards of linking before, and I scarcely understand LLVM. But I do know how to work a hex editor, so I popped open alpha.so and hexed the call to make it call the non-plt address and it worked perfectly. Did the same for beta.so, and the output is now mirroring Windows.

Some further digging led me to a possible fix, which I will send up shortly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug replicated We were able to replicate the bug.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants