summaryrefslogtreecommitdiff
path: root/kernel/old/user.c
blob: c41867eeef2fb5f4a63cc87cbb4351e31ec14456 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
/**
** @file	user.c
**
** @author	CSCI-452 class of 20245
**
** @brief	User-level code manipulation routines
*/

#define	KERNEL_SRC

#include <common.h>

#include <bootstrap.h>
#include <elf.h>
#include <user.h>
#include <vm.h>

/*
** PRIVATE DEFINITIONS
*/

/*
** PRIVATE DATA TYPES
*/

/*
** PRIVATE GLOBAL VARIABLES
*/

/*
** PUBLIC GLOBAL VARIABLES
*/

/*
** Location of the "user blob" in memory.
**
** These variables are filled in by the code in startup.S using values
** passed to it from the bootstrap. 
**
** These are visible so that the startup code can find them.
*/
uint16_t user_offset;      // byte offset from the segment base
uint16_t user_segment;     // segment base address
uint16_t user_sectors;     // number of 512-byte sectors it occupies

header_t *user_header;    // filled in by the user_init routine
prog_t *prog_table;       // filled in by the user_init routine

/*
** PRIVATE FUNCTIONS
*/

#if TRACING_ELF

/*
** This is debugging support code; if not debugging the ELF
** handling code, it won't be compiled into the kernel.
*/

// buffer used by some of these functions
static char ebuf[16];

/*
** File header functions
*/

// interpret the file class
static const char *fh_eclass( e32_si class ) {
	switch( class ) {
	case ELF_CLASS_NONE:  return( "None" ); break;
	case ELF_CLASS_32:    return( "EC32" ); break;
	case ELF_CLASS_64:    return( "EC64" ); break;
	}
	return( "????" );
}

// interpret the data encoding
static const char *fh_edata( e32_si data ) {
	switch( data ) {
	case ELF_DATA_NONE:  return( "Invd" ); break;
	case ELF_DATA_2LSB:  return( "2CLE" ); break;
	case ELF_DATA_2MSB:  return( "2CBE" ); break;
	}
	return( "????" );
}

// interpret the file type
static const char *fh_htype( e32_h type ) {
	switch( type ) {
	case ET_NONE:	return( "none" ); break;
	case ET_REL:	return( "rel" ); break;
	case ET_EXEC:	return( "exec" ); break;
	case ET_DYN:	return( "dyn" ); break;
	case ET_CORE:	return( "core" ); break;
	default:
		if( type >= ET_LO_OS && type <= ET_HI_OS )
			return( "OSsp" );
		else if( type >= ET_LO_CP && type <= ET_HI_CP )
			return( "CPsp" );
	}
	sprint( ebuf, "0x%04x", type );
	return( (const char *) ebuf );
}

// interpret the machine type
static const char *fh_mtype( e32_h machine ) {
	switch( machine ) {
	case EM_NONE:     return( "None" ); break;
	case EM_386:      return( "386" ); break;
	case EM_ARM:      return( "ARM" ); break;
	case EM_X86_64:   return( "AMD64" ); break;
	case EM_AARCH64:  return( "AARCH64" ); break;
	case EM_RISCV:    return( "RISC-V" ); break;
	}
	return( "Other" );
}

// dump the program header
static void dump_fhdr( elfhdr_t *hdr ) {
	cio_puts( "File header: magic " );
	for( int i = EI_MAG0; i <= EI_MAG3; ++i )
		put_char_or_code( hdr->e_ident.bytes[i] );
	cio_printf( " class %s", fh_eclass(hdr->e_ident.f.class) );
	cio_printf( " enc %s", fh_edata(hdr->e_ident.f.data) );
	cio_printf( " ver %u\n", hdr->e_ident.f.version );
	cio_printf( " type %s", fh_htype(hdr->e_type) );
	cio_printf( " mach %s", fh_mtype(hdr->e_machine) );
	cio_printf( " vers %d", hdr->e_version );
	cio_printf( " entr %08x\n", hdr->e_entry );

	cio_printf( " phoff %08x", hdr->e_phoff );
	cio_printf( " shoff %08x", hdr->e_shoff );
	cio_printf( " flags %08x", (uint32_t) hdr->e_flags );
	cio_printf( " ehsize %u\n", hdr->e_ehsize );
	cio_printf( " phentsize %u", hdr->e_phentsize );
	cio_printf( " phnum %u", hdr->e_phnum );
	cio_printf( " shentsize %u", hdr->e_shentsize );
	cio_printf( " shnum %u", hdr->e_shnum );
	cio_printf( " shstrndx %u\n", hdr->e_shstrndx );
}

