← Architecture 비교DUJINLABS.COM

Linux 6.18.37 LTS · Architecture comparison 11/21

System call entry: SVC, SYSCALL과 ECALL

user ABI register에서 syscall number와 argument를 꺼내 seccomp, ptrace, audit를 거쳐 handler를 호출하고 user mode로 돌아가는 경로를 비교합니다.

비교 대상
arm64 / x86-64 / RISC-V
실제 원본
3 files · 194 annotated lines
기준 tag
Linux v6.18.37
분석 축
state · ordering · lifetime · latency

01 · QUESTION

무엇을 확인할 것인가

libc wrapper가 설정한 register가 kernel pt_regs와 syscall table index로 언제 변환되고, restart와 signal은 return value를 어떻게 다시 씁니까?

entry assembly가 user register를 pt_regs로 저장하고 architecture C entry가 syscall number 범위를 확인한 뒤 공통 syscall-enter work를 수행한다. 실제 handler 반환 뒤 exit work가 signal, tracing, audit, reschedule을 처리한다.

user/kernel privilege, user address 접근, instrumentation 금지 entry 영역과 일반 C code 경계를 구분한다. seccomp가 argument를 관찰하는 시점에는 완전한 pt_regs가 있어야 한다.

지연 시간 관점최소 entry/exit instruction 비용에 KPTI, speculation mitigation, seccomp BPF, audit, ptrace, signal과 reschedule 비용이 선택적으로 더해진다.

02 · CONTRACT

공통 계약과 architecture 구현

architecture핵심 mechanism실패 형태확인할 상태
arm64SVC #0, x8 syscall number와 x0-x5 argumentx8 범위 검사 누락, compat AArch32 table 혼동 또는 restart 시 original x0 손실은 엉뚱한 syscall과 argument로 이어진다.ESR.EC=SVC64, regs[0..8], syscallno, thread flags, seccomp result와 final x0를 추적한다.
x86-64SYSCALL, RAX number와 RDI/RSI/RDX/R10/R8/R9 argumentcanonical RIP/RSP 검사 없이 SYSRET하거나 R10을 RCX argument로 오인하면 return fault 또는 네 번째 argument corruption이 생긴다.MSR_LSTAR/STAR/FMASK, orig_ax, ax, ip, sp, cs/ss와 exit path가 SYSRET인지 IRET인지 확인한다.
RISC-VECALL from U-mode, a7 number와 a0-a5 argumentSEPC를 무조건 증가시킨 뒤 restart 보정을 놓치거나 compressed instruction처럼 2 bytes로 오인하면 잘못된 PC에서 복귀한다.SCAUSE=U_ECALL, SEPC, a0-a7, orig_a0, thread flags와 syscall table index를 본다.

03 · DIAGRAMS

세 그림으로 먼저 읽기

그림 1. 같은 목적, 서로 다른 mechanism각 ISA에서 실제로 추적할 state와 checkpoint를 한 줄에 맞췄습니다.

arm64

mechanism
SVC #0, x8 syscall number와 x0-x5 argument
state
SVC64 exception이 vector를 거쳐 el0_svc()invoke_syscall()로 들어간다. x8을 table index로 사용하고 x0에 return value를 쓰며 x7과 orig_x0 등 restart에 필요한 값을 pt_regs에 보존한다.
checkpoint
ESR.EC=SVC64, regs[0..8], syscallno, thread flags, seccomp result와 final x0를 추적한다.

x86-64

mechanism
SYSCALL, RAX number와 RDI/RSI/RDX/R10/R8/R9 argument
state
hardware가 RCX에 user RIP, R11에 RFLAGS를 남기고 STAR/LSTAR 설정으로 kernel RIP/CS를 선택하지만 RSP는 자동 교체하지 않는다. entry assembly가 per-CPU stack으로 바꾸고 pt_regs를 만든 뒤 do_syscall_64()가 table을 호출한다.
checkpoint
MSR_LSTAR/STAR/FMASK, orig_ax, ax, ip, sp, cs/ss와 exit path가 SYSRET인지 IRET인지 확인한다.

RISC-V

