[SeaBIOS] [PATCH 1/2] Don't pass return address to transition(32, 16, 16big) on stack.
H. Peter Anvin
hpa at zytor.com
Wed Dec 8 02:14:11 CET 2010
First of all, sorry for the very late reply, however, I thought I really
ought to offer my perspective on this:
On 11/25/2010 06:24 AM, Kevin O'Connor wrote:
> It's difficult to have a uniform view of the stack when transition
> modes, so pass the return address in a register. As a result, the
> transition functions only access memory via the %cs selector now.
I think this assertion is rather unfortunate, because my own experience
with thunking is that it is actually a very useful thing to have access
to the real-mode stack.
This is simply accomplished by computing a 32-bit register containing
the value (ss << 4) + sp, for example:
movzwl %sp, %eax
movl %ss, %ecx
shrl $4, %ecx
addl %ecx, %eax
This is particularly handy if there is a push/pop of the 16-bit register
set in the entry/exit sequence. Furthermore, pushing the target address
onto the stack rather than stuffing it into a register allows a 32-bit
routine to have full access to the 16-bit register image, whereas
burning a register means that that register is going to have to be
handled differently.
In Syslinux I have this formalized so that the sequence:
pushl $func32
callw _pm_call
... turns into the C function call:
void func32(com32sys_t *regs)
... where com32sys_t is a structure which contains the 16-bit register
image:
typedef struct {
uint16_t gs; /* Offset 0 */
uint16_t fs; /* Offset 2 */
uint16_t es; /* Offset 4 */
uint16_t ds; /* Offset 6 */
reg32_t edi; /* Offset 8 */
reg32_t esi; /* Offset 12 */
reg32_t ebp; /* Offset 16 */
reg32_t _unused_esp; /* Offset 20 */
reg32_t ebx; /* Offset 24 */
reg32_t edx; /* Offset 28 */
reg32_t ecx; /* Offset 32 */
reg32_t eax; /* Offset 36 */
reg32_t eflags; /* Offset 40 */
} com32sys_t;
This is simply the image created on the stack by the sequence (in NASM
syntax):
_pm_call:
pushfd
pushad
push ds
push es
push fs
push gs
This has been shown to be amazingly versatile, especially since the
16-bit register image can be not just observed but written directly.
One can implement this either with or without a stack switch (to do so
without a stack switch, the protected-mode ESP is computed from SS:SP).
However, since real-mode stacks tend to be very small -- often only a
few hundred bytes -- it is probably a bad idea.
In Syslinux this is actually implemeted in form of a lower-level
function which does indeed take an address in a register, so the two
approaches are not mutually exclusive. The actual full implementation
of the _pm_call routine looks like (note: this code assumes CS = 0).
;
; _pm_call: call PM routine in low memory from RM
;
; on stack = PM routine to call (a 32-bit address)
;
; ECX, ESI, EDI passed to the called function;
; EAX = EBP in the called function points to the stack frame
; which includes all registers (which can be changed if desired.)
;
; All registers and the flags saved/restored
;
; This routine is invoked by the pm_call macro.
;
_pm_call:
pushfd
pushad
push ds
push es
push fs
push gs
mov bp,sp
mov ax,cs
mov ebx,.pm
mov ds,ax
jmp enter_pm
bits 32
section .textnr
.pm:
; EAX points to the top of the RM stack, which is EFLAGS
test RM_FLAGSH,02h ; RM EFLAGS.IF
jz .no_sti
sti
.no_sti:
call [ebp+4*2+9*4+2] ; Entrypoint on RM stack
mov bx,.rm
jmp enter_rm
bits 16
section .text16
.rm:
pop gs
pop fs
pop es
pop ds
popad
popfd
ret 4 ; Drop entrypoint
The entire file including the enter_pm/enter_rm functions can be seen at:
http://tinyurl.com/2cjvt4a
-hpa
More information about the SeaBIOS
mailing list