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

함수 앞에 추적 함수를 끼워 넣더라도 원래 함수의 인자와 복귀 주소는 유지되어야 합니다. 추적 자체가 프로그램의 계산을 바꾸면 관찰 도구가 될 수 없기 때문입니다. 이 코드는 추적용 정보를 따로 모아 콜백에 넘긴 뒤 원래 함수가 시작하려던 자리로 돌아갑니다.

## 처음 읽을 때
원래 함수가 부모에게 돌아갈 주소와 추적을 마친 직후 원래 함수 본문으로 갈 주소는 서로 다릅니다. x9와 x30을 구분해 따라가세요.

## 더 깊이 살펴볼 때
함수 진입 패치, ftrace_regs, 프레임 연결, direct trampoline은 서로 다른 규약입니다. 반환값을 관찰하는 경로에서는 x0–x7을 왜 다시 저장하는지도 ABI와 연결해 보세요.

## 추적 지점과 direct call
원본 1–63행

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

### 10행
```asm
#include <linux/cfi_types.h>
```
간접 호출 대상의 함수 형식을 검사하는 CFI용 어셈블리 심볼 표기를 가져옵니다. 추적 callback과 stub가 빌드의 제어 흐름 보호 규약에 맞는 정보를 갖도록 합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

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

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

### 13행
```asm
#include <asm/ftrace.h>
```
ARM64 ftrace의 저장 레지스터 형식과 호출 규약에 관한 정의를 가져옵니다. 추적 callback과 어셈블리 trampoline이 같은 프레임 크기·의미를 사용하게 합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 14행
```asm
#include <asm/insn.h>
```
AArch64 명령의 크기·인코딩 관련 정의를 가져옵니다. 계측 BL 다음 주소에서 실제 callsite를 구할 때 명령 한 개의 크기만큼 보정하는 근거입니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 16행
```asm
#ifdef CONFIG_DYNAMIC_FTRACE_WITH_ARGS
```
함수 인수도 추적기에 제공하는 동적 ftrace 구현을 선택합니다. 켜지면 x0~x8과 SP·FP·LR 등의 ftrace_regs를 만들어 callback에 넘기고, 꺼지면 아래 #else의 전통적인 _mcount/frame 기반 경로를 사용합니다. 추적을 실제로 켜는 런타임 설정과 빌드 기능을 구분해야 합니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.

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

### 37행
```asm
	bti	c
```
간접 호출이 들어와도 되는 위치임을 표시합니다. 실제 보호 적용은 CPU 기능과 페이지 설정에 따릅니다.

### 39행
```asm
#ifdef CONFIG_DYNAMIC_FTRACE_WITH_CALL_OPS
```
호출 지점마다 연결된 ftrace_ops를 읽을 수 있는 구성을 선택합니다. 켜지면 callsite 앞의 데이터에서 op를 얻어 그 op의 callback을 직접 고르고, 꺼지면 공통 function_trace_op와 패치 가능한 ftrace_call 지점을 사용합니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.

### 51행
```asm
	bic	x11, x30, 0x7
```
ftrace 호출 뒤의 LR을 8바이트 경계로 내려 callsite 앞에 둔 op 포인터 슬롯을 찾을 기준을 만듭니다. 임의로 함수 주소의 하위 비트를 지우는 보안 검사는 아닙니다. BIC는 마스크에서 1인 위치만 0으로 만들고 나머지 비트는 유지합니다.
LR은 ops 리터럴+16 또는 +20입니다. 하위 세 비트를 지우면 두 경우 모두 리터럴+16이 됩니다.

### 52행
```asm
	ldr	x11, [x11, #-(4 * AARCH64_INSN_SIZE)]		// op
```
정렬 기준에서 네 개 AArch64 명령 크기만큼 앞에 둔 ftrace_ops 포인터를 읽습니다. CALL_OPS가 정의한 callsite 배치와 맞아야 callback을 올바르게 찾을 수 있습니다. 실제로 x11 + (-(4 * AARCH64_INSN_SIZE)) 주소의 메모리 8바이트를 x11에 읽습니다.

### 54행
```asm
#ifdef CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS
```
direct-call 구성이면 일반 ftrace callback을 거치지 않고 지정된 trampoline으로 보낼 수 있습니다. 켜진 빌드에서 op->direct_call을 확인하고 0이 아닐 때 직접 경로를 선택하며, 꺼지면 이 빠른 선택 코드는 없습니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.

