PHP擴展編寫、PHP擴展調試、VLD源碼分析、基於嵌入式Embed SAPI實現opcode查看

catalogue

複製代碼

1. 編譯PHP源碼
2. 擴展結構、優缺點
3. 使用PHP原生擴展框架wizard ext_skel編寫擴展
4. 編譯安裝VLD
5. Debug調試VLD
6. VLD源碼分析
7. 嵌入式Embed SAPI編程

複製代碼

 

1. 編譯PHP源碼

複製代碼

wget http://cn2.php.net/distributions/php-5.5.31.tar.gz
tar -zvzf php-5.5.31.tar.gz
//爲了儘快得到可以測試的環境,我們僅編譯一個最精簡的PHP。通過執行 ./configure –disable-all來進行配置。 以後如果需要其他功能可以重新編譯。如果configure命令出現錯誤,可能是缺少PHP所依賴的庫,各個系統的環境可能不一樣。 出現錯誤可根據出錯信息上網搜索。 直到完成configure。configure完成後我們就可以開始編譯
./configure --enable-debug --enable-tokenizer  
/*
apt-get install -y libxml2
*/
make
//運行編譯後PHP
./sapi/cli/php -v

//install NetBeans
http://download.netbeans.org/netbeans/8.1/final/bundles/netbeans-8.1-linux.sh
chmod 777 ./*
./netbeans-8.1-linux.sh

./configure CC=${IDE_CC} CXX=${IDE_CXX} CFLAGS="-g3 -gdwarf-2" CXXFLAGS="-g3 -gdwarf-2" --disable-all --enable-debug --enable-tokenizer

複製代碼

Relevant Link:

http://www.cnblogs.com/LittleHann/p/3562259.html

 

2. 擴展結構、優缺點

0x1: C/C++擴展PHP的優點

複製代碼

1. 效率 
減少PHP腳本的複雜度,極端情況下,你只需要在PHP腳本中,簡單的調用一個擴展實現的函數,然後所有的功能都就被擴展實現 

2. 與外部的庫做交互
比如你有一個C/C++的庫, 不妨假設,這個庫呢就是實現了一個字符串加密和解密,而你並沒有這個庫的源碼,也就是說,你無法把這個庫在PHP中實現,那麼你只有編寫一個PHP擴展,來做爲一個橋樑,連接起你的PHP和這個庫

3. 複用Zend的詞法/語法處理邏輯
在特殊領域的應用,例如WEBSHELL檢測中,通過擴展Hook機制實現Token AST樹的獲取,然後在此基礎上進行語法還原、和語法層面模擬執行

複製代碼

0x2: 缺點

1. 開發複雜
2. 可維護性降低
開發週期變長, 最簡單的一個例子,當你用PHP腳本的時候, 如果你發現某個判斷條件出錯,你只要修改了這一行,保存,那麼就立刻能見效。而如果是在C/C++編寫的PHP擴展中, 那你可需要,修改源碼,重新編譯,然後重新load進PHP,然後重啓Apache,才能見效 

0x3: 擴展框架的基礎結構

複製代碼

root@ubuntu1204-virtual-machine:/home/ubuntu1204/phpsourcecode/php-5.5.31/ext# cd helloworld/
root@ubuntu1204-virtual-machine:/home/ubuntu1204/phpsourcecode/php-5.5.31/ext/helloworld# ll
total 44
drwxr-xr-x  3 root       root       4096 Jan 27 16:10 ./
drwxr-xr-x 80 ubuntu1204 ubuntu1204 4096 Jan 27 16:10 ../
-rw-r--r--  1 root       root       2178 Jan 27 16:10 config.m4
-rw-r--r--  1 root       root        324 Jan 27 16:10 config.w32
-rw-r--r--  1 root       root         10 Jan 27 16:10 CREDITS
-rw-r--r--  1 root       root          0 Jan 27 16:10 EXPERIMENTAL
-rw-r--r--  1 root       root       5296 Jan 27 16:10 helloworld.c
-rw-r--r--  1 root       root        514 Jan 27 16:10 helloworld.php
-rw-r--r--  1 root       root       2962 Jan 27 16:10 php_helloworld.h
-rw-r--r--  1 root       root         16 Jan 27 16:10 .svnignore
drwxr-xr-x  2 root       root       4096 Jan 27 16:10 tests/

複製代碼

1. helloworld.c

這個文件是我們擴展的主要文件,擴展的主體邏輯都在裏面實現

複製代碼

/*
  +----------------------------------------------------------------------+
  | PHP Version 5                                                        |
  +----------------------------------------------------------------------+
  | Copyright (c) 1997-2015 The PHP Group                                |
  +----------------------------------------------------------------------+
  | This source file is subject to version 3.01 of the PHP license,      |
  | that is bundled with this package in the file LICENSE, and is        |
  | available through the world-wide-web at the following url:           |
  | http://www.php.net/license/3_01.txt                                  |
  | If you did not receive a copy of the PHP license and are unable to   |
  | obtain it through the world-wide-web, please send a note to          |
  | [email protected] so we can mail you a copy immediately.               |
  +----------------------------------------------------------------------+
  | Author:                                                              |
  +----------------------------------------------------------------------+
*/

/* $Id$ */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "php.h"
#include "php_ini.h"
#include "ext/standard/info.h"
#include "php_helloworld.h"

/* If you declare any globals in php_helloworld.h uncomment this:
ZEND_DECLARE_MODULE_GLOBALS(helloworld)
*/

/* True global resources - no need for thread safety here */
static int le_helloworld;

/* {{{ helloworld_functions[]
 *
 * Every user visible function must have an entry in helloworld_functions[].
 */
const zend_function_entry helloworld_functions[] = {
    PHP_FE(confirm_helloworld_compiled,    NULL)        /* For testing, remove later. */
    PHP_FE_END    /* Must be the last line in helloworld_functions[] */
};
/* }}} */

/* {{{ helloworld_module_entry
 */
zend_module_entry helloworld_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
    STANDARD_MODULE_HEADER,
#endif
    "helloworld",
    helloworld_functions,
    PHP_MINIT(helloworld),
    PHP_MSHUTDOWN(helloworld),
    PHP_RINIT(helloworld),        /* Replace with NULL if there's nothing to do at request start */
    PHP_RSHUTDOWN(helloworld),    /* Replace with NULL if there's nothing to do at request end */
    PHP_MINFO(helloworld),
#if ZEND_MODULE_API_NO >= 20010901
    PHP_HELLOWORLD_VERSION,
#endif
    STANDARD_MODULE_PROPERTIES
};
/* }}} */

#ifdef COMPILE_DL_HELLOWORLD
ZEND_GET_MODULE(helloworld)
#endif

/* {{{ PHP_INI
 */
/* Remove comments and fill if you need to have entries in php.ini
PHP_INI_BEGIN()
    STD_PHP_INI_ENTRY("helloworld.global_value",      "42", PHP_INI_ALL, OnUpdateLong, global_value, zend_helloworld_globals, helloworld_globals)
    STD_PHP_INI_ENTRY("helloworld.global_string", "foobar", PHP_INI_ALL, OnUpdateString, global_string, zend_helloworld_globals, helloworld_globals)
PHP_INI_END()
*/
/* }}} */

/* {{{ php_helloworld_init_globals
 */
/* Uncomment this function if you have INI entries
static void php_helloworld_init_globals(zend_helloworld_globals *helloworld_globals)
{
    helloworld_globals->global_value = 0;
    helloworld_globals->global_string = NULL;
}
*/
/* }}} */

/* {{{ PHP_MINIT_FUNCTION
 */
PHP_MINIT_FUNCTION(helloworld)
{
    /* If you have INI entries, uncomment these lines 
    REGISTER_INI_ENTRIES();
    */
    return SUCCESS;
}
/* }}} */

/* {{{ PHP_MSHUTDOWN_FUNCTION
 */
PHP_MSHUTDOWN_FUNCTION(helloworld)
{
    /* uncomment this line if you have INI entries
    UNREGISTER_INI_ENTRIES();
    */
    return SUCCESS;
}
/* }}} */

/* Remove if there's nothing to do at request start */
/* {{{ PHP_RINIT_FUNCTION
 */
PHP_RINIT_FUNCTION(helloworld)
{
    return SUCCESS;
}
/* }}} */

/* Remove if there's nothing to do at request end */
/* {{{ PHP_RSHUTDOWN_FUNCTION
 */
PHP_RSHUTDOWN_FUNCTION(helloworld)
{
    return SUCCESS;
}
/* }}} */

/* {{{ PHP_MINFO_FUNCTION
 */
PHP_MINFO_FUNCTION(helloworld)
{
    php_info_print_table_start();
    php_info_print_table_header(2, "helloworld support", "enabled");
    php_info_print_table_end();

    /* Remove comments if you have entries in php.ini
    DISPLAY_INI_ENTRIES();
    */
}
/* }}} */


/* Remove the following function when you have successfully modified config.m4
   so that your module can be compiled into PHP, it exists only for testing
   purposes. */

/* Every user-visible function in PHP should document itself in the source */
/* {{{ proto string confirm_helloworld_compiled(string arg)
   Return a string to confirm that the module is compiled in */
PHP_FUNCTION(confirm_helloworld_compiled)
{
    char *arg = NULL;
    int arg_len, len;
    char *strg;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &arg, &arg_len) == FAILURE) {
        return;
    }

    len = spprintf(&strg, 0, "Congratulations! You have successfully modified ext/%.78s/config.m4. Module %.78s is now compiled into PHP.", "helloworld", arg);
    RETURN_STRINGL(strg, len, 0);
}
/* }}} */
/* The previous line is meant for vim and emacs, so it can correctly fold and 
   unfold functions in source code. See the corresponding marks just before 
   function definition, where the functions purpose is also documented. Please 
   follow this convention for the convenience of others editing your code.
*/


/*
 * Local variables:
 * tab-width: 4
 * c-basic-offset: 4
 * End:
 * vim600: noet sw=4 ts=4 fdm=marker
 * vim<600: noet sw=4 ts=4
 */

複製代碼

函數自身的定義使用了宏PHP_FUNCTION(),該宏可以生成一個適合於Zend引擎的函數原型
爲了獲得函數傳遞的參數,可以使用zend_parse_parameters()API函數

zend_parse_parameters(int num_args TSRMLS_DC, char *type_spec, ...);
1. num_args: 傳遞給函數的參數個數。通常的做法是傳給它ZEND_NUM_ARGS(),這是一個表示傳遞給函數參數總個數的宏。
2. TSRMLS_DC: 爲了線程安全,總是傳遞TSRMLS_CC宏
3. *type_spec: 是一個字符串,指定了函數期望的參數類型
4. ...: 需要隨參數值更新的變量列表,是一個變長數量參數傳遞

2. config.m4
m4是一個宏解釋工具,它會把輸入文件中的宏展開到輸出文件。所以這個config.m4是PHP擴展框架所必須的,也是關鍵的一個文件,用來生成我們擴展的makefile

複製代碼

dnl $Id$
dnl config.m4 for extension helloworld

//在m4中,dnl表示註釋,如果需要啓用對應配置項,則刪除行首的dnl即可
dnl Comments in this file start with the string 'dnl'.
dnl Remove where necessary. This file will not work
dnl without editing.

dnl If your extension references something external, use with:

/*
with說明了,要啓用這個模塊,必須要的先決條件,也就是說這個模塊依賴於某些其他模塊
*/
dnl PHP_ARG_WITH(helloworld, for helloworld support,
dnl Make sure that the comment is aligned:
dnl [  --with-helloworld             Include helloworld support])

dnl Otherwise use enable:

/*
這段指令創建了一個configure時的參數“enable-example”, 第二個參數會顯示在當configure處理到這個模塊的configure文件的時候。第三個參數,會在用戶輸入./configurehelp的時候,作爲一個可選的選項被顯示
即相當於:  ./configure --enable-helloworld 
*/
dnl PHP_ARG_ENABLE(helloworld, whether to enable helloworld support,
dnl Make sure that the comment is aligned:
dnl [  --enable-helloworld           Enable helloworld support])

if test "$PHP_HELLOWORLD" != "no"; then
  dnl Write more examples of tests here...

  dnl # --with-helloworld -> check with-path
  dnl SEARCH_PATH="/usr/local /usr"     # you might want to change this
  dnl SEARCH_FOR="/include/helloworld.h"  # you most likely want to change this
  dnl if test -r $PHP_HELLOWORLD/$SEARCH_FOR; then # path given as parameter
  dnl   HELLOWORLD_DIR=$PHP_HELLOWORLD
  dnl else # search default path list
  dnl   AC_MSG_CHECKING([for helloworld files in default path])
  dnl   for i in $SEARCH_PATH ; do
  dnl     if test -r $i/$SEARCH_FOR; then
  dnl       HELLOWORLD_DIR=$i
  dnl       AC_MSG_RESULT(found in $i)
  dnl     fi
  dnl   done
  dnl fi
  dnl
  dnl if test -z "$HELLOWORLD_DIR"; then
  dnl   AC_MSG_RESULT([not found])
  dnl   AC_MSG_ERROR([Please reinstall the helloworld distribution])
  dnl fi

  dnl # --with-helloworld -> add include path
  dnl PHP_ADD_INCLUDE($HELLOWORLD_DIR/include)

  dnl # --with-helloworld -> check for lib and symbol presence
  dnl LIBNAME=helloworld # you may want to change this
  dnl LIBSYMBOL=helloworld # you most likely want to change this 

/*
在庫library中查找第二個參數是否存在(這裏是AC_DEFINE(HAVE_HELLOWORLDLIB,1,[ ])),如果存在則這個宏會被展開成found,否則not-found;
*/
  dnl PHP_CHECK_LIBRARY($LIBNAME,$LIBSYMBOL,
  dnl [
  dnl   PHP_ADD_LIBRARY_WITH_PATH($LIBNAME, $HELLOWORLD_DIR/$PHP_LIBDIR, HELLOWORLD_SHARED_LIBADD)
  dnl   AC_DEFINE(HAVE_HELLOWORLDLIB,1,[ ])
  dnl ],[
  dnl   AC_MSG_ERROR([wrong helloworld lib version or lib not found])
  dnl ],[
  dnl   -L$HELLOWORLD_DIR/$PHP_LIBDIR -lm
  dnl ])
  dnl
  dnl PHP_SUBST(HELLOWORLD_SHARED_LIBADD)

/*
這個就是對AC_DEFUN簡單包裝,最終會被展開成: #define what value
*/
  dnl PHP_DEFINE(what, [value]) 

/*
如果你的擴展是使用C++編寫,那麼你就必須使用這個宏,來告訴編譯器使用C++編譯器。這個宏會被展開成
AC_PROG_CXX
AC_PROG_CXXCPP
*/
  dnl PHP_REQURE_CXX 

  PHP_NEW_EXTENSION(helloworld, helloworld.c, $ext_shared)
fi

複製代碼

3. CREDITES

用來在發佈你的擴展的時候附加一些其他信息 ,比如等等

4. helloworld.php

用來簡單測試你的擴展的

複製代碼

<?php
$br = (php_sapi_name() == "cli")? "":"<br>";

if(!extension_loaded('helloworld')) {
    dl('helloworld.' . PHP_SHLIB_SUFFIX);
}
$module = 'helloworld';
$functions = get_extension_funcs($module);
echo "Functions available in the test extension:$br\n";
foreach($functions as $func) {
    echo $func."$br\n";
}
echo "$br\n";
$function = 'confirm_' . $module . '_compiled';
if (extension_loaded($module)) {
    $str = $function($module);
} else {
    $str = "Module $module is not compiled into PHP";
}
echo "$str\n";
?>

複製代碼

5. php_helloworld.h

複製代碼

/*
  +----------------------------------------------------------------------+
  | PHP Version 5                                                        |
  +----------------------------------------------------------------------+
  | Copyright (c) 1997-2015 The PHP Group                                |
  +----------------------------------------------------------------------+
  | This source file is subject to version 3.01 of the PHP license,      |
  | that is bundled with this package in the file LICENSE, and is        |
  | available through the world-wide-web at the following url:           |
  | http://www.php.net/license/3_01.txt                                  |
  | If you did not receive a copy of the PHP license and are unable to   |
  | obtain it through the world-wide-web, please send a note to          |
  | [email protected] so we can mail you a copy immediately.               |
  +----------------------------------------------------------------------+
  | Author:                                                              |
  +----------------------------------------------------------------------+
*/

/* $Id$ */

#ifndef PHP_HELLOWORLD_H
#define PHP_HELLOWORLD_H

extern zend_module_entry helloworld_module_entry;
#define phpext_helloworld_ptr &helloworld_module_entry

#define PHP_HELLOWORLD_VERSION "0.1.0" /* Replace with version number for your extension */

#ifdef PHP_WIN32
#    define PHP_HELLOWORLD_API __declspec(dllexport)
#elif defined(__GNUC__) && __GNUC__ >= 4
#    define PHP_HELLOWORLD_API __attribute__ ((visibility("default")))
#else
#    define PHP_HELLOWORLD_API
#endif

#ifdef ZTS
#include "TSRM.h"
#endif

PHP_MINIT_FUNCTION(helloworld);
PHP_MSHUTDOWN_FUNCTION(helloworld);
PHP_RINIT_FUNCTION(helloworld);
PHP_RSHUTDOWN_FUNCTION(helloworld);
PHP_MINFO_FUNCTION(helloworld);

PHP_FUNCTION(confirm_helloworld_compiled);    /* For testing, remove later. */

/* 
      Declare any global variables you may need between the BEGIN
    and END macros here:     

ZEND_BEGIN_MODULE_GLOBALS(helloworld)
    long  global_value;
    char *global_string;
ZEND_END_MODULE_GLOBALS(helloworld)
*/

/* In every utility function you add that needs to use variables 
   in php_helloworld_globals, call TSRMLS_FETCH(); after declaring other 
   variables used by that function, or better yet, pass in TSRMLS_CC
   after the last function argument and declare your utility function
   with TSRMLS_DC after the last declared argument.  Always refer to
   the globals in your function as HELLOWORLD_G(variable).  You are 
   encouraged to rename these macros something shorter, see
   examples in any other php module directory.
*/

#ifdef ZTS
#define HELLOWORLD_G(v) TSRMG(helloworld_globals_id, zend_helloworld_globals *, v)
#else
#define HELLOWORLD_G(v) (helloworld_globals.v)
#endif

#endif    /* PHP_HELLOWORLD_H */


/*
 * Local variables:
 * tab-width: 4
 * c-basic-offset: 4
 * End:
 * vim600: noet sw=4 ts=4 fdm=marker
 * vim<600: noet sw=4 ts=4
 */

複製代碼

Relevant Link:

http://www.laruence.com/2008/08/16/301.html

 

3. 使用PHP原生擴展框架wizard ext_skel編寫擴展

0x1: Hello world擴展框架代碼生成

複製代碼

root@ubuntu1204-virtual-machine:/home/ubuntu1204/phpsourcecode/php-5.5.31# cd ext/
root@ubuntu1204-virtual-machine:/home/ubuntu1204/phpsourcecode/php-5.5.31/ext# pwd
/home/ubuntu1204/phpsourcecode/php-5.5.31/ext 
root@ubuntu1204-virtual-machine:/home/ubuntu1204/phpsourcecode/php-5.5.31/ext# ./ext_skel --extname=helloworld
Creating directory helloworld
Creating basic files: config.m4 config.w32 .svnignore helloworld.c php_helloworld.h CREDITS EXPERIMENTAL tests/001.phpt helloworld.php [done].

To use your new extension, you will have to execute the following steps:

1.  $ cd ..
2.  $ vi ext/helloworld/config.m4
3.  $ ./buildconf
4.  $ ./configure --[with|enable]-helloworld
5.  $ make
6.  $ ./sapi/cli/php -f ext/helloworld/helloworld.php
7.  $ vi ext/helloworld/helloworld.c
8.  $ make

Repeat steps 3-6 until you are satisfied with ext/helloworld/config.m4 and
step 6 confirms that your module is compiled into PHP. Then, start writing
code and repeat the last two steps as often as necessary.

複製代碼

在ext目錄下生成了一個名爲example的目錄,並在這個目錄下生成了所有的要完成一個PHP擴展所必須的文件和代碼

0x2: 修改config.m4

去除註釋

PHP_ARG_WITH(helloworld, for helloworld support,
dnl Make sure that the comment is aligned:
[  --with-helloworld             Include helloworld support])

0x3: 編譯

編譯擴展有兩種方法

1. 可裝載模塊或者DSO(動態共享對象)
2. 靜態編譯到PHP

1. 可裝載模塊或者DSO(動態共享對象)

複製代碼

./buildconf --force
root@ubuntu1204-virtual-machine:/home/ubuntu1204/phpsourcecode/php-5.5.31# ./configure --help | grep helloworld
  --with-helloworld             Include helloworld support
make
/*
安裝PHP
Installing shared extensions:     /usr/local/lib/php/extensions/debug-non-zts-20121212/
Installing PHP CLI binary:        /usr/local/bin/
Installing PHP CLI man page:      /usr/local/php/man/man1/
Installing PHP CGI binary:        /usr/local/bin/
Installing PHP CGI man page:      /usr/local/php/man/man1/
Installing build environment:     /usr/local/lib/php/build/
Installing header files:          /usr/local/include/php/
Installing helper programs:       /usr/local/bin/
  program: phpize
  program: php-config
Installing man pages:             /usr/local/php/man/man1/
  page: phpize.1
  page: php-config.1
Installing PEAR environment:      /usr/local/lib/php/
[PEAR] Archive_Tar    - installed: 1.4.0
[PEAR] Console_Getopt - installed: 1.4.1
[PEAR] Structures_Graph- installed: 1.1.1
[PEAR] XML_Util       - installed: 1.3.0
[PEAR] PEAR           - installed: 1.10.1
Wrote PEAR system config file at: /usr/local/etc/pear.conf
You may want to add: /usr/local/lib/php to your php.ini include_path
/home/ubuntu1204/phpsourcecode/php-5.5.31/build/shtool install -c ext/phar/phar.phar /usr/local/bin
ln -s -f phar.phar /usr/local/bin/phar
Installing PDO headers:          /usr/local/include/php/ext/pdo/
*/
make install

//編譯擴展
cd ./ext/helloword
phpize
./configure --with-helloworld
make

//複製擴展so庫到PHP目錄
make install

複製代碼

2. 靜態編譯到PHP

將靜態庫編譯進PHP,和普通的靜態庫編譯鏈接是一個道理,我們使用C/C++編寫邏輯代碼,並編譯成靜態.a庫,然後重新編譯PHP主程序源代碼,並在編譯參數中顯式說明需要鏈接對應靜態庫

1. 用C/C++寫PHP擴展
2. 編譯靜態庫.a庫
3. 把靜態庫加入PHP: 假設靜態庫的文件名叫libnpc.a,放在/home目錄下。在PHP的安裝目錄下輸入如下命令: export LDFLAGS="–L/home –lnpc",這個環境變量的作用就是讓PHP在編譯時知道要把這個庫也一起編譯進去
4. 編譯PHP

Relevant Link:

http://www.laruence.com/2011/09/13/2139.html
https://segmentfault.com/a/1190000003952548
http://hzcsky.blog.51cto.com/1560073/820232
http://www.laruence.com/2009/04/28/719.html
http://www.thinksaas.cn/manual/php/features.commandline.html
http://weizhifeng.net/write-php-extension-part1.html

 

4. 編譯安裝VLD

VLD(Vulcan Logic Dumper)是一個在Zend引擎中,以掛鉤的方式實現的用於輸出PHP腳本生成的中間代碼(執行單元)的擴展

0x1: 編譯安裝

複製代碼

cd /home/ubuntu1204/phpsourcecode/php-5.5.31/ext/vld
解壓VLD(Vulcan Logic Dumper)
phpize
./configure --enable-vld  
make
make install

//需要注意的,在PHP CLI模式下,php.ini的配置會被覆蓋,我們可以自己顯式配置php.ini指令
./sapi/cli/php -d extension=vld.so -dvld.active=1 /home/ubuntu1204/phpsourcecode/sample/shell.php

複製代碼

0x2: VLD擴展的參數列表

複製代碼

//./sapi/cli/php -d extension=vld.so -dvld.active=1 -dvld.verbosity=3 /home/ubuntu1204/phpsourcecode/sample/shell.php
//./sapi/cli/php -d extension=vld.so -dvld.active=1 -dvld.execute=0 /home/ubuntu1204/phpsourcecode/sample/shell.php

1. -dvld.active: 是否在執行PHP時激活VLD掛鉤
    1) 默認爲0: 表示禁用
    2) 使用-dvld.active=1啓用
2. -dvld.skip_prepend: 是否跳過php.ini配置文件中auto_prepend_file指定的文件
    1) 默認爲0,即不跳過包含的文件,顯示這些包含的文件中的代碼所生成的中間代碼。此參數生效有一個前提條件:-dvld.execute=0
3. -dvld.execute: 是否執行這段PHP腳本
    1) 默認值爲1,表示執行
    2) 使用-dvld.execute=0,表示只顯示中間代碼,不執行生成的中間代碼 
4. -dvld.format: 是否以自定義的格式顯示
    1) 默認爲0,表示否
    2) 使用-dvld.format=1,表示以自己定義的格式顯示。這裏自定義的格式輸出是以-dvld.col_sep指定的參數間隔
5. -dvld.col_sep: 在-dvld.format參數啓用時此函數纔會有效,默認爲 "t"
6. -dvld.verbosity: 是否顯示更詳細的信息
    1) 默認爲1
    2) 其值可以爲0,1,2,3 其實比0小的也可以,只是效果和0一樣,比如0.1之類,但是負數除外,負數和效果和3的效果一樣 比3大的值也是可以的,只是效果和3一樣,3代表最詳細
7. -dvld.save_dir: 指定文件輸出的路徑,默認路徑爲/tmp 
8. -dvld.save_paths: 控制是否輸出文件,默認爲0,表示不輸出文件
9. -dvld.dump_paths: 控制輸出的內容,現在只有0和1兩種情況,默認爲1,輸出內容

複製代碼

Relevant Link:

http://techlog.cn/article/list/10182879
http://pecl.php.net/package/vld
http://www.lampweb.org/seo/8/20.html
http://hilojack.com/p/php-vld/

 

5. Debug調試VLD

複製代碼

cd /home/ubuntu1204/phpsourcecode/php-5.5.31/ext/vld
vim config.m4
/*
在最後一行添加
if test -z "$PHP_DEBUG"; then
        AC_ARG_ENABLE(debug,
                [--enable-debg  compile with debugging system],
                [PHP_DEBUG=$enableval], [PHP_DEBUG=no]
        )
fi

這樣就表示該擴展能夠進行調試了,然後編譯該擴展
*/ 
 
phpize
./configure --enable-vld  
make
make install

//編輯netbeans Debug參數
"${OUTPUT_PATH}" -d extension=vld.so -dvld.active=3 /home/ubuntu1204/phpsourcecode/sample/shell.php

複製代碼

Relevant Link:

https://segmentfault.com/a/1190000002703073
http://www.codefrom.com/paper/%E4%BD%BF%E7%94%A8gdb%E8%B0%83%E8%AF%95php%E6%89%A9%E5%B1%95

 

6. VLD源碼分析

0x1: vld_compile_file

vld.c

複製代碼

/* {{{ zend_op_array vld_compile_file (file_handle, type)
 *    This function provides a hook for compilation */
static zend_op_array *vld_compile_file(zend_file_handle *file_handle, int type TSRMLS_DC)
{
    zend_op_array *op_array;

    if (!VLD_G(execute) &&
        ((VLD_G(skip_prepend) && PG(auto_prepend_file) && PG(auto_prepend_file)[0] && PG(auto_prepend_file) == file_handle->filename) ||
         (VLD_G(skip_append)  && PG(auto_append_file)  && PG(auto_append_file)[0]  && PG(auto_append_file)  == file_handle->filename)))
    {
        zval nop;
#if PHP_VERSION_ID >= 70000
        zend_op_array *ret;
        ZVAL_STRINGL(&nop, "RETURN ;", 8);
        ret = compile_string(&nop, "NOP" TSRMLS_CC);
        zval_dtor(&nop);
        return ret;
#else
        ZVAL_STRINGL(&nop, "RETURN ;", 8, 0);
        return compile_string(&nop, "NOP" TSRMLS_CC);
#endif
    }

    //調用Zend的編譯Lex函數,獲取待檢測樣本Opcode array
    op_array = old_compile_file (file_handle, type TSRMLS_CC);

    if (VLD_G(path_dump_file)) {
        fprintf(VLD_G(path_dump_file), "subgraph cluster_file_%08x { label=\"file %s\";\n", op_array, op_array->filename ? ZSTRING_VALUE(op_array->filename) : "__main");
    }
    if (op_array) {
        //打印opcod數組
        vld_dump_oparray (op_array TSRMLS_CC);
    }

    zend_hash_apply_with_arguments (CG(function_table) APPLY_TSRMLS_CC, (apply_func_args_t) vld_dump_fe, 0);
    zend_hash_apply (CG(class_table), (apply_func_t) vld_dump_cle TSRMLS_CC);

    if (VLD_G(path_dump_file)) {
        fprintf(VLD_G(path_dump_file), "}\n");
    }

    return op_array;
}
/* }}} */

複製代碼

0x2: vld_dump_oparray

srm_oparray.c

複製代碼

void vld_dump_oparray(zend_op_array *opa TSRMLS_DC)
{
    unsigned int i;
    vld_set *set;
    vld_branch_info *branch_info;
    //獲取oparray中保存opcode數組的基地址
    unsigned int base_address = (unsigned int)(zend_intptr_t)&(opa->opcodes[0]);

    //爲分析結果集申請空間,初始化
    set = vld_set_create(opa->last);
    //爲分支邏輯塊申請空間,初始化
    branch_info = vld_branch_info_create(opa->last);

    if (VLD_G(dump_paths)) {
        vld_analyse_oparray(opa, set, branch_info TSRMLS_CC);
    }
    if (VLD_G(format)) {
        vld_printf (stderr, "filename:%s%s\n", VLD_G(col_sep), ZSTRING_VALUE(opa->filename));
        vld_printf (stderr, "function name:%s%s\n", VLD_G(col_sep), ZSTRING_VALUE(opa->function_name));
        vld_printf (stderr, "number of ops:%s%d\n", VLD_G(col_sep), opa->last);
    } else {
        vld_printf (stderr, "filename:       %s\n", ZSTRING_VALUE(opa->filename));
        vld_printf (stderr, "function name:  %s\n", ZSTRING_VALUE(opa->function_name));
        vld_printf (stderr, "number of ops:  %d\n", opa->last);
    }
#ifdef IS_CV /* PHP >= 5.1 */
    vld_printf (stderr, "compiled vars:  ");
    for (i = 0; i < opa->last_var; i++) {
        vld_printf (stderr, "!%d = $%s%s", i, OPARRAY_VAR_NAME(opa->vars[i]), ((i + 1) == opa->last_var) ? "\n" : ", ");
    }
    if (!opa->last_var) {
        vld_printf(stderr, "none\n");
    }
#endif
    if (VLD_G(format)) {
        vld_printf(stderr, "line%s# *%s%s%sop%sfetch%sext%sreturn%soperands\n",VLD_G(col_sep),VLD_G(col_sep),VLD_G(col_sep),VLD_G(col_sep),VLD_G(col_sep),VLD_G(col_sep),VLD_G(col_sep),VLD_G(col_sep));
    } else {
        vld_printf(stderr, "line     #* E I O op                           fetch          ext  return  operands\n");
        vld_printf(stderr, "-------------------------------------------------------------------------------------\n");
    }
    for (i = 0; i < opa->last; i++) {
        vld_dump_op(i, opa->opcodes, base_address, vld_set_in(set, i), vld_set_in(branch_info->entry_points, i), vld_set_in(branch_info->starts, i), vld_set_in(branch_info->ends, i), opa TSRMLS_CC);
    }
    vld_printf(stderr, "\n");

    if (VLD_G(dump_paths)) {
        vld_branch_post_process(opa, branch_info);
        vld_branch_find_paths(branch_info);
        vld_branch_info_dump(opa, branch_info TSRMLS_CC);
    }

    vld_set_free(set);
    vld_branch_info_free(branch_info);
}

複製代碼

0x3: vld_analyse_oparray

複製代碼

void vld_analyse_oparray(zend_op_array *opa, vld_set *set, vld_branch_info *branch_info TSRMLS_DC)
{
    unsigned int position = 0;

    VLD_PRINT(1, "Finding entry points\n");
    while (position < opa->last) {
        if (position == 0) {
            vld_analyse_branch(opa, position, set, branch_info TSRMLS_CC);
            vld_set_add(branch_info->entry_points, position);
#if PHP_MAJOR_VERSION >= 5
        } else if (opa->opcodes[position].opcode == ZEND_CATCH) {
            if (VLD_G(format)) {
                VLD_PRINT2(1, "Found catch point at position:%s%d\n", VLD_G(col_sep),position);
            } else {
                VLD_PRINT1(1, "Found catch point at position: %d\n", position);
            }
            vld_analyse_branch(opa, position, set, branch_info TSRMLS_CC);
            vld_set_add(branch_info->entry_points, position);
#endif
        }
        position++;
    }
    vld_set_add(branch_info->ends, opa->last-1);
    branch_info->branches[opa->last-1].start_lineno = opa->opcodes[opa->last-1].lineno;
}

複製代碼

0x4: vld_analyse_branch

可以看到,VLD是根據Branch進行"逐塊"翻譯的,從PHP Zend的角度來看,構成"分支結構語法"的用戶態代碼結構有

1. if
2. for
3. foreach
4. goto
5. exit
..

void vld_analyse_branch(zend_op_array *opa, unsigned int position, vld_set *set, vld_branch_info *branch_info TSRMLS_DC)

複製代碼