mechanism
ECALL from U-mode, a7 number와 a0-a5 argument
state
ECALL exception은 SEPC가 trapping instruction을 가리키므로 return 전에 4 bytes를 더한다. do_trap_ecall_u()가 a7을 syscall number로 사용하고 a0에 반환값을 기록한다.
checkpoint
SCAUSE=U_ECALL, SEPC, a0-a7, orig_a0, thread flags와 syscall table index를 본다.
그림 2. 공통 kernel과 architecture hook의 소유권공통 정책이 hardware state를 직접 소유하지 않는 경계를 표시합니다.
Linux common contractentry assembly가 user register를 pt_regs로 저장하고 architecture C entry가 syscall number 범위를 확인한 뒤 공통 syscall-enter work를 수행한다. 실제 handler 반환 뒤 exit work가 signal, tracing, audit, reschedule을 처리한다.
arm64SVC #0, x8 syscall number와 x0-x5 argumentPAN과 user access 상태를 차단한 뒤 syscall work를 실행하고 exit-to-user ordering을 거쳐야 한다.
x86-64SYSCALL, RAX number와 RDI/RSI/RDX/R10/R8/R9 argumentSWAPGS, CR3/KPTI, stack switch와 speculation barrier가 일반 C 진입 전에 완료되어야 한다. SYSRET 가능 조건이 아니면 IRET로 돌아간다.
RISC-VECALL from U-mode, a7 number와 a0-a5 argumentSSTATUS.SPP/SUM, SEPC 증가와 syscall-enter work 순서를 지켜 restart가 필요할 때 원래 ECALL을 다시 실행할 수 있게 한다.
lifetime boundarypt_regs와 syscall argument는 현재 task kernel stack에 있고 syscall이 끝날 때까지 유효하다. user pointer가 가리키는 memory는 별도 copy_from_user와 fault/revalidation 규칙을 따른다.
그림 3. publication과 관찰 순서state를 준비한 뒤 architecture ordering을 거쳐 관찰 가능한 checkpoint가 됩니다.
arm64state 준비PAN과 user access 상태를 차단한 뒤 syscall work를 실행하고 exit-to-user ordering을 거쳐야 한다.관찰: ESR.EC=SVC64, regs[0..8], syscallno, thread flags, seccomp result와 final x0를 추적한다.
x86-64state 준비SWAPGS, CR3/KPTI, stack switch와 speculation barrier가 일반 C 진입 전에 완료되어야 한다. SYSRET 가능 조건이 아니면 IRET로 돌아간다.관찰: MSR_LSTAR/STAR/FMASK, orig_ax, ax, ip, sp, cs/ss와 exit path가 SYSRET인지 IRET인지 확인한다.
RISC-Vstate 준비SSTATUS.SPP/SUM, SEPC 증가와 syscall-enter work 순서를 지켜 restart가 필요할 때 원래 ECALL을 다시 실행할 수 있게 한다.관찰: SCAUSE=U_ECALL, SEPC, a0-a7, orig_a0, thread flags와 syscall table index를 본다.

04 · SOURCE

Linux 6.18.37 원본 코드와 줄별 설명

소스 위치를 고정된 숫자로 복사하지 않고 Linux v6.18.37 tree에서 함수 선언을 다시 찾아 발췌했습니다. 아래 코드와 각 줄의 설명은 1:1로 대응합니다.

arm64 · Linux 6.18.37

SVC #0, x8 syscall number와 x0-x5 argument

SVC64 exception이 vector를 거쳐 el0_svc()invoke_syscall()로 들어간다. x8을 table index로 사용하고 x0에 return value를 쓰며 x7과 orig_x0 등 restart에 필요한 값을 pt_regs에 보존한다.

원본 코드: arch/arm64/kernel/syscall.c:30-92

