iretq returning to incorrect location when I context switch
Posted: Sat Feb 25, 2023 10:35 am
I'm working on some code to test the theory behind context switching. I have two assembly procedures `save_regs` and `load_regs`, and this question concerns `load_regs`.
This procedure is supposed to load register values from memory into the CPU. The values are passed to it from C, as the first and only parameter to the procedure. Of course one of the registers it loads is RIP, and so the behaviour I want is that the CPU will jump to execute wherever RIP points.
The code is as follows (GNU assembly):
If I call this procedure where the target RIP is 0xffff,ffff,8000,0d10, for example, then just before the iretq instruction the stack looks like this:
As you can see, at the top of the stack is the correct address for RIP, followed by the correct CS value and the correct RFLAGS value. When iretq is executed though, RIP is for some reason set to 0xffffffff80000cd0.
My understanding of iretq is that it should perform the following operation:(https://namazso.github.io/x86/html/IRET ... IRETQ.html)
and so I don't see why RIP should not end up being equal to 0xffffffff80000d10 rather than 0x[...]0cd0. It seems my understanding must be lacking somewhere.
I'll explain the logic of the `load_regs` procedure:
- Up until the cli instruction I simply take the corresponding register values from the struct pointer and load them into the registers. This works.
- After the cli instruction, I set up the top of the stack for iretq to use. First RFLAGS is pushed, then the CS register is pushed from the struct.
- Finally, I push the RIP value from the struct on to the stack, and then put the correct value of rax into that register.
What's going wrong here? Clearly most of this procedure is working as intended, at least to my understanding, because the stack looks as if it's set up correctly by the end.
This procedure is supposed to load register values from memory into the CPU. The values are passed to it from C, as the first and only parameter to the procedure. Of course one of the registers it loads is RIP, and so the behaviour I want is that the CPU will jump to execute wherever RIP points.
The code is as follows (GNU assembly):
Code: Select all
load_regs:
push %rbp
mov %rsp, %rbp
xchg %bx, %bx
# Load RFLAGS
mov 152(%rdi), %rax
push %rax
popfq
# Load CR3
mov 160(%rdi), %rax
mov %rax, %cr3
# Load general purpose regs
mov 8(%rdi), %rbx
... These lines omitted
mov 120(%rdi), %r15
cli
# Push RFLAGS
pushfq
# Push CS
mov 136(%rdi), %rax
push %rax
# Push saved instruction pointer as iret return address
mov 128(%rdi), %rax
push %rax
mov (%rdi), %rax
iretq
Code: Select all
| STACK 0xffff80007ff85f18 [0xffffffff80000d10]
| STACK 0xffff80007ff85f20 [0x0010001000100008]
| STACK 0xffff80007ff85f28 [0x0000000000000082]
My understanding of iretq is that it should perform the following operation:(https://namazso.github.io/x86/html/IRET ... IRETQ.html)
Code: Select all
RIP := Pop();
CS := Pop(); (* 64-bit pop, high-order 48 bits discarded *)
tempRFLAGS := Pop();
I'll explain the logic of the `load_regs` procedure:
- Up until the cli instruction I simply take the corresponding register values from the struct pointer and load them into the registers. This works.
- After the cli instruction, I set up the top of the stack for iretq to use. First RFLAGS is pushed, then the CS register is pushed from the struct.
- Finally, I push the RIP value from the struct on to the stack, and then put the correct value of rax into that register.
What's going wrong here? Clearly most of this procedure is working as intended, at least to my understanding, because the stack looks as if it's set up correctly by the end.