How to Statically Link and Call Free Pascal Code from C (Without Segfaults)
When experimenting with polyglot programming and building multi-language binaries, you might find yourself wanting to statically link Free Pascal (FPC) code directly into a C project using standard tools like gcc or ld. However, as soon as you introduce built-in Pascal features like writeln or dynamic memory allocation, your binary will likely throw linker relocation warnings and crash with an immediate Segmentation Fault (often inside SYSTEM_$$_FINALIZEUNITS).
In this guide, we'll explain why this happens and walk you through the step-by-step process of properly initializing the Free Pascal runtime inside a C program to achieve seamless static linking.
The Root Causes of the Segmentation Fault
Two main issues cause the crash and linker warnings when mixing FPC and C directly:
- Uninitialized Free Pascal RTL (Run-Time Library): Pascal is not just compiled assembly; it relies on a runtime library that initializes file handles (like
InputandOutputforwriteln), sets up exception handling, and manages heap memory. When C provides the entry point (main()), Free Pascal's unit initialization table is never executed. - PIE (Position Independent Executable) Mismatch: Modern GCC compiles with PIE enabled by default. If your Pascal code is compiled without position-independent code (PIC) flags, the linker emits warnings like
creating DT_TEXTREL in a PIE, which can lead to relocation errors.
Step-by-Step Solution
1. Write the Pascal Unit
Export your Pascal procedure using the cdecl calling convention so it can be called seamlessly using the standard C ABI.
unit hello;
{$mode objfpc}{$H+}
interface
procedure hello; cdecl; public name 'hello';
implementation
procedure hello; cdecl;
begin
writeln('Hello from Free Pascal!');
end;
end.
2. Initialize the Pascal Runtime from C
Because C controls main(), you must manually trigger the FPC initialization and finalization routines before invoking Pascal functions that use I/O or strings. Free Pascal exposes internal hooks for this purpose:
fpc_initializeunits()(orFPC_INITIALIZEUNITS): Runs initialization sections for units and sets up standard I/O streams.fpc_do_exit()(orFPC_DO_EXIT): Runs finalization sections and flushes standard I/O buffers.
Here is how your main.c should look:
#include <stdio.h>
/* Function exported from Pascal */
void hello(void);
/* Free Pascal internal RTL lifecycle functions */
extern void fpc_initializeunits(void);
extern void fpc_do_exit(void);
int main(int argc, char **argv, char **envp) {
// 1. Initialize the Free Pascal RTL
fpc_initializeunits();
// 2. Call your Pascal function safely
hello();
// 3. Finalize and clean up Pascal units
fpc_do_exit();
return 0;
}
3. Compile and Link Statically
When compiling with fpc, ensure you pass the -Cg flag to generate PIC (Position Independent Code). When linking with gcc, make sure to link the compiled Pascal unit alongside the necessary RTL units.
# 1. Compile the Pascal unit with PIC support
fpc -Cg -Cn hello.pas
# 2. Compile C and link everything with GCC
gcc -no-pie main.c hello.o \
/usr/lib/x86_64-linux-gnu/fpc/3.2.2/units/x86_64-linux/rtl/system.o \
/usr/lib/x86_64-linux-gnu/fpc/3.2.2/units/x86_64-linux/rtl/objpas.o \
-o my_program
Note: Adjust the path to your FPC RTL units based on your system and architecture.
4. Run the Binary
$ ./my_program
Hello from Free Pascal!
Best Practices for Multi-Language Static Linking
- Use Static Archives (
.a): Instead of manually tracking individual.ofiles for Pascal RTL units, you can bundle your Pascal objects into a static library usingar rcs libhello.a hello.oand link against it. - Mind Name Mangling: Always specify
cdecl; public name 'function_name';in Pascal so the symbol name matches what C expects without Pascal's uppercase name mangling. - Position Independence: Always use
-Cgin FPC if you're building on modern Linux distributions where GCC defaults to PIE.