This is a story of a program that worked, until it broke on a 64-bit platform.
Consider this 1-line code:
This code can fail mysteriously with a
Segmentation fault on 64-bit
platforms, if you do not include the header unistd.h. The compiler
does not complain about a missing prototype for strerror() if you do
not include that header.1 Instead it silently assumes the function
The generated assembly2 can explain things a bit better:
The interesting part is in the line right after
call strerror. The
movl copies the contents of EAX register to ESI.3 But
guess what? strerror() returns a memory address, and the address is
64 bits. But EAX register is only 32 bits wide! The correct
register would be RAX. RAX consists of EAX and an additional
higher-order 32 bits.
This is because the compiler assumed that strerror() returns an
int, which is 32 bits wide on amd64 platform.
A quick run of the program under
gdb reveals the state of the
Before the copy:
rax 0x800874100 34368602368 ... rsi 0x800740b43 34367343427
After the copy:
rax 0x800874100 34368602368 ... rsi 0x874100 8864000
Note how only the lower 32 bits have been copied from RAX to RSI. This is not a valid address, so when printf() tries to look it up, it fails.
We can try the same program on a 32-bit system and it
works.5 The reason is that an
int and a memory address
(pointer) are both 32 bits wide there. The generated assembly for
strerror() returned a 32-bit address on EAX, and all of it went to the stack as an argument for printf(). 6
On amd64 platform, the calling convention is to put the arguments of a function on to RDI, RSI, RDX, RCX.
- gcc 4.2 on FreeBSD 8.4. It does generate a warning with
gcc -Scommand saves generated assembly in a
- The amd64 convention is to pass our two function arguments in RDI, RSI registers.
- On gcc under amd64, use
gcc -m32to specify a 32-bit target.
- The i386 convention is to push function arguments onto the process stack.