Skip to content
VLSI Mentor

C Part II · Module 25 · Debugging DPI-C and Simulator Integration

Shared Library Loading Failures

Between a library that links and a simulation that runs there is a step where something goes looking for a file, and then for a name inside it. The messages here are unusually good — a failed open lists every path it tried, in order, with a reason for each. And one finding worth carrying: a library with an unresolved dependency of its own is either a named diagnosis before anything runs or a silent crash with exit 139 and no message, decided entirely by a resolution mode that nobody chose deliberately.

Advanced21 min readCPart IIVerificationDPI-C

Part II · Module 25 · Chapter 25.7 · Shared Library Loading Failures

1. The Step Between Building and Running

Chapter 25.6 ended at a library that links. That library still has to be found and its names located before any simulated time exists, and those are two separate operations that fail in two distinct ways.

A simulator loading a DPI library does what any program does: it opens the file, then looks up names in it. Everything below is measured through that same pair of operations, so the messages are the platform's rather than any particular simulator's.

2. The File Was Not Where It Looked

Measured:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  dlopen FAILED: dlopen(./nosuch.dylib, 0x0002): tried:
    './nosuch.dylib' (no such file),
    '/System/Volumes/Preboot/Cryptexes/OS./nosuch.dylib' (no such file),
    '/usr/lib/./nosuch.dylib' (no such file, not in dyld cache),
    '/private/tmp/…/nosuch.dylib' (no such file), …
  exit 1

This is the best diagnostic in the module, and it is routinely skimmed. It lists every path tried, in order, with a reason for each — which answers the purpose's question directly: what was it looking for, and where.

Reading that list is faster than reasoning about search paths, because it is not a description of the policy but a record of what happened. If the directory you expected is absent from the list, the problem is the search path. If it is present and says "no such file", the problem is the file.

3. It Opened, and the Name Was Not in It

Measured:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  dlopen ok
  dlsym("crc8_steps") FAILED: dlsym(0x6c3f0a40, crc8_steps): symbol not found
  exit 1

A different failure with a different fix. The file is right; the name is not in it.

Two causes, and Chapter 25.6 supplies the discriminator for both:

  • the symbol is absent — the function was never built into this library
  • the symbol is present under a mangled name — it was compiled as C++
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
nm -gU libmodel.dylib | grep -i mysym

Note the connection back to Chapter 25.2 §3: "not defined by any module" from the simulator covers both of this chapter's first two failures plus a registration mistake. Three causes, one message — and nm plus the loader's own output separate all three without editing anything.

4. The Finding: The Same Defect, Named or Silent

A library can link, exist, open — and still carry an unresolved reference of its own, to something that was expected to be supplied later.

Measured, resolved at load:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  mode: RTLD_NOW (resolve at load)
  dlopen FAILED: dlopen(./libdep.dylib, 0x0002): symbol not found in flat namespace '_helper'
  exit 1

A clean, named diagnosis before anything runs. _helper is missing; go and find it.

Measured, the same library, resolved lazily:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  mode: RTLD_LAZY (resolve on first use)
  dlopen ok
  dlsym ok -- now CALLING it
  exit 139

No message at all. The open succeeded, the lookup succeeded, and the process died the moment the call was made.

The same defect is a named error or a silent crash, decided entirely by a resolution mode.

That is worth stating as a decision rather than a detail: immediate resolution is a debugging choice, not a performance one. It converts a Chapter 25.8 crash — exit 139, no output, a backtrace to acquire — into a sentence naming the missing symbol.

Four load-time failures and what each one reports. A library that is not where the loader looks fails at the open call and, usefully, lists every path it tried in order. A library that opens but does not contain the name reports a symbol-not-found message naming the symbol, which means the file is right and the build or the spelling is wrong. A library with an unresolved dependency of its own behaves completely differently depending on the resolution mode: under immediate resolution it fails at the open call and names the missing symbol, while under lazy resolution it opens successfully, the symbol lookup also succeeds, and the process dies with exit 139 and no message at all when the call is finally made. The same defect is therefore a named diagnosis or a silent crash depending only on a flag nobody chose deliberately. And the bottom line is that a load-time message is about files and names rather than values, so the first move is to confirm what the library actually exports.The failureafter the build, before anysimulated timeWhat it reportsmeasuredWhat it narrows toThe file is not where itlookeda path, a name, a build that didnot runit lists every path ittriedmeasured: in order, with a reasonfor eachthe search path, not yourcodethe most useful message in themoduleIt opened, the name is notin itthe file is rightsymbol not found, namingthe symbolmeasuredthe build or the spellingconfirm with nm before editingeitherIts own dependency ismissing, resolved at loadthe immediate modeopen fails, naming themissing symbolmeasured: symbol not found in flatnamespacea library the library needsa clean diagnosis before anythingrunsThe same library, resolvedlazilythe deferred modeopen succeeds, lookupsucceeds, then exit 139measured: no message of any kindthe identical defectnamed or silent, decided by a flagnobody choseA load-time message is about files and names, never about valuesmeasured — so the first move is to confirm what the library exports, and the second is to prefer immediateresolution, which converts a silent crash into a named symbol12
Figure 1 — four load-time failures and what each reports. The first two are the good cases and they are genuinely good: a missing file produces a list of every path tried with a reason for each, and a missing name produces a message naming the symbol. The third and fourth rows are the same library with the same defect, differing only in when symbols are resolved — immediately, giving a named missing symbol before anything runs, or lazily, giving a clean open, a clean lookup, and then exit 139 with nothing printed. The bottom bar is the practical consequence: resolution mode is a debugging decision, because one setting turns the module's worst symptom into its best one.

