ARM v7 BKPT指令在Linux 2.6.35上无法正常运行

时间:2011-05-11 12:58:42

标签: linux arm

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

以下是复制的步骤:

  1. 将OS SIGBUS处理程序重新定义为我的SIGBUS处理程序:

    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. 使用内联_asm并将“BKPT”指令放入 main()函数中的代码中:

    int main(int argc, char **argv)  
    {
         InitSigBusHandler();
         __asm
         (
              "bkpt\n\t"
         );
    
         return 0;
    }
    
  3. 这是我的SIGBUS处理程序:

    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. 问题是指令的故障地址(bkpt)是错误的,并不符合ARM v7规范。这是程序工作后的控制台输出:

      

    在SIGBUS处理程序中:
      故障地址:86b0
      在SIGBUS处理程序中:
      故障地址:86c0
      在SIGBUS处理程序中:
      故障地址:86c0
      在SIGBUS处理程序中:
      故障地址:86c0
      在SIGBUS处理程序中:
      故障地址:86c0
      在SIGBUS处理程序中:
      故障地址:86b0
      在SIGBUS处理程序中:
      故障地址:86a8
      在SIGBUS处理程序中:
      故障地址:86f0

  5. 在x86架构上,此示例可正常工作。在ARM v7架构上,此示例具有奇怪的行为。

    如果我在ARM v7上使用GDB,他会使用正确的地址捕获我的BKPT指令。

    也许有人知道我做错了什么?

1 个答案:

答案 0 :(得分: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);

希望有所帮助!