Linux系统ELF程序的执行过程
[摘要][正文]用后态执行[正文]内核态执行[ELF文件加载][实例][动态库加载][总结]注意:请使用谷歌浏览器阅读(IE浏览器排版混乱)【摘要】本文将介绍linux程序的执行过程,并以实际问题为切入点简单介绍下ELF程序的加载过程。【正文】用后态执行我们知道在linux系统中可以通过诸如"./debug"方式执行一个程序,那么这个程序的执...
[摘要]
[正文]用后态执行
[正文]内核态执行
[ELF文件加载]
[实例]
[动态库加载]
[总结]
注意:请使用谷歌浏览器阅读(IE浏览器排版混乱)
【摘要】
本文将介绍linux程序的执行过程,并以实际问题为切入点简单介绍下ELF程序的加载过程。
【正文】用后态执行
我们知道在linux系统中可以通过诸如"./debug"方式执行一个程序,那么这个程序的执行过程中linux系统都做了什么?
本文以debug程序为例,介绍linux内核是如何一步步将debug进程执行起来的.
1 执行过程:
以system()实现为例,它是一种典型的可执行程序运行过程:
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include <unistd.h>
int system(const char * cmdstring)
{
pid_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<unistd.h>
int execve(const char *filename, char *const argv[], char *const envp[]);
#include<stdio.h>
#include<unistd.h>
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
/* 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.native = __argv };
struct user_arg_ptr envp = { .ptr.native = __envp };
return do_execve_common(filename, argv, envp);
}
2 execve->do_execve->do_execve_common()注意此时当前进程是上文中创建的子进程。
bprm_mm_init()完成进程地址空间vma(包括栈)的初始化.
/*
* sys_execve() executes a new program.
*/
static 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 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(&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_unsafe_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()
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 candidate 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_version,处理器特定标志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"来察看文件头的内容。
文件头的数据结构如下:
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"察看程序头表中的内容。程序头表的结构如下:
typedef struct elf32_phdr{
Elf32_Word p_type; //段的类型
Elf32_Off p_offset; //段的位置相对于文件开始处的偏移
Elf32_Addr p_vaddr; //段在内存中的首字节地址
Elf32_Addr p_paddr;//段的物理地址
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"来察看节头表的内容。节头表的结构如下:
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()
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 (memcmp(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-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
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, <unknown>
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 SVr4 maps page 0 as read-only,
and some applications "depend" upon this behavior.
Since we do not have the power to recompile 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 debug > debug
ELF Header:
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, <unknown>
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段地址需要参考这个头信息;
[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
Key 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
6load_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_execve_common->search_binary_handler-> load_elf_binary-> elf_map
elf_map-342
current:snmpd;addr:0x8000;size:0x1000;p_filesz:0x490;p_offset:0x0;p_vaddr:0x8000
load_elf_binary-1058current:snmpd;bprm->filename is /home/snmpd
[00008000-00009000],0000018f 00000875—代码段
[7ea7a000-7ea9c000],0000038f 00100173 –栈
6i=4:6type=0x1;offset=0x490;vaddr=0x10490;paddr=0x10490;filesz=0x120;memsz=0x128;flags=0x6;align=0x8000
进程地址空间增加数据段之前
6load_elf_binary-1039i=4 current:snmpd;bprm->filename is /home/snmpd,e_type:2,p_type:1
[00008000-00009000],0000018f 00000875 – 代码段
[7ea7a000-7ea9c000],0000038f 00100173 –栈
4 进程地址空间中增加程序段elf_map,调整后为[00010000-00011000]
do_execve_common->search_binary_handler-> load_elf_binary-> elf_map
elf_map-342current:snmpd;addr:0x10000;size:0x1000;p_filesz:0x120;p_offset:0x490;p_vaddr:0x10490
6load_elf_binary-1058current:snmpd;bprm->filename is /home/snmpd
[00008000-00009000],0000018f 00000875 –代码段
[00010000-00011000],0000038f 00100873 –数据段
[7ea7a000-7ea9c000],0000038f 00100173—栈
6i=5:6type=0x2;offset=0x49c;vaddr=0x1049c;paddr=0x1049c;filesz=0xe8;memsz=0xe8;flags=0x6;align=0x4
6i=6:6type=0x4;offset=0x150;vaddr=0x8150;paddr=0x8150;filesz=0x44;memsz=0x44;flags=0x4;align=0x4
6i=7:6type=0x6474e551;offset=0x0;vaddr=0x0;paddr=0x0;filesz=0x0;memsz=0x0;flags=0x6;align=0x4
进程地址空间中增加bss段之前
load_elf_binary-1136current:snmpd;bprm->filename is/home/snmpd,elf_bss=0x105b0,elf_brk=0x105b8;bias=0x0
6start_code=0x8000,end_code=0x8490;start_data=0x10490;end_data=0x105b0
[00008000-00009000],0000018f 00000875
[00010000-00011000],0000038f 00100873
[7ea7a000-7ea9c000],0000038f 00100173
5 进程地址空间中增加bss段set_brk,调整后为[00010000-00011000]
do_execve_common->search_binary_handler-> load_elf_binary-> set_brk
注意bss段增加之后,程序段并没有变化,因为bss段在data段的区间内
load_elf_binary-1168current:snmpd;bprm->filename is /home/snmpd
[00008000-00009000],0000018f 00000875
[00010000-00011000],0000038f 00100873
[7ea7a000-7ea9c000],0000038f 00100173
6 进程地址空间中增加其余段load_elf_interp,调整后为:
do_execve_common->search_binary_handler-> load_elf_binary-> load_elf_interp
6elf_map-342current:snmpd;addr:0x0 ;size:0x1a000;p_filesz:0x19308;p_offset:0x0;p_vaddr:0x0
6elf_map-342current:snmpd;addr:0x76f24000;size:0x2000;p_filesz:0xb50;p_offset:0x19d38;p_vaddr:0x21d38
分析对比smaps:
6load_elf_binary-1226current:snmpd;bprm->filename is /home/snmpd
[00008000-00009000],0000018f 00000875 –代码段
[00010000-00011000],0000038f 00100873 –数据段/bss段
[76f03000-76f1d000],0000018f 00000875
[76f23000-76f24000],0000018f 00040075
[76f24000-76f26000],0000038f 00100873
[7ea7a000-7ea9c000],0000038f 00100173 –栈
[00008000-00009000],0000018f 00000875
[00010000-00011000],0000038f 00100873
[76f03000-76f1d000],0000018f 00000875
[76f23000-76f24000],0000018f 00040075
[76f24000-76f26000],0000038f 00100873
[7ea7a000-7ea9c000],0000038f 00100173
buf=0x103d008
以上分析对比smaps:
#cat /proc/239/smaps
00008000-00009000r-xp 00000000 00:0e 23330821 /home/snmpd -代码段
Size: 4 kB
Rss: 4 kB
Pss: 4 kB
Shared_Clean: 0 kB
Shared_Dirty: 0 kB
Private_Clean: 4 kB
Private_Dirty: 0 kB
Referenced: 4 kB
Anonymous: 0 kB
AnonHugePages: 0 kB
Swap: 0 kB
KernelPageSize: 4 kB
MMUPageSize: 4 kB
Locked: 0 kB
VmFlags:rd ex mr mw me dw
00010000-00011000rw-p 00000000 00:0e 23330821 /home/snmpd–数据段/bss段
Size: 4 kB
Rss: 4 kB
Pss: 4 kB
Shared_Clean: 0 kB
Shared_Dirty: 0 kB
Private_Clean: 0 kB
Private_Dirty: 4 kB
Referenced: 4 kB
Anonymous: 4 kB
AnonHugePages: 0 kB
Swap: 0 kB
KernelPageSize: 4 kB
MMUPageSize: 4 kB
Locked: 0 kB
VmFlags:rd wr mr mw me dw ac
0103d000-01060000rw-p 00000000 00:00 0 [heap]
Size: 140 kB
Rss: 8 kB
Pss: 8 kB
Shared_Clean: 0 kB
Shared_Dirty: 0 kB
Private_Clean: 0 kB
Private_Dirty: 8 kB
Referenced: 8 kB
Anonymous: 8 kB
AnonHugePages: 0 kB
Swap: 0 kB
KernelPageSize: 4 kB
MMUPageSize: 4 kB
Locked: 0 kB
VmFlags:rd wr mr mw me ac
76dff000-76ef6000r-xp 00000000 1f:05 154 /lib/libc-2.19-2014.06.so
Size: 988 kB
Rss: 236 kB
Pss: 34 kB
Shared_Clean: 236 kB
Shared_Dirty: 0 kB
Private_Clean: 0 kB
Private_Dirty: 0 kB
Referenced: 236 kB
Anonymous: 0 kB
AnonHugePages: 0 kB
Swap: 0 kB
KernelPageSize: 4 kB
MMUPageSize: 4 kB
Locked: 0 kB
VmFlags:rd ex mr mw me
【动态库加载】
对于同一个动态库来说,不同进程中的vma区间不同,但对应相同的文件页,所以一旦动态库被一个进程加载到了内存中,其他进程不用再次从flash上加载,也就是说这个动态库是共享的.
不同进程中同一动态库对应的虚拟地址虽然不同,但动态库加载进内存的物理偏移地址是相同的.当进程访问动态库中的一个函数时,这个函数的地址如果未经过分页映射,即有可能初次访问,则触发缺页异常,文件页的缺页异常处理流程会根据这个函数在动态库中的偏移地址(即时不同进程,这个偏移地址也是相同的)找到对应文件页,并判断是否需要从flash中读数据到该文件页.
1 加载动态库的系统调用:sys_userlib->load_shlib=load_elf_library
2 查找动态库的bss段信息:binfmt_elf.c:
/*file表示动态库文件;shdata输出变量,区section头部信息*/
int load_elf_library_section(struct file *file,struct elf_shdr *shdata)
{
struct elf_shdr *elf_shdata;
struct elf_shdr *eppnt;
unsigned long elf_bss,bss,len;
int retval,error,i,j;
struct elfhdr elf_ex;
/*读取elf头*/
retval = kernel_read(file,0,(char *)&elf_ex,sizeof(elf_ex));
/*所有section头的总大小*/
j =sizeof(struct elf_shdr)*elf_ex.e_shnum;
elf_shdata = kmalloc(j,GFP_KERNEL);
eppnt = elf_shdata;
/*读取section header*/
retval = kernel_read(file,elf_ex.e_shoff,(char *)eppnt,j);
for(j=0,i=0;i<elf_ex.e_shnum;i++)
{
if(SH_NOBITS==eppnt->sh_type)
{
printk("bss section:\n");
memcpy(shdata,eppnt,sizeof(struct elf_shdr));
}
dump_elf_shdr(eppnt);//打印section头部信息
eppnt++;
}
kfree(elf_shdata);
return error;
}
filemap_fault()文件页缺页异常处理中可以根据动态库的名称,导出动态库的指定段信息(如bss、data段等) .
3 动态库加载过程:
1>可以通过命令:
#strace ls --查看动态库加载过程,一般流程open(".so")->mmap();
2>文件页缺页异常中真正从flash上获取动态库内容:filemap_fault;
3>具体过程可以参考如下博文:
linux文件读取过程:http://blog.csdn.net/eleven_xiy/article/details/73609237
linux内存回收机制:http://blog.csdn.net/eleven_xiy/article/details/75195490;
4 动态库链接过程举例:
4.1 基本信息
程序名:debug;连接动态库:libdebug.so;
窗口终端环境变量:
#export
export HOME='/'
export PATH='/sbin:/usr/sbin:/bin:/usr/bin'
export PWD='/'
执行过程:./debug sh进程中执行execve(debug);
execve返回时debug进程开始执行;
debug进程首先链接动态库,默认尝试路径open(/lib/libdebug.so);open(/usr/lib/libdebug.so);
如果配置export LD_LIBRARY_PATH='/mnt/mtd'
则尝试open(/mnt/mtd/libdebug.so);open(/lib/libdebug.so);open(/usr/lib/libdebug.so);
debug进程执行main入口函数;
telnet终端环境变量,遵循/etc/profile中配置:
export HOME='/'
export LD_LIBRARY_PATH='/usr/local/lib:/usr/lib:/mnt/mtd/'
export PATH='/sbin:/usr/sbin:/bin:/usr/bin'
export PWD='/'
可执行程序链接动态库是在elf程序exec执行之后,main入口函数执行之前;
编译过程指定链接路径为:/lib;/usr/lib + LD_LIBRARY_PATH;
可执行文件中定位动态库中符号: GOT和PLT原理简析
指定可执行程序的动态库链接路径:
envp=" LD_LIBRARY_PATH=/mnt/mtd"
execve("./debug",NULL,envp);
sh里执行进程找不到动态库的情况:
例如:./test 执行命令找不到动态库。
解决方法:可以在内核启动sh时的环境变量中修改:
start_kernel->kernel_init->run_init_process:envp_init 中添加"LD_LIBRARY_PATH=/usr/xx"
$ g++ -Wl,-rpath,/usr/local/lib/ -oevh libevent_http.cpp -levent
-Wl,-rpath, 用于指定程序运行时查找动态链接库的路径,多个路径是使用冒号隔开。这样就不用添加路径到 /etc/ld.so.conf 文件中了,在需要多个so版本共存时很有用
编译完成后可以使用以下命令查看路径是否设置成功了
$ readelf -dl evh
看到类似下面的信息则是路径设置成功了
0x000000000000000f (RPATH) Library rpath: [/usr/local/lib/:/data1/thd/jsoncpp/lib/:/data1/thd/leveldb/lib/:/data1/tools/boost_1_53_0/stage/lib/]
另外以下命令可以查看可执行文件的依赖库
5 改变进程的权限(capablility): setcap
【总结】
本文介绍了可执行文件和动态库加载过程,举例说明了ELF 文件的elf头,程序头(program header)和section header. 值得注意的是 :
1 程序头中的LOAD(PT_LOAD)段,是需要加载进内存的,且load_elf_binary/load_elf_library过程都需要加载,这个段中不只包含data和bss段,
可以通过readelf -a查看Section to Segment mapping中表明了LOAD包含的段;
2 section header中真正指明了程序的数据段、bss段(SH_NOBITS).
3 ELF程序执行过程中,读取ELF各头部信息,并逐步替换掉当前进程(包括进程名,进程地址空间等),最后切换到ELF程序执行.
当前进程不需要主动退出,就切换到ELF程序中,因为当前进程所有信息都被ELF替换掉了.
更多推荐
所有评论(0)