1// Licensed to the .NET Foundation under one or more agreements.
2// The .NET Foundation licenses this file to you under the MIT license.
3// See the LICENSE file in the project root for more information.
4
5/*++
6
7
8
9Module Name:
10
11 misc/dbgmsg.cpp
12
13Abstract:
14 Implementation of Debug Message utilies. Relay channel information,
15 output functions, etc.
16
17
18
19--*/
20
21/* PAL headers */
22
23#include "pal/thread.hpp"
24#include "pal/malloc.hpp"
25#include "pal/file.hpp"
26
27#include "config.h"
28#include "pal/dbgmsg.h"
29#include "pal/cruntime.h"
30#include "pal/critsect.h"
31#include "pal/file.h"
32#include "pal/environ.h"
33
34/* standard headers */
35
36#include <stdio.h>
37#include <stdlib.h>
38#include <string.h>
39#include <pthread.h> /* for pthread_self */
40#include <errno.h>
41#include <dirent.h>
42#include <dlfcn.h>
43
44/* <stdarg.h> needs to be included after "palinternal.h" to avoid name
45 collision for va_start and va_end */
46#include <stdarg.h>
47
48using namespace CorUnix;
49
50/* append mode file I/O is safer */
51#define _PAL_APPEND_DBG_OUTPUT_
52
53static const char FOPEN_FLAGS[] = "at";
54
55/* number of ENTRY nesting levels to indicate with a '.' */
56#define MAX_NESTING 50
57
58/* size of output buffer (arbitrary) */
59#define DBG_BUFFER_SIZE 20000
60
61/* global and static variables */
62
63LPCWSTR W16_NULLSTRING = (LPCWSTR) "N\0U\0L\0L\0\0";
64
65DWORD dbg_channel_flags[DCI_LAST];
66BOOL g_Dbg_asserts_enabled;
67
68/* we must use stdio functions directly rather that rely on PAL functions for
69 output, because those functions do tracing and we need to avoid recursion */
70FILE *output_file = NULL;
71
72/* master switch for debug channel enablement, to be modified by debugger */
73Volatile<BOOL> dbg_master_switch = TRUE;
74
75
76static const char *dbg_channel_names[]=
77{
78 "PAL",
79 "LOADER",
80 "HANDLE",
81 "SHMEM",
82 "PROCESS",
83 "THREAD",
84 "EXCEPT",
85 "CRT",
86 "UNICODE",
87 "ARCH",
88 "SYNC",
89 "FILE",
90 "VIRTUAL",
91 "MEM",
92 "SOCKET",
93 "DEBUG",
94 "LOCALE",
95 "MISC",
96 "MUTEX",
97 "CRITSEC",
98 "POLL",
99 "CRYPT",
100 "SHFOLDER"
101#ifdef FEATURE_PAL_SXS
102 , "SXS"
103#endif // FEATURE_PAL_SXS
104 , "DCI_NUMA"
105};
106
107// Verify the number of elements in dbg_channel_names
108static_assert_no_msg(_countof(dbg_channel_names) == DCI_LAST);
109
110static const char *dbg_level_names[]=
111{
112 "ENTRY",
113 "TRACE",
114 "WARN",
115 "ERROR",
116 "ASSERT",
117 "EXIT"
118};
119
120static const char ENV_FILE[]="PAL_API_TRACING";
121static const char ENV_CHANNELS[]="PAL_DBG_CHANNELS";
122static const char ENV_ASSERTS[]="PAL_DISABLE_ASSERTS";
123static const char ENV_ENTRY_LEVELS[]="PAL_API_LEVELS";
124
125/* per-thread storage for ENTRY tracing level */
126static pthread_key_t entry_level_key;
127
128/* entry level limitation */
129static int max_entry_level;
130
131/* character to use for ENTRY indentation */
132static const char INDENT_CHAR = '.';
133
134static BOOL DBG_get_indent(DBG_LEVEL_ID level, const char *format,
135 char *indent_string);
136
137static CRITICAL_SECTION fprintf_crit_section;
138
139/* Function definitions */
140
141/*++
142Function :
143 DBG_init_channels
144
145 Parse environment variables PAL_DBG_CHANNELS and PAL_API_TRACING for debug
146 channel settings; initialize static variables.
147
148 (no parameters, no return value)
149--*/
150BOOL DBG_init_channels(void)
151{
152 INT i;
153 LPSTR env_string;
154 LPSTR env_workstring;
155 LPSTR env_pcache;
156 LPSTR entry_ptr;
157 LPSTR level_ptr;
158 CHAR plus_or_minus;
159 DWORD flag_mask = 0;
160 int ret;
161
162 InternalInitializeCriticalSection(&fprintf_crit_section);
163
164 /* output only asserts by default [only affects no-vararg-support case; if
165 we have varargs, these flags aren't even checked for ASSERTs] */
166 for(i=0;i<DCI_LAST;i++)
167 dbg_channel_flags[i]=1<<DLI_ASSERT;
168
169 /* parse PAL_DBG_CHANNELS environment variable */
170
171 env_string = EnvironGetenv(ENV_CHANNELS);
172 env_pcache = env_workstring = env_string;
173
174 while(env_workstring)
175 {
176 entry_ptr=env_workstring;
177
178 /* find beginning of next entry */
179 while((*entry_ptr != '\0') &&(*entry_ptr != '+') && (*entry_ptr != '-'))
180 {
181 entry_ptr++;
182 }
183
184 /* break if end of string is reached */
185 if(*entry_ptr == '\0')
186 {
187 break;
188 }
189
190 plus_or_minus=*entry_ptr++;
191
192 /* find end of entry; if strchr returns NULL, we have reached the end
193 of the string and we will leave the loop at the end of this pass. */
194 env_workstring=strchr(entry_ptr,':');
195
196 /* NULL-terminate entry, make env_string point to rest of string */
197 if(env_workstring)
198 {
199 *env_workstring++='\0';
200 }
201
202 /* find period that separates channel name from level name */
203 level_ptr=strchr(entry_ptr,'.');
204
205 /* an entry with no period is illegal : ignore it */
206 if(!level_ptr)
207 {
208 continue;
209 }
210 /* NULL-terminate channel name, make level_ptr point to the level name */
211 *level_ptr++='\0';
212
213 /* build the flag mask based on requested level */
214
215 /* if "all" level is specified, we want to open/close all levels at
216 once, so mask is either all ones or all zeroes */
217 if(!strcmp(level_ptr,"all"))
218 {
219 if(plus_or_minus=='+')
220 {
221 flag_mask=0xFFFF; /* OR this to open all levels */
222 }
223 else
224 {
225 flag_mask=0; /* AND this to close all levels*/
226 }
227 }
228 else
229 {
230 for(i=0;i<DLI_LAST;i++)
231 {
232 if(!strcmp(level_ptr,dbg_level_names[i]))
233 {
234 if(plus_or_minus=='+')
235 {
236 flag_mask=1<<i; /* OR this to open the level */
237 }
238 else
239 {
240 flag_mask=~(1<<i); /* AND this to close the level */
241 }
242 break;
243 }
244 }
245 /* didn't find a matching level : skip it. */
246 if(i==DLI_LAST)
247 {
248 continue;
249 }
250 }
251
252 /* Set EXIT and ENTRY channels to be identical */
253 if(!(flag_mask & (1<<DLI_ENTRY)))
254 {
255 flag_mask = flag_mask & (~(1<<DLI_EXIT));
256 }
257 else
258 {
259 flag_mask = flag_mask | (1<<DLI_EXIT);
260 }
261
262 /* apply the flag mask to the specified channel */
263
264 /* if "all" channel is specified, apply mask to all channels */
265 if(!strcmp(entry_ptr,"all"))
266 {
267 if(plus_or_minus=='+')
268 {
269 for(i=0;i<DCI_LAST;i++)
270 {
271 dbg_channel_flags[i] |= flag_mask; /* OR to open levels*/
272 }
273 }
274 else
275 {
276 for(i=0;i<DCI_LAST;i++)
277 {
278 dbg_channel_flags[i] &= flag_mask; /* AND to close levels */
279 }
280 }
281 }
282 else
283 {
284 for(i=0;i<DCI_LAST;i++)
285 {
286 if(!strcmp(entry_ptr,dbg_channel_names[i]))
287 {
288 if(plus_or_minus=='+')
289 {
290 dbg_channel_flags[i] |= flag_mask;
291 }
292 else
293 {
294 dbg_channel_flags[i] &= flag_mask;
295 }
296
297 break;
298 }
299 }
300 /* ignore the entry if the channel name is unknown */
301 }
302 /* done processing this entry; on to the next. */
303 }
304 PAL_free(env_pcache);
305
306 /* select output file */
307 env_string = EnvironGetenv(ENV_FILE);
308 if(env_string && *env_string!='\0')
309 {
310 if(!strcmp(env_string, "stderr"))
311 {
312 output_file = stderr;
313 }
314 else if(!strcmp(env_string, "stdout"))
315 {
316 output_file = stdout;
317 }
318 else
319 {
320 output_file = fopen(env_string,FOPEN_FLAGS);
321
322 /* if file can't be opened, default to stderr */
323 if(!output_file)
324 {
325 output_file = stderr;
326 fprintf(stderr, "Can't open %s for writing : debug messages "
327 "will go to stderr. Check your PAL_API_TRACING "
328 "variable!\n", env_string);
329 }
330 }
331 }
332 else
333 {
334 output_file = stderr; /* output to stderr by default */
335 }
336
337 if(env_string)
338 {
339 PAL_free(env_string);
340 }
341
342 /* see if we need to disable assertions */
343 env_string = EnvironGetenv(ENV_ASSERTS);
344 if(env_string && 0 == strcmp(env_string,"1"))
345 {
346 g_Dbg_asserts_enabled = FALSE;
347 }
348 else
349 {
350 g_Dbg_asserts_enabled = TRUE;
351 }
352
353 if(env_string)
354 {
355 PAL_free(env_string);
356 }
357
358 /* select ENTRY level limitation */
359 env_string = EnvironGetenv(ENV_ENTRY_LEVELS);
360 if(env_string)
361 {
362 max_entry_level = atoi(env_string);
363 PAL_free(env_string);
364 }
365 else
366 {
367 max_entry_level = 1;
368 }
369
370 /* if necessary, allocate TLS key for entry nesting level */
371 if(0 != max_entry_level)
372 {
373 if ((ret = pthread_key_create(&entry_level_key,NULL)) != 0)
374 {
375 fprintf(stderr, "ERROR : pthread_key_create() failed error:%d (%s)\n",
376 ret, strerror(ret));
377 DeleteCriticalSection(&fprintf_crit_section);;
378 return FALSE;
379 }
380 }
381
382 return TRUE;
383}
384
385/*++
386Function :
387 DBG_close_channels
388
389 Stop outputting debug messages by closing the associated file.
390
391 (no parameters, no return value)
392--*/
393void DBG_close_channels()
394{
395 if(output_file && output_file != stderr && output_file != stdout)
396 {
397 if (fclose(output_file) != 0)
398 {
399 fprintf(stderr, "ERROR : fclose() failed errno:%d (%s)\n",
400 errno, strerror(errno));
401 }
402 }
403
404 output_file = NULL;
405
406 DeleteCriticalSection(&fprintf_crit_section);
407
408 /* if necessary, release TLS key for entry nesting level */
409 if(0 != max_entry_level)
410 {
411 int retval;
412
413 retval = pthread_key_delete(entry_level_key);
414 if(0 != retval)
415 {
416 fprintf(stderr, "ERROR : pthread_key_delete() returned %d! (%s)\n",
417 retval, strerror(retval));
418 }
419 }
420}
421
422
423#ifdef FEATURE_PAL_SXS
424static const void *DBG_get_module_id()
425{
426 static const void *s_module_id = NULL;
427 if (s_module_id == NULL)
428 {
429 Dl_info dl_info;
430 if (dladdr((void *) DBG_get_module_id, &dl_info) == 0 || dl_info.dli_sname == NULL)
431 {
432 s_module_id = (void *) -1;
433 }
434 else
435 {
436 s_module_id = dl_info.dli_fbase;
437 }
438 }
439 return s_module_id;
440}
441
442#define MODULE_ID DBG_get_module_id,
443#define MODULE_FORMAT "-%p"
444#else
445#define MODULE_ID
446#define MODULE_FORMAT
447#endif // FEATURE_PAL_SXS
448
449/*++
450Function :
451 DBG_printf
452
453 Internal function for debug channels; don't use.
454 This function outputs a complete debug message, including the function name.
455
456Parameters :
457 DBG_CHANNEL_ID channel : debug channel to use
458 DBG_LEVEL_ID level : debug message level
459 BOOL bHeader : whether or not to output message header (thread id, etc)
460 LPCSTR function : current function
461 LPCSTR file : current file
462 INT line : line number
463 LPCSTR format, ... : standard printf parameter list.
464
465Return Value :
466 always 1.
467
468Notes :
469 This version is for compilers that support the C99 flavor of
470 variable-argument macros but not the gnu flavor, and do not support the
471 __FUNCTION__ pseudo-macro.
472
473--*/
474int DBG_printf(DBG_CHANNEL_ID channel, DBG_LEVEL_ID level, BOOL bHeader,
475 LPCSTR function, LPCSTR file, INT line, LPCSTR format, ...)
476{
477 CHAR *buffer = (CHAR*)alloca(DBG_BUFFER_SIZE);
478 CHAR indent[MAX_NESTING+1];
479 LPSTR buffer_ptr;
480 INT output_size;
481 va_list args;
482 void *thread_id;
483 int old_errno = 0;
484
485 old_errno = errno;
486
487 if(!DBG_get_indent(level, format, indent))
488 {
489 return 1;
490 }
491
492 thread_id = (void *)THREADSilentGetCurrentThreadId();
493
494 if(bHeader)
495 {
496 /* Print file instead of function name for ENTRY messages, because those
497 already include the function name */
498 /* also print file name for ASSERTs, to match Win32 behavior */
499 if( DLI_ENTRY == level || DLI_ASSERT == level || DLI_EXIT == level)
500 {
501 output_size=snprintf(buffer, DBG_BUFFER_SIZE,
502 "{%p" MODULE_FORMAT "} %-5s [%-7s] at %s.%d: ",
503 thread_id, MODULE_ID
504 dbg_level_names[level], dbg_channel_names[channel], file, line);
505 }
506 else
507 {
508 output_size=snprintf(buffer, DBG_BUFFER_SIZE,
509 "{%p" MODULE_FORMAT "} %-5s [%-7s] at %s.%d: ",
510 thread_id, MODULE_ID
511 dbg_level_names[level], dbg_channel_names[channel], function, line);
512 }
513
514 if(output_size + 1 > DBG_BUFFER_SIZE)
515 {
516 fprintf(stderr, "ERROR : buffer overflow in DBG_printf");
517 return 1;
518 }
519
520 buffer_ptr=buffer+output_size;
521 }
522 else
523 {
524 buffer_ptr = buffer;
525 output_size = 0;
526 }
527
528 va_start(args, format);
529
530 output_size+=_vsnprintf_s(buffer_ptr, DBG_BUFFER_SIZE-output_size, _TRUNCATE,
531 format, args);
532 va_end(args);
533
534 if( output_size > DBG_BUFFER_SIZE )
535 {
536 fprintf(stderr, "ERROR : buffer overflow in DBG_printf");
537 }
538
539 /* Use a Critical section before calling printf code to
540 avoid holding a libc lock while another thread is calling
541 SuspendThread on this one. */
542
543 InternalEnterCriticalSection(NULL, &fprintf_crit_section);
544 fprintf( output_file, "%s%s", indent, buffer );
545 InternalLeaveCriticalSection(NULL, &fprintf_crit_section);
546
547 /* flush the output to file */
548 if ( fflush(output_file) != 0 )
549 {
550 fprintf(stderr, "ERROR : fflush() failed errno:%d (%s)\n",
551 errno, strerror(errno));
552 }
553
554 // Some systems support displaying a GUI dialog. We attempt this only for asserts.
555 if ( level == DLI_ASSERT )
556 PAL_DisplayDialog("PAL ASSERT", buffer);
557
558 if ( old_errno != errno )
559 {
560 fprintf( stderr,"ERROR: errno changed by DBG_printf\n" );
561 errno = old_errno;
562 }
563
564 return 1;
565}
566
567/*++
568Function :
569 DBG_get_indent
570
571 generate an indentation string to be used for message output
572
573Parameters :
574 DBG_LEVEL_ID level : level of message (DLI_ENTRY, etc)
575 const char *format : printf format string of message
576 char *indent_string : destination for indentation string
577
578Return value :
579 TRUE if output can proceed, FALSE otherwise
580
581Notes:
582As a side-effect, this function updates the ENTRY nesting level for the current
583thread : it decrements it if 'format' contains the string 'return', increments
584it otherwise (but only if 'level' is DLI_ENTRY). The function will return
585FALSE if the current nesting level is beyond our treshold (max_nesting_level);
586it always returns TRUE for other message levels
587--*/
588static BOOL DBG_get_indent(DBG_LEVEL_ID level, const char *format,
589 char *indent_string)
590{
591 int ret;
592
593 /* determine whether to output an ENTRY line */
594 if(DLI_ENTRY == level||DLI_EXIT == level)
595 {
596 if(0 != max_entry_level)
597 {
598 INT_PTR nesting;
599
600 /* Determine if this is an entry or an
601 exit */
602 if(DLI_EXIT == level)
603 {
604 nesting = (INT_PTR) pthread_getspecific(entry_level_key);
605 /* avoid going negative */
606 if(nesting != 0)
607 {
608 nesting--;
609 if ((ret = pthread_setspecific(entry_level_key,
610 (LPVOID)nesting)) != 0)
611 {
612 fprintf(stderr, "ERROR : pthread_setspecific() failed "
613 "error:%d (%s)\n", ret, strerror(ret));
614 }
615 }
616 }
617 else
618 {
619 nesting = (INT_PTR) pthread_getspecific(entry_level_key);
620
621 if ((ret = pthread_setspecific(entry_level_key,
622 (LPVOID)(nesting+1))) != 0)
623 {
624 fprintf(stderr, "ERROR : pthread_setspecific() failed "
625 "error:%d (%s)\n", ret, strerror(ret));
626 }
627 }
628
629 /* see if we're past the level treshold */
630 if(nesting >= max_entry_level)
631 {
632 return FALSE;
633 }
634
635 /* generate indentation string */
636 if(MAX_NESTING < nesting)
637 {
638 nesting = MAX_NESTING;
639 }
640 memset(indent_string,INDENT_CHAR ,nesting);
641 indent_string[nesting] = '\0';
642 }
643 else
644 {
645 indent_string[0] = '\0';
646 }
647 }
648 else
649 {
650 indent_string[0] = '\0';
651 }
652 return TRUE;
653}
654
655/*++
656Function :
657 DBG_change_entrylevel
658
659 retrieve current ENTRY nesting level and [optionnally] modify it
660
661Parameters :
662 int new_level : value to which the nesting level must be set, or -1
663
664Return value :
665 nesting level at the time the function was called
666
667Notes:
668if new_level is -1, the nesting level will not be modified
669--*/
670int DBG_change_entrylevel(int new_level)
671{
672 int old_level;
673 int ret;
674
675 if(0 == max_entry_level)
676 {
677 return 0;
678 }
679 old_level = PtrToInt(pthread_getspecific(entry_level_key));
680 if(-1 != new_level)
681 {
682 if ((ret = pthread_setspecific(entry_level_key,(LPVOID)(IntToPtr(new_level)))) != 0)
683 {
684 fprintf(stderr, "ERROR : pthread_setspecific() failed "
685 "error:%d (%s)\n", ret, strerror(ret));
686 }
687 }
688 return old_level;
689}
690
691#if _DEBUG && defined(__APPLE__)
692/*++
693Function:
694 DBG_ShouldCheckStackAlignment
695
696 Wires up stack alignment checks (debug builds only)
697--*/
698static const char * PAL_CHECK_ALIGNMENT_MODE = "PAL_CheckAlignmentMode";
699enum CheckAlignmentMode
700{
701 // special value to indicate we've not initialized yet
702 CheckAlignment_Uninitialized = -1,
703
704 CheckAlignment_Off = 0,
705 CheckAlignment_On = 1,
706
707 CheckAlignment_Default = CheckAlignment_On
708};
709
710bool DBG_ShouldCheckStackAlignment()
711{
712 static CheckAlignmentMode caMode = CheckAlignment_Uninitialized;
713
714 if (caMode == CheckAlignment_Uninitialized)
715 {
716 char* checkAlignmentSettings;
717 bool shouldFreeCheckAlignmentSettings = false;
718 if (palEnvironment == nullptr)
719 {
720 // This function might be called before the PAL environment is initialized.
721 // In this case, use the system getenv instead.
722 checkAlignmentSettings = ::getenv(PAL_CHECK_ALIGNMENT_MODE);
723 }
724 else
725 {
726 checkAlignmentSettings = EnvironGetenv(PAL_CHECK_ALIGNMENT_MODE);
727 shouldFreeCheckAlignmentSettings = true;
728 }
729
730 caMode = checkAlignmentSettings ?
731 (CheckAlignmentMode)atoi(checkAlignmentSettings) : CheckAlignment_Default;
732
733 if (checkAlignmentSettings && shouldFreeCheckAlignmentSettings)
734 {
735 free(checkAlignmentSettings);
736 }
737 }
738
739 return caMode == CheckAlignment_On;
740}
741#endif // _DEBUG && __APPLE__
742
743#ifdef __APPLE__
744#include "CoreFoundation/CFUserNotification.h"
745#include "CoreFoundation/CFString.h"
746#include "Security/AuthSession.h"
747
748static const char * PAL_DISPLAY_DIALOG = "PAL_DisplayDialog";
749enum DisplayDialogMode
750{
751 DisplayDialog_Uninitialized = -1,
752
753 DisplayDialog_Suppress = 0,
754 DisplayDialog_Show = 1,
755
756 DisplayDialog_Default = DisplayDialog_Suppress,
757};
758
759/*++
760Function :
761 PAL_DisplayDialog
762
763 Display a simple modal dialog with an alert icon and a single OK button. Caller supplies the title of the
764 dialog and the main text. The dialog is displayed only if the PAL_DisplayDialog environment
765 variable is set to the value "1" and the session has access to the display.
766
767--*/
768void PAL_DisplayDialog(const char *szTitle, const char *szText)
769{
770 static DisplayDialogMode dispDialog = DisplayDialog_Uninitialized;
771
772 if (dispDialog == DisplayDialog_Uninitialized)
773 {
774 char* displayDialog = EnvironGetenv(PAL_DISPLAY_DIALOG);
775 if (displayDialog)
776 {
777 int i = atoi(displayDialog);
778 free(displayDialog);
779
780 switch (i)
781 {
782 case 0:
783 dispDialog = DisplayDialog_Suppress;
784 break;
785
786 case 1:
787 dispDialog = DisplayDialog_Show;
788 break;
789
790 default:
791 // Asserting here would just be re-entrant. :/
792 dispDialog = DisplayDialog_Default;
793 break;
794 }
795 }
796 else
797 dispDialog = DisplayDialog_Default;
798
799 if (dispDialog == DisplayDialog_Show)
800 {
801 // We may not be allowed to show.
802 OSStatus osstatus;
803 SecuritySessionId secSession;
804 SessionAttributeBits secSessionInfo;
805
806 osstatus = SessionGetInfo(callerSecuritySession, &secSession, &secSessionInfo);
807 if (noErr != osstatus || (secSessionInfo & sessionHasGraphicAccess) == 0)
808 dispDialog = DisplayDialog_Suppress;
809 }
810 }
811
812 if (dispDialog == DisplayDialog_Suppress)
813 return;
814
815 CFStringRef cfsTitle = CFStringCreateWithCString(kCFAllocatorDefault,
816 szTitle,
817 kCFStringEncodingUTF8);
818 if (cfsTitle != NULL)
819 {
820 CFStringRef cfsText = CFStringCreateWithCString(kCFAllocatorDefault,
821 szText,
822 kCFStringEncodingUTF8);
823 if (cfsText != NULL)
824 {
825 CFOptionFlags response;
826 CFUserNotificationDisplayAlert(0, // Never time-out, wait for user to hit 'OK'
827 0, // No flags
828 NULL, // Default icon
829 NULL, // Default sound
830 NULL, // No-localization support for text
831 cfsTitle, // Title for dialog
832 cfsText, // The actual alert text
833 NULL, // Default default button title ('OK')
834 NULL, // No alternate button
835 NULL, // No third button
836 &response); // User's response (discarded)
837 CFRelease(cfsText);
838 }
839 CFRelease(cfsTitle);
840 }
841}
842
843/*++
844Function :
845 PAL_DisplayDialogFormatted
846
847 As above but takes a printf-style format string and insertion values to form the main text.
848
849--*/
850void PAL_DisplayDialogFormatted(const char *szTitle, const char *szTextFormat, ...)
851{
852 va_list args;
853
854 va_start(args, szTextFormat);
855
856 const int cchBuffer = 4096;
857 char *szBuffer = (char*)alloca(cchBuffer);
858 _vsnprintf_s(szBuffer, cchBuffer, _TRUNCATE, szTextFormat, args);
859 PAL_DisplayDialog(szTitle, szBuffer);
860
861 va_end(args);
862}
863#endif // __APPLE__
864