Версия для печати
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум на Исходниках.RU > Assembler > floating-point invalid в dll


Автор: Abraziv 28.04.17, 07:32
Здравствуйте. Использую dll в VS C++ написанную в Borlande. Эта dll писалась для одного приложения, в котором она отлично работает.
Используя эту .dll в VS, вылетает ошибка floating-point invalid при использовании одной из функций.
Ковыряю dll в IDA pro, обнаружил, что ошибка вылетает при выполнении следующей конструкции:

<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    do
    {
    ...
    var = (unsigned __int64) (pow(2.0, (double) k) - 1.0);
    ...
    k++;
    }
    while(k < 64)


Попробовал выполнить этот код в консольном приложение VS, вылетает точно такая же ошибка: floating-point invalid.
Как быть? Почему в приложении собранном в Borland эта .dll успешно работает, при использовании же в VS вылетает ошибка.
Кстати под Win x64 .dll так же работает, по понятным причинам. С чем это связано? VS при сборке проекта изменяет эту .dll, может нужны какие нибудь параметры компиляции?
Было бы здорово заменить конструкцию приведённую выше на:

<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    do
    {
    ...
    var = ((unsigned __int64) 1 << k) - 1;
    ...
    k++;
    }
    while(k < 64)

Автор: MBo 28.04.17, 07:58
Соглашение о вызове совпадает?

Автор: Abraziv 28.04.17, 07:59
100%
В Win x64 же работает.

Автор: Jin X 28.04.17, 08:22
А код этой DLL есть? Это функция pow?

Автор: Abraziv 28.04.17, 08:23
Нету. Только псевдокод в IDA.

Добавлено
Нет, это не функция pow(). То что я привёл, это кусок псевдокода из IDA. Несколько итераций проходит, потом падает.

Добавлено
Как вообще происходит использование .dll. В нете не могу найти инфу. Просто мне кажется, что компилятор что то делает с этой .dll.

Добавлено
Хотя нет. Exe собранная на Win64 под x86 запускается только на Win64, если запустить на Win32, то падает. Компилятор здесь не причём. Это винда значит, что-то делает.

Автор: Jin X 28.04.17, 08:43
А что делает эта функция-то вообще?
Прикрепите эту DLL-ку и вызов функции (нерабочий и рабочий код - VS, Borland).
Сложно что-то сказать, ничего не видя...

Вызов функции из DLL происходит как обычный вызов функции. Небольшие отличия есть только в вызове через указатель (GetProcAddress) и когда DLL грузится системой (когда функция в импорте), но на работе это никак не должно отражаться.

Автор: MBo 28.04.17, 10:22
Цитата
100%
В Win x64 же работает.


В 64-битном режиме соглашение о вызове единственное, так что это не аргумент.

Покажи объявление функции в DLL, если оно есть и в использующем её приложении Билдера и VS

Автор: Abraziv 28.04.17, 10:24
Не в 64 битном режиме, а на 64 битной винде 32 битное приложение.

Добавлено
Ок. Скоро скину сюда.

Автор: MBo 28.04.17, 10:37
Еще вариант - посмотреть, совпадает ли кодовое слово сопроцессора 8087CW

Автор: Abraziv 28.04.17, 10:40
А можно по подробнее ? Где в VS глянуть?

Добавлено
Сразу же не вкурил что вы имели ввиду. Вы имеете ввиду слово состояния сопроцессора. Хм. Как узнать, какой используется в том приложении. В IDA pro? Но как?

Автор: MBo 28.04.17, 11:30
IDA не пользовался. В отладчике Borland его состояние можно видеть.

В Borland-овских продуктах его получают функцией Get8087CW и устанавливают Set8087CW. Если аналоги есть в VS, то можно сравнить состояние перед вызовом

Автор: Abraziv 28.04.17, 11:31
У меня только .dll. Нет возможности запустить в отладке.

Автор: MBo 28.04.17, 11:45
В отладке запускается приложение, использующее DLL.
Приложение же своё, самописное? Тогда и отладка не нужна - вывести слово состояния перед вызовом.

Автор: Abraziv 28.04.17, 13:18
Да самописное. Народ подскажите как в плюсах (WinAPI) установить слово состояния арифметического сопроцессора. Или без ассемблера ни как не обойтись? И какой слово состояния используется в делфях по дефолту???

