When things go wrong in Perl, you probably get an error message. When they go wrong in C, you get a segfault, a frozen terminal, or data corruption that only shows up three hours later. This chapter provides a survival kit for when things go very wrong and it's your job to figure it all out.
The Bug: The Vanishing Callback
A common source of crashes in FFI is the scope mismatch. Perl relies on reference counting while C expects you to know what you are doing.
# 1. C library (A Fake Event System)
# void register_handler(void (*cb)(void));
# void trigger_event();
# 2. Perl binding
affix $lib, 'register_handler', [ Callback[ [] => Void ] ] => Void;
affix $lib, 'trigger_event', [] => Void;
# 3. The Buggy Code
sub setup {
# We pass an anonymous subroutine.
# Affix creates a trampoline for it.
register_handler( sub { say "Event!" } );
# END OF SCOPE
# The anonymous sub may have a refcount of 0. If so, perl frees it and the reverse trampoline is destroyed.
}
setup();
# ... later ...
trigger_event(); # SEGFAULT! C jumps to freed memory.
The Fix
You must ensure the Perl CodeRef lives as long as the C library needs it.
my $KEEP_ALIVE; # Global or Object-level storage
sub setup {
$KEEP_ALIVE = sub { say "Event!" };
register_handler( $KEEP_ALIVE );
}
# ...
trigger_event(); # Works!
The Debugging Toolbox
Affix provides tools to inspect raw memory and internal states.
Affix::dump( $ptr, $bytes )
If you suspect struct alignment issues or garbage data, look at the raw bytes.
my $ptr = malloc(16);
# ... C writes to ptr ...
# Dump 16 bytes to STDOUT in Hex/ASCII format
Affix::dump($ptr, 16);
address( $ptr ) & is_null( $ptr )
Sanity check your pointers. A pointer value of 0 (NULL) or 0xFFFFFF... (Uninitialized memory) is a smoking gun.
my $ptr = some_c_function();
say sprintf("Pointer address: 0x%X", address($ptr));
errno()
If a C function returns -1 or NULL to indicate failure, it usually sets a system error code (errno/GetLastError).
if ( some_call() < 0 ) {
die "C Error: " . errno();
}
4. sv_dump( $scalar )
Debugging the Perl side with this function that's a part of perl's internal API. If Affix refuses to accept a variable, checking its internal flags (Integer vs String, Readonly status) often reveals why.
my $val = 10;
Affix::sv_dump($val);
SV = IV(0x25b7a6b4118) at 0x25b7a6b4128
REFCNT = 1
FLAGS = (IOK,pIOK)
IV = 10
Common Pitfalls
-
Writing to Strings
Stringarguments (const char*) are read-only copies. If the C function modifies the string in-place, you MUST usePointer[Char]and pass a mutable scalar. -
Struct padding
C compilers insert padding bytes to align members. If your Perl
Struct[...]definition doesn't match the C compiler's packing rules, fields will be read from the wrong offsets. Usedump()to verify alignment. -
VarArg types
Passing a Perl float to a variadic function? It promotes to
double. Passing an integer? It promotes toint64_t. If the C function expects a 32-bit int, usecoerce(Int32, $val).