void vld_analyse_branch(zend_op_array *opa, unsigned int position, vld_set *set, vld_branch_info *branch_info TSRMLS_DC)
{
    long jump_pos1 = VLD_JMP_NOT_SET;
    long jump_pos2 = VLD_JMP_NOT_SET;

    if (VLD_G(format)) {
        VLD_PRINT2(1, "Branch analysis from position:%s%d\n", VLD_G(col_sep),position);
    } else {
        VLD_PRINT1(1, "Branch analysis from position: %d\n", position);
    }

    vld_set_add(branch_info->starts, position);
    branch_info->branches[position].start_lineno = opa->opcodes[position].lineno;

    /* First we see if the branch has been visited, if so we bail out. */
    if (vld_set_in(set, position)) {
        return;
    }
    /* Loop over the opcodes until the end of the array, or until a jump point has been found */
    VLD_PRINT1(2, "Add %d\n", position);
    vld_set_add(set, position);
    while (position < opa->last) {
        jump_pos1 = VLD_JMP_NOT_SET;
        jump_pos2 = VLD_JMP_NOT_SET;

        /* See if we have a jump instruction */
        //定位條件邏輯塊的起始、結束位置
        if (vld_find_jump(opa, position, &jump_pos1, &jump_pos2)) {
            VLD_PRINT1(1, "Jump found. Position 1 = %d", jump_pos1);
            if (jump_pos2 != VLD_JMP_NOT_SET) {
                VLD_PRINT1(1, ", Position 2 = %d\n", jump_pos2);
            } else {
                VLD_PRINT(1, "\n");
            }

            //從當前條件分支(Branch)開始,遞歸處理子語句塊
            if (jump_pos1 == VLD_JMP_EXIT || jump_pos1 >= 0) {
                vld_branch_info_update(branch_info, position, opa->opcodes[position].lineno, 0, jump_pos1);
                if (jump_pos1 != VLD_JMP_EXIT) {
                    vld_analyse_branch(opa, jump_pos1, set, branch_info TSRMLS_CC);
                }
            }

            if (jump_pos2 == VLD_JMP_EXIT || jump_pos2 >= 0) {
                vld_branch_info_update(branch_info, position, opa->opcodes[position].lineno, 1, jump_pos2);
                if (jump_pos2 != VLD_JMP_EXIT) {
                    vld_analyse_branch(opa, jump_pos2, set, branch_info TSRMLS_CC);
                }
            }
            break;
        }
#ifdef ZEND_ENGINE_2
        /* See if we have a throw instruction */
        if (opa->opcodes[position].opcode == ZEND_THROW) {
            VLD_PRINT1(1, "Throw found at %d\n", position);
            vld_set_add(branch_info->ends, position);
            branch_info->branches[position].start_lineno = opa->opcodes[position].lineno;
            break;
        }
#endif
        /* See if we have an exit instruction */
        if (opa->opcodes[position].opcode == ZEND_EXIT) {
            VLD_PRINT(1, "Exit found\n");
            vld_set_add(branch_info->ends, position);
            branch_info->branches[position].start_lineno = opa->opcodes[position].lineno;
            break;
        }
        /* See if we have a return instruction */
        if (
            opa->opcodes[position].opcode == ZEND_RETURN
#if PHP_VERSION_ID >= 50400
            || opa->opcodes[position].opcode == ZEND_RETURN_BY_REF
#endif
        ) {
            VLD_PRINT(1, "Return found\n");
            vld_set_add(branch_info->ends, position);
            branch_info->branches[position].start_lineno = opa->opcodes[position].lineno;
            break;
        }

        position++;
        VLD_PRINT1(2, "Add %d\n", position);
        vld_set_add(set, position);
    }
}

複製代碼

0x5: vld_dump_op

複製代碼

