# entry.S — 말로 풀어 읽기
Linux v6.18.37 · arch/arm64/kernel/entry.S

예외는 실행 중인 프로그램을 중간에 멈추고 다른 코드를 실행해야 하는 상황입니다. CPU는 복귀할 PC와 실행 상태를 일부 시스템 레지스터에 남기지만, 프로그램이 사용하던 모든 일반 레지스터를 대신 저장해 주지는 않습니다. entry.S는 그 나머지를 맡습니다. 나중에 마치 중단되지 않았던 것처럼 돌아가려면 무엇을 어디에 보관해야 하는지가 이 파일의 중심 질문입니다.

## 처음 읽을 때
한 줄의 STP를 볼 때는 “예외 처리 코드가 이 두 레지스터를 쓰기 전에 원래 값을 메모리에 보관한다”고 읽으세요. 복귀의 LDP는 같은 기록을 다시 꺼내는 단계입니다.

## 더 깊이 살펴볼 때
예외 프레임 보존과 태스크 문맥 전환은 저장 범위가 다릅니다. 하드웨어가 저장하는 상태, ABI가 보존을 요구하는 상태, Linux가 추가하는 메타데이터를 나누어 보세요. KPTI에서는 매핑을 지운 뒤에도 실행 가능한 최소 코드가 필요합니다.

## 헤더와 범용 레지스터 초기화
원본 1–38행

### 10행
```asm
#include <linux/arm-smccc.h>
```
SMC/HVC로 호출하는 펌웨어 서비스의 함수 번호와 규약을 가져옵니다. 이 파일의 Spectre 완화 요청은 이 번호를 w0에 넣어 펌웨어가 서비스를 구분하도록 합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 11행
```asm
#include <linux/init.h>
```
부팅 때만 사용할 코드·데이터의 섹션 표기를 가져옵니다. 초기화가 끝난 뒤 해제 가능한 영역과 계속 남겨야 하는 영역을 링커가 구분하게 합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 12행
```asm
#include <linux/linkage.h>
```
SYM_FUNC_START·SYM_CODE_START·SYM_INNER_LABEL 등의 심볼 선언 규칙을 가져옵니다. 함수인지 코드 내부 라벨인지, 다른 오브젝트에 보일 이름인지와 정렬·크기 정보를 빌드 도구에 전달하는 데 필요합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 14행
```asm
#include <asm/alternative.h>
```
CPU 기능에 따라 같은 위치의 명령을 부팅 시 교체하는 alternatives 문법을 가져옵니다. CONFIG의 빌드 선택과 달리 실제 CPU에 맞는 코드가 남도록 하는 장치입니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 15행
```asm
#include <asm/assembler.h>
```
ARM64의 큰 주소 구성, CPU별 값 조회, 시스템 레지스터 처리, 동기화 등에 쓰는 어셈블리 매크로를 가져옵니다. 각 사용 위치에서 일반 명령 여러 개로 펼쳐지는 작업입니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 16행
```asm
#include <asm/asm-offsets.h>
```
C 구조체에서 계산한 필드 오프셋을 가져옵니다. 어셈블리가 task_struct·pt_regs 등에서 값을 읽고 쓸 때 C 빌드의 실제 배치와 일치하도록 생성하는 헤더입니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 17행
```asm
#include <asm/asm_pointer_auth.h>
```
PAC 키 설치 매크로를 가져옵니다. 사용자·커널 또는 task 사이를 전환할 때 instruction-address 인증 키가 해당 실행 문맥과 맞아야 하므로 필요합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 18행
```asm
#include <asm/bug.h>
```
복구를 기대하지 않는 커널 오류를 BRK와 BUG 메타데이터로 표현하는 규칙을 가져옵니다. 돌아오지 않아야 할 초기화 함수가 반환한 경우 등의 오류 경로에서 사용합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 19행
```asm
#include <asm/cpufeature.h>
```
CPU 기능과 erratum capability 이름을 가져옵니다. alternatives가 MTE·PAC·PAN·CPU 우회 코드 중 어느 명령열을 사용할지 선택하는 기준입니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 20행
```asm
#include <asm/errno.h>
```
아키텍처의 오류 번호 정의를 가져옵니다. 음수 반환값을 오류 종류와 연결하는 정수 상수이며 CPU 예외 벡터 번호와는 다릅니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 21행
```asm
#include <asm/esr.h>
```
ESR 예외 원인 레지스터의 종류와 필드를 해석할 정의를 가져옵니다. 예외 번호와 시스템 호출 번호는 서로 다른 정보입니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 22행
```asm
#include <asm/irq.h>
```
ARM64 IRQ의 스택 크기와 관련 인터럽트 정의를 가져옵니다. 별도 IRQ 스택으로 실행을 옮길 때 task 스택과 크기·역할을 구분하는 데 필요합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 23행
```asm
#include <asm/memory.h>
```
커널 가상 주소 배치, 스택 크기·정렬, 주소 변환 관련 상수를 가져옵니다. 같은 숫자가 주소·크기·오프셋 중 무엇인지 각 사용 문맥에서 구분해야 합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 24행
```asm
#include <asm/mmu.h>
```
주소 공간 식별과 MMU 상태 관련 정의를 가져옵니다. 소프트웨어 PAN과 KPTI의 TTBR·ASID 전환에서 주소 변환 문맥을 구분하는 데 사용합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 25행
```asm
#include <asm/processor.h>
```
ARM64 task의 CPU 실행 상태에 관한 정의를 가져옵니다. 저장 문맥과 task별 제어 정보를 저수준 코드에서 일관되게 해석하도록 합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 26행
```asm
#include <asm/ptrace.h>
```
pt_regs와 예외 전 PSTATE의 레벨·폭·마스크 비트 정의를 가져옵니다. 저장된 실행 문맥을 구성하고 32/64비트 복귀 상태를 해석하는 데 필요합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 27행
```asm
#include <asm/scs.h>
```
별도 보호 스택에 복귀 주소를 보관하는 Shadow Call Stack의 저장·복원 규칙을 가져옵니다. task나 IRQ 문맥을 바꿀 때 일반 SP와 SCS 포인터를 함께 관리해야 합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 28행
```asm
#include <asm/stacktrace/frame.h>
```
스택 추적기의 프레임 메타데이터 형식을 가져옵니다. 예외가 끊은 커널 프레임인지 추적의 마지막 프레임인지 표시하여 FP 연결을 안전하게 해석하게 합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 29행
```asm
#include <asm/thread_info.h>
```
task의 저수준 실행 플래그와 스택 관련 정의를 가져옵니다. 예외 진입·복귀에서 single-step·SSBD·MTE 등 task별 처리를 고를 때 쓰는 정보입니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 30행
```asm
#include <asm/asm-uaccess.h>
```
커널의 사용자 메모리 접근을 열고 닫는 어셈블리 매크로를 가져옵니다. 소프트웨어 PAN에서 TTBR0를 예약 표와 사용자 표 사이로 바꾸는 구현을 제공합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 31행
```asm
#include <asm/unistd.h>
```
ARM64 시스템 호출 관련 번호와 정의를 가져옵니다. 예외로 들어왔다는 사실만으로 syscall이라고 단정하지 않도록 NO_SYSCALL 같은 구분도 함께 사용합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 33행
```asm
	.macro	clear_gp_regs
```
x0~x29를 0으로 정리할 매크로입니다. 사용자 예외 진입에서는 원래 값을 먼저 pt_regs에 저장한 뒤 사용하여 복귀 상태를 보존하면서 커널이 불필요한 사용자 레지스터 값을 계속 들고 있지 않게 합니다. 이 줄은 어셈블러 매크로 정의의 시작입니다. 본문은 매크로를 사용하는 위치에 펼쳐지며, 정의 자체가 런타임 함수 호출은 아닙니다.

### 34행
```asm
	.irp	n,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29
```
여러 레지스터 이름에 대해 같은 패턴을 반복 생성합니다. 소스에는 짧게 적혀도 결과에는 여러 명령이 생깁니다. n,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29

### 35행
```asm
	mov	x\n, xzr
```
x\n에 0을 넣습니다. 원래 x\n의 내용은 덮어쓰며, 다른 레지스터나 그 주소의 메모리까지 복사하지는 않습니다.

### 36행
```asm
	.endr
```
반복 생성할 범위의 끝입니다. 

### 37행
```asm
	.endm
```
공통 작업의 정의를 여기서 마칩니다. RET가 아니라 빌드 도구가 읽는 경계입니다. 

## 벡터 슬롯과 스택 초과 검사
원본 39–101행

### 39행
```asm
	.macro kernel_ventry, el:req, ht:req, regsize:req, label:req
```
128바이트 예외 벡터 입구 하나를 만드는 매크로입니다. el은 중단된 레벨, ht는 SP 선택, regsize는 AArch64/AArch32, label은 sync·irq·fiq·error 종류입니다. 저장 공간과 스택 넘침 검사를 마련한 뒤 그 조합의 실제 handler로 연결합니다. 이 줄은 어셈블러 매크로 정의의 시작입니다. 본문은 매크로를 사용하는 위치에 펼쳐지며, 정의 자체가 런타임 함수 호출은 아닙니다.

### 40행
```asm
	.align 7
```
벡터 입구를 2^7=128바이트 경계에 맞춥니다. AArch64 하드웨어가 예외 종류별 고정 offset으로 입구를 고르기 때문에 단순한 성능 정렬이 아니라 벡터 배치 규칙입니다.

### 41행
```asm
.Lventry_start\@:
```
여기에 .Lventry_start\@라는 위치 이름을 붙입니다. 다른 줄의 분기나 주소 계산이 이 위치를 찾아올 수 있습니다. 이름 자체가 CPU 작업을 추가하지는 않습니다.

### 42행
```asm
	.if	\el == 0
```
어셈블 단계에서 인자나 식을 검사해 필요한 명령만 만듭니다. \el == 0

### 47행
```asm
	b	.Lskip_tramp_vectors_cleanup\@
```
.Lskip_tramp_vectors_cleanup\@부터 실행을 계속합니다. 돌아올 주소를 새로 적지 않으므로 일반 함수 호출과는 다른 제어 이동입니다.

### 48행
```asm
	.if	\regsize == 64
```
어셈블 단계에서 인자나 식을 검사해 필요한 명령만 만듭니다. \regsize == 64

### 49행
```asm
	mrs	x30, tpidrro_el0
```
trampoline이 임시로 TPIDRRO_EL0에 보관한 사용자 x30을 복구합니다. 일반 TLS 값을 읽는 문맥이 아니라 제한된 매핑으로 들어오는 동안 잃지 않도록 맡겨 둔 복귀 주소를 되찾는 단계입니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 50행
```asm
	msr	tpidrro_el0, xzr
```
임시 저장에 사용한 TPIDRRO_EL0를 0으로 정리합니다. 사용자 x30은 앞줄에서 복구했으며 이후 커널 진입이 이 임시값을 다시 사용자 데이터로 오인하지 않게 합니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 51행
```asm
	.else
```
앞 어셈블 조건의 반대 경우에 생성할 코드입니다. 

### 52행
```asm
	mov	x30, xzr
```
x30에 0을 넣습니다. 원래 x30의 내용은 덮어쓰며, 다른 레지스터나 그 주소의 메모리까지 복사하지는 않습니다.

### 53행
```asm
	.endif
```
어셈블 조건에 따라 생성할 코드를 나누던 범위가 끝납니다. 

### 54행
```asm
.Lskip_tramp_vectors_cleanup\@:
```
여기에 .Lskip_tramp_vectors_cleanup\@라는 위치 이름을 붙입니다. 다른 줄의 분기나 주소 계산이 이 위치를 찾아올 수 있습니다. 이름 자체가 CPU 작업을 추가하지는 않습니다.

### 55행
```asm
	.endif
```
어셈블 조건에 따라 생성할 코드를 나누던 범위가 끝납니다. 

### 57행
```asm
	sub	sp, sp, #PT_REGS_SIZE
```
pt_regs를 놓을 만큼 커널 SP를 낮춥니다. 뒤의 레지스터 저장이 호출자의 스택 자료를 덮지 않게 확보하는 공간입니다. 이 명령은 SP의 주소값을 조정하며, 확보하거나 반환하는 스택 공간의 데이터를 직접 저장하거나 지우지는 않습니다. 이 SUB 형식은 NZCV 조건 플래그를 바꾸지 않습니다.
pt_regs 프레임을 위한 공간 확보입니다. 이 줄 자체가 기존 레지스터 값을 저장하지는 않습니다.

### 63행
```asm
	add	sp, sp, x0			// sp' = sp + x0
```
sp를 기준으로 x0을 더해 sp에 결과를 둡니다. 주소 계산에 쓰이는 경우에는 구조체의 필드나 다음 저장 위치를 찾는 동작입니다. 이 형태는 조건 플래그를 바꾸지 않습니다.
x0를 잃지 않고 원래 SP를 검사하기 위한 임시 산술입니다. 일반적인 스택 할당 ADD가 아닙니다.

### 64행
```asm
	sub	x0, sp, x0			// x0' = sp' - x0 = (sp + x0) - x0 = sp
```
sp를 기준으로 x0을 빼서 x0에 결과를 둡니다. 주소 계산에 쓰이는 경우에는 구조체의 필드나 다음 저장 위치를 찾는 동작입니다. 이 형태는 조건 플래그를 바꾸지 않습니다.
새 x0=(원래 SP−PT_REGS_SIZE)입니다.

### 65행
```asm
	tbnz	x0, #THREAD_SHIFT, 0f
```
THREAD_SHIFT 위치의 비트로 정렬된 task 스택 경계를 넘어갔는지 검사합니다. 이 구간은 임시 레지스터를 잃지 않도록 SP와 x0의 가역 계산을 사용해 원래 SP를 x0에 복원한 상태입니다. 이 분기는 지정 레지스터의 값이나 비트를 직접 검사하며 CMP가 남긴 NZCV를 읽거나 바꾸지 않습니다. 조건이 맞지 않으면 바로 다음 명령으로 진행합니다.
스택 정렬 규칙에 따라 THREAD_SHIFT 비트가 1이면 overflow 경로를 선택합니다.

### 66행
```asm
	sub	x0, sp, x0			// x0'' = sp' - x0' = (sp + x0) - sp = x0
```
sp를 기준으로 x0을 빼서 x0에 결과를 둡니다. 주소 계산에 쓰이는 경우에는 구조체의 필드나 다음 저장 위치를 찾는 동작입니다. 이 형태는 조건 플래그를 바꾸지 않습니다.
원래 x0 값을 재구성합니다.

### 67행
```asm
	sub	sp, sp, x0			// sp'' = sp' - x0 = (sp + x0) - x0 = sp
```
sp를 기준으로 x0을 빼서 sp에 결과를 둡니다. 주소 계산에 쓰이는 경우에는 구조체의 필드나 다음 저장 위치를 찾는 동작입니다. 이 형태는 조건 플래그를 바꾸지 않습니다.
pt_regs 공간을 확보한 SP로 되돌립니다.

### 68행
```asm
	b	el\el\ht\()_\regsize\()_\label
```
el\el\ht\()_\regsize\()_\label부터 실행을 계속합니다. 돌아올 주소를 새로 적지 않으므로 일반 함수 호출과는 다른 제어 이동입니다.

### 70행
```asm
0:
```
여기에 0라는 위치 이름을 붙입니다. 다른 줄의 분기나 주소 계산이 이 위치를 찾아올 수 있습니다. 이름 자체가 CPU 작업을 추가하지는 않습니다.

### 78행
```asm
	msr	tpidr_el0, x0
```
스택 넘침 처리에서 원래 SP를 TPIDR_EL0에 임시 보관합니다. 보통 사용자 TLS 용도로 알려진 레지스터지만 이 비정상 진입 구간에서는 안전한 스택으로 옮기기 위한 scratch 저장소입니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 81행
```asm
	sub	x0, sp, x0
```
sp를 기준으로 x0을 빼서 x0에 결과를 둡니다. 주소 계산에 쓰이는 경우에는 구조체의 필드나 다음 저장 위치를 찾는 동작입니다. 이 형태는 조건 플래그를 바꾸지 않습니다.

### 82행
```asm
	msr	tpidrro_el0, x0
```
스택 검사 중 보존해야 할 원래 x0를 TPIDRRO_EL0에 임시 저장합니다. SP를 overflow stack으로 바꾼 뒤 원래 예외 문맥을 다시 만들기 위해서입니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 85행
```asm
	adr_this_cpu sp, overflow_stack + OVERFLOW_STACK_SIZE, x0
```
현재 CPU의 overflow stack 높은 주소 끝을 SP로 선택합니다. 정상 task 스택이 넘쳤을 수 있으므로 같은 스택에 오류 처리 프레임을 더 쌓지 않게 합니다. 심벌의 기준 주소에 현재 CPU의 per-CPU 오프셋을 더해 이 CPU의 저장 위치를 찾습니다.
overflow_stack의 끝 주소에서 낮은 주소 방향으로 스택을 사용합니다.

### 91행
```asm
	mrs	x0, tpidr_el0			// sp of interrupted context
```
TPIDR_EL0에 맡겨 둔 중단 시 SP를 다시 읽습니다. 새 overflow stack 범위 안에서 이미 처리 중이었는지 다음 차이 계산으로 검사합니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 92행
```asm
	sub	x0, sp, x0			// delta with top of overflow stack
```
sp를 기준으로 x0을 빼서 x0에 결과를 둡니다. 주소 계산에 쓰이는 경우에는 구조체의 필드나 다음 저장 위치를 찾는 동작입니다. 이 형태는 조건 플래그를 바꾸지 않습니다.

### 93행
```asm
	tst	x0, #~(OVERFLOW_STACK_SIZE - 1)	// within range?
```
원래 SP와 overflow stack 상단의 차이가 OVERFLOW_STACK_SIZE 범위에 들어가는지 검사합니다. 이미 overflow stack에서 발생한 중첩 예외와 최초 스택 손상을 구분하려는 조건입니다. TST는 두 피연산자의 비트 AND 결과로 조건 플래그를 갱신하며 원래 레지스터 값은 바꾸지 않습니다.

### 94행
```asm
	b.ne	__bad_stack			// no? -> bad stack pointer
```
앞서 계산한 조건 플래그가 Z=0이면 __bad_stack로 갑니다. 사이에 플래그를 바꾸지 않는 명령이 있어도 비교 결과는 남아 있습니다.

### 97행
```asm
	sub	sp, sp, x0
```
sp를 기준으로 x0을 빼서 sp에 결과를 둡니다. 주소 계산에 쓰이는 경우에는 구조체의 필드나 다음 저장 위치를 찾는 동작입니다. 이 형태는 조건 플래그를 바꾸지 않습니다.

### 98행
```asm
	mrs	x0, tpidrro_el0
```
임시 TPIDRRO_EL0에서 원래 x0를 복구하여 정상 handler가 보는 저장 문맥을 유지합니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 99행
```asm
	b	el\el\ht\()_\regsize\()_\label
```
el\el\ht\()_\regsize\()_\label부터 실행을 계속합니다. 돌아올 주소를 새로 적지 않으므로 일반 함수 호출과는 다른 제어 이동입니다.

### 100행
```asm
.org .Lventry_start\@ + 128	// Did we overflow the ventry slot?
```
이 벡터 입구가 시작부터 128바이트를 넘지 않도록 assembler 위치를 강제합니다. 코드가 슬롯보다 커지면 빌드에서 드러나게 하여 다음 벡터를 덮어쓰지 않게 합니다.

### 101행
```asm
	.endm
```
공통 작업의 정의를 여기서 마칩니다. RET가 아니라 빌드 도구가 읽는 경계입니다. 

## trampoline 주소와 SSBD
원본 102–128행

### 103행
```asm
	.macro	tramp_alias, dst, sym
```
trampoline의 최소 매핑에서 접근할 별칭 주소를 dst에 만드는 매크로입니다. sym의 일반 커널 가상 주소 대신 TRAMP_VALIAS와 섹션 내부 offset을 사용하여 커널 전체 매핑을 없앤 뒤에도 해당 코드를 실행할 수 있게 합니다. 이 줄은 어셈블러 매크로 정의의 시작입니다. 본문은 매크로를 사용하는 위치에 펼쳐지며, 정의 자체가 런타임 함수 호출은 아닙니다.

### 104행
```asm
	.set	.Lalias\@, TRAMP_VALIAS + \sym - .entry.tramp.text
```
sym이 .entry.tramp.text 시작에서 떨어진 거리에 고정 trampoline 가상 기준을 더합니다. 같은 코드 바이트의 다른 매핑 주소를 계산하는 식이며 코드를 복사하지 않습니다. 이름에 식의 값을 부여하는 어셈블러 지시문입니다. 실행 중 일반 레지스터나 메모리에 값을 쓰는 명령은 아닙니다.

### 105행
```asm
	movz	\dst, :abs_g2_s:.Lalias\@
```
한 명령에 담기 어려운 큰 값이나 주소를 레지스터에 구성합니다. 즉시값 조각과 재배치 표현을 사용하며, 메모리에서 그 주소의 본문을 복사하는 작업은 아닙니다.

### 106행
```asm
	movk	\dst, :abs_g1_nc:.Lalias\@
```
한 명령에 담기 어려운 큰 값이나 주소를 레지스터에 구성합니다. MOVK는 일부 16비트만 덮어쓰므로 앞에서 만든 나머지 조각을 유지합니다.

### 107행
```asm
	movk	\dst, :abs_g0_nc:.Lalias\@
```
한 명령에 담기 어려운 큰 값이나 주소를 레지스터에 구성합니다. MOVK는 일부 16비트만 덮어쓰므로 앞에서 만든 나머지 조각을 유지합니다.

### 108행
```asm
	.endm
```
공통 작업의 정의를 여기서 마칩니다. RET가 아니라 빌드 도구가 읽는 경계입니다. 

### 114행
```asm
	.macro	apply_ssbd, state, tmp1, tmp2
```
Spectre v4의 speculative store bypass 완화를 펌웨어에 요청하는 매크로입니다. state가 1이면 커널 진입 쪽의 활성 요청, 0이면 사용자 복귀 쪽 요청에 쓰이며 CPU·task 정책상 필요할 때만 SMCCC 호출이 남습니다. 이 줄은 어셈블러 매크로 정의의 시작입니다. 본문은 매크로를 사용하는 위치에 펼쳐지며, 정의 자체가 런타임 함수 호출은 아닙니다.

### 115행
```asm
alternative_cb	ARM64_ALWAYS_SYSTEM, spectre_v4_patch_fw_mitigation_enable
```
시스템 전체의 Spectre v4 정책을 본 callback이 다음 skip 분기를 NOP로 바꿀지 결정하도록 패치 지점을 등록합니다. ARM64_ALWAYS_SYSTEM은 여기서 정책 callback을 실행시키는 공통 capability이며 모든 CPU가 무조건 펌웨어 완화를 호출한다는 뜻은 아닙니다. 이 매크로는 기본·교체 명령의 패치 정보를 만들며, CPU가 이 줄을 매번 if 조건처럼 실행하지 않습니다.

### 116행
```asm
	b	.L__asm_ssbd_skip\@		// Patched to NOP
```
.L__asm_ssbd_skip\@부터 실행을 계속합니다. 돌아올 주소를 새로 적지 않으므로 일반 함수 호출과는 다른 제어 이동입니다.

### 117행
```asm
alternative_cb_end
```
115행에서 시작한 ARM64_ALWAYS_SYSTEM, spectre_v4_patch_fw_mitigation_enable 패치 묶음을 닫습니다. callback이 선택한 분기·NOP·펌웨어 호출 명령이 같은 자리를 사용하도록 교체 범위를 확정합니다. callback 함수가 여기서 매번 호출되는 것은 아닙니다.

### 118행
```asm
	ldr_this_cpu	\tmp2, arm64_ssbd_callback_required, \tmp1
```
이 CPU가 SSBD 변경을 펌웨어 callback으로 처리해야 하는지 per-CPU 플래그를 읽습니다. 하드웨어나 정책에 따라 callback이 필요 없는 CPU는 뒤에서 바로 건너뜁니다. 심벌의 기준 주소에 현재 CPU의 per-CPU 오프셋을 더해 이 CPU의 저장 위치를 찾습니다.

### 119행
```asm
	cbz	\tmp2,	.L__asm_ssbd_skip\@
```
\tmp2의 값가 0이면 .L__asm_ssbd_skip\@로 갑니다. 조건을 만족하지 않으면 바로 다음 명령을 읽습니다. 이 분기는 CMP의 결과가 아니라 지정 레지스터를 직접 검사합니다.

### 120행
```asm
	ldr	\tmp2, [tsk, #TSK_TI_FLAGS]
```
tsk를 기준으로 TSK_TI_FLAGS만큼 계산한 메모리 위치에 보관된 값을 \tmp2로 가져옵니다. 주소 숫자를 옮기는 MOV와 달리 실제 메모리를 읽습니다.

### 121행
```asm
	tbnz	\tmp2, #TIF_SSBD, .L__asm_ssbd_skip\@
```
task의 TIF_SSBD 정책이 이미 완화를 유지하도록 요구하는 경우 진입·복귀 때 상태를 바꾸는 호출을 건너뜁니다. 비트가 세워졌다는 사실만 보고 여기서 취약성 완화를 끈다고 해석하면 안 됩니다. 이 분기는 지정 레지스터의 값이나 비트를 직접 검사하며 CMP가 남긴 NZCV를 읽거나 바꾸지 않습니다. 조건이 맞지 않으면 바로 다음 명령으로 진행합니다.

### 122행
```asm
	mov	w0, #ARM_SMCCC_ARCH_WORKAROUND_2
```
SMCCC ARCH_WORKAROUND_2 함수 ID를 첫 인수 w0에 넣습니다. 펌웨어에 Spectre v4의 SSBD 상태를 요청하는 인터페이스 번호입니다. MOV는 w0의 기존 값을 대체합니다. 원본이 주소값이어도 그 주소의 메모리를 읽거나 복사하지 않습니다. w0에 쓰면 대응하는 x0의 상위 32비트도 0이 됩니다.

### 123행
```asm
	mov	w1, #\state
```
매크로 호출자가 지정한 완화 상태 state를 펌웨어의 두 번째 인수 w1에 넣습니다. 이 값은 호출 서비스 번호와 구분됩니다. MOV는 w1의 기존 값을 대체합니다. 원본이 주소값이어도 그 주소의 메모리를 읽거나 복사하지 않습니다. w1에 쓰면 대응하는 x1의 상위 32비트도 0이 됩니다.

### 124행
```asm
alternative_cb	ARM64_ALWAYS_SYSTEM, smccc_patch_fw_mitigation_conduit
```
시스템의 SMCCC 통로에 맞춰 다음 NOP를 SMC 또는 HVC 명령으로 바꾸는 callback을 등록합니다. 펌웨어가 어느 레벨에서 서비스를 제공하는지에 맞추기 위한 코드 패치입니다. 이 매크로는 기본·교체 명령의 패치 정보를 만들며, CPU가 이 줄을 매번 if 조건처럼 실행하지 않습니다.

### 125행
```asm
	nop					// Patched to SMC/HVC #0
```
초기 NOP는 펌웨어 완화 통로가 결정되면 SMC #0 또는 HVC #0으로 바뀔 수 있습니다. 실행 시에는 w0의 서비스 번호와 w1의 상태를 그 통로로 전달합니다.

### 126행
```asm
alternative_cb_end
```
124행에서 시작한 ARM64_ALWAYS_SYSTEM, smccc_patch_fw_mitigation_conduit 패치 묶음을 닫습니다. callback이 선택한 분기·NOP·펌웨어 호출 명령이 같은 자리를 사용하도록 교체 범위를 확정합니다. callback 함수가 여기서 매번 호출되는 것은 아닙니다.

### 127행
```asm
.L__asm_ssbd_skip\@:
```
여기에 .L__asm_ssbd_skip\@라는 위치 이름을 붙입니다. 다른 줄의 분기나 주소 계산이 이 위치를 찾아올 수 있습니다. 이름 자체가 CPU 작업을 추가하지는 않습니다.

### 128행
```asm
	.endm
```
공통 작업의 정의를 여기서 마칩니다. RET가 아니라 빌드 도구가 읽는 경계입니다. 

## MTE 비동기 오류와 태그 생성 설정
원본 129–196행

### 131행
```asm
	.macro check_mte_async_tcf, tmp, ti_flags, thread_sctlr
```
사용자 실행 중 누적된 비동기 MTE 태그 오류를 task 플래그에 반영하는 매크로입니다. tmp는 상태 조회용, ti_flags는 task 플래그 주소용, thread_sctlr는 이 task의 검사 mode를 담습니다. 이 줄은 어셈블러 매크로 정의의 시작입니다. 본문은 매크로를 사용하는 위치에 펼쳐지며, 정의 자체가 런타임 함수 호출은 아닙니다.

### 132행
```asm
#ifdef CONFIG_ARM64_MTE
```
MTE는 포인터의 논리 태그와 메모리의 할당 태그를 검사해 일부 잘못된 접근을 찾는 기능입니다. 지원 빌드에만 비동기 오류 조회를 포함하며, 꺼지면 이 매크로의 본문이 사라집니다. 실제 CPU 지원은 아래 alternative에서 다시 확인합니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.

### 133행
```asm
	.arch_extension lse
```
어셈블러가 뒤의 LSE 원자 명령 STSET를 받아들이도록 명령 확장을 허용합니다. 여기서 CPU 기능을 켜는 것이 아니라 MTE 오류 플래그를 원자적으로 OR할 명령의 빌드 문법을 준비합니다.

### 134행
```asm
alternative_if_not ARM64_MTE
```
실제 시스템에 ARM64_MTE가 없으면 다음 건너뛰기 분기가 남도록 합니다. MTE가 있으면 그 분기를 NOP로 바꿔 태그 오류 레지스터를 검사합니다. 이 매크로는 기본·교체 명령의 패치 정보를 만들며, CPU가 이 줄을 매번 if 조건처럼 실행하지 않습니다.

### 135행
```asm
	b	1f
```
1f (L149)부터 실행을 계속합니다. 돌아올 주소를 새로 적지 않으므로 일반 함수 호출과는 다른 제어 이동입니다.

### 136행
```asm
alternative_else_nop_endif
```
134행에서 시작한 ARM64_MTE 패치 묶음을 닫습니다. 조건에 맞는 본문을 사용하지 않는 쪽은 같은 길이의 NOP로 채워 뒤 명령의 배치를 유지합니다. 이 줄 자체가 CPU에서 조건을 검사하는 명령은 아닙니다.

### 142행
```asm
	tbz	\thread_sctlr, #(SCTLR_EL1_TCF0_SHIFT + 1), 1f
```
task의 TCF0 mode가 비동기 또는 비대칭 검사에 해당하는지 관련 비트를 확인합니다. 동기 mode만 사용하면 이 누적 오류 조회는 필요 없어 끝으로 갑니다. 이 분기는 지정 레지스터의 값이나 비트를 직접 검사하며 CMP가 남긴 NZCV를 읽거나 바꾸지 않습니다. 조건이 맞지 않으면 바로 다음 명령으로 진행합니다.

### 143행
```asm
	mrs_s	\tmp, SYS_TFSRE0_EL1
```
TFSRE0_EL1에서 EL0 태그 검사 실패의 누적 상태를 읽습니다. 비동기 실패는 원래 접근 즉시 일반 동기 fault로 처리되지 않을 수 있으므로 커널 진입 때 수거합니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 144행
```asm
	tbz	\tmp, #SYS_TFSR_EL1_TF0_SHIFT, 1f
```
EL0 태그 실패 표식 TF0가 없으면 task에 새 오류를 표시할 필요가 없습니다. 세워져 있을 때만 다음 원자적 플래그 갱신을 합니다. 이 분기는 지정 레지스터의 값이나 비트를 직접 검사하며 CMP가 남긴 NZCV를 읽거나 바꾸지 않습니다. 조건이 맞지 않으면 바로 다음 명령으로 진행합니다.

