Linux系统ELF程序的执行过程

 本文将介绍     linux      程序的执行过程,并以实际问题为切入点简单介绍下ELF程序的加载过程。

 Linux系统ELF程序的执行过程_设计制作_RF/无线

正文】用后态执行

我们知道在linux系统中可以通过诸如"./debug"方式执行一个程序,那么这个程序的执行过程中linux系统都做了什么?

本文以debug程序为例,介绍linux内核是如何一步步将debug进程执行起来的.

1 执行过程:

以sys     te   m()实现为例,它是一种典型的可执行程序运行过程:

[cpp] view pl     ai   n copy

#include

#include

#include

#include

int system(const char * cmdstring)

{

    pi   d_t pid;

int status;

if(cmdstring == NULL){

return (1);

}

if((pid = fork())<0){

status = -1;

}

else if(pid = 0){

execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);

-exit(127); //子进程正常执行则不会执行此语句

}

else{

while(waitpid(pid, &status, 0) < 0){

if(errno != EINTER){

status = -1;

break;

}

}

}

return status;

}

观察上面system实现:

1)system在当前进程中fork创建了一个子进程,并执行execl函数运行可执行文件;

2)execl/execve系列函数执行elf文件;

实际上系统通过execve->do_execve_common函数,将上步创建的子进程,完全替换成了可执行程序.

这个替换过程,其实也就是可执行程序的加载过程,也是本文着重介绍的内容.

3) execve使用实例:

#include

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

[cpp] view plain copy

#include

#include

int main(int arg, char **args)

{

char *argv[]={"ls","-al","/home/", NULL};

char *envp[]={0,NULL};

execve("/bin/ls",argv,envp);

}

正文】内核态执行

linux系统中,可执行程序大多属于ELF文件格式.

本节以实例介绍:execve("/home/debug",NULL,NULL);其中debug程序是elf格式.

当用后执行execve时,系统都做了什么?下面逐层分析:

1 系统调用:execve->do_execve->do_execve_common

[cpp] view plain copy

/* filename为可执行文件:/home/debug;

argv为NULL,表示可行程序不带参数;

envp为NULL,表示没有指定环境变量;  */

int do_execve(const char *filename,const char __user *const __user *__argv,

const char __user *const __user *__envp)

{

struct user_arg_ptr argv = { .ptr.na     ti   ve = __argv };

struct user_arg_ptr envp = { .ptr.na  TI ve = __envp };

return do_execve_common(filename, argv, envp);

}

2 execve->do_execve->do_execve_common()注意此时当前进程是上文中创建的子进程。

bprm_mm_init()完成进程地址空间vma(包括栈)的初始化.

[cpp] view plain copy

/*

* sys_execve() executes a new prog     ram   .

*/

sta  TI c int do_execve_common(const char *filename,

struct user_arg_ptr argv,

struct user_arg_ptr envp)

