Linux系统ELF程序的执行过程

本文将介绍linux程序的执行过程,并以实际问题为切入点简单介绍下elf程序的加载过程。
【正文】用后态执行
我们知道在linux系统中可以通过诸如./debug方式执行一个程序,那么这个程序的执行过程中linux系统都做了什么?
本文以debug程序为例,介绍linux内核是如何一步步将debug进程执行起来的.
1 执行过程:
以system()实现为例,它是一种典型的可执行程序运行过程:
[cpp]view plaincopy
#include
#include
#include
#include
intsystem(constchar*cmdstring)
{
pid_tpid;
intstatus;
if(cmdstring==null){
return(1);
}
if((pid=fork())<0){
status=-1;
}
elseif(pid=0){
execl(/bin/sh,sh,-c,cmdstring,(char*)0);
-exit(127);//子进程正常执行则不会执行此语句
}
else{
while(waitpid(pid,&status,0)do_execve_common函数,将上步创建的子进程,完全替换成了可执行程序.
这个替换过程,其实也就是可执行程序的加载过程,也是本文着重介绍的内容.
3) execve使用实例:
#include
int execve(const char *filename, char *const argv[], char *const envp[]);
[cpp]view plaincopy
#include
#include
intmain(intarg,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 plaincopy
/*filename为可执行文件:/home/debug;
argv为null,表示可行程序不带参数;
envp为null,表示没有指定环境变量;*/
intdo_execve(constchar*filename,constchar__user*const__user*__argv,
constchar__user*const__user*__envp)
{
structuser_arg_ptrargv={.ptr.native=__argv};
structuser_arg_ptrenvp={.ptr.native=__envp};
returndo_execve_common(filename,argv,envp);
}
2execve->do_execve->do_execve_common()注意此时当前进程是上文中创建的子进程。
bprm_mm_init()完成进程地址空间vma(包括栈)的初始化.
[cpp]view plaincopy
/*
*sys_execve()executesanewprogram.
*/
staticintdo_execve_common(constchar*filename,
structuser_arg_ptrargv,
structuser_arg_ptrenvp)
{
/*注意linux_binprm是核心数据结构,它保存了可执行文件的信息;*/
structlinux_binprm*bprm;
structfile*file;
structfiles_struct*displaced;
boolclear_in_exec;
intretval;
conststructcred*cred=current_cred();
/*
*wemovetheactualfailureincaseofrlimit_nprocexcessfrom
*set*uid()toexecve()becausetoomanypoorlywrittenprograms
*don'tchecksetuid()returncode.hereweadditionallyrecheck
*whethernproclimitisstillexceeded.
*/
if((current->flags&pf_nproc_exceeded)&&
atomic_read(&cred->user->processes)>rlimit(rlimit_nproc)){
retval=-eagain;
gotoout_ret;
}
/*we'rebelowthelimit(stilloragain),sowedon'twanttomake
*furtherexecve()callsfail.*/
current->flags&=~pf_nproc_exceeded;
retval=unshare_files(&displaced);
if(retval)
gotoout_ret;
retval=-enomem;
/*申请linux_binprm描述符,用以保存elf可执行文件信息*/
bprm=kzalloc(sizeof(*bprm),gfp_kernel);
if(!bprm)
gotoout_files;
/*生成bprm->cred即准备可执行程序运行的用户和组信息,主要根据当前进程的task->cred信息生成*/
retval=prepare_bprm_creds(bprm);
if(retval)
gotoout_free;
retval=check_unsafe_exec(bprm);
if(retvalin_execve=1;
/*
1:打开可执行程序/home/debug;
*/
file=open_exec(filename);
retval=ptr_err(file);
if(is_err(file))
gotoout_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)
gotoout_file;
/*可执行文件参数个数,对/home/debug来说argc=0,因为指定参数为null*/
bprm->argc=count(argv,max_arg_strings);
if((retval=bprm->argc)argc*/
bprm->envc=count(envp,max_arg_strings);
if((retval=bprm->envc)buf中;
实现方式:kernel_read(bprm->file,0,bprm->buf,binprm_buf_size);//128bytes
*/
retval=prepare_binprm(bprm);
if(retvalfilename,bprm);
if(retvalexec=bprm->p;
retval=copy_strings(bprm->envc,envp,bprm);
if(retvalargc,argv,bprm);
if(retvalload_binary=load_elf_binary->arch_setup_additional_pages:register_binfmt中注册的elf_format
->install_special_mapping->insert_vm_struct
*/
retval=search_binary_handler(bprm);
if(retvalfs->in_exec=0;
current->in_execve=0;
acct_update_integrals(current);
free_bprm(bprm);
if(displaced)
put_files_struct(displaced);
returnretval;
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:
returnretval;
}
2.1 elf头读取过程:do_execve_common()->prepare_binprm()
[cpp]view plaincopy
intprepare_binprm(structlinux_binprm*bprm)
{
umode_tmode;
structinode*inode=file_inode(bprm->file);
intretval;
mode=inode->i_mode;
if(bprm->file->f_op==null)
return-eacces;
/*clearanypreviousset[ug]iddatafromapreviousbinary*/
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?*/
/*
*ifsetgidissetbutnogroupexecutebitthenthis
*isacandidateformandatorylocking,notasetgid
*executable.
*/
if((mode&(s_isgid|s_ixgrp))==(s_isgid|s_ixgrp)){
bprm->per_clear|=per_clear_on_setid;
bprm->cred->egid=inode->i_gid;
}
}
/*fillinbinprmsecurityblob*/
retval=security_bprm_set_creds(bprm);
if(retval)
returnretval;
bprm->cred_prepared=1;
memset(bprm->buf,0,binprm_buf_size);
/*
elf头保存到bprm->buf中
*/
returnkernel_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来察看文件头的内容。
文件头的数据结构如下:
[cpp]view plaincopy
typedefstructelf32_hdr{
unsignedchare_ident[ei_nident];
elf32_halfe_type;//目标文件类型
elf32_halfe_machine;//硬件平台
elf32_worde_version;//elf头部版本
elf32_addre_entry;//程序进入点
elf32_offe_phoff;//程序头表偏移量
elf32_offe_shoff;//节头表偏移量
elf32_worde_flags;/处理器特定标志
elf32_halfe_ehsize;//elf头部长度
elf32_halfe_phentsize;//程序头表中一个条目的长度
elf32_halfe_phnum;//程序头表条目数目
elf32_halfe_shentsize;//节头表中一个条目的长度
elf32_halfe_shnum;//节头表条目个数
elf32_halfe_shstrmdx;//节头表字符索引
}elf32_ehdr;
程序头表(program header table)
程序头表告诉系统如何建立一个进程映像.它是从加载执行的角度来看待elf文件.从它的角度看.elf文件被分成许多段,elf文件中的代码、链接信息和注释都以段的形式存放。每个段都在程序头表中有一个表项描述,包含以下属性:段的类型,段的驻留位置相对于文件开始处的偏移,段在内存中的首字节地址,段的物理地址,段在文件映像中的字节数.段在内存映像中的字节数,段在内存和文件中的对齐标记。可用readelf -l filename察看程序头表中的内容。程序头表的结构如下:
[cpp]view plaincopy
typedefstructelf32_phdr{
elf32_wordp_type;//段的类型
elf32_offp_offset;//段的位置相对于文件开始处的偏移
elf32_addrp_vaddr;//段在内存中的首字节地址
elf32_addrp_paddr;//段的物理地址
elf32_wordp_filesz;//段在文件映像中的字节数
elf32_wordp_memsz;//段在内存映像中的字节数
elf32_wordp_flags;//段的标记
elf32_wordp_align;,/段在内存中的对齐标记
)elf32_phdr;
节头表(section header table)
节头表描述程序节,为编译器和链接器服务。它把elf文件分成了许多节.每个节保存着用于不同目的的数据.这些数据可能被前面的程序头重复使用,完成一次任务所需的信息往往被分散到不同的节里。由于节中数据的用途不同,节被分成不同的类型,每种类型的节都有自己组织数据的方式。每一个节在节头表中都有一个表项描述该节的属性,节的属性包括小节名在字符表中的索引,类型,属性,运行时的虚拟地址,文件偏移,以字节为单位的大小,小节的对齐等信息,可使用readelf -s filename来察看节头表的内容。节头表的结构如下:
[cpp]view plaincopy
typedefstruct{
elf32_wordsh_name;//小节名在字符表中的索引
e1t32_wordsh_type;//小节的类型
elf32_wordsh_flags;//小节属性
elf32_addrsh_addr;//小节在运行时的虚拟地址
elf32_offsh_offset;//小节的文件偏移
elf32_wordsh_size;//小节的大小.以字节为单位
elf32_wordsh_link;//链接的另外一小节的索引
elf32wordsh_info;//附加的小节信息
elf32wordsh_addralign;//小节的对齐
elf32wordsh_entsize;//一些sections保存着一张固定大小入口的表。就像符号表
}elf32_shdr;
3.2 elf文件加载的的实现代码:
代码流程:do_execve_common()->search_binary_handler->load_binary=load_elf_binary()
[cpp]view plaincopy
staticintload_elf_binary(structlinux_binprm*bprm)
{
structfile*interpreter=null;/*toshutgccup*/
unsignedlongload_addr=0,load_bias=0;
intload_addr_set=0;
char*elf_interpreter=null;
unsignedlongerror;
structelf_phdr*elf_ppnt,*elf_phdata;
unsignedlongelf_bss,elf_brk;
intretval,i;
unsignedintsize;
unsignedlongelf_entry;
unsignedlonginterp_load_addr=0;
unsignedlongstart_code,end_code,start_data,end_data;
unsignedlongreloc_func_desc__maybe_unused=0;
intexecutable_stack=exstack_default;
unsignedlongdef_flags=0;
structpt_regs*regs=current_pt_regs();
//elf32_ehdr
struct{
structelfhdrelf_ex;
structelfhdrinterp_elf_ex;
}*loc;
loc=kmalloc(sizeof(*loc),gfp_kernel);
if(!loc){
retval=-enomem;
gotoout_ret;
}
/*
进程的elf头保存在此
*/
/*gettheexec-header*/
loc->elf_ex=*((structelfhdr*)bprm->buf);
retval=-enoexec;
/*firstofall,somesimpleconsistencychecks*/
if(memcmp(loc->elf_ex.e_ident,elfmag,selfmag)!=0)
gotoout;
if(loc->elf_ex.e_type!=et_exec&&loc->elf_ex.e_type!=et_dyn)
gotoout;
if(!elf_check_arch(&loc->elf_ex))
gotoout;
if(!bprm->file->f_op||!bprm->file->f_op->mmap)
gotoout;
/*nowreadinalloftheheaderinformation*/
if(loc->elf_ex.e_phentsize!=sizeof(structelf_phdr))
gotoout;
if(loc->elf_ex.e_phnumelf_ex.e_phnum>65536u/sizeof(structelf_phdr))
gotoout;
size=loc->elf_ex.e_phnum*sizeof(structelf_phdr);
retval=-enomem;
elf_phdata=kmalloc(size,gfp_kernel);
if(!elf_phdata)
gotoout;
/*
保存所有程序段到elf_phdata;注意此处elf_phdr与elfhdr的区别
1elf_phdr如下:程序头
programheaders:
typeoffsetvirtaddrphysaddrfilesizmemsizflgalign
exidx0x0000000x000000000x000000000x000000x00000r0x4
phdr0x0000340x000080340x000080340x001200x00120re0x4
interp0x0001540x000081540x000081540x000190x00019r0x1
[requestingprograminterpreter:/lib/ld-linux-armhf.so.3]
load0x0000000x000080000x000080000xb229140xb22914re0x8000
load0xb229140x00b329140x00b329140x16b4a00x3a22d0rw0x8000
dynamic0xb25df80x00b35df80x00b35df80x001780x00178rw0x4
note0x0001700x000081700x000081700x000440x00044r0x4
tls0xb229140x00b329140x00b329140x000000x00004r0x4
gnu_stack0x0000000x000000000x000000000x000000x00000rwe0x4
2elfhder如下:elf头
elfheader:
magic:7f454c46010101000000000000000000
class:elf32
data:2'scomplement,littleendian
version:1(current)
os/abi:unix-systemv
abiversion:0
type:exec(executablefile)
machine:arm
version:0x1
entrypointaddress:0x2fa31
startofprogramheaders:52(bytesintofile)
startofsectionheaders:13164260(bytesintofile)
flags:0x5000402,hasentrypoint,version5eabi,
sizeofthisheader:52(bytes)
sizeofprogramheaders:32(bytes)
numberofprogramheaders:9
sizeofsectionheaders:40(bytes)
numberofsectionheaders:28
sectionheaderstringtableindex: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;
gotoout_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;ielf_ex.e_phnum;i++){
if(elf_ppnt->p_type==pt_interp){
/*thisistheprograminterpreterusedfor
*sharedlibraries-fornowassumethatthis
*isana.outformatbinary
*/
retval=-enoexec;
if(elf_ppnt->p_filesz>path_max||
elf_ppnt->p_fileszp_filesz,
gfp_kernel);
if(!elf_interpreter)
gotoout_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;
gotoout_free_interp;
}
/*makesurepathisnullterminated*/
retval=-enoexec;
if(elf_interpreter[elf_ppnt->p_filesz-1]!='\0')
gotoout_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))
gotoout_free_interp;
/*
*ifthebinaryisnotreadablethenenforce
*mm->dumpable=0regardlessoftheinterpreter'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;
gotoout_free_dentry;
}
/*gettheexecheaders*/
loc->interp_elf_ex=*((structelfhdr*)bprm->buf);
break;
}
elf_ppnt++;
}
elf_ppnt=elf_phdata;
for(i=0;ielf_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;
}
/*somesimpleconsistencychecksfortheinterpreter*/
if(elf_interpreter){
retval=-elibbad;
/*notanelfinterpreter*/
if(memcmp(loc->interp_elf_ex.e_ident,elfmag,selfmag)!=0)
gotoout_free_dentry;
/*verifytheinterpreterhasavalidarch*/
if(!elf_check_arch(&loc->interp_elf_ex))
gotoout_free_dentry;
}
/*flushalltracesofthecurrentlyrunningexecutable*/
retval=flush_old_exec(bprm);
if(retval)
gotoout_free_dentry;
/*ok,thisisthepointofnoreturn*/
current->mm->def_flags=def_flags;
/*dothisimmediately,sincestack_topasusedinsetup_arg_pages
maydependonthepersonality.*/
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);
/*dothissothatwecanloadtheinterpreter,ifneedbe.wewill
changesomeoftheselater*/
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(retvalmm->start_stack=bprm->p;
/*nowwedoalittlegrungyworkbymmappingtheelfimageinto
thecorrectlocationinmemory.*/
for(i=0,elf_ppnt=elf_phdata;
ielf_ex.e_phnum;i++,elf_ppnt++){
intelf_prot=0,elf_flags;
unsignedlongk,vaddr;
#ifndefgsysdebuginfoexec
/*
programheaders:
typeoffsetvirtaddrphysaddrfilesizmemsizflgalign
exidx0x0000000x000000000x000000000x000000x00000r0x4
phdr0x0000340x000080340x000080340x001200x00120re0x4
interp0x0001540x000081540x000081540x000190x00019r0x1
[requestingprograminterpreter:/lib/ld-linux-armhf.so.3]
load0x0000000x000080000x000080000xb229140xb22914re0x8000
load0xb229140x00b329140x00b329140x16b4a00x3a22d0rw0x8000
dynamic0xb25df80x00b35df80x00b35df80x001780x00178rw0x4
note0x0001700x000081700x000081700x000440x00044r0x4
tls0xb229140x00b329140x00b329140x000000x00004r0x4
gnu_stack0x0000000x000000000x000000000x000000x00000rwe0x4
*/
/*此处可以打印出/usr/bin/snmp进程的所有程序段
也可以通过readelf命令读出programheader
*/
#endif
/*
programheader中load表示的就是p_type
p_type为pt_load的段需要加载进内存
*/
if(elf_ppnt->p_type!=pt_load)
continue;
if(unlikely(elf_brk>elf_bss)){
unsignedlongnbyte;
/*therewasapt_loadsegmentwithp_memsz>p_filesz
beforethisone.mapanonymouspages,ifneeded,
andclearthearea.*/
retval=set_brk(elf_bss+load_bias,
elf_brk+load_bias);
if(retval){
send_sig(sigkill,current,0);
gotoout_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)){
/*
*thisbss-zeroingcanfailiftheelf
*filespecifiesoddprotections.so
*wedon'tcheckthereturnvalue
*/
}
}
}
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;
}elseif(loc->elf_ex.e_type==et_dyn){
/*tryandgetdynamicprogramsoutofthewayofthe
*defaultmmapbase,aswellaswhateverprogramthey
*mighttrytoexec.thisisbecausethebrkwill
*followtheloader,andisnotmovable.*/
#ifdefconfig_arch_binfmt_elf_randomize_pie
/*memoryrandomizationmighthavebeenswitchedoff
*inruntimeviasysctlorexplicitsettingof
*personalityflags.
*ifthatisthecase,retaintheoriginalnon-zero
*load_biasvalueinordertoestablishproper
*non-randomizedmappings.
*/
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;
gotoout_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=k;
if(start_data start_data=k;
/*
*checktoseeifthesection'ssizewilloverflowthe
*allowedtasksize.notethatp_fileszmustalwaysbe
*p_filesz>elf_ppnt->p_memsz||
elf_ppnt->p_memsz>task_size||
task_size-elf_ppnt->p_memszp_vaddr+elf_ppnt->p_filesz;
if(k>elf_bss)
elf_bss=k;
if((elf_ppnt->p_flags&pf_x)&&end_code end_code=k;
if(end_datap_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;
#ifndefgsysdebuginfoexec
/*
此时已经有3个vma;
elf_bss=0x105b0,elf_brk=0x105b8;bias=0x0
start_code=0x8000,end_code=0x8490;start_data=0x10490;end_data=0x105b0
[00008000-00009000],0000018f00000875--代码段
[00010000-00011000],0000038f00100873--数据段
[7ecbb000-7ecdd000],0000038f00100173--进程的栈
*/
#endif
/*callingset_brkeffectivelymmapsthepagesthatweneed
*forthebssandbreaksections.wemustdothisbefore
*mappingintheinterpreter,tomakesureitdoesn'twind
*upgettingplacedwherethebssneedstogo.
*/
/*
1在此为bss段申请虚拟地址空间,注意此处的地址空间
为用户态进程的虚拟地址空间vm_brk,类似于malloc过程。
如果申请的虚拟地址空间即bss段大小大于系统空闲的物理内存
则有可能申请失败,可以通过echo1>/proc/sys/vm/overcommit_memory
去掉对内存大小的检测来规避失败的风险。
2并未真正分配物理内存
3set_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],0000018f00000875--代码段
[00010000-00011000],0000038f00100873--数据段
[7ecbb000-7ecdd000],0000038f00100173--进程的栈
*/
retval=set_brk(elf_bss,elf_brk);
if(retval){
send_sig(sigkill,current,0);
gotoout_free_dentry;
}
if(likely(elf_bss!=elf_brk)&&unlikely(padzero(elf_bss))){
send_sig(sigsegv,current,0);
retval=-efault;/*nobodygetstoseethis,but..*/
gotoout_free_dentry;
}
if(elf_interpreter){
unsignedlonginterp_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()returnsrelocation
*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;
gotoout_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;
gotoout_free_dentry;
}
}
kfree(elf_phdata);
set_binfmt(&elf_format);
#ifdefarch_has_setup_additional_pages
retval=arch_setup_additional_pages(bprm,!!elf_interpreter);
if(retvalelf_ex,
load_addr,interp_load_addr);
if(retvalmm->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;
#ifdefarch_randomize_brk
if((current->flags&pf_randomize)&&(randomize_va_space>1)){
current->mm->brk=current->mm->start_brk=
arch_randomize_brk(current->mm);
#ifdefconfig_compat_brk
current->brk_randomized=1;
#endif
}
#endif
if(current->personality&mmap_page_zero){
/*whythis,youask?wellsvr4mapspage0asread-only,
andsomeapplicationsdependuponthisbehavior.
sincewedonothavethepowertorecompilethese,we
emulatethesvr4behavior.sigh.*/
error=vm_mmap(null,0,page_size,prot_read|prot_exec,
map_fixed|map_private,0);
}
#ifdefelf_plat_init
/*
*theabimayspecifythatcertainregistersbesetupinspecial
*ways(oni386%edxistheaddressofadt_finifunction,for
*example.inaddition,itmayalsospecify(eg,powerpc64elf)
*thatthee_entryfieldistheaddressofthefunctiondescriptor
*forthestartuproutine,ratherthantheaddressofthestartup
*routineitself.thismacroperformswhateverinitializationto
*theregsstructureisrequiredaswellasanyrelocationstothe
*functiondescriptorentrieswhenexecutingdynamicallylinksapps.
*/
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:
returnretval;
/*errorcleanup*/
out_free_dentry:
allow_write_access(interpreter);
if(interpreter)
fput(interpreter);
out_free_interp:
kfree(elf_interpreter);
out_free_ph:
kfree(elf_phdata);
gotoout;
}
总结:
此处要重点区分理解 elf header和programheader的概念。
1>elf头描述整个程序的信息。
praogramheader:每个程序段(比如代码段、bss段、数据段等)都有一个这样的头部信息,用来描述这个程序段在文件中的大小,位置 以及放到内存上的大小和位置信息。
程序段的头部信息,保存在文件的e_phoff处,且程序段个数为e_phnum个,如例子中为9个;
2>加载可执行的elf文件。do_execve_common->search_binary_handler
/*
load elfload_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 plaincopy
magic:7f454c46010101000000000000000000
class:elf32
data:2'scomplement,littleendian
version:1(current)
os/abi:unix-systemv
abiversion:0
type:exec(executablefile)
machine:arm
version:0x1
entrypointaddress:0x32cfd
startofprogramheaders:52(bytesintofile)
startofsectionheaders:20061364(bytesintofile)
flags:0x5000402,hasentrypoint,version5eabi,
sizeofthisheader:52(bytes)
sizeofprogramheaders:32(bytes)
numberofprogramheaders:9
sizeofsectionheaders:40(bytes)
numberofsectionheaders:28
sectionheaderstringtableindex:27
section headers: [25]bss段即未初始化全局变量和静态变量保存地;查找对应代码段和bss段地址需要参考这个头信息;
[cpp]view plaincopy
[nr]nametypeaddroffsizeesflglkinfal
[0]null0000000000000000000000000
[1].interpprogbits0000815400015400001900a001
[2].note.abi-tagnote0000817000017000002000a004
[3].note.gnu.build-inote0000819000019000002400a004
[4].hashhash000081b40001b400245c04a504
[5].dynsymdynsym0000a6100026100050e010a614
[6].dynstrstrtab0000f6f00076f0006ea400a001
[7].gnu.versionversym0001659400e594000a1c02a502
[8].gnu.version_rverneed00016fb000efb000018000a684
[9].rel.dynrel0001713000f1300001a008a504
[10].rel.pltrel000172d000f2d00015e008a5124
[11].initprogbits000188b00108b000000c00ax004
[12].pltprogbits000188bc0108bc00230004ax004
[13].textprogbits0001ac00012c00822c1800ax00256
[14].finiprogbits0083d81883581800000800ax004
[15].rodataprogbits0083d8208358202b36d800a008
[16].eh_frameprogbits00bb01dcba81dc00000400a004
[17].tbssnobits00bb81e0ba81e000000400wat004
[18].init_arrayinit_array00bb81e0ba81e0000b0c00wa004
[19].fini_arrayfini_array00bb8cecba8cec00000400wa004
[20].jcrprogbits00bb8cf0ba8cf000000400wa004
[21].data.rel.roprogbits00bb8cf8ba8cf8002a0000wa008
[22].dynamicdynamic00bbb6f8bab6f800017808wa604
[23].gotprogbits00bbb870bab8700012e004wa004
[24].dataprogbits00bbcb50bacb5077503400wa008
[25].bssnobits01331b881321b84417f88400wa008
[26].arm.attributesarm_attributes000000001321b8400003900001
[27].shstrtabstrtab000000001321bbd0000f500001
eytoflags:
w(write),a(alloc),x(execute),m(merge),s(strings)
i(info),l(linkorder),g(group),t(tls),e(exclude),x(unknown)
o(extraosprocessingrequired)o(osspecific),p(processorspecific)
there are no section groups in this file.
program headers: 注意load表示需要加载入内存的程序段
type offset virtaddrphysaddr 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 0x00bb81e00x7799a4 0x48f922c rw0x8000
->.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
[cpp]view plaincopy
00008000-00009000r-xp0000000000:0e23330821/home/snmpd-代码段
size:4kb
rss:4kb
pss:4kb
shared_clean:0kb
shared_dirty:0kb
private_clean:4kb
private_dirty:0kb
referenced:4kb
anonymous:0kb
anonhugepages:0kb
swap:0kb
kernelpagesize:4kb
mmupagesize:4kb
locked:0kb
vmflags:rdexmrmwmedw
00010000-00011000rw-p0000000000:0e23330821/home/snmpd–数据段/bss段
size:4kb
rss:4kb
pss:4kb
shared_clean:0kb
shared_dirty:0kb
private_clean:0kb
private_dirty:4kb
referenced:4kb
anonymous:4kb
anonhugepages:0kb
swap:0kb
kernelpagesize:4kb
mmupagesize:4kb
locked:0kb
vmflags:rdwrmrmwmedwac
0103d000-01060000rw-p0000000000:000[heap]
size:140kb
rss:8kb
pss:8kb
shared_clean:0kb
shared_dirty:0kb
private_clean:0kb
private_dirty:8kb
referenced:8kb
anonymous:8kb
anonhugepages:0kb
swap:0kb
kernelpagesize:4kb
mmupagesize:4kb
locked:0kb
vmflags:rdwrmrmwmeac
76dff000-76ef6000r-xp000000001f:05154/lib/libc-2.19-2014.06.so
size:988kb
rss:236kb
pss:34kb
shared_clean:236kb
shared_dirty:0kb
private_clean:0kb
private_dirty:0kb
referenced:236kb
anonymous:0kb
anonhugepages:0kb
swap:0kb
kernelpagesize:4kb
mmupagesize:4kb
locked:0kb
vmflags:rdexmrmwme
【动态库加载】
对于同一个动态库来说,不同进程中的vma区间不同,但对应相同的文件页,所以一旦动态库被一个进程加载到了内存中,其他进程不用再次从flash上加载,也就是说这个动态库是共享的.
不同进程中同一动态库对应的虚拟地址虽然不同,但动态库加载进内存的物理偏移地址是相同的.当进程访问动态库中的一个函数时,这个函数的地址如果未经过分页映射,即有可能初次访问,则触发缺页异常,文件页的缺页异常处理流程会根据这个函数在动态库中的偏移地址(即时不同进程,这个偏移地址也是相同的)找到对应文件页,并判断是否需要从flash中读数据到该文件页.
1 加载动态库的系统调用:sys_userlib->load_shlib=load_elf_library
2 查找动态库的bss段信息:binfmt_elf.c:
/*file表示动态库文件;shdata输出变量,区section头部信息*/
[cpp]view plaincopy
intload_elf_library_section(structfile*file,structelf_shdr*shdata)
{
structelf_shdr*elf_shdata;
structelf_shdr*eppnt;
unsignedlongelf_bss,bss,len;
intretval,error,i,j;
structelfhdrelf_ex;
/*读取elf头*/
retval=kernel_read(file,0,(char*)&elf_ex,sizeof(elf_ex));
/*所有section头的总大小*/
j=sizeof(structelf_shdr)*elf_ex.e_shnum;
elf_shdata=kmalloc(j,gfp_kernel);
eppnt=elf_shdata;
/*读取sectionheader*/
retval=kernel_read(file,elf_ex.e_shoff,(char*)eppnt,j);
for(j=0,i=0;i
{
if(sh_nobits==eppnt->sh_type)
{
printk(bsssection:\n);
memcpy(shdata,eppnt,sizeof(structelf_shdr));
}
dump_elf_shdr(eppnt);//打印section头部信息
eppnt++;
}
kfree(elf_shdata);
returnerror;
}
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原理简析
【总结】
本文介绍了可执行文件和动态库加载过程,举例说明了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替换掉了.

关于对产品进行EMC兼容性的测试和认证方法浅析
物联网和5G技术对于城市智能化发展的影响
高性能低功耗LED电源芯片SM7012的PWM应用
在三频无线手机中使用MAX2308 IF接收器IC应用三个IF滤波器
人脸识别+自动售票机助力智慧景区的建设
Linux系统ELF程序的执行过程
办公空间照明融入互联网文化是种什么体验?
电机能量回收原理
蓝牙耳机哪个好?2021年游戏蓝牙耳机排行榜
上半年全球风险投资额缩水48%至1739亿美元 近四分之一流向AI初创公司
存储芯片巨头竞逐HBM 新品发布成美光股价助推器
渠道、行业市场SSD最新报价
体外无线供电技术 可望推动全新医疗应用
电池仿真器的功能和应用
java环境下如何使用hash函数
华为向外界透露表示华为是一家100%由员工持有的民营企业
基于PCIe-Native机制的热插拔
ROHS指令(标准)中实施环境管理物质适用范围
iphone14配置确认 iphone14什么配置
华为云等保合规方案,助力企业最快30天过等保