Task
Before me there was a task to write a library loader, which is able to provide some interface functions to an external dynamic library. The solution should be as cross-platform as possible (at least, work on Linux and Windows). Libraries written in various programming languages that support the creation of dynamic libraries should be loaded. As an example, the languages C and Pascal were chosen.
Decision
The main library loader is written in C. In order for the loaded libraries to have the opportunity to use the functions of the main program, the main program is divided into 2 parts: the main and loadable modules. The main module is needed just to start the program, the loadable module is also a dynamic library associated with the main module during its launch. Gcc (MinGW for Windows) and fpc were chosen as compilers.
Here will be given a simplified example of a program that allows you to sort out this issue and teach first-year students to write modules for their program (Pascal is often taught at school).
Library loader
The main library loader file looks very simple:
main.c
#include "loader.h" #ifdef __cplusplus extern "C" { #endif int main(int argc, char *argv[]) { if (argc > 1) { loadRun(argv[1]); } return 0; } #ifdef __cplusplus } #endif
')
And this is the module responsible for loading dynamic libraries, which itself is moved to the dynamic library so that loaded libraries can use the functions provided to them:
loader.c
#include "loader.h" #include "functions.h" #include <stdio.h> #ifndef WIN32 #include <dlfcn.h> #else #include <windows.h> #endif #ifdef __cplusplus extern "C" { #endif void printString(const char * const s) { printf("String from library: %s\n", s); } void loadRun(const char * const s) { void * lib; void (*fun)(void); #ifndef WIN32 lib = dlopen(s, RTLD_LAZY); #else lib = LoadLibrary(s); #endif if (!lib) { printf("cannot open library '%s'\n", s); return; } #ifndef WIN32 fun = (void (*)(void))dlsym(lib, "run"); #else fun = (void (*)(void))GetProcAddress((HINSTANCE)lib, "run"); #endif if (fun == NULL) { printf("cannot load function run\n"); } else { fun(); } #ifndef WIN32 dlclose(lib); #else FreeLibrary((HINSTANCE)lib); #endif } #ifdef __cplusplus } #endif
Header files
It was all an implementation, and now the header files.
Here is the interface of the module loading dynamic libraries for the main module:
loader.h
#ifndef LOADER_H #define LOADER_H #ifdef __cplusplus extern "C" { #endif extern void loadRun(const char * const s); #ifdef __cplusplus } #endif #endif
Here is the loader interface for dynamic libraries loaded by it (list of functions that dynamic libraries can use in the main program):
functions.h
#ifndef FUNCTIONS_H #define FUNCTIONS_H #ifdef __cplusplus extern "C" { #endif extern void printString(const char * const s); #ifdef __cplusplus } #endif #endif
As you can see, there is only one printString function for an example.
Bootloader compilation
An example of a non-distributive compilation (in the case of Windows, the compiler option just needs to add -DWIN32):
$ gcc -Wall -c main.c $ gcc -Wall -fPIC -c loader.c $ gcc -shared -o libloader.so loader.o -ldl $ gcc main.o -ldl -o run -L. -lloader -Wl,-rpath,.
Distributive compilation from non-distributive differs in that in the distributive case, dynamic libraries are searched in / usr / lib and are of the form lib $ (NAME) .so. $ (VERSION), in the case of non-distributive compilation they are called lib $ (NAME) .so, and are searched for in the program launch directory.
Now let's see what we did after compilation:
$ nm run | tail -n 2 U loadRun 08048504 T main $ nm libloader.so| tail -n 4 000005da T loadRun 000005ac T printString U printf@@GLIBC_2.0 U puts@@GLIBC_2.0
Here we see that the functions marked as U are searched in external dynamic libraries, and the functions marked as T are provided by the module. This is a binary program interface (ABI).
Dynamic libraries
We now proceed to the description of the dynamic libraries themselves.
C language library
Here is an example of the simplest C library:
lib.c
#include "functions.h" #ifdef __cplusplus extern "C" { #endif void run(void) { printString("Hello, world!"); } #ifdef __cplusplus } #endif
Here, the
extern “C” {} environment is needed in order for our program to be compiled with a C ++ compiler, such as g ++. Simply in C ++, you can declare functions with the same name, but with a different signature, respectively, in this case, the so-called
decoration of function names is used, that is, the function signature is written to the ABI. The
extern "C" {} environment is required so that this decoration is not used (especially since this decoration depends on the compiler used).
Compilation
$ gcc -Wall -fPIC -c lib.c $ gcc -shared -o lib.so lib.o
ABI:
$ nm lib.so | tail -n 2 U printString 0000043c T run
Run:
$ ./run lib.so String from library: Hello, world!
If we remove the
extern "C" {} environment in our module and compile it with g ++ instead of gcc, we will see the following:
$ nm lib.so | grep run 0000045c T _Z3runv
That is, as expected, the ABI library has changed, now our loader will not be able to see the run function in this library:
$ ./run lib.so cannot load function run
Pascal Library
As we saw above, in order for our loader to see functions in dynamic libraries created by the C ++ compiler, we had to supplement our code with
extern "C" {} inserts, despite the fact that C / C ++ are compilers and related languages. What can we say about the compiler FreePascal completely different language - Pascal? Naturally, even here we cannot do without additional gestures.
First we need to learn how to use exported C functions for dynamic libraries. Here is an example of a similar C / C ++ header file in Pascal:
func.pas
unit func; interface procedure printString(const s:string); stdcall; external name 'printString'; implementation end.
Here is an example of the module itself in the Pascal language:
modul.pas
library modul; uses func; procedure run; stdcall; begin printString('Hello from module!'); end; exports run; begin end.
Compilation
$ fpc -Cg modul.pas Free Pascal 2.5.1 [2011/02/21] i386 Copyright (c) 1993-2010 by Florian Klaempfl : Linux for i386 modul.pas libmodul.so /usr/bin/ld: warning: link.res contains output sections; did you forget -T? /usr/bin/ld: warning: creating a DT_TEXTREL in a shared object. 13 p, 6.6
We look ABI of the resulting library:
$ nm libmodul.so U printString 000050c0 T run
As you can see, nothing superfluous, however, the warning ld during compilation is alarming. We find in Google a
possible reason for the warning , it is associated with a compilation without a PIC (Position Independent Code - the code is not tied to a physical address), however in man fpc we find that our option -Cg should generate a PIC code, which is in itself strange, apparently Fpc does not fulfill its promises, or I do something wrong.
Now we will try to remove the piece
'printString' in our header file and see what the compiler will
produce now:
$ nm libmodul.so U FUNC_PRINTSTRING$SHORTSTRING 000050d0 T run
As you can see, decoration in FreePascal is of a completely different kind than in g ++ and also present.
When launched with this module, we get:
$ ./run libmodul.so ./run: symbol lookup error: ./libmodul.so: undefined symbol: FUNC_PRINTSTRING$SHORTSTRING
And with the correct module we get:
$ ./run libmodul.so String from library: Hello from module!
That's all, our task - the use of dynamic libraries written in various languages - we have achieved, and our code works both under Linux and under Windows (Mac doesn’t have it, so it didn’t look like that there). In addition, loaded libraries have the ability to use the functions provided in the main program to interact with it (that is, they are plug-ins of the main program).
The bootloader itself can be executed in a separate process so that these plugins can not do anything extra with the main program. Accordingly, the main program can be written in any other convenient programming language (for example, in Java or in the same C ++).
Literature