{

/*注意linux_binprm是核心数据结构,它保存了可执行文件的信息;*/

struct linux_binprm *bprm;

struct file *file;

struct files_struct *displaced;

bool clear_in_exec;

int retval;

const struct cred *cred = current_cred();

/*

* We move the actual failure in case of RLI     MI   T_NPROC excess f     rom  

* set*uid() to execve() because too many poorly written progr     ams  

* don't check setuid() return code.  Here we addi  TI onally recheck

* whether NPROC limit is s  TI ll exceeded.

*/

if ((current->flags & PF_NPROC_EXCEEDED) &&

atomic_read(&cred->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;

/*申请linux_binprm描述符,用以保存ELF可执行文件信息*/

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

if (!bprm)

goto out_files;

/*生成bprm->cred即准备可执行程序运行的用户和组信息,主要根据当前进程的task->cred信息生成*/

retval = prepare_bprm_creds(bprm);

if (retval)

goto out_free;

retval = check_uns     afe   _exec(bprm);

if (retval < 0)

goto out_free;

clear_in_exec = retval;

current->in_execve = 1;

/*

1:打开可执行程序 /home/debug;

*/

file = open_exec(filename);

retval = PTR_ERR(file);

if (IS_ERR(file))

goto out_unmark;

sched_exec();

/*bprm->file为/home/debug文件描述符*/

bprm->file = file;

/*可执行文件名保存到bprm->filename中*/

bprm->filename = filename;

bprm->interp = filename;

/*生成bprm->mm,即准备可执行程序的mm_struct信息,

注意此时生成栈空间信息,不过后面会对栈空间再次调整

注意此处的bprm->mm不是当前进程的,是bprm_mm_init申请的

以后用作/home/debug进程的mm_struct;

*/

retval = bprm_mm_init(bprm);

if (retval)

goto out_file;

/*可执行文件参数个数,对/home/debug来说argc=0,因为指定参数为NULL*/

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

if ((retval = bprm->argc) < 0)

goto out;

/*envc=0参加bprm->argc*/

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

if ((retval = bprm->envc) < 0)

goto out;

/*

elf头保存到bprm->buf中;

实现方式: kernel_read(bprm->file, 0, bprm->buf, BINPRM_BUF_SIZE);//128bytes

*/

retval = prepare_binprm(bprm);

if (retval < 0)

goto out;

retval = copy_strings_kernel(1, &bprm->filename, bprm);

if (retval < 0)

goto out;

/*保存execve中指定的环境变量到linux_binprm结构中*/

bprm->exec = bprm->p;

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

if (retval < 0)

goto out;

/*保存execve中指定的可执行程序参数到linux_binprm结构中*/

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

if (retval < 0)

goto out;

/*

该函数负责从flash上加载ELF文件:并将当前子进程信息替换为可执行文件中读取的信息.

elf_format->load_binary=load_elf_binary->arch_setup_additional_pages : register_binfmt中注册的elf_format

->install_special_mapping->insert_vm_struct

*/

retval = search_binary_handler(bprm);

if (retval < 0)

goto out;

/* execve succeeded */

current->fs->in_exec = 0;

current->in_execve = 0;

acct_update_integrals(current);

free_bprm(bprm);

if (displaced)

put_files_struct(displaced);

return retval;

out:

if (bprm->mm) {

acct_arg_size(bprm, 0);

mmput(bprm->mm);

}

out_file:

if (bprm->file) {

allow_write_access(bprm->file);

fput(bprm->file);

}

out_unmark:

if (clear_in_exec)

current->fs->in_exec = 0;

current->in_execve = 0;

out_free:

free_bprm(bprm);

out_files:

if (displaced)

reset_files_struct(displaced);

out_ret:

return retval;

}

2.1 ELF头读取过程:do_execve_common()->prepare_binprm()

[cpp] view plain copy

int prepare_binprm(struct linux_binprm *bprm)

{

umode_t mode;

struct inode * inode = file_inode(bprm->file);

int retval;

mode = inode->i_mode;

if (bprm->file->f_op == NULL)

return -EACCES;

/* clear any previous set[ug]id data from a previous binary */

bprm->cred->euid = current_euid();

bprm->cred->egid = current_egid();

if (!(bprm->file->f_path.mnt->mnt_flags & MNT_NOSUID) &&

!current->no_new_privs &&

kuid_has_mapping(bprm->cred->user_ns, inode->i_uid) &&

kgid_has_mapping(bprm->cred->user_ns, inode->i_gid)) {

/* Set-uid? */

if (mode & S_ISUID) {

bprm->per_clear |= PER_CLEAR_ON_SETID;

bprm->cred->euid = inode->i_uid;

}

/* Set-gid? */

/*

* If setgid is set but no group execute bit then this

* is a     can   didate for mandatory locking, not a setgid

* executable.

*/

if ((mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP)) {

bprm->per_clear |= PER_CLEAR_ON_SETID;

bprm->cred->egid = inode->i_gid;

}

}

/* fill in binprm security blob */

retval = security_bprm_set_creds(bprm);

if (retval)

return retval;

bprm->cred_prepared = 1;

memset(bprm->buf, 0, BINPRM_BUF_SIZE);

/*

elf头保存到bprm->buf中

*/

return kernel_read(bprm->file, 0, bprm->buf, BINPRM_BUF_SIZE);

}

[ELF文件加载]

ELF文件格式:https://baike.baidu.com/item/ELF/7120560?fr=aladdin

3.1文件头(Elf header) :

Elf头在程序的开始部位,作为引路表描述整个ELF的文件结构,其信息大致分为四部分:一是系统相关信息,二是目标文件类型,三是加载相关信息,四是链接相关信息。

其中系统相关信息包括elf文件魔数(标识elf文件),平台位数,数据编码方式,elf头部版本,硬件平台e_machine,目标文件版本 e_ve     rs   ion,处理器特定标志e_ftags:这些信息的引入极大增强了elf文件的可移植性,使交叉编译成为可能。目标文件类型用e_type的值表示,可重定位文件为1,可执行文件为2,共享文件为3;加载相关信息有:程序进入点e_entry.程序头表偏移量e_phoff,elf头部长度 e_ehsize,程序头表中一个条目的长度e_phentsize,程序头表条目数目e_phnum;链接相关信息有:节头表偏移量e_shoff,节头表中一个条目的长度e_shentsize,节头表条目个数e_shnum ,节头表     字符   索引e shstmdx。可使用命令"readelf -h filename"来察看文件头的内容。

文件头的数据结构如下:

[cpp] view plain copy

typedef struct elf32_hdr{

unsigned char e_ident[EI_NIDENT];

Elf32_Half e_type;//目标文件类型

Elf32_Half e_machine;//硬件平台

Elf32_Word e_version;//elf头部版本

Elf32_Addr e_entry;//程序进入点

Elf32_Off e_phoff;//程序头表偏移量

Elf32_Off e_shoff;//节头表偏移量

Elf32_Word e_flags;/处理器特定标志

Elf32_Half e_ehsize;//elf头部长度

Elf32_Half e_phentsize;//程序头表中一个条目的长度

Elf32_Half e_phnum;//程序头表条目数目

Elf32_Half e_shentsize;//节头表中一个条目的长度

Elf32_Half e_shnum;//节头表条目个数

Elf32_Half e_shstrmdx;//节头表字符索引

}Elf32_Ehdr;

程序头表(program header table)

程序头表告诉系统如何建立一个进程映像.它是从加载执行的角度来看待elf文件.从它的角度看.elf文件被分成许多段,elf文件中的代码、链接信息和注释都以段的形式存放。每个段都在程序头表中有一个表项描述,包含以下属性:段的类型,段的驻留位置相对于文件开始处的偏移,段在内存中的首字节地址,段的物理地址,段在文件映像中的字节数.段在内存映像中的字节数,段在内存和文件中的对齐     标记   。可用"readelf -l filename"察看程序头表中的内容。程序头表的结构如下:

[cpp] view plain copy

typedef struct elf32_phdr{

Elf32_Word p_type; //段的类型

Elf32_Off p_offset; //段的位置相对于文件开始处的偏移

Elf32_Addr p_vaddr; //段在内存中的首字节地址

Elf32_Addr p_     pad   dr;//段的物理地址

Elf32_Word p_filesz;//段在文件映像中的字节数

Elf32_Word p_memsz;//段在内存映像中的字节数

Elf32_Word p_flags;//段的标记

Elf32_Word p_align;,/段在内存中的对齐标记

)Elf32_Phdr;

节头表(section header table)

节头表描述程序节,为编译器和链接器服务。它把elf文件分成了许多节.每个节保存着用于不同目的的数据.这些数据可能被前面的程序头重复使用,完成一次任务所需的信息往往被分散到不同的节里。由于节中数据的用途不同,节被分成不同的类型,每种类型的节都有自己组织数据的方式。每一个节在节头表中都有一个表项描述该节的属性,节的属性包括小节名在字符表中的索引,类型,属性,运行时的虚拟地址,文件偏移,以字节为单位的大小,小节的对齐等信息,可使用"readelf -S filename"来察看节头表的内容。节头表的结构如下:

[cpp] view plain copy

typedef struct{

Elf32_Word sh_name;//小节名在字符表中的索引

E1t32_Word sh_type;//小节的类型

Elf32_Word sh_flags;//小节属性

Elf32_Addr sh_addr; //小节在运行时的虚拟地址

Elf32_Off sh_offset;//小节的文件偏移

Elf32_Word sh_size;//小节的大小.以字节为单位

Elf32_Word sh_link;//链接的另外一小节的索引

Elf32 Word sh_info;//附加的小节信息

Elf32 Word sh_addralign;//小节的对齐

Elf32 Word sh_entsize; //一些sections保存着一张固定大小入口的表。就像符号表

}Elf32_Shdr;

3.2 ELF文件加载的的实现代码:

代码流程: do_execve_common()->search_binary_handler->load_binary=load_elf_binary()

[cpp] view plain copy

static int load_elf_binary(struct linux_binprm *bprm)

{

struct file *interpreter = NULL; /* to shut gcc up */

unsigned long load_addr = 0, load_bias = 0;

int load_addr_set = 0;

char * elf_interpreter = NULL;

unsigned long error;

struct elf_phdr *elf_ppnt, *elf_phdata;

unsigned long elf_bss, elf_brk;

int retval, i;

unsigned int size;

unsigned long elf_entry;

unsigned long interp_load_addr = 0;

unsigned long start_code, end_code, start_data, end_data;

unsigned long reloc_func_desc __maybe_unused = 0;

int executable_stack = EXSTACK_DEFAULT;

unsigned long def_flags = 0;

struct pt_regs *regs = current_pt_regs();

//Elf32_Ehdr

struct {

struct elfhdr elf_ex;

struct elfhdr interp_elf_ex;

} *loc;

loc = kmalloc(sizeof(*loc), GFP_KERNEL);

if (!loc) {

retval = -ENOMEM;

goto out_ret;

}

/*

进程的ELF头保存在此

*/

/* Get the exec-header */

loc->elf_ex = *((struct elfhdr *)bprm->buf);

retval = -ENOEXEC;

/* First of all, some simple consistency checks */

if (m     emc   mp(loc->elf_ex.e_ident, ELFMAG, SELFMAG) != 0)

goto out;

if (loc->elf_ex.e_type != ET_EXEC && loc->elf_ex.e_type != ET_DYN)

goto out;

if (!elf_check_arch(&loc->elf_ex))

goto out;

if (!bprm->file->f_op || !bprm->file->f_op->mmap)

goto out;

/* Now read in all of the header information */

if (loc->elf_ex.e_phentsize != sizeof(struct elf_phdr))

goto out;

if (loc->elf_ex.e_phnum < 1 ||

loc->elf_ex.e_phnum > 65536U / sizeof(struct elf_phdr))

goto out;

size = loc->elf_ex.e_phnum * sizeof(struct elf_phdr);

retval = -ENOMEM;

elf_phdata = kmalloc(size, GFP_KERNEL);

if (!elf_phdata)

goto out;

/*

保存所有程序段到elf_phdata;注意此处elf_phdr与elfhdr的区别

1  elf_phdr如下:程序头

Program Headers:

Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align

EXIDX          0x000000 0x00000000 0x00000000 0x00000 0x00000 R   0x4

PHDR           0x000034 0x00008034 0x00008034 0x00120 0x00120 R E 0x4

INTERP         0x000154 0x00008154 0x00008154 0x00019 0x00019 R   0x1

[Requesting program interpreter: /lib/ld-linux-     arm   hf.so.3]

LOAD           0x000000 0x00008000 0x00008000 0xb22914 0xb22914 R E 0x8000

LOAD           0xb22914 0x00b32914 0x00b32914 0x16b4a0 0x3a22d0 RW  0x8000

DYNAMIC        0xb25df8 0x00b35df8 0x00b35df8 0x00178 0x00178 RW  0x4

NOTE           0x000170 0x00008170 0x00008170 0x00044 0x00044 R   0x4

TLS            0xb22914 0x00b32914 0x00b32914 0x00000 0x00004 R   0x4

GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RWE 0x4

2  elfhder如下:elf头

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:               0x2fa31

Start of program headers:          52 (bytes into file)

Start of section headers:          13164260 (bytes into file)

Flags:                             0x5000402, has entry point, Version5 EABI,

Size of this header:               52 (bytes)

Size of program headers:           32 (bytes)

Number of program headers:         9

Size of section headers:           40 (bytes)

Number of section headers:         28

Section header string table index: 27

*/

/* 程序段存到elf_phdata */

retval = kernel_read(bprm->file, loc->elf_ex.e_phoff,

(char *)elf_phdata, size);

if (retval != size) {

if (retval >= 0)

retval = -EIO;

goto out_free_ph;

}

elf_ppnt = elf_phdata;

elf_bss = 0;

elf_brk = 0;

start_code = ~0UL;

end_code = 0;

start_data = 0;

end_data = 0;

/*遍历程序段,每个段32字节描述*/

for (i = 0; i < loc->elf_ex.e_phnum; i++) {

if (elf_ppnt->p_type == PT_INTERP) {

/* This is the program interpreter used for

* shared libraries - for now assume that this

* is an a.out format binary

*/

retval = -ENOEXEC;

if (elf_ppnt->p_filesz > PATH_MAX ||

elf_ppnt->p_filesz < 2)

goto out_free_ph;

retval = -ENOMEM;

elf_interpreter = kmalloc(elf_ppnt->p_filesz,

GFP_KERNEL);

if (!elf_interpreter)

goto out_free_ph;

retval = kernel_read(bprm->file, elf_ppnt->p_offset,

elf_interpreter,

elf_ppnt->p_filesz);

if (retval != elf_ppnt->p_filesz) {

if (retval >= 0)

retval = -EIO;

goto out_free_interp;

}

/* make sure path is NULL terminated */

retval = -ENOEXEC;

if (elf_interpreter[elf_ppnt->p_filesz - 1] != '\0')

goto out_free_interp;

/*elf_interpreter:/lib/ld-linux-armhf.so.3;bprm->filename:/bin/echo 见上面注释*/

interpreter = open_exec(elf_interpreter);

retval = PTR_ERR(interpreter);

if (IS_ERR(interpreter))

goto out_free_interp;

/*

* If the binary is not readable then enforce

* mm->dumpable = 0 regardless of the interpreter's

* permissions.

*/

would_dump(bprm, interpreter);

retval = kernel_read(interpreter, 0, bprm->buf,

BINPRM_BUF_SIZE);

if (retval != BINPRM_BUF_SIZE) {

if (retval >= 0)

retval = -EIO;

goto out_free_dentry;

}

/* Get the exec headers */

loc->interp_elf_ex = *((struct elfhdr *)bprm->buf);

break;

}

elf_ppnt++;

}

elf_ppnt = elf_phdata;

for (i = 0; i < loc->elf_ex.e_phnum; i++, elf_ppnt++)

if (elf_ppnt->p_type == PT_GNU_STACK) {

if (elf_ppnt->p_flags & PF_X)

executable_stack = EXSTACK_ENABLE_X;

else

executable_stack = EXSTACK_DISABLE_X;

break;

}

/* Some simple consistency checks for the interpreter */

if (elf_interpreter) {

retval = -ELIBBAD;

/* Not an ELF interpreter */

if (memcmp(loc->interp_elf_ex.e_ident, ELFMAG, SELFMAG) != 0)

goto out_free_dentry;

/* Verify the interpreter has a valid arch */

if (!elf_check_arch(&loc->interp_elf_ex))

goto out_free_dentry;

}

/* Flush all traces of the currently running executable */

retval = flush_old_exec(bprm);

if (retval)

goto out_free_dentry;

/* OK, This is the point of no return */

current->mm->def_flags = def_flags;

/* Do this immediately, since STACK_TOP as used in setup_arg_pages

may depend on the personality.  */

SET_PERSONALITY(loc->elf_ex);

//executable_stack = EXSTACK_DISABLE_X;

if (elf_read_implies_exec(loc->elf_ex, executable_stack))

current->personality |= READ_IMPLIES_EXEC;

if (!(current->personality & ADDR_NO_RANDOMIZE) && randomize_va_space)

current->flags |= PF_RANDOMIZE;

/*

current切换为bprm->filename,bprm->tcomm为进程名

*/

setup_new_exec(bprm);

/* Do this so that we can load the interpreter, if need be.  We will

change some of these later */

current->mm->free_area_cache = current->mm->mmap_base;

current->mm->cached_hole_size = 0;

//最终指定进程栈对应的vma

retval = setup_arg_pages(bprm, randomize_stack_top(STACK_TOP),

executable_stack);

if (retval < 0) {

send_sig(SIGKILL, current, 0);

goto out_free_dentry;

}

current->mm->start_stack = bprm->p;

/* Now we do a little grungy work by mmapping the ELF image into

the correct location in memory. */

for(i = 0, elf_ppnt = elf_phdata;

i < loc->elf_ex.e_phnum; i++, elf_ppnt++) {

int elf_prot = 0, elf_flags;

unsigned long k, vaddr;

#ifndef gSysDebugInfoExec

/*

Program Headers:

Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align

EXIDX          0x000000 0x00000000 0x00000000 0x00000 0x00000 R   0x4

PHDR           0x000034 0x00008034 0x00008034 0x00120 0x00120 R E 0x4

INTERP         0x000154 0x00008154 0x00008154 0x00019 0x00019 R   0x1

[Requesting program interpreter: /lib/ld-linux-armhf.so.3]

LOAD           0x000000 0x00008000 0x00008000 0xb22914 0xb22914 R E 0x8000

LOAD           0xb22914 0x00b32914 0x00b32914 0x16b4a0 0x3a22d0 RW  0x8000

DYNAMIC        0xb25df8 0x00b35df8 0x00b35df8 0x00178 0x00178 RW  0x4

NOTE           0x000170 0x00008170 0x00008170 0x00044 0x00044 R   0x4

TLS            0xb22914 0x00b32914 0x00b32914 0x00000 0x00004 R   0x4

GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RWE 0x4

*/

/* 此处可以打印出/usr/bin/snmp进程的所有程序段

也可以通过readelf 命令读出program header

*/

#endif

/*

program header中LOAD表示的就是p_type

p_type为PT_LOAD的段需要加载进内存

*/

if (elf_ppnt->p_type != PT_LOAD)

continue;

if (unlikely (elf_brk > elf_bss)) {

unsigned long nbyte;

/* There was a PT_LOAD segment with p_memsz > p_filesz

before this one. Map anonymous pages, if needed,

and clear the area.  */

retval = set_brk(elf_bss + load_bias,

elf_brk + load_bias);

if (retval) {

send_sig(SIGKILL, current, 0);

goto out_free_dentry;

}

nbyte = ELF_PAGEOFFSET(elf_bss);

if (nbyte) {

nbyte = ELF_MIN_ALIGN - nbyte;

if (nbyte > elf_brk - elf_bss)

nbyte = elf_brk - elf_bss;

if (clear_user((void __user *)elf_bss +

load_bias, nbyte)) {

/*

* This bss-zeroing can fail if the ELF

* file specifies odd protections. So

* we don't check the return value

*/

}

}

}

if (elf_ppnt->p_flags & PF_R)

elf_prot |= PROT_READ;

if (elf_ppnt->p_flags & PF_W)

elf_prot |= PROT_WRITE;

if (elf_ppnt->p_flags & PF_X)

elf_prot |= PROT_EXEC;

elf_flags = MAP_PRIVATE | MAP_DENYWRITE | MAP_EXECUTABLE;

vaddr = elf_ppnt->p_vaddr;

if (loc->elf_ex.e_type == ET_EXEC || load_addr_set) {

elf_flags |= MAP_FIXED;

} else if (loc->elf_ex.e_type == ET_DYN) {

/* Try and get dynamic programs out of the way of the

* default mmap base, as well as whatever program they

* might try to exec.  This is because the brk will

* follow the loader, and is not movable.  */

#ifdef CONFIG_ARCH_BINFMT_ELF_RANDOMIZE_PIE

/* Memory randomization might have been switched off

* in runtime     via   sysctl or explicit setting of

* personality flags.

* If that is the case, retain the original non-zero

* load_bias value in order to establish proper

* non-randomized mappings.

*/

if (current->flags & PF_RANDOMIZE)

load_bias = 0;

else

load_bias = ELF_PAGESTART(ELF_ET_DYN_BASE - vaddr);

#else

load_bias = ELF_PAGESTART(ELF_ET_DYN_BASE - vaddr);

#endif

}

/*

该函数增加vma;增加/proc/smaps的一个段

*/

error = elf_map(bprm->file, load_bias + vaddr, elf_ppnt, elf_prot, elf_flags, 0);

if (BAD_ADDR(error)) {

send_sig(SIGKILL, current, 0);

retval = IS_ERR((void *)error) ?

PTR_ERR((void*)error) : -EINVAL;

goto out_free_dentry;

}

if (!load_addr_set) {

load_addr_set = 1;

load_addr = (elf_ppnt->p_vaddr - elf_ppnt->p_offset);

if (loc->elf_ex.e_type == ET_DYN) {

load_bias += error -

ELF_PAGESTART(load_bias + vaddr);

load_addr += load_bias;

reloc_func_desc = load_bias;

}

}

k = elf_ppnt->p_vaddr;

if (k < start_code)

start_code = k;

if (start_data < k)

start_data = k;

/*

* Check to see if the section's size will overflow the

* allowed task size. Note that p_filesz must always be

* <= p_memsz so it is only necessary to check p_memsz.

*/

if (BAD_ADDR(k) || elf_ppnt->p_filesz > elf_ppnt->p_memsz ||

elf_ppnt->p_memsz > TASK_SIZE ||

TASK_SIZE - elf_ppnt->p_memsz < k) {

/* set_brk can never work. Avoid overflows. */

send_sig(SIGKILL, current, 0);

retval = -EINVAL;

goto out_free_dentry;

}

/*

代码段:

i=3:type=0x1;offset=0x0;vaddr=0x8000;paddr=0x8000;

filesz=0xba81e0;memsz=0xba81e0;flags=0x5;align=0x8000

数据段+程序段:

i=4:type=0x1;offset=0xba81e0;vaddr=0xbb81e0;paddr=0xbb81e0;

filesz=0x7799a4;memsz=0x48f922c;flags=0x6;align=0x8000

//elf_bss=0x1331b84,elf_brk=0x54b140c

*/

k = elf_ppnt->p_vaddr + elf_ppnt->p_filesz;

if (k > elf_bss)

elf_bss = k;

if ((elf_ppnt->p_flags & PF_X) && end_code < k)

end_code = k;

if (end_data < k)

end_data = k;

k = elf_ppnt->p_vaddr + elf_ppnt->p_memsz;

if (k > elf_brk)

elf_brk = k;

}

loc->elf_ex.e_entry += load_bias;

elf_bss += load_bias;

elf_brk += load_bias;

start_code += load_bias;

end_code += load_bias;

start_data += load_bias;

end_data += load_bias;

#ifndef gSysDebugInfoExec

/*

此时已经有3个vma;

elf_bss=0x105b0,elf_brk=0x105b8;bias=0x0

start_code=0x8000,end_code=0x8490;start_data=0x10490;end_data=0x105b0

[00008000-00009000] ,0000018f 00000875 --代码段

[00010000-00011000] ,0000038f 00100873 --数据段

[7ecbb000-7ecdd000] ,0000038f 00100173 --进程的栈

*/

#endif

/* Calling set_brk effectively mmaps the pages that we need

* for the bss and break sections.  We must do this before

* mapping in the interpreter, to make sure it doesn't wind

* up getting placed where the bss needs to go.

*/

/*

1 在此为bss段申请虚拟地址空间,注意此处的地址空间

为用户态进程的虚拟地址空间vm_brk,类似于malloc过程。

如果申请的虚拟地址空间即bss段大小 大于系统空闲的物理内存

则有可能申请失败,可以通过 echo 1 > /proc/sys/vm/overcommit_memory

去掉对内存大小的检测来规避失败的风险。

2 并未真正分配物理内存

3 set_brk后vma没有变化因为elf_bss,elf_brk在数据段区间

elf_bss=0x105b0,elf_brk=0x105b8;bias=0x0

start_code=0x8000,end_code=0x8490;start_data=0x10490;end_data=0x105b0

[00008000-00009000] ,0000018f 00000875 --代码段

[00010000-00011000] ,0000038f 00100873 --数据段

[7ecbb000-7ecdd000] ,0000038f 00100173 --进程的栈

*/

retval = set_brk(elf_bss, elf_brk);

if (retval) {

send_sig(SIGKILL, current, 0);

goto out_free_dentry;

}

if (likely(elf_bss != elf_brk) && unlikely(padzero(elf_bss))) {

send_sig(SIGSEGV, current, 0);

retval = -EFAULT; /* Nobody gets to see this, but.. */

goto out_free_dentry;

}

if (elf_interpreter) {

unsigned long interp_map_addr = 0;

elf_entry = load_elf_interp(&loc->interp_elf_ex,

interpreter,

&interp_map_addr,

load_bias);

if (!IS_ERR((void *)elf_entry)) {

/*

* load_elf_interp() returns relocation

* adjustment

*/

interp_load_addr = elf_entry;

elf_entry += loc->interp_elf_ex.e_entry;

}

if (BAD_ADDR(elf_entry)) {

force_sig(SIGSEGV, current);

retval = IS_ERR((void *)elf_entry) ?

(int)elf_entry : -EINVAL;

goto out_free_dentry;

}

reloc_func_desc = interp_load_addr;

allow_write_access(interpreter);

fput(interpreter);

kfree(elf_interpreter);

} else {

elf_entry = loc->elf_ex.e_entry;

if (BAD_ADDR(elf_entry)) {

force_sig(SIGSEGV, current);

retval = -EINVAL;

goto out_free_dentry;

}

}

kfree(elf_phdata);

set_binfmt(&elf_format);

#ifdef ARCH_HAS_SETUP_ADDITIONAL_PAGES

retval = arch_setup_additional_pages(bprm, !!elf_interpreter);

if (retval < 0) {

send_sig(SIGKILL, current, 0);

goto out;

}

#endif /* ARCH_HAS_SETUP_ADDITIONAL_PAGES */

install_exec_creds(bprm);

retval = create_elf_tables(bprm, &loc->elf_ex,

load_addr, interp_load_addr);

if (retval < 0) {

send_sig(SIGKILL, current, 0);

goto out;

}

/* N.B. passed_fileno might not be initialized? */

current->mm->end_code = end_code;

current->mm->start_code = start_code;

current->mm->start_data = start_data;

current->mm->end_data = end_data;

current->mm->start_stack = bprm->p;

#ifdef arch_randomize_brk

if ((current->flags & PF_RANDOMIZE) && (randomize_va_space > 1)) {

current->mm->brk = current->mm->start_brk =

arch_randomize_brk(current->mm);

#ifdef CONFIG_COMPAT_BRK

current->brk_randomized = 1;

#endif

}

#endif

if (current->personality & MMAP_PAGE_ZERO) {

/* Why this, you ask???  Well S     Vr   4 maps page 0 as read-only,

and some applications "depend" upon this behavior.

Since we do not have the power to     recom   pile these, we

emulate the SVr4 behavior. Sigh. */

error = vm_mmap(NULL, 0, PAGE_SIZE, PROT_READ | PROT_EXEC,

MAP_FIXED | MAP_PRIVATE, 0);

}

#ifdef ELF_PLAT_INIT

/*

* The ABI may specify that certain registers be set up in special

* ways (on i386 %edx is the address of a DT_FINI function, for

* example.  In addition, it may also specify (eg, PowerPC64 ELF)

* that the e_entry field is the address of the function descriptor

* for the startup routine, rather than the address of the startup

* routine itself.  This macro performs whatever initialization to

* the regs structure is required as well as any relocations to the

* function descriptor entries when executing dynamically links apps.

*/

ELF_PLAT_INIT(regs, reloc_func_desc);

#endif

/* 可执行程序从elf_entry开始运行,exec返回时pc=elf_entry出栈 */

start_thread(regs, elf_entry, bprm->p);

retval = 0;

out:

kfree(loc);

out_ret:

return retval;

/* error cleanup */

out_free_dentry:

allow_write_access(interpreter);

if (interpreter)

fput(interpreter);

out_free_interp:

kfree(elf_interpreter);

out_free_ph:

kfree(elf_phdata);

goto out;

}

总结:

此处要重点区分理解 ELF header和programheader的概念。

1>ELF头描述整个程序的信息。

Praogramheader:每个程序段(比如代码段、bss段、数据段等)都有一个这样的头部信息,用来描述这个程序段在文件中的大小,位置 以及放到内存上的大小和位置信息。

程序段的头部信息,保存在文件的e_phoff处,且程序段个数为e_phnum个,如例子中为9个;

2>加载可执行的elf文件。do_execve_common->search_binary_handler

/*

load elf load_binary=load_elf_binary->arch_setup_additional_pages

->install_special_mapping->insert_vm_struct插入虚拟内存区,即进程地址空间.

*/

search_binary_handler(bprm)->(*fn)(struct linux_binprm *) = fmt->load_binary;

3> 加载程序program段,load_elf_binary:

1) setup_new_exec(bprm);切换当前进程为bprm->filename程序。

->   __set_task_comm(current,kbasename(bprm->filename), true);

设置进程名称、current信息,以便切换时current即为bprm->filename程序。

注意此时当前进程current被替换掉了。

2) elf_map函数增加vma;增加/proc/smaps的一个段

error = elf_map(bprm->file, load_bias +vaddr, elf_ppnt, elf_prot, elf_flags, 0);

3) 系统在load_elf_binary获取程序段头部信息,并进行校验。

4> creds设置:

1) prepare_exec_creds会准备bprm->cred,日后install_exec_creds设置给当前进程。

2)setup_new_exec在install_exec_creds之前会比较bprm->cred,current->cred等

3)install_exec_creds(bprm);中安装bprm->cred到当前进程的creds

之后bprm->cred = NULL;

在install_exec_creds中要比较current->cred和current->real_cred,

可以考虑cred与real_cred设置成相同。

开放平台的方案是在install_exec_creds->security_bprm_committing_creds(bprm);

阶段将用户id和组id改变,之前阶段cred和real_cred都是0.

实例】

举例:一个进程的ELF header 和program header和section header

ps:可执行文件和动态库各自分别有自己的头部信息;

#readelf –a sonia > sonia

ELF Header:

[cpp] view plain copy

Magic:   7f 45 4c 46 01 01 01 0000 00 00 00 00 00 00 00

Class:                            ELF32

Data:                             2's complement, little endian

Version:                          1 (current)

OS/ABI:                           UNIX - System V

ABIVersion:                       0

Type:                             EXEC (Executable file)

