요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.
1. 요약·해설
원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.
2. 영어 원문 전체
번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.
원문 전체 펼치기
.. SPDX-License-Identifier: GPL-2.0
==================================
Fprobe - Function entry/exit probe
==================================
.. Author: Masami Hiramatsu <mhiramat@kernel.org>
Introduction
============
Fprobe is a function entry/exit probe based on the function-graph tracing
feature in ftrace.
Instead of tracing all functions, if you want to attach callbacks on specific
function entry and exit, similar to the kprobes and kretprobes, you can
use fprobe. Compared with kprobes and kretprobes, fprobe gives faster
instrumentation for multiple functions with single handler. This document
describes how to use fprobe.
The usage of fprobe
===================
The fprobe is a wrapper of ftrace (+ kretprobe-like return callback) to
attach callbacks to multiple function entry and exit. User needs to set up
the `struct fprobe` and pass it to `register_fprobe()`.
Typically, `fprobe` data structure is initialized with the `entry_handler`
and/or `exit_handler` as below.
.. code-block:: c
struct fprobe fp = {
.entry_handler = my_entry_callback,
.exit_handler = my_exit_callback,
};
To enable the fprobe, call one of register_fprobe(), register_fprobe_ips(), and
register_fprobe_syms(). These functions register the fprobe with different types
of parameters.
The register_fprobe() enables a fprobe by function-name filters.
E.g. this enables @fp on "func*()" function except "func2()".::
register_fprobe(&fp, "func*", "func2");
The register_fprobe_ips() enables a fprobe by ftrace-location addresses.
E.g.
.. code-block:: c
unsigned long ips[] = { 0x.... };
register_fprobe_ips(&fp, ips, ARRAY_SIZE(ips));
And the register_fprobe_syms() enables a fprobe by symbol names.
E.g.
.. code-block:: c
char syms[] = {"func1", "func2", "func3"};
register_fprobe_syms(&fp, syms, ARRAY_SIZE(syms));
To disable (remove from functions) this fprobe, call::
unregister_fprobe(&fp);
You can temporally (soft) disable the fprobe by::
disable_fprobe(&fp);
and resume by::
enable_fprobe(&fp);
The above is defined by including the header::
#include <linux/fprobe.h>
Same as ftrace, the registered callbacks will start being called some time
after the register_fprobe() is called and before it returns. See
:file:`Documentation/trace/ftrace.rst`.
Also, the unregister_fprobe() will guarantee that both enter and exit
handlers are no longer being called by functions after unregister_fprobe()
returns as same as unregister_ftrace_function().
The fprobe entry/exit handler
=============================
The prototype of the entry/exit callback function are as follows:
.. code-block:: c
int entry_callback(struct fprobe *fp, unsigned long entry_ip, unsigned long ret_ip, struct ftrace_regs *fregs, void *entry_data);
void exit_callback(struct fprobe *fp, unsigned long entry_ip, unsigned long ret_ip, struct ftrace_regs *fregs, void *entry_data);
Note that the @entry_ip is saved at function entry and passed to exit
handler.
If the entry callback function returns !0, the corresponding exit callback
will be cancelled.
@fp
This is the address of `fprobe` data structure related to this handler.
You can embed the `fprobe` to your data structure and get it by
container_of() macro from @fp. The @fp must not be NULL.
@entry_ip
This is the ftrace address of the traced function (both entry and exit).
Note that this may not be the actual entry address of the function but
the address where the ftrace is instrumented.
@ret_ip
This is the return address that the traced function will return to,
somewhere in the caller. This can be used at both entry and exit.
@fregs
This is the `ftrace_regs` data structure at the entry and exit. This
includes the function parameters, or the return values. So user can
access thos values via appropriate `ftrace_regs_*` APIs.
@entry_data
This is a local storage to share the data between entry and exit handlers.
This storage is NULL by default. If the user specify `exit_handler` field
and `entry_data_size` field when registering the fprobe, the storage is
allocated and passed to both `entry_handler` and `exit_handler`.
Entry data size and exit handlers on the same function
======================================================
Since the entry data is passed via per-task stack and it has limited size,
the entry data size per probe is limited to `15 * sizeof(long)`. You also need
to take care that the different fprobes are probing on the same function, this
limit becomes smaller. The entry data size is aligned to `sizeof(long)` and
each fprobe which has exit handler uses a `sizeof(long)` space on the stack,
you should keep the number of fprobes on the same function as small as
possible.
Share the callbacks with kprobes
================================
Since the recursion safeness of the fprobe (and ftrace) is a bit different
from the kprobes, this may cause an issue if user wants to run the same
code from the fprobe and the kprobes.
Kprobes has per-cpu 'current_kprobe' variable which protects the kprobe
handler from recursion in all cases. On the other hand, fprobe uses
only ftrace_test_recursion_trylock(). This allows interrupt context to
call another (or same) fprobe while the fprobe user handler is running.
This is not a matter if the common callback code has its own recursion
detection, or it can handle the recursion in the different contexts
(normal/interrupt/NMI.)
But if it relies on the 'current_kprobe' recursion lock, it has to check
kprobe_running() and use kprobe_busy_*() APIs.
Fprobe has FPROBE_FL_KPROBE_SHARED flag to do this. If your common callback
code will be shared with kprobes, please set FPROBE_FL_KPROBE_SHARED
*before* registering the fprobe, like:
.. code-block:: c
fprobe.flags = FPROBE_FL_KPROBE_SHARED;
register_fprobe(&fprobe, "func*", NULL);
This will protect your common callback from the nested call.
The missed counter
==================
The `fprobe` data structure has `fprobe::nmissed` counter field as same as
kprobes.
This counter counts up when;
- fprobe fails to take ftrace_recursion lock. This usually means that a function
which is traced by other ftrace users is called from the entry_handler.
- fprobe fails to setup the function exit because of failing to allocate the
data buffer from the per-task shadow stack.
The `fprobe::nmissed` field counts up in both cases. Therefore, the former
skips both of entry and exit callback and the latter skips the exit
callback, but in both case the counter will increase by 1.
Note that if you set the FTRACE_OPS_FL_RECURSION and/or FTRACE_OPS_FL_RCU to
`fprobe::ops::flags` (ftrace_ops::flags) when registering the fprobe, this
counter may not work correctly, because ftrace skips the fprobe function which
increase the counter.
Functions and structures
========================
.. kernel-doc:: include/linux/fprobe.h
.. kernel-doc:: kernel/trace/fprobe.c
3. 한국어 전문 번역
영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.
Fprobe 소개
1-19이 문서는 GPL-2.0 조건을 따르며 Masami Hiramatsu가 작성했다.
Fprobe는 ftrace의 function-graph tracing 기능을 기반으로 function entry와 exit를 탐사하는 probe다.
모든 function을 trace하지 않고 kprobe·kretprobe처럼 특정 function의 진입과 종료에 callback을 연결하려면 fprobe를 사용할 수 있다. 하나의 handler로 여러 function을 계측할 때 fprobe는 kprobe·kretprobe보다 빠른 instrumentation을 제공한다. 이 문서는 fprobe 사용법을 설명한다.
function-graph tracing 위에서 선택한 function의 entry와 exit callback을 실행한다.
.. SPDX-License-Identifier: GPL-2.0
==================================
Fprobe - Function entry/exit probe
==================================
.. Author: Masami Hiramatsu <mhiramat@kernel.org>
Introduction
============
Fprobe is a function entry/exit probe based on the function-graph tracing
feature in ftrace.
Instead of tracing all functions, if you want to attach callbacks on specific
function entry and exit, similar to the kprobes and kretprobes, you can
use fprobe. Compared with kprobes and kretprobes, fprobe gives faster
instrumentation for multiple functions with single handler. This document
describes how to use fprobe.
Fprobe 사용법
20-87fprobe는 ftrace에 kretprobe와 비슷한 return callback을 더한 wrapper로, 여러 function의 entry와 exit에 callback을 연결한다. 사용자는 `struct fprobe`를 설정해 `register_fprobe()` 계열 함수에 전달한다.
일반적으로 `struct fprobe`의 `entry_handler`와 `exit_handler` 중 필요한 항목을 callback으로 초기화한다.
fprobe를 활성화하는 등록 함수는 `register_fprobe()`, `register_fprobe_ips()`, `register_fprobe_syms()` 세 가지이며 서로 다른 방식으로 탐사 대상을 지정한다.
`register_fprobe()`는 function name filter를 사용한다. `register_fprobe(&fp, "func*", "func2")`는 `func*()`에 일치하는 function 가운데 `func2()`를 제외하고 `fp`를 활성화한다.
`register_fprobe_ips()`는 ftrace location address array와 원소 수로 대상을 지정한다. `register_fprobe_syms()`는 symbol name array와 원소 수로 대상을 지정한다.
등록된 fprobe를 function에서 제거하려면 `unregister_fprobe()`를 호출한다. 등록은 유지한 채 잠시 soft-disable하려면 `disable_fprobe()`를 사용하고, `enable_fprobe()`로 다시 시작한다. 이 API는 `<linux/fprobe.h>`를 include하면 사용할 수 있다.
ftrace와 마찬가지로 등록 callback은 `register_fprobe()` 호출 뒤 반환하기 전 어느 시점부터 호출되기 시작할 수 있다. 자세한 timing 규칙은 `Documentation/trace/ftrace.rst`를 참조한다.
`unregister_fprobe()`는 반환한 뒤 function이 entry handler와 exit handler 어느 쪽도 더 호출하지 않음을 `unregister_ftrace_function()`과 동일하게 보장한다.
탐사 대상 지정 형태에 따라 등록 함수를 고른다.
등록, 일시 정지, 재개, 제거 순서다.
The usage of fprobe
===================
The fprobe is a wrapper of ftrace (+ kretprobe-like return callback) to
attach callbacks to multiple function entry and exit. User needs to set up
the `struct fprobe` and pass it to `register_fprobe()`.
Typically, `fprobe` data structure is initialized with the `entry_handler`
and/or `exit_handler` as below.
.. code-block:: c
struct fprobe fp = {
.entry_handler = my_entry_callback,
.exit_handler = my_exit_callback,
};
To enable the fprobe, call one of register_fprobe(), register_fprobe_ips(), and
register_fprobe_syms(). These functions register the fprobe with different types
of parameters.
The register_fprobe() enables a fprobe by function-name filters.
E.g. this enables @fp on "func*()" function except "func2()".::
register_fprobe(&fp, "func*", "func2");
The register_fprobe_ips() enables a fprobe by ftrace-location addresses.
E.g.
.. code-block:: c
unsigned long ips[] = { 0x.... };
register_fprobe_ips(&fp, ips, ARRAY_SIZE(ips));
And the register_fprobe_syms() enables a fprobe by symbol names.
E.g.
.. code-block:: c
char syms[] = {"func1", "func2", "func3"};
register_fprobe_syms(&fp, syms, ARRAY_SIZE(syms));
To disable (remove from functions) this fprobe, call::
unregister_fprobe(&fp);
You can temporally (soft) disable the fprobe by::
disable_fprobe(&fp);
and resume by::
enable_fprobe(&fp);
The above is defined by including the header::
#include <linux/fprobe.h>
Same as ftrace, the registered callbacks will start being called some time
after the register_fprobe() is called and before it returns. See
:file:`Documentation/trace/ftrace.rst`.
Also, the unregister_fprobe() will guarantee that both enter and exit
handlers are no longer being called by functions after unregister_fprobe()
returns as same as unregister_ftrace_function().
Fprobe entry·exit handler
88-128entry callback은 `struct fprobe *`, `entry_ip`, `ret_ip`, `struct ftrace_regs *`, `entry_data`를 받고 `int`를 반환한다. exit callback은 같은 parameter를 받지만 반환 type은 `void`다.
`entry_ip`는 function 진입 때 저장돼 exit handler에도 전달된다. entry callback이 0이 아닌 값을 반환하면 그 진입에 대응하는 exit callback은 취소된다.
`fp`는 이 handler와 연관된 `fprobe` data structure의 주소다. fprobe를 사용자 data structure 안에 embed하고 `container_of()`로 enclosing object를 얻을 수 있다. `fp`는 `NULL`이면 안 된다.
`entry_ip`는 탐사한 function의 ftrace address로 entry와 exit 양쪽에 전달된다. 실제 function entry address가 아니라 ftrace instrumentation이 삽입된 위치일 수 있다.
`ret_ip`는 탐사한 function이 돌아갈 caller 내부의 return address이며 entry와 exit 모두에서 사용할 수 있다.
`fregs`는 entry 또는 exit 시점의 `ftrace_regs` data structure다. function parameter나 return value를 포함하므로 적절한 `ftrace_regs_*` API로 값을 읽을 수 있다.
`entry_data`는 entry handler와 exit handler 사이에서 data를 공유하는 local storage다. 기본은 `NULL`이다. 등록할 때 `exit_handler`와 `entry_data_size`를 지정하면 storage가 할당돼 두 handler에 모두 전달된다.
entry와 exit callback에 전달되는 context다.
The fprobe entry/exit handler
=============================
The prototype of the entry/exit callback function are as follows:
.. code-block:: c
int entry_callback(struct fprobe *fp, unsigned long entry_ip, unsigned long ret_ip, struct ftrace_regs *fregs, void *entry_data);
void exit_callback(struct fprobe *fp, unsigned long entry_ip, unsigned long ret_ip, struct ftrace_regs *fregs, void *entry_data);
Note that the @entry_ip is saved at function entry and passed to exit
handler.
If the entry callback function returns !0, the corresponding exit callback
will be cancelled.
@fp
This is the address of `fprobe` data structure related to this handler.
You can embed the `fprobe` to your data structure and get it by
container_of() macro from @fp. The @fp must not be NULL.
@entry_ip
This is the ftrace address of the traced function (both entry and exit).
Note that this may not be the actual entry address of the function but
the address where the ftrace is instrumented.
@ret_ip
This is the return address that the traced function will return to,
somewhere in the caller. This can be used at both entry and exit.
@fregs
This is the `ftrace_regs` data structure at the entry and exit. This
includes the function parameters, or the return values. So user can
access thos values via appropriate `ftrace_regs_*` APIs.
@entry_data
This is a local storage to share the data between entry and exit handlers.
This storage is NULL by default. If the user specify `exit_handler` field
and `entry_data_size` field when registering the fprobe, the storage is
allocated and passed to both `entry_handler` and `exit_handler`.
같은 function의 entry data 크기와 exit handler
129-139entry data는 크기가 제한된 per-task stack을 통해 전달되므로 probe 하나의 `entry_data_size`는 `15 * sizeof(long)`으로 제한된다.
서로 다른 fprobe가 같은 function을 탐사하면 사용할 수 있는 한도가 더 작아진다. entry data 크기는 `sizeof(long)` 경계로 정렬되고, exit handler를 가진 각 fprobe는 stack에서 `sizeof(long)` 공간을 추가로 사용한다. 따라서 같은 function에 거는 fprobe 수는 가능한 한 적게 유지해야 한다.
같은 function에 여러 fprobe를 걸 때의 공간 제약이다.
Entry data size and exit handlers on the same function
======================================================
Since the entry data is passed via per-task stack and it has limited size,
the entry data size per probe is limited to `15 * sizeof(long)`. You also need
to take care that the different fprobes are probing on the same function, this
limit becomes smaller. The entry data size is aligned to `sizeof(long)` and
each fprobe which has exit handler uses a `sizeof(long)` space on the stack,
you should keep the number of fprobes on the same function as small as
possible.
Kprobe와 callback 공유
140-169fprobe와 ftrace의 recursion 안전 방식은 kprobe와 조금 다르므로 같은 callback code를 fprobe와 kprobe에서 함께 실행하면 문제가 생길 수 있다.
kprobe는 per-CPU `current_kprobe` variable로 모든 경우에 handler recursion을 막는다. 반면 fprobe는 `ftrace_test_recursion_trylock()`만 사용한다. 따라서 fprobe user handler가 실행 중이어도 interrupt context가 다른 fprobe 또는 같은 fprobe를 다시 호출할 수 있다.
공통 callback code가 자체 recursion detection을 갖거나 normal·interrupt·NMI의 서로 다른 context에서 recursion을 처리할 수 있다면 문제가 되지 않는다. 하지만 `current_kprobe` recursion lock에 의존한다면 `kprobe_running()`을 검사하고 `kprobe_busy_*()` API를 사용해야 한다.
이 처리를 위해 fprobe는 `FPROBE_FL_KPROBE_SHARED` flag를 제공한다. callback code를 kprobe와 공유한다면 fprobe 등록 전에 이 flag를 설정해야 하며, 그러면 공통 callback을 nested call로부터 보호한다.
등록 전에 shared flag를 설정해 kprobe 호환 recursion guard를 적용한다.
Share the callbacks with kprobes
================================
Since the recursion safeness of the fprobe (and ftrace) is a bit different
from the kprobes, this may cause an issue if user wants to run the same
code from the fprobe and the kprobes.
Kprobes has per-cpu 'current_kprobe' variable which protects the kprobe
handler from recursion in all cases. On the other hand, fprobe uses
only ftrace_test_recursion_trylock(). This allows interrupt context to
call another (or same) fprobe while the fprobe user handler is running.
This is not a matter if the common callback code has its own recursion
detection, or it can handle the recursion in the different contexts
(normal/interrupt/NMI.)
But if it relies on the 'current_kprobe' recursion lock, it has to check
kprobe_running() and use kprobe_busy_*() APIs.
Fprobe has FPROBE_FL_KPROBE_SHARED flag to do this. If your common callback
code will be shared with kprobes, please set FPROBE_FL_KPROBE_SHARED
*before* registering the fprobe, like:
.. code-block:: c
fprobe.flags = FPROBE_FL_KPROBE_SHARED;
register_fprobe(&fprobe, "func*", NULL);
This will protect your common callback from the nested call.
Missed counter
170-192`struct fprobe`에는 kprobe와 마찬가지로 `fprobe::nmissed` counter field가 있다.
첫 번째 증가 조건은 fprobe가 `ftrace_recursion` lock을 얻지 못한 경우다. 일반적으로 `entry_handler`가 호출한 function을 다른 ftrace user가 trace하고 있음을 뜻한다. 이 경우 entry와 exit callback이 모두 건너뛰어진다.
두 번째 조건은 per-task shadow stack에서 data buffer를 할당하지 못해 function exit 설정에 실패한 경우다. 이 경우에는 exit callback만 건너뛴다.
두 상황 모두 `fprobe::nmissed`가 1 증가하므로 counter만으로 어느 실패였는지는 구분할 수 없다.
등록할 때 `fprobe::ops::flags`, 즉 `ftrace_ops::flags`에 `FTRACE_OPS_FL_RECURSION` 또는 `FTRACE_OPS_FL_RCU`를 설정하면 이 counter가 정확히 동작하지 않을 수 있다. counter를 증가시키는 fprobe function 자체를 ftrace가 건너뛸 수 있기 때문이다.
실패 지점에 따라 생략되는 callback이 다르다.
The missed counter
==================
The `fprobe` data structure has `fprobe::nmissed` counter field as same as
kprobes.
This counter counts up when;
- fprobe fails to take ftrace_recursion lock. This usually means that a function
which is traced by other ftrace users is called from the entry_handler.
- fprobe fails to setup the function exit because of failing to allocate the
data buffer from the per-task shadow stack.
The `fprobe::nmissed` field counts up in both cases. Therefore, the former
skips both of entry and exit callback and the latter skips the exit
callback, but in both case the counter will increase by 1.
Note that if you set the FTRACE_OPS_FL_RECURSION and/or FTRACE_OPS_FL_RCU to
`fprobe::ops::flags` (ftrace_ops::flags) when registering the fprobe, this
counter may not work correctly, because ftrace skips the fprobe function which
increase the counter.
함수와 구조체 문서
193-198`include/linux/fprobe.h`와 `kernel/trace/fprobe.c`의 kernel-doc에서 fprobe 함수와 구조체의 상세 API 문서를 확인할 수 있다.
Functions and structures
========================
.. kernel-doc:: include/linux/fprobe.h
.. kernel-doc:: kernel/trace/fprobe.c
요약·해설
fprobe.rst:1-198ftrace 기반 fprobe의 등록 방식, entry·exit handler context, recursion 보호와 missed counter를 설명합니다.