This article describers the PIC (Position Independant Code) and GOT (Global Offset Table) used in CellOS. When
the system creates a process image, the executable file portion of the
process has fixed addresses and the system chooses shared object
library virtual addresses to avoid conflicts with other segments in the
process. To maximize text sharing, shared objects conventionally use
position-independent code, in which instructions contain no absolute
addresses. Shared object text segments can be loaded at various virtual
addresses without having to change the segment images. Thus multiple
processes can share a single shared object text segment, even if the
segment reside at a different virtual address in each process. A
"Global Offset Table," or GOT, provides information for address
calculation. Position independent object files (executable and shared
object files) have a table in their data segment that holds addresses.
When the system creates the memory image for an object file, the table
entries are relocated to reflect the absolute virtual address as
assigned for an individual process. Because data segments are private
for each process, the table entries can change—unlike text segments,
which multiple processes share. When the
dynamic linker creates memory segments for a loadable object file, it
processes the relocation entries, some of which will be of type
R_PPC_GLOB_DAT, referring to the global offset table. The dynamic
linker determines the associated symbol values, calculates their
absolute addresses, and sets the global offset table entries to the
proper values. Although the absolute addresses are unknown when the
link editor builds an object file, the dynamic linker knows the
addresses of all memory segments and can thus calculate the absolute
addresses of the symbols contained therein. A
global offset table entry provides direct access to the absolute
address of a symbol without compromising position-independence and
sharability. Because the executable file and shared objects have
separate global offset tables, a symbol may appear in several tables.
The dynamic linker processes all the global offset table relocations
before giving control to any code in the process image, thus ensuring
the absolute addresses are available during execution. The
dynamic linker may choose different memory segment addresses for the
same shared object in different programs; it may even choose different
library addresses for different executions of the same program.
Nonetheless, memory segments do not change addresses once the process
image is established. As long as a process exists, its memory segments
reside at fixed virtual addresses. This
allows a program, such as the dynamic linker, to find its own dynamic
structure without having yet processed its relocation entries. This is
especially important for the dynamic linker, because it must initialize
itself without relying on other programs to relocate its memory image. "
instrcution; However, the ".text 2" seems to trigger the compiler to
move the immediate value to somewhere else, not between the two
instructions. Should there is no ".text 2", the immediate value were to
be put in between the two intructions, thus the (0f - 1f) were to be
-4; However, with the ".text 2", the (0f - 1f) becomes 936 (in
this compilation, and can vary with different compilations if you have
other code added or changed), because the immediate value has been
"moved" to a position higer than the CIA. The (0f - 1f), in
this case, the value 936, is an intermediate offset, at that offset (to
the CIA), stores the real offset value from the centor of the GOT to
CIA; In our case, the CIA for the "1: mflr " is 0x000021bc; 0x000021bc + 936 = 0x2564; check the disasm and debug the code, you will find the following: (gdb) x/xw 0x2564 #to display the memory at 0x2564 0x2564 <in32+8>: 0x0000f468 (gdb) So, the "lwz ,936()" say "lwz r0,0b-1b(r14)" is to load the with a value 0x0000f468; This 0x0000f468 is the real offset from CIA to the centor of GOT; Thus the "add ,," is actually to add the CIA with the offset to the centor of GOT, which in effect is to set to "sit" in the centor of GOT; Note that I said several times of "centor of GOT", becasue I interpret that the can be added with a 16bit "signed" offset to access the contents around ; I call the GOT anchor. This is a little bit like the SDA (Small Data Area) concept. So, now the is set to sit in the centor of a GOT area, where it can easily used to locate the "values" around it (+/- 32KB); is not changed across the execution from now on. Let's now remember the value of , which is 0x0000f468 + 0x000021bc = 0x11624; We need to use this 0x11624 to calculate the address of the entries in the GOT. , that is "lwz ,-32748()", or "lwz ,GOT(transfer_to_handler)". Remember that = 0x11624, this instrcution is to load from EA = 0x11624 - 32748 = 0x9638. So what is the value in 0x9638? (gdb) x/xw 0x9638 0x9638 <_GOT2_TABLE_+56>: 0x0000229c (gdb) OK, we see something called "GOT2_TABLE", seems familar? Yes, GOT! Looking back in this article, there is (yes, I copied it twice, becasue the space is for free :-)) /* * Set up GOT: Global Offset Table * * Use r14 to access the GOT */ START_GOT GOT_ENTRY(_GOT2_TABLE_) GOT_ENTRY(_FIXUP_TABLE_)
GOT_ENTRY(_start) GOT_ENTRY(_start_of_vectors) GOT_ENTRY(_end_of_vectors) GOT_ENTRY(transfer_to_handler)
GOT_ENTRY(__init_end) GOT_ENTRY(_end) GOT_ENTRY(__bss_start) END_GOT The GOT_ENTRY is to define an entry "around" the centor of GOT (the GOT anchor). #define GOT_ENTRY(NAME) .L_ ## NAME = . - .LCTOC1 ; .long NAME The actual GOT table looks like this: 00009600 <_GOT2_TABLE_>: 9600: 00 00 96 00 .long 0x9600 9604: 00 00 96 48 .long 0x9648 9608: 00 00 46 68 .long 0x4668 960c: 00 00 26 68 .long 0x2668 9610: 00 00 46 00 .long 0x4600 9614: 00 00 48 04 .long 0x4804 9618: 00 00 9c 00 .long 0x9c00 961c: 00 60 d0 00 .long 0x60d000 9620: 00 00 9c 00 .long 0x9c00 9624: 00 00 96 00 .long 0x9600 9628: 00 00 96 48 .long 0x9648 962c: 00 00 21 00 .long 0x2100 9630: 00 00 01 00 .long 0x100 9634: 00 00 20 98 .long 0x2098 9638: 00 00 22 9c .long 0x229c 963c: 00 00 9c 00 .long 0x9c00 9640: 00 60 d0 00 .long 0x60d000 9644: 00 00 9c 00 .long 0x9c00 Disassembly of section .data: So, at "9638: 00 00 22 9c .long 0x229c", there stores a value 0x229c; (gdb) x/xw 0x229c 0x229c : 0x92d50090 (gdb) Right, that is what we want! The address of the function transfer_to_handler is stored in the GOT entry. So
now we have a very clear understanding to the GOT. It is actually a
"jumping table", through which an indirect addressing to the absolute
addresses are performed. This technic is is mostly used for operating
systems to load exe images using shared objects. For CellOS, derived
from u-boot, it simply severs as a way to locate some absolute symbols.
In the linker script,there is a section: .reloc : { *(.got) _GOT2_TABLE_ = .; *(.got2) _FIXUP_TABLE_ = .; *(.fixup) } __got2_entries = (_FIXUP_TABLE_ - _GOT2_TABLE_) >>2; __fixup_entries = (. |