Browse Source

[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>
tags/v1.20.1
Geoff Lywood 14 years ago
parent
commit
a4023e02e8

+ 48
- 6
src/arch/i386/image/com32.c View File

@@ -42,6 +42,13 @@ FILE_LICENCE ( GPL2_OR_LATER );
42 42
 
43 43
 struct image_type com32_image_type __image_type ( PROBE_NORMAL );
44 44
 
45
+struct idt_register com32_external_idtr = {
46
+	.limit = COM32_NUM_IDT_ENTRIES * sizeof ( struct idt_descriptor ) - 1,
47
+	.base = COM32_IDT
48
+};
49
+
50
+struct idt_register com32_internal_idtr;
51
+
45 52
 /**
46 53
  * Execute COMBOOT image
47 54
  *
@@ -89,9 +96,12 @@ static int com32_exec ( struct image *image ) {
89 96
 		unregister_image ( image );
90 97
 
91 98
 		__asm__ __volatile__ (
99
+			"sidt com32_internal_idtr\n\t"
100
+			"lidt com32_external_idtr\n\t"	       /* Set up IDT */
92 101
 			"movl %%esp, (com32_internal_esp)\n\t" /* Save internal virtual address space ESP */
93 102
 			"movl (com32_external_esp), %%esp\n\t" /* Switch to COM32 ESP (top of available memory) */
94 103
 			"call _virt_to_phys\n\t"               /* Switch to flat physical address space */
104
+			"sti\n\t"			       /* Enable interrupts */
95 105
 			"pushl %0\n\t"                         /* Pointer to CDECL helper function */
96 106
 			"pushl %1\n\t"                         /* Pointer to FAR call helper function */
97 107
 			"pushl %2\n\t"                         /* Size of low memory bounce buffer */
@@ -100,7 +110,9 @@ static int com32_exec ( struct image *image ) {
100 110
 			"pushl %5\n\t"                         /* Pointer to the command line arguments */
101 111
 			"pushl $6\n\t"                         /* Number of additional arguments */
102 112
 			"call *%6\n\t"                         /* Execute image */
103
-			"call _phys_to_virt\n\t"               /* Switch back to internal virtual address space */
113
+			"cli\n\t"			       /* Disable interrupts */
114
+			"call _phys_to_virt\n\t"	       /* Switch back to internal virtual address space */
115
+			"lidt com32_internal_idtr\n\t"	       /* Switch back to internal IDT (for debugging) */
104 116
 			"movl (com32_internal_esp), %%esp\n\t" /* Switch back to internal stack */
105 117
 		:
106 118
 		:
@@ -191,25 +203,55 @@ static int com32_identify ( struct image *image ) {
191 203
 
192 204
 
193 205
 /**
194
- * Load COM32 image into memory
206
+ * Load COM32 image into memory and set up the IDT
195 207
  * @v image		COM32 image
196 208
  * @ret rc		Return status code
197 209
  */
198 210
 static int comboot_load_image ( struct image *image ) {
211
+	physaddr_t com32_irq_wrapper_phys;
212
+	struct idt_descriptor *idt;
213
+	struct ijb_entry *ijb;
199 214
 	size_t filesz, memsz;
200 215
 	userptr_t buffer;
201
-	int rc;
202
-
203
-	filesz = image->len;
216
+	int rc, i;
217
+
218
+	/* The interrupt descriptor table, interrupt jump buffer, and
219
+	 * image data are all contiguous in memory. Prepare them all at once.
220
+	 */
221
+	filesz = image->len +
222
+		COM32_NUM_IDT_ENTRIES * sizeof ( struct idt_descriptor ) +
223
+		COM32_NUM_IDT_ENTRIES * sizeof ( struct ijb_entry );
204 224
 	memsz = filesz;
205
-	buffer = phys_to_user ( COM32_START_PHYS );
225
+	buffer = phys_to_user ( COM32_IDT );
206 226
 	if ( ( rc = prep_segment ( buffer, filesz, memsz ) ) != 0 ) {
207 227
 		DBGC ( image, "COM32 %p: could not prepare segment: %s\n",
208 228
 		       image, strerror ( rc ) );
209 229
 		return rc;
210 230
 	}
211 231
 
232
+	/* Write the IDT and IJB */
233
+	idt = phys_to_virt ( COM32_IDT );
234
+	ijb = phys_to_virt ( COM32_IJB );
235
+	com32_irq_wrapper_phys = virt_to_phys ( com32_irq_wrapper );
236
+
237
+	for ( i = 0; i < COM32_NUM_IDT_ENTRIES; i++ ) {
238
+		uint32_t ijb_address = virt_to_phys ( &ijb[i] );
239
+
240
+		idt[i].offset_low = ijb_address & 0xFFFF;
241
+		idt[i].selector = PHYSICAL_CS;
242
+		idt[i].flags = IDT_INTERRUPT_GATE_FLAGS;
243
+		idt[i].offset_high = ijb_address >> 16;
244
+
245
+		ijb[i].pusha_instruction = IJB_PUSHA;
246
+		ijb[i].mov_instruction = IJB_MOV_AL_IMM8;
247
+		ijb[i].mov_value = i;
248
+		ijb[i].jump_instruction = IJB_JMP_REL32;
249
+		ijb[i].jump_destination = com32_irq_wrapper_phys -
250
+			virt_to_phys ( &ijb[i + 1] );
251
+	}
252
+
212 253
 	/* Copy image to segment */
254
+	buffer = phys_to_user ( COM32_START_PHYS );
213 255
 	memcpy_user ( buffer, 0, image->data, 0, filesz );
214 256
 
215 257
 	return 0;

+ 45
- 0
src/arch/i386/include/comboot.h View File

@@ -13,6 +13,50 @@ FILE_LICENCE ( GPL2_OR_LATER );
13 13
 #include <setjmp.h>
14 14
 #include <ipxe/in.h>
15 15
 
16
+/** Descriptor in a 32-bit IDT */
17
+struct idt_descriptor {
18
+	uint16_t offset_low;
19
+	uint16_t selector;
20
+	uint16_t flags;
21
+	uint16_t offset_high;
22
+} __attribute__ (( packed ));
23
+
24
+/** Operand for the LIDT instruction */
25
+struct idt_register {
26
+	uint16_t limit;
27
+	uint32_t base;
28
+} __attribute__ (( packed ));
29
+
30
+/** Entry in the interrupt jump buffer */
31
+struct ijb_entry {
32
+	uint8_t pusha_instruction;
33
+	uint8_t mov_instruction;
34
+	uint8_t mov_value;
35
+	uint8_t jump_instruction;
36
+	uint32_t jump_destination;
37
+} __attribute__ (( packed ));
38
+
39
+/** The x86 opcode for "pushal" */
40
+#define IJB_PUSHA 0x60
41
+
42
+/** The x86 opcode for "movb $imm8,%al" */
43
+#define IJB_MOV_AL_IMM8 0xB0
44
+
45
+/** The x86 opcode for "jmp rel32" */
46
+#define IJB_JMP_REL32 0xE9
47
+
48
+/** Flags that specify a 32-bit interrupt gate with DPL=0 */
49
+#define IDT_INTERRUPT_GATE_FLAGS 0x8E00
50
+
51
+/** Address of COM32 interrupt descriptor table */
52
+#define COM32_IDT 0x100000
53
+
54
+/** Number of entries in a fully populated IDT */
55
+#define COM32_NUM_IDT_ENTRIES 256
56
+
57
+/** Address of COM32 interrupt jump buffer */
58
+#define COM32_IJB 0x100800
59
+
16 60
 /** Segment used for COMBOOT PSP and image */
17 61
 #define COMBOOT_PSP_SEG 0x07C0
18 62
 
@@ -109,6 +153,7 @@ extern void unhook_comboot_interrupts ( );
109 153
 extern void com32_intcall_wrapper ( );
110 154
 extern void com32_farcall_wrapper ( );
111 155
 extern void com32_cfarcall_wrapper ( );
156
+extern void com32_irq_wrapper ( );
112 157
 
113 158
 /* Resolve a hostname to an (IPv4) address */
114 159
 extern int comboot_resolv ( const char *name, struct in_addr *address );

+ 17
- 0
src/arch/i386/interface/syslinux/com32_call.c View File

@@ -188,3 +188,20 @@ int __asmcall com32_cfarcall ( uint32_t proc, physaddr_t stack, size_t stacksz )
188 188
 
189 189
 	return eax;
190 190
 }
191
+
192
+/**
193
+ * IRQ handler
194
+ */
195
+void __asmcall com32_irq ( uint32_t vector ) {
196
+	uint32_t *ivt_entry = phys_to_virt( vector * 4 );
197
+
198
+	__asm__ __volatile__ (
199
+		REAL_CODE ( "pushfw\n\t"
200
+			    "pushw %%cs\n\t"
201
+			    "pushw $com32_irq_return\n\t"
202
+			    "pushl %0\n\t"
203
+			    "lret\n"
204
+			    "com32_irq_return:\n\t" )
205
+		: /* no outputs */
206
+		: "r" ( *ivt_entry ) );
207
+}

+ 28
- 0
src/arch/i386/interface/syslinux/com32_wrapper.S View File

@@ -22,6 +22,26 @@ FILE_LICENCE ( GPL2_OR_LATER )
22 22
 	.arch i386
23 23
 	.code32
24 24
 
25
+	/*
26
+	 * This code is entered after running the following sequence out of
27
+	 * the interrupt jump buffer:
28
+	 *
29
+	 * pushal
30
+	 * movb $vector, %al
31
+	 * jmp com32_irq_wrapper
32
+	 */
33
+
34
+	.globl com32_irq_wrapper
35
+com32_irq_wrapper:
36
+
37
+	movzbl %al,%eax
38
+	pushl %eax
39
+	movl $com32_irq, %eax
40
+	call com32_wrapper
41
+	popl %eax
42
+	popal
43
+	iret
44
+
25 45
 	.globl com32_farcall_wrapper
26 46
 com32_farcall_wrapper:
27 47
 
@@ -43,10 +63,14 @@ com32_intcall_wrapper:
43 63
 	/*jmp com32_wrapper*/ /* fall through */
44 64
 
45 65
 com32_wrapper:
66
+	cli
46 67
 
47 68
 	/* Switch to internal virtual address space */
48 69
 	call _phys_to_virt
49 70
 
71
+	/* Switch to internal IDT (if we have one for debugging) */
72
+	lidt com32_internal_idtr
73
+
50 74
 	mov %eax, (com32_helper_function)
51 75
 
52 76
 	/* Save external COM32 stack pointer */
@@ -74,9 +98,13 @@ com32_wrapper:
74 98
 	movl %esp, (com32_internal_esp)
75 99
 	movl (com32_external_esp), %esp
76 100
 
101
+	/* Switch to com32 IDT */
102
+	lidt com32_external_idtr
103
+
77 104
 	/* Switch to external flat physical address space */
78 105
 	call _virt_to_phys
79 106
 
107
+	sti
80 108
 	ret
81 109
 
82 110
 

Loading…
Cancel
Save