30	return sys_ni_syscall();
31}
32 
33static long __invoke_syscall(struct pt_regs *regs, syscall_fn_t syscall_fn)
34{
35	return syscall_fn(regs);
36}
37 
38static void invoke_syscall(struct pt_regs *regs, unsigned int scno,
39			   unsigned int sc_nr,
40			   const syscall_fn_t syscall_table[])
41{
42	long ret;
43 
44	add_random_kstack_offset();
45 
46	if (likely(scno < sc_nr)) {
47		syscall_fn_t syscall_fn;
48		syscall_fn = syscall_table[array_index_nospec(scno, sc_nr)];
49		ret = __invoke_syscall(regs, syscall_fn);
50	} else {
51		ret = do_ni_syscall(regs, scno);
52	}
53 
54	syscall_set_return_value(current, regs, 0, ret);
55 
56	/*
57	 * This value will get limited by KSTACK_OFFSET_MAX(), which is 10
58	 * bits. The actual entropy will be further reduced by the compiler
59	 * when applying stack alignment constraints: the AAPCS mandates a
60	 * 16-byte aligned SP at function boundaries, which will remove the
61	 * 4 low bits from any entropy chosen here.
62	 *
63	 * The resulting 6 bits of entropy is seen in SP[9:4].
64	 */
65	choose_random_kstack_offset(get_random_u16());
66}
67 
68static inline bool has_syscall_work(unsigned long flags)
69{
70	return unlikely(flags & _TIF_SYSCALL_WORK);
71}
72 
73static void el0_svc_common(struct pt_regs *regs, int scno, int sc_nr,
74			   const syscall_fn_t syscall_table[])
75{
76	unsigned long flags = read_thread_flags();
77 
78	regs->orig_x0 = regs->regs[0];
79	regs->syscallno = scno;
80 
81	/*
82	 * BTI note:
83	 * The architecture does not guarantee that SPSR.BTYPE is zero
84	 * on taking an SVC, so we could return to userspace with a
85	 * non-zero BTYPE after the syscall.
86	 *
87	 * This shouldn't matter except when userspace is explicitly
88	 * doing something stupid, such as setting PROT_BTI on a page
89	 * that lacks conforming BTI/PACIxSP instructions, falling
90	 * through from one executable page to another with differing
91	 * PROT_BTI, or messing with BTYPE via ptrace: in such cases,
92	 * userspace should not be surprised if a SIGILL occurs on

라인 바이 라인 주석

빈 줄과 전처리 경계도 생략하지 않았습니다. 원본의 63개 줄에 각각 설명을 붙였습니다.

L30 return sys_ni_syscall();

이 함수가 System call entry: SVC, SYSCALL과 ECALL 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L31}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L32(blank)

빈 줄은 arm64 System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L33static long __invoke_syscall(struct pt_regs *regs, syscall_fn_t syscall_fn)

이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L34{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L35 return syscall_fn(regs);

이 함수가 System call entry: SVC, SYSCALL과 ECALL 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L36}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L37(blank)

빈 줄은 arm64 System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L38static void invoke_syscall(struct pt_regs *regs, unsigned int scno,

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. System call entry: SVC, SYSCALL과 ECALL의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L39 unsigned int sc_nr,

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L40 const syscall_fn_t syscall_table[])

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. System call entry: SVC, SYSCALL과 ECALL의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L41{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L42 long ret;

저장된 실행 문맥으로 돌아가는 제어 이전이다. PC뿐 아니라 privilege, interrupt mask, stack과 architecture status가 함께 복원된다.

L43(blank)

빈 줄은 arm64 System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L44 add_random_kstack_offset();

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L45(blank)

빈 줄은 arm64 System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L46 if (likely(scno < sc_nr)) {

이 조건이 arm64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L47 syscall_fn_t syscall_fn;

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L48 syscall_fn = syscall_table[array_index_nospec(scno, sc_nr)];

speculative out-of-bounds 접근을 막으며 handler pointer를 선택한다.

L49 ret = __invoke_syscall(regs, syscall_fn);

저장된 실행 문맥으로 돌아가는 제어 이전이다. PC뿐 아니라 privilege, interrupt mask, stack과 architecture status가 함께 복원된다.

L50 } else {

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. System call entry: SVC, SYSCALL과 ECALL의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L51 ret = do_ni_syscall(regs, scno);

저장된 실행 문맥으로 돌아가는 제어 이전이다. PC뿐 아니라 privilege, interrupt mask, stack과 architecture status가 함께 복원된다.

L52 }

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L53(blank)

빈 줄은 arm64 System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L54 syscall_set_return_value(current, regs, 0, ret);

저장된 실행 문맥으로 돌아가는 제어 이전이다. PC뿐 아니라 privilege, interrupt mask, stack과 architecture status가 함께 복원된다.

L55(blank)

빈 줄은 arm64 System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L56 /*

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L57 * This value will get limited by KSTACK_OFFSET_MAX(), which is 10

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L58 * bits. The actual entropy will be further reduced by the compiler

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L59 * when applying stack alignment constraints: the AAPCS mandates a

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L60 * 16-byte aligned SP at function boundaries, which will remove the

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L61 * 4 low bits from any entropy chosen here.

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L62 *

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L63 * The resulting 6 bits of entropy is seen in SP[9:4].

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L64 */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L65 choose_random_kstack_offset(get_random_u16());

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L66}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L67(blank)

