BCC를 통한 Linux I/O 추적

EEEFFEE·2024년 1월 27일

eBPF

목록 보기
2/3

24.01.26 최초 작성

1. I/O를 수행하는 함수 찾기

  • linux/syscalls.h에서 I/O 관련 함수 찾기

2. BCC를 통해 코드 작성

  • 다음 코드를 통해 추적한 결과 file->f_op->read라는 함수 포인터가 나타남
  • 파일 시스템마다 해당 함수 포인터에 등록되는 함수가 달라짐 (ext4라 가정하고 다음 단계 진행)

from BCC import BPF
import os
import sys

prog = """
#include <uapi/linux/ptrace.h>
#include <linux/sched.h>

// PID정보 담을 구조체
struct pid_struct{
	pid_t pid;
};

//	PID 저장할 BPF 해시맵 생성
BPF_HASH(pid_map, u32, struct pid_struct);

//	유저 레벨에 출력할 데이터 구조체 정의
struct data_t {
	u32 pid;
    u32 uid;
    char comm[TASK_COMM_LEN];
    int fd;
};

//	출력 이벤트용 BPF 퍼포먼스 출력 정의
BPF_PERF_OUTPUT(events);

//	이벤트를 추적하고 데이터 수집하는 함수
static int do_trace(struct pt_regs *ctx, int fd) {
	struct data_t data = {};
    struct task_struct *task;		// 현재 task의 정보 담을 구조체
    
    task = (struct task_struct *)bpf_get_current_task();	// 현재 task 구조체 정보 가져오기
    
    
    //	현재 task의 pid, uid, 커맨드 이름, 파일 디스크립터 가져오기
    data.pid = bpf_get_current_pid_tgid();
    data.uid = bpf_get_current_uid_gid();
    bpf_get_current_comm(&data.comm, sizeof(data.comm));
    data.fd = fd;
    
    //	이벤트 유저공간에 전송 
    events.perf_submit(ctx, &data, sizeof(data));
    
    return 0;
}

//	sys_clone 발생 시 호출되는 kprobe함수
int kprobe__sys_clone(struct pt_regs *ctx) {
	u64 pid_tgid = bpf_get_current_pid_tgid();
    u32 tgid = pid_tgid >> 32;
    u32 pid = pid_tgid;
    
    //	현재 프로세스가 추적 대상일 경우 새 PID를 맵에 저장
    if (tgid != BPF_SID) {
    	struct pid_struct new_pid = {};
        new_pid.pid = PT_REGS_RC(ctx);
        pid_map.update(&pid, &new_pid);
    }
    
    return 0;
}

int kprobe__sys_read(struct pt_regs *ctx, int fd) {
	return do_trace(ctx, PT_REGS_PARM1(ctx));
}

int kprobe_sys_write(struct pt_regs *ctx, int fd) {
	return do_trace(ctx, PT_REGS_PARM1(ctx));
}
"""

printf(os.getpid() + 1)

b = BPF(text=prog.replace('BPF_SID', str(os.getpid() + 1)))

#	이벤트 처리함수
def print_event(cpu, data,size):
	// 데이터에서 이벤트 추출
	event = b["events"].event(data)
    print("PID: %d UID: %d Comm: %-16s FD: %d" % (event.pid, event.uid, events.comm, events.fd))
    
#	이벤트에 대한 콜백 함수 지정
b["events"].open_perf_buffer(print_event)

#	콜백 함수를 사용해 이벤트를 계속 감지
while 1:
	try:
    	b.perf_buffer_poll()
    except KeyboardInterrupt:
    	exit()

  • fs/ext4에서 검색 결과 generic_file_read_iter()라는 함수에서 처리되는 것을 확인
  • generic_file_read_iter()를 추적하면 또 함수 포인터가 나타남
  • 해당 함수 포인터는 fs/ext4/readpage.creadpages()함수를 가리키며 최종적으로 submit_bio()를 호출

3. submit_bio()를 kprobe를 통해 동작 확인

  • submit_bio()실행 시 요청 사이즈 별로 히스토그램 그리는 코드
  • 실행 결과 히스토그램이 이상하게 나타남
  • ctags를 활용해 콜스택을 확인한 결과 blk_mq_submit_bio()함수를 찾음

from bcc import BPF
import time

prog = """
#include <linux/blkdev.h>
#include <linux/blk-mp.h>

struct val_t {
	u32 size;
}

BPF_HASH(start, struct request *, struct val_t);
BPF_HISTOGRAM(hist);

void trace_start(struct pt_regs *ctx, struct request *req) {
	struct val_t val {};
    
    if (bpf_probe_read_kernel(&val.size, sizeof(val.size), &(req->__data_len))) {
    	return;
    }
    
    val.size = val.size / 1024;
    start.update(&req, &val);
    
    hist.increment(bpf_log21(val.size));
}
"""

b = BPF(text=prog)
b.attach_kprobe(event="submit_bio", fn_name="trace_start")

try:
	time.sleep(10)
finally:
	b["hist"].print_log2_hist("request size (KB)")

4. blk_mq_submit_bio() 확인


from bcc import BPF
import time

prog = """
#include <linux/blkdev.h>
#include <linux/blk-mq.h>
#include <linux/bio.h>

struct val_t {
	u32 size;
}

BPF_HASH(start, struct bio *, struct val_t);
BPF_HISTOGRAM(hist);

void trace_start(struct pt_regs *ctx, struct request *req) {
	struct val_t val {};
    
     bpf_probe_read_kernel(&val.size, sizeof(val.size), &bio->bi_iter.bi_size);
    val.size = val.size / 1024;
    start.update(&bio, &val);
    
    hist.increment(bpf_log21(val.size));

"""

b = BPF(text=prog)
b.attach_kprobe(event="blk_mq_submit_bio", fn_name="trace_start")

try:
	time.sleep(10)
finally:
	b["hist"].print_log2_hist("request size (KB)")

ex) ctags

###utility

  • tags 파일(소스코드의 전역변수, 함수, 매크로들의 Database) 생성하는 명령어
  • ctags <파일 이름1> <파일 이름2> .. : 지정한 파일들을 기반으로 tags파일 생성
  • -R * : 현재 디렉토리와 하위 디렉토리의 모든 파일들에 대해 tags파일 생성
  • tjump <함수 이름> : 지정한 함수 이름이 정의된 소스파일 위치로 이동

sudo apt-get install exuberant-ctags

# 추적 시작할 경로에서 아래 명령어 입력
ctags -R *

  • vim에서 자동으로 tags파일 인식하도록 설정

vi ~/.vimrc

#	vim열 때 마다 현재 디렉토리에 tags파일 있으면 자동으로 읽도록 설정
set tags=./tags,tags

#	Ctrl + \ 입력 시 현재 커서가 위치한 함수의 소스파일 위치로 이동
map <C-\> :tjump <C-R><C-W><CR>

0개의 댓글