Kevin Lawton <[EMAIL PROTECTED]> wrote:

> A good point indeed.  BTW, the reason I'm trying so hard
> to eliminate the necessity for prescanning various instructions
> is this.  If we're running a fairly clean guest OS, at
> the point when we can determine we have a prescan list
> of 0 instructions, we can drop prescanning altogether for
> the user code.  If you can dump prescanning, then you can
> also omit a lot of the other framework, like monitoring
> out of page jumps since all code becomes trusted.

Indeed.  I agree that we should try as hard as possible to avoid
prescanning at all.  The efficiency gained by this will IMO far
outweigh any overhead caused by increased page faults etc.  (Note
that even if we need to prescan only for seldomly executed instructions
like sgdt, we still need to prescan *all* code, because it just
*might* contain those instructions ...)

> If we needed a huge GDT, we could protect multiple pages
> in this way.  It's sort of an "overlay" technique.  This
> way our GDT can be completely of a different size, yet
> remain located at the right linear address.

Yes.

> I'm not very familiar with PIC code, other than I know it's
> used for dynamic libs.  What I'm wondering about, is if
> PIC code is relocatable *once* as the dynamic loader loads the
> code into memory, and thereafter becomes static?  In this case,
> we are no better off unless we want to keep relocating it
> with our own built-in ELF re-loader.

Well, having just reimplemented various assembly routines in Wine
so as to conform to the PIC conventions, I have a quite good idea
how PIC works ;-)

The basic idea is this:  the ELF code section of an PIC object
does not contain any relocations at all.  This means that the
loader simply mmap()s the code section to just about any arbitrary
absolute address, and it works.

To make this possible, the code section does not contain any 
instructions that take an absolute memory address as argument.
For jumps/calls within the module this is easy, as they take 
EIP-relative offsets anyway.  The problem is referencing global 
data variables (either within the module or elsewhere), and 
jumps/calls to routines outside the module.

When compiling with -fPIC, this is solved like this:  At the
beginning of every routine that accesses any external reference
or global data, the compiler generates code like this:

     call +0
     pop %ebx
     addl $NNN, %ebx

where NNN is a compile-time generated constant that gives the
distance between the 'pop %ebx' line and one specifc address
inside the current module (with name _GLOBAL_OFFSET_TABLE_).

So, after this code stub is executed, %ebx contains the
absolute address of the global offset table, as currently 
loaded.  This value will remain in %ebx throughout the rest
of the function. 

Referencing a global data variable inside the own module is
now simple, as this variable has an offset relative to the
global offset table that is known at compile-time.  Thus, 
the compiler generates code like:

    movl XXX(%ebx), %eax

where XXX is the distance between the global offset table and
the variable in question.

Accessing data from *other* modules is more complex.  Here,
the dynamic linker scans the list of imported references,
loads the corresponding libraries if neccessary, computes the
actual absolute address if necessary, and writes the this 
address into the corresponding slot of the global offset table.
This is then accessed using an additional indirection by the 
compiled code, like so:

   movl YYY(%ebx), %eax
   movl (%eax), %eax

where YYY now gives the offset of the slot in the global offset
table corresponding to the imported global symbol.  (Note that
as the GOT is modified at load time, it must stay inside the
data section of the ELF object, which is mapped copy-on-write.)

To access external routines, there's an additional twist:  for
every imported routine, the compiler creates a proxy routine
inside the so-called Procedure Linkage Table.  A call to an
imported routine is then replaced at compiled time with a
call (EIP-relative) to the corresponding PLT stub.  The PLT
stub itself now performs an indirect jump via a slot of the
global offset table (which is filled at link time as usual).
(Actually, ELF supports lazy linking, which means that the
slot is filled with a pointer to a routine inside ld.so; the
first time the imported routine is called, this ld.so routine
gets invoked, actually loads the required library at this point,
and modifies the GOT entry to point to the real routine from 
now on.)


Now, in the case of our monitor, I'd propose that the monitor
does not reference any external symbol at all (you can't use
library routines anyway, you can't call into the kernel etc,
so what's the point?).   If we then compile the monitor code
using the -fPIC switch, which means that all accesses to our
own global data is modified to accesses relative to the GOT,
we have code that can be moved even to a different *offset*
(inside its CS) at run time at will, without needing *any*
modifications ...


Bye,
Ulrich

Reply via email to