/src/php-src/Zend/zend_extensions.c
Line | Count | Source |
1 | | /* |
2 | | +----------------------------------------------------------------------+ |
3 | | | Zend Engine | |
4 | | +----------------------------------------------------------------------+ |
5 | | | Copyright © Zend Technologies Ltd., a subsidiary company of | |
6 | | | Perforce Software, Inc., and Contributors. | |
7 | | +----------------------------------------------------------------------+ |
8 | | | This source file is subject to the Modified BSD License that is | |
9 | | | bundled with this package in the file LICENSE, and is available | |
10 | | | through the World Wide Web at <https://www.php.net/license/>. | |
11 | | | | |
12 | | | SPDX-License-Identifier: BSD-3-Clause | |
13 | | +----------------------------------------------------------------------+ |
14 | | | Authors: Andi Gutmans <andi@php.net> | |
15 | | | Zeev Suraski <zeev@php.net> | |
16 | | +----------------------------------------------------------------------+ |
17 | | */ |
18 | | |
19 | | #include "zend_extensions.h" |
20 | | #include "zend_system_id.h" |
21 | | |
22 | | ZEND_API zend_llist zend_extensions; |
23 | | ZEND_API uint32_t zend_extension_flags = 0; |
24 | | ZEND_API int zend_op_array_extension_handles = 0; |
25 | | ZEND_API int zend_internal_function_extension_handles = 0; |
26 | | static int last_resource_number; |
27 | | |
28 | | zend_result zend_load_extension(const char *path) |
29 | 0 | { |
30 | 0 | #if ZEND_EXTENSIONS_SUPPORT |
31 | 0 | DL_HANDLE handle; |
32 | |
|
33 | 0 | handle = DL_LOAD(path); |
34 | 0 | if (!handle) { |
35 | 0 | #ifndef ZEND_WIN32 |
36 | 0 | fprintf(stderr, "Failed loading %s: %s\n", path, DL_ERROR()); |
37 | | #else |
38 | | fprintf(stderr, "Failed loading %s\n", path); |
39 | | /* See http://support.microsoft.com/kb/190351 */ |
40 | | fflush(stderr); |
41 | | #endif |
42 | 0 | return FAILURE; |
43 | 0 | } |
44 | | #ifdef ZEND_WIN32 |
45 | | char *err; |
46 | | if (!php_win32_image_compatible(handle, &err)) { |
47 | | zend_error(E_CORE_WARNING, "%s", err); |
48 | | return FAILURE; |
49 | | } |
50 | | #endif |
51 | 0 | return zend_load_extension_handle(handle, path); |
52 | | #else |
53 | | fprintf(stderr, "Extensions are not supported on this platform.\n"); |
54 | | /* See http://support.microsoft.com/kb/190351 */ |
55 | | #ifdef ZEND_WIN32 |
56 | | fflush(stderr); |
57 | | #endif |
58 | | return FAILURE; |
59 | | #endif |
60 | 0 | } |
61 | | |
62 | | zend_result zend_load_extension_handle(DL_HANDLE handle, const char *path) |
63 | 0 | { |
64 | 0 | #if ZEND_EXTENSIONS_SUPPORT |
65 | 0 | zend_extension *new_extension; |
66 | |
|
67 | 0 | const zend_extension_version_info *extension_version_info = (const zend_extension_version_info *) DL_FETCH_SYMBOL(handle, "extension_version_info"); |
68 | 0 | if (!extension_version_info) { |
69 | 0 | extension_version_info = (const zend_extension_version_info *) DL_FETCH_SYMBOL(handle, "_extension_version_info"); |
70 | 0 | } |
71 | 0 | new_extension = (zend_extension *) DL_FETCH_SYMBOL(handle, "zend_extension_entry"); |
72 | 0 | if (!new_extension) { |
73 | 0 | new_extension = (zend_extension *) DL_FETCH_SYMBOL(handle, "_zend_extension_entry"); |
74 | 0 | } |
75 | 0 | if (!extension_version_info || !new_extension) { |
76 | 0 | fprintf(stderr, "%s doesn't appear to be a valid Zend extension\n", path); |
77 | | /* See http://support.microsoft.com/kb/190351 */ |
78 | | #ifdef ZEND_WIN32 |
79 | | fflush(stderr); |
80 | | #endif |
81 | 0 | DL_UNLOAD(handle); |
82 | 0 | return FAILURE; |
83 | 0 | } |
84 | | |
85 | | /* allow extension to proclaim compatibility with any Zend version */ |
86 | 0 | if (extension_version_info->zend_extension_api_no != ZEND_EXTENSION_API_NO &&(!new_extension->api_no_check || new_extension->api_no_check(ZEND_EXTENSION_API_NO) != SUCCESS)) { |
87 | 0 | if (extension_version_info->zend_extension_api_no > ZEND_EXTENSION_API_NO) { |
88 | 0 | fprintf(stderr, "%s requires Zend Engine API version %d.\n" |
89 | 0 | "The Zend Engine API version %d which is installed, is outdated.\n\n", |
90 | 0 | new_extension->name, |
91 | 0 | extension_version_info->zend_extension_api_no, |
92 | 0 | ZEND_EXTENSION_API_NO); |
93 | | /* See http://support.microsoft.com/kb/190351 */ |
94 | | #ifdef ZEND_WIN32 |
95 | | fflush(stderr); |
96 | | #endif |
97 | 0 | DL_UNLOAD(handle); |
98 | 0 | return FAILURE; |
99 | 0 | } else if (extension_version_info->zend_extension_api_no < ZEND_EXTENSION_API_NO) { |
100 | 0 | fprintf(stderr, "%s requires Zend Engine API version %d.\n" |
101 | 0 | "The Zend Engine API version %d which is installed, is newer.\n" |
102 | 0 | "Contact %s at %s for a later version of %s.\n\n", |
103 | 0 | new_extension->name, |
104 | 0 | extension_version_info->zend_extension_api_no, |
105 | 0 | ZEND_EXTENSION_API_NO, |
106 | 0 | new_extension->author, |
107 | 0 | new_extension->URL, |
108 | 0 | new_extension->name); |
109 | | /* See http://support.microsoft.com/kb/190351 */ |
110 | | #ifdef ZEND_WIN32 |
111 | | fflush(stderr); |
112 | | #endif |
113 | 0 | DL_UNLOAD(handle); |
114 | 0 | return FAILURE; |
115 | 0 | } |
116 | 0 | } else if (strcmp(ZEND_EXTENSION_BUILD_ID, extension_version_info->build_id) && |
117 | 0 | (!new_extension->build_id_check || new_extension->build_id_check(ZEND_EXTENSION_BUILD_ID) != SUCCESS)) { |
118 | 0 | fprintf(stderr, "Cannot load %s - it was built with configuration %s, whereas running engine is %s\n", |
119 | 0 | new_extension->name, extension_version_info->build_id, ZEND_EXTENSION_BUILD_ID); |
120 | | /* See http://support.microsoft.com/kb/190351 */ |
121 | | #ifdef ZEND_WIN32 |
122 | | fflush(stderr); |
123 | | #endif |
124 | 0 | DL_UNLOAD(handle); |
125 | 0 | return FAILURE; |
126 | 0 | } else if (zend_get_extension(new_extension->name)) { |
127 | 0 | fprintf(stderr, "Cannot load %s - it was already loaded\n", new_extension->name); |
128 | | /* See http://support.microsoft.com/kb/190351 */ |
129 | | #ifdef ZEND_WIN32 |
130 | | fflush(stderr); |
131 | | #endif |
132 | 0 | DL_UNLOAD(handle); |
133 | 0 | return FAILURE; |
134 | 0 | } |
135 | | |
136 | 0 | zend_register_extension(new_extension, handle); |
137 | 0 | return SUCCESS; |
138 | | #else |
139 | | fprintf(stderr, "Extensions are not supported on this platform.\n"); |
140 | | /* See http://support.microsoft.com/kb/190351 */ |
141 | | #ifdef ZEND_WIN32 |
142 | | fflush(stderr); |
143 | | #endif |
144 | | return FAILURE; |
145 | | #endif |
146 | 0 | } |
147 | | |
148 | | |
149 | | void zend_register_extension(zend_extension *new_extension, DL_HANDLE handle) |
150 | 2 | { |
151 | 2 | #if ZEND_EXTENSIONS_SUPPORT |
152 | 2 | zend_extension extension; |
153 | | |
154 | 2 | extension = *new_extension; |
155 | 2 | extension.handle = handle; |
156 | | |
157 | 2 | zend_extension_dispatch_message(ZEND_EXTMSG_NEW_EXTENSION, &extension); |
158 | | |
159 | 2 | zend_llist_add_element(&zend_extensions, &extension); |
160 | | |
161 | 2 | if (extension.op_array_ctor) { |
162 | 0 | zend_extension_flags |= ZEND_EXTENSIONS_HAVE_OP_ARRAY_CTOR; |
163 | 0 | } |
164 | 2 | if (extension.op_array_dtor) { |
165 | 0 | zend_extension_flags |= ZEND_EXTENSIONS_HAVE_OP_ARRAY_DTOR; |
166 | 0 | } |
167 | 2 | if (extension.op_array_handler) { |
168 | 0 | zend_extension_flags |= ZEND_EXTENSIONS_HAVE_OP_ARRAY_HANDLER; |
169 | 0 | } |
170 | 2 | if (extension.op_array_persist_calc) { |
171 | 0 | zend_extension_flags |= ZEND_EXTENSIONS_HAVE_OP_ARRAY_PERSIST_CALC; |
172 | 0 | } |
173 | 2 | if (extension.op_array_persist) { |
174 | 0 | zend_extension_flags |= ZEND_EXTENSIONS_HAVE_OP_ARRAY_PERSIST; |
175 | 0 | } |
176 | | /*fprintf(stderr, "Loaded %s, version %s\n", extension.name, extension.version);*/ |
177 | 2 | #endif |
178 | 2 | } |
179 | | |
180 | | |
181 | | static void zend_extension_shutdown(zend_extension *extension) |
182 | 0 | { |
183 | 0 | #if ZEND_EXTENSIONS_SUPPORT |
184 | 0 | if (extension->shutdown) { |
185 | 0 | extension->shutdown(extension); |
186 | 0 | } |
187 | 0 | #endif |
188 | 0 | } |
189 | | |
190 | | /* int return due to zend linked list API */ |
191 | | static int zend_extension_startup(zend_extension *extension) |
192 | 2 | { |
193 | 2 | #if ZEND_EXTENSIONS_SUPPORT |
194 | 2 | if (extension->startup) { |
195 | 2 | if (extension->startup(extension)!=SUCCESS) { |
196 | 0 | return 1; |
197 | 0 | } |
198 | 2 | zend_append_version_info(extension); |
199 | 2 | } |
200 | 2 | #endif |
201 | 2 | return 0; |
202 | 2 | } |
203 | | |
204 | | |
205 | | void zend_startup_extensions_mechanism(void) |
206 | 2 | { |
207 | | /* Startup extensions mechanism */ |
208 | 2 | zend_llist_init(&zend_extensions, sizeof(zend_extension), (void (*)(void *)) zend_extension_dtor, 1); |
209 | 2 | zend_op_array_extension_handles = 0; |
210 | 2 | zend_internal_function_extension_handles = 0; |
211 | 2 | last_resource_number = 0; |
212 | 2 | } |
213 | | |
214 | | |
215 | | void zend_startup_extensions(void) |
216 | 2 | { |
217 | 2 | zend_llist_apply_with_del(&zend_extensions, (int (*)(void *)) zend_extension_startup); |
218 | 2 | } |
219 | | |
220 | | |
221 | | void zend_shutdown_extensions(void) |
222 | 0 | { |
223 | 0 | zend_llist_apply(&zend_extensions, (llist_apply_func_t) zend_extension_shutdown); |
224 | 0 | zend_llist_destroy(&zend_extensions); |
225 | 0 | } |
226 | | |
227 | | |
228 | | void zend_extension_dtor(zend_extension *extension) |
229 | 0 | { |
230 | | #if ZEND_EXTENSIONS_SUPPORT && !ZEND_DEBUG |
231 | | if (extension->handle && !getenv("ZEND_DONT_UNLOAD_MODULES")) { |
232 | | DL_UNLOAD(extension->handle); |
233 | | } |
234 | | #endif |
235 | 0 | } |
236 | | |
237 | | |
238 | | static void zend_extension_message_dispatcher(const zend_extension *extension, int num_args, va_list args) |
239 | 0 | { |
240 | 0 | int message; |
241 | 0 | void *arg; |
242 | |
|
243 | 0 | if (!extension->message_handler || num_args!=2) { |
244 | 0 | return; |
245 | 0 | } |
246 | 0 | message = va_arg(args, int); |
247 | 0 | arg = va_arg(args, void *); |
248 | 0 | extension->message_handler(message, arg); |
249 | 0 | } |
250 | | |
251 | | |
252 | | ZEND_API void zend_extension_dispatch_message(int message, void *arg) |
253 | 2 | { |
254 | 2 | zend_llist_apply_with_arguments(&zend_extensions, (llist_apply_with_args_func_t) zend_extension_message_dispatcher, 2, message, arg); |
255 | 2 | } |
256 | | |
257 | | |
258 | | ZEND_API int zend_get_resource_handle(const char *module_name) |
259 | 2 | { |
260 | 2 | if (last_resource_number<ZEND_MAX_RESERVED_RESOURCES) { |
261 | 2 | zend_add_system_entropy(module_name, "zend_get_resource_handle", &last_resource_number, sizeof(int)); |
262 | 2 | return last_resource_number++; |
263 | 2 | } else { |
264 | 0 | return -1; |
265 | 0 | } |
266 | 2 | } |
267 | | |
268 | | /** |
269 | | * The handle returned by this function can be used with |
270 | | * `ZEND_OP_ARRAY_EXTENSION(op_array, handle)`. |
271 | | * |
272 | | * The extension slot has been available since PHP 7.4 on user functions and |
273 | | * has been available since PHP 8.2 on internal functions. |
274 | | * |
275 | | * # Safety |
276 | | * The extension slot made available by calling this function is initialized on |
277 | | * the first call made to the function in that request. If you need to |
278 | | * initialize it before this point, call `zend_init_func_run_time_cache`. |
279 | | * |
280 | | * The function cache slots are not available if the function is a trampoline, |
281 | | * which can be checked with something like: |
282 | | * |
283 | | * if (fbc->type == ZEND_USER_FUNCTION |
284 | | * && !(fbc->op_array.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE) |
285 | | * ) { |
286 | | * // Use ZEND_OP_ARRAY_EXTENSION somehow |
287 | | * } |
288 | | */ |
289 | | ZEND_API int zend_get_op_array_extension_handle(const char *module_name) |
290 | 0 | { |
291 | 0 | int handle = zend_op_array_extension_handles++; |
292 | 0 | zend_add_system_entropy(module_name, "zend_get_op_array_extension_handle", &zend_op_array_extension_handles, sizeof(int)); |
293 | 0 | return handle; |
294 | 0 | } |
295 | | |
296 | | /** See zend_get_op_array_extension_handle for important usage information. */ |
297 | | ZEND_API int zend_get_op_array_extension_handles(const char *module_name, int handles) |
298 | 0 | { |
299 | 0 | int handle = zend_op_array_extension_handles; |
300 | 0 | zend_op_array_extension_handles += handles; |
301 | 0 | zend_add_system_entropy(module_name, "zend_get_op_array_extension_handle", &zend_op_array_extension_handles, sizeof(int)); |
302 | 0 | return handle; |
303 | 0 | } |
304 | | |
305 | | ZEND_API int zend_get_internal_function_extension_handle(const char *module_name) |
306 | 0 | { |
307 | 0 | int handle = zend_internal_function_extension_handles++; |
308 | 0 | zend_add_system_entropy(module_name, "zend_get_internal_function_extension_handle", &zend_internal_function_extension_handles, sizeof(int)); |
309 | 0 | return handle; |
310 | 0 | } |
311 | | |
312 | | ZEND_API int zend_get_internal_function_extension_handles(const char *module_name, int handles) |
313 | 0 | { |
314 | 0 | int handle = zend_internal_function_extension_handles; |
315 | 0 | zend_internal_function_extension_handles += handles; |
316 | 0 | zend_add_system_entropy(module_name, "zend_get_internal_function_extension_handle", &zend_internal_function_extension_handles, sizeof(int)); |
317 | 0 | return handle; |
318 | 0 | } |
319 | | |
320 | 2 | ZEND_API size_t zend_internal_run_time_cache_reserved_size(void) { |
321 | 2 | return zend_internal_function_extension_handles * sizeof(void *); |
322 | 2 | } |
323 | | |
324 | 2 | ZEND_API void zend_init_internal_run_time_cache(void) { |
325 | 2 | size_t rt_size = zend_internal_run_time_cache_reserved_size(); |
326 | 2 | if (rt_size) { |
327 | 0 | size_t functions = zend_hash_num_elements(CG(function_table)); |
328 | 0 | zend_class_entry *ce; |
329 | 0 | ZEND_HASH_MAP_FOREACH_PTR(CG(class_table), ce) { |
330 | 0 | functions += zend_hash_num_elements(&ce->function_table); |
331 | 0 | } ZEND_HASH_FOREACH_END(); |
332 | |
|
333 | 0 | size_t alloc_size = functions * rt_size; |
334 | 0 | char *ptr = pemalloc(alloc_size, 1); |
335 | |
|
336 | 0 | CG(internal_run_time_cache) = ptr; |
337 | 0 | CG(internal_run_time_cache_size) = alloc_size; |
338 | |
|
339 | 0 | zend_internal_function *zif; |
340 | 0 | ZEND_HASH_MAP_FOREACH_PTR(CG(function_table), zif) { |
341 | 0 | if (!ZEND_USER_CODE(zif->type) && ZEND_MAP_PTR_GET(zif->run_time_cache) == NULL) { |
342 | 0 | ZEND_MAP_PTR_SET(zif->run_time_cache, (void *)ptr); |
343 | 0 | ptr += rt_size; |
344 | 0 | } |
345 | 0 | } ZEND_HASH_FOREACH_END(); |
346 | 0 | ZEND_HASH_MAP_FOREACH_PTR(CG(class_table), ce) { |
347 | 0 | ZEND_HASH_MAP_FOREACH_PTR(&ce->function_table, zif) { |
348 | 0 | if (!ZEND_USER_CODE(zif->type) && ZEND_MAP_PTR_GET(zif->run_time_cache) == NULL) { |
349 | 0 | ZEND_MAP_PTR_SET(zif->run_time_cache, (void *)ptr); |
350 | 0 | ptr += rt_size; |
351 | 0 | } |
352 | 0 | } ZEND_HASH_FOREACH_END(); |
353 | 0 | } ZEND_HASH_FOREACH_END(); |
354 | 0 | } |
355 | 2 | } |
356 | | |
357 | 38.8k | ZEND_API void zend_reset_internal_run_time_cache(void) { |
358 | 38.8k | if (CG(internal_run_time_cache)) { |
359 | 0 | memset(CG(internal_run_time_cache), 0, CG(internal_run_time_cache_size)); |
360 | 0 | } |
361 | 38.8k | } |
362 | | |
363 | | ZEND_API zend_extension *zend_get_extension(const char *extension_name) |
364 | 0 | { |
365 | 0 | zend_llist_element *element; |
366 | |
|
367 | 0 | for (element = zend_extensions.head; element; element = element->next) { |
368 | 0 | zend_extension *extension = (zend_extension *) element->data; |
369 | |
|
370 | 0 | if (!strcmp(extension->name, extension_name)) { |
371 | 0 | return extension; |
372 | 0 | } |
373 | 0 | } |
374 | 0 | return NULL; |
375 | 0 | } |
376 | | |
377 | | typedef struct _zend_extension_persist_data { |
378 | | zend_op_array *op_array; |
379 | | size_t size; |
380 | | char *mem; |
381 | | } zend_extension_persist_data; |
382 | | |
383 | | static void zend_extension_op_array_persist_calc_handler(zend_extension *extension, zend_extension_persist_data *data) |
384 | 0 | { |
385 | 0 | if (extension->op_array_persist_calc) { |
386 | 0 | data->size += extension->op_array_persist_calc(data->op_array); |
387 | 0 | } |
388 | 0 | } |
389 | | |
390 | | static void zend_extension_op_array_persist_handler(zend_extension *extension, zend_extension_persist_data *data) |
391 | 0 | { |
392 | 0 | if (extension->op_array_persist) { |
393 | 0 | size_t size = extension->op_array_persist(data->op_array, data->mem); |
394 | 0 | if (size) { |
395 | 0 | data->mem = (void*)((char*)data->mem + size); |
396 | 0 | data->size += size; |
397 | 0 | } |
398 | 0 | } |
399 | 0 | } |
400 | | |
401 | | ZEND_API size_t zend_extensions_op_array_persist_calc(zend_op_array *op_array) |
402 | 0 | { |
403 | 0 | if (zend_extension_flags & ZEND_EXTENSIONS_HAVE_OP_ARRAY_PERSIST_CALC) { |
404 | 0 | zend_extension_persist_data data; |
405 | |
|
406 | 0 | data.op_array = op_array; |
407 | 0 | data.size = 0; |
408 | 0 | data.mem = NULL; |
409 | 0 | zend_llist_apply_with_argument(&zend_extensions, (llist_apply_with_arg_func_t) zend_extension_op_array_persist_calc_handler, &data); |
410 | 0 | return data.size; |
411 | 0 | } |
412 | 0 | return 0; |
413 | 0 | } |
414 | | |
415 | | ZEND_API size_t zend_extensions_op_array_persist(zend_op_array *op_array, void *mem) |
416 | 0 | { |
417 | 0 | if (zend_extension_flags & ZEND_EXTENSIONS_HAVE_OP_ARRAY_PERSIST) { |
418 | 0 | zend_extension_persist_data data; |
419 | |
|
420 | 0 | data.op_array = op_array; |
421 | 0 | data.size = 0; |
422 | 0 | data.mem = mem; |
423 | 0 | zend_llist_apply_with_argument(&zend_extensions, (llist_apply_with_arg_func_t) zend_extension_op_array_persist_handler, &data); |
424 | 0 | return data.size; |
425 | 0 | } |
426 | 0 | return 0; |
427 | 0 | } |