Check Mono DllImport Dependencies with mono-shlib-cop

mono-shlib-cop catches a misspelled native library or a missing entry point before a user ever sees a DllNotFoundException.

Allow about fifteen minutes for a small assembly and its first check. You need Mono's development tools, including mono-shlib-cop and a compiler such as mcs. The examples use Mono 6.8.0.105 from Ubuntu package mono-devel, version 6.8.0.105+dfsg-3.6ubuntu2. Other Mono releases may format diagnostics differently.

1. Confirm the installed tool

Start with ordinary, read-only checks. No elevated privileges needed:

$ command -v mono-shlib-cop
/usr/bin/mono-shlib-cop
$ dpkg-query -W -f='${Package} ${Version}\n' mono-devel
mono-devel 6.8.0.105+dfsg-3.6ubuntu2

The manpage describes the syntax as mono-shlib-cop [OPTIONS]* [ASSEMBLY-FILE-NAME]*. The useful option is -p or --prefixes=PREFIX, which supplies a Mono installation prefix when the default prefix detection is not suitable. Normal use only needs one or more assembly filenames.

Checkpoint: do not use --help as a discovery step with this installed build. It is treated as an assembly filename and produces an invalid-image diagnostic. The command's exit status is not a reliable pass or fail result, so read its diagnostics and keep them in your build log.

2. Build a small assembly to inspect

Use a temporary directory so the test does not alter your project. This assembly has one valid native entry point and one deliberately missing symbol:

$ work=$(mktemp -d /tmp/mono-shlib-cop.XXXXXX)
$ cd "$work"
$ cat > Check.cs <<'EOF'
using System.Runtime.InteropServices;
class Check {
    [DllImport("libc.so.6")]
    private static extern int getpid();
    [DllImport("libc.so.6")]
    private static extern void DefinitelyNotASymbol();
    static void Main() { }
}
EOF
$ mcs -out:Check.exe Check.cs

For your own project, skip this test assembly and pass the compiled .exe or library instead. The checker examines the assembly metadata, not the surrounding C# control flow.

3. Run the checker and capture diagnostics

Run the command as the ordinary build user:

$ mono-shlib-cop "$work/Check.exe" 2>&1
error: in Check.DefinitelyNotASymbol: library `libc.so.6' is missing symbol `DefinitelyNotASymbol'
$ printf 'process status: %s\n' "$?"
process status: 0

The wording and quoting can vary, but the missing symbol should be visible. This installed version returns status 0 after printing that error, so a script must inspect the output or apply its own policy. A successful process status means the checker ran; it does not mean the assembly passed.

Checkpoint: treat any line beginning with error: as a release-blocking finding until the assembly or its deployment configuration has been reviewed.

4. Separate library, symbol and platform findings

Platform-specific imports need extra care. The manpage says the checker examines every DllImport entry and ignores the runtime branch around it, so a Windows-only kernel32.dll import can produce an error during a Linux check even when Unix code would never call it. Keep that finding visible, but judge it against the platforms your assembly genuinely supports rather than deleting a valid platform path.

Do not hide a finding by installing random development packages: that can make a build host look healthier than a clean end-user machine.

5. Handle an unversioned Unix library name

The manpage warns about names ending in .so. On many Unix systems, libc.so is a linker development file while the runtime library has a version such as libc.so.6. A DllImport("libc.so") reference may work on a development machine and fail after deployment. The checker can also report this as a warning when the file exists locally.

The preferred fix for portable managed code is an assembly configuration file beside the assembly. Its dll value must exactly match the string in DllImport, while target names the library Mono should load:

<configuration>
    <dllmap dll="libc.so" target="libc.so.6" />
</configuration>

For an assembly named Check.exe, save that XML as Check.exe.config. The configuration is read for the assembly name, so putting it in an unrelated directory or naming it after the source file will not apply it.

Verify the pair without changing system files:

$ printf '%s\n' '--- Check.exe.config ---'
$ sed -n '1,20p' "$work/Check.exe.config"
$ mono-shlib-cop "$work/Check.exe" 2>&1

There is no service restart or persistent system change in this workflow. To undo the temporary test, remove only the directory named by $work after checking that it contains no project files. Do not use a broad recursive deletion command for a real source directory.

6. Use a prefix only when you can identify it

--prefixes=PREFIX makes the checker read PREFIX/etc/mono/config. Use a prefix containing that path, not the directory holding one arbitrary Mono executable. A host whose Mono configuration is under /etc/mono/config can test the root prefix:

$ test -r /etc/mono/config
$ mono-shlib-cop --prefixes=/ "$work/Check.exe" 2>&1

If the prefix is wrong, the program can fail with a configuration-file or directory error before it even inspects the assembly. Fix the prefix or omit the option and let the installed default locate its configuration.

Warning: sudo is not a normal requirement for this read-only check. Use elevated privileges only if your deployment policy specifically restricts access to the assembly or its configuration.

Done means