요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.
1. 요약·해설
원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.
2. 영어 원문 전체
번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.
원문 전체 펼치기
======================
Function Tracer Design
======================
:Author: Mike Frysinger
.. caution::
This document is out of date. Some of the description below doesn't
match current implementation now.
Introduction
------------
Here we will cover the architecture pieces that the common function tracing
code relies on for proper functioning. Things are broken down into increasing
complexity so that you can start simple and at least get basic functionality.
Note that this focuses on architecture implementation details only. If you
want more explanation of a feature in terms of common code, review the common
ftrace.txt file.
Ideally, everyone who wishes to retain performance while supporting tracing in
their kernel should make it all the way to dynamic ftrace support.
Prerequisites
-------------
Ftrace relies on these features being implemented:
- STACKTRACE_SUPPORT - implement save_stack_trace()
- TRACE_IRQFLAGS_SUPPORT - implement include/asm/irqflags.h
HAVE_FUNCTION_TRACER
--------------------
You will need to implement the mcount and the ftrace_stub functions.
The exact mcount symbol name will depend on your toolchain. Some call it
"mcount", "_mcount", or even "__mcount". You can probably figure it out by
running something like::
$ echo 'main(){}' | gcc -x c -S -o - - -pg | grep mcount
call mcount
We'll make the assumption below that the symbol is "mcount" just to keep things
nice and simple in the examples.
Keep in mind that the ABI that is in effect inside of the mcount function is
*highly* architecture/toolchain specific. We cannot help you in this regard,
sorry. Dig up some old documentation and/or find someone more familiar than
you to bang ideas off of. Typically, register usage (argument/scratch/etc...)
is a major issue at this point, especially in relation to the location of the
mcount call (before/after function prologue). You might also want to look at
how glibc has implemented the mcount function for your architecture. It might
be (semi-)relevant.
The mcount function should check the function pointer ftrace_trace_function
to see if it is set to ftrace_stub. If it is, there is nothing for you to do,
so return immediately. If it isn't, then call that function in the same way
the mcount function normally calls __mcount_internal -- the first argument is
the "frompc" while the second argument is the "selfpc" (adjusted to remove the
size of the mcount call that is embedded in the function).
For example, if the function foo() calls bar(), when the bar() function calls
mcount(), the arguments mcount() will pass to the tracer are:
- "frompc" - the address bar() will use to return to foo()
- "selfpc" - the address bar() (with mcount() size adjustment)
Also keep in mind that this mcount function will be called *a lot*, so
optimizing for the default case of no tracer will help the smooth running of
your system when tracing is disabled. So the start of the mcount function is
typically the bare minimum with checking things before returning. That also
means the code flow should usually be kept linear (i.e. no branching in the nop
case). This is of course an optimization and not a hard requirement.
Here is some pseudo code that should help (these functions should actually be
implemented in assembly)::
void ftrace_stub(void)
{
return;
}
void mcount(void)
{
/* save any bare state needed in order to do initial checking */
extern void (*ftrace_trace_function)(unsigned long, unsigned long);
if (ftrace_trace_function != ftrace_stub)
goto do_trace;
/* restore any bare state */
return;
do_trace:
/* save all state needed by the ABI (see paragraph above) */
unsigned long frompc = ...;
unsigned long selfpc = <return address> - MCOUNT_INSN_SIZE;
ftrace_trace_function(frompc, selfpc);
/* restore all state needed by the ABI */
}
Don't forget to export mcount for modules !
::
extern void mcount(void);
EXPORT_SYMBOL(mcount);
HAVE_FUNCTION_GRAPH_TRACER
--------------------------
Deep breath ... time to do some real work. Here you will need to update the
mcount function to check ftrace graph function pointers, as well as implement
some functions to save (hijack) and restore the return address.
The mcount function should check the function pointers ftrace_graph_return
(compare to ftrace_stub) and ftrace_graph_entry (compare to
ftrace_graph_entry_stub). If either of those is not set to the relevant stub
function, call the arch-specific function ftrace_graph_caller which in turn
calls the arch-specific function prepare_ftrace_return. Neither of these
function names is strictly required, but you should use them anyway to stay
consistent across the architecture ports -- easier to compare & contrast
things.
The arguments to prepare_ftrace_return are slightly different than what are
passed to ftrace_trace_function. The second argument "selfpc" is the same,
but the first argument should be a pointer to the "frompc". Typically this is
located on the stack. This allows the function to hijack the return address
temporarily to have it point to the arch-specific function return_to_handler.
That function will simply call the common ftrace_return_to_handler function and
that will return the original return address with which you can return to the
original call site.
Here is the updated mcount pseudo code::
void mcount(void)
{
...
if (ftrace_trace_function != ftrace_stub)
goto do_trace;
+#ifdef CONFIG_FUNCTION_GRAPH_TRACER
+ extern void (*ftrace_graph_return)(...);
+ extern void (*ftrace_graph_entry)(...);
+ if (ftrace_graph_return != ftrace_stub ||
+ ftrace_graph_entry != ftrace_graph_entry_stub)
+ ftrace_graph_caller();
+#endif
/* restore any bare state */
...
Here is the pseudo code for the new ftrace_graph_caller assembly function::
#ifdef CONFIG_FUNCTION_GRAPH_TRACER
void ftrace_graph_caller(void)
{
/* save all state needed by the ABI */
unsigned long *frompc = &...;
unsigned long selfpc = <return address> - MCOUNT_INSN_SIZE;
/* passing frame pointer up is optional -- see below */
prepare_ftrace_return(frompc, selfpc, frame_pointer);
/* restore all state needed by the ABI */
}
#endif
For information on how to implement prepare_ftrace_return(), simply look at the
x86 version (the frame pointer passing is optional; see the next section for
more information). The only architecture-specific piece in it is the setup of
the fault recovery table (the asm(...) code). The rest should be the same
across architectures.
Here is the pseudo code for the new return_to_handler assembly function. Note
that the ABI that applies here is different from what applies to the mcount
code. Since you are returning from a function (after the epilogue), you might
be able to skimp on things saved/restored (usually just registers used to pass
return values).
::
#ifdef CONFIG_FUNCTION_GRAPH_TRACER
void return_to_handler(void)
{
/* save all state needed by the ABI (see paragraph above) */
void (*original_return_point)(void) = ftrace_return_to_handler();
/* restore all state needed by the ABI */
/* this is usually either a return or a jump */
original_return_point();
}
#endif
HAVE_FUNCTION_GRAPH_FP_TEST
---------------------------
An arch may pass in a unique value (frame pointer) to both the entering and
exiting of a function. On exit, the value is compared and if it does not
match, then it will panic the kernel. This is largely a sanity check for bad
code generation with gcc. If gcc for your port sanely updates the frame
pointer under different optimization levels, then ignore this option.
However, adding support for it isn't terribly difficult. In your assembly code
that calls prepare_ftrace_return(), pass the frame pointer as the 3rd argument.
Then in the C version of that function, do what the x86 port does and pass it
along to ftrace_push_return_trace() instead of a stub value of 0.
Similarly, when you call ftrace_return_to_handler(), pass it the frame pointer.
HAVE_SYSCALL_TRACEPOINTS
------------------------
You need very few things to get the syscalls tracing in an arch.
- Support HAVE_ARCH_TRACEHOOK (see arch/Kconfig).
- Have a NR_syscalls variable in <asm/unistd.h> that provides the number
of syscalls supported by the arch.
- Support the TIF_SYSCALL_TRACEPOINT thread flags.
- Put the trace_sys_enter() and trace_sys_exit() tracepoints calls from ptrace
in the ptrace syscalls tracing path.
- If the system call table on this arch is more complicated than a simple array
of addresses of the system calls, implement an arch_syscall_addr to return
the address of a given system call.
- If the symbol names of the system calls do not match the function names on
this arch, define ARCH_HAS_SYSCALL_MATCH_SYM_NAME in asm/ftrace.h and
implement arch_syscall_match_sym_name with the appropriate logic to return
true if the function name corresponds with the symbol name.
- Tag this arch as HAVE_SYSCALL_TRACEPOINTS.
HAVE_DYNAMIC_FTRACE
-------------------
See scripts/recordmcount.pl for more info. Just fill in the arch-specific
details for how to locate the addresses of mcount call sites via objdump.
This option doesn't make much sense without also implementing dynamic ftrace.
You will first need HAVE_FUNCTION_TRACER, so scroll your reader back up if you
got over eager.
Once those are out of the way, you will need to implement:
- asm/ftrace.h:
- MCOUNT_ADDR
- ftrace_call_adjust()
- struct dyn_arch_ftrace{}
- asm code:
- mcount() (new stub)
- ftrace_caller()
- ftrace_call()
- ftrace_stub()
- C code:
- ftrace_dyn_arch_init()
- ftrace_make_nop()
- ftrace_make_call()
- ftrace_update_ftrace_func()
First you will need to fill out some arch details in your asm/ftrace.h.
Define MCOUNT_ADDR as the address of your mcount symbol similar to::
#define MCOUNT_ADDR ((unsigned long)mcount)
Since no one else will have a decl for that function, you will need to::
extern void mcount(void);
You will also need the helper function ftrace_call_adjust(). Most people
will be able to stub it out like so::
static inline unsigned long ftrace_call_adjust(unsigned long addr)
{
return addr;
}
<details to be filled>
Lastly you will need the custom dyn_arch_ftrace structure. If you need
some extra state when runtime patching arbitrary call sites, this is the
place. For now though, create an empty struct::
struct dyn_arch_ftrace {
/* No extra data needed */
};
With the header out of the way, we can fill out the assembly code. While we
did already create a mcount() function earlier, dynamic ftrace only wants a
stub function. This is because the mcount() will only be used during boot
and then all references to it will be patched out never to return. Instead,
the guts of the old mcount() will be used to create a new ftrace_caller()
function. Because the two are hard to merge, it will most likely be a lot
easier to have two separate definitions split up by #ifdefs. Same goes for
the ftrace_stub() as that will now be inlined in ftrace_caller().
Before we get confused anymore, let's check out some pseudo code so you can
implement your own stuff in assembly::
void mcount(void)
{
return;
}
void ftrace_caller(void)
{
/* save all state needed by the ABI (see paragraph above) */
unsigned long frompc = ...;
unsigned long selfpc = <return address> - MCOUNT_INSN_SIZE;
ftrace_call:
ftrace_stub(frompc, selfpc);
/* restore all state needed by the ABI */
ftrace_stub:
return;
}
This might look a little odd at first, but keep in mind that we will be runtime
patching multiple things. First, only functions that we actually want to trace
will be patched to call ftrace_caller(). Second, since we only have one tracer
active at a time, we will patch the ftrace_caller() function itself to call the
specific tracer in question. That is the point of the ftrace_call label.
With that in mind, let's move on to the C code that will actually be doing the
runtime patching. You'll need a little knowledge of your arch's opcodes in
order to make it through the next section.
Every arch has an init callback function. If you need to do something early on
to initialize some state, this is the time to do that. Otherwise, this simple
function below should be sufficient for most people::
int __init ftrace_dyn_arch_init(void)
{
return 0;
}
There are two functions that are used to do runtime patching of arbitrary
functions. The first is used to turn the mcount call site into a nop (which
is what helps us retain runtime performance when not tracing). The second is
used to turn the mcount call site into a call to an arbitrary location (but
typically that is ftracer_caller()). See the general function definition in
linux/ftrace.h for the functions::
ftrace_make_nop()
ftrace_make_call()
The rec->ip value is the address of the mcount call site that was collected
by the scripts/recordmcount.pl during build time.
The last function is used to do runtime patching of the active tracer. This
will be modifying the assembly code at the location of the ftrace_call symbol
inside of the ftrace_caller() function. So you should have sufficient padding
at that location to support the new function calls you'll be inserting. Some
people will be using a "call" type instruction while others will be using a
"branch" type instruction. Specifically, the function is::
ftrace_update_ftrace_func()
HAVE_DYNAMIC_FTRACE + HAVE_FUNCTION_GRAPH_TRACER
------------------------------------------------
The function grapher needs a few tweaks in order to work with dynamic ftrace.
Basically, you will need to:
- update:
- ftrace_caller()
- ftrace_graph_call()
- ftrace_graph_caller()
- implement:
- ftrace_enable_ftrace_graph_caller()
- ftrace_disable_ftrace_graph_caller()
<details to be filled>
Quick notes:
- add a nop stub after the ftrace_call location named ftrace_graph_call;
stub needs to be large enough to support a call to ftrace_graph_caller()
- update ftrace_graph_caller() to work with being called by the new
ftrace_caller() since some semantics may have changed
- ftrace_enable_ftrace_graph_caller() will runtime patch the
ftrace_graph_call location with a call to ftrace_graph_caller()
- ftrace_disable_ftrace_graph_caller() will runtime patch the
ftrace_graph_call location with nops
3. 한국어 전문 번역
영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.
문서 정보와 주의 사항
1-10이 문서는 Mike Frysinger가 작성한 함수 추적기 아키텍처 설계 안내서다.
주의: 이 문서는 오래되었으며 아래 설명 가운데 일부는 현재 구현과 일치하지 않는다. 따라서 포팅의 개념적 순서와 인터페이스 의도를 이해하는 자료로 사용하되, 실제 구현은 Linux v6.18.37 소스와 해당 아키텍처 코드를 함께 확인해야 한다.
======================
Function Tracer Design
======================
:Author: Mike Frysinger
.. caution::
This document is out of date. Some of the description below doesn't
match current implementation now.
소개
11-25이 문서는 공통 함수 추적 코드가 올바르게 동작하기 위해 아키텍처가 제공해야 하는 구성 요소를 설명한다. 요구 사항을 복잡도가 높아지는 순서로 나누므로, 먼저 기본 기능을 구현한 뒤 단계적으로 고급 기능을 추가할 수 있다.
초점은 공통 코드의 사용법이 아니라 아키텍처 구현 세부 사항이다. 공통 기능의 의미와 사용자 인터페이스는 공통 `ftrace.txt` 문서를 참고한다.
추적 기능을 제공하면서 비활성 상태의 실행 성능도 유지하려면 최종적으로 동적 ftrace까지 지원하는 것이 바람직하다. 동적 ftrace는 추적하지 않는 호출 지점을 런타임에 패치해 비용을 줄인다.
기본 함수 진입 추적에서 동적 패치와 함수 그래프 추적으로 확장한다.
Introduction
------------
Here we will cover the architecture pieces that the common function tracing
code relies on for proper functioning. Things are broken down into increasing
complexity so that you can start simple and at least get basic functionality.
Note that this focuses on architecture implementation details only. If you
want more explanation of a feature in terms of common code, review the common
ftrace.txt file.
Ideally, everyone who wishes to retain performance while supporting tracing in
their kernel should make it all the way to dynamic ftrace support.
선행 조건
26-33Ftrace는 두 아키텍처 기능을 전제로 한다. `STACKTRACE_SUPPORT`는 `save_stack_trace()`를 구현해 호출 스택을 수집할 수 있어야 하고, `TRACE_IRQFLAGS_SUPPORT`는 `include/asm/irqflags.h`의 인터럽트 플래그 추적 지원을 제공해야 한다.
함수 추적 포팅에 앞서 준비할 항목이다.
Prerequisites
-------------
Ftrace relies on these features being implemented:
- STACKTRACE_SUPPORT - implement save_stack_trace()
- TRACE_IRQFLAGS_SUPPORT - implement include/asm/irqflags.h
HAVE_FUNCTION_TRACER
34-115기본 함수 추적기를 지원하려면 `mcount`와 `ftrace_stub` 함수를 구현해야 한다. 정확한 심볼 이름은 도구 체인에 따라 `mcount`, `_mcount`, `__mcount` 등으로 달라질 수 있으므로, 문서의 `gcc -pg` 예제처럼 어셈블리 출력을 검사해 실제 이름을 확인한다. 아래 설명은 편의를 위해 `mcount`를 사용한다.
`mcount` 내부에서 유효한 ABI는 아키텍처와 도구 체인에 매우 의존적이다. 인자 레지스터와 임시 레지스터의 보존 규칙뿐 아니라 `mcount` 호출이 함수 프롤로그 앞인지 뒤인지도 중요하다. 해당 아키텍처의 오래된 ABI 문서와 glibc의 `mcount` 구현이 참고 자료가 될 수 있다.
`mcount`는 먼저 함수 포인터 `ftrace_trace_function`이 `ftrace_stub`인지 확인한다. 같으면 활성 추적기가 없으므로 즉시 반환한다. 다르면 전통적인 `__mcount_internal` 호출과 같은 방식으로 첫 번째 인자 `frompc`와 두 번째 인자 `selfpc`를 넘겨 활성 추적 함수를 호출한다.
`frompc`는 추적 대상 함수가 호출자에게 돌아갈 주소다. 예를 들어 `foo()`가 `bar()`를 호출했다면 `bar()`의 `mcount()`가 전달하는 `frompc`는 `foo()` 안의 복귀 지점이다. `selfpc`는 추적 대상 함수 `bar()`의 주소이며, 함수 안에 삽입된 `mcount` 호출 명령 길이 `MCOUNT_INSN_SIZE`만큼 복귀 주소를 조정해 계산한다.
`mcount`는 거의 모든 함수 진입에서 매우 자주 실행된다. 따라서 추적기가 없는 기본 경로는 최소한의 상태만 저장하고 검사한 뒤 곧바로 반환하도록 최적화해야 한다. nop 경로에서는 분기 없이 선형으로 흐르게 만드는 것이 일반적이지만, 이는 성능 최적화이지 기능상 강제 조건은 아니다.
문서의 의사 코드는 `ftrace_stub()`이 즉시 반환하고, `mcount()`가 최소 상태를 저장한 뒤 활성 추적기 여부를 검사하는 구조다. 활성 추적기가 있으면 ABI가 요구하는 전체 상태를 저장하고 `frompc`와 `selfpc`를 계산해 `ftrace_trace_function(frompc, selfpc)`를 호출한 다음 상태를 복원한다. 실제 구현은 어셈블리로 작성해야 한다.
모듈에서도 계측 호출을 해결할 수 있도록 `mcount`를 선언하고 `EXPORT_SYMBOL(mcount)`로 내보내는 것을 잊지 않아야 한다.
foo()가 bar()를 호출한 경우를 기준으로 주소의 의미를 구분한다.
비활성 경로를 짧게 유지하고 활성 추적기에서만 전체 ABI 상태를 보존한다.
HAVE_FUNCTION_TRACER
--------------------
You will need to implement the mcount and the ftrace_stub functions.
The exact mcount symbol name will depend on your toolchain. Some call it
"mcount", "_mcount", or even "__mcount". You can probably figure it out by
running something like::
$ echo 'main(){}' | gcc -x c -S -o - - -pg | grep mcount
call mcount
We'll make the assumption below that the symbol is "mcount" just to keep things
nice and simple in the examples.
Keep in mind that the ABI that is in effect inside of the mcount function is
*highly* architecture/toolchain specific. We cannot help you in this regard,
sorry. Dig up some old documentation and/or find someone more familiar than
you to bang ideas off of. Typically, register usage (argument/scratch/etc...)
is a major issue at this point, especially in relation to the location of the
mcount call (before/after function prologue). You might also want to look at
how glibc has implemented the mcount function for your architecture. It might
be (semi-)relevant.
The mcount function should check the function pointer ftrace_trace_function
to see if it is set to ftrace_stub. If it is, there is nothing for you to do,
so return immediately. If it isn't, then call that function in the same way
the mcount function normally calls __mcount_internal -- the first argument is
the "frompc" while the second argument is the "selfpc" (adjusted to remove the
size of the mcount call that is embedded in the function).
For example, if the function foo() calls bar(), when the bar() function calls
mcount(), the arguments mcount() will pass to the tracer are:
- "frompc" - the address bar() will use to return to foo()
- "selfpc" - the address bar() (with mcount() size adjustment)
Also keep in mind that this mcount function will be called *a lot*, so
optimizing for the default case of no tracer will help the smooth running of
your system when tracing is disabled. So the start of the mcount function is
typically the bare minimum with checking things before returning. That also
means the code flow should usually be kept linear (i.e. no branching in the nop
case). This is of course an optimization and not a hard requirement.
Here is some pseudo code that should help (these functions should actually be
implemented in assembly)::
void ftrace_stub(void)
{
return;
}
void mcount(void)
{
/* save any bare state needed in order to do initial checking */
extern void (*ftrace_trace_function)(unsigned long, unsigned long);
if (ftrace_trace_function != ftrace_stub)
goto do_trace;
/* restore any bare state */
return;
do_trace:
/* save all state needed by the ABI (see paragraph above) */
unsigned long frompc = ...;
unsigned long selfpc = <return address> - MCOUNT_INSN_SIZE;
ftrace_trace_function(frompc, selfpc);
/* restore all state needed by the ABI */
}
Don't forget to export mcount for modules !
::
extern void mcount(void);
EXPORT_SYMBOL(mcount);
HAVE_FUNCTION_GRAPH_TRACER
116-203함수 그래프 추적은 함수 진입뿐 아니라 복귀도 관찰하므로 `mcount`를 확장하고 원래 복귀 주소를 임시로 가로챈 뒤 복원하는 아키텍처별 코드를 추가해야 한다.
`mcount`는 `ftrace_graph_return`을 `ftrace_stub`과 비교하고 `ftrace_graph_entry`를 `ftrace_graph_entry_stub`과 비교한다. 둘 중 하나라도 대응 스텁이 아니면 아키텍처별 `ftrace_graph_caller`를 호출하고, 이 함수가 다시 아키텍처별 `prepare_ftrace_return`을 호출한다. 이름 자체가 필수 ABI는 아니지만 다른 포트와 비교하기 쉽도록 이 이름을 유지하는 것이 좋다.
`prepare_ftrace_return`의 두 번째 인자 `selfpc`는 기본 추적기와 같다. 첫 번째 인자는 `frompc` 값이 아니라 그 값을 담은 위치의 포인터이며, 대개 스택에 있다. 이 포인터를 통해 원래 복귀 주소를 아키텍처별 `return_to_handler` 주소로 잠시 바꿀 수 있다.
추적 대상 함수가 끝나면 가로챈 주소 때문에 `return_to_handler`가 실행된다. 이 루틴은 공통 `ftrace_return_to_handler()`를 호출해 저장해 둔 원래 복귀 주소를 받고, 필요한 상태를 복원한 뒤 그 주소로 반환하거나 점프한다.
갱신된 `mcount` 의사 코드는 일반 함수 추적기 검사 뒤에 `CONFIG_FUNCTION_GRAPH_TRACER` 조건부 코드를 둔다. 그래프 진입 또는 복귀 함수가 활성화되어 있으면 `ftrace_graph_caller()`를 부른 뒤 기존의 최소 상태 복원 경로로 돌아간다.
`ftrace_graph_caller`는 ABI 상태를 저장하고 스택에서 `frompc`의 주소를 구하며, `selfpc`를 계산한 다음 선택적인 세 번째 인자인 프레임 포인터와 함께 `prepare_ftrace_return(frompc, selfpc, frame_pointer)`를 호출한다.
`prepare_ftrace_return()`의 구현은 x86 포트를 참고할 수 있다. 아키텍처에 따라 달라지는 핵심은 어셈블리로 구성하는 fault recovery table이고, 나머지 로직은 여러 아키텍처에서 같아야 한다. 프레임 포인터 전달은 선택 사항이며 다음 절의 검사를 사용할 때 필요하다.
`return_to_handler`는 함수 에필로그 이후의 복귀 경로에서 실행되므로 `mcount`와 적용 ABI가 다르다. 보통 반환 값을 전달하는 레지스터 정도만 저장하고 복원하면 되지만, 정확한 집합은 해당 아키텍처 ABI에 맞춰야 한다.
원래 복귀 주소를 보관하고 공통 핸들러를 거쳐 호출자에게 돌아간다.
진입과 복귀 경로에 나뉜 아키텍처별 책임이다.
HAVE_FUNCTION_GRAPH_TRACER
--------------------------
Deep breath ... time to do some real work. Here you will need to update the
mcount function to check ftrace graph function pointers, as well as implement
some functions to save (hijack) and restore the return address.
The mcount function should check the function pointers ftrace_graph_return
(compare to ftrace_stub) and ftrace_graph_entry (compare to
ftrace_graph_entry_stub). If either of those is not set to the relevant stub
function, call the arch-specific function ftrace_graph_caller which in turn
calls the arch-specific function prepare_ftrace_return. Neither of these
function names is strictly required, but you should use them anyway to stay
consistent across the architecture ports -- easier to compare & contrast
things.
The arguments to prepare_ftrace_return are slightly different than what are
passed to ftrace_trace_function. The second argument "selfpc" is the same,
but the first argument should be a pointer to the "frompc". Typically this is
located on the stack. This allows the function to hijack the return address
temporarily to have it point to the arch-specific function return_to_handler.
That function will simply call the common ftrace_return_to_handler function and
that will return the original return address with which you can return to the
original call site.
Here is the updated mcount pseudo code::
void mcount(void)
{
...
if (ftrace_trace_function != ftrace_stub)
goto do_trace;
+#ifdef CONFIG_FUNCTION_GRAPH_TRACER
+ extern void (*ftrace_graph_return)(...);
+ extern void (*ftrace_graph_entry)(...);
+ if (ftrace_graph_return != ftrace_stub ||
+ ftrace_graph_entry != ftrace_graph_entry_stub)
+ ftrace_graph_caller();
+#endif
/* restore any bare state */
...
Here is the pseudo code for the new ftrace_graph_caller assembly function::
#ifdef CONFIG_FUNCTION_GRAPH_TRACER
void ftrace_graph_caller(void)
{
/* save all state needed by the ABI */
unsigned long *frompc = &...;
unsigned long selfpc = <return address> - MCOUNT_INSN_SIZE;
/* passing frame pointer up is optional -- see below */
prepare_ftrace_return(frompc, selfpc, frame_pointer);
/* restore all state needed by the ABI */
}
#endif
For information on how to implement prepare_ftrace_return(), simply look at the
x86 version (the frame pointer passing is optional; see the next section for
more information). The only architecture-specific piece in it is the setup of
the fault recovery table (the asm(...) code). The rest should be the same
across architectures.
Here is the pseudo code for the new return_to_handler assembly function. Note
that the ABI that applies here is different from what applies to the mcount
code. Since you are returning from a function (after the epilogue), you might
be able to skimp on things saved/restored (usually just registers used to pass
return values).
::
#ifdef CONFIG_FUNCTION_GRAPH_TRACER
void return_to_handler(void)
{
/* save all state needed by the ABI (see paragraph above) */
void (*original_return_point)(void) = ftrace_return_to_handler();
/* restore all state needed by the ABI */
/* this is usually either a return or a jump */
original_return_point();
}
#endif
HAVE_FUNCTION_GRAPH_FP_TEST
204-219아키텍처는 함수 진입과 종료에 같은 고유 값, 즉 프레임 포인터를 전달할 수 있다. 종료 시 값이 일치하지 않으면 커널 패닉을 일으킨다. 이 기능은 주로 GCC가 잘못된 코드를 생성했는지 확인하는 건전성 검사다. 여러 최적화 수준에서 프레임 포인터가 정상적으로 갱신되는 포트라면 사용하지 않아도 된다.
지원하려면 `prepare_ftrace_return()`을 호출하는 어셈블리 코드에서 프레임 포인터를 세 번째 인자로 넘긴다. C 구현은 x86처럼 스텁 값 0 대신 이 값을 `ftrace_push_return_trace()`에 전달한다. `ftrace_return_to_handler()` 호출에도 같은 프레임 포인터를 넘겨 진입과 종료 값을 비교할 수 있게 한다.
함수 진입과 종료의 프레임 포인터가 다르면 손상으로 처리한다.
HAVE_FUNCTION_GRAPH_FP_TEST
---------------------------
An arch may pass in a unique value (frame pointer) to both the entering and
exiting of a function. On exit, the value is compared and if it does not
match, then it will panic the kernel. This is largely a sanity check for bad
code generation with gcc. If gcc for your port sanely updates the frame
pointer under different optimization levels, then ignore this option.
However, adding support for it isn't terribly difficult. In your assembly code
that calls prepare_ftrace_return(), pass the frame pointer as the 3rd argument.
Then in the C version of that function, do what the x86 port does and pass it
along to ftrace_push_return_trace() instead of a stub value of 0.
Similarly, when you call ftrace_return_to_handler(), pass it the frame pointer.
HAVE_SYSCALL_TRACEPOINTS
220-240아키텍처가 시스템 호출 추적을 지원하는 데 필요한 항목은 많지 않다. 먼저 `arch/Kconfig`의 `HAVE_ARCH_TRACEHOOK`을 지원하고, `<asm/unistd.h>`에 아키텍처가 제공하는 시스템 호출 수를 나타내는 `NR_syscalls`를 둔다.
스레드 플래그 `TIF_SYSCALL_TRACEPOINT`를 지원하고 ptrace 시스템 호출 추적 경로에서 `trace_sys_enter()`와 `trace_sys_exit()` tracepoint를 호출해야 한다.
시스템 호출 테이블이 단순한 함수 주소 배열보다 복잡하면 주어진 시스템 호출의 주소를 반환하는 `arch_syscall_addr`를 구현한다. 시스템 호출 심볼 이름과 함수 이름 규칙이 다르면 `asm/ftrace.h`에 `ARCH_HAS_SYSCALL_MATCH_SYM_NAME`을 정의하고, 두 이름의 대응 여부를 판정하는 `arch_syscall_match_sym_name`을 구현한다.
이 요구 사항을 모두 갖춘 뒤 아키텍처를 `HAVE_SYSCALL_TRACEPOINTS`로 표시한다.
아키텍처가 제공해야 할 설정과 훅이다.
HAVE_SYSCALL_TRACEPOINTS
------------------------
You need very few things to get the syscalls tracing in an arch.
- Support HAVE_ARCH_TRACEHOOK (see arch/Kconfig).
- Have a NR_syscalls variable in <asm/unistd.h> that provides the number
of syscalls supported by the arch.
- Support the TIF_SYSCALL_TRACEPOINT thread flags.
- Put the trace_sys_enter() and trace_sys_exit() tracepoints calls from ptrace
in the ptrace syscalls tracing path.
- If the system call table on this arch is more complicated than a simple array
of addresses of the system calls, implement an arch_syscall_addr to return
the address of a given system call.
- If the symbol names of the system calls do not match the function names on
this arch, define ARCH_HAS_SYSCALL_MATCH_SYM_NAME in asm/ftrace.h and
implement arch_syscall_match_sym_name with the appropriate logic to return
true if the function name corresponds with the symbol name.
- Tag this arch as HAVE_SYSCALL_TRACEPOINTS.
HAVE_DYNAMIC_FTRACE
241-369동적 ftrace를 구현하려면 먼저 `scripts/recordmcount.pl`이 `objdump` 출력에서 `mcount` 호출 지점 주소를 찾도록 아키텍처별 세부 사항을 채운다. 이 단계는 `HAVE_FUNCTION_TRACER` 구현을 전제로 한다.
`asm/ftrace.h`에는 `MCOUNT_ADDR`, `ftrace_call_adjust()`, `struct dyn_arch_ftrace`를 제공한다. 어셈블리에는 부팅 때만 쓰는 새 `mcount()` 스텁, `ftrace_caller()`, 패치 지점 `ftrace_call()`, `ftrace_stub()`을 구현한다. C에는 `ftrace_dyn_arch_init()`, `ftrace_make_nop()`, `ftrace_make_call()`, `ftrace_update_ftrace_func()`를 구현한다.
`MCOUNT_ADDR`은 보통 `((unsigned long)mcount)`로 정의하며 다른 선언이 없으므로 `extern void mcount(void);`도 추가한다. `ftrace_call_adjust()`는 기록된 주소를 아키텍처가 사용하는 실제 호출 지점으로 보정하는 함수다. 별도 보정이 필요 없는 포트는 입력 주소를 그대로 반환할 수 있다.
원문에는 `ftrace_call_adjust()` 뒤의 설명이 아직 작성되지 않았음을 나타내는 리터럴 `<details to be filled>`이 남아 있다. 이 미완성 표시는 원문 상태 그대로 보존하며 임의의 구현 규칙으로 보충하지 않는다.
`struct dyn_arch_ftrace`는 임의 호출 지점을 런타임에 패치할 때 필요한 아키텍처별 상태를 보관한다. 추가 상태가 없으면 주석만 있는 빈 구조체로 만들 수 있다.
동적 ftrace에서 `mcount()`는 부팅 과정에만 사용되는 즉시 반환 스텁이다. 초기화 뒤 모든 계측 호출은 패치되어 다시 `mcount`로 돌아오지 않는다. 기존 `mcount`의 실제 추적 로직은 새 `ftrace_caller()`로 옮기며, 두 정의는 합치기 어렵기 때문에 보통 `#ifdef`로 분리한다. `ftrace_stub()` 로직도 `ftrace_caller()`에 인라인된다.
`ftrace_caller()`는 ABI 상태를 저장하고 `frompc`와 `selfpc`를 계산한다. 내부의 `ftrace_call` 레이블에는 처음에 `ftrace_stub(frompc, selfpc)` 호출에 해당하는 패치 가능 공간을 둔다. 이후 상태를 복원하고 스텁 반환 경로로 나간다.
런타임 패치는 두 층으로 일어난다. 먼저 실제 추적할 함수의 계측 지점만 `ftrace_caller()`를 호출하도록 바꾼다. 다음으로 한 번에 하나인 활성 추적기에 맞춰 `ftrace_caller()` 안의 `ftrace_call` 위치를 해당 추적 함수 호출로 바꾼다.
`ftrace_dyn_arch_init()`는 초기 아키텍처 상태가 필요할 때 준비 작업을 수행한다. 별도 작업이 없다면 0을 반환하는 간단한 초기화 함수면 충분하다.
`ftrace_make_nop()`는 임의 함수의 `mcount` 호출 지점을 nop으로 바꿔 추적 비활성 성능을 확보한다. `ftrace_make_call()`은 그 지점을 임의 목적지, 보통 `ftrace_caller()` 호출로 바꾼다. 두 함수가 받는 `rec->ip`는 빌드 때 `scripts/recordmcount.pl`이 수집한 `mcount` 호출 지점 주소다.
`ftrace_update_ftrace_func()`는 `ftrace_caller()` 내부의 `ftrace_call` 심볼 위치를 현재 활성 추적기 호출로 패치한다. 아키텍처가 call 명령을 쓰든 branch 명령을 쓰든 새 명령을 넣을 수 있도록 해당 위치에 충분한 패딩을 확보해야 한다.
헤더, 어셈블리, C 코드에 필요한 요소를 구분한다.
빌드 시 수집한 주소를 부팅과 런타임에 선택적으로 바꾼다.
HAVE_DYNAMIC_FTRACE
-------------------
See scripts/recordmcount.pl for more info. Just fill in the arch-specific
details for how to locate the addresses of mcount call sites via objdump.
This option doesn't make much sense without also implementing dynamic ftrace.
You will first need HAVE_FUNCTION_TRACER, so scroll your reader back up if you
got over eager.
Once those are out of the way, you will need to implement:
- asm/ftrace.h:
- MCOUNT_ADDR
- ftrace_call_adjust()
- struct dyn_arch_ftrace{}
- asm code:
- mcount() (new stub)
- ftrace_caller()
- ftrace_call()
- ftrace_stub()
- C code:
- ftrace_dyn_arch_init()
- ftrace_make_nop()
- ftrace_make_call()
- ftrace_update_ftrace_func()
First you will need to fill out some arch details in your asm/ftrace.h.
Define MCOUNT_ADDR as the address of your mcount symbol similar to::
#define MCOUNT_ADDR ((unsigned long)mcount)
Since no one else will have a decl for that function, you will need to::
extern void mcount(void);
You will also need the helper function ftrace_call_adjust(). Most people
will be able to stub it out like so::
static inline unsigned long ftrace_call_adjust(unsigned long addr)
{
return addr;
}
<details to be filled>
Lastly you will need the custom dyn_arch_ftrace structure. If you need
some extra state when runtime patching arbitrary call sites, this is the
place. For now though, create an empty struct::
struct dyn_arch_ftrace {
/* No extra data needed */
};
With the header out of the way, we can fill out the assembly code. While we
did already create a mcount() function earlier, dynamic ftrace only wants a
stub function. This is because the mcount() will only be used during boot
and then all references to it will be patched out never to return. Instead,
the guts of the old mcount() will be used to create a new ftrace_caller()
function. Because the two are hard to merge, it will most likely be a lot
easier to have two separate definitions split up by #ifdefs. Same goes for
the ftrace_stub() as that will now be inlined in ftrace_caller().
Before we get confused anymore, let's check out some pseudo code so you can
implement your own stuff in assembly::
void mcount(void)
{
return;
}
void ftrace_caller(void)
{
/* save all state needed by the ABI (see paragraph above) */
unsigned long frompc = ...;
unsigned long selfpc = <return address> - MCOUNT_INSN_SIZE;
ftrace_call:
ftrace_stub(frompc, selfpc);
/* restore all state needed by the ABI */
ftrace_stub:
return;
}
This might look a little odd at first, but keep in mind that we will be runtime
patching multiple things. First, only functions that we actually want to trace
will be patched to call ftrace_caller(). Second, since we only have one tracer
active at a time, we will patch the ftrace_caller() function itself to call the
specific tracer in question. That is the point of the ftrace_call label.
With that in mind, let's move on to the C code that will actually be doing the
runtime patching. You'll need a little knowledge of your arch's opcodes in
order to make it through the next section.
Every arch has an init callback function. If you need to do something early on
to initialize some state, this is the time to do that. Otherwise, this simple
function below should be sufficient for most people::
int __init ftrace_dyn_arch_init(void)
{
return 0;
}
There are two functions that are used to do runtime patching of arbitrary
functions. The first is used to turn the mcount call site into a nop (which
is what helps us retain runtime performance when not tracing). The second is
used to turn the mcount call site into a call to an arbitrary location (but
typically that is ftracer_caller()). See the general function definition in
linux/ftrace.h for the functions::
ftrace_make_nop()
ftrace_make_call()
The rec->ip value is the address of the mcount call site that was collected
by the scripts/recordmcount.pl during build time.
The last function is used to do runtime patching of the active tracer. This
will be modifying the assembly code at the location of the ftrace_call symbol
inside of the ftrace_caller() function. So you should have sufficient padding
at that location to support the new function calls you'll be inserting. Some
people will be using a "call" type instruction while others will be using a
"branch" type instruction. Specifically, the function is::
ftrace_update_ftrace_func()
HAVE_DYNAMIC_FTRACE와 HAVE_FUNCTION_GRAPH_TRACER
370-395함수 그래프 추적기를 동적 ftrace와 함께 쓰려면 `ftrace_caller()`, `ftrace_graph_call()`, `ftrace_graph_caller()`를 갱신하고 `ftrace_enable_ftrace_graph_caller()`와 `ftrace_disable_ftrace_graph_caller()`를 구현해야 한다.
이 절에도 원문 저자가 세부 내용을 채우지 않았음을 나타내는 `<details to be filled>` 표시가 남아 있다. 아래의 빠른 메모가 제공하는 범위 이상을 원문 요구 사항으로 단정하지 않는다.
`ftrace_call` 위치 뒤에는 `ftrace_graph_call`이라는 nop 스텁을 둔다. 이 공간은 `ftrace_graph_caller()` 호출 명령을 넣을 만큼 커야 한다. 또한 새 `ftrace_caller()`에서 호출될 때 달라진 의미에 맞춰 `ftrace_graph_caller()`를 조정한다.
그래프 추적을 켤 때 `ftrace_enable_ftrace_graph_caller()`가 `ftrace_graph_call` 위치를 `ftrace_graph_caller()` 호출로 패치한다. 끌 때는 `ftrace_disable_ftrace_graph_caller()`가 같은 위치를 nop으로 되돌린다.
별도 패치 지점을 호출과 nop 사이에서 전환한다.
HAVE_DYNAMIC_FTRACE + HAVE_FUNCTION_GRAPH_TRACER
------------------------------------------------
The function grapher needs a few tweaks in order to work with dynamic ftrace.
Basically, you will need to:
- update:
- ftrace_caller()
- ftrace_graph_call()
- ftrace_graph_caller()
- implement:
- ftrace_enable_ftrace_graph_caller()
- ftrace_disable_ftrace_graph_caller()
<details to be filled>
Quick notes:
- add a nop stub after the ftrace_call location named ftrace_graph_call;
stub needs to be large enough to support a call to ftrace_graph_caller()
- update ftrace_graph_caller() to work with being called by the new
ftrace_caller() since some semantics may have changed
- ftrace_enable_ftrace_graph_caller() will runtime patch the
ftrace_graph_call location with a call to ftrace_graph_caller()
- ftrace_disable_ftrace_graph_caller() will runtime patch the
ftrace_graph_call location with nops
요약·해설
ftrace-design.rst:1-395아키텍처가 기본 함수 추적, 함수 그래프 복귀 가로채기, 시스템 호출 tracepoint, 동적 런타임 패치를 구현하는 순서와 ABI 책임을 설명합니다.