PHP pwn 學習 (1)

文章目錄

  • 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-developmentphp.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_STARTZEND_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_PUBLICpublic訪問權限等。

如果需要定義類屬性,則需在類注冊函數中完成定義。下面是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即釋放字符串值。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/39723.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/39723.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/39723.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Python 作業題1 (猜數字)

題目 你要根據線索猜出一個三位數。游戲會根據你的猜測給出以下提示之一&#xff1a;如果你猜對一位數字但數字位置不對&#xff0c;則會提示“Pico”&#xff1b;如果你同時猜對了一位數字及其位置&#xff0c;則會提示“Fermi”&#xff1b;如果你猜測的數字及其位置都不對&…

Flower花所:穩定運營的數字貨幣交易所

Flower花所是一家穩定運營的數字貨幣交易所&#xff0c;致力于為全球用戶提供安全、高效的數字資產交易服務。作為一家長期穩定運營的數字貨幣交易平臺&#xff0c;Flower花所以其可靠的技術基礎和優質的客戶服務而聞名。 平臺穩定性與可靠性&#xff1a; 持續運營&#xff1a;…

Vue前端練習

此練習項目只涉及前端&#xff0c;主要是vue和ElementUI框架的使用。&#xff08;ElementUI官網&#xff1a;Element - The worlds most popular Vue UI framework&#xff09; 一、環境準備 安裝idea 安裝Node.js 一鍵式安裝(不需要做任何配置) npm -v&#xff08;也可用nod…

mysql-sql-第十五周

學習目標&#xff1a; sql 學習內容&#xff1a; 41.查詢沒有學全所有課程的同學的信息 select *from students where students.stunm not in (select score.stunm from score group by score.stunm having count(score.counm) (select count(counm) from course)) 42.查詢…

數據結構_線性表

線性表的定義和特點 線性表是具有相同特性的數據元素的一個有限序列 :線性起點/起始節點 :的直接前驅 :的直接后繼 :線性終點/終端節點 n:元素總個數,表長 下標:是元素的序號,表示元素在表中的位置 n0時稱為空表 線性表 由n(n>0)個數據元素(結點),組成的有限序列 將…

安卓模擬器如何修改ip地址

最近很多老鐵玩游戲的&#xff0c;想多開模擬器一個窗口一個IP&#xff0c;若模擬器窗口開多了&#xff0c;IP一樣會受到限制&#xff0c;那么怎么更換自己電腦手機模擬器IP地址呢&#xff0c;今天就教大家一個修改模擬器IP地址的方法&#xff01;廢話不多說&#xff0c;直接上…

Matlab 中 fftshift 與 ifftshift

文章目錄 【 1. fftshift、ifftshift 的區別】【 2. fftshift(fft(A)) 作圖 】【 3. fftshift(fft(A)) 還原到 A 】Matlab 直接對信號進行 FFT 的結果中,前半部分是正頻,后半部分是負頻,為了更直觀的表示,需要將 負頻 部分移到 前面。【 1. fftshift、ifftshift 的區別】 M…

alibaba EasyExcel 簡單導出數據到Excel

導入依賴 <dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>4.0.1</version> </dependency> 1、alibaba.excel.EasyExcel導出工具類 import com.alibaba.excel.EasyExcel; import …

探索哈希函數:數據完整性的守護者

引言 銀行在處理數以百萬計的交易時&#xff0c;如何確保每一筆交易都沒有出錯&#xff1f;快遞公司如何跟蹤成千上萬的包裹&#xff0c;確保每個包裹在運輸過程中沒有丟失或被替換&#xff1f;醫院和診所為龐大的患者提供有效的醫療保健服務&#xff0c;如何確保每個患者的醫療…

假陽性和假陰性、真陽性和真陰性

在深度學習的分類問題中&#xff0c;真陽性、真陰性、假陽性和假陰性是評估模型性能的重要指標。它們的定義和計算如下&#xff1a; 真陽性&#xff08;True Positive, TP&#xff09;&#xff1a; 定義&#xff1a;模型預測為正類&#xff08;陽性&#xff09;&#xff0c;且實…

電梯修理升級,安裝【電梯節能】能量回饋設備

