How to call a build function from C ++ dynamically?

REQUIREMENT . For a specific project, we have a unique requirement. The application supports an expression language that allows the user to define their own complex expressions that can be evaluated at runtime (many hundred times per second), and they must be executed at the machine level to improve performance.

WORK . Our parser perfectly converts the script into the appropriate assembly language routine. We tested it by statically linking the object files generated with our test program C, and they give the correct result. Since the client can change the script at any time, our program (at runtime) detects the change, calls the parser, which generates the appropriate build procedure. Then we call the assembler on the back to create the object code.

PROBLEM

How can you call this assembly procedure dynamically from a C ++ program (loader)?

We do not have to call the C ++ compiler to associate it with the bootloader, because the bootloader has already run other routines, and we cannot disable the bootloader, recompile and then run the new bootloader program.

I tried to find a solution on the Internet, but every time the results are dotted with a dynamic call to .NET assembly. Our application has nothing to do with .NET.

+4
source share
4 answers

Firstly, the generated plugin "(on Linux, my answer is focused on Linux, but can be adapted to Windows with some effort, you can use multi-platform frameworks such as Qt or POCO or Glib from GTK , and then all the plugin download options for à la dlopen with a common API that you can use on Windows, Linux, MacOSX, on Android):

  • generate C code (or assemblies) in some kind of /tmp/generated01.c file (you can even generate C ++ code using standard C ++ containers, but its compilation will be much slower, beware of name mangling , so we emit and use extern "C" functions, read C ++ dlopen mini HowTo ). See this answer for an explanation of why generating C is worthwhile (and might be better and more portable than generating assembler code).
  • start (using fork + execve + waitpid or just system ) compile this generated file into a common object /tmp/genenerated01.so by running gcc -fPIC -Wall -O /tmp/generated01.c -shared -o /tmp/generated01.so ; you almost need to get position-independent code , hence the -fPIC flag. If you use dlopen for your generated assembler code, you need to improve the assembler generator to emit PIC code.
  • dlopen what's new /tmp/generated01.so (use dynamic linker ), see dlopen (3) ; you could even remove now useless C generated file /tmp/generated01.c
  • dlsym corresponding characters to get pointers to the generated code, see dlsym (3) ; your application will simply call the generated code using these function pointers.
  • when you are sure that you do not need any functions from it, and that no call frame uses it, you could dlclose use this shared library of objects (but you can accept a leak of some address space without calling dlclose at all)

The above approach is noteworthy and can be used many times (my manydl.c demonstrates that you could dlopen a million different common objects) and is almost even compatible (even when emitting C code!) With the interactive Read-Eval-Print-Loop - on most modern desktops and laptops and servers, since most of the time the generated /tmp/generated01.c would be quite small (for example, a few hundred lines no more) to be very quickly generated and compiled (via gcc , etc.) ) I even use this in MELT for my REPL mode. On Linux, for this plugin approach, you usually need to associate the main application with -rdynamic (so dlopen -ed plugins can reference and call functions from the main application).


Then other approaches may be to use the Just-In-Time compilation library , for example p>

  • GNU lightning (which produces very slow machine code very quickly - this is a very short JIT emission time, but the generated code is slow because it is very non-optimized)
  • asmjit ; this is an x86-64 specification and allows the creation of individual x86-64 machine instructions.
  • GNU libjit is available for several platforms and offers an interpreter mode for other platforms.
  • LLVM (part of Clang / LLVM that can be used as a JIT library)
  • GCCJIT (new JIT library interface for GCC )

In general, the first elements of this list can issue JIT machine code quite quickly, but this code will not work as fast as compiling with gcc -fPIC -O1 or -O2 equivalent generated C code (but will usually run 2 to 5 times slower !); the last two elements (LLVM and GCCJIT) are compiler-based: they can optimize and emit efficient code by slower JIT code emission. All JIT libraries are capable (for example, dlsym for plugins) of providing function pointers for new JIT-built functions.

Note that there is a trade-off: some methods can quickly generate some machine code if you accept this generated code to work a bit slow later; other methods (especially GCCJIT or LLVM) spend time optimizing the generated machine code, so it takes more time to emit the machine code, but this code will work faster. You should not expect both (short generation time and fast lead time), since there is no free lunch.


I believe that it is practically not worth generating some assembler codes manually . You will not be able to generate very optimized code (because optimization is a very complex art, and both GCC and Clang have millions of source codes for optimization) if you do not spend many years on it. Using some JIT library is simpler, and "compiling" in C or C ++ is also quite simple (you leave the optimization load on the C compiler that you invoke).


You can also consider rewriting your application into some language using homoiconicity and metaprogramming (e.g. multi-stage programming ), such as Common Lisp (and many others, such as those that provide eval ). Its SBCL implementation always emits machine code ...

You can also embed an interpreter such as Lua - maybe even LuaJit - or Guile in your application. The main advantage of introducing an existing language is that there are resources (books, modules, ...) and a community of people who know them (creating a good language is difficult!). In addition, the built-in interpreter library is well-designed and probably well-debugged (since a lot is used), and some of them are quite fast (using bytecode ).

+3
source

As comments have already noted, LoadLibrary (Windows) and dlopen (Linux / POSIX) are by far the easiest solution. They are specifically designed for dynamically loading code. It is equally important that both of them allow unloading, and there are functions in order to then get the function entry point by name.

+2
source

You can do this dynamically. As an example, I'll take the linux example. Since your parser is working fine and generating machine code, you should be able to generate .so (for linux) or .dll for windows.

Then load the library as

 handle = dlopen(so_file_name, RTLD_LAZY); 

Next, find the function pointer

 func = dlsym(handle, "function_name"); 

Then you can execute it as func ()

One thing that you need to experiment (in case you do not get the desired result) is closed and the file file or dll opens (you only need to do it if necessary, otherwise it may slow down the performance).

+2
source

It looks like you can create the correct byte code. That way, you can simply ensure that you create position-independent code, write it to an executable piece of memory, and then call or create a stream in the code. The easiest way is to simply direct the pointer to the memory base into which you wrote the code as a function pointer, and then call it.

If you write your bytecode in order not to refer to different sections, but instead refer to offsets from the loaded database, "loading" the code is as simple as writing it to executable memory. You can make call / pop / jmp to find the code base after it starts.

Conversely, and perhaps the simplest solution would be to simply write the code as a function that expects arguments, so you could pass the code base and any other arguments, as with any other function, as long as you use the correct typedef for a function pointer, and the generated assembly handles the arguments correctly. If you avoid creating absolute clicks or links to absolute addresses, you should have no problem.

+2
source

All Articles