前言
在看代碼的時候遇到了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上的開發人員工具欄目自行選擇下載下傳即可。
安裝過程非常簡單,注意兩個調試必備勾選,其他的選項根據自己需求選擇
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiI05WZpJ3bth3LcVGb5R3c9M3clN2byBXLzN3btg3Pn5GcuczMzcDOfp3ctgzN3QWYmBTY4kzM0gzYiVWNiJGO4AjYmhDO1U2YzYDMvwVavw1UzgjQx8CXhV3LclWb49CXzVnLp1WdphnLn1Wavw1LcpDc0RHaiojIsJye.png)
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内容如下
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);
這也是這個函數加載網絡圖檔資源的原因,之前印象中一直以為隻能擷取本地資源,踩過大坑。
修改為擷取本地圖檔資源
#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);
這個函數就是