FFI/ABI Boundaries and Safety (Panics, Alignment, Lifetimes) TODO
Concept
The ABI is the binary-level contract that compiled code must honor — it specifies the calling convention (which registers pass arguments, stack alignment, where return values go), struct layout and padding, name mangling, and more. FFI is the mechanism by which different languages call each other through that ABI, and since the C ABI is usually the common denominator, structs typically need an explicit C layout. Safety problems arise along three axes. First, if a panic or exception unwinds across an FFI boundary, the other language's runtime can't handle it, so it becomes undefined behavior — it must be caught at the boundary and converted into an error code. Second, if alignment or size assumptions are wrong, you get invalid memory accesses. Third, if pointer ownership and lifetime cross the language boundary, memory a GC has moved or reclaimed can be left dangling for the other side to reference — so the contract must nail down exactly who allocates and who frees.
When native libraries for cryptography or a DB engine are wired in, most crashes trace back not to logic but to this boundary contract — leaked panics, GC-moved memory, unclear ownership of freeing.
Code & Formula
# FFI·ABI 경계와 안전성 — ctypes 로 구조체 레이아웃(패딩)을 확인하고, 패닉이 경계를 넘지 못하도록 에러 코드로 변환한다.
import ctypes
class Header(ctypes.Structure):
# C ABI 기준: int8 뒤에 int32 가 오면 정렬(4바이트) 때문에 3바이트 패딩이 끼어든다.
_fields_ = [("flag", ctypes.c_int8), ("value", ctypes.c_int32)]
h = Header(flag=1, value=1000)
print("sizeof(Header):", ctypes.sizeof(h), "bytes (1 + 3 padding + 4, not 5)")
print("offsetof(value):", Header.value.offset, " <- 정렬 때문에 1이 아니라 4")
# FFI 경계에서는 상대 언어 런타임이 이해 못 하는 예외/패닉이 넘어가면 정의되지 않은 동작이 된다.
# 규칙: 경계 함수는 절대 예외를 던지지 않고, 항상 (ok, error_code) 형태로 변환해 반환한다.
ERR_OK = 0
ERR_DIVIDE_BY_ZERO = 1
ERR_OUT_OF_RANGE = 2
def ffi_safe_divide(a, b):
"""다른 언어에서 호출한다고 가정한 경계 함수 — 내부 예외를 절대 누출시키지 않는다."""
try:
return (ERR_OK, a / b)
except ZeroDivisionError:
return (ERR_DIVIDE_BY_ZERO, None) # 예외 대신 에러 코드로 변환해서 경계를 넘긴다
except OverflowError:
return (ERR_OUT_OF_RANGE, None)
ok1, r1 = ffi_safe_divide(10, 2)
ok2, r2 = ffi_safe_divide(10, 0)
print("divide(10, 2) ->", "code", ok1, "result", r1)
print("divide(10, 0) ->", "code", ok2, "result", r2, " (호출자는 예외가 아니라 코드로 실패를 본다)")
docs/code/algorithms/algorithms-32.py
Exercise
Build a minimal example that calls a C function from Go via cgo, write code that passes a Go slice pointer into C, stores it, and uses it later, then check against the documentation to see which rule it violates.
Practical Connection
Ethereum clients often delegate signature and pairing operations like secp256k1 or BLS to C/assembly libraries, so lifetime and panic handling at the FFI boundary directly affect node stability.
Where it lands in Jayverse
- Devnet/Rabbit: write the ownership contract before any native-crypto FFI call. If a service ever calls a native library (secp256k1, BLS, a precompile wrapper) via cgo or a Node native addon, decide who allocates and who frees before wiring it in — the boundary the practical connection already flags for node stability.
- Wallet: catch panics at any native-signer boundary. If the simulate-before-sign flow ever talks to a hardware or native signing library through FFI, catch panics at that boundary and convert them to an error code rather than letting them cross into the TypeScript runtime.
- CI: flag new native-library dependencies in review. Add a check (even a PR-template note) for any new FFI/native dependency, since these bugs are boundary contract violations that ordinary unit tests won't catch.
Key expressions
| Expression | 뜻 · 쓰이는 자리 |
|---|---|
| common denominator | 공통 기준, 최소 공통분모 · C ABI가 여러 언어 간 공통 규격으로 쓰이는 상황. "the C ABI is usually the common denominator" |
| wire in | (부품·라이브러리를) 연결해 붙이다 · 네이티브 라이브러리를 시스템에 결합하는 것. "When native libraries... are wired in" |
| nail down | (애매한 것을) 명확히 정해두다 · 누가 할당하고 누가 해제하는지 확실히 규정해야 한다는 뜻. "the contract must nail down exactly who allocates and who frees" |
| trace back to | (원인이) ~로 거슬러 올라가다 · 크래시의 원인이 로직이 아니라 경계 계약임을 밝힐 때. "most crashes trace back not to logic but to this boundary contract" |
| left dangling | (참조가) 허공에 뜬 채로 남다 · GC가 옮긴 메모리를 다른 쪽이 계속 가리키는 위험. "can be left dangling for the other side to reference" |
| undefined behavior | (규격에 정의되지 않아) 결과를 예측할 수 없는 동작 · 예외가 경계를 넘어갈 때 벌어지는 일. "it becomes undefined behavior" |
| ABI | 응용 이진 인터페이스(Application Binary Interface) · 컴파일된 코드 간 바이너리 수준의 규약. "The ABI is the binary-level contract" |
| FFI | 외부 함수 인터페이스(Foreign Function Interface) · 서로 다른 언어가 ABI를 통해 서로를 호출하는 메커니즘. "FFI is the mechanism by which different languages call each" |
| cgo | Go 언어에서 C 함수를 호출할 때 쓰는 도구(cgo) · FFI 경계를 실습하는 예시로 언급. "calls a C function from Go via cgo" |
If you study this on a given day, add a note link and a ✅ to this line in the source curriculum (docs/knowledge/dev-100-curriculum.md) and this spot will lead straight to the note body. You can also write directly on this page — but regenerating overwrites it, so it's safer to keep anything you want to save as markdown under docs/algorithms/.