← Architecture 비교DUJINLABS.COM

Linux 6.18.37 LTS · Architecture comparison 12/21

Signal frame과 user context 복원

kernel이 pending signal을 선택한 뒤 user stack에 rt_sigframe을 쓰고 handler로 PC를 바꾸며, rt_sigreturn이 register와 extended state를 복원하는 과정을 비교합니다.

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

01 · QUESTION

무엇을 확인할 것인가

비동기 signal이 syscall 또는 user instruction을 중단했을 때 원래 PC, stack, mask, FPU/vector state는 어디에 보관되고 어떤 검증 뒤 신뢰되는가?

exit-to-user path가 deliverable signal을 고르고 architecture setup_rt_frame()이 user stack 또는 altstack에 ABI frame을 만든다. handler return trampoline은 rt_sigreturn syscall로 들어와 frame을 copy/validate한 뒤 pt_regs와 signal mask를 복원한다.

kernel-owned pt_regs에서 user-writable signal frame으로 상태를 복사하는 순간 신뢰 경계가 바뀐다. sigreturn은 user가 수정한 frame을 읽으므로 privilege bit, canonical address, reserved field를 반드시 검증한다.

지연 시간 관점fixed integer context 외에 SVE/SME, XSAVE/AMX, RISC-V vector처럼 가변 크기 state를 frame에 materialize하면 signal delivery와 sigreturn 시간이 state byte 수에 비례해 증가한다.

02 · CONTRACT

공통 계약과 architecture 구현

architecture핵심 mechanism실패 형태확인할 상태
arm64rt_sigframe과 extensible context record chainrecord size overflow, duplicate magic, 잘못된 VL 또는 PSTATE privilege bit를 허용하면 out-of-bounds read와 privilege corruption 위험이 생긴다.SP 16-byte alignment, frame record magic/size, FPSIMD/SVE VL, PC/LR, PSTATE와 sigmask를 확인한다.
x86-64ucontext, sigcontext와 XSAVE fpstate pointernoncanonical RIP/RSP, 잘못된 CS/SS, forged xfeatures 또는 shadow-stack token 불일치는 sigreturn fault로 막아야 한다.frame rsp, uc_mcontext, fpstate magic/size/xfeatures, CS/SS/RFLAGS, CET SSP와 restorer 주소를 본다.
RISC-Vrt_sigframe의 sigcontext와 vector state datap pointerVLEN 기반 size overflow, vector datap pointer가 frame 밖을 가리키는 경우 또는 SSTATUS.SPP 조작을 검증하지 않으면 memory corruption과 privilege 문제가 생긴다.EPC/SP/RA, a0-a2, sigmask, fp fcsr, vector vlenb/vl/vtype/vstart와 SSTATUS를 확인한다.

03 · DIAGRAMS

세 그림으로 먼저 읽기

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

arm64

mechanism
rt_sigframe과 extensible context record chain
state
sigcontext 뒤 FPSIMD, ESR, SVE, ZA, ZT 같은 _aarch64_ctx record를 magic/size로 연결한다. setup_rt_frame()은 VL에 따라 frame 크기를 계산하고 handler x0-x2와 PC/LR/SP를 설정한다.
checkpoint
SP 16-byte alignment, frame record magic/size, FPSIMD/SVE VL, PC/LR, PSTATE와 sigmask를 확인한다.

x86-64

mechanism
ucontext, sigcontext와 XSAVE fpstate pointer
state
x64 rt frame은 pretcode, ucontext, siginfo와 fpstate area를 user stack에 둔다. x64_setup_rt_frame()은 pt_regs를 sigcontext에 복사하고 handler RDI/RSI/RDX와 RIP/RSP를 설정한다.
checkpoint
frame rsp, uc_mcontext, fpstate magic/size/xfeatures, CS/SS/RFLAGS, CET SSP와 restorer 주소를 본다.

RISC-V

mechanism
rt_sigframe의 sigcontext와 vector state datap pointer
state
integer pt_regs와 FP state를 frame에 저장하고 vector 사용 task는 vstate header와 datap 영역을 정렬해 추가한다. handler a0-a2와 RA trampoline, EPC/SP를 새 값으로 바꾼다.
checkpoint
EPC/SP/RA, a0-a2, sigmask, fp fcsr, vector vlenb/vl/vtype/vstart와 SSTATUS를 확인한다.
그림 2. 공통 kernel과 architecture hook의 소유권공통 정책이 hardware state를 직접 소유하지 않는 경계를 표시합니다.
Linux common contractexit-to-user path가 deliverable signal을 고르고 architecture setup_rt_frame()이 user stack 또는 altstack에 ABI frame을 만든다. handler return trampoline은 rt_sigreturn syscall로 들어와 frame을 copy/validate한 뒤 pt_regs와 signal mask를 복원한다.
arm64rt_sigframe과 extensible context record chainuser frame write를 모두 성공시킨 뒤 pt_regs를 handler context로 바꿔야 하며 MTE tag와 altstack alignment도 지켜야 한다.
x86-64ucontext, sigcontext와 XSAVE fpstate pointerred-zone 회피, 16-byte stack alignment, CET shadow stack와 XSAVE frame validation을 함께 적용한다.
RISC-Vrt_sigframe의 sigcontext와 vector state datap pointervector state size 계산과 user pointer write가 완료된 뒤 pt_regs를 publish하고 sigreturn에서 SSTATUS의 허용 bit만 복원한다.
lifetime boundarysignal frame은 user stack memory에 있으며 nested signal마다 아래쪽으로 추가된다. altstack overflow, stack mapping 변경과 handler가 frame을 덮는 경우를 모두 오류로 처리해야 한다.
그림 3. publication과 관찰 순서state를 준비한 뒤 architecture ordering을 거쳐 관찰 가능한 checkpoint가 됩니다.
arm64state 준비user frame write를 모두 성공시킨 뒤 pt_regs를 handler context로 바꿔야 하며 MTE tag와 altstack alignment도 지켜야 한다.관찰: SP 16-byte alignment, frame record magic/size, FPSIMD/SVE VL, PC/LR, PSTATE와 sigmask를 확인한다.
x86-64state 준비red-zone 회피, 16-byte stack alignment, CET shadow stack와 XSAVE frame validation을 함께 적용한다.관찰: frame rsp, uc_mcontext, fpstate magic/size/xfeatures, CS/SS/RFLAGS, CET SSP와 restorer 주소를 본다.
RISC-Vstate 준비vector state size 계산과 user pointer write가 완료된 뒤 pt_regs를 publish하고 sigreturn에서 SSTATUS의 허용 bit만 복원한다.관찰: EPC/SP/RA, a0-a2, sigmask, fp fcsr, vector vlenb/vl/vtype/vstart와 SSTATUS를 확인한다.

04 · SOURCE

Linux 6.18.37 원본 코드와 줄별 설명

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

arm64 · Linux 6.18.37

rt_sigframe과 extensible context record chain

sigcontext 뒤 FPSIMD, ESR, SVE, ZA, ZT 같은 _aarch64_ctx record를 magic/size로 연결한다. setup_rt_frame()은 VL에 따라 frame 크기를 계산하고 handler x0-x2와 PC/LR/SP를 설정한다.

원본 코드: arch/arm64/kernel/signal.c:1529-1609

1529		task_smstop_sm(current);
1530		current->thread.svcr &= ~SVCR_ZA_MASK;
1531		write_sysreg_s(0, SYS_TPIDR2_EL0);
1532	}
1533 
1534	return 0;
1535}
1536 
1537static int setup_rt_frame(int usig, struct ksignal *ksig, sigset_t *set,
1538			  struct pt_regs *regs)
1539{
1540	struct rt_sigframe_user_layout user;
1541	struct rt_sigframe __user *frame;
1542	struct user_access_state ua_state = {};
1543	int err = 0;
1544 
1545	fpsimd_save_and_flush_current_state();
1546 
1547	if (get_sigframe(&user, ksig, regs))
1548		return 1;
1549 
1550	save_reset_user_access_state(&ua_state);
1551	frame = user.sigframe;
1552 
1553	__put_user_error(0, &frame->uc.uc_flags, err);
1554	__put_user_error(NULL, &frame->uc.uc_link, err);
1555 
1556	err |= __save_altstack(&frame->uc.uc_stack, regs->sp);
1557	err |= setup_sigframe(&user, regs, set, &ua_state);
1558	if (ksig->ka.sa.sa_flags & SA_SIGINFO)
1559		err |= copy_siginfo_to_user(&frame->info, &ksig->info);
1560 
1561	if (err == 0)
1562		err = setup_return(regs, ksig, &user, usig);
1563 
1564	/*
1565	 * We must not fail if setup_return() succeeded - see comment at the
1566	 * beginning of setup_return().
1567	 */
1568 
1569	if (err == 0)
1570		set_handler_user_access_state();
1571	else
1572		restore_user_access_state(&ua_state);
1573 
1574	return err;
1575}
1576 
1577static void setup_restart_syscall(struct pt_regs *regs)
1578{
1579	if (is_compat_task())
1580		compat_setup_restart_syscall(regs);
1581	else
1582		regs->regs[8] = __NR_restart_syscall;
1583}
1584 
1585/*
1586 * OK, we're invoking a handler
1587 */
1588static void handle_signal(struct ksignal *ksig, struct pt_regs *regs)
1589{
1590	sigset_t *oldset = sigmask_to_save();
1591	int usig = ksig->sig;
1592	int ret;
1593 
1594	rseq_signal_deliver(ksig, regs);
1595 
1596	/*
1597	 * Set up the stack frame
1598	 */
1599	if (is_compat_task()) {
1600		if (ksig->ka.sa.sa_flags & SA_SIGINFO)
1601			ret = compat_setup_rt_frame(usig, ksig, oldset, regs);
1602		else
1603			ret = compat_setup_frame(usig, ksig, oldset, regs);
1604	} else {
1605		ret = setup_rt_frame(usig, ksig, oldset, regs);
1606	}
1607 
1608	/*
1609	 * Check that the resulting registers are actually sane.

라인 바이 라인 주석

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

L1529 task_smstop_sm(current);

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

L1530 current->thread.svcr &= ~SVCR_ZA_MASK;

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

L1531 write_sysreg_s(0, SYS_TPIDR2_EL0);

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

L1532 }

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

L1533(blank)

빈 줄은 arm64 Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1534 return 0;

이 함수가 Signal frame과 user context 복원 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L1535}

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

L1536(blank)

빈 줄은 arm64 Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1537static int setup_rt_frame(int usig, struct ksignal *ksig, sigset_t *set,

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Signal frame과 user context 복원의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L1538 struct pt_regs *regs)

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Signal frame과 user context 복원의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L1539{

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

L1540 struct rt_sigframe_user_layout user;

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

L1541 struct rt_sigframe __user *frame;

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

L1542 struct user_access_state ua_state = {};

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

L1543 int err = 0;

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

L1544(blank)

빈 줄은 arm64 Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1545 fpsimd_save_and_flush_current_state();

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

L1546(blank)

빈 줄은 arm64 Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1547 if (get_sigframe(&user, ksig, regs))

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

L1548 return 1;

이 함수가 Signal frame과 user context 복원 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L1549(blank)

빈 줄은 arm64 Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1550 save_reset_user_access_state(&ua_state);

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

L1551 frame = user.sigframe;

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

L1552(blank)

빈 줄은 arm64 Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1553 __put_user_error(0, &frame->uc.uc_flags, err);

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

L1554 __put_user_error(NULL, &frame->uc.uc_link, err);

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

L1555(blank)

빈 줄은 arm64 Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1556 err |= __save_altstack(&frame->uc.uc_stack, regs->sp);

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

L1557 err |= setup_sigframe(&user, regs, set, &ua_state);

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

L1558 if (ksig->ka.sa.sa_flags & SA_SIGINFO)

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

L1559 err |= copy_siginfo_to_user(&frame->info, &ksig->info);

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

L1560(blank)

빈 줄은 arm64 Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1561 if (err == 0)

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

L1562 err = setup_return(regs, ksig, &user, usig);

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

L1563(blank)

빈 줄은 arm64 Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1564 /*

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

L1565 * We must not fail if setup_return() succeeded - see comment at the

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

L1566 * beginning of setup_return().

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

L1567 */

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

L1568(blank)

빈 줄은 arm64 Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1569 if (err == 0)

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

L1570 set_handler_user_access_state();

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

L1571 else

앞 조건이 성립하지 않았을 때의 대체 경로다. fast path와 같은 ownership, ordering과 반환 계약을 제공해야 한다.

L1572 restore_user_access_state(&ua_state);

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

L1573(blank)

빈 줄은 arm64 Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1574 return err;

이 함수가 Signal frame과 user context 복원 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L1575}

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

L1576(blank)

빈 줄은 arm64 Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1577static void setup_restart_syscall(struct pt_regs *regs)

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

L1578{

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

L1579 if (is_compat_task())

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

L1580 compat_setup_restart_syscall(regs);

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

L1581 else

앞 조건이 성립하지 않았을 때의 대체 경로다. fast path와 같은 ownership, ordering과 반환 계약을 제공해야 한다.

L1582 regs->regs[8] = __NR_restart_syscall;

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

L1583}

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

L1584(blank)

빈 줄은 arm64 Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1585/*

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

L1586 * OK, we're invoking a handler

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

L1587 */

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

L1588static void handle_signal(struct ksignal *ksig, struct pt_regs *regs)

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

L1589{

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

L1590 sigset_t *oldset = sigmask_to_save();

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

L1591 int usig = ksig->sig;

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

L1592 int ret;

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

L1593(blank)

빈 줄은 arm64 Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1594 rseq_signal_deliver(ksig, regs);

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

L1595(blank)

빈 줄은 arm64 Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1596 /*

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

L1597 * Set up the stack frame

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

L1598 */

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

L1599 if (is_compat_task()) {

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

L1600 if (ksig->ka.sa.sa_flags & SA_SIGINFO)

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

L1601 ret = compat_setup_rt_frame(usig, ksig, oldset, regs);

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

L1602 else

앞 조건이 성립하지 않았을 때의 대체 경로다. fast path와 같은 ownership, ordering과 반환 계약을 제공해야 한다.

L1603 ret = compat_setup_frame(usig, ksig, oldset, regs);

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

L1604 } else {

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Signal frame과 user context 복원의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L1605 ret = setup_rt_frame(usig, ksig, oldset, regs);

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

L1606 }

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

L1607(blank)

빈 줄은 arm64 Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1608 /*

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

L1609 * Check that the resulting registers are actually sane.

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

x86-64 · Linux 6.18.37

ucontext, sigcontext와 XSAVE fpstate pointer

x64 rt frame은 pretcode, ucontext, siginfo와 fpstate area를 user stack에 둔다. x64_setup_rt_frame()은 pt_regs를 sigcontext에 복사하고 handler RDI/RSI/RDX와 RIP/RSP를 설정한다.

원본 코드: arch/x86/kernel/signal_64.c:156-228

156		flags = UC_SIGCONTEXT_SS;
157 
158	if (likely(user_64bit_mode(regs)))
159		flags |= UC_STRICT_RESTORE_SS;
160 
161	return flags;
162}
163 
164int x64_setup_rt_frame(struct ksignal *ksig, struct pt_regs *regs)
165{
166	sigset_t *set = sigmask_to_save();
167	struct rt_sigframe __user *frame;
168	void __user *fp = NULL;
169	unsigned long uc_flags;
170 
171	/* x86-64 should always use SA_RESTORER. */
172	if (!(ksig->ka.sa.sa_flags & SA_RESTORER))
173		return -EFAULT;
174 
175	frame = get_sigframe(ksig, regs, sizeof(struct rt_sigframe), &fp);
176	uc_flags = frame_uc_flags(regs);
177 
178	if (!user_access_begin(frame, sizeof(*frame)))
179		return -EFAULT;
180 
181	/* Create the ucontext.  */
182	unsafe_put_user(uc_flags, &frame->uc.uc_flags, Efault);
183	unsafe_put_user(0, &frame->uc.uc_link, Efault);
184	unsafe_save_altstack(&frame->uc.uc_stack, regs->sp, Efault);
185 
186	/* Set up to return from userspace.  If provided, use a stub
187	   already in userspace.  */
188	unsafe_put_user(ksig->ka.sa.sa_restorer, &frame->pretcode, Efault);
189	unsafe_put_sigcontext(&frame->uc.uc_mcontext, fp, regs, set, Efault);
190	unsafe_put_sigmask(set, frame, Efault);
191	user_access_end();
192 
193	if (ksig->ka.sa.sa_flags & SA_SIGINFO) {
194		if (copy_siginfo_to_user(&frame->info, &ksig->info))
195			return -EFAULT;
196	}
197 
198	if (setup_signal_shadow_stack(ksig))
199		return -EFAULT;
200 
201	/* Set up registers for signal handler */
202	regs->di = ksig->sig;
203	/* In case the signal handler was declared without prototypes */
204	regs->ax = 0;
205 
206	/* This also works for non SA_SIGINFO handlers because they expect the
207	   next argument after the signal number on the stack. */
208	regs->si = (unsigned long)&frame->info;
209	regs->dx = (unsigned long)&frame->uc;
210	regs->ip = (unsigned long) ksig->ka.sa.sa_handler;
211 
212	regs->sp = (unsigned long)frame;
213 
214	/*
215	 * Set up the CS and SS registers to run signal handlers in
216	 * 64-bit mode, even if the handler happens to be interrupting
217	 * 32-bit or 16-bit code.
218	 *
219	 * SS is subtle.  In 64-bit mode, we don't need any particular
220	 * SS descriptor, but we do need SS to be valid.  It's possible
221	 * that the old SS is entirely bogus -- this can happen if the
222	 * signal we're trying to deliver is #GP or #SS caused by a bad
223	 * SS value.  We also have a compatibility issue here: DOSEMU
224	 * relies on the contents of the SS register indicating the
225	 * SS value at the time of the signal, even though that code in
226	 * DOSEMU predates sigreturn's ability to restore SS.  (DOSEMU
227	 * avoids relying on sigreturn to restore SS; instead it uses
228	 * a trampoline.)  So we do our best: if the old SS was valid,

라인 바이 라인 주석

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

L156 flags = UC_SIGCONTEXT_SS;

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

L157(blank)

빈 줄은 x86-64 Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L158 if (likely(user_64bit_mode(regs)))

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

L159 flags |= UC_STRICT_RESTORE_SS;

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

L160(blank)

빈 줄은 x86-64 Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L161 return flags;

이 함수가 Signal frame과 user context 복원 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L162}

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

L163(blank)

빈 줄은 x86-64 Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L164int x64_setup_rt_frame(struct ksignal *ksig, struct pt_regs *regs)

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

L165{

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

L166 sigset_t *set = sigmask_to_save();

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

L167 struct rt_sigframe __user *frame;

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

L168 void __user *fp = NULL;

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

L169 unsigned long uc_flags;

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

L170(blank)

빈 줄은 x86-64 Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L171 /* x86-64 should always use SA_RESTORER. */

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

L172 if (!(ksig->ka.sa.sa_flags & SA_RESTORER))

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

L173 return -EFAULT;

이 함수가 Signal frame과 user context 복원 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L174(blank)

빈 줄은 x86-64 Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L175 frame = get_sigframe(ksig, regs, sizeof(struct rt_sigframe), &fp);

red-zone, altstack, fpstate 크기를 반영해 새 user RSP를 계산한다.

L176 uc_flags = frame_uc_flags(regs);

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

L177(blank)

빈 줄은 x86-64 Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L178 if (!user_access_begin(frame, sizeof(*frame)))

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

L179 return -EFAULT;

이 함수가 Signal frame과 user context 복원 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L180(blank)

빈 줄은 x86-64 Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L181 /* Create the ucontext. */

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

L182 unsafe_put_user(uc_flags, &frame->uc.uc_flags, Efault);

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

L183 unsafe_put_user(0, &frame->uc.uc_link, Efault);

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

L184 unsafe_save_altstack(&frame->uc.uc_stack, regs->sp, Efault);

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

L185(blank)

빈 줄은 x86-64 Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L186 /* Set up to return from userspace. If provided, use a stub

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

L187 already in userspace. */

이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Signal frame과 user context 복원의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L188 unsafe_put_user(ksig->ka.sa.sa_restorer, &frame->pretcode, Efault);

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

L189 unsafe_put_sigcontext(&frame->uc.uc_mcontext, fp, regs, set, Efault);

현재 integer register와 trap metadata를 user-visible sigcontext로 복사한다.

L190 unsafe_put_sigmask(set, frame, Efault);

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

L191 user_access_end();

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

L192(blank)

빈 줄은 x86-64 Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L193 if (ksig->ka.sa.sa_flags & SA_SIGINFO) {

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

L194 if (copy_siginfo_to_user(&frame->info, &ksig->info))

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

L195 return -EFAULT;

이 함수가 Signal frame과 user context 복원 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L196 }

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

L197(blank)

빈 줄은 x86-64 Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L198 if (setup_signal_shadow_stack(ksig))

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

L199 return -EFAULT;

이 함수가 Signal frame과 user context 복원 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L200(blank)

빈 줄은 x86-64 Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L201 /* Set up registers for signal handler */

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

L202 regs->di = ksig->sig;

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

L203 /* In case the signal handler was declared without prototypes */

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

L204 regs->ax = 0;

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

L205(blank)

빈 줄은 x86-64 Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L206 /* This also works for non SA_SIGINFO handlers because they expect the

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

L207 next argument after the signal number on the stack. */

이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Signal frame과 user context 복원의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L208 regs->si = (unsigned long)&frame->info;

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

L209 regs->dx = (unsigned long)&frame->uc;

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

L210 regs->ip = (unsigned long) ksig->ka.sa.sa_handler;

IRET/SYSRET 이후 signal handler를 실행하도록 saved instruction pointer를 바꾼다.

L211(blank)

빈 줄은 x86-64 Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L212 regs->sp = (unsigned long)frame;

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

L213(blank)

빈 줄은 x86-64 Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L214 /*

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

L215 * Set up the CS and SS registers to run signal handlers in

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

L216 * 64-bit mode, even if the handler happens to be interrupting

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

L217 * 32-bit or 16-bit code.

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

L218 *

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

L219 * SS is subtle. In 64-bit mode, we don't need any particular

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

L220 * SS descriptor, but we do need SS to be valid. It's possible

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

L221 * that the old SS is entirely bogus -- this can happen if the

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

L222 * signal we're trying to deliver is #GP or #SS caused by a bad

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

L223 * SS value. We also have a compatibility issue here: DOSEMU

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

L224 * relies on the contents of the SS register indicating the

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

L225 * SS value at the time of the signal, even though that code in

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

L226 * DOSEMU predates sigreturn's ability to restore SS. (DOSEMU

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

L227 * avoids relying on sigreturn to restore SS; instead it uses

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

L228 * a trampoline.) So we do our best: if the old SS was valid,

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

RISC-V · Linux 6.18.37

rt_sigframe의 sigcontext와 vector state datap pointer

integer pt_regs와 FP state를 frame에 저장하고 vector 사용 task는 vstate header와 datap 영역을 정렬해 추가한다. handler a0-a2와 RA trampoline, EPC/SP를 새 값으로 바꾼다.

원본 코드: arch/riscv/kernel/signal.c:307-381

307	sp = sigsp(sp, ksig) - framesize;
308 
309	/* Align the stack frame. */
310	sp &= ~0xfUL;
311 
312	return (void __user *)sp;
313}
314 
315static int setup_rt_frame(struct ksignal *ksig, sigset_t *set,
316	struct pt_regs *regs)
317{
318	struct rt_sigframe __user *frame;
319	long err = 0;
320	unsigned long __maybe_unused addr;
321	size_t frame_size = get_rt_frame_size(false);
322 
323	frame = get_sigframe(ksig, regs, frame_size);
324	if (!access_ok(frame, frame_size))
325		return -EFAULT;
326 
327	err |= copy_siginfo_to_user(&frame->info, &ksig->info);
328 
329	/* Create the ucontext. */
330	err |= __put_user(0, &frame->uc.uc_flags);
331	err |= __put_user(NULL, &frame->uc.uc_link);
332	err |= __save_altstack(&frame->uc.uc_stack, regs->sp);
333	err |= setup_sigcontext(frame, regs);
334	err |= __copy_to_user(&frame->uc.uc_sigmask, set, sizeof(*set));
335	if (err)
336		return -EFAULT;
337 
338	/* Set up to return from userspace. */
339#ifdef CONFIG_MMU
340	regs->ra = (unsigned long)VDSO_SYMBOL(
341		current->mm->context.vdso, rt_sigreturn);
342#else
343	/*
344	 * For the nommu case we don't have a VDSO.  Instead we push two
345	 * instructions to call the rt_sigreturn syscall onto the user stack.
346	 */
347	if (copy_to_user(&frame->sigreturn_code, __user_rt_sigreturn,
348			 sizeof(frame->sigreturn_code)))
349		return -EFAULT;
350 
351	addr = (unsigned long)&frame->sigreturn_code;
352	/* Make sure the two instructions are pushed to icache. */
353	flush_icache_range(addr, addr + sizeof(frame->sigreturn_code));
354 
355	regs->ra = addr;
356#endif /* CONFIG_MMU */
357 
358	/*
359	 * Set up registers for signal handler.
360	 * Registers that we don't modify keep the value they had from
361	 * user-space at the time we took the signal.
362	 * We always pass siginfo and mcontext, regardless of SA_SIGINFO,
363	 * since some things rely on this (e.g. glibc's debug/segfault.c).
364	 */
365	regs->epc = (unsigned long)ksig->ka.sa.sa_handler;
366	regs->sp = (unsigned long)frame;
367	regs->a0 = ksig->sig;                     /* a0: signal number */
368	regs->a1 = (unsigned long)(&frame->info); /* a1: siginfo pointer */
369	regs->a2 = (unsigned long)(&frame->uc);   /* a2: ucontext pointer */
370 
371#if DEBUG_SIG
372	pr_info("SIG deliver (%s:%d): sig=%d pc=%p ra=%p sp=%p\n",
373		current->comm, task_pid_nr(current), ksig->sig,
374		(void *)regs->epc, (void *)regs->ra, frame);
375#endif
376 
377	return 0;
378}
379 
380static void handle_signal(struct ksignal *ksig, struct pt_regs *regs)
381{

라인 바이 라인 주석

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

L307 sp = sigsp(sp, ksig) - framesize;

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

L308(blank)

빈 줄은 RISC-V Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L309 /* Align the stack frame. */

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

L310 sp &= ~0xfUL;

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

L311(blank)

빈 줄은 RISC-V Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L312 return (void __user *)sp;

이 함수가 Signal frame과 user context 복원 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L313}

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

L314(blank)

빈 줄은 RISC-V Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L315static int setup_rt_frame(struct ksignal *ksig, sigset_t *set,

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Signal frame과 user context 복원의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L316 struct pt_regs *regs)

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Signal frame과 user context 복원의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L317{

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

L318 struct rt_sigframe __user *frame;

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

L319 long err = 0;

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

L320 unsigned long __maybe_unused addr;

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

L321 size_t frame_size = get_rt_frame_size(false);

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

L322(blank)

빈 줄은 RISC-V Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L323 frame = get_sigframe(ksig, regs, frame_size);

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

L324 if (!access_ok(frame, frame_size))

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

L325 return -EFAULT;

이 함수가 Signal frame과 user context 복원 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L326(blank)

빈 줄은 RISC-V Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L327 err |= copy_siginfo_to_user(&frame->info, &ksig->info);

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

L328(blank)

빈 줄은 RISC-V Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L329 /* Create the ucontext. */

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

L330 err |= __put_user(0, &frame->uc.uc_flags);

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

L331 err |= __put_user(NULL, &frame->uc.uc_link);

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

L332 err |= __save_altstack(&frame->uc.uc_stack, regs->sp);

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

L333 err |= setup_sigcontext(frame, regs);

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

L334 err |= __copy_to_user(&frame->uc.uc_sigmask, set, sizeof(*set));

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

L335 if (err)

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

L336 return -EFAULT;

이 함수가 Signal frame과 user context 복원 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L337(blank)

빈 줄은 RISC-V Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L338 /* Set up to return from userspace. */

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

L339#ifdef CONFIG_MMU

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

L340 regs->ra = (unsigned long)VDSO_SYMBOL(

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

L341 current->mm->context.vdso, rt_sigreturn);

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Signal frame과 user context 복원의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L342#else

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

L343 /*

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

L344 * For the nommu case we don't have a VDSO. Instead we push two

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

L345 * instructions to call the rt_sigreturn syscall onto the user stack.

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

L346 */

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

L347 if (copy_to_user(&frame->sigreturn_code, __user_rt_sigreturn,

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

L348 sizeof(frame->sigreturn_code)))

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

L349 return -EFAULT;

이 함수가 Signal frame과 user context 복원 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L350(blank)

빈 줄은 RISC-V Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L351 addr = (unsigned long)&frame->sigreturn_code;

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

L352 /* Make sure the two instructions are pushed to icache. */

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

L353 flush_icache_range(addr, addr + sizeof(frame->sigreturn_code));

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

L354(blank)

빈 줄은 RISC-V Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L355 regs->ra = addr;

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

L356#endif /* CONFIG_MMU */

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

L357(blank)

빈 줄은 RISC-V Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L358 /*

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

L359 * Set up registers for signal handler.

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

L360 * Registers that we don't modify keep the value they had from

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

L361 * user-space at the time we took the signal.

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

L362 * We always pass siginfo and mcontext, regardless of SA_SIGINFO,

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

L363 * since some things rely on this (e.g. glibc's debug/segfault.c).

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

L364 */

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

L365 regs->epc = (unsigned long)ksig->ka.sa.sa_handler;

sret 뒤 signal handler에서 시작하도록 saved EPC를 교체한다.

L366 regs->sp = (unsigned long)frame;

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

L367 regs->a0 = ksig->sig; /* a0: signal number */

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

L368 regs->a1 = (unsigned long)(&frame->info); /* a1: siginfo pointer */

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

L369 regs->a2 = (unsigned long)(&frame->uc); /* a2: ucontext pointer */

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

L370(blank)

빈 줄은 RISC-V Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L371#if DEBUG_SIG

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

L372 pr_info("SIG deliver (%s:%d): sig=%d pc=%p ra=%p sp=%p\n",

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

L373 current->comm, task_pid_nr(current), ksig->sig,

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Signal frame과 user context 복원의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L374 (void *)regs->epc, (void *)regs->ra, frame);

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Signal frame과 user context 복원의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L375#endif

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

L376(blank)

빈 줄은 RISC-V Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L377 return 0;

이 함수가 Signal frame과 user context 복원 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L378}

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

L379(blank)

빈 줄은 RISC-V Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L380static void handle_signal(struct ksignal *ksig, struct pt_regs *regs)

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

L381{

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

05 · WORKED EXAMPLE

숫자로 검산하기

01

가변 vector state가 signal frame에 미치는 영향

기본 frame 1KiB, vector state 4KiB, altstack 8KiB에서 signal 두 개가 중첩된다고 가정한다.

  1. first정렬과 header를 제외해도 첫 frame은 약 5KiB를 사용한다.
  2. nested두 번째 frame도 같은 state를 저장하면 누적 약 10KiB로 8KiB altstack을 넘는다.
  3. failureget_sigframe 또는 user write가 실패하면 kernel은 SIGSEGV 처리 경로로 가며 손상된 일부 frame을 복원에 사용하지 않는다.
  4. mitigation실제 최대 VL/XSAVE feature를 기준으로 sigaltstack 크기를 산정하고 guard page를 둔다.

결론MINSIGSTKSZ 같은 고정 상수만으로 scalable state의 worst case를 보장할 수 없는 이유를 frame byte 수로 확인할 수 있다.

06 · DEEP DIVE

경계별 상세 분석

01

공통 kernel core와 architecture hook의 경계

exit-to-user path가 deliverable signal을 고르고 architecture setup_rt_frame()이 user stack 또는 altstack에 ABI frame을 만든다. handler return trampoline은 rt_sigreturn syscall로 들어와 frame을 copy/validate한 뒤 pt_regs와 signal mask를 복원한다.

kernel-owned pt_regs에서 user-writable signal frame으로 상태를 복사하는 순간 신뢰 경계가 바뀐다. sigreturn은 user가 수정한 frame을 읽으므로 privilege bit, canonical address, reserved field를 반드시 검증한다.

02

arm64: rt_sigframe과 extensible context record chain

sigcontext 뒤 FPSIMD, ESR, SVE, ZA, ZT 같은 _aarch64_ctx record를 magic/size로 연결한다. setup_rt_frame()은 VL에 따라 frame 크기를 계산하고 handler x0-x2와 PC/LR/SP를 설정한다.

user frame write를 모두 성공시킨 뒤 pt_regs를 handler context로 바꿔야 하며 MTE tag와 altstack alignment도 지켜야 한다. 디버깅할 때는 SP 16-byte alignment, frame record magic/size, FPSIMD/SVE VL, PC/LR, PSTATE와 sigmask를 확인한다.

03

x86-64: ucontext, sigcontext와 XSAVE fpstate pointer

x64 rt frame은 pretcode, ucontext, siginfo와 fpstate area를 user stack에 둔다. x64_setup_rt_frame()은 pt_regs를 sigcontext에 복사하고 handler RDI/RSI/RDX와 RIP/RSP를 설정한다.

red-zone 회피, 16-byte stack alignment, CET shadow stack와 XSAVE frame validation을 함께 적용한다. 디버깅할 때는 frame rsp, uc_mcontext, fpstate magic/size/xfeatures, CS/SS/RFLAGS, CET SSP와 restorer 주소를 본다.

04

RISC-V: rt_sigframe의 sigcontext와 vector state datap pointer

integer pt_regs와 FP state를 frame에 저장하고 vector 사용 task는 vstate header와 datap 영역을 정렬해 추가한다. handler a0-a2와 RA trampoline, EPC/SP를 새 값으로 바꾼다.

vector state size 계산과 user pointer write가 완료된 뒤 pt_regs를 publish하고 sigreturn에서 SSTATUS의 허용 bit만 복원한다. 디버깅할 때는 EPC/SP/RA, a0-a2, sigmask, fp fcsr, vector vlenb/vl/vtype/vstart와 SSTATUS를 확인한다.

05

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

signal frame은 user stack memory에 있으며 nested signal마다 아래쪽으로 추가된다. altstack overflow, stack mapping 변경과 handler가 frame을 덮는 경우를 모두 오류로 처리해야 한다.

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

06

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

fixed integer context 외에 SVE/SME, XSAVE/AMX, RISC-V vector처럼 가변 크기 state를 frame에 materialize하면 signal delivery와 sigreturn 시간이 state byte 수에 비례해 증가한다.

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

07 · FAILURE

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

분류관찰되는 결과첫 확인값
arm64record size overflow, duplicate magic, 잘못된 VL 또는 PSTATE privilege bit를 허용하면 out-of-bounds read와 privilege corruption 위험이 생긴다.SP 16-byte alignment, frame record magic/size, FPSIMD/SVE VL, PC/LR, PSTATE와 sigmask를 확인한다.
x86-64noncanonical RIP/RSP, 잘못된 CS/SS, forged xfeatures 또는 shadow-stack token 불일치는 sigreturn fault로 막아야 한다.frame rsp, uc_mcontext, fpstate magic/size/xfeatures, CS/SS/RFLAGS, CET SSP와 restorer 주소를 본다.
RISC-VVLEN 기반 size overflow, vector datap pointer가 frame 밖을 가리키는 경우 또는 SSTATUS.SPP 조작을 검증하지 않으면 memory corruption과 privilege 문제가 생긴다.EPC/SP/RA, a0-a2, sigmask, fp fcsr, vector vlenb/vl/vtype/vstart와 SSTATUS를 확인한다.

08 · LAB

재현과 계측 절차

  1. handler에서 ucontext를 출력하고 GDB pt_regs와 비교해 signal delivery가 바꾼 PC/SP/argument를 확인한다.
  2. vector state를 dirty하게 만든 뒤 altstack 크기를 줄여 frame allocation 실패 경로를 재현한다.
  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 원문을 기준으로 하며, 분석 문장은 해당 코드의 실행 조건과 상태 경계를 설명합니다.