Page 1 of 1

gcc visibility

Posted: Sun Dec 25, 2016 12:46 am
by bzt
I know exactly what I want, but I don't know how to explain it to gcc. I've googled, read all the documentation, but it does not work as it described. My problem is, I want to access internal variables from asm, which seems impossible.

So given this code:

Code: Select all

int var=0;
void _init()
{
    var++;
}

// gcc -shared
00000000000000e9 <_init>:
  e9:	55                   	push   %rbp
  ea:	48 89 e5             	mov    %rsp,%rbp
  ed:	48 8b 05 1c 10 00 00 	mov    0x101c(%rip),%rax        # 1110 <_DYNAMIC+0x100>
  f4:	8b 00                	mov    (%rax),%eax
  f6:	8d 50 01             	lea    0x1(%rax),%edx
  f9:	48 8b 05 10 10 00 00 	mov    0x1010(%rip),%rax        # 1110 <_DYNAMIC+0x100>
 100:	89 10                	mov    %edx,(%rax)
 102:	90                   	nop
 103:	5d                   	pop    %rbp
 104:	c3                   	retq   

nm -D test.so 
0000000000002000 A _edata
0000000000001130 D _end
00000000000000e9 T _init
0000000000001000 D var
The function is exported, but the variable is relocated, so it's not a surprise asm can't handle it:

Code: Select all

.section .text
asmfunc:
    movq var, %rax
/usr/bin/ld: aaa.o: relocation R_X86_64_32S against symbol `var' can not be used when making a shared object; recompile with -fPIC
/usr/bin/ld: final link failed: Nonrepresentable section on output
Telling gcc to handle symbols internally:

Code: Select all

int var=0;
void _init()
{
    var++;
}

// gcc -shared -fvisibility=hidden
00000000000000e9 <_init>:
  e9:	55                   	push   %rbp
  ea:	48 89 e5             	mov    %rsp,%rbp
  ed:	8b 05 0d 0f 00 00    	mov    0xf0d(%rip),%eax        # 1000 <var>
  f3:	83 c0 01             	add    $0x1,%eax
  f6:	89 05 04 0f 00 00    	mov    %eax,0xf04(%rip)        # 1000 <var>
  fc:	90                   	nop
  fd:	5d                   	pop    %rbp
  fe:	c3                   	retq   

nm -D test.so 
0000000000002000 A _edata
00000000000010e0 D _end
Okay, now gcc generates relocation-free, rip relative address for var, which is what I want. But relocation information disappeared! So

Code: Select all

int var=0;
void __attribute__ ((__visibility__("default"))) _init()
{
    var++;
}

// gcc -shared -fvisibility=hidden
00000000000000e9 <_init>:
  e9:	55                   	push   %rbp
  ea:	48 89 e5             	mov    %rsp,%rbp
  ed:	8b 05 0d 0f 00 00    	mov    0xf0d(%rip),%eax        # 1000 <var>
  f3:	83 c0 01             	add    $0x1,%eax
  f6:	89 05 04 0f 00 00    	mov    %eax,0xf04(%rip)        # 1000 <var>
  fc:	90                   	nop
  fd:	5d                   	pop    %rbp
  fe:	c3                   	retq   

nm -D test.so 
0000000000002000 A _edata
00000000000010e0 D _end
00000000000000e9 T _init
Seems good. Variable is not relocated and the function is exported. Now let's see asm:

Code: Select all

.section .text
asmfunc:
    movq var, %rax
/usr/bin/ld: aaa.o: relocation R_X86_64_32S against hidden symbol `var' can not be used when making a shared object
/usr/bin/ld: final link failed: Nonrepresentable section on output
The error about "recompile with -fPIC" gone, but still not working!

I've tried all of the directives ".hidden", ".internal" etc., neither helped. I've also tried to compile without -fvisibility=hidden and adding attributes "hidden", "internal" to "var" without luck.

My question is, given a C internal variable (local, hidden whatever you call it), which is rip-relatively referenced from C, how to access that from asm?

Re: gcc visibility

Posted: Sun Dec 25, 2016 4:39 am
by bzt
I've found a solution, you have to add the string "(%rip)" you see in disasm to your symbol. I don't know why can't gas add it on it's own (specially when you use ".extern" directive), but hey, I got a solution :-)

Code: Select all

movq var(%rip), %rdi
That will compile to:

Code: Select all

 108:	48 8b 3d f1 0e 00 00 	mov    0xef1(%rip),%rdi        # 1000 <var>
As you can see, this version of mov does not need any more registers or relocation records, just a few bytes longer. So it's totally transparent, and since long mode only supports rip relative addressing, these two instructions are identical (as far as mnemonics concerned):

Code: Select all

