0
点赞
收藏
分享

微信扫一扫

Linux物理内存查看工具

我们知道程序的内存最终会落到物理内存上,这该如何验证呢,本文通过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.测试

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内容了。

举报

相关推荐

0 条评论