当前位置: 首页 > 后端技术 > PHP

PHP7.4预览:FFI

时间:2023-03-29 20:01:48 PHP

FFI扩展通过了RFC,正式成为PHP7.4的捆绑扩展库(BundledExtensions)。什么是FFIFFI(ForeignFunctionInterface),即外部函数接口,是指在一种语言中调用另一种语言代码的技术。PHP的FFI扩展是一种允许您在PHP中调用C代码的技术。FFI的使用非常简单,只需要声明和调用两步。对于有C语言经验但不懂Zend引擎的程序员来说,这简直打开了新世界的大门,可以快速使用C类库。进行原型测试。(这里有图:溜了,你要懂C。。。)下面通过3个例子来看看FFI是怎么用的。Libbloomlibbloom是一个用C语言实现的布隆过滤器。知名用户是Shadowsocks-libev。我们来看看如何通过FFI调用PHP中的libbloom。第一步是从头文件bloom.h中复制主要数据结构和函数声明:$ffi=FFI::cdef("structbloom{intentries;doubleerror;intbits;intbytes;inthashes;doublebpe;unsignedchar*bf;intready;};intbloom_init(structbloom*bloom,intentries,doubleerror);intbloom_check(structbloom*bloom,constvoid*buffer,intlen);intbloom_add(structbloom*bloom,constvoid*buffer,intlen);voidbloom_free(structbloom*bloom);","libbloom.so.1.5");FFI目前不支持预处理器(除了FFI_LIB和FFI_SCOPE),所以宏定义必须自己扩展。之后就可以创建声明的数据结构并通过$ffi调用函数了://创建一个bloom结构体,然后使用FFI::addr获取地址//libbloom的函数都是使用bloom结构体的指针$bloom=FFI::addr($ffi->new("structbloom"));//调用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);$布卢姆=空;LinuxNamespaceLinux命名空间是容器技术的基石之一,FFI可以直接调用glibc对应的系统调用包,从而通过PHP实现容器。下面是让bash在新命名空间中运行的示例。首先是一些常量,可以从Linux的头文件得到://cloneconstCLONE_NEWNS=0x00020000;//挂载命名空间constCLONE_NEWCGROUP=0x02000000;//cgroup命名空间常量CLONE_NEWUTS=0x04000000;//utsnamenamespaceconstCLONE_NEWIPC=0x08000000;//IPC命名空间常量CLONE_NEWUSER=0x10000000;//用户命名空间constCLONE_NEWPID=0x20000000;//pid命名空间常量CLONE_NEWNET=0x40000000;//网络命名空间//mountconstMS_NOSUID=2;常量MS_NODEV=4;常量MS_NOEXEC=8;常量MS_RE=116384;接下来,我们将使用的函数声明:$cdef="//forkprocessintclone(int(*fn)(void*),void*child_stack,intflags,void*arg);//mountfileSystemintmount(constchar*source,constchar*target,constchar*filesystemtype,unsignedlongmountflags,constvoid*data);//设置gidintsetgid(intgid);//设置uidintsetuid(intuid);//设置主机名intsethostname(char*name,unsignedintlen);";$libc=FFI::cdef($cdef,“libc.so.6”);定义我们的子进程://生成容器ID$containerId=sha1(random_bytes(8));//定义子进程$childfn=function()use($libc,$containerId){usleep(1000);//等待uid/gid映射$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;//派生子进程$pid=$libc->clone($childfn,$child_stack,CLONE_NEWUSER|CLONE_NEWNS|CLONE_NEWPID|CLONE_NEWUTS|CLONE_NEWIPC|CLONE_NEWNET|CLONE_NEWCGROUP|SIGCHLD,null);//设置UID、GID映射,将容器中的根映射到当前用户$uid=getmuid();$gid=getmuid();file_put_contents("/proc/$pid/uid_map","0$uid1");file_put_contents("/proc/$pid/setgroups","deny");file_put_contents("/proc/$pid/gid_map","0$gid1");//等待子进程pcntl_wait($pid);glibc的clone函数就是对clone系统调用的封装。它需要一个函数指针作为子进程/线程的执行体。我们可以直接使用PHP的闭包和匿名函数作为函数指针。运行效果:$phpcontainer.phpsh-5.0#id#容器中是rootuid=0(root)gid=0(root)groups=0(root),65534(nobody)sh-5.0#psaux#独立PID进程空间USERPID%CPU%MEMVSZRSSTTYSTATSTARTTIMECOMMANDroot10.00.1105244124pts/1S10:190:00/bin/shroot30.00.0158643076pts/1R+10:190:00psauxsh-5.0#ipa#独立网络命名空间1:lo:mtu65536qdiscnoopstateDOWNgroupdefaultqlen1000link/loopback00:00:00:00:00:00brd00:00:00:00:00:00raylibraylib是一个功能丰富、简单易用的游戏库,简单打包后即可在PHP中使用。以下示例实现了一个跟随鼠标的圆圈: