Linking OCaml binaries Last updated Feb 11, 2025

Linking is the most complex build action. Not only can the linker produce either native or bytecode executables, it can also emit C object files (from the same inputs) that can then be linked to and called by C programs. Furthermore, for bytecode linking, it can embed the vm interpreter (ocamlrun) in the emitted bytecode executable; alternatively, it can build a customized version of the interpreter containing the foreign libraries needed by the application code. In all cases, the user can choose from among standard, debug, and instrumented system runtimes, and where foreign libraries are involved, can choose between static and dynamic linking.

The OCaml tools can also emit a “packed” module, which can be considered a kind of binary output.

NB: design choices. One ocaml_binary with attributes to control output type, or one rule for each type, e.g. ocaml_executable, ocaml_pack, ocaml_obj, etc.

Invoking the compiler (ocamlc, ocamlopt) without -c or -a tells it to produce an executable (unless you also pass -output-obj or -output-complete-obj; see below).

Common options

  • -runtime-variant: Use one of three system runtimes (standard, debug, instrumented). See also 5.3. 25 Building with the instrumented runtime.

  • -noautolink. "When linking .cma/.cmxa libraries, ignore -cclib and -ccopt options potentially contained in the libraries (if these options were given when building the libraries)."

  • -output-obj. "Cause the linker to produce a C object file instead of a native/bytecode executable file."

  • -output-complete-obj. "Same as -output-obj option except the object file produced includes the runtime and autolink libraries."

  • -linkall?

Native binaries

Executables

This is the default. “The output of the linking phase is a regular Unix or Windows executable file. It does not need ocamlrun to run.”

  • -nodynlink “Allow the compiler to use some optimizations that are valid only for code that is statically linked to produce a non-relocatable executable. The generated code cannot be linked to produce a shared library nor a position-independent executable (PIE). Many operating systems produce PIEs by default, causing errors when linking code compiled with -nodynlink. Either do not use -nodynlink or pass the option -ccopt -no-pie at link-time.” (5.3.16 (ocamlopt))

C objects

  • -output-obj. “Cause the linker to produce a C object file instead of a native/bytecode executable file.”

  • -output-complete-obj. “Same as -output-obj option except the object file produced includes the runtime and autolink libraries.”

Bytecode binaries

Executables

This is the default. “The output of the linking phase is a file containing compiled bytecode that can be executed by the OCaml bytecode interpreter: the command named ocamlrun.”

  • default: “[T]he linker produces bytecode that is intended to be executed with the shared runtime system, ocamlrun.” If the application code depends on foreign (C/C++) libraries, the linker will record their names in the bytecode executable, and ocamlrun will dynamically load and link them when the program is launched. For more information, see 5.3.15.3 Dynamic loading of shared libraries.

  • -custom. “[T]he linker produces an output file that contains both the runtime system and the bytecode for the program.” That is, effectively embeds ocamlrun in the output, which can be executed directly. If the application code depends on foreign (C/C++) libraries, the linker will statically link them into the executable.

    Link in “custom runtime” mode. In the default linking mode, the linker produces bytecode that is intended to be executed with the shared runtime system, ocamlrun. In the custom runtime mode, the linker produces an output file that contains both the runtime system and the bytecode for the program. The resulting file is larger, but it can be executed directly, even if the ocamlrun command is not installed. Moreover, the “custom runtime” mode enables static linking of OCaml code with user-defined C functions, as described in chapter 22.
    
        Unix:  Never use the strip command on executables produced by ocamlc -custom, this would remove the bytecode part of the executable.
    
        Unix:  Security warning: never set the “setuid” or “setgid” bits on executables produced by ocamlc -custom, this would make them vulnerable to attacks.
  • -use-runtime. “Generate a bytecode executable file that can be executed on the custom runtime system runtime-name, built earlier with ocamlc -make-runtime runtime-name.” For more information see 5.3.22.1.6 Building standalone custom runtime systems

  • -output-complete-exe. “Build a self-contained executable by linking a C object file containing the bytecode program, the OCaml runtime system and any other static C code given to ocamlc. The resulting effect is similar to -custom, except that the bytecode is embedded in the C code so it is no longer accessible to tools such as ocamldebug. On the other hand, the resulting binary is resistant to strip.”

C objects

  • -output-obj. “Cause the linker to produce a C object file instead of a native/bytecode executable file.”

  • -output-complete-obj. “Same as -output-obj option except the object file produced includes the runtime and autolink libraries.”

Foreign library linkage

“Foreign library” means a static (.a) or shared (.so, .dll on Windows) library, whether produced from C/C++ code or some other language, e.g. Rust.

