prod/native/loader/code/loader.cpp (142 lines of code) (raw):
/*
* 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 "phpdetection.h"
#include <dlfcn.h>
#include <stddef.h>
#include <stdio.h>
#include <syslog.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <filesystem>
#include "elastic_otel_version.h"
#define LOG_TO_SYSLOG_AND_STDERR(fmt, ... ) \
do { \
fprintf(stderr, \
"[elastic_otel_loader][PID: %d]" \
"[TID: %d] " \
fmt \
,(int)getpid() \
,(int)syscall( SYS_gettid ) \
, ##__VA_ARGS__ ); \
syslog( \
LOG_WARNING, \
"[elastic_otel_loader][PID: %d]" \
"[TID: %d] " \
fmt \
, (int)(getpid()) \
, (int)(syscall( SYS_gettid )) \
, ##__VA_ARGS__ ); \
} \
while(0);
namespace elasticapm::loader {
namespace phpdata {
#define INIT_FUNC_ARGS int type, int module_number
#define INIT_FUNC_ARGS_PASSTHRU type, module_number
#define SHUTDOWN_FUNC_ARGS int type, int module_number
#define SHUTDOWN_FUNC_ARGS_PASSTHRU type, module_number
#define ZEND_MODULE_INFO_FUNC_ARGS zend_module_entry *zend_module
#define ZEND_MODULE_INFO_FUNC_ARGS_PASSTHRU zend_module
struct zend_module_entry {
unsigned short size;
unsigned int zend_api;
unsigned char zend_debug;
unsigned char zts;
const void *ini_entry;
const void *deps;
const char *name;
const void *functions;
int (*module_startup_func)(INIT_FUNC_ARGS);
int (*module_shutdown_func)(SHUTDOWN_FUNC_ARGS);
int (*request_startup_func)(INIT_FUNC_ARGS);
int (*request_shutdown_func)(SHUTDOWN_FUNC_ARGS);
void (*info_func)(ZEND_MODULE_INFO_FUNC_ARGS);
const char *version;
size_t globals_size;
#ifdef ZTS
ts_rsrc_id* globals_id_ptr;
#else
void* globals_ptr;
#endif
void (*globals_ctor)(void *global);
void (*globals_dtor)(void *global);
int (*post_deactivate_func)(void);
int module_started;
unsigned char type;
void *handle;
int module_number;
const char *build_id;
};
}
}
elasticapm::loader::phpdata::zend_module_entry elastic_otel_loader_module_entry = {
sizeof(elasticapm::loader::phpdata::zend_module_entry),
0, // API, f.ex.20220829
0, // DEBUG
0, // USING_ZTS
nullptr,
nullptr,
"elastic_otel_apm_loader", /* Extension name */
nullptr, /* zend_function_entry */
nullptr, /* PHP_MINIT - Module initialization */
nullptr, /* PHP_MSHUTDOWN - Module shutdown */
nullptr, /* PHP_RINIT - Request initialization */
nullptr, /* PHP_RSHUTDOWN - Request shutdown */
nullptr, /* PHP_MINFO - Module info */
ELASTIC_OTEL_VERSION, /* Version */
0, /* globals_size */
nullptr, /* globals ptr */
nullptr, /* PHP_GINIT */
nullptr, /* PHP_GSHUTDOWN */
nullptr, /* post deactivate */
0,
0,
nullptr,
0,
"" // API20220829,NTS ..
};
extern "C" {
__attribute__ ((visibility("default"))) elasticapm::loader::phpdata::zend_module_entry *get_module(void) {
using namespace std::string_view_literals;
using namespace std::string_literals;
auto zendVersion = elasticapm::loader::getMajorMinorZendVersion();
if (zendVersion.empty()) {
LOG_TO_SYSLOG_AND_STDERR( "Can't find Zend/PHP Engine version\n");
return &elastic_otel_loader_module_entry;
}
auto [zendEngineVersion, phpVersion, zendModuleApiVersion, isVersionSupported] = elasticapm::loader::getZendModuleApiVersion(zendVersion);
bool isThreadSafe = elasticapm::loader::isThreadSafe();
static std::string zendBuildId{"API"s};
zendBuildId.append(std::to_string(zendModuleApiVersion));
zendBuildId.append(isThreadSafe ? ",TS"sv : ",NTS"sv);
elastic_otel_loader_module_entry.zend_api = zendModuleApiVersion;
elastic_otel_loader_module_entry.build_id = zendBuildId.c_str();
elastic_otel_loader_module_entry.zts = isThreadSafe;
if (!isVersionSupported) {
LOG_TO_SYSLOG_AND_STDERR("Zend Engine version %s is not supported by Elastic Distribution for OpenTelemetry PHP\n", std::string(zendVersion).c_str());
return &elastic_otel_loader_module_entry;
}
if (isThreadSafe) {
LOG_TO_SYSLOG_AND_STDERR("Thread Safe mode (ZTS) is not supported by Elastic Distribution for OpenTelemetry PHP\n");
return &elastic_otel_loader_module_entry; // unsupported thread safe mode
}
// get path to libraries
Dl_info dl_info;
dladdr((void *)get_module, &dl_info);
if (!dl_info.dli_fname) {
LOG_TO_SYSLOG_AND_STDERR("Unable to resolve path to Elastic Distribution for OpenTelemetry PHP libraries\n");
return &elastic_otel_loader_module_entry;
}
auto elasticAgentPath = std::filesystem::path(dl_info.dli_fname).parent_path();
auto agentLibrary = (elasticAgentPath/"elastic_otel_php_"sv);
agentLibrary += std::to_string(phpVersion);
agentLibrary += ".so"sv;
void *agentHandle = dlopen(agentLibrary.c_str(), RTLD_LAZY | RTLD_GLOBAL);
if (!agentHandle) {
LOG_TO_SYSLOG_AND_STDERR( "Unable to load agent library from path: %s\n", agentLibrary.c_str());
return &elastic_otel_loader_module_entry;
}
auto agentGetModule = reinterpret_cast<elasticapm::loader::phpdata::zend_module_entry *(*)(void)>(dlsym(agentHandle, "get_module"));
if (!agentGetModule) {
LOG_TO_SYSLOG_AND_STDERR( "Unable to resolve agent entry point from library: %s\n", agentLibrary.c_str());
return &elastic_otel_loader_module_entry;
}
return agentGetModule(); // or we can call zend_register_module_ex(agentGetModule())) and have both fully loaded
}
}