PHP获取Opcode及C源码

是什么

在开始之前, 必须要先介绍一下Opcode是什么.

众所周知, Java在执行的时候, 会将.java后缀的文件预先编译为.class字节码文件, JVM加载字节码文件进行解释执行. 而字节码文件存在的意义, 就是为了加速执行.

那么PHPOpcode与之类似, 也是从.php文件到执行的过程中, 所生成的预编译中间文件.

或者也可以这样粗鲁的理解, PHP程序是由C写的二进制程序, Opcode就是将.php文件翻译为c代码的结果.

Opcode有什么用我们最后再说, 先让我们看一下它长什么样子

获得

如何获得php文件的opcode呢? 在PHP的源码中, 可以通过c函数zend_compile_string获取PHP代码解析后的Opcode. 但是我们要是为了获取Opcode得深入到c, 是在有些得不偿失. 好在, 已经有前辈做好的扩展可直接获取. 既: vld.

vld 扩展

安装扩展:

# 安装扩展
pecl install https://pecl.php.net/get/vld
# 启用扩展. 若不是 docker, 将"extension=vld.so" 写入 php.ini 即可
docker-php-ext-enable vld
# 命令行查看, 确保扩展安装成功
php -m | grep vld

我们查看这段小小代码的opcode:

<?php
require 't.php';
$a = 1;
$b = $a;
echo $a;
var_dump($b);
exit(0);

执行如下命令可查看:

php -d vld.active=1 -d vld.execute=0 test.php

image-20220623215444490

对于vld的输出结果, 这里有作者的一篇说明文章: https://derickrethans.nl/more-source-analysis-with-vld.html

vld扩展支持的配置. php的扩展配置可以在跑脚本的时候, 通过-d参数临时修改, 也可以直接修改php.ini文件. 这里建议临时修改, 毕竟并不是所有脚本都要输出opcode.

  • vld.active: 是否输出opcode. 默认为0
  • vld.execute: 是否要运行代码. 默认为1
    • 当为0时, 不会输出require的其他文件内容.
  • vld.verbosity: 显示更详细的信息. 默认为0, 可能值为0123
  • 等等吧, 还有一些其他的配置项, 不过感觉没什么用就不列举了. 可通过命令php -r 'phpinfo();' | grep vld 查看支持的所有配置.

phpdbg

按理说, 这么常用的操作, 应该是带有官方工具才对的吧. 哎, 这不就来了么. phpdbgphp程序的调试器(迄今为止, 我从来没有用过. 甚至没有用过断掉调试). 但同时它也可以用来生成opcode.

命令: phpdbg -p test.php

image-20220623221234892

生成结果与vld扩展基本一致.

还可以通过opcache来生成, 不过就有些绕了, 在这里就不介绍了. 简单介绍一下这两种方式就好.

phpdbg生成的话, 貌似只支持单文件生成(也可能是我没找到使用方法), vld则可以带着引入的文件一起打印出来.

不过对于我们分析程序来说, phpdbg一般是够用的了.

使用

那么上述生成的opcode是什么意思呢? 很遗憾, 官网对opcode的解释已经找不到了, 不过zend opcode document为关键词搜索的话, 还是能搜到一大堆的. 这里就不再重复罗列其含义了了.

我就简单说一下它有什么用吧. 总不能咱这折腾了半天, 拿到了opcode然后就没有然后了.

opcodephp文件翻译后的中间码, 通过它, 我们大致可以知道php文件的执行过程.

又因为php是通过c层面进行解析的, 每一条opcode都会解析为一个c函数进行执行. 对于分析源码、查找问题等等, 可直接定位到php代码在c源码级别的执行, 方便得很嘛. (类似需求我之前碰到过很多次, 比如查找sort的实现原理等等)

