【问题标题】:Serious clang bug for compiling arm code编译arm代码的严重clang错误
【发布时间】:2018-05-23 09:18:11
【问题描述】:

昨天,当我尝试为 arm(至少在 android arm-v7a 中)编译代码时,我在 clang 中发现了一个灾难性问题。看到这个小代码:

void init_c_32(uint8_t *ptr)
{
    uint32_t tmp[SIZE];
    memcpy(tmp, ptr, 33);
}

这里是调用memcpy这里生成的汇编代码:

0x7903d714 <+20>: ldr    r0, [sp, #0x10]
0x7903d716 <+22>: add    r3, sp, #0x14
0x7903d718 <+24>: mov.w  r12, #0x20
0x7903d71c <+28>: str    r0, [sp, #0xc]
0x7903d71e <+30>: mov    r0, r3
0x7903d720 <+32>: ldr    r3, [sp, #0xc]
0x7903d722 <+34>: str    r1, [sp, #0x8]
0x7903d724 <+36>: mov    r1, r3
0x7903d726 <+38>: str    r2, [sp, #0x4]
0x7903d728 <+40>: mov    r2, r12
0x7903d72a <+42>: blx    0x7903d658                ; symbol stub for: __aeabi_memcpy

它使用__aeabi_memcpy,任何ptr 地址都可以。现在如果我们将参数类型更改为uint32_t *,生成的汇编代码将更改如下:

void init_c_32(uint32_t *ptr)
{
    uint32_t tmp[SIZE];
    memcpy(tmp, ptr, 33);
}

0x790456dc <+20>: ldr    r0, [sp, #0x8]
0x790456de <+22>: add    r3, sp, #0xc
0x790456e0 <+24>: ldm.w  r0!, {r4, r5, r12, lr}
0x790456e4 <+28>: stm.w  r3!, {r4, r5, r12, lr}
0x790456e8 <+32>: ldm.w  r0, {r4, r5, r12, lr}
0x790456ec <+36>: stm.w  r3, {r4, r5, r12, lr}

这段代码做了很多优化,使用ldm.wstm.w而不是memcpy。结果是一个更快的代码,但有一个缺点。此代码无法与奇数的ptr 地址一起正常工作,并创建SIGBUS 异常,该异常基于生成的汇编代码是正确的。 .w 寻址将寻址模型限制为偶数值,但也许我们可以说这是设计使然,因为我们已将参数定义为 unit32_t * 并且我们说该参数必须对齐。

主要问题发生在这里。检查以下代码:

void init_c_32(__packed uint32_t *ptr)
{
    uint32_t tmp[SIZE];
    memcpy(tmp, ptr, 33);
}

如您所见,尽管我们指定了uint32_t * 作为输入参数,但我们使用了__packed 说明符。正如standard 指定的那样,__packed 表示:

使用非对齐访问读取或写入打包类型的对象。

但是当我们看到生成的汇编代码时,我们会看到以下内容:

0x78ec56dc <+20>: ldr    r0, [sp, #0x8]
0x78ec56de <+22>: add    r3, sp, #0xc
0x78ec56e0 <+24>: ldm.w  r0!, {r4, r5, r12, lr}
0x78ec56e4 <+28>: stm.w  r3!, {r4, r5, r12, lr}
0x78ec56e8 <+32>: ldm.w  r0, {r4, r5, r12, lr}
0x78ec56ec <+36>: stm.w  r3, {r4, r5, r12, lr}

如您所见,生成的代码与非__packed 模式没有区别,这与ARM 标准冲突。您仍然不能使用奇数地址进行引用,您将得到SIGBUS 异常。我认为在这种情况下生成的代码应该类似于我们使用uint8_t * 作为参数时的代码。

我认为这是一个非常严重的错误,会产生意想不到的结果,欢迎任何好的解决方案。

我使用 ndk 16 来解决这个问题,它使用 clang 5.0.3 作为编译器。

当前的解决方法是始终使用uint8_t * 作为输入,从而创建正确的代码。但从效率上来说,如果这个问题解决了会更好。

【问题讨论】:

  • 通常错误会报告给上游,而不是 SO。试试llvm.org/bugs
  • @arrowd 遗憾的是,注册现已关闭,仅限电子邮件。这就是为什么我在这里写它以便其他人也知道它。
  • 我们也鼓励您open an issue Android NDK。
  • 我还没有在这里看到错误...这是标准操作,您应该避免未对齐的地址,如果您希望此代码正常工作,那么您需要禁用处理器中的未对齐陷阱。将未对齐的地址馈送到设置为捕获未对齐地址的处理器中,与编译器无关的设置不是编译器的错。无论您在哪个平台上,最好的解决方案是永远不要产生不一致的原因。令人不安的是,ARM 现在允许未对齐的地址,最好不要生成该代码。
  • 由于您可能无法控制该平台的对齐,因此您需要生成与该平台兼容的代码,您要求编译器为对齐的平台生成未对齐的代码,看起来它做了你问的。程序员知道平台是否捕获了未对齐的访问,如果程序员不告诉工具,编译器就无法知道这一点,因此作为责任方,您不应该告诉编译器生成无法在平台上运行的代码,而不是告诉编译器要解决的问题.

标签: android-ndk arm clang


【解决方案1】:

FWIW,clang,与 ARM C 编译器不同,不允许使用 __packed 指针。对于 clang__packed__attribute__((__packed__)) 的同义词,仅适用于枚举、结构或联合:http://gcc.gnu.org/onlinedocs/gcc-3.3/gcc/Type-Attributes.html

【讨论】:

  • 但问题是 gcc 中的代码生成总是使用调用 memcpy 然后 SIGBUS 异常不会发生未对齐访问。 clang 优化很棒,但同时也产生了这个问题。确实__packed 等于__attribute__((__packed__)),但我认为生成的代码不应该产生这种行为。也许我错了,但我认为clang的做法不正确。
  • 对我来说,Clang 合法地警告该属性被忽略。在此警告之后,它没有义务尊重该属性。
  • memcpy 在这里不相关,您的打包结构可以/将会产生对齐错误,如果您没有看到这些,那么这只是时间问题或运气不好。
  • 充其量这可能是一个错误,因为编译器没有忽略请求和/或编译器未能警告代码不起作用
  • 但是gccclang 之间还有另一个区别使这个问题升级。我有一个像struct {char a; char b[4];} c; 这样的小结构。在 gcc 中,生成的代码在 4 字节边界上对齐 b。但是clang 不会为b 执行此操作,并且它的地址变得奇怪。所以现在,如果出于任何原因,我通过将其转换为*(uint32_t*)(&amp;(c.b)) 来访问b 的地址,它可能会产生同样的问题。主要问题是你无法预测这个问题会发生。只有向结构添加一个 1 字节的成员才能为其他结构成员和 SIGBUS 创建奇数地址。
猜你喜欢
  • 2018-11-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-01-19
  • 1970-01-01
  • 2019-01-18
  • 2015-10-21
相关资源
最近更新 更多