I'm trying to create a Shared library (*.so) in ASM and I'm not sure that i do it correct...
My code is:
.section .data
.globl var1
var1:
.quad 0x012345
.section .text
.globl func1
func1:
xor %rax, %rax
# mov var1, %rcx # this is commented
ret
To compile it i run
gcc ker.s -g -fPIC -m64 -o ker.o
gcc ker.o -shared -fPIC -m64 -o libker.so
I can access variable var1 and call func1 with dlopen() and dlsym() from a program in C.
The problem is in variable var1. When i try to access it from func1, i.e. uncomment that line, the compiler generates an error:
/usr/bin/ld: ker.o: relocation R_X86_64_32S against `var1' can not be used when making a shared object; recompile with -fPIC
ker.o: could not read symbols: Bad value
collect2: ld returned 1 exit status
I don't understand. I've already compiled with -fPIC, so what's wrong?
x86-64 assembly language is a human-readable version of this machine code. x86-64 has hundreds of instructions, and compiling programs to the most efficient machine code requires a good understanding of all of them–indeed, the fastest C compiler for x86-64 processors is developed by Intel!
A shared library or shared object is a file that is intended to be shared by multiple programs. Symbols used by a program are loaded from shared libraries into memory at load time or runtime.
Shared libraries are the most common way to manage dependencies on Linux systems. These shared resources are loaded into memory before the application starts, and when several processes require the same library, it will be loaded only once on the system. This feature saves on memory usage by the application.
Shared Libraries are loaded by the executable (or other shared library) at runtime.
I've already compiled with -fPIC, so what's wrong?
That part of the error message is for people who are linking compiler-generated code.
You're writing asm by hand, so as datenwolf correctly wrote, when writing a shared library in assembly, you have to take care for yourself that the code is position independent.
This means file must not contain any 32-bit absolute addresses (because relocation to an arbitrary 64-bit base is impossible). 64-bit absolute relocations are supported, but normally you should only use that for jump tables.
mov var1, %rcx
uses a 32-bit absolute addressing mode. You should normally never do this, even in position-dependent x86-64 code. The normal use-cases for 32-bit absolute addresses are: putting an address into a 64-bit register withmov $var1, %edi
(zero-extends into RDI)
and indexing static arrays: mov arr(,%rdx,4), %edx
mov var1(%rip), %rcx
uses a RIP-relative 32-bit offset. It's the efficient way to address static data, and compilers always use this even without -fPIE
or -fPIC
for static/global variables.
You have basically two possibilities:
Normal library-private static data, like C compilers will make for __attribute__((visibility("hidden"))) long var1;
, same as for -fno-PIC
.
.data
.globl var1 # linkable from other .o files in the same shared object / library
.hidden var1 # not visible for *dynamic* linking outside the library
var1:
.quad 0x012345
.text
.globl func1
func1:
xor %eax, %eax # return 0
mov var1(%rip), %rcx
ret
full symbol-interposition-aware code like compilers generate for -fPIC
.
You have to use the Global Offset Table. This is how a compiler does it, if you tell him to produce code for a shared library. Note that this comes with a performance hit because of the additional indirection.
See Sorry state of dynamic libraries on Linux for more about symbol-interposition and the overheads it imposes on code-gen for shared libraries if you're not careful about restricting symbol visibility to allow inlining.
var1@GOTPCREL
is the address of a pointer to your var1
, the pointer itself is reachable with rip-relative addressing, while the content (the address of var1
) is filled by the linker during loading of the library. This supports the case where the program using your library defined var1
, so var1
in your library should resolve to that memory location instead of the one in the .data
or .bss
(or .text
) of your .so
.
.section .data
.globl var1
# without .hidden
var1:
.quad 0x012345
.section .text
.globl func1
func1:
xor %eax, %eax
mov var1@GOTPCREL(%rip), %rcx
mov (%rcx), %rcx
ret
See some additional information at http://www.bottomupcs.com/global_offset_tables.html
An example on the Godbolt compiler explorer of -fPIC
vs. -fPIE
shows the difference that symbol-interposition makes for getting the address of non-hidden global variables:
movl $x, %eax
5 bytes, -fno-pie
leaq x(%rip), %rax
7 bytes, -fPIE
and hidden globals or static
with -fPIC
y@GOTPCREL(%rip), %rax
7 bytes and a load instead of just ALU, -fPIC
with non-hidden globals.Actually loading always uses x(%rip)
, except for non-hidden / non-static
vars with -fPIC
where it has to get the runtime address from the GOT first, because it's not a link-time constant offset relative to the code.
Related: 32-bit absolute addresses no longer allowed in x86-64 Linux? (PIE executables).
A previous version of this answer stated that the DATA and BSS segments could move relative to TEXT when loading a dynamic library. This is incorrect, only the library base address is relocatable. RIP-relative access to other segments within the same library is guaranteed to be ok, and compilers emit code that does this. The ELF headers specify how the segments (which contain the sections) need to be loaded/mapped into memory.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With