Overview
리눅스 커널 버전이 업그레이드 되면서 함수의 이름이나 루틴이 계속 바뀌고 있습니다.
리눅스 커널이 오픈 소스 프로젝트로 운영되니 당연한 현상으로 볼 수 있는데요.
커널 버전이 업그레이드되면서 예전 버전에 잘 동작하던 셸 스크립트도 업데이트해야 합니다.
* 집필 시점 라즈비안 커널 버전: v4.19.30
* 현재(2022년 6월) 라즈비안 커널 버전: v5.15.31
리눅스 커널은 생명체와 같이 살아 숨시는 것 같네요.
4.11.2 절에서 언급된 스크립트
271 페이지를 보면 ftrace 로그를 설정하는 셸 스크립트 코드가 확인됩니다.
echo do_exit search_binary_handler copy_process.part.5 > /sys/kernel/debug/tracing/set_ftrace_filter
echo sys_clone _do_fork >> /sys/kernel/debug/tracing/set_ftrace_filter
위 코드는 v4.19 버전에서 잘 동작합니다. 그런데 최신 라즈비안 커널인 v5.15.31에서는 제대로 동작하지 않습니다. 그 이유는 무엇일까요? '/sys/kernel/debug/tracing/available_filter_functions' 파일에 search_binary_handler와 _do_fork 심벌이 존재하지 않기 때문입니다.
업데이트된 셸 스크립트
최신 라즈비안 커널 버전(v5.15.31)에서는 다음 셸 스크립트를 실행하면 실습을 잘 진행할 수 있습니다.
01 #!/bin/bash
02
03 echo 0 > /sys/kernel/debug/tracing/tracing_on
04 sleep 1
05 echo "tracing_off"
06
07 echo 0 > /sys/kernel/debug/tracing/events/enable
08 sleep 1
09 echo "events disabled"
10
11 echo secondary_start_kernel > /sys/kernel/debug/tracing/set_ftrace_filter
12 sleep 1
13 echo "set_ftrace_filter init"
14
15 echo function > /sys/kernel/debug/tracing/current_tracer
16 sleep 1
17 echo "function tracer enabled"
18
19 #echo do_exit __audit_bprm copy_process > /sys/kernel/debug/tracing/set_ftrace_filter
20 #echo sys_clone kernel_clone >> /sys/kernel/debug/tracing/set_ftrace_filter
21
22 sleep 1
23 echo "set_ftrace_filter enabled"
24
25 echo 1 > /sys/kernel/debug/tracing/events/sched/sched_process_exec/enable
26 echo 1 > /sys/kernel/debug/tracing/events/sched/sched_process_exit/enable
27 echo 1 > /sys/kernel/debug/tracing/events/sched/sched_process_fork/enable
28 echo 1 > /sys/kernel/debug/tracing/events/sched/sched_process_free/enable
29 sleep 1
30 echo "event enabled"
31
32 echo 1 > /sys/kernel/debug/tracing/options/func_stack_trace
33 echo 1 > /sys/kernel/debug/tracing/options/stacktrace
34 echo 1 > /sys/kernel/debug/tracing/options/sym-offset
35 echo "function stack trace enabled"
36
37 echo 1 > /sys/kernel/debug/tracing/tracing_on
38 echo "tracing_on"
셸 스크립트의 내용을 설명하면요.
19 #echo do_exit search_binary_handler copy_process.part.5 > /sys/kernel/debug/tracing/set_ftrace_filter
20 #echo sys_clone kernel_clone >> /sys/kernel/debug/tracing/set_ftrace_filter
먼저 19~20번째 줄을 주석 처리합니다.
그 다음에 33번째 줄을 추가합니다.
33 echo 1 > /sys/kernel/debug/tracing/options/stacktrace
'/sys/kernel/debug/tracing/options/stacktrace'을 1로 설정하면 ftrace 이벤트가 출력될 때
콜 스택을 출력해줍니다. 아래와 같이 /sys/kernel/debug/tracing/set_ftrace_filter을 설정하지 않아도 됩니다.
그래서 아래 코드를 주석 처리하는 것입니다.
19 #echo do_exit search_binary_handler copy_process.part.5 > /sys/kernel/debug/tracing/set_ftrace_filter
20 #echo sys_clone kernel_clone >> /sys/kernel/debug/tracing/set_ftrace_filter
실습
위에서 언급한 셸 스크립트를 설정한 후 273 페이지에 명시된 내용을 따라서 실습을 하면 됩니다.
실습 과정을 요약하면 다음과 같습니다.
1. 셸 스크립트 설정: ftrace 설정
2. 터미널에서 아래 명령어 입력
$ whoami
$ cat /proc/interrupts
3. get_ftrace.sh 명령어를 입력해 ftrace 메시지 추출
ftrace 메시지 분석 #1
whoami 명령어를 입력했을 때 프로세스가 생성, 실행 그리고 종료되는 메시지는 다음과 같습니다.
01 bash-1558 [002] ..... 2150.857123: sched_process_fork: comm=bash pid=1558 child_comm=bash child_pid=2935
02 bash-1558 [002] ..... 2150.857129: <stack trace>
03 => __traceiter_sched_process_fork+0x44/0x58
04 => kernel_clone+0x1bc/0x470
05 => sys_clone+0x80/0x9c
06 => ret_fast_syscall+0x0/0x1c
07 => 0xbe933240
08 whoami-2935 [003] ..... 2150.860094: sched_process_exec: filename=/usr/bin/whoami pid=2935 old_pid=2935
09 whoami-2935 [003] ..... 2150.860100: <stack trace>
10 => __traceiter_sched_process_exec+0x4c/0x60
11 => bprm_execve+0x600/0x668
12 => do_execveat_common+0x17c/0x194
13 => sys_execve+0x40/0x48
14 => ret_fast_syscall+0x0/0x1c
15 => 0xbeacb760
16 whoami-2935 [003] ..... 2150.864758: sched_process_exit: comm=whoami pid=2935 prio=120
17 whoami-2935 [003] ..... 2150.864763: <stack trace>
18 => __traceiter_sched_process_exit+0x3c/0x50
19 => do_exit+0x674/0xb88
20 => do_group_exit+0x50/0xe0
21 => __wake_up_parent+0x0/0x2c
22 => ret_fast_syscall+0x0/0x1c
23 => 0xbeacb5e0
먼저 01~07번째 줄을 보겠습니다.
01 bash-1558 [002] ..... 2150.857123: sched_process_fork: comm=bash pid=1558 child_comm=bash child_pid=2935
02 bash-1558 [002] ..... 2150.857129: <stack trace>
03 => __traceiter_sched_process_fork+0x44/0x58
04 => kernel_clone+0x1bc/0x470
05 => sys_clone+0x80/0x9c
06 => ret_fast_syscall+0x0/0x1c
07 => 0xbe933240
PID가 1558인 bash 프로세스가 PID가 2935인 whoami 프로세스를 생성하는 메시지입니다.
책에 설명된 바와 같이 부모 프로세스가 자식 프로세스를 생성하는 단계에서 자식 프로세스의 이름은 부모 프로세스의 이름과 같습니다.
여기서 눈여겨 볼 메시지는 04번째 줄입니다. v4.19 버전에서 04번째 줄은 다음과 같이 출력될 것입니다.
04 => _do_fork+0x1bc/0x470
그런데 커널 버전이 업그레이드 되면서 _do_fork() 함수의 이름이 kernel_clone() 함수로 변경됐습니다.
관련 커밋과 소스 위치는 다음과 같습니다.
[커밋]
https://lkml.org/lkml/2020/8/18/987
...
diff --git a/kernel/fork.c b/kernel/fork.c
index 4d32190861bd..34e37cee239f 100644
--- a/kernel/fork.c
+++ b/kernel/fork.c
@@ -2384,7 +2384,7 @@ struct mm_struct *copy_init_mm(void)
*
* args->exit_signal is expected to be checked for sanity by the caller.
*/
-long _do_fork(struct kernel_clone_args *args)
+int kernel_clone(struct kernel_clone_args *args)
{
u64 clone_flags = args->flags;
struct completion vfork;
[소스 위치]
https://elixir.bootlin.com/linux/v5.15.31/source/kernel/fork.c#L2503
pid_t kernel_clone(struct kernel_clone_args *args)
{
u64 clone_flags = args->flags;
struct completion vfork;
struct pid *pid;
이어서 08~15번째 줄을 보겠습니다.
08 whoami-2935 [003] ..... 2150.860094: sched_process_exec: filename=/usr/bin/whoami pid=2935 old_pid=2935
09 whoami-2935 [003] ..... 2150.860100: <stack trace>
10 => __traceiter_sched_process_exec+0x4c/0x60
11 => bprm_execve+0x600/0x668
12 => do_execveat_common+0x17c/0x194
13 => sys_execve+0x40/0x48
14 => ret_fast_syscall+0x0/0x1c
15 => 0xbeacb760
위 콜 스택에서 13번째 줄의 함수를 눈여겨 볼 필요가 있습니다.
sys_execve() 함수는 execve 시스템 콜 핸들러 함수이므로 유저 공간에서 execve 시스템 콜을
실행했다는 사실을 유추할 수 있습니다.
08번째 줄은 프로세스의 자세한 실행 정보를 출력합니다.
08 whoami-2935 [003] ..... 2150.860094: sched_process_exec: filename=/usr/bin/whoami pid=2935 old_pid=2935
PID가 2935인 whoami 프로세스의 정체는 /usr/bin/whoami이란 사실을 알 수 있습니다.
리눅스 커널 버전이 업그레이드되도 리눅스 배포판에서 지정한 유틸리티 파일의 디렉토리는 자주 변경되지 않습니다.
마지막으로 16~23번째 줄을 분석하겠습니다.
16 whoami-2935 [003] ..... 2150.864758: sched_process_exit: comm=whoami pid=2935 prio=120
17 whoami-2935 [003] ..... 2150.864763: <stack trace>
18 => __traceiter_sched_process_exit+0x3c/0x50
19 => do_exit+0x674/0xb88
20 => do_group_exit+0x50/0xe0
21 => __wake_up_parent+0x0/0x2c // sys_exit_group 심벌이 맞는 정보입니다.
22 => ret_fast_syscall+0x0/0x1c
23 => 0xbeacb5e0
whoami 프로세스가 종료되는 함수 호출 흐름을 볼 수 있습니다.
위 메시지로 유저 공간에서 exit() 함수가 호출된 다음에 exit 시스템 콜이 유발됐다는
사실을 유추할 수 있습니다.
ftrace 메시지 분석 #2
이어서 cat 명령어를 입력했을 때 프로세스가 생성, 실행 그리고 종료되는 메시지는 다음과 같습니다.
01 bash-1558 [002] ..... 2159.250439: sched_process_fork: comm=bash pid=1558 child_comm=bash child_pid=2937
02 bash-1558 [002] ..... 2159.250445: <stack trace>
03 => __traceiter_sched_process_fork+0x44/0x58
04 => kernel_clone+0x1bc/0x470
05 => sys_clone+0x80/0x9c
06 => ret_fast_syscall+0x0/0x1c
07 => 0xbe933240
08 cat-2937 [003] ..... 2159.253458: sched_process_exec: filename=/usr/bin/cat pid=2937 old_pid=2937
09 cat-2937 [003] ..... 2159.253464: <stack trace>
10 => __traceiter_sched_process_exec+0x4c/0x60
11 => bprm_execve+0x600/0x668
12 => do_execveat_common+0x17c/0x194
13 => sys_execve+0x40/0x48
14 => ret_fast_syscall+0x0/0x1c
15 => 0xbeade760
16 cat-2937 [003] ..... 2159.257431: sched_process_exit: comm=cat pid=2937 prio=120
17 cat-2937 [003] ..... 2159.257436: <stack trace>
18 => __traceiter_sched_process_exit+0x3c/0x50
19 => do_exit+0x674/0xb88
20 => do_group_exit+0x50/0xe0
21 => __wake_up_parent+0x0/0x2c
22 => ret_fast_syscall+0x0/0x1c
23 => 0xbeade5e0
먼저 01~07번째 줄을 분석하겠습니다.
01 bash-1558 [002] ..... 2159.250439: sched_process_fork: comm=bash pid=1558 child_comm=bash child_pid=2937
02 bash-1558 [002] ..... 2159.250445: <stack trace>
03 => __traceiter_sched_process_fork+0x44/0x58
04 => kernel_clone+0x1bc/0x470
05 => sys_clone+0x80/0x9c
06 => ret_fast_syscall+0x0/0x1c
07 => 0xbe933240
PID가 1558인 bash 프로세스가 PID가 1558인 cat 프로세스를 생성하는 메시지입니다.
04번째 줄과 같이 kernel_clone 심벌이 보이니 프로세스가 생성되는 실행 흐름이란 사실을
알 수 있습니다.
이어서 08~15번째 줄을 보겠습니다.
08 cat-2937 [003] ..... 2159.253458: sched_process_exec: filename=/usr/bin/cat pid=2937 old_pid=2937
09 cat-2937 [003] ..... 2159.253464: <stack trace>
10 => __traceiter_sched_process_exec+0x4c/0x60
11 => bprm_execve+0x600/0x668
12 => do_execveat_common+0x17c/0x194
13 => sys_execve+0x40/0x48
14 => ret_fast_syscall+0x0/0x1c
15 => 0xbeade760
역시 위 콜 스택에서 13번째 줄의 함수를 눈여겨 볼 필요가 있습니다.
유저 공간에서 execve 시스템 콜을 실행해 execve 시스템 콜 핸들러 함수인 sys_execve() 함수가 호출됐습니다.
08번째 줄은 프로세스의 자세한 실행 정보를 출력합니다.
08 cat-2937 [003] ..... 2159.253458: sched_process_exec: filename=/usr/bin/cat pid=2937 old_pid=2937
PID가 2937인 cat 프로세스의 정체는 /usr/bin/cat이란 사실을 알 수 있습니다.
마지막으로 16~23번째 줄을 보겠습니다.
16 cat-2937 [003] ..... 2159.257431: sched_process_exit: comm=cat pid=2937 prio=120
17 cat-2937 [003] ..... 2159.257436: <stack trace>
18 => __traceiter_sched_process_exit+0x3c/0x50
19 => do_exit+0x674/0xb88
20 => do_group_exit+0x50/0xe0
21 => __wake_up_parent+0x0/0x2c
22 => ret_fast_syscall+0x0/0x1c
23 => 0xbeade5e0
cat 프로세스가 종료되는 함수 호출 흐름입니다. 핵심 함수인 do_exit() 함수가 19번째 줄에서 확인됩니다.
최근 덧글