linux程式的命令列引數

2021-08-25 01:31:42 字數 3820 閱讀 3751

程式執行的時候需要命令列引數,linux中更是這樣,隨便在shell輸入/bin/xx --help後列舉出來的引數讓你頭暈眼花,可是這些引數是怎麼進入程式的呢,我們知道程式執行的時候一般從main開始,而mian有兩個引數,乙個是 argc代表引數的個數,乙個是argv代表具體字串型別的引數,這是我們所看到的,我們都知道函式的引數都在堆疊中,在呼叫函式前,主調函式應該將引數壓入堆疊後再呼叫被調函式,那麼是誰呼叫的main函式呢?又是誰將main的引數壓入堆疊的呢?

關於第乙個問題,是誰呼叫的main函式,我就不多說了,因為網上已經有了一篇叫做《before main》的文章了,寫得非常好,可以搜尋一下,讀了此文你會明白實際上使用者程序的開始函式並不是main,在main之前還有很多任務作要做,但是如果說 是xx呼叫了main,那麼就是xx壓入了引數,我們很多人喜歡糾著乙個問題一直到底,那我們就較較真兒,又是誰將引數給了xx呢?我們開始乙個程式的時 候要呼叫exec系列函式,比如execve,我們看看execve的宣告:

int execve(const char *filename, char *const argv,char *const envp);

我 們看一下這第二個和第三個引數實際上就是main的引數(main的第乙個引數argc是由這些引數算出來的),而呼叫execve的時候還是原來的進 程,新的程序還只是乙個filename,具體能否執行還有待商榷呢,新程序根本沒有對映進使用者空間,這時這些引數是怎麼傳遞給新的程序的呢?我們於是就來正式解答第二個問題:又是誰將main的引數壓入堆疊的呢?

研究linux有個好的不得了的資源就是核心,當你遇到任何棘手的問題都可以從核心得到解答,當然今天我們的問題並不算棘手!我們還是看看sys_ececve是怎麼做的:

asmlinkage int sys_execve(struct pt_regs regs)

int error;

char * filename;

filename = getname((char __user *) regs.ebx);

error = ptr_err(filename);

if (is_err(filename))

goto out;

error = do_execve(filename,

(char __user * __user *) regs.ecx,

(char __user * __user *) regs.edx,

®s);

...//我們今天的問題到此為止,以下省略

繼續do_execve

int do_execve(char * filename,

char __user *__user *argv,

char __user *__user *envp,

struct pt_regs * regs)

struct linux_binprm *bprm;

bprm = kzalloc(sizeof(*bprm), gfp_kernel);

bprm->file = file;

bprm->filename = filename;

bprm->interp = filename;

bprm->argc = count(argv, max_arg_strings);

if ((retval = bprm->argc)

goto out_mm;

bprm->envc = count(envp, max_arg_strings);

if ((retval = bprm->envc)

goto out_mm;

retval = copy_strings_kernel(1, &bprm->filename, bprm);//拷貝檔名稱

bprm->exec = bprm->p;

retval = copy_strings(bprm->envc, envp, bprm);//拷貝envc

if (retval

goto out;

env_p = bprm->p;

retval = copy_strings(bprm->argc, argv, bprm);//拷貝argc

if (retval

goto out;

bprm->argv_len = env_p - bprm->p;

retval = search_binary_handler(bprm,regs);

我們看到argc是怎麼算出來的:

bprm->argc = count(argv, max_arg_strings);

它實際上就是算出了引數的個數,下面最重要的就是copy_strings函式了,這個函式的意義就是將引數拷貝到乙個核心的頁面當中並設定為bprm的乙個字段,我們先看看bprm結構:

struct linux_binprm while (0)

ei_index += 2;

sp = stack_add(p, ei_index);

items = (argc + 1) + (envc + 1); //items就是引數的數量

if (interp_aout) else else {

argv = sp;

envp = argv + argc + 1;

p = current->mm->arg_start;

while (argc-- > 0) { //一次壓入argv引數的指標

size_t len;

__put_user((elf_addr_t)p, argv++);

len = strnlen_user((void __user *)p, page_size*max_arg_pages);

if (!len || len > page_size*max_arg_pages)

return;

p += len;

__put_user(0, argv);

current->mm->arg_end = current->mm->env_start = p;

while (envc-- > 0) {

size_t len;

__put_user((elf_addr_t)p, envp++);

len = strnlen_user((void __user *)p, page_size*max_arg_pages);

if (!len || len > page_size*max_arg_pages)

return;

p += len;

__put_user(0, envp);

current->mm->env_end = p;

sp = (elf_addr_t __user *)envp + 1;

copy_to_user(sp, elf_info, ei_index * sizeof(elf_addr_t));

要 想徹底明白這個機制,其實明白main的引數結構就可以了,看看第二個引數char *argv,實際上就是個指標的指標了,argv指向乙個陣列的頭指標,而此陣列的元素是字串,copy_strings拷貝的是各個字串的內容,在當時由於新的位址空間還未就位因此根本談不上指標,因為指標其實就是位址空間的乙個位址,後來到了create_elf_tables的時候,起碼引數相關的vma已經就位了,因此位址資訊就確定了,因此只有在這裡推入各個引數的指標資訊,而這些指標指向的就是copy_strings拷貝的內容,相應的指標值是通過引數vma的內部位址和引數數量算出來的。

這就是堆疊的好處,幫助一切函式呼叫傳遞引數,包括main函式(rom中無法呼叫函式就是因為rom不可寫,而操作堆疊必須寫記憶體)。linux將一切 策略留給使用者,僅僅幫助使用者推入了一系列main函式的引數,不光linux,windows也是這樣,不過windows除了這些,還幫使用者做了更多,包括把使用者煩死。

linux程式的命令列引數

程式執行的時候需要命令列引數,linux中更是這樣,隨便在shell輸入 bin xx help後列舉出來的引數讓你頭暈眼花,可是這些引數是怎麼進入程式的呢,我們知道程式執行的時候一般從main開始,而mian有兩個引數,乙個是 argc代表引數的個數,乙個是argv代表具體字串型別的引數,這是我們...

linux程式的命令列引數

程式執行的時候需要命令列引數,linux中更是這樣,隨便在shell輸入 bin xx help後列舉出來的引數讓你頭暈眼花,可是這些引數是怎麼進入程式的呢,我們知道程式執行的時候一般從main開始,而mian有兩個引數,乙個是 argc代表引數的個數,乙個是ar 代表具體字串型別的引數,這是我們所...

C C 程式的命令列引數

c 程式的main函式有兩個引數 int main int argc,char argv 我以前 大學學習的時候 一直沒有弄清楚這兩個引數的真正目的,一直到做linux c開發的時候,才知道,原來這兩個引數就是用來提供我們在linux的終端上執行某個命令時,攜帶的額外引數,例如 gcc o test...