Linux物理内存查看工具
Posted Lee_1985
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux物理内存查看工具相关的知识,希望对你有一定的参考价值。
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 drivers 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 files 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.测试
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内容了。
以上是关于Linux物理内存查看工具的主要内容,如果未能解决你的问题,请参考以下文章