[英]Why does this code break when -O2 or higher is enabled?
我試圖在8位PIC單片機中安裝NSA的SPECK實現。 他們的編譯器的免費版本(基於CLANG)將不會啟用優化,因此我的內存不足。 我嘗試了“試用”版本,它可以啟用-O2,-O3和-Os(針對大小進行優化)。 使用-Os它設法使我的代碼適合2K程序存儲空間。
這是代碼:
#include <stdint.h>
#include <string.h>
#define ROR(x, r) ((x >> r) | (x << (32 - r)))
#define ROL(x, r) ((x << r) | (x >> (32 - r)))
#define R(x, y, k) (x = ROR(x, 8), x += y, x ^= k, y = ROL(y, 3), y ^= x)
#define ROUNDS 27
void encrypt_block(uint32_t ct[2],
uint32_t const pt[2],
uint32_t const K[4]) {
uint32_t x = pt[0], y = pt[1];
uint32_t a = K[0], b = K[1], c = K[2], d = K[3];
R(y, x, a);
for (int i = 0; i < ROUNDS - 3; i += 3) {
R(b, a, i);
R(y, x, a);
R(c, a, i + 1);
R(y, x, a);
R(d, a, i + 2);
R(y, x, a);
}
R(b, a, ROUNDS - 3);
R(y, x, a);
R(c, a, ROUNDS - 2);
R(y, x, a);
ct[0] = x;
ct[1] = y;
}
不幸的是,在逐行調試時,將其與實施指南中的測試向量進行比較,從第32頁“15 SPECK64 / 128測試向量”開始,結果與預期結果不同。
這是調用此函數的方法:
uint32_t out[2];
uint32_t in[] = { 0x7475432d, 0x3b726574 };
uint32_t key[] = { 0x3020100, 0xb0a0908, 0x13121110, 0x1b1a1918 };
encrypt_block(out, in, key);
assert(out[0] == 0x454e028b);
assert(out[1] == 0x8c6fa548);
根據指南,“out”的預期值應為0x454e028b, 0x8c6fa548
。 我得到-O2的結果是0x8FA3FED7 0x53D8CEA8
。 使用0x454e028b, 0x8c6fa548
,我得到0x454e028b, 0x8c6fa548
,這是正確的結果。
步調試
實施指南包括所有中間密鑰安排其他值,所以我逐行逐步完成代碼,將結果與指南進行比較。
為“×”的預期結果是: 03020100
, 131d0309
, bbd80d53
, 0d334df3
。 我開始步驟調試,但是當達到第4個結果0d334df3
,調試器窗口顯示0d334df0
。 到下一輪,預期的7fa43565
值為7FA43578
並且每次迭代都會變得更糟。
這僅在啟用-O2或更高時發生。 沒有優化或使用-O1,代碼按預期工作。
這是編譯器中的一個錯誤。
我在制造商的論壇上發布了這個問題。 其他人確實復制了這個問題,這在編譯某些部分時會發生。 其他部分不受影響。
作為一種解決方法,我將宏更改為實際函數,並將操作拆分為兩行:
uint32_t ROL(uint32_t x, uint8_t r) {
uint32_t intermedio;
intermedio = x << r;
intermedio |= x >> (32 - r);
return intermedio;
}
這給出了正確的結果。
發布可編譯的測試代碼作為參考。
#include <stdint.h>
#include <string.h>
//#include "speck.h"
#define ROR(x, r) ((x >> r) | (x << (32 - r)))
#define ROL(x, r) ((x << r) | (x >> (32 - r)))
#define R(x, y, k) (x = ROR(x, 8), x += y, x ^= k, y = ROL(y, 3), y ^= x)
#define ROUNDS 27
void encrypt_block(uint32_t ct[2], uint32_t const pt[2], uint32_t const K[4]) {
uint32_t x = pt[0], y = pt[1];
uint32_t a = K[0], b = K[1], c = K[2], d = K[3];
R(y, x, a);
// for (int i = 0; i < ROUNDS - 3; i += 3) {
for (uint32_t i = 0; i < ROUNDS - 3; i += 3) {
R(b, a, i);
R(y, x, a);
R(c, a, i + 1);
R(y, x, a);
R(d, a, i + 2);
R(y, x, a);
}
R(b, a, ROUNDS - 3);
R(y, x, a);
R(c, a, ROUNDS - 2);
R(y, x, a);
ct[0] = x;
ct[1] = y;
}
int main(void) {
uint32_t out[2];
uint32_t in[] = {0x7475432d, 0x3b726574};
uint32_t key[] = {0x03020100, 0x0b0a0908, 0x13121110, 0x1b1a1918};
encrypt_block(out, in, key);
printf("%8lx %8lx\n", (unsigned long) out[0], 0x454e028bLU);
printf("%8lx %8lx\n", (unsigned long) out[1], 0x8c6fa548LU);
}
產量
454e028b 454e028b
8c6fa548 8c6fa548
意外的輸出
0x8FA3FED7
0x53D8CEA8
我沒有在代碼中看到任何未定義行為的跡象,除非它在您未顯示的設置/調用點的某些方面。 因此,根據優化級別,行為應該是不可能的。 通常情況下,我不會快速責怪編譯器錯誤這樣的東西,而是FOSS編譯器的嵌入式東西,特別是在未編譯為16位int
的編譯器中將int
重新定義為16位的forks,以及特別是專有的問題,他們的代碼非常糟糕,甚至不想讓你看到它,很可能是一個編譯器錯誤。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.