Skip to content

Commit

Permalink
Fixed exception handling (#1077) (#1078)
Browse files Browse the repository at this point in the history
  • Loading branch information
intuibase authored Nov 6, 2023
1 parent 3430276 commit f874c1f
Show file tree
Hide file tree
Showing 8 changed files with 182 additions and 278 deletions.
52 changes: 51 additions & 1 deletion agent/native/ext/Hooking.cpp
Original file line number Diff line number Diff line change
@@ -1,14 +1,63 @@

#include "Hooking.h"

#include <string_view>
#include <Zend/zend_API.h>

#include "php_elastic_apm.h"

#include "PhpBridge.h"
#include "PhpErrorData.h"

#include <memory>
#include <string_view>

namespace elasticapm::php {

#if PHP_VERSION_ID < 80000
void elastic_apm_error_cb(int type, const char *error_filename, const Hooking::zend_error_cb_lineno_t error_lineno, const char *format, va_list args) { //<8.0
#elif PHP_VERSION_ID < 80100
void elastic_apm_error_cb(int type, const char *error_filename, const uint32_t error_lineno, zend_string *message) { // 8.0
#else
void elastic_apm_error_cb(int type, zend_string *error_filename, const uint32_t error_lineno, zend_string *message) { // 8.1+
#endif
using namespace std::string_view_literals;

if (ELASTICAPM_G(captureErrors)) {
#if PHP_VERSION_ID < 80000
char * message = nullptr;
va_list messageArgsCopy;
va_copy(messageArgsCopy, args);
vspprintf(/* out */ &message, 0, format, messageArgsCopy); // vspprintf allocates memory for the resulted string buffer and it needs to be freed with efree()
va_end(messageArgsCopy);

ELASTICAPM_G(lastErrorData) = std::make_unique<elasticapm::php::PhpErrorData>(type, error_filename ? error_filename : ""sv, error_lineno, message ? message : ""sv);

if (message) {
efree(message);
}
#elif PHP_VERSION_ID < 80100
ELASTICAPM_G(lastErrorData) = std::make_unique<elasticapm::php::PhpErrorData>(type, error_filename ? error_filename : ""sv, error_lineno, message ? std::string_view{ZSTR_VAL(message), ZSTR_LEN(message)} : ""sv);
#else
ELASTICAPM_G(lastErrorData) = nullptr;
ELASTICAPM_G(lastErrorData) = std::make_unique<elasticapm::php::PhpErrorData>(type, error_filename ? std::string_view{ZSTR_VAL(error_filename), ZSTR_LEN(error_filename)} : ""sv, error_lineno, message ? std::string_view{ZSTR_VAL(message), ZSTR_LEN(message)} : ""sv);
#endif
}

auto original = Hooking::getInstance().getOriginalZendErrorCb();
if (original == elastic_apm_error_cb) {
ELASTIC_APM_LOG_DIRECT_CRITICAL("originalZendErrorCallback == elasticApmZendErrorCallback dead loop detected");
return;
}

if (original) {
#if PHP_VERSION_ID < 80000
original(type, error_filename, error_lineno, format, args);
#else
original(type, error_filename, error_lineno, message);
#endif
}
}

static void elastic_execute_internal(INTERNAL_FUNCTION_PARAMETERS) {

zend_try {
Expand Down Expand Up @@ -42,6 +91,7 @@ static void elastic_interrupt_function(zend_execute_data *execute_data) {
void Hooking::replaceHooks() {
zend_execute_internal = elastic_execute_internal;
zend_interrupt_function = elastic_interrupt_function;
zend_error_cb = elastic_apm_error_cb;
}

}
29 changes: 26 additions & 3 deletions agent/native/ext/Hooking.h
Original file line number Diff line number Diff line change
@@ -1,12 +1,30 @@
#pragma once

#include <main/php_version.h>
#include <Zend/zend_execute.h>
#include <Zend/zend_types.h>

#include <optional>

namespace elasticapm::php {

class Hooking {
public:

#if PHP_VERSION_ID < 70400
using zend_error_cb_lineno_t = uint;
#else
using zend_error_cb_lineno_t = uint32_t;
#endif

#if PHP_VERSION_ID < 80000
using zend_error_cb_t = void (*)(int type, const char *error_filename, const zend_error_cb_lineno_t error_lineno, const char *format, va_list args); //<8.0
#elif PHP_VERSION_ID < 80100
using zend_error_cb_t = void (*)(int type, const char *error_filename, const uint32_t error_lineno, zend_string *message); // 8.0
#else
using zend_error_cb_t = void (*)(int type, zend_string *error_filename, const uint32_t error_lineno, zend_string *message); // 8.1+
#endif

using zend_execute_internal_t = void (*)(zend_execute_data *execute_data, zval *return_value);
using zend_interrupt_function_t = void (*)(zend_execute_data *execute_data);

Expand All @@ -19,16 +37,17 @@ class Hooking {
void fetchOriginalHooks() {
original_execute_internal_ = zend_execute_internal;
original_zend_interrupt_function_ = zend_interrupt_function;
original_zend_error_cb_ = zend_error_cb;
}

void restoreOriginalHooks() {
zend_execute_internal = original_execute_internal_;
zend_interrupt_function = original_zend_interrupt_function_;
zend_error_cb = original_zend_error_cb_;
}

void replaceHooks();


zend_execute_internal_t getOriginalExecuteInternal() {
return original_execute_internal_;
}
Expand All @@ -37,15 +56,19 @@ class Hooking {
return original_zend_interrupt_function_;
}

zend_error_cb_t getOriginalZendErrorCb() {
return original_zend_error_cb_;
}


private:
Hooking(Hooking const &) = delete;
void operator=(Hooking const &) = delete;
Hooking() = default;



zend_execute_internal_t original_execute_internal_ = nullptr;
zend_interrupt_function_t original_zend_interrupt_function_ = nullptr;
zend_error_cb_t original_zend_error_cb_ = nullptr;
};


Expand Down
43 changes: 43 additions & 0 deletions agent/native/ext/PhpErrorData.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@

#include "PhpErrorData.h"

#include <main/php.h>
#include <Zend/zend_alloc.h>
#include <Zend/zend_builtin_functions.h>
#include <Zend/zend_types.h>
#include <Zend/zend_variables.h>

#include <string>
#include <string_view>

namespace elasticapm::php {
PhpErrorData::PhpErrorData(int type, std::string_view fileName, uint32_t lineNumber, std::string_view message) : type_(type), fileName_(fileName), lineNumber_(lineNumber), message_(message) {
ZVAL_UNDEF(&stackTrace_);
zend_fetch_debug_backtrace(&stackTrace_, /* skip_last */ 0, /* options */ 0, /* limit */ 0);
}

PhpErrorData::~PhpErrorData() {
zval_ptr_dtor(&stackTrace_);
}

int PhpErrorData::getType() const {
return type_;
}

std::string_view PhpErrorData::getFileName() const {
return fileName_;
}

int PhpErrorData::getLineNumber() const {
return lineNumber_;
}

std::string_view PhpErrorData::getMessage() const {
return message_;
}

zval *PhpErrorData::getStackTrace() {
return &stackTrace_;
}

}
28 changes: 28 additions & 0 deletions agent/native/ext/PhpErrorData.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#pragma once

#include <Zend/zend_types.h>
#include <string>
#include <string_view>

namespace elasticapm::php {

class PhpErrorData {
public:
PhpErrorData(int type, std::string_view fileName, uint32_t lineNumber, std::string_view message);
~PhpErrorData();

int getType() const;
std::string_view getFileName() const;
int getLineNumber() const;
std::string_view getMessage() const;
zval *getStackTrace();

private:
int type_ = -1;
std::string fileName_;
uint32_t lineNumber_ = 0;
std::string message_;
zval stackTrace_;
};

}
8 changes: 8 additions & 0 deletions agent/native/ext/elastic_apm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -283,13 +283,21 @@ static PHP_GINIT_FUNCTION(elastic_apm)
}

ZVAL_UNDEF(&elastic_apm_globals->lastException);
new (&elastic_apm_globals->lastErrorData) std::unique_ptr<elasticapm::php::PhpErrorData>;
elastic_apm_globals->captureErrors = false;
}

static PHP_GSHUTDOWN_FUNCTION(elastic_apm) {
ELASTIC_APM_LOG_DIRECT_DEBUG( "%s: GSHUTDOWN called; parent PID: %d", __FUNCTION__, (int)getParentProcessId() );
if (elastic_apm_globals->globals) {
delete elastic_apm_globals->globals;
}

if (elastic_apm_globals->lastErrorData) {
ELASTIC_APM_LOG_DIRECT_WARNING( "%s: still holding error", __FUNCTION__);
// we need to relese any dangling php error data beacause it is already freed (it was allocated in request pool)
elastic_apm_globals->lastErrorData.release();
}
}

PHP_MINIT_FUNCTION(elastic_apm)
Expand Down
Loading

0 comments on commit f874c1f

Please sign in to comment.