/*
** Program header functions
*/

// categorize the header type
static const char *ph_type( e32_w type ) {
	switch( type ) {
	case PT_NULL:     return( "Unused" ); break;
	case PT_LOAD:     return( "Load" ); break;
	case PT_DYNAMIC:  return( "DLI" ); break;
	case PT_INTERP:   return( "Interp" ); break;
	case PT_NOTE:     return( "Aux" ); break;
	case PT_SHLIB:    return( "RSVD" ); break;
	case PT_PHDR:     return( "PTentry" ); break;
	case PT_TLS:      return( "TLS" ); break;
	default:
		if( type >= PT_LO_OS && type <= PT_HI_OS )
			return( "OSsp" );
		else if( type >= PT_LO_CP && type <= PT_HI_CP )
			return( "CPsp" );
	}
	sprint( ebuf, "0x%08x", type );
	return( (const char *) ebuf );
}

// report the individual flags
static void ph_flags( e32_w flags ) {
	if( (flags & PF_R) != 0 ) cio_putchar( 'R' );
	if( (flags & PF_W) != 0 ) cio_putchar( 'W' );
	if( (flags & PF_E) != 0 ) cio_putchar( 'X' );
}

// dump a program header
static void dump_phdr( elfproghdr_t *hdr, int n ) {
	cio_printf( "Prog header %d, type %s\n", n, ph_type(hdr->p_type) );
	cio_printf( " offset %08x", hdr->p_offset );
	cio_printf( " va %08x", hdr->p_va );
	cio_printf( " pa %08x\n", hdr->p_pa );
	cio_printf( " filesz %08x", hdr->p_filesz );
	cio_printf( " memsz %08x", hdr->p_memsz );
	cio_puts( " flags " );
	ph_flags( hdr->p_flags );
	cio_printf( " align %08x", hdr->p_align );
	cio_putchar( '\n' );
}

/*
** Section header functions
*/

// interpret the header type
static const char *sh_type( e32_w type ) {
	switch( type ) {
	case SHT_NULL:          return( "Unused" ); break;
	case SHT_PROGBITS:      return( "Progbits" ); break;
	case SHT_SYMTAB:        return( "Symtab" ); break;
	case SHT_STRTAB:        return( "Strtab" ); break;
	case SHT_RELA:          return( "Rela" ); break;
	case SHT_HASH:          return( "Hash" ); break;
	case SHT_DYNAMIC:       return( "Dynamic" ); break;
	case SHT_NOTE:          return( "Note" ); break;
	case SHT_NOBITS:        return( "Nobits" ); break;
	case SHT_REL:           return( "Rel" ); break;
	case SHT_SHLIB:         return( "Shlib" ); break;
	case SHT_DYNSYM:        return( "Dynsym" ); break;
	default:
		if( type >= SHT_LO_CP && type <= SHT_HI_CP )
			return( "CCsp" );
		else if( type >= SHT_LO_US && type <= SHT_HI_US )
			return( "User" );
	}
	sprint( ebuf, "0x%08x", type );
	return( (const char *) ebuf );
}

// report the various flags
static void sh_flags( unsigned int flags ) {
	if( (flags & SHF_WRITE) != 0 )            cio_putchar( 'W' );
	if( (flags & SHF_ALLOC) != 0 )            cio_putchar( 'A' );
	if( (flags & SHF_EXECINSTR) != 0 )        cio_putchar( 'X' );
	if( (flags & SHF_MERGE) != 0 )            cio_putchar( 'M' );
	if( (flags & SHF_STRINGS) != 0 )          cio_putchar( 'S' );
	if( (flags & SHF_INFO_LINK) != 0 )        cio_putchar( 'L' );
	if( (flags & SHF_LINK_ORDER) != 0 )       cio_putchar( 'o' );
	if( (flags & SHF_OS_NONCON) != 0 )        cio_putchar( 'n' );
	if( (flags & SHF_GROUP) != 0 )            cio_putchar( 'g' );
	if( (flags & SHF_TLS) != 0 )              cio_putchar( 't' );
}

