Explorar el Código

[comboot] Allow for tail recursion of COMBOOT images

Multi-level menus via COMBOOT rely on the COMBOOT program being able
to exit and invoke a new COMBOOT program (the next menu).  This works,
but rapidly (within about five iterations) runs out of space in gPXE's
internal stack, since each new image is executed in a new function
context.

Fix by allowing tail recursion between images; an image can now
specify a replacement image for itself, and image_exec() will perform
the necessary tail recursion.
tags/v0.9.7
Michael Brown hace 15 años
padre
commit
8904cd55f1

+ 23
- 17
src/arch/i386/image/com32.c Ver fichero

@@ -70,20 +70,20 @@ static int com32_exec ( struct image *image ) {
70 70
 		}
71 71
 
72 72
 		DBGC ( image, "COM32 %p: available memory top = 0x%x\n",
73
-			 image, (int)avail_mem_top );
73
+		       image, avail_mem_top );
74 74
 
75 75
 		assert ( avail_mem_top != 0 );
76 76
 
77 77
 		com32_external_esp = phys_to_virt ( avail_mem_top );
78 78
 
79 79
 		/* Hook COMBOOT API interrupts */
80
-		hook_comboot_interrupts( );
80
+		hook_comboot_interrupts();
81 81
 
82
-		/* Temporarily de-register image, so that a "boot" command
83
-		 * doesn't throw us into an execution loop.  Hold a reference
84
-		 * to avoid the image's being freed.
82
+		/* Unregister image, so that a "boot" command doesn't
83
+		 * throw us into an execution loop.  We never
84
+		 * reregister ourselves; COMBOOT images expect to be
85
+		 * removed on exit.
85 86
 		 */
86
-		image_get ( image );
87 87
 		unregister_image ( image );
88 88
 
89 89
 		__asm__ __volatile__ (
@@ -111,25 +111,31 @@ static int com32_exec ( struct image *image ) {
111 111
 			/* %6 */ "r" ( COM32_START_PHYS )
112 112
 		:
113 113
 			"memory" );
114
+		DBGC ( image, "COM32 %p: returned\n", image );
114 115
 		break;
115 116
 
116
-	case COMBOOT_RETURN_RUN_KERNEL:
117
-		DBGC ( image, "COM32 %p: returned to run kernel...\n", image );
118
-		comboot_run_kernel ( );
117
+	case COMBOOT_EXIT:
118
+		DBGC ( image, "COM32 %p: exited\n", image );
119 119
 		break;
120 120
 
121
-	case COMBOOT_RETURN_EXIT:
121
+	case COMBOOT_EXIT_RUN_KERNEL:
122
+		DBGC ( image, "COM32 %p: exited to run kernel %p\n",
123
+		       image, comboot_replacement_image );
124
+		image->replacement = comboot_replacement_image;
125
+		image_autoload ( image->replacement );
122 126
 		break;
123 127
 
124
-	}
125
-
126
-	comboot_force_text_mode ( );
128
+	case COMBOOT_EXIT_COMMAND:
129
+		DBGC ( image, "COM32 %p: exited after executing command\n",
130
+		       image );
131
+		break;
127 132
 
128
-	DBGC ( image, "COM32 %p returned\n", image );
133
+	default:
134
+		assert ( 0 );
135
+		break;
136
+	}
129 137
 
130
-	/* Re-register image and return */
131
-	register_image ( image );
132
-	image_put ( image );
138
+	comboot_force_text_mode();
133 139
 
134 140
 	return 0;
135 141
 }

+ 23
- 17
src/arch/i386/image/comboot.c Ver fichero

@@ -142,16 +142,16 @@ static int comboot_exec ( struct image *image ) {
142 142
 		comboot_init_psp ( image, seg_userptr );
143 143
 
144 144
 		/* Hook COMBOOT API interrupts */
145
-		hook_comboot_interrupts ( );
145
+		hook_comboot_interrupts();
146 146
 
147 147
 		DBGC ( image, "executing 16-bit COMBOOT image at %4x:0100\n",
148
-			 COMBOOT_PSP_SEG );
148
+		       COMBOOT_PSP_SEG );
149 149
 
