views:

401

answers:

7

How is a program (e.g. C or C++) arranged in computer memory? I kind of know a little about segments, variables etc, but basically I have no solid understanding of the entire structure.

Since the in-memory structure may differ, let's assume a C++ console application on Windows.

Some pointers to what I'm after specifically:

  • Outline of a function, and how is it called?
  • Each function has a stack frame, what does that contain and how is it arranged in memory?
  • Function arguments and return values
  • Global and local variables?
  • const static variables?
  • Thread local storage..

Links to tutorial-like material and such is welcome, but please no reference-style material assuming knowledge of assembler etc.

+9  A: 

Might this be what you are looking for:

http://en.wikipedia.org/wiki/Portable_Executable

The PE file format is the binary file structure of windows binaries (.exe, .dll etc). Basically, they are mapped into memory like that. More details are described here with an explanation how you yourself can take a look at the binary representation of loaded dlls in memory:

http://msdn.microsoft.com/en-us/magazine/cc301805.aspx

Edit:

Now I understand that you want to learn how source code relates to the binary code in the PE file. That's a huge field.

First, you have to understand the basics about computer architecture which will involve learning the general basics of assembly code. Any "Introduction to Computer Architecture" college course will do. Literature includes e.g. "John L. Hennessy and David A. Patterson. Computer Architecture: A Quantitative Approach" or "Andrew Tanenbaum, Structured Computer Organization".

After reading this, you should understand what a stack is and its difference to the heap. What the stack-pointer and the base pointer are and what the return address is, how many registers there are etc.

Once you've understood this, it is relatively easy to put the pieces together:

A C++ object contains code and data, i.e., member variables. A class

class SimpleClass {
     int m_nInteger;
     double m_fDouble;

     double SomeFunction() { return m_nInteger + m_fDouble; }
}

will be 4 + 8 consecutives bytes in memory. What happens when you do:

SimpleClass c1;
c1.m_nInteger = 1;
c1.m_fDouble = 5.0;
c1.SomeFunction();

First, object c1 is created on the stack, i.e., the stack pointer esp is decreased by 12 bytes to make room. Then constant "1" is written to memory address esp-12 and constant "5.0" is written to esp-8.

Then we call a function that means two things.

  1. The computer has to load the part of the binary PE file into memory that contains function SomeFunction(). SomeFunction will only be in memory once, no matter how many instances of SimpleClass you create.

  2. The computer has to execute function SomeFunction(). That means several things:

    1. Calling the function also implies passing all parameters, often this is done on the stack. SomeFunction has one (!) parameter, the this pointer, i.e., the pointer to the memory address on the stack where we have just written the values "1" and "5.0"
    2. Save the current program state, i.e., the current instruction address which is the code address that will be executed if SomeFunction returns. Calling a function means pushing the return address on the stack and setting the instruction pointer (register eip) to the address of the function SomeFunction.
    3. Inside function SomeFunction, the old stack is saved by storing the old base pointer (ebp) on the stack (push ebp) and making the stack pointer the new base pointer (mov ebp, esp).
    4. The actual binary code of SomeFunction is executed which will call the machine instruction that converts m_nInteger to a double and adds it to m_fDouble. m_nInteger and m_fDouble are found on the stack, at ebp - x bytes.
    5. The result of the addition is stored in a register and the function returns. That means the stack is discarded which means the stack pointer is set back to the base pointer. The base pointer is set back (next value on the stack) and then the instruction pointer is set to the return address (again next value on the stack). Now we're back in the original state but in some register lurks the result of the SomeFunction().

I suggest, you build yourself such a simple example and step through the disassembly. In debug build the code will be easy to understand and Visual Studio displays variable names in the disassembly view. See what the registers esp, ebp and eip do, where in memory your object is allocated, where the code is etc.

Sebastian
Thanks. Good general info about the module structure. However I'm hoping for more info on how everything maps to the program code itself.
sharkin
Ok, understood. You want to know the relation between source code and what actually happens on the machine.
Sebastian
@Sebastian: Kind of, but with focus on structure. E.g. I'm more after how the stackfram of a function looks in memory rather than the assembly instructions of the function code.
sharkin
@Sebastian: After your updates this has really become a great and rich answer, thanks!
sharkin
A: 

It might not be the most accurate information, but MS Press provides some sample chapters of of the book Inside Microsoft® Windows® 2000, Third Edition, containing information about processes and their creation along with images of some important data structures.

I also stumbled upon this PDF that summarizes some of the above information in an nice chart.

But all the provided information is more from the OS point of view and not to much detailed about the application aspects.

Frank Bollack
A: 

Actually - you won't get far in this matter with at least a little bit of knowledge in Assembler. I'd recoomend a reversing (tutorial) site, e.g. OpenRCE.org.

Tobias Langner
+3  A: 

What a huge question!

First you want to learn about virtual memory. Without that, nothing else will make sense. In short, C/C++ pointers are not physical memory addresses. Pointers are virtual addresses. There's a special CPU feature (the MMU, memory management unit) that transparently maps them to physical memory. Only the operating system is allowed to configure the MMU.

This provides safety (there is no C/C++ pointer value you can possibly make that points into another process's virtual address space, unless that process is intentionally sharing memory with you) and lets the OS do some really magical things that we now take for granted (like transparently swap some of a process's memory to disk, then transparently load it back when the process tries to use it).

A process's address space (a.k.a. virtual address space, a.k.a. addressable memory) contains:

  • a huge region of memory that's reserved for the Windows kernel, which the process isn't allowed to touch;

  • regions of virtual memory that are "unmapped", i.e. nothing is loaded there, there's no physical memory assigned to those addresses, and the process will crash if it tries to access them;

  • parts the various modules (EXE and DLL files) that have been loaded (each of these contains machine code, string constants, and other data); and

  • whatever other memory the process has allocated from the system.

Now typically a process lets the C Runtime Library or the Win32 libraries do most of the super-low-level memory management, which includes setting up:

  • a stack (for each thread), where local variables and function arguments and return values are stored; and

  • a heap, where memory is allocated if the process calls malloc or does new X.

For more about the stack is structured, read about calling conventions. For more about how the heap is structured, read about malloc implementations. In general the stack really is a stack, a last-in-first-out data structure, containing arguments, local variables, and the occasional temporary result, and not much more. Since it is easy for a program to write straight past the end of the stack (the common C/C++ bug after which this site is named), the system libraries typically make sure that there is an unmapped page adjacent to the stack. This makes the process crash instantly when such a bug happens, so it's much easier to debug (and the process is killed before it can do any more damage).

The heap is not really a heap in the data structure sense. It's a data structure maintained by the CRT or Win32 library that takes pages of memory from the operating system and parcels them out whenever the process requests small pieces of memory via malloc and friends. (Note that the OS does not micromanage this; a process can to a large extent manage its address space however it wants, if it doesn't like the way the CRT does it.)

A process can also request pages directly from the operating system, using an API like VirtualAlloc or MapViewOfFile.

There's more, but I'd better stop!

Jason Orendorff
+1  A: 

For understanding stack frame structure you can refer to http://en.wikipedia.org/wiki/Call_stack

It gives you information about structure of call stack, how locals , globals , return address is stored on call stack

atv
Thanks, good stuff!
sharkin
A: 

Stevens book "Advanced Unix Programming" has several pages with that exact answer if you can get hold of it. Of course, you should own the book.

Rob
+1  A: 

Another good illustration http://www.cs.uleth.ca/~holzmann/C/system/memorylayout.pdf

Tanuj