Skip to content

Commit 350a95d

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 <[email protected]> Co-authored-by: Sammy Powers <[email protected]>
1 parent 35e0a91 commit 350a95d

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
@@ -2061,6 +2061,7 @@ ZEND_API int zend_register_functions(zend_class_entry *scope, const zend_functio
20612061
}
20622062
internal_function->type = ZEND_INTERNAL_FUNCTION;
20632063
internal_function->module = EG(current_module);
2064+
ZEND_MAP_PTR_NEW(internal_function->instrument_cache);
20642065
memset(internal_function->reserved, 0, ZEND_MAX_RESERVED_RESOURCES * sizeof(void*));
20652066

20662067
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
@@ -6258,9 +6258,14 @@ void zend_compile_func_decl(znode *result, zend_ast *ast, zend_bool toplevel) /*
62586258
op_array->fn_flags |= ZEND_ACC_PRELOADED;
62596259
ZEND_MAP_PTR_NEW(op_array->run_time_cache);
62606260
ZEND_MAP_PTR_NEW(op_array->static_variables_ptr);
6261+
ZEND_MAP_PTR_NEW(op_array->instrument_cache);
62616262
} else {
62626263
ZEND_MAP_PTR_INIT(op_array->run_time_cache, zend_arena_alloc(&CG(arena), sizeof(void*)));
62636264
ZEND_MAP_PTR_SET(op_array->run_time_cache, NULL);
6265+
6266+
ZEND_MAP_PTR_INIT(op_array->instrument_cache,
6267+
zend_arena_alloc(&CG(arena), sizeof(void*)));
6268+
ZEND_MAP_PTR_SET(op_array->instrument_cache, NULL);
62646269
}
62656270

62666271
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
@@ -403,6 +403,7 @@ struct _zend_op_array {
403403
uint32_t num_args;
404404
uint32_t required_num_args;
405405
zend_arg_info *arg_info;
406+
ZEND_MAP_PTR_DEF(struct zend_instrument_cache *, instrument_cache);
406407
/* END of common elements */
407408

408409
int cache_size; /* number of run_time_cache_slots * sizeof(void*) */
@@ -452,6 +453,7 @@ typedef struct _zend_internal_function {
452453
uint32_t num_args;
453454
uint32_t required_num_args;
454455
zend_internal_arg_info *arg_info;
456+
ZEND_MAP_PTR_DEF(struct zend_instrument_cache *, instrument_cache);
455457
/* END of common elements */
456458

457459
zif_handler handler;
@@ -475,6 +477,7 @@ union _zend_function {
475477
uint32_t num_args;
476478
uint32_t required_num_args;
477479
zend_arg_info *arg_info; /* index -1 represents the return value info, if any */
480+
ZEND_MAP_PTR_DEF(struct zend_instrument_cache *, instrument_cache);
478481
} common;
479482

480483
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 */
@@ -3307,10 +3309,7 @@ static int zend_check_symbol(zval *pz)
33073309
#define CHECK_SYMBOL_TABLES()
33083310
#endif
33093311

3310-
ZEND_API void execute_internal(zend_execute_data *execute_data, zval *return_value)
3311-
{
3312-
execute_data->func->internal_function.handler(execute_data, return_value);
3313-
}
3312+
ZEND_API extern inline void execute_internal(zend_execute_data *execute_data, zval *return_value);
33143313

33153314
ZEND_API void zend_clean_and_cache_symbol_table(zend_array *symbol_table) /* {{{ */
33163315
{
@@ -3508,6 +3507,9 @@ ZEND_API zend_function * ZEND_FASTCALL zend_fetch_function(zend_string *name) /*
35083507
if (EXPECTED(fbc->type == ZEND_USER_FUNCTION) && UNEXPECTED(!RUN_TIME_CACHE(&fbc->op_array))) {
35093508
init_func_run_time_cache_i(&fbc->op_array);
35103509
}
3510+
if (UNEXPECTED(!ZEND_MAP_PTR_GET(fbc->common.instrument_cache))) {
3511+
zend_instrument_install_handlers(fbc);
3512+
}
35113513
return fbc;
35123514
}
35133515
return NULL;
@@ -3523,6 +3525,9 @@ ZEND_API zend_function * ZEND_FASTCALL zend_fetch_function_str(const char *name,
35233525
if (EXPECTED(fbc->type == ZEND_USER_FUNCTION) && UNEXPECTED(!RUN_TIME_CACHE(&fbc->op_array))) {
35243526
init_func_run_time_cache_i(&fbc->op_array);
35253527
}
3528+
if (UNEXPECTED(!ZEND_MAP_PTR_GET(fbc->common.instrument_cache))) {
3529+
zend_instrument_install_handlers(fbc);
3530+
}
35263531
return fbc;
35273532
}
35283533
return NULL;
@@ -3555,6 +3560,12 @@ static zend_always_inline void i_init_code_execute_data(zend_execute_data *execu
35553560
ZEND_MAP_PTR_SET(op_array->run_time_cache, ptr);
35563561
memset(ptr, 0, op_array->cache_size);
35573562
}
3563+
if (!ZEND_MAP_PTR(op_array->instrument_cache)) {
3564+
ZEND_MAP_PTR_INIT(op_array->instrument_cache,
3565+
zend_arena_alloc(&CG(arena), sizeof(void*)));
3566+
ZEND_MAP_PTR_SET(op_array->instrument_cache, NULL);
3567+
zend_instrument_install_handlers((zend_function *) op_array);
3568+
}
35583569
EX(run_time_cache) = RUN_TIME_CACHE(op_array);
35593570

35603571
EG(current_execute_data) = execute_data;
@@ -3579,6 +3590,9 @@ ZEND_API void zend_init_func_execute_data(zend_execute_data *ex, zend_op_array *
35793590
if (!RUN_TIME_CACHE(op_array)) {
35803591
init_func_run_time_cache(op_array);
35813592
}
3593+
if (UNEXPECTED(!ZEND_MAP_PTR_GET(op_array->instrument_cache))) {
3594+
zend_instrument_install_handlers((zend_function *) op_array);
3595+
}
35823596
i_init_func_execute_data(op_array, return_value, 1 EXECUTE_DATA_CC);
35833597

35843598
#if defined(ZEND_VM_IP_GLOBAL_REG) && ((ZEND_VM_KIND == ZEND_VM_KIND_CALL) || (ZEND_VM_KIND == ZEND_VM_KIND_HYBRID))
@@ -3930,6 +3944,9 @@ static zend_never_inline zend_execute_data *zend_init_dynamic_call_string(zend_s
39303944
if (EXPECTED(fbc->type == ZEND_USER_FUNCTION) && UNEXPECTED(!RUN_TIME_CACHE(&fbc->op_array))) {
39313945
init_func_run_time_cache(&fbc->op_array);
39323946
}
3947+
if (UNEXPECTED(!ZEND_MAP_PTR_GET(fbc->common.instrument_cache))) {
3948+
zend_instrument_install_handlers(fbc);
3949+
}
39333950
} else {
39343951
if (ZSTR_VAL(function)[0] == '\\') {
39353952
lcname = zend_string_alloc(ZSTR_LEN(function) - 1, 0);
@@ -3948,6 +3965,9 @@ static zend_never_inline zend_execute_data *zend_init_dynamic_call_string(zend_s
39483965
if (EXPECTED(fbc->type == ZEND_USER_FUNCTION) && UNEXPECTED(!RUN_TIME_CACHE(&fbc->op_array))) {
39493966
init_func_run_time_cache(&fbc->op_array);
39503967
}
3968+
if (UNEXPECTED(!ZEND_MAP_PTR_GET(fbc->common.instrument_cache))) {
3969+
zend_instrument_install_handlers(fbc);
3970+
}
39513971
called_scope = NULL;
39523972
}
39533973

@@ -3992,6 +4012,9 @@ static zend_never_inline zend_execute_data *zend_init_dynamic_call_object(zend_o
39924012
if (EXPECTED(fbc->type == ZEND_USER_FUNCTION) && UNEXPECTED(!RUN_TIME_CACHE(&fbc->op_array))) {
39934013
init_func_run_time_cache(&fbc->op_array);
39944014
}
4015+
if (UNEXPECTED(!ZEND_MAP_PTR_GET(fbc->common.instrument_cache))) {
4016+
zend_instrument_install_handlers(fbc);
4017+
}
39954018

39964019
return zend_vm_stack_push_call_frame(call_info,
39974020
fbc, num_args, object_or_called_scope);
@@ -4077,6 +4100,9 @@ static zend_never_inline zend_execute_data *zend_init_dynamic_call_array(zend_ar
40774100
if (EXPECTED(fbc->type == ZEND_USER_FUNCTION) && UNEXPECTED(!RUN_TIME_CACHE(&fbc->op_array))) {
40784101
init_func_run_time_cache(&fbc->op_array);
40794102
}
4103+
if (UNEXPECTED(!ZEND_MAP_PTR_GET(fbc->common.instrument_cache))) {
4104+
zend_instrument_install_handlers(fbc);
4105+
}
40804106

40814107
return zend_vm_stack_push_call_frame(call_info,
40824108
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
@@ -797,6 +797,10 @@ int zend_call_function(zend_fcall_info *fci, zend_fcall_info_cache *fci_cache) /
797797
const zend_op *current_opline_before_exception = EG(opline_before_exception);
798798

799799
zend_init_func_execute_data(call, &func->op_array, fci->retval);
800+
zend_instrument_cache *cache = ZEND_MAP_PTR_GET(func->common.instrument_cache);
801+
if (cache != ZEND_NOT_INSTRUMENTED) {
802+
zend_instrument_call_begin_handlers(call, cache);
803+
}
800804
zend_execute_ex(call);
801805
EG(opline_before_exception) = current_opline_before_exception;
802806
if (call_via_handler) {
@@ -807,12 +811,16 @@ int zend_call_function(zend_fcall_info *fci, zend_fcall_info_cache *fci_cache) /
807811
int call_via_handler = (func->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE) != 0;
808812

809813
ZEND_ASSERT(func->type == ZEND_INTERNAL_FUNCTION);
814+
815+
if (UNEXPECTED(!ZEND_MAP_PTR_GET(func->common.instrument_cache))) {
816+
zend_instrument_install_handlers(func);
817+
}
818+
810819
ZVAL_NULL(fci->retval);
811820
call->prev_execute_data = EG(current_execute_data);
812821
EG(current_execute_data) = call;
813822
if (EXPECTED(zend_execute_internal == NULL)) {
814-
/* saves one function call if zend_execute_internal is not used */
815-
func->internal_function.handler(call, fci->retval);
823+
execute_internal(call, fci->retval);
816824
} else {
817825
zend_execute_internal(call, fci->retval);
818826
}

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+
| [email protected] so we can mail you a copy immediately. |
14+
+----------------------------------------------------------------------+
15+
| Authors: Levi Morrison <[email protected]> |
16+
| Sammy Kaye Powers <[email protected]> |
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)