I'm having trouble with GCC optimization in a C program. It's x86_64 code (C and inline assembly, though there's no asm in the .o file that is troublesome), run on OS X 10.6.
Here's how it looks while running with -O0 vs -O2:
$ gcc -m64 -std=gnu99 -o bin/ctr keyschedule.c aes.c ctr.c debug.c misc.c -O0 -Wall
$ bin/ctr -e testing/plaintext -o testing/cipher
encrypt_file called with inpath: testing/plaintext
encrypt_file called with outpath: testing/ciphe开发者_运维技巧r
file_size called with argument 0x7fff5fbff84b
nonce = 6e32b6797e849081
(... no more output, but it works as it should)
$ gcc -m64 -std=gnu99 -o bin/ctr keyschedule.c aes.c ctr.c debug.c misc.c -O2 -Wall
$ bin/ctr -e testing/plaintext -o testing/cipher
encrypt_file called with inpath: testing/plaintext
encrypt_file called with outpath: testing/cipher
file_size called with argument 0x7fff5f000000
Segmentation fault
... along with the OS X crash reporter popping up:
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Codes: KERN_PROTECTION_FAILURE at 0x00007fff5f000000
Crashed Thread: 0 Dispatch queue: com.apple.main-thread
Thread 0 Crashed: Dispatch queue: com.apple.main-thread
0 libSystem.B.dylib 0x00007fff88d3bad8 perror + 52
1 ctr 0x0000000100001339 file_size + 73 (ctr.c:32)
2 ctr 0x00000001000017be encrypt_file + 158 (ctr.c:72)
3 ctr 0x0000000100001bd1 main + 289 (ctr.c:242)
4 ctr 0x0000000100000bd4 start + 52
... and here's most of the code that is needed (let's just say that the argument parser is rather temporary):
int main(int argc, char *argv[]) {
...
if (strcmp(argv[1], "-e") == 0)
encrypt_file(argv[2], argv[4], key); // It crashes with static paths as well
...
}
void encrypt_file(const char *inpath, const char *outpath, const unsigned char *key) {
printf("encrypt_file called with inpath: %s\n", inpath);
printf("encrypt_file called with outpath: %s\n", outpath);
// Create a pointer to the correct function to use for this CPU
void (*aes_encrypt)(const unsigned char *, unsigned char *, const unsigned char *);
if (test_aesni_support()) {
aes_encrypt = aes_encrypt_aesni;
}
else {
aes_encrypt = aes_encrypt_c;
}
unsigned char expanded_keys[176] = {0};
aes_expand_key(key, expanded_keys);
off_t size = file_size(inpath);
if (size <= 0) {
fprintf(stderr, "Cannot encrypt a file of size zero!\n");
exit(1);
}
....
}
off_t file_size(const char *path) {
printf("file_size called with argument %p\n", path);
struct stat st;
if (stat(path, &st) != 0) {
perror(path);
exit(1);
}
return (st.st_size);
}
I suspect that there's something in my code (rather than assuming the bug is in GCC), but I can't find anything.
If the code above looks fine (I can't really imagine the problem is in any other code, as it's quite unrelated to the crash), what should I be doing to track this down?
GDB:
#0 0x00007fff88d3bad8 in perror ()
(gdb) bt
#0 0x00007fff88d3bad8 in perror ()
#1 0x0000000100001339 in file_size (path=0x7fff5f000000 "") at ctr.c:31
#2 0x00000001000017be in encrypt_file (inpath=0x7fff5f000000 "", outpath=0x7fff5fbff860 "testing/cipher", key=0x7fff5fbff6b0 "-~??9?9>?W\n\021\001?N\026") at ctr.c:72
#3 0x0000000100001bd1 in main (argc=<value temporarily unavailable, due to optimizations>, argv=0x7fff5fbff718) at ctr.c:242
(gdb) frame 2
#2 0x00000001000017be in encrypt_file (inpath=0x7fff5f000000 "", outpath=0x7fff5fbff860 "testing/cipher", key=0x7fff5fbff6b0 "-~??9?9>?W\n\021\001?N\026") at ctr.c:72
72 off_t size = file_size(inpath);
(gdb) l
67 }
68
69 unsigned char expanded_keys[176] = {0};
70 aes_expand_key(key, expanded_keys);
71
72 off_t size = file_size(inpath);
73 if (size <= 0) {
74 fprintf(stderr, "Cannot encrypt a file of size zero!\n");
75 exit(1);
76 }
Valgrind:
$ valgrind bin/ctr -e testing/plaintext -o testing/cipher
==1165== Memcheck, a memory error detector
==1165== Copyright (C) 2002-2010, and GNU GPL'd, by Julian Seward et al.
==1165== Using Valgrind-3.7.0.SVN and LibVEX; rerun with -h for copyright info
==1165== Command: bin/ctr -e testing/plaintext -o testing/cipher
==1165==
encrypt_file called with inpath: testing/plaintext
encrypt_file called with outpath: testing/cipher
file_size called with argument 0x7fff5f000000
==1165== Syscall param stat64(path) points to unaddressable byte(s)
==1165== at 0x2BB52: stat$INODE64 (in /usr/lib/libSystem.B.dylib)
==1165== by 0x10000179D: encrypt_file (ctr.c:73) // this is the call to file_size() in encrypt_file()
==1165== by 0x100001BB0: main (ctr.c:243)
==1165== Address 0x7fff5f000000 is not stack'd, malloc'd or (recently) free'd
==1165==
file_size: Bad address
==1165==
==1165== HEAP SUMMARY:
==1165== in use at exit: 4,184 bytes in 2 blocks
==1165== total heap usage: 2 allocs, 0 frees, 4,184 bytes allocated
==1165==
==1165== LEAK SUMMARY:
==1165== definitely lost: 0 bytes in 0 blocks
==1165== indirectly lost: 0 bytes in 0 blocks
==1165== possibly lost: 0 bytes in 0 blocks
==1165== still reachable: 4,096 bytes in 1 blocks
==1165== suppressed: 88 bytes in 1 blocks
==1165== Rerun with --leak-check=full to see details of leaked memory
==1165==
==1165== For counts of detected and suppressed errors, rerun with: -v
==1165== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
Edit: Replaced encrypt_file() with the unabridged version (to the point it crashes), added GDB info and replaced the OS X crash reporter output with the output from running a -g binary.
Edit 2: Added valgrind output.
It appears that your variable inpath
is getting (partially) overwritten; I suspect that if you remove the call to aes_expand_key
that it will get past the problem fine.
Without seeing aes_expand_key
, I'm going to guess that it has some inline assembly and you are not properly preserving at least one register that is supposed to be preserved according to the ABI of your target platform. In the -O0 case, inpath
probably remains on the stack and so is not affected, while in the -O2 case it is probably being held in a register of which the low 32 bits are being smashed by aes_expand_key
.
It sounds like you have some sort of memory stomping happening somewhere. The argument path
in file_size
is clearly an invalid pointer when it gets there.
If what you posted for encrypt_file
is everything that happens in that function before the call to file_size
, then the culprit is almost certainly aes_expand_key
-- are you sure you're passing a correctly sized buffer of 176 bytes? If that buffer is too small, then aes_expand_key
will overwrite other data on the stack, including the local variable inpath
. Try increasing the size of expanded_keys
; check the documentation on aes_expand_key
to find out how big it needs to be.
精彩评论