빈 줄은 arm64 System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L68static inline bool has_syscall_work(unsigned long flags)

이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L69{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L70 return unlikely(flags & _TIF_SYSCALL_WORK);

이 함수가 System call entry: SVC, SYSCALL과 ECALL 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L71}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L72(blank)

빈 줄은 arm64 System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L73static void el0_svc_common(struct pt_regs *regs, int scno, int sc_nr,

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. System call entry: SVC, SYSCALL과 ECALL의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L74 const syscall_fn_t syscall_table[])

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. System call entry: SVC, SYSCALL과 ECALL의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L75{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L76 unsigned long flags = read_thread_flags();

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L77(blank)

빈 줄은 arm64 System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L78 regs->orig_x0 = regs->regs[0];

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L79 regs->syscallno = scno;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L80(blank)

빈 줄은 arm64 System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L81 /*

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L82 * BTI note:

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L83 * The architecture does not guarantee that SPSR.BTYPE is zero

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L84 * on taking an SVC, so we could return to userspace with a

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L85 * non-zero BTYPE after the syscall.

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L86 *

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L87 * This shouldn't matter except when userspace is explicitly

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L88 * doing something stupid, such as setting PROT_BTI on a page

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L89 * that lacks conforming BTI/PACIxSP instructions, falling

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L90 * through from one executable page to another with differing

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L91 * PROT_BTI, or messing with BTYPE via ptrace: in such cases,

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L92 * userspace should not be surprised if a SIGILL occurs on

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

x86-64 · Linux 6.18.37

SYSCALL, RAX number와 RDI/RSI/RDX/R10/R8/R9 argument

hardware가 RCX에 user RIP, R11에 RFLAGS를 남기고 STAR/LSTAR 설정으로 kernel RIP/CS를 선택하지만 RSP는 자동 교체하지 않는다. entry assembly가 per-CPU stack으로 바꾸고 pt_regs를 만든 뒤 do_syscall_64()가 table을 호출한다.

원본 코드: arch/x86/entry/syscall_64.c:79-142

79		xnr = array_index_nospec(xnr, X32_NR_syscalls);
80		regs->ax = x32_sys_call(regs, xnr);
81		return true;
82	}
83	return false;
84}
85 
86/* Returns true to return using SYSRET, or false to use IRET */
87__visible noinstr bool do_syscall_64(struct pt_regs *regs, int nr)
88{
89	add_random_kstack_offset();
90	nr = syscall_enter_from_user_mode(regs, nr);
91 
92	instrumentation_begin();
93 
94	if (!do_syscall_x64(regs, nr) && !do_syscall_x32(regs, nr) && nr != -1) {
95		/* Invalid system call, but still a system call. */
96		regs->ax = __x64_sys_ni_syscall(regs);
97	}
98 
99	instrumentation_end();
100	syscall_exit_to_user_mode(regs);
101 
102	/*
103	 * Check that the register state is valid for using SYSRET to exit
104	 * to userspace.  Otherwise use the slower but fully capable IRET
105	 * exit path.
106	 */
107 
108	/* XEN PV guests always use the IRET path */
109	if (cpu_feature_enabled(X86_FEATURE_XENPV))
110		return false;
111 
112	/* SYSRET requires RCX == RIP and R11 == EFLAGS */
113	if (unlikely(regs->cx != regs->ip || regs->r11 != regs->flags))
114		return false;
115 
116	/* CS and SS must match the values set in MSR_STAR */
117	if (unlikely(regs->cs != __USER_CS || regs->ss != __USER_DS))
118		return false;
119 
120	/*
121	 * On Intel CPUs, SYSRET with non-canonical RCX/RIP will #GP
122	 * in kernel space.  This essentially lets the user take over
123	 * the kernel, since userspace controls RSP.
124	 *
125	 * TASK_SIZE_MAX covers all user-accessible addresses other than
126	 * the deprecated vsyscall page.
127	 */
128	if (unlikely(regs->ip >= TASK_SIZE_MAX))
129		return false;
130 
131	/*
132	 * SYSRET cannot restore RF.  It can restore TF, but unlike IRET,
133	 * restoring TF results in a trap from userspace immediately after
134	 * SYSRET.
135	 */
136	if (unlikely(regs->flags & (X86_EFLAGS_RF | X86_EFLAGS_TF)))
137		return false;
138 
139	/* Use SYSRET to exit to userspace */
140	return true;
141}
142 

라인 바이 라인 주석

빈 줄과 전처리 경계도 생략하지 않았습니다. 원본의 64개 줄에 각각 설명을 붙였습니다.

L79 xnr = array_index_nospec(xnr, X32_NR_syscalls);

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L80 regs->ax = x32_sys_call(regs, xnr);

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L81 return true;

이 함수가 System call entry: SVC, SYSCALL과 ECALL 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L82 }

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L83 return false;