Machine:                          ARM

Version:                          0x1

Entry point address:              0x32cfd

Start of program headers:         52 (bytes into file)

Start of section headers:         20061364 (bytes into file)

Flags:                            0x5000402, has entry point, Version5 EABI,

Size of this header:              52 (bytes)

Size of program headers:          32 (bytes)

Number of program headers:        9

Size of section headers:          40 (bytes)

Number of section headers:        28

Section header string table index: 27

Section Headers: [25]bss段即未初始化全局变量和静态变量保存地;查找对应代码段和bss段地址需要参考这个头信息;

[cpp] view plain copy

[Nr] Name              Type            Addr     Off   Size   ES Flg Lk Inf Al

[0]                   NULL            00000000 000000 000000 00      0  0  0

[1] .interp           PROGBITS        00008154 000154 000019 00   A 0   0  1

[2] .note.ABI-tag     NOTE            00008170 000170 000020 00   A 0   0  4

[3] .note.gnu.build-i NOTE           00008190 000190 000024 00   A 0   0  4

[4] .hash             HASH            000081b4 0001b4 00245c 04   A 5   0  4

[5] .dynsym           DYNSYM          0000a610 002610 0050e0 10   A 6   1  4

[6] .dynstr           STRTAB          0000f6f0 0076f0 006ea4 00   A 0   0  1

