If there were never routines written with more than six arguments, we could forget about stacks for a while and move onto another topic. Unfortunately, there are such routines within the UNIX kernel, so we really need to talk about this. Let's refer to /usr/include/sys/frame.h again for a moment. /* * Definition of the sparc stack frame (when it is pushed on the stack). */ struct frame { int fr_local[8]; /* saved locals */ int fr_arg[6]; /* saved arguments [0 - 5] */ struct frame *fr_savfp; /* saved frame pointer */ int fr_savpc; /* saved program counter */ char *fr_stret; /* struct return addr */ int fr_argd[6]; /* arg dump area */ int fr_argx[1]; /* array of args past the sixth */ }; Quickly, let's review what happens when we execute the save instruction upon entering a new routine. A new stack frame is created, the window is shifted, and possibly a window overflow trap occurs. The kernel will process the trap, copying the former output registers (aka the new input registers) onto the new stack frame. We are okay so far, right? Now, if the calling routine needs to send more than six arguments to the callee, it cannot pass them through the window registers, as we have already used all of the overlapping registers. Looking at the frame structure, we see that the arguments past the sixth are to be loaded into the fr_argx array. We are still okay, right? Here's the problem. Timing! We can't load up the callee's frame fr_argx array prior to calling the callee. The callee's frame doesn't exist yet! So, exactly where and when do we load up the additional parameters? The answer is: in the caller's frame, in his own fr_argx array, before we invoke the callee. We've seen that the compiler can be quite clever. When it comes to this scenario, the compiler creates assembly code that has the callee routine referencing not only his own frame, pointed to by %sp , but also the caller's frame via %fp. Compile the following program on your own system. We recommend you compile first with optimization. After you get through the optimized version, try a straight compile for fun. Here's the program, nine-args.c . /* * Demo file to show how the compiler produces * SPARC assembly code to handle more than 6 * calling arguments. * */ main() { printf ("Main calling fred.\n"); fred (1, 2, 3, 4, 5, 6, 7, 8, 9); } int fred (i1, i2, i3, i4, i5, i6, i7, i8, i9) int i1, i2, i3, i4, i5, i6, i7, i8, i9; { printf ("args are %x, %x, %x, %x, %x, %x, %x, %x, %x\n", i1, i2, i3, i4, i5, i6, i7, i8, i9); george (0x111, 0x222, 0x333, 0x444, 0x555, 0x666, 0x777, 0x888, 0x999); } int george (k1, k2, k3, k4, k5, k6, k7, k8, k9) int k1, k2, k3, k4, k5, k6, k7, k8, k9; { int value; printf ("args are %x, %x, %x, %x, %x, %x, %x, %x, %x\n", k1, k2, k3, k4, k5, k6, k7, k8, k9); } For demonstration purposes, we will compile this program with optimization and show you what the whole stack looks like just after we create george() 's frame. Since we are expecting to see additional information in the frames , we will be displaying more than 24 words per frame (the minimum frame size ). After we walk through the stack, we also will show you the SPARC assembly program the compiler generated. This will allow you to try running the program on paper in case you don't have a computer handy. Yes, we highly recommend you try this method of program execution, especially on those nights when you are convinced you absolutely cannot fall sleep.
And here are the actual assembly instructions that were generated by the compile we used in this example. We'll see you in the next chapter where we will introduce you to the UNIX kernel. Until then, sweet dreams! Figure 18-5 Viewing nineargs's assembly instructions via adb main,50?ai main: main: save %sp, -0x68, %sp main+4: sethi %hi(0x20800), %l0 main+8: call _PROCEDURE_LINKAGE_TABLE_ + 0x54 main+0xc: add %l0, 0x1c0, %o0 main+0x10: mov 0x7, %l0 main+0x14: mov 0x8, %l1 main+0x18: st %l0, [%sp + 0x5c] main+0x1c: mov 0x1, %o0 main+0x20: mov 0x2, %o1 main+0x24: mov 0x3, %o2 main+0x28: mov 0x4, %o3 main+0x2c: mov 0x5, %o4 main+0x30: mov 0x6, %o5 main+0x34: st %l1, [%sp + 0x60] main+0x38: mov 0x9, %l0 main+0x3c: call fred main+0x40: st %l0, [%sp + 0x64] main+0x44: ret main+0x48: restore main+0x4c: unimp 0x0 main+0x50: unimp 0x0 main+0x54: unimp 0x0 main+0x58: unimp 0x0 main+0x5c: unimp 0x0 fred: save %sp, -0x78, %sp fred+4: ld [%fp + 0x5c], %l0 fred+8: mov %i0, %o1 fred+0xc: st %l0, [%sp + 0x60] fred+0x10: mov %i1, %o2 fred+0x14: ld [%fp + 0x60], %l1 fred+0x18: mov %i2, %o3 fred+0x1c: st %l1, [%sp + 0x64] fred+0x20: sethi %hi(0x20800), %l1 fred+0x24: ld [%fp + 0x64], %l0 fred+0x28: add %l1, 0x1d4, %o0 fred+0x2c: mov %i3, %o4 fred+0x30: mov %i4, %o5 fred+0x34: st %i5, [%sp + 0x5c] fred+0x38: call _PROCEDURE_LINKAGE_TABLE_ + 0x54 fred+0x3c: st %l0, [%sp + 0x68] fred+0x40: mov 0x777, %l0 fred+0x44: mov 0x888, %l1 fred+0x48: st %l0, [%sp + 0x5c] fred+0x4c: mov 0x111, %o0 fred+0x50: mov 0x222, %o1 fred+0x54: mov 0x333, %o2 fred+0x58: mov 0x444, %o3 fred+0x5c: mov 0x555, %o4 fred+0x60: mov 0x666, %o5 fred+0x64: st %l1, [%sp + 0x60] fred+0x68: mov 0x999, %l0 fred+0x6c: call george fred+0x70: st %l0, [%sp + 0x64] fred+0x74: ret fred+0x78: restore fred+0x7c: unimp 0x0 fred+0x80: unimp 0x0 fred+0x84: unimp 0x0 fred+0x88: unimp 0x0 fred+0x8c: unimp 0x0 george: save %sp, -0x70, %sp george+4: ld [%fp + 0x5c], %l0 george+8: mov %i0, %o1 george+0xc: st %l0, [%sp + 0x60] george+0x10: mov %i1, %o2 george+0x14: ld [%fp + 0x60], %l1 george+0x18: mov %i2, %o3 george+0x1c: st %l1, [%sp + 0x64] george+0x20: sethi %hi(0x20800), %l1 george+0x24: ld [%fp + 0x64], %l0 george+0x28: add %l1, 0x204, %o0 george+0x2c: mov %i3, %o4 george+0x30: mov %i4, %o5 george+0x34: st %i5, [%sp + 0x5c] george+0x38: call _PROCEDURE_LINKAGE_TABLE_ + 0x54 george+0x3c: st %l0, [%sp + 0x68] george+0x40: ret george+0x44: restore _ex_text1: save %sp, -0x60, %sp _ex_text1+4: call _ex_text1 + 0x10 |