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

Linux0.11內核--加載可執行二進制文件之3.exec,linux0.113.exec

編輯:關於android開發

Linux0.11內核--加載可執行二進制文件之3.exec,linux0.113.exec


最後剩下最核心的函數do_execve了,由於這裡為了簡單起見我不分析shell命令的情況,

/*
* 'do_execve()'函數執行一個新程序。
*/
//// execve()系統中斷調用函數。加載並執行子進程(其它程序)。
// 該函數系統中斷調用(int 0x80)功能號__NR_execve 調用的函數。
// 參數:eip - 指向堆棧中調用系統中斷的程序代碼指針eip 處,參見kernel/system_call.s 程序
// 開始部分的說明;tmp - 系統中斷調用本函數時的返回地址,無用;
// filename - 被執行程序文件名;argv - 命令行參數指針數組;envp - 環境變量指針數組。
// 返回:如果調用成功,則不返回;否則設置出錯號,並返回-1。
int
do_execve (unsigned long *eip, long tmp, char *filename,
	   char **argv, char **envp)
{
  struct m_inode *inode;	// 內存中I 節點指針結構變量。
  struct buffer_head *bh;	// 高速緩存塊頭指針。
  struct exec ex;		// 執行文件頭部數據結構變量。
  unsigned long page[MAX_ARG_PAGES];	// 參數和環境字符串空間的頁面指針數組。
  int i, argc, envc;
  int e_uid, e_gid;		// 有效用戶id 和有效組id。
  int retval;			// 返回值。
  int sh_bang = 0;		// 控制是否需要執行腳本處理代碼。
// 參數和環境字符串空間中的偏移指針,初始化為指向該空間的最後一個長字處。
  unsigned long p = PAGE_SIZE * MAX_ARG_PAGES - 4;

// eip[1]中是原代碼段寄存器cs,其中的選擇符不可以是內核段選擇符,也即內核不能調用本函數。
  if ((0xffff & eip[1]) != 0x000f)
    panic ("execve called from supervisor mode");
// 初始化參數和環境串空間的頁面指針數組(表)。
  for (i = 0; i < MAX_ARG_PAGES; i++)	/* clear page-table */
    page[i] = 0;
// 取可執行文件的對應i 節點號。
  if (!(inode = namei (filename)))	/* get executables inode */
    return -ENOENT;
// 計算參數個數和環境變量個數。
  argc = count (argv);
  envc = count (envp);

// 執行文件必須是常規文件。若不是常規文件則置出錯返回碼,跳轉到exec_error2(第347 行)。
restart_interp:
  if (!S_ISREG (inode->i_mode))
    {				/* must be regular file */
      retval = -EACCES;
      goto exec_error2;
    }
// 檢查被執行文件的執行權限。根據其屬性(對應i 節點的uid 和gid),看本進程是否有權執行它。
  i = inode->i_mode;				// 取文件屬性字段值。

  // 如果文件的設置用戶ID 標志(set-user-id)置位的話,則後面執行進程的有效用戶ID(euid)就
  // 設置為文件的用戶ID,否則設置成當前進程的euid。這裡將該值暫時保存在e_uid 變量中。
  // 如果文件的設置組ID 標志(set-group-id)置位的話,則執行進程的有效組ID(egid)就設置為
  // 文件的組ID。否則設置成當前進程的egid。
  e_uid = (i & S_ISUID) ? inode->i_uid : current->euid;
  e_gid = (i & S_ISGID) ? inode->i_gid : current->egid;
  // 如果文件屬於運行進程的用戶,則把文件屬性字右移6 位,則最低3 位是文件宿主的訪問權限標志。
  // 否則的話如果文件與運行進程的用戶屬於同組,則使屬性字最低3 位是文件組用戶的訪問權限標志。
  // 否則屬性字最低3 位是其他用戶訪問該文件的權限。
  if (current->euid == inode->i_uid)
    i >>= 6;
  else if (current->egid == inode->i_gid)
    i >>= 3;
  // 如果上面相應用戶沒有執行權並且其他用戶也沒有任何權限,並且不是超級用戶,則表明該文件不
  // 能被執行。於是置不可執行出錯碼,跳轉到exec_error2 處去處理。
  if (!(i & 1) && !((inode->i_mode & 0111) && suser ()))
    {
      retval = -ENOEXEC;
      goto exec_error2;
    }
// 讀取執行文件的第一塊數據到高速緩沖區,若出錯則置出錯碼,跳轉到exec_error2 處去處理。
  if (!(bh = bread (inode->i_dev, inode->i_zone[0])))
    {
      retval = -EACCES;
      goto exec_error2;
    }
// 下面對執行文件的頭結構數據進行處理,首先讓ex 指向執行頭部分的數據結構。
  ex = *((struct exec *) bh->b_data);	/* read exec-header *//* 讀取執行頭部分 */
...
//shell
...
// 釋放該緩沖區。
  brelse (bh);
// 下面對執行頭信息進行處理。
// 對於下列情況,將不執行程序:如果執行文件不是需求頁可執行文件(ZMAGIC)、或者代碼重定位部分
// 長度a_trsize 不等於0、或者數據重定位信息長度不等於0、或者代碼段+數據段+堆段長度超過50MB、
// 或者i 節點表明的該執行文件長度小於代碼段+數據段+符號表長度+執行頭部分長度的總和。
  if (N_MAGIC (ex) != ZMAGIC || ex.a_trsize || ex.a_drsize ||
      ex.a_text + ex.a_data + ex.a_bss > 0x3000000 ||
      inode->i_size < ex.a_text + ex.a_data + ex.a_syms + N_TXTOFF (ex))
    {
      retval = -ENOEXEC;
      goto exec_error2;
    }
// 如果執行文件執行頭部分長度不等於一個內存塊大小(1024 字節),也不能執行。轉exec_error2。
  if (N_TXTOFF (ex) != BLOCK_SIZE)
    {
      printk ("%s: N_TXTOFF != BLOCK_SIZE. See a.out.h.", filename);
      retval = -ENOEXEC;
      goto exec_error2;
    }
// 如果sh_bang 標志沒有設置,則復制指定個數的環境變量字符串和參數到參數和環境空間中。
// 若sh_bang 標志已經設置,則表明是將運行腳本程序,此時環境變量頁面已經復制,無須再復制。
  if (!sh_bang)
    {
      p = copy_strings (envc, envp, page, p, 0);
      p = copy_strings (argc, argv, page, p, 0);
// 如果p=0,則表示環境變量與參數空間頁面已經被占滿,容納不下了。轉至出錯處理處。
      if (!p)
	{
	  retval = -ENOMEM;
	  goto exec_error2;
	}
    }
/* OK, This is the point of no return */
/* OK,下面開始就沒有返回的地方了 */
// 如果原程序也是一個執行程序,則釋放其i 節點,並讓進程executable 字段指向新程序i 節點。
  if (current->executable)
    iput (current->executable);
  current->executable = inode;
// 清復位所有信號處理句柄。但對於SIG_IGN 句柄不能復位,因此在322 與323 行之間需添加一條
// if 語句:if (current->sa[I].sa_handler != SIG_IGN)。這是源代碼中的一個bug。
  for (i = 0; i < 32; i++)
    current->sigaction[i].sa_handler = NULL;
// 根據執行時關閉(close_on_exec)文件句柄位圖標志,關閉指定的打開文件,並復位該標志。
  for (i = 0; i < NR_OPEN; i++)
    if ((current->close_on_exec >> i) & 1)
      sys_close (i);
  current->close_on_exec = 0;
// 根據指定的基地址和限長,釋放原來程序代碼段和數據段所對應的內存頁表指定的內存塊及頁表本身。
  // 此時被執行程序沒有占用主內存區任何頁面。在執行時會引起內存管理程序執行缺頁處理而為其申請
  // 內存頁面,並把程序讀入內存。
  free_page_tables (get_base (current->ldt[1]), get_limit (0x0f));
  free_page_tables (get_base (current->ldt[2]), get_limit (0x17));
// 如果“上次任務使用了協處理器”指向的是當前進程,則將其置空,並復位使用了協處理器的標志。
  if (last_task_used_math == current)
    last_task_used_math = NULL;
  current->used_math = 0;
// 根據a_text 修改局部表中描述符基址和段限長,並將參數和環境空間頁面放置在數據段末端。
// 執行下面語句之後,p 此時是以數據段起始處為原點的偏移值,仍指向參數和環境空間數據開始處,
// 也即轉換成為堆棧的指針。
  p += change_ldt (ex.a_text, page) - MAX_ARG_PAGES * PAGE_SIZE;
// create_tables()在新用戶堆棧中創建環境和參數變量指針表,並返回該堆棧指針。
  p = (unsigned long) create_tables ((char *) p, argc, envc);
// 修改當前進程各字段為新執行程序的信息。令進程代碼段尾值字段end_code = a_text;令進程數據
// 段尾字段end_data = a_data + a_text;令進程堆結尾字段brk = a_text + a_data + a_bss。
  current->brk = ex.a_bss +
    (current->end_data = ex.a_data + (current->end_code = ex.a_text));
// 設置進程堆棧開始字段為堆棧指針所在的頁面,並重新設置進程的有效用戶id 和有效組id。
  current->start_stack = p & 0xfffff000;
  current->euid = e_uid;
  current->egid = e_gid;
// 初始化一頁bss 段數據,全為零。
  i = ex.a_text + ex.a_data;
  while (i & 0xfff)
    put_fs_byte (0, (char *) (i++));
// 將原調用系統中斷的程序在堆棧上的代碼指針替換為指向新執行程序的入口點,並將堆棧指針替換
// 為新執行程序的堆棧指針。返回指令將彈出這些堆棧數據並使得CPU 去執行新的執行程序,因此不會
// 返回到原調用系統中斷的程序中去了。
  eip[0] = ex.a_entry;		/* eip, magic happens :-) *//* eip,魔法起作用了 */
  eip[3] = p;			/* stack pointer *//* esp,堆棧指針 */
  return 0;
exec_error2:
  iput (inode);
exec_error1:
  for (i = 0; i < MAX_ARG_PAGES; i++)
    free_page (page[i]);
  return (retval);
}

盡管刪掉很大一部分,但代碼還是很長。不過沒有關系,核心代碼還是一小部分,大部分是判斷性的代碼。判斷性的代碼就不做分析了,仔細看也是能看懂。

注意bh = bread (inode->i_dev, inode->i_zone[0]))首先讀取可執行文件的第一塊數據到高速緩沖區,緊接著ex = *((struct exec *) bh->b_data);把b_data數據復制到ex中,這說明文件的b_data型數據就是exec結構。

後面又是一堆對ex的判斷。

接著調用copy_strings,這時p指向參數和環境空間的已使用的地址處。

後面又是一堆給當前進程current賦值的操作。

然後是兩次free_page_tables釋放LDT的代碼段和數據段。

然後調用change_ldt設置當前進程的LDT,這時p指向的位置之前分析過了。

接下來分析create_tables:

/*
* create_tables()函數在新用戶內存中解析環境變量和參數字符串,由此
* 創建指針表,並將它們的地址放到"堆棧"上,然後返回新棧的指針值。
*/
//// 在新用戶堆棧中創建環境和參數變量指針表。
// 參數:p - 以數據段為起點的參數和環境信息偏移指針;argc - 參數個數;envc -環境變量數。
// 返回:堆棧指針。
static unsigned long *
create_tables (char *p, int argc, int envc)
{
  unsigned long *argv, *envp;
  unsigned long *sp;

// 堆棧指針是以4 字節(1 節)為邊界尋址的,因此這裡讓sp 為4 的整數倍。
  sp = (unsigned long *) (0xfffffffc & (unsigned long) p);
// sp 向下移動,空出環境參數占用的空間個數,並讓環境參數指針envp 指向該處。
  sp -= envc + 1;
  envp = sp;
// sp 向下移動,空出命令行參數指針占用的空間個數,並讓argv 指針指向該處。
// 下面指針加1,sp 將遞增指針寬度字節值。
  sp -= argc + 1;
  argv = sp;
// 將環境參數指針envp 和命令行參數指針以及命令行參數個數壓入堆棧。
  put_fs_long ((unsigned long) envp, --sp);
  put_fs_long ((unsigned long) argv, --sp);
  put_fs_long ((unsigned long) argc, --sp);
// 將命令行各參數指針放入前面空出來的相應地方,最後放置一個NULL 指針。
  while (argc-- > 0)
    {
      put_fs_long ((unsigned long) p, argv++);
      while (get_fs_byte (p++)) /* nothing */ ;	// p 指針前移4 字節。
    }
  put_fs_long (0, argv);
// 將環境變量各指針放入前面空出來的相應地方,最後放置一個NULL 指針。
  while (envc-- > 0)
    {
      put_fs_long ((unsigned long) p, envp++);
      while (get_fs_byte (p++)) /* nothing */ ;
    }
  put_fs_long (0, envp);
  return sp;			// 返回構造的當前新堆棧指針。
}

create_tables()函數用於根據給定的當前堆棧指針值p 以及參數變量個數值argc 和環境變量個數
envc,在新的程序堆棧中創建環境和參數變量指針表,並返回此時的堆棧指針值sp。創建完畢後堆棧指
針表的形式見下圖9-24 所示。

注意這裡有三條連續的put_fs_long函數調用,這裡的壓入堆棧並不是真的壓棧,而是以壓棧的方式存數據。

然後是兩個循環放置p對應的字節值,注意這裡是p++,因為之前拷貝的時候是--p。

數據填充完成後返回sp。

 

接著後面給當前進程的start_stack賦值,賦值p所在的頁面。

最後值得注意的是:

// 將原調用系統中斷的程序在堆棧上的代碼指針替換為指向新執行程序的入口點,並將堆棧指針替換
// 為新執行程序的堆棧指針。返回指令將彈出這些堆棧數據並使得CPU 去執行新的執行程序,因此不會
// 返回到原調用系統中斷的程序中去了。
  eip[0] = ex.a_entry;		/* eip, magic happens :-) *//* eip,魔法起作用了 */
  eip[3] = p;			/* stack pointer *//* esp,堆棧指針 */

do_execve是在system_call.s中調用的:

#### 這是sys_execve()系統調用。取中斷調用程序的代碼指針作為參數調用C 函數do_execve()。
# do_execve()在(fs/exec.c,182)。
.align 2
_sys_execve:
lea EIP(%esp),%eax
pushl %eax
call _do_execve
addl $4,%esp 						# 丟棄調用時壓入棧的EIP 值。
ret

可以觀察到,這個EIP是調用_system_call之前壓入的,所以do_execve的eip就是這個EIP:

/*
* 0(%esp) - %eax
* 4(%esp) - %ebx
* 8(%esp) - %ecx
* C(%esp) - %edx
* 10(%esp) - %fs
* 14(%esp) - %es
* 18(%esp) - %ds
* 1C(%esp) - %eip
* 20(%esp) - %cs
* 24(%esp) - %eflags
* 28(%esp) - %oldesp
* 2C(%esp) - %oldss
*/

所以eip[0]就是%eip,賦值了程序的入口地址,eip[3]就是%oldesp,賦值了p的值。

至此exec.c分析結束!

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