#include "php_xdebug.h"
#include "ext/standard/info.h"
#include "headers.h"
#include "lib_private.h"
#include "log.h"
#include "var.h"
#define DOCS_LINK_ICON "⊕"
char* xdebug_lib_docs_base(void)
{
char *env = getenv("XDEBUG_DOCS_BASE");
if (env) {
return env;
}
return (char*) "https://xdebug.org/docs/";
}
extern ZEND_DECLARE_MODULE_GLOBALS(xdebug);
const char *xdebug_level_msg_prefix[11] = {
"C", "E", "", "W", "", "", "", "I", "", "", "D"
};
const char *xdebug_log_prefix[11] = {
"CRIT:", "ERR: ", "", "WARN: ", "", "", "", "INFO: ", "", "", "DEBUG: "
};
const char *xdebug_log_prefix_emoji[11] = {
"☠", "🛑 ", "", "⚠️ ", "", "", "", "🛈 ", "", "", "• "
};
const char *xdebug_channel_msg_prefix[8] = {
"CFG-", "LOG-", "DBG-", "GC-", "PROF-", "TRACE-", "COV-", "BASE-"
};
const char *xdebug_channel_name[8] = {
"[Config] ", "[Log Files] ", "[Step Debug] ", "[GC Stats] ", "[Profiler] ", "[Tracing] ", "[Coverage] ", "[Base] "
};
static inline int xdebug_internal_log(int channel, int log_level, const char *message)
{
zend_ulong pid;
if (!XG_LIB(log_file)) {
return 0;
}
pid = xdebug_get_pid();
if (!XG_LIB(log_opened_message_sent) && XG_LIB(log_open_timestring)) {
XG_LIB(log_opened_message_sent) = 1;
fprintf(XG_LIB(log_file), "[" ZEND_ULONG_FMT "] Log opened at %s\n", pid, XG_LIB(log_open_timestring));
fflush(XG_LIB(log_file));
xdfree(XG_LIB(log_open_timestring));
XG_LIB(log_open_timestring) = NULL;
}
fprintf(
XG_LIB(log_file),
"[" ZEND_ULONG_FMT "] %s%s%s\n",
pid,
xdebug_channel_name[channel],
xdebug_log_prefix[log_level],
message
);
fflush(XG_LIB(log_file));
return 1;
}
#define TR_START ""
#define TR_END " |
\n"
static inline void xdebug_diagnostic_log(int channel, int log_level, const char *error_code, const char *message)
{
if (!XG_LIB(diagnosis_buffer) || log_level > XLOG_WARN) {
return;
}
if (sapi_module.phpinfo_as_text) {
xdebug_str_add(XG_LIB(diagnosis_buffer), xdebug_channel_name[channel], 0);
xdebug_str_add(XG_LIB(diagnosis_buffer), xdebug_log_prefix[log_level], 0);
xdebug_str_add(XG_LIB(diagnosis_buffer), message, 0);
} else {
xdebug_str_add_const(XG_LIB(diagnosis_buffer), "");
xdebug_str_add(XG_LIB(diagnosis_buffer), xdebug_log_prefix_emoji[log_level], 0);
xdebug_str_add_const(XG_LIB(diagnosis_buffer), " | ");
xdebug_str_add(XG_LIB(diagnosis_buffer), xdebug_channel_name[channel], 0);
xdebug_str_add(XG_LIB(diagnosis_buffer), message, 0);
xdebug_str_add_const(XG_LIB(diagnosis_buffer), " | " DOCS_LINK_ICON " |
");
}
xdebug_str_addc(XG_LIB(diagnosis_buffer), '\n');
}
static inline void xdebug_php_log(int channel, int log_level, const char *error_code, const char *message)
{
xdebug_str formatted_message = XDEBUG_STR_INITIALIZER;
if (log_level > XLOG_ERR) {
return;
}
xdebug_str_add_const(&formatted_message, "Xdebug: ");
xdebug_str_add(&formatted_message, xdebug_channel_name[channel], 0);
xdebug_str_add(&formatted_message, message, 0);
if (error_code && log_level == XLOG_CRIT) {
xdebug_str_add_const(&formatted_message, " (See: ");
xdebug_str_add(&formatted_message, xdebug_lib_docs_base(), 0);
xdebug_str_add_const(&formatted_message, "errors#");
xdebug_str_add(&formatted_message, xdebug_channel_msg_prefix[channel], 0);
xdebug_str_add(&formatted_message, xdebug_level_msg_prefix[log_level], 0);
xdebug_str_addc(&formatted_message, '-');
xdebug_str_add(&formatted_message, error_code, 0);
xdebug_str_addc(&formatted_message, ')');
}
php_log_err(formatted_message.d);
xdebug_str_destroy(&formatted_message);
}
void XDEBUG_ATTRIBUTE_FORMAT(printf, 4, 5) xdebug_log_ex(int channel, int log_level, const char *error_code, const char *fmt, ...)
{
xdebug_str message = XDEBUG_STR_INITIALIZER;
va_list argv;
int logged_to_xdebug_log = 0;
if (XINI_LIB(log_level) < log_level) {
return;
}
va_start(argv, fmt);
xdebug_str_add_va_fmt(&message, fmt, argv);
va_end(argv);
logged_to_xdebug_log = xdebug_internal_log(channel, log_level, message.d);
xdebug_diagnostic_log(channel, log_level, error_code, message.d);
if (!logged_to_xdebug_log || XINI_LIB(log_level) == XLOG_CRIT) {
xdebug_php_log(channel, log_level, error_code, message.d);
}
xdebug_str_destroy(&message);
}
static void log_filename_not_opened(int channel, const char *directory, const char *filename)
{
xdebug_str full_filename = XDEBUG_STR_INITIALIZER;
if (directory) {
xdebug_str_add(&full_filename, directory, 0);
if (!IS_SLASH(directory[strlen(directory) - 1])) {
xdebug_str_addc(&full_filename, DEFAULT_SLASH);
}
}
xdebug_str_add(&full_filename, filename, 0);
xdebug_log_ex(channel, XLOG_ERR, "OPEN", "File '%s' could not be opened.", full_filename.d);
xdebug_str_destroy(&full_filename);
}
void xdebug_log_diagnose_permissions(int channel, const char *directory, const char *filename)
{
#ifndef WIN32
struct stat dir_info;
#endif
log_filename_not_opened(channel, directory, filename);
#ifndef WIN32
if (!directory) {
return;
}
if (stat(directory, &dir_info) == -1) {
xdebug_log_ex(channel, XLOG_WARN, "STAT", "%s: %s", directory, strerror(errno));
return;
}
if (!S_ISDIR(dir_info.st_mode)) {
xdebug_log_ex(channel, XLOG_WARN, "NOTDIR", "The path '%s' is not a directory.", directory);
return;
}
xdebug_log_ex(channel, XLOG_WARN, "PERM", "The path '%s' has the permissions: 0%03o.", directory, dir_info.st_mode & 0777);
#endif
}
static int xdebug_info_printf(const char *fmt, ...) /* {{{ */
{
char *buf;
size_t len, written;
va_list argv;
va_start(argv, fmt);
len = vspprintf(&buf, 0, fmt, argv);
va_end(argv);
written = php_output_write(buf, len);
efree(buf);
return written;
}
/* }}} */
static void print_logo(void)
{
if (!sapi_module.phpinfo_as_text) {
PUTS("");
PUTS("");
PUTS(" |
\n");
} else {
PUTS("\33[1m__ __ _ _ \n"
"\33[1m\\ \\ / / | | | | \n"
"\33[1m \\ V / __| | ___| |__ _ _ __ _ \n"
"\33[1m > < / _` |/ _ \\ '_ \\| | | |/ _` |\n"
"\33[1m / . \\ (_| | __/ |_) | |_| | (_| |\n"
"\33[1m/_/ \\_\\__,_|\\___|_.__/ \\__,_|\\__, |\n"
"\33[1m __/ |\n"
"\33[1m |___/ \n\n\33[0m");
}
}
static void print_feature_row(const char *name, int flag, const char *doc_name)
{
if (!sapi_module.phpinfo_as_text) {
PUTS("");
PUTS("");
PUTS(name);
PUTS(" | ");
PUTS(XDEBUG_MODE_IS(flag) ? "✔ enabled" : "✘ disabled");
PUTS(" | " DOCS_LINK_ICON " |
\n");
} else {
php_info_print_table_row(2, name, XDEBUG_MODE_IS(flag) ? "✔ enabled" : "✘ disabled");
}
}
void xdebug_print_info(void)
{
/* Header block */
php_info_print_table_start();
print_logo();
php_info_print_table_row(2, "Version", XDEBUG_VERSION);
if (!sapi_module.phpinfo_as_text) {
xdebug_info_printf("%s |
\n", "Support Xdebug on Patreon, GitHub, or as a business");
} else {
xdebug_info_printf("Support Xdebug on Patreon, GitHub, or as a business: https://xdebug.org/support\n");
}
php_info_print_table_end();
/* Modes block */
php_info_print_table_start();
if (!sapi_module.phpinfo_as_text) {
php_info_print_table_colspan_header(
3,
(char*) (XG_LIB(mode_from_environment) ? "Enabled Features
(through 'XDEBUG_MODE' env variable)" : "Enabled Features
(through 'xdebug.mode' setting)")
);
} else {
php_info_print_table_colspan_header(
2,
(char*) (XG_LIB(mode_from_environment) ? "Enabled Features (through 'XDEBUG_MODE' env variable)" : "Enabled Features (through 'xdebug.mode' setting)")
);
}
if (!sapi_module.phpinfo_as_text) {
php_info_print_table_header(3, "Feature", "Enabled/Disabled", "Docs");
} else {
php_info_print_table_header(2, "Feature", "Enabled/Disabled");
}
print_feature_row("Development Helpers", XDEBUG_MODE_DEVELOP, "develop");
print_feature_row("Coverage", XDEBUG_MODE_COVERAGE, "code_coverage");
print_feature_row("GC Stats", XDEBUG_MODE_GCSTATS, "garbage_collection");
print_feature_row("Profiler", XDEBUG_MODE_PROFILING, "profiler");
print_feature_row("Step Debugger", XDEBUG_MODE_STEP_DEBUG, "remote");
print_feature_row("Tracing", XDEBUG_MODE_TRACING, "trace");
php_info_print_table_end();
/* Optional compiled in features */
php_info_print_table_start();
php_info_print_table_colspan_header(2, (char*) "Optional Features");
#if HAVE_XDEBUG_ZLIB
php_info_print_table_row(2, "Compressed File Support", "yes (gzip)");
#else
php_info_print_table_row(2, "Compressed File Support", "no");
#endif
#if WIN32
php_info_print_table_row(2, "Clock Source", XG_BASE(nanotime_context).use_rel_time ? "QueryPerformanceFrequency" : "GetSystemTimePreciseAsFileTime");
#else
# if HAVE_XDEBUG_CLOCK_GETTIME_NSEC_NP
php_info_print_table_row(2, "Clock Source", "clock_gettime_nsec_np");
# elif HAVE_XDEBUG_CLOCK_GETTIME
php_info_print_table_row(2, "Clock Source", "clock_gettime");
php_info_print_table_row(2, "TSC Clock Source", XG_BASE(working_tsc_clock) ? "available" : "unavailable");
# else
php_info_print_table_row(2, "Clock Source", "gettimeofday");
# endif
#endif
#if HAVE_LINUX_RTNETLINK_H
php_info_print_table_row(2, "'xdebug://gateway' pseudo-host support", "yes");
#else
php_info_print_table_row(2, "'xdebug://gateway' pseudo-host support", "no");
#endif
#if HAVE_RES_NINIT
php_info_print_table_row(2, "'xdebug://nameserver' pseudo-host support", "yes");
#else
php_info_print_table_row(2, "'xdebug://nameserver' pseudo-host support", "no");
#endif
if (XG_BASE(private_tmp)) {
php_info_print_table_row(2, "Systemd Private Temp Directory", XG_BASE(private_tmp));
} else {
php_info_print_table_row(2, "Systemd Private Temp Directory", "not enabled");
}
php_info_print_table_end();
}
PHPAPI extern char *php_ini_opened_path;
PHPAPI extern char *php_ini_scanned_path;
PHPAPI extern char *php_ini_scanned_files;
static void xdebug_print_php_section(void)
{
php_info_print_table_start();
php_info_print_table_colspan_header(2, (char*) "PHP");
php_info_print_table_colspan_header(2, (char*) "Build Configuration");
php_info_print_table_row(2, "Version (Run Time)", XG_BASE(php_version_run_time));
php_info_print_table_row(2, "Version (Compile Time)", XG_BASE(php_version_compile_time));
#if ZEND_DEBUG
php_info_print_table_row(2, "Debug Build", "yes");
#else
php_info_print_table_row(2, "Debug Build", "no");
#endif
#ifdef ZTS
php_info_print_table_row(2, "Thread Safety", "enabled");
php_info_print_table_row(2, "Thread API", tsrm_api_name());
#else
php_info_print_table_row(2, "Thread Safety", "disabled");
#endif
php_info_print_table_colspan_header(2, (char*) "Settings");
php_info_print_table_row(2, "Configuration File (php.ini) Path", PHP_CONFIG_FILE_PATH);
php_info_print_table_row(2, "Loaded Configuration File", php_ini_opened_path ? php_ini_opened_path : "(none)");
php_info_print_table_row(2, "Scan this dir for additional .ini files", php_ini_scanned_path ? php_ini_scanned_path : "(none)");
php_info_print_table_row(2, "Additional .ini files parsed", php_ini_scanned_files ? php_ini_scanned_files : "(none)");
php_info_print_table_end();
}
static ZEND_COLD void php_ini_displayer_cb(zend_ini_entry *ini_entry, int type)
{
if (ini_entry->displayer) {
ini_entry->displayer(ini_entry, type);
} else {
const char *display_string;
size_t display_string_length;
int esc_html=0;
if (type == ZEND_INI_DISPLAY_ORIG && ini_entry->modified) {
if (ini_entry->orig_value && ZSTR_VAL(ini_entry->orig_value)[0]) {
display_string = ZSTR_VAL(ini_entry->orig_value);
display_string_length = ZSTR_LEN(ini_entry->orig_value);
esc_html = !sapi_module.phpinfo_as_text;
} else {
if (!sapi_module.phpinfo_as_text) {
display_string = "no value";
display_string_length = sizeof("no value") - 1;
} else {
display_string = "no value";
display_string_length = sizeof("no value") - 1;
}
}
} else if (ini_entry->value && ZSTR_VAL(ini_entry->value)[0]) {
display_string = ZSTR_VAL(ini_entry->value);
display_string_length = ZSTR_LEN(ini_entry->value);
esc_html = !sapi_module.phpinfo_as_text;
} else {
if (!sapi_module.phpinfo_as_text) {
display_string = "no value";
display_string_length = sizeof("no value") - 1;
} else {
display_string = "no value";
display_string_length = sizeof("no value") - 1;
}
}
if (esc_html) {
zend_html_puts(display_string, display_string_length);
} else {
PHPWRITE(display_string, display_string_length);
}
}
}
static int if_overridden_xdebug_mode(char *name)
{
if ((strcmp("xdebug.mode", name) == 0) && XG_LIB(mode_from_environment)) {
return 1;
}
return 0;
}
static int is_using_private_tmp_directory(char *file_name)
{
if (!file_name) {
return 0;
}
return (XG_BASE(private_tmp) && (strstr(file_name, "/tmp") == file_name));
}
static const char* private_tmp_directory(char *file_name)
{
if (is_using_private_tmp_directory(file_name)) {
return XG_BASE(private_tmp);
}
return "";
}
static void xdebug_print_settings(void)
{
zend_module_entry *module;
zend_ini_entry *ini_entry;
int module_number;
zend_string *name = zend_string_init("xdebug", 6, 0);
module = zend_hash_find_ptr(&module_registry, name);
zend_string_release(name);
if (!module) {
return;
}
module_number = module->module_number;
php_info_print_table_start();
if (!sapi_module.phpinfo_as_text) {
php_info_print_table_header(4, "Directive", "Local Value", "Master Value", "Docs");
} else {
php_info_print_table_header(3, "Directive", "Local Value", "Master Value");
}
ZEND_HASH_FOREACH_PTR(EG(ini_directives), ini_entry) {
if (ini_entry->module_number != module_number) {
continue;
}
/* Hack to not show changed and removed settings */
if (ini_entry->value && strncmp(ZSTR_VAL(ini_entry->value), "This setting has", 16) == 0) {
continue;
}
if (!sapi_module.phpinfo_as_text) {
PUTS("");
PUTS("");
PHPWRITE(ZSTR_VAL(ini_entry->name), ZSTR_LEN(ini_entry->name));
if (if_overridden_xdebug_mode(ZSTR_VAL(ini_entry->name))) {
PUTS(" (through XDEBUG_MODE)");
}
PUTS(" | ");
if (if_overridden_xdebug_mode(ZSTR_VAL(ini_entry->name))) {
PUTS(getenv("XDEBUG_MODE"));
} else {
/* Hack for Systemd PrivateTmp */
if (
(
(strcmp(ZSTR_VAL(ini_entry->name), "xdebug.output_dir") == 0) ||
(strcmp(ZSTR_VAL(ini_entry->name), "xdebug.log") == 0)
) &&
(ini_entry->value && ZSTR_VAL(ini_entry->value)[0]) &&
is_using_private_tmp_directory(ZSTR_VAL(ini_entry->value))
) {
PUTS(XG_BASE(private_tmp));
}
/* Normal value */
php_ini_displayer_cb(ini_entry, ZEND_INI_DISPLAY_ACTIVE);
}
PUTS(" | ");
php_ini_displayer_cb(ini_entry, ZEND_INI_DISPLAY_ORIG);
PUTS(" | name), ZSTR_LEN(ini_entry->name));
PUTS("\">" DOCS_LINK_ICON " |
\n");
} else {
PHPWRITE(ZSTR_VAL(ini_entry->name), ZSTR_LEN(ini_entry->name));
if (if_overridden_xdebug_mode(ZSTR_VAL(ini_entry->name))) {
PUTS(" (through XDEBUG_MODE)");
}
PUTS(" => ");
if (if_overridden_xdebug_mode(ZSTR_VAL(ini_entry->name))) {
PUTS(getenv("XDEBUG_MODE"));
} else {
php_ini_displayer_cb(ini_entry, ZEND_INI_DISPLAY_ACTIVE);
}
PUTS(" => ");
php_ini_displayer_cb(ini_entry, ZEND_INI_DISPLAY_ORIG);
PUTS("\n");
}
} ZEND_HASH_FOREACH_END();
php_info_print_table_end();
}
static void print_html_header(void)
{
if (sapi_module.phpinfo_as_text) {
return;
}
PUTS("\n");
PUTS("");
PUTS("\n");
PUTS("\n");
PUTS("Xdebug ");
PUTS(XDEBUG_VERSION);
PUTS("");
PUTS("");
PUTS("\n");
PUTS("\n");
}
static void print_html_footer(void)
{
if (sapi_module.phpinfo_as_text) {
return;
}
php_output_write("
", strlen("