// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. /*++ Module Name: misc/dbgmsg.cpp Abstract: Implementation of Debug Message utilies. Relay channel information, output functions, etc. --*/ /* PAL headers */ #include "pal/thread.hpp" #include "pal/malloc.hpp" #include "pal/file.hpp" #include "config.h" #include "pal/dbgmsg.h" #include "pal/cruntime.h" #include "pal/critsect.h" #include "pal/file.h" #include "pal/environ.h" /* standard headers */ #include #include #include #include /* for pthread_self */ #include #include #include /* needs to be included after "palinternal.h" to avoid name collision for va_start and va_end */ #include using namespace CorUnix; /* append mode file I/O is safer */ #define _PAL_APPEND_DBG_OUTPUT_ #if defined(_PAL_APPEND_DBG_OUTPUT_) static const char FOPEN_FLAGS[] = "at"; #else static const char FOPEN_FLAGS[] = "wt"; #endif /* number of ENTRY nesting levels to indicate with a '.' */ #define MAX_NESTING 50 /* size of output buffer (arbitrary) */ #define DBG_BUFFER_SIZE 20000 /* global and static variables */ LPCWSTR W16_NULLSTRING = (LPCWSTR) "N\0U\0L\0L\0\0"; DWORD dbg_channel_flags[DCI_LAST]; BOOL g_Dbg_asserts_enabled; /* we must use stdio functions directly rather that rely on PAL functions for output, because those functions do tracing and we need to avoid recursion */ FILE *output_file = NULL; /* master switch for debug channel enablement, to be modified by debugger */ Volatile dbg_master_switch = TRUE; static const char *dbg_channel_names[]= { "PAL", "LOADER", "HANDLE", "SHMEM", "PROCESS", "THREAD", "EXCEPT", "CRT", "UNICODE", "ARCH", "SYNC", "FILE", "VIRTUAL", "MEM", "SOCKET", "DEBUG", "LOCALE", "MISC", "MUTEX", "CRITSEC", "POLL", "CRYPT", "SHFOLDER" #ifdef FEATURE_PAL_SXS , "SXS" #endif // FEATURE_PAL_SXS }; static const char *dbg_level_names[]= { "ENTRY", "TRACE", "WARN", "ERROR", "ASSERT", "EXIT" }; static const char ENV_FILE[]="PAL_API_TRACING"; static const char ENV_CHANNELS[]="PAL_DBG_CHANNELS"; static const char ENV_ASSERTS[]="PAL_DISABLE_ASSERTS"; static const char ENV_ENTRY_LEVELS[]="PAL_API_LEVELS"; /* per-thread storage for ENTRY tracing level */ static pthread_key_t entry_level_key; /* entry level limitation */ static int max_entry_level; /* character to use for ENTRY indentation */ static const char INDENT_CHAR = '.'; static BOOL DBG_get_indent(DBG_LEVEL_ID level, const char *format, char *indent_string); static CRITICAL_SECTION fprintf_crit_section; /* Function definitions */ /*++ Function : DBG_init_channels Parse environment variables PAL_DBG_CHANNELS and PAL_API_TRACING for debug channel settings; initialize static variables. (no parameters, no return value) --*/ BOOL DBG_init_channels(void) { INT i; LPSTR env_string; LPSTR env_workstring; LPSTR env_pcache; LPSTR entry_ptr; LPSTR level_ptr; CHAR plus_or_minus; DWORD flag_mask = 0; int ret; InternalInitializeCriticalSection(&fprintf_crit_section); /* output only asserts by default [only affects no-vararg-support case; if we have varargs, these flags aren't even checked for ASSERTs] */ for(i=0;i DBG_BUFFER_SIZE) { fprintf(stderr, "ERROR : buffer overflow in DBG_printf_gcc"); return 1; } buffer_ptr=buffer+output_size; } else { buffer_ptr = buffer; output_size = 0; } va_start(args, format); output_size+=_vsnprintf_s(buffer_ptr, DBG_BUFFER_SIZE-output_size, _TRUNCATE, format, args); va_end(args); if( output_size > DBG_BUFFER_SIZE ) { fprintf(stderr, "ERROR : buffer overflow in DBG_printf_gcc"); } /* Use a Critical section before calling printf code to avoid holding a libc lock while another thread is calling SuspendThread on this one. */ InternalEnterCriticalSection(pthrCurrent, &fprintf_crit_section); fprintf( output_file, "%s%s", indent, buffer ); InternalLeaveCriticalSection(pthrCurrent, &fprintf_crit_section); /* flush the output to file */ if ( fflush(output_file) != 0 ) { fprintf(stderr, "ERROR : fflush() failed errno:%d (%s)\n", errno, strerror(errno)); } // Some systems support displaying a GUI dialog. We attempt this only for asserts. if ( level == DLI_ASSERT ) PAL_DisplayDialog("PAL ASSERT", buffer); if ( old_errno != errno ) { fprintf( stderr,"ERROR: errno changed by DBG_printf_gcc\n" ); errno = old_errno; } return 1; } /*++ Function : DBG_printf_c99 Internal function for debug channels; don't use. This function outputs a complete debug message, without function name. Parameters : DBG_CHANNEL_ID channel : debug channel to use DBG_LEVEL_ID level : debug message level BOOL bHeader : whether or not to output message header (thread id, etc) LPSTR file : current file INT line : line number LPSTR format, ... : standard printf parameter list. Return Value : always 1. Notes : This version is for compilers that support the C99 flavor of variable-argument macros but not the gnu flavor, and do not support the __FUNCTION__ pseudo-macro. --*/ int DBG_printf_c99(DBG_CHANNEL_ID channel, DBG_LEVEL_ID level, BOOL bHeader, LPSTR file, INT line, LPSTR format, ...) { CHAR *buffer = (CHAR*)alloca(DBG_BUFFER_SIZE); CHAR indent[MAX_NESTING+1]; LPSTR buffer_ptr; INT output_size; va_list args; static INT call_count=0; void *thread_id; int old_errno = 0; CPalThread *pthrCurrent = InternalGetCurrentThread(); old_errno = errno; if(!DBG_get_indent(level, format, indent)) { return 1; } thread_id= (void *)THREADSilentGetCurrentThreadId(); if(bHeader) { output_size=snprintf(buffer, DBG_BUFFER_SIZE, "{%p" MODULE_FORMAT "} %-5s [%-7s] at %s.%d: ", thread_id, MODULE_ID dbg_level_names[level], dbg_channel_names[channel], file, line); if(output_size + 1 > DBG_BUFFER_SIZE) { fprintf(stderr, "ERROR : buffer overflow in DBG_printf_gcc"); return 1; } buffer_ptr=buffer+output_size; } else { output_size = 0; buffer_ptr = buffer; } va_start(args, format); output_size+=_vsnprintf_s(buffer_ptr, DBG_BUFFER_SIZE-output_size, _TRUNCATE, format, args); va_end(args); if(output_size>DBG_BUFFER_SIZE) fprintf(stderr, "ERROR : buffer overflow in DBG_printf_c99"); /* Use a Critical section before calling printf code to avoid holding a libc lock while another thread is calling SuspendThread on this one. */ InternalEnterCriticalSection(pthrCurrent, &fprintf_crit_section); fprintf( output_file, "%s", buffer ); InternalLeaveCriticalSection(pthrCurrent, &fprintf_crit_section); /* flush the output to file every once in a while */ call_count++; if(call_count>5) { call_count=0; if ( fflush(output_file) != 0 ) { fprintf(stderr, "ERROR : fflush() failed errno:%d (%s)\n", errno, strerror(errno)); } } if ( old_errno != errno ) { fprintf( stderr, "ERROR: DBG_printf_c99 changed the errno.\n" ); errno = old_errno; } return 1; } /*++ Function : DBG_get_indent generate an indentation string to be used for message output Parameters : DBG_LEVEL_ID level : level of message (DLI_ENTRY, etc) const char *format : printf format string of message char *indent_string : destination for indentation string Return value : TRUE if output can proceed, FALSE otherwise Notes: As a side-effect, this function updates the ENTRY nesting level for the current thread : it decrements it if 'format' contains the string 'return', increments it otherwise (but only if 'level' is DLI_ENTRY). The function will return FALSE if the current nesting level is beyond our treshold (max_nesting_level); it always returns TRUE for other message levels --*/ static BOOL DBG_get_indent(DBG_LEVEL_ID level, const char *format, char *indent_string) { int ret; /* determine whether to output an ENTRY line */ if(DLI_ENTRY == level||DLI_EXIT == level) { if(0 != max_entry_level) { INT_PTR nesting; /* Determine if this is an entry or an exit */ if(DLI_EXIT == level) { nesting = (INT_PTR) pthread_getspecific(entry_level_key); /* avoid going negative */ if(nesting != 0) { nesting--; if ((ret = pthread_setspecific(entry_level_key, (LPVOID)nesting)) != 0) { fprintf(stderr, "ERROR : pthread_setspecific() failed " "error:%d (%s)\n", ret, strerror(ret)); } } } else { nesting = (INT_PTR) pthread_getspecific(entry_level_key); if ((ret = pthread_setspecific(entry_level_key, (LPVOID)(nesting+1))) != 0) { fprintf(stderr, "ERROR : pthread_setspecific() failed " "error:%d (%s)\n", ret, strerror(ret)); } } /* see if we're past the level treshold */ if(nesting >= max_entry_level) { return FALSE; } /* generate indentation string */ if(MAX_NESTING < nesting) { nesting = MAX_NESTING; } memset(indent_string,INDENT_CHAR ,nesting); indent_string[nesting] = '\0'; } else { indent_string[0] = '\0'; } } else { indent_string[0] = '\0'; } return TRUE; } /*++ Function : DBG_change_entrylevel retrieve current ENTRY nesting level and [optionnally] modify it Parameters : int new_level : value to which the nesting level must be set, or -1 Return value : nesting level at the time the function was called Notes: if new_level is -1, the nesting level will not be modified --*/ int DBG_change_entrylevel(int new_level) { int old_level; int ret; if(0 == max_entry_level) { return 0; } old_level = PtrToInt(pthread_getspecific(entry_level_key)); if(-1 != new_level) { if ((ret = pthread_setspecific(entry_level_key,(LPVOID)(IntToPtr(new_level)))) != 0) { fprintf(stderr, "ERROR : pthread_setspecific() failed " "error:%d (%s)\n", ret, strerror(ret)); } } return old_level; } #if _DEBUG && defined(__APPLE__) /*++ Function: DBG_ShouldCheckStackAlignment Wires up stack alignment checks (debug builds only) --*/ static const char * PAL_CHECK_ALIGNMENT_MODE = "PAL_CheckAlignmentMode"; enum CheckAlignmentMode { // special value to indicate we've not initialized yet CheckAlignment_Uninitialized = -1, CheckAlignment_Off = 0, CheckAlignment_On = 1, CheckAlignment_Default = CheckAlignment_On }; bool DBG_ShouldCheckStackAlignment() { static CheckAlignmentMode caMode = CheckAlignment_Uninitialized; if (caMode == CheckAlignment_Uninitialized) { char* checkAlignmentSettings; bool shouldFreeCheckAlignmentSettings = false; if (palEnvironment == nullptr) { // This function might be called before the PAL environment is initialized. // In this case, use the system getenv instead. checkAlignmentSettings = ::getenv(PAL_CHECK_ALIGNMENT_MODE); } else { checkAlignmentSettings = EnvironGetenv(PAL_CHECK_ALIGNMENT_MODE); shouldFreeCheckAlignmentSettings = true; } caMode = checkAlignmentSettings ? (CheckAlignmentMode)atoi(checkAlignmentSettings) : CheckAlignment_Default; if (checkAlignmentSettings && shouldFreeCheckAlignmentSettings) { free(checkAlignmentSettings); } } return caMode == CheckAlignment_On; } #endif // _DEBUG && __APPLE__ #ifdef __APPLE__ #include "CoreFoundation/CFUserNotification.h" #include "CoreFoundation/CFString.h" #include "Security/AuthSession.h" static const char * PAL_DISPLAY_DIALOG = "PAL_DisplayDialog"; enum DisplayDialogMode { DisplayDialog_Uninitialized = -1, DisplayDialog_Suppress = 0, DisplayDialog_Show = 1, DisplayDialog_Default = DisplayDialog_Suppress, }; /*++ Function : PAL_DisplayDialog Display a simple modal dialog with an alert icon and a single OK button. Caller supplies the title of the dialog and the main text. The dialog is displayed only if the PAL_DisplayDialog environment variable is set to the value "1" and the session has access to the display. --*/ void PAL_DisplayDialog(const char *szTitle, const char *szText) { static DisplayDialogMode dispDialog = DisplayDialog_Uninitialized; if (dispDialog == DisplayDialog_Uninitialized) { char* displayDialog = EnvironGetenv(PAL_DISPLAY_DIALOG); if (displayDialog) { int i = atoi(displayDialog); free(displayDialog); switch (i) { case 0: dispDialog = DisplayDialog_Suppress; break; case 1: dispDialog = DisplayDialog_Show; break; default: // Asserting here would just be re-entrant. :/ dispDialog = DisplayDialog_Default; break; } } else dispDialog = DisplayDialog_Default; if (dispDialog == DisplayDialog_Show) { // We may not be allowed to show. OSStatus osstatus; SecuritySessionId secSession; SessionAttributeBits secSessionInfo; osstatus = SessionGetInfo(callerSecuritySession, &secSession, &secSessionInfo); if (noErr != osstatus || (secSessionInfo & sessionHasGraphicAccess) == 0) dispDialog = DisplayDialog_Suppress; } } if (dispDialog == DisplayDialog_Suppress) return; CFStringRef cfsTitle = CFStringCreateWithCString(kCFAllocatorDefault, szTitle, kCFStringEncodingUTF8); if (cfsTitle != NULL) { CFStringRef cfsText = CFStringCreateWithCString(kCFAllocatorDefault, szText, kCFStringEncodingUTF8); if (cfsText != NULL) { CFOptionFlags response; CFUserNotificationDisplayAlert(0, // Never time-out, wait for user to hit 'OK' 0, // No flags NULL, // Default icon NULL, // Default sound NULL, // No-localization support for text cfsTitle, // Title for dialog cfsText, // The actual alert text NULL, // Default default button title ('OK') NULL, // No alternate button NULL, // No third button &response); // User's response (discarded) CFRelease(cfsText); } CFRelease(cfsTitle); } } /*++ Function : PAL_DisplayDialogFormatted As above but takes a printf-style format string and insertion values to form the main text. --*/ void PAL_DisplayDialogFormatted(const char *szTitle, const char *szTextFormat, ...) { va_list args; va_start(args, szTextFormat); const int cchBuffer = 4096; char *szBuffer = (char*)alloca(cchBuffer); _vsnprintf_s(szBuffer, cchBuffer, _TRUNCATE, szTextFormat, args); PAL_DisplayDialog(szTitle, szBuffer); va_end(args); } #endif // __APPLE__