Skip to content

docs: libgcc edge-case windows compilation issue #526

Description

@JosiahParry

Yesterday during our rust workshop we ran into this very niche issue compiling rust on a windows machine.

The issue is related to libgcc. the below wasa the fix summary from claude.

Details What libgcc_eh.a is libgcc is GCC's runtime support library — a bundle of low-level helpers the compiler emits calls to for things like 64-bit arithmetic on 32-bit systems, software floating point, etc. Most C/C++ programs need it linked in.

libgcc_eh is the exception handling part of that runtime, split out into its own library. The _eh stands for "exception handling." It contains the unwinder code that runs when a C++ throw propagates up the stack, or when Rust does a panic! that needs to unwind.

Historically GCC ships both as separate libraries when built as shared libraries, so you can dynamically link against just the EH bits if that's all you need. But when GCC is built with --disable-shared (static-only), the EH code gets folded directly into libgcc.a and the separate libgcc_eh.a doesn't get produced. There's nothing to put in it — it's already inside libgcc.a.

Why this matters for Rtools
Rtools is built --disable-shared because R packages need to be statically self-contained on Windows. So Rtools' libgcc.a has the EH code baked in, and there's no separate libgcc_eh.a.

For C and C++ R packages, this is fine — R's build system knows to just link -lgcc and not ask for -lgcc_eh separately.

Why Rust breaks
Rust's Windows-GNU target was designed against a "normal" mingw-w64 toolchain (like MSYS2's), which does ship a separate libgcc_eh.a. So when rustc generates linker commands, it hardcodes -lgcc_eh in the list. It doesn't know or care that Rtools merged everything into libgcc.

When the linker hits -lgcc_eh, it searches for libgcc_eh.a, doesn't find one, and bails out:

ld.exe: cannot find -lgcc_eh: No such file or directory
What the fix does
By copying libgcc.a to libgcc_eh.a, you're handing the linker exactly what it's asking for. Since the EH code is already inside libgcc.a, the duplicate file has all the symbols the linker needs. When -lgcc_eh is processed, the linker finds the file, scans it for the needed symbols, and is happy. The fact that it's a byte-for-byte copy of libgcc.a means no symbols are missing.

It's slightly wasteful (some symbols get pulled in twice during linking, but the linker dedupes), but it's harmless and it works.

Why this is everyone's problem, not just yours
This has been an open issue in the extendr/rextendr/Rtools world for ~5 years. There's a long-standing request to have Rtools just include the symlink/copy automatically (the "libgcc_eh hack" — it's literally a one-line patch in the Rtools build script). The mingw32 build of Rtools has it; the mingw64 build doesn't. Anyone trying to build Rust-based R packages on Windows hits this exact wall.

So the TL;DR:

Rust's linker asks for a library file (libgcc_eh.a) that doesn't exist in Rtools because Rtools builds GCC in a mode where that file's contents get merged into another library (libgcc.a). The fix is to make a copy of libgcc.a named libgcc_eh.a so the linker finds what it's looking for. The two files are identical; you're just giving the linker the filename it expects.

Glad it's working! That was a four-layer onion of Windows build pain.

