Curious readonly symbols showing up as in initialized-data-section (D) in nm

Viewed 323

I've noticed that with gcc (but not clang), const (readonly) initialized data objects no longer show up as R data objects in nm and instead they become D (initialized section) objects.

That would suggest the data object will be placed in writable memory, however, when the same object file is linked with either gcc or clang (but not tcc), then it seems to be placed in readonly memory anyway.

clang doesn't seem to use these curious readonly-D symbols (instead the object remains R). Tinycc does make such objects into D symbols too, but those D symbols doesn't seem to have that curious property that causes linkers to put them in readonly memory.

Can you explain what's going on here?

The script below demonstrates the behavior with gcc, clang, and tinycc used in all combinations as compilers and linkers:

#!/bin/sh -eu
cat > file.c <<EOF
struct obj { void (*fnptr)(void); int z; };
static void fn0(void) { }
const struct obj constInitedReadonlyObject = { 0, 42 };
const struct obj readonlyObject = { &fn0, 42 };

int main()
{
    int volatile*z = (int volatile*)&readonlyObject.z;
    *z = 1000;
}
EOF
for cc in gcc tcc clang; do
    $cc -c file.c
    echo cc=$cc type=$( nm file.o |grep readonlyObject |cut -d ' ' -f 2 )
    for ld in gcc tcc clang; do
        $ld file.o
        printf '\t%s\n' "ld=$ld $(if ./a.out 2>/dev/null; then echo NOTHING; else echo FAULT; fi)"
    done
done

Output on my system:

cc=gcc type=D
    ld=gcc FAULT
    ld=tcc NOTHING
    ld=clang FAULT
cc=tcc type=D
    ld=gcc NOTHING
    ld=tcc NOTHING
    ld=clang NOTHING
cc=clang type=R
    ld=gcc FAULT
    ld=tcc FAULT
    ld=clang FAULT

Edit: Doing readelf -s on the object files and grepping for the two data objects yields:

clang
     4: 0000000000000000    16 OBJECT  GLOBAL DEFAULT    4 constInitedReadonlyObject
     6: 0000000000000010    16 OBJECT  GLOBAL DEFAULT    4 readonlyObject
gcc
    11: 0000000000000000    16 OBJECT  GLOBAL DEFAULT    5 constInitedReadonlyObject
    12: 0000000000000000    16 OBJECT  GLOBAL DEFAULT    6 readonlyObject
tcc
     3: 0000000000000000    16 OBJECT  GLOBAL DEFAULT    3 constInitedReadonlyObject
     4: 0000000000000010    16 OBJECT  GLOBAL DEFAULT    3 readonlyObject

I guess the different numbers (in the column whose name is Ndx (not shown)) have something to do with the behavior.

1 Answers

Most likely your gcc is configured with --enable-default-pie (gcc -v to check).

In PIE, readonlyObject needs to be writable at program startup to allow dynamic relocation processing code to write the address of fn0 into its first field. To arrange for that, gcc places such objects into sections with .data.rel.ro prefix, and the linker collects such sections separately from other .data sections. The dynamic linker (or, in case of static PIEs, linked-in relocation processing code) can then mprotect that region after writing into it.

Thus, with gcc (and implicit -fpie -pie) you have:

  • readonlyObject in .data.rel.ro
  • classified by nm as "global data"
  • writable at program startup for relocation
  • readonly when main is reached

With clang or gcc -fno-pie you have:

  • readonlyObject in .rodata
  • classified by nm as "global constant"
  • readonly even on program startup
Related