php-src-debug记录

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);