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:

  1. Uninitialized Free Pascal RTL (Run-Time Library): Pascal is not just compiled assembly; it relies on a runtime library that initializes file handles (like Input and Output for writeln), sets up exception handling, and manages heap memory. When C provides the entry point (main()), Free Pascal's unit initialization table is never executed.
  2. 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() (or FPC_INITIALIZEUNITS): Runs initialization sections for units and sets up standard I/O streams.
  • fpc_do_exit() (or FPC_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 .o files for Pascal RTL units, you can bundle your Pascal objects into a static library using ar rcs libhello.a hello.o and 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 -Cg in FPC if you're building on modern Linux distributions where GCC defaults to PIE.