### 59행
```asm
	ldr	x17, [x11, #FTRACE_OPS_DIRECT_CALL]		// op->direct_call
```
이 callsite의 op가 지정한 direct trampoline 주소를 x17에 읽습니다. 값 0은 이 op에 직접 보낼 목적지가 없음을 뜻합니다. 실제로 x11 + (FTRACE_OPS_DIRECT_CALL) 주소의 메모리 8바이트를 x17에 읽습니다. 이 주소 형식은 기준 레지스터 x11 자체를 갱신하지 않습니다.

### 60행
```asm
	cbnz	x17, ftrace_caller_direct
```
direct trampoline 주소가 있으면 아직 공통 저장 프레임을 만들기 전에 직접 경로로 갑니다. 0이면 일반 ftrace_regs를 만드는 처리로 이어집니다. 이 분기는 지정 레지스터의 값이나 비트를 직접 검사하며 CMP가 남긴 NZCV를 읽거나 바꾸지 않습니다. 조건이 맞지 않으면 바로 다음 명령으로 진행합니다.

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

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

## ftrace_regs와 두 프레임
원본 64–112행

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

### 68행
```asm
	sub	sp, sp, #(FREGS_SIZE + 32)
```
함수 인수와 추적 상태를 담을 FREGS_SIZE에 unwind용 프레임 두 개 32바이트를 더해 확보합니다. 추적 callback이 원래 함수 인수를 덮어써도 되돌릴 수 있게 할 공간입니다. 이 명령은 SP의 주소값을 조정하며, 확보하거나 반환하는 스택 공간의 데이터를 직접 저장하거나 지우지는 않습니다. 이 SUB 형식은 NZCV 조건 플래그를 바꾸지 않습니다.
FREGS_SIZE 외 32바이트는 16바이트 프레임 레코드 두 개입니다.

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

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

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

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

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

### 77행
```asm
#ifdef CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS
```
direct-call을 지원하는 빌드에는 ftrace_regs 안의 direct_tramp 슬롯을 준비합니다. 일반 callback이 뒤에서 직접 trampoline을 선택할 가능성을 표현하는 필드이며 미지원 빌드에는 이 경로가 없습니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.

### 78행
```asm
	str	xzr, [sp, #FREGS_DIRECT_TRAMP]
```
아직 선택된 direct trampoline이 없다는 뜻으로 direct_tramp를 0으로 초기화합니다. callback 후 이 값이 채워졌는지 검사하는 늦은 direct 경로와 연결됩니다. 실제로 sp + (FREGS_DIRECT_TRAMP) 주소의 메모리 8바이트를 xzr의 값으로 씁니다. 이 주소 형식은 기준 레지스터 sp 자체를 갱신하지 않습니다.

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

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

### 83행
```asm
	str	x9,  [sp, #FREGS_LR]
```
x9의 값을 sp를 기준으로 FREGS_LR만큼 계산한 메모리 위치에 기록합니다. 레지스터 값을 나중에 다시 쓰거나 다른 코드가 읽을 수 있도록 메모리에 남기는 줄입니다. 이 스택 위치는 현재 함수 또는 예외 처리의 보관 공간입니다.
x9에는 BL 전에 MOV x9,LR로 보존한 추적 대상 함수의 원래 LR이 있습니다.

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

### 87행
```asm
	str	x30, [sp, #FREGS_PC]
```
ftrace로 들어오기 직전의 LR, 즉 추적 대상 함수의 callsite 다음 위치를 PC 필드에 저장합니다. 실제 부모에게 돌아갈 주소 x9를 보관한 FREGS_LR과 구분합니다. 실제로 sp + (FREGS_PC) 주소의 메모리 8바이트를 x30의 값으로 씁니다. 이 주소 형식은 기준 레지스터 sp 자체를 갱신하지 않습니다.
현재 x30은 추적 대상 함수로 재개할 PC입니다.

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

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

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

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

### 98행
```asm
	sub	x0, x30, #AARCH64_INSN_SIZE		// ip (callsite's BL insn)
```
BL 다음 주소인 x30에서 명령 크기 4바이트를 빼 실제 ftrace callsite의 주소 ip를 첫 인수 x0로 만듭니다. 이 SUB 형식은 NZCV 조건 플래그를 바꾸지 않습니다.
BL 이후 PC에서 4를 빼 추적 호출 지점 ip를 전달합니다.

### 99행
```asm
	mov	x1, x9					// parent_ip (callsite's LR)
```
추적 대상 함수가 부모로 돌아갈 주소 x9를 parent_ip 인수 x1로 넘깁니다. ftrace 내부에서 돌아갈 LR과 다른 주소입니다. MOV는 x1의 기존 값을 대체합니다. 원본이 주소값이어도 그 주소의 메모리를 읽거나 복사하지 않습니다.

### 100행
```asm
	mov	x3, sp					// regs
```
현재 SP가 가리키는 ftrace_regs 저장 영역을 네 번째 인수 x3로 넘겨 callback이 함수 인수·문맥을 확인할 수 있게 합니다. x3에 담기는 것은 SP의 주소값이며, SP와 스택 메모리의 내용은 그대로입니다.

### 102행
```asm
#ifdef CONFIG_DYNAMIC_FTRACE_WITH_CALL_OPS
```
CALL_OPS가 켜져 있으면 이미 callsite에서 얻은 op와 op->func를 사용합니다. 꺼진 빌드는 다음 #else에서 공통 op 및 패치 가능한 호출을 사용합니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.

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

### 104행
```asm
	ldr	x4, [x2, #FTRACE_OPS_FUNC]		// op->func
```
ftrace_ops의 func 필드에서 실제 callback 주소를 꺼냅니다. 단순한 코드 상수가 아니라 op마다 다른 함수 포인터입니다. 실제로 x2 + (FTRACE_OPS_FUNC) 주소의 메모리 8바이트를 x4에 읽습니다. 이 주소 형식은 기준 레지스터 x2 자체를 갱신하지 않습니다.

### 105행
```asm
	blr	x4					// op->func(ip, parent_ip, op, regs)
```
op->func(ip, parent_ip, op, regs)를 호출합니다. x0~x3는 앞서 준비한 추적 위치·부모 주소·op·저장 레지스터이며, 원래 대상 함수의 인수는 스택에 보존되어 callback 뒤에 복원합니다. BLR은 다음 명령의 주소를 LR(x30)에 기록하고 레지스터에 든 목적지로 분기합니다.

### 107행
```asm
#else
```
callsite별 op를 쓰지 않는 구성에서는 공통 function_trace_op를 로드하고 ftrace_call의 명령을 패치하는 경로를 사용합니다. ftrace 전체가 꺼진다는 뜻은 아닙니다.

### 108행
```asm
	ldr_l   x2, function_trace_op			// op
```
현재 추적 작업 객체 function_trace_op의 값을 세 번째 인수 x2로 읽습니다. callback이 어느 ftrace_ops 설정으로 호출되었는지 알 수 있게 하며, 변수의 주소 자체를 넘기는 것이 아닙니다. ldr_l은 ADRP로 심벌이 속한 페이지를 구한 뒤 LDR로 그 위치의 값을 읽는 매크로입니다. 주소를 구하는 adr_l과 달리 실제 메모리 접근이 있습니다.

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

### 111행
```asm
	bl      ftrace_stub				// func(ip, parent_ip, op, regs)
```
초기에는 아무 일도 하지 않는 ftrace_stub로 호출하지만 ftrace_call 지점은 실행 중 추적 callback으로 패치될 수 있습니다. 인수는 ip, parent_ip, op, ftrace_regs이며 추적이 끝나면 저장한 대상 함수 인수를 복구합니다. BL은 다음 명령의 주소를 LR(x30)에 기록하고 목적지로 분기합니다. 호출된 코드가 x0 같은 작업 레지스터를 바꿀 수 있으므로, 호출 뒤의 값은 그 코드의 반환·보존 규칙에 따릅니다.

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

## 복원과 direct trampoline
원본 113–178행

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

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

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

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

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

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

### 129행
```asm
#ifdef CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS
```
direct-call 지원 빌드에서 일반 callback이 ftrace_regs.direct_tramp를 설정했는지 검사하는 코드를 포함합니다. 초기에 direct 목적지가 없었더라도 callback 후 늦게 경로가 선택될 수 있습니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.