### 146행
```asm
	mov	\tmp, #_TIF_MTE_ASYNC_FAULT
```
후속 사용자 복귀 경로가 비동기 MTE 오류를 처리하도록 _TIF_MTE_ASYNC_FAULT 마스크를 준비합니다. MOV는 \tmp의 기존 값을 대체합니다. 원본이 주소값이어도 그 주소의 메모리를 읽거나 복사하지 않습니다.

### 147행
```asm
	add	\ti_flags, tsk, #TSK_TI_FLAGS
```
tsk를 기준으로 TSK_TI_FLAGS을 더해 \ti_flags에 결과를 둡니다. 주소 계산에 쓰이는 경우에는 구조체의 필드나 다음 저장 위치를 찾는 동작입니다. 이 형태는 조건 플래그를 바꾸지 않습니다.

### 148행
```asm
	stset	\tmp, [\ti_flags]
```
task의 thread-info flags에 MTE 오류 비트를 원자적으로 OR합니다. 다른 비트 갱신을 잃지 않고 비동기 태그 실패를 커널의 후속 처리에 전달하기 위해 STSET를 사용합니다.

### 149행
```asm
1:
```
여기에 1라는 위치 이름을 붙입니다. 다른 줄의 분기나 주소 계산이 이 위치를 찾아올 수 있습니다. 이름 자체가 CPU 작업을 추가하지는 않습니다.

### 150행
```asm
#endif
```
빌드 조건으로 나누었던 구간을 닫습니다. 대상: 

### 151행
```asm
	.endm
```
공통 작업의 정의를 여기서 마칩니다. RET가 아니라 빌드 도구가 읽는 경계입니다. 

### 154행
```asm
	.macro clear_mte_async_tcf thread_sctlr
```
사용자 복귀 전에 이전 비동기 MTE 실패의 누적 상태를 지우는 매크로입니다. 인수 thread_sctlr로 비동기 수거 대상 mode인지 확인해 필요 없는 레지스터 작업은 생략합니다. 이 줄은 어셈블러 매크로 정의의 시작입니다. 본문은 매크로를 사용하는 위치에 펼쳐지며, 정의 자체가 런타임 함수 호출은 아닙니다.

### 155행
```asm
#ifdef CONFIG_ARM64_MTE
```
MTE 지원 빌드에만 오류 상태 정리 코드를 포함합니다. 이 기능이 꺼져 있으면 MTE 시스템 레지스터를 쓰는 본문은 생성되지 않습니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.

### 156행
```asm
alternative_if ARM64_MTE
```
실제 MTE 지원이 확인된 시스템에만 아래 TFSRE0_EL1 정리가 남습니다. 빌드 지원과 실제 CPU 기능을 나누어 없는 레지스터에 접근하지 않게 합니다. 이 매크로는 기본·교체 명령의 패치 정보를 만들며, CPU가 이 줄을 매번 if 조건처럼 실행하지 않습니다.

### 158행
```asm
	tbz	\thread_sctlr, #(SCTLR_EL1_TCF0_SHIFT + 1), 1f
```
\thread_sctlr의 (SCTLR_EL1_TCF0_SHIFT + 1)번 비트가 0이면 1f (L161)로 갑니다. 조건을 만족하지 않으면 바로 다음 명령을 읽습니다. 이 분기는 CMP의 결과가 아니라 지정 레지스터를 직접 검사합니다.

### 159행
```asm
	dsb	ish
```
기존 태그 검사 결과가 필요한 공유 범위에서 반영된 뒤 상태를 지우도록 DSB ISH를 수행합니다. 누적 실패가 늦게 나타나 다음 사용자 실행에 섞이는 것을 피하려는 순서입니다. DSB는 지정한 범위에 대해 앞선 작업의 완료를 기다린 뒤 다음 명령으로 진행하게 합니다.

### 160행
```asm
	msr_s	SYS_TFSRE0_EL1, xzr
```
TFSRE0_EL1의 누적 오류 표식을 0으로 지웁니다. 앞에서 task 플래그로 넘긴 오류와 이후 사용자 실행에서 발생할 새 오류를 구분하기 위해서입니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 161행
```asm
1:
```
여기에 1라는 위치 이름을 붙입니다. 다른 줄의 분기나 주소 계산이 이 위치를 찾아올 수 있습니다. 이름 자체가 CPU 작업을 추가하지는 않습니다.

### 162행
```asm
alternative_else_nop_endif
```
156행에서 시작한 ARM64_MTE 패치 묶음을 닫습니다. 조건에 맞는 본문을 사용하지 않는 쪽은 같은 길이의 NOP로 채워 뒤 명령의 배치를 유지합니다. 이 줄 자체가 CPU에서 조건을 검사하는 명령은 아닙니다.

### 163행
```asm
#endif
```
빌드 조건으로 나누었던 구간을 닫습니다. 대상: 

### 164행
```asm
	.endm
```
공통 작업의 정의를 여기서 마칩니다. RET가 아니라 빌드 도구가 읽는 경계입니다. 

### 166행
```asm
	.macro mte_set_gcr, mte_ctrl, tmp
```
MTE 태그 생성 제어 GCR_EL1을 task 정책에 맞게 만드는 매크로입니다. mte_ctrl에서 제외 태그 마스크를 꺼내 임의 태그 생성 제어와 함께 tmp에 만든 뒤 레지스터에 씁니다. 이 줄은 어셈블러 매크로 정의의 시작입니다. 본문은 매크로를 사용하는 위치에 펼쳐지며, 정의 자체가 런타임 함수 호출은 아닙니다.

### 167행
```asm
#ifdef CONFIG_ARM64_MTE
```
MTE 지원 빌드에서만 태그 생성 제어를 갱신합니다. 꺼진 빌드에는 GCR_EL1 쓰기가 없어 일반 CPU 실행 경로에 영향을 주지 않습니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.

### 168행
```asm
	ubfx	\tmp, \mte_ctrl, #MTE_CTRL_GCR_USER_EXCL_SHIFT, #16
```
task의 mte_ctrl에서 사용자 제외 태그 마스크 16비트를 꺼냅니다. 각 비트는 임의 태그 생성에서 제외할 태그를 나타내며 주소의 16비트 일부가 아닙니다. 추출한 필드는 결과의 하위 비트에 놓이고 나머지 상위 비트는 0으로 채워집니다.

### 169행
```asm
	orr	\tmp, \tmp, #SYS_GCR_EL1_RRND
```
GCR_EL1의 RRND 비트를 더해 지원되는 임의 태그 선택 동작을 사용하도록 합니다. 제외 마스크와 임의 생성 제어는 다른 필드입니다. ORR은 어느 한쪽이 1인 비트를 결과에 세우므로 원래 1이던 비트도 유지됩니다.

### 170행
```asm
	msr_s	SYS_GCR_EL1, \tmp
```
계산한 제외 태그와 생성 정책을 GCR_EL1에 설치합니다. 이후 태그 생성 명령이 현재 task에 맞는 정책을 사용하게 합니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 171행
```asm
#endif
```
빌드 조건으로 나누었던 구간을 닫습니다. 대상: 

### 172행
```asm
	.endm
```
공통 작업의 정의를 여기서 마칩니다. RET가 아니라 빌드 도구가 읽는 경계입니다. 

### 174행
```asm
	.macro mte_set_kernel_gcr, tmp, tmp2
```
커널의 하드웨어 태그 KASAN이 사용하는 태그 생성 정책으로 GCR을 전환하는 매크로입니다. 사용자 정책을 그대로 쓰면 커널이 기대한 태그 범위와 달라질 수 있습니다. 이 줄은 어셈블러 매크로 정의의 시작입니다. 본문은 매크로를 사용하는 위치에 펼쳐지며, 정의 자체가 런타임 함수 호출은 아닙니다.

### 175행
```asm
#ifdef CONFIG_KASAN_HW_TAGS
```
KASAN_HW_TAGS는 Arm MTE 하드웨어로 커널 메모리 태그를 검사하는 KASAN 방식입니다. 켜진 빌드에만 커널용 GCR 전환을 포함하고, 꺼지면 이 매크로는 비어 있습니다. 소프트웨어 shadow를 사용하는 GENERIC/SW_TAGS와 구분합니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.

### 176행
```asm
alternative_cb	ARM64_ALWAYS_SYSTEM, kasan_hw_tags_enable
```
KASAN 하드웨어 태그 검사를 런타임에도 활성화했는지 보는 callback입니다. 활성인 경우 다음 skip 분기를 NOP로 바꾸어 커널 GCR을 설치하고, 비활성이면 건너뜁니다. 이 매크로는 기본·교체 명령의 패치 정보를 만들며, CPU가 이 줄을 매번 if 조건처럼 실행하지 않습니다.

### 177행
```asm
	b	1f
```
1f (L181)부터 실행을 계속합니다. 돌아올 주소를 새로 적지 않으므로 일반 함수 호출과는 다른 제어 이동입니다.

### 178행
```asm
alternative_cb_end
```
176행에서 시작한 ARM64_ALWAYS_SYSTEM, kasan_hw_tags_enable 패치 묶음을 닫습니다. callback이 선택한 분기·NOP·펌웨어 호출 명령이 같은 자리를 사용하도록 교체 범위를 확정합니다. callback 함수가 여기서 매번 호출되는 것은 아닙니다.

### 179행
```asm
	mov	\tmp, KERNEL_GCR_EL1
```
커널 태그 KASAN이 사용할 GCR_EL1의 제외 마스크와 생성 설정 KERNEL_GCR_EL1을 준비합니다. 사용자 task의 mte_ctrl 값과 의도적으로 다른 정책입니다. MOV는 \tmp의 기존 값을 대체합니다. 원본이 주소값이어도 그 주소의 메모리를 읽거나 복사하지 않습니다.

### 180행
```asm
	msr_s	SYS_GCR_EL1, \tmp
```
커널 메모리용 태그 생성 제어를 실제 GCR_EL1에 씁니다. 사용자 진입에서 커널 태그 정책으로 넘어오는 지점입니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 181행
```asm
1:
```
여기에 1라는 위치 이름을 붙입니다. 다른 줄의 분기나 주소 계산이 이 위치를 찾아올 수 있습니다. 이름 자체가 CPU 작업을 추가하지는 않습니다.

### 182행
```asm
#endif
```
빌드 조건으로 나누었던 구간을 닫습니다. 대상: 

### 183행
```asm
	.endm
```
공통 작업의 정의를 여기서 마칩니다. RET가 아니라 빌드 도구가 읽는 경계입니다. 

### 185행
```asm
	.macro mte_set_user_gcr, tsk, tmp, tmp2
```
커널 하드웨어 태그 KASAN 때문에 바뀐 GCR을 사용자 task 정책으로 되돌리는 매크로입니다. tsk에서 mte_ctrl을 읽어 mte_set_gcr에 넘기며 tmp·tmp2를 계산용으로 씁니다. 이 줄은 어셈블러 매크로 정의의 시작입니다. 본문은 매크로를 사용하는 위치에 펼쳐지며, 정의 자체가 런타임 함수 호출은 아닙니다.

### 186행
```asm
#ifdef CONFIG_KASAN_HW_TAGS
```
KASAN_HW_TAGS 구성에서는 커널 실행 중 GCR을 바꾸므로 사용자 복귀 때 task의 태그 생성 정책을 복원해야 합니다. 이 구성이 아니면 해당 전환 코드 자체를 생략합니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.

### 187행
```asm
alternative_cb	ARM64_ALWAYS_SYSTEM, kasan_hw_tags_enable
```
런타임 하드웨어 태그 KASAN이 실제 활성일 때만 사용자 GCR 복원 경로를 남기는 callback입니다. 커널이 GCR을 바꾸지 않은 경우 불필요한 복원을 피합니다. 이 매크로는 기본·교체 명령의 패치 정보를 만들며, CPU가 이 줄을 매번 if 조건처럼 실행하지 않습니다.

### 188행
```asm
	b	1f
```
1f (L193)부터 실행을 계속합니다. 돌아올 주소를 새로 적지 않으므로 일반 함수 호출과는 다른 제어 이동입니다.

### 189행
```asm
alternative_cb_end
```
187행에서 시작한 ARM64_ALWAYS_SYSTEM, kasan_hw_tags_enable 패치 묶음을 닫습니다. callback이 선택한 분기·NOP·펌웨어 호출 명령이 같은 자리를 사용하도록 교체 범위를 확정합니다. callback 함수가 여기서 매번 호출되는 것은 아닙니다.

### 190행
```asm
	ldr	\tmp, [\tsk, #THREAD_MTE_CTRL]
```
현재 task의 THREAD_MTE_CTRL에서 사용자 태그 생성 정책을 읽습니다. 이전 task나 커널의 제외 마스크를 사용자 실행에 남기지 않으려는 입력입니다. 실제로 \tsk + (THREAD_MTE_CTRL) 주소의 메모리 8바이트를 \tmp에 읽습니다. 이 주소 형식은 기준 레지스터 \tsk 자체를 갱신하지 않습니다.

### 192행
```asm
	mte_set_gcr \tmp, \tmp2
```
현재 task에서 읽은 MTE 제어값으로 사용자 제외 태그 마스크와 RRND를 조합해 GCR_EL1에 기록합니다. 커널 하드웨어 태그 KASAN의 생성 정책을 사용자 복귀에 남기지 않도록 되돌리는 호출입니다.

### 193행
```asm
1:
```
여기에 1라는 위치 이름을 붙입니다. 다른 줄의 분기나 주소 계산이 이 위치를 찾아올 수 있습니다. 이름 자체가 CPU 작업을 추가하지는 않습니다.

### 194행
```asm
#endif
```
빌드 조건으로 나누었던 구간을 닫습니다. 대상: 

### 195행
```asm
	.endm
```
공통 작업의 정의를 여기서 마칩니다. RET가 아니라 빌드 도구가 읽는 경계입니다. 

![단계별 개념 그림](../media/entry-overview.png)
A와 B는 설명용 값입니다. 오른쪽 화살표는 메모리 저장, 왼쪽은 레지스터 복원을 뜻합니다. 여러 명령을 단계별로 묶었습니다.

## kernel_entry: x0–x29 저장과 current 설정
원본 197–280행

### 197행
```asm
	.macro	kernel_entry, el, regsize = 64
```
예외 프레임에 범용 레지스터·SP·PC·PSTATE를 저장하고 current, 보안 제어, unwind 메타데이터를 준비하는 진입 매크로입니다. el=0이면 사용자 진입용 전환을 추가하고 regsize=32이면 AArch32 인수 폭을 정리합니다. 이 줄은 어셈블러 매크로 정의의 시작입니다. 본문은 매크로를 사용하는 위치에 펼쳐지며, 정의 자체가 런타임 함수 호출은 아닙니다.

### 198행
```asm
	.if	\el == 0
```
어셈블 단계에서 인자나 식을 검사해 필요한 명령만 만듭니다. \el == 0

### 199행
```asm
	alternative_insn nop, SET_PSTATE_DIT(1), ARM64_HAS_DIT
```
DIT 지원 CPU에서는 EL0에서 들어올 때 PSTATE.DIT를 1로 만드는 명령으로 NOP를 교체합니다. DIT는 지원 명령의 실행 시간이 데이터 값에 의존하는 것을 제한하는 기능이며 모든 코드의 절대 상수 시간을 보장한다는 뜻은 아닙니다. 이 매크로는 기본·교체 명령의 패치 정보를 만들며, CPU가 이 줄을 매번 if 조건처럼 실행하지 않습니다.

### 200행
```asm
	.endif
```
어셈블 조건에 따라 생성할 코드를 나누던 범위가 끝납니다. 

### 201행
```asm
	.if	\regsize == 32
```
어셈블 단계에서 인자나 식을 검사해 필요한 명령만 만듭니다. \regsize == 32

### 202행
```asm
	mov	w0, w0				// zero upper 32 bits of x0
```
w0에 w0에 들어 있는 값을 넣습니다. 원래 w0의 내용은 덮어쓰며, 다른 레지스터나 그 주소의 메모리까지 복사하지는 않습니다. W 레지스터에 쓰므로 대응 X 레지스터의 상위 절반도 0이 됩니다.
32비트 사용자 x0의 상위 32비트를 확실히 0으로 만듭니다.

### 203행
```asm
	.endif
```
어셈블 조건에 따라 생성할 코드를 나누던 범위가 끝납니다. 

### 204행
```asm
	stp	x0, x1, [sp, #16 * 0]
```
x0, x1의 값을 sp를 기준으로 16 * 0만큼 계산한 메모리 위치에 기록합니다. 레지스터 값을 나중에 다시 쓰거나 다른 코드가 읽을 수 있도록 메모리에 남기는 줄입니다. 첫 위치에는 x0, 그보다 8바이트 높은 위치에는 x1의 값이 들어갑니다. 이 스택 위치는 현재 함수 또는 예외 처리의 보관 공간입니다.

### 205행
```asm
	stp	x2, x3, [sp, #16 * 1]
```
x2, x3의 값을 sp를 기준으로 16 * 1만큼 계산한 메모리 위치에 기록합니다. 레지스터 값을 나중에 다시 쓰거나 다른 코드가 읽을 수 있도록 메모리에 남기는 줄입니다. 첫 위치에는 x2, 그보다 8바이트 높은 위치에는 x3의 값이 들어갑니다. 이 스택 위치는 현재 함수 또는 예외 처리의 보관 공간입니다.

### 206행
```asm
	stp	x4, x5, [sp, #16 * 2]
```
x4, x5의 값을 sp를 기준으로 16 * 2만큼 계산한 메모리 위치에 기록합니다. 레지스터 값을 나중에 다시 쓰거나 다른 코드가 읽을 수 있도록 메모리에 남기는 줄입니다. 첫 위치에는 x4, 그보다 8바이트 높은 위치에는 x5의 값이 들어갑니다. 이 스택 위치는 현재 함수 또는 예외 처리의 보관 공간입니다.

### 207행
```asm
	stp	x6, x7, [sp, #16 * 3]
```
x6, x7의 값을 sp를 기준으로 16 * 3만큼 계산한 메모리 위치에 기록합니다. 레지스터 값을 나중에 다시 쓰거나 다른 코드가 읽을 수 있도록 메모리에 남기는 줄입니다. 첫 위치에는 x6, 그보다 8바이트 높은 위치에는 x7의 값이 들어갑니다. 이 스택 위치는 현재 함수 또는 예외 처리의 보관 공간입니다.

### 208행
```asm
	stp	x8, x9, [sp, #16 * 4]
```
x8, x9의 값을 sp를 기준으로 16 * 4만큼 계산한 메모리 위치에 기록합니다. 레지스터 값을 나중에 다시 쓰거나 다른 코드가 읽을 수 있도록 메모리에 남기는 줄입니다. 첫 위치에는 x8, 그보다 8바이트 높은 위치에는 x9의 값이 들어갑니다. 이 스택 위치는 현재 함수 또는 예외 처리의 보관 공간입니다.

### 209행
```asm
	stp	x10, x11, [sp, #16 * 5]
```
x10, x11의 값을 sp를 기준으로 16 * 5만큼 계산한 메모리 위치에 기록합니다. 레지스터 값을 나중에 다시 쓰거나 다른 코드가 읽을 수 있도록 메모리에 남기는 줄입니다. 첫 위치에는 x10, 그보다 8바이트 높은 위치에는 x11의 값이 들어갑니다. 이 스택 위치는 현재 함수 또는 예외 처리의 보관 공간입니다.

### 210행
```asm
	stp	x12, x13, [sp, #16 * 6]
```
x12, x13의 값을 sp를 기준으로 16 * 6만큼 계산한 메모리 위치에 기록합니다. 레지스터 값을 나중에 다시 쓰거나 다른 코드가 읽을 수 있도록 메모리에 남기는 줄입니다. 첫 위치에는 x12, 그보다 8바이트 높은 위치에는 x13의 값이 들어갑니다. 이 스택 위치는 현재 함수 또는 예외 처리의 보관 공간입니다.

### 211행
```asm
	stp	x14, x15, [sp, #16 * 7]
```
x14, x15의 값을 sp를 기준으로 16 * 7만큼 계산한 메모리 위치에 기록합니다. 레지스터 값을 나중에 다시 쓰거나 다른 코드가 읽을 수 있도록 메모리에 남기는 줄입니다. 첫 위치에는 x14, 그보다 8바이트 높은 위치에는 x15의 값이 들어갑니다. 이 스택 위치는 현재 함수 또는 예외 처리의 보관 공간입니다.

### 212행
```asm
	stp	x16, x17, [sp, #16 * 8]
```
x16, x17의 값을 sp를 기준으로 16 * 8만큼 계산한 메모리 위치에 기록합니다. 레지스터 값을 나중에 다시 쓰거나 다른 코드가 읽을 수 있도록 메모리에 남기는 줄입니다. 첫 위치에는 x16, 그보다 8바이트 높은 위치에는 x17의 값이 들어갑니다. 이 스택 위치는 현재 함수 또는 예외 처리의 보관 공간입니다.

### 213행
```asm
	stp	x18, x19, [sp, #16 * 9]
```
x18, x19의 값을 sp를 기준으로 16 * 9만큼 계산한 메모리 위치에 기록합니다. 레지스터 값을 나중에 다시 쓰거나 다른 코드가 읽을 수 있도록 메모리에 남기는 줄입니다. 첫 위치에는 x18, 그보다 8바이트 높은 위치에는 x19의 값이 들어갑니다. 이 스택 위치는 현재 함수 또는 예외 처리의 보관 공간입니다.

### 214행
```asm
	stp	x20, x21, [sp, #16 * 10]
```
x20, x21의 값을 sp를 기준으로 16 * 10만큼 계산한 메모리 위치에 기록합니다. 레지스터 값을 나중에 다시 쓰거나 다른 코드가 읽을 수 있도록 메모리에 남기는 줄입니다. 첫 위치에는 x20, 그보다 8바이트 높은 위치에는 x21의 값이 들어갑니다. 이 스택 위치는 현재 함수 또는 예외 처리의 보관 공간입니다.

### 215행
```asm
	stp	x22, x23, [sp, #16 * 11]
```
x22, x23의 값을 sp를 기준으로 16 * 11만큼 계산한 메모리 위치에 기록합니다. 레지스터 값을 나중에 다시 쓰거나 다른 코드가 읽을 수 있도록 메모리에 남기는 줄입니다. 첫 위치에는 x22, 그보다 8바이트 높은 위치에는 x23의 값이 들어갑니다. 이 스택 위치는 현재 함수 또는 예외 처리의 보관 공간입니다.

### 216행
```asm
	stp	x24, x25, [sp, #16 * 12]
```
x24, x25의 값을 sp를 기준으로 16 * 12만큼 계산한 메모리 위치에 기록합니다. 레지스터 값을 나중에 다시 쓰거나 다른 코드가 읽을 수 있도록 메모리에 남기는 줄입니다. 첫 위치에는 x24, 그보다 8바이트 높은 위치에는 x25의 값이 들어갑니다. 이 스택 위치는 현재 함수 또는 예외 처리의 보관 공간입니다.

### 217행
```asm
	stp	x26, x27, [sp, #16 * 13]
```
x26, x27의 값을 sp를 기준으로 16 * 13만큼 계산한 메모리 위치에 기록합니다. 레지스터 값을 나중에 다시 쓰거나 다른 코드가 읽을 수 있도록 메모리에 남기는 줄입니다. 첫 위치에는 x26, 그보다 8바이트 높은 위치에는 x27의 값이 들어갑니다. 이 스택 위치는 현재 함수 또는 예외 처리의 보관 공간입니다.

### 218행
```asm
	stp	x28, x29, [sp, #16 * 14]
```
x28, x29의 값을 sp를 기준으로 16 * 14만큼 계산한 메모리 위치에 기록합니다. 레지스터 값을 나중에 다시 쓰거나 다른 코드가 읽을 수 있도록 메모리에 남기는 줄입니다. 첫 위치에는 x28, 그보다 8바이트 높은 위치에는 x29의 값이 들어갑니다. 이 스택 위치는 현재 함수 또는 예외 처리의 보관 공간입니다.

### 220행
```asm
	.if	\el == 0
```
어셈블 단계에서 인자나 식을 검사해 필요한 명령만 만듭니다. \el == 0

### 221행
```asm
	clear_gp_regs
```
원래 프로그램의 값은 이미 스택에 보관했습니다. 이제 예외 처리 코드에서 사용할 일반 레지스터를 0으로 정리합니다. 여기의 인자는 없음입니다.

### 222행
```asm
	mrs	x21, sp_el0
```
예외 전 사용자 SP를 SP_EL0에서 x21로 읽어 pt_regs에 보존할 준비를 합니다. 이후 SP_EL0를 커널 current 포인터로 바꾸기 전에 원래 사용자 스택 위치를 확보해야 합니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.
EL0에서 들어온 당시 SP_EL0는 사용자 스택 포인터입니다.

### 223행
```asm
	ldr_this_cpu	tsk, __entry_task, x20
```
CPU별 __entry_task에서 실제 현재 task 주소를 얻습니다. EL0에서 들어온 순간 SP_EL0에는 사용자 SP가 있으므로 그것을 current로 사용할 수 없습니다. 심벌의 기준 주소에 현재 CPU의 per-CPU 오프셋을 더해 이 CPU의 저장 위치를 찾습니다.

### 224행
```asm
	msr	sp_el0, tsk
```
SP_EL0를 current task 주소로 바꾸어 커널 내부의 current 조회를 가능하게 합니다. 사용자 SP는 x21에 따로 저장했고 뒤에서 프레임에 기록합니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.
이후 커널 실행에서는 SP_EL0에 current 태스크 포인터가 들어갑니다.

### 230행
```asm
	ldr	x19, [tsk, #TSK_TI_FLAGS]
```
tsk를 기준으로 TSK_TI_FLAGS만큼 계산한 메모리 위치에 보관된 값을 x19로 가져옵니다. 주소 숫자를 옮기는 MOV와 달리 실제 메모리를 읽습니다.

### 231행
```asm
	disable_step_tsk x19, x20
```
task가 사용자 single-step 상태였더라도 커널 진입 코드를 한 명령씩 사용자 디버거에 노출하지 않도록 스텝 상태를 끕니다. 원래 task 플래그는 복귀 시 다시 확인합니다. TIF_SINGLESTEP이 설정된 경우 MDSCR_EL1.SS를 지우고 ISB로 동기화합니다.

### 234행
```asm
	ldr	x0, [tsk, THREAD_SCTLR_USER]
```
task의 사용자 SCTLR 정책 사본을 읽습니다. 뒤에서 MTE 검사 mode와 사용자 포인터 인증 활성 여부를 판단할 공통 입력입니다. 실제로 tsk + (THREAD_SCTLR_USER) 주소의 메모리 8바이트를 x0에 읽습니다. 이 주소 형식은 기준 레지스터 tsk 자체를 갱신하지 않습니다.

### 235행
```asm
	check_mte_async_tcf x22, x23, x0
```
이 task가 비동기 MTE를 사용했다면 누적 태그 실패를 확인해 TIF_MTE_ASYNC_FAULT에 반영합니다. x22·x23은 조회와 thread flags 주소 계산용 임시 레지스터입니다.

### 237행
```asm
#ifdef CONFIG_ARM64_PTR_AUTH
```
포인터 인증 지원 빌드에만 사용자→커널 전환의 PAC 키/제어 처리를 포함합니다. PAC는 포인터를 키로 서명·인증하며, 커널에서 사용할 키와 사용자 키를 구분해야 하므로 이 위치에서 전환합니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.

### 238행
```asm
alternative_if ARM64_HAS_ADDRESS_AUTH
```
실제 주소 인증 기능이 있는 CPU에서만 아래 APIA 키와 ENIA 처리가 남도록 합니다. 커널 설정만 켰다고 미지원 CPU에서 인증 레지스터를 쓰지 않습니다. 이 매크로는 기본·교체 명령의 패치 정보를 만들며, CPU가 이 줄을 매번 if 조건처럼 실행하지 않습니다.

### 248행
```asm
	tbz	x0, SCTLR_ELx_ENIA_SHIFT, 1f
```
task의 사용자 SCTLR.ENIA가 켜져 있으면 현재 APIA 키가 사용자 키일 수 있으므로 커널 키 설치 경로를 탑니다. 꺼져 있으면 커널 키를 유지한 상태로 ENIA만 켜는 경로를 선택합니다. 이 분기는 지정 레지스터의 값이나 비트를 직접 검사하며 CMP가 남긴 NZCV를 읽거나 바꾸지 않습니다. 조건이 맞지 않으면 바로 다음 명령으로 진행합니다.

### 249행
```asm
	__ptrauth_keys_install_kernel_nosync tsk, x20, x22, x23
```
current task의 커널 APIA 키를 설치합니다. x20·x22·x23은 키를 읽고 시스템 레지스터에 쓸 임시 레지스터이며 nosync는 공통 ISB를 뒤에서 모아 수행한다는 뜻입니다.

### 250행
```asm
	b	2f
```
2f (L255)부터 실행을 계속합니다. 돌아올 주소를 새로 적지 않으므로 일반 함수 호출과는 다른 제어 이동입니다.

### 251행
```asm
1:
```
여기에 1라는 위치 이름을 붙입니다. 다른 줄의 분기나 주소 계산이 이 위치를 찾아올 수 있습니다. 이름 자체가 CPU 작업을 추가하지는 않습니다.

### 252행
```asm
	mrs	x0, sctlr_el1
```
사용자에서 ENIA를 꺼 둔 경우 현재 SCTLR을 읽고 다른 설정을 보존하면서 커널 주소 인증만 다시 활성화할 준비를 합니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 253행
```asm
	orr	x0, x0, SCTLR_ELx_ENIA
```
SCTLR.ENIA 비트를 세워 커널 코드의 instruction-address A-key 인증을 사용할 수 있게 합니다. 사용자 task가 요청한 비활성 상태를 커널에 그대로 적용하지 않습니다. ORR은 어느 한쪽이 1인 비트를 결과에 세우므로 원래 1이던 비트도 유지됩니다.

### 254행
```asm
	msr	sctlr_el1, x0
```
커널에서 쓸 ENIA 활성 SCTLR을 설치합니다. 아래 공통 ISB가 PAC/MTE 변경 뒤의 실행을 동기화합니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 255행
```asm
2:
```
여기에 2라는 위치 이름을 붙입니다. 다른 줄의 분기나 주소 계산이 이 위치를 찾아올 수 있습니다. 이름 자체가 CPU 작업을 추가하지는 않습니다.

### 256행
```asm
alternative_else_nop_endif
```
238행에서 시작한 ARM64_HAS_ADDRESS_AUTH 패치 묶음을 닫습니다. 조건에 맞는 본문을 사용하지 않는 쪽은 같은 길이의 NOP로 채워 뒤 명령의 배치를 유지합니다. 이 줄 자체가 CPU에서 조건을 검사하는 명령은 아닙니다.

### 257행
```asm
#endif
```
빌드 조건으로 나누었던 구간을 닫습니다. 대상: 

### 259행
```asm
	apply_ssbd 1, x22, x23
```
커널 진입 쪽 SSBD 상태 1을 요청합니다. 펌웨어 완화가 필요한 CPU와 task 정책에서만 실제 호출이 남으며 Spectre v4 완화 상태를 사용자 실행과 구분합니다.

### 261행
```asm
	mte_set_kernel_gcr x22, x23
```
하드웨어 태그 KASAN이 활성일 때 GCR을 커널 태그 생성 정책으로 바꿉니다. x22·x23은 그 계산에 사용하는 임시 레지스터입니다.

### 267행
```asm
alternative_if ARM64_MTE
```
MTE 제어를 바꾼 CPU에는 동기화 ISB가 필요하므로 아래 한 번의 ISB를 남깁니다. 뒤의 PAC 경로와 중복 수행하지 않도록 공통 끝으로 분기합니다. 이 매크로는 기본·교체 명령의 패치 정보를 만들며, CPU가 이 줄을 매번 if 조건처럼 실행하지 않습니다.

### 268행
```asm
	isb
```
뒤 명령을 변경된 CPU 제어 상태에 맞춰 실행하도록 동기화합니다. 주소 변환이나 벡터 설정을 바꾼 뒤 예전 실행 상태가 계속 쓰이지 않게 합니다.