[7] .gnu.version      VERSYM          00016594 00e594 000a1c 02   A 5   0  2

[8] .gnu.version_r    VERNEED         00016fb0 00efb0 000180 00   A 6   8  4

[9] .rel.dyn          REL             00017130 00f130 0001a0 08   A 5   0  4

[10] .rel.plt          REL             000172d0 00f2d0 0015e0 08   A 5  12  4

[11] .init            PROGBITS        000188b0 0108b000000c 00  AX  0  0  4

[12] .plt             PROGBITS        000188bc 0108bc002300 04  AX  0  0  4

[13] .text             PROGBITS        0001ac00 012c00 822c18 00  AX 0   0 256

[14] .fini            PROGBITS        0083d818 835818000008 00  AX  0  0  4

[15] .rodata          PROGBITS        0083d820 8358202b36d8 00   A  0  0  8

[16] .eh_frame        PROGBITS        00bb01dc ba81dc 000004 00   A 0   0  4

[17] .tbss             NOBITS          00bb81e0 ba81e0 000004 00 WAT  0  0  4

[18] .init_array      INIT_ARRAY      00bb81e0 ba81e0000b0c 00  WA  0  0  4

[19] .fini_array      FINI_ARRAY      00bb8cec ba8cec000004 00  WA  0  0  4

