-
Notifications
You must be signed in to change notification settings - Fork 0
/
loader.xml
205 lines (187 loc) · 8 KB
/
loader.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
<sect1><title>ELF加载器</title>
在多数现代系统中,每一个程序被加载到一个新的地址空间,这就意味着所有的程序都被加载到一个已知的固定地址,并可以从这个地址被链接。这种情况下,加载是颇为简单
的:
<itemizedlist>
<listitem>从目标文件中读取足够的头部信息,找出需要多少地址空间。</listitem>
<listitem>分配地址空间,如果目标代码的格式具有独立的段,那么就将地址空间按独立的段划分。</listitem>
<listitem>将程序读入地址空间的段中。</listitem>
<listitem>将程序末尾的bss段空间填充为0,如果虚拟内存系统不自动这么做得话。</listitem>
<listitem>如果体系结构需要的话,创建一个堆栈段(stack segment)。</listitem>
<listitem>设置诸如程序参数和环境变量的其他运行时信息。</listitem>
<listitem>开始运行程序。</listitem>
</itemizedlist>
<para>
如果程序不是通过虚拟内存系统映射的,读取目标文件就意味着通过普通的read系统调用读取文件。在支持共享只读代码段的系统上,系统检查是否在内存中已经加载了该代码
段的一个拷贝,而不是生成另外一份拷贝。
</para>
<para>可执行ELF有两种,一种是静态连接的,在装入/启动其运行时无需装入函数库映像、也无需进行动态连接。另一种是动态连接的,需要在装入/启动其运行
时同时装入函数库映像并进行动态连接。显然,Linux 内核应该既支持静态连接的ELF 映像、也支持动态连接的ELF 映像。进一步的分析表明:装入/启动ELF 映像必需由内核完成,而
动态连接的实现则既可以在内核中完成,也可在用户空间完成。因此,GNU 把对于动态连接ELF 映像的支持作了分工:把ELF 映像的装入/启动放在Linux 内核中;而把动态连接的
实现放在用户空间,并为此提供一个称为“解释器”的工具软件,而解释器的装入/启动也由内核负责。
</para>
<sect2><title>系统空间加载</title>
<para>
无论是在终端下输入命令行,还是在程序中使用execv系列函数通过参数传递一个命令,最终都将唤醒内核的sys_execve系统调用。
glibc中提供的系列函数如下,通过它们可以调用一个可执行文件完全代替当前的进程。
<programlisting><![CDATA[
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg,
..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
]]></programlisting>
</para>
<sect3><title>sys_execve</title>
<para>
sys_execve是和系统体系架构相关的系统调用,对于arm来说,它被定义在arch/arm/kernel/sys_arm.c中。
<programlisting><![CDATA[
/* sys_execve() executes a new program.
* This is called indirectly via a small wrapper */
asmlinkage int sys_execve(char __user *filenamei, char __user * __user *argv,
char __user * __user *envp, struct pt_regs *regs)
{
int error;
char * filename;
filename = getname(filenamei);
error = PTR_ERR(filename);
if (IS_ERR(filename))
goto out;
error = do_execve(filename, argv, envp, regs);
putname(filename);
out:
return error;
}
]]></programlisting>
参数filenamei是执行的命令名,argv和envp则分别传递了参数和环境变量,最后一个则传递了系统调用时已经压入堆栈的寄存器,它被定义在arch/arm
/include/asm/ptrace.h中:
<programlisting><![CDATA[
/*
* This struct defines the way the registers are stored on the
* stack during a system call. Note that sizeof(struct pt_regs)
* has to be a multiple of 8.
*/
struct pt_regs {
long uregs[18];
};
#define ARM_cpsr uregs[16]
#define ARM_pc uregs[15]
#define ARM_lr uregs[14]
#define ARM_sp uregs[13]
#define ARM_ip uregs[12]
#define ARM_fp uregs[11]
#define ARM_r10 uregs[10]
#define ARM_r9 uregs[9]
#define ARM_r8 uregs[8]
#define ARM_r7 uregs[7]
#define ARM_r6 uregs[6]
#define ARM_r5 uregs[5]
#define ARM_r4 uregs[4]
#define ARM_r3 uregs[3]
#define ARM_r2 uregs[2]
#define ARM_r1 uregs[1]
#define ARM_r0 uregs[0]
#define ARM_ORIG_r0 uregs[17]
]]></programlisting>
getname函数将用户空间的命令字符串拷贝到内核空间,接着做了参数合法性的检查。PTR_ERR将filename地址转化成int型,而IS_ERR则是检查地址的合法性。它们被定义在include/linux/err.h:
<programlisting><![CDATA[
#define MAX_ERRNO 4095
......
#define IS_ERR_VALUE(x) unlikely((x) >= (unsigned long)-MAX_ERRNO)
......
static inline long PTR_ERR(const void *ptr)
{
return (long) ptr;
}
static inline long IS_ERR(const void *ptr)
{
return IS_ERR_VALUE((unsigned long)ptr);
}
]]></programlisting>
对于IS_ERR_VALUE的理解是,最高的4095个地址被安排错误号对应的地址,每个错误号对应一个虚拟地址,它就是ERR_PTR(-ERRNO),所以使用地址与它比较可以确定是否返回的是一个错误号对应的地址。对于4095个地址来说这个地址范围是0xfffff001-0xffffffff。而
(unsigned long)-MAX_ERRNO的值就是0xfffff001。
putname是用来释放getname申请的空间,getname->_getname->kmem_cache_alloc,而putname调用的_putname则是kmem_cache_free的直接宏定义。它们均在include/linux/fs.h中定义。
一个重要的函数是do_execve,它完成了最终要的加载使命。
</para>
</sect3>
<sect3><title>do_execve</title>
相对于sys_execve,do_execve则要复杂得多,不同系统架构的sys_execve系统调用都被内核同一到了fs/exec.c中的do_execve。
<para>
<programlisting><![CDATA[
/*
* sys_execve() executes a new program.
*/
int do_execve(char * filename,
char __user *__user *argv,
char __user *__user *envp,
struct pt_regs * regs)
{
struct linux_binprm *bprm;
struct file *file;
struct files_struct *displaced;
int retval;
retval = unshare_files(&displaced);
if (retval)
goto out_ret;
retval = -ENOMEM;
bprm = kzalloc(sizeof(*bprm), GFP_KERNEL);
if (!bprm)
goto out_files;
]]></programlisting>
首先内核互斥打开该文件,并且申请一个名为bprm的结构体,它被用来存储转载可执行文件时的所有相关信息。
<programlisting><![CDATA[
file = open_exec(filename);
retval = PTR_ERR(file);
if (IS_ERR(file))
goto out_kfree;
]]></programlisting>
open_exec对filename指定的文件进行可执权限的测试并确保该文件不可再被写入,返回一个struct file指针。
sched_exec对于单CPU是一个空函数,接下来是对bprm的初始化。
<programlisting><![CDATA[
sched_exec();
bprm->file = file;
bprm->filename = filename;
bprm->interp = filename;
retval = bprm_mm_init(bprm);
if (retval)
goto out_file;
bprm->argc = count(argv, MAX_ARG_STRINGS);
if ((retval = bprm->argc) < 0)
goto out_mm;
bprm->envc = count(envp, MAX_ARG_STRINGS);
if ((retval = bprm->envc) < 0)
goto out_mm;
retval = security_bprm_alloc(bprm);
if (retval)
goto out;
retval = prepare_binprm(bprm);
if (retval < 0)
goto out;
retval = copy_strings_kernel(1, &bprm->filename, bprm);
if (retval < 0)
goto out;
bprm->exec = bprm->p;
retval = copy_strings(bprm->envc, envp, bprm);
if (retval < 0)
goto out;
retval = copy_strings(bprm->argc, argv, bprm);
if (retval < 0)
goto out;
]]></programlisting>
将当前线程的内核线程属性去除,表明它将成为一个用户进程。search_binary_handler则搜索内核支持的所有可执行文件的加载器。
<programlisting><![CDATA[
current->flags &= ~PF_KTHREAD;
retval = search_binary_handler(bprm,regs);
]]></programlisting>
<programlisting><![CDATA[
]]></programlisting>
</para>
</sect3>
</sect2>
<programlisting><![CDATA[
]]></programlisting>
<programlisting><![CDATA[
]]></programlisting>
<para>
</para>
</sect1>