php7.4 ffi,PHP 7.4 前瞻:FFI
FFI扩展已经通过RFC,正式成为PHP 7.4的捆绑扩展库(Bundled Extensions)。
什么是FFI
FFI(Foreign Function Interface),即外部函数接口,是指在一种语言里调用另一种语言代码的技术。PHP的FFI扩展就是一个让你在PHP里调用C代码的技术。
FFI的使用非常简单,只用声明和调用两步就可以,对于有C语言经验,但是不了解Zend引擎的程序员来说,这简直是打开了新世界的大门,可以快速地使用C类库进行原型试验。
(此处有图:溜了溜了,要懂C的……)
下面通过3个例子,看一下FFI是怎样使用的。
Libbloom
libbloom是一个C实现的bloom filter,比较知名的用户有Shadowsocks-libev,下面看一下怎样通过FFI在PHP里调用libbloom。
第一步,从头文件bloom.h把主要的数据结构和函数声明复制出来:
$ffi = FFI::cdef("
struct bloom
{
int entries;
double error;
int bits;
int bytes;
int hashes;
double bpe;
unsigned char * bf;
int ready;
};
int bloom_init(struct bloom * bloom, int entries, double error);
int bloom_check(struct bloom * bloom, const void * buffer, int len);
int bloom_add(struct bloom * bloom, const void * buffer, int len);
void bloom_free(struct bloom * bloom);
", "libbloom.so.1.5");
FFI目前不支持预处理器(除了FFI_LIB和FFI_SCOPE),所以宏定义要自己展开。
之后就可以通过$ffi创建已声明的数据结构和调用函数:
// 创建一个bloom结构体,然后用FFI::addr取地址
// libbloom的函数都是使用bloom结构体的指针
$bloom = FFI::addr($ffi->new("struct bloom"));
// 调用libbloom的初始化函数
$ffi->bloom_init($bloom, 10000, 0.01);
// 添加数据
$ffi->bloom_add($bloom, "PHP", 3);
$ffi->bloom_add($bloom, "C", 1);
// PHP可能存在
var_dump($ffi->bloom_check($bloom, "PHP", 3)); // 1
// Laravel不存在
var_dump($ffi->bloom_check($bloom, "Laravel", 7)); // 0
// 释放
$ffi->bloom_free($bloom);
$bloom = null;
Linux Namespace
Linux命名空间是容器技术的基石之一,通过FFI可以直接调用glibc的对应系统调用封装,从而通过PHP实现容器。下面是一个让bash在一个新的命名空间里运行的例子。
首先是一些常量,可以从Linux的头文件得到:
// clone
const CLONE_NEWNS = 0x00020000; // mount namespace
const CLONE_NEWCGROUP = 0x02000000; // cgroup namespace
const CLONE_NEWUTS = 0x04000000; // utsname namespace
const CLONE_NEWIPC = 0x08000000; // ipc namespace
const CLONE_NEWUSER = 0x10000000; // user namespace
const CLONE_NEWPID = 0x20000000; // pid namespace
const CLONE_NEWNET = 0x40000000; // network namespace
// mount
const MS_NOSUID = 2;
const MS_NODEV = 4;
const MS_NOEXEC = 8;
const MS_PRIVATE = 1 << 18;
const MS_REC = 16384;
接着时我们要用到的函数声明:
$cdef="
// fork进程
int clone(int (*fn)(void *), void *child_stack, int flags, void *arg);
// 挂载文件系统
int mount(const char *source, const char *target, const char *filesystemtype,
unsigned long mountflags, const void *data);
// 设置gid
int setgid(int gid);
// 设置uid
int setuid(int uid);
// 设置hostname
int sethostname(char *name, unsigned int len);
";
$libc = FFI::cdef($cdef, "libc.so.6");
定义我们的子进程:
// 生成一个容器ID
$containerId = sha1(random_bytes(8));
// 定义子进程
$childfn = function() use ($libc, $containerId) {
usleep(1000); // wait for uid/gid map
$libc->mount("proc", "/proc", "proc", MS_NOSUID | MS_NODEV | MS_NOEXEC, null);
$libc->setuid(0);
$libc->setgid(0);
$libc->sethostname($containerId, strlen($containerId));
pcntl_exec("/bin/sh");
};
在子进程里,我们重新挂载了/proc,设置了uid、gid和hostname,然后启动/bin/sh。
父进程通过clone函数,创建子进程:
// 分配子进程的栈
$child_stack = FFI::new("char[1024 * 4]");
$child_stack = FFI::cast('void *', FFI::addr($child_stack)) - 1024 * 4;
// fork子进程
$pid = $libc->clone($childfn, $child_stack, CLONE_NEWUSER
| CLONE_NEWNS
| CLONE_NEWPID
| CLONE_NEWUTS
| CLONE_NEWIPC
| CLONE_NEWNET
| CLONE_NEWCGROUP
| SIGCHLD, null);
// 设置UID、GID映射,把容器内的root映射到当前用户
$uid = getmyuid();
$gid = getmyuid();
file_put_contents("/proc/$pid/uid_map", "0 $uid 1");
file_put_contents("/proc/$pid/setgroups", "deny");
file_put_contents("/proc/$pid/gid_map", "0 $gid 1");
// 等待子进程
pcntl_wait($pid);
glibc的clone函数是clone系统调用的封装,它需要一个函数指针作为子进程/线程的执行体,我们可以直接把PHP的闭包和匿名函数当作函数指针使用。
运行效果:
$ php container.php
sh-5.0# id # 在容器内是root
uid=0(root) gid=0(root) groups=0(root),65534(nobody)
sh-5.0# ps aux # 独立的PID进程空间
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.1 10524 4124 pts/1 S 10:19 0:00 /bin/sh
root 3 0.0 0.0 15864 3076 pts/1 R+ 10:19 0:00 ps aux
sh-5.0# ip a # 独立的网络命名空间
1: lo: mtu 65536 qdisc noop state DOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
raylib
raylib是个特性丰富而且易用的游戏库,经过简单的封装就可以在PHP里使用。下面这个例子实现了一个跟随鼠标的圆:
include __DIR__ . "/../../RayLib.php";
// 初始化
RayLib::init(); // 初始化FFI和“常量”
RayLib::InitWindow(400, 300, "raylib example");
// 状态:球的位置
$ballPosition = RayLib::Vector2(-100.0, 100.0);
// 主循环
while (!RayLib::WindowShouldClose())
{
// 状态更新
$ballPosition = RayLib::GetMousePosition(); // 获取鼠标位置
// 渲染
RayLib::BeginDrawing();
RayLib::ClearBackground(RayLib::$RAYWHITE); // 清除背景颜色
RayLib::DrawCircleV($ballPosition, 40, RayLib::$RED); // 画个圈圈
RayLib::DrawFPS(10, 10); // 显示FPS
RayLib::EndDrawing();
}
// 释放
RayLib::CloseWindow();
不足
性能
C类库性能可能很高,但是FFI调用的消耗也非常大,通过FFI访问数据要比PHP访问对象和数组慢两倍,所以用FFI不一定能提高性能,RFC里给出的一个测试结果:
就算用了JIT,还是比不上不用JIT的PHP。
功能
目前(20190301)FFI扩展还没实现的一些功能:
返回struct/union和数组
嵌套的struct(我写了个简单的补丁)
使用这些功能的时候,会抛出异常,提示功能未实现,所以只用等等或者马上贡献代码就好:)
参考
php7.4 ffi,PHP 7.4 前瞻:FFI相关推荐
- PHP7.4 FFI 扩展安全问题
在前面 [极客大挑战 2020] 的Roamphp5-FighterFightsInvincibly 题,遇到了 FFI扩展 调用函数进行rce to bypass disable_function, ...
- [RCTF 2019]Nextphp(php7.4的FFI扩展安全问题)
题目 打开题目,一段简单的php代码: <?php if (isset($_GET['a'])) {eval($_GET['a']); } else {show_source(__FILE__) ...
- php curl https_PHP FFI:一种全新的PHP扩展方式
(给PHP开发者加星标,提升PHP技能) 转自:laruence/鸟哥 www.laruence.com/2020/03/11/5475.html 随着 PHP7.4 而来的有一个我认为非常有用的一个 ...
- luajit日记-FFI库
2019独角兽企业重金招聘Python工程师标准>>> LuaJIT FFI LibraryThe FFI library allows calling external C fun ...
- openresty通过ffi调用一个c编写的base64动态库
base64编码的原理 base64不是加密,就是一种编码,将字符串的二进制按6个bit一组,每组的6个bit的形式转换一下. 每组6个bit对应的字符是 有特定的映射表决定. 映射表: 字符 序号 ...
- Rust FFI 与C语言互相调用
Rust FFI 与C语言互相调用 参考 cbindgen 简介 二进制方式构建 脚本构建 Demo程序说明 示例工程: makefile test脚本 基本数据类型 Rust侧 C侧 对象 Rust ...
- 01、Flutter FFI 最简示例
Flutter FFI 学习笔记系列 <Flutter FFI 最简示例> <Flutter FFI 基础数据类型> <Flutter FFI 函数> <Fl ...
- luajit ffi 小结
luajit ffi 小结 Lua 是一种语法简单,上手快的语言,虽然原生库比较少,但是可以方便的和 C 语言互相调用,常被用于脚本嵌入到 C 程序中.如 Redis 中可以加载 Lua 脚本,作用类 ...
- PHP 7.4/8.x FFI的使用例子
本篇主要讲例子,至于关于FFI的知识点,可以参考以下文档: 官网文档FFI介绍入口 http://php.p2hp.com/manual/zh/class.ffi.php 或 https://www. ...
- 03、Flutter FFI 函数
Flutter FFI 学习笔记系列 <Flutter FFI 最简示例> <Flutter FFI 基础数据类型> <Flutter FFI 函数> <Fl ...
最新文章
- 事务,Oracle,MySQL及Spring事务隔离级别
- 原始性能数字– Spring Boot 2 Webflux与Spring Boot 1
- Approximation and fitting、Statistical estimation
- java 小数处理_java 小数点处理
- Java多线程学习二十五:阻塞和非阻塞队列的并发安全原理||如何选择适合自己的阻塞队列?
- 同一路由器下两台电脑ping不通_复杂网络环境下路由器配置问题导致业务故障处理案例分享...
- php 无符号位移怎么使用,,(有符号位移)和(无符号位移)的使用方法,及差别...
- 深度学习之LSTM完全图解
- 固态硬盘替换机械硬盘
- 有了这些组件和模板,天下没有难做的移动端驾驶舱
- 视频码率与分辨率的参考表
- QQ如何应对中年困境?
- java vim编辑器的基本使用_vim编辑器之神的高效率使用
- python中使用modbus_tk操作浮点数
- .Net中DLL冲突解决(真假美猴王)
- HG 兄弟组织 D2 的“民营技术日报”故事
- 这都是什么奇葩网站,最后一个根本玩不转
- 游戏开发之棋牌游戏的未来
- 有限单元法基础 -- ING
- 12月原生APP的淘宝客+代理系统完整前后端带完整安装教程
热门文章
- 古城钟楼微博地支报时程序铛,100行代码实现,价值一天20万粉丝
- ASP.NET网站制作
- 解决SSLHandshakeException :sun.security.validator.ValidatorException: PKIX path building failed:
- ConneR and the A.R.C. Markland-N
- IO口电压域io-domain核对流程
- EL$JSTL简化jsp开发中的代码量
- java 自动登录_java代码实现自动登录功能
- 书名:男人一本书 前言
- 一文读懂卷积神经网络中的1x1卷积核
- Oracle Spatial 安装和使用