[comboot] Run com32 programs with a valid IDT

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 iPXE, 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.

Signed-off-by: Stefan Hajnoczi <stefanha@gmail.com>
Signed-off-by: Michael Brown <mcb30@ipxe.org>
This commit is contained in:
Geoff Lywood
2010-07-07 15:35:01 -07:00
committed by Michael Brown
parent 315524e703
commit a4023e02e8
4 changed files with 137 additions and 5 deletions

View File

@@ -13,6 +13,50 @@ FILE_LICENCE ( GPL2_OR_LATER );
#include <setjmp.h>
#include <ipxe/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;
} __attribute__ (( packed ));
/** Operand for the LIDT instruction */
struct idt_register {
uint16_t limit;
uint32_t base;
} __attribute__ (( 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;
} __attribute__ (( 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 );