tags:

views:

173

answers:

3

A stack is a contiguous block of memory containing data. A register called the stack pointer (SP) points to the top of the stack. The bottom of the stack is at a fixed address.

How is the stack bottom fixed by the kernel?

+1  A: 

That would be part of the implementation of the stack itself. If you're implementing a stack in (for example) C, you may store the stack pointer and the current number of elements. Or the stack pointer along with the base of the stack, something like:

typedef struct {
    int *sp_empty;
    int *sp;
    int *sp_full;
} tIntStack;

tIntStack stk;

// Initialise 20-element stack.
stk.sp = stk.sp_empty = malloc (sizeof(int) * 20);
stk.sp_full = &(stack[20]);

// Push a value x, detecting overflow.    
if (stk.sp == stk.sp_full) { error here}
*(stk.sp) = x;
stk.sp++;

// Pop a value x, detecting underflow.    
if (stk.sp == stk.sp_empty) { error here}
stk.sp--;
x = *(stk.sp);

If you're talking about a CPU stack (return addresses and so forth), you may detect the stack underflow by virtue of the fact that you crash. Badly.


As an example, in olden days when processors were limited to 64K address space, CPUs typically tested memory until they found the first byte that didn't read the same as what was just written (in the boot up process). The was the first byte beyond actual physical memory so they set SP to one below that. Of course, some (small number of) machines had 64K of physical RAM so just set SP to the top of the address space.

The process nowadays, in huge-address-space, virtual-memory, multi-tasking operating systems, is a little more complicated but it still boils down to (in most cases):

  • Address space is allocated for a process.
  • SP is set to somewhere in that address space.
  • Process runs.

At that point, you're probably on your own as far as the kernel is concerned. It's responsibility stops at the point where your code starts running other than task switching and providing services as requested, but that's nothing to do with your stack. If your buggy code overflows or underflows the stack, that's your problem.

The kernel may check your SP on task switch to see if you've done something wrong but that's by no means guaranteed. It may also use hardware memory protection to detect an underflow (if the stack is at the top of your allocated address space). But again, this depends entirely on the kernel you're using.

paxdiablo
What's do you mean by `underflow`,I've only heard of `overflow`.
Mask
Underflow is when you attempt to pop an element off of an empty stack. In most systems, it will involve accessing an unmapped memory address, resulting in a segmentation fault.
Mike DeSimone
Why a segmentation fault is necessary instead of just return a `false` value?
Mask
Because, in assembly, pop operations return what they popped, not a result code.
Mike DeSimone
@Mask, if you're implementing a stack yourself, you can have it do that (return a success indicator, giving the popped value some other way (like a pointer or reference) or return the value, giving an exception if the stack was empty). Hardware stacks are not that complicated - they will generally crash (the only reason I'm mentioning hardware stacks is because you used the magic "kernel" work). Software stacks you can make as safe as you want.
paxdiablo
"so they set SP to one below that."... unless they used predecrement stacks (like the m68k architecture), in which case omit "one below".
Mike DeSimone
+2  A: 

I typed out a longer answer, but it rambled, so here's a shorter one...

When a process is started, it needs a stack for the purpose of storing temporary values (i.e. automatic allocation) or stack frames when functions are called. The memory for this stack has to come from somewhere.

So what the OS does is create a mapping in virtual memory for the stack, and assign the stack pointer to the high address of this block. In a predecrement stack, where the stack pointer is decremented prior to dereferencing, the initial stack pointer is actually the address past the last address in the mapped space.

When the process tries to put something on the stack, it results in an access to this memory region, which doesn't have any physical RAM mapped to it. This causes a page fault, which results in the OS kicking the least-used (or close to it) page of RAM into the swap drive or page file, and reassigning the physical RAM page to the stack page being accessed. Now that there's physical RAM, the OS returns and the process continues, putting the pushed data into the stack memory.

So what happens if you pop everything off the stack and then try to pop again? That last pop, where the stack pointer is at its initial value, results in an access to a virtual memory address that's not mapped to the stack. This causes a segmentation fault, which means the process tried to access memory it never allocated. The OS responds by terminating the process and cleaning up whatever it can.

Why not map a page past the end of the stack? because that would result in reading uninitialized RAM, which will contain whatever used to be using that page of physical RAM. There is simply no way this can produce a correctly-functioning program (to say nothing of how this is a huge security risk), so it is still best to kill the program.

Mike DeSimone
Nice answer. I'd love to see what the *long* version is. :-)
Sam
virtual memory is essentially hard disk,which is very slow,so I think it's only used when the RAM is not being enough,right?
Mask
@Mask, virtual memory is *not* just hard disk. It is a disconnect between the address space your process sees and actual physical memory. You can have virtual memory without swapping or paging to disk in which case you don't need to worry about position-independent code - you just assume your code will run at the same memory address regardless of where it actually is in physical memory. You could also have paging without virtual memory as well if multiple processes wanted to use the same addresses.
paxdiablo
@Sam: The long version got into how the kernel sets up stdin/stdout/stderr for you, maps in the executable to another chunk of memory (so it doesn't have to load code until it really needs it), and finally sets up the heap. (The fact that the heap starts from low memory and grows upward explains how using too much heap can corrupt the stack.)
Mike DeSimone
@Mask: "Virtual memory" is a term that refers to the ability of a complex enough computer to maintain separate address spaces for processes that may or may not have any relation to physical addresses. In other words, if you have a virtual memory OS, and your process has an address (i.e. pointer), that address may be different from the address presented to the RAM chips. Also, that address will likely *not* make any sense to another process. This is why you have to go to the OS and beg in order to share memory between processes.
Mike DeSimone
@Mask: Do not confuse the term "virtual memory" with "swap" (which is where memory that is not in physical RAM is stored) or "virtual addresses" (which are the addresses/pointers your process gets from `malloc()` etc.).
Mike DeSimone
A: 

I assume that you mean "fixed in the memory space of the process", and not "fixed in the overall memory". You can look at where the stack bottom is in any recent Linux system by searching for a line like

bfbce000-bfbe3000 rw-p bffeb000 00:00 0          [stack]

in the output of cat /proc/<pid-here>/maps. The bottom of the stack is, in this case, at 0xbffeb000. In my system, all stack bottoms seem to fall in one of bffca000 bffcb000 bffdd000 bffe0000 bffe4000 bffe6000 bffeb000 (after looping through ~200 processes).

I guess that these values are assigned deep in the kernel, wherever processes are first created.

tucuxi
I think the stack addresses are randomized some to make it harder for stack overflow exploits to work.
Mike DeSimone
@Mike I do recall some sort of randomization going on in memory-mapped addresses, but if the stacks are indeed being randomized, then 7 values from 200 samples is a very poor randomization indeed :-)
tucuxi
@tucuxi: Now that you mention it, it does look more like something else getting mapped into the space closer to c0000000 and which has an inconsistent size (54, 53, 35, 32, 28, 26, or 21 4k pages).
Mike DeSimone