이 함수가 System call entry: SVC, SYSCALL과 ECALL 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L84}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L85(blank)

빈 줄은 x86-64 System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L86/* Returns true to return using SYSRET, or false to use IRET */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L87__visible noinstr bool do_syscall_64(struct pt_regs *regs, int nr)

이 함수의 진입 계약이 시작된다. x86-64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L88{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L89 add_random_kstack_offset();

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L90 nr = syscall_enter_from_user_mode(regs, nr);

ptrace, seccomp, audit 등 syscall-entry work를 수행하고 skip 또는 변경된 syscall number를 돌려준다.

L91(blank)

빈 줄은 x86-64 System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L92 instrumentation_begin();

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L93(blank)

빈 줄은 x86-64 System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L94 if (!do_syscall_x64(regs, nr) && !do_syscall_x32(regs, nr) && nr != -1) {

이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L95 /* Invalid system call, but still a system call. */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L96 regs->ax = __x64_sys_ni_syscall(regs);

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L97 }

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L98(blank)

빈 줄은 x86-64 System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L99 instrumentation_end();

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L100 syscall_exit_to_user_mode(regs);

signal, reschedule, audit와 user return 준비를 반복 처리한다.

L101(blank)

빈 줄은 x86-64 System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L102 /*

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L103 * Check that the register state is valid for using SYSRET to exit

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L104 * to userspace. Otherwise use the slower but fully capable IRET

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L105 * exit path.

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L106 */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L107(blank)

빈 줄은 x86-64 System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L108 /* XEN PV guests always use the IRET path */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L109 if (cpu_feature_enabled(X86_FEATURE_XENPV))

이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L110 return false;

이 함수가 System call entry: SVC, SYSCALL과 ECALL 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L111(blank)

빈 줄은 x86-64 System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L112 /* SYSRET requires RCX == RIP and R11 == EFLAGS */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L113 if (unlikely(regs->cx != regs->ip || regs->r11 != regs->flags))

이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L114 return false;

이 함수가 System call entry: SVC, SYSCALL과 ECALL 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L115(blank)

빈 줄은 x86-64 System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L116 /* CS and SS must match the values set in MSR_STAR */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L117 if (unlikely(regs->cs != __USER_CS || regs->ss != __USER_DS))

이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L118 return false;

이 함수가 System call entry: SVC, SYSCALL과 ECALL 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L119(blank)

빈 줄은 x86-64 System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L120 /*

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L121 * On Intel CPUs, SYSRET with non-canonical RCX/RIP will #GP

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L122 * in kernel space. This essentially lets the user take over

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L123 * the kernel, since userspace controls RSP.

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L124 *

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L125 * TASK_SIZE_MAX covers all user-accessible addresses other than

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L126 * the deprecated vsyscall page.

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L127 */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L128 if (unlikely(regs->ip >= TASK_SIZE_MAX))

이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L129 return false;

이 함수가 System call entry: SVC, SYSCALL과 ECALL 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L130(blank)

빈 줄은 x86-64 System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L131 /*

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L132 * SYSRET cannot restore RF. It can restore TF, but unlike IRET,

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L133 * restoring TF results in a trap from userspace immediately after

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L134 * SYSRET.

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L135 */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L136 if (unlikely(regs->flags & (X86_EFLAGS_RF | X86_EFLAGS_TF)))

이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L137 return false;

이 함수가 System call entry: SVC, SYSCALL과 ECALL 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L138(blank)

빈 줄은 x86-64 System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L139 /* Use SYSRET to exit to userspace */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L140 return true;

이 함수가 System call entry: SVC, SYSCALL과 ECALL 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L141}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L142(blank)

빈 줄은 x86-64 System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

RISC-V · Linux 6.18.37

ECALL from U-mode, a7 number와 a0-a5 argument

ECALL exception은 SEPC가 trapping instruction을 가리키므로 return 전에 4 bytes를 더한다. do_trap_ecall_u()가 a7을 syscall number로 사용하고 a0에 반환값을 기록한다.

원본 코드: arch/riscv/kernel/traps.c:319-385

319 
320		handle_break(regs);
321 
322		irqentry_nmi_exit(regs, state);
323	}
324}
325 
326asmlinkage __visible __trap_section  __no_stack_protector
327void do_trap_ecall_u(struct pt_regs *regs)
328{
329	if (user_mode(regs)) {
330		long syscall = regs->a7;
331 
332		regs->epc += 4;
333		regs->orig_a0 = regs->a0;
334		regs->a0 = -ENOSYS;
335 
336		riscv_v_vstate_discard(regs);
337 
338		syscall = syscall_enter_from_user_mode(regs, syscall);
339 
340		add_random_kstack_offset();
341 
342		if (syscall >= 0 && syscall < NR_syscalls) {
343			syscall = array_index_nospec(syscall, NR_syscalls);
344			syscall_handler(regs, syscall);
345		}
346 
347		/*
348		 * Ultimately, this value will get limited by KSTACK_OFFSET_MAX(),
349		 * so the maximum stack offset is 1k bytes (10 bits).
350		 *
351		 * The actual entropy will be further reduced by the compiler when
352		 * applying stack alignment constraints: 16-byte (i.e. 4-bit) aligned
353		 * for RV32I or RV64I.
354		 *
355		 * The resulting 6 bits of entropy is seen in SP[9:4].
356		 */
357		choose_random_kstack_offset(get_random_u16());
358 
359		syscall_exit_to_user_mode(regs);
360	} else {
361		irqentry_state_t state = irqentry_nmi_enter(regs);
362 
363		do_trap_error(regs, SIGILL, ILL_ILLTRP, regs->epc,
364			"Oops - environment call from U-mode");
365 
366		irqentry_nmi_exit(regs, state);
367	}
368 
369}
370 
371#ifdef CONFIG_MMU
372asmlinkage __visible noinstr void do_page_fault(struct pt_regs *regs)
373{
374	irqentry_state_t state = irqentry_enter(regs);
375 
376	handle_page_fault(regs);
377 
378	local_irq_disable();
379 
380	irqentry_exit(regs, state);
381}
382#endif
383 
384static void noinstr handle_riscv_irq(struct pt_regs *regs)
385{

라인 바이 라인 주석

빈 줄과 전처리 경계도 생략하지 않았습니다. 원본의 67개 줄에 각각 설명을 붙였습니다.

L319(blank)

빈 줄은 RISC-V System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L320 handle_break(regs);

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L321(blank)

빈 줄은 RISC-V System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L322 irqentry_nmi_exit(regs, state);

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L323 }

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L324}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L325(blank)

빈 줄은 RISC-V System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L326asmlinkage __visible __trap_section __no_stack_protector

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L327void do_trap_ecall_u(struct pt_regs *regs)

