整数溢出不一致

时间:2012-08-20 03:55:53

标签: c integer-overflow libexif

请原谅我这个问题。我寻找类似问题的答案,但我仍然对我的问题感到困惑。所以无论如何我会拍这个问题。 我正在使用名为libexif的C库来处理图像数据。我在我的Linux桌面和MIPS板上运行我的应用程序(使用此库)。 对于特定的图像文件,当我尝试获取创建的时间时,我收到错误/无效值。在进一步调试时,我看到对于这个特定的图像文件,我没有按预期获得标记(EXIF_TAG_DATE_TIME)。

这个库有几个实用功能。大多数功能的结构如下

int16_t 
exif_get_sshort (const unsigned char *buf, ExifByteOrder order)
{
    if (!buf) return 0;
        switch (order) {
        case EXIF_BYTE_ORDER_MOTOROLA:
                return ((buf[0] << 8) | buf[1]);
        case EXIF_BYTE_ORDER_INTEL:
                return ((buf[1] << 8) | buf[0]);
        }

    /* Won't be reached */
    return (0);
}

uint16_t
exif_get_short (const unsigned char *buf, ExifByteOrder order)
{
    return (exif_get_sshort (buf, order) & 0xffff);
}

当库试图调查原始数据中是否存在标记时,它会调用exif_get_short()并将返回的值赋给一个类型为enum(int)的变量。

在错误情况下,应该返回无符号值(34687)的exif_get_short()返回一个负数(-30871),这会从图像数据中提取整个标记。

34687超出了最大可表示的int16_t值的范围。因此导致溢出。当我在代码中进行这种轻微修改时,一切似乎都能正常工作

uint16_t
exif_get_short (const unsigned char *buf, ExifByteOrder order)
{
    int temp = (exif_get_sshort (buf, order) & 0xffff);
        return temp;
}

但是因为这是一个非常稳定的库并且在相当长的一段时间内使用,它让我相信我可能会遗漏一些东西。此外,这是为其他实用程序功能构建代码的一般方式。例如:exif_get_long()来电exif_get_slong()。然后我必须更改所有实用程序功能。

令我困惑的是,当我在我的linux桌面上运行这段代码以获取错误文件时,我发现没有问题,并且原始库代码的工作正常。这让我相信UINT16_MAX和INT16_MAX宏可能在我的桌面和MIPS板上有不同的值。但不幸的是,情况并非如此。两者都在电路板和桌面上打印相同的值。如果这段代码失败,它也应该在我的桌面上失败。

我在这里缺少什么?任何提示都将非常感激。

编辑: 调用exif_get_short()的代码是这样的:

ExifTag tag;
...
tag = exif_get_short (d + offset + 12 * i, data->priv->order);
switch (tag) {
...
...

ExifTag类型如下:

typedef enum {
    EXIF_TAG_GPS_VERSION_ID             = 0x0000,
EXIF_TAG_INTEROPERABILITY_INDEX     = 0x0001,
    ...
    ...
    }ExifTag ;

使用的交叉编译器是mipsisa32r2el-timesys-linux-gnu-gcc

CFLAGS        = -pipe -mips32r2 -mtune=74kc -mdspr2 -Werror -O3 -Wall -W -D_REENTRANT -fPIC $(DEFINES)

我在Qt-Qt Media hub中使用libexif(实际上libexif与Qt Media hub一起使用)

EDIT2:其他一些观察: 我正在观察奇怪的事情。我已将print语句放在exif_get_short()中。就在回来之前

printf("return_value %d\n %u\n",exif_get_sshort (buf, order) & 0xffff, exif_get_sshort (buf, order) & 0xffff);
return (exif_get_sshort (buf, order) & 0xffff);

我看到以下o / p: return_value 34665 34665

然后我还在调用exif_get_short()

的代码中插入了print语句
....
tag = exif_get_short (d + offset + 12 * i, data->priv->order);
printf("TAG %d %u\n",tag,tag);

我看到以下o / p: TAG -30871 4294936425

EDIT3:在MIPS板上发布exif_get_short()和exif_get_sshort()的汇编代码

        .file   1 "exif-utils.c"
    .section .mdebug.abi32
    .previous
    .gnu_attribute 4, 1
    .abicalls
    .text
    .align  2
    .globl  exif_get_sshort
    .ent    exif_get_sshort
    .type   exif_get_sshort, @function
exif_get_sshort:
    .set    nomips16
    .frame  $sp,0,$31       # vars= 0, regs= 0/0, args= 0, gp= 0
    .mask   0x00000000,0
    .fmask  0x00000000,0
    .set    noreorder
    .set    nomacro

    beq $4,$0,$L2
    nop

    beq $5,$0,$L3
    nop

    li  $2,1            # 0x1
    beq $5,$2,$L8
    nop

$L2:

    j   $31
    move    $2,$0

$L3:

    lbu $2,0($4)
    lbu $3,1($4)
    sll $2,$2,8
    or  $2,$2,$3
    j   $31
    seh $2,$2

$L8:

    lbu $2,1($4)
    lbu $3,0($4)
    sll $2,$2,8
    or  $2,$2,$3
    j   $31
    seh $2,$2

    .set    macro
    .set    reorder
    .end    exif_get_sshort
    .align  2
    .globl  exif_get_short
    .ent    exif_get_short
    .type   exif_get_short, @function

exif_get_short:

    .set    nomips16
    .frame  $sp,0,$31       # vars= 0, regs= 0/0, args= 0, gp= 0
    .mask   0x00000000,0
    .fmask  0x00000000,0
    .set    noreorder
    .cpload $25
    .set    nomacro

    lw  $25,%call16(exif_get_sshort)($28)
    jr  $25
    nop

    .set    macro
    .set    reorder
    .end    exif_get_short

为了完整起见,我的linux机器上的ASM代码

    .file   "exif-utils.c"
    .text
    .p2align 4,,15
    .globl  exif_get_sshort
    .type   exif_get_sshort, @function

exif_get_sshort:

.LFB1:

        .cfi_startproc
    xorl    %eax, %eax
    testq   %rdi, %rdi
    je  .L2
    testl   %esi, %esi
    jne .L8
    movzbl  (%rdi), %edx
    movzbl  1(%rdi), %eax
    sall    $8, %edx
    orl %edx, %eax
    ret
    .p2align 4,,10
    .p2align 3

.L8:
    cmpl    $1, %esi
    jne .L2
    movzbl  1(%rdi), %edx
    movzbl  (%rdi), %eax
    sall    $8, %edx
    orl %edx, %eax

.L2:
    rep
    ret
    .cfi_endproc

.LFE1:
    .size   exif_get_sshort, .-exif_get_sshort
    .p2align 4,,15
    .globl  exif_get_short
    .type   exif_get_short, @function

exif_get_short:

.LFB2:
    .cfi_startproc
    jmp exif_get_sshort@PLT
    .cfi_endproc
.LFE2:
    .size   exif_get_short, .-exif_get_short

EDIT4:希望我的最新更新:-) 编译器选项设置为-O1

的ASM代码

exif_get_short:

.set    nomips16
.frame  $sp,32,$31      # vars= 0, regs= 1/0, args= 16, gp= 8
.mask   0x80000000,-4
.fmask  0x00000000,0
.set    noreorder
.cpload $25
.set    nomacro

addiu   $sp,$sp,-32
sw  $31,28($sp)
.cprestore  16
lw  $25,%call16(exif_get_sshort)($28)
jalr    $25
nop

lw  $28,16($sp)
andi    $2,$2,0xffff
lw  $31,28($sp)
j   $31
addiu   $sp,$sp,32

.set    macro
.set    reorder
.end    exif_get_short

2 个答案:

答案 0 :(得分:4)

MIPS程序集显示的一件事(虽然我不是MIPS程序集的专家,所以我有一个不错的机会我错过了什么或其他错误)是exif_get_short()函数只是一个别名exif_get_sshort()功能。 exif_get_short()所做的只是跳转到exif_get_sshort()函数的地址。

exif_get_sshort()函数符号扩展了它返回到用于返回的完整32位寄存器的16位值。这没有什么不对 - 它实际上可能是MIPS ABI指定的(我不确定)。

但是,由于exif_get_short()函数只是跳转到exif_get_sshort()函数,因此没有机会清除寄存器的高16位。

因此,当从缓冲区返回16位值0x8769时(无论是来自exif_get_sshort()还是exif_get_short()),用于返回函数结果的$2寄存器包含{{1} },可以有以下解释:

  • 作为32位0xffff8769: - 30871
  • 作为32位`unsigned int:4294936425

  • 作为16位签名signed int: - 30871

  • 作为16位无符号int16_t:34665

如果编译器应该确保uint16_t返回寄存器对$2返回类型的前16位设置为零,那么它在为{发出的代码中有一个错误{1}} - 而不是跳转到uint16_t,它应该在返回之前调用exif_get_short()并清除exif_get_sshort()的上半部分。

根据您所看到的行为的描述,看起来调用exif_get_sshort()的代码期望用于返回值的$2寄存器将清除高16位以便整个32位寄存器可以原样用于16位exif_get_short()值。

我不确定MIPS ABI指定了什么(但我猜它指定$2寄存器的高16位应该由uint16_t清除,但似乎是$2在返回之前不确保exif_get_short()完全正确的代码生成错误,或者是exif_get_short()的调用者假定{{1}的完整32位的错误仅当16位时才有效。

答案 1 :(得分:0)

这在很多层面都被打破了,我不知道从哪里开始。看看这里做了什么:

  • 从缓冲区中读取无符号字符。
  • 他们被分配到int16_t中的签名 exif_get_sshort
  • 这已分配给uint16_t中的未签名 exif_get_short
  • 最后将其分配给{{1>},其类型为已签名 enum

我认为这是一个奇迹,它可以起作用。

首先,从字符到int的赋值是使用值完成的,而不是使用表示:

int16_t

当结果实际为负时,已经将你引入了未定义行为的陷阱。另外,它仅在实现的signed int表示与文件格式中使用的表示相同时才起作用(我想是两个补码)。它会因一个补码和符号幅度而失败。因此,请检查MIPS实现的情况。

干净的方式将是另一种方式:将两个字符从缓冲区分配给return ((buf[0] << 8) | buf[1]); ,这将是明确定义的操作,并使用它来返回uint16_t。如有必要,您可以关注不同表示的值的进一步更正。

此外,在这里:

int16_t

0是返回值的一个非常糟糕的选择,因为它是一个有效的枚举常量:

if (!buf) return 0;

如果预期这是无效的默认值,则应返回常量,而不是幻数。虽然这似乎是返回EXIF_TAG_GPS_VERSION_ID = 0x0000, 的通用函数,但是这里应该使用其他一些错误机制。

对于您的具体问题,请遵循MIPS实施中已签名和未签名的转换流程,包括已完成的默认促销,并检查所有中间值以找到中断点。你的MIPS使用32位整数,而不是16位,对吗?检查INT_MAX和UINT_MAX。