DockerfileFROM centos:7
# 安装依赖工具
RUN yum -y install gcc gcc-c++ gdb autoconf libjpeg libjpeg-devel libpng libpng-devel freetype freetype-devel libxml2 libxml2-devel zlib zlib-devel glibc glibc-devel glib2 glib2-devel bzip2 bzip2-devel ncurses ncurses-devel curl curl-devel e2fsprogs e2fsprogs-devel krb5 krb5-devel libidn libidn-devel openssl openssl-devel openldap openldap-devel nss_ldap openldap-clients openldap-servers gd gd2 gd-devel gd2-devel perl-CPAN pcre-devel libicu-devel wget
# 下载指定版本源码,如果需要调试其它版本,可自行切换
RUN wget -O /tmp/php.tar.gz https://www.php.net/distributions/php-7.1.0.tar.gz
RUN mkdir ~/php71 && tar -xvf /tmp/php.tar.gz --strip-components 1 -C ~/php71
# 安装目录 /var/php71
# 源码目录 /var/www
###################################################################
# 1. 生成 Makefile (看是否要指定安装目录, 和开启的扩展, 这里安装到了 /var/php71)
# 2. 编译(根据生成的 Makefile)
# 3/ 安装(执行 Makefile 中的 install部分)
RUN cd ~/php71 && \
./configure --prefix=/var/php71 --enable-fpm --enable-debug --enable-phpdbg-debug CFLAGS="-g3 -gdwarf-4" && \
make && \
make install
# 1. 复制 php 配置文件
# 2. 复制 fpm 主配置文件
# 3. 加入环境变量
RUN cp ~/php71/php.ini-production /var/php71/lib/php.ini && \
cp /var/php71/etc/php-fpm.conf.default /var/php71/etc/php-fpm.conf && \
echo $'export PATH=$PATH:/var/php71/bin:/var/php71/sbin' >> ~/.bashrc
#
## 安装 nginx, 用于调试 php-fpm
RUN yum -y install epel-release && \
yum -y install nginx
## 针对 nginx 对 fpm 的配置
RUN echo $'server {\n\
listen 9999;\n\
root /var/www;\n\
# \n\
location / {\n\
root /var/www;\n\
index index.php index.html index.htm;\n\
}\n\
# \n\
error_page 500 502 503 504 /50x.html;\n\
# \n\
location ~ \.php$ {\n\
fastcgi_pass 127.0.0.1:9000;\n\
fastcgi_index index.php;\n\
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;\n\
include fastcgi_params;\n\
}\n\
}' > /etc/nginx/conf.d/fpm.conf
## fpm 的配置
RUN echo $'[www]\n\
user = nobody\n\
group = nobody\n\
listen = 127.0.0.1:9000\n\
pm = static\n\
pm.max_children = 1' > var/php71/etc/php-fpm.d/www.confdocker-compose.ymlversion: '3'
services:
centos:
build: ./
tty: true
# 不开启这个会导致`debug php-fpm`进程的时候会提示`No symbol table is loaded`
cap_add:
- SYS_PTRACE
working_dir: /var/www
volumes:
- ./:/var/www
ports:
- 9999:9999docker-compose up -d && docker-compose exec centos bashGDB调试docker-compose exec centos bash
// tui 模式运行 也可先调试, 然后 CTRL+X+A
gdb --tui
// 调试可执行文件
gdb php
// 调试进程号
gdb --pid=xxx常用命令 | 说明 |
|---|---|
run | 重新开始运行文件 |
start | 单步执行,运行程序,停在第一执行语句 |
list | 查看原代码,简写 l |
set | 设置变量的值 |
next | 单步调试(逐过程,函数直接执行), 简写 n |
step | 单步调试(逐语句:跳入自定义函数内部执行), 简写 s |
break | 断点, 简写 b, 参数 function filename:linenum filename:function |
delete | 删除断点,可跟断点 number |
finish | 结束当前函数,返回到函数调用点 |
continue | 继续运行,简写 c |
打印值及地址,简写 p | |
quit | 退出 gdb, 简写 q |
info | 查看函数内部局部变量的数值,简写 i |
php-fpm(php-fpm已设置为只有一个worker进程,方便跟踪调试)docker-compose exec centos bash
php-fpm
nginx
# 查看 worker 进程号
ps aux | grep fpm
gdb --pid=xxxUnderstandCLion和Visual Studio 很多代码都不能进行跳转Dockerfile中PHP版本相同的源码用于阅读PHP按本的PHP源码cd ~/php71/ext/curl/var/php71/bin/phpize./configure -with-php-config=/var/php71/bin/php-configmakemake install## 假设默认对齐 4 个字节
struct A
{
int a;
char b;
short c;
char d;
}
成员 a 占用 4 个字节
成员 b 占用 1 个字节
成员 c 占用 2 个字节, 对齐是 2n (b 成员后的填空 1 个字节)
成员 d 占用 1 个字节, 偏移 8
最后填充的字节为默认字节位填满, 就是填充空到 11
总占用字节为: 0 ~ 11 = 12 个字节
## c 是找到 2n 的位置
aaaa b0cc d0000x1A2B3C4D0x1A | 0x2B | 0x3C | 0x4D0x4D | 0x3C | 0x2B | 0x1A// 其他文件会使用
typedef struct _zend_refcounted zend_refcounted;
typedef struct _zend_string zend_string;
typedef struct _zend_array zend_array;
typedef struct _zend_object zend_object;
typedef struct _zend_resource zend_resource;
typedef struct _zend_reference zend_reference;
typedef struct _zend_ast_ref zend_ast_ref;
typedef struct _zend_ast zend_ast;
// 只在当前 zend_types.h 文件使用
typedef union _zend_value {
zend_long lval; /* long value */
double dval; /* double value */
zend_refcounted *counted;
zend_string *str;
zend_array *arr;
zend_object *obj;
zend_resource *res;
zend_reference *ref;
zend_ast_ref *ast;
zval *zv;
void *ptr;
zend_class_entry *ce;
zend_function *func;
struct {
uint32_t w1;
uint32_t w2;
} ww;
} zend_value;zval所有变量的实现(zval是_zval_struct的别名) value[zend_value]存储变量的值u1[union] 存储变量类型u2[union] 存储扩展字段zend_value (zend_value是_zend_value的别名) zval.u1去获取到对应的类型值zval.u1.v.type有几种特殊值,0是未定义变量,1是null,2是true,3是false, 不需要存储实际的值zval.u1.v.type=4是IS_LONG, 就会去获取zval.value.lvalzval.u1.v.type=10是IS_REFERENCE,就会去获取zval.value.ref,是一个zend_reference类型(_zend_reference的别名)_zend_reference结构体里有一个成员val是zval类型, 这个val才是存储实际的值zval.value.ref.val这个结构体内部的值, 因为引用变量指向zval.value.ref的指针都是一样的, 所以都会修改成功unset操作), 只是把当前zval的u1.v.type赋值为0,内部的引用指针还是指向实际存储的zvalzvalPHP最令人感受到魅力所在的地方就是数组了Map,Queue, Stack. 特别是对于Map, 并且PHP对Map数组提供了顺序存储, 真的是令人又爱又恨. 使用方便缺容易导致出现问题 zval.value.arr.arData
─┐
│
│
│
│
▼
┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐
│ │ │ │ │ │ │ │ │ │ │ │ │
───────┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴─────────►
-6 -5 -4 -3 -2 -1 0 1 2 3 4 5 6
│ │ │
│ ├─────────────────┘
│ │ bucket size
│ │
└─────────────────┘
map table sizePHP是运行时进行解释或编译token流token流生成抽象语法树(AST)op codezend虚拟机把op code转成机器码执行php_module_startup 模块初始化阶段
│
▼
php_request_startup 请求初始化阶段
│
▼
php_execute_script 脚本执行阶段
│
▼
php_request_shutdown 请求关闭阶段
│
▼
php_module_shutdown 模块关闭阶段php_module_startup
│
▼
fcgi_accept_request ◄──────┐
│ │
│ │
▼ │
php_request_startup │
│ │
│ │
▼ │
fpm_request_executing │
│ │
▼ │
php_execute_script │
│ │
│ │
▼ │
fpm_request_end │
│ │
│ │
│ │
▼ │
php_request_shutdown │
│ │
▼ │
php_module_shutdown────────┘kill fpm-master: master进程会同时把worker进程杀死, 服务不可访问kill -9 fpm-master: master进程直接被杀死, worker进程还存活, 可提供服务kill fpm-worker: worker进程被杀死不影响,master进程会重新调度管理_表明是标准库的变量__开头表明是编译器的变量typedef说明 zend_string)中字符串为什么不是char *,而是char[1] C struct hack是一种把结构体所有成员分配在同一块内存的技术, 利于cpu cache,也是一种可变长数组的实现方式char[0], 但是 some compilers would allow [0] herephp-fpm Remote Code Execution 分析(CVE-2019-11043) gdb in docker container returns “ptrace: Operation not permitted.” FastCGI进程管理器(FPM) PHP 内核与原生扩展开发