### 269행
```asm
	b	1f
```
1f (L274)부터 실행을 계속합니다. 돌아올 주소를 새로 적지 않으므로 일반 함수 호출과는 다른 제어 이동입니다.

### 270행
```asm
alternative_else_nop_endif
```
267행에서 시작한 ARM64_MTE 패치 묶음을 닫습니다. 조건에 맞는 본문을 사용하지 않는 쪽은 같은 길이의 NOP로 채워 뒤 명령의 배치를 유지합니다. 이 줄 자체가 CPU에서 조건을 검사하는 명령은 아닙니다.

### 271행
```asm
alternative_if ARM64_HAS_ADDRESS_AUTH
```
MTE 경로에서 이미 ISB를 하지 않았더라도 주소 인증 상태를 바꾼 CPU에는 ISB를 남깁니다. 두 기능이 모두 없어 제어를 바꾸지 않으면 이 추가 ISB는 NOP 대안으로 빠집니다. 이 매크로는 기본·교체 명령의 패치 정보를 만들며, CPU가 이 줄을 매번 if 조건처럼 실행하지 않습니다.

### 272행
```asm
	isb
```
뒤 명령을 변경된 CPU 제어 상태에 맞춰 실행하도록 동기화합니다. 주소 변환이나 벡터 설정을 바꾼 뒤 예전 실행 상태가 계속 쓰이지 않게 합니다.

### 273행
```asm
alternative_else_nop_endif
```
271행에서 시작한 ARM64_HAS_ADDRESS_AUTH 패치 묶음을 닫습니다. 조건에 맞는 본문을 사용하지 않는 쪽은 같은 길이의 NOP로 채워 뒤 명령의 배치를 유지합니다. 이 줄 자체가 CPU에서 조건을 검사하는 명령은 아닙니다.

### 274행
```asm
1:
```
여기에 1라는 위치 이름을 붙입니다. 다른 줄의 분기나 주소 계산이 이 위치를 찾아올 수 있습니다. 이름 자체가 CPU 작업을 추가하지는 않습니다.

### 276행
```asm
	scs_load_current
```
태스크에 저장해 둔 반환 주소 보호용 스택 위치를 되살립니다. 일반 SP를 읽는 것과는 다른 상태입니다. 여기의 인자는 없음입니다.

### 277행
```asm
	.else
```
앞 어셈블 조건의 반대 경우에 생성할 코드입니다. 

### 278행
```asm
	add	x21, sp, #PT_REGS_SIZE
```
sp를 기준으로 PT_REGS_SIZE을 더해 x21에 결과를 둡니다. 주소 계산에 쓰이는 경우에는 구조체의 필드나 다음 저장 위치를 찾는 동작입니다. 이 형태는 조건 플래그를 바꾸지 않습니다.
EL1에서 중단된 원래 SP는 프레임을 잡기 전의 SP입니다.

### 279행
```asm
	get_current_task tsk
```
지금 이 CPU가 실행 중인 태스크의 주소를 가져옵니다. 단순한 정수 레지스터 값에 운영체제의 current라는 의미를 부여하는 부분입니다. 여기의 인자는 tsk입니다.

### 280행
```asm
	.endif /* \el == 0 */
```
어셈블 조건에 따라 생성할 코드를 나누던 범위가 끝납니다. 

## kernel_entry: PC·PSTATE·프레임 메타데이터
원본 281–333행

### 281행
```asm
	mrs	x22, elr_el1
```
ELR_EL1은 예외 후 돌아갈 PC를 담습니다. C handler·시그널·디버거가 저장 프레임을 통해 복귀 위치를 다룰 수 있도록 x22에 읽어 둡니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 282행
```asm
	mrs	x23, spsr_el1
```
SPSR_EL1의 예외 전 PSTATE를 x23에 읽습니다. 원래 실행 레벨·마스크·condition flags를 pt_regs에 보존해 나중에 ERET로 복원할 자료입니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 283행
```asm
	stp	lr, x21, [sp, #S_LR]
```
lr, x21의 값을 sp를 기준으로 S_LR만큼 계산한 메모리 위치에 기록합니다. 레지스터 값을 나중에 다시 쓰거나 다른 코드가 읽을 수 있도록 메모리에 남기는 줄입니다. 이 스택 위치는 현재 함수 또는 예외 처리의 보관 공간입니다.
LR과 중단된 SP를 S_LR에서 시작하는 연속 두 슬롯에 저장합니다.

### 289행
```asm
	stp	xzr, xzr, [sp, #S_STACKFRAME]
```
xzr, xzr의 값을 sp를 기준으로 S_STACKFRAME만큼 계산한 메모리 위치에 기록합니다. 레지스터 값을 나중에 다시 쓰거나 다른 코드가 읽을 수 있도록 메모리에 남기는 줄입니다. 첫 위치에는 xzr, 그보다 8바이트 높은 위치에는 xzr의 값이 들어갑니다. 이 스택 위치는 현재 함수 또는 예외 처리의 보관 공간입니다.

### 290행
```asm
	.if \el == 0
```
어셈블 단계에서 인자나 식을 검사해 필요한 명령만 만듭니다. \el == 0

### 291행
```asm
	mov	x0, #FRAME_META_TYPE_FINAL
```
사용자에서 왔으면 커널 스택 추적이 여기서 끝나도록 FRAME_META_TYPE_FINAL을 선택합니다. 사용자 FP를 커널 스택 연결로 계속 따라가지 않게 합니다. MOV는 x0의 기존 값을 대체합니다. 원본이 주소값이어도 그 주소의 메모리를 읽거나 복사하지 않습니다.

### 292행
```asm
	.else
```
앞 어셈블 조건의 반대 경우에 생성할 코드입니다. 

### 293행
```asm
	mov	x0, #FRAME_META_TYPE_PT_REGS
```
커널 실행 중 예외이면 FRAME_META_TYPE_PT_REGS로 표시하여 unwinder가 저장된 pt_regs를 통해 중단된 커널 프레임으로 이어갈 수 있게 합니다. MOV는 x0의 기존 값을 대체합니다. 원본이 주소값이어도 그 주소의 메모리를 읽거나 복사하지 않습니다.

### 294행
```asm
	.endif
```
어셈블 조건에 따라 생성할 코드를 나누던 범위가 끝납니다. 

### 295행
```asm
	str	x0, [sp, #S_STACKFRAME_TYPE]
```
x0의 값을 sp를 기준으로 S_STACKFRAME_TYPE만큼 계산한 메모리 위치에 기록합니다. 레지스터 값을 나중에 다시 쓰거나 다른 코드가 읽을 수 있도록 메모리에 남기는 줄입니다. 이 스택 위치는 현재 함수 또는 예외 처리의 보관 공간입니다.

### 296행
```asm
	add	x29, sp, #S_STACKFRAME
```
sp를 기준으로 S_STACKFRAME을 더해 x29에 결과를 둡니다. 주소 계산에 쓰이는 경우에는 구조체의 필드나 다음 저장 위치를 찾는 동작입니다. 이 형태는 조건 플래그를 바꾸지 않습니다.

### 298행
```asm
#ifdef CONFIG_ARM64_SW_TTBR0_PAN
```
소프트웨어 PAN은 TTBR0를 예약된 표로 바꿔 커널의 무분별한 사용자 메모리 접근을 막는 구성입니다. 켜진 빌드에서는 진입 시 그 상태를 보존·차단하고, 꺼지면 아래 emulation 호출은 없습니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.

### 299행
```asm
alternative_if_not ARM64_HAS_PAN
```
하드웨어 PAN이 없는 CPU에서만 소프트웨어 TTBR0 전환 호출을 유지합니다. PAN이 있으면 하드웨어 접근 차단을 쓰므로 이 호출은 NOP로 교체됩니다. 이 매크로는 기본·교체 명령의 패치 정보를 만들며, CPU가 이 줄을 매번 if 조건처럼 실행하지 않습니다.

### 300행
```asm
	bl	__swpan_entry_el\el
```
중단된 레벨에 맞는 __swpan_entry_el0 또는 el1을 호출합니다. EL1 진입이면 원래 사용자 접근 허용 여부를 SPSR의 모의 PAN 표식에 남기고, 필요하면 TTBR0를 예약 상태로 바꿉니다. BL은 다음 명령의 주소를 LR(x30)에 기록하고 목적지로 분기합니다. 호출된 코드가 x0 같은 작업 레지스터를 바꿀 수 있으므로, 호출 뒤의 값은 그 코드의 반환·보존 규칙에 따릅니다.

### 301행
```asm
alternative_else_nop_endif
```
299행에서 시작한 ARM64_HAS_PAN 패치 묶음을 닫습니다. 조건에 맞는 본문을 사용하지 않는 쪽은 같은 길이의 NOP로 채워 뒤 명령의 배치를 유지합니다. 이 줄 자체가 CPU에서 조건을 검사하는 명령은 아닙니다.

### 302행
```asm
#endif
```
빌드 조건으로 나누었던 구간을 닫습니다. 대상: 

### 304행
```asm
	stp	x22, x23, [sp, #S_PC]
```
x22, x23의 값을 sp를 기준으로 S_PC만큼 계산한 메모리 위치에 기록합니다. 레지스터 값을 나중에 다시 쓰거나 다른 코드가 읽을 수 있도록 메모리에 남기는 줄입니다. 첫 위치에는 x22, 그보다 8바이트 높은 위치에는 x23의 값이 들어갑니다. 이 스택 위치는 현재 함수 또는 예외 처리의 보관 공간입니다.
x22=ELR, x23=SPSR이므로 S_PC에는 복귀 PC와 PSTATE가 들어갑니다.

### 307행
```asm
	.if	\el == 0
```
어셈블 단계에서 인자나 식을 검사해 필요한 명령만 만듭니다. \el == 0

### 308행
```asm
	mov	w21, #NO_SYSCALL
```
NO_SYSCALL은 아직 이 예외가 시스템 호출로 판정되지 않았음을 나타내는 값입니다. EL0 예외에는 IRQ·fault도 있으므로 진입부터 유효한 syscall 번호라고 가정하지 않습니다. MOV는 w21의 기존 값을 대체합니다. 원본이 주소값이어도 그 주소의 메모리를 읽거나 복사하지 않습니다. w21에 쓰면 대응하는 x21의 상위 32비트도 0이 됩니다.
아직 시스템 호출이라고 확정하지 않은 예외 프레임의 초기 번호입니다.

### 309행
```asm
	str	w21, [sp, #S_SYSCALLNO]
```
w21의 값을 sp를 기준으로 S_SYSCALLNO만큼 계산한 메모리 위치에 기록합니다. 레지스터 값을 나중에 다시 쓰거나 다른 코드가 읽을 수 있도록 메모리에 남기는 줄입니다. 이 스택 위치는 현재 함수 또는 예외 처리의 보관 공간입니다.

### 310행
```asm
	.endif
```
어셈블 조건에 따라 생성할 코드를 나누던 범위가 끝납니다. 

### 312행
```asm
#ifdef CONFIG_ARM64_PSEUDO_NMI
```
pseudo-NMI는 GIC 우선순위 마스크로 일반 IRQ를 막아도 더 높은 우선순위의 요청을 받도록 하는 기능입니다. 켜진 빌드에서 PMR 저장·복원을 포함하며, 꺼지면 일반 DAIF 중심의 경로만 남습니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.

### 313행
```asm
alternative_if_not ARM64_HAS_GIC_PRIO_MASKING
```
실제 시스템이 GIC 우선순위 마스킹을 사용하지 않으면 PMR 저장 코드를 건너뛰도록 분기를 유지합니다. 사용하는 시스템에서는 분기를 NOP로 바꿔 PMR 상태를 프레임에 담습니다. 이 매크로는 기본·교체 명령의 패치 정보를 만들며, CPU가 이 줄을 매번 if 조건처럼 실행하지 않습니다.

### 314행
```asm
	b	.Lskip_pmr_save\@
```
.Lskip_pmr_save\@부터 실행을 계속합니다. 돌아올 주소를 새로 적지 않으므로 일반 함수 호출과는 다른 제어 이동입니다.

### 315행
```asm
alternative_else_nop_endif
```
313행에서 시작한 ARM64_HAS_GIC_PRIO_MASKING 패치 묶음을 닫습니다. 조건에 맞는 본문을 사용하지 않는 쪽은 같은 길이의 NOP로 채워 뒤 명령의 배치를 유지합니다. 이 줄 자체가 CPU에서 조건을 검사하는 명령은 아닙니다.

### 317행
```asm
	mrs_s	x20, SYS_ICC_PMR_EL1
```
ICC_PMR_EL1에서 현재 CPU가 받아들일 인터럽트 우선순위 문턱을 읽습니다. 단순 IRQ 허용 비트보다 세분화된 마스크 상태이므로 예외 프레임에 별도로 저장합니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 318행
```asm
	str	w20, [sp, #S_PMR]
```
w20의 값을 sp를 기준으로 S_PMR만큼 계산한 메모리 위치에 기록합니다. 레지스터 값을 나중에 다시 쓰거나 다른 코드가 읽을 수 있도록 메모리에 남기는 줄입니다. 이 스택 위치는 현재 함수 또는 예외 처리의 보관 공간입니다.

### 319행
```asm
	mov	x20, #GIC_PRIO_IRQON | GIC_PRIO_PSR_I_SET
```
커널 진입 후 사용할 GIC 우선순위 기준과 PSTATE.I 관련 표시를 결합합니다. 일반 IRQ와 pseudo-NMI의 차단 수준을 맞추기 위한 값입니다. MOV는 x20의 기존 값을 대체합니다. 원본이 주소값이어도 그 주소의 메모리를 읽거나 복사하지 않습니다.

### 320행
```asm
	msr_s	SYS_ICC_PMR_EL1, x20
```
준비한 우선순위 마스크를 ICC_PMR_EL1에 설치하여 이 예외 처리 구간의 인터럽트 수신 조건을 정합니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 322행
```asm
.Lskip_pmr_save\@:
```
여기에 .Lskip_pmr_save\@라는 위치 이름을 붙입니다. 다른 줄의 분기나 주소 계산이 이 위치를 찾아올 수 있습니다. 이름 자체가 CPU 작업을 추가하지는 않습니다.

### 323행
```asm
#endif
```
빌드 조건으로 나누었던 구간을 닫습니다. 대상: 

### 333행
```asm
	.endm
```
공통 작업의 정의를 여기서 마칩니다. RET가 아니라 빌드 도구가 읽는 경계입니다. 

## kernel_exit: 복귀 전 제어 상태
원본 334–410행

### 335행
```asm
	.macro	kernel_exit, el
```
pt_regs에 저장한 예외 전 문맥을 복원하는 매크로입니다. el=0은 사용자 복귀라 PAC·MTE·KPTI 등의 전환을 포함하고, el=1은 중단된 커널 실행으로 돌아갈 상태를 복구합니다. 이 줄은 어셈블러 매크로 정의의 시작입니다. 본문은 매크로를 사용하는 위치에 펼쳐지며, 정의 자체가 런타임 함수 호출은 아닙니다.

### 336행
```asm
	.if	\el != 0
```
어셈블 단계에서 인자나 식을 검사해 필요한 명령만 만듭니다. \el != 0

### 337행
```asm
	disable_daif
```
EL1으로 돌아갈 상태를 복구하는 동안 새 디버그·SError·IRQ·FIQ 예외가 끼지 않도록 DAIF 마스크를 모두 세웁니다. 최종 ERET가 저장된 SPSR의 원래 마스크 상태를 복원합니다. 매크로가 펼치는 MSR DAIFSet, #0xf는 네 예외 마스크 비트를 모두 1로 만듭니다.

### 338행
```asm
	.endif
```
어셈블 조건에 따라 생성할 코드를 나누던 범위가 끝납니다. 

### 340행
```asm
#ifdef CONFIG_ARM64_PSEUDO_NMI
```
pseudo-NMI 지원 빌드에서는 진입 때 저장했던 PMR도 복원해야 원래 코드의 인터럽트 우선순위 차단 상태가 돌아옵니다. 미지원 빌드는 이 추가 복원이 없습니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.

### 341행
```asm
alternative_if_not ARM64_HAS_GIC_PRIO_MASKING
```
GIC 우선순위 마스킹을 실제로 사용하지 않는 CPU는 아래 PMR 복원을 건너뜁니다. 사용 여부에 따른 런타임 코드 패치이며 매번 capability를 C if로 검사하는 문장은 아닙니다.

### 342행
```asm
	b	.Lskip_pmr_restore\@
```
.Lskip_pmr_restore\@부터 실행을 계속합니다. 돌아올 주소를 새로 적지 않으므로 일반 함수 호출과는 다른 제어 이동입니다.

### 343행
```asm
alternative_else_nop_endif
```
341행에서 시작한 ARM64_HAS_GIC_PRIO_MASKING 패치 묶음을 닫습니다. 조건에 맞는 본문을 사용하지 않는 쪽은 같은 길이의 NOP로 채워 뒤 명령의 배치를 유지합니다. 이 줄 자체가 CPU에서 조건을 검사하는 명령은 아닙니다.

### 345행
```asm
	ldr	w20, [sp, #S_PMR]
```
sp를 기준으로 S_PMR만큼 계산한 메모리 위치에 보관된 값을 w20로 가져옵니다. 주소 숫자를 옮기는 MOV와 달리 실제 메모리를 읽습니다. 앞서 보관한 상태를 되살리거나 스택으로 전달된 인자를 얻는 과정입니다.

### 346행
```asm
	msr_s	SYS_ICC_PMR_EL1, x20
```
pt_regs에 저장한 우선순위 마스크를 ICC_PMR_EL1에 되돌립니다. 예외 전 코드가 막아 두었던 IRQ가 복귀 과정에서 잘못 허용되지 않게 합니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 349행
```asm
alternative_if_not ARM64_HAS_GIC_PRIO_RELAXED_SYNC
```
GIC가 PMR 갱신의 완화된 동기화 조건을 지원하지 않으면 DSB SY를 남겨 변경 완료를 기다립니다. 지원하는 시스템에서는 이 DSB를 NOP 대안으로 제거할 수 있습니다. 이 매크로는 기본·교체 명령의 패치 정보를 만들며, CPU가 이 줄을 매번 if 조건처럼 실행하지 않습니다.

### 350행
```asm
	dsb	sy
```
지정 범위·종류의 앞선 작업이 완료되기를 기다립니다. 페이지 복사나 캐시 관리가 끝나기 전에 다음 상태로 넘어가지 않도록 하는 대기 지점입니다.

### 351행
```asm
alternative_else_nop_endif
```
349행에서 시작한 ARM64_HAS_GIC_PRIO_RELAXED_SYNC 패치 묶음을 닫습니다. 조건에 맞는 본문을 사용하지 않는 쪽은 같은 길이의 NOP로 채워 뒤 명령의 배치를 유지합니다. 이 줄 자체가 CPU에서 조건을 검사하는 명령은 아닙니다.

### 353행
```asm
.Lskip_pmr_restore\@:
```
여기에 .Lskip_pmr_restore\@라는 위치 이름을 붙입니다. 다른 줄의 분기나 주소 계산이 이 위치를 찾아올 수 있습니다. 이름 자체가 CPU 작업을 추가하지는 않습니다.

### 354행
```asm
#endif
```
빌드 조건으로 나누었던 구간을 닫습니다. 대상: 

### 356행
```asm
	ldp	x21, x22, [sp, #S_PC]		// load ELR, SPSR
```
sp를 기준으로 S_PC만큼 계산한 메모리 위치에 보관된 값을 x21, x22로 가져옵니다. 주소 숫자를 옮기는 MOV와 달리 실제 메모리를 읽습니다. 첫 위치에서 x21, 그보다 8바이트 높은 위치에서 x22의 값을 읽습니다. 앞서 보관한 상태를 되살리거나 스택으로 전달된 인자를 얻는 과정입니다.

### 358행
```asm
#ifdef CONFIG_ARM64_SW_TTBR0_PAN
```
소프트웨어 PAN을 사용하는 빌드에서는 복귀 대상이 원래 사용자 접근을 허용했는지에 따라 TTBR0를 복구합니다. 설정이 없으면 이 emulation 경로를 포함하지 않습니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.

### 359행
```asm
alternative_if_not ARM64_HAS_PAN
```
하드웨어 PAN이 없는 CPU에서만 __swpan_exit 호출을 남깁니다. 하드웨어 PAN이 있으면 저장된 PSTATE의 실제 PAN 상태가 의미를 갖고 이 소프트웨어 TTBR0 복원은 필요하지 않습니다. 이 매크로는 기본·교체 명령의 패치 정보를 만들며, CPU가 이 줄을 매번 if 조건처럼 실행하지 않습니다.

### 360행
```asm
	bl	__swpan_exit_el\el
```
복귀 대상 EL에 맞는 소프트웨어 PAN 해제를 호출합니다. EL0 복귀는 사용자 표를 다시 연결하고, EL1 복귀는 저장한 모의 PAN 표식에 따라 기존 사용자 접근 허용 여부를 복원합니다. BL은 다음 명령의 주소를 LR(x30)에 기록하고 목적지로 분기합니다.

### 361행
```asm
alternative_else_nop_endif
```
359행에서 시작한 ARM64_HAS_PAN 패치 묶음을 닫습니다. 조건에 맞는 본문을 사용하지 않는 쪽은 같은 길이의 NOP로 채워 뒤 명령의 배치를 유지합니다. 이 줄 자체가 CPU에서 조건을 검사하는 명령은 아닙니다.

### 362행
```asm
#endif
```
빌드 조건으로 나누었던 구간을 닫습니다. 대상: 

### 364행
```asm
	.if	\el == 0
```
어셈블 단계에서 인자나 식을 검사해 필요한 명령만 만듭니다. \el == 0

### 365행
```asm
	ldr	x23, [sp, #S_SP]		// load return stack pointer
```
sp를 기준으로 S_SP만큼 계산한 메모리 위치에 보관된 값을 x23로 가져옵니다. 주소 숫자를 옮기는 MOV와 달리 실제 메모리를 읽습니다. 앞서 보관한 상태를 되살리거나 스택으로 전달된 인자를 얻는 과정입니다.

### 366행
```asm
	msr	sp_el0, x23
```
pt_regs에서 읽은 사용자 SP를 SP_EL0에 되돌립니다. 커널 실행 중에는 이 레지스터를 current 포인터로 썼지만 ERET 후에는 사용자 스택 위치여야 합니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 367행
```asm
	tst	x22, #PSR_MODE32_BIT		// native task?
```
저장한 PSTATE의 MODE32 비트로 AArch32 사용자 복귀인지 확인합니다. 아래 Cortex-A53 erratum 처리는 이 32비트 복귀 문맥에만 필요합니다. TST는 두 피연산자의 비트 AND 결과로 조건 플래그를 갱신하며 원래 레지스터 값은 바꾸지 않습니다.
NZCV는 이 TST가 정합니다. EQ이면 AArch64 사용자 태스크 경로입니다.

### 368행
```asm
	b.eq	3f
```
앞서 계산한 조건 플래그가 Z=1이면 3f (L380)로 갑니다. 사이에 플래그를 바꾸지 않는 명령이 있어도 비교 결과는 남아 있습니다.

### 370행
```asm
#ifdef CONFIG_ARM64_ERRATUM_845719
```
Cortex-A53 erratum 845719 우회 코드를 빌드하는 설정입니다. 켜진 빌드에서는 영향을 받는 CPU의 AArch32 복귀 전에 CONTEXTIDR을 다시 쓰는 경로를 포함하고, 꺼지면 해당 보완이 없습니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.

### 371행
```asm
alternative_if ARM64_WORKAROUND_845719
```
실제로 erratum 845719의 영향을 받는 CPU에서만 CONTEXTIDR 재기록을 남깁니다. 설정을 켰어도 영향을 받지 않으면 같은 크기의 NOP로 바뀝니다. 이 매크로는 기본·교체 명령의 패치 정보를 만들며, CPU가 이 줄을 매번 if 조건처럼 실행하지 않습니다.

### 372행
```asm
#ifdef CONFIG_PID_IN_CONTEXTIDR
```
PID를 CONTEXTIDR에 기록해 추적하는 빌드라면 그 값을 보존한 채 다시 써야 합니다. 이 기능이 꺼진 빌드는 아래 #else에서 0을 써도 됩니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.

### 373행
```asm
	mrs	x29, contextidr_el1
```
추적용 PID 등의 기존 CONTEXTIDR 값을 읽습니다. erratum 보완을 위해 재기록하되 디버깅·추적 식별자를 잃지 않으려는 목적입니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 374행
```asm
	msr	contextidr_el1, x29
```
방금 읽은 CONTEXTIDR을 같은 값으로 다시 기록합니다. 값 변경이 아니라 이 시스템 레지스터 쓰기 자체가 필요한 CPU 보완입니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 375행
```asm
#else
```
CONTEXTIDR에 PID를 기록하지 않는 빌드는 유지해야 할 PID 값이 없으므로 0을 재기록하는 보완 경로를 사용합니다.

### 376행
```asm
	msr contextidr_el1, xzr
```
CONTEXTIDR에 0을 써 AArch32 복귀에 필요한 erratum 보완 동작을 수행합니다. 현재 task의 PID를 0으로 바꾸는 코드가 아닙니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 377행
```asm
#endif
```
빌드 조건으로 나누었던 구간을 닫습니다. 대상: 

### 378행
```asm
alternative_else_nop_endif
```
371행에서 시작한 ARM64_WORKAROUND_845719 패치 묶음을 닫습니다. 조건에 맞는 본문을 사용하지 않는 쪽은 같은 길이의 NOP로 채워 뒤 명령의 배치를 유지합니다. 이 줄 자체가 CPU에서 조건을 검사하는 명령은 아닙니다.

### 379행
```asm
#endif
```
빌드 조건으로 나누었던 구간을 닫습니다. 대상: 

### 380행
```asm
3:
```
여기에 3라는 위치 이름을 붙입니다. 다른 줄의 분기나 주소 계산이 이 위치를 찾아올 수 있습니다. 이름 자체가 CPU 작업을 추가하지는 않습니다.

### 381행
```asm
	scs_save tsk
```
일반 스택과 별도로 보호하는 반환 주소 스택의 현재 위치를 태스크에 기억해 둡니다. 여기의 인자는 tsk입니다.

### 384행
```asm
	ldr	x0, [tsk, THREAD_SCTLR_USER]
```
사용자 task의 SCTLR 정책을 다시 읽어 MTE 오류 정리와 PAC 사용자 상태 복원에 사용합니다. 커널이 쓰던 설정을 그대로 사용자에게 넘기지 않기 위한 기준입니다. 실제로 tsk + (THREAD_SCTLR_USER) 주소의 메모리 8바이트를 x0에 읽습니다. 이 주소 형식은 기준 레지스터 tsk 자체를 갱신하지 않습니다.

### 385행
```asm
	clear_mte_async_tcf x0
```
비동기 MTE mode의 이전 실패 누적 상태를 동기화한 뒤 지웁니다. 새 사용자 실행에서 발생할 태그 오류와 이미 처리 중인 오류를 분리합니다. 매크로는 DSB로 앞선 태그 검사 처리를 동기화한 뒤 TFSRE0_EL1에 0을 씁니다.

### 387행
```asm
#ifdef CONFIG_ARM64_PTR_AUTH
```
포인터 인증 지원 빌드에서는 사용자로 돌아가기 전에 사용자 APIA 키 또는 ENIA 비활성 정책을 복원합니다. 꺼진 빌드는 이 키·제어 전환 코드가 없습니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.

### 388행
```asm
alternative_if ARM64_HAS_ADDRESS_AUTH
```
실제 주소 인증 기능이 있을 때만 사용자 키/ENIA 복구를 수행합니다. 없는 CPU는 시스템 레지스터 접근 대신 NOP 대안을 사용합니다. 이 매크로는 기본·교체 명령의 패치 정보를 만들며, CPU가 이 줄을 매번 if 조건처럼 실행하지 않습니다.

### 396행
```asm
	tbz	x0, SCTLR_ELx_ENIA_SHIFT, 1f
```
사용자 task가 ENIA를 활성화했으면 사용자 키를 설치하고, 꺼 두었으면 ENIA를 지우는 경로를 선택합니다. task별 인증 정책을 존중하는 분기입니다. 이 분기는 지정 레지스터의 값이나 비트를 직접 검사하며 CMP가 남긴 NZCV를 읽거나 바꾸지 않습니다. 조건이 맞지 않으면 바로 다음 명령으로 진행합니다.

### 397행
```asm
	__ptrauth_keys_install_user tsk, x0, x1, x2
```
task_struct에 보존된 사용자 포인터 인증 키를 CPU에 설치합니다. 커널 키로 사용자 포인터를 검사하면 정상 복귀 주소도 인증되지 않으므로 구분해야 합니다. 이 매크로에서 복원하는 키는 사용자 명령어 주소 인증용 APIA 키이며, 모든 종류의 PAC 키를 한꺼번에 설치하는 것은 아닙니다.

### 398행
```asm
	b	2f
```
2f (L403)부터 실행을 계속합니다. 돌아올 주소를 새로 적지 않으므로 일반 함수 호출과는 다른 제어 이동입니다.

### 399행
```asm
1:
```
여기에 1라는 위치 이름을 붙입니다. 다른 줄의 분기나 주소 계산이 이 위치를 찾아올 수 있습니다. 이름 자체가 CPU 작업을 추가하지는 않습니다.

### 400행
```asm
	mrs	x0, sctlr_el1
```
기존 SCTLR의 다른 비트는 유지하면서 사용자 요청에 따라 ENIA만 끌 값을 준비합니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 401행
```asm
	bic	x0, x0, SCTLR_ELx_ENIA
```
사용자가 instruction-address A-key 인증을 비활성화한 정책에 맞춰 ENIA 비트를 지웁니다. 모든 PAC 기능과 모든 키를 0으로 만드는 동작은 아닙니다. BIC는 마스크에서 1인 위치만 0으로 만들고 나머지 비트는 유지합니다.

### 402행
```asm
	msr	sctlr_el1, x0
```
ENIA를 지운 사용자 실행용 SCTLR 값을 설치합니다. 커널 진입 때 다시 켰던 상태를 사용자 요청으로 되돌리는 단계입니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 403행
```asm
2:
```
여기에 2라는 위치 이름을 붙입니다. 다른 줄의 분기나 주소 계산이 이 위치를 찾아올 수 있습니다. 이름 자체가 CPU 작업을 추가하지는 않습니다.

### 404행
```asm
alternative_else_nop_endif
```
388행에서 시작한 ARM64_HAS_ADDRESS_AUTH 패치 묶음을 닫습니다. 조건에 맞는 본문을 사용하지 않는 쪽은 같은 길이의 NOP로 채워 뒤 명령의 배치를 유지합니다. 이 줄 자체가 CPU에서 조건을 검사하는 명령은 아닙니다.

### 405행
```asm
#endif
```
빌드 조건으로 나누었던 구간을 닫습니다. 대상: 

### 407행
```asm
	mte_set_user_gcr tsk, x0, x1
```
커널 하드웨어 태그 KASAN이 바꾸었던 GCR을 현재 사용자 task의 제외 태그 정책으로 복원합니다. tsk가 정책의 출처이며 x0·x1은 임시 레지스터입니다. 매크로는 task의 THREAD_MTE_CTRL 필드에서 사용자 정책을 읽습니다.

### 409행
```asm
	apply_ssbd 0, x0, x1
```
사용자 복귀 쪽 SSBD 상태 0을 요청합니다. task가 완화를 유지하도록 요구하는 경우 등에는 apply_ssbd 내부에서 이 전환을 건너뛰므로 무조건 완화를 끄는 명령이 아닙니다.

### 410행
```asm
	.endif
```
어셈블 조건에 따라 생성할 코드를 나누던 범위가 끝납니다. 

## kernel_exit: 레지스터 복원과 ERET
원본 411–463행