150
-		/* Temporarily de-register image, so that a "boot" command
151
-		 * doesn't throw us into an execution loop.  Hold a reference
152
-		 * to avoid the image's being freed.
150
+		/* Unregister image, so that a "boot" command doesn't
151
+		 * throw us into an execution loop.  We never
152
+		 * reregister ourselves; COMBOOT images expect to be
153
+		 * removed on exit.
153 154
 		 */
154
-		image_get ( image );
155 155
 		unregister_image ( image );
156 156
 
157 157
 		/* Store stack segment at 0x38 and stack pointer at 0x3A
@@ -180,26 +180,32 @@ static int comboot_exec ( struct image *image ) {
180 180
 				    "xorw %%bp, %%bp\n\t"
181 181
 				    "lret\n\t" )
182 182
 					 : : "r" ( COMBOOT_PSP_SEG ) : "eax" );
183
+		DBGC ( image, "COMBOOT %p: returned\n", image );
183 184
 		break;
184 185
 
185
-	case COMBOOT_RETURN_RUN_KERNEL:
186
-		DBGC ( image, "COMBOOT %p: returned to run kernel...\n", image );
187
-		comboot_run_kernel ( );
186
+	case COMBOOT_EXIT:
187
+		DBGC ( image, "COMBOOT %p: exited\n", image );
188 188
 		break;
189 189
 
190
-	case COMBOOT_RETURN_EXIT:
190
+	case COMBOOT_EXIT_RUN_KERNEL:
191
+		DBGC ( image, "COMBOOT %p: exited to run kernel %p\n",
192
+		       image, comboot_replacement_image );
193
+		image->replacement = comboot_replacement_image;
194
+		image_autoload ( image->replacement );
191 195
 		break;
192 196
 
193
-	}
197
+	case COMBOOT_EXIT_COMMAND:
198
+		DBGC ( image, "COMBOOT %p: exited after executing command\n",
199
+		       image );
200
+		break;
194 201
 
195
-	comboot_force_text_mode ( );
202
+	default:
203
+		assert ( 0 );
204
+		break;
205
+	}
196 206
 
197
-	DBGC ( image, "COMBOOT %p returned\n", image );
207
+	comboot_force_text_mode();
198 208
 
199
-	/* Re-register image and return */
200
-	register_image ( image );
201
-	image_put ( image );
202
-	
203 209
 	return 0;
204 210
 }
205 211
 

+ 1
- 0
src/arch/i386/include/bits/errfile.h Ver fichero

@@ -23,6 +23,7 @@
23 23
 #define ERRFILE_comboot        ( ERRFILE_ARCH | ERRFILE_IMAGE | 0x00070000 )
24 24
 #define ERRFILE_com32          ( ERRFILE_ARCH | ERRFILE_IMAGE | 0x00080000 )
25 25
 #define ERRFILE_comboot_resolv ( ERRFILE_ARCH | ERRFILE_IMAGE | 0x00090000 )
26
+#define ERRFILE_comboot_call   ( ERRFILE_ARCH | ERRFILE_IMAGE | 0x000a0000 )
26 27
 
27 28
 #define ERRFILE_undi		 ( ERRFILE_ARCH | ERRFILE_NET | 0x00000000 )
28 29
 #define ERRFILE_undiload	 ( ERRFILE_ARCH | ERRFILE_NET | 0x00010000 )

+ 5
- 9
src/arch/i386/include/comboot.h Ver fichero

@@ -79,18 +79,14 @@ extern int comboot_resolv ( const char *name, struct in_addr *address );
79 79
 /* setjmp/longjmp context buffer used to return after loading an image */
80 80
 extern jmp_buf comboot_return;
81 81
 
82
-/* Command line to execute when returning via comboot_return 
83
- * with COMBOOT_RETURN_RUN_KERNEL
84
- */
85
-extern char *comboot_kernel_cmdline;
86
-
87
-/* Execute comboot_image_cmdline */
88
-extern void comboot_run_kernel ( );
82
+/* Replacement image when exiting with COMBOOT_EXIT_RUN_KERNEL */
83
+extern struct image *comboot_replacement_image;
89 84
 
90 85
 extern void *com32_external_esp;
91 86
 
92
-#define COMBOOT_RETURN_EXIT 1
93
-#define COMBOOT_RETURN_RUN_KERNEL 2
87
+#define COMBOOT_EXIT 1
88
+#define COMBOOT_EXIT_RUN_KERNEL 2
89
+#define COMBOOT_EXIT_COMMAND 3
94 90
 
95 91
 extern void comboot_force_text_mode ( void );
96 92
 

+ 82
- 61
src/arch/i386/interface/syslinux/comboot_call.c Ver fichero

@@ -35,6 +35,8 @@
35 35
 #include <gpxe/process.h>
36 36
 #include <gpxe/serial.h>
37 37
 #include <gpxe/init.h>
38
+#include <gpxe/image.h>
39
+#include <usr/imgmgmt.h>
38 40
 
39 41
 /** The "SYSLINUX" version string */
40 42
 static char __data16_array ( syslinux_version, [] ) = "gPXE " VERSION;
@@ -67,10 +69,8 @@ extern void int22_wrapper ( void );
67 69
 /* setjmp/longjmp context buffer used to return after loading an image */
68 70
 jmp_buf comboot_return;
69 71
 
70
-/* Command line to execute when returning via comboot_return 
71
- * with COMBOOT_RETURN_RUN_KERNEL
72
- */
73
-char *comboot_kernel_cmdline;
72
+/* Replacement image when exiting with COMBOOT_EXIT_RUN_KERNEL */
73
+struct image *comboot_replacement_image;
74 74
 
75 75
 /* Mode flags set by INT 22h AX=0017h */
76 76
 static uint16_t comboot_graphics_mode = 0;
