Upgrade LD linker for Free Pascal?
Dibo via fpc-pascal <[email protected]> Wed, 22 Jul 2026 16:09:34 +0200
| Newsgroups | gmane.comp.compilers.free-pascal.general |
|---|---|
| Message-ID | <[email protected]> |
This is a multi-part message in MIME format.
--===============7555339537089055523==
Content-Type: multipart/alternative;
boundary="------------0VWtjhsyjp3dAv9HMM7R6x20"
Content-Language: en-US
This is a multi-part message in MIME format.
--------------0VWtjhsyjp3dAv9HMM7R6x20
Content-Type: text/plain; charset=UTF-8; format=flowed
Content-Transfer-Encoding: 8bit
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?
--------------0VWtjhsyjp3dAv9HMM7R6x20
Content-Type: text/html; charset=UTF-8
Content-Transfer-Encoding: 8bit
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
</head>
<body>
<p>Hi,</p>
<p><span class="HwtZe" lang="en"><span class="jCAhz ChMk0b"><span
class="ryNqvb">I suspect that my first message was blocked
because I attached ODT document. Please ignore duplicate if
it show up.</span></span></span></p>
<p>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.</p>
<p>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.</p>
<p>Static linking has issues which I'm <span class="HwtZe" lang="en"><span
class="jCAhz ChMk0b"><span class="ryNqvb">consistently</span></span></span> 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:</p>
<p>Class Fl_File_Chooser2.cxx has this public variable:</p>
<p>const char *Fl_File_Chooser::filesystems_label =
Fl::system_driver()->filesystems_label();</p>
<p>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:<br>
<br>
</p>
<p>type<br>
TCtorProc = procedure(); cdecl;<br>
PCtorProc = ^TCtorProc;<br>
<br>
var<br>
__init_array_start: Byte; external name '__init_array_start';<br>
__init_array_end: Byte; external name '__init_array_end';<br>
<br>
procedure RunCppGlobalConstructorsLinux;<br>
var<br>
p, pend: PCtorProc;<br>
begin<br>
p := PCtorProc(@__init_array_start);<br>
pend := PCtorProc(@__init_array_end);<br>
while p < pend do<br>
begin<br>
if Assigned(p^) then<br>
p^();<br>
Inc(p);<br>
end;<br>
end; <br>
<br>
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.</p>
<p>Now windows version. Here complication start. First, build-in FPC
linker is useless and throw error:</p>
<p>Error: Failed reading coff file, invalid section index while
reading
C:\Users\vboxuser\Documents\CFLTKlibs\libcfltk.a(cfl_window.cpp.obj)<br>
<br>
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).</p>
<p>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 <span class="HwtZe" lang="en"><span
class="jCAhz ChMk0b"><span class="ryNqvb">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:</span></span></span></p>
<p><span class="HwtZe" lang="en"><span class="jCAhz ChMk0b"><span
class="ryNqvb">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<br>
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</span></span></span></p>
<p><span class="HwtZe" lang="en"><span class="jCAhz ChMk0b"><span
class="ryNqvb">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 (</span></span></span><span
class="HwtZe" lang="en"><span class="jCAhz ChMk0b"><span
class="ryNqvb">equivalent of </span></span></span>crtbegin.o
and crtend.o) which are missing in FPC ld linker.</p>
<p>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 <span class="HwtZe"
lang="en"><span class="jCAhz ChMk0b"><span class="ryNqvb">engagement
everytime when something changed in CFLT/FLTK source or even
in MinGW.</span></span></span></p>
<p><span class="HwtZe" lang="en"><span class="jCAhz ChMk0b"><span
class="ryNqvb">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? </span></span></span><span class="HwtZe" lang="en"><span
class="jCAhz ChMk0b"><span class="ryNqvb">The difference
between FPC ld and GCC ld is almost 10 years. </span></span></span><span
class="HwtZe" lang="en"><span class="jCAhz ChMk0b"><span
class="ryNqvb">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. </span></span></span></p>
<p><span class="HwtZe" lang="en"><span class="jCAhz ChMk0b"><span
class="ryNqvb">Below is Claude's summary.</span></span></span></p>
<p><span class="HwtZe" lang="en"><span class="jCAhz ChMk0b"><span
class="ryNqvb">Regards, Dibo.</span></span></span></p>
<p><span class="HwtZe" lang="en"><span class="jCAhz ChMk0b"><span
class="ryNqvb">Environment:<br>
<br>
Free Pascal Compiler 3.2.2, Windows target x86_64-win64<br>
<br>
Bundled linker:
C:\programowanie\lazarus\fpc\3.2.2\bin\x86_64-win64\ld.exe —
GNU ld (GNU Binutils) 2.28<br>
<br>
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<br>
<br>
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"<br>
<br>
FLTK version: 1.4.5, CFLTK version: 1.5.23<br>
<br>
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.<br>
<br>
Evidence:<br>
<br>
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.<br>
<br>
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.<br>
<br>
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<br>
<br>
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<br>
<br>
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).<br>
<br>
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<br>
<br>
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?</span></span></span></p>
<p><br>
</p>
<p><span class="HwtZe" lang="en"><span class="jCAhz ChMk0b"><span
class="ryNqvb"><br>
</span></span></span></p>
</body>
</html>
--------------0VWtjhsyjp3dAv9HMM7R6x20--
--===============7555339537089055523==
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: inline
_______________________________________________
fpc-pascal maillist - [email protected]
https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
--===============7555339537089055523==--