前言
在看代碼的時候遇到了PHP的一些函數,有些函數的特性很魔性,並不好理解。
於是嘗試搭建環境對PHP源碼進行調試,希望更加深入的一些理解PHP的特性。
必備安裝
目標:在Windows環境下,構建PHP7.2的源代碼編譯和調試環境
安裝VisualStudio
首先需要安裝最強IDEVisual Studio,這個軟件的版本有很多,我選擇的是Visual Studio Professional 2017 (version 15.7)。
下載鏈接:
ed2k://|file|mu_visual_studio_professional_2017_version_15.3_x86_x64_11100064.exe|1069960|900673A59F0798822207F72FAA0DA6A9|/
需要其他版本(VS2015等),可以到MSDN上的開發人員工具欄目自行選擇下載即可。
安裝過程非常簡單,注意兩個調試必備勾選,其他的選項根據自己需求選擇
php-sdk
PHP SDK是用於Windows PHP構建的工具包,也是必不可少的工具。
下載地址:
https://github.com/Microsoft/php-sdk-binary-tools
我選擇的版本爲最新版php-sdk-2.1.7。
PHP
本文主角,我選擇的版本爲PHP 7.2.1。
下載地址:
https://github.com/php/php-src
下載完成後使用Git Bash切換分支
cd php-src git checkout PHP-7.2.1
編譯PHP
進入php-sdk的目錄,可以看到目錄下面有4個Windows批處理文件
前面安裝的是Visual Studio 2017,操作系統也是64位的,因此這裏選擇phpsdk-vc15-x64.bat,打算編譯64位的。
在php-sdk目錄打開CMD窗口,運行phpsdk-vc15-x64.bat。
可以發現有了新的Shell提示符$。
繼續在新的shell下運行命令
phpsdk_buildtree phpdev
這時候我們會發現php-sdk這個目錄下面會多一個名爲phpdev的文件夾。
注意Shell的運行路徑也發生了變化php-sdk\phpdev\vc15\x64\。
再將php-src整個文件夾移動至php-sdk\phpdev\vc15\x64\下面。
然後shell中進入php-src目錄,執行命令,下載依賴關係組件。
phpsdk_deps --update --branch maste
成功信息如下:
運行buildconf.bat生成的configure文件,配置好參數,執行命令如下
configure --disable-all --enable-cli --enable-debug
成功信息如下:
執行編譯命令nmake
編譯成功信息爲:
SAPI sapi\cli build complete
可執行的二進制文件路徑爲
php-sdk\phpdev\vc15\x64\php-src\x64\Debug_TS\php.exe
觀察是否輸出php信息,編譯成功則輸出
php.exe -v
調試配置
斷點調試的需要一個趁手的工具,可以使用之前安裝的Visual Studio 2017,但我個人選擇的是輕量級的Vs code。
安裝Vs code,然後這裏需要安裝C/C++的拓展,調試的方式爲啓動調試。
點擊調試 --> 打開配置,設置配置文件launch.json的參數如下
-
program,二進制可執行文件路徑。
-
args,同目錄下運行的PHP文件,也就是我們要調試的文件
-
cwd,二進制可執行文件目錄
點擊調試按鈕,即可開始調試。
調試getimagesize函數
這裏選擇getimagesize這個函數進行斷點調試。
探究爲何這個函數如何加載網絡圖片資源;爲何這個函數在Windows下<爲通配符,
修改調試的1.php內容如下
<?php getimagesize("http://www.rai4over.cn/images/avatar.jpg"); #getimagesize("./avatar.jpg"); ?>
在php-sdk\phpdev\vc15\x64\php-src\ext\standard\image.c中設置getimagesize的斷點
點擊調試後程序會停在這裏則表示斷點成功。
通常會使用F10,F11進行調試。
- F10,單步跳過,調試時不進入函數內部。
- F11,單步調試,調試時進入函數內部。
比較麻煩的,單純的F11調試耗費時間,而F10又可能跳過函數關鍵函數,難以定位。
於是我便採F10爲主,提升調試效率,一邊F10,一邊打開Wireshark觀察HTTP請求流量。
當過某個函數產生流量後,再F11進入函數內部進行調試。
最終得到函數調用棧(由下至上):
-
send
-
php_sockop_write
-
_php_stream_write_buffer
-
_php_stream_write
-
php_stream_url_wrap_http_ex
-
php_stream_url_wrap_http
-
_php_stream_open_wrapper_ex
-
php_getimagesize_from_any
-
PHP_FUNCTION
send()是Windows Api,能夠通過已經建立的連接發送數據。
在phpdev\vc15\x64\php-src\main\streams\xp_socket.c第77行被調用。
didwrite = send(sock->socket, buf, XP_SOCK_BUF_SIZE(count), (sock->is_blocked && ptimeout) ? MSG_DONTWAIT : 0);
這也是這個函數加載網絡圖片資源的原因,之前印象中一直以爲只能獲取本地資源,踩過大坑。
修改爲獲取本地圖片資源
<?php #getimagesize("http://www.rai4over.cn/images/avatar.jpg"); getimagesize("./avatar.jpg"); ?>
函數棧調用
在php-sdk\phpdev\vc15\x64\php-src\Zend\zend_virtual_cwd.c第841行,發現調用了FindFirstFileExW()函數。
hFind = FindFirstFileExW(pathw, FindExInfoBasic, &dataw, FindExSearchNameMatch, NULL, 0);
這個函數就是<爲通配符的原因,因此其他調用FindFirstFileExW()的函數應該也同樣存在該問題。