### 130행
```asm
	ldr	x17, [sp, #FREGS_DIRECT_TRAMP]
```
callback이 지정했을 수 있는 direct trampoline 주소를 x17에 읽습니다. 이 슬롯을 앞에서 0으로 초기화했기 때문에 0은 일반 복귀를 뜻합니다. 실제로 sp + (FREGS_DIRECT_TRAMP) 주소의 메모리 8바이트를 x17에 읽습니다. 이 주소 형식은 기준 레지스터 sp 자체를 갱신하지 않습니다.

### 131행
```asm
	cbnz	x17, ftrace_caller_direct_late
```
callback 뒤에 direct 목적지가 생겼으면 이미 만든 저장 프레임을 정리하는 late 경로로 갑니다. 바로 초기 direct 경로로 가면 SP·LR 배치가 맞지 않습니다. 이 분기는 지정 레지스터의 값이나 비트를 직접 검사하며 CMP가 남긴 NZCV를 읽거나 바꾸지 않습니다. 조건이 맞지 않으면 바로 다음 명령으로 진행합니다.

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

### 135행
```asm
	ldr	x30, [sp, #FREGS_LR]
```
sp를 기준으로 FREGS_LR만큼 계산한 메모리 위치에 보관된 값을 x30로 가져옵니다. 주소 숫자를 옮기는 MOV와 달리 실제 메모리를 읽습니다. 앞서 보관한 상태를 되살리거나 스택으로 전달된 인자를 얻는 과정입니다.
원래 함수가 나중에 부모에게 돌아갈 LR을 복원합니다.

### 136행
```asm
	ldr	x9,  [sp, #FREGS_PC]
```
sp를 기준으로 FREGS_PC만큼 계산한 메모리 위치에 보관된 값을 x9로 가져옵니다. 주소 숫자를 옮기는 MOV와 달리 실제 메모리를 읽습니다. 앞서 보관한 상태를 되살리거나 스택으로 전달된 인자를 얻는 과정입니다.
추적을 끝내고 즉시 계속할 위치는 별도 x9에 읽습니다.

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

### 141행
```asm
	ret	x9
```
x9에 적힌 주소로 실행을 돌립니다. 그 주소가 어떻게 준비됐는지가 중요합니다. 정상 함수의 호출자일 수도 있고, 절전 복귀나 trampoline에서 별도로 기록한 재개 지점일 수도 있습니다.
X30의 부모 주소가 아니라 X9의 함수 본문 재개 주소로 이동합니다.

### 143행
```asm
#ifdef CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS
```
늦은 direct-call 전환이 필요한 빌드에만 아래 프레임 해제·LR 재배치 경로를 생성합니다. 일반 callback을 호출한 뒤의 상태를 direct trampoline의 호출 규칙에 맞춥니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.

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

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

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

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

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

### 168행
```asm
	br	x17
```
x17의 direct trampoline으로 새 LR을 만들지 않고 이동합니다. 앞서 x9와 x30을 이 경로의 규칙대로 준비하여 trampoline이 대상 함수와 부모의 복귀 주소를 구분할 수 있게 합니다. 레지스터가 담은 주소로 실행 위치를 바꾸며, 그 주소에서 값을 읽어 다른 레지스터에 복사하는 load 명령은 아닙니다.
직접 trampoline 호출 규약의 x9·x30을 유지합니다.

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

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

### 172행
```asm
#ifdef CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS
```
direct-call 지원 빌드에는 특별한 작업 없이 정상 복귀만 이어 주는 stub trampoline도 포함합니다. 아래에서 x30과 x9의 역할을 교환해 direct 호출 규칙을 유지합니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.

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

### 174행
```asm
	bti	c
```
간접 호출이 들어와도 되는 위치임을 표시합니다. 실제 보호 적용은 CPU 기능과 페이지 설정에 따릅니다.

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

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

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

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

![단계별 개념 그림](../media/entry-ftrace-overview.png)
화살표는 복귀 기록의 저장·복원입니다. 모든 ftrace 설정이 동일한 진입 코드를 사용하는 것은 아닙니다.

## 전통적인 _mcount 프레임 매크로
원본 179–254행

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

### 181행
```asm
#else /* CONFIG_DYNAMIC_FTRACE_WITH_ARGS */
```
인수 전달형 동적 ftrace를 사용하지 않는 빌드는 기존 _mcount/frame 기반 구현을 만듭니다. 아래 코드는 저장 프레임을 따라 함수·부모 주소를 구하며 위의 ftrace_regs 구성과 섞어 읽으면 안 됩니다.

### 215행
```asm
	.macro mcount_enter
```
추적 코드가 C 함수를 호출해도 원래 FP와 LR을 잃지 않도록 16바이트 프레임을 만드는 매크로입니다. x29를 새 프레임의 기준으로 삼아 뒤의 부모 프레임 조회가 가능해집니다. 이 줄은 어셈블러 매크로 정의의 시작입니다. 본문은 매크로를 사용하는 위치에 펼쳐지며, 정의 자체가 런타임 함수 호출은 아닙니다.

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

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

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

### 220행
```asm
	.macro mcount_exit
```
mcount_enter가 만든 프레임에서 FP·LR을 되돌리고 SP를 16바이트 복구한 뒤 반환하는 매크로입니다. 진입과 대칭이어야 대상 함수의 스택이 유지됩니다. 이 줄은 어셈블러 매크로 정의의 시작입니다. 본문은 매크로를 사용하는 위치에 펼쳐지며, 정의 자체가 런타임 함수 호출은 아닙니다.

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

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

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

### 225행
```asm
	.macro mcount_adjust_addr rd, rn
```
BL이 LR에 남긴 다음 명령 주소 rn에서 AArch64 명령 크기 4를 빼 호출 명령 자체의 주소를 rd에 만듭니다. 추적 로그에 callsite를 정확히 기록하기 위한 주소 보정입니다. 이 줄은 어셈블러 매크로 정의의 시작입니다. 본문은 매크로를 사용하는 위치에 펼쳐지며, 정의 자체가 런타임 함수 호출은 아닙니다.

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

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

### 230행
```asm
	.macro mcount_get_parent_fp reg
```
현재 mcount 프레임과 그 호출 프레임의 FP 연결을 두 번 따라 부모 함수의 FP를 구합니다. 두 load는 같은 값을 중복 읽는 것이 아니라 한 단계씩 스택 프레임을 거슬러 갑니다. 이 줄은 어셈블러 매크로 정의의 시작입니다. 본문은 매크로를 사용하는 위치에 펼쳐지며, 정의 자체가 런타임 함수 호출은 아닙니다.

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

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

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

### 236행
```asm
	.macro mcount_get_pc0 reg
```
새 추적 프레임을 만들기 전의 x30에서 4를 빼 대상 함수의 ftrace callsite 주소를 구합니다. 입력 위치가 이미 스택에 저장된 PC를 읽는 아래 매크로와 다릅니다. 이 줄은 어셈블러 매크로 정의의 시작입니다. 본문은 매크로를 사용하는 위치에 펼쳐지며, 정의 자체가 런타임 함수 호출은 아닙니다.

### 237행
```asm
	mcount_adjust_addr	\reg, x30
```
현재 LR에서 4를 빼 ftrace 호출 명령의 주소를 만듭니다. 추적기에 전달할 PC는 BL 다음 복귀 위치가 아니라 계측된 callsite여야 합니다.

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

### 240행
```asm
	.macro mcount_get_pc reg
```
mcount 프레임에 저장된 LR을 읽고 4를 빼 callsite PC로 만듭니다. 추가 호출로 현재 x30이 바뀐 경우에도 저장된 주소를 사용합니다. 이 줄은 어셈블러 매크로 정의의 시작입니다. 본문은 매크로를 사용하는 위치에 펼쳐지며, 정의 자체가 런타임 함수 호출은 아닙니다.

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

### 242행
```asm
	mcount_adjust_addr	\reg, \reg
```
프레임에서 읽은 LR에서 4를 빼 원래 계측 callsite를 구합니다. 현재 LR은 다른 호출로 바뀔 수 있으므로 저장 사본을 기준으로 합니다.

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

### 245행
```asm
	.macro mcount_get_lr reg
```
이전 프레임의 LR 슬롯을 읽어 추적 대상 함수가 부모로 돌아갈 주소를 구합니다. 함수 시작 주소나 현재 PC를 구하는 매크로가 아닙니다. 이 줄은 어셈블러 매크로 정의의 시작입니다. 본문은 매크로를 사용하는 위치에 펼쳐지며, 정의 자체가 런타임 함수 호출은 아닙니다.

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

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

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