所有操作码都定义在源码文件zend_vm_opcodes.h中. 既然php会根据不同的操作码, 执行不同的操作. 那么, 我们是不是就可以根据操作码, 来还原php底层执行的操作了呢? 不好意思, 可以但是很难. php通过函数zend_vm_get_opcode_handler来获取操作码对应的handle函数. 但是, 当看过源码后, 我失望了, 函数zend_vm_get_opcode_handler获取的过程是一个动态解析的过程. 也就是说, 同一个操作码, 解析后可能会是不同的函数. 啊这不就尴尬了么.

于是, 不信邪的我, 决定通过修改PHP源码来实现. 为了方便使用, 我将其封装为了一个docker镜像, 对实现方式感兴趣的, 请移至Dockerfile. 使用方式如下(镜像的详情见: 调试镜像):

docker run --rm -it -v `pwd`:`pwd` -w `pwd` hujingnb/php_opcode:8.1.7 php test.php

如下所示输出结果:

image-20220625102703295

同时会在当前目录生成opcode.log文件, 内容如下:

image-20220625102750935

可查看到opcode及每一个操作码具体执行的c函数是哪个.

其中require所对应的opcodeINCLUDE_OR_EVAL, 所执行的c函数为ZEND_INCLUDE_OR_EVAL_SPEC_CONST_HANDLER.

总结

至此, opcode我们也见过了, 也能将php文件转换为opcode了. 不过说实话, 这玩意在平常的开发中不能说是用不到, 可以说是根本用不到.

它的作用我觉得还是在分析源码的时候. 可以方便的看到php代码的每一步操作, 其对应的源码执行.

以后研究源码, 或者是对php行为感到疑惑的时候, 有这个工具就可以加速解惑的过程啦.

调试镜像

介绍

此镜像是为了方便查看phpopcode及操作码对应执行的c函数. 为了方便对php源码进行分析. 通过结果, 可通过php文件直接定位到php源码的c函数.

此镜像在vld扩展的基础上, 额外输出了:

  • 操作码对应的c执行handle函数

此镜像基于php分支php-8.1.7, commitId 为d35e577a1bd0b35b9386cea97cddc73fd98eed6d.

镜像地址. 这里就不说明我是怎么做的了, 感兴趣的可查看Dockerfile

使用

通过此镜像获得操作码简单方式:

docker run --rm -it -v `pwd`:`pwd` -w `pwd` hujingnb/php_opcode:8.1.7 php test.php

此命令产生如下结果:

  1. 获取php文件的opcode
  2. 获取opcode操作码对应的执行c函数. 将结果输出到当前目录的opcode.log文件中

高级

若需要安装扩展, 可进入镜像后执行如下操作:

  • php源码编译安装gd扩展: docker-php-ext-configure gd
  • php源码安装gd扩展: docker-php-ext-install gd
  • 启用gd扩展: docker-php-ext-enable gd
  • 通过官方库安装扩展: pecl install redis && docker-php-ext-enable redis

环境变量:

  • PHP_SRC_DIR: 源码位置
  • PHP_INI_DIR: 配置文件位置
  • PHP_INSTALL_DIR: 安装路径

若需要添加额外操作, 可基于此镜像进行操作, 请根据Dockerfile自行修改.

若想要修改php源码, 可在修改后执行命令重新安装: docker-php-install

原文地址: https://hujingnb.com/archives/836

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/508343.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PHP require/include 区别

前言 在PHP中, 载入文件可以选择使用require, 也可以使用include, 那么那他们有什么区别呢? 看了网上的一些文章, 说他们使用场景不同, require一般在文件开头引入文件, include一般在函数中动态引入文件. 但是我觉得并不是这么简单, require是作为语言结构(关键字)出现的, …

Golang 接口原理

