DUJINLABS.COM

Linux v6.18.37 · ARM64

entry-ftrace.S — 함수 진입과 반환을 추적하는 어셈블리

컴파일러가 함수 앞에 넣은 추적 지점에서 레지스터를 보존하고 추적 함수를 호출합니다. CONFIG_DYNAMIC_FTRACE_WITH_ARGS에 따른 새 경로와 전통적인 _mcount 경로가 #else로 나뉩니다.

역할과 입력

  • WITH_ARGS: x9=원래 LR, x30=패치된 BL 다음 주소
  • 콜백 인자: x0=호출 지점, x1=부모 주소, x2=ops, x3=ftrace_regs

원본 .S · 빌드 조건: Makefile · 명령·주소 표기 읽는 법

357줄 · SHA-256: 49fd1b7d14a3bd9ee95a1cd5d7e925a40f76bc22c63375fff6b0ce6c103b3460

구조와 흐름

함수 진입과 반환을 추적하는 어셈블리
화살표: 처리 흐름입니다. 조건이 적힌 단계에서는 해당 조건에 따라 경로가 갈립니다.

추적 지점과 direct call

WITH_ARGS 경로에서 컴파일러의 NOP 두 개가 MOV x9,LR와 BL로 바뀝니다. WITH_CALL_OPS이면 LR을 8바이트 경계로 내린 뒤 16바이트 앞의 ops 포인터를 읽습니다. direct_call이 존재하면 전체 ftrace_regs 저장을 생략하고 x17로 분기합니다.

entry-ftrace.S · L1–L63
1/* SPDX-License-Identifier: GPL-2.0-only */
2/*
3 * arch/arm64/kernel/entry-ftrace.S
4 *
5 * Copyright (C) 2013 Linaro Limited
6 * Author: AKASHI Takahiro <[email protected]>
7 */
8
9#include <linux/linkage.h>
10#include <linux/cfi_types.h>
11#include <asm/asm-offsets.h>
12#include <asm/assembler.h>
13#include <asm/ftrace.h>
14#include <asm/insn.h>
15
16#ifdef CONFIG_DYNAMIC_FTRACE_WITH_ARGS
17/*
18 * Due to -fpatchable-function-entry=2, the compiler has placed two NOPs before
19 * the regular function prologue. For an enabled callsite, ftrace_init_nop() and
20 * ftrace_make_call() have patched those NOPs to:
21 *
22 * 	MOV	X9, LR
23 * 	BL	ftrace_caller
24 *
25 * Each instrumented function follows the AAPCS, so here x0-x8 and x18-x30 are
26 * live (x18 holds the Shadow Call Stack pointer), and x9-x17 are safe to
27 * clobber.
28 *
29 * We save the callsite's context into a struct ftrace_regs before invoking any
30 * ftrace callbacks. So that we can get a sensible backtrace, we create frame
31 * records for the callsite and the ftrace entry assembly. This is not
32 * sufficient for reliable stacktrace: until we create the callsite stack
33 * record, its caller is missing from the LR and existing chain of frame
34 * records.
35 */
36SYM_CODE_START(ftrace_caller)
37	bti	c
38
39#ifdef CONFIG_DYNAMIC_FTRACE_WITH_CALL_OPS
40	/*
41	 * The literal pointer to the ops is at an 8-byte aligned boundary
42	 * which is either 12 or 16 bytes before the BL instruction in the call
43	 * site. See ftrace_call_adjust() for details.
44	 *
45	 * Therefore here the LR points at `literal + 16` or `literal + 20`,
46	 * and we can find the address of the literal in either case by
47	 * aligning to an 8-byte boundary and subtracting 16. We do the
48	 * alignment first as this allows us to fold the subtraction into the
49	 * LDR.
50	 */
51	bic	x11, x30, 0x7
52	ldr	x11, [x11, #-(4 * AARCH64_INSN_SIZE)]		// op
53
54#ifdef CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS
55	/*
56	 * If the op has a direct call, handle it immediately without
57	 * saving/restoring registers.
58	 */
59	ldr	x17, [x11, #FTRACE_OPS_DIRECT_CALL]		// op->direct_call
60	cbnz	x17, ftrace_caller_direct
61#endif
62#endif
63
L9
SYM_FUNC_START·SYM_CODE_START·SYM_INNER_LABEL 등의 심볼 선언 규칙을 가져옵니다. 함수인지 코드 내부 라벨인지, 다른 오브젝트에 보일 이름인지와 정렬·크기 정보를 빌드 도구에 전달하는 데 필요합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.
L10
간접 호출 대상의 함수 형식을 검사하는 CFI용 어셈블리 심볼 표기를 가져옵니다. 추적 callback과 stub가 빌드의 제어 흐름 보호 규약에 맞는 정보를 갖도록 합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.
L11
C 구조체에서 계산한 필드 오프셋을 가져옵니다. 어셈블리가 task_struct·pt_regs 등에서 값을 읽고 쓸 때 C 빌드의 실제 배치와 일치하도록 생성하는 헤더입니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.
L12
ARM64의 큰 주소 구성, CPU별 값 조회, 시스템 레지스터 처리, 동기화 등에 쓰는 어셈블리 매크로를 가져옵니다. 각 사용 위치에서 일반 명령 여러 개로 펼쳐지는 작업입니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.
L13
ARM64 ftrace의 저장 레지스터 형식과 호출 규약에 관한 정의를 가져옵니다. 추적 callback과 어셈블리 trampoline이 같은 프레임 크기·의미를 사용하게 합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.
L14
AArch64 명령의 크기·인코딩 관련 정의를 가져옵니다. 계측 BL 다음 주소에서 실제 callsite를 구할 때 명령 한 개의 크기만큼 보정하는 근거입니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.
L16
함수 인수도 추적기에 제공하는 동적 ftrace 구현을 선택합니다. 켜지면 x0~x8과 SP·FP·LR 등의 ftrace_regs를 만들어 callback에 넘기고, 꺼지면 아래 #else의 전통적인 _mcount/frame 기반 경로를 사용합니다. 추적을 실제로 켜는 런타임 설정과 빌드 기능을 구분해야 합니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.
L36
(ftrace_caller)의 시작 심볼을 정의합니다. 외부에서 참조할 수 있습니다. 정의에 따른 정렬을 적용합니다. CODE 시작은 BTI나 프레임을 자동 생성하지 않습니다.
L37
BTI c: 간접 호출이 도착할 수 있는 위치를 표시합니다. 보호의 실제 적용은 CPU와 매핑 설정에 따릅니다.
L39
호출 지점마다 연결된 ftrace_ops를 읽을 수 있는 구성을 선택합니다. 켜지면 callsite 앞의 데이터에서 op를 얻어 그 op의 callback을 직접 고르고, 꺼지면 공통 function_trace_op와 패치 가능한 ftrace_call 지점을 사용합니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.
L51
ftrace 호출 뒤의 LR을 8바이트 경계로 내려 callsite 앞에 둔 op 포인터 슬롯을 찾을 기준을 만듭니다. 임의로 함수 주소의 하위 비트를 지우는 보안 검사는 아닙니다. BIC는 마스크에서 1인 위치만 0으로 만들고 나머지 비트는 유지합니다.
L52
정렬 기준에서 네 개 AArch64 명령 크기만큼 앞에 둔 ftrace_ops 포인터를 읽습니다. CALL_OPS가 정의한 callsite 배치와 맞아야 callback을 올바르게 찾을 수 있습니다. 실제로 x11 + (-(4 * AARCH64_INSN_SIZE)) 주소의 메모리 8바이트를 x11에 읽습니다.
L54
direct-call 구성이면 일반 ftrace callback을 거치지 않고 지정된 trampoline으로 보낼 수 있습니다. 켜진 빌드에서 op->direct_call을 확인하고 0이 아닐 때 직접 경로를 선택하며, 꺼지면 이 빠른 선택 코드는 없습니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.
L59
이 callsite의 op가 지정한 direct trampoline 주소를 x17에 읽습니다. 값 0은 이 op에 직접 보낼 목적지가 없음을 뜻합니다. 실제로 x11 + (FTRACE_OPS_DIRECT_CALL) 주소의 메모리 8바이트를 x17에 읽습니다. 이 주소 형식은 기준 레지스터 x11 자체를 갱신하지 않습니다.
L60
direct trampoline 주소가 있으면 아직 공통 저장 프레임을 만들기 전에 직접 경로로 갑니다. 0이면 일반 ftrace_regs를 만드는 처리로 이어집니다. 이 분기는 지정 레지스터의 값이나 비트를 직접 검사하며 CMP가 남긴 NZCV를 읽거나 바꾸지 않습니다. 조건이 맞지 않으면 바로 다음 명령으로 진행합니다.
L61
— 전처리 조건 구간을 끝냅니다.
L62
— 전처리 조건 구간을 끝냅니다.

ftrace_regs와 두 프레임

원래 SP를 x10에 보존한 후 FREGS_SIZE+32바이트를 확보합니다. 인자 x0–x8, 원래 FP·LR·SP, BL 이후 PC를 저장합니다. 추가 32바이트는 호출 지점과 ftrace 자체의 프레임 두 개입니다. 콜백에는 BL 주소, 원래 LR, ops, regs를 전달합니다.

entry-ftrace.S · L64–L112
64	/* Save original SP */
65	mov	x10, sp
66
67	/* Make room for ftrace regs, plus two frame records */
68	sub	sp, sp, #(FREGS_SIZE + 32)
69
70	/* Save function arguments */
71	stp	x0, x1, [sp, #FREGS_X0]
72	stp	x2, x3, [sp, #FREGS_X2]
73	stp	x4, x5, [sp, #FREGS_X4]
74	stp	x6, x7, [sp, #FREGS_X6]
75	str	x8,     [sp, #FREGS_X8]
76
77#ifdef CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS
78	str	xzr, [sp, #FREGS_DIRECT_TRAMP]
79#endif
80
81	/* Save the callsite's FP, LR, SP */
82	str	x29, [sp, #FREGS_FP]
83	str	x9,  [sp, #FREGS_LR]
84	str	x10, [sp, #FREGS_SP]
85
86	/* Save the PC after the ftrace callsite */
87	str	x30, [sp, #FREGS_PC]
88
89	/* Create a frame record for the callsite above the ftrace regs */
90	stp	x29, x9, [sp, #FREGS_SIZE + 16]
91	add	x29, sp, #FREGS_SIZE + 16
92
93	/* Create our frame record above the ftrace regs */
94	stp	x29, x30, [sp, #FREGS_SIZE]
95	add	x29, sp, #FREGS_SIZE
96
97	/* Prepare arguments for the the tracer func */
98	sub	x0, x30, #AARCH64_INSN_SIZE		// ip (callsite's BL insn)
99	mov	x1, x9					// parent_ip (callsite's LR)
100	mov	x3, sp					// regs
101
102#ifdef CONFIG_DYNAMIC_FTRACE_WITH_CALL_OPS
103	mov	x2, x11					// op
104	ldr	x4, [x2, #FTRACE_OPS_FUNC]		// op->func
105	blr	x4					// op->func(ip, parent_ip, op, regs)
106
107#else
108	ldr_l   x2, function_trace_op			// op
109
110SYM_INNER_LABEL(ftrace_call, SYM_L_GLOBAL)
111	bl      ftrace_stub				// func(ip, parent_ip, op, regs)
112#endif
L65
x10 ← sp. 값을 복사합니다. NZCV는 유지합니다.
L68
함수 인수와 추적 상태를 담을 FREGS_SIZE에 unwind용 프레임 두 개 32바이트를 더해 확보합니다. 추적 callback이 원래 함수 인수를 덮어써도 되돌릴 수 있게 할 공간입니다. 이 명령은 SP의 주소값을 조정하며, 확보하거나 반환하는 스택 공간의 데이터를 직접 저장하거나 지우지는 않습니다. 이 SUB 형식은 NZCV 조건 플래그를 바꾸지 않습니다.
L71
x0, x1의 값을 [sp, #FREGS_X0]에 16바이트 저장합니다. 기준 주소 레지스터는 바꾸지 않습니다.
L72
x2, x3의 값을 [sp, #FREGS_X2]에 16바이트 저장합니다. 기준 주소 레지스터는 바꾸지 않습니다.
L73
x4, x5의 값을 [sp, #FREGS_X4]에 16바이트 저장합니다. 기준 주소 레지스터는 바꾸지 않습니다.
L74
x6, x7의 값을 [sp, #FREGS_X6]에 16바이트 저장합니다. 기준 주소 레지스터는 바꾸지 않습니다.
L75
x8의 값을 [sp, #FREGS_X8]에 8바이트 저장합니다. 기준 주소 레지스터는 바꾸지 않습니다.
L77
direct-call을 지원하는 빌드에는 ftrace_regs 안의 direct_tramp 슬롯을 준비합니다. 일반 callback이 뒤에서 직접 trampoline을 선택할 가능성을 표현하는 필드이며 미지원 빌드에는 이 경로가 없습니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.
L78
아직 선택된 direct trampoline이 없다는 뜻으로 direct_tramp를 0으로 초기화합니다. callback 후 이 값이 채워졌는지 검사하는 늦은 direct 경로와 연결됩니다. 실제로 sp + (FREGS_DIRECT_TRAMP) 주소의 메모리 8바이트를 xzr의 값으로 씁니다. 이 주소 형식은 기준 레지스터 sp 자체를 갱신하지 않습니다.
L79
— 전처리 조건 구간을 끝냅니다.
L82
x29의 값을 [sp, #FREGS_FP]에 8바이트 저장합니다. 기준 주소 레지스터는 바꾸지 않습니다.
L83
x9의 값을 [sp, #FREGS_LR]에 8바이트 저장합니다. 기준 주소 레지스터는 바꾸지 않습니다. x9에는 BL 전에 MOV x9,LR로 보존한 추적 대상 함수의 원래 LR이 있습니다.
L84
x10의 값을 [sp, #FREGS_SP]에 8바이트 저장합니다. 기준 주소 레지스터는 바꾸지 않습니다.
L87
ftrace로 들어오기 직전의 LR, 즉 추적 대상 함수의 callsite 다음 위치를 PC 필드에 저장합니다. 실제 부모에게 돌아갈 주소 x9를 보관한 FREGS_LR과 구분합니다. 실제로 sp + (FREGS_PC) 주소의 메모리 8바이트를 x30의 값으로 씁니다. 이 주소 형식은 기준 레지스터 sp 자체를 갱신하지 않습니다.
L90
x29, x9의 값을 [sp, #FREGS_SIZE + 16]에 16바이트 저장합니다. 기준 주소 레지스터는 바꾸지 않습니다.
L91
x29 ← sp + #FREGS_SIZE + 16. NZCV는 유지합니다.
L94
x29, x30의 값을 [sp, #FREGS_SIZE]에 16바이트 저장합니다. 기준 주소 레지스터는 바꾸지 않습니다.
L95
x29 ← sp + #FREGS_SIZE. NZCV는 유지합니다.
L98
BL 다음 주소인 x30에서 명령 크기 4바이트를 빼 실제 ftrace callsite의 주소 ip를 첫 인수 x0로 만듭니다. 이 SUB 형식은 NZCV 조건 플래그를 바꾸지 않습니다.
L99
추적 대상 함수가 부모로 돌아갈 주소 x9를 parent_ip 인수 x1로 넘깁니다. ftrace 내부에서 돌아갈 LR과 다른 주소입니다. MOV는 x1의 기존 값을 대체합니다. 원본이 주소값이어도 그 주소의 메모리를 읽거나 복사하지 않습니다.
L100
현재 SP가 가리키는 ftrace_regs 저장 영역을 네 번째 인수 x3로 넘겨 callback이 함수 인수·문맥을 확인할 수 있게 합니다. x3에 담기는 것은 SP의 주소값이며, SP와 스택 메모리의 내용은 그대로입니다.
L102
CALL_OPS가 켜져 있으면 이미 callsite에서 얻은 op와 op->func를 사용합니다. 꺼진 빌드는 다음 #else에서 공통 op 및 패치 가능한 호출을 사용합니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.
L103
x2 ← x11. 값을 복사합니다. NZCV는 유지합니다.
L104
ftrace_ops의 func 필드에서 실제 callback 주소를 꺼냅니다. 단순한 코드 상수가 아니라 op마다 다른 함수 포인터입니다. 실제로 x2 + (FTRACE_OPS_FUNC) 주소의 메모리 8바이트를 x4에 읽습니다. 이 주소 형식은 기준 레지스터 x2 자체를 갱신하지 않습니다.
L105
op->func(ip, parent_ip, op, regs)를 호출합니다. x0~x3는 앞서 준비한 추적 위치·부모 주소·op·저장 레지스터이며, 원래 대상 함수의 인수는 스택에 보존되어 callback 뒤에 복원합니다. BLR은 다음 명령의 주소를 LR(x30)에 기록하고 레지스터에 든 목적지로 분기합니다.
L107
callsite별 op를 쓰지 않는 구성에서는 공통 function_trace_op를 로드하고 ftrace_call의 명령을 패치하는 경로를 사용합니다. ftrace 전체가 꺼진다는 뜻은 아닙니다.
L108
현재 추적 작업 객체 function_trace_op의 값을 세 번째 인수 x2로 읽습니다. callback이 어느 ftrace_ops 설정으로 호출되었는지 알 수 있게 하며, 변수의 주소 자체를 넘기는 것이 아닙니다. ldr_l은 ADRP로 심벌이 속한 페이지를 구한 뒤 LDR로 그 위치의 값을 읽는 매크로입니다. 주소를 구하는 adr_l과 달리 실제 메모리 접근이 있습니다.
L110
(ftrace_call, SYM_L_GLOBAL): 함수 안의 심볼을 정의합니다. 두 번째 인자가 외부 공개 여부를 결정하며 별도 함수 프레임·RET·BTI를 만들지 않습니다.
L111
초기에는 아무 일도 하지 않는 ftrace_stub로 호출하지만 ftrace_call 지점은 실행 중 추적 callback으로 패치될 수 있습니다. 인수는 ip, parent_ip, op, ftrace_regs이며 추적이 끝나면 저장한 대상 함수 인수를 복구합니다. BL은 다음 명령의 주소를 LR(x30)에 기록하고 목적지로 분기합니다. 호출된 코드가 x0 같은 작업 레지스터를 바꿀 수 있으므로, 호출 뒤의 값은 그 코드의 반환·보존 규칙에 따릅니다.
L112
— 전처리 조건 구간을 끝냅니다.

복원과 direct trampoline

정상 경로는 x30에 원래 LR을 복구하고 x9에는 함수 본문을 계속할 PC를 읽어 RET x9를 실행합니다. direct_late는 추적 콜백을 거친 뒤 x9=원래 LR, x30=재개 PC라는 별도 규약으로 복원합니다. BR x17은 LR을 다시 덮어쓰지 않습니다. stub trampoline은 두 주소를 임시 x10을 이용해 정리합니다.

entry-ftrace.S · L113–L178
113
114/*
115 * At the callsite x0-x8 and x19-x30 were live. Any C code will have preserved
116 * x19-x29 per the AAPCS, and we created frame records upon entry, so we need
117 * to restore x0-x8, x29, and x30.
118 */
119	/* Restore function arguments */
120	ldp	x0, x1, [sp, #FREGS_X0]
121	ldp	x2, x3, [sp, #FREGS_X2]
122	ldp	x4, x5, [sp, #FREGS_X4]
123	ldp	x6, x7, [sp, #FREGS_X6]
124	ldr	x8,     [sp, #FREGS_X8]
125
126	/* Restore the callsite's FP */
127	ldr	x29, [sp, #FREGS_FP]
128
129#ifdef CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS
130	ldr	x17, [sp, #FREGS_DIRECT_TRAMP]
131	cbnz	x17, ftrace_caller_direct_late
132#endif
133
134	/* Restore the callsite's LR and PC */
135	ldr	x30, [sp, #FREGS_LR]
136	ldr	x9,  [sp, #FREGS_PC]
137
138	/* Restore the callsite's SP */
139	add	sp, sp, #FREGS_SIZE + 32
140
141	ret	x9
142
143#ifdef CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS
144SYM_INNER_LABEL(ftrace_caller_direct_late, SYM_L_LOCAL)
145	/*
146	 * Head to a direct trampoline in x17 after having run other tracers.
147	 * The ftrace_regs are live, and x0-x8 and FP have been restored. The
148	 * LR, PC, and SP have not been restored.
149	 */
150
151	/*
152	 * Restore the callsite's LR and PC matching the trampoline calling
153	 * convention.
154	 */
155	ldr	x9,  [sp, #FREGS_LR]
156	ldr	x30, [sp, #FREGS_PC]
157
158	/* Restore the callsite's SP */
159	add	sp, sp, #FREGS_SIZE + 32
160
161SYM_INNER_LABEL(ftrace_caller_direct, SYM_L_LOCAL)
162	/*
163	 * Head to a direct trampoline in x17.
164	 *
165	 * We use `BR X17` as this can safely land on a `BTI C` or `PACIASP` in
166	 * the trampoline, and will not unbalance any return stack.
167	 */
168	br	x17
169#endif /* CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS */
170SYM_CODE_END(ftrace_caller)
171
172#ifdef CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS
173SYM_CODE_START(ftrace_stub_direct_tramp)
174	bti	c
175	mov	x10, x30
176	mov	x30, x9
177	ret	x10
178SYM_CODE_END(ftrace_stub_direct_tramp)
L120
[sp, #FREGS_X0]에서 16바이트를 읽어 x0, x1에 넣습니다. 기준 주소 레지스터는 바꾸지 않습니다.
L121
[sp, #FREGS_X2]에서 16바이트를 읽어 x2, x3에 넣습니다. 기준 주소 레지스터는 바꾸지 않습니다.
L122
[sp, #FREGS_X4]에서 16바이트를 읽어 x4, x5에 넣습니다. 기준 주소 레지스터는 바꾸지 않습니다.
L123
[sp, #FREGS_X6]에서 16바이트를 읽어 x6, x7에 넣습니다. 기준 주소 레지스터는 바꾸지 않습니다.
L124
[sp, #FREGS_X8]에서 8바이트를 읽어 x8에 넣습니다. 기준 주소 레지스터는 바꾸지 않습니다.
L127
[sp, #FREGS_FP]에서 8바이트를 읽어 x29에 넣습니다. 기준 주소 레지스터는 바꾸지 않습니다.
L129
direct-call 지원 빌드에서 일반 callback이 ftrace_regs.direct_tramp를 설정했는지 검사하는 코드를 포함합니다. 초기에 direct 목적지가 없었더라도 callback 후 늦게 경로가 선택될 수 있습니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.
L130
callback이 지정했을 수 있는 direct trampoline 주소를 x17에 읽습니다. 이 슬롯을 앞에서 0으로 초기화했기 때문에 0은 일반 복귀를 뜻합니다. 실제로 sp + (FREGS_DIRECT_TRAMP) 주소의 메모리 8바이트를 x17에 읽습니다. 이 주소 형식은 기준 레지스터 sp 자체를 갱신하지 않습니다.
L131
callback 뒤에 direct 목적지가 생겼으면 이미 만든 저장 프레임을 정리하는 late 경로로 갑니다. 바로 초기 direct 경로로 가면 SP·LR 배치가 맞지 않습니다. 이 분기는 지정 레지스터의 값이나 비트를 직접 검사하며 CMP가 남긴 NZCV를 읽거나 바꾸지 않습니다. 조건이 맞지 않으면 바로 다음 명령으로 진행합니다.
L132
— 전처리 조건 구간을 끝냅니다.
L135
[sp, #FREGS_LR]에서 8바이트를 읽어 x30에 넣습니다. 기준 주소 레지스터는 바꾸지 않습니다. 원래 함수가 나중에 부모에게 돌아갈 LR을 복원합니다.
L136
[sp, #FREGS_PC]에서 8바이트를 읽어 x9에 넣습니다. 기준 주소 레지스터는 바꾸지 않습니다. 추적을 끝내고 즉시 계속할 위치는 별도 x9에 읽습니다.
L139
sp ← sp + #FREGS_SIZE + 32. NZCV는 유지합니다. SP 감소는 낮은 주소 쪽의 스택 공간 확보, 증가는 해제에 해당합니다.
L141
x9에 든 주소로 이동합니다. SPSR/PSTATE를 복원하는 ERET와는 다릅니다. X30의 부모 주소가 아니라 X9의 함수 본문 재개 주소로 이동합니다.
L143
늦은 direct-call 전환이 필요한 빌드에만 아래 프레임 해제·LR 재배치 경로를 생성합니다. 일반 callback을 호출한 뒤의 상태를 direct trampoline의 호출 규칙에 맞춥니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.
L144
(ftrace_caller_direct_late, SYM_L_LOCAL): 함수 안의 심볼을 정의합니다. 두 번째 인자가 외부 공개 여부를 결정하며 별도 함수 프레임·RET·BTI를 만들지 않습니다.
L155
[sp, #FREGS_LR]에서 8바이트를 읽어 x9에 넣습니다. 기준 주소 레지스터는 바꾸지 않습니다.
L156
[sp, #FREGS_PC]에서 8바이트를 읽어 x30에 넣습니다. 기준 주소 레지스터는 바꾸지 않습니다.
L159
sp ← sp + #FREGS_SIZE + 32. NZCV는 유지합니다. SP 감소는 낮은 주소 쪽의 스택 공간 확보, 증가는 해제에 해당합니다.
L161
(ftrace_caller_direct, SYM_L_LOCAL): 함수 안의 심볼을 정의합니다. 두 번째 인자가 외부 공개 여부를 결정하며 별도 함수 프레임·RET·BTI를 만들지 않습니다.
L168
x17의 direct trampoline으로 새 LR을 만들지 않고 이동합니다. 앞서 x9와 x30을 이 경로의 규칙대로 준비하여 trampoline이 대상 함수와 부모의 복귀 주소를 구분할 수 있게 합니다. 레지스터가 담은 주소로 실행 위치를 바꾸며, 그 주소에서 값을 읽어 다른 레지스터에 복사하는 load 명령은 아닙니다.
L169
— 전처리 조건 구간을 끝냅니다.
L170
(ftrace_caller)의 심볼 타입과 크기를 기록합니다. 실행을 끝내는 RET를 생성하지 않습니다.
L172
direct-call 지원 빌드에는 특별한 작업 없이 정상 복귀만 이어 주는 stub trampoline도 포함합니다. 아래에서 x30과 x9의 역할을 교환해 direct 호출 규칙을 유지합니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.
L173
(ftrace_stub_direct_tramp)의 시작 심볼을 정의합니다. 외부에서 참조할 수 있습니다. 정의에 따른 정렬을 적용합니다. CODE 시작은 BTI나 프레임을 자동 생성하지 않습니다.
L174
BTI c: 간접 호출이 도착할 수 있는 위치를 표시합니다. 보호의 실제 적용은 CPU와 매핑 설정에 따릅니다.
L175
x10 ← x30. 값을 복사합니다. NZCV는 유지합니다.
L176
x30 ← x9. 값을 복사합니다. NZCV는 유지합니다.
L177
x10에 든 주소로 이동합니다. SPSR/PSTATE를 복원하는 ERET와는 다릅니다.
L178
(ftrace_stub_direct_tramp)의 심볼 타입과 크기를 기록합니다. 실행을 끝내는 RET를 생성하지 않습니다.

전통적인 _mcount 프레임 매크로

mcount_enter는 16바이트 FP/LR 프레임을 만듭니다. get_pc는 저장된 LR에서 명령 크기 4를 빼 호출 위치를 계산하고, get_lr_addr는 추적 대상 함수의 저장 LR 슬롯 주소를 구합니다. 메모리에서 읽은 FP와 LR의 역할을 구분해야 그래프 추적의 주소 교체가 보입니다.

entry-ftrace.S · L179–L254
179#endif /* CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS */
180
181#else /* CONFIG_DYNAMIC_FTRACE_WITH_ARGS */
182
183/*
184 * Gcc with -pg will put the following code in the beginning of each function:
185 *      mov x0, x30
186 *      bl _mcount
187 *	[function's body ...]
188 * "bl _mcount" may be replaced to "bl ftrace_caller" or NOP if dynamic
189 * ftrace is enabled.
190 *
191 * Please note that x0 as an argument will not be used here because we can
192 * get lr(x30) of instrumented function at any time by winding up call stack
193 * as long as the kernel is compiled without -fomit-frame-pointer.
194 * (or CONFIG_FRAME_POINTER, this is forced on arm64)
195 *
196 * stack layout after mcount_enter in _mcount():
197 *
198 * current sp/fp =>  0:+-----+
199 * in _mcount()        | x29 | -> instrumented function's fp
200 *                     +-----+
201 *                     | x30 | -> _mcount()'s lr (= instrumented function's pc)
202 * old sp       => +16:+-----+
203 * when instrumented   |     |
204 * function calls      | ... |
205 * _mcount()           |     |
206 *                     |     |
207 * instrumented => +xx:+-----+
208 * function's fp       | x29 | -> parent's fp
209 *                     +-----+
210 *                     | x30 | -> instrumented function's lr (= parent's pc)
211 *                     +-----+
212 *                     | ... |
213 */
214
215	.macro mcount_enter
216	stp	x29, x30, [sp, #-16]!
217	mov	x29, sp
218	.endm
219
220	.macro mcount_exit
221	ldp	x29, x30, [sp], #16
222	ret
223	.endm
224
225	.macro mcount_adjust_addr rd, rn
226	sub	\rd, \rn, #AARCH64_INSN_SIZE
227	.endm
228
229	/* for instrumented function's parent */
230	.macro mcount_get_parent_fp reg
231	ldr	\reg, [x29]
232	ldr	\reg, [\reg]
233	.endm
234
235	/* for instrumented function */
236	.macro mcount_get_pc0 reg
237	mcount_adjust_addr	\reg, x30
238	.endm
239
240	.macro mcount_get_pc reg
241	ldr	\reg, [x29, #8]
242	mcount_adjust_addr	\reg, \reg
243	.endm
244
245	.macro mcount_get_lr reg
246	ldr	\reg, [x29]
247	ldr	\reg, [\reg, #8]
248	.endm
249
250	.macro mcount_get_lr_addr reg
251	ldr	\reg, [x29]
252	add	\reg, \reg, #8
253	.endm
254
L179
— 전처리 조건 구간을 끝냅니다.
L181
인수 전달형 동적 ftrace를 사용하지 않는 빌드는 기존 _mcount/frame 기반 구현을 만듭니다. 아래 코드는 저장 프레임을 따라 함수·부모 주소를 구하며 위의 ftrace_regs 구성과 섞어 읽으면 안 됩니다.
L215
추적 코드가 C 함수를 호출해도 원래 FP와 LR을 잃지 않도록 16바이트 프레임을 만드는 매크로입니다. x29를 새 프레임의 기준으로 삼아 뒤의 부모 프레임 조회가 가능해집니다. 이 줄은 어셈블러 매크로 정의의 시작입니다. 본문은 매크로를 사용하는 위치에 펼쳐지며, 정의 자체가 런타임 함수 호출은 아닙니다.
L216
x29, x30의 값을 [sp, #-16]!에 16바이트 저장합니다. 대괄호 안의 오프셋을 기준 레지스터에 먼저 반영한 뒤 접근합니다.
L217
x29 ← sp. 값을 복사합니다. NZCV는 유지합니다.
L218
— 매크로 정의를 끝냅니다.
L220
mcount_enter가 만든 프레임에서 FP·LR을 되돌리고 SP를 16바이트 복구한 뒤 반환하는 매크로입니다. 진입과 대칭이어야 대상 함수의 스택이 유지됩니다. 이 줄은 어셈블러 매크로 정의의 시작입니다. 본문은 매크로를 사용하는 위치에 펼쳐지며, 정의 자체가 런타임 함수 호출은 아닙니다.
L221
[sp], #16에서 16바이트를 읽어 x29, x30에 넣습니다. 접근 후 대괄호 뒤의 값만큼 기준 레지스터를 갱신합니다.
L222
x30(LR)에 든 주소로 이동합니다. SPSR/PSTATE를 복원하는 ERET와는 다릅니다.
L223
— 매크로 정의를 끝냅니다.
L225
BL이 LR에 남긴 다음 명령 주소 rn에서 AArch64 명령 크기 4를 빼 호출 명령 자체의 주소를 rd에 만듭니다. 추적 로그에 callsite를 정확히 기록하기 위한 주소 보정입니다. 이 줄은 어셈블러 매크로 정의의 시작입니다. 본문은 매크로를 사용하는 위치에 펼쳐지며, 정의 자체가 런타임 함수 호출은 아닙니다.
L226
\rd ← \rn − #AARCH64_INSN_SIZE. NZCV는 유지합니다.
L227
— 매크로 정의를 끝냅니다.
L230
현재 mcount 프레임과 그 호출 프레임의 FP 연결을 두 번 따라 부모 함수의 FP를 구합니다. 두 load는 같은 값을 중복 읽는 것이 아니라 한 단계씩 스택 프레임을 거슬러 갑니다. 이 줄은 어셈블러 매크로 정의의 시작입니다. 본문은 매크로를 사용하는 위치에 펼쳐지며, 정의 자체가 런타임 함수 호출은 아닙니다.
L231
[x29]에서 8바이트를 읽어 \reg에 넣습니다. 기준 주소 레지스터는 바꾸지 않습니다.
L232
[\reg]에서 8바이트를 읽어 \reg에 넣습니다. 기준 주소 레지스터는 바꾸지 않습니다.
L233
— 매크로 정의를 끝냅니다.
L236
새 추적 프레임을 만들기 전의 x30에서 4를 빼 대상 함수의 ftrace callsite 주소를 구합니다. 입력 위치가 이미 스택에 저장된 PC를 읽는 아래 매크로와 다릅니다. 이 줄은 어셈블러 매크로 정의의 시작입니다. 본문은 매크로를 사용하는 위치에 펼쳐지며, 정의 자체가 런타임 함수 호출은 아닙니다.
L237
현재 LR에서 4를 빼 ftrace 호출 명령의 주소를 만듭니다. 추적기에 전달할 PC는 BL 다음 복귀 위치가 아니라 계측된 callsite여야 합니다.
L238
— 매크로 정의를 끝냅니다.
L240
mcount 프레임에 저장된 LR을 읽고 4를 빼 callsite PC로 만듭니다. 추가 호출로 현재 x30이 바뀐 경우에도 저장된 주소를 사용합니다. 이 줄은 어셈블러 매크로 정의의 시작입니다. 본문은 매크로를 사용하는 위치에 펼쳐지며, 정의 자체가 런타임 함수 호출은 아닙니다.
L241
[x29, #8]에서 8바이트를 읽어 \reg에 넣습니다. 기준 주소 레지스터는 바꾸지 않습니다.
L242
프레임에서 읽은 LR에서 4를 빼 원래 계측 callsite를 구합니다. 현재 LR은 다른 호출로 바뀔 수 있으므로 저장 사본을 기준으로 합니다.
L243
— 매크로 정의를 끝냅니다.
L245
이전 프레임의 LR 슬롯을 읽어 추적 대상 함수가 부모로 돌아갈 주소를 구합니다. 함수 시작 주소나 현재 PC를 구하는 매크로가 아닙니다. 이 줄은 어셈블러 매크로 정의의 시작입니다. 본문은 매크로를 사용하는 위치에 펼쳐지며, 정의 자체가 런타임 함수 호출은 아닙니다.
L246
[x29]에서 8바이트를 읽어 \reg에 넣습니다. 기준 주소 레지스터는 바꾸지 않습니다.
L247
[\reg, #8]에서 8바이트를 읽어 \reg에 넣습니다. 기준 주소 레지스터는 바꾸지 않습니다.
L248
— 매크로 정의를 끝냅니다.
L250
이전 프레임에서 LR 값 자체가 아니라 LR이 저장된 메모리 주소를 구합니다. function graph tracer가 이 슬롯을 return_to_handler로 바꿀 수 있게 하기 위한 인수입니다. 이 줄은 어셈블러 매크로 정의의 시작입니다. 본문은 매크로를 사용하는 위치에 펼쳐지며, 정의 자체가 런타임 함수 호출은 아닙니다.
L251
[x29]에서 8바이트를 읽어 \reg에 넣습니다. 기준 주소 레지스터는 바꾸지 않습니다.
L252
\reg ← \reg + #8. NZCV는 유지합니다.
L253
— 매크로 정의를 끝냅니다.

패치 위치와 함수 그래프 진입

_mcount 자체는 RET입니다. 동적 추적이 켜지면 호출 지점이 ftrace_caller를 향합니다. ftrace_call과 ftrace_graph_call의 NOP는 패치할 위치를 나타냅니다. prepare_ftrace_return은 저장 LR 주소를 받아 나중에 return_to_handler를 거치도록 준비합니다.

entry-ftrace.S · L255–L314
255/*
256 * _mcount() is used to build the kernel with -pg option, but all the branch
257 * instructions to _mcount() are replaced to NOP initially at kernel start up,
258 * and later on, NOP to branch to ftrace_caller() when enabled or branch to
259 * NOP when disabled per-function base.
260 */
261SYM_FUNC_START(_mcount)
262	ret
263SYM_FUNC_END(_mcount)
264EXPORT_SYMBOL(_mcount)
265NOKPROBE(_mcount)
266
267/*
268 * void ftrace_caller(unsigned long return_address)
269 * @return_address: return address to instrumented function
270 *
271 * This function is a counterpart of _mcount() in 'static' ftrace, and
272 * makes calls to:
273 *     - tracer function to probe instrumented function's entry,
274 *     - ftrace_graph_caller to set up an exit hook
275 */
276SYM_FUNC_START(ftrace_caller)
277	mcount_enter
278
279	mcount_get_pc0	x0		//     function's pc
280	mcount_get_lr	x1		//     function's lr
281
282SYM_INNER_LABEL(ftrace_call, SYM_L_GLOBAL)	// tracer(pc, lr);
283	nop				// This will be replaced with "bl xxx"
284					// where xxx can be any kind of tracer.
285
286#ifdef CONFIG_FUNCTION_GRAPH_TRACER
287SYM_INNER_LABEL(ftrace_graph_call, SYM_L_GLOBAL) // ftrace_graph_caller();
288	nop				// If enabled, this will be replaced
289					// "b ftrace_graph_caller"
290#endif
291
292	mcount_exit
293SYM_FUNC_END(ftrace_caller)
294
295#ifdef CONFIG_FUNCTION_GRAPH_TRACER
296/*
297 * void ftrace_graph_caller(void)
298 *
299 * Called from _mcount() or ftrace_caller() when function_graph tracer is
300 * selected.
301 * This function w/ prepare_ftrace_return() fakes link register's value on
302 * the call stack in order to intercept instrumented function's return path
303 * and run return_to_handler() later on its exit.
304 */
305SYM_FUNC_START(ftrace_graph_caller)
306	mcount_get_pc		  x0	//     function's pc
307	mcount_get_lr_addr	  x1	//     pointer to function's saved lr
308	mcount_get_parent_fp	  x2	//     parent's fp
309	bl	prepare_ftrace_return	// prepare_ftrace_return(pc, &lr, fp)
310
311	mcount_exit
312SYM_FUNC_END(ftrace_graph_caller)
313#endif /* CONFIG_FUNCTION_GRAPH_TRACER */
314#endif /* CONFIG_DYNAMIC_FTRACE_WITH_ARGS */
L261
(_mcount)의 시작 심볼을 정의합니다. 외부에서 참조할 수 있습니다. 정의에 따른 정렬을 적용합니다. ARM64 함수 시작에는 BTI C가 들어갑니다.
L262
x30(LR)에 든 주소로 이동합니다. SPSR/PSTATE를 복원하는 ERET와는 다릅니다.
L263
(_mcount)의 심볼 타입과 크기를 기록합니다. 실행을 끝내는 RET를 생성하지 않습니다.
L264
계측된 커널 모듈도 _mcount 진입점을 연결할 수 있도록 커널 export 정보를 만듭니다. 모듈의 함수 추적 호출이 이 공통 구현을 참조하기 위한 조치입니다. 빌드할 때 심벌을 가리키는 메타데이터를 만드는 표기이며, 이 위치에서 함수를 호출하는 명령은 아닙니다.
L265
_mcount을 kprobe 설치 금지 목록에 넣습니다. 추적기 자체에 probe를 걸면 재귀 계측으로 이어질 수 있어 이 저수준 추적 진입을 보호합니다. CONFIG_KPROBES가 꺼지면 이 목록 항목도 필요 없습니다. 빌드할 때 심벌을 가리키는 메타데이터를 만드는 표기이며, 이 위치에서 함수를 호출하는 명령은 아닙니다.
L276
(ftrace_caller)의 시작 심볼을 정의합니다. 외부에서 참조할 수 있습니다. 정의에 따른 정렬을 적용합니다. ARM64 함수 시작에는 BTI C가 들어갑니다.
L277
SP를 16 감소시키고 FP/LR을 저장한 뒤 FP를 현재 SP로 설정합니다. 인자: 없음.
L279
추적 대상 함수의 callsite PC를 첫 인수 x0에 만듭니다. mcount_enter를 하기 전이므로 현재 x30에서 4를 빼면 원래 BL 위치를 얻습니다.
L280
스택 프레임에 보존된 부모 복귀 주소를 두 번째 인수 x1에 읽습니다. 첫 인수의 계측 위치와 함께 어느 함수 호출 관계인지 추적 callback에 전달합니다. 먼저 현재 프레임에 저장된 FP를 읽고, 그 FP의 +8 위치에서 부모에게 돌아갈 LR을 읽습니다.
L282
(ftrace_call, SYM_L_GLOBAL): 함수 안의 심볼을 정의합니다. 두 번째 인자가 외부 공개 여부를 결정하며 별도 함수 프레임·RET·BTI를 만들지 않습니다.
L283
아무 작업 없이 다음 명령으로 진행합니다. 이 위치가 alternative/ftrace 패치 대상이면 이후 다른 명령으로 바뀔 수 있습니다. 런타임 패치 시 추적 함수 BL로 바뀌는 위치입니다.
L286
function graph tracer는 함수 진입뿐 아니라 복귀도 기록하여 호출 관계와 수행 시간을 관찰합니다. 켜지면 graph caller를 부를 패치 지점을 포함하고, 꺼지면 일반 함수 진입 추적만 이어집니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.
L287
(ftrace_graph_call, SYM_L_GLOBAL): 함수 안의 심볼을 정의합니다. 두 번째 인자가 외부 공개 여부를 결정하며 별도 함수 프레임·RET·BTI를 만들지 않습니다.
L288
아무 작업 없이 다음 명령으로 진행합니다. 이 위치가 alternative/ftrace 패치 대상이면 이후 다른 명령으로 바뀔 수 있습니다. 함수 그래프 추적이 켜지면 ftrace_graph_caller로 향하는 B로 바뀝니다.
L290
— 전처리 조건 구간을 끝냅니다.
L292
FP/LR을 읽고 SP를 16 증가시킨 뒤 RET합니다. 인자: 없음.
L293
(ftrace_caller)의 심볼 타입과 크기를 기록합니다. 실행을 끝내는 RET를 생성하지 않습니다.
L295
함수 복귀를 가로채는 graph caller 구현을 빌드합니다. 원래 복귀 주소는 추적 자료에 보존하고 스택의 LR 슬롯을 추적용 handler로 바꾸는 준비가 아래에서 이루어집니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.
L305
(ftrace_graph_caller)의 시작 심볼을 정의합니다. 외부에서 참조할 수 있습니다. 정의에 따른 정렬을 적용합니다. ARM64 함수 시작에는 BTI C가 들어갑니다.
L306
추가 추적 프레임에 저장된 LR에서 대상 함수의 callsite PC를 복원하여 x0에 전달합니다. 현재 x30을 바로 쓰면 직전 추적 호출의 주소와 섞일 수 있습니다. 저장된 LR은 BL 다음 주소이므로 여기서 4바이트를 빼 호출 명령의 위치를 구합니다.
L307
대상 함수의 부모 LR이 저장된 슬롯 주소를 x1에 전달합니다. prepare_ftrace_return이 그 슬롯을 복귀 추적 handler로 바꿀 수 있어야 하므로 LR 값 자체가 아니라 주소가 필요합니다. 매크로는 현재 프레임에 저장된 FP를 읽고 8을 더해 LR 슬롯의 주소를 구합니다.
L308
FP 연결을 따라 부모 프레임 포인터를 x2에 전달합니다. prepare_ftrace_return이 함수 진입 정보와 복귀 추적 상태를 올바른 프레임에 연결하는 입력입니다. 매크로 내부에서는 저장된 FP 연결을 두 번 읽어 부모 프레임에 도달합니다.
L309
prepare_ftrace_return에 대상 PC, 저장된 부모 LR 슬롯의 주소, 부모 FP를 전달합니다. graph 추적이 허용되면 원래 복귀 주소를 보관하고 그 슬롯을 return_to_handler로 바꾸어 함수가 끝날 때도 추적기를 거치게 합니다. BL은 다음 명령의 주소를 LR(x30)에 기록하고 목적지로 분기합니다. 호출된 코드가 x0 같은 작업 레지스터를 바꿀 수 있으므로, 호출 뒤의 값은 그 코드의 반환·보존 규칙에 따릅니다.
L311
FP/LR을 읽고 SP를 16 증가시킨 뒤 RET합니다. 인자: 없음.
L312
(ftrace_graph_caller)의 심볼 타입과 크기를 기록합니다. 실행을 끝내는 RET를 생성하지 않습니다.
L313
— 전처리 조건 구간을 끝냅니다.
L314
— 전처리 조건 구간을 끝냅니다.

반환값 보존과 원래 부모로 복귀

ftrace_stub와 ftrace_stub_graph는 아무 작업 없이 반환합니다. return_to_handler는 결과를 담을 수 있는 x0–x7을 저장하고 ftrace_return_to_handler가 돌려준 원래 복귀 주소를 x30에 넣습니다. 저장한 결과 레지스터와 SP를 복구한 RET가 부모로 돌아갑니다.

entry-ftrace.S · L315–L357
315
316SYM_TYPED_FUNC_START(ftrace_stub)
317	ret
318SYM_FUNC_END(ftrace_stub)
319
320#ifdef CONFIG_FUNCTION_GRAPH_TRACER
321SYM_TYPED_FUNC_START(ftrace_stub_graph)
322	ret
323SYM_FUNC_END(ftrace_stub_graph)
324
325/*
326 * void return_to_handler(void)
327 *
328 * Run ftrace_return_to_handler() before going back to parent.
329 * @fp is checked against the value passed by ftrace_graph_caller().
330 */
331SYM_CODE_START(return_to_handler)
332	/* Make room for ftrace_regs */
333	sub	sp, sp, #FREGS_SIZE
334
335	/* Save return value regs */
336	stp	x0, x1, [sp, #FREGS_X0]
337	stp	x2, x3, [sp, #FREGS_X2]
338	stp	x4, x5, [sp, #FREGS_X4]
339	stp	x6, x7, [sp, #FREGS_X6]
340
341	/* Save the callsite's FP */
342	str	x29, [sp, #FREGS_FP]
343
344	mov	x0, sp
345	bl	ftrace_return_to_handler	// addr = ftrace_return_to_hander(fregs);
346	mov	x30, x0				// restore the original return address
347
348	/* Restore return value regs */
349	ldp	x0, x1, [sp, #FREGS_X0]
350	ldp	x2, x3, [sp, #FREGS_X2]
351	ldp	x4, x5, [sp, #FREGS_X4]
352	ldp	x6, x7, [sp, #FREGS_X6]
353	add	sp, sp, #FREGS_SIZE
354
355	ret
356SYM_CODE_END(return_to_handler)
357#endif /* CONFIG_FUNCTION_GRAPH_TRACER */
L316
(ftrace_stub)의 시작 심볼을 정의합니다. 외부에서 참조할 수 있습니다. 정의에 따른 정렬을 적용합니다. ARM64 함수 시작에는 BTI C가 들어갑니다. CONFIG_CFI에서는 함수 앞의 타입 식별자도 생성합니다.
L317
x30(LR)에 든 주소로 이동합니다. SPSR/PSTATE를 복원하는 ERET와는 다릅니다.
L318
(ftrace_stub)의 심볼 타입과 크기를 기록합니다. 실행을 끝내는 RET를 생성하지 않습니다.
L320
function graph tracer가 켜진 빌드에서만 복귀 추적용 stub와 return_to_handler를 포함합니다. 꺼진 경우 함수의 원래 반환 경로를 가로챌 이 코드는 필요 없습니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.
L321
(ftrace_stub_graph)의 시작 심볼을 정의합니다. 외부에서 참조할 수 있습니다. 정의에 따른 정렬을 적용합니다. ARM64 함수 시작에는 BTI C가 들어갑니다. CONFIG_CFI에서는 함수 앞의 타입 식별자도 생성합니다.
L322
x30(LR)에 든 주소로 이동합니다. SPSR/PSTATE를 복원하는 ERET와는 다릅니다.
L323
(ftrace_stub_graph)의 심볼 타입과 크기를 기록합니다. 실행을 끝내는 RET를 생성하지 않습니다.
L331
(return_to_handler)의 시작 심볼을 정의합니다. 외부에서 참조할 수 있습니다. 정의에 따른 정렬을 적용합니다. CODE 시작은 BTI나 프레임을 자동 생성하지 않습니다.
L333
sp ← sp − #FREGS_SIZE. NZCV는 유지합니다. SP 감소는 낮은 주소 쪽의 스택 공간 확보, 증가는 해제에 해당합니다.
L336
x0, x1의 값을 [sp, #FREGS_X0]에 16바이트 저장합니다. 기준 주소 레지스터는 바꾸지 않습니다.
L337
x2, x3의 값을 [sp, #FREGS_X2]에 16바이트 저장합니다. 기준 주소 레지스터는 바꾸지 않습니다.
L338
x4, x5의 값을 [sp, #FREGS_X4]에 16바이트 저장합니다. 기준 주소 레지스터는 바꾸지 않습니다.
L339
x6, x7의 값을 [sp, #FREGS_X6]에 16바이트 저장합니다. 기준 주소 레지스터는 바꾸지 않습니다.
L342
x29의 값을 [sp, #FREGS_FP]에 8바이트 저장합니다. 기준 주소 레지스터는 바꾸지 않습니다.
L344
x0 ← sp. 값을 복사합니다. NZCV는 유지합니다.
L345
저장한 ftrace_regs 주소를 x0로 전달해 함수 복귀 사건을 기록하고, 따로 보관했던 원래 부모 복귀 주소를 x0로 돌려받습니다. 추적 대상 함수의 실제 반환값은 스택의 x0~x7 사본에 있으므로 이 반환 주소와 섞이지 않습니다. BL은 다음 명령의 주소를 LR(x30)에 기록하고 목적지로 분기합니다.
L346
추적기가 반환한 원래 부모 주소를 LR에 옮깁니다. 뒤에서 실제 함수 반환값을 x0~x7로 되살린 후 RET가 이 LR로 돌아갑니다. MOV는 x30의 기존 값을 대체합니다. 원본이 주소값이어도 그 주소의 메모리를 읽거나 복사하지 않습니다.
L349
[sp, #FREGS_X0]에서 16바이트를 읽어 x0, x1에 넣습니다. 기준 주소 레지스터는 바꾸지 않습니다.
L350
[sp, #FREGS_X2]에서 16바이트를 읽어 x2, x3에 넣습니다. 기준 주소 레지스터는 바꾸지 않습니다.
L351
[sp, #FREGS_X4]에서 16바이트를 읽어 x4, x5에 넣습니다. 기준 주소 레지스터는 바꾸지 않습니다.
L352
[sp, #FREGS_X6]에서 16바이트를 읽어 x6, x7에 넣습니다. 기준 주소 레지스터는 바꾸지 않습니다.
L353
sp ← sp + #FREGS_SIZE. NZCV는 유지합니다. SP 감소는 낮은 주소 쪽의 스택 공간 확보, 증가는 해제에 해당합니다.
L355
x30(LR)에 든 주소로 이동합니다. SPSR/PSTATE를 복원하는 ERET와는 다릅니다.
L356
(return_to_handler)의 심볼 타입과 크기를 기록합니다. 실행을 끝내는 RET를 생성하지 않습니다.
L357
— 전처리 조건 구간을 끝냅니다.

매크로 정의와 확장

각 정의는 v6.18.37 원문입니다. 역슬래시 인자는 사용 위치의 값으로 치환되며, 안쪽 매크로와 조건부 블록은 이후 단계에서 다시 처리됩니다.

EXPORT_SYMBOL

이 심볼을 모듈이 참조할 수 있도록 내보내기 정보를 생성합니다.

include/linux/export.h L89–L89

앞뒤 조건을 포함한 원문

#define EXPORT_SYMBOL(sym)		_EXPORT_SYMBOL(sym, "")

SYM_CODE_END

아래 정의의 인자 치환, 조건부 생성과 중첩 매크로를 통해 코드를 만듭니다. SYM 계열의 타입·범위·BTI 차이는 공통 표기 설명에 정리했습니다.

include/linux/linkage.h L305–L306

앞뒤 조건을 포함한 원문

#define SYM_CODE_END(name)				\
	SYM_END(name, SYM_T_NONE)

SYM_CODE_START

아래 정의의 인자 치환, 조건부 생성과 중첩 매크로를 통해 코드를 만듭니다. SYM 계열의 타입·범위·BTI 차이는 공통 표기 설명에 정리했습니다.

include/linux/linkage.h L278–L279

앞뒤 조건을 포함한 원문

#define SYM_CODE_START(name)				\
	SYM_START(name, SYM_L_GLOBAL, SYM_A_ALIGN)

SYM_FUNC_END

아래 정의의 인자 치환, 조건부 생성과 중첩 매크로를 통해 코드를 만듭니다. SYM 계열의 타입·범위·BTI 차이는 공통 표기 설명에 정리했습니다.

include/linux/linkage.h L248–L249

앞뒤 조건을 포함한 원문

#define SYM_FUNC_END(name)				\
	SYM_END(name, SYM_T_FUNC)

SYM_FUNC_START

아래 정의의 인자 치환, 조건부 생성과 중첩 매크로를 통해 코드를 만듭니다. SYM 계열의 타입·범위·BTI 차이는 공통 표기 설명에 정리했습니다.

arch/arm64/include/asm/linkage.h L18–L20

앞뒤 조건을 포함한 원문

#define SYM_FUNC_START(name)				\
	SYM_START(name, SYM_L_GLOBAL, SYM_A_ALIGN)	\
	bti c ;
include/linux/linkage.h L209–L210

앞뒤 조건을 포함한 원문

#define SYM_FUNC_START(name)				\
	SYM_START(name, SYM_L_GLOBAL, SYM_A_ALIGN)

SYM_INNER_LABEL

아래 정의의 인자 치환, 조건부 생성과 중첩 매크로를 통해 코드를 만듭니다. SYM 계열의 타입·범위·BTI 차이는 공통 표기 설명에 정리했습니다.

include/linux/linkage.h L202–L204

앞뒤 조건을 포함한 원문

#define SYM_INNER_LABEL(name, linkage)		\
	.type name SYM_T_NONE ASM_NL			\
	SYM_ENTRY(name, linkage, SYM_A_NONE)

SYM_TYPED_FUNC_START

아래 정의의 인자 치환, 조건부 생성과 중첩 매크로를 통해 코드를 만듭니다. SYM 계열의 타입·범위·BTI 차이는 공통 표기 설명에 정리했습니다.

arch/arm64/include/asm/linkage.h L42–L44

앞뒤 조건을 포함한 원문

#define SYM_TYPED_FUNC_START(name)				\
	SYM_TYPED_START(name, SYM_L_GLOBAL, SYM_A_ALIGN)	\
	bti c ;
include/linux/cfi_types.h L40–L41

앞뒤 조건을 포함한 원문

#define SYM_TYPED_FUNC_START(name) 			\
	SYM_TYPED_START(name, SYM_L_GLOBAL, SYM_A_ALIGN)

mcount_adjust_addr

복귀 주소에서 명령 크기 4를 빼 호출 명령의 주소를 구합니다.

arch/arm64/kernel/entry-ftrace.S L225–L227

앞뒤 조건을 포함한 원문

	.macro mcount_adjust_addr rd, rn
	sub	\rd, \rn, #AARCH64_INSN_SIZE
	.endm

mcount_enter

SP를 16 감소시키고 FP/LR을 저장한 뒤 FP를 현재 SP로 설정합니다.

arch/arm64/kernel/entry-ftrace.S L215–L218

앞뒤 조건을 포함한 원문

	.macro mcount_enter
	stp	x29, x30, [sp, #-16]!
	mov	x29, sp
	.endm
인자 치환 예: mcount_enter

1단계 치환 결과입니다. 내부 매크로·조건·고유 라벨 번호는 아직 펼치지 않았습니다.

	stp	x29, x30, [sp, #-16]!
	mov	x29, sp

mcount_exit

FP/LR을 읽고 SP를 16 증가시킨 뒤 RET합니다.

arch/arm64/kernel/entry-ftrace.S L220–L223

앞뒤 조건을 포함한 원문

	.macro mcount_exit
	ldp	x29, x30, [sp], #16
	ret
	.endm
인자 치환 예: mcount_exit

1단계 치환 결과입니다. 내부 매크로·조건·고유 라벨 번호는 아직 펼치지 않았습니다.

	ldp	x29, x30, [sp], #16
	ret

mcount_get_lr

저장된 FP가 가리키는 프레임의 LR을 읽습니다.

arch/arm64/kernel/entry-ftrace.S L245–L248

앞뒤 조건을 포함한 원문

	.macro mcount_get_lr reg
	ldr	\reg, [x29]
	ldr	\reg, [\reg, #8]
	.endm
인자 치환 예: mcount_get_lr x1

1단계 치환 결과입니다. 내부 매크로·조건·고유 라벨 번호는 아직 펼치지 않았습니다.

	ldr	x1, [x29]
	ldr	x1, [x1, #8]

mcount_get_lr_addr

저장된 FP+8을 구해 복귀 주소가 들어 있는 메모리 슬롯의 주소를 반환합니다.

arch/arm64/kernel/entry-ftrace.S L250–L253

앞뒤 조건을 포함한 원문

	.macro mcount_get_lr_addr reg
	ldr	\reg, [x29]
	add	\reg, \reg, #8
	.endm
인자 치환 예: mcount_get_lr_addr x1

1단계 치환 결과입니다. 내부 매크로·조건·고유 라벨 번호는 아직 펼치지 않았습니다.

	ldr	x1, [x29]
	add	x1, x1, #8

mcount_get_parent_fp

두 번 FP 연결을 따라 부모의 프레임 포인터를 읽습니다.

arch/arm64/kernel/entry-ftrace.S L230–L233

앞뒤 조건을 포함한 원문

	.macro mcount_get_parent_fp reg
	ldr	\reg, [x29]
	ldr	\reg, [\reg]
	.endm
인자 치환 예: mcount_get_parent_fp x2

1단계 치환 결과입니다. 내부 매크로·조건·고유 라벨 번호는 아직 펼치지 않았습니다.

	ldr	x2, [x29]
	ldr	x2, [x2]

mcount_get_pc

현재 프레임의 LR을 읽고 4를 빼 추적 호출 위치를 구합니다.

arch/arm64/kernel/entry-ftrace.S L240–L243

앞뒤 조건을 포함한 원문

	.macro mcount_get_pc reg
	ldr	\reg, [x29, #8]
	mcount_adjust_addr	\reg, \reg
	.endm
인자 치환 예: mcount_get_pc x0

1단계 치환 결과입니다. 내부 매크로·조건·고유 라벨 번호는 아직 펼치지 않았습니다.

	ldr	x0, [x29, #8]
	mcount_adjust_addr	x0, x0

mcount_get_pc0

현재 x30에서 4를 빼 추적 호출 명령 위치를 구합니다.

arch/arm64/kernel/entry-ftrace.S L236–L238

앞뒤 조건을 포함한 원문

	.macro mcount_get_pc0 reg
	mcount_adjust_addr	\reg, x30
	.endm
인자 치환 예: mcount_get_pc0 x0

1단계 치환 결과입니다. 내부 매크로·조건·고유 라벨 번호는 아직 펼치지 않았습니다.

	mcount_adjust_addr	x0, x30

상수·구조체 오프셋

S_*·THREAD_*·FREGS_* 등의 구조체 오프셋은 빌드한 C 구조체 배치에서 생성됩니다. 숫자를 고정하지 않고 원래 기호를 사용했습니다.

asm-offsets.c의 생성 식

맨 위로 ↑