### 412행
```asm
	msr	elr_el1, x21			// set up the return data
```
저장 프레임의 복귀 PC를 ELR_EL1에 설치합니다. 시그널·디버거 등이 pt_regs.pc를 바꾸었다면 변경된 위치로 ERET하게 됩니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 413행
```asm
	msr	spsr_el1, x22
```
저장된 복귀 PSTATE를 SPSR_EL1에 설치합니다. ERET가 실행 레벨·마스크·flags를 이 값에서 복원합니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 414행
```asm
	ldp	x0, x1, [sp, #16 * 0]
```
sp를 기준으로 16 * 0만큼 계산한 메모리 위치에 보관된 값을 x0, x1로 가져옵니다. 주소 숫자를 옮기는 MOV와 달리 실제 메모리를 읽습니다. 첫 위치에서 x0, 그보다 8바이트 높은 위치에서 x1의 값을 읽습니다. 앞서 보관한 상태를 되살리거나 스택으로 전달된 인자를 얻는 과정입니다.

### 415행
```asm
	ldp	x2, x3, [sp, #16 * 1]
```
sp를 기준으로 16 * 1만큼 계산한 메모리 위치에 보관된 값을 x2, x3로 가져옵니다. 주소 숫자를 옮기는 MOV와 달리 실제 메모리를 읽습니다. 첫 위치에서 x2, 그보다 8바이트 높은 위치에서 x3의 값을 읽습니다. 앞서 보관한 상태를 되살리거나 스택으로 전달된 인자를 얻는 과정입니다.

### 416행
```asm
	ldp	x4, x5, [sp, #16 * 2]
```
sp를 기준으로 16 * 2만큼 계산한 메모리 위치에 보관된 값을 x4, x5로 가져옵니다. 주소 숫자를 옮기는 MOV와 달리 실제 메모리를 읽습니다. 첫 위치에서 x4, 그보다 8바이트 높은 위치에서 x5의 값을 읽습니다. 앞서 보관한 상태를 되살리거나 스택으로 전달된 인자를 얻는 과정입니다.

### 417행
```asm
	ldp	x6, x7, [sp, #16 * 3]
```
sp를 기준으로 16 * 3만큼 계산한 메모리 위치에 보관된 값을 x6, x7로 가져옵니다. 주소 숫자를 옮기는 MOV와 달리 실제 메모리를 읽습니다. 첫 위치에서 x6, 그보다 8바이트 높은 위치에서 x7의 값을 읽습니다. 앞서 보관한 상태를 되살리거나 스택으로 전달된 인자를 얻는 과정입니다.

### 418행
```asm
	ldp	x8, x9, [sp, #16 * 4]
```
sp를 기준으로 16 * 4만큼 계산한 메모리 위치에 보관된 값을 x8, x9로 가져옵니다. 주소 숫자를 옮기는 MOV와 달리 실제 메모리를 읽습니다. 첫 위치에서 x8, 그보다 8바이트 높은 위치에서 x9의 값을 읽습니다. 앞서 보관한 상태를 되살리거나 스택으로 전달된 인자를 얻는 과정입니다.

### 419행
```asm
	ldp	x10, x11, [sp, #16 * 5]
```
sp를 기준으로 16 * 5만큼 계산한 메모리 위치에 보관된 값을 x10, x11로 가져옵니다. 주소 숫자를 옮기는 MOV와 달리 실제 메모리를 읽습니다. 첫 위치에서 x10, 그보다 8바이트 높은 위치에서 x11의 값을 읽습니다. 앞서 보관한 상태를 되살리거나 스택으로 전달된 인자를 얻는 과정입니다.

### 420행
```asm
	ldp	x12, x13, [sp, #16 * 6]
```
sp를 기준으로 16 * 6만큼 계산한 메모리 위치에 보관된 값을 x12, x13로 가져옵니다. 주소 숫자를 옮기는 MOV와 달리 실제 메모리를 읽습니다. 첫 위치에서 x12, 그보다 8바이트 높은 위치에서 x13의 값을 읽습니다. 앞서 보관한 상태를 되살리거나 스택으로 전달된 인자를 얻는 과정입니다.

### 421행
```asm
	ldp	x14, x15, [sp, #16 * 7]
```
sp를 기준으로 16 * 7만큼 계산한 메모리 위치에 보관된 값을 x14, x15로 가져옵니다. 주소 숫자를 옮기는 MOV와 달리 실제 메모리를 읽습니다. 첫 위치에서 x14, 그보다 8바이트 높은 위치에서 x15의 값을 읽습니다. 앞서 보관한 상태를 되살리거나 스택으로 전달된 인자를 얻는 과정입니다.

### 422행
```asm
	ldp	x16, x17, [sp, #16 * 8]
```
sp를 기준으로 16 * 8만큼 계산한 메모리 위치에 보관된 값을 x16, x17로 가져옵니다. 주소 숫자를 옮기는 MOV와 달리 실제 메모리를 읽습니다. 첫 위치에서 x16, 그보다 8바이트 높은 위치에서 x17의 값을 읽습니다. 앞서 보관한 상태를 되살리거나 스택으로 전달된 인자를 얻는 과정입니다.

### 423행
```asm
	ldp	x18, x19, [sp, #16 * 9]
```
sp를 기준으로 16 * 9만큼 계산한 메모리 위치에 보관된 값을 x18, x19로 가져옵니다. 주소 숫자를 옮기는 MOV와 달리 실제 메모리를 읽습니다. 첫 위치에서 x18, 그보다 8바이트 높은 위치에서 x19의 값을 읽습니다. 앞서 보관한 상태를 되살리거나 스택으로 전달된 인자를 얻는 과정입니다.

### 424행
```asm
	ldp	x20, x21, [sp, #16 * 10]
```
sp를 기준으로 16 * 10만큼 계산한 메모리 위치에 보관된 값을 x20, x21로 가져옵니다. 주소 숫자를 옮기는 MOV와 달리 실제 메모리를 읽습니다. 첫 위치에서 x20, 그보다 8바이트 높은 위치에서 x21의 값을 읽습니다. 앞서 보관한 상태를 되살리거나 스택으로 전달된 인자를 얻는 과정입니다.

### 425행
```asm
	ldp	x22, x23, [sp, #16 * 11]
```
sp를 기준으로 16 * 11만큼 계산한 메모리 위치에 보관된 값을 x22, x23로 가져옵니다. 주소 숫자를 옮기는 MOV와 달리 실제 메모리를 읽습니다. 첫 위치에서 x22, 그보다 8바이트 높은 위치에서 x23의 값을 읽습니다. 앞서 보관한 상태를 되살리거나 스택으로 전달된 인자를 얻는 과정입니다.

### 426행
```asm
	ldp	x24, x25, [sp, #16 * 12]
```
sp를 기준으로 16 * 12만큼 계산한 메모리 위치에 보관된 값을 x24, x25로 가져옵니다. 주소 숫자를 옮기는 MOV와 달리 실제 메모리를 읽습니다. 첫 위치에서 x24, 그보다 8바이트 높은 위치에서 x25의 값을 읽습니다. 앞서 보관한 상태를 되살리거나 스택으로 전달된 인자를 얻는 과정입니다.

### 427행
```asm
	ldp	x26, x27, [sp, #16 * 13]
```
sp를 기준으로 16 * 13만큼 계산한 메모리 위치에 보관된 값을 x26, x27로 가져옵니다. 주소 숫자를 옮기는 MOV와 달리 실제 메모리를 읽습니다. 첫 위치에서 x26, 그보다 8바이트 높은 위치에서 x27의 값을 읽습니다. 앞서 보관한 상태를 되살리거나 스택으로 전달된 인자를 얻는 과정입니다.

### 428행
```asm
	ldp	x28, x29, [sp, #16 * 14]
```
sp를 기준으로 16 * 14만큼 계산한 메모리 위치에 보관된 값을 x28, x29로 가져옵니다. 주소 숫자를 옮기는 MOV와 달리 실제 메모리를 읽습니다. 첫 위치에서 x28, 그보다 8바이트 높은 위치에서 x29의 값을 읽습니다. 앞서 보관한 상태를 되살리거나 스택으로 전달된 인자를 얻는 과정입니다.

### 430행
```asm
	.if	\el == 0
```
어셈블 단계에서 인자나 식을 검사해 필요한 명령만 만듭니다. \el == 0

### 431행
```asm
#ifdef CONFIG_UNMAP_KERNEL_AT_EL0
```
KPTI는 사용자 실행 중 커널의 넓은 매핑을 감추어 일부 추측 실행 정보 노출을 줄이는 기능입니다. 지원 빌드에서만 최소 trampoline으로 이동해 매핑을 제거하는 복귀 경로를 포함합니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.

### 432행
```asm
	alternative_insn "b .L_skip_tramp_exit_\@", nop, ARM64_UNMAP_KERNEL_AT_EL0
```
실제 KPTI가 활성인 시스템에서는 skip 분기를 NOP로 바꿔 trampoline 복귀를 실행합니다. 비활성이면 일반 ERET 경로로 건너뜁니다. CONFIG 지원과 런타임 적용 여부는 다릅니다. 이 매크로는 기본·교체 명령의 패치 정보를 만들며, CPU가 이 줄을 매번 if 조건처럼 실행하지 않습니다.

### 434행
```asm
	msr	far_el1, x29
```
사용자 x29를 FAR_EL1에 임시 보관합니다. 여기서는 fault 주소를 보고하는 것이 아니라 커널 매핑을 제거할 때 x29를 작업용으로 쓰기 위한 scratch 사용입니다. tramp_exit에서 되찾습니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.
trampoline 분기 주소를 담기 위해 x29를 빌리므로 원래 사용자 x29를 FAR_EL1에 잠시 보존합니다.

### 436행
```asm
	ldr_this_cpu	x30, this_cpu_vector, x29
```
이 CPU의 KPTI/BHB 정책에 맞는 진입 벡터 주소를 읽습니다. 다음 사용자 예외가 올바른 trampoline·완화 경로로 들어오게 할 값입니다. 심벌의 기준 주소에 현재 CPU의 per-CPU 오프셋을 더해 이 CPU의 저장 위치를 찾습니다.

### 437행
```asm
	tramp_alias	x29, tramp_exit
```
최소 trampoline 매핑에서도 접근할 수 있는 코드 주소를 계산합니다. 같은 코드라도 어느 매핑에서 부르는지에 따라 사용할 주소가 달라집니다. 여기의 인자는 x29, tramp_exit입니다.

### 438행
```asm
	msr		vbar_el1, x30		// install vector table
```
다음 EL0 예외를 받을 trampoline 벡터 주소를 VBAR_EL1에 설치합니다. 커널 전체 매핑을 없앤 뒤에도 이 입구는 접근 가능해야 합니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 439행
```asm
	ldr		lr, [sp, #S_LR]		// restore x30
```
sp를 기준으로 S_LR만큼 계산한 메모리 위치에 보관된 값을 lr로 가져옵니다. 주소 숫자를 옮기는 MOV와 달리 실제 메모리를 읽습니다. 앞서 보관한 상태를 되살리거나 스택으로 전달된 인자를 얻는 과정입니다.

### 440행
```asm
	add		sp, sp, #PT_REGS_SIZE	// restore sp
```
현재 사용한 스택 공간 PT_REGS_SIZE바이트를 해제합니다. SP를 높은 주소로 되돌리며 메모리 내용을 0으로 지우는 것은 아닙니다.

### 441행
```asm
	br		x29
```
x29에 준비한 주소부터 실행합니다. 주소가 가리키는 값을 메모리에서 읽는 것이 아니라 CPU의 다음 실행 위치를 바꿉니다. 새 복귀 주소는 만들지 않습니다.
현재 x29는 복구된 사용자 FP가 아니라 trampoline 별칭 주소입니다.

### 443행
```asm
.L_skip_tramp_exit_\@:
```
여기에 .L_skip_tramp_exit_\@라는 위치 이름을 붙입니다. 다른 줄의 분기나 주소 계산이 이 위치를 찾아올 수 있습니다. 이름 자체가 CPU 작업을 추가하지는 않습니다.

### 444행
```asm
#endif
```
빌드 조건으로 나누었던 구간을 닫습니다. 대상: 

### 445행
```asm
	.endif
```
어셈블 조건에 따라 생성할 코드를 나누던 범위가 끝납니다. 

### 447행
```asm
	ldr	lr, [sp, #S_LR]
```
sp를 기준으로 S_LR만큼 계산한 메모리 위치에 보관된 값을 lr로 가져옵니다. 주소 숫자를 옮기는 MOV와 달리 실제 메모리를 읽습니다. 앞서 보관한 상태를 되살리거나 스택으로 전달된 인자를 얻는 과정입니다.

### 448행
```asm
	add	sp, sp, #PT_REGS_SIZE		// restore sp
```
현재 사용한 스택 공간 PT_REGS_SIZE바이트를 해제합니다. SP를 높은 주소로 되돌리며 메모리 내용을 0으로 지우는 것은 아닙니다.
pt_regs 프레임 공간을 해제합니다. 저장된 LR은 바로 앞에서 읽었습니다.

### 450행
```asm
	.if \el == 0
```
어셈블 단계에서 인자나 식을 검사해 필요한 명령만 만듭니다. \el == 0

### 452행
```asm
alternative_if ARM64_WORKAROUND_SPECULATIVE_UNPRIV_LOAD
```
speculative unprivileged load 관련 CPU 보완이 필요한 경우에만 다음 TLBI와 DSB를 남깁니다. 사용자 복귀 경계에서 잘못된 추측 접근 상태를 정리하는 CPU별 우회이며 일반적인 페이지 테이블 변경 flush로 설명하면 안 됩니다. 이 매크로는 기본·교체 명령의 패치 정보를 만들며, CPU가 이 줄을 매번 if 조건처럼 실행하지 않습니다.

### 453행
```asm
	tlbi	vale1, xzr
```
CPU가 기억해 둔 주소 변환 결과를 무효화합니다. 메모리의 페이지 테이블을 고쳤다고 기존 TLB 기록까지 자동으로 사라지는 것은 아닙니다.

### 454행
```asm
	dsb	nsh
```
지정 범위·종류의 앞선 작업이 완료되기를 기다립니다. 페이지 복사나 캐시 관리가 끝나기 전에 다음 상태로 넘어가지 않도록 하는 대기 지점입니다.

### 455행
```asm
alternative_else_nop_endif
```
452행에서 시작한 ARM64_WORKAROUND_SPECULATIVE_UNPRIV_LOAD 패치 묶음을 닫습니다. 조건에 맞는 본문을 사용하지 않는 쪽은 같은 길이의 NOP로 채워 뒤 명령의 배치를 유지합니다. 이 줄 자체가 CPU에서 조건을 검사하는 명령은 아닙니다.

### 456행
```asm
	.else
```
앞 어셈블 조건의 반대 경우에 생성할 코드입니다. 

### 458행
```asm
	alternative_insn nop, "dmb sy", ARM64_WORKAROUND_1508412
```
erratum 1508412가 적용되는 CPU의 커널 복귀 경로에는 NOP 대신 DMB SY를 넣습니다. 정상 프로그램의 함수 인수가 아니라 영향을 받는 CPU에서 필요한 메모리 순서를 보완합니다. 이 매크로는 기본·교체 명령의 패치 정보를 만들며, CPU가 이 줄을 매번 if 조건처럼 실행하지 않습니다.

### 459행
```asm
	.endif
```
어셈블 조건에 따라 생성할 코드를 나누던 범위가 끝납니다. 

### 461행
```asm
	eret
```
예외가 끝났으므로 저장해 둔 PC와 실행 상태를 함께 되살립니다. 일반 RET와 달리 어느 예외 레벨과 스택 모드로 돌아갈지도 SPSR의 내용에 따라 정해집니다.

### 462행
```asm
	sb
```
ERET 뒤의 명령이 잘못된 직선 경로로 추측 실행되는 것을 막는 배리어입니다. SB 지원 CPU는 SB를, 미지원 CPU는 DSB NSH와 ISB를 사용하며 정상 복귀 경로는 이미 ERET로 떠납니다.

### 463행
```asm
	.endm
```
공통 작업의 정의를 여기서 마칩니다. RET가 아니라 빌드 도구가 읽는 경계입니다. 

## 소프트웨어 PAN과 tsk 별명
원본 464–509행

### 465행
```asm
#ifdef CONFIG_ARM64_SW_TTBR0_PAN
```
하드웨어 PAN이 없는 CPU에서도 사용자 주소에 대한 커널의 직접 접근을 막을 수 있도록 TTBR0 기반의 소프트웨어 PAN 함수들을 빌드합니다. 사용자 복사 helper가 필요할 때만 유효 TTBR0를 임시로 복원합니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.

### 474행
```asm
SYM_CODE_START_LOCAL(__swpan_entry_el1)
```
이 위치부터 (__swpan_entry_el1)라는 코드가 시작한다고 도구에 알려 줍니다. 정렬과 외부 공개 여부도 정의에 포함됩니다. 실제 작업은 아래의 명령들이 수행합니다.

### 475행
```asm
	mrs	x21, ttbr0_el1
```
현재 TTBR0의 ASID 필드를 읽어 사용자 접근용 표가 이미 예약 상태로 비활성화되어 있었는지 알아냅니다. 단순 페이지 표 주소만 검사하는 것이 아닙니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 476행
```asm
	tst	x21, #TTBR_ASID_MASK		// Check for the reserved ASID
```
예약 ASID 여부를 검사합니다. 예약값이면 예외 전에도 소프트웨어 PAN이 사용자 접근을 막고 있었으므로 복귀 때도 그 상태를 유지해야 합니다. TST는 두 피연산자의 비트 AND 결과로 조건 플래그를 갱신하며 원래 레지스터 값은 바꾸지 않습니다.

### 477행
```asm
	orr	x23, x23, #PSR_PAN_BIT		// Set the emulated PAN in the saved SPSR
```
저장 PSTATE에 모의 PAN 비트를 우선 세웁니다. 하드웨어 PAN이 없는 CPU에서 이 비트는 소프트웨어가 예외 전 사용자 접근 상태를 기록하는 용도입니다. ORR은 어느 한쪽이 1인 비트를 결과에 세우므로 원래 1이던 비트도 유지됩니다.

### 478행
```asm
	b.eq	1f				// TTBR0 access already disabled
```
앞서 계산한 조건 플래그가 Z=1이면 1f (L482)로 갑니다. 사이에 플래그를 바꾸지 않는 명령이 있어도 비교 결과는 남아 있습니다.

### 479행
```asm
	and	x23, x23, #~PSR_PAN_BIT		// Clear the emulated PAN in the saved SPSR
```
TTBR0가 유효한 사용자 접근 상태였다면 모의 PAN 표식을 지웁니다. EL1으로 복귀할 때 원래 허용 상태를 되살릴 근거가 됩니다. AND는 두 피연산자의 같은 위치 비트가 모두 1인 곳만 결과에 남깁니다.

### 480행
```asm
SYM_INNER_LABEL(__swpan_entry_el0, SYM_L_LOCAL)
```
기존 함수 안의 중간 위치에 이름을 붙입니다. 다른 분기에서 찾아올 수 있지만 새 함수를 호출하거나 스택 프레임을 만드는 동작은 아닙니다.

### 481행
```asm
	__uaccess_ttbr0_disable x21
```
유효한 사용자 TTBR0를 예약된 표와 ASID로 바꾸어 일반 커널 코드의 사용자 메모리 접근을 막습니다. tmp x21을 쓰며 명시적인 uaccess에서만 다시 허용합니다.

### 482행
```asm
1:	ret
```
LR(x30)에 적힌 주소로 실행을 돌립니다. 그 주소가 어떻게 준비됐는지가 중요합니다. 정상 함수의 호출자일 수도 있고, 절전 복귀나 trampoline에서 별도로 기록한 재개 지점일 수도 있습니다.

### 483행
```asm
SYM_CODE_END(__swpan_entry_el1)
```
도구에 이 코드 범위의 끝을 알려 크기와 심볼 정보를 기록합니다. CPU가 이 줄 때문에 자동으로 반환하지는 않습니다.

### 489행
```asm
SYM_CODE_START_LOCAL(__swpan_exit_el1)
```
이 위치부터 (__swpan_exit_el1)라는 코드가 시작한다고 도구에 알려 줍니다. 정렬과 외부 공개 여부도 정의에 포함됩니다. 실제 작업은 아래의 명령들이 수행합니다.

### 490행
```asm
	tbnz	x22, #22, 1f			// Skip re-enabling TTBR0 access if the PSR_PAN_BIT is set
```
저장한 모의 PAN bit 22가 켜져 있으면 예외 전에도 사용자 접근이 막혀 있었으므로 TTBR0를 다시 열지 않습니다. 이 분기는 지정 레지스터의 값이나 비트를 직접 검사하며 CMP가 남긴 NZCV를 읽거나 바꾸지 않습니다. 조건이 맞지 않으면 바로 다음 명령으로 진행합니다.

### 491행
```asm
	__uaccess_ttbr0_enable x0, x1
```
예외 전 커널이 uaccess 중이었다면 유효한 TTBR0를 복원해 그 접근을 계속할 수 있게 합니다. x0·x1은 그 전환에 쓰는 임시 레지스터입니다. 매크로는 현재 task에 저장된 TTBR0와 그 주소 공간의 ASID를 함께 복원합니다.

### 492행
```asm
1:	and	x22, x22, #~PSR_PAN_BIT		// ARMv8.0 CPUs do not understand this bit
```
하드웨어 PAN을 모르는 ARMv8.0 CPU에 실제 PSTATE 예약 비트를 넘기지 않도록 모의 PAN 표식을 지웁니다. 소프트웨어 기록은 이미 TTBR0 복원 판단에 사용했습니다. AND는 두 피연산자의 같은 위치 비트가 모두 1인 곳만 결과에 남깁니다.

### 493행
```asm
	ret
```
LR(x30)에 적힌 주소로 실행을 돌립니다. 그 주소가 어떻게 준비됐는지가 중요합니다. 정상 함수의 호출자일 수도 있고, 절전 복귀나 trampoline에서 별도로 기록한 재개 지점일 수도 있습니다.

### 494행
```asm
SYM_CODE_END(__swpan_exit_el1)
```
도구에 이 코드 범위의 끝을 알려 크기와 심볼 정보를 기록합니다. CPU가 이 줄 때문에 자동으로 반환하지는 않습니다.

### 496행
```asm
SYM_CODE_START_LOCAL(__swpan_exit_el0)
```
이 위치부터 (__swpan_exit_el0)라는 코드가 시작한다고 도구에 알려 줍니다. 정렬과 외부 공개 여부도 정의에 포함됩니다. 실제 작업은 아래의 명령들이 수행합니다.

### 497행
```asm
	__uaccess_ttbr0_enable x0, x1
```
사용자 모드로 돌아가므로 task의 유효 TTBR0를 복원합니다. 사용자 자신의 메모리 접근을 소프트웨어 PAN의 예약 표로 막아 두어서는 안 됩니다. 매크로는 현재 task에 저장된 TTBR0와 그 주소 공간의 ASID를 함께 복원합니다.

### 504행
```asm
	b	post_ttbr_update_workaround
```
TTBR 변경 뒤 필요한 CPU별 보완으로 tail-call합니다. 이 함수에서 새 LR을 만들지 않아 보완 함수가 RET하면 원래 kernel_exit 호출 위치로 돌아갑니다.

### 505행
```asm
SYM_CODE_END(__swpan_exit_el0)
```
도구에 이 코드 범위의 끝을 알려 크기와 심볼 정보를 기록합니다. CPU가 이 줄 때문에 자동으로 반환하지는 않습니다.

### 506행
```asm
#endif
```
빌드 조건으로 나누었던 구간을 닫습니다. 대상: 

### 509행
```asm
tsk	.req	x28		// current thread_info
```
같은 레지스터에 역할을 나타내는 별명을 붙입니다. 이름이 두 개가 되어도 저장 공간이 두 개 생기는 것은 아닙니다. tsk	.req	x28

## 16개 벡터와 잘못된 스택
원본 510–567행

### 511행
```asm
	.text
```
이후 출력할 바이트를 어느 섹션에 넣을지 바꿉니다. 빌드 도구가 코드를 분류하는 작업이며 현재 CPU의 PC나 SP를 옮기는 줄은 아닙니다. 

### 516행
```asm
	.pushsection ".entry.text", "ax"
```
이후 출력할 바이트를 어느 섹션에 넣을지 바꿉니다. 빌드 도구가 코드를 분류하는 작업이며 현재 CPU의 PC나 SP를 옮기는 줄은 아닙니다. ".entry.text", "ax"

### 518행
```asm
	.align	11
```
전체 벡터 표를 2KiB 경계에 맞춥니다. 16개 입구 각각 128바이트라는 하드웨어 벡터 배치에 필요한 정렬입니다. AArch64의 .align 인자는 바이트 수가 아니라 2의 지수입니다. 빌드할 때 필요한 패딩을 넣어 그 경계를 맞춥니다.

### 519행
```asm
SYM_CODE_START(vectors)
```
이 위치부터 (vectors)라는 코드가 시작한다고 도구에 알려 줍니다. 정렬과 외부 공개 여부도 정의에 포함됩니다. 실제 작업은 아래의 명령들이 수행합니다.
전체 16개 슬롯의 시작이며 VBAR_EL1이 가리키는 기준 주소입니다.

### 520행
```asm
	kernel_ventry	1, t, 64, sync		// Synchronous EL1t
```
EL1t 커널 실행(SP_EL0 선택) 중 발생한 동기 예외(SVC·fault·debug 등)의 128바이트 벡터 입구를 생성합니다. CPU는 벡터 표의 고정 위치를 고르며 이 입구는 pt_regs 공간·스택 검사 후 같은 종류의 실제 handler로 이어집니다. 이 줄에서 동기 예외(SVC·fault·debug 등)을 일부러 발생시키는 것은 아닙니다.

### 521행
```asm
	kernel_ventry	1, t, 64, irq		// IRQ EL1t
```
EL1t 커널 실행(SP_EL0 선택) 중 발생한 IRQ의 128바이트 벡터 입구를 생성합니다. CPU는 벡터 표의 고정 위치를 고르며 이 입구는 pt_regs 공간·스택 검사 후 같은 종류의 실제 handler로 이어집니다. 이 줄에서 IRQ을 일부러 발생시키는 것은 아닙니다.

### 522행
```asm
	kernel_ventry	1, t, 64, fiq		// FIQ EL1t
```
EL1t 커널 실행(SP_EL0 선택) 중 발생한 FIQ의 128바이트 벡터 입구를 생성합니다. CPU는 벡터 표의 고정 위치를 고르며 이 입구는 pt_regs 공간·스택 검사 후 같은 종류의 실제 handler로 이어집니다. 이 줄에서 FIQ을 일부러 발생시키는 것은 아닙니다.

### 523행
```asm
	kernel_ventry	1, t, 64, error		// Error EL1t
```
EL1t 커널 실행(SP_EL0 선택) 중 발생한 SError의 128바이트 벡터 입구를 생성합니다. CPU는 벡터 표의 고정 위치를 고르며 이 입구는 pt_regs 공간·스택 검사 후 같은 종류의 실제 handler로 이어집니다. 이 줄에서 SError을 일부러 발생시키는 것은 아닙니다.

### 525행
```asm
	kernel_ventry	1, h, 64, sync		// Synchronous EL1h
```
EL1h 커널 실행(SP_EL1 선택) 중 발생한 동기 예외(SVC·fault·debug 등)의 128바이트 벡터 입구를 생성합니다. CPU는 벡터 표의 고정 위치를 고르며 이 입구는 pt_regs 공간·스택 검사 후 같은 종류의 실제 handler로 이어집니다. 이 줄에서 동기 예외(SVC·fault·debug 등)을 일부러 발생시키는 것은 아닙니다.

### 526행
```asm
	kernel_ventry	1, h, 64, irq		// IRQ EL1h
```
EL1h 커널 실행(SP_EL1 선택) 중 발생한 IRQ의 128바이트 벡터 입구를 생성합니다. CPU는 벡터 표의 고정 위치를 고르며 이 입구는 pt_regs 공간·스택 검사 후 같은 종류의 실제 handler로 이어집니다. 이 줄에서 IRQ을 일부러 발생시키는 것은 아닙니다.

### 527행
```asm
	kernel_ventry	1, h, 64, fiq		// FIQ EL1h
```
EL1h 커널 실행(SP_EL1 선택) 중 발생한 FIQ의 128바이트 벡터 입구를 생성합니다. CPU는 벡터 표의 고정 위치를 고르며 이 입구는 pt_regs 공간·스택 검사 후 같은 종류의 실제 handler로 이어집니다. 이 줄에서 FIQ을 일부러 발생시키는 것은 아닙니다.

### 528행
```asm
	kernel_ventry	1, h, 64, error		// Error EL1h
```
EL1h 커널 실행(SP_EL1 선택) 중 발생한 SError의 128바이트 벡터 입구를 생성합니다. CPU는 벡터 표의 고정 위치를 고르며 이 입구는 pt_regs 공간·스택 검사 후 같은 종류의 실제 handler로 이어집니다. 이 줄에서 SError을 일부러 발생시키는 것은 아닙니다.

### 530행
```asm
	kernel_ventry	0, t, 64, sync		// Synchronous 64-bit EL0
```
EL0의 AArch64 사용자 실행 중 발생한 동기 예외(SVC·fault·debug 등)의 128바이트 벡터 입구를 생성합니다. CPU는 벡터 표의 고정 위치를 고르며 이 입구는 pt_regs 공간·스택 검사 후 같은 종류의 실제 handler로 이어집니다. 이 줄에서 동기 예외(SVC·fault·debug 등)을 일부러 발생시키는 것은 아닙니다.

### 531행
```asm
	kernel_ventry	0, t, 64, irq		// IRQ 64-bit EL0
```
EL0의 AArch64 사용자 실행 중 발생한 IRQ의 128바이트 벡터 입구를 생성합니다. CPU는 벡터 표의 고정 위치를 고르며 이 입구는 pt_regs 공간·스택 검사 후 같은 종류의 실제 handler로 이어집니다. 이 줄에서 IRQ을 일부러 발생시키는 것은 아닙니다.

### 532행
```asm
	kernel_ventry	0, t, 64, fiq		// FIQ 64-bit EL0
```
EL0의 AArch64 사용자 실행 중 발생한 FIQ의 128바이트 벡터 입구를 생성합니다. CPU는 벡터 표의 고정 위치를 고르며 이 입구는 pt_regs 공간·스택 검사 후 같은 종류의 실제 handler로 이어집니다. 이 줄에서 FIQ을 일부러 발생시키는 것은 아닙니다.

### 533행
```asm
	kernel_ventry	0, t, 64, error		// Error 64-bit EL0
```
EL0의 AArch64 사용자 실행 중 발생한 SError의 128바이트 벡터 입구를 생성합니다. CPU는 벡터 표의 고정 위치를 고르며 이 입구는 pt_regs 공간·스택 검사 후 같은 종류의 실제 handler로 이어집니다. 이 줄에서 SError을 일부러 발생시키는 것은 아닙니다.

### 535행
```asm
	kernel_ventry	0, t, 32, sync		// Synchronous 32-bit EL0
```
EL0의 AArch32 사용자 실행 중 발생한 동기 예외(SVC·fault·debug 등)의 128바이트 벡터 입구를 생성합니다. CPU는 벡터 표의 고정 위치를 고르며 이 입구는 pt_regs 공간·스택 검사 후 같은 종류의 실제 handler로 이어집니다. 이 줄에서 동기 예외(SVC·fault·debug 등)을 일부러 발생시키는 것은 아닙니다.

### 536행
```asm
	kernel_ventry	0, t, 32, irq		// IRQ 32-bit EL0
```
EL0의 AArch32 사용자 실행 중 발생한 IRQ의 128바이트 벡터 입구를 생성합니다. CPU는 벡터 표의 고정 위치를 고르며 이 입구는 pt_regs 공간·스택 검사 후 같은 종류의 실제 handler로 이어집니다. 이 줄에서 IRQ을 일부러 발생시키는 것은 아닙니다.

### 537행
```asm
	kernel_ventry	0, t, 32, fiq		// FIQ 32-bit EL0
```
EL0의 AArch32 사용자 실행 중 발생한 FIQ의 128바이트 벡터 입구를 생성합니다. CPU는 벡터 표의 고정 위치를 고르며 이 입구는 pt_regs 공간·스택 검사 후 같은 종류의 실제 handler로 이어집니다. 이 줄에서 FIQ을 일부러 발생시키는 것은 아닙니다.

### 538행
```asm
	kernel_ventry	0, t, 32, error		// Error 32-bit EL0
```
EL0의 AArch32 사용자 실행 중 발생한 SError의 128바이트 벡터 입구를 생성합니다. CPU는 벡터 표의 고정 위치를 고르며 이 입구는 pt_regs 공간·스택 검사 후 같은 종류의 실제 handler로 이어집니다. 이 줄에서 SError을 일부러 발생시키는 것은 아닙니다.

### 539행
```asm
SYM_CODE_END(vectors)
```
도구에 이 코드 범위의 끝을 알려 크기와 심볼 정보를 기록합니다. CPU가 이 줄 때문에 자동으로 반환하지는 않습니다.

### 541행
```asm
SYM_CODE_START_LOCAL(__bad_stack)
```
이 위치부터 (__bad_stack)라는 코드가 시작한다고 도구에 알려 줍니다. 정렬과 외부 공개 여부도 정의에 포함됩니다. 실제 작업은 아래의 명령들이 수행합니다.

### 549행
```asm
	mrs	x0, tpidrro_el0
```
정상 스택을 사용할 수 없던 시점에 TPIDRRO_EL0에 맡긴 원래 x0를 읽어 비정상 예외 문맥을 재구성합니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 555행
```asm
	sub	sp, sp, #PT_REGS_SIZE
```
스택에서 PT_REGS_SIZE바이트만큼 사용할 공간을 잡습니다. SP가 낮은 주소로 이동하지만, 아직 이 명령이 데이터를 저장한 것은 아닙니다.

### 556행
```asm
	kernel_entry 1
```
overflow stack에서 EL1 예외 프레임을 완성하여 오류 진단 코드가 레지스터를 안전하게 볼 수 있게 합니다. 손상된 원래 SP는 뒤에서 별도로 덮어써 프레임에 기록합니다.

### 557행
```asm
	mrs	x0, tpidr_el0
```
TPIDR_EL0에 맡긴 중단 당시 SP를 읽습니다. 현재 SP는 안전한 overflow stack이므로 둘을 구분해 오류 보고에 원래 값을 남깁니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 558행
```asm
	add	x0, x0, #PT_REGS_SIZE
```
x0를 기준으로 PT_REGS_SIZE을 더해 x0에 결과를 둡니다. 주소 계산에 쓰이는 경우에는 구조체의 필드나 다음 저장 위치를 찾는 동작입니다. 이 형태는 조건 플래그를 바꾸지 않습니다.

### 559행
```asm
	str	x0, [sp, #S_SP]
```
x0의 값을 sp를 기준으로 S_SP만큼 계산한 메모리 위치에 기록합니다. 레지스터 값을 나중에 다시 쓰거나 다른 코드가 읽을 수 있도록 메모리에 남기는 줄입니다. 이 스택 위치는 현재 함수 또는 예외 처리의 보관 공간입니다.

### 562행
```asm
	mov	x0, sp
```
현재 SP의 숫자를 x0에 기억해 둡니다. 스택 자체는 이동하거나 복사되지 않습니다. 나중에 이 값으로 원래 위치를 찾을 수 있습니다.

### 565행
```asm
	bl	handle_bad_stack
```
x0의 pt_regs를 handle_bad_stack에 전달해 task 스택 넘침/손상 상태를 보고합니다. 이미 안전한 overflow stack에 옮겼으며 정상적인 사용자 복귀를 기대하는 경로가 아닙니다. BL은 다음 명령의 주소를 LR(x30)에 기록하고 목적지로 분기합니다. 호출된 코드가 x0 같은 작업 레지스터를 바꿀 수 있으므로, 호출 뒤의 값은 그 코드의 반환·보존 규칙에 따릅니다.

### 566행
```asm
	ASM_BUG()
```
handle_bad_stack이 뜻하지 않게 반환해도 손상된 스택의 실행을 이어가지 않도록 BUG 예외를 발생시킵니다. 원래 예외로 정상 복귀하는 지점이 아닙니다. 어셈블 단계에는 진단용 BUG 메타데이터와 BRK 인코딩을 배치하고, 실행이 이 BRK에 도달하면 예외가 발생합니다.

### 567행
```asm
SYM_CODE_END(__bad_stack)
```
도구에 이 코드 범위의 끝을 알려 크기와 심볼 정보를 기록합니다. CPU가 이 줄 때문에 자동으로 반환하지는 않습니다.

## C 핸들러와 사용자·커널 복귀
원본 568–619행

### 570행
```asm
	.macro entry_handler el:req, ht:req, regsize:req, label:req
```
벡터별 처리 본문을 만드는 매크로입니다. 예외 문맥을 저장하고 x0=pt_regs로 해당 C handler를 호출한 뒤, 중단된 EL에 맞춰 ret_to_user 또는 ret_to_kernel로 연결합니다. 이 줄은 어셈블러 매크로 정의의 시작입니다. 본문은 매크로를 사용하는 위치에 펼쳐지며, 정의 자체가 런타임 함수 호출은 아닙니다.

### 571행
```asm
SYM_CODE_START_LOCAL(el\el\ht\()_\regsize\()_\label)
```
이 위치부터 (el\el\ht\()_\regsize\()_\label)라는 코드가 시작한다고 도구에 알려 줍니다. 정렬과 외부 공개 여부도 정의에 포함됩니다. 실제 작업은 아래의 명령들이 수행합니다.

### 572행
```asm
	kernel_entry \el, \regsize
```
중단된 프로그램의 상태를 스택에 보관하고 커널이 사용할 현재 태스크와 제어 상태를 준비합니다. 이후 C 함수가 레지스터를 써도 복귀 기록은 남습니다. 여기의 인자는 \el, \regsize입니다.

### 573행
```asm
	mov	x0, sp
```
현재 SP의 숫자를 x0에 기억해 둡니다. 스택 자체는 이동하거나 복사되지 않습니다. 나중에 이 값으로 원래 위치를 찾을 수 있습니다.
C 예외 핸들러의 첫 인자로 pt_regs 포인터를 전달합니다.

### 574행
```asm
	bl	el\el\ht\()_\regsize\()_\label\()_handler
```
el·ht·regsize·label로 조립한 예외 종류별 C handler를 호출합니다. x0는 완성된 pt_regs이며, 예를 들어 EL0 AArch64 sync는 ESR을 해석해 SVC·fault 등의 실제 처리로 분기합니다. BL은 다음 명령의 주소를 LR(x30)에 기록하고 목적지로 분기합니다. 호출된 코드가 x0 같은 작업 레지스터를 바꿀 수 있으므로, 호출 뒤의 값은 그 코드의 반환·보존 규칙에 따릅니다.

### 575행
```asm
	.if \el == 0
```
어셈블 단계에서 인자나 식을 검사해 필요한 명령만 만듭니다. \el == 0

### 576행
```asm
	b	ret_to_user
```
ret_to_user부터 실행을 계속합니다. 돌아올 주소를 새로 적지 않으므로 일반 함수 호출과는 다른 제어 이동입니다.

### 577행
```asm
	.else
```
앞 어셈블 조건의 반대 경우에 생성할 코드입니다. 

### 578행
```asm
	b	ret_to_kernel
```
ret_to_kernel부터 실행을 계속합니다. 돌아올 주소를 새로 적지 않으므로 일반 함수 호출과는 다른 제어 이동입니다.

### 579행
```asm
	.endif
```
어셈블 조건에 따라 생성할 코드를 나누던 범위가 끝납니다. 

### 580행
```asm
SYM_CODE_END(el\el\ht\()_\regsize\()_\label)
```
도구에 이 코드 범위의 끝을 알려 크기와 심볼 정보를 기록합니다. CPU가 이 줄 때문에 자동으로 반환하지는 않습니다.

### 581행
```asm
	.endm
```
공통 작업의 정의를 여기서 마칩니다. RET가 아니라 빌드 도구가 읽는 경계입니다. 

### 586행
```asm
	entry_handler	1, t, 64, sync
```
EL1t 커널 실행(SP_EL0 선택)에서 온 동기 예외(SVC·fault·debug 등)의 저장·C 호출·복귀 본문을 생성합니다. kernel_entry로 원래 상태를 보존하고 x0=pt_regs를 해당 C handler에 전달한 뒤 ret_to_kernel로 이어집니다.

### 587행
```asm
	entry_handler	1, t, 64, irq
```
EL1t 커널 실행(SP_EL0 선택)에서 온 IRQ의 저장·C 호출·복귀 본문을 생성합니다. kernel_entry로 원래 상태를 보존하고 x0=pt_regs를 해당 C handler에 전달한 뒤 ret_to_kernel로 이어집니다.

### 588행
```asm
	entry_handler	1, t, 64, fiq
```
EL1t 커널 실행(SP_EL0 선택)에서 온 FIQ의 저장·C 호출·복귀 본문을 생성합니다. kernel_entry로 원래 상태를 보존하고 x0=pt_regs를 해당 C handler에 전달한 뒤 ret_to_kernel로 이어집니다.

### 589행
```asm
	entry_handler	1, t, 64, error
```
EL1t 커널 실행(SP_EL0 선택)에서 온 SError의 저장·C 호출·복귀 본문을 생성합니다. kernel_entry로 원래 상태를 보존하고 x0=pt_regs를 해당 C handler에 전달한 뒤 ret_to_kernel로 이어집니다.

### 591행
```asm
	entry_handler	1, h, 64, sync
```
EL1h 커널 실행(SP_EL1 선택)에서 온 동기 예외(SVC·fault·debug 등)의 저장·C 호출·복귀 본문을 생성합니다. kernel_entry로 원래 상태를 보존하고 x0=pt_regs를 해당 C handler에 전달한 뒤 ret_to_kernel로 이어집니다.

### 592행
```asm
	entry_handler	1, h, 64, irq
```
EL1h 커널 실행(SP_EL1 선택)에서 온 IRQ의 저장·C 호출·복귀 본문을 생성합니다. kernel_entry로 원래 상태를 보존하고 x0=pt_regs를 해당 C handler에 전달한 뒤 ret_to_kernel로 이어집니다.

### 593행
```asm
	entry_handler	1, h, 64, fiq
```
EL1h 커널 실행(SP_EL1 선택)에서 온 FIQ의 저장·C 호출·복귀 본문을 생성합니다. kernel_entry로 원래 상태를 보존하고 x0=pt_regs를 해당 C handler에 전달한 뒤 ret_to_kernel로 이어집니다.

### 594행
```asm
	entry_handler	1, h, 64, error
```
EL1h 커널 실행(SP_EL1 선택)에서 온 SError의 저장·C 호출·복귀 본문을 생성합니다. kernel_entry로 원래 상태를 보존하고 x0=pt_regs를 해당 C handler에 전달한 뒤 ret_to_kernel로 이어집니다.

### 596행
```asm
	entry_handler	0, t, 64, sync
```
EL0의 AArch64 사용자 실행에서 온 동기 예외(SVC·fault·debug 등)의 저장·C 호출·복귀 본문을 생성합니다. kernel_entry로 원래 상태를 보존하고 x0=pt_regs를 해당 C handler에 전달한 뒤 ret_to_user로 이어집니다.

### 597행
```asm
	entry_handler	0, t, 64, irq
```
EL0의 AArch64 사용자 실행에서 온 IRQ의 저장·C 호출·복귀 본문을 생성합니다. kernel_entry로 원래 상태를 보존하고 x0=pt_regs를 해당 C handler에 전달한 뒤 ret_to_user로 이어집니다.

### 598행
```asm
	entry_handler	0, t, 64, fiq
```
EL0의 AArch64 사용자 실행에서 온 FIQ의 저장·C 호출·복귀 본문을 생성합니다. kernel_entry로 원래 상태를 보존하고 x0=pt_regs를 해당 C handler에 전달한 뒤 ret_to_user로 이어집니다.

### 599행
```asm
	entry_handler	0, t, 64, error
```
EL0의 AArch64 사용자 실행에서 온 SError의 저장·C 호출·복귀 본문을 생성합니다. kernel_entry로 원래 상태를 보존하고 x0=pt_regs를 해당 C handler에 전달한 뒤 ret_to_user로 이어집니다.

### 601행
```asm
	entry_handler	0, t, 32, sync
```
EL0의 AArch32 사용자 실행에서 온 동기 예외(SVC·fault·debug 등)의 저장·C 호출·복귀 본문을 생성합니다. kernel_entry로 원래 상태를 보존하고 x0=pt_regs를 해당 C handler에 전달한 뒤 ret_to_user로 이어집니다.

### 602행
```asm
	entry_handler	0, t, 32, irq
```
EL0의 AArch32 사용자 실행에서 온 IRQ의 저장·C 호출·복귀 본문을 생성합니다. kernel_entry로 원래 상태를 보존하고 x0=pt_regs를 해당 C handler에 전달한 뒤 ret_to_user로 이어집니다.

### 603행
```asm
	entry_handler	0, t, 32, fiq
```
EL0의 AArch32 사용자 실행에서 온 FIQ의 저장·C 호출·복귀 본문을 생성합니다. kernel_entry로 원래 상태를 보존하고 x0=pt_regs를 해당 C handler에 전달한 뒤 ret_to_user로 이어집니다.

### 604행
```asm
	entry_handler	0, t, 32, error
```
EL0의 AArch32 사용자 실행에서 온 SError의 저장·C 호출·복귀 본문을 생성합니다. kernel_entry로 원래 상태를 보존하고 x0=pt_regs를 해당 C handler에 전달한 뒤 ret_to_user로 이어집니다.

### 606행
```asm
SYM_CODE_START_LOCAL(ret_to_kernel)
```
이 위치부터 (ret_to_kernel)라는 코드가 시작한다고 도구에 알려 줍니다. 정렬과 외부 공개 여부도 정의에 포함됩니다. 실제 작업은 아래의 명령들이 수행합니다.

### 607행
```asm
	kernel_exit 1
```
스택의 기록을 CPU로 되돌리고 원래 실행을 재개합니다. 사용자에게 돌아가는 경우에는 사용자용 접근 제어와 매핑도 맞춥니다. 여기의 인자는 1입니다.

### 608행
```asm
SYM_CODE_END(ret_to_kernel)
```
도구에 이 코드 범위의 끝을 알려 크기와 심볼 정보를 기록합니다. CPU가 이 줄 때문에 자동으로 반환하지는 않습니다.

### 610행
```asm
SYM_CODE_START_LOCAL(ret_to_user)
```
이 위치부터 (ret_to_user)라는 코드가 시작한다고 도구에 알려 줍니다. 정렬과 외부 공개 여부도 정의에 포함됩니다. 실제 작업은 아래의 명령들이 수행합니다.

### 611행
```asm
	ldr	x19, [tsk, #TSK_TI_FLAGS]	// re-check for single-step
```
tsk를 기준으로 TSK_TI_FLAGS만큼 계산한 메모리 위치에 보관된 값을 x19로 가져옵니다. 주소 숫자를 옮기는 MOV와 달리 실제 메모리를 읽습니다.

### 612행
```asm
	enable_step_tsk x19, x2
```
사용자 복귀 직전에 task의 TIF_SINGLESTEP를 다시 확인해 디버거가 요청한 단일 스텝 상태를 복구합니다. 커널 진입 때 꺼 두었던 스텝 상태와 대응합니다. 실제 설정 대상은 MDSCR_EL1.SS이며 TIF_SINGLESTEP이 있는 경우에만 이 비트를 세웁니다.

### 613행
```asm
#ifdef CONFIG_KSTACK_ERASE
```
KSTACK_ERASE는 syscall/예외 처리에서 사용한 커널 스택의 잔여값을 지워 정보 노출 위험을 줄이는 기능입니다. 켜진 빌드에서는 사용자 복귀 전에 정리 함수를 호출하고, 꺼지면 추가 스택 지우기 비용이 없습니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.

### 614행
```asm
	bl	stackleak_erase_on_task_stack
```
현재 task 스택에서 지울 수 있는 사용 흔적을 정리합니다. 예외 복귀에 필요한 활성 프레임을 무작정 0으로 만드는 것이 아니라 stackleak가 추적한 범위와 현재 SP를 고려하는 호출입니다. BL은 다음 명령의 주소를 LR(x30)에 기록하고 목적지로 분기합니다. 호출된 코드가 x0 같은 작업 레지스터를 바꿀 수 있으므로, 호출 뒤의 값은 그 코드의 반환·보존 규칙에 따릅니다.

### 615행
```asm
#endif
```
빌드 조건으로 나누었던 구간을 닫습니다. 대상: 

### 616행
```asm
	kernel_exit 0
```
스택의 기록을 CPU로 되돌리고 원래 실행을 재개합니다. 사용자에게 돌아가는 경우에는 사용자용 접근 제어와 매핑도 맞춥니다. 여기의 인자는 0입니다.

### 617행
```asm
SYM_CODE_END(ret_to_user)
```
도구에 이 코드 범위의 끝을 알려 크기와 심볼 정보를 기록합니다. CPU가 이 줄 때문에 자동으로 반환하지는 않습니다.

### 619행
```asm
	.popsection				// .entry.text
```
이후 출력할 바이트를 어느 섹션에 넣을지 바꿉니다. 빌드 도구가 코드를 분류하는 작업이며 현재 CPU의 PC나 SP를 옮기는 줄은 아닙니다. 

## KPTI 매핑 교체와 데이터 접근
원본 620–677행

### 622행
```asm
	.macro tramp_map_kernel, tmp
```
KPTI의 최소 trampoline 매핑에서 정상 커널 매핑으로 돌아가는 매크로입니다. TTBR1의 두 표 사이 고정 offset과 사용자 ASID 표식을 조정하며 필요한 CPU 보완을 수행합니다. 이 줄은 어셈블러 매크로 정의의 시작입니다. 본문은 매크로를 사용하는 위치에 펼쳐지며, 정의 자체가 런타임 함수 호출은 아닙니다.

### 623행
```asm
	mrs	\tmp, ttbr1_el1
```
현재 trampoline 표를 가리키는 TTBR1을 읽습니다. 이 값을 기준으로 커널 swapper 표 주소를 계산하므로 별도 메모리 포인터를 먼저 읽지 않아도 됩니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 624행
```asm
	add	\tmp, \tmp, #TRAMP_SWAPPER_OFFSET
```
링커가 검증한 TRAMP_SWAPPER_OFFSET을 더해 trampoline 표에서 정상 swapper 페이지 표 루트로 이동합니다. 임의 메모리 offset이 아니라 두 표의 배치 차이입니다. 이 ADD 형식은 NZCV 조건 플래그를 바꾸지 않습니다.

### 625행
```asm
	bic	\tmp, \tmp, #USER_ASID_FLAG
```
TTBR1에서 사용자 쪽 ASID 선택 표식을 지워 커널 쪽 번역 식별자를 사용하게 합니다. KPTI의 두 매핑 번역을 섞지 않도록 하는 전환입니다. BIC는 마스크에서 1인 위치만 0으로 만들고 나머지 비트는 유지합니다.

### 626행
```asm
	msr	ttbr1_el1, \tmp
```
계산한 정상 커널 TTBR1을 설치합니다. 뒤에서 원래 커널 vectors와 데이터에 접근할 수 있게 만드는 핵심 매핑 복구입니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 627행
```asm
#ifdef CONFIG_QCOM_FALKOR_ERRATUM_1003
```
Qualcomm Falkor erratum 1003 우회 지원을 빌드하면 KPTI 매핑 복구 뒤의 추가 TLBI 경로를 포함합니다. 꺼지면 이 CPU별 보완 코드는 생성되지 않습니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.

### 628행
```asm
alternative_if ARM64_WORKAROUND_QCOM_FALKOR_E1003
```
실제로 Falkor E1003 영향을 받는 시스템에만 trampoline 영역 번역 무효화와 동기화를 남깁니다. 같은 CPU별 작업을 일반 KPTI 필수 작업과 구분합니다. 이 매크로는 기본·교체 명령의 패치 정보를 만들며, CPU가 이 줄을 매번 if 조건처럼 실행하지 않습니다.

### 630행
```asm
	movk	\tmp, #:abs_g2_nc:(TRAMP_VALIAS >> 12)
```
한 명령에 담기 어려운 큰 값이나 주소를 레지스터에 구성합니다. MOVK는 일부 16비트만 덮어쓰므로 앞에서 만든 나머지 조각을 유지합니다.

### 631행
```asm
	movk	\tmp, #:abs_g1_nc:(TRAMP_VALIAS >> 12)
```
한 명령에 담기 어려운 큰 값이나 주소를 레지스터에 구성합니다. MOVK는 일부 16비트만 덮어쓰므로 앞에서 만든 나머지 조각을 유지합니다.

### 633행
```asm
	movk	\tmp, #:abs_g0_nc:((TRAMP_VALIAS & ~(SZ_2M - 1)) >> 12)
```
한 명령에 담기 어려운 큰 값이나 주소를 레지스터에 구성합니다. MOVK는 일부 16비트만 덮어쓰므로 앞에서 만든 나머지 조각을 유지합니다.

### 634행
```asm
	isb
```
뒤 명령을 변경된 CPU 제어 상태에 맞춰 실행하도록 동기화합니다. 주소 변환이나 벡터 설정을 바꾼 뒤 예전 실행 상태가 계속 쓰이지 않게 합니다.

### 635행
```asm
	tlbi	vae1, \tmp
```
앞에서 만든 trampoline 영역 주소를 TLBI 피연산자로 사용해 관련 번역을 무효화합니다. Falkor E1003 보완의 일부이며 데이터 캐시 내용을 삭제하는 명령이 아닙니다. 페이지 테이블 메모리를 수정해도 이미 저장된 TLB 변환이 자동으로 없어지는 것은 아니므로 별도로 무효화합니다.

### 636행
```asm
	dsb	nsh
```
지정 범위·종류의 앞선 작업이 완료되기를 기다립니다. 페이지 복사나 캐시 관리가 끝나기 전에 다음 상태로 넘어가지 않도록 하는 대기 지점입니다.

### 637행
```asm
alternative_else_nop_endif
```
628행에서 시작한 ARM64_WORKAROUND_QCOM_FALKOR_E1003 패치 묶음을 닫습니다. 조건에 맞는 본문을 사용하지 않는 쪽은 같은 길이의 NOP로 채워 뒤 명령의 배치를 유지합니다. 이 줄 자체가 CPU에서 조건을 검사하는 명령은 아닙니다.

### 638행
```asm
#endif /* CONFIG_QCOM_FALKOR_ERRATUM_1003 */
```
빌드 조건으로 나누었던 구간을 닫습니다. 대상: 

### 639행
```asm
	.endm
```
공통 작업의 정의를 여기서 마칩니다. RET가 아니라 빌드 도구가 읽는 경계입니다. 

### 642행
```asm
	.macro tramp_unmap_kernel, tmp
```
사용자 복귀를 위해 정상 커널 TTBR1에서 최소 trampoline 표로 바꾸는 매크로입니다. 커널의 넓은 주소를 감추되 이 매크로 다음의 복귀 코드는 계속 실행할 수 있어야 합니다. 이 줄은 어셈블러 매크로 정의의 시작입니다. 본문은 매크로를 사용하는 위치에 펼쳐지며, 정의 자체가 런타임 함수 호출은 아닙니다.

### 643행
```asm
	mrs	\tmp, ttbr1_el1
```
현재 정상 커널 루트와 ASID를 담은 TTBR1을 읽어 제한된 사용자 실행용 매핑으로 바꿀 기준으로 사용합니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 644행
```asm
	sub	\tmp, \tmp, #TRAMP_SWAPPER_OFFSET
```
swapper와 trampoline 루트 사이의 고정 배치 차이를 빼서 최소 표 주소로 이동합니다. 이 SUB 형식은 NZCV 조건 플래그를 바꾸지 않습니다.

### 645행
```asm
	orr	\tmp, \tmp, #USER_ASID_FLAG
```
사용자 쪽 ASID 표식을 세워 최소 매핑의 번역을 커널 쪽 번역과 구분합니다. ORR은 어느 한쪽이 1인 비트를 결과에 세우므로 원래 1이던 비트도 유지됩니다.

### 646행
```asm
	msr	ttbr1_el1, \tmp
```
사용자 실행용 최소 TTBR1을 설치합니다. 이후에는 일반 커널 데이터에 기대지 않고 trampoline 안에서 ERET까지 마쳐야 합니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 652행
```asm
	.endm
```
공통 작업의 정의를 여기서 마칩니다. RET가 아니라 빌드 도구가 읽는 경계입니다. 

### 654행
```asm
	.macro		tramp_data_read_var	dst, var
```
최소 trampoline 매핑에서 사용할 외부 기호 주소를 읽는 매크로입니다. 재배치 커널은 전용 rodata 슬롯을 통해 런타임 주소를 얻고, 고정 주소 커널은 MOVZ/MOVK로 주소 상수를 조립합니다. 이 줄은 어셈블러 매크로 정의의 시작입니다. 본문은 매크로를 사용하는 위치에 펼쳐지며, 정의 자체가 런타임 함수 호출은 아닙니다.

### 655행
```asm
#ifdef CONFIG_RELOCATABLE
```
재배치 가능한 커널은 적재 위치·KASLR에 따라 기호 주소가 달라질 수 있으므로 trampoline 전용 데이터 슬롯에 든 주소를 읽습니다. 꺼진 빌드는 고정 가상 주소를 명령 즉시값으로 만들 수 있습니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.

### 656행
```asm
	ldr		\dst, .L__tramp_data_\var
```
trampoline rodata의 해당 기호 슬롯에서 재배치된 실제 주소를 읽습니다. 제한된 매핑 안에서도 사용할 수 있게 따로 둔 리터럴입니다. 이 LDR 리터럴 형식은 PC 기준으로 레이블 위치를 찾아 그곳의 메모리를 읽습니다. 레이블 주소 자체를 구하는 ADR과 구분합니다.

### 657행
```asm
	.ifndef		.L__tramp_data_\var
```
아직 만들어지지 않은 심볼인지 검사해 중복 생성을 피합니다. .L__tramp_data_\var

### 658행
```asm
	.pushsection	".entry.tramp.rodata", "a", %progbits
```
이후 출력할 바이트를 어느 섹션에 넣을지 바꿉니다. 빌드 도구가 코드를 분류하는 작업이며 현재 CPU의 PC나 SP를 옮기는 줄은 아닙니다. ".entry.tramp.rodata", "a", %progbits

### 659행
```asm
	.align		3
```
다음 코드나 데이터가 정해진 경계에서 시작하도록 빈 공간을 채웁니다. 이 .align 인자는 바이트 수가 아니라 2의 지수입니다. 값은 3입니다.

### 660행
```asm
.L__tramp_data_\var:
```
여기에 .L__tramp_data_\var라는 위치 이름을 붙입니다. 다른 줄의 분기나 주소 계산이 이 위치를 찾아올 수 있습니다. 이름 자체가 CPU 작업을 추가하지는 않습니다.

### 661행
```asm
	.quad		\var
```
각 값을 8바이트 크기로 파일 안에 기록합니다. \var는 CPU가 지금 계산할 식이 아니라 어셈블·링크 단계에서 채울 데이터입니다.

### 662행
```asm
	.popsection
```
이후 출력할 바이트를 어느 섹션에 넣을지 바꿉니다. 빌드 도구가 코드를 분류하는 작업이며 현재 CPU의 PC나 SP를 옮기는 줄은 아닙니다. 

### 663행
```asm
	.endif
```
어셈블 조건에 따라 생성할 코드를 나누던 범위가 끝납니다. 

### 664행
```asm
#else
```
고정 주소 빌드에서는 기호의 가상 주소를 MOVZ/MOVK 즉시값으로 조립합니다. 재배치 포인터를 담은 별도 trampoline rodata 슬롯이 필요 없는 대안입니다.

### 673행
```asm
	movz		\dst, :abs_g2_s:\var
```
한 명령에 담기 어려운 큰 값이나 주소를 레지스터에 구성합니다. 즉시값 조각과 재배치 표현을 사용하며, 메모리에서 그 주소의 본문을 복사하는 작업은 아닙니다.

### 674행
```asm
	movk		\dst, :abs_g1_nc:\var
```
한 명령에 담기 어려운 큰 값이나 주소를 레지스터에 구성합니다. MOVK는 일부 16비트만 덮어쓰므로 앞에서 만든 나머지 조각을 유지합니다.

### 675행
```asm
	movk		\dst, :abs_g0_nc:\var
```
한 명령에 담기 어려운 큰 값이나 주소를 레지스터에 구성합니다. MOVK는 일부 16비트만 덮어쓰므로 앞에서 만든 나머지 조각을 유지합니다.

### 676행
```asm
#endif
```
빌드 조건으로 나누었던 구간을 닫습니다. 대상: 

### 677행
```asm
	.endm
```
공통 작업의 정의를 여기서 마칩니다. RET가 아니라 빌드 도구가 읽는 경계입니다. 

## 분기 이력 완화와 trampoline 슬롯
원본 678–750행

### 679행
```asm
#define BHB_MITIGATION_NONE	0
```
BHB_MITIGATION_NONE=0은 분기 이력 완화를 추가하지 않은 벡터 변형을 고르는 생성 인수입니다. 런타임 오류 코드가 아닙니다. 전처리기는 이후 이 이름이 쓰인 곳에 정의한 내용을 치환합니다. 실행 중 값을 보관하는 변수를 만드는 줄은 아닙니다.

### 680행
```asm
#define BHB_MITIGATION_LOOP	1
```
BHB_MITIGATION_LOOP=1은 CPU에 맞는 분기 반복으로 이력 영향을 줄이는 벡터 변형입니다. 반복 자체를 보안 목적의 진입 코드로 생성합니다. 전처리기는 이후 이 이름이 쓰인 곳에 정의한 내용을 치환합니다. 실행 중 값을 보관하는 변수를 만드는 줄은 아닙니다.

### 681행
```asm
#define BHB_MITIGATION_FW	2
```
BHB_MITIGATION_FW=2는 펌웨어 SMCCC 완화를 호출하는 벡터 변형입니다. LOOP 방식과 동시에 모두 실행하라는 뜻이 아니라 별도 후보를 구분합니다. 전처리기는 이후 이 이름이 쓰인 곳에 정의한 내용을 치환합니다. 실행 중 값을 보관하는 변수를 만드는 줄은 아닙니다.

### 682행
```asm
#define BHB_MITIGATION_INSN	3
```
BHB_MITIGATION_INSN=3은 CLEARBHB 명령으로 분기 이력을 정리하는 벡터 변형입니다. 지원 방식에 맞는 벡터가 이후 CPU별로 선택됩니다. 전처리기는 이후 이 이름이 쓰인 곳에 정의한 내용을 치환합니다. 실행 중 값을 보관하는 변수를 만드는 줄은 아닙니다.

### 684행
```asm
	.macro tramp_ventry, vector_start, regsize, kpti, bhb
```
사용자 예외용 trampoline 입구를 생성합니다. regsize는 원래 사용자 상태의 폭, kpti는 매핑 교체 여부, bhb는 Spectre 분기 이력 완화 방식을 선택하며 vector_start로 같은 예외 슬롯의 정상 벡터 offset을 계산합니다. 이 줄은 어셈블러 매크로 정의의 시작입니다. 본문은 매크로를 사용하는 위치에 펼쳐지며, 정의 자체가 런타임 함수 호출은 아닙니다.

### 685행
```asm
	.align	7
```
다음 코드나 데이터가 정해진 경계에서 시작하도록 빈 공간을 채웁니다. 이 .align 인자는 바이트 수가 아니라 2의 지수입니다. 값은 7입니다.

### 686행
```asm
1:
```
여기에 1라는 위치 이름을 붙입니다. 다른 줄의 분기나 주소 계산이 이 위치를 찾아올 수 있습니다. 이름 자체가 CPU 작업을 추가하지는 않습니다.

