Description of problem:
gcc 10.2 generates code that segfaults due to placing global const pointers in read-only memory area rather than read-write as previous compilers
Version-Release number of selected component (if applicable):
gcc (GCC) 10.2.1 20200723 (Red Hat 10.2.1-1)
How reproducible:
Compile program with a global const pointer prototype to be initialized later.
Additional info:
Here is the simplest program I could come up with to demonstrate the
problem. I call the program "maptest.c" because I'm looking at where
things get mapped to memory by looking in /proc/<pid>/maps and seeing
what permissions each chunk of memory has and where it is located. Using
the debugger I can tell where in that memory space my variables are located.
Here's the program:
/* A program to try and find where a SIGSEV error occurs as
* part of the program DOSEMU
*/
# include <stdio.h>
# include <stdlib.h>
char * const lowmem_base;
int main(int argc, char **argv)
{
void *addr = malloc(114112);
*(char **)(&lowmem_base) = addr;
printf("Hello World!\n");
free(addr);
}
I could probably delete the Hello World! to make it even smaller but
it's an experiment.
When I compile this with gcc like this:
$ gcc -g -Wall -o maptest maptest.c
I get an ELF executable called "maptest" with debug information as expected.
When I compile the program with gcc version 9.1.2 as found in Fedora 31,
the programs runs and exits normally. Running in the debugger shows that
the variable lowmem_base is located in a portion of the maptest space
that is marked rw-p. Activating the disassembler in gdb shows that the
value of addr is in a register and moves to the memory location of
lowmem_base.
When I compile the program with gcc version 10.2.1 as found in Fedora
32, the program runs and segfaults on the line:
*(char **)(&lowmem_base) = addr;
Running in the debugger shows that the variable lowmem_base is located
in a region of maptest marked as read only.
If I copy the binary from Feodra 31 to Fedora 32 it runs fine so I don't
think it has anything to do with any of the shared libraries.
The equivalent of this program has been working since before 2012, so I
think that there is a problem with gcc.
If I compile the program with clang, it functions as expected without a segfault.
The testcase is invalid.
See e.g. ISO C99, 6.7.3/5: "If an attempt is made to modify an object defined with a const-qualified type through use
of an lvalue with non-const-qualified type, the behavior is undefined."
The only reason this "worked" before GCC 10 is that older GCC versions defaulted for C to -fcommon and as the variable has no initializer,
it has been a common symbol which is always writable. If you compile this testcase with -fno-common, even GCC 9 or 8 will segfault on it.
If you compile with C++, it will not even compile.
You can use various workarounds, like -fcommon, or add __attribute__((common)) to the var, but the right fix is to drop the const qualifier if you want to modify the variable.