2

Linux物理内存查看工具

 2 years ago
source link: https://blog.51cto.com/u_14207158/5147528
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

我们知道程序的内存最终会落到物理内存上,这该如何验证呢,本文通过2个开源小工具进行验证。

1.小工具

1.1 dram内核模块

一个内核模块,通过mmap将物理内存映射到一个设备文件,我们通过对这个设备文件进行访问就可以达到访问物理内存的功能了。

#include <linux/module.h>	// for module_init() 
#include <linux/highmem.h>	// for kmap(), kunmap()
#include <linux/uaccess.h>	// for copy_to_user() 

char modname[] = "dram";	// for displaying driver's name
int my_major = 85;		// note static major assignment 
unsigned long dram_size;		// total bytes of system memory

loff_t my_llseek( struct file *file, loff_t offset, int whence );
ssize_t my_read( struct file *file, char *buf, size_t count, loff_t *pos );

struct file_operations 
my_fops =	{
		owner:		THIS_MODULE,
		llseek:		my_llseek,
		read:		my_read,
		};

static int __init dram_init( void )
{
	printk( "<1>\nInstalling \'%s\' module ", modname );
	printk( "(major=%d)\n", my_major );
   
	dram_size = 0x25f5ffff8;
	printk( "<1>  ramtop=%08lX (%lu MB)\n", dram_size, dram_size >> 20 );
	return 	register_chrdev( my_major, modname, &my_fops );
}

static void __exit dram_exit( void )
{
	unregister_chrdev( my_major, modname );
	printk( "<1>Removing \'%s\' module\n", modname );
}

ssize_t my_read( struct file *file, char *buf, size_t count, loff_t *pos )
{
	struct page	*pp;
	void		*from;
	int		page_number, page_indent, more;
	
	// we cannot read beyond the end-of-file
	if ( *pos >= dram_size ) return 0;

	// determine which physical page to temporarily map
	// and how far into that page to begin reading from 
	page_number = *pos / PAGE_SIZE;
	page_indent = *pos % PAGE_SIZE;
	
	// map the designated physical page into kernel space
	/*If kerel vesion is 2.6.32 or later, please use pfn_to_page() to get page, and include
	    asm-generic/memory_model.h*/

       pp = pfn_to_page( page_number);
	
	from = kmap( pp ) + page_indent;
	
	// cannot reliably read beyond the end of this mapped page
	if ( page_indent + count > PAGE_SIZE ) count = PAGE_SIZE - page_indent;

	// now transfer count bytes from mapped page to user-supplied buffer 	
	more = copy_to_user( buf, from, count );
	
	// ok now to discard the temporary page mapping
	kunmap( pp );
	
	// an error occurred if less than count bytes got copied
	if ( more ) return -EFAULT;
	
	// otherwise advance file-pointer and report number of bytes read
	*pos += count;
	return	count;
}

loff_t my_llseek( struct file *file, loff_t offset, int whence )
{
	loff_t	newpos = -1;

	switch( whence )
		{
		case 0: newpos = offset; break;			// SEEK_SET
		case 1: newpos = file->f_pos + offset; break; 	// SEEK_CUR
		case 2: newpos = dram_size + offset; break; 	// SEEK_END
		}

	if (( newpos < 0 )||( newpos > dram_size )) return -EINVAL;
	file->f_pos = newpos;
	return	newpos;
}

MODULE_LICENSE("GPL");
module_init( dram_init );
module_exit( dram_exit );

1.2 fileview程序

可以按照我们想要的格式显示二进制文件。

#include <stdio.h>	// for printf(), perror(), fflush() 
#include <fcntl.h>	// for open()
#include <string.h>	// for strncpy()
#include <unistd.h>	// for read(), lseek64()
#include <stdlib.h>	// for exit()
#include <termios.h>	// for tcgetattr(), tcsetattr()