### 687행
```asm
	.if	\regsize == 64
```
어셈블 단계에서 인자나 식을 검사해 필요한 명령만 만듭니다. \regsize == 64

### 688행
```asm
	msr	tpidrro_el0, x30	// Restored in kernel_ventry
```
최소 매핑에서 x30을 임시로 사용하기 전에 TPIDRRO_EL0에 사용자 값을 보관합니다. 정상 kernel_ventry에 도착하면 앞의 복구 코드가 다시 x30으로 읽습니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 689행
```asm
	.endif
```
어셈블 조건에 따라 생성할 코드를 나누던 범위가 끝납니다. 

### 691행
```asm
	.if	\bhb == BHB_MITIGATION_LOOP
```
어셈블 단계에서 인자나 식을 검사해 필요한 명령만 만듭니다. \bhb == BHB_MITIGATION_LOOP

### 696행
```asm
	__mitigate_spectre_bhb_loop	x30
```
CPU별로 준비된 분기 반복을 실행해 사용자 분기 이력이 커널의 추측 실행에 미치는 영향을 줄입니다. x30은 아직 원래 값을 TPIDRRO_EL0에 보관해 둔 임시 레지스터입니다. 반복 횟수는 CPU에 필요한 값으로 패치됩니다.

### 697행
```asm
	.endif // \bhb == BHB_MITIGATION_LOOP
```
어셈블 조건에 따라 생성할 코드를 나누던 범위가 끝납니다. 

### 699행
```asm
	.if	\bhb == BHB_MITIGATION_INSN
```
어셈블 단계에서 인자나 식을 검사해 필요한 명령만 만듭니다. \bhb == BHB_MITIGATION_INSN

### 700행
```asm
	clearbhb
```
지원 CPU에서 분기 이력 상태를 정리하는 CLEARBHB를 실행합니다. 일반 메모리 캐시를 비우는 명령이 아니며 다음 ISB와 함께 진입 완화를 구성합니다. 매크로는 HINT #22 인코딩을 출력합니다.

### 701행
```asm
	isb
```
뒤 명령을 변경된 CPU 제어 상태에 맞춰 실행하도록 동기화합니다. 주소 변환이나 벡터 설정을 바꾼 뒤 예전 실행 상태가 계속 쓰이지 않게 합니다.

### 702행
```asm
	.endif // \bhb == BHB_MITIGATION_INSN
```
어셈블 조건에 따라 생성할 코드를 나누던 범위가 끝납니다. 

### 704행
```asm
	.if	\kpti == 1
```
어셈블 단계에서 인자나 식을 검사해 필요한 명령만 만듭니다. \kpti == 1

### 710행
```asm
	bl	2f
```
바로 아래 지역 라벨을 BL로 호출해 반환 예측용 이력을 준비합니다. 일반 서비스 함수 호출이 아니라 trampoline의 추측 실행 방어 순서를 만드는 제어 흐름입니다. BL은 다음 명령의 주소를 LR(x30)에 기록하고 목적지로 분기합니다. 호출된 코드가 x0 같은 작업 레지스터를 바꿀 수 있으므로, 호출 뒤의 값은 그 코드의 반환·보존 규칙에 따릅니다.

### 711행
```asm
	b	.
```
같은 명령 위치에서 반복합니다. 이 경로에서는 더 진행하거나 호출자에게 돌아갈 정상 절차를 마련하지 않았습니다.

### 712행
```asm
2:
```
여기에 2라는 위치 이름을 붙입니다. 다른 줄의 분기나 주소 계산이 이 위치를 찾아올 수 있습니다. 이름 자체가 CPU 작업을 추가하지는 않습니다.

### 713행
```asm
	tramp_map_kernel	x30
```
trampoline 표에서 정상 커널 표로 TTBR1을 전환합니다. 원래 vectors 주소와 커널 데이터에 접근하기 전에 매핑을 복구해야 합니다.

### 714행
```asm
alternative_insn isb, nop, ARM64_WORKAROUND_QCOM_FALKOR_E1003
```
Falkor E1003 경로는 tramp_map_kernel 안에서 필요한 ISB를 이미 수행하므로 이 추가 ISB를 NOP로 바꿉니다. 다른 CPU에는 TTBR 갱신 뒤 실행 동기화를 위해 ISB가 남습니다. 이 매크로는 기본·교체 명령의 패치 정보를 만들며, CPU가 이 줄을 매번 if 조건처럼 실행하지 않습니다.

### 715행
```asm
	tramp_data_read_var	x30, vectors
```
재배치 여부에 맞는 방법으로 정상 커널 vectors 주소를 x30에 가져옵니다. 최소 trampoline에서 커널 진입점으로 연결할 목적지입니다. 재배치 구성에서는 별도 rodata의 리터럴을 읽고, 그렇지 않은 구성에서는 MOVZ/MOVK로 주소를 구성합니다.

### 716행
```asm
alternative_if_not ARM64_WORKAROUND_CAVIUM_TX2_219_PRFM
```
ThunderX2의 특정 prefetch 문제 보완이 필요한 CPU에서는 다음 코드 prefetch를 NOP로 대체합니다. 영향을 받지 않는 경우만 정상 벡터의 명령 가져오기를 미리 요청합니다. 이 매크로는 기본·교체 명령의 패치 정보를 만들며, CPU가 이 줄을 매번 if 조건처럼 실행하지 않습니다.

### 717행
```asm
	prfm	plil1strm, [x30, #(1b - \vector_start)]
```
정상 벡터 표에서 현재 예외와 같은 슬롯의 명령을 instruction cache 쪽으로 prefetch합니다. 실제 handler 호출을 수행하는 것은 아니며 CPU 보완 조건에 따라 빠질 수 있습니다. 이는 미리 가져오라는 힌트이며 LDR처럼 결과를 일반 레지스터에 넣지 않습니다.

### 718행
```asm
alternative_else_nop_endif
```
716행에서 시작한 ARM64_WORKAROUND_CAVIUM_TX2_219_PRFM 패치 묶음을 닫습니다. 조건에 맞는 본문을 사용하지 않는 쪽은 같은 길이의 NOP로 채워 뒤 명령의 배치를 유지합니다. 이 줄 자체가 CPU에서 조건을 검사하는 명령은 아닙니다.

### 720행
```asm
	msr	vbar_el1, x30
```
VBAR_EL1을 정상 커널 벡터 표로 바꿔 이후 커널 실행 중의 예외도 정상 입구로 들어오게 합니다. 이어지는 ISB가 새 벡터 설정을 반영합니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 721행
```asm
	isb
```
뒤 명령을 변경된 CPU 제어 상태에 맞춰 실행하도록 동기화합니다. 주소 변환이나 벡터 설정을 바꾼 뒤 예전 실행 상태가 계속 쓰이지 않게 합니다.

### 722행
```asm
	.else
```
앞 어셈블 조건의 반대 경우에 생성할 코드입니다. 

### 723행
```asm
	adr_l	x30, vectors
```
x30에 vectors의 위치를 계산합니다. 그 위치의 데이터는 아직 읽지 않습니다. 이후의 메모리 접근이나 분기가 사용할 주소를 준비하는 줄입니다.

### 724행
```asm
	.endif // \kpti == 1
```
어셈블 조건에 따라 생성할 코드를 나누던 범위가 끝납니다. 

### 726행
```asm
	.if	\bhb == BHB_MITIGATION_FW
```
어셈블 단계에서 인자나 식을 검사해 필요한 명령만 만듭니다. \bhb == BHB_MITIGATION_FW

### 732행
```asm
	__mitigate_spectre_bhb_fw
```
이 CPU가 사용하는 펌웨어 BHB 완화를 호출합니다. LOOP 또는 CLEARBHB를 선택한 별도 벡터에는 이 호출이 생성되지 않습니다. 요청에 사용하는 x0~x3는 스택에 보존했다가 펌웨어에서 돌아온 뒤 복원합니다.

### 733행
```asm
	.endif // \bhb == BHB_MITIGATION_FW
```
어셈블 조건에 따라 생성할 코드를 나누던 범위가 끝납니다. 

### 735행
```asm
	add	x30, x30, #(1b - \vector_start + 4)
```
현재 trampoline 슬롯과 같은 종류의 정상 벡터 위치를 계산하되 첫 분기 4바이트는 건너뜁니다. 그래야 정상 kernel_ventry의 trampoline 복구 부분으로 들어갑니다. 이 ADD 형식은 NZCV 조건 플래그를 바꾸지 않습니다.
대응 슬롯의 첫 B를 건너뛰는 +4입니다. kernel_ventry의 trampoline 정리 부분으로 들어갑니다.

### 736행
```asm
	ret
```
LR(x30)에 적힌 주소로 실행을 돌립니다. 그 주소가 어떻게 준비됐는지가 중요합니다. 정상 함수의 호출자일 수도 있고, 절전 복귀나 trampoline에서 별도로 기록한 재개 지점일 수도 있습니다.
여기서 RET는 예외 복귀 ERET가 아니라 x30에 계산한 kernel vector 내부 주소로 이동합니다.

### 737행
```asm
.org 1b + 128	// Did we overflow the ventry slot?
```
현재 출력 위치를 정해 둔 슬롯 끝까지 맞춥니다. 너무 많은 코드가 들어가 이미 지나쳤다면 빌드에서 드러납니다. 1b + 128

### 738행
```asm
	.endm
```
공통 작업의 정의를 여기서 마칩니다. RET가 아니라 빌드 도구가 읽는 경계입니다. 

### 740행
```asm
	.macro	generate_tramp_vector,	kpti, bhb
```
KPTI 사용 여부와 BHB 완화 방식 한 조합의 벡터 표를 생성합니다. 사용하지 않는 EL1 슬롯 0x400바이트를 건너뛰고 AArch64·AArch32 EL0 예외 입구를 네 개씩 만듭니다. 이 줄은 어셈블러 매크로 정의의 시작입니다. 본문은 매크로를 사용하는 위치에 펼쳐지며, 정의 자체가 런타임 함수 호출은 아닙니다.

### 741행
```asm
.Lvector_start\@:
```
여기에 .Lvector_start\@라는 위치 이름을 붙입니다. 다른 줄의 분기나 주소 계산이 이 위치를 찾아올 수 있습니다. 이름 자체가 CPU 작업을 추가하지는 않습니다.

### 742행
```asm
	.space	0x400
```
원하는 크기의 빈 공간을 배치합니다. 뒤 코드의 상대 위치를 조정하는 빌드 작업입니다. 0x400

### 744행
```asm
	.rept	4
```
지정 횟수만큼 코드를 생성합니다. 실행 중 반복문을 도는 것과는 다릅니다. 4

### 745행
```asm
	tramp_ventry	.Lvector_start\@, 64, \kpti, \bhb
```
AArch64 사용자 예외의 128바이트 진입 슬롯을 만듭니다. kpti 인수에 따라 최소 매핑에서 정상 매핑으로 전환하고 \bhb가 지정한 분기 이력 완화를 적용한 뒤 대응하는 정상 vectors 슬롯의 첫 분기 다음(+4)으로 연결합니다.

### 746행
```asm
	.endr
```
반복 생성할 범위의 끝입니다. 

### 747행
```asm
	.rept	4
```
지정 횟수만큼 코드를 생성합니다. 실행 중 반복문을 도는 것과는 다릅니다. 4

### 748행
```asm
	tramp_ventry	.Lvector_start\@, 32, \kpti, \bhb
```
AArch32 사용자 예외의 128바이트 진입 슬롯을 만듭니다. kpti 인수에 따라 최소 매핑에서 정상 매핑으로 전환하고 \bhb가 지정한 분기 이력 완화를 적용한 뒤 대응하는 정상 vectors 슬롯의 첫 분기 다음(+4)으로 연결합니다.

### 749행
```asm
	.endr
```
반복 생성할 범위의 끝입니다. 

### 750행
```asm
	.endm
```
공통 작업의 정의를 여기서 마칩니다. RET가 아니라 빌드 도구가 읽는 경계입니다. 

## trampoline 테이블과 EL1 완화 벡터
원본 751–813행

### 752행
```asm
#ifdef CONFIG_UNMAP_KERNEL_AT_EL0
```
KPTI를 지원하는 빌드에서만 커널 전체 매핑이 없어도 접근할 trampoline 벡터와 복귀 코드를 전용 섹션에 둡니다. 설정이 꺼지면 이 최소 매핑 경로는 없습니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.

### 758행
```asm
	.pushsection ".entry.tramp.text", "ax"
```
이후 출력할 바이트를 어느 섹션에 넣을지 바꿉니다. 빌드 도구가 코드를 분류하는 작업이며 현재 CPU의 PC나 SP를 옮기는 줄은 아닙니다. ".entry.tramp.text", "ax"

### 759행
```asm
	.align	11
```
다음 코드나 데이터가 정해진 경계에서 시작하도록 빈 공간을 채웁니다. 이 .align 인자는 바이트 수가 아니라 2의 지수입니다. 값은 11입니다.

### 760행
```asm
SYM_CODE_START_LOCAL_NOALIGN(tramp_vectors)
```
이 위치부터 (tramp_vectors)라는 코드가 시작한다고 도구에 알려 줍니다. 정렬과 외부 공개 여부도 정의에 포함됩니다. 실제 작업은 아래의 명령들이 수행합니다.

### 761행
```asm
#ifdef CONFIG_MITIGATE_SPECTRE_BRANCH_HISTORY
```
Spectre 분기 이력 완화 지원 빌드에는 LOOP·펌웨어·명령 방식의 KPTI 벡터 후보를 모두 생성합니다. 실제로 매번 모두 실행하는 것이 아니라 CPU·정책에 맞는 후보를 선택할 수 있게 합니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.

### 762행
```asm
	generate_tramp_vector	kpti=1, bhb=BHB_MITIGATION_LOOP
```
분기 반복 LOOP 방식의 벡터 표 후보를 생성합니다. KPTI를 사용하는 진입이라 정상 커널 매핑 복구도 포함합니다. 실행 중 모든 후보를 순서대로 거치는 것이 아니라 CPU와 정책에 맞는 벡터가 선택됩니다.

### 763행
```asm
	generate_tramp_vector	kpti=1, bhb=BHB_MITIGATION_FW
```
펌웨어 호출 FW 방식의 벡터 표 후보를 생성합니다. KPTI를 사용하는 진입이라 정상 커널 매핑 복구도 포함합니다. 실행 중 모든 후보를 순서대로 거치는 것이 아니라 CPU와 정책에 맞는 벡터가 선택됩니다.

### 764행
```asm
	generate_tramp_vector	kpti=1, bhb=BHB_MITIGATION_INSN
```
CLEARBHB 명령 INSN 방식의 벡터 표 후보를 생성합니다. KPTI를 사용하는 진입이라 정상 커널 매핑 복구도 포함합니다. 실행 중 모든 후보를 순서대로 거치는 것이 아니라 CPU와 정책에 맞는 벡터가 선택됩니다.

### 765행
```asm
#endif /* CONFIG_MITIGATE_SPECTRE_BRANCH_HISTORY */
```
빌드 조건으로 나누었던 구간을 닫습니다. 대상: 

### 766행
```asm
	generate_tramp_vector	kpti=1, bhb=BHB_MITIGATION_NONE
```
추가 BHB 완화가 없는 NONE 방식의 벡터 표 후보를 생성합니다. KPTI를 사용하는 진입이라 정상 커널 매핑 복구도 포함합니다. 실행 중 모든 후보를 순서대로 거치는 것이 아니라 CPU와 정책에 맞는 벡터가 선택됩니다.

### 767행
```asm
SYM_CODE_END(tramp_vectors)
```
도구에 이 코드 범위의 끝을 알려 크기와 심볼 정보를 기록합니다. CPU가 이 줄 때문에 자동으로 반환하지는 않습니다.

### 769행
```asm
SYM_CODE_START_LOCAL(tramp_exit)
```
이 위치부터 (tramp_exit)라는 코드가 시작한다고 도구에 알려 줍니다. 정렬과 외부 공개 여부도 정의에 포함됩니다. 실제 작업은 아래의 명령들이 수행합니다.

### 770행
```asm
	tramp_unmap_kernel	x29
```
사용자 실행으로 돌아가기 전에 커널의 넓은 매핑을 다시 감춥니다. 이 이후에도 실행할 최소 trampoline 코드는 남아 있어야 합니다. 여기의 인자는 x29입니다.

### 771행
```asm
	mrs		x29, far_el1		// restore x29
```
kernel_exit가 FAR_EL1에 임시 보관한 사용자 x29를 되찾습니다. 여기서 FAR는 fault 주소가 아니며, 매핑 제거에 쓴 x29를 원래 사용자 값으로 돌린 뒤 ERET합니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 772행
```asm
	eret
```
예외가 끝났으므로 저장해 둔 PC와 실행 상태를 함께 되살립니다. 일반 RET와 달리 어느 예외 레벨과 스택 모드로 돌아갈지도 SPSR의 내용에 따라 정해집니다.

### 773행
```asm
	sb
```
trampoline ERET 다음에 추측 실행 방지 배리어를 둡니다. 정상 실행은 ERET로 사용자 공간에 갔으며, CPU의 잘못된 직선 추측이 뒤 코드를 진행하지 못하게 합니다.

### 774행
```asm
SYM_CODE_END(tramp_exit)
```
도구에 이 코드 범위의 끝을 알려 크기와 심볼 정보를 기록합니다. CPU가 이 줄 때문에 자동으로 반환하지는 않습니다.

### 775행
```asm
	.popsection				// .entry.tramp.text
```
이후 출력할 바이트를 어느 섹션에 넣을지 바꿉니다. 빌드 도구가 코드를 분류하는 작업이며 현재 CPU의 PC나 SP를 옮기는 줄은 아닙니다. 

### 776행
```asm
#endif /* CONFIG_UNMAP_KERNEL_AT_EL0 */
```
빌드 조건으로 나누었던 구간을 닫습니다. 대상: 

### 782행
```asm
	.macro generate_el1_vector, bhb
```
KPTI 없이도 사용자 예외에 BHB 완화를 적용할 벡터 표를 만드는 매크로입니다. EL1 예외 입구는 정상 kernel_ventry로 만들고 EL0 입구는 지정된 bhb 완화를 포함합니다. 이 줄은 어셈블러 매크로 정의의 시작입니다. 본문은 매크로를 사용하는 위치에 펼쳐지며, 정의 자체가 런타임 함수 호출은 아닙니다.

### 783행
```asm
.Lvector_start\@:
```
여기에 .Lvector_start\@라는 위치 이름을 붙입니다. 다른 줄의 분기나 주소 계산이 이 위치를 찾아올 수 있습니다. 이름 자체가 CPU 작업을 추가하지는 않습니다.

### 784행
```asm
	kernel_ventry	1, t, 64, sync		// Synchronous EL1t
```
EL1t 커널 실행(SP_EL0 선택) 중 발생한 동기 예외(SVC·fault·debug 등)의 128바이트 벡터 입구를 생성합니다. CPU는 벡터 표의 고정 위치를 고르며 이 입구는 pt_regs 공간·스택 검사 후 같은 종류의 실제 handler로 이어집니다. 이 줄에서 동기 예외(SVC·fault·debug 등)을 일부러 발생시키는 것은 아닙니다.

### 785행
```asm
	kernel_ventry	1, t, 64, irq		// IRQ EL1t
```
EL1t 커널 실행(SP_EL0 선택) 중 발생한 IRQ의 128바이트 벡터 입구를 생성합니다. CPU는 벡터 표의 고정 위치를 고르며 이 입구는 pt_regs 공간·스택 검사 후 같은 종류의 실제 handler로 이어집니다. 이 줄에서 IRQ을 일부러 발생시키는 것은 아닙니다.

### 786행
```asm
	kernel_ventry	1, t, 64, fiq		// FIQ EL1h
```
EL1t 커널 실행(SP_EL0 선택) 중 발생한 FIQ의 128바이트 벡터 입구를 생성합니다. CPU는 벡터 표의 고정 위치를 고르며 이 입구는 pt_regs 공간·스택 검사 후 같은 종류의 실제 handler로 이어집니다. 이 줄에서 FIQ을 일부러 발생시키는 것은 아닙니다.

### 787행
```asm
	kernel_ventry	1, t, 64, error		// Error EL1t
```
EL1t 커널 실행(SP_EL0 선택) 중 발생한 SError의 128바이트 벡터 입구를 생성합니다. CPU는 벡터 표의 고정 위치를 고르며 이 입구는 pt_regs 공간·스택 검사 후 같은 종류의 실제 handler로 이어집니다. 이 줄에서 SError을 일부러 발생시키는 것은 아닙니다.

### 789행
```asm
	kernel_ventry	1, h, 64, sync		// Synchronous EL1h
```
EL1h 커널 실행(SP_EL1 선택) 중 발생한 동기 예외(SVC·fault·debug 등)의 128바이트 벡터 입구를 생성합니다. CPU는 벡터 표의 고정 위치를 고르며 이 입구는 pt_regs 공간·스택 검사 후 같은 종류의 실제 handler로 이어집니다. 이 줄에서 동기 예외(SVC·fault·debug 등)을 일부러 발생시키는 것은 아닙니다.

### 790행
```asm
	kernel_ventry	1, h, 64, irq		// IRQ EL1h
```
EL1h 커널 실행(SP_EL1 선택) 중 발생한 IRQ의 128바이트 벡터 입구를 생성합니다. CPU는 벡터 표의 고정 위치를 고르며 이 입구는 pt_regs 공간·스택 검사 후 같은 종류의 실제 handler로 이어집니다. 이 줄에서 IRQ을 일부러 발생시키는 것은 아닙니다.

### 791행
```asm
	kernel_ventry	1, h, 64, fiq		// FIQ EL1h
```
EL1h 커널 실행(SP_EL1 선택) 중 발생한 FIQ의 128바이트 벡터 입구를 생성합니다. CPU는 벡터 표의 고정 위치를 고르며 이 입구는 pt_regs 공간·스택 검사 후 같은 종류의 실제 handler로 이어집니다. 이 줄에서 FIQ을 일부러 발생시키는 것은 아닙니다.

### 792행
```asm
	kernel_ventry	1, h, 64, error		// Error EL1h
```
EL1h 커널 실행(SP_EL1 선택) 중 발생한 SError의 128바이트 벡터 입구를 생성합니다. CPU는 벡터 표의 고정 위치를 고르며 이 입구는 pt_regs 공간·스택 검사 후 같은 종류의 실제 handler로 이어집니다. 이 줄에서 SError을 일부러 발생시키는 것은 아닙니다.

### 794행
```asm
	.rept	4
```
지정 횟수만큼 코드를 생성합니다. 실행 중 반복문을 도는 것과는 다릅니다. 4

### 795행
```asm
	tramp_ventry	.Lvector_start\@, 64, 0, \bhb
```
AArch64 사용자 예외의 128바이트 진입 슬롯을 만듭니다. KPTI 매핑 전환은 하지 않고 \bhb가 지정한 분기 이력 완화를 적용한 뒤 대응하는 정상 vectors 슬롯의 첫 분기 다음(+4)으로 연결합니다.

### 796행
```asm
	.endr
```
반복 생성할 범위의 끝입니다. 

### 797행
```asm
	.rept 4
```
지정 횟수만큼 코드를 생성합니다. 실행 중 반복문을 도는 것과는 다릅니다. 4

### 798행
```asm
	tramp_ventry	.Lvector_start\@, 32, 0, \bhb
```
AArch32 사용자 예외의 128바이트 진입 슬롯을 만듭니다. KPTI 매핑 전환은 하지 않고 \bhb가 지정한 분기 이력 완화를 적용한 뒤 대응하는 정상 vectors 슬롯의 첫 분기 다음(+4)으로 연결합니다.

### 799행
```asm
	.endr
```
반복 생성할 범위의 끝입니다. 

### 800행
```asm
	.endm
```
공통 작업의 정의를 여기서 마칩니다. RET가 아니라 빌드 도구가 읽는 경계입니다. 

### 803행
```asm
	.pushsection ".entry.text", "ax"
```
이후 출력할 바이트를 어느 섹션에 넣을지 바꿉니다. 빌드 도구가 코드를 분류하는 작업이며 현재 CPU의 PC나 SP를 옮기는 줄은 아닙니다. ".entry.text", "ax"

### 804행
```asm
	.align	11
```
다음 코드나 데이터가 정해진 경계에서 시작하도록 빈 공간을 채웁니다. 이 .align 인자는 바이트 수가 아니라 2의 지수입니다. 값은 11입니다.

### 805행
```asm
SYM_CODE_START(__bp_harden_el1_vectors)
```
이 위치부터 (__bp_harden_el1_vectors)라는 코드가 시작한다고 도구에 알려 줍니다. 정렬과 외부 공개 여부도 정의에 포함됩니다. 실제 작업은 아래의 명령들이 수행합니다.

### 806행
```asm
#ifdef CONFIG_MITIGATE_SPECTRE_BRANCH_HISTORY
```
BHB 완화를 지원하는 빌드에만 KPTI 없는 경우의 LOOP·펌웨어·명령 벡터 후보를 생성합니다. 커널 매핑을 감추는 기능과 분기 이력 완화는 별개이므로 이 표가 따로 필요합니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.

### 807행
```asm
	generate_el1_vector	bhb=BHB_MITIGATION_LOOP
```
분기 반복 LOOP 방식의 벡터 표 후보를 생성합니다. KPTI 없이 커널 매핑을 유지하는 경우의 진입입니다. 실행 중 모든 후보를 순서대로 거치는 것이 아니라 CPU와 정책에 맞는 벡터가 선택됩니다.

### 808행
```asm
	generate_el1_vector	bhb=BHB_MITIGATION_FW
```
펌웨어 호출 FW 방식의 벡터 표 후보를 생성합니다. KPTI 없이 커널 매핑을 유지하는 경우의 진입입니다. 실행 중 모든 후보를 순서대로 거치는 것이 아니라 CPU와 정책에 맞는 벡터가 선택됩니다.

### 809행
```asm
	generate_el1_vector	bhb=BHB_MITIGATION_INSN
```
CLEARBHB 명령 INSN 방식의 벡터 표 후보를 생성합니다. KPTI 없이 커널 매핑을 유지하는 경우의 진입입니다. 실행 중 모든 후보를 순서대로 거치는 것이 아니라 CPU와 정책에 맞는 벡터가 선택됩니다.

### 810행
```asm
#endif /* CONFIG_MITIGATE_SPECTRE_BRANCH_HISTORY */
```
빌드 조건으로 나누었던 구간을 닫습니다. 대상: 

### 811행
```asm
SYM_CODE_END(__bp_harden_el1_vectors)
```
도구에 이 코드 범위의 끝을 알려 크기와 심볼 정보를 기록합니다. CPU가 이 줄 때문에 자동으로 반환하지는 않습니다.

### 812행
```asm
	.popsection
```
이후 출력할 바이트를 어느 섹션에 넣을지 바꿉니다. 빌드 도구가 코드를 분류하는 작업이며 현재 CPU의 PC나 SP를 옮기는 줄은 아닙니다. 

## cpu_switch_to: 태스크 상태 교체
원본 814–851행

### 823행
```asm
SYM_FUNC_START(cpu_switch_to)
```
이 위치부터 (cpu_switch_to)라는 코드가 시작한다고 도구에 알려 줍니다. 정렬과 외부 공개 여부도 정의에 포함됩니다. ARM64 함수 시작 매크로는 간접 호출의 도착점을 표시하는 BTI도 추가합니다.

### 824행
```asm
	save_and_disable_daif x11
```
현재 예외 마스크를 기억해 둔 뒤 상태 교체 도중 예외가 끼어들지 않도록 마스크합니다. 작업이 끝나면 원래 값을 복구할 수 있습니다. 여기의 인자는 x11입니다.
SP/current가 교체되는 동안 예외 관찰 상태가 섞이지 않도록 DAIF를 보존하고 마스크합니다.

### 825행
```asm
	mov	x10, #THREAD_CPU_CONTEXT
```
x10에 THREAD_CPU_CONTEXT라는 값을 넣습니다. 원래 x10의 내용은 덮어쓰며, 다른 레지스터나 그 주소의 메모리까지 복사하지는 않습니다.

### 826행
```asm
	add	x8, x0, x10
```
x0를 기준으로 x10을 더해 x8에 결과를 둡니다. 주소 계산에 쓰이는 경우에는 구조체의 필드나 다음 저장 위치를 찾는 동작입니다. 이 형태는 조건 플래그를 바꾸지 않습니다.

### 827행
```asm
	mov	x9, sp
```
현재 SP의 숫자를 x9에 기억해 둡니다. 스택 자체는 이동하거나 복사되지 않습니다. 나중에 이 값으로 원래 위치를 찾을 수 있습니다.

### 828행
```asm
	stp	x19, x20, [x8], #16		// store callee-saved registers
```
x19, x20의 값을 x8가 가리키는 메모리 위치에 기록합니다. 레지스터 값을 나중에 다시 쓰거나 다른 코드가 읽을 수 있도록 메모리에 남기는 줄입니다. 첫 위치에는 x19, 그보다 8바이트 높은 위치에는 x20의 값이 들어갑니다. 이번 저장을 마친 뒤 기준 포인터를 다음 위치로 옮깁니다.

### 829행
```asm
	stp	x21, x22, [x8], #16
```
x21, x22의 값을 x8가 가리키는 메모리 위치에 기록합니다. 레지스터 값을 나중에 다시 쓰거나 다른 코드가 읽을 수 있도록 메모리에 남기는 줄입니다. 첫 위치에는 x21, 그보다 8바이트 높은 위치에는 x22의 값이 들어갑니다. 이번 저장을 마친 뒤 기준 포인터를 다음 위치로 옮깁니다.

### 830행
```asm
	stp	x23, x24, [x8], #16
```
x23, x24의 값을 x8가 가리키는 메모리 위치에 기록합니다. 레지스터 값을 나중에 다시 쓰거나 다른 코드가 읽을 수 있도록 메모리에 남기는 줄입니다. 첫 위치에는 x23, 그보다 8바이트 높은 위치에는 x24의 값이 들어갑니다. 이번 저장을 마친 뒤 기준 포인터를 다음 위치로 옮깁니다.

### 831행
```asm
	stp	x25, x26, [x8], #16
```
x25, x26의 값을 x8가 가리키는 메모리 위치에 기록합니다. 레지스터 값을 나중에 다시 쓰거나 다른 코드가 읽을 수 있도록 메모리에 남기는 줄입니다. 첫 위치에는 x25, 그보다 8바이트 높은 위치에는 x26의 값이 들어갑니다. 이번 저장을 마친 뒤 기준 포인터를 다음 위치로 옮깁니다.

### 832행
```asm
	stp	x27, x28, [x8], #16
```
x27, x28의 값을 x8가 가리키는 메모리 위치에 기록합니다. 레지스터 값을 나중에 다시 쓰거나 다른 코드가 읽을 수 있도록 메모리에 남기는 줄입니다. 첫 위치에는 x27, 그보다 8바이트 높은 위치에는 x28의 값이 들어갑니다. 이번 저장을 마친 뒤 기준 포인터를 다음 위치로 옮깁니다.

### 833행
```asm
	stp	x29, x9, [x8], #16
```
x29, x9의 값을 x8가 가리키는 메모리 위치에 기록합니다. 레지스터 값을 나중에 다시 쓰거나 다른 코드가 읽을 수 있도록 메모리에 남기는 줄입니다. 첫 위치에는 x29, 그보다 8바이트 높은 위치에는 x9의 값이 들어갑니다. 이번 저장을 마친 뒤 기준 포인터를 다음 위치로 옮깁니다.
이전 태스크의 FP와 SP를 연속해서 저장합니다.

### 834행
```asm
	str	lr, [x8]
```
lr의 값을 x8가 가리키는 메모리 위치에 기록합니다. 레지스터 값을 나중에 다시 쓰거나 다른 코드가 읽을 수 있도록 메모리에 남기는 줄입니다.

### 835행
```asm
	add	x8, x1, x10
```
x1를 기준으로 x10을 더해 x8에 결과를 둡니다. 주소 계산에 쓰이는 경우에는 구조체의 필드나 다음 저장 위치를 찾는 동작입니다. 이 형태는 조건 플래그를 바꾸지 않습니다.