### 250행
```asm
	.macro mcount_get_lr_addr reg
```
이전 프레임에서 LR 값 자체가 아니라 LR이 저장된 메모리 주소를 구합니다. function graph tracer가 이 슬롯을 return_to_handler로 바꿀 수 있게 하기 위한 인수입니다. 이 줄은 어셈블러 매크로 정의의 시작입니다. 본문은 매크로를 사용하는 위치에 펼쳐지며, 정의 자체가 런타임 함수 호출은 아닙니다.

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

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

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

## 패치 위치와 함수 그래프 진입
원본 255–314행

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

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

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

### 264행
```asm
EXPORT_SYMBOL(_mcount)
```
계측된 커널 모듈도 _mcount 진입점을 연결할 수 있도록 커널 export 정보를 만듭니다. 모듈의 함수 추적 호출이 이 공통 구현을 참조하기 위한 조치입니다. 빌드할 때 심벌을 가리키는 메타데이터를 만드는 표기이며, 이 위치에서 함수를 호출하는 명령은 아닙니다.

### 265행
```asm
NOKPROBE(_mcount)
```
_mcount을 kprobe 설치 금지 목록에 넣습니다. 추적기 자체에 probe를 걸면 재귀 계측으로 이어질 수 있어 이 저수준 추적 진입을 보호합니다. CONFIG_KPROBES가 꺼지면 이 목록 항목도 필요 없습니다. 빌드할 때 심벌을 가리키는 메타데이터를 만드는 표기이며, 이 위치에서 함수를 호출하는 명령은 아닙니다.

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

### 277행
```asm
	mcount_enter
```
추적 코드가 다른 함수를 부르기 전에 원래 프레임과 복귀 주소를 스택에 보관합니다. 추적을 마친 뒤 돌아갈 자리를 남기는 일입니다. 여기의 인자는 없음입니다.

### 279행
```asm
	mcount_get_pc0	x0		//     function's pc
```
추적 대상 함수의 callsite PC를 첫 인수 x0에 만듭니다. mcount_enter를 하기 전이므로 현재 x30에서 4를 빼면 원래 BL 위치를 얻습니다.

### 280행
```asm
	mcount_get_lr	x1		//     function's lr
```
스택 프레임에 보존된 부모 복귀 주소를 두 번째 인수 x1에 읽습니다. 첫 인수의 계측 위치와 함께 어느 함수 호출 관계인지 추적 callback에 전달합니다. 먼저 현재 프레임에 저장된 FP를 읽고, 그 FP의 +8 위치에서 부모에게 돌아갈 LR을 읽습니다.

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

### 283행
```asm
	nop				// This will be replaced with "bl xxx"
```
현재 바이트는 아무 일도 하지 않는 자리입니다. 추적이나 CPU 기능 대체 패치의 대상이면 나중에 다른 명령이 들어갈 수 있습니다.
런타임 패치 시 추적 함수 BL로 바뀌는 위치입니다.

### 286행
```asm
#ifdef CONFIG_FUNCTION_GRAPH_TRACER
```
function graph tracer는 함수 진입뿐 아니라 복귀도 기록하여 호출 관계와 수행 시간을 관찰합니다. 켜지면 graph caller를 부를 패치 지점을 포함하고, 꺼지면 일반 함수 진입 추적만 이어집니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.

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

### 288행
```asm
	nop				// If enabled, this will be replaced
```
현재 바이트는 아무 일도 하지 않는 자리입니다. 추적이나 CPU 기능 대체 패치의 대상이면 나중에 다른 명령이 들어갈 수 있습니다.
함수 그래프 추적이 켜지면 ftrace_graph_caller로 향하는 B로 바뀝니다.

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

### 292행
```asm
	mcount_exit
```
추적에 쓰려고 만든 프레임을 해제하고 보관한 복귀 주소로 돌아갑니다. 여기의 인자는 없음입니다.

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

### 295행
```asm
#ifdef CONFIG_FUNCTION_GRAPH_TRACER
```
함수 복귀를 가로채는 graph caller 구현을 빌드합니다. 원래 복귀 주소는 추적 자료에 보존하고 스택의 LR 슬롯을 추적용 handler로 바꾸는 준비가 아래에서 이루어집니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.

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

### 306행
```asm
	mcount_get_pc		  x0	//     function's pc
```
추가 추적 프레임에 저장된 LR에서 대상 함수의 callsite PC를 복원하여 x0에 전달합니다. 현재 x30을 바로 쓰면 직전 추적 호출의 주소와 섞일 수 있습니다. 저장된 LR은 BL 다음 주소이므로 여기서 4바이트를 빼 호출 명령의 위치를 구합니다.

