01 · QUESTION
무엇을 확인할 것인가
예외가 들어왔을 때 어느 stack을 선택하고, user register를 어디까지 저장하며, kernel이 다시 sleep 가능한 context라고 판단하는 시점은 언제인가?
모든 architecture는 hardware가 제공한 최소 상태를 kernel entry frame으로 확장하고 exception class를 해석한 뒤 공통 irq, fault, syscall 계층으로 보낸다. 다만 hardware가 자동 저장하는 값, vector 주소 계산법, privilege 전환 때 stack을 바꾸는 방법은 서로 다르다.
assembly entry가 pt_regs를 완성하기 전에는 C ABI와 scheduler의 일반 가정을 적용할 수 없다. NMI와 같은 재진입 경로, user access 차단, speculation 방어, lockdep/RCU 진입은 정해진 순서로만 호출해야 한다.
02 · CONTRACT
공통 계약과 architecture 구현
| architecture | 핵심 mechanism | 실패 형태 | 확인할 상태 |
|---|---|---|---|
| arm64 | VBAR_EL1 vector slot과 EL0/EL1 sync, IRQ, FIQ, SError 분기 | 잘못된 vector offset, 불완전한 register save 또는 PAN 복원 누락은 return 직후 register corruption이나 kernel user-memory 접근 허용으로 나타난다. | VBAR_EL1, ESR_EL1.EC, ELR_EL1, FAR_EL1, SPSR_EL1과 pt_regs의 pc/pstate/sp를 함께 덤프한다. |
| x86-64 | IDT gate와 entry_64.S의 idtentry macro | error-code 유무를 잘못 맞추면 모든 frame offset이 한 word씩 밀린다. SWAPGS 판단 실패는 per-CPU 주소를 user GSBASE로 해석하는 치명적 오류를 만든다. | IDT descriptor, vector number, error_code, CS privilege, RSP, CR2와 ORC unwind 결과를 확인한다. |
| RISC-V | STVEC가 가리키는 handle_exception과 SCAUSE dispatch | kernel trap에서 user stack으로 오인하거나 sscratch를 늦게 복원하면 nested trap이 잘못된 task와 stack을 사용한다. | STVEC, SCAUSE interrupt bit와 code, SEPC, STVAL, SSTATUS.SPP/SPIE, sscratch와 pt_regs.sp를 기록한다. |
03 · DIAGRAMS
세 그림으로 먼저 읽기
arm64
- mechanism
- VBAR_EL1 vector slot과 EL0/EL1 sync, IRQ, FIQ, SError 분기
- state
- hardware는 ELR_EL1, SPSR_EL1, ESR_EL1, FAR_EL1 등을 system register에 남긴다. vector assembly가 x0-x30, SP와 원래 PSTATE를 pt_regs로 옮긴 뒤
el0t_64_sync_handler()가 ESR exception class를 syscall, abort, FP trap 등으로 분기한다. - checkpoint
- VBAR_EL1, ESR_EL1.EC, ELR_EL1, FAR_EL1, SPSR_EL1과 pt_regs의 pc/pstate/sp를 함께 덤프한다.
x86-64
- mechanism
- IDT gate와 entry_64.S의 idtentry macro
- state
- 일부 exception은 CPU가 error code를 push하고 privilege가 바뀌면 SS, RSP, RFLAGS, CS, RIP도 hardware frame에 쌓는다. entry macro가 빠진 register를 저장해
pt_regs형태를 맞추고 IST가 설정된 vector는 TSS의 전용 stack으로 전환한다. - checkpoint
- IDT descriptor, vector number, error_code, CS privilege, RSP, CR2와 ORC unwind 결과를 확인한다.
RISC-V
- mechanism
- STVEC가 가리키는 handle_exception과 SCAUSE dispatch
- state
- trap 진입 시 hardware는 SEPC, SCAUSE, STVAL과 SSTATUS만 갱신하고 일반 register는 저장하지 않는다. assembly가
sscratch와tp를 이용해 user/kernel trap을 구별하고 task kernel stack의 pt_regs에 x1-x31을 저장한다. - checkpoint
- STVEC, SCAUSE interrupt bit와 code, SEPC, STVAL, SSTATUS.SPP/SPIE, sscratch와 pt_regs.sp를 기록한다.
sscratch 교환 순서가 재진입 가능한 시점을 정한다.sscratch 교환 순서가 재진입 가능한 시점을 정한다.→관찰: STVEC, SCAUSE interrupt bit와 code, SEPC, STVAL, SSTATUS.SPP/SPIE, sscratch와 pt_regs.sp를 기록한다.04 · SOURCE
Linux 6.18.37 원본 코드와 줄별 설명
소스 위치를 고정된 숫자로 복사하지 않고 Linux v6.18.37 tree에서 함수 선언을 다시 찾아 발췌했습니다. 아래 코드와 각 줄의 설명은 1:1로 대응합니다.
arm64 · Linux 6.18.37
VBAR_EL1 vector slot과 EL0/EL1 sync, IRQ, FIQ, SError 분기
hardware는 ELR_EL1, SPSR_EL1, ESR_EL1, FAR_EL1 등을 system register에 남긴다. vector assembly가 x0-x30, SP와 원래 PSTATE를 pt_regs로 옮긴 뒤 el0t_64_sync_handler()가 ESR exception class를 syscall, abort, FP trap 등으로 분기한다.
원본 코드: arch/arm64/kernel/entry-common.c:752-834
752}
753
754static void noinstr el0_fpac(struct pt_regs *regs, unsigned long esr)
755{
756 arm64_enter_from_user_mode(regs);
757 local_daif_restore(DAIF_PROCCTX);
758 do_el0_fpac(regs, esr);
759 arm64_exit_to_user_mode(regs);
760}
761
762asmlinkage void noinstr el0t_64_sync_handler(struct pt_regs *regs)
763{
764 unsigned long esr = read_sysreg(esr_el1);
765
766 switch (ESR_ELx_EC(esr)) {
767 case ESR_ELx_EC_SVC64:
768 el0_svc(regs);
769 break;
770 case ESR_ELx_EC_DABT_LOW:
771 el0_da(regs, esr);
772 break;
773 case ESR_ELx_EC_IABT_LOW:
774 el0_ia(regs, esr);
775 break;
776 case ESR_ELx_EC_FP_ASIMD:
777 el0_fpsimd_acc(regs, esr);
778 break;
779 case ESR_ELx_EC_SVE:
780 el0_sve_acc(regs, esr);
781 break;
782 case ESR_ELx_EC_SME:
783 el0_sme_acc(regs, esr);
784 break;
785 case ESR_ELx_EC_FP_EXC64:
786 el0_fpsimd_exc(regs, esr);
787 break;
788 case ESR_ELx_EC_SYS64:
789 case ESR_ELx_EC_WFx:
790 el0_sys(regs, esr);
791 break;
792 case ESR_ELx_EC_SP_ALIGN:
793 el0_sp(regs, esr);
794 break;
795 case ESR_ELx_EC_PC_ALIGN:
796 el0_pc(regs, esr);
797 break;
798 case ESR_ELx_EC_UNKNOWN:
799 el0_undef(regs, esr);
800 break;
801 case ESR_ELx_EC_BTI:
802 el0_bti(regs);
803 break;
804 case ESR_ELx_EC_MOPS:
805 el0_mops(regs, esr);
806 break;
807 case ESR_ELx_EC_GCS:
808 el0_gcs(regs, esr);
809 break;
810 case ESR_ELx_EC_BREAKPT_LOW:
811 el0_breakpt(regs, esr);
812 break;
813 case ESR_ELx_EC_SOFTSTP_LOW:
814 el0_softstp(regs, esr);
815 break;
816 case ESR_ELx_EC_WATCHPT_LOW:
817 el0_watchpt(regs, esr);
818 break;
819 case ESR_ELx_EC_BRK64:
820 el0_brk64(regs, esr);
821 break;
822 case ESR_ELx_EC_FPAC:
823 el0_fpac(regs, esr);
824 break;
825 default:
826 el0_inv(regs, esr);
827 }
828}
829
830static void noinstr el0_interrupt(struct pt_regs *regs,
831 void (*handler)(struct pt_regs *))
832{
833 arm64_enter_from_user_mode(regs);
834 라인 바이 라인 주석
빈 줄과 전처리 경계도 생략하지 않았습니다. 원본의 83개 줄에 각각 설명을 붙였습니다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 arm64 Exception entry: vector table, IDT와 trap entry 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
static void noinstr el0_fpac(struct pt_regs *regs, unsigned long esr)이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
arm64_enter_from_user_mode(regs);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
local_daif_restore(DAIF_PROCCTX);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
do_el0_fpac(regs, esr);최소 한 번 실행되는 retry 또는 순회 구간이다. 탈출 조건이 다른 CPU나 hardware의 진행에 의존하는지 확인한다.
arm64_exit_to_user_mode(regs);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 arm64 Exception entry: vector table, IDT와 trap entry 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
asmlinkage void noinstr el0t_64_sync_handler(struct pt_regs *regs)이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
unsigned long esr = read_sysreg(esr_el1);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 arm64 Exception entry: vector table, IDT와 trap entry 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
switch (ESR_ELx_EC(esr)) {ESR의 Exception Class를 읽어 system call, instruction/data abort, alignment, debug와 FP/SVE trap을 나누는 중심 분기다.
case ESR_ELx_EC_SVC64:AArch64 SVC64를 Linux syscall entry로 전달하는 경계다.
el0_svc(regs);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
break;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
case ESR_ELx_EC_DABT_LOW:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
el0_da(regs, esr);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
break;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
case ESR_ELx_EC_IABT_LOW:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
el0_ia(regs, esr);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
break;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
case ESR_ELx_EC_FP_ASIMD:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
el0_fpsimd_acc(regs, esr);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
break;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
case ESR_ELx_EC_SVE:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
el0_sve_acc(regs, esr);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
break;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
case ESR_ELx_EC_SME:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
el0_sme_acc(regs, esr);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
break;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
case ESR_ELx_EC_FP_EXC64:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
el0_fpsimd_exc(regs, esr);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
break;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
case ESR_ELx_EC_SYS64:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
case ESR_ELx_EC_WFx:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
el0_sys(regs, esr);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
break;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
case ESR_ELx_EC_SP_ALIGN:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
el0_sp(regs, esr);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
break;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
case ESR_ELx_EC_PC_ALIGN:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
el0_pc(regs, esr);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
break;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
case ESR_ELx_EC_UNKNOWN:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
el0_undef(regs, esr);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
break;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
case ESR_ELx_EC_BTI:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
el0_bti(regs);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
break;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
case ESR_ELx_EC_MOPS:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
el0_mops(regs, esr);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
break;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
case ESR_ELx_EC_GCS:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
el0_gcs(regs, esr);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
break;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
case ESR_ELx_EC_BREAKPT_LOW:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
el0_breakpt(regs, esr);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
break;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
case ESR_ELx_EC_SOFTSTP_LOW:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
el0_softstp(regs, esr);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
break;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
case ESR_ELx_EC_WATCHPT_LOW:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
el0_watchpt(regs, esr);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
break;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
case ESR_ELx_EC_BRK64:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
el0_brk64(regs, esr);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
break;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
case ESR_ELx_EC_FPAC:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
el0_fpac(regs, esr);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
break;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
default:분기 label이다. 이 위치로 들어오는 모든 선행 경로가 같은 register, stack, lock과 interrupt 상태를 만족하는지 비교한다.
el0_inv(regs, esr);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 arm64 Exception entry: vector table, IDT와 trap entry 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
static void noinstr el0_interrupt(struct pt_regs *regs,이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Exception entry: vector table, IDT와 trap entry의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
void (*handler)(struct pt_regs *))이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
arm64_enter_from_user_mode(regs);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 arm64 Exception entry: vector table, IDT와 trap entry 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
x86-64 · Linux 6.18.37
IDT gate와 entry_64.S의 idtentry macro
일부 exception은 CPU가 error code를 push하고 privilege가 바뀌면 SS, RSP, RFLAGS, CS, RIP도 hardware frame에 쌓는다. entry macro가 빠진 register를 저장해 pt_regs 형태를 맞추고 IST가 설정된 vector는 TSS의 전용 stack으로 전환한다.
원본 코드: arch/x86/entry/entry_64.S:317-401
317.endm
318
319/**
320 * idtentry - Macro to generate entry stubs for simple IDT entries
321 * @vector: Vector number
322 * @asmsym: ASM symbol for the entry point
323 * @cfunc: C function to be called
324 * @has_error_code: Hardware pushed error code on stack
325 *
326 * The macro emits code to set up the kernel context for straight forward
327 * and simple IDT entries. No IST stack, no paranoid entry checks.
328 */
329.macro idtentry vector asmsym cfunc has_error_code:req
330SYM_CODE_START(\asmsym)
331
332 .if \vector == X86_TRAP_BP
333 /* #BP advances %rip to the next instruction */
334 UNWIND_HINT_IRET_ENTRY offset=\has_error_code*8 signal=0
335 .else
336 UNWIND_HINT_IRET_ENTRY offset=\has_error_code*8
337 .endif
338
339 ENDBR
340 ASM_CLAC
341 cld
342
343 .if \has_error_code == 0
344 pushq $-1 /* ORIG_RAX: no syscall to restart */
345 .endif
346
347 .if \vector == X86_TRAP_BP
348 /*
349 * If coming from kernel space, create a 6-word gap to allow the
350 * int3 handler to emulate a call instruction.
351 */
352 testb $3, CS-ORIG_RAX(%rsp)
353 jnz .Lfrom_usermode_no_gap_\@
354 .rept 6
355 pushq 5*8(%rsp)
356 .endr
357 UNWIND_HINT_IRET_REGS offset=8
358.Lfrom_usermode_no_gap_\@:
359 .endif
360
361 idtentry_body \cfunc \has_error_code
362
363_ASM_NOKPROBE(\asmsym)
364SYM_CODE_END(\asmsym)
365.endm
366
367/*
368 * Interrupt entry/exit.
369 *
370 + The interrupt stubs push (vector) onto the stack, which is the error_code
371 * position of idtentry exceptions, and jump to one of the two idtentry points
372 * (common/spurious).
373 *
374 * common_interrupt is a hotpath, align it to a cache line
375 */
376.macro idtentry_irq vector cfunc
377 .p2align CONFIG_X86_L1_CACHE_SHIFT
378 idtentry \vector asm_\cfunc \cfunc has_error_code=1
379.endm
380
381/**
382 * idtentry_mce_db - Macro to generate entry stubs for #MC and #DB
383 * @vector: Vector number
384 * @asmsym: ASM symbol for the entry point
385 * @cfunc: C function to be called
386 *
387 * The macro emits code to set up the kernel context for #MC and #DB
388 *
389 * If the entry comes from user space it uses the normal entry path
390 * including the return to user space work and preemption checks on
391 * exit.
392 *
393 * If hits in kernel mode then it needs to go through the paranoid
394 * entry as the exception can hit any random state. No preemption
395 * check on exit to keep the paranoid path simple.
396 */
397.macro idtentry_mce_db vector asmsym cfunc
398SYM_CODE_START(\asmsym)
399 UNWIND_HINT_IRET_ENTRY
400 ENDBR
401 ASM_CLAC라인 바이 라인 주석
빈 줄과 전처리 경계도 생략하지 않았습니다. 원본의 85개 줄에 각각 설명을 붙였습니다.
.endm이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Exception entry: vector table, IDT와 trap entry의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
(blank)빈 줄은 x86-64 Exception entry: vector table, IDT와 trap entry 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/**Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* idtentry - Macro to generate entry stubs for simple IDT entriesLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* @vector: Vector numberLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* @asmsym: ASM symbol for the entry pointLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* @cfunc: C function to be calledLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* @has_error_code: Hardware pushed error code on stackLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* The macro emits code to set up the kernel context for straight forwardLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* and simple IDT entries. No IST stack, no paranoid entry checks.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
.macro idtentry vector asmsym cfunc has_error_code:req이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Exception entry: vector table, IDT와 trap entry의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
SYM_CODE_START(\asmsym)assembler/linker가 symbol, section과 정렬을 정의한다. runtime instruction은 아니지만 entry 주소와 relocation 가능한 범위를 결정한다.
(blank)빈 줄은 x86-64 Exception entry: vector table, IDT와 trap entry 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
.if \vector == X86_TRAP_BP이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Exception entry: vector table, IDT와 trap entry의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
/* #BP advances %rip to the next instruction */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
UNWIND_HINT_IRET_ENTRY offset=\has_error_code*8 signal=0계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
.else이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Exception entry: vector table, IDT와 trap entry의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
UNWIND_HINT_IRET_ENTRY offset=\has_error_code*8계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
.endif이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Exception entry: vector table, IDT와 trap entry의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
(blank)빈 줄은 x86-64 Exception entry: vector table, IDT와 trap entry 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
ENDBRassembler/linker가 symbol, section과 정렬을 정의한다. runtime instruction은 아니지만 entry 주소와 relocation 가능한 범위를 결정한다.
ASM_CLAC선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
cld선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
(blank)빈 줄은 x86-64 Exception entry: vector table, IDT와 trap entry 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
.if \has_error_code == 0이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Exception entry: vector table, IDT와 trap entry의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
pushq $-1 /* ORIG_RAX: no syscall to restart */이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Exception entry: vector table, IDT와 trap entry의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
.endif이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Exception entry: vector table, IDT와 trap entry의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
(blank)빈 줄은 x86-64 Exception entry: vector table, IDT와 trap entry 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
.if \vector == X86_TRAP_BP이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Exception entry: vector table, IDT와 trap entry의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
/*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* If coming from kernel space, create a 6-word gap to allow theLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* int3 handler to emulate a call instruction.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
testb $3, CS-ORIG_RAX(%rsp)helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
jnz .Lfrom_usermode_no_gap_\@이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Exception entry: vector table, IDT와 trap entry의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
.rept 6이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Exception entry: vector table, IDT와 trap entry의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
pushq 5*8(%rsp)이 함수의 진입 계약이 시작된다. x86-64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
.endr이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Exception entry: vector table, IDT와 trap entry의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
UNWIND_HINT_IRET_REGS offset=8계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
.Lfrom_usermode_no_gap_\@:이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Exception entry: vector table, IDT와 trap entry의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
.endif이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Exception entry: vector table, IDT와 trap entry의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
(blank)빈 줄은 x86-64 Exception entry: vector table, IDT와 trap entry 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
idtentry_body \cfunc \has_error_code이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Exception entry: vector table, IDT와 trap entry의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
(blank)빈 줄은 x86-64 Exception entry: vector table, IDT와 trap entry 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
_ASM_NOKPROBE(\asmsym)이 함수의 진입 계약이 시작된다. x86-64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
SYM_CODE_END(\asmsym)assembler/linker가 symbol, section과 정렬을 정의한다. runtime instruction은 아니지만 entry 주소와 relocation 가능한 범위를 결정한다.
.endm이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Exception entry: vector table, IDT와 trap entry의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
(blank)빈 줄은 x86-64 Exception entry: vector table, IDT와 trap entry 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* Interrupt entry/exit.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
+ The interrupt stubs push (vector) onto the stack, which is the error_code이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Exception entry: vector table, IDT와 trap entry의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
* position of idtentry exceptions, and jump to one of the two idtentry pointsLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* (common/spurious).Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* common_interrupt is a hotpath, align it to a cache lineLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
.macro idtentry_irq vector cfunc이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Exception entry: vector table, IDT와 trap entry의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
.p2align CONFIG_X86_L1_CACHE_SHIFT이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Exception entry: vector table, IDT와 trap entry의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
idtentry \vector asm_\cfunc \cfunc has_error_code=1계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
.endm이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Exception entry: vector table, IDT와 trap entry의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
(blank)빈 줄은 x86-64 Exception entry: vector table, IDT와 trap entry 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/**Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* idtentry_mce_db - Macro to generate entry stubs for #MC and #DBLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* @vector: Vector numberLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* @asmsym: ASM symbol for the entry pointLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* @cfunc: C function to be calledLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* The macro emits code to set up the kernel context for #MC and #DBLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* If the entry comes from user space it uses the normal entry pathLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* including the return to user space work and preemption checks onLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* exit.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* If hits in kernel mode then it needs to go through the paranoidLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* entry as the exception can hit any random state. No preemptionLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* check on exit to keep the paranoid path simple.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
.macro idtentry_mce_db vector asmsym cfunc이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Exception entry: vector table, IDT와 trap entry의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
SYM_CODE_START(\asmsym)assembler/linker가 symbol, section과 정렬을 정의한다. runtime instruction은 아니지만 entry 주소와 relocation 가능한 범위를 결정한다.
UNWIND_HINT_IRET_ENTRY선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
ENDBRassembler/linker가 symbol, section과 정렬을 정의한다. runtime instruction은 아니지만 entry 주소와 relocation 가능한 범위를 결정한다.
ASM_CLAC선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
RISC-V · Linux 6.18.37
STVEC가 가리키는 handle_exception과 SCAUSE dispatch
trap 진입 시 hardware는 SEPC, SCAUSE, STVAL과 SSTATUS만 갱신하고 일반 register는 저장하지 않는다. assembly가 sscratch와 tp를 이용해 user/kernel trap을 구별하고 task kernel stack의 pt_regs에 x1-x31을 저장한다.
원본 코드: arch/riscv/kernel/entry.S:88-182
88 REG_L a2, TASK_TI_A2(tp)
89.Lnew_vmalloc_restore_context_a1:
90 REG_L a1, TASK_TI_A1(tp)
91.Lnew_vmalloc_restore_context_a0:
92 REG_L a0, TASK_TI_A0(tp)
93.endm
94
95
96SYM_CODE_START(handle_exception)
97 /*
98 * If coming from userspace, preserve the user thread pointer and load
99 * the kernel thread pointer. If we came from the kernel, the scratch
100 * register will contain 0, and we should continue on the current TP.
101 */
102 csrrw tp, CSR_SCRATCH, tp
103 bnez tp, .Lsave_context
104
105.Lrestore_kernel_tpsp:
106 csrr tp, CSR_SCRATCH
107
108#ifdef CONFIG_64BIT
109 /*
110 * The RISC-V kernel does not eagerly emit a sfence.vma after each
111 * new vmalloc mapping, which may result in exceptions:
112 * - if the uarch caches invalid entries, the new mapping would not be
113 * observed by the page table walker and an invalidation is needed.
114 * - if the uarch does not cache invalid entries, a reordered access
115 * could "miss" the new mapping and traps: in that case, we only need
116 * to retry the access, no sfence.vma is required.
117 */
118 new_vmalloc_check
119#endif
120
121 REG_S sp, TASK_TI_KERNEL_SP(tp)
122
123#ifdef CONFIG_VMAP_STACK
124 addi sp, sp, -(PT_SIZE_ON_STACK)
125 srli sp, sp, THREAD_SHIFT
126 andi sp, sp, 0x1
127 bnez sp, handle_kernel_stack_overflow
128 REG_L sp, TASK_TI_KERNEL_SP(tp)
129#endif
130
131.Lsave_context:
132 REG_S sp, TASK_TI_USER_SP(tp)
133 REG_L sp, TASK_TI_KERNEL_SP(tp)
134 addi sp, sp, -(PT_SIZE_ON_STACK)
135 REG_S x1, PT_RA(sp)
136 REG_S x3, PT_GP(sp)
137 REG_S x5, PT_T0(sp)
138 save_from_x6_to_x31
139
140 /*
141 * Disable user-mode memory access as it should only be set in the
142 * actual user copy routines.
143 *
144 * Disable the FPU/Vector to detect illegal usage of floating point
145 * or vector in kernel space.
146 */
147 li t0, SR_SUM | SR_FS_VS
148
149 REG_L s0, TASK_TI_USER_SP(tp)
150 csrrc s1, CSR_STATUS, t0
151 csrr s2, CSR_EPC
152 csrr s3, CSR_TVAL
153 csrr s4, CSR_CAUSE
154 csrr s5, CSR_SCRATCH
155 REG_S s0, PT_SP(sp)
156 REG_S s1, PT_STATUS(sp)
157 REG_S s2, PT_EPC(sp)
158 REG_S s3, PT_BADADDR(sp)
159 REG_S s4, PT_CAUSE(sp)
160 REG_S s5, PT_TP(sp)
161
162 /*
163 * Set the scratch register to 0, so that if a recursive exception
164 * occurs, the exception vector knows it came from the kernel
165 */
166 csrw CSR_SCRATCH, x0
167
168 /* Load the global pointer */
169 load_global_pointer
170
171 /* Load the kernel shadow call stack pointer if coming from userspace */
172 scs_load_current_if_task_changed s5
173
174#ifdef CONFIG_RISCV_ISA_V_PREEMPTIVE
175 move a0, sp
176 call riscv_v_context_nesting_start
177#endif
178 move a0, sp /* pt_regs */
179
180 /*
181 * MSB of cause differentiates between
182 * interrupts and exceptions라인 바이 라인 주석
빈 줄과 전처리 경계도 생략하지 않았습니다. 원본의 95개 줄에 각각 설명을 붙였습니다.
REG_L a2, TASK_TI_A2(tp)helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
.Lnew_vmalloc_restore_context_a1:분기 label이다. 이 위치로 들어오는 모든 선행 경로가 같은 register, stack, lock과 interrupt 상태를 만족하는지 비교한다.
REG_L a1, TASK_TI_A1(tp)helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
.Lnew_vmalloc_restore_context_a0:분기 label이다. 이 위치로 들어오는 모든 선행 경로가 같은 register, stack, lock과 interrupt 상태를 만족하는지 비교한다.
REG_L a0, TASK_TI_A0(tp)helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
.endm이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Exception entry: vector table, IDT와 trap entry의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
(blank)빈 줄은 RISC-V Exception entry: vector table, IDT와 trap entry 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
(blank)빈 줄은 RISC-V Exception entry: vector table, IDT와 trap entry 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
SYM_CODE_START(handle_exception)assembler/linker가 symbol, section과 정렬을 정의한다. runtime instruction은 아니지만 entry 주소와 relocation 가능한 범위를 결정한다.
/*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* If coming from userspace, preserve the user thread pointer and loadLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* the kernel thread pointer. If we came from the kernel, the scratchLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* register will contain 0, and we should continue on the current TP.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
csrrw tp, CSR_SCRATCH, tptp와 sscratch를 교환해 user-origin trap이면 current task pointer를 얻고 kernel-origin이면 별도 경로를 선택한다.
bnez tp, .Lsave_context이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Exception entry: vector table, IDT와 trap entry의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
(blank)빈 줄은 RISC-V Exception entry: vector table, IDT와 trap entry 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
.Lrestore_kernel_tpsp:분기 label이다. 이 위치로 들어오는 모든 선행 경로가 같은 register, stack, lock과 interrupt 상태를 만족하는지 비교한다.
csrr tp, CSR_SCRATCHprivileged system register를 읽거나 쓴다. 일반 변수와 달리 write side effect, privilege level과 serialization 조건을 함께 확인한다.
(blank)빈 줄은 RISC-V Exception entry: vector table, IDT와 trap entry 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
#ifdef CONFIG_64BITKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
/*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* The RISC-V kernel does not eagerly emit a sfence.vma after eachLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* new vmalloc mapping, which may result in exceptions:Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* - if the uarch caches invalid entries, the new mapping would not beLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* observed by the page table walker and an invalidation is needed.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* - if the uarch does not cache invalid entries, a reordered accessLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* could "miss" the new mapping and traps: in that case, we only needLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* to retry the access, no sfence.vma is required.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
new_vmalloc_check선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
#endifKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
(blank)빈 줄은 RISC-V Exception entry: vector table, IDT와 trap entry 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
REG_S sp, TASK_TI_KERNEL_SP(tp)helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 RISC-V Exception entry: vector table, IDT와 trap entry 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
#ifdef CONFIG_VMAP_STACKKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
addi sp, sp, -(PT_SIZE_ON_STACK)이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Exception entry: vector table, IDT와 trap entry의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
srli sp, sp, THREAD_SHIFT이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Exception entry: vector table, IDT와 trap entry의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
andi sp, sp, 0x1이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Exception entry: vector table, IDT와 trap entry의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
bnez sp, handle_kernel_stack_overflow이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Exception entry: vector table, IDT와 trap entry의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
REG_L sp, TASK_TI_KERNEL_SP(tp)helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
#endifKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
(blank)빈 줄은 RISC-V Exception entry: vector table, IDT와 trap entry 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
.Lsave_context:분기 label이다. 이 위치로 들어오는 모든 선행 경로가 같은 register, stack, lock과 interrupt 상태를 만족하는지 비교한다.
REG_S sp, TASK_TI_USER_SP(tp)helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
REG_L sp, TASK_TI_KERNEL_SP(tp)helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
addi sp, sp, -(PT_SIZE_ON_STACK)이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Exception entry: vector table, IDT와 trap entry의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
REG_S x1, PT_RA(sp)hardware가 저장하지 않은 integer register를 Linux pt_regs layout에 맞춰 쌓기 시작한다.
REG_S x3, PT_GP(sp)helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
REG_S x5, PT_T0(sp)helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
save_from_x6_to_x31선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
(blank)빈 줄은 RISC-V Exception entry: vector table, IDT와 trap entry 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* Disable user-mode memory access as it should only be set in theLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* actual user copy routines.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* Disable the FPU/Vector to detect illegal usage of floating pointLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* or vector in kernel space.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
li t0, SR_SUM | SR_FS_VS이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Exception entry: vector table, IDT와 trap entry의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
(blank)빈 줄은 RISC-V Exception entry: vector table, IDT와 trap entry 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
REG_L s0, TASK_TI_USER_SP(tp)helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
csrrc s1, CSR_STATUS, t0이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Exception entry: vector table, IDT와 trap entry의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
csrr s2, CSR_EPCprivileged system register를 읽거나 쓴다. 일반 변수와 달리 write side effect, privilege level과 serialization 조건을 함께 확인한다.
csrr s3, CSR_TVALprivileged system register를 읽거나 쓴다. 일반 변수와 달리 write side effect, privilege level과 serialization 조건을 함께 확인한다.
csrr s4, CSR_CAUSEtrap 원인과 interrupt bit를 읽어 exception table 또는 interrupt dispatch로 보낸다.
csrr s5, CSR_SCRATCHprivileged system register를 읽거나 쓴다. 일반 변수와 달리 write side effect, privilege level과 serialization 조건을 함께 확인한다.
REG_S s0, PT_SP(sp)helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
REG_S s1, PT_STATUS(sp)helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
REG_S s2, PT_EPC(sp)helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
REG_S s3, PT_BADADDR(sp)helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
REG_S s4, PT_CAUSE(sp)helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
REG_S s5, PT_TP(sp)helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 RISC-V Exception entry: vector table, IDT와 trap entry 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* Set the scratch register to 0, so that if a recursive exceptionLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* occurs, the exception vector knows it came from the kernelLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
csrw CSR_SCRATCH, x0privileged system register를 읽거나 쓴다. 일반 변수와 달리 write side effect, privilege level과 serialization 조건을 함께 확인한다.
(blank)빈 줄은 RISC-V Exception entry: vector table, IDT와 trap entry 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/* Load the global pointer */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
load_global_pointer선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
(blank)빈 줄은 RISC-V Exception entry: vector table, IDT와 trap entry 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/* Load the kernel shadow call stack pointer if coming from userspace */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
scs_load_current_if_task_changed s5선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
(blank)빈 줄은 RISC-V Exception entry: vector table, IDT와 trap entry 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
#ifdef CONFIG_RISCV_ISA_V_PREEMPTIVEKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
move a0, sp이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Exception entry: vector table, IDT와 trap entry의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
call riscv_v_context_nesting_start하위 경로로 제어를 넘긴다. 호출 직전의 argument, interrupt/preemption 상태와 호출 뒤에도 살아 있어야 하는 object를 기록한다.
#endifKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
move a0, sp /* pt_regs */이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Exception entry: vector table, IDT와 trap entry의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
(blank)빈 줄은 RISC-V Exception entry: vector table, IDT와 trap entry 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* MSB of cause differentiates betweenLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* interrupts and exceptionsLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
05 · WORKED EXAMPLE
숫자로 검산하기
user page fault 한 건의 entry frame 비교
user PC 0x400812에서 VA 0x7fff0000을 load하다 translation fault가 발생했다고 가정한다.
- hardwarearm64는 ELR/ESR/FAR, x86은 RIP/CS/RFLAGS와 CR2, RISC-V는 SEPC/SCAUSE/STVAL을 각각 남긴다.
- stackarm64와 RISC-V entry는 task kernel stack에 pt_regs를 만들고 x86은 privilege frame과 필요 시 IST/TSS stack을 사용한다.
- classification세 경로 모두 user-origin과 read fault를 판별한 뒤 architecture page-fault handler에서 mm fault core로 넘어간다.
- returnPTE가 복구되면 저장된 PC를 그대로 재실행한다. signal이 필요하면 동일 frame을 signal setup이 수정한다.
결론fault address 하나만 비교하지 말고 origin privilege, saved PC, error class, stack 종류와 register frame 완성 시점을 함께 추적해야 한다.
06 · DEEP DIVE
경계별 상세 분석
공통 kernel core와 architecture hook의 경계
모든 architecture는 hardware가 제공한 최소 상태를 kernel entry frame으로 확장하고 exception class를 해석한 뒤 공통 irq, fault, syscall 계층으로 보낸다. 다만 hardware가 자동 저장하는 값, vector 주소 계산법, privilege 전환 때 stack을 바꾸는 방법은 서로 다르다.
assembly entry가 pt_regs를 완성하기 전에는 C ABI와 scheduler의 일반 가정을 적용할 수 없다. NMI와 같은 재진입 경로, user access 차단, speculation 방어, lockdep/RCU 진입은 정해진 순서로만 호출해야 한다.
arm64: VBAR_EL1 vector slot과 EL0/EL1 sync, IRQ, FIQ, SError 분기
hardware는 ELR_EL1, SPSR_EL1, ESR_EL1, FAR_EL1 등을 system register에 남긴다. vector assembly가 x0-x30, SP와 원래 PSTATE를 pt_regs로 옮긴 뒤 el0t_64_sync_handler()가 ESR exception class를 syscall, abort, FP trap 등으로 분기한다.
DAIF mask, PAN/UAO, MTE tag check, speculation barrier와 context tracking 순서가 user memory 접근 가능 시점을 결정한다. 디버깅할 때는 VBAR_EL1, ESR_EL1.EC, ELR_EL1, FAR_EL1, SPSR_EL1과 pt_regs의 pc/pstate/sp를 함께 덤프한다.
x86-64: IDT gate와 entry_64.S의 idtentry macro
일부 exception은 CPU가 error code를 push하고 privilege가 바뀌면 SS, RSP, RFLAGS, CS, RIP도 hardware frame에 쌓는다. entry macro가 빠진 register를 저장해 pt_regs 형태를 맞추고 IST가 설정된 vector는 TSS의 전용 stack으로 전환한다.
SWAPGS, CR3/KPTI 전환, speculation 방어와 irqentry_enter 순서가 NMI 재진입과 user GSBASE 안전성을 좌우한다. 디버깅할 때는 IDT descriptor, vector number, error_code, CS privilege, RSP, CR2와 ORC unwind 결과를 확인한다.
RISC-V: STVEC가 가리키는 handle_exception과 SCAUSE dispatch
trap 진입 시 hardware는 SEPC, SCAUSE, STVAL과 SSTATUS만 갱신하고 일반 register는 저장하지 않는다. assembly가 sscratch와 tp를 이용해 user/kernel trap을 구별하고 task kernel stack의 pt_regs에 x1-x31을 저장한다.
SUM 차단, FS/VS 상태, interrupt enable bit와 sscratch 교환 순서가 재진입 가능한 시점을 정한다. 디버깅할 때는 STVEC, SCAUSE interrupt bit와 code, SEPC, STVAL, SSTATUS.SPP/SPIE, sscratch와 pt_regs.sp를 기록한다.
객체 수명과 소유권을 먼저 고정한다
pt_regs는 현재 kernel stack 또는 별도 exception stack에 존재하며 handler와 signal/ptrace가 참조하는 동안 유지된다. nested exception은 바깥 frame을 덮지 않도록 stack 경계와 nesting counter를 보존해야 한다.
주소나 register 값이 맞는지만 확인하면 stale state를 놓친다. producer, publication, consumer와 폐기 지점을 같은 표에 기록한다.
latency upper bound는 hardware instruction 하나가 아니다
entry latency는 register save, privilege 경계 방어, vector dispatch, interrupt masking과 cache miss의 합이다. NMI/FIQ 급 경로는 일반 IRQ보다 우선하지만 handler가 긴 critical section에 막히면 tail latency가 늘어난다.
평균값 외에 interrupt-off 구간, remote CPU 응답, firmware 호출과 retry 횟수를 분리해야 최악 지연의 원인을 찾을 수 있다.
07 · FAILURE
실패를 어떤 증거로 나눌 것인가
| 분류 | 관찰되는 결과 | 첫 확인값 |
|---|---|---|
| arm64 | 잘못된 vector offset, 불완전한 register save 또는 PAN 복원 누락은 return 직후 register corruption이나 kernel user-memory 접근 허용으로 나타난다. | VBAR_EL1, ESR_EL1.EC, ELR_EL1, FAR_EL1, SPSR_EL1과 pt_regs의 pc/pstate/sp를 함께 덤프한다. |
| x86-64 | error-code 유무를 잘못 맞추면 모든 frame offset이 한 word씩 밀린다. SWAPGS 판단 실패는 per-CPU 주소를 user GSBASE로 해석하는 치명적 오류를 만든다. | IDT descriptor, vector number, error_code, CS privilege, RSP, CR2와 ORC unwind 결과를 확인한다. |
| RISC-V | kernel trap에서 user stack으로 오인하거나 sscratch를 늦게 복원하면 nested trap이 잘못된 task와 stack을 사용한다. | STVEC, SCAUSE interrupt bit와 code, SEPC, STVAL, SSTATUS.SPP/SPIE, sscratch와 pt_regs.sp를 기록한다. |
08 · LAB
재현과 계측 절차
- QEMU에서 의도적인 user read fault, kernel fault, breakpoint를 발생시켜 각 entry frame과 C handler까지 function graph를 수집한다.
- entry 직전과 irqentry_enter 이후의 RCU/context-tracking 상태를 tracepoint로 비교한다.
- 동일한 workload에서 세 architecture의 tracepoint 이름, CPU 번호, PC, stack pointer와 address-space identifier를 같은 열로 기록한다.
- 소스만 읽고 끝내지 않고 최종
vmlinux의objdump -dr,readelf -SW결과로 선택된 alternative와 section 배치를 확인한다.
09 · REFERENCES
원문 좌표
- arm64arch/arm64/kernel/entry-common.c:752-834
- x86-64arch/x86/entry/entry_64.S:317-401
- RISC-Varch/riscv/kernel/entry.S:88-182
Linux kernel source: GPL-2.0-only. 이 글의 코드 발췌는 Linux v6.18.37 원문을 기준으로 하며, 분석 문장은 해당 코드의 실행 조건과 상태 경계를 설명합니다.