Mistake in register naming

Please note I made a mistake in register naming.

  • rax is 64 bit
  • eax is the lower half of rax and is 32 bits
  • ax is lower half of eax and is 16 bits
  • al is lower half of ax and is 8 bits
  • ah is upper half of ax and is 8 bits

Memory

Memory is the fundamental thing we work with in programming. All a CPU or GPU does is take in some values from memory, modify them, and put them back out to memory.

Anything we want to do wether it's drawing images on the screen, creating audio, 3d graphics, etc.. it's all about putting stuff in memory where other things(aka hardware) could read it and turn it into proper electric signals to create pixels on a screen, audio out of the speaker,printed model, etc....

Programming is manipulating Memory

Programming is fundamentally understanding what is happening with the cpu and the memory.

The primary question is, What is the cpu doing to change memory and how

This thinking should be the basis for all your decisions when it comes to programming

All your design decisions, all of you optimazation decisions should be based on this premise.

No matter what programming language you use, at the end of the day, you are instructing the cpu to modify memory.

abstractions

Higher level abstractions can help us, how every we cannot lose sight of the fact that we are manipulating memory. When we make design decisions about our program it should be in service of this, not in service of the abstraction. If the decisions we make serve only the abstractions it will lead to our code being buggy and slow.

pointers

In c/c++ memory is a first class citizen. In c/c++ you can always get an answer to the question. "Where is this value ?"


	1	char unsigned Test;
	  

The name Test in line 1, refers to a byte memory that holds a numeric value, that will be treated as a positive number. That byte has an Address. In c/c++ we can always find out what that address is.

Addreses are also numeric values that can be stored in memory. When we store an address in memory we call it a pointer.

In line 2 below,by using the * symbol, we ask the compiler to give us some memory for storing an addresss.


	  1	char unsigned Test;
	  2	char unsigned *TestPointer;
	  3
	  4	TestPointer = &Test;
	  

By using the & symbol we can find out the address of any variable. Since TestPointer is a pointer variable, that is a variable that holds addresses it can hold the address of Test, like in line 4

manipulating pointers

the address that is stored in a pointer variable can be manipulated just like any other number stored in memory.

with pointer variables you can

  • see whats in the memory that its' pointing to
  • You can change the address using math primitives
  • you can increment to point to higher addresses
  • you can decrement to point to lower addresses

the * symbol

in any variable declaration if you use the * symbol you are telling the compiler Give me some memory where I can store an address that points to the TYPE specified prior to the *


		int *NumberPointer; // address of a 4 byte value
		char *BytePointer;  // address of a 1 byte value
		long *BigNumPointer; // address of a 8 byte value
	    

In 32 bit machines addresses are 4 byte values

In 64 bit machines addresses are 8 byte values

It doesn't matter where the * is actually aligned it just still means the same thing


		int*  NumberPointer; // address of a 4 byte value
		char * BytePointer;  // address of a 1 byte value
		long  *BigNumPointer; // address of a 8 byte value
	    

the & symbol

The & symbol placed in front of any variable resolves to the address of that variable, that is the location in memory where that variable is.

If the TYPE of variable is wider that one byte, the address that is returned using the & symbol specifies the first byte (the earliest address) of the variable.

what is an address

An address is just a number. It is literally the number of bytes from the bottom of memory, The very first byte of memory, all the way up to where the compiler decided to store your variable. It is like a street address, but all of memory is one street.

On linux you can view the layout of the memory using this command $ cat /proc/$(pgrep )/maps

Virtual Memory

