ARM v7 BKPT instruction doesn't work correctly on Linux 2.6.35
I have a problem is connected with BKPT instruction on ARM v7 on Linux 2.6.35. The main reason is that the address of fault instruction (bkpt) is not correct and does not correspond to ARM v7 manual.
Here is the steps for reproducing:
Redefine OS SIGBUS handler to 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); }
Use the inline _asm and put the "BKPT" instruction into code in main() function:
int main(int argc, char **argv) { InitSigBusHandler(); __asm ( "bkpt\n\t" ); return 0; }
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)); }
The problem is the fault adress of instruction (bkpt) is wrong and does not correspond to ARM v7 specification. Here is the console output after the program worked:
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 sample works correctly. On ARM v7 architecture this sample has a strange behavior.
If I use GDB on ARM v7, he catches my BKPT instruction with correct address.
Maybe someone knows what I do wrong ?
The assumption that si_addr
is precise (i.e. the actual address operated on when the fault occurred) for a breakpoint trap is not necessarily true / portable.
You do need to inspect the saved register state, i.e. the third parameter to the signal handler, which can be cast to ucontext_t*
. The state is not portable between CPUs and hence the generic interface only passes a void *
; GDB inspects it (so that info registers
works) and extracts the program counter of the fault from there, that's why it's able to point you to the breakpoint instruction.
The situation you're encountering on ARM here is similar as to what you'd get on 64bit x86 if you tried:
volatile char *ptr = (char*)0x1234567890abcdef;
char crashme = *ptr;
and you expect the fault address in si_addr
to be 0x1234567890abcdef
. That won't be the case because this address on access will create a #GPF
not #PF
fault, and the former doesn't set the fault address register on x86. If you look into the program counter saved as part of ucontext_t
/ struct sigcontext
(embedded in there) you'll see the faulting instruction address though, and that'll 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 */
...
}
Problem, as said, is that figuring out CPU register state necessarily gives you CPU-dependent code. You'll have to make some adaptations / wrappers to keep this portable, like:
#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 that helps !
精彩评论