Position independent assembly, rip relative addressing.

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
Hellbender
Member
Member
Posts: 63
Joined: Fri May 01, 2015 2:23 am
Libera.chat IRC: Hellbender

Position independent assembly, rip relative addressing.

Post by Hellbender »

Edit4: I hate this kind of sh*t. GAS automagically converts LABEL(%rip) to such offset that you access the LABEL, not VALUE_OF_LABEL+%rip.
On the other hand, VALUE(%rip) is interpret as VALUE+%rip without that magic.
But when I do (expression with labels)(%rip), it does the former magic screwing up my calculations and there is no way to convince not to do it..
The only way I could get RIP relative addressing to absolute address, is to define the value in another .S, so that GAS does it magic, like this:

Code: Select all

.global _vga_base
_vga_base=0xb8000

Code: Select all

.extern _vga_base
        mov $0x0f65, %ax
        mov %ax, (_vga_base+0x10)(%rip)
resulting in

Code: Select all

0x80002000c0 <_start>                   mov    $0xf65,%ax
0x80002000c4 <_start+4>                 mov    %ax,-0x1480bb(%rip)        # 0x80000b8010


====
Edit3: This is just not fair. GAS does something nasty, as the following works:

Code: Select all

        mov %ax, (0xB8010-_start)(%rip)
        mov %ax, (0xB8010-_start)(%rip)
        mov %ax, (0xB8010-_start)(%rip)
resulting in

Code: Select all

0x80002000c4 <_start+4>         mov    %ax,-0x1480bb(%rip)        # 0x80000b8010
0x80002000cb <_start+11>        mov    %ax,-0x1480c2(%rip)        # 0x80000b8010
0x80002000d2 <_start+18>        mov    %ax,-0x1480c9(%rip)        # 0x80000b8010
====
My %rip relative addressing is off by 4, and I cannot figure out why.
Assume the following code will be linked at 2M but loaded to virtual address X+2M, and VGA memory will be mapped at X+0xB8000.
I want to write a character at a fixed location using the following:

Code: Select all

        mov $0x0f65, %ax
        mov %ax, (0xB8010-.)(%rip)
I thought that (VALUE-.)+%rip = 2M+VALUE, because dot should be current link address, and %rip would be current link address + X.
However, the memory address is off by 4, so I have to use the following to get correct result:

Code: Select all

        mov $0x0f65, %ax
        mov %ax, (0xB8010+4-.)(%rip)
Could someone please explain why the +4 is required?

Edit2: There is something fundamentally wrong with this, but I cant figure it out. If I replicate the statement few times, each one gets a different address
(as if the dot refers to _start symbol, not current location):

Code: Select all

   0x80002000c4 <_start+4>         mov    %ax,-0x1480bb(%rip)        # 0x80000b8010
   0x80002000cb <_start+11>        mov    %ax,-0x1480c9(%rip)        # 0x80000b8009
   0x80002000d2 <_start+18>        mov    %ax,-0x1480d7(%rip)        # 0x80000b8002
Edit: if someone would also know how to do that addressing using C symbols, that would be great..
Hellbender OS at github.
jnc100
Member
Member
Posts: 775
Joined: Mon Apr 09, 2007 12:10 pm
Location: London, UK
Contact:

Re: Position independent assembly, rip relative addressing.

Post by jnc100 »

Hellbender wrote:My %rip relative addressing is off by 4, and I cannot figure out why.
The dot operator in gas refers to "the current address that as is assembling into". This is not particularly well defined for when it is used in the middle of an instruction, as it is expected to be rather used in data fields. Having said that, when you use a mov instruction such as you are doing, the 0xB8010-. field will be encoded as a disp32 value, which is usually the last 4 bytes of the instruction. You can therefore assume that the . refers to the address of the last 4 bytes of the current instruction.

