a.outプログラム実行の開始と終了
5266 ワード
簡単に述べる
gccコンパイルにより実行可能ファイルa.outを生成し、Terminalに./a.outは実行に成功し、所望の結果が表示されます../a.out実行可能プログラムの実行プロセスは、いくつかのステップで要約することができる.
bashの仕事
Terminal(Ubuntuでグラフィックインタフェース形式のコマンドライン、また文字インタフェースコマンドラインconsole-Ctrl+Alt+F 1~6で切り替えることができる)で文字列を入力すると、bashは入力した文字列を解析し、外部実行可能ファイルに属するかbash内蔵コマンドに属するかを解析します.
bash組み込みコマンド
echo、cdなどのコマンドがよく見られますが、内蔵コマンド(内蔵コマンド)は、echoなどの外部ファイルの代わりに同じ機能を実現することができます.1つのコマンドがbash内蔵コマンドに属するかどうかを判断するにはtypeコマンドを使用します.内部コマンド(組み込みコマンド)については、bashは自身の内部コードを直接実行すればよく、迅速で負担がありません.しかし、外部ファイルについては、面倒です.
bash外部実行可能ファイル
外部実行可能ファイルの場合bashはデフォルトで./を入力するとa.outを押して車に戻ると、bashはシステム呼び出しforkのサブプロセスを呼び出し、サブプロセスでa.outを実行し、bashはサブプロセスの実行が終了するまで待機します.もう1つの方法は、forkの新しいサブプロセスを使用せずにbashコマンドラインにexec./と入力する方法です.a.out、リターンを押せばいいのですが、a.outプログラムの実行が完了すると、shellプロセスを置き換えたため、コマンドを実行するteminalも閉じます.
fork -> sys_execve
(sys_execveを呼び出す)いくつかのパラメータのチェックコピーを行った後、do_を呼び出すexecve.
do_execve()
do_execve()関数でファイルが存在するかどうかを確認し、prepareを呼び出します.binprm()関数は、実行可能ファイルのフォーマットを判断することを目的として、ファイルの最初の128バイトの読み取りを完了し、各実行可能ファイルの最初の数バイトが重要であり、特に最初の4バイトは、マジック数字(Magic Number)と呼ばれることが多く、最初の4つのワードセクションの判断によって、ファイルのタイプとフォーマットを決定することができ、例えば、ELF実行可能ファイルの最初の4バイトは(それぞれ0 X 7 f、0 X 45、0 X 4 c、0 X 46であり、最初はASCII文字の中のDEL制御子に対応し、後の3バイトはELFの3文字のASCIIコードである)、shellスクリプトやpythonなどの解釈型言語のスクリプトが実行されている場合、最初の行は通常(16進数内容..),"!/bin/shまたは"#!/usr/bin/python」、このとき前の2バイト「#」と「!構成は魔数を構成し,システムはこの2バイトを判断すると,後続の文字列を解析し,具体的な解釈プログラムの経路を決定する.(関連画像)(なぜ128バイトほど読むのですか?)
search_binary_handler()
search_binary_handler()関数でdo_に基づいてexecve()で読み込まれた128バイトは、魔数(文の不順を招く2つの根拠)に基づいて実行可能ファイルのフォーマットを判断し、実行可能ファイルのフォーマットに一致するマウントプログラムを検索します.Linuxでサポートされている実行可能ファイルフォーマットには、対応するマウントハンドラがあり、ELF実行可能ファイルフォーマットのマウントハンドラはload_elf_binary()shell実行可能スクリプトのマウントプロセッサはload_script().ここではloadに注目しますelf_binary().
load_elf_binary
load_elf_binary()関数は私たちの重点分析対象であり、この関数ではプログラム実行に必要な主な準備作業が完了し、関数の主な仕事は次のいくつかの部分に分けることができます.
> //fs/binfmt_elf.c//ELF実行可能ファイルの各プログラムヘッダテーブルに空間size=loc->elf_を割り当てるex.e_phnum * sizeof(struct elf_phdr); elf_phdata = (struct elf_phdr *) kmalloc(size, GFP_KERNEL); if (!elf_phdata) goto out; //ELF実行可能ファイルを読み込む各プログラムヘッダテーブルretval=kernel_read(bprm->file, loc->elf_ex.e_phoff, (char *) elf_phdata, size); …… //現在のプログラムヘッダテーブルが.interp、読みなさい.interpテーブル対応「segment」の内容、すなわち、動的コネクタのアドレスif p_filesz);....//struct file*interpreter=open_exec(elf_interpreter); …. }
>//ダイナミックリンクアドレスが空でない場合は、ダイナミックコネクタif(elf_interpreter){if(interpreter_type===INTERPRETER_AOUT){elf_entry=load_aout_interp(&loc>interp_ex,interpreter);else{ elf_entry = load_elf_interp(&loc->interp_elf_ex, interpreter, &interp_load_addr); if (BAD_ADDR(elf_entry)){ printk(KERN_ERR “Unable to load interpreter %.128s”, elf_interpreter); force_sig(SIGSEGV, current); retval = -ENOEXEC; goto out_free_dentry; } reloc_func_desc = interp_load_addr; allow_write_access(interpreter); fput(interpreter); kfree(elf_interpreter); //静的リンクの場合、エントリポイントは実行可能ファイルのヘッダのe_に設定されます.entry. } else { elf_entry = loc->elf_ex.e_entry; } …. //エントリポイントstart_の設定thread(regs, elf_entry, bprm->p);
/*******include/asm-x86_64/processor.h******/
//
//#define start_thread(regs,new_rip,new_rsp) do { \
asm volatile("movl %0,%%fs; movl %0,%%es; movl %0,%%ds": :"r" (0)); \
load_gs_index(0); \
// new_rip, elf_entry
(regs)->rip = (new_rip); \
(regs)->rsp = (new_rsp); \
write_pda(oldrsp, (new_rsp)); \
(regs)->cs = __USER_CS; \
(regs)->ss = __USER_DS; \
(regs)->eflags = 0x200; \
set_fs(USER_DS); \
} while(0);
以上の作業が完了したらsys_execve()がカーネル状態からユーザ状態に戻ると、EIPレジスタは直接ELFプログラムのエントリアドレスにジャンプし、新しいプログラムが実行され、ELF実行可能ファイルのロードが終了する.
プログラム運転終了
プログラムの実行が終了すると、forkシステムは戻りを呼び出し、bashプロセスforkのサブプロセスが終了し、プロセスbashに戻り、次のコマンドの入力を待つことを意味する.
参考資料
[1]bash実行命令の各種状況分析[2]プログラマーの自己修養.兪甲子、石凡、潘愛民著