Question about importing dynamic libraries (specifically linux)

I ran into an unexpected issue with regards to importing dynamic libraries and was hoping to get some clarification about what the expected behavior ought to be and what I might be able to do to fix the issue. Take the following

foreign import external_lib {"mylib.so"}



foreign external_lib {
	// methods ... 
}


Let’s say that mylib.so happens to be globally available as well. However I’d like to recompile mylib as there are some things I don’t need at the moment, so assume that I have a local copy inside my project.

I saw this line in the news doc about this

foreign import foo "foo.so"         // shared lib in linux links to /path/to/executable/foo.so

However, when I try to compile my Odin app, it always seems to default to the global version and does not appear to use my local copy until I break the path to the global version.

Is there a better way of making Odin use the local copy of the library instead of the global one? I did see this behavior when compiling a C program using Cmake as well so I assume there’s some flag or other option I’m not using.

Apologies if this is described somewhere, I can’t seem to find any other references other than that news doc.

Thank you!

To somewhat answer my own question, though somewhat not ideal as I was hoping to jank-like solutions, simply adding a system wide environmental variable seems like it will work since I’ve already scripted the build/run command anyways

export LD_LIBRARY_PATH="<path to library folder>:$LD_LIBRARY_PATH"

EDIT : jumped the gun! - the above technically kind of works but that test was outside of Odin. Trying in Odin seems to result in the same behavior, back to more investigating haha.

This has to do with how binaries are linked on Linux. A couple things need to be lined up just right for this to work. Let’s pretend we’re overriding SDL2 for the moment.

Here’s my test program.

package link

import "core:fmt"

foreign import lib "system:SDL2"

foreign lib {
	SDL_GetCPUCount :: proc() -> i32 ---
}

main :: proc() {
	fmt.println(SDL_GetCPUCount())
}

I will compile this with a simple odin build link.odin -file. When I run objdump --private-headers on the compiled binary, I can see that libSDL2-2.0.so.0 is one of the NEEDED entries, for my system. (ldd is another way to see just the dependencies.)


Here’s my lib.odin file that I’ll use to override SDL2.

package lib

@(export)
SDL_GetCPUCount :: proc "contextless" () -> i32 {
	return 42
}

I will compile this with odin build lib.odin -build-mode:shared -reloc-mode:pic -file -out:libSDL2-2.0.so.0. The output binary filename must match the soname of the library you’ve linked to in this case and should be in the same directory as the main program. It’s a dynamic link, so naturally our main program only knows the name it should link to; it doesn’t permanently attach to any one file.


Alternatively, you can run your program with LD_PRELOAD set, such as LD_PRELOAD=./lib.so link.

Now I can swap between these two options, renaming files or setting an environment variable before I run my program, and I can see my fake core count or my real core count depending on the situation.

As an aside, if you need to set your own soname, you can do so with -extra-linker-flags:'-Wl,-soname,libSDL2-2.0.so.0'. More documentation can be found here: Shared Libraries

If this is something you happen to do frequently, make sure when you run your program that you have some way to tell that it’s using the right library. Double-checking with ldd is good for this. Before I knew all this, I spent quite some time trying to debug a library that I thought I was linking to with a custom version, but I had still been running with the original library in /usr/lib.

1 Like

IIRC you can do
foreign import foo "./foo.so"

2 Likes

Thank you for the very detailed explanation! This is probably a little more work than I’d like to put into this (just a tiny personal project) but I’ll definitely look back to this in the future.

Oh! I didn’t know I could do that, I thought it was limited to filenames and I had to place libraries in the same folder as the wrapper.

Unfortunately, it doesn’t quite work for me for some reason. Basically get told that the library cant be found and based on the path it looks like it’s appending what I specify in the import statement onto the default lookup path, for instance if I do

foreign import foo “./library/foo.so”

The path the compiler spits back is something along the lines of

<path to where the library is sitting>//<path specified in import>/foo.so

which resulted in a SIGSEGV. Even just prefixing with ./ caused a SIGSEGV .

(side note - not 100% sure but I believe SIGSEGV is caused by the library due to some differences in features between my self-compiled one and the global one so I don’t think it’s an Odin issue)

I ended up still needing to tweak LD_LIBRARY_PATH and add the library root, though this did help a bit in conjunction with figuring out the final(for now) solution.


Thank you both!

You can probably patchelf your way to victory (modify the required shared object path, or rpath).

1 Like

It felt a little weird at first since it seemed like there should be a less hacky way but yea, that more or less what I ended up deciding to do.

I re-export LD_LIBRARY_PATH with the path to my version before compiling/running which seems to allow the linker to pick up the correct version of the library.