Tuesday, August 04, 2026

Firebird Adds an Optional Static fbclient — and Solves the Symbol-Collision Problem Properly

Firebird's client library, libfbclient / fbclient.dll, has always been distributed exclusively as a shared library. PR #9104 by Adriano dos Santos Fernandes, merged into the Firebird source tree on July 24, 2026, changes that: it introduces an optional, non-default static archive (libfbclient.a on POSIX, fbclient_static.lib on Windows) for applications that need to link the client library statically instead of loading it as a shared object/DLL.

Why static linking wasn't offered before

The blocker wasn't packaging — it was memory management. Firebird overrides the global C++ operators operator new, operator new[], operator delete, and operator delete[] (in src/common/classes/alloc.cpp) so that any bare, non-pool allocation inside Firebird is routed through its own internal memory pool.

In the shared library, this is harmless: a POSIX version script (and a Windows .def file) keeps those overridden symbols hidden, so an application linking against libfbclient.so or fbclient.dll keeps its own global allocator untouched.

A static archive has no such boundary. Its object files get merged directly into the host application at link time, so the linker would silently resolve the host's own bare new/delete calls to Firebird's internal definitions — hijacking the host application's memory allocation without any warning. That's a nasty, hard-to-diagnose class of bug, and it's exactly why a static build was never shipped.

The fix: rename every internal symbol at the archive level

Rather than hand-maintain a list of symbols to hide, the PR takes a systematic approach: every internal global symbol not part of Firebird's public API gets renamed with a __fbclient_ prefix as a post-processing step on the compiled archive. This covers the global operators, decNumber symbols, C++ mangled names, vtables, typeinfo, guard variables — everything — generated automatically from the existing export list rather than curated by hand.

On POSIX (Linux and macOS), this happens in two steps:

  1. Archive slimmingld -r with -u flags seeded from every symbol in builds/posix/firebird.vers pulls in only the archive members actually reachable from the public API, using cross-reference output (--cref on ELF, -map on Darwin) to identify and repack just those members.
  2. Symbol renamingobjcopy --redefine-syms (GNU objcopy on Linux, llvm-objcopy on macOS) renames every non-API global symbol to a __fbclient_-prefixed name, using a rename map generated automatically via nm --defined-only -g.

Both platforms share a single helper script, static_client.sh (autoconf-generated from builds/posix/static_client.sh.in), differing only in which cross-reference flag ld uses.

On Windows, the equivalent pipeline runs via a new fix_fbclient_static.bat: it parses builds/win32/defs/firebird.def for the public API, runs llvm-nm on every object file to collect defined symbols, builds the same kind of rename map (preserving __stdcall @n decoration where relevant), and applies it with llvm-objcopy --redefine-syms.

Regular pool-based allocation via FB_NEW / FB_NEW_POOL — the pattern used throughout the Firebird codebase — was never affected by any of this, since it never touched the global operators in the first place.

A side effect: no DllMain on Windows

A statically linked fbclient has no separate DLL module, so DllMain (previously in src/jrd/os/win32/ibinitdll.cpp) never runs for it. Most of what DllMain did turns out to degrade safely without it — config-root lookup falls back to the host executable's path, and loader-lock hazard checks simply don't apply to a statically linked EXE.

The one real gap was per-thread cleanup, which used to run only from DllMain's DLL_THREAD_DETACH notification. The PR closes it with a new ThreadCleanup class (src/common/classes/ThreadCleanup.h/.cpp) that uses Fiber-Local Storage (FlsAlloc / FlsSetValue / FlsFree) on Windows — mirroring the destructor semantics pthread_key_create already gives the POSIX build for free, and working identically whether the code ends up in a DLL or linked directly into the host binary.

Building it

POSIX:

make -C temp/debug TARGET=Debug client_static

produces <firebird>/lib/libfbclient.a from the same object set as the shared library (yValve + remote client + common). Most third-party dependencies (tommath, tomcrypt) still need to be linked separately by the consuming application; decNumber/libdecFloat is the one exception — it's small and vendored in-tree, so it's merged directly into the archive.

Windows:

builds\win32\make_all.bat CLIENT_ONLY=STATIC

builds yvalve as fbclient_static.lib under new DebugStatic / ReleaseStatic configurations, then runs the symbol-fixup script automatically.

Verification is straightforward with nm/llvm-nm: the public isc_* API stays global, while global operators (_Znwm, _ZdlPv, etc.) and internal symbols like decNumberFromString disappear from their original names entirely — replaced by __fbclient_-prefixed equivalents.

What's in the diff

25 files changed, +2168/-268 lines, across three commits:

  • .github/workflows/static-build.yml — new manual CI workflows to build and validate the static client on Linux, macOS, and Windows.
  • builds/posix/static_client.sh.in, builds/win32/fix_fbclient_static.bat, builds/win32/compile_static.bat — the new post-processing pipeline.
  • builds/win32/msvc15/*.vcxproj*, *.props — new Debug/Release static configurations wired into the Visual Studio solution.
  • src/common/classes/ThreadCleanup.h/.cpp — the new FLS-based thread cleanup, replacing the old DLL_THREAD_DETACH path.
  • src/yvalve/MasterImplementation.cpp, utl.cpp, src/common/os/win32/mod_loader.cpp, src/jrd/os/win32/ibinitdll.cpp — call sites updated for the DLL-less code path.
  • doc/README.StaticClient.md — new, thorough documentation covering the rationale, build steps, and verification commands for both platforms.

For any project embedding Firebird's client library directly into a host binary, this closes a real gap — and it does it without punting the symbol-collision risk onto the integrator.

Source: FirebirdSQL/firebird PR #9104

No comments: