역할과 입력
- 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로 분기합니다.
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 <takahiro.akashi@linaro.org>
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를 전달합니다.
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을 이용해 정리합니다.
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의 역할을 구분해야 그래프 추적의 주소 교체가 보입니다.
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를 거치도록 준비합니다.
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가 부모로 돌아갑니다.
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
이 심볼을 모듈이 참조할 수 있도록 내보내기 정보를 생성합니다.
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
.endmmcount_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, spmcount_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
retmcount_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, #8mcount_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, x0mcount_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 구조체 배치에서 생성됩니다. 숫자를 고정하지 않고 원래 기호를 사용했습니다.
SYM_L_GLOBAL: include/linux/linkage.h:98SYM_L_LOCAL: include/linux/linkage.h:100