Skip to content

Commit

Permalink
feat(agent): Registered function begin/end and error handlers with Ob…
Browse files Browse the repository at this point in the history
…server API (#501)

* feat(agent): Registered function begin/end and error handlers with Observer API
1) Registered function begin/end handlers with Observer API
2) Created the function begin/end handler stubs.  Full functionality is schedule for another ticket.
3) Registered currently existing error handler with Observer API
4) created php_observer.c/h module to contain the observer api logic.

Testing:
1) Verified new function begin/end handlers were registered correctly and received zend_execute_data from PHP engine.
2) Current test cases verified that registering our current error handler directly caused no change in functionality.

Additional
* feat(agent): Don't call handlers for internal functions.
Currently internal functions are not handled by OAPI, but they will be in 8.2.  These functions are tailored to USER functions (similar to nr_php_execute) and we don't want internal functions filtered to these handlers.  This will default to INTERNAL functions being handled by the current implementation.  To change in the future, it's possible we'd need to implement handlers specific for internal functions (similar to nr_php_execute_internal).
  • Loading branch information
zsistla committed Sep 29, 2022
1 parent b6dcaf9 commit 0f5dfc4
Show file tree
Hide file tree
Showing 9 changed files with 236 additions and 19 deletions.
2 changes: 1 addition & 1 deletion agent/config.m4
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ if test "$PHP_NEWRELIC" = "yes"; then
php_explain_pdo_mysql.c php_extension.c php_file_get_contents.c \
php_globals.c php_hash.c php_header.c php_httprequest_send.c \
php_internal_instrument.c php_minit.c php_mshutdown.c php_mysql.c \
php_mysqli.c php_newrelic.c php_nrini.c php_output.c php_pdo.c \
php_mysqli.c php_newrelic.c php_nrini.c php_observer.c php_output.c php_pdo.c \
php_pdo_mysql.c php_pdo_pgsql.c php_pgsql.c php_psr7.c php_redis.c \
php_rinit.c php_rshutdown.c php_samplers.c php_stack.c \
php_stacked_segment.c php_txn.c php_user_instrument.c \
Expand Down
25 changes: 15 additions & 10 deletions agent/php_error.c
Original file line number Diff line number Diff line change
Expand Up @@ -449,14 +449,20 @@ static int nr_php_should_record_error(int type, const char* format TSRMLS_DC) {
}

