blob: c30e2f1b513362c909013dfa7598af8b608be3a3 [file]
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license 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/misc.h"
/* standard headers */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h> /* for pthread_self */
#include <errno.h>
#include <dirent.h>
#include <dlfcn.h>
/* <stdarg.h> needs to be included after "palinternal.h" to avoid name
collision for va_start and va_end */
#include <stdarg.h>
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<BOOL> dbg_master_switch PAL_GLOBAL = TRUE;
static const char *dbg_channel_names[]=
{
"PAL",
"LOADER",
"HANDLE",
"SHMEM",
"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 PAL_GLOBAL;
/* 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;
LPCSTR 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<DCI_LAST;i++)
dbg_channel_flags[i]=1<<DLI_ASSERT;
/* parse PAL_DBG_CHANNELS environment variable */
if (!(env_string = MiscGetenv(ENV_CHANNELS)))
{
env_pcache = env_workstring = NULL;
}
else
{
env_pcache = env_workstring = PAL__strdup(env_string);
if (env_workstring == NULL)
{
/* Not enough memory */
DeleteCriticalSection(&fprintf_crit_section);
return FALSE;
}
}
while(env_workstring)
{
entry_ptr=env_workstring;
/* find beginning of next entry */
while((*entry_ptr != '\0') &&(*entry_ptr != '+') && (*entry_ptr != '-'))
{
entry_ptr++;
}
/* break if end of string is reached */
if(*entry_ptr == '\0')
{
break;
}
plus_or_minus=*entry_ptr++;
/* find end of entry; if strchr returns NULL, we have reached the end
of the string and we will leave the loop at the end of this pass. */
env_workstring=strchr(entry_ptr,':');
/* NULL-terminate entry, make env_string point to rest of string */
if(env_workstring)
{
*env_workstring++='\0';
}
/* find period that separates channel name from level name */
level_ptr=strchr(entry_ptr,'.');
/* an entry with no period is illegal : ignore it */
if(!level_ptr)
{
continue;
}
/* NULL-terminate channel name, make level_ptr point to the level name */
*level_ptr++='\0';
/* build the flag mask based on requested level */
/* if "all" level is specified, we want to open/close all levels at
once, so mask is either all ones or all zeroes */
if(!strcmp(level_ptr,"all"))
{
if(plus_or_minus=='+')
{
flag_mask=0xFFFF; /* OR this to open all levels */
}
else
{
flag_mask=0; /* AND this to close all levels*/
}
}
else
{
for(i=0;i<DLI_LAST;i++)
{
if(!strcmp(level_ptr,dbg_level_names[i]))
{
if(plus_or_minus=='+')
{
flag_mask=1<<i; /* OR this to open the level */
}
else
{
flag_mask=~(1<<i); /* AND this to close the level */
}
break;
}
}
/* didn't find a matching level : skip it. */
if(i==DLI_LAST)
{
continue;
}
}
/* Set EXIT and ENTRY channels to be identical */
if(!(flag_mask & (1<<DLI_ENTRY)))
{
flag_mask = flag_mask & (~(1<<DLI_EXIT));
}
else
{
flag_mask = flag_mask | (1<<DLI_EXIT);
}
/* apply the flag mask to the specified channel */
/* if "all" channel is specified, apply mask to all channels */
if(!strcmp(entry_ptr,"all"))
{
if(plus_or_minus=='+')
{
for(i=0;i<DCI_LAST;i++)
{
dbg_channel_flags[i] |= flag_mask; /* OR to open levels*/
}
}
else
{
for(i=0;i<DCI_LAST;i++)
{
dbg_channel_flags[i] &= flag_mask; /* AND to close levels */
}
}
}
else
{
for(i=0;i<DCI_LAST;i++)
{
if(!strcmp(entry_ptr,dbg_channel_names[i]))
{
if(plus_or_minus=='+')
{
dbg_channel_flags[i] |= flag_mask;
}
else
{
dbg_channel_flags[i] &= flag_mask;
}
break;
}
}
/* ignore the entry if the channel name is unknown */
}
/* done processing this entry; on to the next. */
}
PAL_free(env_pcache);
/* select output file */
env_string=MiscGetenv(ENV_FILE);
if(env_string && *env_string!='\0')
{
if(!strcmp(env_string, "stderr"))
{
output_file = stderr;
}
else if(!strcmp(env_string, "stdout"))
{
output_file = stdout;
}
else
{
output_file = fopen(env_string,FOPEN_FLAGS);
/* if file can't be opened, default to stderr */
if(!output_file)
{
output_file = stderr;
fprintf(stderr, "Can't open %s for writing : debug messages "
"will go to stderr. Check your PAL_API_TRACING "
"variable!\n", env_string);
}
}
}
else
{
output_file = stderr; /* output to stderr by default */
}
/* see if we need to disable assertions */
env_string = MiscGetenv(ENV_ASSERTS);
if(env_string && 0 == strcmp(env_string,"1"))
{
g_Dbg_asserts_enabled = FALSE;
}
else
{
g_Dbg_asserts_enabled = TRUE;
}
/* select ENTRY level limitation */
env_string = MiscGetenv(ENV_ENTRY_LEVELS);
if(env_string)
{
max_entry_level = atoi(env_string);
}
else
{
max_entry_level = 1;
}
/* if necessary, allocate TLS key for entry nesting level */
if(0 != max_entry_level)
{
if ((ret = pthread_key_create(&entry_level_key,NULL)) != 0)
{
fprintf(stderr, "ERROR : pthread_key_create() failed error:%d (%s)\n",
ret, strerror(ret));
DeleteCriticalSection(&fprintf_crit_section);;
return FALSE;
}
}
return TRUE;
}
/*++
Function :
DBG_close_channels
Stop outputting debug messages by closing the associated file.
(no parameters, no return value)
--*/
void DBG_close_channels()
{
if(output_file && output_file != stderr && output_file != stdout)
{
if (fclose(output_file) != 0)
{
fprintf(stderr, "ERROR : fclose() failed errno:%d (%s)\n",
errno, strerror(errno));
}
}
output_file = NULL;
DeleteCriticalSection(&fprintf_crit_section);
/* if necessary, release TLS key for entry nesting level */
if(0 != max_entry_level)
{
int retval;
retval = pthread_key_delete(entry_level_key);
if(0 != retval)
{
fprintf(stderr, "ERROR : pthread_key_delete() returned %d! (%s)\n",
retval, strerror(retval));
}
}
}
#ifdef FEATURE_PAL_SXS
static const void *DBG_get_module_id()
{
static const void *s_module_id = NULL;
if (s_module_id == NULL)
{
Dl_info dl_info;
if (dladdr((void *) DBG_get_module_id, &dl_info) == 0 || dl_info.dli_sname == NULL)
{
s_module_id = (void *) -1;
}
else
{
s_module_id = dl_info.dli_fbase;
}
}
return s_module_id;
}
#define MODULE_ID DBG_get_module_id,
#define MODULE_FORMAT "-%p"
#else
#define MODULE_ID
#define MODULE_FORMAT
#endif // FEATURE_PAL_SXS
/*++
Function :
DBG_printf_gcc
Internal function for debug channels; don't use.
This function outputs a complete debug message, including the 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 function : current function
LPSTR file : current file
INT line : line number
LPSTR format, ... : standard printf parameter list.
Return Value :
always 1.
Notes :
This version is for gnu compilers that support variable-argument macros
and the __FUNCTION__ pseudo-macro.
--*/
int DBG_printf_gcc(DBG_CHANNEL_ID channel, DBG_LEVEL_ID level, BOOL bHeader,
LPCSTR function, LPCSTR file, INT line, LPCSTR format, ...)
{
CHAR *buffer = (CHAR*)alloca(DBG_BUFFER_SIZE);
CHAR indent[MAX_NESTING+1];
LPSTR buffer_ptr;
INT output_size;
va_list args;
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)
{
/* Print file instead of function name for ENTRY messages, because those
already include the function name */
/* also print file name for ASSERTs, to match Win32 behavior */
if( DLI_ENTRY == level || DLI_ASSERT == level || DLI_EXIT == level)
{
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);
}
else
{
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], function, 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
{
buffer_ptr = buffer;
output_size = 0;
}
va_start(args, format);
output_size+=Silent_PAL_vsnprintf(buffer_ptr, DBG_BUFFER_SIZE-output_size,
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));
}
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+=Silent_PAL_vsnprintf(buffer_ptr, DBG_BUFFER_SIZE-output_size,
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;
}