Код с и без -std=c99 дает разные результаты (реализация UMAC AE)
Длинная (очень длинная) история - я использую реализацию Теда Кровец для расчета UMAC
и для UMAC AE
шифрование ( http://www.fastcrypto.org/).
Когда я компилирую свой код (и / или тесты в umac.c
) с -std=c99
рассчитанный UMAC
ПОЛНОСТЬЮ отличается от ожидаемого (и неверного). Когда я удаляю эту опцию, все работает как шарм.
Есть идеи, что может вызвать это? И что я могу сделать, чтобы проверить, что происходит и что дает разные результаты?
$ gcc --version
gcc (Ubuntu 4.8.2-19ubuntu1) 4.8.2
$ uname -a
xxx 3.13.0-43-generic #72-Ubuntu SMP .. x86_64 x86_64 x86_64 GNU/Linux
Я не использую никаких других опций - только с и без -std=c99
,
Еще несколько слов:
Я постараюсь связаться с Тедом Кровецом и спросить его об этом (возможно, это какая-то ошибка или что-то в этом роде), НО это не главное. Вопрос немного более общий, и эту конкретную проблему можно рассматривать как пример.
Я побежал valgrind
- ничего особенного. добавленной -Wall
а также -Wextra
- опять ничего. Похоже, UB, но valgrind
ни на что не жалуется.
Ситуация очень интересная, мне потребовалось много дней и головных болей, чтобы понять, что проблема не в моем коде (я использую эту реализацию для реализации сложного протокола), а в алгоритме и особенно в этом варианте. Поэтому я решил спросить мнение.
Может ли код, действительный как на C, так и на C++, вызывать различное поведение при компиляции на каждом языке? не имеет никакого отношения, так как мы говорим здесь на одном языке.
Эта разница в скорости массивного fprintf без "-std=c99" близка, но недостаточна..
РЕДАКТИРОВАТЬ
Вот результаты моего теста и то, что я делаю (исходники / заголовки только что скачаны, я ничего не меняю):
$ ll
total 176K
-rw-r----- 1 kk kk 63K Jan 20 11:00 rijndael-alg-fst.c
-rw-r----- 1 kk kk 2.0K Jan 20 11:00 rijndael-alg-fst.h
-rw-r----- 1 kk kk 3.4K Jan 20 11:00 umac_ae.h
-rw-r----- 1 kk kk 76K Jan 20 11:00 umac.c
-rw-r----- 1 kk kk 4.2K Jan 20 11:00 umac.h
$ gcc -c *.c
$ gcc *.o
$ ./a.out
AES Test :::
Digest is : 3AD78E726C1EC02B7EBFE92B23D9EC34
Digest should be: 3AD78E726C1EC02B7EBFE92B23D9EC34
UMAC Test :::
Msg Should be Is
--- --------- --
'a' * 0 : 4D61E4F5AAB959C8 4D61E4F5AAB959C8
'a' * 3 : 67C1700CA30B532D 67C1700CA30B532D
'a' * 1024 : 05CB9405EC38D9F0 05CB9405EC38D9F0
'a' * 32768 : 048C543CB72443A4 048C543CB72443A4
Verifying consistancy of single- and multiple-call interfaces.
Done.
Authenticating 44 byte messages: 6.45 cpb.
Authenticating 64 byte messages: 4.18 cpb.
Authenticating 256 byte messages: 1.63 cpb.
Authenticating 512 byte messages: 1.20 cpb.
Authenticating 552 byte messages: 1.22 cpb.
Authenticating 1024 byte messages: 1.00 cpb.
Authenticating 1500 byte messages: 1.04 cpb.
Authenticating 8192 byte messages: 0.90 cpb.
Authenticating 262144 byte messages: 0.89 cpb.
UMAC-AE Tests :::
0 bytes ('abc' * 0):
Encrypt/decrypt match, tags match
Should be: 0000000000000000
Is : E8D1DAC3EA21E56D
3 bytes ('abc' * 1):
Encrypt/decrypt match, tags match
Should be: 0000000000000000
Is : 6BEDBA31E074E2A4
48 bytes ('abc' * 16):
Encrypt/decrypt match, tags match
Should be: 0000000000000000
Is : A3F6069B913969DA
300 bytes ('abc' * 100):
Encrypt/decrypt match, tags match
Should be: 0000000000000000
Is : C5B7F3822179FC36
3000000 bytes ('abc' * 1000000):
Encrypt/decrypt match, tags match
Should be: 0000000000000000
Is : EE7F50FDDA60AA04
16 bytes, 38.12 cpb
32 bytes, 25.04 cpb
64 bytes, 19.39 cpb
128 bytes, 16.41 cpb
256 bytes, 14.79 cpb
512 bytes, 13.96 cpb
1024 bytes, 13.79 cpb
2048 bytes, 13.46 cpb
4096 bytes, 13.47 cpb
$ ll
total 176K
-rw-r----- 1 kk kk 63K Jan 20 11:00 rijndael-alg-fst.c
-rw-r----- 1 kk kk 2.0K Jan 20 11:00 rijndael-alg-fst.h
-rw-r----- 1 kk kk 3.4K Jan 20 11:00 umac_ae.h
-rw-r----- 1 kk kk 76K Jan 20 11:00 umac.c
-rw-r----- 1 kk kk 4.2K Jan 20 11:00 umac.h
$ gcc -std=c99 -c *.c
$ gcc -std=c99 *.o
$ ./a.out
AES Test :::
Digest is : 3AD78E726C1EC02B7EBFE92B23D9EC34
Digest should be: 3AD78E726C1EC02B7EBFE92B23D9EC34
UMAC Test :::
Msg Should be Is
--- --------- --
'a' * 0 : 4D61E4F5AAB959C8 9492DE86794C9F2B
'a' * 3 : 67C1700CA30B532D CF9505F52928360E
'a' * 1024 : 05CB9405EC38D9F0 9C48C0D4EFAFAA37
'a' * 32768 : 048C543CB72443A4 7F63C29BB54BB141
Verifying consistancy of single- and multiple-call interfaces.
Done.
Authenticating 44 byte messages: 7.91 cpb.
Authenticating 64 byte messages: 5.20 cpb.
Authenticating 256 byte messages: 3.03 cpb.
Authenticating 512 byte messages: 2.60 cpb.
Authenticating 552 byte messages: 2.71 cpb.
Authenticating 1024 byte messages: 2.41 cpb.
Authenticating 1500 byte messages: 2.43 cpb.
Authenticating 8192 byte messages: 2.27 cpb.
Authenticating 262144 byte messages: 2.23 cpb.
UMAC-AE Tests :::
0 bytes ('abc' * 0):
Encrypt/decrypt match, tags match
Should be: 0000000000000000
Is : 899C50FD244BBA83
3 bytes ('abc' * 1):
Encrypt/decrypt match, tags match
Should be: 0000000000000000
Is : 892D14F581A3A4DD
48 bytes ('abc' * 16):
Encrypt/decrypt match, tags match
Should be: 0000000000000000
Is : 621AB4A63383F3C5
300 bytes ('abc' * 100):
Encrypt/decrypt match, tags match
Should be: 0000000000000000
Is : 324BEF6489F57787
3000000 bytes ('abc' * 1000000):
Encrypt/decrypt match, tags match
Should be: 0000000000000000
Is : 1A25FE3714C9345A
16 bytes, 40.80 cpb
32 bytes, 25.87 cpb
64 bytes, 20.50 cpb
128 bytes, 17.72 cpb
256 bytes, 15.93 cpb
512 bytes, 15.33 cpb
1024 bytes, 14.88 cpb
2048 bytes, 14.71 cpb
4096 bytes, 14.48 cpb
Я только что проверил на другой машине, и это так же, как на моей.
3 ответа
Ну, я понял это. Все еще не уверен, как это исправить, чтобы он был идеально хорошо переносимым, но я продолжу копать.
Короче говоря - похоже, что это зависит от платформы, поэтому у большинства из вас нет этой проблемы.
Проблема заключается в определении порядка байтов.
Подробности:
После сравнения результатов сборки были некоторые существенные различия, которые (почти автоматически) исключали некоторые проблемы с большими постоянными интерпретациями и такими незначительными вещами, как этот.
Тогда я попробовал на более высоком уровне - вывод препроцессора.
Наконец, все привело к этому куску кода в umac.c
:
/* Message "words" are read from memory in an endian-specific manner. */
/* For this implementation to behave correctly, __LITTLE_ENDIAN__ must */
/* be set true if the host computer is little-endian. */
#ifndef __LITTLE_ENDIAN__
#if __i386__ || __alpha__ || _M_IX86 || __LITTLE_ENDIAN
#define __LITTLE_ENDIAN__ 1
#else
#define __LITTLE_ENDIAN__ 0
#endif
#endif
На моей платформе __i386__
, __alpha__
а также _M_IX86
не определены. Ключ находится в __LITTLE_ENDIAN
,
Когда скомпилировано:
- с
-std=c99
:__LITTLE_ENDIAN
не определено =>#define __LITTLE_ENDIAN__ 0
, - без
-std=c99
:__LITTLE_ENDIAN
определяется =>#define __LITTLE_ENDIAN__ 1
,
жестко прописывать #define __LITTLE_ENDIAN__ 1
, все начинает работать отлично с и без -std=c99
,
Заключение: __LITTLE_ENDIAN
является специфичным для gcc макросом, который используется здесь для определения порядка байтов; оказалось, что -std=c99
влияет на этот макрос (он не определен, если используется опция), что приводит к другим (неправильным) результатам.
РЕДАКТИРОВАТЬ
Моим текущим ("временным") решением было бы обновить проблемный оператор if препроцессора. Я знаю, что это далеко не лучший способ решить эту проблему, но обнаружение порядка байтов оказалось не таким простым и далеко не тривиальным.
Проверки во время выполнения кажутся более надежными, НО это приведет к большему количеству изменений в коде, чего я хочу избежать. Похоже, что самое "безвредное" "решение" - обновить и "починить" текущее решение.
Итак, так как мне нужно (пока) для работы с GCC, я сделал следующую модификацию:
#ifndef __LITTLE_ENDIAN__
#if __GNUC__
#include <endian.h>
#if __BYTE_ORDER == __LITTLE_ENDIAN
#define __LITTLE_ENDIAN__ 1
#elif __BYTE_ORDER == __BIG_ENDIAN
#define __LITTLE_ENDIAN__ 0
#else
#error "Cannot determine endianness! Please update this macro!"
#endif
#elif __i386__ || __alpha__ || _M_IX86
#define __LITTLE_ENDIAN__ 1
#else
#warning "Endianness cannot be determined for this platform; using big endian by default! Please be aware and update this macro!"
#define __LITTLE_ENDIAN__ 0
#endif
#endif
Похоже, UB,
Это не должно быть. Есть несколько известных отличий, которые могут привести к тому, что одна и та же программа будет интерпретироваться по-разному как C99 и C90.
но Вальгринд ни на что не жалуется.
Valgrind даже близко не подходит к предупреждению для всех неопределенных поведений ни в одном из стандартов.
Первое различие, которое приходит на ум (но хороший компилятор выдал бы предупреждение), это тип целочисленной константы. 3000000000
, С 32-битным int
и 64-битный long
Типы компилятора C90 3000000000
как unsigned long
, В С99 unsigned long
отсутствует в списке типов, которые могут иметь целочисленные константы без суффиксов, поэтому 3000000000
набирается как long long
(Подпись).
Не глядя, криптографический код, вероятно, будет иметь много больших целочисленных констант, так что это одна из возможностей.
Конечно, в коде, интерпретируемом как C90 или как C99, может быть неопределенное поведение, и тогда компилятору будет простительно выдавать разные результаты в режимах C90 и C99. Все, что я говорю, это то, что не должно быть.
Этот ответ основан на существующем ответе Кирилла Кирова
Кирилл выявил проблему, заключающуюся в том, что система препроцессорных проверок не может идентифицировать существующую платформу:
#if __i386__ || __alpha__ || _M_IX86 || __LITTLE_ENDIAN
#define __LITTLE_ENDIAN__ 1
#else
#define __LITTLE_ENDIAN__ 0
#endif
Хотя платформа имеет младший порядок, ни один из этих идентификаторов не определен в gcc -std=c99
Режим. Так что это проблема с приведенным здесь кодом; код должен быть обновлен, чтобы лучше определить, является ли он платформой с прямым порядком байтов или нет.
Первое, что я хотел бы сделать, это прекратить использовать регистр по умолчанию, так что если платформа не будет распознана, вместо того, чтобы молча работать неправильно, генерируется ошибка:
#if __i386__ || __alpha__ || _M_IX86 || __LITTLE_ENDIAN
#define __LITTLE_ENDIAN__ 1
#elif __arm__ // etc.
#define __LITTLE_ENDIAN__ 0
#else
#error Unrecognized platform, please update this macro
#endif
Следующим шагом будет правильное определение, для какой системы вы находитесь. Вот еще одна тема на эту тему.
Одна вещь, которую вы можете сделать, это выдать gcc -std=c99 -dM -E - <<<''
что приведет к gcc
вывести список всех предопределенных макросов в этом режиме; и тогда вы сможете найти что-то полезное. В моем случае это имеет:
#define __i386 1
#define __i686 1
так что любой из них может быть использован.
Альтернативный подход специально для __LITTLE_ENDIAN__
состоит в том, чтобы обнаружить его с помощью арифметики препроцессора, описанной здесь - хотя первый пример кода на этой странице на самом деле не производит константу предварительной обработки, поэтому он не будет использоваться в качестве условия для будущих проверок препроцессора.
Решив эту конкретную проблему, вы все равно должны попытаться найти другой экземпляр проблемы архитектуры в той же кодовой базе. Похоже, что автор соединил несколько архитектурных макросов, о которых он / она знал. Одно бросающееся в глаза отсутствие _M_IX64
например. Было бы разумно, возможно, пролистать кодовую базу для любых других случаев _M_
или же __i386__
и посмотрим, полагается ли он на них. Если это; затем, возможно, снова попытайтесь абстрагировать этот тест в макрос, который вы лучше контролируете.
В идеале вы должны иметь все такие макросы, определенные в одном заголовке для всей кодовой базы; а затем остальная часть кодовой базы просто использует макросы, которые были определены в этом заголовке.