PHP源碼調試分析

 前言

在看代碼的時候遇到了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()的函數應該也同樣存在該問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章