关注和分享VPS主机优惠活动
www.vpsmr.com
腾讯云双11优惠活动

你学过Linux内核加载ELF文件的源代码分析吗?(linux运行elf文件)

I源代码版本1)版本:V6.3-rc7,x862)elf文件加载源代码:fs/binfmt _ elf.c。

二、Linux可执行文件注册Linux支持多种不同格式的可执行程序,这些可执行程序的加载方式由linux\binfmts.h文件中的linux_binfmt结构定义:

struct linux_binfmt {
结构列表_ head lh
结构模块*模块;
int(* load _ binary)(struct Linux _ bin PRM *);
int(* load _ shlib)(struct file *);
#ifdef CONFIG_COREDUMP
int(* core _ dump)(struct core dump _ params * cprm);
未签名的龙敏_核心转储;/*最小转储大小*/
#endif
} _ _ randomize _ layout结构在3个可执行程序中定义了不同的加载模式:

装载模式

评论

load_binary

读取可执行文件的内容,加载当前进程,建立新的执行环境。

load_shlib

将共享库动态加载到现有进程中。

核心转储

将当前进程的执行上下文存储在核心文件中。

系统支持的每个可执行文件对应一个linux_binfmt对象,统一注册在一个链表中,链表由register_binfmt和unregister_binfmt函数编辑。内核在执行可执行程序时,通过list_for_each_enrty遍历链表中注册的linux_binfmt对象,并以正确的方式加载。elf文件的linux_binfmt对象结构如下,定义了elf文件由load_elf_binary函数加载:

静态结构linux_binfmt elf_format = {
。模块= THIS _模块,
。load_binary = load_elf_binary,
。load_shlib = load_elf_library,
#ifdef CONFIG_COREDUMP
。核心转储= elf核心转储,
。min_coredump = ELF_EXEC_PAGESIZE,
#endif
};三。load_elf_binary函数分析1。文件格式验证struct elf HDR * elf _ ex =(struct elf HDR *)bprm-> buf;

retval =-eno exec;
/*首先是一些简单的一致性检查*/
if(memcmp(elf _ ex-& gt;e_ident,ELFMAG,SELFMAG)!= 0)
外出;

if(elf _ ex-& gt;e_type!= ET _ EXEC & amp& ampelf _ ex-& gt;e_type!= ET_DYN)
外出;
如果(!elf_check_arch(elf_ex))
外出;
if (elf_check_fdpic(elf_ex))
外出;
如果(!bprm-& gt;文件-& gt;f _ op-& gt;mmap)
外出;程序首先读取e_ident中的幻数并检查它。elf_ident是elf文件头部的一个16字节数组,不区分架构和系统位。e_ident的前四个字节固定为\0x7felf,通过检查该位可以判断是否为elf文件。然后确定该文件是可执行文件还是动态链接文件。目前ELF文件主要有四种格式,分别是可重定位文件(ET_REL)、可执行文件(ET_EXEC)、共享对象文件(ET_DYN)和核心文件(ET_CORE)。load_elf_binary函数只负责解析exec和dyn文件。最后,对文件相关系统架构等必要项目进行了分析。

2.读取程序头静态结构elf _ phdr * load _ elf _ phdrs(const struct elf HDR * elf _ ex
结构文件*elf_file)
{
struct elf _ phdr * elf _ ph data = NULL;
int retval =-1;
无符号int大小;

/*
*如果这个结构的大小已经改变,那么就打住,因为
*我们会做错事。
*/
if(elf _ ex-& gt;e_phentsize!= sizeof(struct elf_phdr))
外出;

/*完整性检查程序头的数量…*/
/* …以及它们的总大小。*/
size = sizeof(struct elf _ phdr)* elf _ ex-& gt;e _ phnum
if(size = = 0 | | size & gt;65536 | | size & gtELF_MIN_ALIGN)
外出;

elf_phdata = kmalloc(size,GFP _ KERNEL);
如果(!elf_phdata)
外出;

/*读入程序头*/
retval = elf_read(elf_file,elf_phdata,size,elf _ ex-& gt;e _ phoff);

出局:
if (retval) {
kfree(elf _ phdata);
elf _ phdata = NULL
}
返回elf _ phdata
}程序头是描述与程序执行直接相关的目标文件结构的信息,用于定位文件中各段的映像,同时包含为程序创建进程映像所必需的其他信息。