5. The Procedure

Four steps, each cheap, in an order that does not require guessing.

One — does the file exist where the loader looked? Read the list in §2 literally. Do not reason about LD_LIBRARY_PATH or an equivalent until you have checked whether the directory you expect appears in the list at all.

Two — does the library export the name? nm -gU. Chapter 25.6 §3: absent means a build problem, mangled means C++.

Three — does the library resolve its own dependencies? §4. If the symptom is exit 139 with no message, load it with immediate resolution once and see whether a name appears.

Four — only now consider registration. Chapter 25.2's $-function was never registered, which produces the same simulator message as a library that never loaded — and the three previous steps have now ruled that pair apart.

The ordering matters because step four is the only one that requires reading your own C, and steps one to three cost one command each.

6. The Misconception: The Library Is Right There

"The .so is in the same directory as the command I'm running. The path can't be the problem."

Maybe, and the message settles it in one read.

The loader's search is not "the current directory". Measured, a relative path produced attempts at the relative name, two system prefixes and an absolute expansion — and whether a bare filename is searched relative to the working directory at all is platform- and configuration-dependent.

So the useful move is not to reason about the policy but to read the list:

  • the directory you expect is not in the list → the search path is the problem; the file's location is irrelevant
  • it is in the list and says "no such file" → the file is not there under that name; check the build actually produced it, and check the extension
  • it is in the list with a different reason → an architecture mismatch or a permissions problem, and the reason says which

And the related mistake worth naming: a library that was rebuilt is not necessarily the library that was loaded. If an old copy exists earlier in the search order, everything will work and none of your changes will appear — which presents as "my fix had no effect" rather than as a loading problem at all.

The accurate position:

The loader tells you exactly what it tried and why each attempt failed. That list is a record, not a description, and reading it is faster and more reliable than reasoning about search-path rules — including for the case where the load succeeded and found the wrong file.

7. What You Should Be Able to Say Now

  1. Loading is two operations — find the file, then find the name — and they fail differently.
  2. A failed open lists every path tried, in order, with a reason — the most useful message in the module.
  3. Read that list literally before reasoning about search-path variables.
  4. Measured: a library that opens but lacks the name reports symbol not found, naming the symbol.
  5. Then nm: absent means a build problem, mangled means C++ (25.6 §3).
  6. The simulator's "not defined by any module" covers three causes — no library, no symbol, no registration — and steps 1 to 3 separate them.
  7. The finding: an unresolved dependency is a named error under immediate resolution and a silent exit 139 under lazy resolution.
  8. So resolution mode is a debugging decision, not a performance one.
  9. Procedure: file found? · name exported? · dependencies resolved? · then registration.
  10. Steps 1 to 3 cost one command each and none requires reading your own C.
  11. A rebuilt library is not necessarily the loaded one — an older copy earlier in the order presents as "my fix had no effect".
  12. Read what the tool printed, not what you expected — the authoring note in §5 is this chapter's own example.

8. Exercises

  1. Try to load a library that does not exist. Read the path list — is the directory you expected in it?
  2. Load a real library and ask for a name it does not contain. How does the message differ from the previous one?
  3. Build a library with an unresolved dependency and load it twice, with immediate and with lazy resolution. Record both outcomes.
  4. Which of the two outcomes would you rather debug, and what does that imply about the flag?
  5. Run nm -gU on your DPI library. Is every name the simulator will ask for present?
  6. Put an old copy of your library earlier in the search order. What symptom does that produce, and how would you have diagnosed it?
  7. Your simulator reports "not defined by any module". Write the three commands that split the three causes.
  8. §5 describes a narration that contradicted the output. Find an assumption of your own that a tool has already disproved in a log you have.

9. Summary

  • Two operations: find the file, find the name.
  • A failed open lists every path tried, with a reason — read it literally.
  • Measured: a missing name gives symbol not found, naming it; then nm decides build or C++.
  • "Not defined by any module" covers three causes, separated by three cheap commands.
  • The finding: an unresolved dependency is named under immediate resolution and exit 139 with no message under lazy.
  • Resolution mode is a debugging decision.
  • Procedure: file · name · dependencies · registration — in that order, because only the last needs your source.
  • A rebuilt library may not be the loaded one — the symptom is "my fix did nothing".
  • Read what the tool printed — §5 is this chapter's own instance of failing to.

Immediate resolution turns one silent exit 139 into a named symbol. Every other exit 139 remains, and those are the ones that happen once the library is loaded and running, where no loader message exists and the output has already been lost.

Where this fits

Part of the C Programming curriculum.