鄭德倫 原創作品轉載請註明出處 《Linux內核分析》MOOC課程
http://mooc.study.163.com/course/USTC-1000029000
一、 配置新的MenuOS環境
在終端進入LinuxKernel目錄,輸入
rm –rf menu
git clone https://github.com/mengning/menu.git
mv test_exec.c test.c
make rootfs
完成新的MenuOS的配置。在此版本的MenuOS中,加入了exec功能。也就是執行execlp庫函數,來創建一個新的進程hello。
二、 追蹤sys_execve的執行過程
打開終端,進入LinuxKernel目錄輸入
qemu –kernel linux-3.18.6/arch/x86/boot/bzImage –initrd rootfs.img –S –s
然後打開另一個終端輸入
gdb
(gdb)file linux-3.18.6/vmlinux
(gdb)target remote:1234
(gdb)c
(gdb)b sys_execve
(gdb)b load_elf_binary
(gdb)b start_thread
(gdb)b do_execve
(gdb)c
我們開始跟蹤執行過程,在qemu中輸入exec,首先進入斷點是sys_execve,在在中端處理程序中,調用了do_execve(),其中getname從用戶空間獲取filename(也就是hello)的路徑,到內核中。
進入do_execve函數體內發現,實際工作是完成argv envp賦值,然後調用do_execve_common
我們進入do_execve_common函數體內
我們看下do_execve_common的源代碼,do_execve_common完成了一個linux_binprm的結構體 bprm的初始化工作:
retval = bprm_mm_init(bprm);初始化了mm_strcut
bprm->argc = count(argv, MAX_ARG_STRINGS);//計算參數個數,直到爲NULL
retval = prepare_binprm(bprm);//把要加載文件的前128 讀入bprm->buf
retval = copy_strings_kernel(1, &bprm->filename, bprm);//copy第一個參數filename
bprm->exec = bprm->p;//參數的起始地址
retval = copy_strings(bprm->envc, envp, bprm);//copy環境變量
retval = copy_strings(bprm->argc, argv, bprm);//copy可執行文件所帶參數:argv[0]:hello argv[1]:hello
retval = exec_binprm(bprm);//執行bprm
int do_execve_common(struct filename *filename,
struct user_arg_ptr argv,
struct user_arg_ptr envp)
{
struct linux_binprm *bprm;
struct file *file;
struct files_struct *displaced;
int retval;
/**省略中間一部分***/
bprm = kzalloc(sizeof(*bprm), GFP_KERNEL);
if (!bprm)
goto out_files;
retval = prepare_bprm_creds(bprm);
if (retval)
goto out_free;
check_unsafe_exec(bprm);
current->in_execve = 1;
file = do_open_exec(filename);
retval = PTR_ERR(file);
if (IS_ERR(file))
goto out_unmark;
sched_exec();
bprm->file = file;
bprm->filename = bprm->interp = filename->name;
retval = bprm_mm_init(bprm);
if (retval)
goto out_unmark;
bprm->argc = count(argv, MAX_ARG_STRINGS);
if ((retval = bprm->argc) < 0)
goto out;
bprm->envc = count(envp, MAX_ARG_STRINGS);
if ((retval = bprm->envc) < 0)
goto out;
retval = prepare_binprm(bprm);
if (retval < 0)
goto out;
retval = copy_strings_kernel(1, &bprm->filename, bprm);
if (retval < 0)
goto out;
bprm->exec = bprm->p;
retval = copy_strings(bprm->envc, envp, bprm);
if (retval < 0)
goto out;
retval = copy_strings(bprm->argc, argv, bprm);
if (retval < 0)
goto out;
retval = exec_binprm(bprm);
if (retval < 0)
goto out;
/***省略中間一部分代碼****/
return retval;
}
然後我們跟蹤到exec_binprm,查看函數代碼,這段代碼將父進程的pid保存,獲取新的pid,然後執行search_binary_hander(bprm),用來遍歷format鏈表,找到合適的處理hello的方式。
static int exec_binprm(struct linux_binprm *bprm)
{
pid_t old_pid, old_vpid;
int ret;
/* Need to fetch pid before load_binary changes it */
old_pid = current->pid;
rcu_read_lock();
old_vpid = task_pid_nr_ns(current, task_active_pid_ns(current->parent));
rcu_read_unlock();
ret = search_binary_handler(bprm);
if (ret >= 0) {
audit_bprm(bprm);
trace_sched_process_exec(current, old_pid, bprm);
ptrace_event(PTRACE_EVENT_EXEC, old_vpid);
proc_exec_connector(current);
}
return ret;
}
我們繼續continue 調試,達到斷點search_bintary_handler
在這裏面有個結構體linux_binfmt *fmt;
static struct linux_binfmt elf_format = {
.module = THIS_MODULE,
.load_binary = load_elf_binary,
.load_shlib = load_elf_library,
.core_dump = elf_core_dump,
.min_coredump = ELF_EXEC_PAGESIZE,
};
裏面的.load_binary字段初始化爲load_elf_binary由此可見load_binary應該是一個函數指針。
我們繼續跟蹤到達load_elf_binary,這個函數完成的二進制文件的裝載和啓動,其中在函數的末尾有start_thread(regs, elf_entry, bprm->p);是二進制的啓動代碼,我們繼續跟蹤到達start_thread
start_thread函數完成pt_reg結構的轉換,從當前進程轉換到新進程,完成進程的切換,從而開始執行execve的進程。
三、 總結
通過上面的調試過程,我們已經比較清楚的瞭解到一個sys_execve系統調用的執行過程,具體的流程圖如下: