[gPXE-devel] [PATCH] Run COM32 programs with a valid IDT

Stefan Hajnoczi stefanha at gmail.com
Tue Jun 29 03:23:30 EDT 2010


CCed Daniel Verkamp, who wrote the COMBOOT implementation.

On Tue, Jun 29, 2010 at 2:18 AM, Geoff Lywood <glywood at vmware.com> wrote:
> COM32 binaries generally expect to run with interrupts enabled. Syslinux does
> so, and COM32 programs will execute cli/sti pairs when running a critical
> section, to provide mutual exclusion against BIOS interrupt handlers.
> Previously, under gPXE, the IDT was not valid, so any interrupt (e.g. a timer
> tick) would generally cause the machine to triple fault.
>
> This change introduces code to:
> - Create a valid IDT at the same location that syslinux uses
> - Create an "interrupt jump buffer", which contains small pieces of code that
>  simply record the vector number and jump to a common handler
> - Thunk down to real mode and execute the BIOS's interrupt handler whenever
>  an interrupt is received in a COM32 program
> - Switch IDTs and enable/disable interrupts when context switching to and from
>  COM32 binaries
>
> Testing done:
> - Booted VMware ESX using a COM32 multiboot loader (mboot.c32)
> - Built with GDBSERIAL enabled, and tested breakpoints on int22 and com32_irq
> - Put the following code in a COM32 program:
>    asm volatile ( "sti" );
>    while ( 1 );
>  Before this change, the machine would triple fault immediately. After this
>  change, it hangs as expected. Under Bochs, it is possible to see the
>  interrupt handler run, and the current time in the BIOS data area gets
>  incremented.
>
> I have a feeling that Outlook is going to mangle this patch, so I'm attaching it as well.
>
> VMware, Inc. is providing this patch to you under the terms of the GPL version 2 and any later version. This patch is provided as is, with no warranties or support. VMware disclaims all liability in connection with the use/inability to use this patch. Any use of the attached is considered acceptance of the above.
>
> Signed-off-by: Geoff Lywood <glywood at vmware.com>
> ---
>  src/arch/i386/image/com32.c                      |   73 +++++++++++++++++++++-
>  src/arch/i386/include/comboot.h                  |   45 +++++++++++++
>  src/arch/i386/interface/syslinux/com32_call.c    |   17 +++++
>  src/arch/i386/interface/syslinux/com32_wrapper.S |   28 ++++++++
>  4 files changed, 162 insertions(+), 1 deletions(-)
>
> diff --git a/src/arch/i386/image/com32.c b/src/arch/i386/image/com32.c
> index 6ab347c..0f01027 100644
> --- a/src/arch/i386/image/com32.c
> +++ b/src/arch/i386/image/com32.c
> @@ -42,6 +42,13 @@ FILE_LICENCE ( GPL2_OR_LATER );
>
>  struct image_type com32_image_type __image_type ( PROBE_NORMAL );
>
> +struct idt_register com32_external_idtr = {
> +       .limit = COM32_NUM_IDT_ENTRIES * sizeof ( struct idt_descriptor ) - 1,
> +       .base = COM32_IDT
> +};
> +
> +struct idt_register com32_internal_idtr;
> +
>  /**
>  * Execute COMBOOT image
>  *
> @@ -89,9 +96,12 @@ static int com32_exec ( struct image *image ) {
>                unregister_image ( image );
>
>                __asm__ __volatile__ (
> +                       "sidt com32_internal_idtr\n\t"
> +                       "lidt com32_external_idtr\n\t"         /* Set up IDT */
>                        "movl %%esp, (com32_internal_esp)\n\t" /* Save internal virtual address space ESP */
>                        "movl (com32_external_esp), %%esp\n\t" /* Switch to COM32 ESP (top of available memory) */
>                        "call _virt_to_phys\n\t"               /* Switch to flat physical address space */
> +                       "sti\n\t"                              /* Enable interrupts */
>                        "pushl %0\n\t"                         /* Pointer to CDECL helper function */
>                        "pushl %1\n\t"                         /* Pointer to FAR call helper function */
>                        "pushl %2\n\t"                         /* Size of low memory bounce buffer */
> @@ -100,7 +110,9 @@ static int com32_exec ( struct image *image ) {
>                        "pushl %5\n\t"                         /* Pointer to the command line arguments */
>                        "pushl $6\n\t"                         /* Number of additional arguments */
>                        "call *%6\n\t"                         /* Execute image */
> -                       "call _phys_to_virt\n\t"               /* Switch back to internal virtual address space */
> +                       "cli\n\t"                              /* Disable interrupts */
> +                       "call _phys_to_virt\n\t"               /* Switch back to internal virtual address space */
> +                       "lidt com32_internal_idtr\n\t"         /* Switch back to internal IDT (for debugging) */
>                        "movl (com32_internal_esp), %%esp\n\t" /* Switch back to internal stack */
>                :
>                :
> @@ -216,6 +228,60 @@ static int comboot_load_image ( struct image *image ) {
>  }
>
>  /**
> + * Prepare COM32 Interrupt Descriptor Table and Interrupt Jump Buffer
> + * @v image            COM32 image
> + * @ret rc             Return status code
> + */
> +static int comboot_prepare_idt ( struct image * image ) {
> +       struct idt_descriptor *idt;
> +       struct ijb_entry *ijb;
> +       physaddr_t com32_irq_wrapper_phys;
> +       userptr_t buffer;
> +       size_t memsz;
> +       int i, rc;
> +
> +       buffer = phys_to_user ( COM32_IDT );
> +       memsz = COM32_NUM_IDT_ENTRIES * sizeof ( struct idt_descriptor );
> +       rc = prep_segment ( buffer, memsz, memsz );
> +       if ( rc != 0 ) {
> +               DBGC ( image, "COM32 %p: could not prepare IDT segment: %s\n",
> +                      image, strerror ( rc ) );
> +               return rc;
> +       }
> +
> +       buffer = phys_to_user ( COM32_IJB );
> +       memsz = COM32_NUM_IDT_ENTRIES * sizeof ( struct ijb_entry );
> +       rc = prep_segment ( buffer, memsz, memsz );
> +       if ( rc != 0 ) {
> +               DBGC ( image, "COM32 %p: could not prepare IJB segment: %s\n",
> +                      image, strerror ( rc ) );
> +               return rc;
> +       }
> +
> +       idt = phys_to_virt ( COM32_IDT );
> +       ijb = phys_to_virt ( COM32_IJB );
> +       com32_irq_wrapper_phys = virt_to_phys ( com32_irq_wrapper );
> +
> +       for ( i = 0; i < COM32_NUM_IDT_ENTRIES; i++ ) {
> +               uint32_t ijb_address = virt_to_phys ( &ijb[i] );
> +
> +               idt[i].offset_low = ijb_address & 0xFFFF;
> +               idt[i].selector = PHYSICAL_CS;
> +               idt[i].flags = IDT_INTERRUPT_GATE_FLAGS;
> +               idt[i].offset_high = ijb_address >> 16;
> +
> +               ijb[i].pusha_instruction = IJB_PUSHA;
> +               ijb[i].mov_instruction = IJB_MOV_AL_IMM8;
> +               ijb[i].mov_value = i;
> +               ijb[i].jump_instruction = IJB_JMP_REL32;
> +               ijb[i].jump_destination = com32_irq_wrapper_phys -
> +                       virt_to_phys ( &ijb[i + 1] );
> +       }
> +
> +       return 0;
> +}
> +
> +/**
>  * Prepare COM32 low memory bounce buffer
>  * @v image            COM32 image
>  * @ret rc             Return status code
> @@ -269,6 +335,11 @@ static int com32_load ( struct image *image ) {
>                return rc;
>        }
>
> +       /* Set up IDT */
> +       if ( ( rc = comboot_prepare_idt ( image ) ) != 0 ) {
> +               return rc;
> +       }
> +
>        /* Prepare bounce buffer segment */
>        if ( ( rc = comboot_prepare_bounce_buffer ( image ) ) != 0 ) {
>                return rc;
> diff --git a/src/arch/i386/include/comboot.h b/src/arch/i386/include/comboot.h
> index 1232f0a..ce6990d 100644
> --- a/src/arch/i386/include/comboot.h
> +++ b/src/arch/i386/include/comboot.h
> @@ -13,6 +13,50 @@ FILE_LICENCE ( GPL2_OR_LATER );
>  #include <setjmp.h>
>  #include <gpxe/in.h>
>
> +/** Descriptor in a 32-bit IDT */
> +struct idt_descriptor {
> +       uint16_t offset_low;
> +       uint16_t selector;
> +       uint16_t flags;
> +       uint16_t offset_high;
> +} PACKED;
> +
> +/** Operand for the LIDT instruction */
> +struct idt_register {
> +       uint16_t limit;
> +       uint32_t base;
> +} PACKED;
> +
> +/** Entry in the interrupt jump buffer */
> +struct ijb_entry {
> +       uint8_t pusha_instruction;
> +       uint8_t mov_instruction;
> +       uint8_t mov_value;
> +       uint8_t jump_instruction;
> +       uint32_t jump_destination;
> +} PACKED;
> +
> +/** The x86 opcode for "pushal" */
> +#define IJB_PUSHA 0x60
> +
> +/** The x86 opcode for "movb $imm8,%al" */
> +#define IJB_MOV_AL_IMM8 0xB0
> +
> +/** The x86 opcode for "jmp rel32" */
> +#define IJB_JMP_REL32 0xE9
> +
> +/** Flags that specify a 32-bit interrupt gate with DPL=0 */
> +#define IDT_INTERRUPT_GATE_FLAGS 0x8E00
> +
> +/** Address of COM32 interrupt descriptor table */
> +#define COM32_IDT 0x100000
> +
> +/** Number of entries in a fully populated IDT */
> +#define COM32_NUM_IDT_ENTRIES 256
> +
> +/** Address of COM32 interrupt jump buffer */
> +#define COM32_IJB 0x100800
> +
>  /** Segment used for COMBOOT PSP and image */
>  #define COMBOOT_PSP_SEG 0x07C0
>
> @@ -109,6 +153,7 @@ extern void unhook_comboot_interrupts ( );
>  extern void com32_intcall_wrapper ( );
>  extern void com32_farcall_wrapper ( );
>  extern void com32_cfarcall_wrapper ( );
> +extern void com32_irq_wrapper ( );
>
>  /* Resolve a hostname to an (IPv4) address */
>  extern int comboot_resolv ( const char *name, struct in_addr *address );
> diff --git a/src/arch/i386/interface/syslinux/com32_call.c b/src/arch/i386/interface/syslinux/com32_call.c
> index d2c3f91..d271cc7 100644
> --- a/src/arch/i386/interface/syslinux/com32_call.c
> +++ b/src/arch/i386/interface/syslinux/com32_call.c
> @@ -188,3 +188,20 @@ int __asmcall com32_cfarcall ( uint32_t proc, physaddr_t stack, size_t stacksz )
>
>        return eax;
>  }
> +
> +/**
> + * IRQ handler
> + */
> +void __asmcall com32_irq ( uint32_t vector ) {
> +       uint32_t *ivt_entry = phys_to_virt( vector * 4 );
> +
> +       __asm__ __volatile__ (
> +               REAL_CODE ( "pushfw\n\t"
> +                           "pushw %%cs\n\t"
> +                           "pushw $com32_irq_return\n\t"
> +                           "pushl %0\n\t"
> +                           "retf\n"
> +                           "com32_irq_return:\n\t" )
> +               : /* no outputs */
> +               : "r" ( *ivt_entry ) );
> +}
> diff --git a/src/arch/i386/interface/syslinux/com32_wrapper.S b/src/arch/i386/interface/syslinux/com32_wrapper.S
> index 5c5bd13..84d1571 100644
> --- a/src/arch/i386/interface/syslinux/com32_wrapper.S
> +++ b/src/arch/i386/interface/syslinux/com32_wrapper.S
> @@ -22,6 +22,26 @@ FILE_LICENCE ( GPL2_OR_LATER )
>        .arch i386
>        .code32
>
> +       /*
> +        * This code is entered after running the following sequence out of
> +        * the interrupt jump buffer:
> +        *
> +        * pushal
> +        * movb $vector, %al
> +        * jmp com32_irq_wrapper
> +        */
> +
> +       .globl com32_irq_wrapper
> +com32_irq_wrapper:
> +
> +       movzxb %al,%eax
> +       pushl %eax
> +       movl $com32_irq, %eax
> +       call com32_wrapper
> +       popl %eax
> +       popal
> +       iret
> +
>        .globl com32_farcall_wrapper
>  com32_farcall_wrapper:
>
> @@ -43,10 +63,14 @@ com32_intcall_wrapper:
>        /*jmp com32_wrapper*/ /* fall through */
>
>  com32_wrapper:
> +       cli
>
>        /* Switch to internal virtual address space */
>        call _phys_to_virt
>
> +       /* Switch to internal IDT (if we have one for debugging) */
> +       lidt com32_internal_idtr
> +
>        mov %eax, (com32_helper_function)
>
>        /* Save external COM32 stack pointer */
> @@ -74,9 +98,13 @@ com32_wrapper:
>        movl %esp, (com32_internal_esp)
>        movl (com32_external_esp), %esp
>
> +       /* Switch to com32 IDT */
> +       lidt com32_external_idtr
> +
>        /* Switch to external flat physical address space */
>        call _virt_to_phys
>
> +       sti
>        ret
>
>
> _______________________________________________
> gPXE-devel mailing list
> gPXE-devel at etherboot.org
> http://etherboot.org/mailman/listinfo/gpxe-devel
>
>


More information about the gPXE-devel mailing list