Ulrich Weigand wrote:
>
> Hello,
>
> just a quick question: Kevin, you said you've already planned the use
> of a different address space in the monitor; I was wondering how you
> intend to perform the actual PDBR switch.
>
> AFAIK there's only two ways to do it: either by placing the code that
> performs the switch into a page mapped to the same linear address in
> both spaces, or else using a task switch that reloads the PDBR.
>
> The first method is ugly as we need to force a particular linear address
> inside the Linux address space, but the second method is somewhat ugly
> as well as we need to load the GDT *before* the task switch, so that
> the TSS descriptor is available, but then after the task switch, the
> linear base address of the GDT will have changed :-/
>
> What do you think?
I just remembered I had this mapped out before when
I was playing with it. Refined it a bit as per our
current code, and here it is...
Here's a quick summary on how to switch from the
guest's linear address space to the host's linear
address space. Essentially, the assembly routines
__host2monitor and __monitor2host make up the "shim"
code, and they should be moved into an individual
page. They are the logical place for the addition
of the page mapping transitions, and need to be
a single page for the most efficient transitions.
TRANSITIONING FROM THE MONITOR TO THE HOST LINEAR
ADDRESS SPACE
=================================================
[assuming we're in the monitor]
* Monitor saves current page table entry for the page
where it wants to map in the shim, as the guest may
be using this. The shim code really lives amonst our
other monitor code, just that we want to create an
additional page mapping such that it also lives in
the host linear space at the same address the host
expects it.
* Change the mapping to be the same physical address as the
page is when in the host context.
* Since we have a new mapping, we invalidate that page
by the INVLPG instruction.
* Jump to the shim code. Depending on how we wanted to
implement this, we have some options:
- if we always leave our monitor CS descriptor limit
field to 4G then we can always jump anywhere, since
we are free to wrap around the address space.
- We can save and replace, or use an empty descriptor
to describe a segment which can reach the linear address
space and intersegment jump to that.
* load the descriptor tables with host values:
LGDT [stored host GDT values]
LIDT [stored host IDT values]
* clear busy bit in host TSS
* restore host TR
LTR host_tss_sel
* reload the PDBR
MOV [stored CR3], CR3
// Now we're in the host linear space.
* transfer control back to host kernel module, with
an intersegment jump to correct the CS selector, if
that is needed.
jmp CS:host_target_eip
* The best place for 'host_target_eip', in our existing
code scenario, would be the label 'restore_to_host'.
It is the second half of the host2monitor code, which
pops values off the stack restoring all the segment
and general registers, then returning from the
code which called it.
We'll have to restructure __host2monitor and
__monitor2host accordingly. We do pretty much
the same in the other direction - going from host
to the monitor linear space. This is much the case
already, with the two routines, as you'll notice they
are very symmetrical.
-Kevin