Linux Kernel(4.19) Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

138199
1107
135866


[리눅스커널] 가상파일시스템/디버깅: 아이노드 객체 함수 오퍼레이션 확인 13. 가상 파일 시스템

이전 소절에서 실습했듯이 슈퍼블록 statfs 함수 오퍼레이션으로 파일시스템의 속성 정보를 읽거나 저장합니다. 이와 마찬가지로 파일의 속성 정보를 읽거나 저장하는 과정에서 아이노드 객체 함수 오퍼레이션이 실행됩니다.

이번 소절에서 유저 어플리케이션에서 stat 함수를 호출하면 아이노드 객체 함수 오퍼레이션 함수 흐름을 확인하겠습니다.

실습 패치 코드 작성해보기 

이어서 stat() 함수를 호출해 파일속성을 읽는 코드를 작성해보겠습니다. 다음 소스 코드를 같이 입력해볼까요?
01 #include <stdio.h>
02 #include <stdlib.h>
03 #include <unistd.h>
04 #include <sys/types.h>
05 #include <sys/stat.h>
06 #include <signal.h>
07 #include <string.h>
08 #include <fcntl.h>
09
10 #define GENERAL_FILE "/home/pi/sample_text.text"
11 #define PROC_FILE "/proc/cmdline"
12
13 #define BUFF_SIZE 128
14
15 int main() 
16 {
17 struct stat fileinfo;
18 char fname[BUFF_SIZE] = {0,};
19
20 strcpy(fname, GENERAL_FILE);
21
22 printf("fname[%s] \n", fname);
23 if(stat(fname, &fileinfo)) {
24 printf("Unable to stat %s \n", fname);
25 exit(1);
26 }
27
28 strcpy(fname, PROC_FILE);
29
30 printf("fname[%s] \n", fname);
31 if(stat(fname, &fileinfo)) {
32 printf("Unable to stat %s \n", fname);
33 exit(1);
34 }
35
36 return 0;
37 }

위 코드의 핵심 내용은 다음 파일의 세부 속성을 stat() 함수로 읽는 것입니다.
"/home/pi/sample_text.text"
"/proc/cmdline"

소스 코드를 설명드리기 전에 stat() 함수가 어떤 기능인지 알아볼까요?
이를 위해 라즈베리파이에서 다음 'info stat' 명령어를 입력할 필요가 있습니다.
root@raspberrypi:/home/pi# info stat
Next: sync invocation,  Prev: du invocation,  Up: Disk usage

14.3 ‘stat’: Report file or file system status
==============================================

‘stat’ displays information about the specified file(s).  Synopsis:

위 출력 결과는 다음 내용을 말해줍니다.

    ‘stat’은 지정한 파일의 속성 정보를 알려준다.

이어서 위에서 소개한 소스 코드를 분석해볼까요?

먼저 20~26번째 줄 코드를 보겠습니다.
20 strcpy(fname, GENERAL_FILE);
21
22 printf("fname[%s] \n", fname);
23 if(stat(fname, &fileinfo)) {
24 printf("Unable to stat %s \n", fname);
25 exit(1);
26 }

20~22번째 줄 코드는 fname 변수에 GENERAL_FILE 매크로에서 지정한 "/home/pi/sample_text.text" 스트링을 복사하고 터미널에 출력하는 동작입니다.

23번째 줄 코드는 stat() 함수를 호출해 파일 속성을 읽습니다. 24~25번째 줄은 stat() 함수가 파일속성을 읽지 못해 true를 반환했을 때 실행하는 예외 처리 코드입니다. 

이어서 28~34번째 줄 코드를 보겠습니다.
28 strcpy(fname, PROC_FILE);
29
30 printf("fname[%s] \n", fname);
31 if(stat(fname, &fileinfo)) {
32 printf("Unable to stat %s \n", fname);
33 exit(1);
34 }

28~34번째 줄 코드는 다음 동작을 제외하고 20~26번째 줄 코드와 기능이 같습니다.

    stat() 함수로 "/proc/cmdline" 파일 속성을 읽자.

코드 내용에 대해 알아봤으니 소스 코드를 컴파일하는 방법을 알아볼까요?

여기서 한 가지 의문이 생깁니다.
     
    컴파일을 할때 어느 프로그램을 써야 할까?

프로그램 이름은 터미널입니다. 기존에 소개한 방식과 같인 라즈베리파이에서 터미널을 열어서 컴파일을 하는 것입니다. 

먼저 위에서 소개한 코드를 rpi_vfs_stat_operation.c 파일로 저장합니다.
컴파일을 쉽게 하기 위해 다음과 같이 코드를 작성하고 파일 이름을 Makefile으로 저장합시다.
stat_file_proc: rpi_vfs_stat_operation.c
gcc -o stat_file_proc rpi_vfs_stat_operation.c
이후 다음과 같이 make 명령어를 입력해 rpi_vfs_stat_operation.c 소스 파일을 컴파일합시다.
root@raspberrypi:/home/pi# make
gcc -o stat_file_proc rpi_vfs_stat_operation.c
오타없이 코드를 입력하면 위와 같은 메시지가 출력되면서 stat_file_proc 파일이 생성될 것입니다.
rpi_vfs_stat_operation.c 소스 파일의 실행 파일 이름은 stat_file_proc입니다.

ftrace 설정해보기 

이어서 ftrace 설정하는 방법을 알아볼까요? 명령어는 다음과 같습니다.
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 1 > /sys/kernel/debug/tracing/events/sched/sched_switch/enable
20 echo 1 > /sys/kernel/debug/tracing/events/raw_syscalls/sys_enter/enable
21 echo 1 > /sys/kernel/debug/tracing/events/raw_syscalls/sys_exit/enable
22 sleep 1
23 echo "event enabled"
24
echo ext4_file_getattr generic_fillattr > /sys/kernel/debug/tracing/set_ftrace_filter
28 sleep 1
29 echo "set_ftrace_filter enabled"
30
31 sleep 1
32 echo "set_ftrace_filter enabled"
33
34 echo 1 > /sys/kernel/debug/tracing/options/func_stack_trace
35 echo 1 > /sys/kernel/debug/tracing/options/sym-offset
36 echo "function stack trace enabled"
37
38 echo 1 > /sys/kernel/debug/tracing/tracing_on
39 echo "tracing_on"

위에서 소개한 ftrace 설정 명령어를 입력한 후 rpi_vfs_stat.sh 이름으로 저장합시다.

    이전 소절에서 봤던 ftrace 설정 명령어와 거의 비슷한 것 같다.

맞습니다. 그래서 이전 장에서 소개한 ftrace 설정 명령어와 차이점 부분 위주로 살펴보겠습니다.
echo ext4_file_getattr generic_fillattr > /sys/kernel/debug/tracing/set_ftrace_filter
underline

위 명령어는 set_ftrace_filter에 다음 함수를 설정합니다.
ext4_file_getattr
generic_fillattr

위에서 보이는 함수 콜스택을 ftrace로 보기 위해 set_ftrace_filter 파일에 함수를 지정하는 것입니다.

함수 이름이 조금 친숙해 보이지 않아요? 모두 13.6.2 절에서 분석한 아이노드 함수 오퍼레이션 함수입니다.

ftrace 로그 추출하는 방법

실습 코드 실행 파일 stat_file_proc이 준비됐고 ftrace 설정 방법도 알게 됐습니다.
이어서 ftrace를 설정하고 stat_file_proc 파일을 실행할 차례입니다.

먼저 rpi_vfs_stat.sh 셸 스크립트를 실행해 ftrace를 설정합시다.
root@raspberrypi:/home/pi # ./rpi_vfs_stat.sh

다음 "./vfs_file_proc" 명령어를 입력해 vfs_file_proc 파일을 실행합니다.
root@raspberrypi:/home/pi # ./stat_file_proc
fname[/home/pi/sample_text.text] 
fname[/proc/cmdline] 

이어서 ftrace 받는 방법을 소개합니다.
#!/bin/bash

echo 0 > /sys/kernel/debug/tracing/tracing_on
echo "ftrace off"

sleep 3

cp /sys/kernel/debug/tracing/trace . 
mv trace ftrace_log.c

위 명령어를 입력해 get_ftrace.sh 셸 스크립트로 저장합니다. 
이후 다음 명령어로 이 셸 스크립트를 실행하면 같은 폴더에 ftrace 로그를 저장한 ftrace_log.c 파일이 생성됩니다. 
root@raspberrypi:/home/pi # ./get_ftrace.sh 

여기까지 소개드린 실습 과정을 정리해볼까요?
      첫째, 유저 어플리케이션 rpi_vfs_stat_operation.c 코드를 입력한 다음
       컴파일한다. 실행 파일은 vfs_file_proc 이다.
      둘째, rpi_vfs_stat.sh 셸 스크립트를 실행해 ftace를 설정한다.
      셋째, vfs_file_proc 파일을 실행한다.
      넷째, get_ftrace.sh 셸 스크립트를 실행해 ftrace 로그를 받는다.

이제 이번 소절의 하이라이트인 ftrace 로그를 분석을 시작하겠습니다.

ftrace 로그 분석해보기 

다음은 이번 소절에 분석할 ftrace 로그입니다.
01  stat_file_proc-2848  [002] ....  9731.927338: sys_enter: NR 197 (3, 7ed91450, 7ed91450, 76f19c90, 3, 7ed91534)
02  stat_file_proc-2848  [002] ....  9731.927346: ext4_file_getattr+0x10/0xbc <-vfs_getattr_nosec+0x68/0x7c
03  stat_file_proc-2848  [002] ....  9731.927382: <stack trace>
04 => ext4_file_getattr+0x14/0xbc
05 => vfs_getattr_nosec+0x68/0x7c
06 => vfs_statx_fd+0x4c/0x78
07 => sys_fstat64+0x3c/0x6c
08 => __sys_trace_return+0x0/0x10
09 => 0x7ed91444
10  stat_file_proc-2848  [002] ....  9731.927385: generic_fillattr+0x10/0x108 <-ext4_getattr+0xc8/0xd0
11  stat_file_proc-2848  [002] ....  9731.927402: <stack trace>
12 => generic_fillattr+0x14/0x108
13 => ext4_getattr+0xc8/0xd0
14 => ext4_file_getattr+0x24/0xbc
15 => vfs_getattr_nosec+0x68/0x7c
16 => vfs_statx_fd+0x4c/0x78
17 => sys_fstat64+0x3c/0x6c
18 => __sys_trace_return+0x0/0x10
19 => 0x7ed91444
20  stat_file_proc-2848 [002] .... 9731.927409: sys_exit: NR 197 = 0
...
21  stat_file_proc-2848  [002] ....  9731.929170: sys_enter: NR 197 (1, 7ed90f18, 7ed90f18, 76de5f18, 76eb9d50, 76eb7bec)
22  stat_file_proc-2848  [002] ....  9731.929174: generic_fillattr+0x10/0x108 <-vfs_getattr_nosec+0x74/0x7c
23  stat_file_proc-2848  [002] ....  9731.929190: <stack trace>
24 => generic_fillattr+0x14/0x108
25 => vfs_getattr_nosec+0x74/0x7c
26 => vfs_statx_fd+0x4c/0x78
27 => sys_fstat64+0x3c/0x6c
28 => __sys_trace_return+0x0/0x10
29 => 0x7ed90f10
30  stat_file_proc-2848 [002] .... 9731.929195: sys_exit: NR 197 = 0

이번 실습은 유저 공간에서 stat() 함수로 파일 속성을 읽을 때 커널 공간 아이노드 stat 함수 오퍼레이션 함수 실행 흐름을 알아보기 위한 것입니다. 복잡해보이는 ftrace 로그 실행 흐름은 2단계로 분류할 수 있습니다.
1단계: "/home/pi/sample_text.text" 파일 속성을 읽을 때 커널 공간 아이노드 stat 함수 오퍼레이션 함수 실행 흐름
2단계: "/proc/cmdline" 파일 속성을 읽을 때 커널 공간 아이노드 stat 함수        오퍼레이션 함수 실행 흐름 

1단계: "/home/pi/sample_text.text" 파일 속성을 읽을 때 함수 실행 흐름
"/home/pi/sample_text.text" 파일 속성을 읽을 때 함수 실행 흐름을 분석해볼까요?
01  stat_file_proc-2848  [002] ....  9731.927338: sys_enter: NR 197 (3, 7ed91450, 7ed91450, 76f19c90, 3, 7ed91534)
02  stat_file_proc-2848  [002] ....  9731.927346: ext4_file_getattr+0x10/0xbc <-vfs_getattr_nosec+0x68/0x7c
03  stat_file_proc-2848  [002] ....  9731.927382: <stack trace>
04 => ext4_file_getattr+0x14/0xbc
05 => vfs_getattr_nosec+0x68/0x7c
06 => vfs_statx_fd+0x4c/0x78
07 => sys_fstat64+0x3c/0x6c
08 => __sys_trace_return+0x0/0x10
09 => 0x7ed91444

먼저 01번째 줄 코드를 보겠습니다.
01  stat_file_proc-2848  [002] ....  9731.927338: sys_enter: NR 197 (3, 7ed91450, 7ed91450, 76f19c90, 3, 7ed91534)

01번째 줄 메시지 'sys_enter: NR 197'는 다음 사실을 말해줍니다.

    197번 시스템 콜 실행을 시작한다.

그러면 197번 시스템 콜의 정체는 무엇일까요? 다음 코드를 보면 알 수 있습니다.
[https://elixir.bootlin.com/linux/v4.4.180/source/arch/arm/include/uapi/asm/unistd.h]
#define __NR_fstat64 (__NR_SYSCALL_BASE+197)


"혹시 궁금한 점이 있으면 댓글로 질문 남겨주세요. 아는 한 성실히 답변 올려드리겠습니다!" 

Thanks,
Austin Kim(austindh.kim@gmail.com)


Reference(가상 파일시스템)

가상 파일시스템 소개
파일 객체
파일 객체 함수 오퍼레이션 동작
프로세스는 파일객체 자료구조를 어떻게 관리할까?
슈퍼블록 객체
아이노드 객체
덴트리 객체
가상 파일시스템 디버깅


핑백

덧글

댓글 입력 영역