Skip to content

Commit 21219b6

Browse files
morrisonlevinikicSammyK
committed
Add zend_instrument API
The instrument API can be used to add handlers that get called before and after a function call. An interested extension can add a call to `zend_instrument_register(...)` during MINIT; zend extensions can do the same during STARTUP. When a function is called for the first time, the registered instruments will be queried to provide the begin/end handlers they wish to install. If they do not want to instrument the function then they may return NULL for both handlers. Co-authored-by: Nikita Popov <nikita.ppv@gmail.com> Co-authored-by: Sammy Powers <sammyk@php.net>
1 parent 8300458 commit 21219b6

18 files changed

+661
-34
lines changed

Zend/zend_API.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2053,6 +2053,7 @@ ZEND_API int zend_register_functions(zend_class_entry *scope, const zend_functio
20532053
}
20542054
internal_function->type = ZEND_INTERNAL_FUNCTION;
20552055
internal_function->module = EG(current_module);
2056+
ZEND_MAP_PTR_NEW(internal_function->instrument_cache);
20562057
memset(internal_function->reserved, 0, ZEND_MAX_RESERVED_RESOURCES * sizeof(void*));
20572058

20582059
if (scope) {

Zend/zend_closures.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,10 @@ static int zend_create_closure_from_callable(zval *return_value, zval *callable,
309309
call.function_name = mptr->common.function_name;
310310
call.scope = mptr->common.scope;
311311

312+
// TODO: Is this correct???
313+
static const zend_instrument_cache *dummy_handlers = ZEND_NOT_INSTRUMENTED;
314+
ZEND_MAP_PTR_INIT(call.instrument_cache, (zend_instrument_cache **) &dummy_handlers);
315+
312316
zend_free_trampoline(mptr);
313317
mptr = (zend_function *) &call;
314318
}
@@ -395,6 +399,10 @@ ZEND_API zend_function *zend_get_closure_invoke_method(zend_object *object) /* {
395399
invoke->internal_function.module = 0;
396400
invoke->internal_function.scope = zend_ce_closure;
397401
invoke->internal_function.function_name = ZSTR_KNOWN(ZEND_STR_MAGIC_INVOKE);
402+
403+
// TODO: Is this correct???
404+
static const zend_instrument_cache *dummy_handler = ZEND_NOT_INSTRUMENTED;
405+
ZEND_MAP_PTR_INIT(invoke->internal_function.instrument_cache, (zend_instrument_cache **) &dummy_handler);
398406
return invoke;
399407
}
400408
/* }}} */

Zend/zend_compile.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6257,9 +6257,14 @@ void zend_compile_func_decl(znode *result, zend_ast *ast, zend_bool toplevel) /*
62576257
op_array->fn_flags |= ZEND_ACC_PRELOADED;
62586258
ZEND_MAP_PTR_NEW(op_array->run_time_cache);
62596259
ZEND_MAP_PTR_NEW(op_array->static_variables_ptr);
6260+
ZEND_MAP_PTR_NEW(op_array->instrument_cache);
62606261
} else {
62616262
ZEND_MAP_PTR_INIT(op_array->run_time_cache, zend_arena_alloc(&CG(arena), sizeof(void*)));
62626263
ZEND_MAP_PTR_SET(op_array->run_time_cache, NULL);
6264+
6265+
ZEND_MAP_PTR_INIT(op_array->instrument_cache,
6266+
zend_arena_alloc(&CG(arena), sizeof(void*)));
6267+
ZEND_MAP_PTR_SET(op_array->instrument_cache, NULL);
62636268
}
62646269

62656270
op_array->fn_flags |= (orig_op_array->fn_flags & ZEND_ACC_STRICT_TYPES);

Zend/zend_compile.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,7 @@ struct _zend_op_array {
406406
uint32_t num_args;
407407
uint32_t required_num_args;
408408
zend_arg_info *arg_info;
409+
ZEND_MAP_PTR_DEF(struct zend_instrument_cache *, instrument_cache);
409410
/* END of common elements */
410411

411412
int cache_size; /* number of run_time_cache_slots * sizeof(void*) */
@@ -455,6 +456,7 @@ typedef struct _zend_internal_function {
455456
uint32_t num_args;
456457
uint32_t required_num_args;
457458
zend_internal_arg_info *arg_info;
459+
ZEND_MAP_PTR_DEF(struct zend_instrument_cache *, instrument_cache);
458460
/* END of common elements */
459461

460462
zif_handler handler;
@@ -478,6 +480,7 @@ union _zend_function {
478480
uint32_t num_args;
479481
uint32_t required_num_args;
480482
zend_arg_info *arg_info; /* index -1 represents the return value info, if any */
483+
ZEND_MAP_PTR_DEF(struct zend_instrument_cache *, instrument_cache);
481484
} common;
482485

483486
zend_op_array op_array;

Zend/zend_execute.c

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343

4444
/* Virtual current working directory support */
4545
#include "zend_virtual_cwd.h"
46+
#include "zend_instrument.h"
4647

4748
#ifdef HAVE_GCC_GLOBAL_REGS
4849
# if defined(__GNUC__) && ZEND_GCC_VERSION >= 4008 && defined(i386)
@@ -137,6 +138,7 @@ ZEND_API const zend_internal_function zend_pass_function = {
137138
0, /* num_args */
138139
0, /* required_num_args */
139140
NULL, /* arg_info */
141+
NULL,
140142
ZEND_FN(pass), /* handler */
141143
NULL, /* module */
142144
{NULL,NULL,NULL,NULL} /* reserved */
@@ -3302,10 +3304,7 @@ static int zend_check_symbol(zval *pz)
33023304
#define CHECK_SYMBOL_TABLES()
33033305
#endif
33043306

3305-
ZEND_API void execute_internal(zend_execute_data *execute_data, zval *return_value)
3306-
{
3307-
execute_data->func->internal_function.handler(execute_data, return_value);
3308-
}
3307+
ZEND_API extern inline void execute_internal(zend_execute_data *execute_data, zval *return_value);
33093308

33103309
ZEND_API void zend_clean_and_cache_symbol_table(zend_array *symbol_table) /* {{{ */
33113310
{
@@ -3503,6 +3502,9 @@ ZEND_API zend_function * ZEND_FASTCALL zend_fetch_function(zend_string *name) /*
35033502
if (EXPECTED(fbc->type == ZEND_USER_FUNCTION) && UNEXPECTED(!RUN_TIME_CACHE(&fbc->op_array))) {
35043503
init_func_run_time_cache_i(&fbc->op_array);
35053504
}
3505+
if (UNEXPECTED(!ZEND_MAP_PTR_GET(fbc->common.instrument_cache))) {
3506+
zend_instrument_install_handlers(fbc);
3507+
}
35063508
return fbc;
35073509
}
35083510
return NULL;
@@ -3518,6 +3520,9 @@ ZEND_API zend_function * ZEND_FASTCALL zend_fetch_function_str(const char *name,
35183520
if (EXPECTED(fbc->type == ZEND_USER_FUNCTION) && UNEXPECTED(!RUN_TIME_CACHE(&fbc->op_array))) {
35193521
init_func_run_time_cache_i(&fbc->op_array);
35203522
}
3523+
if (UNEXPECTED(!ZEND_MAP_PTR_GET(fbc->common.instrument_cache))) {
3524+
zend_instrument_install_handlers(fbc);
3525+
}
35213526
return fbc;
35223527
}
35233528
return NULL;
@@ -3550,6 +3555,12 @@ static zend_always_inline void i_init_code_execute_data(zend_execute_data *execu
35503555
ZEND_MAP_PTR_SET(op_array->run_time_cache, ptr);
35513556
memset(ptr, 0, op_array->cache_size);
35523557
}
3558+
if (!ZEND_MAP_PTR(op_array->instrument_cache)) {
3559+
ZEND_MAP_PTR_INIT(op_array->instrument_cache,
3560+
zend_arena_alloc(&CG(arena), sizeof(void*)));
3561+
ZEND_MAP_PTR_SET(op_array->instrument_cache, NULL);
3562+
zend_instrument_install_handlers((zend_function *) op_array);
3563+
}
35533564
EX(run_time_cache) = RUN_TIME_CACHE(op_array);
35543565

35553566
EG(current_execute_data) = execute_data;
@@ -3574,6 +3585,9 @@ ZEND_API void zend_init_func_execute_data(zend_execute_data *ex, zend_op_array *
35743585
if (!RUN_TIME_CACHE(op_array)) {
35753586
init_func_run_time_cache(op_array);
35763587
}
3588+
if (UNEXPECTED(!ZEND_MAP_PTR_GET(op_array->instrument_cache))) {
3589+
zend_instrument_install_handlers((zend_function *) op_array);
3590+
}
35773591
i_init_func_execute_data(op_array, return_value, 1 EXECUTE_DATA_CC);
35783592

35793593
#if defined(ZEND_VM_IP_GLOBAL_REG) && ((ZEND_VM_KIND == ZEND_VM_KIND_CALL) || (ZEND_VM_KIND == ZEND_VM_KIND_HYBRID))
@@ -3925,6 +3939,9 @@ static zend_never_inline zend_execute_data *zend_init_dynamic_call_string(zend_s
39253939
if (EXPECTED(fbc->type == ZEND_USER_FUNCTION) && UNEXPECTED(!RUN_TIME_CACHE(&fbc->op_array))) {
39263940
init_func_run_time_cache(&fbc->op_array);
39273941
}
3942+
if (UNEXPECTED(!ZEND_MAP_PTR_GET(fbc->common.instrument_cache))) {
3943+
zend_instrument_install_handlers(fbc);
3944+
}
39283945
} else {
39293946
if (ZSTR_VAL(function)[0] == '\\') {
39303947
lcname = zend_string_alloc(ZSTR_LEN(function) - 1, 0);
@@ -3943,6 +3960,9 @@ static zend_never_inline zend_execute_data *zend_init_dynamic_call_string(zend_s
39433960
if (EXPECTED(fbc->type == ZEND_USER_FUNCTION) && UNEXPECTED(!RUN_TIME_CACHE(&fbc->op_array))) {
39443961
init_func_run_time_cache(&fbc->op_array);
39453962
}
3963+
if (UNEXPECTED(!ZEND_MAP_PTR_GET(fbc->common.instrument_cache))) {
3964+
zend_instrument_install_handlers(fbc);
3965+
}
39463966
called_scope = NULL;
39473967
}
39483968

@@ -3987,6 +4007,9 @@ static zend_never_inline zend_execute_data *zend_init_dynamic_call_object(zend_o
39874007
if (EXPECTED(fbc->type == ZEND_USER_FUNCTION) && UNEXPECTED(!RUN_TIME_CACHE(&fbc->op_array))) {
39884008
init_func_run_time_cache(&fbc->op_array);
39894009
}
4010+
if (UNEXPECTED(!ZEND_MAP_PTR_GET(fbc->common.instrument_cache))) {
4011+
zend_instrument_install_handlers(fbc);
4012+
}
39904013

39914014
return zend_vm_stack_push_call_frame(call_info,
39924015
fbc, num_args, object_or_called_scope);
@@ -4072,6 +4095,9 @@ static zend_never_inline zend_execute_data *zend_init_dynamic_call_array(zend_ar
40724095
if (EXPECTED(fbc->type == ZEND_USER_FUNCTION) && UNEXPECTED(!RUN_TIME_CACHE(&fbc->op_array))) {
40734096
init_func_run_time_cache(&fbc->op_array);
40744097
}
4098+
if (UNEXPECTED(!ZEND_MAP_PTR_GET(fbc->common.instrument_cache))) {
4099+
zend_instrument_install_handlers(fbc);
4100+
}
40754101

40764102
return zend_vm_stack_push_call_frame(call_info,
40774103
fbc, num_args, object_or_called_scope);

Zend/zend_execute.h

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
#include "zend_compile.h"
2525
#include "zend_hash.h"
26+
#include "zend_instrument.h"
2627
#include "zend_operators.h"
2728
#include "zend_variables.h"
2829

@@ -39,7 +40,20 @@ ZEND_API void zend_init_func_execute_data(zend_execute_data *execute_data, zend_
3940
ZEND_API void zend_init_code_execute_data(zend_execute_data *execute_data, zend_op_array *op_array, zval *return_value);
4041
ZEND_API void zend_execute(zend_op_array *op_array, zval *return_value);
4142
ZEND_API void execute_ex(zend_execute_data *execute_data);
42-
ZEND_API void execute_internal(zend_execute_data *execute_data, zval *return_value);
43+
44+
ZEND_API inline void execute_internal(zend_execute_data *execute_data, zval *return_value)
45+
{
46+
zend_instrument_cache *cache = (zend_instrument_cache *)
47+
ZEND_MAP_PTR_GET(execute_data->func->common.instrument_cache);
48+
if (UNEXPECTED(cache != ZEND_NOT_INSTRUMENTED)) {
49+
zend_instrument_call_begin_handlers(execute_data, cache);
50+
execute_data->func->internal_function.handler(execute_data, return_value);
51+
zend_instrument_call_end_handlers(execute_data, cache);
52+
} else {
53+
execute_data->func->internal_function.handler(execute_data, return_value);
54+
}
55+
}
56+
4357
ZEND_API zend_class_entry *zend_lookup_class(zend_string *name);
4458
ZEND_API zend_class_entry *zend_lookup_class_ex(zend_string *name, zend_string *lcname, uint32_t flags);
4559
ZEND_API zend_class_entry *zend_get_called_scope(zend_execute_data *ex);

Zend/zend_execute_API.c

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -799,6 +799,10 @@ int zend_call_function(zend_fcall_info *fci, zend_fcall_info_cache *fci_cache) /
799799
const zend_op *current_opline_before_exception = EG(opline_before_exception);
800800

801801
zend_init_func_execute_data(call, &func->op_array, fci->retval);
802+
zend_instrument_cache *cache = ZEND_MAP_PTR_GET(func->common.instrument_cache);
803+
if (cache != ZEND_NOT_INSTRUMENTED) {
804+
zend_instrument_call_begin_handlers(call, cache);
805+
}
802806
zend_execute_ex(call);
803807
EG(opline_before_exception) = current_opline_before_exception;
804808
if (call_via_handler) {
@@ -809,12 +813,16 @@ int zend_call_function(zend_fcall_info *fci, zend_fcall_info_cache *fci_cache) /
809813
int call_via_handler = (func->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE) != 0;
810814

811815
ZEND_ASSERT(func->type == ZEND_INTERNAL_FUNCTION);
816+
817+
if (UNEXPECTED(!ZEND_MAP_PTR_GET(func->common.instrument_cache))) {
818+
zend_instrument_install_handlers(func);
819+
}
820+
812821
ZVAL_NULL(fci->retval);
813822
call->prev_execute_data = EG(current_execute_data);
814823
EG(current_execute_data) = call;
815824
if (EXPECTED(zend_execute_internal == NULL)) {
816-
/* saves one function call if zend_execute_internal is not used */
817-
func->internal_function.handler(call, fci->retval);
825+
execute_internal(call, fci->retval);
818826
} else {
819827
zend_execute_internal(call, fci->retval);
820828
}

Zend/zend_instrument.c

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
/*
2+
+----------------------------------------------------------------------+
3+
| Zend Engine |
4+
+----------------------------------------------------------------------+
5+
| Copyright (c) Zend Technologies Ltd. (http://www.zend.com) |
6+
+----------------------------------------------------------------------+
7+
| This source file is subject to version 2.00 of the Zend license, |
8+
| that is bundled with this package in the file LICENSE, and is |
9+
| available through the world-wide-web at the following url: |
10+
| http://www.zend.com/license/2_00.txt. |
11+
| If you did not receive a copy of the Zend license and are unable to |
12+
| obtain it through the world-wide-web, please send a note to |
13+
| license@zend.com so we can mail you a copy immediately. |
14+
+----------------------------------------------------------------------+
15+
| Authors: Levi Morrison <levim@php.net> |
16+
| Sammy Kaye Powers <sammyk@php.net> |
17+
+----------------------------------------------------------------------+
18+
*/
19+
20+
#include "zend.h"
21+
#include "zend_API.h"
22+
#include "zend_instrument.h"
23+
24+
struct zend_instruments_list {
25+
struct zend_instruments_list *prev;
26+
zend_instrument instrument;
27+
};
28+
typedef struct zend_instruments_list zend_instruments_list;
29+
30+
static zend_instruments_list *_zend_instruments;
31+
32+
ZEND_API void zend_instrument_init(void)
33+
{
34+
_zend_instruments = NULL;
35+
}
36+
37+
ZEND_API void zend_instrument_shutdown(void)
38+
{
39+
zend_instruments_list *curr, *prev;
40+
for (curr = _zend_instruments; curr; curr = prev) {
41+
prev = curr->prev;
42+
free(curr);
43+
}
44+
}
45+
46+
ZEND_API void zend_instrument_register(zend_instrument instrument)
47+
{
48+
zend_instruments_list *node = malloc(sizeof(zend_instruments_list));
49+
node->instrument = instrument;
50+
node->prev = _zend_instruments;
51+
_zend_instruments = node;
52+
}
53+
54+
struct zend_instrument_handlers_list {
55+
struct zend_instrument_handlers_list *prev;
56+
zend_instrument_handlers handlers;
57+
size_t count;
58+
};
59+
typedef struct zend_instrument_handlers_list zend_instrument_handlers_list;
60+
61+
62+
extern inline void zend_instrument_call_begin_handlers(
63+
zend_execute_data *execute_data, zend_instrument_cache *cache);
64+
extern inline void zend_instrument_call_end_handlers(
65+
zend_execute_data *execute_data, zend_instrument_cache *cache);
66+
67+
static zend_instrument_handlers_list *_instrument_add(
68+
zend_instrument_handlers_list *instruments,
69+
zend_instrument_handlers handlers)
70+
{
71+
if (!handlers.begin && !handlers.end) {
72+
return instruments;
73+
}
74+
75+
zend_instrument_handlers_list *n =
76+
emalloc(sizeof(zend_instrument_handlers_list));
77+
if (instruments) {
78+
n->prev = instruments;
79+
n->count = instruments->count + 1;
80+
} else {
81+
n->prev = NULL;
82+
n->count = 1;
83+
}
84+
n->handlers = handlers;
85+
return n;
86+
}
87+
88+
static zend_instrument_cache *_instrument_attach_handler(
89+
zend_function *function,
90+
zend_instrument_handlers_list *handlers_list)
91+
{
92+
zend_instrument_cache *cache =
93+
malloc(sizeof(zend_instrument_cache));
94+
cache->instruments_len = handlers_list->count;
95+
cache->handlers =
96+
calloc(handlers_list->count, sizeof(zend_instrument_handlers));
97+
98+
zend_instrument_handlers *handlers = cache->handlers;
99+
zend_instrument_handlers_list *curr;
100+
for (curr = handlers_list; curr; curr = curr->prev) {
101+
*handlers++ = curr->handlers;
102+
}
103+
104+
ZEND_MAP_PTR_SET(function->common.instrument_cache, cache);
105+
106+
return cache;
107+
}
108+
109+
ZEND_API void zend_instrument_install_handlers(zend_function *function)
110+
{
111+
zend_instrument_handlers_list *handlers_list = NULL;
112+
zend_instruments_list *elem;
113+
114+
for (elem = _zend_instruments; elem; elem = elem->prev) {
115+
zend_instrument_handlers handlers = elem->instrument(function);
116+
handlers_list = _instrument_add(handlers_list, handlers);
117+
}
118+
119+
if (handlers_list) {
120+
_instrument_attach_handler(function, handlers_list);
121+
122+
// cleanup handlers_list
123+
zend_instrument_handlers_list *curr, *prev;
124+
for (curr = handlers_list; curr; curr = prev) {
125+
prev = curr->prev;
126+
efree(curr);
127+
}
128+
} else {
129+
ZEND_MAP_PTR_SET(function->common.instrument_cache, ZEND_NOT_INSTRUMENTED);
130+
}
131+
}

0 commit comments

Comments
 (0)