【问题标题】:ARM v7 BKPT instruction doesn't work correctly on Linux 2.6.35ARM v7 BKPT 指令在 Linux 2.6.35 上无法正常工作
【发布时间】:2011-05-11 12:58:41
【问题描述】:

我有一个问题是与 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”指令放入ma​​in()函数的代码中:

    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

在 x86 架构上,此示例可以正常工作。在 ARM v7 架构上,此示例有一个奇怪的行为。

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

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

【问题讨论】:

    标签: linux arm


    【解决方案1】:

    对于断点陷阱,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);
    

    希望有帮助!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-09-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-12-04
      • 1970-01-01
      相关资源
      最近更新 更多