Linux Kernel(4.19) Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

130199
1107
135858


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

이번 소절에서 유저 어플리케이션에서 statfs() 함수를 호출했을때 커널 내부 슈퍼블록 객체 함수 오퍼레이션 동작을 살펴보겠습니다. 

먼저 실습 패치 코드를 작성해볼까요?

실습 패치 코드 작성해보기

소스 코드는 다음과 같으니 같이 입력해 봅시다.
01 #include <stdio.h>
02 #include <stdlib.h>
03 #include <unistd.h>
04 #include <sys/types.h>
05 #include <sys/vfs.h>
06 #include <string.h>
07 #include <fcntl.h>
08 
09 #define GENERAL_DIR "/home/pi"
10 #define PROC_DIR "/proc"
11 #define BUFF_SIZE 128
12 
13 int main() 
14 {
15 struct statfs file_sys_info;
16 char fname[BUFF_SIZE] = {0,};
17 
18 strcpy(fname, GENERAL_DIR);
19
20 printf("statfs under %s \n", GENERAL_DIR);
21 if(statfs(fname, &file_sys_info)) {
22 printf("Unable to statfs %s \n", fname);
23 exit(1);
24 }
25
26 strcpy(fname, PROC_DIR);
27 printf("statfs under %s \n", PROC_DIR);
28 if(statfs(fname, &file_sys_info)) {
29 printf("Unable to statfs %s \n", fname);
30 exit(1);
31 }
32
33 return 0;
34 }

코드 내용은 어렵지 않으니 간단히 리뷰하는 수준으로 설명을 드리겠습니다.

18~24번째 줄 코드를 보겠습니다.
18 strcpy(fname, GENERAL_DIR);
19
20 printf("statfs under %s \n", GENERAL_DIR);
21 if(statfs(fname, &file_sys_info)) {
22 printf("Unable to statfs %s \n", fname);
23 exit(1);
24 }

fname 버퍼에 GENERAL_DIR("/home/pi") 경로를 복사한 다음 statfs() 함수를 호출합니다.
statfs() 함수를 호출하면 지정된 디렉토리를 관리하는 파일시스템 속성을 알 수 있습니다.

이어서 26~31번째 줄 코드를 보겠습니다.
26 strcpy(fname, PROC_DIR);
27 printf("statfs under %s \n", PROC_DIR);
28 if(statfs(fname, &file_sys_info)) {
29 printf("Unable to statfs %s \n", fname);
30 exit(1);
31 }

fname 버퍼에 PROC_DIR("/proc") 경로를 복사한 다음 statfs() 함수를 호출합니다.
statfs() 함수를 호출하면 "/proc" 디렉토리를 관리하는 파일시스템 속성을 알 수 있습니다.
 
리눅스 시스템 프로그램을 한번이라도 해본 분이면 이해할 수 있는 수준의 코드입니다.

위에서 소개한 코드를 rpi_vfs_statfs_operation.c 파일로 저장합니다.
코드를 입력했으니 컴파일을 해볼 차례입니다. 컴파일을 쉽게 하기 위해 다음과 같이 코드를 작성하고 파일 이름을 Makefile으로 저장합시다.
statfs_proc: rpi_vfs_statfs_operation.c
gcc -o statfs_proc rpi_vfs_statfs_operation.c
이후 다음과 같이 make 명령어를 입력해 rpi_vfs_statfs_operation.c 소스 파일을 컴파일합시다.
root@raspberrypi:/home/pi# make
gcc -o statfs_proc rpi_vfs_statfs_operation.c
오타없이 코드를 입력하면 위와 같은 메시지가 출력되면서 statfs_proc 파일이 생성될 것입니다. rpi_vfs_statfs_operation.c 소스 파일의 실행 파일 이름은 statfs_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
25 echo ext4_statfs simple_statfs > /sys/kernel/debug/tracing/set_ftrace_filter
26 sleep 1
27 echo "set_ftrace_filter enabled"
28
29 sleep 1
30 echo "set_ftrace_filter enabled"
31
32 echo 1 > /sys/kernel/debug/tracing/options/func_stack_trace
33 echo 1 > /sys/kernel/debug/tracing/options/sym-offset
34 echo "function stack trace enabled"
35
36 echo 1 > /sys/kernel/debug/tracing/tracing_on
37 echo "tracing_on"
이전 장에서 소개한 ftrace 설정 명령어와 차이점 부분 위주로 살펴보겠습니다.

다음 명령어를 소개합니다.
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

시스템 콜 동작을 확인하기 위해 프로세스 스케줄링과 시스템 콜 이벤트를 키는 명령어입니다.

이어서 함수 필터를 설정하는    째 줄 명령어를 보겠습니다.
25 echo ext4_statfs simple_statfs > /sys/kernel/debug/tracing/set_ftrace_filter

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

함수 이름이 조금 친숙해 보이지 않아요? 모두 13.3 절에서 분석한 슈퍼블록 함수 오퍼레이션 함수입니다. 위에서 소개한 ftrace 설정 명령어를 입력한 후 rpi_vfs_statfs.sh 이름으로 저장합시다.

ftrace 로그 추출하는 방법 알아보기 

실습 코드 실행 파일(statfs_proc)이 준비됐고 ftrace 설정 방법을 확인했습니다.
이어서 ftrace 를 설정하고 statfs_proc 파일을 실행할 차례입니다.

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

다음 "./vfs_file_proc" 명령어를 입력해 vfs_file_proc 파일을 실행합니다.
root@raspberrypi:/home/pi # ./vfs_file_proc
statfs under /home/pi 
statfs under /proc 

다음 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 

ftrace 로그 분석해보기 

01 statfs_proc-2571 [002] .... 8294.737296: sys_enter: NR 99 (7e8ac508, 7e8ac588, 7e8ac588, 7e8ac508, 105c8, 0)
02 statfs_proc-2571 [002] .... 8294.737310: ext4_statfs+0x14/0x398 <-statfs_by_dentry+0x58/0x7c
03 statfs_proc-2571  [002] .... 8294.737345: <stack trace>
04  => ext4_statfs+0x18/0x398
05  => statfs_by_dentry+0x58/0x7c
06  => vfs_statfs+0x24/0x94
07  => user_statfs+0x64/0xac
08  => sys_statfs+0x34/0x64
09  => __sys_trace_return+0x0/0x10
10  => 0x7e8ac504
11 statfs_proc-2571 [002] .... 8294.737358: sys_exit: NR 99 = 0
...
12 statfs_proc-2571 [002] .... 8294.737395: simple_statfs+0x10/0x38 <-statfs_by_dentry+0x58/0x7c
13 statfs_proc-2571  [002] .... 8294.737412: <stack trace>
14  => simple_statfs+0x14/0x38
15  => statfs_by_dentry+0x58/0x7c
16  => vfs_statfs+0x24/0x94
17  => user_statfs+0x64/0xac
18  => sys_statfs+0x34/0x64
19  => __sys_trace_return+0x0/0x10
20 => 0x7e8ac504
21 statfs_proc-2571  [002] ....  8294.737417: sys_exit: NR 99 = 0 

ftrace 로그에서 함수 실행 흐름은 다음과 같이 2단계로 나눌 수 있습니다.
 
[그림 13.25] ftrace: 슈퍼블록 statfs 함수 오퍼레이션 실행 흐름도

유저 공간에서 statfs() 함수를 호출하면 커널 공간에서 시스템 콜 핸들러인 sys_statfs() 함수가 호출되는 실행 흐름입니다. 이후 statfs_by_dentry() 함수가 호출되어 슈퍼블록 객체 함수 오퍼레이션을 수행합니다.

ext4로 표시된 부분은 유저 공간에서 다음 함수가 실행하면 호출됩니다.
18 strcpy(fname, GENERAL_DIR);
19
20 printf("statfs under %s \n", GENERAL_DIR);
21 if(statfs(fname, &file_sys_info)) {
22 printf("Unable to statfs %s \n", fname);
23 exit(1);
24 }

가상 파일 시스템 내부에서 "/home/pi" 디렉토리를 관리하는 슈퍼블록 객체에 접근해 해당 폴더를 관리하는 ext4 파일시스템 정보를 읽습니다.

proc로 표시된 부분은 유저 공간에서 다음 함수가 실행하면 호출됩니다.
26 strcpy(fname, PROC_DIR);
27 printf("statfs under %s \n", PROC_DIR);
28 if(statfs(fname, &file_sys_info)) {
29 printf("Unable to statfs %s \n", fname);
30 exit(1);
31 }

가상 파일 시스템 내부에서 "/proc" 디렉토리를 관리하는 슈퍼블록 객체에 접근해 해당 폴더를 관리하는 proc 파일시스템 정보를 읽습니다.

먼저 01번째 줄 로그를 보겠습니다.
01 statfs_proc-2571 [002] .... 8294.737296: sys_enter: NR 99 (7e8ac508, 7e8ac588, 7e8ac588, 7e8ac508, 105c8, 0)

99번 statfs 시스템 콜이 발생했다는 정보입니다. 99번 시스템 콜 번호는 다음 코드에서 확인할 수 있습니다.
[https://elixir.bootlin.com/linux/v4.4.180/source/arch/arm/include/uapi/asm/unistd.h]
#define __NR_statfs (__NR_SYSCALL_BASE+ 99)

02~10번째 줄 로그에서 statfs 시스템 콜이 발생한 다음 ext4_statfs() 함수 콜스택을 볼 수 있습니다.

이어서 ext4_statfs() 함수를 호출하는 statfs_by_dentry() 함수를 같이 볼까요?
[https://elixir.bootlin.com/linux/v4.19.30/source/source/fs/statfs.c]
1 static int statfs_by_dentry(struct dentry *dentry, struct kstatfs *buf)
2 {
3 int retval;
4
5 if (!dentry->d_sb->s_op->statfs)
6 return -ENOSYS;
7
8 memset(buf, 0, sizeof(*buf));
9 retval = security_sb_statfs(dentry);
10 if (retval)
11 return retval;
12 retval = dentry->d_sb->s_op->statfs(dentry, buf);

다음 12번째 줄 코드를 분석하겠습니다. 
12 retval = dentry->d_sb->s_op->statfs(dentry, buf);

슈퍼 블록 함수 오퍼레이션으로 지정한 statfs 함수를 호출합니다.

ext4 파일시스템 슈퍼블록 함수 오퍼레이션에서 statfs 함수 포인터는 ext4_statfs() 함수 주소를 저장하고 있습니다. 

    따라서 04~05번째 줄 로그와 같이 ext4_statfs() 함수를 호출합니다.

proc 파일시스템의 경우 statfs 함수 포인터는 simple_statfs() 함수 주소를 저장하므로 14~15번째 줄 로그와 같이 simple_statfs() 함수를 호출합니다.


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

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


Reference(가상 파일시스템)

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


핑백

덧글

댓글 입력 영역