Monday, July 2, 2012

Linux寫入資料到其他進程的記憶體

如果Process A要在Process B的某記憶體位置寫入資料,在Windows下可以使用Windows API WriteProcessMemory 。但在Linux下似乎找不到內建的相關函式可以用,或是相關的函式並沒有在Kernel中被導出(Export),因此經過測試,發現可以透過以下方法來實現。

Linux Driver函式透過syscall被呼叫時,虛擬記憶體User Mode部分是映射到呼叫syscall者的Process記憶體內容,因此像是copy_from_user之類的函式可以直接複製呼叫者的記憶體資料。

但是在這種情況下如果要寫入資料到其他Process的記憶體,就必須切換User Mode的映射。一般CPU的虛擬記憶體與物理記憶體映射對應是透過記憶體映射表,由CPU的CR3暫存器指向當前的記憶體映射表。


因此透過更改CR3暫存器,我們就可以暫時將User Mode的映射切換到其他Process,並透過copy_from_user或copy_to_user讀取或寫入到該Process。

部份Code(將Process A的資料複製到當前Process):
 
void *src;    //指向Process A的User Mode資料記憶體位置
void *dst;    //指向要寫入到當前(current)process的User Mode記憶體位置
void *buf;    //臨時緩衝區
int size;     //要複製的大小
struct task_struct *a_task;    //Process A的task_struct,a_task =  pid_task(find_vpid(Process A的PID),PIDTYPE_PID)
struct mm_struct *oldmm;

oldmm = current->mm;    //保存當前mm_struct
current->mm = a_task->mm;
current->active_mm = current->mm;    //如果當前Process不是一個Kernel Thread,active_mm = current->mm
load_cr3(current->mm->pgd);    //更新CR3
     
copy_from_user(buf,src,size);    //從Process A複製資料到緩衝區

current->mm = oldmm;    //恢復當前mm_struct
current->active_mm = current->mm;
load_cr3(current->mm->pgd);    //恢復CR3
     
copy_to_user(dst,buf,size);    //將緩衝區資料寫到當前Process的目標位置
 

一開始,我沒對current->mm和current->active_mm作替換,但是由於Linux在進程調度時,會根據current->mm載入CR3,為了避免呼叫完load_cr3後發生進程調度,導致CR3被改回去(經過測試,確定會發生此狀況),用preempt_disable()暫時關閉搶佔,保證從load_cr3到copy_to_user結束都不會發生進程調度。

但是後來測試時,發現對於size比較大時,會發生copy_to_user失敗的情形。追究原因,應該是因為copy_from_user和copy_to_user皆會發生Page Fault(複製時剛好該記憶體頁被置換出物理記憶體,這時會發生Page Fault,然後Linux去把該記憶體頁重新映射回物理記憶體中),但是關閉搶佔的情況下,是無法處理Page Fault,導致讀寫失敗。

1 comment:

  1. 寫些比較親民的啦...寫這我都有看沒有懂..

    ReplyDelete