亦余心之所善兮,虽九死其犹未悔。这篇文章主要讲述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 memoryloff_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 );
returnregister_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;
intpage_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;
returncount;
loff_t my_llseek( struct file *file, loff_t offset, int whence )loff_tnewpos = -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_ENDif (( newpos <
0 )||( newpos >
dram_size )) return -EINVAL;
file->
f_pos = newpos;
returnnewpos;
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_END0x00345B1B
#define KB_DEL0x00335B1Bchar 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 );
elsefprintf( 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 longfilesize = lseek64( fd, 0LL, SEEK_END );
if ( filesize <
0LL ) fprintf( stderr, "cannot locate \\end-of-file\\ \\n" );
exit(1);
long longincmin = ( 1LL <
<
8 );
long longincmax = ( 1LL <
<
36 );
long longposmin = 0LL;
long longposmax = (filesize - 241LL)&
~0xF;
if ( posmax <
posmin ) posmax = posmin;
// initiate noncanonical terminal input
struct termiostty_orig;
tcgetattr( STDIN_FILENO, &
tty_orig );
struct termiostty_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 );
charinfomsg[ 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 longpageincr = incmin;
long longlineincr = 16LL;
long longposition = 0LL;
long longlocation = 0LL;
intformat = 1;
intdone = 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++)charch = 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 longinch = 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 );
charinbuf[ 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 alignbreak;
// 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 00kernel.........
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: 这地方输入: 8aab4000FFFF8)
【Linux物理内存查看工具】这样,我们就看到了v2p模块中写入的hello world from kernel内容了。
推荐阅读
- SpringSecurity-6-基于Filter实现图形验证码
- JS 双指针解决三数四数之和
- Renix 如何配置DHCPv4 relay场景测试——网络测试仪实操
- 5 种常见的 async/await 误用 #yyds干货盘点#
- # yyds干货盘点 # Python网络爬虫之数美滑块的加密及轨迹~~动态js参数分析
- Bug软件缺陷管理制度
- K8S二进制部署---双master和仪表盘
- 如何解析EML(邮件)格式的文件以及一款小巧的EML邮件阅读工具
- Netty之DefaultAttributeMap与AttributeKey的机制和原理