mov symbol, reg
mov symbol(%rip), reg

Re: gcc visibility

Posted: Mon Jan 02, 2017 12:17 am
by bzt
No, it's not working :-( :-( :-(

Let's see this example

Code: Select all

int a = 0x123456;

void _init()
{
    a = 1;
}
Compile, and see what we've got:

Code: Select all

$ gcc -shared -fPIC -ffreestanding -nostdlib a.c -o ac.so
$ nm -D ac.so 
0000000000201018 D a
000000000020101c D __bss_start
000000000020101c D _edata
0000000000201020 D _end
00000000000002f0 T _init
$ objdump -d ac.so 

ac.so:     file format elf64-x86-64


Disassembly of section .text:

00000000000002f0 <_init>:
 2f0:	55                   	push   %rbp
 2f1:	48 89 e5             	mov    %rsp,%rbp
 2f4:	48 8b 05 fd 0c 20 00 	mov    0x200cfd(%rip),%rax        # 200ff8 <_DYNAMIC+0xf0>
 2fb:	c7 00 01 00 00 00    	movl   $0x1,(%rax)
 301:	90                   	nop
 302:	5d                   	pop    %rbp
 303:	c3                   	retq   
As you can see, the variable "a" is exported as a dynamic symbol, and it's rip-relatively referenced. Great, exactly what I what.

Now do the same from assembly:

Code: Select all

.global _init
.global a

.section .data
a:
    .quad 0x123456

.section .text
_init:
    pushq   %rbp
    movq    %rsp, %rbp
    movq    a(%rip), %rax
    movl    $0x1, (%rax)
    nop
    popq    %rbp
    ret
Now compile, and...

Code: Select all