3.读取解释器段elf _ ppnt = elf _ phdata
for(I = 0;我& ltelf _ ex-& gt;e _ phnumi++,elf_ppnt++) {
char * elf _ interpreter

if(elf _ ppnt-& gt;p_type == PT_GNU_PROPERTY) {
elf _ property _ phdata = elf _ ppnt
继续;
}

if(elf _ ppnt-& gt;p_type!= PT_INTERP)
继续;

/*
*这是用于共享库的程序解释器-
*现在假设这是一个a.out格式的二进制文件。
*/
retval =-eno exec;
if(elf _ ppnt-& gt;p _ filesz & gtPATH _ MAX | | elf _ ppnt-& gt;p _ filesz & lt2)
goto out _ free _ ph

retval =-eno mem;
elf _ interpreter = kmalloc(elf _ ppnt-& gt;p_filesz,GFP _ KERNEL);
如果(!elf _解释器)
goto out _ free _ ph

retval = elf _ read(bprm-& gt;文件,elf_interpreter,elf _ ppnt-& gt;p_filesz,
elf _ ppnt-& gt;p _ offset);
if(retval & lt;0)
goto out _ free _ interp
/*确保路径以NULL结尾*/
retval =-eno exec;
if(elf _ interpreter[elf _ ppnt-& gt;p_filesz – 1]!= ‘\0’)
goto out _ free _ interp

解释器= open _ exec(elf _ interpreter);
kfree(elf _ interpreter);
retval = PTR_ERR(解释器);
if (IS_ERR(解释器))
goto out _ free _ ph

/*
*如果二进制文件不可读,则强制执行mm-& gt;可转储= 0
*不考虑解释器的权限。
*/
would_dump(bprm,解释器);

interp _ elf _ ex = kmalloc(sizeof(* interp _ elf _ ex),GFP _ KERNEL);
如果(!interp_elf_ex) {
retval =-eno mem;
goto out _ free _ file
}

/*获取exec头*/
retval = elf_read(解释器,interp_elf_ex,
sizeof(*interp_elf_ex),0);
if(retval & lt;0)
goto out _ free _ dentry

打破;

输出_自由_解释:
kfree(elf _ interpreter);
goto out _ free _ ph
}如果程序需要动态链接,需要加载解释器段(PT_INTERP)。程序遍历所有的程序头,识别解释程序段,并读取该段的内容。解释器段实际上是一个指示解释器程序文件路径的字符串,内核根据字符串指向的文件,使用open_exec函数打开解释器。

4.获取堆栈可执行属性和其他定制信息的elf _ ppnt = elf _ phdata
for(I = 0;我& ltelf _ ex-& gt;e _ phnumi++,elf_ppnt++)
开关(elf _ ppnt-& gt;p_type) {
案例PT_GNU_STACK:
if(elf _ ppnt-& gt;旗帜& ampPF_X)
executive _ stack = ex stack _ ENABLE _ X;
其他
executable _ stack = ex stack _ DISABLE _ X;
打破;

案例PT_LOPROC…PT_HIPROC:
retval = arch_elf_pt_proc(elf_ex,elf_ppnt,
bprm-& gt;文件,假,
& amparch _ state);
如果(返回)
goto out _ free _ dentry
打破;
}同样通过for循环遍历,如果识别出堆栈属性段(PT_GNU_STACK),则根据程序头中的p_flags标志位确定堆栈的可执行属性。如果处理器特定的语义段(PT_LOPROC和PT _ HIROPOC之间)被识别,则调用ARCH _ ELF _ PT_HIPROC函数来完成相应的配置。

