Linux0.11核心--載入可執行二進位檔案之1.copy_strings,notepad二進位copy
從現在開始就是分析最後的核心模組exec.c了,分析完這個檔案後,就會和之前的所有分析形成一個環路,從建立進程、載入進程程式到進程調度、記憶體管理。
exec.c的核心do_execve函數很長,而且用到了很多其他的函數,copy_strings就是其中一個,我們這裡就先來分析這個函數。
首先看調用處,在main.c中:
static char *argv_rc[] ={"/bin/sh", NULL};// 調用執行程式時參數的字串數組。static char *envp_rc[] ={"HOME=/", NULL};// 調用執行程式時的環境字串數組。void init(void){... execve ("/bin/sh", argv_rc, envp_rc);// 替換成/bin/sh 程式並執行。...}
再看exec.c中:
/** MAX_ARG_PAGES 定義了新程式分配給參數和環境變數使用的記憶體最大頁數。* 32 頁記憶體應該足夠了,這使得環境和參數(env+arg)空間的總合達到128kB!*/#define MAX_ARG_PAGES 32do_execve (unsigned long *eip, long tmp, char *filename, char **argv, char **envp){ unsigned long page[MAX_ARG_PAGES];// 參數和環境字串空間的頁面指標數組。 int i, argc, envc; // 參數和環境字串空間中的位移指標,初始化為指向該空間的最後一個長字處。 unsigned long p = PAGE_SIZE * MAX_ARG_PAGES - 4;... // 計算參數個數和環境變數個數。 argc = count (argv); envc = count (envp); // 若sh_bang 標誌沒有設定,則設定它,並複製指定個數的環境變數串和參數串到參數和環境空間中。 if (sh_bang++ == 0){ p = copy_strings (envc, envp, page, p, 0); p = copy_strings (--argc, argv + 1, page, p, 0);}...}
mm.h:
#define PAGE_SIZE 4096// 定義記憶體頁面的大小(位元組數)。
exec.c和segment.h放在一起:
/** count()Function Compute命令列參數/環境變數的個數。*///// 計算參數個數。// 參數:argv - 參數指標數組,最後一個指標項是NULL。// 返回:參數個數。static intcount (char **argv){ int i = 0; char **tmp; if (tmp = argv) while (get_fs_long ((unsigned long *) (tmp++))) i++; return i;}//// 讀取fs 段中指定地址處的長字(4 位元組)。// 參數:addr - 指定的記憶體位址。// %0 - (返回的長字_v);%1 - (記憶體位址addr)。// 返回:返回記憶體fs:[addr]處的長字。extern inline unsigned longget_fs_long (const unsigned long *addr){ unsigned long _v;__asm__ ("movl %%fs:%1,%0": "=r" (_v):"m" (*addr)); return _v;}
先分析擷取參數/環境變數的個數,首先聲明了兩個指標數組argv_rc和envp_rc並傳入execve。
int* a[4] 指標數組
表示:數組a中的元素都為int型指標
注意do_execve的形參為char **argv, char **envp,指標的指標。所以也就是說在count函數中,tmp++是指標數組argv_rc的其中的元素的地址,那麼在get_fs_long中*addr指的是argv_rc的元素的值(也就是"/bin/sh"這個char類型指標),因為使用的是fs:%1而不是fs:[%1],因此最終_v得到的是char類型的完整地址。所以count就是根據是不是有地址值來判斷數量。
/** 'copy_string()'函數從使用者記憶體空間拷貝參數和環境字串到核心空閑頁面記憶體中。* 這些已具有直接放到新使用者記憶體中的格式。** 由TYT(Tytso)於1991.12.24 日修改,增加了from_kmem 參數,該參數指明了字串或* 字串數組是來自使用者段還是核心段。** from_kmem argv * argv *** 0 使用者空間 使用者空間* 1 核心空間 使用者空間* 2 核心空間 核心空間** 我們是通過巧妙處理fs 段寄存器來操作的。由於載入一個段寄存器代價太大,所以* 我們盡量避免調用set_fs(),除非實在必要。*///// 複製指定個數的參數字串到參數和環境空間。// 參數:argc - 欲添加的參數個數;argv - 參數指標數組;page - 參數和環境空間頁面指標數組。// p -在參數資料表空間中的位移指標,始終指向已複製串的頭部;from_kmem - 字串來源標誌。// 在do_execve()函數中,p 初始化為指向參數表(128kB)空間的最後一個長字處,參數字串// 是以堆棧操作方式逆嚮往其中複製存放的,因此p 指標會始終指向參數字串的頭部。// 返回:參數和環境空間當前頭部指標。static unsigned longcopy_strings (int argc, char **argv, unsigned long *page, unsigned long p, int from_kmem){ char *tmp, *pag; int len, offset = 0; unsigned long old_fs, new_fs; if (!p) return 0;/* bullet-proofing *//* 位移指標驗證 */// 取ds 寄存器值到new_fs,並儲存原fs 寄存器值到old_fs。 new_fs = get_ds (); old_fs = get_fs ();// 如果字串和字串數組來自核心空間,則設定fs 段寄存器指向核心資料區段(ds)。 if (from_kmem == 2) set_fs (new_fs);// 迴圈處理各個參數,從最後一個參數逆向開始複製,複製到指定位移地址處。 while (argc-- > 0) {// 如果字串在使用者空間而字串數組在核心空間,則設定fs 段寄存器指向核心資料區段(ds)。 if (from_kmem == 1)set_fs (new_fs);// 從最後一個參數開始逆向操作,取fs 段中最後一參數指標到tmp,如果為空白,則出錯死機。 if (!(tmp = (char *) get_fs_long (((unsigned long *) argv) + argc)))panic ("argc is wrong");// 如果字串在使用者空間而字串數組在核心空間,則恢複fs 段寄存器原值。 if (from_kmem == 1)set_fs (old_fs);// 計算該參數字串長度len,並使tmp 指向該參數字串末端。 len = 0;/* remember zero-padding */ do{/* 我們知道串是以NULL 位元組結尾的 */ len++;} while (get_fs_byte (tmp++));// 如果該字串長度超過此時參數和環境空間中還剩餘的空閑長度,則恢複fs 段寄存器並返回0。 if (p - len < 0){/* this shouldn't happen - 128kB */ set_fs (old_fs);/* 不會發生-因為有128kB 的空間 */ return 0;}// 複製fs 段中當前指定的參數字串,是從該字串尾逆向開始複製。 while (len){ --p; --tmp; --len;// 函數剛開始執行時,位移變數offset 被初始化為0,因此若offset-1<0,說明是首次複製字串,// 則令其等於p 指標在頁面內的位移值,並申請空閑頁面。 if (--offset < 0) { offset = p % PAGE_SIZE;// 如果字串和字串數組在核心空間,則恢複fs 段寄存器原值。 if (from_kmem == 2)set_fs (old_fs);// 如果當前位移值p 所在的串空間頁面指標數組項page[p/PAGE_SIZE]==0,表示相應頁面還不存在,// 則需申請新的記憶體空閑頁面,將該頁面指標填入指標數組,並且也使pag 指向該新頁面,若申請不// 到空閑頁面則返回0。 if (!(pag = (char *) page[p / PAGE_SIZE]) && !(pag = (char *) page[p / PAGE_SIZE] = (unsigned long *) get_free_page ()))return 0;// 如果字串和字串數組來自核心空間,則設定fs 段寄存器指向核心資料區段(ds)。 if (from_kmem == 2)set_fs (new_fs); }// 從fs 段中複製參數字串中一位元組到pag+offset 處。 *(pag + offset) = get_fs_byte (tmp);} }// 如果字串和字串數組在核心空間,則恢複fs 段寄存器原值。 if (from_kmem == 2) set_fs (old_fs);// 最後,返回參數和環境空間中已複製參數資訊的頭部位移值。 return p;}
首先p是指向參數和環境空間的最後一個長字處,邏輯地址,如所示
首先從最後一個參數開始逆向操作,取fs段中最後一個參數指標到tmp。
然後取字串長度,注意get_fs_byte的*addr為字元指標指向的值,也就是_v得到的是字元值一個位元組。
最後是從字串尾部開始逆向複製,注意page數組不是用來映射的,而是儲存記憶體頁的地址。而offset是每次迴圈都會變化。
最終返回p。