Well, you can certainly implement a stack with an array. The difference is in access. In an array, you have a list of elements and you can access any of them at any time. (Think of a bunch of wooden blocks all laid out in a row.)
But in a stack, there's no random-access operation; there are only Push
, Peek
and Pop
, all of which deal exclusively with the element on the top of the stack. (Think of the wooden blocks stacked up vertically now. You can't touch anything below the top of the tower or it'll fall over.)
Often times NOP
is used to align instruction addresses. This is usually encountered for example when writing Shellcode to exploit buffer overflow or format string vulnerability.
Say you have a relative jump to 100 bytes forwards, and make some modifications to the code. The chances are that your modifications mess up the jump target's address and as such you'd have to also change the aforementioned relative jump. Here, you can add NOP
s to push the target address forward. If you have multiple NOP
s between the target address and the jump instruction, you can remove the NOP
s to pull the target address backward.
This would not be a problem if you are working with an assembler which supports labels. You can simply do JXX someLabel
(where JXX is some conditional jump) and the assembler will replace the someLabel
with the address of that label. However, if you simply modify the assembled machine code(the actual opcodes) by hand(as it sometimes happens with writing shellcode), you also have to change the jump instruction manually. Either you modify it, or then move the target code address by using NOP
s.
Another use-case for NOP
instruction would be something called a NOP sled. In essence the idea is to create a large enough array of instructions which cause no side-effects(such as NOP
or incrementing and then decrementing a register) but increase the instruction pointer. This is useful for example when one wants to jump to a certain piece of code which address isn't known. The trick is to place the said NOP sled in front of the target code and then jumping somewhere to the said sled. What happens is that the execution continues hopefully from the array which has no side-effects and it traverses forwards instruction-per-instruction until it hits the desired piece of code. This technique is commonly used in aforementioned buffer overflow exploits and especially to counter security measures such as ASLR.
Yet another particular use for the NOP
instruction is when one is modifying code of some program. For example, you can replace parts of conditional jumps with NOP
s and as such circumvent the condition. This is a often used method when "cracking" copy protection of software. At simplest it's just about removing the assembly code construct for if(genuineCopy) ...
line of code and replacing the instructions with NOP
s and.. VoilĂ ! No checks are made and non-genuine copy works!
Note that in essence both examples of shellcode and cracking do the same; modify existing code without updating the relative addresses of operations which rely on relative addressing.
Best Answer
The red zone is, purely and simply, an optimization that can save instructions. It means that it's no longer necessary for the emitted code for every function to subtract from the stack pointer to make local storage like so
at the beginning of every function call, even if they are not leaf functions. Often times the code emitted from the compiler can use the temporary space in the red zone below the stack pointer without needing to save it and before calling other functions. This is a useful optimization to have available.
If you no longer have to sub from the stack pointer, the emitted code can use rsp as the base pointer, a job normally reserved for rbp, and the emitted code can use rbp as another general purpose register.
This ultimately means the prologue and epilogue of each function call can save two instructions that would save and restore rbp:
(gnu assembler)
Note that in gcc you can pass the -mno-red-zone flag if you don't want it (but the x86-64 ABI requires it). The Linux kernel does not need to be ABI compliant and thus all kernel code is compiled with -mno-red-zone.
Furthermore, accessing memory beyond the stack pointer is not dangerous if that is the expected mode of operation. It's only dangerous and can lead to corruption when it's unplanned, and unexpected. When the emitted code does it, it knows what it is doing.