#define MAXNAME	80
#define BUFHIGH 16
#define BUFWIDE 16
#define BUFSIZE 256
#define ROW	6
#define COL	2

#define KB_SEEK 0x0000000A
#define KB_QUIT	0x0000001B
#define KB_BACK 0x0000007F
#define KB_HOME	0x00315B1B
#define KB_LNUP 0x00415B1B
#define KB_PGUP	0x00355B1B
#define KB_LEFT 0x00445B1B
#define KB_RGHT 0x00435B1B
#define KB_LNDN 0x00425B1B
#define KB_PGDN 0x00365B1B
#define KB_END  0x00345B1B
#define KB_DEL  0x00335B1B


char progname[] = "FILEVIEW";
char filename[ MAXNAME + 1 ];
char buffer[ BUFSIZE ];
char outline[ 80 ];

int main( int argc, char *argv[] )
{
	// setup the filename (if supplied), else terminate
	if ( argc > 1 ) strncpy( filename, argv[1], MAXNAME );
 	else { fprintf( stderr, "argument needed\n" ); exit(1); }

	// open the file for reading
	int	fd = open( filename, O_RDONLY );
	if ( fd < 0 ) { perror( filename ); exit(1); }

	// obtain the filesize (if possible)
	long long	filesize = lseek64( fd, 0LL, SEEK_END );
	if ( filesize < 0LL ) 
		{ 
		fprintf( stderr, "cannot locate \'end-of-file\' \n" ); 
		exit(1); 
		}

	long long	incmin = ( 1LL <<  8 );
	long long	incmax = ( 1LL << 36 );		
	long long	posmin = 0LL;
	long long	posmax = (filesize - 241LL)&~0xF;
	if ( posmax < posmin ) posmax = posmin;

	// initiate noncanonical terminal input
	struct termios	tty_orig;
	tcgetattr( STDIN_FILENO, &tty_orig );
	struct termios	tty_work = tty_orig;
	tty_work.c_lflag &= ~( ECHO | ICANON );  // | ISIG );
	tty_work.c_cc[ VMIN ]  = 1;
	tty_work.c_cc[ VTIME ] = 0;
	tcsetattr( STDIN_FILENO, TCSAFLUSH, &tty_work );	
	printf( "\e[H\e[J" );

	// display the legend
	int	i, j, k;
	k = (77 - strlen( progname ))/2;
	printf( "\e[%d;%dH %s ", 1, k, progname );
	k = (77 - strlen( filename ))/2;
	printf( "\e[%d;%dH\'%s\'", 3, k, filename );
	char	infomsg[ 80 ];
	sprintf( infomsg, "filesize: %llu (=0x%013llX)", filesize, filesize );
	k = (78 - strlen( infomsg ));
	printf( "\e[%d;%dH%s", 24, k, infomsg );
	fflush( stdout );

	// main loop to navigate the file
	long long	pageincr = incmin;
	long long	lineincr = 16LL;
	long long	position = 0LL;
	long long	location = 0LL;
	int		format = 1;
	int		done = 0;
	while ( !done )
		{
		// erase prior buffer contents
		for (j = 0; j < BUFSIZE; j++) buffer[ j ] = ~0;

		// restore 'pageincr' to prescribed bounds
		if ( pageincr == 0LL ) pageincr = incmax;
		else if ( pageincr < incmin ) pageincr = incmin;
		else if ( pageincr > incmax ) pageincr = incmax;
		
		// get current location of file-pointer position
		location = lseek64( fd, position, SEEK_SET );
		
		// try to fill 'buffer[]' with data from the file
		char	*where = buffer;
		int	to_read = BUFSIZE;
		while ( to_read > 0 )
			{
			int	nbytes = read( fd, where, to_read );
			if ( nbytes <= 0 ) break; 
			to_read -= nbytes;
			where += nbytes;
			}
		int	datalen = BUFSIZE - to_read; 

		// display the data just read into the 'buffer[]' array
		unsigned char		*bp;
		unsigned short		*wp;
		unsigned int		*dp;
		unsigned long long	*qp;
		for (i = 0; i < BUFHIGH; i++)
			{
			int	linelen;

			// draw the line-location (13-digit hexadecimal)
			linelen = sprintf( outline, "%013llX ", location );
			
			// draw the line in the selected hexadecimal format
			switch ( format )
				{
				case 1:	// 'byte' format
				bp = (unsigned char*)&buffer[ i*BUFWIDE ];
				for (j = 0; j < BUFWIDE; j++)
					linelen += sprintf( outline+linelen, 
						"%02X ", bp[j] );
				break;

				case 2:	// 'word' format
				wp = (unsigned short*)&buffer[ i*BUFWIDE ];
				for (j = 0; j < BUFWIDE/2; j++)
					linelen += sprintf( outline+linelen,
						" %04X ", wp[j] );
				break;

				case 4:	// 'dword' format
				dp = (unsigned int*)&buffer[ i*BUFWIDE ];
				for (j = 0; j < BUFWIDE/4; j++)
					linelen += sprintf( outline+linelen,
						"  %08X  ", dp[j] );
				break;

				case 8:	// 'qword' format
				qp = (unsigned long long*)&buffer[ i*BUFWIDE ];
				for (j = 0; j < BUFWIDE/8; j++)
					linelen += sprintf( outline+linelen,
						"    %016llX    ", qp[j] );
				break;

				case 16: // 'octaword'
				qp = (unsigned long long*)&buffer[ i*BUFWIDE ];
				linelen += sprintf( outline+linelen, "     " );
				linelen += sprintf( outline+linelen, 
					"   %016llX%016llX   ", qp[1], qp[0] );
				linelen += sprintf( outline+linelen, "     " ); 
				break;
				}

			// draw the line in ascii format
			for (j = 0; j < BUFWIDE; j++)
				{
				char	ch = buffer[ i*BUFWIDE + j ];
				if (( ch < 0x20 )||( ch > 0x7E )) ch = '.';
				linelen += sprintf( outline+linelen, "%c", ch);
				}

			// transfer this output-line to the screen 
			printf( "\e[%d;%dH%s", ROW+i, COL, outline );

			// advance 'location' for the next output-line
			location += BUFWIDE;
			} 	
		printf( "\e[%d;%dH", 23, COL );
		fflush( stdout );	
	
		// await keypress 	
		long long	inch = 0LL;
		read( STDIN_FILENO, &inch, sizeof( inch ) );
		printf( "\e[%d;%dH%60s", 23, COL, " " );	

		// interpret navigation or formatting command
		inch &= 0x00FFFFFFLL;
		switch ( inch )
			{
			// move to the file's beginning/ending
			case 'H': case 'h':
			case KB_HOME:	position = posmin; break;
			case 'E': case 'e':
			case KB_END:	position = posmax; break;

			// move forward/backward by one line
			case KB_LNDN:	position += BUFWIDE; break;
			case KB_LNUP:	position -= BUFWIDE; break;

			// move forward/packward by one page
			case KB_PGDN:	position += pageincr; break;
			case KB_PGUP:	position -= pageincr; break;

			// increase/decrease the page-size increment
			case KB_RGHT:	pageincr >>= 4; break;
			case KB_LEFT:	pageincr <<= 4; break;

			// reset the hexadecimal output-format
			case 'B': case 'b':	format = 1; break;
			case 'W': case 'w':	format = 2; break;
			case 'D': case 'd':	format = 4; break;
			case 'Q': case 'q':	format = 8; break;
			case 'O': case 'o':	format = 16; break;

			// seek to a user-specified file-position
			case KB_SEEK:
			printf( "\e[%d;%dHAddress: ", 23, COL );
			fflush( stdout );
			{
			char	inbuf[ 16 ] = {0};
				//tcsetattr( STDIN_FILENO, TCSANOW, &tty_orig );
			int	i = 0;
			while ( i < 15 )
				{
				long long ch = 0;
				read( STDIN_FILENO, &ch, sizeof( ch ) );
				ch &= 0xFFFFFF;
				if ( ch == '\n' ) break;
				if ( ch == KB_QUIT ) { inbuf[0] = 0; break; }
				if ( ch == KB_LEFT ) ch = KB_BACK;
				if ( ch == KB_DEL ) ch = KB_BACK;
				if (( ch == KB_BACK )&&( i > 0 ))
					{ 
					inbuf[--i] = 0; 
					printf( "\b \b" ); 
					fflush( stdout );
					}
				if (( ch < 0x20 )||( ch > 0x7E )) continue;
				inbuf[ i++ ] = ch;
				printf( "%c", ch );
				fflush( stdout );
				}		
			printf( "\e[%d;%dH%70s", 23, COL, " " );
			fflush( stdout );
			position = strtoull( inbuf, NULL, 16 );
			position &= ~0xFLL;	// paragraph align
			}
			break;			

			// program termination 
			case KB_QUIT:	done = 1; break;

			default:	
			printf( "\e[%d;%dHHit <ESC> to quit", 23, 2 ); 
			}
		fflush( stdout );

		// insure that 'position' remains within bounds
		if ( position < posmin ) position = posmin;
		if ( position > posmax ) position = posmax;
		}	

	// restore canonical terminal behavior
	tcsetattr( STDIN_FILENO, TCSAFLUSH, &tty_orig );	
	printf( "\e[%d;%dH\e[0J\n", 23, 0 );
}