void vld_dump_op(int nr, zend_op * op_ptr, unsigned int base_address, int notdead, int entry, int start, int end, zend_op_array *opa TSRMLS_DC)
{
    static uint last_lineno = (uint) -1;
    int print_sep = 0, len;
    char *fetch_type = "";
    unsigned int flags, op1_type, op2_type, res_type;
    const zend_op op = op_ptr[nr];
    
    if (op.lineno == 0) {
        return;
    }

    if (op.opcode >= NUM_KNOWN_OPCODES) {
        flags = ALL_USED;
    } else {
        flags = opcodes[op.opcode].flags;
    }

    op1_type = op.VLD_TYPE(op1);
    op2_type = op.VLD_TYPE(op2);
    res_type = op.VLD_TYPE(result);

    if (flags == SPECIAL) {
        flags = vld_get_special_flags(&op, base_address);
    } 
    if (flags & OP1_OPLINE) {
        op1_type = VLD_IS_OPLINE;
    }
    if (flags & OP2_OPLINE) {
        op2_type = VLD_IS_OPLINE;
    }
    if (flags & OP1_OPNUM) {
        op1_type = VLD_IS_OPNUM;
    }
    if (flags & OP2_OPNUM) {
        op2_type = VLD_IS_OPNUM;
    }
    if (flags & OP1_CLASS) {
        op1_type = VLD_IS_CLASS;
    }
    if (flags & RES_CLASS) {
        res_type = VLD_IS_CLASS;
    }

    if (flags & OP_FETCH) {
#ifdef ZEND_ENGINE_2
        switch (op.VLD_EXTENDED_VALUE(op2)) {
            case ZEND_FETCH_GLOBAL:
                fetch_type = "global";
                break;
            case ZEND_FETCH_LOCAL:
                fetch_type = "local";
                break;
            case ZEND_FETCH_STATIC:
                fetch_type = "static";
                break;
            case ZEND_FETCH_STATIC_MEMBER:
                fetch_type = "static member";
                break;
#ifdef ZEND_FETCH_GLOBAL_LOCK
            case ZEND_FETCH_GLOBAL_LOCK:
                fetch_type = "global lock";
                break;
#endif
#ifdef ZEND_FETCH_AUTO_GLOBAL
            case ZEND_FETCH_AUTO_GLOBAL:
                fetch_type = "auto global";
                break;
#endif
            default:
                fetch_type = "unknown";
                break;
        }
#else 
        if (op.op2.u.fetch_type == ZEND_FETCH_GLOBAL) {
            fetch_type = "global";
        } else if (op.op2.u.fetch_type == ZEND_FETCH_STATIC) {
            fetch_type = "static";
        }
#endif
    }

    if (op.lineno == last_lineno) {
        vld_printf(stderr, "     ");
    } else {
        vld_printf(stderr, "%4d ", op.lineno);
        last_lineno = op.lineno;
    }

    if (op.opcode >= NUM_KNOWN_OPCODES) {
        if (VLD_G(format)) {
            vld_printf(stderr, "%5d %s %c %c %c %c %s <%03d>%-23s %s %-14s ", nr, VLD_G(col_sep), notdead ? ' ' : '*', entry ? 'E' : ' ', start ? '>' : ' ', end ? '>' : ' ', VLD_G(col_sep), op.opcode, VLD_G(col_sep), fetch_type);
        } else {
            vld_printf(stderr, "%5d%c %c %c %c <%03d>%-23s %-14s ", nr, notdead ? ' ' : '*', entry ? 'E' : ' ', start ? '>' : ' ', end ? '>' : ' ', op.opcode, "", fetch_type);
        }
    } else {
        if (VLD_G(format)) {
            vld_printf(stderr, "%5d %s %c %c %c %c %s %-28s %s %-14s ", nr, VLD_G(col_sep), notdead ? ' ' : '*', entry ? 'E' : ' ', start ? '>' : ' ', end ? '>' : ' ', VLD_G(col_sep), opcodes[op.opcode].name, VLD_G(col_sep), fetch_type);
        } else {
            vld_printf(stderr, "%5d%c %c %c %c %-28s %-14s ", nr, notdead ? ' ' : '*', entry ? 'E' : ' ', start ? '>' : ' ', end ? '>' : ' ', opcodes[op.opcode].name, fetch_type);
        }
    }

    if (flags & EXT_VAL) {
        vld_printf(stderr, "%3d  ", op.extended_value);
    } else {
        vld_printf(stderr, "     ");
    }

    if ((flags & RES_USED) && !(op.VLD_EXTENDED_VALUE(result) & EXT_TYPE_UNUSED)) {
        VLD_PRINT(3, " RES[ ");
        len = vld_dump_znode (NULL, res_type, op.result, base_address TSRMLS_CC);
        VLD_PRINT(3, " ]");
        if (VLD_G(format)) {
            if (len==0) {
                vld_printf(stderr, " ");
            }
        } else {
            vld_printf(stderr, "%*s", 8-len, " ");
        }
    } else {
        vld_printf(stderr, "        ");
    }

    if (flags & OP1_USED) {
        VLD_PRINT(3, " OP1[ ");
        vld_dump_znode (&print_sep, op1_type, op.op1, base_address TSRMLS_CC);
        VLD_PRINT(3, " ]");
    }        
    if (flags & OP2_USED) {
        VLD_PRINT(3, " OP2[ ");
        if (flags & OP2_INCLUDE) {
            if (VLD_G(verbosity) < 3 && print_sep) {
                vld_printf(stderr, ", ");
            }
#if PHP_VERSION_ID >= 50399
            switch (op.extended_value) {
#else
            switch (Z_LVAL(op.op2.u.constant)) {
#endif
                case ZEND_INCLUDE_ONCE:
                    vld_printf(stderr, "INCLUDE_ONCE");
                    break;
                case ZEND_REQUIRE_ONCE:
                    vld_printf(stderr, "REQUIRE_ONCE");
                    break;
                case ZEND_INCLUDE:
                    vld_printf(stderr, "INCLUDE");
                    break;
                case ZEND_REQUIRE:
                    vld_printf(stderr, "REQUIRE");
                    break;
                case ZEND_EVAL:
                    vld_printf(stderr, "EVAL");
                    break;
                default:
                    vld_printf(stderr, "!!ERROR!!");
                    break;
            }
        } else {
            vld_dump_znode (&print_sep, op2_type, op.op2, base_address TSRMLS_CC);
        }
        VLD_PRINT(3, " ]");
    }
    if (flags & OP2_BRK_CONT) {
        long jmp;
        zend_brk_cont_element *el;

        VLD_PRINT(3, " BRK_CONT[ ");
#if PHP_VERSION_ID >= 50399
        el = vld_find_brk_cont(Z_LVAL_P(op.op2.zv), VLD_ZNODE_ELEM(op.op1, opline_num), opa);
#else
        el = vld_find_brk_cont(op.op2.u.constant.value.lval, VLD_ZNODE_ELEM(op.op1, opline_num), opa);
#endif
        jmp = op.opcode == ZEND_BRK ? el->brk : el->cont;
        vld_printf (stderr, ", ->%d", jmp);
        VLD_PRINT(3, " ]");
    }
    if (flags & NOP2_OPNUM) {
        zend_op next_op = op_ptr[nr+1];
        vld_dump_znode (&print_sep, VLD_IS_OPNUM, next_op.op2, base_address TSRMLS_CC);
    }
    vld_printf (stderr, "\n");
}

複製代碼

Relevant Link:

http://bug1874.com/archives/125
http://blog.csdn.net/phpkernel/article/details/5718519

 

7. 嵌入式Embed SAPI編程

嵌入式PHP類似CLI,也是SAPI接口的另一種實現。 一般情況下,它的一個請求的生命週期也會和其它的SAPI一樣:模塊初始化=>請求初始化=>處理請求=>關閉請求=>關閉模塊

0x1: 編譯PHP Embed SPAI運行時so庫

./configure --enable-embed --enable-debug
make install

0x2: 宿主C程序

複製代碼

#include <sapi/embed/php_embed.h>
#ifdef ZTS
    void ***tsrm_ls;
#endif
/* Extension bits */
zend_module_entry php_mymod_module_entry = {
    STANDARD_MODULE_HEADER,
    "mymod", /* extension name */
    NULL, /* function entries */
    NULL, /* MINIT */
    NULL, /* MSHUTDOWN */
    NULL, /* RINIT */
    NULL, /* RSHUTDOWN */
    NULL, /* MINFO */
    "1.0", /* version */
    STANDARD_MODULE_PROPERTIES
};
/* Embedded bits */
static void startup_php(void)
{
    int argc = 1;
    char *argv[2] = { "embed5", NULL };
    php_embed_init(argc, argv PTSRMLS_CC);
    zend_startup_module(&php_mymod_module_entry);
}
static void execute_php(char *filename)
{
    zend_first_try {
        char *include_script;
        spprintf(&include_script, 0, "include '%s'", filename);
        zend_eval_string(include_script, NULL, filename TSRMLS_CC);
        efree(include_script);
    } zend_end_try();
}
int main(int argc, char *argv[])
{
    if (argc <= 1) {
        printf("Usage: embed4 scriptfile";);
        return -1;
    }
    startup_php();
    execute_php(argv[1]);
    php_embed_shutdown(TSRMLS_CC);
    return 0;
}

複製代碼

Relevant Link:

複製代碼

https://code.google.com/archive/p/opcodesdumper/downloads
http://www.nowamagic.net/librarys/veda/detail/1318
http://udn.yyuap.com/doc/wiki/project/extending-embedding-php/20.1.html
http://udn.yyuap.com/doc/wiki/project/extending-embedding-php/19.html
https://code.google.com/archive/p/opcodesdumper/
http://www.laruence.com/2008/09/23/539.html
http://www.php-internals.com/book/?p=chapt02/02-02-02-embedding-php

複製代碼

 

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