I was surprised by this, today:
#include <stdlib.h> // for the 'exit' call
int foo() {
// return 0;
}
int main() {
int res = foo();
exit(res);
}
I know it's not good form to forget to return the expected integer value in foo
; but would you expect this code to segfault?
Here's what happens with GCC7.5:
(thanassis)$ g++ -O3 -Wall a.cc
a.cc: In function ‘int foo()’:
a.cc:5:5: warning: no return statement in function returning non-void [-Wreturn-type]
}
^
(thanassis)$ ./a.out
(thanassis)$ gdb ./a.out
GNU gdb (Ubuntu 10.2-0ubuntu1~18.04~2) 10.2
Copyright (C) 2021 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./a.out...
(No debugging symbols found in ./a.out)
(gdb) run
Starting program: /home/thanassis/a.out
[Inferior 1 (process 24749) exited normally]
(gdb) quit
No problems. All good.
Yes, the fact that foo
neglected to set the returned valued, means that the register picked for the task by the ABI (EAX) will have garbage. Whatever.
Now look at what happens with GCC11:
(thanassis)$ g++ -O3 -Wall ./a.cc
./a.cc: In function ‘int foo()’:
./a.cc:5:5: warning: no return statement in function returning non-void [-Wreturn-type]
5 | }
| ^
(thanassis)$ ./a.out
Segmentation fault (core dumped)
(thanassis)$ gdb ./a.out
GNU gdb (Ubuntu 9.2-0ubuntu1~20.04.1) 9.2
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./a.out...
(No debugging symbols found in ./a.out)
(gdb) run
Starting program: /home/thanassis/a.out
Program received signal SIGSEGV, Segmentation fault.
0x00007ffff7ddbfaa in __libc_start_main (main=0x555555555044 <main>, argc=-136462205, argv=0x7fffff7ff0b0, init=0x555555555140 <__libc_csu_init>,
fini=0x5555555551b0 <__libc_csu_fini>, rtld_fini=0x7fffffffe0e8, stack_end=0x7fffff7ff0a8) at ../csu/libc-start.c:141
141 ../csu/libc-start.c: No such file or directory.
(gdb) bt
#0 0x00007ffff7ddbfaa in __libc_start_main (main=0x555555555044 <main>, argc=-136462205, argv=0x7fffff7ff0b0, init=0x555555555140 <__libc_csu_init>,
fini=0x5555555551b0 <__libc_csu_fini>, rtld_fini=0x7fffffffe0e8, stack_end=0x7fffff7ff0a8) at ../csu/libc-start.c:141
#1 0x000055555555507e in _start ()
(gdb)
Now this, I did not expect.
For one, the stack frames seem to be messed up - where's the main
stack frame?
Looking at the output of objdump for main
...
$ objdump -d -S ./a.out
...
Disassembly of section .text:
0000000000001040 <_Z3foov>:
#include <stdlib.h>
int foo() {
1040: f3 0f 1e fa endbr64
0000000000001044 <main>:
// return 0;
}
int main() {
1044: f3 0f 1e fa endbr64
1048: 0f 1f 84 00 00 00 00 nopl 0x0(%rax,%rax,1)
104f: 00
0000000000001050 <_start>:
1050: f3 0f 1e fa endbr64
1054: 31 ed xor %ebp,%ebp
1056: 49 89 d1 mov %rdx,%r9
1059: 5e pop %rsi
105a: 48 89 e2 mov %rsp,%rdx
105d: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp
...it looks like GCC decided to "merge" stack frames?!
This looks like a compiler bug to me. Note that ignoring older compilers, it also doesn't manifest with optimisation levels -O0
and -O1
- but it does after -O2
.
Again, to be clear: I know it's bad form, and I do use -Wall
and -Wextra
- so I did fix this in my code. But I thought I'd share this here, since I just never expected an int-returning function not returning an int to create a segfault (due to the compiler creating code that misses a stack frame).
UPDATE: Note also that compiling with gcc
and not g++
creates normal code. The issue only manifests when compiling the code with a C++ compiler.