When the x86_64 architecture performs a rip-relative memory access, it is relative to the rip of the next instruction, and is therefore 4 bytes higher than you expect it to be.
Hellbender wrote:GAS automagically converts LABEL(%rip) to such offset that you access the LABEL, not VALUE_OF_LABEL+%rip.
This is expected. The typical use of rip relative addressing is to facilitate position-independent code. This will be generated by gcc as relocations to labels that are typically in the GOT, and thus this is what gas is expecting you are doing.
Hellbender wrote:if someone would also know how to do that addressing using C symbols, that would be great
If you could explain a bit more as to what exactly you are trying to achieve? Its quite probable that RIP-relative addressing is not the appropriate tool to be using. If you do wish to use it from C though, you may want to investigate the -fpic option to gcc.

Regards,
John.
Hellbender
Member
Member
Posts: 63
Joined: Fri May 01, 2015 2:23 am
Libera.chat IRC: Hellbender

Re: Position independent assembly, rip relative addressing.

Post by Hellbender »

If you could explain a bit more as to what exactly you are trying to achieve? Its quite probable that RIP-relative addressing is not the appropriate tool to be using. If you do wish to use it from C though, you may want to investigate the -fpic option to gcc.
This was me struggling to get my first -fPIC code to work. I had some asm and C code that wrote few chars to VGA buffer. That VGA buffer was mapped at 0xB8000. When I loaded that code at 0x8000000000, that buffer obviously moved to 0x80000B8000, and any code writing to 0xB8000 page faults. So I needed a way to get the RIP relative code writing to that address. I finally figured how to do that in ASM (the rant in the original post) but couldn't figure out how to write it in C. Anyway it's not a big issue as now I can use inline LEA with RIP to get the correct address into a C variable.
Hellbender OS at github.
jnc100
Member
Member
Posts: 775
Joined: Mon Apr 09, 2007 12:10 pm
Location: London, UK
Contact:

Re: Position independent assembly, rip relative addressing.

Post by jnc100 »

Hellbender wrote:That VGA buffer was mapped at 0xB8000. When I loaded that code at 0x8000000000, that buffer obviously moved to 0x80000B8000, and any code writing to 0xB8000 page faults.
From this it sounds like you're identity mapping the first x MiB to 0x8000000000. That is perfectly correct for executing code, and PIC may help you here, however if the load address is known in advance by the linker thent you shouldn't need any PIC. PIC was not designed for accessing data that is defined outside the binary however (you need to do run-time patch up through the GOT for that).

Are you planning on using this mechanism for all memory mapped devices? I'd argue that instead when you enumerate devices (legacy, PCI bus etc) you find out the physical address of the device, then map it somewhere in the virtual space to an address you know and then you already know the address and don't need to try and make sure every address is relative to the kernel code address.

e.g.
Device scan finds VGA card.
VGA driver is started and requests block of virtual memory from the kernel at an arbritrary address, to which is mapped 0xb8000.
When VGA driver wants to write to memory, it writes to virt_addr + 2 * char_offset.

Regards,
John.
Hellbender
Member
Member
Posts: 63
Joined: Fri May 01, 2015 2:23 am
Libera.chat IRC: Hellbender

Re: Position independent assembly, rip relative addressing.

Post by Hellbender »

jnc100 wrote:From this it sounds like you're identity mapping the first x MiB to 0x8000000000. That is perfectly correct for executing code, and PIC may help you here, however if the load address is known in advance by the linker thent you shouldn't need any PIC. PIC was not designed for accessing data that is defined outside the binary however (you need to do run-time patch up through the GOT for that).
Yeah, this is for "service modules" that are mapped to unknown address space (basically reserving 1GB for each service as they are loaded, because I want to keep them visible for all processes due to the way I do "service IPC").
I didn't have time to implement .got section handling, so currently I just carefully avoid any code depending on that.
jnc100 wrote:Are you planning on using this mechanism for all memory mapped devices? I'd argue that instead when you enumerate devices (legacy, PCI bus etc) you find out the physical address of the device, then map it somewhere in the virtual space to an address you know and then you already know the address and don't need to try and make sure every address is relative to the kernel code address.
No, I needed this just for VGA text mode debugging purposes before usermode is fully set up (basically just to inform me that _start was called).
I already changed the logic so that process is told the virtual memory ranges where devices (including VGA) are mapped.
So now I can use that information in C side once main() is called.
I'll do the proper enumeration, mapping for other devices once I get to the PCI side of things.
Hellbender OS at github.
Post Reply