118 lines
4.1 KiB
Plaintext
118 lines
4.1 KiB
Plaintext
|
linux_main.c defines the executable linux_main.exe
|
||
|
it depends on load-time linking with linux_base.so
|
||
|
it tries to perform run-time linking with linux_plugin.so
|
||
|
|
||
|
linux_base.c defines the binary linux_base.so
|
||
|
|
||
|
linux_plugin.c defines the binary linux_plugin.so
|
||
|
it depends on load-time linking with linux_base.so
|
||
|
|
||
|
The build script has to build linux_base.c first because it needs the
|
||
|
results of that build to setup the load-time linking in the other builds.
|
||
|
The linux_base.so is used to resolve load-time imported symbols.
|
||
|
|
||
|
The program has a shared data structure 'int x' in the 'base' layer that is
|
||
|
read and modified from both the 'main' layer and 'plugin' layer.
|
||
|
|
||
|
The expected output if the plugin loads successfully is:
|
||
|
|
||
|
```
|
||
|
x = 0
|
||
|
provided by plugin: {
|
||
|
x = 1
|
||
|
x = 2
|
||
|
}
|
||
|
x = 3
|
||
|
```
|
||
|
|
||
|
The expected output if the plugin is not found is:
|
||
|
|
||
|
```
|
||
|
x = 0
|
||
|
x = 1
|
||
|
```
|
||
|
|
||
|
The default Linux search paths for loading binaries do not include the
|
||
|
current directory of the process, or the path to the executable binary file.
|
||
|
It is possible to get it to behave like Windows binary loading, but some
|
||
|
extra steps need to be taken. (Load-Time Search Paths) (Run-Time Search Paths)
|
||
|
|
||
|
|
||
|
########################### Load-Time Search Paths ############################
|
||
|
|
||
|
For load-time binary dependencies, we can actually bake extra search paths
|
||
|
into a binary. GCC's options do not cover this, but there is a backdoor in
|
||
|
GCC for talking right to the underlying linker (ld).
|
||
|
|
||
|
The backdoor is the option -Wl (lowercase L). The syntax of this option is a
|
||
|
little unusual. As soon as a space occurs the backdoor is closed, so the
|
||
|
entire option has to be specified without any spaces. Since we need spaces,
|
||
|
the backdoor lets us use commas. It will remove the commas and replace them
|
||
|
with spaces before passing the command on to ld. It looks something like this:
|
||
|
|
||
|
gcc ... -Wl,-option,value ...
|
||
|
|
||
|
The specific option we want to pass through this way is -rpath. This option
|
||
|
tells the linker to bake a path into the search paths of the binary, so the
|
||
|
syntax for specifying a path this way with the backdoor syntax is:
|
||
|
|
||
|
gcc ... -Wl,-rpath,loadpath ...
|
||
|
|
||
|
In order to get the same behavior as we have on Windows, we want the path to
|
||
|
be relative to the binary itself. This can be done using the special syntax
|
||
|
'$ORIGIN/' as the path. But there is a problem here too. The dollar sign
|
||
|
already has a meaning in the shell, so to actually pass a raw dollar sign we
|
||
|
actually have to escape it with a backslash. Putting it all together the option
|
||
|
looks like this:
|
||
|
|
||
|
gcc ... -Wl,-rpath,\$ORIGIN/ ...
|
||
|
|
||
|
If that seems like a lot, that's because it is A LOT.
|
||
|
|
||
|
It's also pretty atypical to do things this way on Linux where the system tries
|
||
|
to have a specific place to put all the different pieces of executables. In
|
||
|
particular you might try to put the executable in a 'bin/' folder and the
|
||
|
shared object binaries in a 'lib/' folder. Then you would use the binary
|
||
|
relative path like this:
|
||
|
|
||
|
gcc ... -Wl,-rpath,\$ORIGIN/../lib ...
|
||
|
|
||
|
|
||
|
############################ Run-Time Search Paths ############################
|
||
|
|
||
|
The search paths for dlopen only include the system binary paths by default.
|
||
|
This matters if we are calling dlopen like this:
|
||
|
|
||
|
dlopen("mylayer.so", flags)
|
||
|
|
||
|
In this case the search paths will not include any binary relative rules or
|
||
|
current directory relative rules.
|
||
|
|
||
|
|
||
|
We can use $ORIGIN like we did in the load-time case to specify paths relative
|
||
|
to the calling binary:
|
||
|
|
||
|
dlopen("$ORIGIN/mylayer.so", flags)
|
||
|
|
||
|
Since this version is not just a base file name, the system search paths are
|
||
|
ignored and the path indicated by $ORIGIN is inspected directly.
|
||
|
|
||
|
|
||
|
We can also use . to specify the current directory like we do on the command
|
||
|
line when we call a script or run an executable in the current directory:
|
||
|
|
||
|
dlopen("./mylayer.so", flags)
|
||
|
|
||
|
In this case the system will look exactly in the current directory.
|
||
|
|
||
|
|
||
|
Finally we can use full paths to specify a directory without ambiguity:
|
||
|
|
||
|
dlopen("/home/username/pluginproject/mylayer.so", flags)
|
||
|
|
||
|
The nice thing about this option is that it means we can perform our own
|
||
|
search on the file system, and assemble a full path to get exactly what we want
|
||
|
if we have to.
|
||
|
|
||
|
|