Real hardware, as opposed to QEMU, does not allow to have a call gate

in long mode which transfers control to 32bit code segment. Unbreak
the lcall $7,$0 implementation on amd64 by putting the 64bit user code
segment' selector into call gate, and execute the 64bit trampoline
which converts the return frame into 32bit format and switches back to
32bit mode for executing int $0x80 trampoline.

Note that all jumps over the hoops are performed in the user mode.

MFC after:	1 week
This commit is contained in:
Konstantin Belousov 2012-08-14 12:13:27 +00:00
parent ee4116b8f7
commit 95fd15898b
2 changed files with 27 additions and 6 deletions

View File

@ -91,8 +91,29 @@ ia32_osigcode:
*/
ALIGN_TEXT
lcall_tramp:
.code64
/*
* There, we are in 64bit mode and need to return to 32bit.
* First, convert call frame from 64 to 32 bit format.
*/
pushq %rax
movl 16(%rsp),%eax
movl %eax,20(%rsp) /* ret %cs */
movl 8(%rsp),%eax
movl %eax,16(%rsp) /* ret %rip -> %eip */
popq %rax
addq $8,%rsp
/* Now return to 32bit */
pushq $0x33 /* _ucode32sel UPL */
callq 1f
1:
addq $2f-1b,(%rsp)
lretq
2:
/* Back in 32bit mode */
.code32
cmpl $SYS_vfork,%eax
je 2f
je 4f
pushl %ebp
movl %esp,%ebp
pushl 0x24(%ebp) /* arg 6 */
@ -101,19 +122,19 @@ lcall_tramp:
pushl 0x18(%ebp)
pushl 0x14(%ebp)
pushl 0x10(%ebp) /* arg 1 */
pushl 0xc(%ebp) /* gap */
pushl 0xc(%ebp) /* gap */
int $0x80
leavel
1:
3:
lretl
2:
4:
/*
* vfork handling is special and relies on the libc stub saving
* the return ip in %ecx. If vfork failed, then there is no
* child which can corrupt the frame created by call gate.
*/
int $0x80
jb 1b
jb 3b
addl $8,%esp
jmpl *%ecx
#endif

View File

@ -244,7 +244,7 @@ setup_lcall_gate(void)
bzero(ssd, sizeof(*ssd));
ssd->gd_looffset = lcall_addr;
ssd->gd_hioffset = lcall_addr >> 16;
ssd->gd_selector = _ucode32sel;
ssd->gd_selector = _ucodesel;
ssd->gd_type = SDT_SYSCGT;
ssd->gd_dpl = SEL_UPL;
ssd->gd_p = 1;