[20] .jcr             PROGBITS        00bb8cf0 ba8cf0000004 00  WA  0  0  4

[21] .data.rel.ro     PROGBITS        00bb8cf8 ba8cf8002a00 00  WA  0  0  8

[22] .dynamic         DYNAMIC         00bbb6f8 bab6f8000178 08  WA  6  0  4

[23] .got             PROGBITS        00bbb870 bab8700012e0 04  WA  0  0  4

[24] .data            PROGBITS        00bbcb50 bacb50775034 00  WA  0  0  8

[25] .bss              NOBITS          01331b88 1321b84 417f884 00  WA 0   0  8

[26] .ARM.attributes  ARM_ATTRIBUTES  00000000 1321b84000039 00      0   0  1

[27] .shstrtab         STRTAB          00000000 1321bbd 0000f5 00      0  0  1

ey to Flags:

W(write), A (alloc), X (execute), M (merge), S (strings)

I(info), L (link order), G (group), T (TLS), E (exclude), x (unknown)

O(extra OS processing required) o (OS specific), p (processor specific)

There are no section groups in this file.

Program Headers: 注意LOAD表示需要加载入内存的程序段

Type           Offset   VirtAddr  PhysAddr   FileSiz MemSiz  Flg Align

EXIDX          0x000000 0x000000000x00000000 0x00000 0x00000 R   0x4

