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를 반드시 검증한다.
02 · CONTRACT
공통 계약과 architecture 구현
| architecture | 핵심 mechanism | 실패 형태 | 확인할 상태 |
|---|---|---|---|
| arm64 | rt_sigframe과 extensible context record chain | record 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-64 | ucontext, sigcontext와 XSAVE fpstate pointer | noncanonical 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-V | rt_sigframe의 sigcontext와 vector state datap pointer | VLEN 기반 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
세 그림으로 먼저 읽기
arm64
- mechanism
- rt_sigframe과 extensible context record chain
- state
- sigcontext 뒤 FPSIMD, ESR, SVE, ZA, ZT 같은
_aarch64_ctxrecord를 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를 확인한다.
setup_rt_frame()이 user stack 또는 altstack에 ABI frame을 만든다. handler return trampoline은 rt_sigreturn syscall로 들어와 frame을 copy/validate한 뒤 pt_regs와 signal mask를 복원한다.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개 줄에 각각 설명을 붙였습니다.
task_smstop_sm(current);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
current->thread.svcr &= ~SVCR_ZA_MASK;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
write_sysreg_s(0, SYS_TPIDR2_EL0);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 arm64 Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
return 0;이 함수가 Signal frame과 user context 복원 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 arm64 Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
static int setup_rt_frame(int usig, struct ksignal *ksig, sigset_t *set,이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Signal frame과 user context 복원의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
struct pt_regs *regs)이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Signal frame과 user context 복원의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
struct rt_sigframe_user_layout user;선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
struct rt_sigframe __user *frame;선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
struct user_access_state ua_state = {};계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
int err = 0;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
(blank)빈 줄은 arm64 Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
fpsimd_save_and_flush_current_state();helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 arm64 Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
if (get_sigframe(&user, ksig, regs))이 조건이 arm64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
return 1;이 함수가 Signal frame과 user context 복원 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
(blank)빈 줄은 arm64 Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
save_reset_user_access_state(&ua_state);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
frame = user.sigframe;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
(blank)빈 줄은 arm64 Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
__put_user_error(0, &frame->uc.uc_flags, err);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
__put_user_error(NULL, &frame->uc.uc_link, err);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 arm64 Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
err |= __save_altstack(&frame->uc.uc_stack, regs->sp);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
err |= setup_sigframe(&user, regs, set, &ua_state);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
if (ksig->ka.sa.sa_flags & SA_SIGINFO)이 조건이 arm64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
err |= copy_siginfo_to_user(&frame->info, &ksig->info);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 arm64 Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
if (err == 0)이 조건이 arm64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
err = setup_return(regs, ksig, &user, usig);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 arm64 Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* We must not fail if setup_return() succeeded - see comment at theLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* beginning of setup_return().Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
(blank)빈 줄은 arm64 Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
if (err == 0)이 조건이 arm64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
set_handler_user_access_state();helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
else앞 조건이 성립하지 않았을 때의 대체 경로다. fast path와 같은 ownership, ordering과 반환 계약을 제공해야 한다.
restore_user_access_state(&ua_state);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 arm64 Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
return err;이 함수가 Signal frame과 user context 복원 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 arm64 Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
static void setup_restart_syscall(struct pt_regs *regs)이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
if (is_compat_task())이 조건이 arm64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
compat_setup_restart_syscall(regs);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
else앞 조건이 성립하지 않았을 때의 대체 경로다. fast path와 같은 ownership, ordering과 반환 계약을 제공해야 한다.
regs->regs[8] = __NR_restart_syscall;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 arm64 Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* OK, we're invoking a handlerLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
static void handle_signal(struct ksignal *ksig, struct pt_regs *regs)이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
sigset_t *oldset = sigmask_to_save();helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
int usig = ksig->sig;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
int ret;저장된 실행 문맥으로 돌아가는 제어 이전이다. PC뿐 아니라 privilege, interrupt mask, stack과 architecture status가 함께 복원된다.
(blank)빈 줄은 arm64 Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
rseq_signal_deliver(ksig, regs);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 arm64 Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* Set up the stack frameLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
if (is_compat_task()) {이 조건이 arm64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
if (ksig->ka.sa.sa_flags & SA_SIGINFO)이 조건이 arm64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
ret = compat_setup_rt_frame(usig, ksig, oldset, regs);저장된 실행 문맥으로 돌아가는 제어 이전이다. PC뿐 아니라 privilege, interrupt mask, stack과 architecture status가 함께 복원된다.
else앞 조건이 성립하지 않았을 때의 대체 경로다. fast path와 같은 ownership, ordering과 반환 계약을 제공해야 한다.
ret = compat_setup_frame(usig, ksig, oldset, regs);저장된 실행 문맥으로 돌아가는 제어 이전이다. PC뿐 아니라 privilege, interrupt mask, stack과 architecture status가 함께 복원된다.
} else {이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Signal frame과 user context 복원의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
ret = setup_rt_frame(usig, ksig, oldset, regs);저장된 실행 문맥으로 돌아가는 제어 이전이다. PC뿐 아니라 privilege, interrupt mask, stack과 architecture status가 함께 복원된다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 arm64 Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* 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개 줄에 각각 설명을 붙였습니다.
flags = UC_SIGCONTEXT_SS;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
(blank)빈 줄은 x86-64 Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
if (likely(user_64bit_mode(regs)))이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
flags |= UC_STRICT_RESTORE_SS;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
(blank)빈 줄은 x86-64 Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
return flags;이 함수가 Signal frame과 user context 복원 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 x86-64 Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
int x64_setup_rt_frame(struct ksignal *ksig, struct pt_regs *regs)이 함수의 진입 계약이 시작된다. x86-64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
sigset_t *set = sigmask_to_save();helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
struct rt_sigframe __user *frame;선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
void __user *fp = NULL;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
unsigned long uc_flags;선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
(blank)빈 줄은 x86-64 Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/* x86-64 should always use SA_RESTORER. */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
if (!(ksig->ka.sa.sa_flags & SA_RESTORER))이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
return -EFAULT;이 함수가 Signal frame과 user context 복원 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
(blank)빈 줄은 x86-64 Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
frame = get_sigframe(ksig, regs, sizeof(struct rt_sigframe), &fp);red-zone, altstack, fpstate 크기를 반영해 새 user RSP를 계산한다.
uc_flags = frame_uc_flags(regs);helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 x86-64 Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
if (!user_access_begin(frame, sizeof(*frame)))이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
return -EFAULT;이 함수가 Signal frame과 user context 복원 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
(blank)빈 줄은 x86-64 Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/* Create the ucontext. */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
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를 연결해 본다.
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를 연결해 본다.
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를 연결해 본다.
(blank)빈 줄은 x86-64 Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/* Set up to return from userspace. If provided, use a stubLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
already in userspace. */이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Signal frame과 user context 복원의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
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를 연결해 본다.
unsafe_put_sigcontext(&frame->uc.uc_mcontext, fp, regs, set, Efault);현재 integer register와 trap metadata를 user-visible sigcontext로 복사한다.
unsafe_put_sigmask(set, frame, Efault);helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
user_access_end();helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 x86-64 Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
if (ksig->ka.sa.sa_flags & SA_SIGINFO) {이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
if (copy_siginfo_to_user(&frame->info, &ksig->info))이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
return -EFAULT;이 함수가 Signal frame과 user context 복원 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 x86-64 Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
if (setup_signal_shadow_stack(ksig))이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
return -EFAULT;이 함수가 Signal frame과 user context 복원 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
(blank)빈 줄은 x86-64 Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/* Set up registers for signal handler */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
regs->di = ksig->sig;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
/* In case the signal handler was declared without prototypes */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
regs->ax = 0;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
(blank)빈 줄은 x86-64 Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/* This also works for non SA_SIGINFO handlers because they expect theLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
next argument after the signal number on the stack. */이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Signal frame과 user context 복원의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
regs->si = (unsigned long)&frame->info;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
regs->dx = (unsigned long)&frame->uc;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
regs->ip = (unsigned long) ksig->ka.sa.sa_handler;IRET/SYSRET 이후 signal handler를 실행하도록 saved instruction pointer를 바꾼다.
(blank)빈 줄은 x86-64 Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
regs->sp = (unsigned long)frame;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
(blank)빈 줄은 x86-64 Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* Set up the CS and SS registers to run signal handlers inLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* 64-bit mode, even if the handler happens to be interruptingLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* 32-bit or 16-bit code.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* SS is subtle. In 64-bit mode, we don't need any particularLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* SS descriptor, but we do need SS to be valid. It's possibleLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* that the old SS is entirely bogus -- this can happen if theLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* signal we're trying to deliver is #GP or #SS caused by a badLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* SS value. We also have a compatibility issue here: DOSEMULinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* relies on the contents of the SS register indicating theLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* SS value at the time of the signal, even though that code inLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* DOSEMU predates sigreturn's ability to restore SS. (DOSEMULinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* avoids relying on sigreturn to restore SS; instead it usesLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* 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개 줄에 각각 설명을 붙였습니다.
sp = sigsp(sp, ksig) - framesize;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
(blank)빈 줄은 RISC-V Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/* Align the stack frame. */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
sp &= ~0xfUL;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
(blank)빈 줄은 RISC-V Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
return (void __user *)sp;이 함수가 Signal frame과 user context 복원 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 RISC-V Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
static int setup_rt_frame(struct ksignal *ksig, sigset_t *set,이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Signal frame과 user context 복원의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
struct pt_regs *regs)이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Signal frame과 user context 복원의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
struct rt_sigframe __user *frame;선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
long err = 0;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
unsigned long __maybe_unused addr;선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
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를 연결해 본다.
(blank)빈 줄은 RISC-V Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
frame = get_sigframe(ksig, regs, frame_size);helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
if (!access_ok(frame, frame_size))이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
return -EFAULT;이 함수가 Signal frame과 user context 복원 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
(blank)빈 줄은 RISC-V Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
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를 연결해 본다.
(blank)빈 줄은 RISC-V Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/* Create the ucontext. */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
err |= __put_user(0, &frame->uc.uc_flags);helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
err |= __put_user(NULL, &frame->uc.uc_link);helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
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를 연결해 본다.
err |= setup_sigcontext(frame, regs);helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
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를 연결해 본다.
if (err)이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
return -EFAULT;이 함수가 Signal frame과 user context 복원 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
(blank)빈 줄은 RISC-V Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/* Set up to return from userspace. */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
#ifdef CONFIG_MMUKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
regs->ra = (unsigned long)VDSO_SYMBOL(계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
current->mm->context.vdso, rt_sigreturn);이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Signal frame과 user context 복원의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
#elseKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
/*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* For the nommu case we don't have a VDSO. Instead we push twoLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* instructions to call the rt_sigreturn syscall onto the user stack.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
if (copy_to_user(&frame->sigreturn_code, __user_rt_sigreturn,이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
sizeof(frame->sigreturn_code)))이 함수의 진입 계약이 시작된다. RISC-V에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
return -EFAULT;이 함수가 Signal frame과 user context 복원 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
(blank)빈 줄은 RISC-V Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
addr = (unsigned long)&frame->sigreturn_code;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
/* Make sure the two instructions are pushed to icache. */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
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를 연결해 본다.
(blank)빈 줄은 RISC-V Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
regs->ra = addr;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
#endif /* CONFIG_MMU */Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
(blank)빈 줄은 RISC-V Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* Set up registers for signal handler.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* Registers that we don't modify keep the value they had fromLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* user-space at the time we took the signal.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* We always pass siginfo and mcontext, regardless of SA_SIGINFO,Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* since some things rely on this (e.g. glibc's debug/segfault.c).Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
regs->epc = (unsigned long)ksig->ka.sa.sa_handler;sret 뒤 signal handler에서 시작하도록 saved EPC를 교체한다.
regs->sp = (unsigned long)frame;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
regs->a0 = ksig->sig; /* a0: signal number */계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
regs->a1 = (unsigned long)(&frame->info); /* a1: siginfo pointer */계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
regs->a2 = (unsigned long)(&frame->uc); /* a2: ucontext pointer */계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
(blank)빈 줄은 RISC-V Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
#if DEBUG_SIGKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
pr_info("SIG deliver (%s:%d): sig=%d pc=%p ra=%p sp=%p\n",계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
current->comm, task_pid_nr(current), ksig->sig,이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Signal frame과 user context 복원의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
(void *)regs->epc, (void *)regs->ra, frame);이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Signal frame과 user context 복원의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
#endifKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
(blank)빈 줄은 RISC-V Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
return 0;이 함수가 Signal frame과 user context 복원 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 RISC-V Signal frame과 user context 복원 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
static void handle_signal(struct ksignal *ksig, struct pt_regs *regs)이 함수의 진입 계약이 시작된다. RISC-V에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
05 · WORKED EXAMPLE
숫자로 검산하기
가변 vector state가 signal frame에 미치는 영향
기본 frame 1KiB, vector state 4KiB, altstack 8KiB에서 signal 두 개가 중첩된다고 가정한다.
- first정렬과 header를 제외해도 첫 frame은 약 5KiB를 사용한다.
- nested두 번째 frame도 같은 state를 저장하면 누적 약 10KiB로 8KiB altstack을 넘는다.
- failureget_sigframe 또는 user write가 실패하면 kernel은 SIGSEGV 처리 경로로 가며 손상된 일부 frame을 복원에 사용하지 않는다.
- mitigation실제 최대 VL/XSAVE feature를 기준으로 sigaltstack 크기를 산정하고 guard page를 둔다.
결론MINSIGSTKSZ 같은 고정 상수만으로 scalable state의 worst case를 보장할 수 없는 이유를 frame byte 수로 확인할 수 있다.
06 · DEEP DIVE
경계별 상세 분석
공통 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를 반드시 검증한다.
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를 확인한다.
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 주소를 본다.
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를 확인한다.
객체 수명과 소유권을 먼저 고정한다
signal frame은 user stack memory에 있으며 nested signal마다 아래쪽으로 추가된다. altstack overflow, stack mapping 변경과 handler가 frame을 덮는 경우를 모두 오류로 처리해야 한다.
주소나 register 값이 맞는지만 확인하면 stale state를 놓친다. producer, publication, consumer와 폐기 지점을 같은 표에 기록한다.
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
실패를 어떤 증거로 나눌 것인가
| 분류 | 관찰되는 결과 | 첫 확인값 |
|---|---|---|
| arm64 | record 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-64 | noncanonical 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-V | VLEN 기반 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
재현과 계측 절차
- handler에서 ucontext를 출력하고 GDB pt_regs와 비교해 signal delivery가 바꾼 PC/SP/argument를 확인한다.
- vector state를 dirty하게 만든 뒤 altstack 크기를 줄여 frame allocation 실패 경로를 재현한다.
- 동일한 workload에서 세 architecture의 tracepoint 이름, CPU 번호, PC, stack pointer와 address-space identifier를 같은 열로 기록한다.
- 소스만 읽고 끝내지 않고 최종
vmlinux의objdump -dr,readelf -SW결과로 선택된 alternative와 section 배치를 확인한다.
09 · REFERENCES
원문 좌표
- arm64arch/arm64/kernel/signal.c:1529-1609
- x86-64arch/x86/kernel/signal_64.c:156-228
- RISC-Varch/riscv/kernel/signal.c:307-381
Linux kernel source: GPL-2.0-only. 이 글의 코드 발췌는 Linux v6.18.37 원문을 기준으로 하며, 분석 문장은 해당 코드의 실행 조건과 상태 경계를 설명합니다.