Debugging.h#
Fully qualified name: carb/extras/Debugging.h
File members: carb/extras/Debugging.h
// Copyright (c) 2019-2024, NVIDIA CORPORATION. All rights reserved.
//
// NVIDIA CORPORATION and its licensors retain all intellectual property
// and proprietary rights in and to this software, related documentation
// and any modifications thereto. Any use, reproduction, disclosure or
// distribution of this software and related documentation without an express
// license agreement from NVIDIA CORPORATION is strictly prohibited.
//
#pragma once
#include "../detail/PushBadMacros.h"
#include "../Defines.h"
#if CARB_PLATFORM_WINDOWS
# include "../CarbWindows.h"
extern "C"
{
// Forge doesn't define these functions, and it can be included before CarbWindows.h in some cases
// So ensure that they're defined here.
__declspec(dllimport) BOOL __stdcall IsDebuggerPresent(void);
__declspec(dllimport) void __stdcall DebugBreak(void);
// Undocumented function from ntdll.dll, only present in ntifs.h from the Driver Development Kit
__declspec(dllimport) unsigned short __stdcall RtlCaptureStackBackTrace(unsigned long,
unsigned long,
void**,
unsigned long*);
}
#elif CARB_POSIX
# include <chrono>
# include <cctype>
# include <execinfo.h>
# include <signal.h>
# include <stdint.h>
# include <string.h>
# include <fcntl.h>
# include <sys/types.h>
# include <unistd.h>
#else
CARB_UNSUPPORTED_PLATFORM();
#endif
#include <cstdio>
namespace carb
{
namespace extras
{
#ifndef DOXYGEN_SHOULD_SKIP_THIS
#endif
inline bool isDebuggerAttached(void)
{
#if CARB_PLATFORM_WINDOWS
return IsDebuggerPresent();
#elif CARB_PLATFORM_LINUX
// the maximum amount of time in milliseconds that the cached debugger state is valid for.
// If multiple calls to isDebuggerAttached() are made within this period, a cached state
// will be returned instead of re-querying. Outside of this period however, a new call
// to check the debugger state with isDebuggerAttached() will cause the state to be
// refreshed.
using namespace std::chrono;
static constexpr auto kDebugUtilsDebuggerCheckPeriod = milliseconds(500);
static bool queried = false;
static bool state = false;
auto now = steady_clock::now();
static auto lastCheckTime = now;
auto elapsedTime = now - lastCheckTime;
if (!queried || elapsedTime > kDebugUtilsDebuggerCheckPeriod)
{
// on Android and Linux we can check the '/proc/self/status' file for the line
// "TracerPid:" to check if the associated value is *not* 0. Note that this is
// not a cheap check so we'll cache its result and only re-query once every few
// seconds.
// fopen() and fgets() can use the heap, and we may be called from the crashreporter, so avoid using those
// functions to avoid heap usage.
int fd = open("/proc/self/status", 0, O_RDONLY);
if (fd < 0)
return false;
lastCheckTime = now;
queried = true;
char data[256];
constexpr static char TracerPid[] = "TracerPid:";
for (;;)
{
// Read some bytes from the file
ssize_t bytes = CARB_RETRY_EINTR(read(fd, data, CARB_COUNTOF(data) - 1));
if (bytes <= 0)
{
// Reached the end without finding the line, shouldn't happen
CARB_ASSERT(0);
close(fd);
state = false;
break;
}
data[bytes] = '\0';
// Look for the 'T' in "TracerPid"
auto p = strchr(data, 'T');
if (!p)
continue;
// Can we see the whole line?
auto cr = strchr(p, '\n');
if (!cr)
{
if (p == data)
// This line is too long; skip the 'T' and try again
lseek(fd, -off_t(bytes - 1), SEEK_CUR);
else
// Cannot see the whole line. Back up to where we found the 'T'
lseek(fd, -off_t(data + bytes - p), SEEK_CUR);
continue;
}
// Back up to the next line for the next read
lseek(fd, -off_t(data + bytes - (cr + 1)), SEEK_CUR);
// TracerPid line?
if (strncmp(p, TracerPid, CARB_COUNTOF(TracerPid) - 1) != 0)
// Nope, on to the next line
continue;
// Yep, get the result.
p += (CARB_COUNTOF(TracerPid) - 1);
while (p != cr && (*p == '0' || isspace(*p)))
++p;
// If we find any characters other than space or zero, we have a tracer
state = p != cr;
close(fd);
break;
}
}
return state;
#else
CARB_UNSUPPORTED_PLATFORM();
#endif
}
inline void debuggerBreak(void)
{
if (!isDebuggerAttached())
return;
#if CARB_PLATFORM_WINDOWS
DebugBreak();
#elif CARB_POSIX
// NOTE: the __builtin_trap() call is the more 'correct and portable' way to do this. However
// that unfortunately raises a SIGILL signal which is not continuable (at least not in
// MSVC Android or GDB) so its usefulness in a debugger is limited. Directly raising a
// SIGTRAP signal instead still gives the desired behavior and is also continuable.
raise(SIGTRAP);
#else
CARB_UNSUPPORTED_PLATFORM();
#endif
}
inline size_t debugBacktrace(size_t skipFrames, void** array, size_t count) noexcept
{
#if CARB_PLATFORM_WINDOWS
// Apparently RtlCaptureStackBackTrace() can "fail" (i.e. not write anything and return 0) without setting any
// error for GetLastError(). Try a few times in a loop.
constexpr static int kRetries = 3;
for (int i = 0; i != kRetries; ++i)
{
unsigned short frames =
::RtlCaptureStackBackTrace((unsigned long)skipFrames, (unsigned long)count, array, nullptr);
if (frames)
return frames;
}
// Failed
return 0;
#elif CARB_PLATFORM_LINUX
void** target = array;
if (skipFrames)
{
target = CARB_STACK_ALLOC(void*, count + skipFrames);
}
size_t captured = (size_t)::backtrace(target, int(count + skipFrames));
if (captured <= skipFrames)
return 0;
if (skipFrames)
{
captured -= skipFrames;
memcpy(array, target + skipFrames, sizeof(void*) * captured);
}
return captured;
#else
CARB_UNSUPPORTED_PLATFORM();
#endif
}
void debugPrint(const char* fmt, ...) CARB_PRINTF_FUNCTION(1, 2);
inline void debugPrint(const char* fmt, ...)
{
#if CARB_PLATFORM_WINDOWS
va_list va, va2;
va_start(va, fmt);
va_copy(va2, va);
int count = vsnprintf(nullptr, 0, fmt, va2);
va_end(va2);
if (count > 0)
{
char* buffer = CARB_STACK_ALLOC(char, size_t(count) + 1);
vsnprintf(buffer, size_t(count + 1), fmt, va);
::OutputDebugStringA(buffer);
}
va_end(va);
#else
va_list va;
va_start(va, fmt);
vfprintf(stdout, fmt, va);
va_end(va);
#endif
}
} // namespace extras
} // namespace carb
#include "../detail/PopBadMacros.h"