// dump a section header
ATTR_UNUSED
static void dump_shdr( elfsecthdr_t *hdr, int n ) {
	cio_printf( "Sect header %d, type %d (%s), name %s\n",
			n, hdr->sh_type, sh_type(hdr->sh_type) );
	cio_printf( " flags %08x ", (uint32_t) hdr->sh_flags );
	sh_flags( hdr->sh_flags );
	cio_printf( " addr %08x", hdr->sh_addr );
	cio_printf( " offset %08x", hdr->sh_offset );
	cio_printf( " size %08x\n", hdr->sh_size );
	cio_printf( " link %08x", hdr->sh_link );
	cio_printf( " info %08x", hdr->sh_info );
	cio_printf( " align %08x", hdr->sh_addralign );
	cio_printf( " entsz %08x\n", hdr->sh_entsize );
}
#endif

/**
** read_phdrs(addr,phoff,phentsize,phnum)
**
** Parses the ELF program headers and each segment described into memory.
**
** @param hdr  Pointer to the program header
** @param pcb  Pointer to the PCB (and its PDE)
**
** @return status of the attempt:
**     SUCCESS         everything loaded correctly
**     E_LOAD_LIMIT    more than N_LOADABLE PT_LOAD sections
**     other           status returned from vm_add()
*/
static int read_phdrs( elfhdr_t *hdr, pcb_t *pcb ) {

	// sanity check
	assert1( hdr != NULL );
	assert2( pcb != NULL );

#if TRACING_USER
	cio_printf( "read_phdrs(%08x,%08x)\n", (uint32_t) hdr, (uint32_t) pcb );
#endif

	// iterate through the program headers
	uint_t nhdrs = hdr->e_phnum;

	// pointer to the first header table entry
	elfproghdr_t *curr = (elfproghdr_t *) ((uint32_t) hdr + hdr->e_phoff);

	// process them all
	int loaded = 0;
	for( uint_t i = 0; i < nhdrs; ++i, ++curr ) {

#if TRACING_ELF
		dump_phdr( curr, i );
#endif
		if( curr->p_type != PT_LOAD ) {
			// not loadable --> we'll skip it
			continue;
		}

		if( loaded >= N_LOADABLE ) {
#if TRACING_USER
			cio_puts( " LIMIT\n" );
#endif
			return E_LOAD_LIMIT;
		}

		// set a pointer to the bytes within the object file
		char *data = (char *) (((uint32_t)hdr) + curr->p_offset);
#if TRACING_USER
		cio_printf( " data @ %08x", (uint32_t) data );
#endif

		// copy the pages into memory
		int stat = vm_add( pcb->pdir, curr->p_flags & PF_W, false,
				(char *) curr->p_va, curr->p_memsz, data, curr->p_filesz );
		if( stat != SUCCESS ) {
			// TODO what else should we do here? check for memory leak?
			return stat;
		}

		// set the section table entry in the PCB
		pcb->sects[loaded].length = curr->p_memsz;
		pcb->sects[loaded].addr = curr->p_va;
#if TRACING_USER
		cio_printf( " loaded %u @ %08x\n",
				pcb->sects[loaded].length, pcb->sects[loaded].addr );
#endif
		++loaded;
	}

	return SUCCESS;
}

