Slide 8 of L-L3 (Linking and Loading Part III) mentions how 32-bit x86 requires PC materialization within the Implementation of Shared Libraries. What does PC materialization mean and how can it be used to obtain the value of $eip?
PC materialization is a fancy name for a technique to obtain the value of the PC (program counter, on the x86 called the instruction pointer $eip) in a register so that it can be used for instructions that use relative addressing.
As an example, consider this code:
// pcmat.c
static int x;
int getx()
{
return x;
}
when this code is compiled as a shared library, it may be loaded anywhere in the address space.
However, the address where x
is located will be at a known offset from where the function getx
is located. So if the compiler could generate code that finds out where getx
was loaded, it
could use this value to generate code to access and return x
. This is what it does.
When compiled with gcc -m32 -shared -fPIC -S pcmat.c
, above code is compiled to:
.file "pcmat.c"
.text
.local x
.comm x,4,4
.globl getx
.type getx, @function
getx:
pushl %ebp
movl %esp, %ebp
call __x86.get_pc_thunk.ax
addl $_GLOBAL_OFFSET_TABLE_, %eax
movl x@GOTOFF(%eax), %eax
popl %ebp
ret
.LFE0:
.size getx, .-getx
.section .text.__x86.get_pc_thunk.ax,"axG",@progbits,__x86.get_pc_thunk.ax,comdat
.globl __x86.get_pc_thunk.ax
.hidden __x86.get_pc_thunk.ax
.type __x86.get_pc_thunk.ax, @function
__x86.get_pc_thunk.ax:
movl (%esp), %eax
ret
.ident "GCC: (GNU) 8.3.1 20191121 (Red Hat 8.3.1-5)"
.section .note.GNU-stack,"",@progbits
The relevant parts here are:
call __x86.get_pc_thunk.ax
this calls a "function" that, upon return, has the value of $eip in $eax.
Since a call
instruction places the current value of the instruction pointer on
the stack, this auto-generated function simply needs to read it from the top
of the stack before returning:
__x86.get_pc_thunk.ax:
movl (%esp), %eax
ret
Subsequently, after adjusting for the _GLOBAL_OFFSET_TABLE_
and the offset of x
in that
table, the value of x
is read and returned:
addl $_GLOBAL_OFFSET_TABLE_, %eax
movl x@GOTOFF(%eax), %eax
popl %ebp
ret
To avoid this indirection/complication, the x86_64 architecture introduced PC-relative addressing,
so on this architecture, above code uses simply x(%rip)
to achieve the same effect:
getx:
pushq %rbp
movq %rsp, %rbp
movl x(%rip), %eax
popq %rbp
ret