- URL: https://www.laruence.com/en/2009/06/14/945.html
- Please include attribution when republishing.
why xdebug extension must be loaded as a zend extension?
what is zend extension and what are the differents between regular php extension and zend extension?
let's start from that the extension loading process.
PHP can be extended, and so can PHP's core engine, the Zend Engine. If you have ever looked into writing an Apache module, the following structure will feel very familiar:
struct _zend_extension {
char *name;
char *version;
char *author;
char *URL;
char *copyright;
startup_func_t startup;
shutdown_func_t shutdown;
activate_func_t activate;
deactivate_func_t deactivate;
message_handler_func_t message_handler;
op_array_handler_func_t op_array_handler;
statement_handler_func_t statement_handler;
fcall_begin_handler_func_t fcall_begin_handler;
fcall_end_handler_func_t fcall_end_handler;
op_array_ctor_func_t op_array_ctor;
op_array_dtor_func_t op_array_dtor;
int (*api_no_check)(int api_no);
void *reserved2;
void *reserved3;
void *reserved4;
void *reserved5;
void *reserved6;
void *reserved7;
void *reserved8;
DL_HANDLE handle;
int resource_number;
};
Now let's compare it with the module entry of a PHP extension:
struct _zend_module_entry {
unsigned short size;
unsigned int zend_api;
unsigned char zend_debug;
unsigned char zts;
struct _zend_ini_entry *ini_entry;
struct _zend_module_dep *deps;
char *name;
struct _zend_function_entry *functions;
int (*module_startup_func)(INIT_FUNC_ARGS);
int (*module_shutdown_func)(SHUTDOWN_FUNC_ARGS);
int (*request_startup_func)(INIT_FUNC_ARGS);
int (*request_shutdown_func)(SHUTDOWN_FUNC_ARGS);
void (*info_func)(ZEND_MODULE_INFO_FUNC_ARGS);
char *version;
size_t globals_size;
#ifdef ZTS
ts_rsrc_id* globals_id_ptr;
#else
void* globals_ptr;
#endif
void (*globals_ctor)(void *global TSRMLS_DC);
void (*globals_dtor)(void *global TSRMLS_DC);
int (*post_deactivate_func)(void);
int module_started;
unsigned char type;
void *handle;
int module_number;
};
You can read the structures above together with my earlier article Extending PHP with C/C++ to make sense of them.
OK, back to the topic: since Xdebug has to be loaded as a Zend extension, it must have requirements that only a Zend extension can satisfy. What are they?
Well, we know Xdebug can profile PHP, and that is exactly what statement_handler is for:
the statement handler callback inserts an additional opcode at the end of every statement in a script in which the callback is called. One of the primary uses for this sort of callback is to implement per-line profiling, stepping debuggers, or code-coverage utilities.
And because Xdebug also provides functions for user scripts, it has a partial PHP extension implementation as well; and since it is loaded as a Zend extension, it has to handle the loading of its own PHP extension part by itself.
Finally, the PHP extension loading process is laid out below (I will keep adding annotations as I go). Of course, if you can't wait to find out, you are welcome to discuss it in the comments on my blog Wind and Snow Corner.
Taking apache/mod_php5.c as an example:
1. In mod_php5.c, the Apache module structure is defined:
module MODULE_VAR_EXPORT php5_module =
{
STANDARD_MODULE_STUFF,
php_init_handler, /* initializer */
php_create_dir, /* per-directory config creator */
php_merge_dir, /* dir merger */
NULL, /* per-server config creator */
NULL, /* merge server config */
php_commands, /* command table */
php_handlers, /* handlers */
NULL, /* filename translation */
NULL, /* check_user_id */
NULL, /* check auth */
NULL, /* check access */
NULL, /* type_checker */
NULL, /* fixups */
NULL /* logger */
#if MODULE_MAGIC_NUMBER >= 19970103
, NULL /* header parser */
#endif
#if MODULE_MAGIC_NUMBER >= 19970719
, NULL /* child_init */
#endif
#if MODULE_MAGIC_NUMBER >= 19970728
, php_child_exit_handler /* child_exit */
#endif
#if MODULE_MAGIC_NUMBER >= 19970902
, NULL /* post read-request */
#endif
};
/* }}} */
As you can see, the first thing to be called is php_init_handler,
static void php_init_handler(server_rec *s, pool *p)
{
register_cleanup(p, NULL, (void (*)(void *))apache_php_module_shutdown_wrapper, (void (*)(void *))php_module_shutdown_for_exec);
if (!apache_php_initialized) {
apache_php_initialized = 1;
#ifdef ZTS
tsrm_startup(1, 1, 0, NULL);
#endif
sapi_startup(&apache_sapi_module);
php_apache_startup(&apache_sapi_module);
}
#if MODULE_MAGIC_NUMBER >= 19980527
{
TSRMLS_FETCH();
if (PG(expose_php)) {
ap_add_version_component("PHP/" PHP_VERSION);
}
}
#endif
}
Here sapi_startup is called, which initializes PHP's apache SAPI.
Then php_apache_startup is called:
static int php_apache_startup(sapi_module_struct *sapi_module)
{
if (php_module_startup(sapi_module, &apache_module_entry, 1) == FAILURE) {
return FAILURE;
} else {
return SUCCESS;
}
}
At this point php_module_startup is called, and inside it there is:
/* this will read in php.ini, set up the configuration parameters,
load zend extensions and register php function extensions
to be loaded later */
if (php_init_config(TSRMLS_C) == FAILURE) {
return FAILURE;
}
php_init_config is called: it reads all the php.ini and scanned ini files, and then for every configuration directive it calls:
....
if (sapi_module.ini_entries) {
zend_parse_ini_string(sapi_module.ini_entries, 1, php_config_ini_parser_cb, &extension_lists);
}
Then, inside php_config_ini_parser_cb:
if (!strcasecmp(Z_STRVAL_P(arg1), "extension")) { /* load function module */
zval copy;
copy = *arg2;
zval_copy_ctor(©);
copy.refcount = 0;
zend_llist_add_element(&extension_lists.functions, ©);
} else if (!strcasecmp(Z_STRVAL_P(arg1), ZEND_EXTENSION_TOKEN)) { /* load Zend extension */
char *extension_name = estrndup(Z_STRVAL_P(arg2), Z_STRLEN_P(arg2));
zend_llist_add_element(&extension_lists.engine, &extension_name);
} else {
zend_hash_update(&configuration_hash, Z_STRVAL_P(arg1), Z_STRLEN_P(arg1) + 1, arg2, sizeof(zval), (void **) &entry);
Z_STRVAL_P(entry) = zend_strndup(Z_STRVAL_P(entry), Z_STRLEN_P(entry));
}
This records all the php extensions and zend extensions that are to be loaded.
Then let's go back to php_module_startup, where a bit later the following is called:
php_ini_register_extensions(TSRMLS_C);
void php_ini_register_extensions(TSRMLS_D)
{
zend_llist_apply(&extension_lists.engine, php_load_zend_extension_cb TSRMLS_CC);
zend_llist_apply(&extension_lists.functions, php_load_function_extension_cb TSRMLS_CC);
zend_llist_destroy(&extension_lists.engine);
zend_llist_destroy(&extension_lists.functions);
}
As we can see, for each recorded extension a callback is invoked. Here we will only look at php_load_function_extension_cb:
static void php_load_function_extension_cb(void *arg TSRMLS_DC)
{
zval *extension = (zval *) arg;
zval zval;
php_dl(extension, MODULE_PERSISTENT, &zval, 0 TSRMLS_CC);
}
And finally, the core loading logic:
void php_dl(zval *file, int type, zval *return_value, int start_now TSRMLS_DC)
{
void *handle;
char *libpath;
zend_module_entry *module_entry;
zend_module_entry *(*get_module)(void);
int error_type;
char *extension_dir;
if (type == MODULE_PERSISTENT) {
extension_dir = INI_STR("extension_dir");
} else {
extension_dir = PG(extension_dir);
}
if (type == MODULE_TEMPORARY) {
error_type = E_WARNING;
} else {
error_type = E_CORE_WARNING;
}
if (extension_dir && extension_dir[0]){
int extension_dir_len = strlen(extension_dir);
if (type == MODULE_TEMPORARY) {
if (strchr(Z_STRVAL_P(file), '/') != NULL || strchr(Z_STRVAL_P(file), DEFAULT_SLASH) != NULL) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Temporary module name should contain only filename");
RETURN_FALSE;
}
}
if (IS_SLASH(extension_dir[extension_dir_len-1])) {
spprintf(&libpath, 0, "%s%s", extension_dir, Z_STRVAL_P(file));
} else {
spprintf(&libpath, 0, "%s%c%s", extension_dir, DEFAULT_SLASH, Z_STRVAL_P(file));
}
} else {
libpath = estrndup(Z_STRVAL_P(file), Z_STRLEN_P(file));
}
/* load dynamic symbol */
handle = DL_LOAD(libpath);
if (!handle) {
php_error_docref(NULL TSRMLS_CC, error_type, "Unable to load dynamic library '%s' - %s", libpath, GET_DL_ERROR());
GET_DL_ERROR(); /* free the buffer storing the error */
efree(libpath);
RETURN_FALSE;
}
efree(libpath);
get_module = (zend_module_entry *(*)(void)) DL_FETCH_SYMBOL(handle, "get_module");
/*
* some OS prepend _ to symbol names while their dynamic linker
* does not do that automatically. Thus we check manually for
* _get_module.
*/
if (!get_module)
get_module = (zend_module_entry *(*)(void)) DL_FETCH_SYMBOL(handle, "_get_module");
if (!get_module) {
DL_UNLOAD(handle);
php_error_docref(NULL TSRMLS_CC, error_type, "Invalid library (maybe not a PHP library) '%s' ", Z_STRVAL_P(file));
RETURN_FALSE;
}
module_entry = get_module();
if ((module_entry->zend_debug != ZEND_DEBUG) || (module_entry->zts != USING_ZTS)
|| (module_entry->zend_api != ZEND_MODULE_API_NO)) {
/* Check for pre-4.1.0 module which has a slightly different module_entry structure :( */
struct pre_4_1_0_module_entry {
char *name;
zend_function_entry *functions;
int (*module_startup_func)(INIT_FUNC_ARGS);
int (*module_shutdown_func)(SHUTDOWN_FUNC_ARGS);
int (*request_startup_func)(INIT_FUNC_ARGS);
int (*request_shutdown_func)(SHUTDOWN_FUNC_ARGS);
void (*info_func)(ZEND_MODULE_INFO_FUNC_ARGS);
int (*global_startup_func)(void);
int (*global_shutdown_func)(void);
int globals_id;
int module_started;
unsigned char type;
void *handle;
int module_number;
unsigned char zend_debug;
unsigned char zts;
unsigned int zend_api;
};
char *name;
int zend_api;
unsigned char zend_debug, zts;
if ((((struct pre_4_1_0_module_entry *)module_entry)->zend_api > 20000000) &&
(((struct pre_4_1_0_module_entry *)module_entry)->zend_api < 20010901)
) {
name = ((struct pre_4_1_0_module_entry *)module_entry)->name;
zend_api = ((struct pre_4_1_0_module_entry *)module_entry)->zend_api;
zend_debug = ((struct pre_4_1_0_module_entry *)module_entry)->zend_debug;
zts = ((struct pre_4_1_0_module_entry *)module_entry)->zts;
} else {
name = module_entry->name;
zend_api = module_entry->zend_api;
zend_debug = module_entry->zend_debug;
zts = module_entry->zts;
}
php_error_docref(NULL TSRMLS_CC, error_type,
"%s: Unable to initialize modulen"
"Module compiled with module API=%d, debug=%d, thread-safety=%dn"
"PHP compiled with module API=%d, debug=%d, thread-safety=%dn"
"These options need to matchn",
name, zend_api, zend_debug, zts,
ZEND_MODULE_API_NO, ZEND_DEBUG, USING_ZTS);
DL_UNLOAD(handle);
RETURN_FALSE;
}
module_entry->type = type;
module_entry->module_number = zend_next_free_module();
module_entry->handle = handle;
if ((module_entry = zend_register_module_ex(module_entry TSRMLS_CC)) == NULL) {
DL_UNLOAD(handle);
RETURN_FALSE;
}
if ((type == MODULE_TEMPORARY || start_now) && zend_startup_module_ex(module_entry TSRMLS_CC) == FAILURE) {
DL_UNLOAD(handle);
RETURN_FALSE;
}
if ((type == MODULE_TEMPORARY || start_now) && module_entry->request_startup_func) {
if (module_entry->request_startup_func(type, module_entry->module_number TSRMLS_CC) == FAILURE) {
php_error_docref(NULL TSRMLS_CC, error_type, "Unable to initialize module '%s'", module_entry->name);
DL_UNLOAD(handle);
RETURN_FALSE;
}
}
RETURN_TRUE;
}
Be First to Comment