Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

arm64: Avoid clobbering the stack pointer when returning to EL1 #2270

Merged
merged 1 commit into from
Jan 1, 2025

Conversation

markjdb
Copy link
Contributor

@markjdb markjdb commented Dec 18, 2024

No description provided.

@bsdjhb
Copy link
Collaborator

bsdjhb commented Dec 18, 2024

How has this not broken before? This would seem to indicate that in hybrid kernels, every nested exception return sets the stack pointer to the per-CPU pointer?

@markjdb
Copy link
Contributor Author

markjdb commented Dec 18, 2024

How has this not broken before? This would seem to indicate that in hybrid kernels, every nested exception return sets the stack pointer to the per-CPU pointer?

Indeed, I don't quite understand how this worked. Is there some other magic which restores the stack pointer? I only noticed this problem when handling breakpoints exceptions where dtrace has to manually adjust sp in order to emulate the overwritten instruction.

@bsdjhb
Copy link
Collaborator

bsdjhb commented Dec 18, 2024

Oh, so in the save_registers_head, we actually save sp in the x18 slot in the trapframe (that's confusing):

.macro	save_registers_head el
.if \el == 1
	mov	PTR(18), PTRN(sp)
....
	stp	CAP(18), CAP(19), [PTRN(sp), #(18 * CAP_WIDTH)]

So in actual fact, the load here is re-loading the saved sp again. It must be that in your case with dtrace you have adjusted sp to not reflect the value it used on exception entry so the extra value cached in tf_x18 is "wrong". OTOH, this also means that the saved tf_x18 value in nested trapframes is wrong. That doesn't really matter except perhaps for stack unwinding in debuggers which will get confused.

@bsdjhb
Copy link
Collaborator

bsdjhb commented Dec 18, 2024

This is also broken in upstream FWIW. If we really cared we would just do another store of x18 in the trapframe in the el ==1 case after restoring its value from tpidr_el1. That might run us out of instructions though? We could just add it at the start of save_registers instead?

sys/arm64/arm64/exception.S Outdated Show resolved Hide resolved
@markjdb
Copy link
Contributor Author

markjdb commented Dec 18, 2024

This is also broken in upstream FWIW.

Are you sure? In upstream kernels we don't bother restoring x18 when returning to EL1, see the comment, "We only restore called-saved registers...".

If we really cared we would just do another store of x18 in the trapframe in the el ==1 case after restoring its value from tpidr_el1. That might run us out of instructions though? We could just add it at the start of save_registers instead?

@bsdjhb
Copy link
Collaborator

bsdjhb commented Dec 18, 2024

In fact, I'd be tempted to refine this a bit further even upstream to avoid stomping on x18 in the el == 1 case. That is, I would only load the pair of registers in the el == 0 case and just load LR:

.if \el == 0
     ldp  CAP(18), CAPN(lr), [PTRN(sp), #(TF_SP)]
     msr CAPN(sp_el0), CAP(18)
.else
     ldr  CAPN(lr), [PTRN(sp), #(TF_LR)]
.endif

then at the end, you have:

.if \el == 0
     add PTRN(sp), PTRN(sp), #(TF_SIZE)
.else
     ldr   PTRN(sp), [PTRN(sp), #(TF_SP)
.endif

@bsdjhb
Copy link
Collaborator

bsdjhb commented Dec 18, 2024

This is also broken in upstream FWIW.

Are you sure? In upstream kernels we don't bother restoring x18 when returning to EL1, see the comment, "We only restore called-saved registers...".

By broken I mean that the values in struct trapframe are broken, so if you are doing a stack trace in kgdb and walk up the stack of a nested exception and p $x18 above the nested exception you will see the wrong value (you will see an alias of the stack pointer, not the per-CPU pointer). That is mostly cosmetic though and doesn't impact the running system, just potential confusion when debugging.

We use x18 as a temp register but in hybrid kernels this is clobbered when
restoring callee-saved registers.
Copy link
Collaborator

@bsdjhb bsdjhb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll work on adjusting x18 upstream, but this should be fine for now.

@markjdb markjdb merged commit fb0e4b1 into CTSRD-CHERI:dev Jan 1, 2025
29 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants