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가 있어야 한다.
02 · CONTRACT
공통 계약과 architecture 구현
| architecture | 핵심 mechanism | 실패 형태 | 확인할 상태 |
|---|---|---|---|
| arm64 | SVC #0, x8 syscall number와 x0-x5 argument | x8 범위 검사 누락, compat AArch32 table 혼동 또는 restart 시 original x0 손실은 엉뚱한 syscall과 argument로 이어진다. | ESR.EC=SVC64, regs[0..8], syscallno, thread flags, seccomp result와 final x0를 추적한다. |
| x86-64 | SYSCALL, RAX number와 RDI/RSI/RDX/R10/R8/R9 argument | canonical 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-V | ECALL from U-mode, a7 number와 a0-a5 argument | SEPC를 무조건 증가시킨 뒤 restart 보정을 놓치거나 compressed instruction처럼 2 bytes로 오인하면 잘못된 PC에서 복귀한다. | SCAUSE=U_ECALL, SEPC, a0-a7, orig_a0, thread flags와 syscall table index를 본다. |
03 · DIAGRAMS
세 그림으로 먼저 읽기
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를 본다.
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개 줄에 각각 설명을 붙였습니다.
return sys_ni_syscall();이 함수가 System call entry: SVC, SYSCALL과 ECALL 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 arm64 System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
static long __invoke_syscall(struct pt_regs *regs, syscall_fn_t syscall_fn)이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
return syscall_fn(regs);이 함수가 System call entry: SVC, SYSCALL과 ECALL 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 arm64 System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
static void invoke_syscall(struct pt_regs *regs, unsigned int scno,이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. System call entry: SVC, SYSCALL과 ECALL의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
unsigned int sc_nr,선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
const syscall_fn_t syscall_table[])이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. System call entry: SVC, SYSCALL과 ECALL의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
long ret;저장된 실행 문맥으로 돌아가는 제어 이전이다. PC뿐 아니라 privilege, interrupt mask, stack과 architecture status가 함께 복원된다.
(blank)빈 줄은 arm64 System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
add_random_kstack_offset();helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 arm64 System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
if (likely(scno < sc_nr)) {이 조건이 arm64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
syscall_fn_t syscall_fn;선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
syscall_fn = syscall_table[array_index_nospec(scno, sc_nr)];speculative out-of-bounds 접근을 막으며 handler pointer를 선택한다.
ret = __invoke_syscall(regs, syscall_fn);저장된 실행 문맥으로 돌아가는 제어 이전이다. PC뿐 아니라 privilege, interrupt mask, stack과 architecture status가 함께 복원된다.
} else {이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. System call entry: SVC, SYSCALL과 ECALL의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
ret = do_ni_syscall(regs, scno);저장된 실행 문맥으로 돌아가는 제어 이전이다. PC뿐 아니라 privilege, interrupt mask, stack과 architecture status가 함께 복원된다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 arm64 System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
syscall_set_return_value(current, regs, 0, ret);저장된 실행 문맥으로 돌아가는 제어 이전이다. PC뿐 아니라 privilege, interrupt mask, stack과 architecture status가 함께 복원된다.
(blank)빈 줄은 arm64 System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* This value will get limited by KSTACK_OFFSET_MAX(), which is 10Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* bits. The actual entropy will be further reduced by the compilerLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* when applying stack alignment constraints: the AAPCS mandates aLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* 16-byte aligned SP at function boundaries, which will remove theLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* 4 low bits from any entropy chosen here.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* The resulting 6 bits of entropy is seen in SP[9:4].Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
choose_random_kstack_offset(get_random_u16());helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 arm64 System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
static inline bool has_syscall_work(unsigned long flags)이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
return unlikely(flags & _TIF_SYSCALL_WORK);이 함수가 System call entry: SVC, SYSCALL과 ECALL 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 arm64 System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
static 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를 분리해 해석한다.
const syscall_fn_t syscall_table[])이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. System call entry: SVC, SYSCALL과 ECALL의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
unsigned long flags = read_thread_flags();helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 arm64 System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
regs->orig_x0 = regs->regs[0];계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
regs->syscallno = scno;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
(blank)빈 줄은 arm64 System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* BTI note:Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* The architecture does not guarantee that SPSR.BTYPE is zeroLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* on taking an SVC, so we could return to userspace with aLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* non-zero BTYPE after the syscall.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* This shouldn't matter except when userspace is explicitlyLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* doing something stupid, such as setting PROT_BTI on a pageLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* that lacks conforming BTI/PACIxSP instructions, fallingLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* through from one executable page to another with differingLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* PROT_BTI, or messing with BTYPE via ptrace: in such cases,Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* userspace should not be surprised if a SIGILL occurs onLinux 원본 주석이다. 바로 아래 코드의 호출 조건, 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개 줄에 각각 설명을 붙였습니다.
xnr = array_index_nospec(xnr, X32_NR_syscalls);helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
regs->ax = x32_sys_call(regs, xnr);helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
return true;이 함수가 System call entry: SVC, SYSCALL과 ECALL 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
return false;이 함수가 System call entry: SVC, SYSCALL과 ECALL 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 x86-64 System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/* Returns true to return using SYSRET, or false to use IRET */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
__visible noinstr bool do_syscall_64(struct pt_regs *regs, int nr)이 함수의 진입 계약이 시작된다. x86-64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
add_random_kstack_offset();helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
nr = syscall_enter_from_user_mode(regs, nr);ptrace, seccomp, audit 등 syscall-entry work를 수행하고 skip 또는 변경된 syscall number를 돌려준다.
(blank)빈 줄은 x86-64 System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
instrumentation_begin();helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 x86-64 System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
if (!do_syscall_x64(regs, nr) && !do_syscall_x32(regs, nr) && nr != -1) {이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
/* Invalid system call, but still a system call. */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
regs->ax = __x64_sys_ni_syscall(regs);helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 x86-64 System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
instrumentation_end();helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
syscall_exit_to_user_mode(regs);signal, reschedule, audit와 user return 준비를 반복 처리한다.
(blank)빈 줄은 x86-64 System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* Check that the register state is valid for using SYSRET to exitLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* to userspace. Otherwise use the slower but fully capable IRETLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* exit path.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
(blank)빈 줄은 x86-64 System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/* XEN PV guests always use the IRET path */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
if (cpu_feature_enabled(X86_FEATURE_XENPV))이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
return false;이 함수가 System call entry: SVC, SYSCALL과 ECALL 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
(blank)빈 줄은 x86-64 System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/* SYSRET requires RCX == RIP and R11 == EFLAGS */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
if (unlikely(regs->cx != regs->ip || regs->r11 != regs->flags))이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
return false;이 함수가 System call entry: SVC, SYSCALL과 ECALL 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
(blank)빈 줄은 x86-64 System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/* CS and SS must match the values set in MSR_STAR */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
if (unlikely(regs->cs != __USER_CS || regs->ss != __USER_DS))이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
return false;이 함수가 System call entry: SVC, SYSCALL과 ECALL 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
(blank)빈 줄은 x86-64 System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* On Intel CPUs, SYSRET with non-canonical RCX/RIP will #GPLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* in kernel space. This essentially lets the user take overLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* the kernel, since userspace controls RSP.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* TASK_SIZE_MAX covers all user-accessible addresses other thanLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* the deprecated vsyscall page.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
if (unlikely(regs->ip >= TASK_SIZE_MAX))이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
return false;이 함수가 System call entry: SVC, SYSCALL과 ECALL 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
(blank)빈 줄은 x86-64 System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* SYSRET cannot restore RF. It can restore TF, but unlike IRET,Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* restoring TF results in a trap from userspace immediately afterLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* SYSRET.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
if (unlikely(regs->flags & (X86_EFLAGS_RF | X86_EFLAGS_TF)))이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
return false;이 함수가 System call entry: SVC, SYSCALL과 ECALL 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
(blank)빈 줄은 x86-64 System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/* Use SYSRET to exit to userspace */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
return true;이 함수가 System call entry: SVC, SYSCALL과 ECALL 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(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개 줄에 각각 설명을 붙였습니다.
(blank)빈 줄은 RISC-V System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
handle_break(regs);helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 RISC-V System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
irqentry_nmi_exit(regs, state);helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 RISC-V System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
asmlinkage __visible __trap_section __no_stack_protector선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
void do_trap_ecall_u(struct pt_regs *regs)이 함수의 진입 계약이 시작된다. RISC-V에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
if (user_mode(regs)) {이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
long syscall = regs->a7;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
(blank)빈 줄은 RISC-V System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
regs->epc += 4;ECALL은 항상 32-bit instruction이므로 정상 return이 다음 instruction으로 가도록 SEPC를 이동한다.
regs->orig_a0 = regs->a0;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
regs->a0 = -ENOSYS;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
(blank)빈 줄은 RISC-V System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
riscv_v_vstate_discard(regs);helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 RISC-V System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
syscall = syscall_enter_from_user_mode(regs, syscall);공통 ptrace/seccomp/audit entry work가 syscall number를 유지, 변경 또는 취소할 수 있다.
(blank)빈 줄은 RISC-V System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
add_random_kstack_offset();helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 RISC-V System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
if (syscall >= 0 && syscall < NR_syscalls) {이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
syscall = array_index_nospec(syscall, NR_syscalls);helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
syscall_handler(regs, syscall);helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 RISC-V System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* Ultimately, this value will get limited by KSTACK_OFFSET_MAX(),Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* so the maximum stack offset is 1k bytes (10 bits).Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* The actual entropy will be further reduced by the compiler whenLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* applying stack alignment constraints: 16-byte (i.e. 4-bit) alignedLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* for RV32I or RV64I.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* The resulting 6 bits of entropy is seen in SP[9:4].Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
choose_random_kstack_offset(get_random_u16());helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 RISC-V System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
syscall_exit_to_user_mode(regs);helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
} else {이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. System call entry: SVC, SYSCALL과 ECALL의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
irqentry_state_t state = irqentry_nmi_enter(regs);helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 RISC-V System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
do_trap_error(regs, SIGILL, ILL_ILLTRP, regs->epc,최소 한 번 실행되는 retry 또는 순회 구간이다. 탈출 조건이 다른 CPU나 hardware의 진행에 의존하는지 확인한다.
"Oops - environment call from U-mode");하위 경로로 제어를 넘긴다. 호출 직전의 argument, interrupt/preemption 상태와 호출 뒤에도 살아 있어야 하는 object를 기록한다.
(blank)빈 줄은 RISC-V System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
irqentry_nmi_exit(regs, state);helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 RISC-V System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 RISC-V System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
#ifdef CONFIG_MMUKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
asmlinkage __visible noinstr void do_page_fault(struct pt_regs *regs)이 함수의 진입 계약이 시작된다. RISC-V에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
irqentry_state_t state = irqentry_enter(regs);helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 RISC-V System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
handle_page_fault(regs);helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 RISC-V System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
local_irq_disable();helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 RISC-V System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
irqentry_exit(regs, state);helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
#endifKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
(blank)빈 줄은 RISC-V System call entry: SVC, SYSCALL과 ECALL 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
static void noinstr handle_riscv_irq(struct pt_regs *regs)이 함수의 진입 계약이 시작된다. RISC-V에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
05 · WORKED EXAMPLE
숫자로 검산하기
write(fd, buf, 12) register와 return 흐름
64-bit native ABI에서 fd=1, buf=0x7fff1000, count=12인 호출을 가정한다.
- user ABIarm64는 x8=__NR_write, x0=1, x1=buf, x2=12. x86-64는 rax, rdi, rsi, rdx. RISC-V는 a7, a0, a1, a2를 사용한다.
- entry각 exception instruction이 privilege를 바꾸고 kernel stack의 pt_regs에 원래 argument를 고정한다.
- dispatchentry work 뒤 table handler가 user buffer 12 bytes를 검증하고 file descriptor 1의 file operation으로 보낸다.
- return성공 시 12, 실패 시 음수 errno가 x0/rax/a0에 들어가고 libc가 -1과 errno로 변환할 수 있다.
결론같은 C prototype이라도 네 번째 이후 argument register와 trap PC 규칙은 architecture ABI에서 확인해야 한다.
06 · DEEP DIVE
경계별 상세 분석
공통 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가 있어야 한다.
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를 추적한다.
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인지 확인한다.
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를 본다.
객체 수명과 소유권을 먼저 고정한다
pt_regs와 syscall argument는 현재 task kernel stack에 있고 syscall이 끝날 때까지 유효하다. user pointer가 가리키는 memory는 별도 copy_from_user와 fault/revalidation 규칙을 따른다.
주소나 register 값이 맞는지만 확인하면 stale state를 놓친다. producer, publication, consumer와 폐기 지점을 같은 표에 기록한다.
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
실패를 어떤 증거로 나눌 것인가
| 분류 | 관찰되는 결과 | 첫 확인값 |
|---|---|---|
| arm64 | x8 범위 검사 누락, compat AArch32 table 혼동 또는 restart 시 original x0 손실은 엉뚱한 syscall과 argument로 이어진다. | ESR.EC=SVC64, regs[0..8], syscallno, thread flags, seccomp result와 final x0를 추적한다. |
| x86-64 | canonical 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-V | SEPC를 무조건 증가시킨 뒤 restart 보정을 놓치거나 compressed instruction처럼 2 bytes로 오인하면 잘못된 PC에서 복귀한다. | SCAUSE=U_ECALL, SEPC, a0-a7, orig_a0, thread flags와 syscall table index를 본다. |
08 · LAB
재현과 계측 절차
- 직접 assembly syscall wrapper를 작성해 모든 argument register와 pt_regs를 kprobe로 대조한다.
- seccomp, ptrace, pending signal을 하나씩 켜 entry/exit latency와 return path 변화를 측정한다.
- 동일한 workload에서 세 architecture의 tracepoint 이름, CPU 번호, PC, stack pointer와 address-space identifier를 같은 열로 기록한다.
- 소스만 읽고 끝내지 않고 최종
vmlinux의objdump -dr,readelf -SW결과로 선택된 alternative와 section 배치를 확인한다.
09 · REFERENCES
원문 좌표
- arm64arch/arm64/kernel/syscall.c:30-92
- x86-64arch/x86/entry/syscall_64.c:79-142
- RISC-Varch/riscv/kernel/traps.c:319-385
Linux kernel source: GPL-2.0-only. 이 글의 코드 발췌는 Linux v6.18.37 원문을 기준으로 하며, 분석 문장은 해당 코드의 실행 조건과 상태 경계를 설명합니다.