Автор: MBo 28.04.17, 13:28
$1332 или $1372

Автор: Abraziv 28.04.17, 13:34
Попробую сделать так:
__asm {
MOV EAX, 0x1332;
PUSH EAX;
FLDCW [ESP];
POP EAX;
}
Сейчас нет возможности проверить, т.к. винда 64 бита.

Добавлено
Спасибо всем заранее.

Автор: Jin X 28.04.17, 16:56
Цитата Abraziv @
Попробую сделать так
Попробуйте 0x133F, т.к. это замаскирует все ошибки, и исключения при ошибках вызываться не будут. Но вообще, если причина в этом, значит либо в коде DLL ошибка, либо ей передаются неверные данные, которые не проверяются.

Цитата Abraziv @
Не в 64 битном режиме, а на 64 битной винде 32 битное приложение.
В 32-битных программах соглашений куча. И они могут быть другими в Borland'е (и Borland - это что: Builder, Delphi...?) А в какой винде запускается 32-битная прога (x86 или x64) разницы быть не должно. Если возникает ошибка только в x86, то это странно.

Почему не хотите прислать DLL и код, который его вызывает (рабочий - борландовский и MSVSCP)? Мы бы могли посмотреть в отладчике что происходит, а не гадать на кофейной гуще.
И что вообще эта функция должна делать?
Может, там вообще глюк из разряда: нужно передать Double через 2 даблворда в стеке, а он передаётся через ссылку...

p.s. CW - управляющее слово только, а не кодовое ;)

Добавлено
Только сейчас увидел сообщение в личке... :)

Автор: Qraizer 28.04.17, 18:40
Вангую переполнение стека FPU.

Автор: Jin X 28.04.17, 20:14
В общем, ситуация следующая...
DLL-функция вызывает функцию Trunc(X), которая удаляет дробную часть числа. Результат возвращается в виде 64-битного целого.
Код этой функции таков:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    add esp,FFFFFFF4
    wait
    fnstcw word ptr ss:[esp]
    wait
    mov al,byte ptr ss:[esp+1]
    or byte ptr ss:[esp+1],C
    fldcw word ptr ss:[esp]
    fistp qword ptr ss:[esp+4]
    mov byte ptr ss:[esp+1],al
    fldcw word ptr ss:[esp]
    mov eax,dword ptr ss:[esp+4]
    mov edx,dword ptr ss:[esp+8]
    add esp,C
    ret
Т.е. читается CW, устанавливаются биты, отвечающие за округление в режим "обрезания дробной части", выполняется fistp, затем CW восстанавливается.

Так вот, каждый раз туда передаётся число, вдвое больше предыдущего (с вычетом единицы). В определённый момент (на последнем цикле) значение этого числа выходит за рамки 64 битного знакового числа, fistp помечает соответствующие биты в SW (status word) о том, что произошла исключительная ситуация, однако исключения сразу не происходит (оно происходит при вызове fwait и т.п. инструкций, в т.ч. после fldcw). И тут попадается fldcw, и возникает исключение.
Почему только в 32-битной Windows? Потому что в 64-битной Windows CW=027F, в 32-битной CW=1272 (по крайней мере, в 7-ке, все вопросы к мелкомягким и их VSC++). Т.е. в 32-битной не замаскировано исключение #IA (invalid arithmetic), которое возникает при записи слишком большого значения как целого.

Что делать?
Вызывать finit из основной проги перед вызовом функции armInit :)
Или выполнять такой код:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
        short CW, CWnew;
        __asm {
            fnstcw CW
            fnstcw CWnew
            or CWnew, 1  // можно сделать or CWnew,0x3F для надёжности :)
            fldcw CWnew
        }
        amrInit(0);
        __asm fldcw CW


Добавлено
Ну или так:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
        __asm {
            push eax
            fnstcw [esp]
            push eax
            fnstcw [esp]
            or [esp], 1
            fldcw [esp]
            pop eax
        }
        amrInit(0);
        __asm {
            fldcw [esp]
            pop eax
        }

Автор: Qraizer 28.04.17, 20:30
В таком случае это не проблема ОСи. Trunc() не должна сообщать о проблеме, т.к. от проебразования к unsigned __int64 требуется беззнаковый результат, значит коли это происходит, Trunc() реализована неверно.

