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.mzph.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;直接上…

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神经网络梯度计算 问题集合打卡记录 网络构建 …

华为DCN之:SDN和NFV

1. SDN概述 1.1 SDN的起源 SDN&#xff08;Software Defined Network&#xff09;即软件定义网络。是由斯坦福大学Clean Slate研究组提出的一种新型网络创新架构。其核心理念通过将网络设备控制平面与数据平面分离&#xff0c;从而实现了网络控制平面的集中控制&#xff0c;为…

移动网络捕获在数字化转型中的重要性

数字化转型重新定义了企业运营和与客户互动的方式。它为组织提供价值的方式带来了根本性的转变&#xff0c;使流程更易于访问、更高效、更具协作性和更安全。然而&#xff0c;跟上不断发展的数字环境可能是一项挑战&#xff0c;而未能接受数字化转型的企业则面临被淘汰的风险。…

【MindSpore学习打卡】应用实践-计算机视觉-ShuffleNet图像分类:从理论到实践

在当今的深度学习领域&#xff0c;卷积神经网络&#xff08;CNN&#xff09;已经成为图像分类任务的主流方法。然而&#xff0c;随着网络深度和复杂度的增加&#xff0c;计算资源的消耗也显著增加&#xff0c;特别是在移动设备和嵌入式系统中&#xff0c;这种资源限制尤为突出。…

25计算机考研,这些学校双非闭眼入,性价比超高!

计算机考研&#xff0c;好的双非院校也很多&#xff01; 对于一些二本准备考研的同学来说&#xff0c;没必要一直盯着985/211这些院校&#xff0c;竞争激烈不说&#xff0c;容易当陪跑&#xff0c;下面这些就是不错的双非院校&#xff1a; 燕山大学南京邮电大学南京信息工程大…

WPS-Word文档表格分页

一、问题描述 这种情况不好描述 就是像这种表格内容&#xff0c;但是会有离奇的分页的情况。这种情况以前的错误解决办法就是不断地调整表格的内容以及间隔显得很乱&#xff0c;于是今天去查了解决办法&#xff0c;现在学会了记录一下避免以后忘记了。 二、解决办法 首先记…

8.SQL注入-基于insert,update利用案例

SQL注入-基于insert/update利用案例 sql语句正常插入表中的数据 insert into member(username,pw,sex,phonenum,address,email) values(xiaoqiang,1111,1,2,3,4); select * from member;例如插入小强数据&#xff0c;如图所示&#xff1a; 采用or这个运算符&#xff0c;构造…

实测有效:Win11右键默认显示更多

Win11最大的变化之一莫过于右键菜单发生了变化&#xff0c;最大的问题是什么&#xff0c;是右键菜单很多时候需要点两次&#xff0c;实在是反人类 第一步 复制以下命令直接运行&#xff1a; reg.exe add "HKCU\Software\Classes\CLSID\{86ca1aa0-34aa-4e8b-a509-50c905ba…

python_zabbix

zabbix官网地址&#xff1a;19. API19. APIhttps://www.zabbix.com/documentation/4.2/zh/manual/api 每个版本可以有些差异&#xff0c;选择目前的版本在查看对于的api接口#token接口代码 import requests apiurl "http://zabbix地址/api_jsonrpc.php" data {&quo…