exec本质就是个替换进程代码段的过程执行了exec,你就是放弃了当前的进程地址空间而使用了新的地址空间来加载exec需要的可执行文件
写一个hello world程序,使用gcc工具编译,并在本地执行,可是在kernel中是怎么执行这个ELF格式的文件呢?
flush_old_exec(bprm)我们看一下这个非常的retval = flush_old_exec(bprm)
int flush_old_exec(struct linux_binprm * bprm)
{undefined
...
retval = exec_mmap(bprm->mm); //这个函数最终实现了替换
...
set_task_comm(current, tcomm);
current->flags &= ~PF_RANDOMIZE;
flush_thread();
current->mm->task_size = TASK_SIZE;
...
current->self_exec_id++;
flush_signal_handlers(current, 0);
flush_old_files(current->files);
...
}
在exec_mmap中有几个重要的操作实现了替换
tsk = current;
old_mm = current->mm;
mm_release(tsk, old_mm);
active_mm = tsk->active_mm;
tsk->mm = mm;
tsk->active_mm = mm;
activate_mm(active_mm, mm);
bash进程会调用fork来创建新进程,然后新进程调用execve执行指定的elf文件
strace ./hello
execve("./hello", ["./hello"], [/* 22 vars */]) = 0
brk(NULL) = 0x1e4d000
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
... ...
brk(NULL) = 0x1e4d000
brk(0x1e6e000) = 0x1e6e000
write(1, "hello world\n", 12hello world
) = 12
exit_group(0) = ?
+++ exited with 0 +++
从strace跟踪的系统调用执行ELF会使用execve这个syscall,r然后load若干共享库,最后输出`hello world`
SYSCALL_DEFINE3(execve,
const char __user *, filename,
const char __user *const __user *, argv,
const char __user *const __user *, envp)
{
// 文件名 参数 环境变量
return do_execve(getname(filename), argv, envp);
}
通过strace 工具我们知道执行可执行文件,通过`execve`,陷入内核态,但不知道在kernel中是怎么执行的,我们通过ftrace工具获取这一调用过程
在`/sys/kernel/debug/tracing`目录下查看可与exec相关的可追踪的函数
root@ubuntu:/sys/kernel/debug/tracing# cat available_filter_functions |grep execv
audit_log_execve_info
do_execveat_common.isra.31
do_execve
do_execveat
SyS_execve
SyS_execveat
compat_SyS_execve
compat_SyS_execveat
我们选择最终`SyS_execve`,然后写一个ftrace的脚本,通过`./ftrace.sh ./hello`可以得到`trace.log`这一文件,里面抓到了执行hello程序具体的细节
#!/bin/bash
d=/sys/kernel/debug/tracing
COMMAND="$@"
PID=$$
echo 0 > "${d}/tracing_on"
echo > "${d}/trace"
echo nop > "${d}/current_tracer"
echo "$PID" > "${d}/set_ftrace_pid"
echo function_graph >"${d}/current_tracer"
echo sys_execve > "${d}/set_graph_function"
# Start tracing.
echo 1 > "${d}/tracing_on"
eval "$COMMAND"
# Stop tracing.
echo 0 > "${d}/tracing_on"
cp /sys/kernel/debug/tracing/trace ./trace.log
linux_binfmt结构体成员如下 ,linux提供了load_binary,load_shlib,core_dump三种方式加载和执行可执行文件。
struct linux_binfmt {
struct list_head lh;
struct module *module;
int (*load_binary)(struct linux_binprm *);
int (*load_shlib)(struct file *);
int (*core_dump)(struct coredump_params *cprm);
unsigned long min_coredump; /* minimal dump size */
};
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,
};
加载的是script,则调用load_script,加载的是elf可执行文件的话就是load_elf_binary
load_binary 有以上几种不同的格式, 为什么Linux可以运行ELF文件?
内核对所支持的每种可执行的程序类型都有个struct linux_binfmt的数据结构,这个结构主要提供在加载可执行文件的时候提供若干参数其定义如下,
指向进程所属的全局执行文件格式结构,共有a.out、script、elf、java等四种。
#define CORENAME_MAX_SIZE 128
/*
* This structure is used to hold the arguments that are used when loading binaries.
*/
struct linux_binprm {
char buf[BINPRM_BUF_SIZE];
#ifdef CONFIG_MMU
struct vm_area_struct *vma;
unsigned long vma_pages;
struct mm_struct *mm;
unsigned long p; /* current top of mem */
unsigned int
cred_prepared:1,/* true if creds already prepared (multiple
* preps happen for interpreters) */
cap_effective:1;/* true if has elevated effective capabilities,
* false if not; except for init which inherits
* its parent's caps anyway */
unsigned int recursion_depth; /* only for search_binary_handler() */
struct file * file;
struct cred *cred; /* new credentials */
int unsafe; /* how unsafe this exec is (mask of LSM_UNSAFE_*) */
unsigned int per_clear; /* bits to clear in current->personality */
int argc, envc;
const char * filename; /* Name of binary as seen by procps */
const char * interp; /* Name of the binary really executed. Most 解析器
of the time same as filename, but could be
different for binfmt_{misc,script} */
unsigned interp_flags;
unsigned interp_data;
unsigned long loader, exec;
};
\
(gdb) p loc->elf_ex
$3 = {e_ident = "\177ELF\001\001\001\000\000\000\000\000\000\000\000", e_type = 2, e_machine = 40, e_version = 1, e_entry = 66636, e_phoff = 52, e_shoff = 578164, e_flags = 83886592,
e_ehsize = 52, e_phentsize = 32, e_phnum = 7, e_shentsize = 40, e_shnum = 30, e_shstrndx = 27}
(gdb) p/x bprm->buf (映像文件的前128个字节)
$4 = {0x7f, 0x45, 0x4c, 0x46, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x0, 0x28, 0x0, 0x1, 0x0, 0x0, 0x0, 0x4c, 0x4, 0x1, 0x0, 0x34, 0x0, 0x0, 0x0, 0x74, 0xd2, 0x8,
0x0, 0x0, 0x2, 0x0, 0x5, 0x34, 0x0, 0x20, 0x0, 0x7, 0x0, 0x28, 0x0, 0x1e, 0x0, 0x1b, 0x0, 0x1, 0x0, 0x0, 0x70, 0x68, 0x5f, 0x7, 0x0, 0x68, 0x5f, 0x8, 0x0, 0x68, 0x5f, 0x8, 0x0, 0x30, 0x6,
0x0, 0x0, 0x30, 0x6, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, 0x0, 0x9c, 0x65, 0x7, 0x0, 0x9c, 0x65, 0x7,
0x0, 0x5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x1, 0x0, 0x0, 0x0, 0x6c, 0x6f, 0x7, 0x0, 0x6c, 0x6f, 0x9, 0x0}
wu@ubuntu:~/runninglinuxkernel_4.0/rlk_lab/rlk_basic/simple_elf$ readelf -h hello
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: ARM
Version: 0x1
Entry point address: 0x1044c
Start of program headers: 52 (bytes into file)
Start of section headers: 578164 (bytes into file)
Flags: 0x5000200, Version5 EABI, soft-float ABI
Size of this header: 52 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 7
Size of section headers: 40 (bytes)
Number of section headers: 30
Section header string table index: 27
大家可以关注我的微信公众号:文章优先更新在微信公众号