Почему сравнение с плавающей точкой работает, если внутренне это не так, как описано или задокументировано? Это причуды GDB? - PullRequest
2 голосов
/ 15 апреля 2020

Я работал с Professional Assembly Language Ричарда Блюма, и его начальная программа сравнения с плавающей точкой работает , но при запуске через gdb на Linux некоторые промежуточные шаги неожиданны .

Для эксперимента:

Вот программа (были добавлены мои комментарии, и я удалил nop, который не нужен при отладке дварфа):

.section .data
value1:
    .float 10.923
value2:
    .float 4.5532

.section .text
.globl _start
_start:

    flds value1     # Load a 32-bit floating-point value into the FPU stack
    fcoms value2    # Compare the value in ST0 with mem address &value2

        # Retrieving the value of the FPU status register (with no argument ...
    fstsw   # ... this defaults to AX)

    sahf    # Store AH into Flags: 00 => CF, 02 => PF, 06 => ZF
    ja greater
    jb lessthan

    movl $1, %eax   # Exit (condition: value1 = value2)
    movl $0, %ebx
    int $0x80

greater:
    movl $1, %eax   # Exit (condition: value1 is greater than value2)
    movl $2, %ebx
    int $0x80
lessthan:
    movl $1, %eax   # Exit (condition: value1 is less than value2)
    movl $1, %ebx
    int $0x80

(у меня также есть версия, переведенная и работающая в синтаксисе nasm, если кто-то захочет это сделать - но мой быстрый тест этого, похоже, не дал других результатов.)

Он построен и связан с:

as -o fcomtest.o fcomtest.s --32 --gdwarf-2
ld -o fcomtest fcomtest.o -m elf_i386

Я использую следующий gdb-input-script (возможно, вам придется добавить или удалить команду 'step' в последних двух строках в зависимости от того, какие значения1 и значение2 установлены и какая ветвь берется):

# suppressing the output when setting the breakpoint at _start 
set logging file /dev/null
set logging redirect on
set logging on
br _start
set logging off

printf "\nFirst outputting values1 and 2:\n"
x/f &value1
x/f &value2

# suppressing the output when the breakpoint is triggered
set logging on
run
set logging off

printf "Now, ST0 = %f\n", $st0

printf "\nChecking the intial value of the FPU status register...\n"
print/t $fstat

printf "\nfcoms value2  # Comparing the value in ST0 with mem address &value2\n\n"

set logging on
step
set logging off

printf "The FPU status register FSTAT now contains...\n"
print/t $fstat

printf "\nBefore copying FSTAT, the AX register contains: "
print/t $ax

printf "\nfstsw     #  Copying (16-bit) FPU status register, FSTAT, to AX"
set logging on
step
set logging off

printf "\n\nNow the contents of the AX register are: \n"
print/t $ax

printf "\nInitially, the EFLAGS register in binary is: \n"
print/t $eflags

printf "\nThat is, the flags set are: "
print $eflags

printf "\nsahf      # store the high 8-bits of AX into the corresponding flags "

set logging on
step
set logging off

printf "\n\nNow, the EFLAGS register contains: \n"
print/t $eflags

printf "\nIn other words, these flags are set: "
print $eflags

printf "\nNow branch according to the CF and ZF flags...\n"
step
step
step
step
q

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

$ gdb -q -x gdb-input-script > gdb.output fcomtest

Вы можете затем см. вывод, взбитый с:

$ cat gdb.output.

Теория Существует дискуссия о механизме , как это должно работать в следующих постах: x86 ассемблер: сравнение с плавающей запятой , Сборка: JA и JB работают некорректно , а в связанных статьях.

В частности, предполагается, что FCOM сравните значение в ST0 стека FPU и другое значение и измените биты кода C3, C2 и C0 регистра состояния FPU в соответствии с:

+-----------------+----+-----+----+
| Condition       | C3 |  C2 | C0 |
+-----------------+----+-----+----+
| ST0 > argument  | 0  |  0  | 0  |
| ST0 < argument  | 0  |  0  | 1  |
| ST0 = argument  | 1  |  0  | 0  |
+-----------------+----+-----+----+

Кроме того, SAHF должен отобразить спецификацию c биты для EFLAG. Вместо этого: вот краткое описание того, что на самом деле происходит с вводом этих значений («руководство» - это документация Intel):

** Странная часть **

Case 4II (value1 +ve, value2 +ve; val1 greater magnitude, val2 lesser magnitude)

15      14      13      12      11      10      09      08  -- FPU status reg.
FPU     C3      SP      SP      SP      C2      C1      C0

07      06      05      04      03      02      01      00  -- AH register

1       1       1       0       0       0       0       0

[In this result of FCOM:  C3 has been set to 1 -- not what was expected.  The
expected result was C3 = 0, C2 = 0, C0 = 0]

SAHF instruction:
From the manual: 07 => SF, 06 => ZF, 04 => AF, 02 => PF, 00 => CF
From the textbook: 06 => ZF, 02 => PF, 00 => CF
Actual behavior: -07 => SF, -06 => ZF , -04 => AF, 02 => PF ?, 00 => CF ? ]

EFLAGS register:

0       0       0       0       0       0       1       0   -- Before SAHF

SF      ZF      -       AF      -       PF      -       CF

0       0       0       1       0       0       1       0   -- After SAHF

[Here, CF = 0 and ZF = 0, so the JA branch is taken and the result is as
desired]

Я знаю, что это было несколько долго (но это показывает, как восстановить это очень легко). В итоге: если вы измените значения 1 и 2 и перекомпилируете, я обнаружу, что на самом деле происходит следующее - по крайней мере, в выводе gnu-debugger:


C3      C2      C0
1       0       0   -- Equal numbers case (behavior as described in the documentation)

-----------------------------------------------

1       1       0   -- Value1 < Value2, actual
    (SAHF acts as though it has a NOT applied before altering the flags)

(The status register itself seems to be the inverse of the expected:)
0       0       1   -- expected

-----------------------------------------------

1       0       0   -- Value1 > Value2

0       0       0   -- expected

(Output of SAHF contorts somehow to permit CF = ZF = 0 )

I я описал, как воспроизвести это, так что это просто копирование для просмотра результатов. Является ли это просто странностью в gdb, которая изменяет значения C3, C2 и C0, а затем настраивает их так, чтобы флаги работали? Во всех случаях правильная ветвь в конечном итоге берется ... Я не экспериментировал (не практиковался) с другими отладчиками, чтобы увидеть, не отладчик ли просто выполняет промежуточные шаги для случаев value1! = Value2.

...