Skip to content

Add new observer API #5857

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 57 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
6a73552
Add zend_observer API
morrisonlevi Jul 10, 2020
45a8540
Add temporary observer extension
morrisonlevi Jul 13, 2020
ecfca90
Fix some issues
nikic Jul 13, 2020
1e7c59d
Guard observer begin handler and start adding end handler
morrisonlevi Jul 13, 2020
01308fd
WIP .end handler
morrisonlevi Jul 15, 2020
dc74744
Update for zend_get_zval_ptr changes
morrisonlevi Jul 15, 2020
23b7d37
Expand ternary short-hand for Windows
morrisonlevi Jul 15, 2020
1553336
Try fixing missing symbol
morrisonlevi Jul 15, 2020
947c955
Free OP1 of INCLUDE_OR_EVAL later so observers can look at it
morrisonlevi Jul 24, 2020
fe6b447
Move observer arena allocation for call handers to first-time register
SammyK Jul 25, 2020
3f617d1
Refactor where function init/begin/end handlers are called
SammyK Aug 7, 2020
a3e0139
Remove fcall init handlers from zend_call_function()
SammyK Aug 7, 2020
e5e7bce
Clean up
SammyK Aug 7, 2020
e7badd9
Clean up one more
SammyK Aug 7, 2020
71bbb1f
Add observer hooks to zend_test extension
SammyK Aug 7, 2020
0b13ca6
Merge branch 'master' into levim/observer
SammyK Aug 10, 2020
9837e2b
Add test for magic
SammyK Aug 10, 2020
ef1bcdc
Add basic generator support
SammyK Aug 10, 2020
f6edb80
Make cleaner test output
SammyK Aug 10, 2020
c66d659
Pass proper return value for generators
SammyK Aug 11, 2020
a6faa3b
Expose return values in tests
SammyK Aug 11, 2020
5f24f65
Add more generator tests
SammyK Aug 11, 2020
9e9bfc9
Remove temporary observer extension
SammyK Aug 12, 2020
b5b2762
Merge branch 'master' into levim/observer
SammyK Aug 12, 2020
2958a20
Fix memleak on TMPVAR with eval()
SammyK Aug 12, 2020
95bcf05
Fix arena allocation again :facepalm:
SammyK Aug 12, 2020
1e3b6be
Whitespace
SammyK Aug 21, 2020
1445b3b
Disable observer API when loaded via dl()
SammyK Aug 21, 2020
228807a
Wrap up some TODOs and cleanup
SammyK Aug 21, 2020
fc73942
Implement ZEND_OBSERVE_RETURN
SammyK Aug 24, 2020
b4731c5
Clean up fcall observer
morrisonlevi Aug 24, 2020
c76e8f5
Revert "Implement ZEND_OBSERVE_RETURN"
SammyK Aug 25, 2020
bb429b0
Implement SPEC(OBSERVER) for return handlers
SammyK Aug 25, 2020
c57324d
Add SPEC(OBSERVER) to DO_FCALL and friends
SammyK Aug 26, 2020
e3cdb9f
Add SPEC(OBSERVER) to INCLUDE_OR_EVAL
SammyK Aug 26, 2020
791c921
Add SPEC(OBSERVER) to fcall INIT handlers
SammyK Aug 26, 2020
0f78895
Add test for observing eval
SammyK Aug 26, 2020
bf56f27
Add SPEC(OBSERVER) to ZEND_HANDLE_EXCEPTION
SammyK Aug 26, 2020
b8e1408
Do only one observer check in zend_generator_resume
SammyK Aug 26, 2020
7792c18
Add test for observing closures
SammyK Aug 26, 2020
f0da73f
Merge remote-tracking branch 'upstream/master' into levim/observer
SammyK Aug 26, 2020
3f93dc8
Check for ZEND_OBSERVER_ENABLED before installing observers for closures
SammyK Aug 26, 2020
00a038b
Install observers only if enabled
SammyK Aug 26, 2020
1d2f9f6
Disable JIT when observer extension present
SammyK Aug 27, 2020
d22d421
Add SKIPIF's to the observer tests
SammyK Aug 27, 2020
96d1040
Add observer to zend_call_function
SammyK Aug 27, 2020
639a09e
Move observer install to first time fcall
SammyK Aug 27, 2020
971824b
Move LOAD_OPLINE_EX() back to the end of DO_UCALL handler
SammyK Aug 27, 2020
f9e10a0
Rename ZEND_SHOULD_OBSERVE_FN to ZEND_OBSERVABLE_FN
SammyK Aug 27, 2020
e4a6c45
Mark SPEC(OBSERVER) handlers as ZEND_VM_COLD
SammyK Aug 27, 2020
58a075f
Only register zend_test as observer when enabled
SammyK Aug 27, 2020
1698665
Add zend_test.observer.show_output INI setting
SammyK Aug 28, 2020
6181716
Skip JIT tests on non-JIT builds
SammyK Aug 28, 2020
63dba3d
Update observer arena size
morrisonlevi Aug 31, 2020
1ca066b
Add ZEND_API to some observer functions
morrisonlevi Aug 31, 2020
85083ff
Fix C++ compat for zend_observer.h
morrisonlevi Aug 31, 2020
6d5faf5
Merge branch 'master' into levim/observer
morrisonlevi Sep 1, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Zend/zend.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#include "zend_smart_string.h"
#include "zend_cpuinfo.h"
#include "zend_attributes.h"
#include "zend_observer.h"

static size_t global_map_ptr_last = 0;

Expand Down Expand Up @@ -1205,6 +1206,7 @@ ZEND_API void zend_activate(void) /* {{{ */
if (CG(map_ptr_last)) {
memset(ZEND_MAP_PTR_REAL_BASE(CG(map_ptr_base)), 0, CG(map_ptr_last) * sizeof(void*));
}
zend_observer_activate();
}
/* }}} */

Expand All @@ -1221,6 +1223,8 @@ ZEND_API void zend_deactivate(void) /* {{{ */
/* we're no longer executing anything */
EG(current_execute_data) = NULL;

zend_observer_deactivate();

zend_try {
shutdown_scanner();
} zend_end_try();
Expand Down
1 change: 1 addition & 0 deletions Zend/zend_execute.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
#include "zend_inheritance.h"
#include "zend_type_info.h"
#include "zend_smart_str.h"
#include "zend_observer.h"

/* Virtual current working directory support */
#include "zend_virtual_cwd.h"
Expand Down
4 changes: 4 additions & 0 deletions Zend/zend_execute_API.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
#include "zend_float.h"
#include "zend_weakrefs.h"
#include "zend_inheritance.h"
#include "zend_observer.h"
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
Expand Down Expand Up @@ -869,6 +870,9 @@ zend_result zend_call_function(zend_fcall_info *fci, zend_fcall_info_cache *fci_
uint32_t orig_jit_trace_num = EG(jit_trace_num);

zend_init_func_execute_data(call, &func->op_array, fci->retval);
if (ZEND_OBSERVER_ENABLED) {
zend_observer_maybe_fcall_call_begin(call);
}
zend_execute_ex(call);
EG(jit_trace_num) = orig_jit_trace_num;
EG(opline_before_exception) = current_opline_before_exception;
Expand Down
21 changes: 20 additions & 1 deletion Zend/zend_generators.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include "zend_generators.h"
#include "zend_closures.h"
#include "zend_generators_arginfo.h"
#include "zend_observer.h"

ZEND_API zend_class_entry *zend_ce_generator;
ZEND_API zend_class_entry *zend_ce_ClosedGeneratorException;
Expand Down Expand Up @@ -774,7 +775,25 @@ ZEND_API void zend_generator_resume(zend_generator *orig_generator) /* {{{ */

/* Resume execution */
generator->flags |= ZEND_GENERATOR_CURRENTLY_RUNNING;
zend_execute_ex(generator->execute_data);
if (!ZEND_OBSERVER_ENABLED) {
zend_execute_ex(generator->execute_data);
} else {
zend_op_array *op_array = &generator->execute_data->func->op_array;
void *observer_handlers = ZEND_OBSERVER_HANDLERS(op_array);
if (!observer_handlers) {
zend_observer_fcall_install((zend_function *)op_array);
observer_handlers = ZEND_OBSERVER_HANDLERS(op_array);
}
ZEND_ASSERT(observer_handlers);
if (observer_handlers != ZEND_OBSERVER_NOT_OBSERVED) {
zend_observe_fcall_begin(observer_handlers, generator->execute_data);
}
zend_execute_ex(generator->execute_data);
if (generator->execute_data) {
/* On the final return, this will be called from ZEND_GENERATOR_RETURN */
zend_observer_maybe_fcall_call_end(generator->execute_data, &generator->value);
}
}
generator->flags &= ~ZEND_GENERATOR_CURRENTLY_RUNNING;

generator->frozen_call_stack = NULL;
Expand Down
160 changes: 160 additions & 0 deletions Zend/zend_observer.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
/*
+----------------------------------------------------------------------+
| Zend Engine |
+----------------------------------------------------------------------+
| Copyright (c) Zend Technologies Ltd. (http://www.zend.com) |
+----------------------------------------------------------------------+
| This source file is subject to version 2.00 of the Zend license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.zend.com/license/2_00.txt. |
| If you did not receive a copy of the Zend license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| [email protected] so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
| Authors: Levi Morrison <[email protected]> |
| Sammy Kaye Powers <[email protected]> |
+----------------------------------------------------------------------+
*/

#include "zend_observer.h"

#include "zend_extensions.h"
#include "zend_llist.h"
#include "zend_vm.h"

zend_llist zend_observers_fcall_list;
int zend_observer_fcall_op_array_extension = -1;

ZEND_TLS zend_arena *fcall_handlers_arena = NULL;

ZEND_API extern inline void zend_observer_maybe_fcall_call_begin(
zend_execute_data *execute_data);
ZEND_API extern inline void zend_observer_maybe_fcall_call_end(
zend_execute_data *execute_data,
zval *return_value);

// Call during minit/startup ONLY
ZEND_API void zend_observer_fcall_register(zend_observer_fcall_init init) {
/* We don't want to get an extension handle unless an ext installs an observer */
if (!ZEND_OBSERVER_ENABLED) {
zend_observer_fcall_op_array_extension =
zend_get_op_array_extension_handle();

/* ZEND_CALL_TRAMPOLINE has SPEC(OBSERVER) but zend_init_call_trampoline_op()
* is called before any extensions have registered as an observer. So we
* adjust the offset to the observed handler when we know we need to observe. */
ZEND_VM_SET_OPCODE_HANDLER(&EG(call_trampoline_op));

/* ZEND_HANDLE_EXCEPTION also has SPEC(OBSERVER) and no observer extensions
* exist when zend_init_exception_op() is called. */
ZEND_VM_SET_OPCODE_HANDLER(EG(exception_op));
ZEND_VM_SET_OPCODE_HANDLER(EG(exception_op)+1);
ZEND_VM_SET_OPCODE_HANDLER(EG(exception_op)+2);
}
zend_llist_add_element(&zend_observers_fcall_list, &init);
}

// Called by engine before MINITs
ZEND_API void zend_observer_startup(void) {
zend_llist_init(&zend_observers_fcall_list, sizeof(zend_observer_fcall_init), NULL, 1);
}

ZEND_API void zend_observer_activate(void) {
if (ZEND_OBSERVER_ENABLED) {
fcall_handlers_arena = zend_arena_create(4096);
}
}

ZEND_API void zend_observer_deactivate(void) {
if (fcall_handlers_arena) {
zend_arena_destroy(fcall_handlers_arena);
}
}

ZEND_API void zend_observer_shutdown(void) {
zend_llist_destroy(&zend_observers_fcall_list);
}

ZEND_API void zend_observer_fcall_install(zend_function *function) {
zend_llist_element *element;
zend_llist *list = &zend_observers_fcall_list;
zend_op_array *op_array = &function->op_array;

if (fcall_handlers_arena == NULL) {
return;
}

ZEND_ASSERT(function->type != ZEND_INTERNAL_FUNCTION);

zend_llist handlers_list;
zend_llist_init(&handlers_list, sizeof(zend_observer_fcall), NULL, 0);
for (element = list->head; element; element = element->next) {
zend_observer_fcall_init init;
memcpy(&init, element->data, sizeof init);
zend_observer_fcall handlers = init(function);
if (handlers.begin || handlers.end) {
zend_llist_add_element(&handlers_list, &handlers);
}
}

ZEND_ASSERT(RUN_TIME_CACHE(op_array));
void *ext;
if (handlers_list.count) {
size_t size = sizeof(zend_observer_fcall_cache) + (handlers_list.count - 1) * sizeof(zend_observer_fcall);
zend_observer_fcall_cache *cache = zend_arena_alloc(&fcall_handlers_arena, size);
zend_observer_fcall *handler = cache->handlers;
for (element = handlers_list.head; element; element = element->next) {
memcpy(handler++, element->data, sizeof *handler);
}
cache->end = handler;
ext = cache;
} else {
ext = ZEND_OBSERVER_NOT_OBSERVED;
}

ZEND_OBSERVER_HANDLERS(op_array) = ext;
zend_llist_destroy(&handlers_list);
}

ZEND_API void zend_observe_fcall_begin(
zend_observer_fcall_cache *cache,
zend_execute_data *execute_data)
{
zend_observer_fcall *handler, *end = cache->end;
for (handler = cache->handlers; handler != end; ++handler) {
if (handler->begin) {
handler->begin(execute_data);
}
}
}

ZEND_API void zend_observer_fcall_call_end_helper(
zend_execute_data *execute_data,
zval *return_value)
{
zend_function *func = execute_data->func;
ZEND_ASSUME(ZEND_OBSERVABLE_FN(func->common.fn_flags));
void *observer_handlers = ZEND_OBSERVER_HANDLERS(&func->op_array);
// TODO: Fix exceptions from generators
// ZEND_ASSERT(observer_handlers);
if (observer_handlers && observer_handlers != ZEND_OBSERVER_NOT_OBSERVED) {
zend_observer_fcall_cache *cache = observer_handlers;
zend_observe_fcall_end(cache, execute_data, return_value);
}
}

ZEND_API void zend_observe_fcall_end(
zend_observer_fcall_cache *cache,
zend_execute_data *execute_data,
zval *return_value)
{
zend_observer_fcall *handler = cache->end, *end = cache->handlers;
while (handler-- != end) {
if (handler->end) {
handler->end(execute_data, return_value);
}
}
}


115 changes: 115 additions & 0 deletions Zend/zend_observer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/*
+----------------------------------------------------------------------+
| Zend Engine |
+----------------------------------------------------------------------+
| Copyright (c) Zend Technologies Ltd. (http://www.zend.com) |
+----------------------------------------------------------------------+
| This source file is subject to version 2.00 of the Zend license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.zend.com/license/2_00.txt. |
| If you did not receive a copy of the Zend license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| [email protected] so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
| Authors: Levi Morrison <[email protected]> |
| Sammy Kaye Powers <[email protected]> |
+----------------------------------------------------------------------+
*/

#ifndef ZEND_OBSERVER_H
#define ZEND_OBSERVER_H

#include "zend.h"
#include "zend_compile.h"

BEGIN_EXTERN_C()

extern ZEND_API int zend_observer_fcall_op_array_extension;

#define ZEND_OBSERVER_ENABLED (zend_observer_fcall_op_array_extension != -1)

#define ZEND_OBSERVER_HANDLERS(op_array) \
ZEND_OP_ARRAY_EXTENSION(op_array, zend_observer_fcall_op_array_extension)

#define ZEND_OBSERVER_NOT_OBSERVED ((void *) 2)

#define ZEND_OBSERVABLE_FN(fn_flags) \
(ZEND_OBSERVER_ENABLED && \
!(fn_flags & (ZEND_ACC_CALL_VIA_TRAMPOLINE | ZEND_ACC_FAKE_CLOSURE)))

struct zend_observer_fcall {
void (*begin)(zend_execute_data *execute_data);
void (*end)(zend_execute_data *execute_data, zval *retval);
};
typedef struct zend_observer_fcall zend_observer_fcall;

struct zend_observer_fcall_cache {
// points after the last handler
zend_observer_fcall *end;
// a variadic array using "struct hack"
zend_observer_fcall handlers[1];
};
typedef struct zend_observer_fcall_cache zend_observer_fcall_cache;

/* If the fn should not be observed then return {NULL, NULL} */
typedef zend_observer_fcall(*zend_observer_fcall_init)(zend_function *func);

// Call during minit/startup ONLY
ZEND_API void zend_observer_fcall_register(zend_observer_fcall_init init);

ZEND_API void zend_observer_startup(void); // Called by engine before MINITs
ZEND_API void zend_observer_activate(void);
ZEND_API void zend_observer_deactivate(void);
ZEND_API void zend_observer_shutdown(void);

ZEND_API void zend_observer_fcall_install(zend_function *function);

ZEND_API void zend_observe_fcall_begin(
zend_observer_fcall_cache *cache,
zend_execute_data *execute_data);

ZEND_API void zend_observe_fcall_end(
zend_observer_fcall_cache *cache,
zend_execute_data *execute_data,
zval *return_value);

ZEND_API void zend_observer_fcall_call_end_helper(
zend_execute_data *execute_data,
zval *return_value);

ZEND_API zend_always_inline void zend_observer_maybe_fcall_call_begin(
zend_execute_data *execute_data)
{
ZEND_ASSUME(execute_data->func);
zend_op_array *op_array = &execute_data->func->op_array;
uint32_t fn_flags = op_array->fn_flags;
if (ZEND_OBSERVABLE_FN(fn_flags) && !(fn_flags & ZEND_ACC_GENERATOR)) {
void *observer_handlers = ZEND_OBSERVER_HANDLERS(op_array);
if (!observer_handlers) {
zend_observer_fcall_install((zend_function *)op_array);
observer_handlers = ZEND_OBSERVER_HANDLERS(op_array);
}

ZEND_ASSERT(observer_handlers);
if (observer_handlers != ZEND_OBSERVER_NOT_OBSERVED) {
zend_observe_fcall_begin(
(zend_observer_fcall_cache *)observer_handlers,
execute_data);
}
}
}

ZEND_API zend_always_inline void zend_observer_maybe_fcall_call_end(
zend_execute_data *execute_data,
zval *return_value)
{
zend_function *func = execute_data->func;
if (ZEND_OBSERVABLE_FN(func->common.fn_flags)) {
zend_observer_fcall_call_end_helper(execute_data, return_value);
}
}

END_EXTERN_C()

#endif /* ZEND_OBSERVER_H */
Loading