The three linkage strategies

  • Build-time: static libs. All links are resolved at build-time by the OCaml linker. The OCaml linker is actually a linker driver; it does not do the job itself, but delegates it to the linker of the CC toolchain. You can see the command it uses by passing -verbose.

  • Load-time: shared objects. Links are resolved by the system loader-linker when the program is launched. This is conventionally called dynamic linkage, but it’s only partially dynamic. At build-time, the OCaml (i.e. CC) linker partially resolves links, which are then fully resolved by the system linker at load-time.

    For native executables, this strategy requires that both the OCaml linker and the system loader be informed of the location of the DSO! For example, on the mac, we need to pass -Lpath/to/lib/dir so that the OCaml linker (i.e. the CC linker) can find the DSO at build time, and we also need to pass -rpath path/to/lib/dir so that the dynamic linker (dyld) can find the DSO at load/launch time. (In both cases, use -ccopt to pass the parameter to OCaml.)
  • Run-time: shared objects. Linkage is controlled by the application itself at run-time, using dlopen, dlsym, etc.

So there are three ways to tell OCaml about foreign libraries:

  • Static libraries:

    • Native binaries: pass directly on the command line (e.g. libfoo.a);

    • Bytecode binaries (-custom mode):

      • pass directly on the command line;

      • pass using -cclib -lname

        • If the library is not located in one of the standard library directories, also pass -ccopt -Ldir

  • Shared libraries:

    • Native binaries:

      • the manual says pass on command line (e.g. libfoo.so) but this does not work

      • pass on cmd line using -cclib -lfoo -ccopt -Lpath/to/libfoo

    • Bytecode binaries: (default mode)

      • pass directly on command line

      • “a library named dllname.so (respectively, .dll) residing in one of the standard library directories can also be specified as -dllib -lname.”

      • -dllpath dir may be used to add a directory dir to the run-time search path for shared C libraries. “The -dllpath option simply stores dir in the produced executable file, where ocamlrun can find it and use it as described in section 15.3.”

      • Question: can we pass shared libs in -custom mode?

Manual:

For bytecode executables that depend on foreign libraries:

  • use static archives or dynamic shared objects (-cclib, -dllib, etc.)

  • use a standard, custom (-custom), or user-define application runtime (-use-runtime)

We have two cc link options:

  • dynamic. The default; means dynamic load & link, not just linking a shared lib.

  • static. build-time loading of static (.a) lib. (Or shared lib?) Enabled by -custom flag.

I.e. dynamic means run-time linkage; static means build-time linkage.

Scenarios:

  • case: no cclib deps

  • case: static (.a) cclib deps

    • subcase: link dynamic

    • subcase: link static

  • case: shared (.so) cclib deps

    • subcase: link dynamic

    • subcase: link static

  • case: both static and shared cclib deps

    • subcase: link dynamic

    • subcase: link static

Build-time static linkage

CLI Options

  • -cclib -llibname

    Pass the -llibname option to the C linker when linking in “custom runtime” mode (see the -custom option). This causes the given C library to be linked with the program at build-time.

    The library can be either an archive (.a) or a shared lib (.so) (?)
  • -ccopt option

    Pass the given option to the C compiler and linker. When linking in “custom runtime” mode, for instance -ccopt -Ldir causes the C linker to search for C libraries in directory dir. (See the -custom option.)

  • -cc ccomp

    Use ccomp as the C linker when linking in “custom runtime” mode (see the -custom option) and as the C compiler for compiling .c source files. When linking object files produced by a C++ compiler (such as g++ or clang++), it is recommended to use -cc c++.

    OBazl never uses the OCaml compiler to compile C/C++ code, so this option is not relevant for OBazl users.

Load-time shared-object linkage

No special requirements, just pass the .so file on the command line, just as for .a files.

For bytecode executables:

CLI Options

  • -dllib -llibname

    Arrange for the C shared library dlllibname.so (dlllibname.dll under Windows) to be loaded dynamically by the run-time system ocamlrun at program start-up time.

  • -dllpath dir

    Adds the directory dir to the run-time search path for shared C libraries. At link-time, shared libraries are searched in the standard search path (the one corresponding to the -I option). The -dllpath option simply stores dir in the produced executable file, where ocamlrun can find it and use it as described in section 15.3.

Diagnostics

MacOS

Linker options (man ld); pass with -ccopts, with or without -verbose:

  • -t Logs each file (object, archive, or dylib) the linker loads. Useful for debugging problems with search paths where the wrong library is loaded.

  • -why_load Log why each object file in a static library is loaded. That is, what symbol was needed. Also called -whyload for compatibility.

  • -why_live symbol_name Logs a chain of references to symbol_name. Only applicable with -dead_strip . It can help debug why something that you think should be dead strip removed is not removed. See -exported_symbols_list for syntax and use of wildcards.

  • -print_statistics Logs information about the amount of memory and time the linker used.

  • -order_file_statistics Logs information about the processing of a -order_file.

  • -map map_file_path Writes a map file to the specified path which details all symbols and their addresses in the output image.