PPC64 ABI

Notes on comparision between the PPC64 ABI and the IA64 ABI.

Function Pointers

Both IA64 and PPC64 require function pointers.

The format is slightly different; IA64 has two doublewords -- first is fn address and second is gp value.

PPC64 has three doublewords, first is address, the second is a TOC pointer, which is similar to a gp and the final is an "environment pointer" for langugages such as Pascal and PL/1.

IA64 is a caller saved gp, while TOC is callee saved.

Function Pointers

taking address of a local function

ianw@mingus:~/tmp/ppc64$ cat test.c
int localfunction(void)
{
        return 100;
}

int main(void)
{
        void *p = &localfunction;
}

This creates the following assembly

        .file   "test.c"
        .section        ".text"
        .align 2
        .globl localfunction
        .section        ".opd","aw"
        .align 3
localfunction:
        .quad   .localfunction,.TOC.@tocbase,0
        .previous
        .size   localfunction,24
        .type   .localfunction,@function
        .globl  .localfunction
.localfunction:
        std 31,-8(1)
        stdu 1,-64(1)
        mr 31,1
        li 0,100
        mr 3,0
        ld 1,0(1)
        ld 31,-8(1)
        blr
        .long 0
        .byte 0,0,0,0,128,1,0,1
        .size   .localfunction,.-.localfunction
        .section        ".toc","aw"
.LC0:
        .tc localfunction[TC],localfunction
        .section        ".text"
        .align 2
        .globl main
        .section        ".opd","aw"
        .align 3
main:
        .quad   .main,.TOC.@tocbase,0
        .previous
        .size   main,24
        .type   .main,@function
        .globl  .main
.main:
        std 31,-8(1)
        stdu 1,-80(1)
        mr 31,1
        lfd 0,.LC0@toc(2)
        stfd 0,48(31)
        mr 3,0
        ld 1,0(1)
        ld 31,-8(1)
        blr
        .long 0
        .byte 0,0,0,0,128,1,0,1
        .size   .main,.-.main
        .section        .note.GNU-stack,"",@progbits
        .ident  "GCC: (GNU) 3.4.2"

Some things to note

Let's disassemble it

0000000010000518 <.main>:
...
    10000524:   c8 02 80 38     lfd     f0,-32712(r2)

Main loads up the function pointer via the TOC register r2. The TOC register is always defined to be 0x8000 past the base of the TOC, which is the .got according to the ABI. So do some maths

32718 = 0x7FC8

0x8000 -
0x7FC8
======
0x38 = 56 bytes past the {{{.got

(which is at 0x10010a38); hence we are loading address 0x10010a70}}}

Disassembling 0x10010a70 we see that it is

   10010a70:   00 00 00 00     .long 0x0
   10010a74:   10 01 08 28     vmsumshm v0,v1,v1,v0

or in normal format 0x0000000010010828 which is in ...

  [16] .opd              PROGBITS         0000000010010798  00000798
       0000000000000108  0000000000000000  WA       0     0     8

We can further disassemble this to see the function descriptor

0000000010010828 <localfunction>:
    10010828:   00 00 00 00     .long 0x0
    1001082c:   10 00 04 ec     vsldoi  v0,v0,v0,3
    10010830:   00 00 00 00     .long 0x0
    10010834:   10 01 8a 38     .long 0x10018a38
    10010838:   00 00 00 00     .long 0x0
    1001083c:   00 00 00 00     .long 0x0

i.e.

comparing to ia64

IA64 also makes function pointers live in .opd, and we also load them up via the .got. Esentially, they work the same.

address of an external function

When we take the address of an external function, we require some way to ensure pointer comparison. Since the program calls through the PLT, we can not give the address of the function pointer in the PLT as this will not compare as the same value if the shared library were to take the address of the function.

As an example, see this trivial program that takes a function from a shared library

ianw@mingus:~/tmp/ppc64$ cat test.c
extern int externfunction(void);

int main(void)
{
        void *p = &externfunction;
}
ianw@mingus:~/tmp/ppc64$ cat libtest.c
int externfunction(void)
{
        return 100;
}
ianw@mingus:~/tmp/ppc64$ gcc -shared -o libtest.so libtest.c
ianw@mingus:~/tmp/ppc64$ gcc -o test -L. -ltest test.c

Looking at the asm code for test.c

        .file   "test.c"
        .section        ".toc","aw"
.LC0:
        .tc externfunction[TC],externfunction
        .section        ".text"
        .align 2
        .globl main
        .section        ".opd","aw"
        .align 3
main:
        .quad   .main,.TOC.@tocbase,0
        .previous
        .size   main,24
        .type   .main,@function
        .globl  .main
