php-src-debug记录
wsl + vscode + qemu + bear 甚至可以调试跨架构的操作系统源码
php-src clone以及编译
https://github.com/php/php-src 请RTFM,很多都可以在mannul找到
git clone https://github.com/php/php-src.git
git checkout PHP-8.2
# 安装一些必要的依赖,based on ubuntu
sudo apt install -y pkg-config build-essential autoconf bison re2c libxml2-dev libsqlite3-dev
./buildconf
./configure --disable-all --enable-debug --enable-phar --prefix=/home/yyz/php-src/build/php/
# 推荐使用bear生成compile_commands.json
# bear -- make -j8
make -j8
bear神器
https://github.com/rizsotto/Bear 请RTFM,很多都可以在mannul找到
Bear is a tool that generates a compilation database for clang tooling.
Usage – bear -- <your-build-command>
这个编译出来后会在目录下面生成compile_commands.json
,在vscode中实现代码跳转的话需要clangd
插件支持,当然在debug的时候本身gdb会自带符号表信息,所以可以不需要这个就可以自动跳转。
vscode-clangd-plugins支持
找到插件后装好即可
注意需要clangd的binary在PATH下面,这样vscode才能找到,但是vscode会自动帮你安装的,如果出错,请看log,或者手动安装。
RTFM
WSL - vscode 远程调试
一些插件
- WSL
- C/C++ Extension Pack
- Native Debug
- others you like
wsl远程开发环境
WSL2的优点无需多说,yyds
下载插件WSL
找到左侧栏的远程按钮,自己探索一下就知道了
打开你的php-src目录,这样的环境就是vscode远程开发环境了
开始你的调试准备工作
.vscode/launch.json文件 – 控制调试相关
在vscode左侧Run and Debug -> add Configuration
可以添加控制当前程序调试的相关配置文件launch.json
,你可以设置成这样,相关参数自行搜索了解
{
// 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "debuug php source",
"type": "cppdbg",
"request": "launch",
"program": "/home/yyz/php-src/build/php/bin/php",
"args": ["-f","/home/yyz/php-src/yyz.php"],
"stopAtEntry": true,
"cwd": "${fileDirname}",
"environment": [],
"externalConsole": false,
"MIMode": "gdb"
}
]
}
.vscode/tasks.json文件 – 控制编译相关
可选,可以知道这个文件配置好的话,可以在debug之前重新编译一下你的binary,这样你可以实时patch代码。
例如,下面是chatgpt为我生成的一个json示例
{
// generated by chatgpt3.5
// 在task中,label可以定义任务的名称,在command中定义重新编译php的命令。在这个例子中,每次重新编译时会先运行make clean来清空之前的编译信息,然后再运行buildconf和configure等命令重新配置和编译php。
// 此外,还可以在.vscode/tasks.json中配置problemMatcher以捕获编译过程中出现的问题(例如警告和错误),以便能够在输出面板中捕获和解释问题。
"version": "2.0.0",
"tasks": [
{
"label": "rebuild-php",
"type": "shell",
"command": "make clean && ./buildconf && ./configure && make",
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": {
"owner": "php",
"fileLocation": ["relative", "${workspaceFolder}/"],
"pattern": {
"regexp": "^(.*)\\((\\d+)\\):\\s+(warning|error):\\s+(.*)$",
"file": 1,
"line": 2,
"severity": 3,
"message": 4
}
}
}
]
}
如果一些准备工作都就绪了,按下f5,程序会停在./sapi/cli/php_cli.c:1161
glibc调试
查看本地glibc源码
其实想要调试libc,可以不看本地的glibc版本号
ldd --version
下载源码
git clone https://mirrors.tuna.tsinghua.edu.cn/git/glibc.git
# 切换分支,如果必要的话
git branch -r
git checkout origin/release/2.35/master
编译
mkdir build
cd build
../configure --prefix=/glibc/x64/2.23/ --disable-werror --enable-debug=yes
make j8
sudo make install
vscode或者gdb中添加环境变量配置即可调试时步进到glibc源码
{
"version": "0.2.0",
"configurations": [
{
"name": "debuug php source",
"type": "cppdbg",
"request": "launch",
"program": "/home/yyz/php-src/build/php/bin/php",
"args": ["-f","/home/yyz/php-src/yyz.php"],
"stopAtEntry": true,
"cwd": "${fileDirname}",
"environment": [
{
"name": "LD_LIBRARY_PATH",
"value": "/glibc/x64/2.35/lib"
}
],
"externalConsole": false,
"MIMode": "gdb"
}
]
}
简单看看base64_decode和system的函数实现
不想写了。。。之前录了个视频给学弟们,应该有简单的提到。直接总结一下吧:
base64_decode
- 实现了下面的论文描述的base64加解密算法
// ext/standard/base64.c:793
/* See: "Faster Base64 Encoding and Decoding using AVX2 Instructions"
* https://arxiv.org/pdf/1704.00605.pdf */
当时我看base64的算法只是想找到为什么base64_decode的时候会跳过处理一些不属于base64 table里面的字符。
根据论文中描述的可以找到如下说法
然后也确实能在源码中找到跳过不合法字符的算法实现:
// ext/standard/base64.c:269
ch = base64_reverse_table[ch];
if (!strict) {
/* skip unknown characters and whitespace */
if (ch < 0) {
continue;
}
} else {
/* skip whitespace */
if (ch == -1) {
continue;
}
/* fail on bad characters or if any data follows padding */
if (ch == -2 || padding) {
goto fail;
}
}
可以自行打断点调试
system
总结:
-
传入的字符串直接以char * 数组整个传进去
-
最底层调用了libc中的popen函数
-
popen最底层调用了
/bin/sh
来传递命令执行的参数 -
clone3系统调用派生新的进程用以执行
/bin/sh
-
跟进
__clone_internal
即可,代码如下// /home/yyz/glibc/sysdeps/unix/sysv/linux/spawni.c:388 new_pid = __clone_internal (&clone_args, __spawni_child, &args);
-
(注意是先尝试调用clone3,并且在ubuntu22上可以直接成功,这一点可以自行源码调试发现)
-
-
注意上面的
__spawni_child
函数的第二个参数__spawni_child
是一个函数指针,他会处理子进程的execve
的流程 -
最终调用execve的地方在下面的代码,(与教科书上的描述一致,fork+execve,只不过细微处理上面clone3比fork处理的更好)
// /home/yyz/glibc/sysdeps/unix/sysv/linux/spawni.c:290 args->exec (args->file, args->argv, args->envp);