From 79887bd5c8d2dfaa13bc85c112ce643fe00f4683 Mon Sep 17 00:00:00 2001 From: Pawel Filipczak Date: Thu, 5 Sep 2024 14:29:15 +0200 Subject: [PATCH] Fixed calling post hook if instrumented functions throws (#459) (#1223) --- agent/native/ext/tracer_PHP_part.cpp | 11 ++-- agent/native/ext/util_for_PHP.cpp | 3 +- agent/native/libphpbridge/code/Exceptions.cpp | 56 +++++++++++++++++ agent/native/libphpbridge/code/Exceptions.h | 61 +++++++++++++++++++ 4 files changed, 125 insertions(+), 6 deletions(-) create mode 100644 agent/native/libphpbridge/code/Exceptions.cpp create mode 100644 agent/native/libphpbridge/code/Exceptions.h diff --git a/agent/native/ext/tracer_PHP_part.cpp b/agent/native/ext/tracer_PHP_part.cpp index c72ab4912..02d8cc73c 100644 --- a/agent/native/ext/tracer_PHP_part.cpp +++ b/agent/native/ext/tracer_PHP_part.cpp @@ -17,13 +17,13 @@ * under the License. */ -#include "tracer_PHP_part.h" -#include "log.h" +#include "ConfigSnapshot.h" +#include "Exceptions.h" #include "Tracer.h" -#include "util_for_PHP.h" #include "basic_macros.h" #include "elastic_apm_API.h" -#include "ConfigSnapshot.h" +#include "log.h" +#include "util_for_PHP.h" #define ELASTIC_APM_CURRENT_LOG_CATEGORY ELASTIC_APM_LOG_CATEGORY_C_TO_PHP @@ -267,6 +267,8 @@ void tracerPhpPartInternalFuncCallPostHook( uint32_t dbgInterceptRegistrationId, ResultCode resultCode; zval phpPartArgs[ 2 ]; + elasticapm::php::AutomaticExceptionStateRestorer restorer; + if (!canInvokeTracerPhpPart()) { if (switchTracerPhpPartStateToFailed( /* reason */ "Unexpected current tracer PHP part state", __FUNCTION__ )) { ELASTIC_APM_SET_RESULT_CODE_AND_GOTO_FAILURE(); @@ -275,6 +277,7 @@ void tracerPhpPartInternalFuncCallPostHook( uint32_t dbgInterceptRegistrationId, } } + // The first argument to PHP part's interceptedCallPostHook() is $hasExitedByException (bool) ZVAL_FALSE( &( phpPartArgs[ 0 ] ) ); diff --git a/agent/native/ext/util_for_PHP.cpp b/agent/native/ext/util_for_PHP.cpp index 450e91eca..e1a486f15 100644 --- a/agent/native/ext/util_for_PHP.cpp +++ b/agent/native/ext/util_for_PHP.cpp @@ -214,8 +214,7 @@ ResultCode callPhpFunction( StringView phpFunctionName, uint32_t argsCount, zval , args ); if ( callUserFunctionRetVal != SUCCESS ) { - ELASTIC_APM_LOG_ERROR( "call_user_function failed. Return value: %d. PHP function name: `%.*s'. argsCount: %u." - , callUserFunctionRetVal, (int) phpFunctionName.length, phpFunctionName.begin, argsCount ); + ELASTIC_APM_LOG_ERROR( "call_user_function failed. Return value: %d. PHP function name: `%.*s'. argsCount: %u. Exception: %p", callUserFunctionRetVal, (int) phpFunctionName.length, phpFunctionName.begin, argsCount, EG( exception ) ); ELASTIC_APM_SET_RESULT_CODE_AND_GOTO_FAILURE(); } diff --git a/agent/native/libphpbridge/code/Exceptions.cpp b/agent/native/libphpbridge/code/Exceptions.cpp new file mode 100644 index 000000000..291a1baa1 --- /dev/null +++ b/agent/native/libphpbridge/code/Exceptions.cpp @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "Exceptions.h" +#include + +namespace elasticapm::php +{ + +SavedException saveExceptionState() +{ + SavedException savedException; + savedException.exception = EG( exception ); + savedException.prev_exception = EG( prev_exception ); + savedException.opline_before_exception = EG( opline_before_exception ); + + EG( exception ) = nullptr; + EG( prev_exception ) = nullptr; + EG( opline_before_exception ) = nullptr; + + if ( EG( current_execute_data ) ) + { + savedException.opline = EG( current_execute_data )->opline; + } + return savedException; +} + +void restoreExceptionState( SavedException savedException ) +{ + EG( exception ) = savedException.exception; + EG( prev_exception ) = savedException.prev_exception; + EG( opline_before_exception ) = savedException.opline_before_exception; + + if ( EG( current_execute_data ) && savedException.opline.has_value() ) + { + EG( current_execute_data )->opline = savedException.opline.value(); + } +} + +}// namespace elasticapm::php diff --git a/agent/native/libphpbridge/code/Exceptions.h b/agent/native/libphpbridge/code/Exceptions.h new file mode 100644 index 000000000..27908e7df --- /dev/null +++ b/agent/native/libphpbridge/code/Exceptions.h @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#pragma once + +#include +#include +#include +#include + +namespace elasticapm::php +{ + +struct SavedException { + zend_object* exception = nullptr; + zend_object* prev_exception = nullptr; + const zend_op* opline_before_exception = nullptr; + std::optional< const zend_op* > opline; +}; + +SavedException saveExceptionState(); +void restoreExceptionState( SavedException savedException ); + + +class AutomaticExceptionStateRestorer +{ +public: + AutomaticExceptionStateRestorer() + : savedException( saveExceptionState() ) + { + } + ~AutomaticExceptionStateRestorer() + { + restoreExceptionState( savedException ); + } + auto getException() + { + return savedException.exception; + } + +private: + SavedException savedException; +}; + +}// namespace elasticapm::php