함수 호출 전의 값이 함수 호출 후에도 유지되어야 하는 레지스터를 의미한다.
함수 호출 전의 값이 함수 호출 후에 바뀔 수 있는 레지스터를 의미한다.
* Registers on entry:
* rax system call number
* rcx return address
* r11 saved rflags (note: r11 is callee-clobbered register in C ABI)
* rdi arg0
* rsi arg1
* rdx arg2
* r10 arg3 (needs to be moved to rcx to conform to C ABI)
* r8 arg4
* r9 arg5
* (note: r12-r15, rbp, rbx are callee-preserved in C ABI)
entry_64.S
에 존재하는 주석이다.
r12 ~ r15, rbp, rbx 레지스터는 callee-preserved이고, 나머지는 callee-clobbered register에 해당한다.
SYM_CODE_START(entry_SYSCALL_64)
UNWIND_HINT_ENTRY
ENDBR
swapgs
/* tss.sp2 is scratch space. */
movq %rsp, PER_CPU_VAR(cpu_tss_rw + TSS_sp2)
SWITCH_TO_KERNEL_CR3 scratch_reg=%rsp
movq PER_CPU_VAR(pcpu_hot + X86_top_of_stack), %rsp
SYM_INNER_LABEL(entry_SYSCALL_64_safe_stack, SYM_L_GLOBAL)
ANNOTATE_NOENDBR
/* Construct struct pt_regs on stack */
pushq $__USER_DS /* pt_regs->ss */
pushq PER_CPU_VAR(cpu_tss_rw + TSS_sp2) /* pt_regs->sp */
pushq %r11 /* pt_regs->flags */
pushq $__USER_CS /* pt_regs->cs */
pushq %rcx /* pt_regs->ip */
SYM_INNER_LABEL(entry_SYSCALL_64_after_hwframe, SYM_L_GLOBAL)
pushq %rax /* pt_regs->orig_ax */
PUSH_AND_CLEAR_REGS rax=$-ENOSYS
/* IRQs are off. */
movq %rsp, %rdi
/* Sign extend the lower 32bit as syscall numbers are treated as int */
movslq %eax, %rsi
/* clobbers %rax, make sure it is after saving the syscall nr */
IBRS_ENTER
UNTRAIN_RET
CLEAR_BRANCH_HISTORY
call do_syscall_64 /* returns with IRQs disabled */
swapgs를 수행한다.
per-cpu 메모리에 userspace의 rsp 값을 저장해 놓는다.
PER_CPU_VAR
매크로%gs:(cpu_tss_rw + TSS_sp2)(%rip)
gs_base 로 부터 rip + cpu_tss_rw + TSS_sp2
의 위치에 있는 값을 의미한다.
참고로 gs_base address에는 per-cpu 메모리의 주소가 저장되어 있다.
cr3 레지스터를 바꾸어 커널의 PGD를 가리키게 한다. (KPTI 인듯)
SWITCH_TO_KERNEL_CR3
매크로.macro SWITCH_TO_KERNEL_CR3 scratch_reg:req
ALTERNATIVE "jmp .Lend_\@", "", X86_FEATURE_PTI
movl %cr3, \scratch_reg
/* Test if we are already on kernel CR3 */
testl $PTI_SWITCH_MASK, \scratch_reg
jz .Lend_\@
andl $(~PTI_SWITCH_MASK), \scratch_reg
movl \scratch_reg, %cr3
/* Return original CR3 in \scratch_reg */
orl $PTI_SWITCH_MASK, \scratch_reg
.Lend_\@:
.endm
cr3 레지스터를 rsp로 옮겨서 연산한다.
cr3의 PTI_SWITCH_MASK
를 unset으로 설정해 주어 cr3가 커널의 PGD를 가리키도록 바꾸어 준다.
ALTERNATIVE
매크로는ALTERNATIVE(oldinstr, newinstr, ft_flags)
형태를 가지고 있다.
- oldinstr: 기본적으로 실행될 명령어
- newinstr: 특정 CPU 기능이 있는 경우 대체될 명령어
- ft_flags: CPU 기능 비트, 특정 CPU 기능을 나타내는 비트 마스크.
위의 코드의 경우
X86_FEATURE_PTI
가 활성화 된 경우에는"jmp .Lend_\@"
가""
로 치환된다는 뜻이다.
즉, KPTI를 지원하지 않으면 그냥 jmp만 하고 끝난다.
PUSH_AND_CLEAR_REGS
매크로.macro PUSH_REGS rdx=%rdx rcx=%rcx rax=%rax save_ret=0 unwind_hint=1
.if \save_ret
pushq %rsi /* pt_regs->si */
movq 8(%rsp), %rsi /* temporarily store the return address in %rsi */
movq %rdi, 8(%rsp) /* pt_regs->di (overwriting original return address) */
.else
pushq %rdi /* pt_regs->di */
pushq %rsi /* pt_regs->si */
.endif
pushq \rdx /* pt_regs->dx */
pushq \rcx /* pt_regs->cx */
pushq \rax /* pt_regs->ax */
pushq %r8 /* pt_regs->r8 */
pushq %r9 /* pt_regs->r9 */
pushq %r10 /* pt_regs->r10 */
pushq %r11 /* pt_regs->r11 */
pushq %rbx /* pt_regs->rbx */
pushq %rbp /* pt_regs->rbp */
pushq %r12 /* pt_regs->r12 */
pushq %r13 /* pt_regs->r13 */
pushq %r14 /* pt_regs->r14 */
pushq %r15 /* pt_regs->r15 */
.if \unwind_hint
UNWIND_HINT_REGS
.endif
.if \save_ret
pushq %rsi /* return address on top of stack */
.endif
.endm
.macro CLEAR_REGS clear_bp=1
/*
* Sanitize registers of values that a speculation attack might
* otherwise want to exploit. The lower registers are likely clobbered
* well before they could be put to use in a speculative execution
* gadget.
*/
xorl %esi, %esi /* nospec si */
xorl %edx, %edx /* nospec dx */
xorl %ecx, %ecx /* nospec cx */
xorl %r8d, %r8d /* nospec r8 */
xorl %r9d, %r9d /* nospec r9 */
xorl %r10d, %r10d /* nospec r10 */
xorl %r11d, %r11d /* nospec r11 */
xorl %ebx, %ebx /* nospec rbx */
.if \clear_bp
xorl %ebp, %ebp /* nospec rbp */
.endif
xorl %r12d, %r12d /* nospec r12 */
xorl %r13d, %r13d /* nospec r13 */
xorl %r14d, %r14d /* nospec r14 */
xorl %r15d, %r15d /* nospec r15 */
.endm
.macro PUSH_AND_CLEAR_REGS rdx=%rdx rcx=%rcx rax=%rax save_ret=0 clear_bp=1 unwind_hint=1
PUSH_REGS rdx=\rdx, rcx=\rcx, rax=\rax, save_ret=\save_ret unwind_hint=\unwind_hint
CLEAR_REGS clear_bp=\clear_bp
.endm
말 그대로 레지스터 값들을 스택에 push 한 후에 레지스터 값들을 0으로 초기화한다.
레지스터 값들을 스택에 다 저장하고 난 이후에는 do_syscall_64(rsp, eax)
를 호출하게 된다.
레지스터 값들이 스택에 push 되어있는 상태이므로 rdi(=rsp)의 값은 struct pt_regs
를 가리키게 되고, rsi(=eax)는 호출한 시스템 콜 넘버이다.
do_syscall_64()
리턴 이후 ALTERNATIVE "testb %al, %al; jz swapgs_restore_regs_and_return_to_usermode", \
"jmp swapgs_restore_regs_and_return_to_usermode", X86_FEATURE_XENPV
/*
* We win! This label is here just for ease of understanding
* perf profiles. Nothing jumps here.
*/
syscall_return_via_sysret:
IBRS_EXIT
POP_REGS pop_rdi=0
/*
* Now all regs are restored except RSP and RDI.
* Save old stack pointer and switch to trampoline stack.
*/
movq %rsp, %rdi
movq PER_CPU_VAR(cpu_tss_rw + TSS_sp0), %rsp
UNWIND_HINT_END_OF_STACK
pushq RSP-RDI(%rdi) /* RSP */
pushq (%rdi) /* RDI */
/*
* We are on the trampoline stack. All regs except RDI are live.
* We can do future final exit work right here.
*/
STACKLEAK_ERASE_NOCLOBBER
SWITCH_TO_USER_CR3_STACK scratch_reg=%rdi
popq %rdi
popq %rsp
SYM_INNER_LABEL(entry_SYSRETQ_unsafe_stack, SYM_L_GLOBAL)
ANNOTATE_NOENDBR
swapgs
CLEAR_CPU_BUFFERS
sysretq
do_syscall_64()
함수는 성공 시 true
, 실패 시 false
를 리턴한다.
true라면 syscall_return_via_sysret
이 실행되게 되고, sysretq를 통해서 유저영역으로 리턴된다.
POP_REGS를 통해 나머지 레지스터 값들을 복원하고, rsp 값을 usermode의 rsp 값으로 복원한 후 cr3 레지스터를 SWITCH_TO_USER_CR3_STACK을 통해 복원한다.
그 후 swapgs -> sysretq를 호출하여 유저모드로 복귀한다.
sysretq에서는 ss, cs, rflags, rip 값을 설정해 준다. 내부 동작은 투머치인듯.
https://www.felixcloutier.com/x86/sysret 여기에 적혀있긴 하다.
sysretq는 iretq에 비해서 더 빠르다고 한다.