Dynlink works in native mode but not in bytecode?

I have the peculiar situation that i can successfully compile and run the native code version of my dynlinked code but not the bytecode version. Who has an idea what I am doing wrong?!

I am using the following setup to dynamically load a module M_dyn:

The relevant source files:

base.ml
m_setup.ml
run.ml
run1/m_dyn.ml
run2/m_dyn.ml
...

They contain:
base.ml: general library code used throughout.
m_setup.ml: common parameter settings for all runs
run<i>/m_dyn.ml:

open Base
include M_setup
(*...specific parameter settings for run <i>*)

run.ml:

open Base
let subdir = Sys.argv.(1)
Dynlink.(
      loadfile (Filename.concat subdir (adapt_filename "m_dyn.cmo")))
(* now run stuff with the loaded parameter settings *)

I am using ocamlbuild for building (no time for conversion to dune right now).
I can successfully build run1/m_dyn.cmxs and run.native and then run ./run.native run1.
I can also build run1/m_dyn.cmo and run.byte, but on running ./run.byte run1 i get the error:

Dynlink error: error while linking _build/run1/m_dyn.cmo.
Reference to undefined global `M_setup'

(edit: this is a simplified version of my actual example, with what i hope are the relevant bits. both m_dyn and run are compiled against the same interface, which is why the native version does work fine - i did not explicitly list that interface file. for the sake of the example, let’s say the interface is included in base.ml)

Did you make sure to link M_setup into run ? You may need to pass -linkall to make sure it is linked in.

hmm. i tried adding the tag linkall for all files to ocamlbuild’s _tags file by including the line
<**/*>: linkall.
this did not help. also, why would it work for native compilation without linkall?

Hard to say without taking a look at the details; you may have more luck if you can come up with a smaller reproduction and possibly the explicit commands you are using to build your plugins and the main program.

Dynlinking is a quite complex topic in OCaml and still unsound (as of OCaml 4.07 and below). And if you will dig further you will even find out that OCaml has 3 dynlinking facilities that differ in their behavior, the toplevel linker is different from the bytecode lifter and both are different from the native linker.

The good news is it is possible to implement a sound and robust linking facility in OCaml (even in the presence of the inherited unsoundness). We have implemented it in BAP, and other projects have their own implementations with varying flexibility and safety.

In BAP the support for dynamic linking is split into three parts,

  1. we have the bapbuild tool which is an ocamlbuild plugin that knows how to properly build plugins. It is shipped as a tool, but could be also used as a normal plugin, since it is packed into a library. Our plugins are zip-files that contain cmxs and cma so that they could be used in both three loaders, as well as meta information (the reason why meta is needed will be given below) and optional dependencies, which makes a plugin independent off the environment. The bapbuild tool is also clever in that it knows how to handle broken packages, that forget to ship cmxs or cma or even both.

  2. The loading part is implemented by the bap-plugins library. This part tracks carefully which compilation units are already linked into the process image. This is why we need the meta information in plugins, that describe which compilation units are already linked into the shared object and which are required.

  3. Finally a myocamlbuild.ml plugin is required for the host program (i.e., the program that loads plugins), since usually the host program is by itself composed of some compilation units and we should be sure that the loaded plugin doesn’t try to link into the program body any compilation unit that is already there. We don’t want to parse the ELF data structure of the host program to figure out which units are already linked since it is not portable and fragile, so we need some support from the build system. In particular we use the ocamlfind.dynlink library that stores this information in a special data structure inside the binary, and extend it to support internal modules (since a plugin and a host program could be built from the same source tree, so that ocamlfind won’t notice that they are linked).

So if you will follow carefully our implementation and ensure that you have all three ingredients in this of that form, you will be able to load your shared code using all three OCaml linkers. If stuck, don’t hesitate to ask questions. This is all not to scare you away from dynlinking. BAP wouldn’t be possible without it, for example.

2 Likes

wow, this is quite more involved than i thought.

in the meantime i got some support from the dune people; i ended up converting the project to dune, and then got both native and byte code to work. however, i’m not sure why, and it would probably break if i tried to extend it further – for now it’s ok though. i did try to make sure to not include libraries twice from plugin and host program.