Re: [PATCH 17/24] x86/entry: Introduce struct ist_regs

From: H. Peter Anvin
Date: Fri Sep 10 2021 - 00:32:21 EST


Note: the examples in this email all compiled with:

gcc -O2 -mpreferred-stack-boundary=3 -mcmodel=kernel

The disassembly has been slightly simplified.


On 9/9/21 5:18 PM, Lai Jiangshan wrote:

This patch was over designed.

In ASM code, we can easily save results in the callee-saved registers.
For example, rc3 is saved in %r14, gsbase info is saved in %rbx.

And in C code, we can't save results in registers.  And I thought there was
no place to save the results because the CR3 and gsbase are not kernel's.
So I extended the pt_regs to ist_regs to save the results.

But it was incorrect.  The results can be saved in percpu data at the end of
paranoid_entry() after the CR3/gsbase are settled down.  And the results
can be read at the beginning of paranoid_exit() before the CR3/gsbase are
switched to the interrupted context's.


OK, count me confused. Of course you can save results in caller-saved registers in C; it is kind of what they do:


extern void bar(void);

unsigned long foo(unsigned long v)
{
bar();
return v;
}

0000000000000000 <foo>:
0: 41 54 push %r12
2: 49 89 fc mov %rdi,%r12
5: e8 00 00 00 00 callq bar
a: 4c 89 e0 mov %r12,%rax
d: 41 5c pop %r12
f: c3 retq

Now, if you need to specify *which* registers, you have to declare them as global register variables - NOT local (which have completely different semantics). This also means that you (probably) want to isolate this code into its own compilation unit, because it will prevent any other code in the same .c file from using that register as well.

For example:

register unsigned long r14 asm("%r14");
unsigned long foo(unsigned long v)
{
r14 = v;
bar();
v = r14;
return v;
}

0000000000000000 <foo>:
0: 49 89 fe mov %rdi,%r14
3: e8 00 00 00 00 callq bar
8: 4c 89 f0 mov %r14,%rax
b: c3 retq

WARNING: This also means that gcc will happily discard the old value in %r14, so if you need it preserved you have to do so explicitly; if you are called direct from assembly and are happy to lose the value then the above code is fine -- and it is even slightly more efficient!

For preserving the old r14, in this case:

register unsigned long r14 asm("%r14");
unsigned long foo(unsigned long v)
{
unsigned long saved_r14 = r14;
r14 = v;
bar();
v = r14;
r14 = saved_r14;
return v;
}

0000000000000000 <foo>:
0: 53 push %rbx
1: 4c 89 f3 mov %r14,%rbx
4: 49 89 fe mov %rdi,%r14
7: e8 00 00 00 00 callq bar
c: 4c 89 f0 mov %r14,%rax
f: 49 89 de mov %rbx,%r14
12: 5b pop %rbx
13: c3 retq


HOWEVER, if you are relying on not using the stack, then using C code is probably very much not a good idea. It is very hard to guarantee that just because the C compiler is *currently* not using a stack, that it won't do so *in the future*.

Again, finally, local register variables DO NOT WORK, this does NOT do what you expect:

unsigned long foo(unsigned long v)
{
register unsigned long r14 asm("%r14") = v;
bar();
return r14;
}

0000000000000000 <foo>:
0: 41 54 push %r12
2: 49 89 fc mov %rdi,%r12
5: e8 00 00 00 00 callq bar
a: 4c 89 e0 mov %r12,%rax
d: 41 5c pop %r12
f: c3 retq