/**
** Name:	stack_setup
**
** Set up the stack for a new process
**
** @param pcb    Pointer to the PCB for the process
** @param entry  Entry point for the new process
** @param args   Argument vector to be put in place
**
** @return A pointer to the context_t on the stack, or NULL
*/
static context_t *stack_setup( pcb_t *pcb, uint32_t entry, const char **args ) {

	/*
	** First, we need to count the space we'll need for the argument
	** vector and strings.
	*/

	int argbytes = 0;
	int argc = 0;

	while( args[argc] != NULL ) {
		int n = strlen( args[argc] ) + 1;
		// can't go over one page in size
		if( (argbytes + n) > SZ_PAGE ) {
			// oops - ignore this and any others
			break;
		}
		argbytes += n;
		++argc;
	}

	// Round up the byte count to the next multiple of four.
	argbytes = (argbytes + 3) & MOD4_MASK;

	/*
	** Allocate the arrays.  We are safe using dynamic arrays here
	** because we're using the OS stack, not the user stack.
	**
	** We want the argstrings and argv arrays to contain all zeroes.
	** The C standard states, in section 6.7.8, that
	**
	**   "21 If there are fewer initializers in a brace-enclosed list
	**       than there are elements or members of an aggregate, or
	**       fewer characters in a string literal used to initialize an
	**       array of known size than there are elements in the array,
	**       the remainder of the aggregate shall be initialized
	**       implicitly the same as objects that have static storage
	**       duration."
	**
	** Sadly, because we're using variable-sized arrays, we can't
	** rely on this, so we have to call memclr() instead. :-(  In
	** truth, it doesn't really cost us much more time, but it's an
	** annoyance.
	*/

	char argstrings[ argbytes ];
	char *argv[ argc + 1 ];

	CLEAR( argstrings );
	CLEAR( argv );

	// Next, duplicate the argument strings, and create pointers to
	// each one in our argv.
	char *tmp = argstrings;
	for( int i = 0; i < argc; ++i ) {
		int nb = strlen(args[i]) + 1; // bytes (incl. NUL) in this string
		strcpy( tmp, args[i] );   // add to our buffer
		argv[i] = tmp;              // remember where it was
		tmp += nb;                  // move on
	}

	// trailing NULL pointer
	argv[argc] = NULL;

	/*
	** The pages for the stack were cleared when they were allocated,
	** so we don't need to remember to do that.
	**
	** We reserve one longword at the bottom of the stack to hold a
	** pointer to where argv is on the stack.
	**
	** The user code was linked with a startup function that defines
	** the entry point (_start), calls main(), and then calls exit()
	** if main() returns. We need to set up the stack this way:
	** 
	**      esp ->  context      <- context save area
	**              ...          <- context save area
	**              context      <- context save area
	**              entry_pt     <- return address for the ISR
	**              argc         <- argument count for main()
	**         /->  argv         <- argv pointer for main()
	**         |     ...         <- argv array w/trailing NULL
	**         |     ...         <- argv character strings
	**         \--- ptr          <- last word in stack
	**
	** Stack alignment rules for the SysV ABI i386 supplement dictate that
	** the 'argc' parameter must be at an address that is a multiple of 16;
	** see below for more information.
	*/

	// Pointer to the last word in stack. We get this from the 
	// VM hierarchy. Get the PDE entry for the user address space.
	pde_t stack_pde = pcb->pdir[USER_PDE];

	// The PDE entry points to the PT, which is an array of PTE. The last
	// two entries are for the stack; pull out the last one.
	pte_t stack_pte = ((pte_t *)(stack_pde & MOD4K_MASK))[USER_STK_PTE2];

	// OK, now we have the PTE. The frame address of the last page is
	// in this PTE. Find the address immediately after that.
	uint32_t *ptr = (uint32_t *)
		((uint32_t)(stack_pte & MOD4K_MASK) + SZ_PAGE);

	// Pointer to where the arg strings should be filled in.
	char *strings = (char *) ( (uint32_t) ptr - argbytes );

	// back the pointer up to the nearest word boundary; because we're
	// moving toward location 0, the nearest word boundary is just the
	// next smaller address whose low-order two bits are zeroes
	strings = (char *) ((uint32_t) strings & MOD4_MASK);

	// Copy over the argv strings.
	memcpy( (void *)strings, argstrings, argbytes );

	/*
	** Next, we need to copy over the argv pointers.  Start by
	** determining where 'argc' should go.
	**
	** Stack alignment is controlled by the SysV ABI i386 supplement,
	** version 1.2 (June 23, 2016), which states in section 2.2.2:
	**
	**   "The end of the input argument area shall be aligned on a 16
	**   (32 or 64, if __m256 or __m512 is passed on stack) byte boundary.
	**   In other words, the value (%esp + 4) is always a multiple of 16
	**   (32 or 64) when control is transferred to the function entry
	**   point. The stack pointer, %esp, always points to the end of the
	**   latest allocated stack frame."
	**
	** Isn't technical documentation fun?  Ultimately, this means that
	** the first parameter to main() should be on the stack at an address
	** that is a multiple of 16.
	**
	** The space needed for argc, argv, and the argv array itself is
	** argc + 3 words (argc+1 for the argv entries, plus one word each
	** for argc and argv).  We back up that much from 'strings'.
	*/

	int nwords = argc + 3;
	uint32_t *acptr = ((uint32_t *) strings) - nwords;

	/*
	** Next, back up until we're at a multiple-of-16 address. Because we
	** are moving to a lower address, its upper 28 bits are identical to
	** the address we currently have, so we can do this with a bitwise
	** AND to just turn off the lower four bits.
	*/

	acptr = (uint32_t *) ( ((uint32_t)acptr) & MOD16_MASK );

	// copy in 'argc'
	*acptr = argc;

	// next, 'argv', which follows 'argc'; 'argv' points to the
	// word that follows it in the stack
	uint32_t *avptr = acptr + 2;
	*(acptr+1) = (uint32_t) avptr;

	/*
	** Next, we copy in all argc+1 pointers.
	*/

	// Adjust and copy the string pointers.
	for( int i = 0; i <= argc; ++i ) {
		if( argv[i] != NULL ) {
			// an actual pointer - adjust it and copy it in
			*avptr = (uint32_t) strings;
			// skip to the next entry in the array
			strings += strlen(argv[i]) + 1;
		} else {
			// end of the line!
			*avptr = NULL;
		}
		++avptr;
	}

	/*
	** Now, we need to set up the initial context for the executing
	** process.
	**
	** When this process is dispatched, the context restore code will
	** pop all the saved context information off the stack, including
	** the saved EIP, CS, and EFLAGS. We set those fields up so that
	** the interrupt "returns" to the entry point of the process.
	*/

	// Locate the context save area on the stack.
	context_t *ctx = ((context_t *) avptr) - 1;

	/*
	** We cleared the entire stack earlier, so all the context
	** fields currently contain zeroes.  We now need to fill in
	** all the important fields.
	*/

	ctx->eflags = DEFAULT_EFLAGS;    // IE enabled, PPL 0
	ctx->eip = entry;                // initial EIP
	ctx->cs = GDT_CODE;              // segment registers
	ctx->ss = GDT_STACK;
	ctx->ds = ctx->es = ctx->fs = ctx->gs = GDT_DATA;

	/*
	** Return the new context pointer to the caller.  It will be our
	** caller's responsibility to schedule this process.
	*/
	
	return( ctx );
}

/*
** PUBLIC FUNCTIONS
*/

/**
** Name:	user_init
**
** Initializes the user support module.
*/
void user_init( void ) {

#if TRACING_INIT
	cio_puts( " User" );
#endif 

	// This is gross, but we need to get this information somehow.
	// Access the "user blob" data in the second bootstrap sector
	uint16_t *blobdata = (uint16_t *) USER_BLOB_DATA;
	user_offset  = *blobdata++;
	user_segment = *blobdata++;
	user_sectors = *blobdata++;

#if TRACING_USER
	cio_printf( "\nUser blob: %u sectors @ %04x:%04x", user_sectors,
			user_segment, user_offset );
#endif

	// calculate the location of the user blob
	if( user_sectors > 0 ) {

		// calculate the address of the header
		user_header = (header_t *)
			( KERN_BASE + 
			  ( (((uint_t)user_segment) << 4) + ((uint_t)user_offset) )
			  );

		// the program table immediate follows the blob header
		prog_table = (prog_t *) (user_header + 1);

#if TRACING_USER
	cio_printf( ", hdr %08x, %u progs, tbl %08x\n", (uint32_t) user_header,
			user_header->num, (uint32_t) prog_table );
#endif

	} else {
		// too bad, so sad!
		user_header = NULL;
		prog_table = NULL;
#if TRACING_USER
		cio_putchar( '\n' );
#endif
	}
}

/**
** Name:	user_locate
**
** Locates a user program in the user code archive.
**
** @param what   The ID of the user program to find
**
** @return pointer to the program table entry in the code archive, or NULL
*/
prog_t *user_locate( uint_t what ) {
	
	// no programs if there is no blob!
	if( user_header == NULL ) {
		return NULL;
	}

	// make sure this is a reasonable program to request
	if( what >= user_header->num ) {
		// no such program!
		return NULL;
	}

	// find the entry in the program table
	prog_t *prog = &prog_table[what];

	// if there are no bytes, it's useless
	if( prog->size < 1 ) {
		return NULL;
	}

	// return the program table pointer
	return prog;
}