问题 小提示, 若想直接查看原理, 可从接口原理开始查看. 有这样一段GO代码: func main() {var obj interface{}fmt.Printf("obj nil. %b\n", obj nil)type st struct{}var s *stobj sfmt.Printf("s nil. %b\n", s nil)fmt.Printf("obj nil. …

三星识别文字_比亚迪电子助力三星Galaxy Note 10系列霸气首发!

三星有子初长成气宇轩昂 秀美俊逸减之一分则嫌柔增之一分则嫌赘2019年8月7日于纽约巴克莱发布Galaxy Note 10系列用简约 重构美三星Galaxy Note 10与Galaxy Note 10分别搭载了6.3英寸和6.8英寸的超感官全视曲面屏&#xff0c;均采用单摄挖孔屏&#xff0c;开孔位于屏幕正上方。…

lisp 设计盘形齿轮铣刀_机械设计基础——周转轮系传动比的计算

点击上方蓝色字体&#xff0c;关注我们15(视频来源于网络&#xff0c;仅供学习交流&#xff0c;侵权请联系删除)机械计重点学习指导机械原理全书重点提要轴的结构改错机械设计作业集01机械设计作业集02机械设计作业集答案机械原理作业集机械原理作业集答案轴的强度计算院校推荐…

b+树阶怎么确定_B站公布年度弹幕,这个排名我不太服气

也忘记了是从什么时候开始&#xff0c;B站开始公布自己的年度弹幕了&#xff0c;今年的年度弹幕排名前五的分别是&#xff1a;爷青回、武汉加油、有内味了、双厨狂喜、禁止套娃。话说今年真的是不容易啊&#xff0c;过年那段时间以及上半年不会忘记那一幕幕感人深邃的瞬间&…

css打印适应纸张_从生态平衡到打印机故障分析

生态平衡(ecological equilibrium)是指在一定时间内生态系统中的生物和环境之间、生物各个种群之间&#xff0c;通过能量流动、物质循环和信息传递&#xff0c;使它们相互之间达到高度适应、协调和统一的状态。也就是说当生态系统处于平衡状态时&#xff0c;系统内各组成成分之…

html5调用系统声音1s响一次_20款奔驰GLC260提车改柏林之声音响,音乐诉请,为爱发声!...

奔驰GLC车型在2020上半年可谓是风生水起&#xff0c;尤其是2020年1-5月份的豪华品牌SUV排名中&#xff0c;奔驰GLC车型以58982的销售量遥遥领先&#xff0c;同比增长了2%&#xff0c;奔驰GLC5月销量高达15275辆&#xff0c;再次打败老对手奥迪Q5L&#xff0c;夺得豪华SUV销量冠…

kotlin将对象转换为map_将网站转换为Photoshop文档

WebToLayers是一款能够帮助大家将网页转换成图像格式的软件&#xff0c;能够Web页面转换成PNG&#xff0c;JPG以及PSD格式的图片。当网页转换为PSD的时候&#xff0c;网页的各个要素都会自动转换为相应的图层&#xff0c;使得大家能够对PSD格式的网页进行设计与管理。WebToLaye…

centos更换网卡后怎么更新配置_CentOS安装

服务器使用的Linux操作系统都使用了CentOS来进行安装&#xff0c;CentOS是一个开源的Linux发行版&#xff0c;具有很好的稳定性和更多的可扩展行。为了能够正常使用Docker&#xff0c;我们将使用CentOS7及以上版本。​下载地址&#xff1a;https://www.centos.org/download/ ​…

centos普通用户修改文件权限_Linux实战014:Centos创建用户并添加root授权

刚收到在腾讯云申请的云服务器8台&#xff0c;现在准备分配给不同项目组来使用。为了确保系统及账号的安全&#xff0c;root账号不能直接给到他们。因为root的权限太大&#xff0c;任何的误操作就可能导致系统异常或者数据丢失找不回来。而且我们这是生产环境&#xff0c;账号会…

mongodb 导出txt_(干货)前端实现导出excel的功能

前言 导出功能其实在开发过程中是很常见的,平时我们做导出功能的时候基本都是后台生成&#xff0c;我们直接只需要调一支接口后台把生成的文件放到服务器或者数据库mongodb中,如果是放到mongodb中的话,我们需要从mongodb中通过唯一生成的id去拿到文件,最后window.location.href…

1971旗舰cpu intel_CPU的历史

很多人都对电脑硬件有一点的了解&#xff0c;本人也算略懂一二&#xff0c;所以今天来为大家说说电脑的主要硬件之一––CPU(中央处理器)。那么我们知道世界上造CPU的公司主要就是Intel和AMD。其实仔细想想&#xff0c;CPU的主要成分是什么?是硅(Si)&#xff0c;硅从那里来&am…

文本显示变量_【RPA课堂】UiPath中的变量、数据类型和组件

自动化出现的那一天起&#xff0c;就有了各种各样的工具来满足自动化的需要。无论是用于windows桌面自动化的简单工具&#xff0c;还是用于企业自动化大量任务的工具&#xff0c;它们都有自己的功能。UiPath就是这样的工具&#xff0c;在本文中&#xff0c;我们介绍一些非常基本…

bootstrap上传图片可实现查看上一张图片和下一张图片_如何实现像人民日报微信推文一样的的点亮效果?...

如何实现向人民日报微信推文一样的的点亮效果&#xff1f;有两种方法&#xff1a;方法一&#xff1a;就是使用代码在编辑器进行编辑emmmmmm这个方法贼麻烦&#xff0c;需要调至HTML模式……方法二&#xff1a;在现有编辑器模板下利用SVG动画进行编辑&#xff0c;因为点亮效果本…

设置log缓存_node多级缓存之redis缓存

在node项目开发过程中&#xff0c;缓存常常被用来解决高性能、高并发等问题。在我们的实际项目中&#xff0c;运用缓存的思路是内存缓存-->接口-->文件缓存。前面的总结中已经详细的说明了怎么实现和封装内存缓存和文件缓存。虽然二级缓存已经基本能够满足现在的所有场景…

c++实现决策树分类汽车评估数据集_R有监督机器学习-分类方法

当我们说机器学习的的时候&#xff0c;我们在说什么&#xff1f;来源于mlr3包的作者&#xff1a;https://mlr3book.mlr-org.com/basics.html上图解释了完整的机器学习流程&#xff0c;包括构建任务、准备训练数据集及测试数据集、选择学习方法&#xff08;leaner&#xff09;、…

lingo编程的主要方法_java并发编程 --并发问题的根源及主要解决方法

并发问题的根源在哪首先&#xff0c;我们要知道并发要解决的是什么问题&#xff1f;并发要解决的是单进程情况下硬件资源无法充分利用的问题。而造成这一问题的主要原因是CPU-内存-磁盘三者之间速度差异实在太大。如果将CPU的速度比作火箭的速度&#xff0c;那么内存的速度就像…

Mysql中Drop删除用户的名字_mysql5.5 使用drop删除用户

在说这个问题之前我们先讨论下关于在mysql中删除用户的方法和问题&#xff1a;其实在以前我删除mysql中的账号的时候用delete&#xff0c;一直没注意其实用这个命令删除账号会有一个问题就是使用delete删除账号后&#xff0c;只会清除user表的&#xff0c;在其它表中的信息还是…

docker建多个mysql_《容器化系列二》利用Docker容器化技术安装多个mysql

前提说明安装的Linux系统版本为Centos7.x一、安装docker并测试1、安装yum相关工具包///安装yum相关工具包yum install -y yum-utils device-mapper-persistent-data lvm2//发些报错&#xff0c;关闭刚刚睡眠中的进程kill -9 13312//再次执行yum install -y yum-utils device-ma…

mysql 元数据获取_[MySQL] 获取元数据的步骤

[MySQL] 获取元数据的方法 MySQL提供了以下三种方法用于获取数据库对象的元数据&#xff1a; 1)show语句 2)从INFORMATION_SCHEMA数据库里查询相关表 3)命令行程序&#xff0c;如mysqlshow, mysqldump 用SHOW语句获取元数据 MySQL用show语句获取元数据是最常用的方法&#xff0…