### 836행
```asm
	ldp	x19, x20, [x8], #16		// restore callee-saved registers
```
x8가 가리키는 메모리 위치에 보관된 값을 x19, x20로 가져옵니다. 주소 숫자를 옮기는 MOV와 달리 실제 메모리를 읽습니다. 첫 위치에서 x19, 그보다 8바이트 높은 위치에서 x20의 값을 읽습니다. 읽은 뒤 기준 포인터를 옮깁니다. SP가 증가하는 경우는 사용한 스택 공간을 해제하는 단계입니다.

### 837행
```asm
	ldp	x21, x22, [x8], #16
```
x8가 가리키는 메모리 위치에 보관된 값을 x21, x22로 가져옵니다. 주소 숫자를 옮기는 MOV와 달리 실제 메모리를 읽습니다. 첫 위치에서 x21, 그보다 8바이트 높은 위치에서 x22의 값을 읽습니다. 읽은 뒤 기준 포인터를 옮깁니다. SP가 증가하는 경우는 사용한 스택 공간을 해제하는 단계입니다.

### 838행
```asm
	ldp	x23, x24, [x8], #16
```
x8가 가리키는 메모리 위치에 보관된 값을 x23, x24로 가져옵니다. 주소 숫자를 옮기는 MOV와 달리 실제 메모리를 읽습니다. 첫 위치에서 x23, 그보다 8바이트 높은 위치에서 x24의 값을 읽습니다. 읽은 뒤 기준 포인터를 옮깁니다. SP가 증가하는 경우는 사용한 스택 공간을 해제하는 단계입니다.

### 839행
```asm
	ldp	x25, x26, [x8], #16
```
x8가 가리키는 메모리 위치에 보관된 값을 x25, x26로 가져옵니다. 주소 숫자를 옮기는 MOV와 달리 실제 메모리를 읽습니다. 첫 위치에서 x25, 그보다 8바이트 높은 위치에서 x26의 값을 읽습니다. 읽은 뒤 기준 포인터를 옮깁니다. SP가 증가하는 경우는 사용한 스택 공간을 해제하는 단계입니다.

### 840행
```asm
	ldp	x27, x28, [x8], #16
```
x8가 가리키는 메모리 위치에 보관된 값을 x27, x28로 가져옵니다. 주소 숫자를 옮기는 MOV와 달리 실제 메모리를 읽습니다. 첫 위치에서 x27, 그보다 8바이트 높은 위치에서 x28의 값을 읽습니다. 읽은 뒤 기준 포인터를 옮깁니다. SP가 증가하는 경우는 사용한 스택 공간을 해제하는 단계입니다.

### 841행
```asm
	ldp	x29, x9, [x8], #16
```
x8가 가리키는 메모리 위치에 보관된 값을 x29, x9로 가져옵니다. 주소 숫자를 옮기는 MOV와 달리 실제 메모리를 읽습니다. 첫 위치에서 x29, 그보다 8바이트 높은 위치에서 x9의 값을 읽습니다. 읽은 뒤 기준 포인터를 옮깁니다. SP가 증가하는 경우는 사용한 스택 공간을 해제하는 단계입니다.

### 842행
```asm
	ldr	lr, [x8]
```
x8가 가리키는 메모리 위치에 보관된 값을 lr로 가져옵니다. 주소 숫자를 옮기는 MOV와 달리 실제 메모리를 읽습니다.

### 843행
```asm
	mov	sp, x9
```
이제 스택으로 사용할 위치를 x9의 값으로 바꿉니다. 메모리 내용을 옮기는 작업은 아닙니다. 다음 저장·호출부터 새 스택 위치를 사용합니다.
이 시점부터 다음 태스크의 커널 스택을 사용합니다.

### 844행
```asm
	msr	sp_el0, x1
```
SP_EL0에 다음 task_struct를 기록하여 커널의 current 조회를 새 task로 바꿉니다. 실제 스택은 앞의 MOV SP로 이미 바뀌었으며 SP_EL0를 그 스택 주소로 쓰는 문맥이 아닙니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.
current를 다음 task_struct로 바꿉니다.

### 845행
```asm
	ptrauth_keys_install_kernel x1, x8, x9, x10
```
x1이 가리키는 다음 task의 커널 instruction-address 인증 키를 설치합니다. x8~x10은 임시 레지스터입니다. 태스크마다 다른 PAC 키를 쓰므로 SP/current를 바꾼 전환에서 이전 task의 키를 그대로 유지하면 안 됩니다.

### 846행
```asm
	scs_save x0
```
일반 스택과 별도로 보호하는 반환 주소 스택의 현재 위치를 태스크에 기억해 둡니다. 여기의 인자는 x0입니다.

### 847행
```asm
	scs_load_current
```
태스크에 저장해 둔 반환 주소 보호용 스택 위치를 되살립니다. 일반 SP를 읽는 것과는 다른 상태입니다. 여기의 인자는 없음입니다.

### 848행
```asm
	restore_irq x11
```
상태 전환 전에 기록해 둔 예외 마스크를 돌려놓습니다. 무조건 인터럽트를 켜는 것이 아니라 원래 상태를 복원합니다. 여기의 인자는 x11입니다.

### 849행
```asm
	ret
```
LR(x30)에 적힌 주소로 실행을 돌립니다. 그 주소가 어떻게 준비됐는지가 중요합니다. 정상 함수의 호출자일 수도 있고, 절전 복귀나 trampoline에서 별도로 기록한 재개 지점일 수도 있습니다.
다음 태스크에 저장된 LR로 실행을 이어갑니다. x0는 이전 태스크 포인터를 유지합니다.

### 850행
```asm
SYM_FUNC_END(cpu_switch_to)
```
도구에 이 코드 범위의 끝을 알려 크기와 심볼 정보를 기록합니다. CPU가 이 줄 때문에 자동으로 반환하지는 않습니다.

### 851행
```asm
NOKPROBE(cpu_switch_to)
```
cpu_switch_to을 kprobe 설치 금지 목록에 넣습니다. 스택·레지스터·주소 공간을 바꾸는 예외/전환 경로 중간에 probe 예외를 추가하지 못하게 보호합니다. CONFIG_KPROBES가 꺼지면 이 목록 항목도 필요 없습니다. 빌드할 때 심벌을 가리키는 메타데이터를 만드는 표기이며, 이 위치에서 함수를 호출하는 명령은 아닙니다.

## fork 뒤의 첫 실행
원본 852–866행

### 856행
```asm
SYM_CODE_START(ret_from_fork)
```
이 위치부터 (ret_from_fork)라는 코드가 시작한다고 도구에 알려 줍니다. 정렬과 외부 공개 여부도 정의에 포함됩니다. 실제 작업은 아래의 명령들이 수행합니다.

### 857행
```asm
	bl	schedule_tail
```
새 task가 처음 실행되는 경로에서 schedule_tail을 호출하여 이전 task와의 스케줄 전환 마무리와 첫 실행 bookkeeping을 완료합니다. 일반 task가 이미 한 번 실행한 뒤의 cpu_switch_to 복귀와 구분합니다. BL은 다음 명령의 주소를 LR(x30)에 기록하고 목적지로 분기합니다. 호출된 코드가 x0 같은 작업 레지스터를 바꿀 수 있으므로, 호출 뒤의 값은 그 코드의 반환·보존 규칙에 따릅니다.

### 858행
```asm
	cbz	x19, 1f				// not a kernel thread
```
x19의 값가 0이면 1f (L861)로 갑니다. 조건을 만족하지 않으면 바로 다음 명령을 읽습니다. 이 분기는 CMP의 결과가 아니라 지정 레지스터를 직접 검사합니다.

### 859행
```asm
	mov	x0, x20
```
x0에 x20에 들어 있는 값을 넣습니다. 원래 x0의 내용은 덮어쓰며, 다른 레지스터나 그 주소의 메모리까지 복사하지는 않습니다.

### 860행
```asm
	blr	x19
```
커널 스레드라면 x19의 시작 함수를 x0=x20 인수로 호출합니다. x19가 0인 사용자 task는 앞의 CBZ로 이 호출을 건너뛰고 사용자 복귀 준비를 합니다. BLR은 다음 명령의 주소를 LR(x30)에 기록하고 레지스터에 든 목적지로 분기합니다. 호출된 코드가 x0 같은 작업 레지스터를 바꿀 수 있으므로, 호출 뒤의 값은 그 코드의 반환·보존 규칙에 따릅니다.

### 861행
```asm
1:	get_current_task tsk
```
지금 이 CPU가 실행 중인 태스크의 주소를 가져옵니다. 단순한 정수 레지스터 값에 운영체제의 current라는 의미를 부여하는 부분입니다. 여기의 인자는 tsk입니다.

### 862행
```asm
	mov	x0, sp
```
현재 SP의 숫자를 x0에 기억해 둡니다. 스택 자체는 이동하거나 복사되지 않습니다. 나중에 이 값으로 원래 위치를 찾을 수 있습니다.

### 863행
```asm
	bl	asm_exit_to_user_mode
```
x0=현재 pt_regs를 전달해 처음 사용자 실행으로 나가기 전 필요한 exit-to-user 작업을 처리합니다. 그 다음 ret_to_user가 실제 레지스터와 예외 상태를 복원합니다. BL은 다음 명령의 주소를 LR(x30)에 기록하고 목적지로 분기합니다.

### 864행
```asm
	b	ret_to_user
```
ret_to_user부터 실행을 계속합니다. 돌아올 주소를 새로 적지 않으므로 일반 함수 호출과는 다른 제어 이동입니다.

### 865행
```asm
SYM_CODE_END(ret_from_fork)
```
도구에 이 코드 범위의 끝을 알려 크기와 심볼 정보를 기록합니다. CPU가 이 줄 때문에 자동으로 반환하지는 않습니다.

### 866행
```asm
NOKPROBE(ret_from_fork)
```
ret_from_fork을 kprobe 설치 금지 목록에 넣습니다. 스택·레지스터·주소 공간을 바꾸는 예외/전환 경로 중간에 probe 예외를 추가하지 못하게 보호합니다. CONFIG_KPROBES가 꺼지면 이 목록 항목도 필요 없습니다. 빌드할 때 심벌을 가리키는 메타데이터를 만드는 표기이며, 이 위치에서 함수를 호출하는 명령은 아닙니다.

## IRQ 스택에서 함수 호출
원본 867–905행

### 874행
```asm
SYM_FUNC_START(call_on_irq_stack)
```
이 위치부터 (call_on_irq_stack)라는 코드가 시작한다고 도구에 알려 줍니다. 정렬과 외부 공개 여부도 정의에 포함됩니다. ARM64 함수 시작 매크로는 간접 호출의 도착점을 표시하는 BTI도 추가합니다.

### 875행
```asm
	save_and_disable_daif x9
```
현재 예외 마스크를 기억해 둔 뒤 상태 교체 도중 예외가 끼어들지 않도록 마스크합니다. 작업이 끝나면 원래 값을 복구할 수 있습니다. 여기의 인자는 x9입니다.

### 876행
```asm
#ifdef CONFIG_SHADOW_CALL_STACK
```
Shadow Call Stack은 일반 스택과 별도 영역에 복귀 주소를 보관하는 보호 기법입니다. 켜진 빌드에서는 task의 SCS 포인터를 저장하고 IRQ 전용 SCS를 사용하며, 꺼지면 일반 IRQ 스택 전환만 수행합니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.

### 877행
```asm
	get_current_task x16
```
지금 이 CPU가 실행 중인 태스크의 주소를 가져옵니다. 단순한 정수 레지스터 값에 운영체제의 current라는 의미를 부여하는 부분입니다. 여기의 인자는 x16입니다.

### 878행
```asm
	scs_save x16
```
일반 스택과 별도로 보호하는 반환 주소 스택의 현재 위치를 태스크에 기억해 둡니다. 여기의 인자는 x16입니다.

### 879행
```asm
	ldr_this_cpu scs_sp, irq_shadow_call_stack_ptr, x17
```
CPU별 IRQ 전용 shadow call stack 포인터를 읽습니다. IRQ 처리의 복귀 주소가 task의 SCS 공간을 계속 소비하지 않도록 일반 IRQ 스택과 함께 보호용 스택도 바꿉니다. 심벌의 기준 주소에 현재 CPU의 per-CPU 오프셋을 더해 이 CPU의 저장 위치를 찾습니다.

### 880행
```asm
#endif
```
빌드 조건으로 나누었던 구간을 닫습니다. 대상: 

### 883행
```asm
	stp	x29, x30, [sp, #-16]!
```
x29, x30의 값을 sp를 기준으로 -16만큼 계산한 메모리 위치에 기록합니다. 레지스터 값을 나중에 다시 쓰거나 다른 코드가 읽을 수 있도록 메모리에 남기는 줄입니다. 첫 위치에는 x29, 그보다 8바이트 높은 위치에는 x30의 값이 들어갑니다. 이 스택 위치는 현재 함수 또는 예외 처리의 보관 공간입니다. 쓰기 전에 기준 주소를 갱신합니다. SP의 음수 오프셋이면 낮은 주소 쪽에 저장 공간을 확보하는 뜻입니다.
원래 스택에서 만든 16바이트 프레임이 IRQ 스택 사용 후 돌아갈 위치를 보존합니다.

### 884행
```asm
	mov	x29, sp
```
현재 SP의 숫자를 x29에 기억해 둡니다. 스택 자체는 이동하거나 복사되지 않습니다. 나중에 이 값으로 원래 위치를 찾을 수 있습니다.

### 886행
```asm
	ldr_this_cpu x16, irq_stack_ptr, x17
```
현재 CPU의 IRQ 스택 시작을 읽습니다. task 스택에서 IRQ 처리 깊이가 과도하게 늘지 않도록 별도 스택으로 작업을 옮길 준비입니다. 심벌의 기준 주소에 현재 CPU의 per-CPU 오프셋을 더해 이 CPU의 저장 위치를 찾습니다.

### 889행
```asm
	add	sp, x16, #IRQ_STACK_SIZE
```
x16를 기준으로 IRQ_STACK_SIZE을 더해 sp에 결과를 둡니다. 주소 계산에 쓰이는 경우에는 구조체의 필드나 다음 저장 위치를 찾는 동작입니다. 이 형태는 조건 플래그를 바꾸지 않습니다.
IRQ 스택 끝을 SP로 선택합니다. 이후 저장할 때 SP는 낮은 주소로 이동합니다.

### 890행
```asm
	restore_irq x9
```
상태 전환 전에 기록해 둔 예외 마스크를 돌려놓습니다. 무조건 인터럽트를 켜는 것이 아니라 원래 상태를 복원합니다. 여기의 인자는 x9입니다.

### 891행
```asm
	blr	x1
```
x0의 pt_regs를 그대로 인수로 전달해 x1의 IRQ 처리 함수를 호출합니다. SP와 필요 시 SCS는 이미 이 CPU의 IRQ 전용 공간으로 바뀌었고, 반환 뒤 원래 task 스택을 복구합니다. BLR은 다음 명령의 주소를 LR(x30)에 기록하고 레지스터에 든 목적지로 분기합니다. 호출된 코드가 x0 같은 작업 레지스터를 바꿀 수 있으므로, 호출 뒤의 값은 그 코드의 반환·보존 규칙에 따릅니다.
x0=pt_regs를 그대로 전달하고 IRQ 스택에서 x1의 함수를 호출합니다.

### 893행
```asm
	save_and_disable_daif x9
```
현재 예외 마스크를 기억해 둔 뒤 상태 교체 도중 예외가 끼어들지 않도록 마스크합니다. 작업이 끝나면 원래 값을 복구할 수 있습니다. 여기의 인자는 x9입니다.

### 898행
```asm
	mov	sp, x29
```
이제 스택으로 사용할 위치를 x29의 값으로 바꿉니다. 메모리 내용을 옮기는 작업은 아닙니다. 다음 저장·호출부터 새 스택 위치를 사용합니다.

### 899행
```asm
	ldp	x29, x30, [sp], #16
```
sp가 가리키는 메모리 위치에 보관된 값을 x29, x30로 가져옵니다. 주소 숫자를 옮기는 MOV와 달리 실제 메모리를 읽습니다. 첫 위치에서 x29, 그보다 8바이트 높은 위치에서 x30의 값을 읽습니다. 앞서 보관한 상태를 되살리거나 스택으로 전달된 인자를 얻는 과정입니다. 읽은 뒤 기준 포인터를 옮깁니다. SP가 증가하는 경우는 사용한 스택 공간을 해제하는 단계입니다.

### 900행
```asm
	scs_load_current
```
태스크에 저장해 둔 반환 주소 보호용 스택 위치를 되살립니다. 일반 SP를 읽는 것과는 다른 상태입니다. 여기의 인자는 없음입니다.

### 901행
```asm
	restore_irq x9
```
상태 전환 전에 기록해 둔 예외 마스크를 돌려놓습니다. 무조건 인터럽트를 켜는 것이 아니라 원래 상태를 복원합니다. 여기의 인자는 x9입니다.

### 902행
```asm
	ret
```
LR(x30)에 적힌 주소로 실행을 돌립니다. 그 주소가 어떻게 준비됐는지가 중요합니다. 정상 함수의 호출자일 수도 있고, 절전 복귀나 trampoline에서 별도로 기록한 재개 지점일 수도 있습니다.

### 903행
```asm
SYM_FUNC_END(call_on_irq_stack)
```
도구에 이 코드 범위의 끝을 알려 크기와 심볼 정보를 기록합니다. CPU가 이 줄 때문에 자동으로 반환하지는 않습니다.

### 904행
```asm
NOKPROBE(call_on_irq_stack)
```
call_on_irq_stack을 kprobe 설치 금지 목록에 넣습니다. 스택·레지스터·주소 공간을 바꾸는 예외/전환 경로 중간에 probe 예외를 추가하지 못하게 보호합니다. CONFIG_KPROBES가 꺼지면 이 목록 항목도 필요 없습니다. 빌드할 때 심벌을 가리키는 메타데이터를 만드는 표기이며, 이 위치에서 함수를 호출하는 명령은 아닙니다.

## SDEI 진입·복귀 trampoline
원본 906–966행

### 906행
```asm
#ifdef CONFIG_ARM_SDE_INTERFACE
```
SDEI는 펌웨어가 운영체제에 소프트웨어 위임 예외 이벤트를 전달하는 인터페이스입니다. 켜진 빌드에만 별도 이벤트 스택·저장 문맥·SMC/HVC 완료 경로를 포함하며, 일반 장치 IRQ의 GIC handler와는 다른 진입 규약입니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.

### 908행
```asm
#include <asm/sdei.h>
```
SDEI 이벤트의 커널 저장 형식·우선순위·스택 관련 정의를 가져옵니다. 일반 IRQ와 다른 펌웨어 이벤트 진입 문맥을 보존하는 데 사용합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 909행
```asm
#include <uapi/linux/arm_sdei.h>
```
펌웨어와 주고받는 SDEI 서비스 번호와 완료 상태 등 인터페이스 상수를 가져옵니다. EVENT_COMPLETE와 COMPLETE_AND_RESUME 같은 요청을 구분하는 데 필요합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 911행
```asm
.macro sdei_handler_exit exit_mode
```
SDEI 이벤트 처리를 펌웨어에 완료 통지하는 매크로입니다. exit_mode가 SMC인지 HVC인지에 따라 통로를 선택하며 x0·x1에 준비한 완료 서비스와 복귀 주소를 전달합니다. 정상 완료하면 원래 중단 문맥으로 돌아가므로 이 호출 위치로 반환하지 않습니다. 이 줄은 어셈블러 매크로 정의의 시작입니다. 본문은 매크로를 사용하는 위치에 펼쳐지며, 정의 자체가 런타임 함수 호출은 아닙니다.

### 913행
```asm
	cmp	\exit_mode, #SDEI_EXIT_SMC
```
펌웨어 SDEI 완료 통로가 SMC인지 확인합니다. 이벤트 번호나 성공 여부를 비교하는 값이 아니라 호출 방식 선택입니다. CMP는 두 피연산자의 뺄셈 결과로 NZCV 조건 플래그만 갱신하고 원래 레지스터 값은 유지합니다.

### 914행
```asm
	b.ne	99f
```
앞서 계산한 조건 플래그가 Z=0이면 99f (L917)로 갑니다. 사이에 플래그를 바꾸지 않는 명령이 있어도 비교 결과는 남아 있습니다.

### 915행
```asm
	smc	#0
```
x0·x1에 준비한 SDEI 완료 또는 complete-and-resume 요청을 SMC로 전달합니다. 성공하면 펌웨어가 중단된 실행을 복원하므로 다음 무한 루프는 비정상 반환을 막는 경로입니다. 일반 함수 호출처럼 BL로 이동하는 대신 동기 예외를 통해 요청을 전달합니다.

### 916행
```asm
	b	.
```
같은 명령 위치에서 반복합니다. 이 경로에서는 더 진행하거나 호출자에게 돌아갈 정상 절차를 마련하지 않았습니다.

### 917행
```asm
99:	hvc	#0
```
SMC가 아닌 구성은 HVC 통로로 SDEI 완료 요청을 보냅니다. 이벤트의 일반 C callback을 호출하는 명령이 아니라 상위 펌웨어/하이퍼바이저 인터페이스입니다. 일반 함수 호출처럼 BL로 이동하는 대신 동기 예외를 통해 요청을 전달합니다.

### 918행
```asm
	b	.
```
같은 명령 위치에서 반복합니다. 이 경로에서는 더 진행하거나 호출자에게 돌아갈 정상 절차를 마련하지 않았습니다.

### 919행
```asm
.endm
```
공통 작업의 정의를 여기서 마칩니다. RET가 아니라 빌드 도구가 읽는 경계입니다. 

### 921행
```asm
#ifdef CONFIG_UNMAP_KERNEL_AT_EL0
```
KPTI가 사용자 실행 중 정상 SDEI 코드까지 매핑에서 제외할 수 있으므로 지원 빌드에는 최소 매핑 안의 SDEI 진입·복귀 trampoline을 포함합니다. 없으면 직접 SDEI handler로 진입할 수 있습니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.

### 930행
```asm
.pushsection ".entry.tramp.text", "ax"
```
이후 출력할 바이트를 어느 섹션에 넣을지 바꿉니다. 빌드 도구가 코드를 분류하는 작업이며 현재 CPU의 PC나 SP를 옮기는 줄은 아닙니다. ".entry.tramp.text", "ax"

### 931행
```asm
SYM_CODE_START(__sdei_asm_entry_trampoline)
```
이 위치부터 (__sdei_asm_entry_trampoline)라는 코드가 시작한다고 도구에 알려 줍니다. 정렬과 외부 공개 여부도 정의에 포함됩니다. 실제 작업은 아래의 명령들이 수행합니다.

### 932행
```asm
	mrs	x4, ttbr1_el1
```
SDEI가 들어왔을 때 TTBR1이 사용자 쪽 최소 매핑인지 확인할 값을 읽습니다. 아래 USER_ASID_BIT가 세워져 있으면 일반 커널 데이터 접근 전 매핑 복구가 필요합니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 933행
```asm
	tbz	x4, #USER_ASID_BIT, 1f
```
USER_ASID_BIT가 없으면 이미 커널 매핑이므로 추가 전환을 건너뜁니다. 있으면 trampoline이 매핑을 복구하고 나중에 되돌릴 필요가 있음을 기록합니다. 이 분기는 지정 레지스터의 값이나 비트를 직접 검사하며 CMP가 남긴 NZCV를 읽거나 바꾸지 않습니다. 조건이 맞지 않으면 바로 다음 명령으로 진행합니다.

### 935행
```asm
	tramp_map_kernel tmp=x4
```
사용자 실행 동안 감춰 둔 커널의 넓은 주소 공간을 다시 연결합니다. 그래야 본래 예외 처리 코드와 데이터를 읽을 수 있습니다. 여기의 인자는 tmp=x4입니다.

### 936행
```asm
	isb
```
뒤 명령을 변경된 CPU 제어 상태에 맞춰 실행하도록 동기화합니다. 주소 변환이나 벡터 설정을 바꾼 뒤 예전 실행 상태가 계속 쓰이지 않게 합니다.

### 937행
```asm
	mov	x4, xzr
```
x4에 0을 넣습니다. 원래 x4의 내용은 덮어쓰며, 다른 레지스터나 그 주소의 메모리까지 복사하지는 않습니다.

### 942행
```asm
1:	str	x4, [x1, #(SDEI_EVENT_INTREGS + S_SDEI_TTBR1)]
```
이벤트 저장 영역에 진입 전 매핑 상태 표식을 남깁니다. 0은 이 trampoline이 커널 매핑을 복구했으므로 복귀 때 다시 감춰야 한다는 뜻이고, 0이 아니면 기존 커널 매핑을 유지합니다. 실제로 x1 + ((SDEI_EVENT_INTREGS + S_SDEI_TTBR1)) 주소의 메모리 8바이트를 x4의 값으로 씁니다. 이 주소 형식은 기준 레지스터 x1 자체를 갱신하지 않습니다.

### 943행
```asm
	tramp_data_read_var     x4, __sdei_asm_handler
```
최소 매핑에서 접근 가능한 방법으로 정상 __sdei_asm_handler 주소를 얻습니다. x1의 이벤트 자료를 읽을 수 있도록 앞에서 커널 매핑을 복구한 뒤 그 handler로 이어집니다. 재배치 구성에서는 별도 rodata의 리터럴을 읽고, 그렇지 않은 구성에서는 MOVZ/MOVK로 주소를 구성합니다.

### 944행
```asm
	br	x4
```
x4에 준비한 주소부터 실행합니다. 주소가 가리키는 값을 메모리에서 읽는 것이 아니라 CPU의 다음 실행 위치를 바꿉니다. 새 복귀 주소는 만들지 않습니다.

### 945행
```asm
SYM_CODE_END(__sdei_asm_entry_trampoline)
```
도구에 이 코드 범위의 끝을 알려 크기와 심볼 정보를 기록합니다. CPU가 이 줄 때문에 자동으로 반환하지는 않습니다.

### 946행
```asm
NOKPROBE(__sdei_asm_entry_trampoline)
```
__sdei_asm_entry_trampoline을 kprobe 설치 금지 목록에 넣습니다. 스택·레지스터·주소 공간을 바꾸는 예외/전환 경로 중간에 probe 예외를 추가하지 못하게 보호합니다. CONFIG_KPROBES가 꺼지면 이 목록 항목도 필요 없습니다. 빌드할 때 심벌을 가리키는 메타데이터를 만드는 표기이며, 이 위치에서 함수를 호출하는 명령은 아닙니다.

### 955행
```asm
SYM_CODE_START(__sdei_asm_exit_trampoline)
```
이 위치부터 (__sdei_asm_exit_trampoline)라는 코드가 시작한다고 도구에 알려 줍니다. 정렬과 외부 공개 여부도 정의에 포함됩니다. 실제 작업은 아래의 명령들이 수행합니다.

### 956행
```asm
	ldr	x4, [x4, #(SDEI_EVENT_INTREGS + S_SDEI_TTBR1)]
```
SDEI 진입 때 남긴 TTBR1 상태 표식을 읽습니다. 현재 이벤트가 사용자 최소 매핑에서 들어왔는지에 따라 원래 매핑으로 되돌릴지 결정합니다. 실제로 x4 + ((SDEI_EVENT_INTREGS + S_SDEI_TTBR1)) 주소의 메모리 8바이트를 x4에 읽습니다.

### 957행
```asm
	cbnz	x4, 1f
```
0이 아니면 진입 전에도 커널 매핑이었으므로 unmap을 생략합니다. 0이면 이 이벤트 때문에 복구했던 커널 매핑을 다시 감춥니다. 이 분기는 지정 레지스터의 값이나 비트를 직접 검사하며 CMP가 남긴 NZCV를 읽거나 바꾸지 않습니다. 조건이 맞지 않으면 바로 다음 명령으로 진행합니다.

### 959행
```asm
	tramp_unmap_kernel	tmp=x4
```
사용자 실행으로 돌아가기 전에 커널의 넓은 매핑을 다시 감춥니다. 이 이후에도 실행할 최소 trampoline 코드는 남아 있어야 합니다. 여기의 인자는 tmp=x4입니다.

### 961행
```asm
1:	sdei_handler_exit exit_mode=x2
```
펌웨어에게 이벤트 처리가 끝났다고 알리고 실행을 넘깁니다. 성공하면 보통 함수처럼 다음 줄로 돌아오지 않는 경로입니다. 여기의 인자는 exit_mode=x2입니다.

### 962행
```asm
SYM_CODE_END(__sdei_asm_exit_trampoline)
```
도구에 이 코드 범위의 끝을 알려 크기와 심볼 정보를 기록합니다. CPU가 이 줄 때문에 자동으로 반환하지는 않습니다.

### 963행
```asm
NOKPROBE(__sdei_asm_exit_trampoline)
```
__sdei_asm_exit_trampoline을 kprobe 설치 금지 목록에 넣습니다. 스택·레지스터·주소 공간을 바꾸는 예외/전환 경로 중간에 probe 예외를 추가하지 못하게 보호합니다. CONFIG_KPROBES가 꺼지면 이 목록 항목도 필요 없습니다. 빌드할 때 심벌을 가리키는 메타데이터를 만드는 표기이며, 이 위치에서 함수를 호출하는 명령은 아닙니다.

### 964행
```asm
.popsection		// .entry.tramp.text
```
이후 출력할 바이트를 어느 섹션에 넣을지 바꿉니다. 빌드 도구가 코드를 분류하는 작업이며 현재 CPU의 PC나 SP를 옮기는 줄은 아닙니다. 

### 965행
```asm
#endif /* CONFIG_UNMAP_KERNEL_AT_EL0 */
```
빌드 조건으로 나누었던 구간을 닫습니다. 대상: 

## SDEI 레지스터·스택 준비
원본 967–1050행

### 980행
```asm
SYM_CODE_START(__sdei_asm_handler)
```
이 위치부터 (__sdei_asm_handler)라는 코드가 시작한다고 도구에 알려 줍니다. 정렬과 외부 공개 여부도 정의에 포함됩니다. 실제 작업은 아래의 명령들이 수행합니다.

### 981행
```asm
	stp     x2, x3, [x1, #SDEI_EVENT_INTREGS + S_PC]
```
x2, x3의 값을 x1를 기준으로 SDEI_EVENT_INTREGS + S_PC만큼 계산한 메모리 위치에 기록합니다. 레지스터 값을 나중에 다시 쓰거나 다른 코드가 읽을 수 있도록 메모리에 남기는 줄입니다. 첫 위치에는 x2, 그보다 8바이트 높은 위치에는 x3의 값이 들어갑니다.

### 982행
```asm
	stp     x4, x5, [x1, #SDEI_EVENT_INTREGS + 16 * 2]
```
x4, x5의 값을 x1를 기준으로 SDEI_EVENT_INTREGS + 16 * 2만큼 계산한 메모리 위치에 기록합니다. 레지스터 값을 나중에 다시 쓰거나 다른 코드가 읽을 수 있도록 메모리에 남기는 줄입니다. 첫 위치에는 x4, 그보다 8바이트 높은 위치에는 x5의 값이 들어갑니다.

### 983행
```asm
	stp     x6, x7, [x1, #SDEI_EVENT_INTREGS + 16 * 3]
```
x6, x7의 값을 x1를 기준으로 SDEI_EVENT_INTREGS + 16 * 3만큼 계산한 메모리 위치에 기록합니다. 레지스터 값을 나중에 다시 쓰거나 다른 코드가 읽을 수 있도록 메모리에 남기는 줄입니다. 첫 위치에는 x6, 그보다 8바이트 높은 위치에는 x7의 값이 들어갑니다.

### 984행
```asm
	stp     x8, x9, [x1, #SDEI_EVENT_INTREGS + 16 * 4]
```
x8, x9의 값을 x1를 기준으로 SDEI_EVENT_INTREGS + 16 * 4만큼 계산한 메모리 위치에 기록합니다. 레지스터 값을 나중에 다시 쓰거나 다른 코드가 읽을 수 있도록 메모리에 남기는 줄입니다. 첫 위치에는 x8, 그보다 8바이트 높은 위치에는 x9의 값이 들어갑니다.

### 985행
```asm
	stp     x10, x11, [x1, #SDEI_EVENT_INTREGS + 16 * 5]
```
x10, x11의 값을 x1를 기준으로 SDEI_EVENT_INTREGS + 16 * 5만큼 계산한 메모리 위치에 기록합니다. 레지스터 값을 나중에 다시 쓰거나 다른 코드가 읽을 수 있도록 메모리에 남기는 줄입니다. 첫 위치에는 x10, 그보다 8바이트 높은 위치에는 x11의 값이 들어갑니다.

### 986행
```asm
	stp     x12, x13, [x1, #SDEI_EVENT_INTREGS + 16 * 6]
```
x12, x13의 값을 x1를 기준으로 SDEI_EVENT_INTREGS + 16 * 6만큼 계산한 메모리 위치에 기록합니다. 레지스터 값을 나중에 다시 쓰거나 다른 코드가 읽을 수 있도록 메모리에 남기는 줄입니다. 첫 위치에는 x12, 그보다 8바이트 높은 위치에는 x13의 값이 들어갑니다.

### 987행
```asm
	stp     x14, x15, [x1, #SDEI_EVENT_INTREGS + 16 * 7]
```
x14, x15의 값을 x1를 기준으로 SDEI_EVENT_INTREGS + 16 * 7만큼 계산한 메모리 위치에 기록합니다. 레지스터 값을 나중에 다시 쓰거나 다른 코드가 읽을 수 있도록 메모리에 남기는 줄입니다. 첫 위치에는 x14, 그보다 8바이트 높은 위치에는 x15의 값이 들어갑니다.

### 988행
```asm
	stp     x16, x17, [x1, #SDEI_EVENT_INTREGS + 16 * 8]
```
x16, x17의 값을 x1를 기준으로 SDEI_EVENT_INTREGS + 16 * 8만큼 계산한 메모리 위치에 기록합니다. 레지스터 값을 나중에 다시 쓰거나 다른 코드가 읽을 수 있도록 메모리에 남기는 줄입니다. 첫 위치에는 x16, 그보다 8바이트 높은 위치에는 x17의 값이 들어갑니다.

### 989행
```asm
	stp     x18, x19, [x1, #SDEI_EVENT_INTREGS + 16 * 9]
```
x18, x19의 값을 x1를 기준으로 SDEI_EVENT_INTREGS + 16 * 9만큼 계산한 메모리 위치에 기록합니다. 레지스터 값을 나중에 다시 쓰거나 다른 코드가 읽을 수 있도록 메모리에 남기는 줄입니다. 첫 위치에는 x18, 그보다 8바이트 높은 위치에는 x19의 값이 들어갑니다.

### 990행
```asm
	stp     x20, x21, [x1, #SDEI_EVENT_INTREGS + 16 * 10]
```
x20, x21의 값을 x1를 기준으로 SDEI_EVENT_INTREGS + 16 * 10만큼 계산한 메모리 위치에 기록합니다. 레지스터 값을 나중에 다시 쓰거나 다른 코드가 읽을 수 있도록 메모리에 남기는 줄입니다. 첫 위치에는 x20, 그보다 8바이트 높은 위치에는 x21의 값이 들어갑니다.

### 991행
```asm
	stp     x22, x23, [x1, #SDEI_EVENT_INTREGS + 16 * 11]
```
x22, x23의 값을 x1를 기준으로 SDEI_EVENT_INTREGS + 16 * 11만큼 계산한 메모리 위치에 기록합니다. 레지스터 값을 나중에 다시 쓰거나 다른 코드가 읽을 수 있도록 메모리에 남기는 줄입니다. 첫 위치에는 x22, 그보다 8바이트 높은 위치에는 x23의 값이 들어갑니다.

### 992행
```asm
	stp     x24, x25, [x1, #SDEI_EVENT_INTREGS + 16 * 12]
```
x24, x25의 값을 x1를 기준으로 SDEI_EVENT_INTREGS + 16 * 12만큼 계산한 메모리 위치에 기록합니다. 레지스터 값을 나중에 다시 쓰거나 다른 코드가 읽을 수 있도록 메모리에 남기는 줄입니다. 첫 위치에는 x24, 그보다 8바이트 높은 위치에는 x25의 값이 들어갑니다.

### 993행
```asm
	stp     x26, x27, [x1, #SDEI_EVENT_INTREGS + 16 * 13]
```
x26, x27의 값을 x1를 기준으로 SDEI_EVENT_INTREGS + 16 * 13만큼 계산한 메모리 위치에 기록합니다. 레지스터 값을 나중에 다시 쓰거나 다른 코드가 읽을 수 있도록 메모리에 남기는 줄입니다. 첫 위치에는 x26, 그보다 8바이트 높은 위치에는 x27의 값이 들어갑니다.

### 994행
```asm
	stp     x28, x29, [x1, #SDEI_EVENT_INTREGS + 16 * 14]
```
x28, x29의 값을 x1를 기준으로 SDEI_EVENT_INTREGS + 16 * 14만큼 계산한 메모리 위치에 기록합니다. 레지스터 값을 나중에 다시 쓰거나 다른 코드가 읽을 수 있도록 메모리에 남기는 줄입니다. 첫 위치에는 x28, 그보다 8바이트 높은 위치에는 x29의 값이 들어갑니다.

### 995행
```asm
	mov	x4, sp
```
현재 SP의 숫자를 x4에 기억해 둡니다. 스택 자체는 이동하거나 복사되지 않습니다. 나중에 이 값으로 원래 위치를 찾을 수 있습니다.

### 996행
```asm
	stp     lr, x4, [x1, #SDEI_EVENT_INTREGS + S_LR]
```
lr, x4의 값을 x1를 기준으로 SDEI_EVENT_INTREGS + S_LR만큼 계산한 메모리 위치에 기록합니다. 레지스터 값을 나중에 다시 쓰거나 다른 코드가 읽을 수 있도록 메모리에 남기는 줄입니다.

### 998행
```asm
	mov	x19, x1
```
x19에 x1에 들어 있는 값을 넣습니다. 원래 x19의 내용은 덮어쓰며, 다른 레지스터나 그 주소의 메모리까지 복사하지는 않습니다.

### 1001행
```asm
	ldrb	w4, [x19, #SDEI_EVENT_PRIORITY]
```
등록된 SDEI 이벤트의 normal/critical 우선순위를 읽습니다. 두 이벤트가 중첩될 수 있으므로 실행 중 이벤트 포인터와 스택을 우선순위별로 분리합니다. 실제로 x19 + (SDEI_EVENT_PRIORITY) 주소의 메모리 1바이트를 w4에 읽습니다. 이 주소 형식은 기준 레지스터 x19 자체를 갱신하지 않습니다.

### 1002행
```asm
	cbnz	w4, 1f
```
w4의 값가 0이 아니면 1f (L1005)로 갑니다. 조건을 만족하지 않으면 바로 다음 명령을 읽습니다. 이 분기는 CMP의 결과가 아니라 지정 레지스터를 직접 검사합니다.

### 1003행
```asm
	adr_this_cpu dst=x5, sym=sdei_active_normal_event, tmp=x6
```
이 CPU의 normal SDEI 활성 이벤트 포인터 슬롯 주소를 x5에 구합니다. 이어지는 store가 현재 이벤트 객체를 여기에 기록하며 x6는 주소 계산용입니다. 심벌의 기준 주소에 현재 CPU의 per-CPU 오프셋을 더해 이 CPU의 저장 위치를 찾습니다.

### 1004행
```asm
	b	2f
```
2f (L1006)부터 실행을 계속합니다. 돌아올 주소를 새로 적지 않으므로 일반 함수 호출과는 다른 제어 이동입니다.

### 1005행
```asm
1:	adr_this_cpu dst=x5, sym=sdei_active_critical_event, tmp=x6
```
critical 이벤트는 별도의 CPU별 활성 포인터 슬롯을 사용합니다. normal 이벤트를 중첩 중단해도 두 종류의 활성 상태를 덮어쓰지 않도록 x5를 critical 슬롯으로 바꿉니다. 심벌의 기준 주소에 현재 CPU의 per-CPU 오프셋을 더해 이 CPU의 저장 위치를 찾습니다.

### 1006행
```asm
2:	str	x19, [x5]
```
현재 CPU가 처리 중인 이벤트 객체를 normal 또는 critical 슬롯에 기록합니다. crash_smp_send_stop 등의 비정상 종료 경로도 활성 이벤트를 찾을 수 있게 합니다. 실제로 x5 주소의 메모리 8바이트를 x19의 값으로 씁니다. 이 주소 형식은 기준 레지스터 x5 자체를 갱신하지 않습니다.

### 1013행
```asm
	cbnz	w4, 1f
```
w4의 값가 0이 아니면 1f (L1016)로 갑니다. 조건을 만족하지 않으면 바로 다음 명령을 읽습니다. 이 분기는 CMP의 결과가 아니라 지정 레지스터를 직접 검사합니다.

### 1014행
```asm
	ldr_this_cpu dst=x5, sym=sdei_stack_normal_ptr, tmp=x6
```
normal SDEI 이벤트용 CPU별 스택을 선택합니다. 예외가 커널 진입의 임시 SP 사용 중간을 끊을 수도 있으므로 기존 SP를 신뢰하지 않고 별도 스택을 씁니다. 심벌의 기준 주소에 현재 CPU의 per-CPU 오프셋을 더해 이 CPU의 저장 위치를 찾습니다.

### 1015행
```asm
	b	2f
```
2f (L1017)부터 실행을 계속합니다. 돌아올 주소를 새로 적지 않으므로 일반 함수 호출과는 다른 제어 이동입니다.

### 1016행
```asm
1:	ldr_this_cpu dst=x5, sym=sdei_stack_critical_ptr, tmp=x6
```
critical 이벤트면 normal과 다른 CPU별 스택을 선택하여 critical 이벤트가 normal 처리의 스택을 덮지 않게 합니다. 심벌의 기준 주소에 현재 CPU의 per-CPU 오프셋을 더해 이 CPU의 저장 위치를 찾습니다.

### 1017행
```asm
2:	mov	x6, #SDEI_STACK_SIZE
```
x6에 SDEI_STACK_SIZE라는 값을 넣습니다. 원래 x6의 내용은 덮어쓰며, 다른 레지스터나 그 주소의 메모리까지 복사하지는 않습니다.

### 1018행
```asm
	add	x5, x5, x6
```
x5를 기준으로 x6을 더해 x5에 결과를 둡니다. 주소 계산에 쓰이는 경우에는 구조체의 필드나 다음 저장 위치를 찾는 동작입니다. 이 형태는 조건 플래그를 바꾸지 않습니다.

### 1019행
```asm
	mov	sp, x5
```
이제 스택으로 사용할 위치를 x5의 값으로 바꿉니다. 메모리 내용을 옮기는 작업은 아닙니다. 다음 저장·호출부터 새 스택 위치를 사용합니다.

### 1021행
```asm
#ifdef CONFIG_SHADOW_CALL_STACK
```
SCS 보호 빌드는 SDEI에서도 normal·critical 이벤트마다 별도 shadow call stack을 선택합니다. 일반 스택을 나눠도 보호용 복귀 스택을 공유하면 중첩 처리 문맥이 섞일 수 있기 때문입니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.

### 1023행
```asm
	cbnz	w4, 3f
```
w4의 값가 0이 아니면 3f (L1026)로 갑니다. 조건을 만족하지 않으면 바로 다음 명령을 읽습니다. 이 분기는 CMP의 결과가 아니라 지정 레지스터를 직접 검사합니다.

### 1024행
```asm
	ldr_this_cpu dst=scs_sp, sym=sdei_shadow_call_stack_normal_ptr, tmp=x6
```
normal SDEI 이벤트 전용 shadow call stack 포인터를 x18에 읽습니다. 일반 task·IRQ의 보호용 복귀 스택과 구분하여 비동기 이벤트 중첩에 대비합니다. 심벌의 기준 주소에 현재 CPU의 per-CPU 오프셋을 더해 이 CPU의 저장 위치를 찾습니다.

### 1025행
```asm
	b	4f
```
4f (L1027)부터 실행을 계속합니다. 돌아올 주소를 새로 적지 않으므로 일반 함수 호출과는 다른 제어 이동입니다.

### 1026행
```asm
3:	ldr_this_cpu dst=scs_sp, sym=sdei_shadow_call_stack_critical_ptr, tmp=x6
```
critical 이벤트의 별도 shadow call stack을 선택합니다. normal SDEI handler를 중단한 경우에도 그 보호용 스택을 계속 소비하지 않도록 합니다. 심벌의 기준 주소에 현재 CPU의 per-CPU 오프셋을 더해 이 CPU의 저장 위치를 찾습니다.

### 1027행
```asm
4:
```
여기에 4라는 위치 이름을 붙입니다. 다른 줄의 분기나 주소 계산이 이 위치를 찾아올 수 있습니다. 이름 자체가 CPU 작업을 추가하지는 않습니다.

### 1028행
```asm
#endif
```
빌드 조건으로 나누었던 구간을 닫습니다. 대상: 

### 1034행
```asm
	mrs	x28, sp_el0
```
중단 시 SP_EL0를 x28에 보존합니다. 이벤트가 사용자·게스트·전환 도중 어디서든 올 수 있어 이 값이 이미 커널 current라고 가정하지 않습니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.
중단 당시 SP_EL0를 x28에 보존하여 이벤트 처리 후 되돌릴 수 있게 합니다.

### 1035행
```asm
	ldr_this_cpu	dst=x0, sym=__entry_task, tmp=x1
```
CPU별 __entry_task에서 신뢰할 현재 task 주소를 얻습니다. SDEI C handler가 current를 사용할 수 있게 할 입력입니다. 심벌의 기준 주소에 현재 CPU의 per-CPU 오프셋을 더해 이 CPU의 저장 위치를 찾습니다.

### 1036행
```asm
	msr	sp_el0, x0
```
SP_EL0에 커널 current 포인터를 설치합니다. 원래 값은 x28에 보관했으므로 이벤트 처리가 끝난 뒤 정확히 되돌릴 수 있습니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 1039행
```asm
	and     x0, x3, #0xc
```
필요한 비트만 남깁니다. 다른 정보가 섞인 값에서 관심 있는 부분을 고르는 작업입니다. 여기서 결과를 받는 레지스터는 x0입니다.

### 1040행
```asm
	mrs     x1, CurrentEL
```
현재 실행 EL과 펌웨어가 준 중단 PSTATE의 EL을 비교할 값을 읽습니다. 같은 커널 문맥이면 기존 FP·PC를 연결하고 사용자나 다른 EL 문맥이면 커널 unwind 연결을 끊습니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 1041행
```asm
	cmp     x0, x1
```
x0와 x1를 비교하여 조건 플래그에 결과를 남깁니다. 비교한 두 값은 그대로이며, 뒤의 조건 분기가 이 결과를 읽습니다.

### 1042행
```asm
	csel	x29, x29, xzr, eq	// fp, or zero
```
분기하지 않고 두 값 중 하나를 고릅니다. Z=1이면 x29, 아니면 xzr를 x29에 넣습니다. 조건 플래그 자체는 유지합니다.

### 1043행
```asm
	csel	x4, x2, xzr, eq		// elr, or zero
```
분기하지 않고 두 값 중 하나를 고릅니다. Z=1이면 x2, 아니면 xzr를 x4에 넣습니다. 조건 플래그 자체는 유지합니다.

### 1045행
```asm
	stp	x29, x4, [sp, #-16]!
```
x29, x4의 값을 sp를 기준으로 -16만큼 계산한 메모리 위치에 기록합니다. 레지스터 값을 나중에 다시 쓰거나 다른 코드가 읽을 수 있도록 메모리에 남기는 줄입니다. 첫 위치에는 x29, 그보다 8바이트 높은 위치에는 x4의 값이 들어갑니다. 이 스택 위치는 현재 함수 또는 예외 처리의 보관 공간입니다. 쓰기 전에 기준 주소를 갱신합니다. SP의 음수 오프셋이면 낮은 주소 쪽에 저장 공간을 확보하는 뜻입니다.

### 1046행
```asm
	mov	x29, sp
```
현재 SP의 숫자를 x29에 기억해 둡니다. 스택 자체는 이동하거나 복사되지 않습니다. 나중에 이 값으로 원래 위치를 찾을 수 있습니다.

### 1048행
```asm
	add	x0, x19, #SDEI_EVENT_INTREGS
```
x19를 기준으로 SDEI_EVENT_INTREGS을 더해 x0에 결과를 둡니다. 주소 계산에 쓰이는 경우에는 구조체의 필드나 다음 저장 위치를 찾는 동작입니다. 이 형태는 조건 플래그를 바꾸지 않습니다.

### 1049행
```asm
	mov	x1, x19
```
x1에 x19에 들어 있는 값을 넣습니다. 원래 x1의 내용은 덮어쓰며, 다른 레지스터나 그 주소의 메모리까지 복사하지는 않습니다.

### 1050행
```asm
	bl	__sdei_handler
```
x0=저장 레지스터, x1=등록 이벤트 객체로 __sdei_handler를 호출합니다. 펌웨어가 보존한 값 수거와 실제 이벤트 callback 등을 처리하고, 반환값은 완료 상태 또는 재개할 주소 선택에 사용됩니다. BL은 다음 명령의 주소를 LR(x30)에 기록하고 목적지로 분기합니다.

## SDEI 완료와 중단 처리
원본 1051–1100행

### 1052행
```asm
	msr	sp_el0, x28
```
이벤트 전 SP_EL0를 되돌립니다. 커널 handler를 위해 임시로 설치했던 current 값을 사용자·게스트의 원래 실행 상태에 남기지 않게 합니다. 이 접근 대상은 CPU의 시스템 레지스터이며, 일반 RAM에 대한 load/store가 아닙니다.

### 1054행
```asm
	mov	x4, x19         // keep x4 for __sdei_asm_exit_trampoline
```
x4에 x19에 들어 있는 값을 넣습니다. 원래 x4의 내용은 덮어쓰며, 다른 레지스터나 그 주소의 메모리까지 복사하지는 않습니다.

### 1055행
```asm
	ldp	x28, x29, [x4, #SDEI_EVENT_INTREGS + 16 * 14]
```
x4를 기준으로 SDEI_EVENT_INTREGS + 16 * 14만큼 계산한 메모리 위치에 보관된 값을 x28, x29로 가져옵니다. 주소 숫자를 옮기는 MOV와 달리 실제 메모리를 읽습니다. 첫 위치에서 x28, 그보다 8바이트 높은 위치에서 x29의 값을 읽습니다.

### 1056행
```asm
	ldp	x18, x19, [x4, #SDEI_EVENT_INTREGS + 16 * 9]
```
x4를 기준으로 SDEI_EVENT_INTREGS + 16 * 9만큼 계산한 메모리 위치에 보관된 값을 x18, x19로 가져옵니다. 주소 숫자를 옮기는 MOV와 달리 실제 메모리를 읽습니다. 첫 위치에서 x18, 그보다 8바이트 높은 위치에서 x19의 값을 읽습니다.

### 1057행
```asm
	ldp	lr, x1, [x4, #SDEI_EVENT_INTREGS + S_LR]
```
x4를 기준으로 SDEI_EVENT_INTREGS + S_LR만큼 계산한 메모리 위치에 보관된 값을 lr, x1로 가져옵니다. 주소 숫자를 옮기는 MOV와 달리 실제 메모리를 읽습니다.

### 1058행
```asm
	mov	sp, x1
```
이제 스택으로 사용할 위치를 x1의 값으로 바꿉니다. 메모리 내용을 옮기는 작업은 아닙니다. 다음 저장·호출부터 새 스택 위치를 사용합니다.

### 1060행
```asm
	mov	x1, x0			// address to complete_and_resume
```
x1에 x0에 들어 있는 값을 넣습니다. 원래 x1의 내용은 덮어쓰며, 다른 레지스터나 그 주소의 메모리까지 복사하지는 않습니다.

### 1064행
```asm
	cmp	x0, #SDEI_EV_FAILED
```
C handler 반환값이 완료 상태 코드 범위인지 아니면 별도 재개 주소인지 구분합니다. 아래 CSEL은 펌웨어의 EVENT_COMPLETE와 COMPLETE_AND_RESUME 중 어떤 API를 보낼지 고릅니다. CMP는 두 피연산자의 뺄셈 결과로 NZCV 조건 플래그만 갱신하고 원래 레지스터 값은 유지합니다.

### 1065행
```asm
	mov_q	x2, SDEI_1_0_FN_SDEI_EVENT_COMPLETE
```
원래 중단 문맥으로 돌아가는 SDEI EVENT_COMPLETE 함수 ID를 준비합니다. 정상 반환 상태를 펌웨어에 보고하는 서비스 번호입니다. mov_q는 64비트 상수를 필요한 MOVZ/MOVN·MOVK 조합으로 구성하는 매크로입니다. 해당 주소의 메모리에서 데이터를 가져오는 명령은 아닙니다.

### 1066행
```asm
	mov_q	x3, SDEI_1_0_FN_SDEI_EVENT_COMPLETE_AND_RESUME
```
명시한 주소에서 실행을 재개하는 EVENT_COMPLETE_AND_RESUME 함수 ID를 준비합니다. x1에 앞에서 복사한 재개 주소와 짝을 이루는 서비스입니다. mov_q는 64비트 상수를 필요한 MOVZ/MOVN·MOVK 조합으로 구성하는 매크로입니다. 해당 주소의 메모리에서 데이터를 가져오는 명령은 아닙니다.

### 1067행
```asm
	csel	x0, x2, x3, ls
```
분기하지 않고 두 값 중 하나를 고릅니다. C=0 또는 Z=1이면 x2, 아니면 x3를 x0에 넣습니다. 조건 플래그 자체는 유지합니다.
LS는 부호 없는 이하(C=0 또는 Z=1)입니다. 실패 이하이면 COMPLETE, 그 외에는 COMPLETE_AND_RESUME를 선택합니다.

### 1069행
```asm
	ldr_l	x2, sdei_exit_mode
```
SDEI 등록 시 정해진 완료 통로 SMC/HVC 종류를 읽습니다. 뒤의 sdei_handler_exit가 이 값으로 실제 명령을 선택합니다. ldr_l은 ADRP로 심벌이 속한 페이지를 구한 뒤 LDR로 그 위치의 값을 읽는 매크로입니다. 주소를 구하는 adr_l과 달리 실제 메모리 접근이 있습니다.

### 1072행
```asm
	ldrb	w3, [x4, #SDEI_EVENT_PRIORITY]
```
x4를 기준으로 SDEI_EVENT_PRIORITY만큼 계산한 메모리 위치에 보관된 값을 w3로 가져옵니다. 주소 숫자를 옮기는 MOV와 달리 실제 메모리를 읽습니다.

### 1073행
```asm
	cbnz	w3, 1f
```
w3의 값가 0이 아니면 1f (L1076)로 갑니다. 조건을 만족하지 않으면 바로 다음 명령을 읽습니다. 이 분기는 CMP의 결과가 아니라 지정 레지스터를 직접 검사합니다.

### 1074행
```asm
	adr_this_cpu dst=x5, sym=sdei_active_normal_event, tmp=x6
```
normal 이벤트 처리를 마친 뒤 비울 CPU별 활성 슬롯의 주소를 x5에 구합니다. 뒤의 store 0은 이벤트 객체 해제가 아니라 활성 표시 제거입니다. 심벌의 기준 주소에 현재 CPU의 per-CPU 오프셋을 더해 이 CPU의 저장 위치를 찾습니다.

### 1075행
```asm
	b	2f
```
2f (L1077)부터 실행을 계속합니다. 돌아올 주소를 새로 적지 않으므로 일반 함수 호출과는 다른 제어 이동입니다.

### 1076행
```asm
1:	adr_this_cpu dst=x5, sym=sdei_active_critical_event, tmp=x6
```
critical 이벤트였다면 normal 슬롯 대신 critical 활성 슬롯 주소를 선택합니다. 방금 종료할 이벤트의 표시만 지우기 위한 선택입니다. 심벌의 기준 주소에 현재 CPU의 per-CPU 오프셋을 더해 이 CPU의 저장 위치를 찾습니다.

### 1077행
```asm
2:	str	xzr, [x5]
```
CPU별 활성 SDEI 이벤트 포인터를 0으로 비워 처리가 끝났음을 표시합니다. 이벤트 객체 자체를 해제하는 store가 아니라 crash 경로에서 볼 활성 표식 정리입니다. 실제로 x5 주소의 메모리 8바이트를 xzr의 값으로 씁니다. 이 주소 형식은 기준 레지스터 x5 자체를 갱신하지 않습니다.

### 1079행
```asm
alternative_if_not ARM64_UNMAP_KERNEL_AT_EL0
```
실제 KPTI가 비활성이면 정상 커널 매핑에서 곧바로 펌웨어 완료 호출을 해도 되므로 이 경로를 유지합니다. 활성인 경우는 최소 exit trampoline에서 매핑을 되돌린 뒤 호출해야 합니다. 이 매크로는 기본·교체 명령의 패치 정보를 만들며, CPU가 이 줄을 매번 if 조건처럼 실행하지 않습니다.

### 1080행
```asm
	sdei_handler_exit exit_mode=x2
```
펌웨어에게 이벤트 처리가 끝났다고 알리고 실행을 넘깁니다. 성공하면 보통 함수처럼 다음 줄로 돌아오지 않는 경로입니다. 여기의 인자는 exit_mode=x2입니다.

### 1081행
```asm
alternative_else_nop_endif
```
1079행에서 시작한 ARM64_UNMAP_KERNEL_AT_EL0 패치 묶음을 닫습니다. 조건에 맞는 본문을 사용하지 않는 쪽은 같은 길이의 NOP로 채워 뒤 명령의 배치를 유지합니다. 이 줄 자체가 CPU에서 조건을 검사하는 명령은 아닙니다.

### 1083행
```asm
#ifdef CONFIG_UNMAP_KERNEL_AT_EL0
```
KPTI 지원 빌드에는 이벤트 전 매핑 상태로 되돌릴 exit trampoline 분기를 포함합니다. 실제 활성 시스템에서 위의 직접 완료 경로가 빠졌을 때 이 경로로 이어집니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.

### 1084행
```asm
	tramp_alias	dst=x5, sym=__sdei_asm_exit_trampoline
```
최소 trampoline 매핑에서도 접근할 수 있는 코드 주소를 계산합니다. 같은 코드라도 어느 매핑에서 부르는지에 따라 사용할 주소가 달라집니다. 여기의 인자는 dst=x5, sym=__sdei_asm_exit_trampoline입니다.

### 1085행
```asm
	br	x5
```
x5에 준비한 주소부터 실행합니다. 주소가 가리키는 값을 메모리에서 읽는 것이 아니라 CPU의 다음 실행 위치를 바꿉니다. 새 복귀 주소는 만들지 않습니다.

### 1086행
```asm
#endif
```
빌드 조건으로 나누었던 구간을 닫습니다. 대상: 

### 1087행
```asm
SYM_CODE_END(__sdei_asm_handler)
```
도구에 이 코드 범위의 끝을 알려 크기와 심볼 정보를 기록합니다. CPU가 이 줄 때문에 자동으로 반환하지는 않습니다.

### 1088행
```asm
NOKPROBE(__sdei_asm_handler)
```
__sdei_asm_handler을 kprobe 설치 금지 목록에 넣습니다. 스택·레지스터·주소 공간을 바꾸는 예외/전환 경로 중간에 probe 예외를 추가하지 못하게 보호합니다. CONFIG_KPROBES가 꺼지면 이 목록 항목도 필요 없습니다. 빌드할 때 심벌을 가리키는 메타데이터를 만드는 표기이며, 이 위치에서 함수를 호출하는 명령은 아닙니다.

### 1090행
```asm
SYM_CODE_START(__sdei_handler_abort)
```
이 위치부터 (__sdei_handler_abort)라는 코드가 시작한다고 도구에 알려 줍니다. 정렬과 외부 공개 여부도 정의에 포함됩니다. 실제 작업은 아래의 명령들이 수행합니다.

### 1091행
```asm
	mov_q	x0, SDEI_1_0_FN_SDEI_EVENT_COMPLETE_AND_RESUME
```
진행 중인 SDEI handler를 중단하면서 바로 아래 지역 라벨에서 다시 실행하도록 COMPLETE_AND_RESUME 서비스 번호를 준비합니다. task 시그널 반환 번호와는 다른 펌웨어 API입니다. mov_q는 64비트 상수를 필요한 MOVZ/MOVN·MOVK 조합으로 구성하는 매크로입니다. 해당 주소의 메모리에서 데이터를 가져오는 명령은 아닙니다.

### 1092행
```asm
	adr	x1, 1f
```
펌웨어가 이벤트 종료 뒤 재개할 주소로 1f를 전달합니다. 원래 LR로 즉시 RET하는 대신 펌웨어의 이벤트 상태도 정상적으로 정리해야 하기 때문입니다. 여기서 얻는 것은 레이블의 주소이며, 그 위치의 메모리 내용은 아직 읽지 않습니다.

### 1093행
```asm
	ldr_l	x2, sdei_exit_mode
```
SDEI가 사용하는 펌웨어 완료 통로의 종류를 x2에 읽습니다. 뒤의 exit 매크로가 SMC인지 HVC인지 골라 중단한 이벤트의 상태를 정리합니다. ldr_l은 ADRP로 심벌이 속한 페이지를 구한 뒤 LDR로 그 위치의 값을 읽는 매크로입니다. 주소를 구하는 adr_l과 달리 실제 메모리 접근이 있습니다.

### 1094행
```asm
	sdei_handler_exit exit_mode=x2
```
펌웨어에게 이벤트 처리가 끝났다고 알리고 실행을 넘깁니다. 성공하면 보통 함수처럼 다음 줄로 돌아오지 않는 경로입니다. 여기의 인자는 exit_mode=x2입니다.

### 1097행
```asm
1:	ret
```
LR(x30)에 적힌 주소로 실행을 돌립니다. 그 주소가 어떻게 준비됐는지가 중요합니다. 정상 함수의 호출자일 수도 있고, 절전 복귀나 trampoline에서 별도로 기록한 재개 지점일 수도 있습니다.

### 1098행
```asm
SYM_CODE_END(__sdei_handler_abort)
```
도구에 이 코드 범위의 끝을 알려 크기와 심볼 정보를 기록합니다. CPU가 이 줄 때문에 자동으로 반환하지는 않습니다.

### 1099행
```asm
NOKPROBE(__sdei_handler_abort)
```
__sdei_handler_abort을 kprobe 설치 금지 목록에 넣습니다. 스택·레지스터·주소 공간을 바꾸는 예외/전환 경로 중간에 probe 예외를 추가하지 못하게 보호합니다. CONFIG_KPROBES가 꺼지면 이 목록 항목도 필요 없습니다. 빌드할 때 심벌을 가리키는 메타데이터를 만드는 표기이며, 이 위치에서 함수를 호출하는 명령은 아닙니다.

### 1100행
```asm
#endif /* CONFIG_ARM_SDE_INTERFACE */
```
빌드 조건으로 나누었던 구간을 닫습니다. 대상: 

## 설명한 뒤 함께 생각해 볼 질문

### CPU가 ELR과 SPSR을 저장했는데 STP가 왜 많이 필요한가요?
ELR/SPSR에는 중단된 PC와 실행 상태가 들어갑니다. 일반 레지스터 x0–x30은 별도 보존이 필요합니다.

### cpu_switch_to의 RET는 누구에게 돌아가나요?
다음 태스크에서 읽은 LR로 이동합니다. 이전 태스크의 호출 흐름을 그대로 계속하는 RET가 아닙니다.

### SP_EL0는 항상 사용자 스택 주소인가요?
EL0에서 예외가 들어올 당시에는 사용자 SP로 읽지만, 커널은 이후 current 태스크 포인터를 담는 용도로 사용합니다. 시점을 구분해야 합니다.
