24.01.26 최초 작성
linux/syscalls.h에서 I/O 관련 함수 찾기 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.c의 readpages()함수를 가리키며 최종적으로 submit_bio()를 호출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)")
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)")
###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>