1.3编译内核模块的Makefile

ifneq	($(KERNELRELEASE),)
obj-m	:= dram.o 

else
KDIR	:= /lib/modules/$(shell uname -r)/build
PWD	:= $(shell pwd)
default:	
	$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules 
	rm -r -f .tmp_versions *.mod.c .*.cmd *.o *.symvers 

endif

2.1 地址转换内核模块

实现一个内核模块,完成虚拟地址转成物理地址。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/mm.h> // 内存管理相关头文件,含有页面大小定义和一些页面释放函数原型。
#include <linux/mm_types.h> //内存管理相关头文件
#include <linux/sched.h> //进程调度相关头文件
#include <linux/export.h>
#include <linux/delay.h>

static unsigned long cr0,cr3; //定义CR0和CR3
static unsigned long vaddr = 0; //定义虚拟地址的全局变量

static void get_pgtable_macro(void)
{
	cr0 = read_cr0(); //获得CR0寄存器的值 
	cr3 = read_cr3_pa(); //获得CR3寄存器的值 

	printk("cr0 = 0x%lx, cr3 = 0x%lx\n",cr0,cr3);

	//_SHIFT宏用来描述线性地址中相应字段所能映射区域大小的位数
	printk("PGDIR_SHIFT = %d\n", PGDIR_SHIFT); //打印页全局目录项能映射的区域大小的位数
	printk("P4D_SHIFT = %d\n",P4D_SHIFT); //打印P4D目录项能映射的区域大小的位数
	printk("PUD_SHIFT = %d\n", PUD_SHIFT); //打印页上级目录项能映射的区域大小的位数
	printk("PMD_SHIFT = %d\n", PMD_SHIFT); //打印页中间目录项可以映射的区域大小的位数
	printk("PAGE_SHIFT = %d\n", PAGE_SHIFT); //打印page_offset字段所能映射区域大小的位数

	//指示相应页目录表中项的个数
	printk("PTRS_PER_PGD = %d\n", PTRS_PER_PGD); //打印页全局目录项数
	printk("PTRS_PER_P4D = %d\n", PTRS_PER_P4D); //打印P4D目录项数
	printk("PTRS_PER_PUD = %d\n", PTRS_PER_PUD); //打印页上级目录项数
	printk("PTRS_PER_PMD = %d\n", PTRS_PER_PMD); //打印页中级目录项数
	printk("PTRS_PER_PTE = %d\n", PTRS_PER_PTE); //打印页表项数
	printk("PAGE_MASK = 0x%lx\n", PAGE_MASK); //页内偏移掩码,屏蔽page_offset字段
}