Автор: Jin X 28.04.17, 20:48
А это и не проблема оси. Это проблема MSVCPP, потому что на момент старта программы CW=027F как в 32 битах, так и в 64-х. И прога его меняет его ещё до вызова mainCRTStartup в зависимости от битности винды...

Автор: Qraizer 29.04.17, 01:22
Цитата Jin X @
Это проблема MSVCPP, потому что на момент старта программы CW=027F как в 32 битах, так и в 64-х. И прога его меняет его ещё до вызова mainCRTStartup в зависимости от битности винды...
Хм.
Visual Studio 2015:
Цитата _control87, _controlfp, __control87_2
По умолчанию библиотеки времени выполнения маскируют все исключения для операций с плавающей запятой .
И насколько я помню, так было всегда. Кто-то однозначно неправ.

Добавлено
Попробую ещё раз вангануть: dll имеет и другие проблемы, по крайней мере с совместимостью.

Автор: Jin X 29.04.17, 05:48
Цитата Qraizer @
Попробую ещё раз вангануть: dll имеет и другие проблемы, по крайней мере с совместимостью.
Ну, скорее всего, да.
Пустой проект показывает нормальный CW (027F) к моменту mainCRTStartup...

Автор: Qraizer 29.04.17, 11:22
Переписать код. Видишь ли, var = (unsigned __int64) (pow(2.0, (double) k) - 1.0); работает – если работает – по чистой случайности. FPU не имеет беззнаковых типов, даже целочисленных. При сохранении в квадрословное целое FPU всегда сохраняет его как знаковое. Если подать ему 263, то будет фиксироваться переполнение, о котором сообщается через IA. Если оно замаскировано, то маскированной реакцией является возврат целочисленной неопределённости, которая имеет вид минимально возможного (знакового) целого, т.е. 0x8000000000000000. И оно по чистой случайности совпадает с 263 в беззнаковом целом формате.

Автор: Qraizer 29.04.17, 11:57
NaN – это нечисло. Думаю, этого достаточно, чтобы понять абсурдность желания. NaN появляется в вычислениях, которые невозможно вычислить. Они не имеют результата. Даже если поделить 123 на 0, будет не NaN, а всего лишь INF, с которым в принципе можно продолжать вычисления. При делении 0 на 0 будет NaN.
Если твоему алгоритму подходят выражения без результата, то мне сложно представить, что полезного такой алгоритм в состоянии предложить.

Автор: leo 30.04.17, 04:33
Цитата Qraizer @
Видишь ли, var = (unsigned __int64) (pow(2.0, (double) k) - 1.0); работает – если работает – по чистой случайности.
... Если подать ему 263, то будет фиксироваться переполнение

С чего это? В соответствии с выражением к целому должно приводиться не 263, а 263-1.0 = _I64_MAX. Может просто ms-оптимизатор тупит, заменяя вещественное вычитание целочисленным?

Автор: Jin X 30.04.17, 06:51
leo, Qraizer, вы оба правы, только есть нюанс: точность.
Оптимизатор не тупит, всё нормально. В Trunc передаётся уже 263-1, но! Мантисса FPU (при 80-битной точности) содержит 63 бита (без знака). 263-1 – это ещё 63 бита, а вот 263 – уже 64. После pow результат содержит число 263, после него идёт вычитание единицы (fsub), вот только вычесть эту единицу он не может, т.к. точности не хватает, и там остаётся 263. И при преобразовании в unsigned __int64:
Цитата Qraizer @
оно по чистой случайности совпадает с 263 в беззнаковом целом формате
:)

Добавлено
Хотя результат по задумке должен быть 263-1... так что, уж не знаю в какой момент эта DLL-ка глюканёт при дальнейшей работе...

Добавлено
Вспоминается статья про Закон дырявых абстракций...
Как раз в тему... :)

Автор: Jin X 30.04.17, 07:33
Хотя, нет, немного не так... FPU нормально вычитает единицу из 263 при точности FPU-операций в 80 бит, результат же умещается в 63 бита. Проблема в другом: CW=1272 в 32 битах и CW=027F в 64 битах. А это вычисления с точностью double, т.е. 64 бита (где составляет 52 бита без учёта знака), т.е. результат будет неверным (без вычета единицы) значительно раньше, чем на последнем цикле. Так что, решить первоначальную проблему лучше изменением в CW не младшего бита, а установкой значения 3 в битах 8-9:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
        __asm {
            push eax
            fnstcw [esp]
            or word ptr [esp],0x300  // если ошибки всё же будут возникать (уже в другом месте), можно заменить 0x300 на 0x301, но тогда будет потеря точности вычислений (местами весьма серьёзная)... но такого быть уже не должно
            fldcw [esp]
            pop eax
        }
        amrInit(0);

Тогда и результаты вычислений будут верными...

Автор: Abraziv 30.04.17, 07:39
Спасибо за ответ. Огромное спасибо. Сейчас буду тестировать. После отпишусь. Скажу сразу же, прога не падает после асм вставки.

Автор: leo 30.04.17, 11:36
Цитата Jin X @
Мантисса FPU (при 80-битной точности) содержит 63 бита (без знака)...

Учти, что вычисляется степень двойки, а двоичная мантисса любого числа 2N строго равна 1 и представляется абсолютно точно в любом вещественном формате. Не знаю, как в билдере и мсвс, а в дельфях math.Power(..) для целых степеней считается через IntPower методом умножения, поэтому число 263 должно вычисляться абсолютно точно с мантиссой строго равной 1 без всякого дребезга в младших разрядах.

Автор: Jin X 30.04.17, 14:29
Цитата leo @
Учти, что вычисляется степень двойки, а двоичная мантисса любого числа 2N строго равна 1 и представляется абсолютно точно в любом вещественном формате.
Это понятно, только ты из 2N не сможешь вычесть единицу, если N > 63 (для 80-битных чисел) или > 52 (для double), или > 23 (для single/float). А поскольку точность была настроена на double, то вычесть из 263 единицу не получалось, и в unsigned __int64 преобразовывалось не _I64_MAX, а _I64_MAX+1, и возникало исключение.

Добавлено
Цитата leo @
в дельфях math.Power(..) для целых степеней считается через IntPower методом умножения
И надо сказать, что это значительно быстрее, чем через fyl2x/f2xm1/fscale. Иногда в 2 раза, а иногда раз в 5-6 (при малых степенях). Специально мерил... :)
Но там есть косяк (в Delphi): он не может вычислять степень > MaxInt (по модулю) из отрицательных чисел. Возникает исключение. Что в Delphi 7, что в Berlin. Т.е. если число > MaxInt или нецелое, он выполняет вычисление через fyl2x/f2xm1/fscale (и вылетает на fyl2x, соответственно).