PHDR           0x000034 0x000080340x00008034 0x00120 0x00120 R E 0x4

INTERP         0x000154 0x000081540x00008154 0x00019 0x00019 R   0x1

[Requesting program interpreter: /lib/ld-linux-armhf.so.3]

LOAD           0x000000 0x000080000x00008000 0xba81e0 0xba81e0 R E 0x8000

->.hash + .rodata等等需要加载进内存的段;load_elf_binary是需要申请内存,存放这些段;

动态库加载和可执行文件执行时都需要对应的加载;

LOAD           0xba81e0 0x00bb81e0 0x00bb81e0 0x7799a4  0x48f922c RW 0x8000

->.data数据段+.bss段+.got段等等段的大小:0x48f922c-0x7799a4=417F888

DYNAMIC        0xbab6f8 0x00bbb6f80x00bbb6f8 0x00178 0x00178 RW  0x4

NOTE           0x000170 0x000081700x00008170 0x00044 0x00044 R   0x4

TLS            0xba81e0 0x00bb81e00x00bb81e0 0x00000 0x00004 R   0x4

GNU_STACK      0x000000 0x000000000x00000000 0x00000 0x00000 RWE 0x4-此段会决定栈的空间。

#/home/snmpd &

1 第一次为用户进程分配栈空间 do_execve_common->__bprm_mm_init

并把栈空间VMA插入进程的地址空间中。

1) 初始化过程指定程序栈的空间为vm_start:7efff000;vm_end:0x7f000000

[0x7efff000,0x7f000000],此处的栈不是当前进程的,是

bprm->filename=/usr/bin/snmp的。

2) 之后我们还会有对栈的空间所调整

3) 因为用户态进程共享用户态虚拟地址空间,所以每个进程的栈顶地址都是这个0x7efff000。

~__bprm_mm_init:current:sh;filename:/home/snmpd;vm_start:7efff000;vm_end:0x7f000000

[7efff000-7f000000],0000038f 00118173

Elf头和程序头

type=0x2;phoff=0x34;flags=0x5000002;phnum=0x8;

type=0x200;phoff=0xfe;flags=0x0;phnum=0x1061;

lf_interpreter:/lib/ld-linux-armhf.so.3;bprm->filename:/home/snmpd

load_elf_binary-913current:sh;bprm->filename is /home/snmpd bprm->tcomm=snmpd

current->top_stack=0x7effff91

进程地址空间的栈区间调整之前

[7effe000-7f000000],0000038f 00118173

2 调整栈空间的大小setup_arg_pages,调整后为[7ea7a000-7ea9c000]

do_execve_common->search_binary_handler-> load_elf_binary->setup_arg_pages

stack_top=0x7ea9c000;mmap_min_addr=4096

6i=0:6type=0x70000001;offset=0x484;vaddr=0x8484;paddr=0x8484;filesz=0x8;memsz=0x8;flags=0x4;align=0x4

6i=1:6type=0x6;offset=0x34;vaddr=0x8034;paddr=0x8034;filesz=0x100;memsz=0x100;flags=0x5;align=0x4

6i=2:6type=0x3;offset=0x134;vaddr=0x8134;paddr=0x8134;filesz=0x19;memsz=0x19;flags=0x4;align=0x1

6i=3:6type=0x1;offset=0x0;vaddr=0x8000;paddr=0x8000;filesz=0x490;memsz=0x490;flags=0x5;align=0x8000

    6l   oad_elf_binary-1039i=3 current:snmpd;bprm->filename is /home/snmpd,e_type:2,p_type:1

进程地址空间增加代码段之前,栈空间调整之后的栈为如下区间:

[7ea7a000-7ea9c000] ,0000038f 00100173 –调整后的栈

3 进程地址空间中增加代码段elf_map,调整后为[00008000-00009000]

do_e

35
8
0
23

相关资讯

  1. 1、《寻秦记》20年主演对比照!年纪最小的林峯变化最大!5073
  2. 2、《骨语》完美收官刘亭作演技大爆发2265
  3. 3、张丹峰只能继续做洪先生,因为她们不会放过他629
  4. 4、苏檀儿怀孕了,宁毅不想回到现代的生活,只想守护着妻儿3726
  5. 5、刘浩存在首映礼哭出声,原来是心疼张艺谋,导演演戏太敬业曾被烧伤2852
  6. 6、张哲瀚电影《维和防暴队》热拍,看到主演阵容,这下票房有保障了1551
  7. 7、李若彤撕下“小龙女”标签,学会放开后让人心动,52岁韵味十足2235
  8. 8、《晴雅集》下架没什么可惜,卖腐的“男色小片”不看也罢2549
  9. 9、黄圣依穿低胸裙现身机场秀事业线,小腹凸起被指怀有第三胎2277
  10. 10、《木府风云》央视热播新潮木府唱火新“神曲”1182
全部评论(0)
我也有话说
0
收藏
点赞
顶部