Skip to content

Commit

Permalink
Add breakpoint if being debugged functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
jeremy-rifkin committed May 31, 2024
1 parent ad9baa7 commit 046da6e
Show file tree
Hide file tree
Showing 9 changed files with 244 additions and 14 deletions.
21 changes: 21 additions & 0 deletions include/libassert/assert.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,13 @@ namespace libassert {

LIBASSERT_ATTR_COLD LIBASSERT_EXPORT bool isatty(int fd);

LIBASSERT_ATTR_COLD LIBASSERT_EXPORT bool is_debugger_present() noexcept;
enum class debugger_check_mode {
check_once,
check_every_time,
};
LIBASSERT_ATTR_COLD LIBASSERT_EXPORT void set_debugger_check_mode(debugger_check_mode mode) noexcept;

// returns the type name of T
template<typename T>
[[nodiscard]] std::string_view type_name() noexcept {
Expand Down Expand Up @@ -689,6 +696,17 @@ namespace libassert {
#define LIBASSERT_IGNORE_UNUSED_VALUE
#endif

#ifdef LIBASSERT_BREAK_ON_FAIL
#define LIBASSERT_BREAKPOINT_IF_DEBUGGING() \
do \
if(libassert::is_debugger_present()) { \
LIBASSERT_BREAKPOINT(); \
} \
while(0)
#else
#define LIBASSERT_BREAKPOINT_IF_DEBUGGING()
#endif

#define LIBASSERT_INVOKE(expr, name, type, failaction, ...) \
/* must push/pop out here due to nasty clang bug https://github.com/llvm/llvm-project/issues/63897 */ \
/* must do awful stuff to workaround differences in where gcc and clang allow these directives to go */ \
Expand All @@ -704,6 +722,7 @@ namespace libassert {
LIBASSERT_WARNING_PRAGMA_POP_GCC \
if(LIBASSERT_STRONG_EXPECT(!static_cast<bool>(libassert_decomposer.get_value()), 0)) { \
libassert::ERROR_ASSERTION_FAILURE_IN_CONSTEXPR_CONTEXT(); \
LIBASSERT_BREAKPOINT_IF_DEBUGGING(); \
failaction \
LIBASSERT_STATIC_DATA(name, libassert::assert_type::type, #expr, __VA_ARGS__) \
if constexpr(sizeof libassert_decomposer > 32) { \
Expand All @@ -727,6 +746,7 @@ namespace libassert {
#define LIBASSERT_INVOKE_PANIC(name, type, ...) \
do { \
libassert::ERROR_ASSERTION_FAILURE_IN_CONSTEXPR_CONTEXT(); \
LIBASSERT_BREAKPOINT_IF_DEBUGGING(); \
LIBASSERT_STATIC_DATA(name, libassert::assert_type::type, "", __VA_ARGS__) \
libassert::detail::process_panic( \
libassert_params \
Expand Down Expand Up @@ -783,6 +803,7 @@ namespace libassert {
/* https://godbolt.org/z/Kq8Wb6q5j https://godbolt.org/z/nMnqnsMYx */ \
if(LIBASSERT_STRONG_EXPECT(!LIBASSERT_STATIC_CAST_TO_BOOL(libassert_value), 0)) { \
libassert::ERROR_ASSERTION_FAILURE_IN_CONSTEXPR_CONTEXT(); \
LIBASSERT_BREAKPOINT_IF_DEBUGGING(); \
failaction \
LIBASSERT_STATIC_DATA(name, libassert::assert_type::type, #expr, __VA_ARGS__) \
if constexpr(sizeof libassert_decomposer > 32) { \
Expand Down
87 changes: 85 additions & 2 deletions include/libassert/platform.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
#define LIBASSERT_CLANG_VERSION 0
#endif


#if (defined(__GNUC__) || defined(__GNUG__)) && !defined(__clang__) && !defined(__INTEL_COMPILER)
#define LIBASSERT_IS_GCC 1
#define LIBASSERT_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__)
Expand All @@ -50,7 +49,6 @@
#define LIBASSERT_GCC_VERSION 0
#endif


#ifdef _MSC_VER
#define LIBASSERT_IS_MSVC 1
#define LIBASSERT_MSVC_VERSION _MSC_VER
Expand All @@ -59,6 +57,18 @@
#define LIBASSERT_MSVC_VERSION 0
#endif

#ifdef __INTEL_COMPILER
#define LIBASSERT_IS_ICC 1
#else
#define LIBASSERT_IS_ICC 0
#endif

#ifdef __INTEL_LLVM_COMPILER
#define LIBASSERT_IS_ICX 1
#else
#define LIBASSERT_IS_ICX 0
#endif

///
/// Detect standard library versions.
///
Expand Down Expand Up @@ -220,4 +230,77 @@ namespace libassert::detail {
}
}

#if LIBASSERT_IS_CLANG || LIBASSERT_IS_GCC
#if LIBASSERT_IS_GCC
#define LIBASSERT_WARNING_PRAGMA_PUSH_GCC _Pragma("GCC diagnostic push")
#define LIBASSERT_WARNING_PRAGMA_POP_GCC _Pragma("GCC diagnostic pop")
#define LIBASSERT_WARNING_PRAGMA_PUSH_CLANG
#define LIBASSERT_WARNING_PRAGMA_POP_CLANG
#else
#define LIBASSERT_WARNING_PRAGMA_PUSH_GCC
#define LIBASSERT_WARNING_PRAGMA_POP_GCC
#define LIBASSERT_WARNING_PRAGMA_PUSH_CLANG _Pragma("GCC diagnostic push")
#define LIBASSERT_WARNING_PRAGMA_POP_CLANG _Pragma("GCC diagnostic pop")
#endif
#else
#define LIBASSERT_WARNING_PRAGMA_PUSH_CLANG
#define LIBASSERT_WARNING_PRAGMA_POP_CLANG
#define LIBASSERT_WARNING_PRAGMA_PUSH_GCC
#define LIBASSERT_WARNING_PRAGMA_POP_GCC
#endif

#if LIBASSERT_IS_CLANG || LIBASSERT_IS_ICX
// clang and icx support this as far back as this library could care
#define LIBASSERT_BREAKPOINT() __builtin_debugtrap()
#elif LIBASSERT_IS_MSVC || LIBASSERT_IS_ICC
// msvc and icc support this as far back as this library could care
#define LIBASSERT_BREAKPOINT() __debugbreak()
#elif LIBASSERT_IS_GCC
#if LIBASSERT_GCC_VERSION >= 1200
#define LIBASSERT_IGNORE_CPP20_EXTENSION_WARNING _Pragma("GCC diagnostic ignored \"-Wc++20-extensions\"")
#else
#define LIBASSERT_IGNORE_CPP20_EXTENSION_WARNING
#endif
#define LIBASSERT_ASM_BREAKPOINT(instruction) \
do { \
LIBASSERT_WARNING_PRAGMA_PUSH_GCC \
LIBASSERT_IGNORE_CPP20_EXTENSION_WARNING \
__asm__ __volatile__(instruction) \
; \
LIBASSERT_WARNING_PRAGMA_POP_GCC \
} while(0)
// precedence for these come from llvm's __builtin_debugtrap() implementation
// arm: https://github.com/llvm/llvm-project/blob/e9954ec087d640809082f46d1c7e5ac1767b798d/llvm/lib/Target/ARM/ARMInstrInfo.td#L2393-L2394
// def : Pat<(debugtrap), (BKPT 0)>, Requires<[IsARM, HasV5T]>;
// def : Pat<(debugtrap), (UDF 254)>, Requires<[IsARM, NoV5T]>;
// thumb: https://github.com/llvm/llvm-project/blob/e9954ec087d640809082f46d1c7e5ac1767b798d/llvm/lib/Target/ARM/ARMInstrThumb.td#L1444-L1445
// def : Pat<(debugtrap), (tBKPT 0)>, Requires<[IsThumb, HasV5T]>;
// def : Pat<(debugtrap), (tUDF 254)>, Requires<[IsThumb, NoV5T]>;
// aarch64: https://github.com/llvm/llvm-project/blob/e9954ec087d640809082f46d1c7e5ac1767b798d/llvm/lib/Target/AArch64/AArch64FastISel.cpp#L3615-L3618
// case Intrinsic::debugtrap:
// BuildMI(*FuncInfo.MBB, FuncInfo.InsertPt, MIMD, TII.get(AArch64::BRK))
// .addImm(0xF000);
// return true;
// x86: https://github.com/llvm/llvm-project/blob/e9954ec087d640809082f46d1c7e5ac1767b798d/llvm/lib/Target/X86/X86InstrSystem.td#L81-L84
// def : Pat<(debugtrap),
// (INT3)>, Requires<[NotPS]>;
#if defined(__i386__) || defined(__x86_64__)
#define LIBASSERT_BREAKPOINT() LIBASSERT_ASM_BREAKPOINT("int3")
#elif defined(__arm__) || defined(__thumb__)
#if __ARM_ARCH >= 5
#define LIBASSERT_BREAKPOINT() LIBASSERT_ASM_BREAKPOINT("bkpt #0")
#else
#define LIBASSERT_BREAKPOINT() LIBASSERT_ASM_BREAKPOINT("udf #0xfe")
#endif
#elif defined(__aarch64__)
#define LIBASSERT_BREAKPOINT() LIBASSERT_ASM_BREAKPOINT("brk #0xf000")
#else
// some architecture we aren't prepared for
#define LIBASSERT_BREAKPOINT()
#endif
#else
// some compiler we aren't prepared for
#define LIBASSERT_BREAKPOINT()
#endif

#endif
10 changes: 10 additions & 0 deletions src/common.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,20 @@
#define BASIC_PURPL ESC "35m"

#define IS_WINDOWS 0
#define IS_LINUX 0
#define IS_APPLE 0

#if defined(_WIN32)
#undef IS_WINDOWS
#define IS_WINDOWS 1
#elif defined(__linux)
#undef IS_LINUX
#define IS_LINUX 1
#elif defined(__APPLE__)
#undef IS_APPLE
#define IS_APPLE 1
#else
#error "Libassert doesn't recognize this system, please open an issue at https://github.com/jeremy-rifkin/libassert"
#endif

#endif
124 changes: 118 additions & 6 deletions src/platform.cpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
#include "platform.hpp"

#include <atomic>
#include <cerrno>
#include <cstdio>
#include <cstring>
#include <mutex>
#include <optional>
#include <string>

#include "common.hpp"
Expand All @@ -14,13 +16,16 @@
#include <io.h>
#undef min // fucking windows headers, man
#undef max
#elif defined(__linux) || defined(__APPLE__) || defined(__unix__)
#elif IS_LINUX
#include <sys/ioctl.h>
#include <unistd.h>
// NOLINTNEXTLINE(misc-include-cleaner)
#include <climits> // MAX_PATH
#else
#error "Libassert doesn't recognize this system, please open an issue at https://github.com/jeremy-rifkin/libassert"
#include <fcntl.h>
#include <charconv>
#elif IS_APPLE
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/sysctl.h>
#include <unistd.h>
#endif

#include <libassert/assert.hpp>
Expand Down Expand Up @@ -52,6 +57,114 @@ namespace libassert {
#endif
}

#if IS_LINUX
class file_closer {
int fd;
public:
file_closer(int fd_) : fd(fd_) {}
file_closer(const file_closer&) = delete;
file_closer(file_closer&&) = delete;
file_closer& operator=(const file_closer&) = delete;
file_closer& operator=(file_closer&&) = delete;
~file_closer() {
if(close(fd) == -1) {
perror("Libassert: Something went wrong when closing a file descriptor.");
}
}
};
#endif

LIBASSERT_ATTR_COLD bool is_debugger_present_internal() noexcept {
#if IS_WINDOWS
return IsDebuggerPresent();
#elif IS_LINUX
// https://www.man7.org/linux/man-pages/man5/proc.5.html
// We're looking at the top of /proc/self/status until we find "TracerPid:"
// Sample:
// Name: cat
// Umask: 0022
// State: R (running)
// Tgid: 106085
// Ngid: 0
// Pid: 106085
// PPid: 104045
// TracerPid: 0
// The name is truncated at 16 characters, so this whole thing should be under 256 chars
constexpr std::size_t read_goal = 256;
int fd = open("/proc/self/status", O_RDONLY);
if(fd == -1) {
// something went wrong
return false;
}
file_closer closer(fd);
char buffer[read_goal];
auto size = read(fd, buffer, read_goal);
if(size == -1) {
// something went wrong
return false;
}
std::string_view status{buffer, std::size_t(size)};
constexpr std::string_view key = "TracerPid:";
auto pos = status.find(key);
if(pos == std::string_view::npos) {
return false;
}
auto pid_start = status.find_first_not_of(detail::whitespace_chars, pos + key.size());
if(pid_start == std::string_view::npos) {
return false;
}
auto pid_end = status.find_first_of(detail::whitespace_chars, pid_start);
if(pid_end == std::string_view::npos) {
return false;
}
// since we found a whitespace character after the pid we know the read wasn't somehow weirdly truncated
auto tracer_pid = status.substr(pid_start, pid_end - pid_start);
int value;
if(std::from_chars(tracer_pid.data(), tracer_pid.data() + tracer_pid.size(), value).ec == std::errc{}) {
return value != 0;
} else {
return false;
}
return false;
#else
// https://developer.apple.com/library/archive/qa/qa1361/_index.html
int mib[4] {CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()};
struct kinfo_proc info;
info.kp_proc.p_flag = 0;
size_t size = sizeof(info);
int res = sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0);
if(res != 0) {
// something went wrong, assume false
return false;
}
return info.kp_proc.p_flag & P_TRACED;
#endif
}

std::atomic<debugger_check_mode> check_mode = debugger_check_mode::check_once;
std::mutex is_debugger_present_mutex;
std::optional<bool> cached_is_debugger_present;

LIBASSERT_ATTR_COLD
bool is_debugger_present() noexcept {
if(check_mode.load() == debugger_check_mode::check_every_time) {
return is_debugger_present_internal();
} else {
std::unique_lock lock(is_debugger_present_mutex);
if(cached_is_debugger_present) {
return *cached_is_debugger_present;
} else {
cached_is_debugger_present = is_debugger_present_internal();
return *cached_is_debugger_present;
}
}
}

LIBASSERT_ATTR_COLD LIBASSERT_EXPORT
void set_debugger_check_mode(debugger_check_mode mode) noexcept {
check_mode = mode;
}

LIBASSERT_ATTR_COLD LIBASSERT_EXPORT void enable_virtual_terminal_processing_if_needed() {
// enable colors / ansi processing if necessary
#if IS_WINDOWS
Expand Down Expand Up @@ -82,7 +195,6 @@ namespace libassert::detail {

LIBASSERT_ATTR_COLD std::string strerror_wrapper(int e) {
std::unique_lock lock(strerror_mutex);
// NOLINTNEXTLINE(concurrency-mt-unsafe)
return strerror(e);
}
}
6 changes: 2 additions & 4 deletions src/utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -101,15 +101,13 @@ namespace libassert::detail {
return vec;
}

constexpr const char * const ws = " \t\n\r\f\v";

LIBASSERT_ATTR_COLD
std::string_view trim(const std::string_view s) {
const size_t l = s.find_first_not_of(ws);
const size_t l = s.find_first_not_of(whitespace_chars);
if(l == std::string_view::npos) {
return "";
}
const size_t r = s.find_last_not_of(ws) + 1;
const size_t r = s.find_last_not_of(whitespace_chars) + 1;
return s.substr(l, r - l);
}

Expand Down
2 changes: 2 additions & 0 deletions src/utils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ namespace libassert::detail {
return a;
}

constexpr const char* const whitespace_chars = " \t\n\r\f\v";

LIBASSERT_ATTR_COLD
std::string_view trim(std::string_view s);

Expand Down
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ if(PROJECT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
set(
binary_sources
tests/binaries/basic_test.cpp
tests/binaries/basic_demo.cpp
tests/binaries/gtest-demo.cpp
tests/binaries/catch2-demo.cpp
tests/binaries/tokens_and_highlighting.cpp
Expand Down
5 changes: 5 additions & 0 deletions tests/binaries/basic_demo.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#include <libassert/assert.hpp>

int main() {
ASSERT(1 + 1 == 3);
}
2 changes: 0 additions & 2 deletions tests/unit/literals.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ inline std::vector<std::string> split(std::string_view s, std::string_view delim
return vec;
}

// https://stackoverflow.com/a/25385766/15675011
// TODO: template
constexpr const char * const ws = " \t\n\r\f\v";

// trim from end of string (right)
Expand Down

0 comments on commit 046da6e

Please sign in to comment.