.main:
        std 31,-8(1)
        stdu 1,-80(1)
        mr 31,1
        lfd 0,.LC0@toc(2)
        stfd 0,48(31)
        mr 3,0
        ld 1,0(1)
        ld 31,-8(1)
        blr
        .long 0
        .byte 0,0,0,0,128,1,0,1
        .size   .main,.-.main
        .section        .note.GNU-stack,"",@progbits
        .ident  "GCC: (GNU) 3.4.2"

This looks a lot like the other version. However we now have a reloc for this function

ianw@mingus:~/tmp/ppc64$ readelf --relocs ./test

Relocation section '.rela.dyn' at offset 0x450 contains 1 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000010010bd8  000400000026 R_PPC64_ADDR64    0000000000000000 externfunction + 0

The ABI tells us that this means just replace offset with the symbol value. So the important thing is where is offset -- it is in the .got

  [22] .got              PROGBITS         0000000010010ba0  00000ba0
       0000000000000068  0000000000000008  WA       0     0     8

So, what is using this value in the .got? Well, we know that we load things from the .got via an offset from the base + 0x8000; thus the offset is 0000000010010ba0 - 000010010bd8 = 0x38 or in decimal taking into account the offset 32712

And lets look how main loads the address

0000000010000654 <.main>:
    10000654:   fb e1 ff f8     std     r31,-8(r1)
    10000658:   f8 21 ff b1     stdu    r1,-80(r1)
    1000065c:   7c 3f 0b 78     mr      r31,r1
    10000660:   c8 02 80 38     lfd     f0,-32712(r2)  <---- right where we thought it would be!

So what is that symbol going to be replaced with? Lets look at the symbols in libtest.so

ianw@mingus:~/tmp/ppc64$ readelf --symbols ./libtest.so  | grep externfunction
    32: 0000000000010cd8    24 FUNC    GLOBAL DEFAULT   14 externfunction

If we look at where that symbol lives

  [14] .opd              PROGBITS         0000000000010c60  00000c60
       00000000000000a8  0000000000000000  WA       0     0     8

So when main takes &externalfunction it ends up getting the function descriptor from .opd in the shared library

Comparison to IA64

This is quite different to the way IA64 implements function pointers. On IA64, taking the address of an external function would generate a R_IA64_FPTR64LSB relocation, which requires the dynamic loader to generate the official function descriptor. If in your shared library you also take the address of the function, it will generate that relocation there too. On PPC64, if you take the address of the function in shared library you only generate another R_PPC64_ADDR64 relocation that fixes up to the .opd descriptor for the function (only since the base address isn't known).

PLT

PPC64 has a .glink section which loosely corresponds to the IA64 .plt section. This code loads the local function descriptor, which on IA64 is held in .IA_64.plt_off but in PPC64 is held in .plt.

A noticable difference between IA64 and PPC64 is the PLT. PPC64 defines the PLT as being

.plt          SHT_NOBITS         SHF_ALLOC + SHF_WRITE

This means no bits are allocated for it in the file, and it should be zeroed, writable memory when executed. This is because the PLT will be setup by the dynamic loader at startup, and it needs to be writable since the fixups are done in this section. Contrast this to IA64 where .plt is considered code because it simply loads and branches to the function descriptor stored in .IA_64.pltoff

side note : as at 15/11/2004, the ABI is actually incorrect as it states

From http://www.linuxbase.org/spec/ELF/ppc64/spec/x954.html

  As shown above, each procedure linkage table entry I, as initialized
  by the link editor, transfers control to the corresponding glink entry
  I at .GLINKI.

That should say as initalized by the dynamic loader

Comparision of the two methods

PPC64

IA64

Startup

ld.so sets up entries in .plt to point to corresponding .glink entries.

Nothing

First function call

.plt entry jumps into .glink entry which the link editor has setup to load the R_PPC64_JMP_SLOT relocation and branch to the special 0 .glink entry which branches into the dynamic loader. Dynamic loader runs and fixes up .plt entry from information in the R_PPC64_JMP_SLOT relocation.

.plt entry first finds the local function descriptor in .IA_64.pltoff by adding the offset to the gp. It then loads this function descriptor, which initially points to a special piece of code which loads the relocation offset (the relocation offset is a 22 bit index into DT_JUMPREL). This then jumps to generic fixup code, which uses the offset to find the required relocation. This then branches to the special 0 .plt entry which branches into the dynamic loader, and can consequently use the relocation to look up the symbol and re-write the function descriptor in .IA_64.pltoff.

Second Function Call

.plt entry is fixed up by the dynamic loader, so second call can load this function descriptor directly.

.plt stub code finds function descriptor in .IA_64.pltoff via offset from gp again, which has now been fixed up and can be branched to.

Notable differences

IA64wiki: PPC64ABI (last edited 2009-12-10 03:13:38 by localhost)

Gelato@UNSW is sponsored by
the University of New South Wales National ICT Australia The Gelato Federation Hewlett-Packard Company Australian Research Council
Please contact us with any questions or comments.