【问题标题】:what type of code can trigger unaligned data access sigbus trap dynamically?什么类型的代码可以动态触发未对齐的数据访问 sigbus 陷阱?
【发布时间】:2019-09-18 12:16:13
【问题描述】:

我正在寻找关于未对齐数据访问的 SIGBUS。我正在跟踪其中一个错误,我想知道这在 sitara am335x 上是如何发生的。谁能给我一个示例代码来描述或确保触发它。

添加代码sn-p:

int Read( void *value, uint32_t *size, const uint32_t baseAddress )
{
    uint8_t *userDataAddress = (uint8_t *)( baseAddress + sizeof( DBANode ));
    memcpy( value, userDataAddress, ourDataSize );
    *size = ourDataSize;
    return 0;
}

DBA 节点是一个 20 字节的类对象。 baseAddress 是一个映射到共享内存文件的另一个 DBANode 类对象类型的映射到一个 uint32_t 以便可以完成算术。

这是该部分的反汇编:

    91a8:   e51b3010    ldr r3, [fp, #-16]
    91ac:   e5933000    ldr r3, [r3]
    91b0:   e51b0014    ldr r0, [fp, #-20]  ; 0xffffffec
    91b4:   e51b1008    ldr r1, [fp, #-8]
    91b8:   e1a02003    mov r2, r3
    91bc:   ebffe72b    bl  2e70 <memcpy@plt>
    91c0:   e51b3010    ldr r3, [fp, #-16]
    91c4:   e5932000    ldr r2, [r3]
    91c8:   e51b3018    ldr r3, [fp, #-24]  ; 0xffffffe8
    91cc:   e5832000    str r2, [r3]

00002e70 <memcpy@plt>:
    2e70:   e28fc600    add ip, pc, #0, 12
    2e74:   e28cca08    add ip, ip, #8, 20  ; 0x8000
    2e78:   e5bcf868    ldr pc, [ip, #2152]!    ; 0x868

重新构建完全相同的代码库后,问题就消失了。 gcc 是否可以创建 2 个不同版本的指令,对 gcc 指定的 -O0 优化相同?

我还对库进行了比较,以便文件 obj 在两个编译中转储。它们完全相同。该api经常被使用。但是,只有在长时间使用几天后才会发生崩溃。我每 500 毫秒读取一次同一个节点。所以这并不一致。 我应该看看指针损坏吗?

【问题讨论】:

  • 由于您想要特定类型的答案,这可能不是重复的,但它应该让您对您的问题有所了解 - stackoverflow.com/a/18269440/1730895 我与这些将 Win32 代码移植到 PocketPC 的错误作斗争,有时它们很难解决。最好的开始是确保您的结构正确包装。然后小心你如何投射和访问这些成员。
  • 由于遗留代码和第 3 方组件的负担,这很复杂。但现在我将满足于基线测试代码。实际代码包含在 cpp 对象中,其中包含大量类型转换和具有动态内存分配的模板。
  • 你知道这是否会发生在 memcpy 上吗?我的核心文件实际上追溯到 memcpy,但我无法重现它。不幸的是,我正在跟踪一个生产单元,这意味着符号实际上已被剥离。
  • 你好吗memcpy()ing,你是什么memcpy()ing - 这一切都回到了对齐。你需要像这样复制块吗?如何编写一个结构复制函数(可能设置为inline),逐个成员复制。您能否将有问题的数据结构添加到您的问题中?
  • 我会添加该信息

标签: gcc arm memory-alignment qnx qnx-neutrino


【解决方案1】:

原来 baseAddress 是问题所在。正如我提到的那样,它是一个指向 mmap 可能失败的共享内存位置的 mmap。失败的 mmap 返回 -1 并且代码正在检查 NULL 并继续写入 -1 即 0xFFFFFFFF 导致 sigbus。 当我们使用 memcpy 时可以看到代码 1。尝试任何其他访问(例如直接字节寻址)会得到带有 sigbus 的代码 3。

我仍然不确定为什么它会触发 SIGBUS 而不是 SIGSEGV。这不应该是内存违规吗? 这是一个例子:

int main(int argc, char **argv)
{
    // Shared memory example                                                    
     const char *NAME = "SharedMemory";                                          
     const int SIZE = 10 * sizeof(uint8_t);                                      
     uint8_t src[]={0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88,0x99,0x00};          
     int shm_fd = -1;                                                            

     shm_fd = shm_open(NAME, O_CREAT | O_RDONLY, 0666);                          
     ftruncate(shm_fd, SIZE);                                                    

    // Map shared memory segment to address space                               
     uint8_t *ptr = (uint8_t *) mmap(0, SIZE, PROT_READ | PROT_WRITE | _NOCACHE, MAP_SHARED, shm_fd, 0);
     if(ptr == MAP_FAILED)                                                       
     {                                                                           
          std::cerr << "ERROR in mmap()" << std::endl;                            
      //  return -1;                                                              
      }                                                                           
      printf("ptr = 0x%08x\n",ptr);                                               
      std::cout << "Now storing data to mmap() memory" << std::endl;              
      #if 0                                                                           
      ptr[0] = 0x11;                                                              
      ptr[1] = 0x22;                                                              
      ptr[2] = 0x33;                                                              
      ptr[3] = 0x44;                                                              
      ptr[4] = 0x55;                                                              
      ptr[5] = 0x66;                                                              
      ptr[6] = 0x77;                                                              
      ptr[7] = 0x88;                                                              
      ptr[8] = 0x99;                                                              
      ptr[9] = 0x00;                                                              
      #endif                                                                          

      memcpy(ptr,src,SIZE);   //causes sigbus code 1                              
      shm_unlink(NAME);
}

我仍然不知道为什么 mmap 在 shm 上失败,即使我有 100MB 的可用 RAM 并且我的所有资源限制都设置为无限制,在 1000 个 fds 限制中仍然有超过 400 个 fds(文件描述符)可用。 !!!

【讨论】:

  • 可能是 SIGBUS,因为-1 不能被 4 整除。
【解决方案2】:

来自 Cortex-A8 技术参考手册:

处理器支持加载和存储未对齐的单词和 半字。处理器进行所需数量的内存访问 并透明传输相邻字节。

注意跨词边界的数据访问会增加访问时间。

设置 CP15 c1 控制寄存器中的 A 位启用对齐 检查。当 A 位设置为 1 时,两种类型的内存访问 生成数据中止信号和对齐故障状态代码:

  • 非半字对齐的 16 位访问

  • 非字对齐的 32 位加载或存储

对齐错误检测是强制性的地址生成功能 而不是可选支持的外部存储器功能 管理硬件。请参阅 ARM 架构参考手册 有关未对齐数据访问支持的更多信息。

从 ARM ARM 中,如果未与传输大小对齐,总是会产生对齐错误的指令: LDREX、STREX、LDREXD、STREXD、LDM、STM、LDRD、RFE、SRS、STRD、SWP、LDC、LDC2、STC、STC2、VLDM、VLDR、VPOP、VPUSH、VSTM、VSTR。

此外,大多数 PUSH、POP 和 VLDx 都指定了 :align:。

进一步,

在包含虚拟化扩展的实现中, 对设备或强排序内存的非对齐访问总是会导致 对齐错误数据中止异常

在链接的问题中,结构是导致“预期”未对齐访问的最明显方式,但堆栈指针或其他变量指针的任何损坏也会产生相同的结果。取决于内核的配置方式,将影响正常的单字/半字访问是缓慢还是触发故障。

如果您有权访问 ETM 跟踪,您将能够识别确切的访问权限。这部分似乎有 ETM/ETB(因此不需要花哨的跟踪捕获设备),但我不知道使用工具会多么容易。

至于什么代码可以触发这个,是的,即使memcpy() 也可能是problem。由于 ARM 指令集针对传输多个寄存器(或 AA64 中的寄存器对)进行了优化,因此优化的库函数将更喜欢“流式传输”数据,而不是执行逐字节加载和存储。根据数据结构和编译目标,完全有可能导致非法 LDM 到未对齐地址。

【讨论】:

  • 你知道这是否会发生在 memcpy 上吗?我的核心文件实际上追溯到 memcpy,但我无法重现它。不幸的是,我正在跟踪一个生产单元,这意味着符号实际上已被剥离。
  • 是的,请参阅链接的问题,以及链接到那里的 ARM 的(旧)文章。
猜你喜欢
  • 2013-02-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-03-22
相关资源
最近更新 更多