Haiku's runtime_loader: ELF Dependencies, Search Paths, and Failure Boundaries
Trace how Haiku's runtime_loader resolves ELF dependencies through DT_RUNPATH, DT_RPATH, image-type paths, ABI subdirectories, and preload variables.
When a Haiku program starts but reports that a shared object cannot be found, the shell’s executable PATH is only one part of the investigation. Haiku’s runtime loader distinguishes executable images, shared libraries, and add-ons; each image type has its own search-path variable and default locations. ELF metadata can add a path before that fallback, and compatibility builds can add an ABI-specific directory at selected system library locations.
This guide follows the implementation in Haiku’s runtime-loader source at the commit linked below. It describes that source revision, not a guarantee that every released Haiku image has identical behavior. It also describes Haiku, not Linux’s ld.so: in the inspected implementation ordinary shared-library lookup uses LIBRARY_PATH, not Linux’s LD_LIBRARY_PATH convention.
Start with the image type
The loader’s search_path_for_type() maps image types to distinct environment variables:
| Image type | Haiku search variable | Typical role |
|---|---|---|
B_APP_IMAGE |
PATH |
Locating an application image by name |
B_LIBRARY_IMAGE |
LIBRARY_PATH |
Locating a shared library dependency |
B_ADD_ON_IMAGE |
ADDON_PATH |
Locating a loadable add-on |
Those names may look familiar, but their scopes are not interchangeable. Finding an executable through PATH does not prove that its DT_NEEDED libraries can be found through LIBRARY_PATH, and an application plugin searched as an add-on follows a different branch from a library dependency.
The distinction also explains why changing a shell variable can appear to fix one launch path but not another. Before changing anything, identify whether the failure names the main executable, a library recorded as a dependency, or a plug-in loaded later by the application. A plug-in may not appear in the executable’s initial dependency list at all; the application can request it after startup.
Set variables replace the default lookup list
In the inspected implementation, if the relevant variable exists, search_path_for_type() returns its value. The source contains a TODO noting that standard system paths should always be searched after the paths specified by these variables, but that is not what this branch currently does. If the variable is absent, the loader supplies defaults instead. For libraries, those defaults include the application-local library directory followed by the non-packaged system library directory and the system library directory; the analogous app and add-on branches use their corresponding directories.
That is a consequential diagnostic detail: adding one custom directory to LIBRARY_PATH is not necessarily an additive operation. A value that omits a needed system directory can make a formerly resolvable dependency disappear. A present-but-empty value is also different from an unset variable in the source: it is a value, not a request to select the built-in defaults. Do not copy a Linux recipe that prepends a directory to LD_LIBRARY_PATH and assume it has the same effect on Haiku.
The loader has a separate path branch when its disable_user_addons setting is enabled. In that branch the environment and normal defaults are bypassed in favor of narrower paths that omit user or non-packaged locations. The source comment connects this flag with safe-mode settings, but the important diagnostic fact is the branch’s actual search policy; do not treat every boot or packaging mode as equivalent without checking the target system.
ELF paths are consulted before the image-type fallback
While loading dependencies, elf.cpp reads each DT_NEEDED entry and passes the requesting image’s path plus its ELF path metadata into load_image(). The implementation obtains DT_RUNPATH; if that is absent, it obtains DT_RPATH. open_executable() tries the selected run path or, when absent, the legacy rpath before it evaluates the image-type search list. The ordering is therefore a source-level behavior worth checking before modifying global environment configuration.
Haiku’s implementation treats those values as colon-separated directory lists. It also recognizes an optional semicolon separating two lists and searches the first before the second. For a path-list entry beginning with %A, the loader substitutes the directory containing the main program. An entry beginning with $ORIGIN is expanded relative to the requesting executable or shared object. These are narrow prefixes implemented by this loader, not general shell expansion: do not assume an arbitrary variable embedded later in a directory name will be interpreted.
If a dependency name already contains a slash, the loader first tries that path. For a shared library, a failed attempt can receive another search through the usual lookup paths after its leading path is stripped. Add-ons have a different rule: an absolute add-on path that fails is not searched under unrelated directories. This distinction matters when a framework passes an explicit path rather than a bare library name.
ABI compatibility can affect the final lookup too. elf_load_image.cpp initializes a search subdirectory for specific compiler-ABI compatibility cases, and runtime_loader.cpp applies it only under recognized library locations. The source names x86 and x86_gcc2 in those conditional branches. This is not a universal rewrite of every directory or every architecture; use the code and the target’s actual ABI to interpret a trace.
Preload variables are a separate, high-impact mechanism
Haiku’s loader also recognizes LD_PRELOAD and LD_PRELOAD_ADDONS. The implementation parses each value as whitespace-separated paths. LD_PRELOAD images are incorporated into dependency loading before the image’s DT_NEEDED entries are processed. LD_PRELOAD_ADDONS is handled while a program image is being launched. Those are not ordinary search directories: they cause additional images to be loaded.
Treat these variables as executable-code controls. Do not test them with untrusted library files, set them globally to hide a packaging defect, or paste sensitive launch environments into a public bug report. A preload can change symbol resolution and program behavior; it is a deliberate debugging or runtime mechanism, not a harmless way to make a directory visible.
A read-only first-pass investigation
Capture the environment of the process that fails, not only the values in an unrelated login shell. On a system with the relevant tools installed, these commands gather useful evidence without changing loader policy:
env | grep -E '^(PATH|LIBRARY_PATH|ADDON_PATH|LD_PRELOAD|LD_PRELOAD_ADDONS)='
readelf -d /path/to/program | grep -E 'NEEDED|RPATH|RUNPATH'
readelf availability depends on the installed toolchain. Its output identifies dynamic tags; it does not show every later plug-in request made by an application. For a missing dependency, record the exact name in the error, the requesting object, whether that object contains DT_RUNPATH or DT_RPATH, and whether the relevant Haiku path variable is absent, empty, or populated. Then compare that evidence with the source branch for the target release or build.
If you need to test a temporary search-path change, scope it to one command and include the complete directories required by that test, because the configured value may replace the defaults. Preserve the original value for comparison and restore it after the experiment. Avoid a persistent system-wide edit until the package or application owner has decided which library version is intended to win.
Test the boundaries, not just one successful launch
A packaging check can cover a bare DT_NEEDED name found in a private library directory, a dependency found through DT_RUNPATH, a requesting object that relies on the fallback list, a missing library, and an add-on loaded by explicit path. Include a case where the image-type path variable is unset and one where it is set, so a future loader change to append system defaults cannot silently alter your assumptions. For ABI-specific deployments, test the compiler compatibility branch that the binary actually uses.
Keep failure categories separate in logs: failure to open the main executable, failure to locate a needed library, ABI or symbol mismatch after the file loads, and an application-level plug-in lookup are different problems. Logging the requesting image and the resolved object path makes shadowing visible; logging only the final missing name often does not.
The implementation provides a useful lookup contract, but it cannot prove that a found library is ABI-compatible, safe, or the version the application expects. It cannot prove that a plug-in’s own dynamic lookup succeeds later, either. Verify the target release, package layout, and application behavior before changing paths, and use a native Haiku test for claims about actual runtime execution.
Related:
- Haiku Kernel Image APIs: Add-On Loading and Symbol Lifetimes
- Haiku’s BRoster and registrar: How Applications Are Found and Launched
Sources: