Целочисленное переполнение не соответствует

Простите, если этот вопрос был задан ранее. Я искал ответы на похожие вопросы, но все еще озадачен своей проблемой. Так что я все равно буду снимать вопрос. Я использую библиотеку C под названием libexif для данных изображений. Я запускаю свое приложение (которое использует эту библиотеку) как на рабочем столе 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).

В случае ошибки exif_get_short() который должен возвращать значение без знака (34687), возвращает отрицательное число (-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)

Я использую libexif в Qt - Qt Media hub (на самом деле libexif поставляется вместе с Qt Media hub)

EDIT2: некоторые дополнительные наблюдения: я наблюдаю что-то странное. Я поместил операторы печати в 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);

Я вижу следующее о / п: return_value 34665 34665

Затем я также вставил операторы print в код, который вызывает exif_get_short()

....
tag = exif_get_short (d + offset + 12 * i, data->priv->order);
printf("TAG %d %u\n",tag,tag);

Я вижу следующее о / п: TAG -30871 4294936425

EDIT3: публикация кода сборки для exif_get_short() и exif_get_sshort(), сделанного на плате MIPS

        .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

Просто для полноты, ASM-код взят с моего Linux-компьютера.

    .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: надеюсь, мое последнее обновление:-) ASM-код с опцией компилятора, установленной в -O1

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 ответа

Решение

Одна вещь, которую показывает сборка MIPS (хотя я не эксперт в сборке MIPS, так что есть большая вероятность, что я что-то упустил или иным образом ошибаюсь), это exif_get_short() функция просто псевдоним для exif_get_sshort() функция. Все это exif_get_short() делает, это перейти к адресу exif_get_sshort() функция.

exif_get_sshort() Знак функции расширяет 16-битное значение, которое он возвращает, до полного 32-битного регистра, используемого для возврата. В этом нет ничего плохого - на самом деле это, вероятно, то, что указывает ABI MIPS (я не уверен).

Тем не менее, так как exif_get_short() функция просто прыгает на exif_get_sshort() функция, она не имеет возможности очистить верхние 16 бит регистра.

Поэтому, когда 16-битное значение 0x8769 возвращается из буфера (из exif_get_sshort() или же exif_get_short()), $2 регистр, используемый для возврата результата функции, содержит 0xffff8769, который может иметь следующие интерпретации:

  • как 32-битный signed int: -30871
  • как 32-битное `unsigned int: 4294936425

  • как 16-битная подпись int16_t: -30871

  • как 16-битный без знака uint16_t: 34665

Если компилятор должен гарантировать, что $2 регистр возврата имеет верхний 16-битный ноль для uint16_t возвращаемый тип, то есть ошибка в коде, который он излучает exif_get_short() - вместо того, чтобы прыгать на exif_get_sshort()должен позвонить exif_get_sshort() и очистить верхнюю половину $2 перед возвращением.

Из описания поведения, которое вы видите, это выглядит как код, вызывающий exif_get_short() ожидает, что $2 resister, используемый для возвращаемого значения, будет очищен верхними 16 битами, так что весь 32-битный регистр может использоваться как есть для 16-битного uint16_t значение.

Я не уверен, что указывает MIPS ABI (но я предполагаю, что он указывает, что старшие 16 бит $2 регистрация должна быть очищена exif_get_short()), но, похоже, есть либо ошибка генерации кода, которая exif_get_short() не гарантирует $2 совершенно правильно, прежде чем он вернется или ошибка, когда вызывающий exif_get_short() предполагает, что полные 32-битные $2 действительны, когда только 16 бит.

Это сломано на многих уровнях, я не знаю, с чего начать. Просто посмотрите, что здесь сделано:

  • Символы без знака читаются из буфера.
  • Они назначены на подписанный int16_t в exif_get_sshort,
  • Это назначено неподписанному uint16_t в exif_get_short,
  • Это наконец назначено enum который имеет тип подписан int,

Я бы сказал, что это чудо, оно работает вообще.

Во-первых, назначение из символов для int16_t делается со значениями, а не с представлением:

return ((buf[0] << 8) | buf[1]);

Который уже бросает вас в яму неопределенного поведения, когда результат на самом деле отрицательный. Кроме того, он работает только в том случае, если представление реализации со знаком int совпадает с тем, которое используется в формате файла (я полагаю, это дополнение к двум). Это потерпит неудачу за свое дополнение и знаменательность. Итак, проверьте, в чем дело к реализации MIPS.

Чистый путь был бы наоборот: назначить два символа из буфера uint16_t, который будет хорошо определенными операциями, и используйте это, чтобы вернуть int16_t, Затем вы могли бы позаботиться о дальнейших исправлениях значения для разных представлений, если это необходимо.

Дополнительно здесь:

if (!buf) return 0;

0 очень плохой выбор возвращаемого значения, потому что это допустимая константа перечисления:

EXIF_TAG_GPS_VERSION_ID             = 0x0000,

Если ожидается, что это значение по умолчанию для недействительности, тогда должна быть возвращена константа, а не магическое число. Хотя это, кажется, универсальная функция для возврата int16_tТаким образом, здесь должен использоваться какой-то другой механизм ошибок.

Что касается вашего конкретного вопроса, хорошо, следуйте потоку конверсий между подписанным и неподписанным в вашей реализации MIPS, включая продвижение по умолчанию, выполненное, и изучите все промежуточные значения, чтобы найти точку, в которой это происходит. Ваш MIPS использует 32-битные, а не 16-битные, верно? Проверьте INT_MAX и UINT_MAX.

Другие вопросы по тегам