이 함수의 진입 계약이 시작된다. RISC-V에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L328{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L329 if (user_mode(regs)) {

이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L330 long syscall = regs->a7;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L331(blank)

빈 줄은 RISC-V System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L332 regs->epc += 4;

ECALL은 항상 32-bit instruction이므로 정상 return이 다음 instruction으로 가도록 SEPC를 이동한다.

L333 regs->orig_a0 = regs->a0;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L334 regs->a0 = -ENOSYS;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L335(blank)

빈 줄은 RISC-V System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L336 riscv_v_vstate_discard(regs);

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L337(blank)

빈 줄은 RISC-V System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L338 syscall = syscall_enter_from_user_mode(regs, syscall);

공통 ptrace/seccomp/audit entry work가 syscall number를 유지, 변경 또는 취소할 수 있다.

L339(blank)

빈 줄은 RISC-V System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L340 add_random_kstack_offset();

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L341(blank)

빈 줄은 RISC-V System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L342 if (syscall >= 0 && syscall < NR_syscalls) {

이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L343 syscall = array_index_nospec(syscall, NR_syscalls);

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L344 syscall_handler(regs, syscall);

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L345 }

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L346(blank)

빈 줄은 RISC-V System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L347 /*

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L348 * Ultimately, this value will get limited by KSTACK_OFFSET_MAX(),

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L349 * so the maximum stack offset is 1k bytes (10 bits).

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L350 *

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L351 * The actual entropy will be further reduced by the compiler when

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L352 * applying stack alignment constraints: 16-byte (i.e. 4-bit) aligned

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L353 * for RV32I or RV64I.

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L354 *

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L355 * The resulting 6 bits of entropy is seen in SP[9:4].

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L356 */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L357 choose_random_kstack_offset(get_random_u16());

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L358(blank)

빈 줄은 RISC-V System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L359 syscall_exit_to_user_mode(regs);

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L360 } else {

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. System call entry: SVC, SYSCALL과 ECALL의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L361 irqentry_state_t state = irqentry_nmi_enter(regs);

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L362(blank)

빈 줄은 RISC-V System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L363 do_trap_error(regs, SIGILL, ILL_ILLTRP, regs->epc,

최소 한 번 실행되는 retry 또는 순회 구간이다. 탈출 조건이 다른 CPU나 hardware의 진행에 의존하는지 확인한다.

L364 "Oops - environment call from U-mode");

하위 경로로 제어를 넘긴다. 호출 직전의 argument, interrupt/preemption 상태와 호출 뒤에도 살아 있어야 하는 object를 기록한다.

L365(blank)

빈 줄은 RISC-V System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L366 irqentry_nmi_exit(regs, state);

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L367 }

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L368(blank)

빈 줄은 RISC-V System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L369}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L370(blank)

빈 줄은 RISC-V System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L371#ifdef CONFIG_MMU

Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.

L372asmlinkage __visible noinstr void do_page_fault(struct pt_regs *regs)

이 함수의 진입 계약이 시작된다. RISC-V에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L373{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L374 irqentry_state_t state = irqentry_enter(regs);

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L375(blank)

빈 줄은 RISC-V System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L376 handle_page_fault(regs);

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L377(blank)

빈 줄은 RISC-V System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L378 local_irq_disable();

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L379(blank)

빈 줄은 RISC-V System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L380 irqentry_exit(regs, state);

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L381}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L382#endif

Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.

L383(blank)

빈 줄은 RISC-V System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L384static void noinstr handle_riscv_irq(struct pt_regs *regs)

