Use a RIP-relative LEA to silence the warning: lea rsi, [rel msg]
.
Or default rel
so lea rsi, [msg]
is treated as [rel msg]
How to load address of function or label into register
mov rsi, msg
with a 64-bit absolute address is the worst way; only use it if your executable will be larger than 2GiB (e.g. with huge arrays) so the normal way can't reach.
Modern Linux distros configure GCC to make PIEs (Position Independent Executables) by default so they can benefit from ASLR (load at a random base address). This includes passing -pie
to ld
when linking a .o
. See
32-bit absolute addresses no longer allowed in x86-64 Linux? for more.
Using absolute addresses in a PIE requires runtime fixups (done by the dynamic linker ld.so
) after the kernel picks an address to load/map your executable. In the .text
section (or probably other read-only sections), these are called "text relocations", DT_TEXTREL
, and require an mprotect
system call to temporarily make the page read+write.
If you make a "static PIE" (gcc -static-pie
), _start
needs to apply the relocations (and gcc's static-PIE CRT startup code does so), or they won't be done at all if you use your own _start
(gcc -static-pie -nostdlib
) - How are relocations supposed to work in static PIE binaries?
Using 32-bit absolute addresses like movzx edx, byte [msg + rcx]
(with the addressing mode being [rcx+disp32]
) isn't even possible in 64-bit PIE executables or shared libraries, because they need to be relocatable anywhere in virtual address-space, not just the low 2 GiB: 32-bit absolute addresses no longer allowed in x86-64 Linux?
Applying a text relocation dirties the whole page of memory so it's not backed by the executable on disk (like a MAP_PRIVATE file mapping that you modify, it becomes basically an anonymous page that could only be paged out to swap space). And it takes up space for metadata to apply it.
When compilers use 64-bit absolute addresse, e.g. for static pointer variables like a global int *const p = &a;
(example on Godbolt) or an array of pointers, they put them in a special section so they're all grouped together (.section .data.rel.ro.local,"aw"
for read-only, .data.rel.local
for read-write), so hopefully only one dirtied page, leaving .rodata
and .text
clean so it can be shared between processes running the same executable or mapping the same library.
Those compiler-generated absolute addresses are in pages that start out read+write (not .text
or .rodata) so you don't get a DT_TEXTREL
warning. The same mechanism of applying relocations during startup still happens, but without the mprotect
call first. The .data.rel.ro.local
section is a subsection of .data
so it starts out read+write. I think it gets made read-only after applying relocations, with mprotect(PROT_READ)
.
GCC does avoid absolute addresses when inventing jump tables for switch
by using 32-bit relative offsets: GCC Jump Table initialization code generating movsxd and add?
This warning exists to help compiler developers and people writing asm by hand find places where they've accidentally used absolute addresses in a PIE or shared library (without putting them in a special section).
Or users of compilers who build some files with -fno-pie
and then linked them into a PIE or shared library, although that will more usually fail entirely with an error like relocation R_X86_64_32S against `.data' can not be used when making a shared object; recompile with -fPIC
. (On most modern distros, GCC is configured with -fPIE
as the default, but that didn't used to be the case, or you might have used -fno-pie
for some files. DT_TEXTREL
also applies to shared libraries.)
You normally don't want 64-bit absolute addresses as part of your machine code in the first place, only as data. mov rsi, msg
is not a good way to do things in 64-bit code unless you're making a huge executable that's larger than 2GiB, so the label is more than +-2GiB away from the instruction (so RIP-relative couldn't reach, like -mcmodel=large
) In a traditional non-PIE executable you'd want mov esi, msg
(32-bit absolute), but the best we can do in position-independent code is lea rsi, [rel msg]
(RIP + rel32).
See How to load address of function or label into register
(RIP-relative addressing doesn't exist in 32-bit mode, so avoiding text relocations in 32-bit code is less trivial and has a performance cost. For beginners especially I'd recommend just using -no-pie
when linking for 32-bit code. That also works for 64-bit code if you want to keep using inefficient but "simple" stuff like mov rsi, msg
and silence the warning.)