電梯修理升級&#xff0c;安裝【電梯節能】能量回饋設備 1、節能率評估 15%—45% 2、降低機房環境溫度&#xff0c;改善電梯控制系統的運行環境&#xff1b; 3、延長電梯使用壽命&#xff1b; 4、機房可以不需要使用空調等散熱設備的耗電&#xff0c;間接節省電能。 歡迎私詢哦…

智能數字人系統的主要功能

智能數字人系統或虛擬數字人系統&#xff0c;是指利用人工智能技術構建的虛擬人物形象&#xff0c;能夠與人進行自然交互的系統。數字人系統的主要功能包括以下幾個方面。北京木奇移動技術有限公司&#xff0c;專業的軟件外包開發公司&#xff0c;歡迎交流合作。 1. 語言理解與…

昇思25天學習打卡營第2天|初學入門

昇思25天學習打卡營第2天 文章目錄 昇思25天學習打卡營第2天網絡構建定義模型類模型層nn.Flattennn.Densenn.ReLUnn.SequentialCellnn.Softmax 模型參數 函數式自動微分函數與計算圖微分函數與梯度計算Stop GradientAuxiliary data神經網絡梯度計算 問題集合打卡記錄 網絡構建 …

橋接模式與適配器模式

一、共性和區別 橋接設計模式和適配器設計模式的共同點和明顯&#xff0c;它們都是使兩種不同的類配合工。 二者的區別在于&#xff0c;適配器模式是將已有的兩個不同接口接口組合到一起&#xff0c;使得適配器同時擁有兩個不同接口的功能&#xff0c;其目的是使兩個不兼…

Spring Boot與微服務治理框架的集成方法

Spring Boot與微服務治理框架的集成方法 大家好&#xff0c;我是免費搭建查券返利機器人省錢賺傭金就用微賺淘客系統3.0的小編&#xff0c;也是冬天不穿秋褲&#xff0c;天冷也要風度的程序猿&#xff01; 在當今快速發展的軟件開發領域&#xff0c;微服務架構因其靈活性、可…

華為DCN之:SDN和NFV

1. SDN概述 1.1 SDN的起源 SDN&#xff08;Software Defined Network&#xff09;即軟件定義網絡。是由斯坦福大學Clean Slate研究組提出的一種新型網絡創新架構。其核心理念通過將網絡設備控制平面與數據平面分離&#xff0c;從而實現了網絡控制平面的集中控制&#xff0c;為…

移動網絡捕獲在數字化轉型中的重要性

數字化轉型重新定義了企業運營和與客戶互動的方式。它為組織提供價值的方式帶來了根本性的轉變&#xff0c;使流程更易于訪問、更高效、更具協作性和更安全。然而&#xff0c;跟上不斷發展的數字環境可能是一項挑戰&#xff0c;而未能接受數字化轉型的企業則面臨被淘汰的風險。…

表達式二叉樹的應用

在計算機科學的廣闊領域中,數據結構是構建高效程序和算法的基石。其中,表達式二叉樹(Expression Tree)是一種特殊而強大的數據結構,它將數學表達式的解析和計算轉化為直觀的圖形表示,不僅簡化了復雜的運算過程,還為編譯器設計、計算器應用以及符號數學軟件提供了堅實的基…

(八)EBO和glDrawElements

EBO EBO(Element Buffer Object)&#xff1a;元素緩沖對象&#xff0c;用于存儲頂點繪制順序索引號的GPU顯存區域 unsigned int indices[] {0, 1, 2,2, 1, 3};//EBO創建和綁定GLuint ebo 0;glGenBuffers(1, &ebo);glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);glBufferD…

【MindSpore學習打卡】應用實踐-計算機視覺-ShuffleNet圖像分類:從理論到實踐

在當今的深度學習領域&#xff0c;卷積神經網絡&#xff08;CNN&#xff09;已經成為圖像分類任務的主流方法。然而&#xff0c;隨著網絡深度和復雜度的增加&#xff0c;計算資源的消耗也顯著增加&#xff0c;特別是在移動設備和嵌入式系統中&#xff0c;這種資源限制尤為突出。…