C-未对准地址和瑞银发现的负载



这个问题不是关于不一致的数据访问的定义,而是为什么memcpy使ubsan的发现沉默,而类型铸造则没有,尽管生成了相同的汇编代码。

我有一些示例代码来解析一个协议,该协议将一个字节阵列分割为六个字节的组。

void f(u8 *ba) {
    // I know this array's length is a multiple of 6
    u8 *p = ba;
    u32 a = *(u32 *)p;
    printf("a = %dn", a);
    p += 4;
    u16 b = *(u16 *)p;
    printf("b = %dn", b);
    p += 2;
    a = *(u32 *)p;
    printf("a = %dn", a);
    p += 4;
    b = *(u16 *)p;
    printf("b = %dn", b);
}

通过6将我的指针递增并进行另外32位读取后,瑞银报告了有关未对准负载的错误。我使用memcpy而不是类型限制来抑制此错误,但是我不太了解原因。需要明确的是,这是没有瑞银错误的同一例程,

void f(u8 *ba) {
    // I know this array's length is a multiple of 6 (
    u8 *p = ba;
    u32 a;
    memcpy(&a, p, 4);
    printf("a = %dn", a);
    p += 4;
    memcpy(&b, p, 2);
    printf("b = %dn", b);
    p += 2;
    memcpy(&a, p, 4);
    printf("a = %dn", a);
    p += 4;
    memcpy(&b, p, 2);
    printf("b = %dn", b);
}

两个例程都编译为相同的汇编代码(使用movl进行32位读取和16位读取的 movzwl),那么,当另一个读取另一个时,为什么一种不确定的行为是一个不确定的行为?memcpy是否有一些特殊的特性可以保证某些东西?

我不想在这里使用memcpy,因为我不能依靠编译器来优化它。

ub消毒剂用于检测 ,代码不是严格符合的,并且实际上取决于无法保证的未定义行为。

实际上C标准说,该行为是未定义的,只要您将指针投入到一个地址不适当对齐的类型的指针。C11(草稿,N1570)6.3.2.3p7:

指向对象类型的指针可以转换为指针转换为其他对象类型。如果结果指针未正确对齐68),则该行为是未定义的。

即。

u8 *p = ba;
u32 *a = (u32 *)p; // undefined behaviour if misaligned. No dereference required

的存在铸件允许编译器假定ba与4字节边界对齐(在需要对齐的u32的平台上,许多编译器将在x86上进行此操作),之后它可以生成假定对齐的代码。

即使在x86平台上,也有一些令人惊叹的说明: 看起来无辜的代码可以编译到机器代码中,这将在运行时导致流产。瑞银应该在 catch 否则,否则在运行它时看起来和行为"预期",但是如果使用另一组选项或其他不同的选项编译,则会失败优化水平。

编译器可以生成memcpy-的正确代码,并且通常会,但这仅仅是因为编译器会知道,未对准的访问将有效,并且表现良好在目标平台上。

最后:

我不想在这里使用memcpy,因为我不能依靠编译器来优化它。

您在这里说的是:"我希望我的代码可靠地工作唯一的每当垃圾或两个十年的旧编译器编译时,它们会生成慢速代码。与那些可以优化其快速运行的人。"

对象的原始类型最好是 u32,一个u32的数组...否则,您使用memcpy明智地处理了此操作。这不太可能是现代系统的重要瓶颈。我不用担心。

在某些平台上,整数在每个可能的地址都不存在。考虑您系统的最大地址,我们可以在0xFFFFFFFFFFFFFFFF上假设。一个四字节的整数不可能在这里存在,对吗?

有时会在硬件上进行优化,以使总线(从CPU通往各种外围设备,内存和何种方式)对齐总线,其中之一就是假设仅发生各种类型的地址例如,它们的大小的倍数。这种平台上的未对准访问可能会导致陷阱(segfault)。

因此,瑞银(UBSAN)正确警告您这个不可携带且难以调试问题的问题。

此问题不仅会导致某些系统无法完全工作,而且您会找到您的系统,使您可以从对齐中访问,需要在总线上进行第二次获取以检索整数的第二部分。


此代码中还有其他一些问题。

printf("a = %dn", a);

如果要打印int,则应使用%d。但是,您的论点是u32。不要像这样的论点不匹配。这也是不确定的行为。我不确定如何为您定义u32,但我想最接近标准的功能可能是uint32_t(来自<stdint.h>)。您应该在要打印uint32_t的任何地方使用"%"PRIu32作为格式字符串。PRIu32(来自<inttypes.h>)符号提供了实现定义的字符序列,该序列将通过实现printf函数识别。

请注意,此问题是在其他地方重复的,您正在使用u16类型:

printf("b = %dn", b);

"%"PRIu16可能就足够了。

相关内容

  • 没有找到相关文章

最新更新