Добавлено
Если кому надо – вот рабочая функция вычисления степени любого числа из любого. Вариант без IntPower (есть ещё и с IntPower).
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    ;##############################################
    ;##                                          ##
    ;##         -= FPU POWER FUNCTION =-         ##
    ;##       ФУНКЦИЯ ВОЗВЕДЕНИЯ В СТЕПЕНЬ       ##
    ;##                [ v1.00 ]                 ##
    ;##            MASM/TASM (16/32)             ##
    ;##                                          ##
    ;##   (c) 2017 by Jin X (jin.x@sources.ru)   ##
    ;##            http://xk7.ru/p/a/i           ##
    ;##                                          ##
    ;##############################################
     
    ;-- FPPower: функция возведения X в степень Y --------------------------------------------------------------------------
    ; Входные данные: st(0) = основание X, st(1) = показатель степени Y
    ; Результат: st(0) = st(0)^st(1) = X^Y (или 0 при ошибке), st(1) пуст; CF=NC=0 - успешный результат, CF=CY=1 - ошибка (при X<0 и нецелом Y)
    ; Функция изменяет AX, DX и флаги
     
    FPPower     proc
            ; Сравнение X с нулём
            ftst                ; st(0)=X=0 ?
            fstsw   ax
            mov dl,ah           ; dl and 1=1, если X<0 (используется в чистом FPU-расчёте и при проверке ошибки), иначе dl and 1=0
            sahf
            jz  short @@Zero        ; если X=0, результат=st(0)=0 (записываем чистый ноль - если допустим ноль со знаком, можно сделать прыжок на @@Free1AndExit); st(1)=Y; CF=NC=0
     
            ; Проверка Y на целое
            fld st(1)           ; st(0)=Y, st(1)=X, st(2)=Y
            frndint             ; st(0)=Round(st(0))=Round(Y), st(1)=X, st(2)=Y
            fcomp   st(2)           ; сравнение st(0) и st(2): Round(Y) и Y; st(0)=X, st(1)=Y, st(2) пуст
            fstsw   ax
            sahf
            jne short @@ErrChk      ; прыгаем, если Y нецелое
     
            ; Степень Y - целое число, проверяем знак X
            shr dl,1            ; проверяем знак X (dl and 1)
            jnc short @@PosX        ; прыгаем, если X>0 (dl=0)
     
            ; Проверка чётности целого Y (при X<0)
            fld1                ; st(0)=1, st(1)=X, st(2)=Y
            fld1                ; st(0)=st(1)=1, st(2)=X, st(3)=Y
            faddp               ; st(0)=2.0, st(1)=X, st(2)=Y
            fld st(2)           ; st(0)=Y, st(1)=2.0, st(2)=X, st(3)=Y
            fabs                ; st(0)=|Y|, st(1)=2.0, st(2)=X, st(3)=Y
            fprem               ; st(0)=|Y| mod 2.0 (если Y не чересчур большой, иначе st(0) будет содержать неверное значение, и тогда мы будем считать, что число чётное), st(1)=2.0, st(2)=X, st(3)=Y
            fld1                ; st(0)=1, st(1)=Y mod 2.0, st(2)=2.0, st(3)=X, st(4)=Y
            fcompp              ; сравнение st(0) и st(1): 1 и (Y mod 2.0) с удалением этих чисел; st(0)=2.0, st(1)=X, st(2)=Y, st(3) и st(4) пусты
            fstsw   ax
            shr ah,6            ; CF=ZF (т.к. нам надо получить CF=нечётный Y)
            sahf                ; CF=NC=0, если Y чётное; CF=CY=1, если Y нечётное
            fstp    st          ; удаляем 2.0; st(0)=X, st(1)=Y, st(2) пуст
            fabs                ; st(0)=|X|, st(1)=Y
        @@PosX: ; CF=NC=0, если dl and 1=0 (т.е. X>0, сюда был прыжок) или Y чётное
            fyl2x               ; st(0)=Y*log2(|X|)=Z,          st(1) пуст[st(0)=st(1)*log2(st(0))]
            fld st          ; st(0)=Z,              st(1)=Z
            frndint             ; st(0)=Round(Z),           st(1)=Z
            fxch                ; st(0)=Z,              st(1)=Round(Z)
            fsub    st,st(1)        ; st(0)=Z-Round(Z),         st(1)=Round(Z)
            f2xm1               ; st(0)=2^(E-Round(Z))-1,       st(1)=Round(Z)
            fld1                ; st(0)=1,              st(1)=2^(E-Round(Z))-1, st(2)=Round(Z)
            faddp   st(1),st        ; st(0)=st(0)+1 = 2^(E-Round(Z)),   st(1)=Round(Z),     st(2) пуст
            fscale              ; st(0)=2^(E-Round(Z))*2^Round(Z)=2^Z,  st(1)=Round(Z)  [st(0)=st(0)*2^RoundTowardZero(st(1))]
            ; st(0)=результат=X^Y
            jnc short @@Free1AndExit    ; прыгаем, если X>0, либо Y чётное
            fchs                ; иначе меняем знак результата
            clc             ; ошибок нет, CF=NC=0
        @@Free1AndExit:
            ffree   st(1)           ; удаляем лишнее значение st(1)=Round(Z) или st(1)=X; st(0)=результат
            ret
            ; Проверка ошибки (Y нецелое!)
        @@ErrChk:
            shr dl,1            ; проверяем знак X (dl and 1)
            jnc short @@PosX        ; прыгаем, если X>0 (dl=0; CF=NC=0)
            ; иначе ошибка (dl=1: X<0 и Y нецелое); CF=CY=1
        @@Zero:
            fldz                ; st(0)=0, st(1)=X, st(2)=Y
            ffree   st(2)           ; удаляем лишнее значение st(2)=Y
            jmp short @@Free1AndExit
    FPPower     endp

Powered by Invision Power Board (https://www.invisionboard.com)
© Invision Power Services (https://www.invisionpower.com)