Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> Android開發 >> 關於android開發 >> Linux0.11內核--加載可執行二進制文件之1.copy_strings,notepad二進制copy

Linux0.11內核--加載可執行二進制文件之1.copy_strings,notepad二進制copy

編輯:關於android開發

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 32

do_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()函數計算命令行參數/環境變量的個數。
*/
//// 計算參數個數。
// 參數:argv - 參數指針數組,最後一個指針項是NULL。
// 返回:參數個數。
static int
count (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 long
get_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 long
copy_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。

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved