kingbes / phpc
写更安全的ffi应用
v0.0.2
2026-07-20 07:04 UTC
Requires
- php: >=8.2
- ext-ffi: *
README
概述
kingbes/phpc是一个为 PHP FFI 扩展提供安全防护层的轻量级库。原生 FFI 直接操作 C 内存,容易出现空指针解引用、缓冲区越界、内存泄漏、类型转换错误等问题。本库通过 RAII 包装、白名单加载、边界检查、内存追踪等手段,让 FFI 编码更安全、更省心。
设计原则:功能上不信任 phper,所有公开方法对入参做防御性校验,把错误拦截在 PHP 层而非 C 层(C 层错误往往直接导致进程崩溃)。
- 命名空间:
Kingbes\Phpc - 要求:PHP >= 8.2,启用
ext-ffi - 许可:MIT
安装
通过 Composer 安装:
composer require kingbes/phpc
最小示例
以下示例加载 libc,调用 strlen 并用 CData::owned 包装器自动释放返回的内存:
<?php require __DIR__ . '/vendor/autoload.php'; use Kingbes\Phpc\Library; use Kingbes\Phpc\CData; use Kingbes\Phpc\SafeCall; use Kingbes\Phpc\Memory; $ffi = Library::load('libc', 'int strlen(const char*); void* malloc(size_t); void free(void*);'); // 用 CData::owned 包装 malloc 返回的指针,析构自动 FFI::free $buf = $ffi->malloc(16); $wrapped = CData::owned($buf); // 安全地填充 C 字符串 Memory::copy($wrapped->raw(), 'hello', 5); $wrapped->raw()[5] = "\0"; // 安全调用 strlen $len = SafeCall::invoke($ffi, 'strlen', [$wrapped->raw()]); echo $len; // 5 // 即使忘记 release,析构也会自动 free $wrapped->release();
更简洁的入口可通过门面类 Kingbes\Phpc\Phpc 完成:
use Kingbes\Phpc\Phpc; $ffi = Phpc::load('libc', 'int strlen(const char*);'); echo Phpc::call($ffi, 'strlen', [Phpc::toString('hello')]);
组件列表与导航
| 组件 | 类 | 文档 | 职责 |
|---|---|---|---|
| CData 包装器 | Kingbes\Phpc\CData |
cdata.md | RAII 自动释放(owned/external/borrowed 三态) |
| Pointer 守卫 | Kingbes\Phpc\Pointer |
pointer.md | 空指针校验、负数 offset 拦截、可选 max 边界校验 |
| Buffer 守卫 | Kingbes\Phpc\Buffer |
buffer.md | 数组类型校验、容量交叉校验、索引越界保护 |
| MemoryTracker | Kingbes\Phpc\MemoryTracker |
memory-tracker.md | 内存泄漏检测 |
| Library 加载 | Kingbes\Phpc\Library |
library.md | 库名格式校验、白名单管理、文件存在性校验 |
| TypeCast | Kingbes\Phpc\TypeCast |
typecast.md | PHP 与 C 类型互转、范围校验 |
| SafeCall | Kingbes\Phpc\SafeCall |
safecall.md | C 标识符校验、函数调用守卫 |
| Struct | Kingbes\Phpc\Struct |
struct.md | structType 校验、字段读写保护 |
| Memory | Kingbes\Phpc\Memory |
memory.md | memcpy/memset/memcmp + 数组越界写校验 |
| 门面 | Kingbes\Phpc\Phpc |
- | 统一便捷入口 |
异常体系
所有库内异常均继承自 Kingbes\Phpc\Exception\SafetyException(其本身继承 \Exception),可一次性捕获:
use Kingbes\Phpc\Exception\SafetyException; try { // 任意 Phpc 调用 } catch (SafetyException $e) { // 统一处理 }
具体异常类型:
NullPointerException- 空指针断言/解引用、expectNotNull返回空指针BufferOverflowException-Buffer::get/set越界、Pointer::deref提供了$max且 offset 越界MemoryLeakException-MemoryTracker::assertNoLeaks检测到泄漏TypeMismatchException-Struct::get/set字段不存在或类型不匹配LibraryNotPermittedException- 库名格式非法、库未在白名单、头文件内容为空、文件不存在CastException-TypeCast::toInt类型不支持或值越界、cast收到空 cTypeCallException-SafeCall::invoke函数名非法、函数不存在、expectZero返回非 0
部分方法还会抛原生异常:
\InvalidArgumentException-Pointer::deref收到负数 offset/max、Memory::set的 value 越界
示例导览
examples/ 目录下提供 19 个可运行示例,覆盖每个组件的常见用法与异常路径,并包含三个跨平台 GUI 示例:
| 编号 | 文件 | 主题 |
|---|---|---|
| 01 | 01_cdata.php | CData RAII 包装器(owned/external/borrowed) |
| 02 | 02_pointer.php | Pointer 空指针校验与安全解引用 |
| 03 | 03_buffer.php | Buffer 数组越界保护 |
| 04 | 04_memory_tracker.php | MemoryTracker 泄漏检测 |
| 05 | 05_typecast.php | TypeCast 类型互转与范围校验 |
| 06 | 06_safecall.php | SafeCall C 函数调用守卫 |
| 07 | 07_struct.php | Struct 结构体字段读写 |
| 08 | 08_memory.php | Memory memcpy/memset/memcmp |
| 09 | 09_library.php | Library 白名单加载 |
| 10 | 10_facade.php | Phpc 门面统一入口 |
| 11 | 11_safety_checks.php | 安全校验综合演示 |
| 12 | 12_advanced_struct.php | Struct 高级用法(嵌套/数组字段/external) |
| 13 | 13_exceptions.php | 所有异常类型演示 |
| 14 | 14_realworld_string.php | 真实场景字符串处理 |
| 15 | 15_hardening.php | 新增安全加固校验演示 |
| 16 | 16_win32_gui.php | Windows GUI(user32.dll + PHP 闭包作为窗口过程) |
| 17 | 17_linux_x11_gui.php | Linux X11 GUI(libX11 + 事件循环) |
| 18 | 18_macos_cocoa_gui.php | macOS Cocoa GUI(ObjC runtime + objc_msgSend) |
| 19 | 19_linux_gtk_gui.php | Linux GTK3 GUI(libgtk-3 + PHP 闭包作为信号回调) |
跨平台示例会在不匹配的操作系统上自动跳过。运行所有示例的回归测试:
php -d ffi.enable=true -f tests/run_examples.php
后续阅读
建议按以下顺序了解各组件:
- Library 加载 - 加载 FFI 库的入口
- CData 包装器 - 掌握 RAII 模式
- Pointer 守卫 - 处理指针安全
- TypeCast - PHP/C 类型互转
- SafeCall - 调用 C 函数
- Struct - 操作结构体
- Buffer 守卫 - 安全数组访问
- Memory - 内存操作
- MemoryTracker - 泄漏检测