This is a story of a program that worked, until it broke on a 64-bit platform.
Consider this 1-line code:
printf("error: %s\n", strerror(errno));
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
returns an int
.
The generated assembly2 can explain things a bit better:
call __error
movl (%rax), %edi
movl $0, %eax
call strerror
movl %eax, %esi
movl $.LC0, %edi
movl $0, %eax
call printf
movl $0, %eax
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
registers.4
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
i386 confirms:
call __error
movl (%eax), %eax
movl %eax, (%esp)
call strerror
movl %eax, 4(%esp)
movl $.LC0, (%esp)
call printf
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.
Footnotes:
- gcc 4.2 on FreeBSD 8.4. It does generate a warning with
-Wall
option. gcc -S
command saves generated assembly in a.s
file.- The amd64 convention is to pass our two function arguments in RDI, RSI registers.
- Under
gdb
, usenexti
andinfo reg
commands. - On gcc under amd64, use
gcc -m32
to specify a 32-bit target. - The i386 convention is to push function arguments onto the process stack.