이 함수의 진입 계약이 시작된다. RISC-V에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L385{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

05 · WORKED EXAMPLE

숫자로 검산하기

01

write(fd, buf, 12) register와 return 흐름

64-bit native ABI에서 fd=1, buf=0x7fff1000, count=12인 호출을 가정한다.

  1. user ABIarm64는 x8=__NR_write, x0=1, x1=buf, x2=12. x86-64는 rax, rdi, rsi, rdx. RISC-V는 a7, a0, a1, a2를 사용한다.
  2. entry각 exception instruction이 privilege를 바꾸고 kernel stack의 pt_regs에 원래 argument를 고정한다.
  3. dispatchentry work 뒤 table handler가 user buffer 12 bytes를 검증하고 file descriptor 1의 file operation으로 보낸다.
  4. return성공 시 12, 실패 시 음수 errno가 x0/rax/a0에 들어가고 libc가 -1과 errno로 변환할 수 있다.

결론같은 C prototype이라도 네 번째 이후 argument register와 trap PC 규칙은 architecture ABI에서 확인해야 한다.

06 · DEEP DIVE

경계별 상세 분석

01

공통 kernel core와 architecture hook의 경계

entry assembly가 user register를 pt_regs로 저장하고 architecture C entry가 syscall number 범위를 확인한 뒤 공통 syscall-enter work를 수행한다. 실제 handler 반환 뒤 exit work가 signal, tracing, audit, reschedule을 처리한다.

user/kernel privilege, user address 접근, instrumentation 금지 entry 영역과 일반 C code 경계를 구분한다. seccomp가 argument를 관찰하는 시점에는 완전한 pt_regs가 있어야 한다.

02

arm64: SVC #0, x8 syscall number와 x0-x5 argument

SVC64 exception이 vector를 거쳐 el0_svc()invoke_syscall()로 들어간다. x8을 table index로 사용하고 x0에 return value를 쓰며 x7과 orig_x0 등 restart에 필요한 값을 pt_regs에 보존한다.

PAN과 user access 상태를 차단한 뒤 syscall work를 실행하고 exit-to-user ordering을 거쳐야 한다. 디버깅할 때는 ESR.EC=SVC64, regs[0..8], syscallno, thread flags, seccomp result와 final x0를 추적한다.

03

x86-64: SYSCALL, RAX number와 RDI/RSI/RDX/R10/R8/R9 argument

hardware가 RCX에 user RIP, R11에 RFLAGS를 남기고 STAR/LSTAR 설정으로 kernel RIP/CS를 선택하지만 RSP는 자동 교체하지 않는다. entry assembly가 per-CPU stack으로 바꾸고 pt_regs를 만든 뒤 do_syscall_64()가 table을 호출한다.

SWAPGS, CR3/KPTI, stack switch와 speculation barrier가 일반 C 진입 전에 완료되어야 한다. SYSRET 가능 조건이 아니면 IRET로 돌아간다. 디버깅할 때는 MSR_LSTAR/STAR/FMASK, orig_ax, ax, ip, sp, cs/ss와 exit path가 SYSRET인지 IRET인지 확인한다.

04

RISC-V: ECALL from U-mode, a7 number와 a0-a5 argument

ECALL exception은 SEPC가 trapping instruction을 가리키므로 return 전에 4 bytes를 더한다. do_trap_ecall_u()가 a7을 syscall number로 사용하고 a0에 반환값을 기록한다.

SSTATUS.SPP/SUM, SEPC 증가와 syscall-enter work 순서를 지켜 restart가 필요할 때 원래 ECALL을 다시 실행할 수 있게 한다. 디버깅할 때는 SCAUSE=U_ECALL, SEPC, a0-a7, orig_a0, thread flags와 syscall table index를 본다.

05

객체 수명과 소유권을 먼저 고정한다

pt_regs와 syscall argument는 현재 task kernel stack에 있고 syscall이 끝날 때까지 유효하다. user pointer가 가리키는 memory는 별도 copy_from_user와 fault/revalidation 규칙을 따른다.

주소나 register 값이 맞는지만 확인하면 stale state를 놓친다. producer, publication, consumer와 폐기 지점을 같은 표에 기록한다.

06

latency upper bound는 hardware instruction 하나가 아니다

최소 entry/exit instruction 비용에 KPTI, speculation mitigation, seccomp BPF, audit, ptrace, signal과 reschedule 비용이 선택적으로 더해진다.

평균값 외에 interrupt-off 구간, remote CPU 응답, firmware 호출과 retry 횟수를 분리해야 최악 지연의 원인을 찾을 수 있다.

07 · FAILURE

실패를 어떤 증거로 나눌 것인가

분류관찰되는 결과첫 확인값
arm64x8 범위 검사 누락, compat AArch32 table 혼동 또는 restart 시 original x0 손실은 엉뚱한 syscall과 argument로 이어진다.ESR.EC=SVC64, regs[0..8], syscallno, thread flags, seccomp result와 final x0를 추적한다.
x86-64canonical RIP/RSP 검사 없이 SYSRET하거나 R10을 RCX argument로 오인하면 return fault 또는 네 번째 argument corruption이 생긴다.MSR_LSTAR/STAR/FMASK, orig_ax, ax, ip, sp, cs/ss와 exit path가 SYSRET인지 IRET인지 확인한다.
RISC-VSEPC를 무조건 증가시킨 뒤 restart 보정을 놓치거나 compressed instruction처럼 2 bytes로 오인하면 잘못된 PC에서 복귀한다.SCAUSE=U_ECALL, SEPC, a0-a7, orig_a0, thread flags와 syscall table index를 본다.

08 · LAB

재현과 계측 절차

  1. 직접 assembly syscall wrapper를 작성해 모든 argument register와 pt_regs를 kprobe로 대조한다.
  2. seccomp, ptrace, pending signal을 하나씩 켜 entry/exit latency와 return path 변화를 측정한다.
  3. 동일한 workload에서 세 architecture의 tracepoint 이름, CPU 번호, PC, stack pointer와 address-space identifier를 같은 열로 기록한다.
  4. 소스만 읽고 끝내지 않고 최종 vmlinuxobjdump -dr, readelf -SW 결과로 선택된 alternative와 section 배치를 확인한다.

09 · REFERENCES

원문 좌표

Linux kernel source: GPL-2.0-only. 이 글의 코드 발췌는 Linux v6.18.37 원문을 기준으로 하며, 분석 문장은 해당 코드의 실행 조건과 상태 경계를 설명합니다.