shell工作方式
通过shell运行命令执行一个用户进程的方法是,通过fork()创建一个子进程,在子进程中调用execve(pathname, argv, envp)加载新程序,为新进程建立文本段,创建栈、数据段以及堆,在shell进程中执行wait调用等待子进程返回。C程序代码框架大致如下:
点击(此处)折叠或打开#include 《stdio.h》
#include 《stdlib.h》
#include 《unistd.h》
int main(int argc, char * argv[])
{
int pid;
/* fork another process */
pid = fork();
if (pid《0)
{
/* error occurred */
exit(-1);
}
else if (pid==0)
{
execve(pathname, argv, envp);
}
else
{
wait(NULL);
exit(0);
}
}
ELF文件格式
ELF是Executable and Linking Format的缩写,是可执行二进制文件格式和目标文件等格式的相关标准。ELF文件由ELF头、程序头、节头、存储器表格和符合表格构成,以下重点介绍ELF头、程序头和节头。
ELF头
ELF头的内容标识这是一个ELF文件,使用readelf的-h选项可以查看。ELF头的结构如下:
点击(此处)折叠或打开unsigned char e_ident[EI_NIDENT];
ElfN_Half e_type; //类型
ElfN_Half e_machine; //机器
ElfN_Word e_version; //版本
ElfN_Addr e_entry; //入口地址
ElfN_Off e_phoff; //程序头的地址
ElfN_Off e_shoff; //节头的起点
ElfN_Half e_ehsize; //标志
ElfN_Half e_phentsize; //头的大小
ElfN_Half e_phnum; //程序头的大小
ElfN_Half e_shentsize; //程序头数
ElfN_Half e_shnum; // 节头数
ElfN_Half e_shstrndx; // 节名的符号串表格
e_ident保存着ELF的幻数和其他信息,最前面四个字节里有如下幻数:0x7F 0x45 0x4C 0x46,用字符串表示为“\177ELF”;其后的字节如果是32位则为ELFCLASS32,如果是64位则用ELFCLASS64;其后的字节表示endian,little endian用ELFDATA2LSB,big endian用ELFDATA2MSB;在此之后,ELF版本和OS、ABI等信息用一个比特位表示。
e_type表示文件类型,用ET_REL, ET_EXEC, ET_DYN, ET_CORE分别表示可重定位文件、可执行文件、共享目标文件和内核文件。
e_machine表示架构类型,e_version表示ELF版本,e_entry表示ELF中开始执行的虚拟地址,e_ehsize表示ELF头的大小,e_phoff、e_phentsize和e_phnum表示程序头表格的位置和数量。
程序头
程序头表格是由ELF头的e_phoff指定的偏移量和e_phentsize、e_phnum共同确定大小的表格组成。e_phentsize表示表格中程序头的大小,e_phnum表示表格中程序头的大小,e_phnum表示表格中程序头的数量。程序头可用readelf的-l选项查看。程序头结构如下:
点击(此处)折叠或打开ElfN_Word p_type; // 段类型
ElfN_Off p_offset; // 偏移量
ElfN_Addr p_vaddr; // 虚拟地址
ElfN_Addr p_paddr; // 物理地址
ElfN_Addr p_filesz; // 文件大小
ElfN_Addr p_memsz; // 内存大小
ElfN_Addr p_flags; // 标志
ElfN_Addr p_align; // 对齐
类型p_type表示的意义如下:
p_typevalue说明
PT_LOAD
1转载的程序段
PT_DYNAMIC
2动态链接信息
PT_INTERP
3程序解释器
PT_NOTE
4辅助信息
PT_PHDR
5程序头表格本身
PT_TLS
6线程本地存储器
PT_GNU_EH_FRAME
0x64744e550GNU .eh_frame_hdr段
PT_GNU_STACK
0x64744e551
堆栈的可执行性
节头
节头表格是由ELF头的e_shoff指定的偏移量以及e_shentsize、e_shnum共同规定了大小的表格组成。readelf的-S选项可显示节头。节头的构成如下:
点击(此处)折叠或打开ElfN_Word sh_name; //名称
ElfN_Word sh_type; //类型
ElfN_Word sh_flags; //标志
ElfN_Addr sh_addr; //地址
ElfN_Off sh_offset; //偏移
ElfN_Word sh_size; //大小
ElfN_Word sh_link; //链接
ElfN_Word sh_info; //节信息
ElfN_Word sh_addralign; //对齐
ElfN_Word sh_entsize; //节为表格时各个条目的大小
sh_type的类型如下:
sh_type值说明
SHT_PROGBITS1程序数据
SHT_SYMTAB
2符号表格
SHT_STRTAB
3存储器格式
SHT_RELA
4带加数的再配置条目
SHT_HASH
5符号散列数据表格
SHT_DYNAMIC
6动态链接信息
SHT_NOTE
7Notes
SHT_NOBITS
8文件上无数据部分
SHT_REL
9再配置条目
SHT_DYNSYM
11动态链接所使用的符号表格
SHT_INIT_ARRAY
14constructor的排列(.init)
SHT_FINI_ARRAY
15destructor的排列(.fini)
SHT_GNU_verdef0x6ffffffd版本定义节
SHT_GNU_verneed
0x6ffffffe
版本要求节
SHT_GNU_versym
0x6fffffff
版本符号表格
execve系统调用
execve在内核中的系统调用服务例程为sys_execve(), 函数定义在fs/exec.c文件中,相关代码如下:
点击(此处)折叠或打开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);
}
int do_execve(struct filename *filename,
const char __user *const __user *__argv,
const char __user *const __user *__envp)
{
struct user_arg_ptr argv = { .ptr.native = __argv };
struct user_arg_ptr envp = { .ptr.native = __envp };
return do_execve_common(filename, argv, envp);
}
static 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;
if (IS_ERR(filename))
return PTR_ERR(filename);
/*
* We move the actual failure in case of RLIMIT_NPROC excess from
* set*uid() to execve() because too many poorly written programs
* don‘t check setuid() return code. Here we additionally recheck
* whether NPROC limit is still exceeded.
*/
if ((current-》flags & PF_NPROC_EXCEEDED) &&
atomic_read(¤t_user()-》processes) 》 rlimit(RLIMIT_NPROC)) {
retval = -EAGAIN;
goto out_ret;
}
/* We’re below the limit (still or again), so we don‘t want to make
* further execve() calls fail. */
current-》flags &= ~PF_NPROC_EXCEEDED;
retval = unshare_files(&displaced);
if (retval)
goto out_ret;
retval = -ENOMEM;
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;
/* execve succeeded */
current-》fs-》in_exec = 0;
current-》in_execve = 0;
acct_update_integrals(current);
task_numa_free(current);
free_bprm(bprm);
putname(filename);
if (displaced)
put_files_struct(displaced);
return retval;
out:
if (bprm-》mm) {
acct_arg_size(bprm, 0);
mmput(bprm-》mm);
}
out_unmark:
current-》fs-》in_exec = 0;
current-》in_execve = 0;
out_free:
free_bprm(bprm);
out_files:
if (displaced)
reset_files_struct(displaced);
out_ret:
putname(filename);
return retval;
}
函数调用链为sys_execve()-》do_execve()-》do_execve_common()。 其中sys_execve()和do_execve()参数列表中的__user标签表示参数中的变量指向用户空间。
第46行unshare_files()用于解除与父进程共享文件描述符(fork后父进程和子进程共享打开的文件描述符),使用dup_fd()创建新的struct files_struct;
第62行do_open_exec()用于打开可执行文件;
第72行bprm_mm_init()用于初始化bprm数据结构,用于保存可执行文件的上下文;
第76行和第80行分别用于统计参数和环境变量个数;
第84行prepare_binprm()用于文件的inode信息来填充一些必要的变量信息;
第88行、92行及96行分别将程序名、参数和环境变量复制到bprm结构;
第100行exec_binprm()调用search_binary_handler()是核心函数,用于加载可执行程序,依次让formats队列中的成员使用load_binary()函数装入可执行程序,若成功则让可执行程序开始运行,在search_binary_handler()使用struct linux_binfmp来保存处理相应格式的可执行文件的指针,定义如下:
点击(此处)折叠或打开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 */
};
其中函数指针load_binary用于加载新进程,load_shlib用于加载共享库,core_dump用于将当前进程的上下文保存在一个名为core的文件中。
Linux内核允许用户通过调用在include/linux/binfmt.h文件中定义的register_binfmt()和unregister_binfmt()来添加和删除linux_binfmt结构体链表中的元素,以支持用户特定的可执行文件类型。在调用特定的load_binary函数加载一定格式的可执行文件后,程序将返回到sys_execve()中继续执行。该函数在完成最后几步的清理工作后,将会结束处理并返回到用户态中,最后,系统将会将CPU分配给新加载的程序。
评论
查看更多