In the old days(up until the late 80's), the address would actually be reffering to physical memory on the machine. But with modern OS, memory has been virtualized. So the numbers that a process(A running program) see's as it's address space, is not the address of actual physical memory. THE OS MAPS the addresses that a process sees to the actual physical memory behind the scenes. So a user process doesn't know the addresses that is actually physically using. But conceptually the virtual address space that a running process see's is exactly the same. It's the addresses on a single street of memory

Why do we have virtual memory

virtual memory allows the physical memory to be shared amongs many programs without them stepping on each others data and code.It allows the Kernel(OS) to keep each processes memory sandboxed and the physical memory under exclusive control of the OS. For the time being When we are programming we can think the address space of our program as physical memory. But ulitmately we should understand the mechanisms used in mapping virtual to physical memory so we can address potential performance issues that may arise

Note: the * has a different meaning when not in a variable declaration

If you set Test = 6; and then dereference TestPointer by using the *, what will print out and why.

Lets revisit these declarations and assignments and view the chunk of memory that is being manipulated.

examining memory

In gdb we change the value of any memory location while the program is running. See the next few slides to see how we do that. We are actually reaching in and manually setting switches when we do this. This is not a simulation

We are examing the top 30 bytes of the stack and viewing how a specific mem location chnages.

We can also write other sizes

We can view the memory in different formats

The Stack

Local variables that are declared within a function have memory automatically allocated for them, on the stack, by the Operating system. You do not need to request that memomry explicitly. By declaring a variable within a function you are implicitly requesting memory for it on the stack. That memory is allocated automatically when the function is called.

Stack elasticiy

Everything you write in code in C/C++ is built upon the notion that things go deeper and shallower in a very consistent way, using function calls.

It is like a russian nesting doll, You call a function from within another function. then you can return from that function to get back to the outer doll, or call another function from there going down to the doll inside, this pattern of calls and returns is called the call stack.

The area of memory known as the stack mirrors the function call stack. As you get deeper down the call stack, the memory stack gets bigger and bigger, return from each function, the memory stack shrinks and gets smaller

Call/stack mirroring

Nothing ever goes away from the middle of the stack

It always starts from 0 gets bigger smaller bigger bigger smaller as one continuous chunk.

This mirrors our call flow

stack only grows and shrinks from the end

CallGrow
ReturnShrink
CallGrow
CallGrow
CallGrow
ReturnShrink
ReturnShrink

Stack Memory walkthru

Lets trace what happens in memory, as we step thru this code

Before the first instruction upon entering main


		$rsp = 0x7fffffffe698
		$rbp = 0x0
	    

We want to observe a large chunk of memory (100 bytes) during our walk thru so we subtract 100 from $rsp to get a lower bounds address


		$rsp       = 0x7fffffffe698
		$rsp - 100 = 0x7fffffffe634
	  

We will be observing 100 bytes from 0xe634 to 0xe698 for our entire walkthru.

This is the state of our memory window prior to executing any instructions

In gdb we can use the following command to set all the bytes in a block of memory to the same value. We are doing this so it will be easier to visualise changes as we step thru our program.

call memset(0x7fffffffe634,0x01,101)

This is how our block looks now.

We still haven't executed any instructions of our program. Here is the state of our stack registers.


		 $rsp = 0x7fffffffe698
		 $rbp = 0x0
	    

When we first enter main the size of the stack is 0

We are veiwing 100 bytes that is in memory that will eventually be used for the stack, but when $rsp is set to the top of the stack, the stacks size is 0. Remember this stack grows downward.

The first thing that happens in stack memory EVERY TIME entering a function is the address stored in $rbp is pushed onto the stack using this instruction

push   %rbp

In this case $rbp was set to 0x00 so the is the value pushed onto the stack is 0x00. All addresses are 8 bytes wide so all 8 bytes are set to 0

But notice when we executed the push instruction, $rsp was decrement by the number of bytes pushed!! The new address of $rsp is


		old $rsp = 0x7fffffffe698
		$rsp     = 0x7fffffffe690

The next thing that happens on the stack EVERY TIME we enter a function is $rsp is copied over to $rbp using the following instruction.

mov    %rsp,%rbp

For the moment $rsp and $rbp are pointing at the same address

0x7fffffffe690

the size of the stack is currently 8 bytes. The only thing that has happened was to push the value of $rbp onto the stack upon entering the function. If you recall we need to allocate some space on the stack for local variables declared in main. In main we as for a single byte of memory.


		void main(void)
		{
			char Test = 0x10;

			foo();
		}
	    

we need to grow the stack to make room for the variable TEST, so to grow the stack $rsp needs to get decremented. The compiler makes the decision about how many bytes should be decremented from $rsp to get the new address for $rsp. The space between $rbp and $rsp is called the STACK FRAME and that is how many bytes are being using on the stack for any particuliar function.

The compiler in this case decided to allocate 16 bytes for Main, even though we only need 1 byte for the char variable TEST. The instruction used was.

sub    $0x10,%rsp

The current addresses now stored in $rbp and $rsp are


		$rbp = 0x7fffffffe690
		$rsp = 0x7fffffffe680
	    

We have space on our current stack frame now. The compiler decided that char variable, Test would be stored at $rbp - 1. So the next instruction sets the value at that location.

The following shows the current values of $rsp, $rbp registers. It also shows the values stored in stack memory. Test is stored at $rbp -1 in green and The previous of $rbp is stored where $rbp is currently pointing

Now is the time to come to our first function call. Below is the disassembly.

When we use the call instruction it affects stack memory as well. An 8 byte address is pushed onto the stack and $rsp is decremented by the same amount. This address is the address of instruction to be executed in the calling function when we return from the called function

right after we execute the call instruction this is the state of $rsp and $rbp. $rbp was not affected by the call but $rsp was.


		$rsp = 0x7fffffffe678
		$rbp = 0x7fffffffe690
	    

We have not exectud any instructions yet in foo(). The 8 byte value stored at the address on the stack pointed at by $rsp is the address of the instruction we are going to return to when we return from foo and go back to main.

The first thing we do EVERY TIME on the stack when entering a function is push $rbp. So $rsp gets decremented and the current value stored in $rbp is copied on to the stack.

$rbp = 0x7fffffffe690

The next thing we do on the stack EVERY TIME is copy $rsp into $rbp to start setting up the new stack frame for foo(). For a moment they are both the same address.

0x7fffffffe670

Room needs to be made for the local variables of foo. So $rsp is decremented 16 bytes, int FooVariable is located at $rbp - 4, its value is set. the foo() stack frame is 16 bytes


		$rbp = 0x7fffffffe670
		$rsp = 0x7fffffffe660
	    

now we call bar() from foo()

in gdb if we call backtrace we can see the whole call stack as it stands now. it shows us where we are in our nesting dolls.

when we called bar. The address of the instruction to return to was pushed onto the stack so $rsp was decremented.

We are now in bar(), We are going to

  • push $rbp, which decrements(grow the stack) $rsp by 8 bytes.
  • copy $rsp to $rbp
  • set $rbp - 4 to 0x20

The compiler decided not to decrement $rsp any further. This is likely for optimization purposes

In bar() prior to return $rsp and $rbp now both point to

0x7fffffffe650

We are now going to return from bar(). The first thing we do is issue the instruction

pop $rbp

We are about to issue our first retq instruction. We already set $rbp to point to the top of stack frame for foo(). Now we need to get back to the right instruction in foo and set $rsp to bottom of the foo() stack frame

Prior to the retq call


		$rip = 0x55555555513a 
	        $rsp = 0x7fffffffe658
	        $rbp = 0x7fffffffe670
	    

$rsp is pointing at the return address that will be loaded into $rip by the retq instruction

$rsp will then be incremented by 8 bytes to shrink the stack

After the retq instruction

$rip = 0x555555555153

back in foo()

Then next instruction leaveq is a combination of two instructions

	      
		mov %rbp,%rsp  // copy $rbp to $rsp
		pop $rbp  // restore previous value of $rbp
	      
	    

After the leaveq instruction


		$rbp = 0x7fffffffe690
		$rsp = 0x7fffffffe678
	    

The last instruction left in foo() is retq, which will load the $rip register with the 8 byte value $rsp is pointing at and increment $rsp by 8 shrinking the stack.

Function Prolouge

When you call a function you push return address onto the stack, that stores the value and grows the stack by decrementing rsp

You then push $rbp, which stores the value of $rbp and grows the stack by decrementing $rsp.

You then copy rsp to rbp, so rbp is pointing at its own previous value

You then grow the stack by decrementing $rsp, to store local variables

Function Epilouge

To return from a function set rsp to rbp's value. this shrinks the stack, both pointing at prev rbp address.

You then pop $rbp which stores that address at rbp and shrinks the stack by decrementing rsp

rsp is now pointing at the address of the instruction to return to. When you call return the address at rsp gets copied to $rip and rsp gets incrementedx.

We are close to the end of our story. we are now in main().

  • Copy rbp to rsp
  • pop $rbp
  • retq, where are we returning to?

next week

  • argc, argv main is just a function