ARM v7 BKPT指令在Linux 2.6.35上无法正常工作

3

我有一个与ARM v7上的Linux 2.6.35中的BKPT指令相关的问题。主要原因是故障指令(bkpt)的地址不正确,与ARM v7手册不符。

以下是重现问题的步骤:

  1. 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);
        } 
    
  2. 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;
    }
    
  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 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

在x86体系结构上,这个样例可以正常工作。但在ARM v7体系结构上,这个样例有奇怪的行为。
如果我在ARM v7上使用GDB,他会正确地捕获我的BKPT指令地址。
也许有人知道我做错了什么?
1个回答

4
si_addr被认为是断点陷阱发生时实际操作的精确地址,这个假设并不一定正确/可移植。
您需要检查已保存的寄存器状态,即信号处理程序的第三个参数,它可以转换为ucontext_t*。该状态在CPU之间不可移植,因此通用接口只传递void *; GDB会检查它(以使info registers工作),并从中提取故障的程序计数器,这就是为什么它能够指向断点指令的原因。
在ARM上遇到的情况类似于在64位x86上尝试的情况:
volatile char *ptr = (char*)0x1234567890abcdef;
char crashme = *ptr;

您希望故障地址位于si_addr,并且为0x1234567890abcdef。然而这不是事实,因为访问此地址将创建#GPF而非#PF故障,在x86上前者不会设置故障地址寄存器。如果您查看作为ucontext_t / struct sigcontext(嵌入其中)的一部分保存的程序计数器,就会看到故障指令的地址,这将是精确的。

将您的信号处理程序更改为:

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 */
    ...
}

问题在于,要想确定CPU寄存器状态,必须编写与CPU相关的代码。您需要进行一些适应性调整/封装以保持可移植性,例如:

#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);

希望这能帮到您!

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接