$ gcc -shared -fPIC -ffreestanding -nostdlib a.S -o as.so
/usr/bin/ld: /tmp/cc0fePuc.o: relocation R_X86_64_PC32 against symbol `a' can not be used when making a shared object; recompile with -fPIC
/usr/bin/ld: final link failed: Bad value
collect2: error: ld returned 1 exit status
Why? Oh why on earth wants gcc to relocate "a"?!? The distance from the instruction to the data would not change under any circumstances! Just as in the C source! How come gcc know this when compiling C, but forget it on assembly?

If I remove the ".global a", it compiles fine, so the asm code is OK, but as expected there's no "a" reference in dynsym section:

Code: Select all

$ gcc -shared -fPIC -ffreestanding -nostdlib a.S -o as.so
$ nm -D as.so 
0000000000201008 D __bss_start
0000000000201008 D _edata
0000000000201008 D _end
000000000000024f T _init
$ objdump -d as.so 

as.so:     file format elf64-x86-64


Disassembly of section .text:

000000000000024f <_init>:
 24f:	55                   	push   %rbp
 250:	48 89 e5             	mov    %rsp,%rbp
 253:	48 8b 05 a6 0d 20 00 	mov    0x200da6(%rip),%rax        # 201000 <_GLOBAL_OFFSET_TABLE_>
 25a:	c7 00 01 00 00 00    	movl   $0x1,(%rax)
 260:	90                   	nop
 261:	5d                   	pop    %rbp
 262:	c3                   	retq   
Can anybody tell me, how to tell "gas" to compile what I need? I mean, put a variable in dynsym, but reference without relocation? I've googled all night without luck...

Re: gcc visibility

Posted: Mon Jan 02, 2017 12:57 am
by Boris
HI,
A shared library code must be able to relocate all of its symbols, because said library will be used with the same binary image ( read : only one physical instance of the library) amongst processes. or many times in a single process .
Either stop building a shared library, either build a relocatable symbol in assembly.

Re: gcc visibility

Posted: Mon Jan 02, 2017 1:41 am
by xenos
What happens if you run gcc on your C file with the -S option to produce assembler output, i.e., something like

Code: Select all

gcc -shared -fPIC -ffreestanding -nostdlib -S a.c -o ac.S
How does the generated assembly file differ from the one you wrote manually? What happens if you run gcc / gas on that one to assemble it?

Re: gcc visibility

Posted: Mon Jan 02, 2017 6:36 am
by bzt
@Boris: please. If you knew the x86_64 architecture, you'd know that relocation is only required for absolute address translations, as x86_64 uses rip relative addressing. And I can't forget shared lib, as I'm creating a library shared among threads...

@XenOs: good thinking! Totally forgot about -S, haven't used it for years. It's using GOTPCREL, which I have tried (among others, like adding GOTOFF to _GLOBAL_OFFSET_TABLE_). Obviously I messed it up somehow, because now it's working... I'm awake and coding for more than 30 hours now, that's why. Still, it's not the solution I'm looking for.

But Boris' post and -S output make me wonder, gcc assumes the text and the data segment position can change (but very unlikely, usually the whole file is loaded at once for performance), therefore I've put it to a test: both data and instruction in the same segment. Guess what? Not working!

This is clearly a bug: I could understand if ld assumes relocation for inter-segment references by default, but not without checking the VirtAddr fields in program header first! That's what linker scripts are for! On the other hand if the reference is inside the segment (only one "load" program header), there's definitely nothing that would require relocation, yet ld demands it! That's very bad for performance.

So back to my original question, how can I use a rip relative reference (one segment only) and have an address exposed in dynsym at the same time from GAS? Which is something that both elf and x86_64 capable of, and supposed to be trivial.

ps.: I have a feeling I know what made people to abandon gcc and start creating clang, nasm etc...

Re: gcc visibility

Posted: Mon Jan 02, 2017 7:28 am
by bzt
Just as I expected. Given the following code (let's forget about the fact that fasm does not require that stupid "(%rip)" suffix at all):

Code: Select all

format ELF64

public _init
public a

section '.text' executable 
_init:
    mov [a], 1
    ret

section '.data' executable 
  a dq 0
Compile, link and check:

Code: Select all

$ fasm a.asm 
flat assembler  version 1.71.57  (16384 kilobytes memory)
3 passes, 654 bytes.
$ ld -shared -ffreestanding -nostdlib a.o -o a.so
$ objdump -d a.so 

a.so:     file format elf64-x86-64


Disassembly of section .text:

0000000000000210 <_init>:
 210:	48 c7 05 e5 0d 20 00 	movq   $0x1,0x200de5(%rip)        # 201000 <_GLOBAL_OFFSET_TABLE_>
 217:	01 00 00 00 
 21b:	c3                   	retq   

Disassembly of section .data:

0000000000201000 <a>:
	...
As you can see, the instruction and the data are located in different sections, so a relocation record is generated (although they are in the same segment). Now remove the data section line, and repeat:

Code: Select all

format ELF64

public _init
public a

section '.text' executable 
_init:
    mov [a], 1
    ret

;this time same section
  a dq 0

$ fasm a.asm 
flat assembler  version 1.71.57  (16384 kilobytes memory)
3 passes, 467 bytes.
$ ld -shared -ffreestanding -nostdlib a.o -o a.so
$ objdump -d a.so 

a.so:     file format elf64-x86-64


Disassembly of section .text:

0000000000000210 <_init>:
 210:	48 c7 05 01 00 00 00 	movq   $0x1,0x1(%rip)        # 21c <a>
 217:	01 00 00 00 
 21b:	c3                   	retq   

000000000000021c <a>:
	...
And voila! I have a shared library which is referencing it's own variable directly without relocation. So it's proven it's possible (not to mention it's more effective, no linking needed, no indirect memory reference, smaller code und so weiter).

Re: gcc visibility

Posted: Tue Jan 03, 2017 10:14 pm
by bzt
Boris wrote:HI,
A shared library code must be able to relocate all of its symbols, because said library will be used with the same binary image ( read : only one physical instance of the library) amongst processes. or many times in a single process .
Either stop building a shared library, either build a relocatable symbol in assembly.
Your comment about not using shared library made me wonder. Is there a way to mark symbols to export them in dynsym section? For an executable I have only symbols (_end and _edata) there which were defined in the linker script and not in the source, therefore no visibility is set for them. If there's a way to do that, then you're right, there's no need for shared object (I mean ELF type of 3).

I really want to achieve something that can be done but has not been done before. I'm 100% sure that relocation for a dynamically loaded segment is not a must. More precisely, I don't want relocations inside my library which has rip relative addressing anyway, only for inter library calls. Let's make this clear:

Suppose we have A and B libraries, and A has a function a(). Now when A calls a(), it should be a simple rip-relative call, but when B calls a(), it has to be done through GOTPLT in B. Does this make any sense to you?

Re: gcc visibility

Posted: Thu Jan 05, 2017 5:56 am
by xenos
Does it help if you don't mark the symbols as global in the assembly file, but export them via a linker script, like this?

https://github.com/xenos1984/NOS/blob/m ... xports.lds

Re: gcc visibility

Posted: Thu Jan 05, 2017 6:05 am
by bzt
XenOS wrote:Does it help if you don't mark the symbols as global in the assembly file, but export them via a linker script, like this?

https://github.com/xenos1984/NOS/blob/m ... xports.lds
Wow, nice trick! For now, I've used two labels, one for export, and one for internal use prefixed by "my":

Code: Select all

.global mq_recv

/* msg_t *mq_recv(from) */
mq_recv:
mymq_recv:
But that can't be used with C functions, so I'll check out!