文章目錄
- A. PHP extensions for C
- 1. 運行環境與工作目錄初始化
- 2. 構建與加載
- 3. 關鍵結構定義
- `PHP_FUNCTION`
- `INTERNAL_FUNCTION_PARAMETERS`
- `zend_execute_data`等
- `ZEND_PARSE_PARAMETERS_START`等
- `zend_parse_arg_string`
- `zend_module_entry`
- `zend_function_entry`等
- PHP類相關
原文鏈接:個人博客
最近在D3CTF中發現了一道與PHP有關的Pwn題,當時由于事務繁忙沒有及時學習,結果在不久之后的長城杯決賽中就遭到報應了,PHP pwn現場不會做。畢設做完之后,現在總算有時間拾起都有點陌生的CTF了。下面就來記錄一下PHP pwn的學習過程。
A. PHP extensions for C
在查閱資料后可以發現,實際上PHP pwn考的還是用戶態pwn。具體而言,賽題一般使用的都是使用C語言編寫的PHP擴展庫文件。
PHP是一門基于C語言編寫的高級語言,歷史悠久。它支持使用C語言編寫可直接用于PHP文件的二進制.so庫文件。具體操作如下:
1. 運行環境與工作目錄初始化
為方便實驗,這里可以基于PHP docker容器完成下面的操作。筆者使用的是php:8.1-apache,這個版本是php的較新版本,且內置apache服務器與PHP源碼,可以開箱即用。
在容器的/usr/src
目錄中保存有php 8.1版本的源碼壓縮包,解壓即可。
在源碼目錄的ext目錄中有一個PHP腳本ext_skel.php
,運行后可指定目錄與腳本名,用于生成PHP擴展的基礎文件。
root@4bc0fa317dea:/usr/src/php-8.1.1/ext# ./ext_skel.php --help
WHAT IT ISIt's a tool for automatically creating the basic framework for a PHP extension.HOW TO USE ITVery simple. First, change to the ext/ directory of the PHP sources. Then runthe followingphp ext_skel.php --ext extension_nameand everything you need will be placed in directory ext/extension_name.If you don't need to test the existence of any external header files,libraries or functions in them, the extension is ready to be compiled in PHP.To compile the extension run the following:cd extension_namephpize./configuremakeDon't forget to run tests once the compilation is done:make testAlternatively, to compile extension in the PHP:cd /path/to/php-src./buildconf./configure --enable-extension_namemakemake test TESTS=ext/extension_name/testsThe definition of PHP_extension_NAME_VERSION will be present in thephp_extension_name.h and injected into the zend_extension_entry definition.This is required by the PECL website for the version string conformity checksagainst package.xmlSOURCE AND HEADER FILE NAMEThe ext_skel.php script generates 'extension_name.c' and 'php_extension_name.h'as the main source and header files. Keep these names.extension functions (User functions) must be namedextension_name_function()When you need to expose extension functions to other extensions, exposefunctions strictly needed by others. Exposed internal function must be namedphp_extension_name_function()See also CODING_STANDARDS.md.OPTIONSphp ext_skel.php --ext <name> [--experimental] [--author <name>][--dir <path>] [--std] [--onlyunix][--onlywindows] [--help]--ext <name> The name of the extension defined as <name>--experimental Passed if this extension is experimental, this createsthe EXPERIMENTAL file in the root of the extension--author <name> Your name, this is used if --std is passed and for theCREDITS file--dir <path> Path to the directory for where extension should becreated. Defaults to the directory of where this scriptlives--std If passed, the standard header used in extensions thatis included in the core, will be used--onlyunix Only generate configure scripts for Unix--onlywindows Only generate configure scripts for Windows--help This help
基本只需要使用--ext
與--dir
選項即可。這里筆者將腳本目錄設置為/var/www/my_extension
。
執行命令后,在/var/www/my_extension
中自動生成了一些文件:
root@4bc0fa317dea:/var/www/my_extension/hello_phpext# ls -al
total 40
drwxr-xr-x 3 root root 4096 Jun 19 12:39 .
drwxr-xr-x 3 root root 4096 Jun 19 12:36 ..
-rw-r--r-- 1 root root 500 Jun 19 12:32 .gitignore
-rw-r--r-- 1 root root 3490 Jun 19 12:32 config.m4
-rw-r--r-- 1 root root 253 Jun 19 12:32 config.w32
-rw-r--r-- 1 root root 1971 Jun 19 12:32 hello_phpext.c
-rw-r--r-- 1 root root 110 Jun 19 12:32 hello_phpext.stub.php
-rw-r--r-- 1 root root 558 Jun 19 12:32 hello_phpext_arginfo.h
-rw-r--r-- 1 root root 372 Jun 19 12:32 php_hello_phpext.h
drwxr-xr-x 2 root root 4096 Jun 19 12:32 tests
- config.m4:Unix下的Build Config配置文件,將通過它完成配置與安裝。
- hello_phpext.c:包含主要邏輯的C語言文件,我們擴展函數的保存位置。
- php_hello_phpext.h:頭文件,包含結構體定義等。
2. 構建與加載
為方便后續對我們的擴展進行測試,首先需要搞清楚應該如何將擴展加載到PHP中。
使用下面的命令可以完成擴展加載:
phpize
./configure
make
make install
執行上述命令后,我們的PHP擴展庫文件就被復制到了/usr/local/lib/php/extensions/no-debug-non-zts-20210902
目錄中。
隨后,我們還需要修改php.ini
文件。在8.1.1版本的PHP中,/usr/local/etc/php
目錄下有兩個文件:php.ini-development
與php.ini-production
,前者一般用于開發調試而后者用于發布。這里使用前者,在代碼中添加一行:
extension=hello_phpext.so
隨后將其復制一份保存為php.ini,重啟apache2服務,即可將我們的擴展加載到PHP中。
3. 關鍵結構定義
上述初始化操作完成后,hello_phpext.c中預先定義了一些必要的結構以及兩個示例函數:
/* hello_phpext extension for PHP */#ifdef HAVE_CONFIG_H
# include "config.h"
#endif#include "php.h"
#include "ext/standard/info.h"
#include "php_hello_phpext.h"
#include "hello_phpext_arginfo.h"/* For compatibility with older PHP versions */
#ifndef ZEND_PARSE_PARAMETERS_NONE
#define ZEND_PARSE_PARAMETERS_NONE() \ZEND_PARSE_PARAMETERS_START(0, 0) \ZEND_PARSE_PARAMETERS_END()
#endif/* {{{ void test1() */
PHP_FUNCTION(test1)
{ZEND_PARSE_PARAMETERS_NONE();php_printf("The extension %s is loaded and working!\r\n", "hello_phpext");
}
/* }}} *//* {{{ string test2( [ string $var ] ) */
PHP_FUNCTION(test2)
{char *var = "World";size_t var_len = sizeof("World") - 1;zend_string *retval;ZEND_PARSE_PARAMETERS_START(0, 1)Z_PARAM_OPTIONALZ_PARAM_STRING(var, var_len)ZEND_PARSE_PARAMETERS_END();retval = strpprintf(0, "Hello %s", var);RETURN_STR(retval);
}
/* }}}*//* {{{ PHP_RINIT_FUNCTION */
PHP_RINIT_FUNCTION(hello_phpext)
{
#if defined(ZTS) && defined(COMPILE_DL_HELLO_PHPEXT)ZEND_TSRMLS_CACHE_UPDATE();
#endifreturn SUCCESS;
}
/* }}} *//* {{{ PHP_MINFO_FUNCTION */
PHP_MINFO_FUNCTION(hello_phpext)
{php_info_print_table_start();php_info_print_table_header(2, "hello_phpext support", "enabled");php_info_print_table_end();
}
/* }}} *//* {{{ hello_phpext_module_entry */
zend_module_entry hello_phpext_module_entry = {STANDARD_MODULE_HEADER,"hello_phpext", /* Extension name */ext_functions, /* zend_function_entry */NULL, /* PHP_MINIT - Module initialization */NULL, /* PHP_MSHUTDOWN - Module shutdown */PHP_RINIT(hello_phpext), /* PHP_RINIT - Request initialization */NULL, /* PHP_RSHUTDOWN - Request shutdown */PHP_MINFO(hello_phpext), /* PHP_MINFO - Module info */PHP_HELLO_PHPEXT_VERSION, /* Version */STANDARD_MODULE_PROPERTIES
};
/* }}} */#ifdef COMPILE_DL_HELLO_PHPEXT
# ifdef ZTS
ZEND_TSRMLS_CACHE_DEFINE()
# endif
ZEND_GET_MODULE(hello_phpext)
#endif
這里包含了大量PHP相關的宏定義,一眼看過去確實難以理解,下面將結合PHP源碼進行分析。
PHP_FUNCTION
這是用于定義PHP庫函數的宏定義,在8.1.1版本PHP源碼中的定義如下:
// /Zend/zend_API.h, line 71#define ZEND_NAMED_FUNCTION(name) void ZEND_FASTCALL name(INTERNAL_FUNCTION_PARAMETERS)
#define ZEND_FUNCTION(name) ZEND_NAMED_FUNCTION(zif_##name)
即上面的PHP_FUNCTION(test1)
就相當于void ZEND_FASTCALL test1(INTERNAL_FUNCTION_PARAMETERS)
。
INTERNAL_FUNCTION_PARAMETERS
上面的函數定義需要使用參數定義的相關宏定義INTERNAL_FUNCTION_PARAMETERS
,它的定義如下:
// /Zend/zend.h, line 48#define INTERNAL_FUNCTION_PARAMETERS zend_execute_data *execute_data, zval *return_value
即所有的PHP庫函數都會通過第一個參數execute_data
傳入所有C函數需要的參數,由第二個參數return_value
獲取返回值,庫函數本身的返回值恒為void。
zend_execute_data
等
這是一個結構體,用于保存C庫函數的相關參數的數據結構。
// /Zend/zend_types.h, line 88typedef struct _zend_execute_data zend_execute_data;// /Zend/zend_compile.h, line 522struct _zend_execute_data {const zend_op *opline; /* executed opline */zend_execute_data *call; /* current call */zval *return_value;zend_function *func; /* executed function */zval This; /* this + call_info + num_args */zend_execute_data *prev_execute_data;zend_array *symbol_table;void **run_time_cache; /* cache op_array->run_time_cache */zend_array *extra_named_params;
};
這里引用了另外一些結構體,下面給出部分定義。
// /Zend/zend_types.h, line 90typedef struct _zval_struct zval;// /Zend/zend_types.h, line 288typedef union _zend_value {zend_long lval; /* long value */double dval; /* double value */zend_refcounted *counted;zend_string *str;zend_array *arr;zend_object *obj;zend_resource *res;zend_reference *ref;zend_ast_ref *ast;zval *zv;void *ptr;zend_class_entry *ce;zend_function *func;struct {uint32_t w1;uint32_t w2;} ww;
} zend_value;struct _zval_struct {zend_value value; /* value */union {uint32_t type_info;struct {ZEND_ENDIAN_LOHI_3(zend_uchar type, /* active type */zend_uchar type_flags,union {uint16_t extra; /* not further specified */} u)} v;} u1;union {uint32_t next; /* hash collision chain */uint32_t cache_slot; /* cache slot (for RECV_INIT) */uint32_t opline_num; /* opline number (for FAST_CALL) */uint32_t lineno; /* line number (for ast nodes) */uint32_t num_args; /* arguments number for EX(This) */uint32_t fe_pos; /* foreach position */uint32_t fe_iter_idx; /* foreach iterator index */uint32_t property_guard; /* single property guard */uint32_t constant_flags; /* constant flags */uint32_t extra; /* not further specified */} u2;
};
可以看到,PHP使用zend_value
定義PHP數據類型,包括整數、浮點數、數組、對象、函數、類等。
ZEND_PARSE_PARAMETERS_START
等
在C庫函數中,有一個重要的流程——解析PHP參數。從上面的示例C文件中,可以看到這樣一段:
ZEND_PARSE_PARAMETERS_START(0, 1)Z_PARAM_OPTIONALZ_PARAM_STRING(var, var_len)ZEND_PARSE_PARAMETERS_END();
這些宏定義均在/Zend/zend_API.h
中定義,將這些宏全部展開帶入參數之后,就變成了下面這個樣子(已刪除無效控制流):
// ZEND_PARSE_PARAMETERS_START
do { \const int _flags = (0); \uint32_t _min_num_args = (0); \uint32_t _max_num_args = (uint32_t) (1); \uint32_t _num_args = (execute_data)->This.u2.num_args; \uint32_t _i = 0; \zval *_real_arg, *_arg = NULL; \zend_expected_type _expected_type = Z_EXPECTED_LONG; \char *_error = NULL; \bool _dummy = 0; \bool _optional = 0; \int _error_code = ZPP_ERROR_OK; \((void)_i); \((void)_real_arg); \((void)_arg); \((void)_expected_type); \((void)_error); \((void)_optional); \((void)_dummy); \\do { \if (UNEXPECTED(_num_args < _min_num_args) || \UNEXPECTED(_num_args > _max_num_args)) { \if (!(_flags & ZEND_PARSE_PARAMS_QUIET)) { \zend_wrong_parameters_count_error(_min_num_args, _max_num_args); \} \_error_code = ZPP_ERROR_FAILURE; \break; \} \_real_arg = ZEND_CALL_VAR_NUM(execute_data, -1);
// Z_PARAM_OPTIONAL_optional = 1;
// Z_PARAM_STRING++_i; \ZEND_ASSERT(_i <= _min_num_args || _optional==1); \ZEND_ASSERT(_i > _min_num_args || _optional==0); \if (_optional) { \if (UNEXPECTED(_i >_num_args)) break; \} \_real_arg++; \_arg = _real_arg; \if (UNEXPECTED(!zend_parse_arg_string(_arg, &var, &var_len, 0, _i))) { \_expected_type = 0 ? Z_EXPECTED_STRING_OR_NULL : Z_EXPECTED_STRING; \_error_code = ZPP_ERROR_WRONG_ARG; \break; \}
// ZEND_PARSE_PARAMETERS_ENDZEND_ASSERT(_i == _max_num_args || _max_num_args == (uint32_t) -1); \} while (0); \if (UNEXPECTED(_error_code != ZPP_ERROR_OK)) { \if (!(_flags & ZEND_PARSE_PARAMS_QUIET)) { \zend_wrong_parameter_error(_error_code, _i, _error, _expected_type, _arg); \} \return; \} \} while (0);
由于C語言沒有類的概念,因此對于一些需要泛型的操作只有通過宏定義實現才能讓代碼更加簡潔,也提升了代碼審計的難度。這段代碼有一個比較有趣的地方——大量使用了do ... while(0)
的控制流結構,這看上去冗余,但實際上是為了隔離作用域,讓宏定義中的臨時變量具有臨時作用域,使宏定義調用方對于臨時變量不可見,避免調用方多次調用相同宏定義時出現變量重復定義的問題。
在上面的代碼中,關鍵邏輯實際上就是一行,即調用zend_parse_arg_string
函數進行參數解析。前后添加了一些安全檢查,包括參數個數、解析是否成功等。下面簡單分析一下zend_parse_arg_string
的相關邏輯。
zend_parse_arg_string
// /Zend/zend_API.h, line 1984static zend_always_inline bool zend_parse_arg_str(zval *arg, zend_string **dest, bool check_null, uint32_t arg_num)
{if (EXPECTED(Z_TYPE_P(arg) == IS_STRING)) {*dest = Z_STR_P(arg);} else if (check_null && Z_TYPE_P(arg) == IS_NULL) {*dest = NULL;} else {return zend_parse_arg_str_slow(arg, dest, arg_num);}return 1;
}static zend_always_inline bool zend_parse_arg_string(zval *arg, char **dest, size_t *dest_len, bool check_null, uint32_t arg_num)
{zend_string *str;if (!zend_parse_arg_str(arg, &str, check_null, arg_num)) {return 0;}if (check_null && UNEXPECTED(!str)) {*dest = NULL;*dest_len = 0;} else {*dest = ZSTR_VAL(str);*dest_len = ZSTR_LEN(str);}return 1;
}
可以清晰地看到這里PHP源代碼對傳入的zval
進行解析的過程。由于zval
結構中的zend_value
是一個聯合類型,因此可以用于表示多種數據類型,相互轉換也非常簡單。
由此可知,ZEND_PARSE_PARAMETERS_START
與ZEND_PARSE_PARAMETERS_END
之間即為C庫函數解析PHP參數的流程,在ZEND_PARSE_PARAMETERS_START
中,需要指定要解析第幾個參數,隨后在內部可通過多次使用Z_PARAM_xxx
進行參數解析。
zend_module_entry
這是一個在預先定義的C庫文件中被使用的數據結構。從最上面的代碼注釋可以看到,這個數據類型定義了PHP模塊的基本信息,包括擴展的名字、庫函數的入口(定義的所有導出函數)、初始化函數、關閉函數、請求初始化函數、請求關閉函數、phpinfo鉤子函數、版本等。
PHP_MINFO_FUNCTION(hello_phpext)
{php_info_print_table_start();php_info_print_table_header(2, "hello_phpext support", "enabled");php_info_print_table_end();
}zend_module_entry hello_phpext_module_entry = {STANDARD_MODULE_HEADER,"hello_phpext", /* Extension name */ext_functions, /* zend_function_entry */NULL, /* PHP_MINIT - Module initialization */NULL, /* PHP_MSHUTDOWN - Module shutdown */PHP_RINIT(hello_phpext), /* PHP_RINIT - Request initialization */NULL, /* PHP_RSHUTDOWN - Request shutdown */PHP_MINFO(hello_phpext), /* PHP_MINFO - Module info */PHP_HELLO_PHPEXT_VERSION, /* Version */STANDARD_MODULE_PROPERTIES
};
其中,PHP_MINFO
用于定義掛鉤在phpinfo函數中的C庫函數。它的作用是當PHP代碼調用phpinfo()
函數時顯示PHP基本信息時,能夠在其上附加顯示本擴展的基本信息,包括擴展名、作者等。
php_info_print_table_start
:開始顯示phpinfo表格。php_info_print_table_header
:輸出表格頭,第一個參數為需要添加的列數,后面的參數個數需要等于第一個參數的值,表示不同列的輸出內容。php_info_print_table_row
:輸出表格內容,第一個參數為該行的列數,后面參數個數等于第一個參數的值,表示不同列的輸出內容。php_info_print_table_end
:結束輸出phpinfo表格。
對于下面的PHP_MINFO_FUNCTION
定義,調用phpinfo
后可看到下圖表格的輸出。
PHP_MINFO_FUNCTION(hello_phpext)
{php_info_print_table_start();php_info_print_table_header(2, "hello_phpext support", "enabled");php_info_print_table_row(2, "author", "CoLin");php_info_print_table_end();
}
zend_function_entry
等
這是用于表示C庫的導出PHP函數的結構體,定義如下:
// /Zend/zend_API.htypedef struct _zend_function_entry {const char *fname;zif_handler handler;const struct _zend_internal_arg_info *arg_info;uint32_t num_args;uint32_t flags;
} zend_function_entry;
在hello_phpext_arginfo.h
中,有一個static const zend_function_entry ext_functions[]
的數組結構,其中即保存了本擴展中導出的,可在PHP代碼中直接調用的函數。
static const zend_function_entry ext_functions[] = {ZEND_FE(test1, arginfo_test1)ZEND_FE(test2, arginfo_test2)ZEND_FE_END
};
其中的每一個導出函數都使用ZEND_FE
宏定義包裹,第一個參數為函數名,第二個參數為函數的參數信息。
頭文件中也對參數類型進行了定義:
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_test1, 0, 0, IS_VOID, 0)
ZEND_END_ARG_INFO()ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_test2, 0, 0, IS_STRING, 0)ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, str, IS_STRING, 0, "\"\"")
ZEND_END_ARG_INFO()
下面給出這些宏的定義:
// /Zend/zend_API.h, line 183#define ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(name, return_reference, required_num_args, type, allow_null) \ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX2(name, return_reference, required_num_args, type, allow_null, 0)// /Zend/zend_API.h, line 179#define ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX2(name, return_reference, required_num_args, type, allow_null, is_tentative_return_type) \static const zend_internal_arg_info name[] = { \{ (const char*)(zend_uintptr_t)(required_num_args), ZEND_TYPE_INIT_CODE(type, allow_null, _ZEND_ARG_INFO_FLAGS(return_reference, 0, is_tentative_return_type)), NULL },// Zend/zend_API.h, line 197#define ZEND_END_ARG_INFO() };// Zend/zend_compile.h, line 400/* arg_info for internal functions */
typedef struct _zend_internal_arg_info {const char *name;zend_type type;const char *default_value;
} zend_internal_arg_info;
因此,ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_test1, 0, 0, IS_VOID, 0)
展開就是:
static const zend_internal_arg_info arginfo_test1[] = { \{ (const char*)(zend_uintptr_t)(0), ZEND_TYPE_INIT_CODE(IS_VOID, 0, _ZEND_ARG_INFO_FLAGS(0, 0, 0)), NULL },
其中括號未閉合,用于在下面繼續定義其他參數。這個宏定義實際上是首先定義了返回值的類型,它的5個參數分別代表:
- 1 - 函數名
- 2 - 返回值是否為引用值
- 3 - 必需的參數數量
- 4 - 返回值類型
- 5 - 返回值是否允許為空
這里需要注意的是,參數3表示必需的參數數量,在PHP函數中還可以添加一些可選參數。即即使傳入的必需參數數量為0,在ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX
之后依然可以定義任意多的可選參數。即使PHP代碼中沒有傳入這些可選參數,在庫函數中只是會被當成默認值看待,而不會直接報錯。
其中,對于ZEND_TYPE_INIT_CODE
的定義如下:
#define ZEND_TYPE_INIT_CODE(code, allow_null, extra_flags) \ZEND_TYPE_INIT_MASK(((code) == _IS_BOOL ? MAY_BE_BOOL : ((code) == IS_MIXED ? MAY_BE_ANY : (1 << (code)))) \| ((allow_null) ? _ZEND_TYPE_NULLABLE_BIT : 0) | (extra_flags))
它的3個參數分別代表數據類型、是否允許空、其他參數標志位。
在ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX
之后,可以定義所有參數。定義使用下面的宏定義:
#define ZEND_ARG_TYPE_INFO(pass_by_ref, name, type_hint, allow_null) \{ #name, ZEND_TYPE_INIT_CODE(type_hint, allow_null, _ZEND_ARG_INFO_FLAGS(pass_by_ref, 0, 0)), NULL },
#define ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(pass_by_ref, name, type_hint, allow_null, default_value) \{ #name, ZEND_TYPE_INIT_CODE(type_hint, allow_null, _ZEND_ARG_INFO_FLAGS(pass_by_ref, 0, 0)), default_value },
type_hint
即為參數的數據類型,pass_by_ref
表示是否傳入引用。
PHP類相關
在C語言的PHP擴展中,可以完成對PHP類的定義,包括類屬性、類方法的定義等。這里以PHP源碼為例。
在/ext/com_dotnet/com_persist_arginfo.h
中,C代碼定義了一個COMPersistHelper
類。要想讓這個類在PHP代碼中能夠直接使用,需要一個類注冊函數:
// /ext/com_dotnet/com_persist_arginfo.h, line 56static zend_class_entry *register_class_COMPersistHelper(void)
{zend_class_entry ce, *class_entry;INIT_CLASS_ENTRY(ce, "COMPersistHelper", class_COMPersistHelper_methods);class_entry = zend_register_internal_class_ex(&ce, NULL);class_entry->ce_flags |= ZEND_ACC_FINAL;return class_entry;
}
類注冊代碼具有固定的函數聲明格式,其必為靜態函數,返回值必為zend_class_entry*
,函數名應被命名為register_class_xxx
,無參。
在函數中,必需進行類的初始化,即調用INIT_CLASS_ENTRY
宏,這個宏的第一個參數固定,第二個參數為類名,第三個參數為定義類中方法的數據結構:
// /ext/com_dotnet/com_persist_arginfo.h, line 44static const zend_function_entry class_COMPersistHelper_methods[] = {ZEND_ME(COMPersistHelper, __construct, arginfo_class_COMPersistHelper___construct, ZEND_ACC_PUBLIC)ZEND_ME(COMPersistHelper, GetCurFileName, arginfo_class_COMPersistHelper_GetCurFileName, ZEND_ACC_PUBLIC)ZEND_ME(COMPersistHelper, SaveToFile, arginfo_class_COMPersistHelper_SaveToFile, ZEND_ACC_PUBLIC)ZEND_ME(COMPersistHelper, LoadFromFile, arginfo_class_COMPersistHelper_LoadFromFile, ZEND_ACC_PUBLIC)ZEND_ME(COMPersistHelper, GetMaxStreamSize, arginfo_class_COMPersistHelper_GetMaxStreamSize, ZEND_ACC_PUBLIC)ZEND_ME(COMPersistHelper, InitNew, arginfo_class_COMPersistHelper_InitNew, ZEND_ACC_PUBLIC)ZEND_ME(COMPersistHelper, LoadFromStream, arginfo_class_COMPersistHelper_LoadFromStream, ZEND_ACC_PUBLIC)ZEND_ME(COMPersistHelper, SaveToStream, arginfo_class_COMPersistHelper_SaveToStream, ZEND_ACC_PUBLIC)ZEND_FE_END
};
若需要定義類中的方法,只需要完成對這個數組的定義即可,數組應命名為class_xxx_methods
,數組中需要使用ZEND_ME
宏表示類方法項。這個宏各個參數的含義如下:
- 1 - 類名,不加引號。
- 2 - 方法名,前加
__
的是內置函數,如構造函數、setter、getter等。 - 3 - 方法的參數定義,與函數參數定義方式相同。
- 4 - 類訪問權限,如
ZEND_ACC_PUBLIC
指public
訪問權限等。
如果需要定義類屬性,則需在類注冊函數中完成定義。下面是PHP類DOMDocumentType
類的注冊函數的一部分:
// /ext/dom/php_dom_arginfo.h, line 905zval property_name_default_value;ZVAL_UNDEF(&property_name_default_value);zend_string *property_name_name = zend_string_init("name", sizeof("name") - 1, 1);zend_declare_typed_property(class_entry, property_name_name, &property_name_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING));zend_string_release(property_name_name);
在上面的代碼中,為類DOMDocumentType
定義了一個屬性,名為name
,這里是使用zend_string_init
定義字符串,前兩個參數分別為char*
和長度,第3個長度指是否為永久字符串。隨后,通過zend_declare_typed_property
正式將屬性添加到類中,參數列表如下:
- 1 - 注冊函數的參數
- 2 - 屬性名
- 3 - 默認值
- 4 - 訪問權限
- 5 - 文檔字符串
- 6 - 屬性類型
// /ext/dom/php_dom_arginfo.h, line 911zend_string *property_entities_class_DOMNamedNodeMap = zend_string_init("DOMNamedNodeMap", sizeof("DOMNamedNodeMap")-1, 1);zval property_entities_default_value;ZVAL_UNDEF(&property_entities_default_value);zend_string *property_entities_name = zend_string_init("entities", sizeof("entities") - 1, 1);zend_declare_typed_property(class_entry, property_entities_name, &property_entities_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_CLASS(property_entities_class_DOMNamedNodeMap, 0, 0));zend_string_release(property_entities_name);
上面的代碼定義了數據類型為類的類屬性,這里是定義一個DOMNamedNodeMap
類型的類屬性,需要使用ZEND_TYPE_INIT_CLASS
宏定義。最后的zend_string_release
即釋放字符串值。