Hide the system, because we just change, just go ahead and change the code about having
the CPU skip it.
If we change the code on one CPU and another CPU executes it, I'm not going to go into the
details why I've been talking before, but it can crash the kernel.
So we have to make sure all the CPUs don't see us modify this.
So we put it in a little breakpoint, we change the code, and then we remove the breakpoint with
the new thing, and we are able to modify running code.
Now, I give a talk about this before, in fact, I have the kernel recipes this year.
You can watch that.
So how does this work?
When we hit the breakpoint, it causes an exception.
Well, the exception just means is that the CPU is going along, and we hit the breakpoint,
the CPU is going to switch mode, jump into the kernel, and jump into a vector and call
another kind of a trampoline, and says, what do we do this?
Well, we have a handler.
We have a do in three handler inside the kernel.
And inside this kernel, I mean, this is overly simplified version of it, it will store all
the registers of that, so basically registers are the state of your code, as you're running,
your applications running, the registers are happening.
When interrupts happen, everything else, we just store all the registers, because that's
the state of your processor, and do whatever we want, then we restore the registers and go
back.
But we are the interrupt handler, or the exception handler is given a pointer to the state.
So what we do here is the IP is the instruction pointer, and we add five to it.
Remember, it was five bytes, and then we return, and it returns past it, so we actually
never actually get to code.
So basically, we just emulated a null up, no matter what was there, we're able to emulate
a null up, and the kernel is all happy about that.
This is great when you're going from function to null up, and from null up to call function.
By emulating a null up, you're emulating one of the states of the transition back or forth.
So when you're going on a function, if you say, I want to make it a null up, and you put
the break point in, you just automatically went to the next state.
We don't care.
Or if you're in a null up, and we want to make a function, you put the null up, or the
break point in, you're emulating a null up, you're keeping the same state, and when you
return back, you've switched to the next state.
All that works.
We just know up is part of the state transition.
But what happens if we want to go from calling food to calling bar?
There's no no up there.
So if we were to do this, if we're calling food, we have to switch to calling bar by emulating
by putting instruction pointer five points or five away, and then coming back, it doesn't
work.
So what we must do is emulate a function call from that in three handler, and this is where
the fun begins, because a call doesn't do one thing, it does two things.
I told you earlier, it pushes the return address onto the stack, and then changes the
instruction pointer to go to where you're calling.
And the return does the opposite, it pops from the stack and goes.
So we need to emulate, to emulate the function call, we need to push the return on the
stack, and that's where things get tricky.
So this is why.
When we're running, and we hit that in three, remember, I told you that we save state,
well, we save a bunch of state on the stack.
Before we ever get to the in three, the hardware will put in the stack segment, the stack
pointer, the flags, all your flags will be done, like you know, all the state of your
flags, you know, whether or not you have a compare, you do compares, those are flags,
that's all stored, it will tell you the code segment, and the instruction pointers all save,
the hardware does this, this is even user space, the hardware does this.
Then we put our own stack, actually, we go back, then we put our own register state, and
then we call this guy.
But if we want to go to food and emulate it, we need to put a return address on the stack
as well.
So you see the problem here, where we need to put the return address, it's exactly where
the hardware had put its own stack address.
So it's not just trace events that are problem.
We could have, if we have, when you register a single call back to one of the function
tracing, it will call your trampoline directly.
What happens is if I say I want to do function trace on a single function, it will create
dynamically a trampoline and call that.
So what's all doing direct calls, there's no indirect calls, it will create a trampoline
that calls your call back directly, and then it will have the tramp, what's called
the no-wop at the beginning of the function, like your trampoline, I'll call your
phone, and make it look really, really nice.
Here's the problem, everyone loved it, we got great reviews from everyone,
and Venus was absolutely against it.
So what do we do?
I think the reason why he was against it is this was the last remaining code from 1991.
We were getting rid of his baby, and he was dead set against us from doing this.
Like I said, this is really, really old code, and we didn't realize this, because
we're all going, why is Venus being such a hard head on this?
This is obviously, this is a good solution, and the solution he came up with, that's
good.
So he wanted these perceived variables again, this is almost like the first patch, and so
what it required was we had to make a special trampoline for every type of context we
were kind of in.
We had to make a trampoline, whether we had interrupts enabled, and we had to make a trampoline,
whether we didn't have interrupts disabled.
And I think, if I was, Venus seems excited about this hack, because it was really a hack.
The way this looked was that, and you had to check to see if you were an enemy, and you
used one trampoline.
If you weren't an enemy, you had to see if interrupts were enabled, and you used another
trampoline.
If you were in another enemy, you were in another trampoline.
And what the trick was, is that you had to jump to a trampoline to, so basically, remember
the stack frame there, when you returned back to the trampoline, you actually had your
returning back to a trampoline, now you got rid of the hardware stack frame, and then you
could do, this trampoline could add the return address onto your stack frame, so you had
to use a perceived variable of where your stack is, jumped to this guy, now if interrupts
were enabled, when you had this happened, if you jump back, an interrupt could come in and
screw everything up for you.
So what he said was, well, when the exception happens, it automatically disables interrupts.
So what you do is, you return back to the trampoline with, and keeping the interrupts disabled,
so you modified the flag spit to say, okay, jump back with interrupts disabled still, and
then modify your flag or modify the stack, and then enable interrupts from this trampoline
and then jump back.
He thought this was better.
So this is what the trampoline looked like, you had enable calls, something like that.
Well, lean us obviously liked it, everyone else hated it, they had locked that problem,
we have shadow stacks that's coming soon to prevent stack corruptions, it's a hardware
feature, so like that that you actually can create a shadow stack, and if anything ever
modifies a stack without doing it in normal way, using you, if there's a bug, usually a
lot of times you'll have a lot of security features done by array overflows, where you
go off by one bugs, or you overflow, usually get to the stack, modify the stack and do something
different, the hardware is going to do a shadow stack, and if anything like this modifies
a stack, it will give a fault, so you wouldn't be able to do these things, so that's exactly
what this is doing.