3 ms·
The original question asked about C++, but in the old days of assembly language, accessing "out of scope" data from the stack was fairly useful. For example, A
by tmoertel 13y ago
The original question asked about C++, but in the old days of assembly language, accessing "out of scope" data from the stack was fairly useful.
For example, Apple II computers had a series of slots into which expansion cards could be inserted to add new features to the computer. These cards, as you might expect, often needed controller software, and this was supplied via on-card ROM. The code in this ROM, by convention, was mapped into memory at address $Cx00, where x was the slot number the user happened to choose for the card. As a result, when this code was called, it had no idea where it resided in memory and, consequently, which slot its card was in. To figure these things out, the controller code used the following trick:
JSR IORTS
TSX
LDA $100,X
The JSR instruction makes a subroutine call, which causes the address of the next instruction to be pushed onto the system stack and then transfers control to the subroutine, in this case a well-known subroutine in system ROM called IORTS. This routine, as its name implies, is just an RTS instruction, so it returns immediately back to its caller. Control thus returns to the second instruction of our code, which copies the stack pointer into the X register. The third instruction then uses this pointer to read above the top of the system stack to obtain the value that was there during the call to IORTS. This value, of course, was the most-significant byte of the return address pushed onto the stack by the JSR instruction. Now the code knows where it resides.
If you want to see a prime example of this technique in some famous code hand-assembled by Steve Wozniak 35 years ago, take a look at page 22 of the following PDF file. It contains the boot code for the Apple Disk II controller card:
https://s3.amazonaws.com/s3data.computerhistory.org/atchm/documents/102723983-05-01-acc.pdf https://s3.amazonaws.com/s3data.computerhistory.org/atchm/do...
- makomk 13y agoFrom what I recall, that's not necessarily safe on modern OSes and processors due to interrupts, but there's still buggy code out there that relies on it.
- monocasa 13y agoI can't think of a modern OS/ISA combination where that's the case. State will get saved on a kernel mode stack (if not somewhere else totally). This is because the stack pointer is controlled by user mode, and you don't want it to do something like set the stack pointer to some area in kernel space, and then invoke a software interrupt in order to overwrite privileged code or data.
- billforsternz 13y agoMaybe in modern OS/CPU combos, but on a 70s era 8 bit CPU like the 6502 the code as described above is definitely vulnerable to a hardware interrupt overwriting the out of scope memory area in question before it is used. One way to make it safe would be to disable interrupts, although non-maskable interrupts would still be a problem. However, although I haven't checked the details, I suspect that something much cleverer does make the trick robust. What would the hardware interrupt service routine overwrite the out of scope memory with ? For many CPUs, maybe for the 6502, it would be the return address from the interrupt service routine - which is slightly different to the originally pushed return address, but still on the same page, which is all that's required. So a little bit of Woz magic perhaps.
- cnvogel 13y agoNote that this was necessary, because on some processors you cannot directly access the address of the program counter, so as this stackoverflow answer explains... http://stackoverflow.com/a/7932364/419237 http://stackoverflow.com/a/7932364/419237 the canonical way to get it on x86 is to jump to a subroutine right in front of you, and pop the return address of the stack, instead of "ret" popping the return-address and, well... returning. But actually you can translate the 6502 solution directly to x86 assembler (gcc on Linux): #include <stdio.h> int main(int argc, char argv) { void p; (void) argc; (void) argv; printf("main is at %p\n",&main); __asm__( " jmp 1f\n" /* that's the IORTS */ "0: ret\n" /* subroutine */ "1: call 0b\n" /* call IORTS */ " sub $0x4,%%esp\n" /* decrease stackpointer */ " pop %0\n": "=r"(p)); /* pop register */ printf("PC = %p\n",p); return 0; }