### 307행
```asm
	mcount_get_lr_addr	  x1	//     pointer to function's saved lr
```
대상 함수의 부모 LR이 저장된 슬롯 주소를 x1에 전달합니다. prepare_ftrace_return이 그 슬롯을 복귀 추적 handler로 바꿀 수 있어야 하므로 LR 값 자체가 아니라 주소가 필요합니다. 매크로는 현재 프레임에 저장된 FP를 읽고 8을 더해 LR 슬롯의 주소를 구합니다.

### 308행
```asm
	mcount_get_parent_fp	  x2	//     parent's fp
```
FP 연결을 따라 부모 프레임 포인터를 x2에 전달합니다. prepare_ftrace_return이 함수 진입 정보와 복귀 추적 상태를 올바른 프레임에 연결하는 입력입니다. 매크로 내부에서는 저장된 FP 연결을 두 번 읽어 부모 프레임에 도달합니다.

### 309행
```asm
	bl	prepare_ftrace_return	// prepare_ftrace_return(pc, &lr, fp)
```
prepare_ftrace_return에 대상 PC, 저장된 부모 LR 슬롯의 주소, 부모 FP를 전달합니다. graph 추적이 허용되면 원래 복귀 주소를 보관하고 그 슬롯을 return_to_handler로 바꾸어 함수가 끝날 때도 추적기를 거치게 합니다. BL은 다음 명령의 주소를 LR(x30)에 기록하고 목적지로 분기합니다. 호출된 코드가 x0 같은 작업 레지스터를 바꿀 수 있으므로, 호출 뒤의 값은 그 코드의 반환·보존 규칙에 따릅니다.

### 311행
```asm
	mcount_exit
```
추적에 쓰려고 만든 프레임을 해제하고 보관한 복귀 주소로 돌아갑니다. 여기의 인자는 없음입니다.

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

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

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

## 반환값 보존과 원래 부모로 복귀
원본 315–357행

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

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

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

### 320행
```asm
#ifdef CONFIG_FUNCTION_GRAPH_TRACER
```
function graph tracer가 켜진 빌드에서만 복귀 추적용 stub와 return_to_handler를 포함합니다. 꺼진 경우 함수의 원래 반환 경로를 가로챌 이 코드는 필요 없습니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.

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

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

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

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

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

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

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

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

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

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

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

### 345행
```asm
	bl	ftrace_return_to_handler	// addr = ftrace_return_to_hander(fregs);
```
저장한 ftrace_regs 주소를 x0로 전달해 함수 복귀 사건을 기록하고, 따로 보관했던 원래 부모 복귀 주소를 x0로 돌려받습니다. 추적 대상 함수의 실제 반환값은 스택의 x0~x7 사본에 있으므로 이 반환 주소와 섞이지 않습니다. BL은 다음 명령의 주소를 LR(x30)에 기록하고 목적지로 분기합니다.
반환 x0는 원래 부모 복귀 주소입니다. 추적 대상 함수의 반환값은 스택에 보존되어 있습니다.

### 346행
```asm
	mov	x30, x0				// restore the original return address
```
추적기가 반환한 원래 부모 주소를 LR에 옮깁니다. 뒤에서 실제 함수 반환값을 x0~x7로 되살린 후 RET가 이 LR로 돌아갑니다. MOV는 x30의 기존 값을 대체합니다. 원본이 주소값이어도 그 주소의 메모리를 읽거나 복사하지 않습니다.
그 주소를 LR에 옮긴 다음 x0–x7의 원래 결과 값을 복원합니다.

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

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

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

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

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

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

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

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

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

### 추적 코드에서 x0를 마음대로 바꾸면 왜 안 되나요?
추적 대상 함수가 아직 사용할 인자가 x0에 남아 있을 수 있습니다. 콜백 전에 저장하고 이후 복원해야 합니다.

### RET x9는 이상한 반환인가요?
RET의 목적 레지스터를 명시한 것입니다. 이 경로에서 x9는 추적 뒤 원래 함수의 실행을 계속할 주소입니다.

### 함수 그래프 추적은 반환 시점을 어떻게 잡나요?
함수의 저장된 복귀 주소를 추적 핸들러를 거치도록 바꾸고, 나중에 원래 부모 주소를 찾아 되돌려 줍니다.