#if ZEND_MODULE_API_NO >= ZEND_8_1_X_API_NO
/* Prior to PHP8 these error_filename and error_lineno were only used to pass
* on to the error handler that the agent overwrote. With PHP8+, these values
* are currently unused since the agent is already recording the stack trace.
* HOWEVER, when code level metrics(CLM) are incorporated, these values can be
* used to add lineno and filename to error traces.
*/
void nr_php_error_cb(int type,
zend_string* error_filename,
uint error_lineno,
zend_string* error_filename NRUNUSED,
uint error_lineno NRUNUSED,
zend_string* message) {
#elif ZEND_MODULE_API_NO == ZEND_8_0_X_API_NO
void nr_php_error_cb(int type,
const char* error_filename,
uint error_lineno,
const char* error_filename NRUNUSED,
uint error_lineno NRUNUSED,
zend_string* message) {
#else
void nr_php_error_cb(int type,
Expand Down Expand Up @@ -502,17 +508,16 @@ void nr_php_error_cb(int type,
}

/*
* Call through to the actual error handler.
* Call through to the actual error handler for PHP 7.4 and below.
* For PHP 8+ we have registered our error handler with the Observer
* API so there is no need to callback to the original.
*/
if (0 != NR_PHP_PROCESS_GLOBALS(orig_error_cb)) {
#if ZEND_MODULE_API_NO < ZEND_8_0_X_API_NO
if (0 != NR_PHP_PROCESS_GLOBALS(orig_error_cb)) {
NR_PHP_PROCESS_GLOBALS(orig_error_cb)
(type, error_filename, error_lineno, format, args);
#else
NR_PHP_PROCESS_GLOBALS(orig_error_cb)
(type, error_filename, error_lineno, message);
#endif /* PHP < 8.0 */
}
#endif /* PHP < 8.0 */
}

nr_status_t nr_php_error_record_exception(nrtxn_t* txn,
Expand Down
42 changes: 41 additions & 1 deletion agent/php_execute.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@
#include "util_url.h"
#include "util_metrics.h"
#include "util_number_converter.h"
#include "php_execute.h"
#include "fw_support.h"
#include "fw_hooks.h"
#include "php_observer.h"

/*
* This wall of text is important. Read it. Understand it. Really.
Expand Down Expand Up @@ -1538,3 +1538,43 @@ void nr_php_user_instrumentation_from_opcache(TSRMLS_D) {
end:
nr_php_zval_free(&status);
}

/*
* nr_php_observer_fcall_begin and nr_php_observer_fcall_end
* are Observer API function handlers that are the entry point to instrumenting
* userland code and should replicate the functionality of
* nr_php_execute_enabled, nr_php_execute, and nr_php_execute_show that are used
* when hooking in via zend_execute_ex.
*
* Observer API functionality was added with PHP 8.0.
* See nr_php_observer.h/c for more information.
*/
#if ZEND_MODULE_API_NO >= ZEND_8_0_X_API_NO /* PHP8+ */
void nr_php_observer_fcall_begin(zend_execute_data* execute_data) {
/*
* Instrument the function.
* This and any other needed helper functions will replace:
* nr_php_execute_enabled
* nr_php_execute
* nr_php_execute_show
*/

if (NULL == execute_data) {
return;
}
}

void nr_php_observer_fcall_end(zend_execute_data* execute_data,
zval* return_value) {
/*
* Instrument the function.
* This and any other needed helper functions will replace:
* nr_php_execute_enabled
* nr_php_execute
* nr_php_execute_show
*/
if ((NULL == execute_data) || (NULL == return_value)) {
return;
}
}
#endif
2 changes: 2 additions & 0 deletions agent/php_execute.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ extern void nr_framework_create_metric(TSRMLS_D);
* This is necessary to correctly instrument frameworks and libraries
* that are preloaded.
*/

extern void nr_php_user_instrumentation_from_opcache(TSRMLS_D);

#include "php_observer.h"
#endif /* PHP_EXECUTE_HDR */
10 changes: 3 additions & 7 deletions agent/php_hooks.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,9 @@ extern void nr_php_execute(NR_EXECUTE_PROTO TSRMLS_DC);
* are strongly encouraged to use the new error notification API instead.
* Error notification callbacks are guaranteed to be called regardless of the
* users error_reporting setting or userland error handler return values.
* Register notification callbacks during MINIT of an extension:
void my_error_notify_cb(int type,
const char *error_filename,
uint32_t error_lineno,
zend_string *message) {
}
zend_register_error_notify_callback(my_error_notify_cb);
*
* The register notification callbacks during MINIT of an extension are done in
* `php_observer.c/h`.
*/
#if ZEND_MODULE_API_NO >= ZEND_8_1_X_API_NO
extern void nr_php_error_cb(int type,
Expand Down
4 changes: 4 additions & 0 deletions agent/php_includes.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@
#define ZEND_8_0_X_API_NO 20200930
#define ZEND_8_1_X_API_NO 20210902

#if ZEND_MODULE_API_NO >= ZEND_8_0_X_API_NO /* PHP8+ */
#include "Zend/zend_observer.h"
#endif

#if ZEND_MODULE_API_NO >= ZEND_5_6_X_API_NO
#include "Zend/zend_virtual_cwd.h"
#else /* PHP < 5.6 */
Expand Down
5 changes: 5 additions & 0 deletions agent/php_minit.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
#include "fw_laravel.h"
#include "lib_guzzle4.h"

#include "php_observer.h"

static void php_newrelic_init_globals(zend_newrelic_globals* nrg) {
if (nrunlikely(NULL == nrg)) {
return;
Expand Down Expand Up @@ -408,6 +410,7 @@ PHP_MINIT_FUNCTION(newrelic) {
zend_extension dummy;
#else
char dummy[] = "newrelic";
nr_php_observer_minit();
#endif

(void)type;
Expand Down Expand Up @@ -723,6 +726,7 @@ void nr_php_late_initialization(void) {
* forward the errors, so if a user has Xdebug loaded, we do not install
* our own error callback handler. Otherwise, we do.
*/
#if ZEND_MODULE_API_NO < ZEND_8_0_X_API_NO /* < PHP8 */
if (0 == zend_get_extension("Xdebug")) {
NR_PHP_PROCESS_GLOBALS(orig_error_cb) = zend_error_cb;
zend_error_cb = nr_php_error_cb;
Expand All @@ -731,6 +735,7 @@ void nr_php_late_initialization(void) {
"the Xdebug extension prevents the New Relic agent from "
"gathering errors. No errors will be recorded.");
}
#endif /* end of < PHP8 */

/*
* Install our signal handler, unless the user has set a special flag
Expand Down
97 changes: 97 additions & 0 deletions agent/php_observer.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* Copyright 2022 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*
* This file handles the initialization that happens once per module load.
*/
#include "php_agent.h"

#include <dlfcn.h>
#include <signal.h>

#include "php_api_distributed_trace.h"
#include "php_environment.h"
#include "php_error.h"
#include "php_extension.h"
#include "php_globals.h"
#include "php_header.h"
#include "php_hooks.h"
#include "php_internal_instrument.h"
#include "php_samplers.h"
#include "php_user_instrument.h"
#include "php_vm.h"
#include "php_wrapper.h"
#include "fw_laravel.h"
#include "lib_guzzle4.h"
#include "lib_guzzle6.h"
#include "nr_agent.h"
#include "nr_app.h"
#include "nr_banner.h"
#include "nr_daemon_spawn.h"
#include "util_logging.h"
#include "util_memory.h"
#include "util_signals.h"
#include "util_strings.h"
#include "util_syscalls.h"
#include "util_threads.h"

#include "php_observer.h"
#include "php_execute.h"

/*
* Observer API functionality was added with PHP 8.0.
*
* The Observer API provide function handlers that trigger on every userland
* function begin and end. The handlers provide all zend_execute_data and the
* end handler provides the return value pointer. The previous way to hook into
* PHP was via zend_execute_ex which will hook all userland function calls with
* significant overhead for doing the call. However, depending on user stack
* size settings, it could potentially generate an extremely deep call stack in
* PHP because zend_execute_ex limits stack size to whatever user settings
* are. Observer API bypasses the stack overflow issue that an agent could run
* into when intercepting userland calls. Additionally, with PHP 8.0, JIT
* optimizations could optimize out a call to zend_execute_ex and the agent
* would not be able to overwite that call properly as the agent wouldn't have
* access to the JITed information. This could lead to segfaults and caused PHP
* to decide to disable JIT when detecting extensions that overwrote
* zend_execute_ex.
*
* It only provides ZEND_USER_FUNCTIONS yet as it was assumed mechanisms already
* exist to monitor internal functions by overwriting internal function
* handlers. This will be included in PHP 8.2: Registered
* zend_observer_fcall_init handlers are now also called for internal functions.
*
* Without overwriting the execute function and therefore being responsible for
* continuing the execution of ALL functions that we intercepted, the agent is
* provided zend_execute_data on each function start/end and is then able to use
* it with our currently existing logic and instrumentation.
*/

#if ZEND_MODULE_API_NO >= ZEND_8_0_X_API_NO /* PHP8+ */
/*
* Register the begin and end function handlers with the Observer API.
*/
static zend_observer_fcall_handlers nr_php_fcall_register_handlers(
zend_execute_data* execute_data) {
zend_observer_fcall_handlers handlers = {NULL, NULL};
if (NULL == execute_data) {
return handlers;
}
if ((NULL == execute_data->func)
|| (ZEND_INTERNAL_FUNCTION == execute_data->func->type)) {
return handlers;
}
handlers.begin = nr_php_observer_fcall_begin;
handlers.end = nr_php_observer_fcall_end;
return handlers;
}

void nr_php_observer_minit() {
/*
* Register the Observer API handlers.
*/
zend_observer_fcall_register(nr_php_fcall_register_handlers);
zend_observer_error_register(nr_php_error_cb);
}

#endif
68 changes: 68 additions & 0 deletions agent/php_observer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright 2022 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

/*
* This file is the wrapper for PHP 8+ Observer API (OAPI) functionality.
*
* The registered function handlers are the entry points of instrumentation and
* are implemented in php_execute.c which contains the brains/helper functions
* required to monitor PHP.
*/

#ifndef NEWRELIC_PHP_AGENT_PHP_OBSERVER_H
#define NEWRELIC_PHP_AGENT_PHP_OBSERVER_H

#if ZEND_MODULE_API_NO >= ZEND_8_0_X_API_NO /* PHP8+ */

#include "Zend/zend_observer.h"

/*
* Purpose : Register the OAPI function handlers and any other minit actions.
*
* Params : None
*
* Returns : Void.
*/
void nr_php_observer_minit();

/*
* Purpose : Call the necessary functions needed to instrument a function by
* updating a transaction or segment for a function that has just
* started. This function is registered via the Observer API and will be called
* by the zend engine every time a function begins. The zend engine directly
* provides the zend_execute_data which has all details we need to know about
* the function. This and nr_php_execute_observer_fcall_end sum to provide all
* the functionality of nr_php_execute and nr_php _execute_enabled and as such
* will use all the helper functions they also used.
*
*
* Params : 1. zend_execute_data: everything we need to know about the
* function.
*
* Returns : Void.
*/
void nr_php_observer_fcall_begin(zend_execute_data* execute_data);
/*
* Purpose : Call the necessary functions needed to instrument a function when
* updating a transaction or segment for a function that has just
* ended. This function is registered via the Observer API and will be called by
* the zend engine every time a function ends. The zend engine directly
* provides the zend_execute_data and the return_value pointer, both of which
* have all details that the agent needs to know about the function. This and
* nr_php_execute_observer_fcall_start sum to provide all the functionality of
* nr_php_execute and nr_php_execute_enabled and as such will use all the helper
* functions they also used.
*
*
* Params : 1. zend_execute_data: everything to know about the function.
* 2. return_value: function return value information
*
* Returns : Void.
*/
void nr_php_observer_fcall_end(zend_execute_data* execute_data,
zval* return_value);
#endif /* PHP8+ */

#endif // NEWRELIC_PHP_AGENT_PHP_OBSERVER_H

0 comments on commit 0f5dfc4

Please sign in to comment.