源碼分析必備:用vld查看opcode資訊

來源:互聯網
上載者:User

 VLD(Vulcan Logic Dumper)的簡介如下:

The Vulcan Logic Dumper hooks into the Zend Engine and dumps all the opcodes (execution units) of a script. It can be used to see what is going on in the Zend Engine.

 

     之前的文章 PHP解譯器引擎執行流程 結尾處提到了VLD的原理,此擴充利用PHP對擴充模組提供的請求初始化鉤子函數(PHP_RINIT_FUNCTION),在每此請求到來的時候將預設的編譯函數指標zend_compile_file和執行函數指標zend_execute指向自己定義的vld_compile_file函數和vld_execute函數,這兩個函數中,對原函數進行了封裝,原編譯函數能返回一個op_array的指標,所以在新的編譯函數中可以截獲這個op_array的指標,然後輸出相關opcode資訊。  

    關於PHP擴充模組的安裝這裡就不介紹了,網路上很多相關資料。

    那麼讓我們看看這個擴充安裝後的實際效果,以下為一個非常簡單的PHP指令碼,test.php:

<?php<br />$a = "Hello world";<br />echo $a;<br />?>

 

在命令列下執行該指令碼:

php -dvld.active=1 test.php

 

於是可以看到vld輸出的內容:

 

希望看到跟詳細的內容可以用以下方式:

php -dvld.active=1 -dvld.verbosity=3 test.php

 

 

這裡簡單的說說輸出內容的含義:

這段代碼一共有3個op分別是:

1:ASSIGN          // #define ZEND_ASSIGN                           38

2:ECHO             // #define ZEND_ECHO                             40

3:RETURN         //  #define ZEND_RETURN                          62

 

第1個op ASSIGN的操作控制代碼是將OP2的值賦值給OP1,對應的就是$a = "Hello world"這句代碼,那麼OP2就是"Hello world"的,OP1應該就是$a,但是實際上輸出的內容中顯示的是!0,實際上$a屬於編譯後的變數,!0就代表了$a,可以在輸出op list的上一行看到

compiled vars:  !0 = $a

這樣的最佳化可以避免每次尋找變數$a都在變數符號表中去檢索,起到一定的緩衝的作用。在這條op執行結束之後,!0的值就等於"Hello world"了。

 

第2個op ECHO的操作控制代碼是將 OP1的內容送到標準輸出,對應的就是echo $a這句代碼,這樣就把"Hello world"輸出到終端了

 

第3個op RETURN 是在每個PHP檔案結尾都會自動加上的,它的操作控制代碼是將OP1的常量值返回

 

這樣我們就能很清晰的知道一段PHP代碼會得到什麼樣的OP code,vld真的是一個不錯的分析工具。

 

 

也許有人會問,你怎麼知道每個op對應的執行控制代碼是什麼呢,vld能輸出這些資訊嗎?非常可惜,vld不能協助我們輸出OP對應的執行控制代碼資訊。在預設以CALL方式執行op的模式下,每個op對應的handler都是一個函數,vld中截獲的op中有這些handler的指標,但是無法通過這些指標知道相應的函數名,c語言沒有一些更進階的語言那樣的反射特性。所以如果想知道每個op對應的handler,就需要另外想辦法了,目前為止,我只發現了兩種方法可以得到這些資訊。下面簡單的介紹這兩種方法。

 

 方法一:

    在之前的文章 PHP代碼如何執行?中介紹過,op的handler都定義在{PHPSRC}/Zend/zend_vm_execute.h中,這是一個由PHP產生的極大的c源檔案,其中有每個handler的函數定義以及op映射到handler的演算法,在zend_init_opcodes_handlers函數中,初始化一個 static const opcode_handler_t labels[]數組,這個 labels數組就是handlers的一張表,這個表有近4000個項,每個項都是一個handler的函數指標,當然有大量的NULL指標,還有一些重複的指標。如果我們能有一個跟labels數組對應的數組handler_names,數組中的每一個項對應的是labels中相應項中函數指標的函數名,那麼我們就可以通過現有的op到handler的映射演算法從handler_names中得到該op的handler的函數名。但是事情沒有想象的那麼容易,我們如何正確產生這個擁有4000個項的數組handler_names,答案就在{PHPSRC}/Zend/zend_vm_gen.php,這個PHP檔案是用來產生{PHPSRC}/Zend/zend_vm_execute.h,可以在其中找到產生labels數組的部分,只要添加相關代碼通過類似方式產生handler_names數組就可以了。有興趣的讀者可以嘗試產生這個handler_names數組檔案,然後編譯到vld擴充中,在輸出op list的時候把每個op執行的控制代碼函數名也一併輸出。

 

方法二:

   此方法是我目前經常用到的,相對來說比較方便,還是在{PHPSRC}/Zend/zend_vm_gen.php這個檔案裡面想辦法。這個檔案會產生每個op的handler,所以如果想辦法在每個handler函數的代碼中輸出該handler名字,那麼就知道哪些handler被調用。這個並不太難,在zend_vm_gen.php第380行左右可以看到類似以下PHP代碼:

if (0 && strpos($code, '{') === 0) {

    ...

}

 

實際上這個條件中的代碼就是在每個handler開始的一行中輸出內容,但是因為條件永遠無法滿足,所以實際條件中的代碼無法執行,可以將if中的條件改成true,然後大括弧輸出函數的名字就可以了,具體的代碼如下:

if (1) {<br />$name = $name.($spec?"_SPEC":"").$prefix[$op1].$prefix[$op2]."_HANDLER";<br />$code = "{/n/tfprintf(stderr, /"$name//n/");/n" . substr($code, 1);<br />}

 代碼具體的原理就不介紹了。在修改好zend_vm_gen.php之後,在命令列下執行該指令碼,就會產生一個新的zend_vm_execute.h( 同時會產生zend_vm_opcodes.h),開啟zend_vm_execute.h檔案,可以看到很多函數開頭都多出了這麼一句:

fprintf(stderr, "ZEND_***/n");

這樣每個函數開始執行的時候就會把自己的名字輸出到標準錯誤。下面的工作,就是重新編譯Zend/zend_execute.lo,然後重新連結sapi/cli/php,如果你不知道如何單獨完成這些操作,那麼也可以更暴力一點重新安裝整個PHP,需要注意的是修改後的PHP千萬不要用在正式環境,因為會輸出一大量不需要的資訊,自己單獨為實驗安裝一個PHP吧。 另外這個方法也會輸出一些非直接的hanlder的函數名,有可能一個handler會調用另外一個函數,這樣可能會輸出這個handler的名字和那個被調用的函數的名字,所以實際輸出的函數名字會多於op的數量。

    我們用方法二來查看前面的test.php的op handler的名字,直接用修改後的php 執行test.php得到以下內容:

 

ZEND_ASSIGN_SPEC_CV_CONST_HANDLER
ZEND_ECHO_SPEC_CV_HANDLER
Hello worldZEND_RETURN_SPEC_CONST_HANDLER
zend_leave_helper_SPEC_HANDLER

 

可以看到一共輸出了4個函數的名字,其中ZEND_ASSIGN_SPEC_CV_CONST_HANDLER函數就是ASSIGN的handler,ZEND_ECHO_SPEC_CV_HANDLER就是ECHO的handler,ZEND_RETURN_SPEC_CONST_HANDLER是RETURN的handler,這個handler會調用zend_leave_helper_SPEC_HANDLER函數,所以會輸出4個函數的名字,知道了這些函數的名字,我們就能在zend_vm_execute.h中去找到其具體定義,這樣就知道每個op到底是怎麼在執行了。

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.