General ELF object in x86-64 assembler

I am trying to create a shared library (* .so) in ASM, and I'm not sure if I am doing it right ...

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, run

 gcc ker.s -g -fPIC -m64 -o ker.o gcc ker.o -shared -fPIC -m64 -o libker.so 

I can access the var1 variable and call func1 with dlopen () and dlsym () from a program in C.

The problem is the var1 variable. When I try to access it from func1, i.e. uncomment this 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 do not understand. I already compiled with -fPIC, so what's wrong?

+5
source share
3 answers

Ok, I think I found something ...

The first solution from drhirsch gives almost the same error, but the type of movement changes. And the type always ends with 32. Why? Why does a 64-bit program use 32-bit move?

I found this from googling: http://www.technovelty.org/code/c/relocation-truncated.html

It says:

For code optimization purposes, the default immediate size for a mov instruction is a 32-bit value.

So, the case. I am using a 64-bit program, but the move is 32-bit, and all I need to do is make it be 64-bit with the movabs instruction.

This code compiles and works (access to var1 from the internal func1 function and from the external C program via dlsym() ):

  .section .data .globl var1 var1: .quad 0x012345 .section .text .globl func1 func1: movabs var1, %rax # if one is symbol, other must be %rax inc %rax movabs %rax, var1 ret 

But I doubt the global offset table. Should I use it, or is this β€œdirect” access absolutely correct?

0
source

I already compiled with -fPIC, so what's wrong?

This part of the error message is intended for people who link the code generated by the compiler.

You write asm manually, therefore, as datenwolf wrote correctly, when writing a shared library in an assembly, you should make sure that the code is position-independent.

This means that the file should not contain 32-bit absolute addresses (since moving to an arbitrary 64-bit base is impossible). 64-bit absolute movements are supported, but usually you should use this only for transition tables.


mov var1, %rcx uses the 32-bit absolute addressing mode . Normally you should never do this, even in position-dependent x86-64 code. Common use mov $var1, %edi for 32-bit absolute addresses: putting an address in a 64-bit register using mov $var1, %edi (zero expands in RDI)
and indexing static arrays: mov arr(,%rdx,4), %edx

mov var1(%rip), %rcx uses a relative RIP 32-bit offset . This is an efficient way to access static data, and compilers always use it even without -fPIE or -fPIC for static / global variables.

You have two options:

  • Normal static data that __attribute__((visibility("hidden"))) long var1; a library , such as C compilers, will create for __attribute__((visibility("hidden"))) long var1; -fno-PIC 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 code, -fPIC character arrangement, for example, compilers generated for -fPIC .

    You must use the global offset table. This is how the compiler does it if you tell it to create code for a shared library. Please note that this leads to performance degradation due to additional indirectness.

    See Sorry for the state of dynamic libraries on Linux for more information on character insertion and the overhead it imposes on code generation for shared libraries, unless you are careful about restricting character visibility to allow embedding.

    var1@GOTPCREL is the address of the pointer to your var1 , the pointer itself is accessible using rip-relative addressing, and the contents (address var1 ) are filled in by the linker during loading the library. This supports the case when a program using your library defined var1 , so var1 in your library should resolve to this memory area, not to 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 More Information at http://www.bottomupcs.com/global_offset_tables.html.

An example in the Godbolt compiler explorer for -fPIC and -fPIE shows the difference that -fPIE has a character arrangement to get the address of hidden non- 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 loading instead of just ALU, -fPIC with hidden global variables non-.

In fact, loading always uses x(%rip) , with the exception of non-hidden / non- static variables with -fPIC where it must first get the runtime address from GOT, because this is not an offset of the connection time constant relative to the code.

Related: 32-bit absolute addresses are no longer allowed on x86-64 Linux? (Pie executable files).


A previous version of this answer said that DATA and BSS segments can move relative to TEXT when loading a dynamic library. This is incorrect, only the base address of the library is moved. RIP-relative access to other segments in the same library is guaranteed to be in order, and compilers produce code that does this. ELF headers define how segments (which contain sections) should be loaded / mapped into memory.

+9
source

I do not understand. I already compiled with -fPIC, so what's wrong?

-fPIC is a flag related to the creation of machine code from non-machine code, i.e. what operations to use. At the compilation stage. The assembly is not compiled, though! Each prefabricated mnemonics is directly mapped to a machine instruction, your code does not compile. It is simply transcribed into a slightly different format.

Since you are writing it in an assembly, your assembler code must be position-independent to bind to a shared library. -fPIC does not affect your case, since it only affects code generation.

+4
source

Source: https://habr.com/ru/post/1397187/


All Articles