@@ -154,58 +154,81 @@ void comboot_force_text_mode ( void ) {
154 154
 
155 155
 
156 156
 /**
157
- * Run the kernel specified in comboot_kernel_cmdline
157
+ * Fetch kernel and optional initrd
158 158
  */
159
-void comboot_run_kernel ( )
160
-{
161
-	char *initrd;
162
-
163
-	comboot_force_text_mode ( );
164
-
165
-	DBG ( "COMBOOT: executing image '%s'\n", comboot_kernel_cmdline );
159
+static int comboot_fetch_kernel ( char *kernel_file, char *cmdline ) {
160
+	struct image *kernel;
161
+	struct image *initrd = NULL;
162
+	char *initrd_file;
163
+	int rc;
166 164
 
167 165
 	/* Find initrd= parameter, if any */
168
-	if ( ( initrd = strstr ( comboot_kernel_cmdline, "initrd=" ) ) ) {
169
-		char old_char = '\0';
170
-		char *initrd_end = strchr( initrd, ' ' );
171
-
172
-		/* Replace space after end of parameter
173
-		 * with a nul terminator if this is not
174
-		 * the last parameter
175
-		 */
176
-		if ( initrd_end ) {
177
-			old_char = *initrd_end;
178
-			*initrd_end = '\0';
179
-		}
166
+	if ( ( initrd_file = strstr ( cmdline, "initrd=" ) ) != NULL ) {
167
+		char *initrd_end;
180 168
 
181
-		/* Replace = with space to get 'initrd filename'
182
-		 * command suitable for system()
183
-		 */
184
-		initrd[6] = ' ';
169
+		/* skip "initrd=" */
170
+		initrd_file += 7;
185 171
 
186
-		DBG( "COMBOOT: loading initrd '%s'\n", initrd );
172
+		/* Find terminating space, if any, and replace with NUL */
173
+		initrd_end = strchr ( initrd_file, ' ' );
174
+		if ( initrd_end )
175
+			*initrd_end = '\0';
187 176
 
188
-		system ( initrd );
177
+		DBG ( "COMBOOT: fetching initrd '%s'\n", initrd_file );
189 178
 
190
-		/* Restore space after parameter */
191
-		if ( initrd_end ) {
192
-			*initrd_end = old_char;
179
+		/* Allocate and fetch initrd */
180
+		initrd = alloc_image();
181
+		if ( ! initrd ) {
182
+			DBG ( "COMBOOT: could not allocate initrd\n" );
183
+			rc = -ENOMEM;
184
+			goto err_alloc_initrd;
185
+		}
186
+		if ( ( rc = imgfetch ( initrd, initrd_file,
187
+				       register_image ) ) != 0 ) {
188
+			DBG ( "COMBOOT: could not fetch initrd: %s\n",
189
+			      strerror ( rc ) );
190
+			goto err_fetch_initrd;
193 191
 		}
194 192
 
195
-		/* Restore = */
196
-		initrd[6] = '=';
193
+		/* Restore space after initrd name, if applicable */
194
+		if ( initrd_end )
195
+			*initrd_end = ' ';
197 196
 	}
198 197
 
199
-	/* Load kernel */
200
-	DBG ( "COMBOOT: loading kernel '%s'\n", comboot_kernel_cmdline );
201
-	system ( comboot_kernel_cmdline );
198
+	DBG ( "COMBOOT: fetching kernel '%s'\n", kernel_file );
202 199
 
203
-	free ( comboot_kernel_cmdline );
200
+	/* Allocate and fetch kernel */
201
+	kernel = alloc_image();
202
+	if ( ! kernel ) {
203
+		DBG ( "COMBOOT: could not allocate kernel\n" );
204
+		rc = -ENOMEM;
205
+		goto err_alloc_kernel;
206
+	}
207
+	if ( ( rc = imgfetch ( kernel, kernel_file,
208
+			       register_image ) ) != 0 ) {
209
+		DBG ( "COMBOOT: could not fetch kernel: %s\n",
210
+		      strerror ( rc ) );
211
+		goto err_fetch_kernel;
212
+	}
213
+	if ( ( rc = image_set_cmdline ( kernel, cmdline ) ) != 0 ) {
214
+		DBG ( "COMBOOT: could not set kernel command line: %s\n",
215
+		      strerror ( rc ) );
216
+		goto err_set_cmdline;
217
+	}
204 218
 
205
-	/* Boot */
206
-	system ( "boot" );
219
+	/* Store kernel as replacement image */
220
+	comboot_replacement_image = kernel;
207 221
 
208
-	DBG ( "COMBOOT: back from executing command\n" );
222
+	return 0;
223
+
224
+ err_set_cmdline:
225
+ err_fetch_kernel:
226
+	image_put ( kernel );
227
+ err_alloc_kernel:
228
+ err_fetch_initrd:
229
+	image_put ( initrd );
230
+ err_alloc_initrd:
231
+	return rc;
209 232
 }
210 233
 
211 234
 
@@ -213,7 +236,7 @@ void comboot_run_kernel ( )
213 236
  * Terminate program interrupt handler
214 237
  */
215 238
 static __asmcall void int20 ( struct i386_all_regs *ix86 __unused ) {
216
-	longjmp ( comboot_return, COMBOOT_RETURN_EXIT );
239
+	longjmp ( comboot_return, COMBOOT_EXIT );
217 240
 }
218 241
 
219 242
 
@@ -226,7 +249,7 @@ static __asmcall void int21 ( struct i386_all_regs *ix86 ) {
226 249
 	switch ( ix86->regs.ah ) {
227 250
 	case 0x00:
228 251
 	case 0x4C: /* Terminate program */
229
-		longjmp ( comboot_return, COMBOOT_RETURN_EXIT );
252
+		longjmp ( comboot_return, COMBOOT_EXIT );
230 253
 		break;
231 254
 
232 255
 	case 0x01: /* Get Key with Echo */
@@ -323,17 +346,15 @@ static __asmcall void int22 ( struct i386_all_regs *ix86 ) {
323 346
 			char cmd[len + 1];
324 347
 			copy_from_user ( cmd, cmd_u, 0, len + 1 );
325 348
 			DBG ( "COMBOOT: executing command '%s'\n", cmd );
326
-
327
-			comboot_kernel_cmdline = strdup ( cmd );
328
-
329
-			DBG ( "COMBOOT: returning to run image...\n" );
330
-			longjmp ( comboot_return, COMBOOT_RETURN_RUN_KERNEL );
349
+			system ( cmd );
350
+			DBG ( "COMBOOT: exiting after executing command...\n" );
351
+			longjmp ( comboot_return, COMBOOT_EXIT_COMMAND );
331 352
 		}
332 353
 		break;
333 354
 
334 355
 	case 0x0004: /* Run default command */
335 356
 		/* FIXME: just exit for now */
336
-		longjmp ( comboot_return, COMBOOT_RETURN_EXIT );
357
+		longjmp ( comboot_return, COMBOOT_EXIT_COMMAND );
337 358
 		break;
338 359
 
339 360
 	case 0x0005: /* Force text mode */
@@ -518,21 +539,21 @@ static __asmcall void int22 ( struct i386_all_regs *ix86 ) {
518 539
 			userptr_t cmd_u = real_to_user ( ix86->segs.es, ix86->regs.bx );
519 540
 			int file_len = strlen_user ( file_u, 0 );
520 541
 			int cmd_len = strlen_user ( cmd_u, 0 );
521
-			char file[file_len + 1 + cmd_len + 7 + 1];
542
+			char file[file_len + 1];
522 543
 			char cmd[cmd_len + 1];
523 544
 
524
-			memcpy( file, "kernel ", 7 );
525
-			copy_from_user ( file + 7, file_u, 0, file_len + 1 );
545
+			copy_from_user ( file, file_u, 0, file_len + 1 );
526 546
 			copy_from_user ( cmd, cmd_u, 0, cmd_len + 1 );
527
-			strcat ( file, " " );
528
-			strcat ( file, cmd );
529
-
530
-			DBG ( "COMBOOT: run kernel image '%s'\n", file );
531 547
 
532
-			comboot_kernel_cmdline = strdup ( file );			
533
-
534
-			DBG ( "COMBOOT: returning to run image...\n" );
535
-			longjmp ( comboot_return, COMBOOT_RETURN_RUN_KERNEL );
548
+			DBG ( "COMBOOT: run kernel %s %s\n", file, cmd );
549
+			comboot_fetch_kernel ( file, cmd );
550
+			/* Technically, we should return if we
551
+			 * couldn't load the kernel, but it's not safe
552
+			 * to do that since we have just overwritten
553
+			 * part of the COMBOOT program's memory space.
554
+			 */
555
+			DBG ( "COMBOOT: exiting to run kernel...\n" );
556
+			longjmp ( comboot_return, COMBOOT_EXIT_RUN_KERNEL );
536 557
 		}
537 558
 		break;
538 559
 

+ 27
- 3
src/core/image.c Ver fichero

@@ -53,6 +53,7 @@ static void free_image ( struct refcnt *refcnt ) {
53 53
 
54 54
 	uri_put ( image->uri );
55 55
 	ufree ( image->data );
56
+	image_put ( image->replacement );
56 57
 	free ( image );
57 58
 	DBGC ( image, "IMAGE %p freed\n", image );
58 59
 }
@@ -142,9 +143,9 @@ int register_image ( struct image *image ) {
142 143
  * @v image		Executable/loadable image
143 144
  */
144 145
 void unregister_image ( struct image *image ) {
146
+	DBGC ( image, "IMAGE %p unregistered\n", image );
145 147
 	list_del ( &image->list );
146 148
 	image_put ( image );
147
-	DBGC ( image, "IMAGE %p unregistered\n", image );
148 149
 }
149 150
 
150 151
 /**
@@ -237,6 +238,7 @@ int image_autoload ( struct image *image ) {
237 238
  * @ret rc		Return status code
238 239
  */
239 240
 int image_exec ( struct image *image ) {
241
+	struct image *replacement;
240 242
 	struct uri *old_cwuri;
241 243
 	int rc;
242 244
 
@@ -257,18 +259,40 @@ int image_exec ( struct image *image ) {
257 259
 	old_cwuri = uri_get ( cwuri );
258 260
 	churi ( image->uri );
259 261
 
262
+	/* Take out a temporary reference to the image.  This allows
263
+	 * the image to unregister itself if necessary, without
264
+	 * automatically freeing itself.
265
+	 */
266
+	image_get ( image );
267
+
260 268
 	/* Try executing the image */
261 269
 	if ( ( rc = image->type->exec ( image ) ) != 0 ) {
262 270
 		DBGC ( image, "IMAGE %p could not execute: %s\n",
263 271
 		       image, strerror ( rc ) );
264
-		goto done;
272
+		/* Do not return yet; we still have clean-up to do */
265 273
 	}
266 274
 
267
- done:
275
+	/* Pick up replacement image before we drop the original
276
+	 * image's temporary reference.
277
+	 */
278
+	if ( ( replacement = image->replacement ) != NULL )
279
+		image_get ( replacement );
280
+
281
+	/* Drop temporary reference to the original image */
282
+	image_put ( image );
283
+
268 284
 	/* Reset current working directory */
269 285
 	churi ( old_cwuri );
270 286
 	uri_put ( old_cwuri );
271 287
 
288
+	/* Tail-recurse into replacement image, if one exists */
289
+	if ( replacement ) {
290
+		DBGC ( image, "IMAGE %p replacing self with IMAGE %p\n",
291
+		       image, replacement );
292
+		if ( ( rc = image_exec ( replacement ) ) != 0 )
293
+			return rc;
294
+	}
295
+
272 296
 	return rc;
273 297
 }
274 298
 

+ 1
- 4
src/image/script.c Ver fichero

@@ -43,10 +43,8 @@ static int script_exec ( struct image *image ) {
43 43
 	int rc;
44 44
 
45 45
 	/* Temporarily de-register image, so that a "boot" command
46
-	 * doesn't throw us into an execution loop.  Hold a reference
47
-	 * to avoid the image's being freed.
46
+	 * doesn't throw us into an execution loop.
48 47
 	 */
49
-	image_get ( image );
50 48
 	unregister_image ( image );
51 49
 
52 50
 	while ( offset < image->len ) {
@@ -80,7 +78,6 @@ static int script_exec ( struct image *image ) {
80 78
  done:
81 79
 	/* Re-register image and return */
82 80
 	register_image ( image );
83
-	image_put ( image );
84 81
 	return rc;
85 82
 }
86 83
 

+ 14
- 0
src/include/gpxe/image.h Ver fichero

@@ -46,6 +46,16 @@ struct image {
46 46
 		userptr_t user;
47 47
 		unsigned long ul;
48 48
 	} priv;
49
+
50
+	/** Replacement image
51
+	 *
52
+	 * An image wishing to replace itself with another image (in a
53
+	 * style similar to a Unix exec() call) should return from its
54
+	 * exec() method with the replacement image set to point to
55
+	 * the new image.  The new image must already be in a suitable
56
+	 * state for execution.
57
+	 */
58
+	struct image *replacement;
49 59
 };
50 60
 
51 61
 /** Image is loaded */
@@ -79,6 +89,10 @@ struct image_type {
79 89
 	 *
80 90
 	 * @v image		Loaded image
81 91
 	 * @ret rc		Return status code
92
+	 *
93
+	 * Note that the image may be invalidated by the act of
94
+	 * execution, i.e. an image is allowed to choose to unregister
95
+	 * (and so potentially free) itself.
82 96
 	 */
83 97
 	int ( * exec ) ( struct image *image );
84 98
 };

Loading…
Cancelar
Guardar