5.读取解释器if(解释器){
retval =-ELIBBAD;
/*不是ELF解释器*/
if(memcmp(interp _ elf _ ex-& gt;e_ident,ELFMAG,SELFMAG)!= 0)
goto out _ free _ dentry
/*验证解释器具有有效的arch */
如果(!elf _ check _ arch(interp _ elf _ ex)| |
elf_check_fdpic(interp_elf_ex))
goto out _ free _ dentry

/*加载解释程序头*/
interp _ elf _ ph data = load _ elf _ phdrs(interp _ elf _ ex,
口译员);
如果(!interp_elf_phdata)
goto out _ free _ dentry解释器也是一个elf文件,在这里解释器被读取用于后续操作。

6.加载程序段for(i = 0,elf _ ppnt = elf _ phdata
我& ltelf _ ex-& gt;e _ phnumi++,elf_ppnt++) {
int elf_prot,elf _ flags
无符号长k,vaddr
无符号长整型total _ size = 0;
无符号长对齐;

if(elf _ ppnt-& gt;p_type!=点负载)
继续;加载PT_LOAD类型的所有段。当处理第一个PT_LOAD段时,如果文件类型为dyn,则需要随机化地址。随机化时,也需要区分解释器或其他常见的so文件。对于解释器来说,为了避免程序之间的冲突,程序总是计算ELF_ET_DYN_BASE的偏移量并加载它。

如果(!first_pt_load) {
elf _ flags | = MAP _ FIXED
} else if(elf _ ex-& gt;e_type == ET_EXEC) {
elf _ flags | = MAP _ FIXED _ no replace;
} else if(elf _ ex-& gt;e_type == ET_DYN) {
if(解释器){
load _ bias = ELF _ ET _ DYN _ BASE
如果(当前-& gt;旗帜& ampPF _随机化)
load _ bias+= arch _ mmap _ rnd();
alignment = maximum _ alignment(elf _ ph data,elf _ ex-& gt;e _ phnum);
if(对齐)
负载偏差& amp= ~(alignment-1);
elf _ flags | = MAP _ FIXED _ no replace;
}否则
load _ bias = 0;

load _ bias = ELF _ PAGESTART(load _ bias-vaddr);
total _ size = total _ mapping _ size(elf _ ph data,
elf _ ex-& gt;e _ phnum);
如果(!total_size) {
retval =-EINVAL;
goto out _ free _ dentry
}
一切准备就绪后,通过elf_map函数建立用户空间的虚拟地址空间与目标镜像文件中段的映射。

error = elf _ map(bprm-& gt;file,load_bias + vaddr,elf_ppnt,
elf_prot,elf_flags,total _ size);7.加载程序入口地址if(解释器){
elf _ entry = load _ elf _ interp(interp _ elf _ ex,
翻译,
load_bias,interp_elf_phdata,
& amparch _ state);
如果(!IS_ERR_VALUE(elf_entry)) {
/*
* load_elf_interp()返回重定位
*调整
*/
interp _ load _ addr = elf _ entry
elf _ entry+= interp _ elf _ ex-& gt;e _ entry
}
if (BAD_ADDR(elf_entry)) {
retval = IS_ERR_VALUE(elf_entry)?
(int)elf _ entry:-EINVAL;
goto out _ free _ dentry
}
reloc _ func _ desc = interp _ load _ addr;

允许写访问(解释器);
fput(解释器);

kfree(interp _ elf _ ex);
kfree(interp _ elf _ ph data);
}否则{
elf _ entry = e _ entry
if (BAD_ADDR(elf_entry)) {
retval =-EINVAL;
goto out _ free _ dentry
}
}对于需要解释器的程序,需要通过load_elf_interp函数加载解释器的镜像,将程序入口点设置为解释器的入口地址。对于不需要解释器的文件,可以直接读取elf_header中入口点的虚拟地址。

8.添加参数和环境变量RETVAL = CREATE _ ELF _ TABLES (BPRM,ELF _ EX,INTERP _ LOAD _ ADDR,
e_entry,phdr _ addr);
if(retval & lt;0)
外出;

mm =当前-& gt;mm;
mm-& gt;end_code =结束_ code;
mm-& gt;start_code =开始_ code;
mm-& gt;开始数据=开始数据;本文作者:齐小奎,转载请注明出处。

未经允许不得转载:国外VPS主机测评 » 你学过Linux内核加载ELF文件的源代码分析吗?(linux运行elf文件)
任何商家都有倒闭和跑路可能,本站仅分享优惠活动及信息,购买前请自行斟酌,衡量评估风险,自负责任。数据勤备份是最佳选择!友情提示

评论 抢沙发

评论前必须登录!