Turns out I can achieve what I want by making a syscall:
#include <unistd.h>
#if defined(__APPLE__) && defined(__aarch64__)
#define __debugbreak() __asm__ __volatile__(
" mov x0, %x0;
" /* pid */
" mov x1, #0x11;
" /* SIGSTOP */
" mov x16, #0x25;
" /* syscall 37 = kill */
" svc #0x80
" /* software interrupt */
" mov x0, x0
" /* nop */
:: "r"(getpid())
: "x0", "x1", "x16", "memory")
#elif defined(__APPLE__) && defined(__arm__)
#define __debugbreak() __asm__ __volatile__(
" mov r0, %0;
" /* pid */
" mov r1, #0x11;
" /* SIGSTOP */
" mov r12, #0x25;
" /* syscall 37 = kill */
" svc #0x80
" /* software interrupt */
" mov r0, r0
" /* nop */
:: "r"(getpid())
: "r0", "r1", "r12", "memory")
#elif defined(__APPLE__) && (defined(__i386__) || defined(__x86_64__))
#define __debugbreak() __asm__ __volatile__("int $3; mov %eax, %eax")
#endif
#define MYASSERT(expr) do { if (!(expr)){ __debugbreak(); } } while(0)
There is a trailing NOP mov x0, x0
for a reason: when assert breaks, debugger will stop exactly at the assert line and not some random line where the following instruction happens to be located.
In case if somebody is looking for equivalent of IsDebuggerPresent on iOS, you can use AmIBeingDebugged.
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…