/**
** Name:    user_duplicate
**
** Duplicates the memory setup for an existing process.
**
** @param new   The PCB for the new copy of the program
** @param old   The PCB for the existing the program
**
** @return the status of the duplicate attempt
*/
int user_duplicate( pcb_t *new, pcb_t *old ) {

	// We need to do a recursive duplication of the process address
	// space of the current process. First, we create a new user
	// page directory. Next, we'll duplicate the USER_PDE page
	// table. Finally, we'll go through that table and duplicate
	// all the frames.

	// create the initial VM hierarchy
	pde_t *pdir = vm_mkuvm();
	if( pdir == NULL ) {
		return E_NO_MEMORY;
	}
	new->pdir = pdir;

	// Next, add a USER_PDE page table that's a duplicate of the
	// current process' page table
	if( !vm_uvmdup(old->pdir,new->pdir) ) {
		// check for memory leak?
		return E_NO_MEMORY;
	}

	// We don't do copy-on-write, so we must duplicate all the
	// individual page frames. Iterate through all the user-level
	// PDE entries, and replace the existing frames with duplicates.
	//
	// NOTE: we only deal with pdir[0] here, as we are limiting
	// the user address space to the first 4MB.  If the size of
	// the address space goes up, this code will need to be
	// modified to loop over the larger space.

	// pointer to the PMT for the user
	pte_t *pt = (pte_t *) (pdir[USER_PDE]);
	assert( pt != NULL );

	for( int i = 0; i < N_PTE; ++i ) {
		// get the current entry from the PMT
		pte_t entry = *pt;

		// if this entry is present,
		if( IS_PRESENT(entry) ) {

			// duplicate the frame pointed to by this PTE
			void *tmp = vm_pagedup( (void *) PTE_ADDR(entry) );

			// replace the old frame number with the new one
			*pt = (pte_t) (((uint32_t)tmp) | PERMS(entry) );

		} else {

			*pt = 0;

		}
		++pt;
	}

	return SUCCESS;
}

/**
** Name:	user_load
**
** Loads a user program from the user code archive into memory.
** Allocates all needed frames and sets up the VM tables.
**
** @param ptab   A pointer to the program table entry to be loaded
** @param pcb    The PCB for the program being loaded
** @param args   The argument vector for the program
**
** @return the status of the load attempt
*/
int user_load( prog_t *ptab, pcb_t *pcb, const char **args ) {

	// NULL pointers are bad!
	assert1( ptab != NULL );
	assert1( pcb != NULL );
	assert1( args != NULL );

	// locate the ELF binary
	elfhdr_t *hdr = (elfhdr_t *) ((uint32_t)user_header + ptab->offset);

#if TRACING_ELF
	cio_printf( "Load: ptab %08x: '%s', off %08x, size %08x, flags %08x\n", 
			(uint32_t) ptab, ptab->name, ptab->offset, ptab->size,
			ptab->flags );
	cio_printf( "      args %08x:", (uint32_t) args );
	for( int i = 0; args[i] != NULL; ++i ) {
		cio_printf( " [%d] %s", i, args[i] );
	}
	cio_printf( "\n      pcb %08x (pid %u)\n", (uint32_t) pcb, pcb->pid );
	dump_fhdr( hdr );
#endif

	// verify the ELF header
	if( hdr->e_ident.f.magic != ELF_MAGIC ) {
		return E_BAD_PARAM;
	}

	// allocate a page directory
	pcb->pdir = vm_mkuvm();
	if( pcb->pdir == NULL ) {
		return E_NO_MEMORY;
	}

	// read all the program headers
	int stat = read_phdrs( hdr, pcb );
	if( stat != SUCCESS ) {
		// TODO figure out a better way to deal with this
		PANIC( 0, "user_load: phdr read failed" );
	}

	// next, set up the runtime stack - just like setting up loadable
	// sections, except nothing to copy
	stat = vm_add( pcb->pdir, true, false, (void *) USER_STACK,
			SZ_USTACK, NULL, 0 );
	if( stat != SUCCESS ) {
		// TODO yadda yadda...
		PANIC( 0, "user_load: vm_add failed" );
	}

	// set up the command-line arguments
	pcb->context = stack_setup( pcb, hdr->e_entry, args );

	return SUCCESS;
}

/**
** Name:	user_cleanup
**
** "Unloads" a user program. Deallocates all memory frames and
** cleans up the VM structures.
**
** @param pcb   The PCB of the program to be unloaded
*/
void user_cleanup( pcb_t *pcb ) {
	
	if( pcb == NULL ) {
		// should this be an error?
		return;
	}

	vm_free( pcb->pdir );
	pcb->pdir = NULL;
}