fbpx

We talk about methods in this episode. There’re many kinds of methods and we’ll need to split this topic into multiple episodes. I’ll explain how methods are called and how they return when they’re done. And a bit about how viruses can sneak in and gain control.

It doesn’t matter what language you’re using, there will be methods involved at some level. Methods allow you to wrap up a bunch of instructions so you can call on them whenever you need. But what does this mean? How can you just call something? It’s not like you’re ringing a dinner bell and your instructions come running towards you.

You’ll learn about two very important registers inside a microprocessor called the instruction pointer and the stack pointer. I explain how the instruction pointer gets updated and always points to the next instruction in line to be executed. And how the stack is a section of memory that helps coordinate methods with the help of the stack pointer.

A modern processor has support to help eliminate the threat of viruses of the type I talked about but it’s a constant struggle to keep up. Learning about how methods work, how the compiler prepares to make a method call, how the processor transfers control to the method, and then how the method returns are critical to understanding how your code behaves and how to keep it secure.

Listen to the full episode or read the full transcript below.

Transcript

It doesn’t matter what language you’re using, there will be methods involved at some level. Methods allow you to wrap up a bunch of instructions so you can call on them whenever you need. But what does this mean? How can you just call something? It’s not like you’re ringing a dinner bell and your instructions come running towards you.

Both microprocessors and microcontrollers have builtin support for methods. I mentioned in an earlier episode that a microprocessor knows how to request its first instruction and then its next instruction and so on. This is possible because the processor has a set of registers it uses for keeping track of information inside of itself. These are like internal memory slots.

Some registers are general purpose and can be used by your program for whatever you want. For example, you can read a couple values from memory, add them together, and save the result in a register. This is sort of like how you can use the memory function of your calculator to temporarily store a number.

There are two registers we’ll talk about now that have special purposes.

The first is the instruction pointer.

It points to the address of the next instruction to be executed. The second is the stack pointer which points to a section of memory used to coordinate method calls.

The instruction pointer updates itself automatically whenever an instruction is read, the processor can figure out how many bytes the instruction requires and will update the instruction pointer to point to the next memory address. Some high level programming concepts such as loops really come down to an instruction that changes the instruction pointer to point to an address that has already been executed. And if statements also come down to changing the instruction pointer so that it will skip over some other instructions. Calling a method is similar. Just change the instruction pointer to point to the first instruction in the method. To be clear, it’s the processor that changes the instruction pointer. But we can get it to make the change with certain instructions that alter the normal flow.

It’s sort of like how when you have your day all planned and full of things to do when a friend asks if you can help real quick with something else. What do you do? You first make sure that you remember what it was you were going to do next, then you help your friend. And when you’re done, you remind yourself what the next task was before you were interrupted and go back your plan.

This is so natural that computers follow almost the same pattern. When you call a method, it’s like you’re that friend asking for a favor. The microprocessor looks at the instruction pointer and copies the value into memory wherever the stack pointer is pointing. When the method is done, there’s a special instruction called a return that does the opposite by retrieving the address pointed to by the stack pointer and putting this into the instruction pointer. Since the instruction pointer points to the next address to be executed, this causes the processor to resume right where it left off.

The stack pointer also has some builtin behavior that helps make all of this go smoothly. Think of a stack pointer like a deck of cards that are stacked up on your desk.

That’s why it’s called a stack.

You can remove a card from the stack and add a new card on top of the stack. Each time you do so, the top of the stack is automatically updated to whichever card is on top.

Adding a new item to a stack is called pushing an item onto the stack. And removing an item is called popping an item off of the stack. Since this is just normal memory, the processor has the ability to read and write the values just like any other memory. But when accessing this memory through the stack pointer, the operations will be pushes and pops to the top only.

You don’t have to push and then immediately pop an item off the stack. It wouldn’t be much of a stack if it could only hold a single item. This means you can call a method and before you get a chance to return, you end up calling another method. This just causes another return address to be pushed onto the stack.

What if you need to call a third method? It’s just another push of the return address onto the stack. As you can see, the stack will just keep getting bigger. There is a limit though which is normally set by your operating system and how your application was built. If you keep calling methods without returning, you’ll eventually cause a stack overflow. Your application is dead at that point.

Modern operating systems have the ability to keep the memory used by one application completely separate from other applications. And even more important, is to keep the memory separate from the memory used by the operating system itself. When an application crashes like this, the operating system can just reclaim all the memory and keep going. Some applications though have much stronger ties with the operating system. If you install an application on your computer that also installs a device driver, just be aware that bugs in the device driver can cause everything to fall apart. This is usually caught and displayed on Windows machines as a blue screen of death. For Linux and Mac computers, it can result in a kernel panic. The result is the same, your computer is unable to continue running and must be powered down and restarted.

A lot of viruses take advantage of how methods are called to get your computer to start running their code. Sometimes this is as simple as changing that return address stored in the stack to point to memory that’s been prepared by the virus to hold its instructions. Think of it this way. You’re back to your list of things to do for the day and this time instead of your friend asking you for a favor directly, he distracts you for just a moment and then replaces one of your tasks with his own. You might notice the difference. But a computer won’t.

The stack is also used for more than just return addresses.

Let’s say you define a method called rotate that turns a game piece around. Sometimes you need to rotate your piece to the left just a bit, sometimes to the right to face an oncoming attack, and sometimes completely the opposite direction. Instead of creating separate rotateLeftBy5, rotateRightBy90, and rotateBack methods, you decide on the much better approach to create a single method called rotate that takes as a parameter the number of degrees to turn. You define that positive amounts will cause the piece to rotate to the right and negative amounts will rotate to the left. Okay, if you’re left handed and want positive to go your way, that’s fine too. You’re in charge here and get to do whatever you want. The computer will listen.

As the caller, you have a variable somewhere in memory that you first set to the desired amount to turn and then call rotate and pass your variable as part of the call. Let’s say your variable is called spin and you set it to 30. The compiler will push 30 onto the stack in preparation for the method call then issue the call instruction with the address of the method to be called. The processor will then look at the instruction pointer which contains the address that would have been up next and pushes it onto the stack then changes the instruction pointer to the address provided in the call instruction.

Let’s stop here for a moment and look at what’s in memory. Your spin variable is sitting somewhere in memory and has the value 30. There’s another value 30 sitting in memory in the area being used for the stack. And the address of the instruction that would have been executed next had the call to the rotate method not been made is also sitting in memory right next to or close to the value 30. The instruction pointer is now pointing to the first instruction in the rotate method.

When the rotate method is done, the compiler would have placed a return instruction at the end. The return instruction is all that the processor cares about and when it sees that instruction, it pops the address currently sitting on the top of the stack and changes the instruction pointer. If everything went as planned and there were no viruses causing havoc, then the processor will pick up right where it left off before calling the rotate method.

A very important thing to note here is how the spin variable was copied onto the stack. The rotate method has access to the stack variable and knows how much to turn the piece. But let’s say the rotate method tries to change the value 30 to some other value. It can do that, sure. But it will be changing its own value 30 and not the original value 30 that is sitting somewhere else in memory entirely.

If you want a method to be able to change one of your variables, then you can’t just pass the variable because the method will get a copy of the variable and changing the copy will have no effect on your variable. The way to accomplish this is to let the method know where in memory your variable lives. In other words, instead of passing your variable, give the method the address of your variable. It’s just memory and when the method knows where your variable really lives, then it can change it for real too.

There’s actually a lot more we can discuss about methods but this should be enough for now.