Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
||
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[3.133.121.160] |
|
Сообщ.
#1
,
|
|
|
Здравствуйте. Использую dll в VS C++ написанную в Borlande. Эта dll писалась для одного приложения, в котором она отлично работает.
Используя эту .dll в VS, вылетает ошибка floating-point invalid при использовании одной из функций. Ковыряю dll в IDA pro, обнаружил, что ошибка вылетает при выполнении следующей конструкции: 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, может нужны какие нибудь параметры компиляции? Было бы здорово заменить конструкцию приведённую выше на: do { ... var = ((unsigned __int64) 1 << k) - 1; ... k++; } while(k < 64) |
Сообщ.
#2
,
|
|
|
Соглашение о вызове совпадает?
|
Сообщ.
#3
,
|
|
|
100%
В Win x64 же работает. |
Сообщ.
#4
,
|
|
|
А код этой DLL есть? Это функция pow?
|
Сообщ.
#5
,
|
|
|
Нету. Только псевдокод в IDA.
Добавлено Нет, это не функция pow(). То что я привёл, это кусок псевдокода из IDA. Несколько итераций проходит, потом падает. Добавлено Как вообще происходит использование .dll. В нете не могу найти инфу. Просто мне кажется, что компилятор что то делает с этой .dll. Добавлено Хотя нет. Exe собранная на Win64 под x86 запускается только на Win64, если запустить на Win32, то падает. Компилятор здесь не причём. Это винда значит, что-то делает. |
Сообщ.
#6
,
|
|
|
А что делает эта функция-то вообще?
Прикрепите эту DLL-ку и вызов функции (нерабочий и рабочий код - VS, Borland). Сложно что-то сказать, ничего не видя... Вызов функции из DLL происходит как обычный вызов функции. Небольшие отличия есть только в вызове через указатель (GetProcAddress) и когда DLL грузится системой (когда функция в импорте), но на работе это никак не должно отражаться. |
Сообщ.
#7
,
|
|
|
Цитата 100% В Win x64 же работает. В 64-битном режиме соглашение о вызове единственное, так что это не аргумент. Покажи объявление функции в DLL, если оно есть и в использующем её приложении Билдера и VS |
Сообщ.
#8
,
|
|
|
Не в 64 битном режиме, а на 64 битной винде 32 битное приложение.
Добавлено Ок. Скоро скину сюда. |
Сообщ.
#9
,
|
|
|
Еще вариант - посмотреть, совпадает ли кодовое слово сопроцессора 8087CW
|
Сообщ.
#10
,
|
|
|
А можно по подробнее ? Где в VS глянуть?
Добавлено Сразу же не вкурил что вы имели ввиду. Вы имеете ввиду слово состояния сопроцессора. Хм. Как узнать, какой используется в том приложении. В IDA pro? Но как? |
Сообщ.
#11
,
|
|
|
IDA не пользовался. В отладчике Borland его состояние можно видеть.
В Borland-овских продуктах его получают функцией Get8087CW и устанавливают Set8087CW. Если аналоги есть в VS, то можно сравнить состояние перед вызовом |
Сообщ.
#12
,
|
|
|
У меня только .dll. Нет возможности запустить в отладке.
|
Сообщ.
#13
,
|
|
|
В отладке запускается приложение, использующее DLL.
Приложение же своё, самописное? Тогда и отладка не нужна - вывести слово состояния перед вызовом. |
Сообщ.
#14
,
|
|
|
Да самописное. Народ подскажите как в плюсах (WinAPI) установить слово состояния арифметического сопроцессора. Или без ассемблера ни как не обойтись? И какой слово состояния используется в делфях по дефолту???
|
Сообщ.
#15
,
|
|
|
$1332 или $1372
|
Сообщ.
#16
,
|
|
|
Попробую сделать так:
__asm { MOV EAX, 0x1332; PUSH EAX; FLDCW [ESP]; POP EAX; } Сейчас нет возможности проверить, т.к. винда 64 бита. Добавлено Спасибо всем заранее. |
Сообщ.
#17
,
|
|
|
Цитата Abraziv @ Попробуйте 0x133F, т.к. это замаскирует все ошибки, и исключения при ошибках вызываться не будут. Но вообще, если причина в этом, значит либо в коде DLL ошибка, либо ей передаются неверные данные, которые не проверяются.Попробую сделать так Цитата Abraziv @ В 32-битных программах соглашений куча. И они могут быть другими в Borland'е (и Borland - это что: Builder, Delphi...?) А в какой винде запускается 32-битная прога (x86 или x64) разницы быть не должно. Если возникает ошибка только в x86, то это странно.Не в 64 битном режиме, а на 64 битной винде 32 битное приложение. Почему не хотите прислать DLL и код, который его вызывает (рабочий - борландовский и MSVSCP)? Мы бы могли посмотреть в отладчике что происходит, а не гадать на кофейной гуще. И что вообще эта функция должна делать? Может, там вообще глюк из разряда: нужно передать Double через 2 даблворда в стеке, а он передаётся через ссылку... p.s. CW - управляющее слово только, а не кодовое Добавлено Только сейчас увидел сообщение в личке... |
Сообщ.
#18
,
|
|
|
Вангую переполнение стека FPU.
|
Сообщ.
#19
,
|
|
|
В общем, ситуация следующая...
DLL-функция вызывает функцию Trunc(X), которая удаляет дробную часть числа. Результат возвращается в виде 64-битного целого. Код этой функции таков: 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 Так вот, каждый раз туда передаётся число, вдвое больше предыдущего (с вычетом единицы). В определённый момент (на последнем цикле) значение этого числа выходит за рамки 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 Или выполнять такой код: short CW, CWnew; __asm { fnstcw CW fnstcw CWnew or CWnew, 1 // можно сделать or CWnew,0x3F для надёжности :) fldcw CWnew } amrInit(0); __asm fldcw CW Добавлено Ну или так: __asm { push eax fnstcw [esp] push eax fnstcw [esp] or [esp], 1 fldcw [esp] pop eax } amrInit(0); __asm { fldcw [esp] pop eax } |
Сообщ.
#20
,
|
|
|
В таком случае это не проблема ОСи. Trunc() не должна сообщать о проблеме, т.к. от проебразования к unsigned __int64 требуется беззнаковый результат, значит коли это происходит, Trunc() реализована неверно.
|
Сообщ.
#21
,
|
|
|
А это и не проблема оси. Это проблема MSVCPP, потому что на момент старта программы CW=027F как в 32 битах, так и в 64-х. И прога его меняет его ещё до вызова mainCRTStartup в зависимости от битности винды...
|
Сообщ.
#22
,
|
|
|
Цитата Jin X @ Хм. Это проблема MSVCPP, потому что на момент старта программы CW=027F как в 32 битах, так и в 64-х. И прога его меняет его ещё до вызова mainCRTStartup в зависимости от битности винды... Visual Studio 2015: Цитата _control87, _controlfp, __control87_2 И насколько я помню, так было всегда. Кто-то однозначно неправ. По умолчанию библиотеки времени выполнения маскируют все исключения для операций с плавающей запятой . Добавлено Попробую ещё раз вангануть: dll имеет и другие проблемы, по крайней мере с совместимостью. |
Сообщ.
#23
,
|
|
|
Цитата Qraizer @ Ну, скорее всего, да.Попробую ещё раз вангануть: dll имеет и другие проблемы, по крайней мере с совместимостью. Пустой проект показывает нормальный CW (027F) к моменту mainCRTStartup... |
Сообщ.
#24
,
|
|
|
Переписать код. Видишь ли, var = (unsigned __int64) (pow(2.0, (double) k) - 1.0); работает – если работает – по чистой случайности. FPU не имеет беззнаковых типов, даже целочисленных. При сохранении в квадрословное целое FPU всегда сохраняет его как знаковое. Если подать ему 263, то будет фиксироваться переполнение, о котором сообщается через IA. Если оно замаскировано, то маскированной реакцией является возврат целочисленной неопределённости, которая имеет вид минимально возможного (знакового) целого, т.е. 0x8000000000000000. И оно по чистой случайности совпадает с 263 в беззнаковом целом формате.
|
Сообщ.
#25
,
|
|
|
NaN – это нечисло. Думаю, этого достаточно, чтобы понять абсурдность желания. NaN появляется в вычислениях, которые невозможно вычислить. Они не имеют результата. Даже если поделить 123 на 0, будет не NaN, а всего лишь INF, с которым в принципе можно продолжать вычисления. При делении 0 на 0 будет NaN.
Если твоему алгоритму подходят выражения без результата, то мне сложно представить, что полезного такой алгоритм в состоянии предложить. |
Сообщ.
#26
,
|
|
|
Цитата Qraizer @ Видишь ли, var = (unsigned __int64) (pow(2.0, (double) k) - 1.0); работает – если работает – по чистой случайности. ... Если подать ему 263, то будет фиксироваться переполнение С чего это? В соответствии с выражением к целому должно приводиться не 263, а 263-1.0 = _I64_MAX. Может просто ms-оптимизатор тупит, заменяя вещественное вычитание целочисленным? |
Сообщ.
#27
,
|
|
|
leo, Qraizer, вы оба правы, только есть нюанс: точность.
Оптимизатор не тупит, всё нормально. В Trunc передаётся уже 263-1, но! Мантисса FPU (при 80-битной точности) содержит 63 бита (без знака). 263-1 – это ещё 63 бита, а вот 263 – уже 64. После pow результат содержит число 263, после него идёт вычитание единицы (fsub), вот только вычесть эту единицу он не может, т.к. точности не хватает, и там остаётся 263. И при преобразовании в unsigned __int64: Цитата Qraizer @ оно по чистой случайности совпадает с 263 в беззнаковом целом формате Добавлено Хотя результат по задумке должен быть 263-1... так что, уж не знаю в какой момент эта DLL-ка глюканёт при дальнейшей работе... Добавлено Вспоминается статья про Закон дырявых абстракций... Как раз в тему... |
Сообщ.
#28
,
|
|
|
Хотя, нет, немного не так... FPU нормально вычитает единицу из 263 при точности FPU-операций в 80 бит, результат же умещается в 63 бита. Проблема в другом: CW=1272 в 32 битах и CW=027F в 64 битах. А это вычисления с точностью double, т.е. 64 бита (где составляет 52 бита без учёта знака), т.е. результат будет неверным (без вычета единицы) значительно раньше, чем на последнем цикле. Так что, решить первоначальную проблему лучше изменением в CW не младшего бита, а установкой значения 3 в битах 8-9:
__asm { push eax fnstcw [esp] or word ptr [esp],0x300 // если ошибки всё же будут возникать (уже в другом месте), можно заменить 0x300 на 0x301, но тогда будет потеря точности вычислений (местами весьма серьёзная)... но такого быть уже не должно fldcw [esp] pop eax } amrInit(0); Тогда и результаты вычислений будут верными... |
Сообщ.
#29
,
|
|
|
Спасибо за ответ. Огромное спасибо. Сейчас буду тестировать. После отпишусь. Скажу сразу же, прога не падает после асм вставки.
|
Сообщ.
#30
,
|
|
|
Цитата Jin X @ Мантисса FPU (при 80-битной точности) содержит 63 бита (без знака)... Учти, что вычисляется степень двойки, а двоичная мантисса любого числа 2N строго равна 1 и представляется абсолютно точно в любом вещественном формате. Не знаю, как в билдере и мсвс, а в дельфях math.Power(..) для целых степеней считается через IntPower методом умножения, поэтому число 263 должно вычисляться абсолютно точно с мантиссой строго равной 1 без всякого дребезга в младших разрядах. |
Сообщ.
#31
,
|
|
|
Цитата leo @ Это понятно, только ты из 2N не сможешь вычесть единицу, если N > 63 (для 80-битных чисел) или > 52 (для double), или > 23 (для single/float). А поскольку точность была настроена на double, то вычесть из 263 единицу не получалось, и в unsigned __int64 преобразовывалось не _I64_MAX, а _I64_MAX+1, и возникало исключение. Учти, что вычисляется степень двойки, а двоичная мантисса любого числа 2N строго равна 1 и представляется абсолютно точно в любом вещественном формате. Добавлено Цитата leo @ И надо сказать, что это значительно быстрее, чем через fyl2x/f2xm1/fscale. Иногда в 2 раза, а иногда раз в 5-6 (при малых степенях). Специально мерил... в дельфях math.Power(..) для целых степеней считается через IntPower методом умножения Но там есть косяк (в Delphi): он не может вычислять степень > MaxInt (по модулю) из отрицательных чисел. Возникает исключение. Что в Delphi 7, что в Berlin. Т.е. если число > MaxInt или нецелое, он выполняет вычисление через fyl2x/f2xm1/fscale (и вылетает на fyl2x, соответственно). Добавлено Если кому надо – вот рабочая функция вычисления степени любого числа из любого. Вариант без IntPower (есть ещё и с IntPower). ;############################################## ;## ## ;## -= 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 |