[fpc-pascal] Upgrade LD linker for Free Pascal?

Dibo dibo20 at wp.pl
Wed Jul 22 16:09:34 CEST 2026


Hi,

I suspect that my first message was blocked because I attached ODT 
document. Please ignore duplicate if it show up.

I mentioned about this issue some time ago in the topic about PasFLTK 
bindings which I made. I decided to make dedicated thread because it 
seems to be interesting.

So. I'm developing PasFLTK bindings for CFLTK which is flatten C binding 
for major FLTK UI framework written in C++. PasFLTK has two modes, 
shared library which work perfectly fine and experimental static linking.

Static linking has issues which I'm consistently fixing. Major problem 
is that C++ classes are not initialized in proper way. Problem is not a 
matter of "Static Initialization Order Fiasco" because CFLTK/FLTK is 
designed to be static linking for default (and that is how other 
language bindings are using it). Problem is how FPC LD linker prepare 
"startup" routines. For example:

Class Fl_File_Chooser2.cxx has this public variable:

const char      *Fl_File_Chooser::filesystems_label = 
Fl::system_driver()->filesystems_label();

System_driver is not initialized yet so filesystems_label become null 
which result with access violations. Since I'm linux user I started from 
this platform, working on this with Claude AI (which amazed me how 
deeply he can analyze binaries on low level). On linux, fix was small 
and easy (for Claude, not for me of course). LD linker prepare wrong 
init structure and doesn't call the __init_array_start in linux ELF 
standard. So patch looks like that:

type
   TCtorProc = procedure(); cdecl;
   PCtorProc = ^TCtorProc;

var
   __init_array_start: Byte; external name '__init_array_start';
   __init_array_end: Byte; external name '__init_array_end';

procedure RunCppGlobalConstructorsLinux;
var
   p, pend: PCtorProc;
begin
   p := PCtorProc(@__init_array_start);
   pend := PCtorProc(@__init_array_end);
   while p < pend do
   begin
     if Assigned(p^) then
       p^();
     Inc(p);
   end;
end;

That small piece of code fixed everything and static linking work like a 
charm on linux. I have project (which I publish soon) totally written as 
static linking and didn't notice any new issues so far.

Now windows version. Here complication start. First, build-in FPC linker 
is useless and throw error:

Error: Failed reading coff file, invalid section index while reading 
C:\Users\vboxuser\Documents\CFLTKlibs\libcfltk.a(cfl_window.cpp.obj)

I had to use -Xe flag which switch to external ld.exe linker. But then I 
had DWARF errors because LD support only up to 4 version and my MinGW 
toolchain (MSYS2 on windows) is using ver. 5. I had to call `strip 
--strip-debug` on all *.a files (including some MinGW - e.g libstd++.a).

Now, when binding are compiling fine, I fall to the same problem with 
initialization as on linux. I spent few evenings with Claude on this. We 
compared objdump -h and extracted objects on exactly the same demos 
written line by line one using C (CFLTK) and second Free Pascal. Since 
linux use ELF we get __init_array_start, on windows PE - it looks 
harder. LD.exe delivered with FPC is quite old and doesn't use modern 
standard. Demo written in C and linked by LD from toolchain has correct 
.ctors and __CTOR_LIST__ which FPC ld.exe is missing (in fact, filling 
it incorrectly). Tried using -FD"C:\msys64\mingw64\bin" which load 
ld.exe from MinGW toolchain but this result with dozens errors:

C:\msys64\mingw64\bin\ld.exe: 
C:\programowanie\lazarus\fpc\3.2.2\units\x86_64-win64\rtl\sysutils.o: 
illegal relocation type 0 at address 0
C:\msys64\mingw64\bin\ld.exe: 
C:\programowanie\lazarus\fpc\3.2.2\units\x86_64-win64\rtl\math.o: 
illegal relocation type 0 at address 0

Claude also compared rust bindings for CFLTK because it also use static 
linking. They had also problems and are stick to the specific toolchain. 
But there problem was easier to fix because Rust linker correctly 
linking crt2.o and has own guardians rsbegin.o and rsend.o (equivalent 
of crtbegin.o and crtend.o) which are missing in FPC ld linker.

Finally I get it working with Claude but this require reorganize *.a and 
C++ objects with --globalize-symbol and this solution is not acceptable 
for me because it require engagement everytime when something changed in 
CFLT/FLTK source or even in MinGW.

I'll be not writting anymore and just attach summary of our work which I 
asked Claude to write in short. It has more technical details. To sum 
up. Is there any plans to upgrade LD linker or exists any alpha / beta 
of upcoming release? The difference between FPC ld and GCC ld is almost 
10 years. I read somewhere that FPC is not designed to link C++ (only C) 
but as we can see, it could be just small step to make it possible and 
that open huge door.

Below is Claude's summary.

Regards, Dibo.

Environment:

     Free Pascal Compiler 3.2.2, Windows target x86_64-win64

     Bundled linker: 
C:\programowanie\lazarus\fpc\3.2.2\bin\x86_64-win64\ld.exe — GNU ld (GNU 
Binutils) 2.28

     Comparison linker: ld.exe from MSYS2 mingw-w64-x86_64-binutils 
package, located at C:\msys64\mingw64\bin\ld.exe — GNU ld (GNU Binutils) 
2.46.1

     GCC/G++ used to build FLTK, CFLTK, and the comparison C++ demo: 
16.1.0 (MSYS2 mingw-w64-x86_64-gcc, reported as "GCC 16.1.0"

     FLTK version: 1.4.5, CFLTK version: 1.5.23

Root cause identified: FPC 3.2.2's bundled ld.exe fails to correctly 
merge .ctors input sections (containing C++ global constructor pointers) 
from object files compiled by a modern GCC (16.1.0, MinGW-w64/MSYS2) 
into the final __CTOR_LIST__ array — even though the linker script 
(identical KEEP(*(.ctors)) rule, confirmed via ld.exe --verbose on both 
linkers) is present in both the old bundled ld.exe and a current MSYS2 
ld.exe.

Evidence:

     A raw hex dump of __CTOR_LIST__ in an FPC-linked executable (with 
10 real global constructors from a statically-linked FLTK/CFLTK library 
present and confirmed via nm) shows: sentinel (-1) immediately followed 
by terminator (0) — zero constructor pointers, despite the constructor 
functions (_GLOBAL__sub_I_*) physically existing in the binary.

     The same source compiled and linked natively with g++.exe/current 
MSYS2 ld.exe produces a fully populated __CTOR_LIST__ with all 10 
pointers correctly resolved to the matching _GLOBAL__sub_I_* addresses.

     Attempting to relink the FPC-built binary with the current MSYS2 
ld.exe (via -FD<path>) fails with illegal relocation type 0 / invalid 
section index errors on FPC's own RTL object files (sysutils.o, math.o) 
— indicating FPC's internal object emitter and modern binutils have 
diverged in mutually incompatible ways. rtl\sysutils.o: illegal 
relocation type 0 at address 0 etc

     The input .o files from the affected FLTK library use COMDAT 
sections (LINK_ONCE_DISCARD) extensively (confirmed via objdump -h), 
which may be related to how the old ld.exe mishandles their .ctors 
content during merging — this is hypothesis

     Consequently, statically linking modern C++ libraries (FLTK/CFLTK, 
built with current MinGW-w64/GCC) into an FPC/Lazarus Windows executable 
silently drops all global C++ constructors, causing use of uninitialized 
static objects downstream (observed as intermittent access violations 
depending on which code paths touch affected statics).

Workaround currently used: manually enumerating _GLOBAL__sub_I_* symbols 
via nm on the linked (even if crashing) executable, globalizing their 
linkage scope via objcopy --globalize-symbol (they are emitted as 
local/t symbols), and generating a small Pascal unit that calls them all 
explicitly at program startup. This resolves some but not all downstream 
issues, suggesting missing constructor invocation is necessary but 
possibly not sufficient — full mainCRTStartup context 
(TLS/locale/guard-variable state) may also matter for some 
lazily-initialized C++ singletons. - this is last hypothesis

Question for the list: Is there a newer/beta ld.exe (or plan to update 
the bundled binutils) for the Windows FPC toolchain that correctly 
handles .ctors/COMDAT sections as emitted by current GCC, without 
breaking compatibility with FPC's own object file emitter?


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.freepascal.org/pipermail/fpc-pascal/attachments/20260722/51020ffd/attachment.htm>


More information about the fpc-pascal mailing list