生活随笔
收集整理的這篇文章主要介紹了
虚拟地址如何访问到物理地址
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
環境:32bit CPU 一、通過二級頁表映射的方式訪問物理地址 1、取一級頁表的基地址Abase1 2、取虛擬地址的前12bit[31:20]地址O1 3、計算得到新地址Apgd=(Abase1&0xFFFFF000)+O1,此地址是PGD頁表上的地址,取此地址中的數據Abase2 4、取虛擬地址的中間8bit[19:12]地址O2 5、計算得到新地址Apte=(Abase2&0xFFFFFF00)+O2,此地址是PTE頁表上的地址,取此地址中的數據Abase3 6、取虛擬地址的后12bit[11:0]地址O3 7、計算得到新地址Aphy=(Abase3&0xFFFFF000)+O3,此地址就是實實在在的物理地址 以上的計算和查找過程有MMU模塊實現,同時會把映射信息存放在MMU中的TLB中,類似于cache,方便下次快速的查找。 二、疑問 1、一級頁表的基地址存放在什么地方? 2、如何找到存放在物理地址的信息? 三、分析問題 1、操作系統有個專門的結構體負責進程的內存使用情況,task_struct里面的mm_struct
struct task_struct { . . . . . . int on_rq
; int prio
, static_prio
, normal_prio
; unsigned int rt_priority
; const struct sched_class * sched_class
; struct sched_entity se
; struct sched_rt_entity rt
; . . . . . . struct mm_struct * mm
, * active_mm
; struct mm_struct { . . . . . . unsigned long mmap_base
; unsigned long mmap_legacy_base
; unsigned long task_size
; unsigned long highest_vm_end
; pgd_t * pgd
; atomic_t mm_users
; atomic_t mm_count
; atomic_long_t nr_ptes
; . . . . . .
其中pgd保存的就是一級頁表的基地址。進程切換時,會把TTBRx寄存器(上圖所示)保存起來,把下一個進程的pgd內容寫到TTBRx寄存器中。 2、寫段程序,找到存放信息的物理地址 2.1、內核態代碼 根據上面所描述的頁表映射方式,查找到虛擬地址對應的物理地址 需要在內核態下面運行,內核態下可以獲取當前進程的PCB task_struct進而獲取mm_struct內存管理模塊
# include <linux/init_task.h>
unsigned int get_phy_addr ( struct mm_struct * mm
, unsigned long addr
)
{ pgd_t * pgd
= NULL ; pud_t * pud
= NULL ; pmd_t * pmd
= NULL ; pte_t * pte
= NULL ; unsigned int phy_addr
= 0 ; printk ( "pgd_base = %p\n" , mm
-> pgd
) ; pgd
= pgd_offset ( mm
, addr
) ; printk ( "vaddr=[0x%08x] *pgd=%08x\n" , addr
, pgd_val ( * pgd
) ) ; if ( pgd_none ( * pgd
) ) goto out
; if ( pgd_bad ( * pgd
) ) { printk ( "pgd bad\n" ) ; goto out
; } pud
= pud_offset ( pgd
, addr
) ; printk ( "pud=0x%08x,*pud=%08x\n" , pud
, pud_val ( * pud
) ) ; if ( pud_none ( * pud
) ) goto out
; if ( pud_bad ( * pud
) ) { printk ( "pud bad" ) ; goto out
; } pmd
= pmd_offset ( pud
, addr
) ; printk ( "pmd=0x%08x,*pmd=%08x\n" , pmd
, pmd_val ( * pmd
) ) ; if ( pmd_none ( * pmd
) ) goto out
; if ( pmd_bad ( * pmd
) ) { printk ( "(bad)" ) ; goto out
; } pte
= pte_offset_map ( pmd
, addr
) ; printk ( "pte=0x%08x, *pte=%08x\n" , pte
, pte_val ( * pte
) ) ; if ( ! pte_none ( * pte
) && pte_present ( * pte
) ) { phy_addr
= ( pte_val ( * pte
) & 0xFFFFF000 ) | ( addr
& 0xFFF ) ; printk ( "phy_addr is 0x%x\n" , phy_addr
) ; } else { printk ( "pte is not present\n" ) ; } pte_unmap ( pte
) ; return phy_addr
;
out
: return - 1 ;
}
2.2、/dev/mem節點說明 知道虛擬地址對應的物理地址,我們就可以借助/dev/mem節點訪問對應的物理地址查看是否是虛擬地址存放的數據。 /dev/mem是系統物理內存的映像文件,這里的物理內存是指我們插在內存槽上的內存條嗎?當然是,但物理內存不單單指內存條。 物理內存嚴格來講應該是指 物理地址空間 ,內存條只是映射到這個地址空間的一部分,其余的還有各種PCI設備,IO端口等。 我們可以從/proc/iomem中看到這個映射:
cat /proc/iomem
其中 內存分配的物理地址是0x80000000~0x83FFFFFF 64M 事實上,它就是一個活著的Linux系統實時映像,所有的進程task_struct結構體,sock結構體,sk_buff結構體,進程數據等等都在里面的某個位置: 如果能定位它們在/dev/mem里的位置,我們就能得到系統中這些數據結構的實時值,所謂的調試工具所做的也不過如此。其實我們在調試內核轉儲文件的時候,vmcore也是一個物理內存映像,和/dev/mem不同的是,它是一具尸體。 無論是活體,還是尸體,均五臟俱全,分析它們的手段是一致。和靜態分析vmcore不同的是,/dev/mem是一個動態的內存映像,有時候借助它可以做一些正經的事情。 2.3、應用層獲取/dev/mem的映射
# include <stdio.h>
# include <unistd.h>
# include <sys/mman.h>
# include <sys/types.h>
# include <sys/stat.h>
# include <fcntl.h>
# include <errno.h> int main ( int argc
, char * argv
[ ] )
{ int i
= 0 , fd
= 0 ; unsigned int addr
= 0 , content
= 0 ; unsigned int page_size
= getpagesize ( ) ; unsigned int taddr
= 0 ; unsigned int * map_base
= NULL ; unsigned int offset_in_page
= 0 ; printf ( "page_size=0x%x\n" , page_size
) ; fd
= open ( "/dev/mem" , O_RDWR
| O_SYNC
) ; if ( fd
== - 1 ) { printf ( "open /dev/mem error.\n" ) ; return - 1 ; } taddr
= strtoll ( argv
[ 1 ] , NULL , 16 ) ; offset_in_page
= taddr
& ( page_size
- 1 ) ; map_base
= mmap ( NULL , page_size
, PROT_READ
, MAP_PRIVATE
, fd
, taddr
& ( ~ ( unsigned int ) ( page_size
- 1 ) ) ) ; if ( - 1 == ( int ) map_base
) { printf ( "mmap failed!, errno=%d\n" , errno
) ; close ( fd
) ; return - 1 ; } printf ( "map_base=0x%x\n" , map_base
) ; addr
= ( unsigned int ) ( ( ( unsigned char * ) map_base
) + offset_in_page
) ; printf ( "addressx:0x%x\n" , addr
) ; printf ( "content 0x%x\n\n" , * ( unsigned int * ) ( addr
) ) ; close ( fd
) ; munmap ( map_base
, page_size
) ; return 1 ;
}
2.4、測試程序
測試程序:
int main ( int argc
, char * * argv
)
{ int fd
= - 1 ; struct param_s param
; char * buf
= "Chinaxxxxx.\n" ; memset ( & param
, 0x00 , sizeof ( struct ty_motor_param_s ) ) ; fd
= open ( "/dev/motor" , O_RDWR
) ; if ( fd
< 0 ) { printf ( "open error.\n" ) ; return 0 ; } printf ( "buf=0x%x" , buf
) ; param
. addr
= buf
; ioctl ( fd
, IOCTL_SET
, & param
) ; close ( fd
) ; sleep ( 10 ) ; printf ( "buf=0x%x" , buf
) ; return 0 ;
}
2.5、實驗開始 2.5.1、運行2.4程序,通過ioctl運行內核代碼,執行2.3程序get_phy_addr函數,獲取物理地址。 2.5.2、通過傳入獲取到的物理地址,運行2.2程序,打印物理地址的信息。 其中0x83e6573c是獲取的物理地址 ARM處理器小端存儲數據,因此0x43 0x68 0x69 0x6e 剛好對應著Chinaxxxxx中的Chin的Ascii碼值 2.6、擴展 2.6.1、既然知道物理地址了,是不是可以修改物理地址里面的內容? 2.6.2、打開2.3代碼的注釋 *(unsigned int *)(addr) = 0x6f6a6944; 2.6.3、再操作2.5中的步驟,發現原先的Chinaxxxxx被修改了
3、遇到的問題 3.1.1、2.3中的代碼mmap一直返回-1,錯誤碼一直是1(errno 1 Operation not permitted) 3.1.2、解決方法 在.config文件中設置CONFIG_STRICT_DEVMEM is not set
正如上面描述的,通過獲取物理地址修改物理地址里面的數據,這對系統來說就毫無安全可言。因此內核通過CONFIG_STRICT_DEVMEM配置項禁止內存空間實現映射。
總結
以上是生活随笔 為你收集整理的虚拟地址如何访问到物理地址 的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網站內容還不錯,歡迎將生活随笔 推薦給好友。