Art of Pr0gr4m

[Linux Kernel 5] Process Memory Viewer HOL 본문

IT/Linux Kernel

[Linux Kernel 5] Process Memory Viewer HOL

pr0gr4m 2020. 5. 17. 19:59

이번 포스트에서는 메모리 학습의 마무리로 메모리 뷰어 모듈 제작 실습을 진행한다.

 

/proc/<pid>/maps 을 읽으면 다음과 같이 프로세스의 메모리 매핑 정보를 볼 수 있다.

cat /proc/1/maps

필드별로 할당 주소, 권한(모드), offset, 디바이스 ID, inode, 매핑 대상 or 타입을 보여준다.

이와 유사하게 인자로 전달받은 pid (인자가 없다면 1번 프로세스)의 메모리 사이즈 및 매핑 정보를 출력하는 예제를 작성한다.

 

 

 

1. 프로세스 메모리 뷰어 예제 소스

 

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/seq_file.h>
#include <linux/sched/mm.h>
#include <linux/mm.h>
#include <linux/fs.h>
#include <linux/slab.h>
 
#ifndef find_task_by_pid
#define find_task_by_pid(nr)	pid_task(find_vpid(nr), PIDTYPE_PID)
#endif
 
#define BUF_SIZE	1024
 
static int pid = 1;
module_param(pid, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
 
static int is_stack(struct vm_area_struct *vma)
{
	return vma->vm_start <= vma->vm_mm->start_stack &&
		vma->vm_end >= vma->vm_mm->start_stack;
}
 
static void show_map(pid_t pid)
{
	struct task_struct *task = NULL;
	struct mm_struct *mm = NULL;
	struct vm_area_struct *vma = NULL;
 
	int flags = 0;
	struct file *file = NULL;
	dev_t dev = 0;
	char *fullname;
	char *buf = kmalloc(BUF_SIZE, GFP_KERNEL);
	struct inode *inode;
	int mm_count = 0;
 
	if ((task = find_task_by_pid(pid)) == NULL) {
		printk(KERN_ERR "find_task_by_pid error \n");
		return;
	}
 
	if ((mm = get_task_mm(task)) == NULL) {
		printk(KERN_ERR "get_task_mm error \n");
		return;
	}
 
	down_read(&mm->mmap_sem);
	vma = mm->mmap;
	up_read(&mm->mmap_sem);
 
	if (vma == NULL) {
		printk(KERN_ERR "get vma error \n");
		return;
	}
 
	printk("Address                     Mode Offset       dev_t   inode       Mapping/Type\n");
 
	down_read(&mm->mmap_sem);
	for (mm_count = mm->map_count; mm_count > 0; mm_count--) {
		file = vma->vm_file;
		flags = vma->vm_flags;
 
		// virtual address scope
		printk(KERN_CONT "%08lx - %08lx ", vma->vm_start, vma->vm_end);
 
		// virtual address privilege
		printk(KERN_CONT "%c%c%c%c ",
				flags & VM_READ ? 'r' : '-',
				flags & VM_WRITE ? 'w' : '-',
				flags & VM_EXEC ? 'x' : '-',
				flags & VM_MAYSHARE ? 's' : 'p');
 
		// virtual address offset
		printk(KERN_CONT "%012lx ", vma->vm_pgoff << PAGE_SHIFT);
 
		if (file) {
			inode = file->f_inode;
			dev = inode->i_sb->s_dev;
			printk(KERN_CONT "%02x:%02x   ", MAJOR(dev), MINOR(dev));
			printk(KERN_CONT "%08lu   ", inode->i_ino);
			memset(buf, 0, BUF_SIZE);
 
			fullname = d_path(&file->f_path, buf, BUF_SIZE);
			printk(KERN_CONT "%s", fullname);
		} else {
			if (!vma->vm_mm) {
				printk(KERN_CONT "                    [ vdso ]");
			}
 
			if (mm) {
				if (vma->vm_start <= mm->brk && vma->vm_end >= mm->start_brk)
					printk(KERN_CONT "                    [ heap ]");
				else if (is_stack(vma))
					printk(KERN_CONT "                    [ stack ]");
			}
 
		}
		printk(KERN_CONT "\n");
		vma = vma->vm_next;
	}
	up_read(&mm->mmap_sem);
 
	kfree(buf);
}
 
#define PRINTK_DEC(str, val) \
	printk(str, (val) << (PAGE_SHIFT - 10))
 
static int __init seq_init(void)
{
	struct task_struct *task = NULL;
	struct mm_struct *mm;
	struct vm_area_struct *vm;
 
	unsigned long text, lib, swap, anon, file, shmem;
	unsigned long hiwater_vm, total_vm, hiwater_rss, total_rss;
 
	if ((task = find_task_by_pid(pid)) == NULL) {
		printk(KERN_ERR "find_task_by_pid error \n");
		return -1;
	}
 
	printk("View Process [%s]\n", task->comm);
 
	if ((mm = get_task_mm(task)) == NULL) {
		printk(KERN_ERR "get_task_mm error \n");
		return -1;
	}
 
	down_read(&mm->mmap_sem);
	vm = mm->mmap;
 
	anon = get_mm_counter(mm, MM_ANONPAGES);
	file = get_mm_counter(mm, MM_FILEPAGES);
	shmem = get_mm_counter(mm, MM_SHMEMPAGES);
 
	hiwater_vm = total_vm = mm->total_vm;
	if (hiwater_vm < mm->hiwater_vm)
		hiwater_vm = mm->hiwater_vm;
	hiwater_rss = total_rss = anon + file + shmem;
	if (hiwater_rss < mm->hiwater_rss)
		hiwater_rss = mm->hiwater_rss;
 
	text = PAGE_ALIGN(mm->end_code) - (mm->start_code & PAGE_MASK);
	text = min(text, mm->exec_vm << PAGE_SHIFT);
	lib = (mm->exec_vm << PAGE_SHIFT) - text;
	swap = get_mm_counter(mm, MM_SWAPENTS);
 
	PRINTK_DEC("VmPeak : \t%8lu kB\n", hiwater_vm);
	PRINTK_DEC("VmSize : \t%8lu kB\n", total_vm);
	PRINTK_DEC("VmLock : \t%8lu kB\n", mm->locked_vm);
	PRINTK_DEC("VmPin  : \t%8llu kB\n", atomic64_read(&mm->pinned_vm));
	PRINTK_DEC("VmHWM  : \t%8lu kB\n", hiwater_rss);
	PRINTK_DEC("VmRSS  : \t%8lu kB\n", total_rss);
	PRINTK_DEC("RssAnon: \t%8lu kB\n", anon);
	PRINTK_DEC("RssFile: \t%8lu kB\n", file);
	PRINTK_DEC("RssShm : \t%8lu kB\n", shmem);
	PRINTK_DEC("VmData : \t%8lu kB\n", mm->data_vm);
	PRINTK_DEC("VmStck : \t%8lu kB\n", mm->stack_vm);
	printk("VmExec : \t%8lu kB\n", text >> 10);
	printk("VmLib  : \t%8lu kB\n", lib >> 10);
	printk("VmPTE  : \t%8lu kB\n", mm_pgtables_bytes(mm) >> 10);
	PRINTK_DEC("VmSwap : \t%8lu kB\n", swap);
 
	up_read(&mm->mmap_sem);
	show_map(pid);
 
	return 0;
}
 
static void __exit seq_exit(void)
{}
 
module_init(seq_init);
module_exit(seq_exit);
MODULE_LICENSE("GPL");

해당 예제는 /fs/proc/task_mmu.c 소스파일을 참고하여 작성한다.

 

find_task_by_pid 매크로는 pid 값으로 task_struct 구조체를 구하도록 정의하였다.

get_task_mm 함수로 task_struct에 설정되어 있는 mm_struct 구조체를 구한다.

mm_struct 내에 가상 메모리 정보를 가져오기 위하여 vm_area_struct 구조체를 구한다.

해당 구조체로부터 프로세스 각 영역에 대한 사이즈를 구하고 출력한다.

show_map 함수에선 vma 리스트에서 할당 영역, 모드, offset, dev_t, inode, 매핑 정보 등을 뽑아내 출력한다.

 

 

 

2. 실행 결과

 

dmesg 결과

정상적으로 procfs에서 제공한 결과와 같은 결과를 볼 수 있다.