On Fri, Jun 26, 2026 at 3:47 PM Alysson Farris <[redacted]> wrote:
> devtools::document()
ℹ Updating cascadia documentation
ℹ Loading cascadia
ℹ Re-compiling cascadia (debug build)
── R CMD INSTALL ─────────────────────────────────────────────────────────────────────────────────────
─  installing *source* package 'cascadia' ... (486ms)
   ** this is package 'cascadia' version '0.0.0.9000'
   ** using staged installation
   Using cargo 1.96.0 (30a34c682 2026-05-25)
   Using rustc 1.96.0 (ac68faa20 2026-05-25)
   Creating DEBUG build.
   Writing `src/Makevars.win`.
   `tools/config.R` has finished.
   ** libs
   using C compiler: 'gcc.exe (GCC) 14.3.0'
   gcc  -I"C:/PROGRA~1/R/R-45~1.3/include" -DNDEBUG     -I"C:/rtools45/x86_64-w64-mingw32.static.posix/include"      -O2 -Wall -std=gnu2x -gdwarf-2 -mfpmath=sse -msse2 -mstackrealign   -UNDEBUG -Wall -pedantic -g -O0 -fdiagnostics-color=always -c entrypoint.c -o entrypoint.o
   mkdir -p ./rust/target/libgcc_mock
   touch ./rust/target/libgcc_mock/libgcc_eh.a
   if [ -d ./vendor ]; then \
   	echo "=== Using offline vendor directory ==="; \
   	mkdir -p /c/Users/alyss/cascadia/src/.cargo && \
   	cp rust/vendor-config.toml /c/Users/alyss/cascadia/src/.cargo/config.toml; \
   elif [ -f ./rust/vendor.tar.xz ]; then \
   	echo "=== Using offline vendor tarball ==="; \
   	tar xf rust/vendor.tar.xz && \
   	mkdir -p /c/Users/alyss/cascadia/src/.cargo && \
   	cp rust/vendor-config.toml /c/Users/alyss/cascadia/src/.cargo/config.toml; \
   fi
   # Build the project using Cargo with additional flags
   export CARGO_HOME=/c/Users/alyss/cascadia/src/.cargo && \
   export LIBRARY_PATH=";/c/Users/alyss/cascadia/src/./rust/target/libgcc_mock" && \
   RUSTFLAGS=" --print=native-static-libs" cargo build  --target=x86_64-pc-windows-gnu --lib  --manifest-path=rust/Cargo.toml --target-dir=./rust/target
   note: link against the following native artifacts when linking against this static library. The order and any duplication can be significant on some platforms
   
   note: native-static-libs: -lR -lkernel32 -lntdll -luserenv -lws2_32 -ldbghelp
   
       Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.09s
   # Generate wrappers
   export CARGO_HOME=/c/Users/alyss/cascadia/src/.cargo && \
   export PATH="/x86_64-w64-mingw32.static.posix/bin:/usr/bin:/c/PROGRA~1/R/R-45~1.3/bin/x64:/x86_64-w64-mingw32.static.posix/bin:/usr/bin:/x86_64-w64-mingw32.static.posix/bin:/usr/bin:/c/Users/alyss/.positron/extensions/posit.air-vscode-0.26.0-win32-x64/bundled/bin:/c/Users/alyss/AppData/Local/Programs/Positron/resources/app/quarto/bin:/c/Users/alyss/AppData/Local/Programs/Positron/resources/app/quarto/bin:/c/WINDOWS/system32:/c/WINDOWS:/c/WINDOWS/System32/Wbem:/c/WINDOWS/System32/WindowsPowerShell/v1.0:/c/WINDOWS/System32/OpenSSH:/c/Program Files/PuTTY:/c/Program Files (x86)/PharosSystems/Core:/c/Users/alyss/.cargo/bin:/c/Users/alyss/AppData/Local/Programs/Python/Python39/Scripts:/c/Users/alyss/AppData/Local/Programs/Python/Python39:/c/Users/alyss/AppData/Local/Microsoft/WindowsApps:/c/Users/alyss/AppData/Roaming/TinyTeX/bin/windows:/c/Users/alyss/AppData/Local/Programs/Positron/bin:/c/Users/alyss/AppData/Local/Programs/Microsoft VS Code/bin:/:/c/Users/alyss/.positron/extensions/ms-python.debugpy-2026.6.0-win32-x64/bundled/scripts/noConfigScripts:/c/Users/alyss/.positron/extensions/ms-python.debugpy-2026.6.0-win32-x64/bundled/scripts/noConfigScripts/:/c/Users/alyss/OneDrive/Documents/.cargo/bin" && \
   cargo run  --bin document --target x86_64-pc-windows-gnu --manifest-path=./rust/Cargo.toml --target-dir ./rust/target
      Compiling cascadia v0.1.0 (C:\Users\alyss\cascadia\src\rust)
   error: linking with `x86_64-w64-mingw32-gcc` failed: exit code: 1
     |
     = note: "x86_64-w64-mingw32-gcc" "-fno-use-linker-plugin" "-Wl,--dynamicbase" "-Wl,--disable-auto-image-base" "-m64" "-Wl,--high-entropy-va" "C:\\Users\\alyss\\cascadia\\src\\./rust/target\\x86_64-pc-windows-gnu\\debug\\deps\\rustcQRq5UU\\symbols.o" "<18 object files omitted>" "-Wl,-Bstatic" "C:\\Users\\alyss\\cascadia\\src\\rust\\target\\x86_64-pc-windows-gnu\\debug\\deps/{libcascadia-86923fca78494bb1,libextendr_api-b01aaf2adf064b97,libonce_cell-930cf3712b58e90c,libextendr_ffi-df408659d551ff2b}.rlib" "<sysroot>\\lib\\rustlib\\x86_64-pc-windows-gnu\\lib/{libstd-*,libpanic_unwind-*,libobject-*,libmemchr-*,libaddr2line-*,libgimli-*,libcfg_if-*,libwindows_link-*,librustc_demangle-*,libstd_detect-*,libhashbrown-*,librustc_std_workspace_alloc-*,libminiz_oxide-*,libadler2-*,libunwind-*,liblibc-*,librustc_std_workspace_core-*,liballoc-*,libcore-*,libcompiler_builtins-*}.rlib" "-Wl,-Bdynamic" "-lR" "-lkernel32" "-lkernel32" "-lkernel32" "-lntdll" "-luserenv" "-lws2_32" "-ldbghelp" "-lgcc_eh" "-l:libpthread.a" "-lmsvcrt" "-lmingwex" "-lmingw32" "-lgcc" "-lmsvcrt" "-lmingwex" "-luser32" "-lkernel32" "-Wl,--nxcompat" "-L" "C:\\Program Files\\R\\R-4.5.3\\bin\\x64" "-o" "C:\\Users\\alyss\\cascadia\\src\\./rust/target\\x86_64-pc-windows-gnu\\debug\\deps\\document-f4f85132dc1121bd.exe" "-Wl,--gc-sections" "-nodefaultlibs"
     = note: some arguments are omitted. use `--verbose` to show all linker arguments
     = note: C:\rtools45\x86_64-w64-mingw32.static.posix\bin/ld.exe: cannot find -lgcc_eh: No such file or directory
             collect2.exe: error: ld returned 1 exit status
             
   
   error: could not compile `cascadia` (bin "document") due to 1 previous error
   make: *** [Makevars.win:24: rust/target/x86_64-pc-windows-gnu/debug/libcascadia.a] Error 101
   ERROR: compilation failed for package 'cascadia'
─  removing 'C:/Users/alyss/AppData/Local/Temp/RtmpAd62ef/devtools_install_266c53d264f7/cascadia'

Error:
! ! System command 'Rcmd.exe' failed


On Fri, Jun 26, 2026 at 3:41 PM Amelia V <redacted> wrote:
cd C:\rtools45\x86_64-w64-mingw32.static.posix\bin
copy gcc.exe x86_64-w64-mingw32-gcc.exe
copy g++.exe x86_64-w64-mingw32-g++.exe
copy ar.exe x86_64-w64-mingw32-ar.exe
copy ld.exe x86_64-w64-mingw32-ld.exe
copy ranlib.exe x86_64-w64-mingw32-ranlib.exe
copy windres.exe x86_64-w64-mingw32-windres.exe

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions