在網上搜了很久都沒有一個很好的解釋,都只說了一方面system調用子進程后繼續執行父進程,execv是調用一個新的進程,所以打算自己讀讀這兩個執行文件源碼,自己再找找其他不同:
相關函數: fork,execl,execle,execlp,execv,execvp 表頭文件: #include<unistd.h> 定義函數: int execve(const char * filename,char * const argv[ ],char * const envp[ ]); 函數說明: execve()用來執行參數filename字符串所代表的文件路徑,第二個參數系利用數組指針來傳遞給執行文件,最后一個參數則為傳遞給執行文件的新環境變量數組。 返回值: 如果執行成功則函數不會返回,執行失敗則直接返回-1,失敗原因存於errno 中。 錯誤代碼: EACCES 1. 欲執行的文件不具有用戶可執行的權限。 2. 欲執行的文件所屬的文件系統是以noexec 方式掛上。 3.欲執行的文件或script翻譯器非一般文件。 EPERM 1.進程處於被追蹤模式,執行者並不具有root權限,欲執行的文件具有SUID 或SGID 位。 2.欲執行的文件所屬的文件系統是以nosuid方式掛上,欲執行的文件具有SUID 或SGID 位元,但執行者並不具有root權限。 E2BIG 參數數組過大 ENOEXEC 無法判斷欲執行文件的執行文件格式,有可能是格式錯誤或無法在此平台執行。 EFAULT 參數filename所指的字符串地址超出可存取空間范圍。 ENAMETOOLONG 參數filename所指的字符串太長。 ENOENT 參數filename字符串所指定的文件不存在。 ENOMEM 核心內存不足 ENOTDIR 參數filename字符串所包含的目錄路徑並非有效目錄 EACCES 參數filename字符串所包含的目錄路徑無法存取,權限不足 ELOOP 過多的符號連接 ETXTBUSY 欲執行的文件已被其他進程打開而且正把數據寫入該文件中 EIO I/O 存取錯誤 ENFILE 已達到系統所允許的打開文件總數。 EMFILE 已達到系統所允許單一進程所能打開的文件總數。 EINVAL 欲執行文件的ELF執行格式不只一個PT_INTERP節區 EISDIR ELF翻譯器為一目錄 ELIBBAD ELF翻譯器有問題。 范例:
#include<unistd.h> main() { char * argv[ ]={“ls”,”-al”,”/etc/passwd”,(char *)0}; char * envp[ ]={“PATH=/bin”,0} execve(“/bin/ls”,argv,envp); } 執行 -rw-r--r-- 1 root root 705 Sep 3 13 :52 /etc/passwd
http://www.tutorialspoint.com/unix_system_calls/execve.htm
http://blog.tianya.cn/blogger/post_read.asp?BlogID=1285060&PostID=12814565
http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=blob;f=fs/exec.c#l1376
下面是system執行文件代碼:
相關函數: fork,execve,waitpid,popen
表頭文件: #i nclude<stdlib.h>
定義函數: int system(const char * string);
函數說明: system()會調用fork()產生子進程,由子進程來調用/bin/sh-c string來執行參數string字符串所代表的命令,此命>令執行完后隨即返回原調用的進程。在調用system()期間SIGCHLD 信號會被暫時擱置,SIGINT和SIGQUIT 信號則會被忽略。
返回值: =-1:出現錯誤 =0:調用成功但是沒有出現子進程 >0:成功退出的子進程的id 如果system()在調用/bin/sh時失敗則返回127,其他失敗原因返回-1。若參數string為空指針(NULL),則返回非零值>。 如果system()調用成功則最后會返回執行shell命令后的返回值,但是此返回值也有可能為 system()調用/bin/sh失敗所返回的127,因此最好能再檢查errno 來確認執行成功。
附加說明: 在編寫具有SUID/SGID權限的程序時請勿使用system(),system()會繼承環境變量,通過環境變量可能會造成系統安全的問題。
范例: #include<stdlib.h> main() { system(“ls -al /etc/passwd /etc/shadow”); }
執行結果: -rw-r--r-- 1 root root 705 Sep 3 13 :52 /etc/passwd -r--------- 1 root root 572 Sep 2 15 :34 /etc/shado 例2: char tmp[]; sprintf(tmp,"/bin/mount -t vfat %s /mnt/usb",dev); system(tmp); 其中dev是/dev/sda1。
system源碼 #include #include #include #include int system(const char * cmdstring) { pid_t pid; int status; if(cmdstring == NULL){ return (1); } if((pid = fork())<0){ status = -1; } else if(pid == 0){ execl("/bin/sh", "sh", "-c", cmdstring, (char *)0); -exit(127); //子進程正常執行則不會執行此語句 } else{ while(waitpid(pid, &status, 0) < 0){ if(errno != EINTER){ status = -1; break; } } } return status; }
先分析一下原理,然后再看上面的代碼大家估計就能看懂了:
當system接受的命令為NULL時直接返回,否則fork出一個子進程,因為fork在兩個進程:父進程和子進程中都返回,這里要檢查返回的pid,fork在子進程中返回0,在父進程中返回子進程的pid,父進程使用waitpid等待子進程結束,子進程則是調用execl來啟動一個程序代替自己,execl("/bin/sh", "sh", "-c", cmdstring,(char*)0)是調用shell,這個shell的路徑是/bin/sh,后面的字符串都是參數,然后子進程就變成了一個shell進程,這個shell的參數是cmdstring,就是system接受的參數。在windows中的shell是command,想必大家很熟悉shell接受命令之后做的事了。
如果上面的你沒有看懂,那我再解釋下fork的原理:當一個進程A調用fork時,系統內核創建一個新的進程B,並將A的內存映像復制到B的進程空間中,因為A和B是一樣的,那么他們怎么知道自己是父進程還是子進程呢,看fork的返回值就知道,上面也說了fork在子進程中返回0,在父進程中返回子進程的pid。
execl是編譯器的函數(在一定程度上隱藏具體系統實現),在linux中它會接着產生一個linux系統的調用execve, 原型見下:
int execve(const char * file,const char **argv,const char **envp);
看到這里你就會明白為什么system()會接受父進程的環境變量,但是用system改變環境變量后,system一返回主函數還是沒變,原因從system的實現可以看到,它是通過產生新進程實現的,從我的分析中可以看到父進程和子進程間沒有進程通信,子進程自然改變不了父進程的環境變量。
if(!fork()) { execve("./helloworld",NULL,NULL); exit(0); }
#include <stdio.h> #include <unistd.h> int main() { execve("./helloworld",NULL,NULL); printf("nothing!\n"); return 0; }
#include <stdio.h> #include <unistd.h> int main() { if(!fork()) execve("./helloworld",NULL,NULL); else printf("nothing!\n"); return 0; }

1 //execve()系統中斷調用函數。加載並執行子進程(其它程序)。 2 // 該函數系統中斷調用(int 0x80)功能號__NR_execve 調用的函數。 3 // 參數:eip - 指向堆棧中調用系統中斷的程序代碼指針eip 處,參見kernel/system_call.s 程序 4 // 開始部分的說明;tmp - 系統中斷調用本函數時的返回地址,無用; 5 // filename - 被執行程序文件名;argv - 命令行參數指針數組;envp - 環境變量指針數組。 6 // 返回:如果調用成功,則不返回;否則設置出錯號,並返回-1。 7 int 8 do_execve (unsigned long *eip, long tmp, char *filename, 9 char **argv, char **envp) 10 { 11 struct m_inode *inode; // 內存中I 節點指針結構變量。 12 struct buffer_head *bh; // 高速緩存塊頭指針。 13 struct exec ex; // 執行文件頭部數據結構變量。 14 unsigned long page[MAX_ARG_PAGES]; // 參數和環境字符串空間的頁面指針數組。 15 int i, argc, envc; 16 int e_uid, e_gid; // 有效用戶id 和有效組id。 17 int retval; // 返回值。 18 int sh_bang = 0; // 控制是否需要執行腳本處理代碼。 19 // 參數和環境字符串空間中的偏移指針,初始化為指向該空間的最后一個長字處。 20 unsigned long p = PAGE_SIZE * MAX_ARG_PAGES - 4; 21 22 // eip[1]中是原代碼段寄存器cs,其中的選擇符不可以是內核段選擇符,也即內核不能調用本函數。 23 if ((0xffff & eip[1]) != 0x000f) 24 panic ("execve called from supervisor mode"); 25 // 初始化參數和環境串空間的頁面指針數組(表)。 26 for (i = 0; i < MAX_ARG_PAGES; i++) /* clear page-table */ 27 page[i] = 0; 28 // 取可執行文件的對應i 節點號。 29 if (!(inode = namei (filename))) /* get executables inode */ 30 return -ENOENT; 31 // 計算參數個數和環境變量個數。 32 argc = count (argv); 33 envc = count (envp); 34 35 // 執行文件必須是常規文件。若不是常規文件則置出錯返回碼,跳轉到exec_error2(第347 行)。 36 restart_interp: 37 if (!S_ISREG (inode->i_mode)) 38 { /* must be regular file */ 39 retval = -EACCES; 40 goto exec_error2; 41 } 42 // 檢查被執行文件的執行權限。根據其屬性(對應i 節點的uid 和gid),看本進程是否有權執行它。 43 i = inode->i_mode; 44 e_uid = (i & S_ISUID) ? inode->i_uid : current->euid; 45 e_gid = (i & S_ISGID) ? inode->i_gid : current->egid; 46 if (current->euid == inode->i_uid) 47 i >>= 6; 48 else if (current->egid == inode->i_gid) 49 i >>= 3; 50 if (!(i & 1) && !((inode->i_mode & 0111) && suser ())) 51 { 52 retval = -ENOEXEC; 53 goto exec_error2; 54 } 55 // 讀取執行文件的第一塊數據到高速緩沖區,若出錯則置出錯碼,跳轉到exec_error2 處去處理。 56 if (!(bh = bread (inode->i_dev, inode->i_zone[0]))) 57 { 58 retval = -EACCES; 59 goto exec_error2; 60 } 61 // 下面對執行文件的頭結構數據進行處理,首先讓ex 指向執行頭部分的數據結構。 62 ex = *((struct exec *) bh->b_data); /* read exec-header *//* 讀取執行頭部分 */ 63 // 如果執行文件開始的兩個字節為'#!',並且sh_bang 標志沒有置位,則處理腳本文件的執行。 64 if ((bh->b_data[0] == '#') && (bh->b_data[1] == '!') && (!sh_bang)) 65 { 66 /* 67 * This section does the #! interpretation. 68 * Sorta complicated, but hopefully it will work. -TYT 69 */ 70 /* 71 * 這部分處理對'#!'的解釋,有些復雜,但希望能工作。-TYT 72 */ 73 74 char buf[1023], *cp, *interp, *i_name, *i_arg; 75 unsigned long old_fs; 76 77 // 復制執行程序頭一行字符'#!'后面的字符串到buf 中,其中含有腳本處理程序名。 78 strncpy (buf, bh->b_data + 2, 1022); 79 // 釋放高速緩沖塊和該執行文件i 節點。 80 brelse (bh); 81 iput (inode); 82 // 取第一行內容,並刪除開始的空格、制表符。 83 buf[1022] = '\0'; 84 if (cp = strchr (buf, '\n')) 85 { 86 *cp = '\0'; 87 for (cp = buf; (*cp == ' ') || (*cp == '\t'); cp++); 88 } 89 // 若該行沒有其它內容,則出錯。置出錯碼,跳轉到exec_error1 處。 90 if (!cp || *cp == '\0') 91 { 92 retval = -ENOEXEC; /* No interpreter name found */ 93 goto exec_error1; 94 } 95 // 否則就得到了開頭是腳本解釋執行程序名稱的一行內容。 96 interp = i_name = cp; 97 // 下面分析該行。首先取第一個字符串,其應該是腳本解釋程序名,iname 指向該名稱。 98 i_arg = 0; 99 for (; *cp && (*cp != ' ') && (*cp != '\t'); cp++) 100 { 101 if (*cp == '/') 102 i_name = cp + 1; 103 } 104 // 若文件名后還有字符,則應該是參數串,令i_arg 指向該串。 105 if (*cp) 106 { 107 *cp++ = '\0'; 108 i_arg = cp; 109 } 110 /* 111 * OK, we've parsed out the interpreter name and 112 * (optional) argument. 113 */ 114 /* 115 * OK,我們已經解析出解釋程序的文件名以及(可選的)參數。 116 */ 117 // 若sh_bang 標志沒有設置,則設置它,並復制指定個數的環境變量串和參數串到參數和環境空間中。 118 if (sh_bang++ == 0) 119 { 120 p = copy_strings (envc, envp, page, p, 0); 121 p = copy_strings (--argc, argv + 1, page, p, 0); 122 } 123 /* 124 * Splice in (1) the interpreter's name for argv[0] 125 * (2) (optional) argument to interpreter 126 * (3) filename of shell script 127 * 128 * This is done in reverse order, because of how the 129 * user environment and arguments are stored. 130 */ 131 /* 132 * 拼接 (1) argv[0]中放解釋程序的名稱 133 * (2) (可選的)解釋程序的參數 134 * (3) 腳本程序的名稱 135 * 136 * 這是以逆序進行處理的,是由於用戶環境和參數的存放方式造成的。 137 */ 138 // 復制腳本程序文件名到參數和環境空間中。 139 p = copy_strings (1, &filename, page, p, 1); 140 // 復制解釋程序的參數到參數和環境空間中。 141 argc++; 142 if (i_arg) 143 { 144 p = copy_strings (1, &i_arg, page, p, 2); 145 argc++; 146 } 147 // 復制解釋程序文件名到參數和環境空間中。若出錯,則置出錯碼,跳轉到exec_error1。 148 p = copy_strings (1, &i_name, page, p, 2); 149 argc++; 150 if (!p) 151 { 152 retval = -ENOMEM; 153 goto exec_error1; 154 } 155 /* 156 * OK, now restart the process with the interpreter's inode. 157 */ 158 /* 159 * OK,現在使用解釋程序的i 節點重啟進程。 160 */ 161 // 保留原fs 段寄存器(原指向用戶數據段),現置其指向內核數據段。 162 old_fs = get_fs (); 163 set_fs (get_ds ()); 164 // 取解釋程序的i 節點,並跳轉到restart_interp 處重新處理。 165 if (!(inode = namei (interp))) 166 { /* get executables inode */ 167 set_fs (old_fs); 168 retval = -ENOENT; 169 goto exec_error1; 170 } 171 set_fs (old_fs); 172 goto restart_interp; 173 } 174 // 釋放該緩沖區。 175 brelse (bh); 176 // 下面對執行頭信息進行處理。 177 // 對於下列情況,將不執行程序:如果執行文件不是需求頁可執行文件(ZMAGIC)、或者代碼重定位部分 178 // 長度a_trsize 不等於0、或者數據重定位信息長度不等於0、或者代碼段+數據段+堆段長度超過50MB、 179 // 或者i 節點表明的該執行文件長度小於代碼段+數據段+符號表長度+執行頭部分長度的總和。 180 if (N_MAGIC (ex) != ZMAGIC || ex.a_trsize || ex.a_drsize || 181 ex.a_text + ex.a_data + ex.a_bss > 0x3000000 || 182 inode->i_size < ex.a_text + ex.a_data + ex.a_syms + N_TXTOFF (ex)) 183 { 184 retval = -ENOEXEC; 185 goto exec_error2; 186 } 187 // 如果執行文件執行頭部分長度不等於一個內存塊大小(1024 字節),也不能執行。轉exec_error2。 188 if (N_TXTOFF (ex) != BLOCK_SIZE) 189 { 190 printk ("%s: N_TXTOFF != BLOCK_SIZE. See a.out.h.", filename); 191 retval = -ENOEXEC; 192 goto exec_error2; 193 } 194 // 如果sh_bang 標志沒有設置,則復制指定個數的環境變量字符串和參數到參數和環境空間中。 195 // 若sh_bang 標志已經設置,則表明是將運行腳本程序,此時環境變量頁面已經復制,無須再復制。 196 if (!sh_bang) 197 { 198 p = copy_strings (envc, envp, page, p, 0); 199 p = copy_strings (argc, argv, page, p, 0); 200 // 如果p=0,則表示環境變量與參數空間頁面已經被占滿,容納不下了。轉至出錯處理處。 201 if (!p) 202 { 203 retval = -ENOMEM; 204 goto exec_error2; 205 } 206 } 207 /* OK, This is the point of no return */ 208 /* OK,下面開始就沒有返回的地方了 */ 209 // 如果原程序也是一個執行程序,則釋放其i 節點,並讓進程executable 字段指向新程序i 節點。 210 if (current->executable) 211 iput (current->executable); 212 current->executable = inode; 213 // 清復位所有信號處理句柄。但對於SIG_IGN 句柄不能復位,因此在322 與323 行之間需添加一條 214 // if 語句:if (current->sa[I].sa_handler != SIG_IGN)。這是源代碼中的一個bug。 215 for (i = 0; i < 32; i++) 216 current->sigaction[i].sa_handler = NULL; 217 // 根據執行時關閉(close_on_exec)文件句柄位圖標志,關閉指定的打開文件,並復位該標志。 218 for (i = 0; i < NR_OPEN; i++) 219 if ((current->close_on_exec >> i) & 1) 220 sys_close (i); 221 current->close_on_exec = 0; 222 // 根據指定的基地址和限長,釋放原來程序代碼段和數據段所對應的內存頁表指定的內存塊及頁表本身。 223 free_page_tables (get_base (current->ldt[1]), get_limit (0x0f)); 224 free_page_tables (get_base (current->ldt[2]), get_limit (0x17)); 225 // 如果“上次任務使用了協處理器”指向的是當前進程,則將其置空,並復位使用了協處理器的標志。 226 if (last_task_used_math == current) 227 last_task_used_math = NULL; 228 current->used_math = 0; 229 // 根據a_text 修改局部表中描述符基址和段限長,並將參數和環境空間頁面放置在數據段末端。 230 // 執行下面語句之后,p 此時是以數據段起始處為原點的偏移值,仍指向參數和環境空間數據開始處, 231 // 也即轉換成為堆棧的指針。 232 p += change_ldt (ex.a_text, page) - MAX_ARG_PAGES * PAGE_SIZE; 233 // create_tables()在新用戶堆棧中創建環境和參數變量指針表,並返回該堆棧指針。 234 p = (unsigned long) create_tables ((char *) p, argc, envc); 235 // 修改當前進程各字段為新執行程序的信息。令進程代碼段尾值字段end_code = a_text;令進程數據 236 // 段尾字段end_data = a_data + a_text;令進程堆結尾字段brk = a_text + a_data + a_bss。 237 current->brk = ex.a_bss + 238 (current->end_data = ex.a_data + (current->end_code = ex.a_text)); 239 // 設置進程堆棧開始字段為堆棧指針所在的頁面,並重新設置進程的用戶id 和組id。 240 current->start_stack = p & 0xfffff000; 241 current->euid = e_uid; 242 current->egid = e_gid; 243 // 初始化一頁bss 段數據,全為零。 244 i = ex.a_text + ex.a_data; 245 while (i & 0xfff) 246 put_fs_byte (0, (char *) (i++)); 247 // 將原調用系統中斷的程序在堆棧上的代碼指針替換為指向新執行程序的入口點,並將堆棧指針替換 248 // 為新執行程序的堆棧指針。返回指令將彈出這些堆棧數據並使得CPU 去執行新的執行程序,因此不會 249 // 返回到原調用系統中斷的程序中去了。 250 eip[0] = ex.a_entry; /* eip, magic happens :-) *//* eip,魔法起作用了 */ 251 eip[3] = p; /* stack pointer *//* esp,堆棧指針 */ 252 return 0; 253 exec_error2: 254 iput (inode); 255 exec_error1: 256 for (i = 0; i < MAX_ARG_PAGES; i++) 257 free_page (page[i]); 258 return (retval); 259 }