9

On Mac OS X and in the iOS simulator (both x86), we can trap to the debugger (LLDB) using the int3 instruction in inline assembly. This is nice because it traps to a particular line of code but we can continue immediately by hitting continue in the debugger.

Is there a way to do this on iOS hardware?

An answer to an older question mentions raise(SIGINT) which as far as I can see (from examining signal.h) does not exist. Another answer mentions the trap assembly instruction, which causes a build error ("Unrecognized instruction mnemonic"). Also unrecognized is the BKPT assembly instruction mentioned in ARM documentation.

I've tried __builtin_trap() which almost, almost does what I want, but does not allow me to continue. I keep hitting it unless I advance the instruction pointer manually using jump +1 or register write pc `$pc+8\`, which is much less convenient than just hitting continue.

I'm building for iOS 9 for 32- and 64-bit devices using Xcode 7.3.1. Any help is appreciated!

Community
  • 1
  • 1
Luke
  • 7,110
  • 6
  • 45
  • 74
  • 1
    Downvoter, want to explain? – Luke Jun 10 '16 at 20:40
  • I guess he downvoted your question because both signal() and SIGINT are part of standard C. http://opensource.apple.com//source/xnu/xnu-1456.1.26/bsd/sys/signal.h – diegog Jun 17 '16 at 17:54
  • raise(SIGINT) can work, but not always reliable, some time it will stop a little bit later, and you need to walk the thread tree to find where is the breakpoint. I once use this solution and then removed. I found later that ASSERT should stop without continue, you should face them instead of ignoring them, why do you want to assert then? – Wilson Chen Jun 20 '16 at 06:12
  • These types of non-fatal asserts are useful in a lot of cases, such as when code hasn't yet been implemented. – Luke Jun 20 '16 at 16:46

3 Answers3

7

Apple's libc signal.h includes XNU's sys/signal.h, which does define SIGINT (on all platforms):

// [...]

#define SIGHUP  1   /* hangup */
#define SIGINT  2   /* interrupt */
#define SIGQUIT 3   /* quit */
// [...]

So while I cannot confirm that this practice does indeed work (due to lack of an iOS 9 device on my part), the barrier that held you back should not actually be a problem.

As for the assembly instructions, BKPT is a valid ARM instruction, though only for A32. The A64 variant is called BRK.
If you're building fat binaries and use either of those unconditionally, you will always run into compiler error.

Also note that both instructions require an immediate value (which is passed to the debugger). Omitting that value will produce a compiler error as well.

That said, you should be able to insert debug instructions for A32 and A64 with a simple #ifdef:

#ifdef __aarch64__
asm volatile("BRK 0");
#else
asm volatile("BKPT 0");
#endif

You can substitute 0 by any value of your choosing between 0 and 255.

A note on the TRAP instruction: While Apple's assembler seems to accept this instruction for A32 and translates it to 0xe7ffdefe, it will issue an "unrecognized instruction mnemonic" on A64, analogous to the BKPT instruction.
I was also unable to find any reference to the instruction on the ARM Information Center or in Apple's documentation.

Siguza
  • 21,155
  • 6
  • 52
  • 89
  • Some great info here, thanks! Unfortunately BRK/BKPT has the same problem that __builtin_trap() does. I will try SIGINT again and see what happens. – Luke Jun 20 '16 at 16:46
  • SIGINT and SIGTRAP do in fact do what I want, although the stack is one level deeper than what I'd prefer. Thanks! – Luke Jun 20 '16 at 17:01
  • Happy I was able to help. [Another answer](https://stackoverflow.com/a/20713868) mentions `SVC`, which is the non-debug counterpart to `BKPT`/`BRK` - care to try if `asm("SVC 0")` works any differently than `BKPT`/`BRK`? – Siguza Jun 20 '16 at 18:50
  • `__LP64__` is probably the wrong macro. There's an AArch64 ILP32 ABI which still runs in 64-bit mode, just using 32-bit pointers to save cache footprint. [What predefined macro can I use to detect the target architecture in Clang?](//stackoverflow.com/q/23934862) says `__aarch64__` is the macro you should use. Should work in at least GCC and clang, hopefully other ARM compilers. – Peter Cordes Feb 17 '19 at 18:13
1

I've had a similar problem using Swift on OS X: 1) raise(SIGINT) didn't work for me in background processes (e.g: SceneKit's Scene Renderer Protocol). (perhaps a missing handler?) 2) __builtin_trap() doesn't allow continuing after 3) asm(" int3 ") requires ObjC and header files, which scared me off at first. But it wasn't too bad. Just added three lines in two new files:

---- NSObject+MachineTrap.h ----

void machineTrap(void);

---- NSObject+MachineTrap.h ----

#import "NSObject+MachineTrap.h"
void machineTrap(void) { asm (" int3 "); } /// Program has TRAPPED to DEBUGGER ///`

(My choice of int3 over say BRK, BKPT, SVC may not be correct.)

Allen King
  • 303
  • 2
  • 8
  • `asm(" int3 ")` is an x86 instruction. This question is about ARM. Also, why would you not want the `int3` to inline? Definite the function as `static inline` in a header and you don't need a `.c` – Peter Cordes Feb 17 '19 at 18:16
0

I had similar problem with ARM based MacBook Pro (Apple M1 chip). This worked for me:

asm ("brk #0xF000");

This may work with iOS devices too.