agent/native/ext/elastic_apm_API.cpp (185 lines of code) (raw):
/*
* 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 "elastic_apm_API.h"
#include <unistd.h>
#include <php.h>
#include "util.h"
#include "log.h"
#include "Tracer.h"
#include "elastic_apm_alloc.h"
#include "numbered_intercepting_callbacks.h"
#include "tracer_PHP_part.h"
#include "backend_comm.h"
#include "lifecycle.h"
#include "ConfigSnapshot.h"
#define ELASTIC_APM_CURRENT_LOG_CATEGORY ELASTIC_APM_LOG_CATEGORY_EXT_API
ResultCode elasticApmApiEntered( String dbgCalledFromFile, int dbgCalledFromLine, String dbgCalledFromFunction )
{
// We SHOULD NOT log before resetting state if forked because logging might be using thread synchronization
// which might deadlock in forked child
return elasticApmEnterAgentCode( dbgCalledFromFile, dbgCalledFromLine, dbgCalledFromFunction );
}
bool elasticApmIsEnabled()
{
const bool result = getTracerCurrentConfigSnapshot( getGlobalTracer() )->enabled;
ELASTIC_APM_LOG_TRACE( "Result: %s", boolToString( result ) );
return result;
}
ResultCode elasticApmGetConfigOption( String optionName, zval* return_value )
{
ELASTIC_APM_LOG_TRACE_FUNCTION_ENTRY_MSG( "optionName: `%s'", optionName );
char txtOutStreamBuf[ELASTIC_APM_TEXT_OUTPUT_STREAM_ON_STACK_BUFFER_SIZE];
GetConfigManagerOptionValueByNameResult getOptValueByNameRes;
getOptValueByNameRes.txtOutStream = ELASTIC_APM_TEXT_OUTPUT_STREAM_FROM_STATIC_BUFFER( txtOutStreamBuf );
getOptValueByNameRes.streamedParsedValue = NULL;
const ResultCode resultCode = getConfigManagerOptionValueByName(
getGlobalTracer()->configManager
, optionName
, &getOptValueByNameRes );
if ( resultCode == resultSuccess ) *return_value = getOptValueByNameRes.parsedValueAsZval;
ELASTIC_APM_LOG_TRACE_RESULT_CODE_FUNCTION_EXIT_MSG( "Option's: name: `%s', value: %s", optionName, getOptValueByNameRes.streamedParsedValue );
return resultCode;
}
UInt elasticApmGetNumberOfDynamicConfigOptions()
{
ELASTIC_APM_LOG_TRACE_FUNCTION_ENTRY();
const ConfigManager* const cfgManager = getGlobalTracer()->configManager;
UInt result = 0;
ELASTIC_APM_FOR_EACH_OPTION_ID( optId )
{
GetConfigManagerOptionMetadataResult getMetaRes;
getConfigManagerOptionMetadata( cfgManager, optId, &getMetaRes );
if ( getMetaRes.isDynamic ) ++result;
}
ELASTIC_APM_LOG_TRACE_FUNCTION_EXIT_MSG( "result: %d", result );
return result;
}
enum { maxFunctionsToIntercept = numberedInterceptingCallbacksCount };
static uint32_t g_nextFreeFunctionToInterceptId = 0;
struct CallToInterceptData
{
zif_handler originalHandler;
zend_function* funcEntry;
};
typedef struct CallToInterceptData CallToInterceptData;
static CallToInterceptData g_functionsToInterceptData[maxFunctionsToIntercept];
static uint32_t g_interceptedCallInProgressRegistrationId = 0;
static
void internalFunctionCallInterceptingImpl( uint32_t interceptRegistrationId, zend_execute_data* execute_data, zval* return_value )
{
ResultCode resultCode;
// We SHOULD NOT log before resetting state if forked because logging might be using thread synchronization
// which might deadlock in forked child
ELASTIC_APM_CALL_IF_FAILED_GOTO( elasticApmEnterAgentCode( __FILE__, __LINE__, __FUNCTION__ ) );
ELASTIC_APM_LOG_TRACE_FUNCTION_ENTRY_MSG( "interceptRegistrationId: %u", interceptRegistrationId );
bool shouldCallPostHook;
if ( g_interceptedCallInProgressRegistrationId != 0 )
{
ELASTIC_APM_LOG_TRACE_FUNCTION_ENTRY_MSG(
"There's already an intercepted call in progress with interceptRegistrationId: %u."
"Nesting intercepted calls is not supported yet so invoking the original handler directly..."
, g_interceptedCallInProgressRegistrationId );
g_functionsToInterceptData[ interceptRegistrationId ].originalHandler( execute_data, return_value );
return;
}
g_interceptedCallInProgressRegistrationId = interceptRegistrationId;
shouldCallPostHook = tracerPhpPartInternalFuncCallPreHook( interceptRegistrationId, execute_data );
g_functionsToInterceptData[ interceptRegistrationId ].originalHandler( execute_data, return_value );
if ( shouldCallPostHook ) {
tracerPhpPartInternalFuncCallPostHook( interceptRegistrationId, return_value );
}
g_interceptedCallInProgressRegistrationId = 0;
ELASTIC_APM_LOG_TRACE_FUNCTION_EXIT_MSG( "interceptRegistrationId: %u", interceptRegistrationId );
resultCode = resultSuccess;
finally:
return;
failure:
goto finally;
}
void resetCallInterceptionOnRequestShutdown()
{
// We restore original handlers in the reverse order
// so that if the same function is registered for interception more than once
// the original handler will be restored correctly
ELASTIC_APM_FOR_EACH_BACKWARDS( i, g_nextFreeFunctionToInterceptId )
{
CallToInterceptData* data = &( g_functionsToInterceptData[ i ] );
data->funcEntry->internal_function.handler = data->originalHandler;
}
g_nextFreeFunctionToInterceptId = 0;
}
bool addToFunctionsToInterceptData( zend_function* funcEntry, uint32_t* interceptRegistrationId, zif_handler replacementFunc )
{
if ( g_nextFreeFunctionToInterceptId >= maxFunctionsToIntercept )
{
ELASTIC_APM_LOG_ERROR( "Reached maxFunctionsToIntercept."
" maxFunctionsToIntercept: %u. g_nextFreeFunctionToInterceptId: %u."
, maxFunctionsToIntercept, g_nextFreeFunctionToInterceptId );
return false;
}
*interceptRegistrationId = g_nextFreeFunctionToInterceptId ++;
g_functionsToInterceptData[ *interceptRegistrationId ].funcEntry = funcEntry;
g_functionsToInterceptData[ *interceptRegistrationId ].originalHandler = funcEntry->internal_function.handler;
funcEntry->internal_function.handler = ( replacementFunc == NULL ) ? g_numberedInterceptingCallback[ *interceptRegistrationId ] : replacementFunc;
return true;
}
ResultCode elasticApmInterceptCallsToInternalMethod( String className, String methodName, uint32_t* interceptRegistrationId )
{
ELASTIC_APM_LOG_DEBUG_FUNCTION_ENTRY_MSG( "className: `%s'; methodName: `%s'", className, methodName );
ResultCode resultCode;
zend_function *funcEntry;
auto classEntry = static_cast<zend_class_entry *>(zend_hash_str_find_ptr(CG(class_table), className, strlen(className)));
if (!classEntry) {
ELASTIC_APM_LOG_ERROR( "zend_hash_str_find_ptr( CG( class_table ), ... ) failed. className: `%s'", className );
ELASTIC_APM_SET_RESULT_CODE_AND_GOTO_FAILURE();
}
funcEntry = static_cast<zend_function *>(zend_hash_str_find_ptr(&classEntry->function_table, methodName, strlen(methodName)));
if (!funcEntry) {
ELASTIC_APM_LOG_ERROR( "zend_hash_str_find_ptr( &classEntry->function_table, ... ) failed."
" className: `%s'; methodName: `%s'", className, methodName );
ELASTIC_APM_SET_RESULT_CODE_AND_GOTO_FAILURE();
}
if ( ! addToFunctionsToInterceptData( funcEntry, interceptRegistrationId, /* replacementFunc */ NULL) )
{
ELASTIC_APM_SET_RESULT_CODE_AND_GOTO_FAILURE();
}
resultCode = resultSuccess;
finally:
ELASTIC_APM_LOG_DEBUG_RESULT_CODE_FUNCTION_EXIT();
return resultCode;
failure:
goto finally;
}
ResultCode elasticApmInterceptCallsToInternalFunctionEx( String functionName, uint32_t* interceptRegistrationId, zif_handler replacementFunc )
{
ELASTIC_APM_LOG_DEBUG_FUNCTION_ENTRY_MSG( "functionName: `%s'", functionName );
ResultCode resultCode;
auto funcEntry = static_cast<zend_function *>(zend_hash_str_find_ptr(CG(function_table), functionName, strlen(functionName)));
if (!funcEntry) {
ELASTIC_APM_LOG_ERROR( "zend_hash_str_find_ptr( CG( function_table ), ... ) failed."
" functionName: `%s'", functionName );
ELASTIC_APM_SET_RESULT_CODE_AND_GOTO_FAILURE();
}
if ( ! addToFunctionsToInterceptData( funcEntry, interceptRegistrationId, replacementFunc ) )
{
ELASTIC_APM_SET_RESULT_CODE_AND_GOTO_FAILURE();
}
resultCode = resultSuccess;
finally:
ELASTIC_APM_LOG_DEBUG_RESULT_CODE_FUNCTION_EXIT_MSG( "interceptRegistrationId: %u", *interceptRegistrationId );
return resultCode;
failure:
goto finally;
}
ResultCode elasticApmInterceptCallsToInternalFunction( String functionName, uint32_t* interceptRegistrationId)
{
return elasticApmInterceptCallsToInternalFunctionEx( functionName, interceptRegistrationId, /* replacementFunc */ NULL );
}
static inline bool longToBool( long longVal )
{
return longVal != 0;
}
ResultCode elasticApmSendToServer( StringView userAgentHttpHeader, StringView serializedEvents )
{
ELASTIC_APM_LOG_DEBUG_FUNCTION_ENTRY();
ResultCode resultCode;
Tracer* const tracer = getGlobalTracer();
ELASTIC_APM_CALL_IF_FAILED_GOTO( sendEventsToApmServer( getTracerCurrentConfigSnapshot( tracer ), userAgentHttpHeader, serializedEvents ) );
resultCode = resultSuccess;
finally:
ELASTIC_APM_LOG_DEBUG_RESULT_CODE_FUNCTION_EXIT();
return resultCode;
failure:
goto finally;
}