static unsigned long vaddr2paddr(unsigned long vaddr)
{
	pgd_t *pgd;
	p4d_t *p4d;
	pud_t *pud;
	pmd_t *pmd;
	pte_t *pte;
	unsigned long paddr = 0;
	unsigned long page_addr = 0;
	unsigned long page_offset = 0;

	//获取页全局目录PGD,第一个参数当前进程的mm_struct,所有进程共享一个内核页表
	pgd = pgd_offset(current->mm,vaddr); //获得pgd的地址
	printk("pgd_val = 0x%lx, pgd_index = %lu\n", pgd_val(*pgd),pgd_index(vaddr));
	if (pgd_none(*pgd)){ //判断pgd页表项是否为空
		printk("not mapped in pgd\n");
		return -1;
	}

	//获取P4D,新的Intel芯片的MMU硬件规定可以进行5级页表管理,内核在PGD和PUD之间,增加了一个叫P4D的页目录
	p4d = p4d_offset(pgd, vaddr); //获得p4d的地址
	printk("p4d_val = 0x%lx, p4d_index = %lu\n", p4d_val(*p4d),p4d_index(vaddr));
	if(p4d_none(*p4d)) //判断p4d页表项是否为空
	{ 
		printk("not mapped in p4d\n");
		return -1;
	}

	//获取页上级目录PUD
	pud = pud_offset(p4d, vaddr); //获得pud的地址
	printk("pud_val = 0x%lx, pud_index = %lu\n", pud_val(*pud),pud_index(vaddr));
	if (pud_none(*pud)) { //判断pud页表项是否为空
		printk("not mapped in pud\n");
		return -1;
	}

	//获取页中间目录PMD 
	pmd = pmd_offset(pud, vaddr); //获得pmd的地址
	printk("pmd_val = 0x%lx, pmd_index = %lu\n", pmd_val(*pmd),pmd_index(vaddr));
	if (pmd_none(*pmd)) { //判断pmd页表项是否为空
		printk("not mapped in pmd\n");
		return -1;
	}

	//获取页表PT 
	pte = pte_offset_kernel(pmd, vaddr); //获得pte的地址
	printk("pte_val = 0x%lx, ptd_index = %lu\n", pte_val(*pte),pte_index(vaddr));
	if (pte_none(*pte)) { //判断pte页表项是否为空
		printk("not mapped in pte\n");
		return -1;
	}

	page_addr = pte_val(*pte) & PAGE_MASK; //获得页框的物理地址
	page_offset = vaddr & ~PAGE_MASK; //获得页偏移地址
	paddr = page_addr | page_offset; //获得物理地址
	printk("page_addr = %lx, page_offset = %lx\n", page_addr, page_offset);
	printk("vaddr = %lx, paddr = %lx\n", vaddr, paddr);
	return paddr;
}

static int __init v2p_init(void)
{
	unsigned long vaddr = 0 ;
	printk("vaddr to paddr module is running..\n");
	get_pgtable_macro();
	printk("\n");
	vaddr = __get_free_page(GFP_KERNEL); //在内核ZONE_NORMAL中申请一块页面
	if (vaddr == 0) {
		printk("__get_free_page failed..\n");
		return 0;
	}
	sprintf((char *)vaddr, "hello world from kernel");
	printk("get_page_vaddr=0x%lx\n", vaddr);
	vaddr2paddr(vaddr); //调用线性地址转换物理地址的函数
	ssleep(600); //延时
	return 0;
}
static void __exit v2p_exit(void)
{
	printk("vaddr to paddr module is leaving..\n");
	free_page(vaddr);
}

module_init(v2p_init);
module_exit(v2p_exit);
MODULE_LICENSE("GPL"); 

Makefile:

obj-m+=v2p.o
all:
	make -C /lib/modules/$(shell uname -r)/build/ M=$(PWD) modules
clean:
	make -C /lib/modules/$(shell uname -r)/build/ M=$(PWD) clean

编译和加载
make
sudo insmod v2p.ko

[14369.803434] vaddr to paddr module is running..
[14369.803434] cr0 = 0x80050033, cr3 = 0xb9246000
[14369.803435] PGDIR_SHIFT = 39
[14369.803435] P4D_SHIFT = 39
[14369.803435] PUD_SHIFT = 30
[14369.803436] PMD_SHIFT = 21
[14369.803436] PAGE_SHIFT = 12
[14369.803436] PTRS_PER_PGD = 512
[14369.803436] PTRS_PER_P4D = 1
[14369.803437] PTRS_PER_PUD = 512
[14369.803437] PTRS_PER_PMD = 512
[14369.803437] PTRS_PER_PTE = 512
[14369.803437] PAGE_MASK = 0xfffffffffffff000

[14369.803438] get_page_vaddr=0xffff8aeacaab4000
[14369.803439] pgd_val = 0x179a7d067, pgd_index = 277
[14369.803439] p4d_val = 0x179a7d067, p4d_index = 0
[14369.803440] pud_val = 0x179a81067, pud_index = 427
[14369.803440] pmd_val = 0x6d201063, pmd_index = 85
[14369.803440] pte_val = 0x800000008aab4063, ptd_index = 180
[14369.803441] page_addr = 800000008aab4000, page_offset = 0
[14369.803441] vaddr = ffff8aeacaab4000, paddr = 800000008aab4000

2.2加载dram和fileview小工具

sudo insmod dram.ko
sudo mknod /dev/dram c 85 0
g++ ./fileview.cpp
./a.out /dev/dram

FILEVIEW 

                                 '/dev/dram'


 000008AAB4000 68 65 6C 6C 6F 20 77 6F 72 6C 64 20 66 72 6F 6D hello world from
 000008AAB4010 20 6B 65 72 6E 65 6C 00 00 00 00 00 00 00 00 00  kernel.........
 000008AAB4020 47 76 4B 03 C8 AB FF FF 47 76 01 00 00 00 00 00 GvK.....Gv......
 000008AAB4030 60 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 `...............
 000008AAB4040 01 00 00 00 00 00 00 00 FF FF FF FF FF FF FF FF ................
 000008AAB4050 17 01 00 00 04 00 00 00 40 00 00 00 00 00 00 00 ........@.......
 000008AAB4060 78 91 4E 03 C8 AB FF FF 78 91 04 00 00 00 00 00 x.N.....x.......
 000008AAB4070 60 00 00 00 00 00 00 00 25 00 00 00 1A 00 00 00 `.......%.......
 000008AAB4080 08 00 00 00 00 00 00 00 FF FF FF FF FF FF FF FF ................
 000008AAB4090 30 01 00 00 01 00 00 00 00 00 00 00 00 00 00 00 0...............
 000008AAB40A0 A7 76 4B 03 C8 AB FF FF A7 76 01 00 00 00 00 00 .vK......v......
 000008AAB40B0 D0 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
 000008AAB40C0 01 00 00 00 00 00 00 00 FF FF FF FF FF FF FF FF ................
 000008AAB40D0 2B 01 00 00 04 00 00 00 40 00 00 00 00 00 00 00 +.......@.......
 000008AAB40E0 D8 91 4E 03 C8 AB FF FF D8 91 04 00 00 00 00 00 ..N.............
 000008AAB40F0 A0 05 00 00 00 00 00 00 25 00 00 00 1C 00 00 00 ........%.......
 Address: 这地方输入: 8aab4000                                           FFFF8)

这样,我们就看到了v2p模块中写入的hello world from kernel内容了。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK