1

I'm reverse engineering an ARM binary written in Go, and the following code sequence occurs fairly often (from runtime.cgoIsGoPointer):

BL  runtime.panicindex
DCD 0xF7FABCFD

or, from runtime.panicmem:

BL  runtime.gopanic
DCD 0xF7FABCFD

0xF7FABCFD is an undefined instruction, so I'm curious what it's purpose is. I think the answer may be related to this question, because compiling for x86 produces the following sequence of instructions in the corresponding functions:

callq runtime.panicindex
ud2

and

callq runtime.gopanic
ud2

It looks like the linux kernel uses different instructions for this purpose. From arch/arm/include/asm/bug.h:

/*
 * Use a suitable undefined instruction to use for ARM/Thumb2 bug handling.
 * We need to be careful not to conflict with those used by other modules and
 * the register_undef_hook() system.
 */
#ifdef CONFIG_THUMB2_KERNEL
#define BUG_INSTR_VALUE 0xde02
#define BUG_INSTR(__value) __inst_thumb16(__value)
#else
#define BUG_INSTR_VALUE 0xe7f001f2
#define BUG_INSTR(__value) __inst_arm(__value)
#endif

But there is reference to 0xf7fabcfd in some of the Go internals. From cmd/internal/obj/arm/asm5.go:

// This is supposed to be something that stops execution.
// It's not supposed to be reached, ever, but if it is, we'd
// like to be able to tell how we got there. Assemble as
// 0xf7fabcfd which is guaranteed to raise undefined instruction
// exception.
case 96: /* UNDEF */
        o1 = 0xf7fabcfd

Anyway, can anyone confirm this suspicion and/or shed more light into why this is done? Also, why not just use 0xe7f001f2 like the linux kernel does?

1 Answer 1

1

The (32-bit) ARM architecture has a instruction with opcode UDF whose encodings are guaranteed to be permanently undefined. There are 3 encodings of this instructions one each for Thumb, Thumb-2, and ARM. In hex these are -

Thumb   0xDE**       
Thumb2  0xF7F*A***   
ARM     0xE7F***F*   
*'s are ignored by the processor so can be any value.

There is also another ARM encoding that the architecture guarantees to be permanently undefined, albeit without giving it an opcode. This is -

ARM     0x*7F***F*
*'s are ignored by the processor so can be any value.

(Note that the UDF instruction is a special case of this encoding with the condition bits set to AL or 1110.)

The Linux code you quote is using the UDF opcode encodings for Thumb and ARM.

The Go code is using the ARM encoding which doesn't have an opcode.

Given the * bits are ignored by the processor and can be picked at the programmer's discretion, I would expect someone to choose their own values rather than spend time trying to find where other people have used the instruction any picking the same values as them.

Indeed, the Linux code you quote explicitly explains that the value was chosen to avoid conflicts with other deliberate uses of undefined instructions with Linux's undefined instruction hook mechanism. So, if the Go authors were aware of the Linux usage here, I'd still expect them to choose different values.

1
  • That makes sense - so, a unique undefined instruction is desirable because it's effectively a marker noting what body of code wanted to indicate that an error occurred (and if the Go code shared the same undefined instruction as is used by BUG_INSTR, it'd be more difficult to distinguish between errors raised by either.)
    – recvfrom
    Mar 17, 2018 at 22:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.