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