ld.so分析3
内核中load_elf_binary如何执行
1.load_elf_binary
fs/binfmt_elf.c
/*
* These are the functions used to load ELF style executables and shared
* libraries. There is no binary dependent code anywhere else.
*/
#define INTERPRETER_NONE 0
#define INTERPRETER_AOUT 1
#define INTERPRETER_ELF 2
static int load_elf_binary(struct linux_binprm * bprm, struct pt_regs * regs)
{
struct file *interpreter = NULL; /* to shut gcc up */
unsigned long load_addr = 0, load_bias;
int load_addr_set = 0;
char * elf_interpreter = NULL;
unsigned int interpreter_type = INTERPRETER_NONE;
unsigned char ibcs2_interpreter = 0;
mm_segment_t old_fs;
unsigned long error;
struct elf_phdr * elf_ppnt, *elf_phdata;
unsigned long elf_bss, k, elf_brk;
int elf_exec_fileno;
int retval, size, i;
unsigned long elf_entry, interp_load_addr = 0;
unsigned long start_code, end_code, start_data, end_data;
struct elfhdr elf_ex;
struct elfhdr interp_elf_ex;
struct exec interp_ex;
char passed_fileno[6];
/* Get the exec-header */
elf_ex = *((struct elfhdr *) bprm->buf);
retval = -ENOEXEC;
/* First of all, some simple consistency checks */
//检查magic
if (memcmp(elf_ex.e_ident, ELFMAG, SELFMAG) != 0)
goto out;
//既非可执行文件又非动态链接库,动态链接库也可直接执行
if (elf_ex.e_type != ET_EXEC && elf_ex.e_type != ET_DYN)
goto out;
if (!elf_check_arch(&elf_ex))//体系结构检查
goto out;
if (!bprm->file->f_op||!bprm->file->f_op->mmap)//不能mmap,error
goto out;
/* Now read in all of the header information */
retval = -ENOMEM;
/*
typedef struct elf32_hdr{
unsigned char e_ident[EI_NIDENT];
Elf32_Half e_type;
Elf32_Half e_machine;
Elf32_Word e_version;
Elf32_Addr e_entry;
Elf32_Off e_phoff;
Elf32_Off e_shoff;
Elf32_Word e_flags;
Elf32_Half e_ehsize;
Elf32_Half e_phentsize;
Elf32_Half e_phnum;
Elf32_Half e_shentsize;
Elf32_Half e_shnum;
Elf32_Half e_shstrndx;
} Elf32_Ehdr;
*/
//e_phentsize 该成员保存着在文件的程序头表(program header table)
//中一个入口的大小(以字节计数)。所有的入口都是同样的大小。
//e_phnum 该成员保存着在程序头表中入口的个数。因此,e_phentsize和e_phnum
//的乘机就是表的大小(以字节计数).假如没有程序头表(program header table),
//e_phnum变量为0。
size = elf_ex.e_phentsize * elf_ex.e_phnum;
if (size > 65536)
goto out;
elf_phdata = (struct elf_phdr *) kmalloc(size, GFP_KERNEL);
if (!elf_phdata)
goto out;
//读入 program headers
retval = kernel_read(bprm->file, elf_ex.e_phoff, (char *) elf_phdata, size);
if (retval < 0)
goto out_free_ph;
retval = get_unused_fd();
if (retval < 0)
goto out_free_ph;
get_file(bprm->file);
//保存原始打开文件
fd_install(elf_exec_fileno = retval, bprm->file);//flush old exec不会关闭
elf_ppnt = elf_phdata;//program headers
elf_bss = 0;
elf_brk = 0;
start_code = ~0UL;// -1
end_code = 0;
start_data = 0;
end_data = 0;
for (i = 0; i < elf_ex.e_phnum; i++) {//处理每一个program headers,寻找PT_INTERP
if (elf_ppnt->p_type == PT_INTERP) {
retval = -EINVAL;
if (elf_interpreter)//已经有interpreter
goto out_free_dentry;
/* This is the program interpreter used for
* shared libraries - for now assume that this
* is an a.out format binary
*/
/*
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;
*/
retval = -ENOMEM;
elf_interpreter = (char *) kmalloc(elf_ppnt->p_filesz,
GFP_KERNEL);
if (!elf_interpreter)
goto out_free_file;
retval = kernel_read(bprm->file, elf_ppnt->p_offset,
elf_interpreter,
elf_ppnt->p_filesz);//读入interp
if (retval < 0)
goto out_free_interp;
/* If the program interpreter is one of these two,
* then assume an iBCS2 image. Otherwise assume
* a native linux image.
redhat 7.2 中是 /lib/ld-linux.so.2
*/
if (strcmp(elf_interpreter,"/usr/lib/libc.so.1") == 0 ||
strcmp(elf_interpreter,"/usr/lib/ld.so.1") == 0)
ibcs2_interpreter = 1;
#if 0
printk("Using ELF interpreter %s\n", elf_interpreter);
#endif
#ifdef __sparc__
if (ibcs2_interpreter) {
unsigned long old_pers = current->personality;
struct exec_domain *old_domain = current->exec_domain;
struct exec_domain *new_domain;
struct fs_struct *old_fs = current->fs, *new_fs;
get_exec_domain(old_domain);
atomic_inc(&old_fs->count);
set_personality(PER_SVR4);
interpreter = open_exec(elf_interpreter);
new_domain = current->exec_domain;
new_fs = current->fs;
current->personality = old_pers;
current->exec_domain = old_domain;
current->fs = old_fs;
put_exec_domain(new_domain);
put_fs_struct(new_fs);
} else
#endif
{
interpreter = open_exec(elf_interpreter);//打开/lib/ld-linux.so.2
}
retval = PTR_ERR(interpreter);
if (IS_ERR(interpreter))
goto out_free_interp;
retval = kernel_read(interpreter, 0, bprm->buf, BINPRM_BUF_SIZE);//读入头部
if (retval < 0)
goto out_free_dentry;
/* Get the exec headers */
interp_ex = *((struct exec *) bprm->buf);//可能是a.out
interp_elf_ex = *((struct elfhdr *) bprm->buf);//可能是elf
}
elf_ppnt++;
}
/* Some simple consistency checks for the interpreter */
if (elf_interpreter) {//有interp,执行/lib/ld-linux.so.2时,没有,或静态链接的可执行文件也没有
interpreter_type = INTERPRETER_ELF | INTERPRETER_AOUT;
/* Now figure out which format our binary is */
if ((N_MAGIC(interp_ex) != OMAGIC) &&
(N_MAGIC(interp_ex) != ZMAGIC) &&
(N_MAGIC(interp_ex) != QMAGIC))
interpreter_type = INTERPRETER_ELF;//是interp elf
if (memcmp(interp_elf_ex.e_ident, ELFMAG, SELFMAG) != 0)
interpreter_type &= ~INTERPRETER_ELF;//是interp aout
retval = -ELIBBAD;
if (!interpreter_type)
goto out_free_dentry;
/* Make sure only one type was selected */
if ((interpreter_type & INTERPRETER_ELF) &&
interpreter_type != INTERPRETER_ELF) {
printk(KERN_WARNING "ELF: Ambiguous type, using ELF\n");
interpreter_type = INTERPRETER_ELF;
}
}
/* OK, we are done with that, now set up the arg stuff,
and then start this sucker up */
if (!bprm->sh_bang) {
char * passed_p;
if (interpreter_type == INTERPRETER_AOUT) {//a.out
sprintf(passed_fileno, "%d", elf_exec_fileno);//原始打开文件号
passed_p = passed_fileno;
if (elf_interpreter) {//interp文件名
retval = copy_strings_kernel(1,&passed_p,bprm);
if (retval)
goto out_free_dentry;
bprm->argc++;//打开文件号作为参数
}
}
}
/* 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->start_data = 0;
current->mm->end_data = 0;
current->mm->end_code = 0;
current->mm->mmap = NULL;
current->flags &= ~PF_FORKNOEXEC;
elf_entry = (unsigned long) elf_ex.e_entry;//原文件代码入口
/* Do this immediately, since STACK_TOP as used in setup_arg_pages
may depend on the personality. */
SET_PERSONALITY(elf_ex, ibcs2_interpreter);
/* Do this so that we can load the interpreter, if need be. We will
change some of these later */
current->mm->rss = 0;
setup_arg_pages(bprm); /* XXX: check error */
2.setup_arg_pages
load_elf_binayr->setup_arg_page
fs/exec.c
//把arg pages页和进程挂钩
int setup_arg_pages(struct linux_binprm *bprm)
{
unsigned long stack_base;
struct vm_area_struct *mpnt;
int i;
stack_base = STACK_TOP - MAX_ARG_PAGES*PAGE_SIZE;//堆栈基址
bprm->p += stack_base;//变换成地址
if (bprm->loader)
bprm->loader += stack_base;
bprm->exec += stack_base;
mpnt = kmem_cache_alloc(vm_area_cachep, SLAB_KERNEL);//为堆栈段分配vm_area_struct结构
if (!mpnt)
return -ENOMEM;
down(¤t->mm->mmap_sem);
{
mpnt->vm_mm = current->mm;
mpnt->vm_start = PAGE_MASK & (unsigned long) bprm->p;//下对齐
mpnt->vm_end = STACK_TOP;
mpnt->vm_page_prot = PAGE_COPY;
mpnt->vm_flags = VM_STACK_FLAGS;
mpnt->vm_ops = NULL;
mpnt->vm_pgoff = 0;
mpnt->vm_file = NULL;
mpnt->vm_private_data = (void *) 0;
insert_vm_struct(current->mm, mpnt);
current->mm->total_vm = (mpnt->vm_end - mpnt->vm_start) >> PAGE_SHIFT;
}
for (i = 0 ; i < MAX_ARG_PAGES ; i++) {
struct page *page = bprm->page[i];
if (page) {
bprm->page[i] = NULL;
current->mm->rss++;//驻内页
put_dirty_page(current,page,stack_base);//该页挂入进程空间
}
stack_base += PAGE_SIZE;
}
up(¤t->mm->mmap_sem);
return 0;
}
3.返回到load_elf_binary
current->mm->start_stack = bprm->p;
/* 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. */
//普通可执行文件load_bias=0;动态链接库load_bias=0x8000 0000,即2G处(单独执行时,给ld-linux.so.2让路)
/*
例如/lib/ld-2.3.2.so执行时的maps如下
[root@mail /proc/30019]# cat maps
80000000-80015000 r-xp 00000000 08:01 272070 /lib/ld-2.3.2.so
80015000-80016000 rw-p 00014000 08:01 272070 /lib/ld-2.3.2.so
bfffe000-c0000000 rwxp fffff000 00:00 0
/lib/libc-2.3.2.so执行时的maps如下
[root@mail /proc/30097]# cat /proc/14541/maps
40000000-40015000 r-xp 00000000 08:01 272070 /lib/ld-2.3.2.so
40015000-40016000 rw-p 00014000 08:01 272070 /lib/ld-2.3.2.so
80000000-80133000 r-xp 00000000 08:01 272077 /lib/libc-2.3.2.so
80133000-80137000 rw-p 00132000 08:01 272077 /lib/libc-2.3.2.so
80137000-80139000 rwxp 00000000 00:00 0
bfffe000-c0000000 rwxp fffff000 00:00 0
*/
load_bias = ELF_PAGESTART(elf_ex.e_type==ET_DYN ? ELF_ET_DYN_BASE : 0);
/* Now we do a little grungy work by mmaping the ELF image into
the correct location in memory. At this point, we assume that
the image should be loaded at fixed address, not at a variable
address. */
old_fs = get_fs();
set_fs(get_ds());
for(i = 0, elf_ppnt = elf_phdata; i < elf_ex.e_phnum; i++, elf_ppnt++) {
//处理每一个program headers
/*
typedef struct elf32_phdr{
Elf32_Word p_type;
Elf32_Off p_offset;//该成员给出了该段的驻留位置相对于文件开始处的偏移。
Elf32_Addr p_vaddr;//该成员给出了该段在内存中的首字节地址。(连接器推荐的加载基址)
Elf32_Addr p_paddr;
Elf32_Word p_filesz;//该成员给出了文件映像中该段的字节数;它可能是 0 。
Elf32_Word p_memsz;//该成员给出了内存映像中该段的字节数;它可能是 0 。
Elf32_Word p_flags;//该成员给出了和该段相关的标志。定义的标志值如下所述。
Elf32_Word p_align;
} Elf32_Phdr;
*/
int elf_prot = 0, elf_flags;
unsigned long vaddr;
if (elf_ppnt->p_type != PT_LOAD)//必须是PT_LOAD
continue;
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 (elf_ex.e_type == ET_EXEC || load_addr_set) {
//是可执行文件或者起始加载地址已设置
elf_flags |= MAP_FIXED;
}
error = elf_map(bprm->file, load_bias + vaddr, elf_ppnt, elf_prot, elf_flags);
//不判断出错 ???
/*
elf文件有两种视图,一种节表,是从程序编译连接的角度看。一种是程序头,是从程序执行的角度看。
举例看看这两种视图的关系
[root@mail /proc/30097]# readelf -l /bin/ls
Elf file type is EXEC (Executable file)
Entry point 0x8049690
There are 7 program headers, starting at offset 52
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
PHDR 0x000034 0x08048034 0x08048034 0x000e0 0x000e0 R E 0x4
INTERP 0x000114 0x08048114 0x08048114 0x00013 0x00013 R 0x1
[Requesting program interpreter: /lib/ld-linux.so.2]
LOAD 0x000000 0x08048000 0x08048000 0x0fa98 0x0fa98 R E 0x1000
LOAD 0x010000 0x08058000 0x08058000 0x00348 0x006c8 RW 0x1000
DYNAMIC 0x010114 0x08058114 0x08058114 0x000d0 0x000d0 RW 0x4
NOTE 0x000128 0x08048128 0x08048128 0x00020 0x00020 R 0x4
GNU_EH_FRAME 0x00f960 0x08057960 0x08057960 0x0002c 0x0002c R 0x4
Section to Segment mapping:
Segment Sections...
00
01 .interp
02 .interp .note.ABI-tag .hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .rodata
.eh_frame_hdr .eh_frame
03 .data .dynamic .ctors .dtors .jcr .got .bss
04 .dynamic
05 .note.ABI-tag
06 .eh_frame_hdr
注意下面的Section to Segment mapping,说明了每个程序头包含了哪些节。我们关心的是代码段程序头和数据段程序头。
LOAD 0x000000 0x08048000 0x08048000 0x0fa98 0x0fa98 R E 0x1000
LOAD 0x010000 0x08058000 0x08058000 0x00348 0x006c8 RW 0x1000
分别对应
02 .interp .note.ABI-tag .hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .rodata
.eh_frame_hdr .eh_frame
03 .data .dynamic .ctors .dtors .jcr .got .bss
代码段开始文件地址是0,开始虚拟地址是0x8048000,文件大小是0xfa98,内存大小是0xfa98,flag是可读可执行,对齐大小是4k
数据段开始文件地址是0x10000,开始虚拟地址0x8058000,文件大小是0x348,内存大小是0x6c8,flag是可读可写,对齐大小是4
k
下面列出节表
[zws@mail /proc/1]$readelf -S /bin/ls
There are 26 section headers, starting at offset 0x10444:
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .interp PROGBITS 08048114 000114 000013 00 A 0 0 1
[ 2] .note.ABI-tag NOTE 08048128 000128 000020 00 A 0 0 4
[ 3] .hash HASH 08048148 000148 00028c 04 A 4 0 4
[ 4] .dynsym DYNSYM 080483d4 0003d4 0005e0 10 A 5 1 4
[ 5] .dynstr STRTAB 080489b4 0009b4 0003ea 00 A 0 0 1
[ 6] .gnu.version VERSYM 08048d9e 000d9e 0000bc 02 A 4 0 2
[ 7] .gnu.version_r VERNEED 08048e5c 000e5c 000070 00 A 5 1 4
[ 8] .rel.dyn REL 08048ecc 000ecc 000028 08 A 4 0 4
[ 9] .rel.plt REL 08048ef4 000ef4 000278 08 A 4 11 4
[10] .init PROGBITS 0804916c 00116c 000017 00 AX 0 0 4
[11] .plt PROGBITS 08049184 001184 000500 04 AX 0 0 4
[12] .text PROGBITS 08049690 001690 00ab4c 00 AX 0 0 16
[13] .fini PROGBITS 080541dc 00c1dc 00001b 00 AX 0 0 4
[14] .rodata PROGBITS 08054200 00c200 003760 00 A 0 0 32
[15] .eh_frame_hdr PROGBITS 08057960 00f960 00002c 00 A 0 0 4
[16] .eh_frame PROGBITS 0805798c 00f98c 00010c 00 A 0 0 4
[17] .data PROGBITS 08058000 010000 000114 00 WA 0 0 32
[18] .dynamic DYNAMIC 08058114 010114 0000d0 08 WA 5 0 4
[19] .ctors PROGBITS 080581e4 0101e4 000008 00 WA 0 0 4
[20] .dtors PROGBITS 080581ec 0101ec 000008 00 WA 0 0 4
[21] .jcr PROGBITS 080581f4 0101f4 000004 00 WA 0 0 4
[22] .got PROGBITS 080581f8 0101f8 000150 04 WA 0 0 4
[23] .bss NOBITS 08058360 010360 000368 00 WA 0 0 32
[24] .gnu_debuglink PROGBITS 00000000 010360 000010 00 0 0 4
[25] .shstrtab STRTAB 00000000 010370 0000d2 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings)
I (info), L (link order), G (group), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)
从开始文件地址和内存大小可看出
代码段包含的节从[0]到[16],数据段包含的节从[17]到[23],和前面的显示正好一致。
由于内存映射以页为单位,映射起始地址向下对齐到页边界,映射大小向上对齐到页边界,因此在进行内存映射的时候,
代码段映射关系是虚拟地址[0x8048000,0x8048000+0x10000)->文件偏移[0,0x10000)
数据段映射关系是虚拟地址[0x8058000,0x1000)->文件偏移[0x10000,0x10000+0x1000)
/bin/ls的文件大小是小于0x11000的,数据的映射超出了,不过没有关系,超出的部分会被当做零页分配。可见这个文件都被映
射了。显然0x8048000就是这个文件镜像加载的起始地址,这个地址后面有用。
我还注意到[23].bss节type是NOBITS,说明文件中没有对应内容,这从[24].gnu_debuglink的文件偏移和[23].bss的文件偏移相
等侧面证明。但是它却是有大小的,0x368,Flag是WA,说明可写且需要分配。
bss是未初始化节,程序中的未初始化变量都放在这个节中,由于未初始化变量的值默认都为0,因此也就不再文件中为其分配空
间了。但是到了内存中就不同了,必须为其分配空间,且清0。这一点后面还会谈到。
还有就是程序中的常量被放在.rodata节中,常量只能读,不能写,代码段是可读,可执行,因此.rodata节被插入在代码段中,没
有创建额外的程序头了。
可执行文件的一般从0x08000000(512M)开始编址,可执行文件加载时不重定位。
动态链接库的一般从0开始编址.动态链接库加载时重定位,重定位地址从0x40000000(1G)开始。
映射的时候,代码段只读映射该页,而数据段COW映射该页.因此虽然上面代码段和数据段是连续的,但是页属性是不同的。
代码段和数据段在文件中的映射可能有重叠,例如
[zws@mail ~]$ readelf -l /lib/libc-2.3.2.so
Elf file type is DYN (Shared object file)
Entry point 0x159d0
There are 7 program headers, starting at offset 52
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
PHDR 0x000034 0x00000034 0x00000034 0x000e0 0x000e0 R E 0x4
INTERP 0x1312f0 0x001312f0 0x001312f0 0x00013 0x00013 R 0x1
[Requesting program interpreter: /lib/ld-linux.so.2]
LOAD 0x000000 0x00000000 0x00000000 0x132804 0x132804 R E 0x1000
LOAD 0x132820 0x00133820 0x00133820 0x02c90 0x056c4 RW 0x1000
DYNAMIC 0x1350d4 0x001360d4 0x001360d4 0x000d8 0x000d8 RW 0x4
NOTE 0x000114 0x00000114 0x00000114 0x00020 0x00020 R 0x4
GNU_EH_FRAME 0x131304 0x00131304 0x00131304 0x0032c 0x0032c R 0x4
假设实际加载地址是x
代码段映射关系是虚拟地址[x+0,x+0+0x133000)->文件偏移[0,0x133000)
数据段映射关系是虚拟地址[x+0x133000,x+0x133000+0x6000)->文件偏移[0x132000,0x132000+0x3000)+零页[0x132000+
0x3000,0x133000+0x6000)
文件偏移有一页重叠,但这种重叠不会引起冲突和访问错误.
总之,ELF文件的节和程序头关系紧密,其中暗藏玄机,值得细细揣摩。
*/
4.计算start_code,end_code等
if (!load_addr_set) {
load_addr_set = 1;
//load_addr 计算整个镜像加载基址
/*
load_bias load_addr
ET_EXEC 0x00000000 0x08048000 /bin/ls
ET_DYN 0x80000000 0x80000000 /lib/ld-linux.so.2
*/
load_addr = (elf_ppnt->p_vaddr - elf_ppnt->p_offset);//链接器推荐的镜像加载基址,对于可执
行文件就是该地址,对于动态链接库,一般是0
if (elf_ex.e_type == ET_DYN) {//load_bias是0x80000000
load_bias += error -//动态链接库实际加载地址-内核推荐的加载地址=加载偏移(一般
是0)
ELF_PAGESTART(load_bias + vaddr);
load_addr += error;//一般load_addr==error
}
}
k = elf_ppnt->p_vaddr;//通常代码段和数据段紧挨在一起,代码段在前,数据段在后
if (k < start_code) start_code = k;//start_code 的初值为0xffffffff,定位代码段开始地址
if (start_data < k) start_data = k;//start_data初值为0,定位数据段开始
k = elf_ppnt->p_vaddr + elf_ppnt->p_filesz;//file size,不是mem size
if (k > elf_bss)//elf_bss初值为0
elf_bss = k;//计算bss起始地址
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;//mem size
if (k > elf_brk)
elf_brk = k;
//[elf_bss,elf_brk)之间是bss section
}
set_fs(old_fs);
//全部重定位
elf_entry += load_bias;
elf_bss += load_bias;//bss为初始化段开始
elf_brk += load_bias;//brk动态内存分配起始地址
start_code += load_bias;
end_code += load_bias;
start_data += load_bias;
end_data += load_bias;
if (elf_interpreter) {
//elf_entry被覆盖
if (interpreter_type == INTERPRETER_AOUT)
elf_entry = load_aout_interp(&interp_ex,
interpreter);
else
elf_entry = load_elf_interp(&interp_elf_ex,
interpreter,
&interp_load_addr);
5.load_elf_interp
/* This is much more generalized than the library routine read function,
so we keep this separate. Technically the library read function
is only provided so that we can read a.out libraries that have
an ELF header */
static unsigned long load_elf_interp(struct elfhdr * interp_elf_ex,
struct file * interpreter,
unsigned long *interp_load_addr)
{
struct elf_phdr *elf_phdata;
struct elf_phdr *eppnt;
unsigned long load_addr = 0;
int load_addr_set = 0;
unsigned long last_bss = 0, elf_bss = 0;
unsigned long error = ~0UL;
int retval, i, size;
/* First of all, some simple consistency checks */
if (interp_elf_ex->e_type != ET_EXEC &&
interp_elf_ex->e_type != ET_DYN)
goto out;
if (!elf_check_arch(interp_elf_ex))
goto out;
if (!interpreter->f_op || !interpreter->f_op->mmap)
goto out;
/*
* If the size of this structure has changed, then punt, since
* we will be doing the wrong thing.
*/
if (interp_elf_ex->e_phentsize != sizeof(struct elf_phdr))
goto out;
/* Now read in all of the header information */
size = sizeof(struct elf_phdr) * interp_elf_ex->e_phnum;
if (size > ELF_MIN_ALIGN)
goto out;
elf_phdata = (struct elf_phdr *) kmalloc(size, GFP_KERNEL);
if (!elf_phdata)
goto out;
//读入interp的prgoram header
retval = kernel_read(interpreter,interp_elf_ex->e_phoff,(char *)elf_phdata,size);
error = retval;
if (retval < 0)
goto out_close;
eppnt = elf_phdata;
for (i=0; ie_phnum; i++, eppnt++) {
if (eppnt->p_type == PT_LOAD) {
int elf_type = MAP_PRIVATE | MAP_DENYWRITE;
int elf_prot = 0;
unsigned long vaddr = 0;
unsigned long k, map_addr;
if (eppnt->p_flags & PF_R) elf_prot = PROT_READ;
if (eppnt->p_flags & PF_W) elf_prot |= PROT_WRITE;
if (eppnt->p_flags & PF_X) elf_prot |= PROT_EXEC;
vaddr = eppnt->p_vaddr;
if (interp_elf_ex->e_type == ET_EXEC || load_addr_set)
elf_type |= MAP_FIXED;
/*
readelf -l /lib/ld-linux.so.2
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
LOAD 0x000000 0x00000000 0x00000000 0x15229 0x15229 R E 0x1000
LOAD 0x015240 0x00016240 0x00016240 0x00300 0x00758 RW 0x1000
DYNAMIC 0x015490 0x00016490 0x00016490 0x000b0 0x000b0 RW 0x4
load_addr + vaddr=0,mmap将从1G处开始处映射
*/
map_addr = elf_map(interpreter, load_addr + vaddr, eppnt, elf_prot, elf_type);
if (!load_addr_set && interp_elf_ex->e_type == ET_DYN) {
//只计算ET_DYN
load_addr = map_addr - ELF_PAGESTART(vaddr);
load_addr_set = 1;
}
/*
* Find the end of the file mapping for this phdr, and keep
* track of the largest address we see for this.
*/
k = load_addr + eppnt->p_vaddr + eppnt->p_filesz;
if (k > elf_bss)//elf bss
elf_bss = k;
/*
* Do the same thing for the memory mapping - between
* elf_bss and last_bss is the bss section.
[elf_bss,last_bss)是bss section
*/
k = load_addr + eppnt->p_memsz + eppnt->p_vaddr;
if (k > last_bss)//last bss
last_bss = k;
}
}
/* Now use mmap to map the library into memory. */
/*
* Now fill out the bss section. First pad the last page up
* to the page boundary, and then perform a mmap to make sure
* that there are zero-mapped pages up to and including the
* last bss page.
*/
padzero(elf_bss);//清bss
elf_bss = ELF_PAGESTART(elf_bss + ELF_MIN_ALIGN - 1); /* What we have mapped so far */
/* Map the last of the bss segment */
if (last_bss > elf_bss)//未映射到文件的部分,分配0页
do_brk(elf_bss, last_bss - elf_bss);//匿名映射[addr,addr+len)
*interp_load_addr = load_addr;//镜像加载基址
error = ((unsigned long) interp_elf_ex->e_entry) + load_addr;//入口地址
out_close:
kfree(elf_phdata);
out:
return error;
}
6.返回load_elf_binary
allow_write_access(interpreter);
fput(interpreter);
kfree(elf_interpreter);
if (elf_entry == ~0UL) {
printk(KERN_ERR "Unable to load interpreter\n");
kfree(elf_phdata);
send_sig(SIGSEGV, current, 0);
return 0;
}
}
kfree(elf_phdata);
if (interpreter_type != INTERPRETER_AOUT)
sys_close(elf_exec_fileno);//ELF不需要
set_binfmt(&elf_format);
compute_creds(bprm);
current->flags &= ~PF_FORKNOEXEC;
//注意bprm->p被更新,指向argc地址
bprm->p = (unsigned long)
create_elf_tables((char *)bprm->p,
bprm->argc,
bprm->envc,
(interpreter_type == INTERPRETER_ELF ? &elf_ex : NULL),
load_addr, load_bias,
interp_load_addr,
(interpreter_type == INTERPRETER_AOUT ? 0 : 1));
7.create_elf_tables
static elf_addr_t *
create_elf_tables(char *p, int argc, int envc,
struct elfhdr * exec,
unsigned long load_addr,
unsigned long load_bias,
unsigned long interp_load_addr, int ibcs)
{
elf_caddr_t *argv;
elf_caddr_t *envp;
elf_addr_t *sp, *csp;
char *k_platform, *u_platform;
long hwcap;
size_t platform_len = 0;
/*
* Get hold of platform and hardware capabilities masks for
* the machine we are running on. In some cases (Sparc),
* this info is impossible to get, in others (i386) it is
* merely difficult.
*/
hwcap = ELF_HWCAP;//CPU特性描述字
k_platform = ELF_PLATFORM;//CPU类型名 例如i686
//p指向argv字符串首地址 即下面的0xbffffc07处
/*
0xbffffc02: "i686"
0xbffffc07: "/root/3/88"
0xbffffc12: "PWD=/root"
0xbffffc1c: "HOSTNAME=proxy"
0xbffffc2b: "QTDIR=/usr/lib/qt-2.3.1"
0xbffffc43: "LESSOPEN=|/usr/bin/lesspipe.sh %s"
0xbffffc65: "KDEDIR=/usr"
*/
if (k_platform) {//一般不为空
platform_len = strlen(k_platform) + 1;
u_platform = p - platform_len;
__copy_to_user(u_platform, k_platform, platform_len);//i686
} else
u_platform = p;
/*
* Force 16 byte _final_ alignment here for generality.
* Leave an extra 16 bytes free so that on the PowerPC we
* can move the aux table up to start on a 16-byte boundary.
*/
//向低地址方向对齐到16字节边界,再减16字节
//较新的内核使用一个随机数,因此布局有所不同
sp = (elf_addr_t *)((~15UL & (unsigned long)(u_platform)) - 16UL);
csp = sp;
//DLINFO -> 动态链接信息?? 这些信息是为ld.so准备的,ld.so需要用到
csp -= ((exec ? DLINFO_ITEMS*2 : 4) + (k_platform ? 2 : 0));//DLINFO_ITEMS*2 + 2,DLINFO_ITEMS定义为13
csp -= envc+1;
csp -= argc+1;
//ibcs 0->a.out 1->elf
csp -= (!ibcs ? 3 : 1);// 1-> argc /* argc itself */
if ((unsigned long)csp & 15UL)//不与16字节边界对齐
//下移sp,使argc对齐到16字节边界
sp -= ((unsigned long)csp & 15UL) / sizeof(*sp);
/*
内存布局如下
position content size (bytes) + comment
------------------------------------------------------------------------
stack pointer -> [ argc = number of args ] 4
[ argv[0] (pointer) ] 4 (program name)
[ argv[1] (pointer) ] 4
[ argv[..] (pointer) ] 4 * x
[ argv[n - 1] (pointer) ] 4
[ argv[n] (pointer) ] 4 (= NULL)
[ envp[0] (pointer) ] 4
[ envp[1] (pointer) ] 4
[ envp[..] (pointer) ] 4
[ envp[term] (pointer) ] 4 (= NULL)
[ auxv[0] AT_PHDR (Elf32_auxv_t) ] 8
[ auxv[1] AT_PHENT (Elf32_auxv_t) ] 8
[ auxv[2] AT_PHNUM (Elf32_auxv_t) ] 8
[ auxv[3] AT_BASE (Elf32_auxv_t) ] 8
[ auxv[4] AT_FLAGS (Elf32_auxv_t) ] 8
[ auxv[5] AT_ENTRY (Elf32_auxv_t) ] 8
[ auxv[6] AT_UID (Elf32_auxv_t) ] 8
[ auxv[7] AT_EUID (Elf32_auxv_t) ] 8
[ auxv[8] AT_GID (Elf32_auxv_t) ] 8
[ auxv[9] AT_EGID (Elf32_auxv_t) ] 8
[ auxv[10] AT_HWCAP (Elf32_auxv_t) ] 8
[ auxv[11] AT_PAGESZ (Elf32_auxv_t) ] 8
[ auxv[12] AT_CLKTCK (Elf32_auxv_t) ] 8
[ auxv[13] AT_PLATFORM (Elf32_auxv_t) ] 8
[ auxv[14] (Elf32_auxv_t) ] 8 (= AT_NULL vector)
[ padding ] 0 - 15
[ padding ] 16
[ padding ] 0 - 15
[k_platform] 0 - 65
[ argument ASCIIZ strings ] >= 0
[ environment ASCIIZ str. ] >= 0
[filename] >=0
(0xbffffffc) [ end marker ] 4 (= NULL)
(0xc0000000) < top of stack > 0 (virtual)
*/
/*
* Put the ELF interpreter info on the stack
*/
#define NEW_AUX_ENT(nr, id, val) \
__put_user ((id), sp+(nr*2)); \
__put_user ((val), sp+(nr*2+1)); \
//开始存放辅助向量
sp -= 2;
NEW_AUX_ENT(0, AT_NULL, 0);//end of vector
if (k_platform) {
sp -= 2;
NEW_AUX_ENT(0, AT_PLATFORM, (elf_addr_t)(unsigned long) u_platform);
}
sp -= 3*2;
NEW_AUX_ENT(0, AT_HWCAP, hwcap);
NEW_AUX_ENT(1, AT_PAGESZ, ELF_EXEC_PAGESIZE);// 4096
NEW_AUX_ENT(2, AT_CLKTCK, CLOCKS_PER_SEC);// 100
if (exec) {//elf interp
sp -= 10*2;
NEW_AUX_ENT(0, AT_PHDR, load_addr + exec->e_phoff);
NEW_AUX_ENT(1, AT_PHENT, sizeof (struct elf_phdr));
NEW_AUX_ENT(2, AT_PHNUM, exec->e_phnum);
NEW_AUX_ENT(3, AT_BASE, interp_load_addr);//interp加载基址,如果就是/lib/ld-linux.so.2或静态链
接可执行文件,则为0
NEW_AUX_ENT(4, AT_FLAGS, 0);
NEW_AUX_ENT(5, AT_ENTRY, load_bias + exec->e_entry);//原程序入口
NEW_AUX_ENT(6, AT_UID, (elf_addr_t) current->uid);
NEW_AUX_ENT(7, AT_EUID, (elf_addr_t) current->euid);
NEW_AUX_ENT(8, AT_GID, (elf_addr_t) current->gid);
NEW_AUX_ENT(9, AT_EGID, (elf_addr_t) current->egid);
}
#undef NEW_AUX_ENT
sp -= envc+1;
envp = (elf_caddr_t *) sp;
sp -= argc+1;
argv = (elf_caddr_t *) sp;
if (!ibcs) {//a.out
__put_user((elf_addr_t)(unsigned long) envp,--sp);
__put_user((elf_addr_t)(unsigned long) argv,--sp);
}
//处理argv数组
__put_user((elf_addr_t)argc,--sp);//argc入栈
current->mm->arg_start = (unsigned long) p;//arg_start
while (argc-->0) {
__put_user((elf_caddr_t)(unsigned long)p,argv++);
p += strlen_user(p);//计算下一个字符串的长度,更新p
}
__put_user(NULL, argv);
//处理envp数组
current->mm->arg_end = current->mm->env_start = (unsigned long) p;
while (envc-->0) {
__put_user((elf_caddr_t)(unsigned long)p,envp++);
p += strlen_user(p);
}
__put_user(NULL, envp);
current->mm->env_end = (unsigned long) p;
return sp;//返回argc地址
}
8.返回load_elf_binary
/* N.B. passed_fileno might not be initialized? */
if (interpreter_type == INTERPRETER_AOUT)
current->mm->arg_start += strlen(passed_fileno) + 1;//多了passed_fileno参数
current->mm->start_brk = current->mm->brk = elf_brk;//动态分配内存起始地址
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;
/* Calling set_brk effectively mmaps the pages that we need
* for the bss and break sections
*/
set_brk(elf_bss, elf_brk);//elf_bss上取整
9.set_brk
static void set_brk(unsigned long start, unsigned long end)
{
start = ELF_PAGEALIGN(start);//上取整到页边界
end = ELF_PAGEALIGN(end);
if (end <= start)
return;
do_brk(start, end - start);
}
10.返回load_elf_binary
padzero(elf_bss);//对最后一映射文件的页中的bss清零
#if 0
printk("(start_brk) %lx\n" , (long) current->mm->start_brk);
printk("(end_code) %lx\n" , (long) current->mm->end_code);
printk("(start_code) %lx\n" , (long) current->mm->start_code);
printk("(start_data) %lx\n" , (long) current->mm->start_data);
printk("(end_data) %lx\n" , (long) current->mm->end_data);
printk("(start_stack) %lx\n" , (long) current->mm->start_stack);
printk("(brk) %lx\n" , (long) current->mm->brk);
#endif
if ( current->personality == PER_SVR4 )
{
/* 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. */
/* N.B. Shouldn't the size here be PAGE_SIZE?? */
down(¤t->mm->mmap_sem);
error = do_mmap(NULL, 0, 4096, PROT_READ | PROT_EXEC,
MAP_FIXED | MAP_PRIVATE, 0);
up(¤t->mm->mmap_sem);
}
#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. This macro performs whatever initialization to
* the regs structure is required.
*/
ELF_PLAT_INIT(regs);//清空所有寄存器
#endif
/*
#define ELF_PLAT_INIT(_r) do { \
_r->ebx = 0; _r->ecx = 0; _r->edx = 0; \
_r->esi = 0; _r->edi = 0; _r->ebp = 0; \
_r->eax = 0; \
} while (0)
*/
start_thread(regs, elf_entry, bprm->p);//一般此处的elf_entry是interp的entry
11.start_thread
//清fs,gs
#define start_thread(regs, new_eip, new_esp) do { \
__asm__("movl %0,%%fs ; movl %0,%%gs": :"r" (0)); \
set_fs(USER_DS); \
regs->xds = __USER_DS; \
regs->xes = __USER_DS; \
regs->xss = __USER_DS; \
regs->xcs = __USER_CS; \
regs->eip = new_eip;/*设置eip,一般指向ld-linux.so.2的入口*/ \
regs->esp = new_esp;/*设置esp,指向argc地址*/ \
} while (0)
12.返回load_elf_binary
if (current->ptrace & PT_PTRACED)
send_sig(SIGTRAP, current, 0);//如果进程被调试,通知父进程
retval = 0;
out:
return retval;
/* error cleanup */
out_free_dentry:
allow_write_access(interpreter);
fput(interpreter);
out_free_interp:
if (elf_interpreter)
kfree(elf_interpreter);
out_free_file:
sys_close(elf_exec_fileno);
out_free_ph:
kfree(elf_phdata);
goto out;
}
阅读(2366) | 评论(0) | 转发(0) |