Linux – ARM v7 BKPT instructions do not work on Linux 2.6.35

ARM v7 BKPT instructions do not work on Linux 2.6.35… here is a solution to the problem.

ARM v7 BKPT instructions do not work on Linux 2.6.35

I have a question related to the BKPT directive on ARM v7 on Linux 2.6.35. The main reason is that the fault instruction address (bkpt) is incorrect, which does not match the ARM v7 manual.

The steps to copy are as follows:

  1. Redefine the operating system SIGBUS handler as my SIGBUS handler:

    void InitSigBusHandler() {  
        struct sigaction sa;  
        memset(&sa, 0, sizeof(sa));    
        sa.sa_flags = SA_SIGINFO;  
        sigfillset(&sa.sa_mask);  
        sa.sa_sigaction = SigBusHandler;  
        sigaction(SIGBUS, &sa, NULL);
        } 
    
  2. Use inline _asm and put the “BKPT” directive into the code of the main() function:

    int main(int argc, char **argv)  
    {
         InitSigBusHandler();
         __asm
         (
              "bkpt\n\t"
         );
    
    return 0;
    }
    
  3. Here is my SIGBUS handler:

    void SigBusHandler(    
        int         signum,  
        siginfo_t   *pAct,  
        void        *pOldAct  
        )
    {
        write(2,
             (const char *) MSG_SIGBUS_IN_HANDLER,  
              strlen((const char *)MSG_SIGBUS_IN_HANDLER)  
              );
    
    uint32_t faultAddr = (uint32_t)pAct->si_addr;  
        memcpy((void *)buffer,  
              (void *) MSG_SIGBUS_FAULT_ADDR,  
              strlen(MSG_SIGBUS_FAULT_ADDR)  
              );
    
    write(2,  
             (const char *) MSG_SIGBUS_FAULT_ADDR,  
              strlen((const char *)MSG_SIGBUS_FAULT_ADDR)  
              );  
    
    sprintf(buffer, "%x\n", faultAddr);  
        write(2, buffer, strlen(buffer));
    }   
    
  4. The problem is that the error address of the instruction (bkpt) is wrong, which is not compliant with the ARM v7 specification. This is the console output after the program is running:

    In SIGBUS handler:
    Fault Address: 86b0
    In SIGBUS handler:
    Fault Address: 86c0
    In SIGBUS handler:
    Fault Address: 86c0
    In SIGBUS handler:
    Fault Address: 86c0
    In SIGBUS handler:
    Fault Address: 86c0
    In SIGBUS handler:
    Fault Address: 86b0
    In SIGBUS handler:
    Fault Address: 86a8
    In SIGBUS handler:
    Fault Address: 86f0

On x86 architecture, this example works fine. On the ARM v7 architecture, this example has a strange behavior.

If I use GDB on ARM v7, he captures my BKPT instructions with the correct address.

Maybe someone knows what I’m doing wrong?

Solution

For breakpoint traps, the assumption that si_addr is precise (i.e., the address that actually operates when the failure occurs) is not necessarily correct/portable.

You do need to check the saved register state, which is the third parameter of the signal handler, which can be converted to ucontext_t*. State is not portable between CPUs, so the generic interface only passes void *; GDB examines it (so that info registers work) and extracts the wrong program counter from there, which is why it is able to point you to breakpoint instructions.

If you tried, what you encounter on ARM is similar to what you encounter on 64-bit x86:

volatile char *ptr = (char*)0x1234567890abcdef;
char crashme = *ptr;

And you want the fault address in si_addr to be 0x1234567890abcdef. This is not the case because accessing this address creates a #GPF instead of a #PF fault, and the former does not set the failure address register on x86. If you look at the program counter saved as part of the ucontext_t/struct sigcontext (embedded in it), you will see the wrong instruction address, which will be precise

Change your signal handler to:

void SigBusHandler(
     int  signum,  
     siginfo_t  *pAct,  
     void  *context
    )
{
    struct sigcontext *ctx = &(((ucontext_t*)context)->uc_mcontext);
    uintptr_t fault_address = ctx->arm_pc;    /* that's what you'll see on ARM */
    ...
}

As mentioned earlier, the problem is that figuring out CPU register status necessarily gives you CPU-dependent code. You have to do some adaptation/packaging to keep this portability, such as:

#if defined(ARM)
#define GET_PC_FROM_CONTEXT(c) (((ucontext_t *)(c))->uc_mcontext.arm_pc)
#elsif defined(__i386__)
define GET_PC_FROM_CONTEXT(c) (((ucontext_t *)(c))->uc_mcontext.eip)
#elsif defined(__amd64__)
define GET_PC_FROM_CONTEXT(c) (((ucontext_t *)(c))->uc_mcontext.rip)
#endif

uintptr_t instr_address = GET_PC_FROM_CONTEXT(context);

Hope this helps!

Related Problems and Solutions