ld.so分析2
内核是如何执行程序的,本分析基于内核版本2.4.0
1.用户空间接口
man execve显示如下的函数原型
execve - execute program
SYNOPSIS
#include
int execve(const char *filename, char *const argv [], char *const
envp[]);
2.glibc中实现
在glibc中,execve对应的文件是
sysdeps/unix/sysv/linux/execve.c
int
__execve (file, argv, envp)
const char *file;
char *const argv[];
char *const envp[];
{
/* If this is a threaded application kill all other threads. */
if (__pthread_kill_other_threads_np)
__pthread_kill_other_threads_np ();
#if __BOUNDED_POINTERS__ //该宏未定义
{
char *const *v;
int i;
char *__unbounded *__unbounded ubp_argv;
char *__unbounded *__unbounded ubp_envp;
char *__unbounded *__unbounded ubp_v;
for (v = argv; *v; v++)
;
i = v - argv + 1;
ubp_argv = (char *__unbounded *__unbounded) alloca (sizeof (*ubp_argv) * i);
for (v = argv, ubp_v = ubp_argv; --i; v++, ubp_v++)
*ubp_v = CHECK_STRING (*v);
*ubp_v = 0;
for (v = envp; *v; v++)
;
i = v - envp + 1;
ubp_envp = (char *__unbounded *__unbounded) alloca (sizeof (*ubp_envp) * i);
for (v = envp, ubp_v = ubp_envp; --i; v++, ubp_v++)
*ubp_v = CHECK_STRING (*v);
*ubp_v = 0;
return INLINE_SYSCALL (execve, 3, CHECK_STRING (file), ubp_argv, ubp_envp);
}
#else
return INLINE_SYSCALL (execve, 3, file, argv, envp);//所以这行有效
#endif
}
INLINE_SYSCALL的定义在
sysdeps/unix/sysv/linux/i386/sysdeps.h
#define INLINE_SYSCALL(name, nr, args...) \
({ \
unsigned int resultvar; \
asm volatile ( \
LOADARGS_##nr \
"movl %1, %%eax\n\t" \
"int $0x80\n\t" \
RESTOREARGS_##nr \
: "=a" (resultvar) \
: "i" (__NR_##name) ASMFMT_##nr(args) : "memory", "cc"); \
if (resultvar >= 0xfffff001) \
{ \
__set_errno (-resultvar); \
resultvar = 0xffffffff; \
} \
(int) resultvar; })
3.手工展开看看
({
unsigned int resultvar;
asm volatile (
LOADARGS_3
"movl %1, %%eax\n\t"
"int $0x80\n\t"
RESTOREARGS_3
: "=a" (resultvar)
: "i" (__NR_execve) ASMFMT_3(args) : "memory", "cc");
if (resultvar >= 0xfffff001)
{
__set_errno (-resultvar);
resultvar = 0xffffffff;
}
(int) resultvar; })
其中__NR_execve是execve的系统调用号,为11,定义在头文件unistd.h中
这其中又涉及到三个宏
#define LOADARGS_1 \
"bpushl .L__X'%k2, %k2\n\t" \
"bmovl .L__X'%k2, %k2\n\t"
#define LOADARGS_3 LOADARGS_1
#define RESTOREARGS_1 \
"bpopl .L__X'%k2, %k2\n\t"
#define RESTOREARGS_3 RESTOREARGS_1
#define ASMFMT_3(arg1, arg2, arg3) \
, "aCD" (arg1), "c" (arg2), "d" (arg3)
展开
({
unsigned int resultvar;
asm volatile (
"bpushl .L__X'%k2, %k2\n\t"
"bmovl .L__X'%k2, %k2\n\t"
"movl %1, %%eax\n\t"
"int $0x80\n\t"
"bpopl .L__X'%k2, %k2\n\t"
: "=a" (resultvar)
: "i" (11) , "aCD" (arg1), "c" (arg2), "d" (arg3) : "memory", "cc");
if (resultvar >= 0xfffff001)
{
__set_errno (-resultvar);
resultvar = 0xffffffff;
}
(int) resultvar; })
这里又涉及到三个asm宏,bpushl,bmovl,bpopl
定义如下(也在该文件sysdeps.h中)
asm (".L__X'%ebx = 1\n\t"
".L__X'%ecx = 2\n\t"
".L__X'%edx = 2\n\t"
".L__X'%eax = 3\n\t"
".L__X'%esi = 3\n\t"
".L__X'%edi = 3\n\t"
".L__X'%ebp = 3\n\t"
".L__X'%esp = 3\n\t"
".macro bpushl name reg\n\t"
".if 1 - \\name\n\t"
".if 2 - \\name\n\t"
"pushl %ebx\n\t"
".else\n\t"
"xchgl \\reg, %ebx\n\t"
".endif\n\t"
".endif\n\t"
".endm\n\t"
".macro bpopl name reg\n\t"
".if 1 - \\name\n\t"
".if 2 - \\name\n\t"
"popl %ebx\n\t"
".else\n\t"
"xchgl \\reg, %ebx\n\t"
".endif\n\t"
".endif\n\t"
".endm\n\t"
".macro bmovl name reg\n\t"
".if 1 - \\name\n\t"
".if 2 - \\name\n\t"
"movl \\reg, %ebx\n\t"
".endif\n\t"
".endif\n\t"
".endm\n\t");
根据约束条件
%eax分配给resultvar
%ecx分配给argv
%edx分配给envp
则约束条件"aCD"中,a(%eax)已分配,C无效,因此分配%edi给file
手工展开
mov file,%edi
mov argv,%ecx
mov envp,%edx
bpushl .L__X'%edi, %edi
bmovl .L__X'·%edi, %%edi
movl 11, %%eax
int $0x80
bpopl .L__X'%edi, %edi
手工展开
mov file,%edi
mov argv,%ecx
mov envp,%edx
.if 1 - .L_X'%edi
.if 2 - .L_X'%edi
pushl %ebx
.else
xchgl %edi, %ebx
.endif
.endif
.if 1 - .L_X'%edi
.if 2 - .L_X'%edi
movl %edi, %ebx
.endif
.endif
movl 11, %%eax
int $0x80
.if 1 - .L_X'%edi
.if 2 - .L_X'%edi
popl %ebx
.else
xchgl %edi, %ebx
.endif
.endif
由于L__X'%edi = 3,展开
mov file,%edi
mov argv,%ecx
mov envp,%edx
.if 1 - 3
.if 2 - 3
pushl %ebx
.else
xchgl %edi, %ebx
.endif
.endif
.if 1 - 3
.if 2 - 3
movl %edi, %ebx
.endif
.endif
movl 11, %%eax
int $0x80
.if 1 - 3
.if 2 - 3
popl %ebx
.else
xchgl %edi, %ebx
.endif
.endif
.if为真的条件是不等于0,展开
mov file,%edi
mov argv,%ecx
mov envp,%edx
pushl %ebx
movl %edi, %ebx
movl 11, %%eax
int $0x80
popl %ebx
最终编译结果是
mov 0x8(%ebp),%edi
mov 0xc(%ebp),%ecx
mov 0x10(%ebp),%edx
push %ebx
mov %edi,%ebx
mov $0xb,%eax
int $0x80
pop %ebx
正好一致
系统调用传参使用%ebx,%ecx,%edx,%esi,%edi这五个寄存器,因此最多只能传五个参数.
4.返回值的处理
# define __set_errno(val) (*__errno_location ()) = (val)
if (resultvar >= 0xfffff001)//如果返回值>=0xfffff001,则出错
{
__set_errno (-resultvar);// 预处理时被替换成(*__errno_location ()) = (-resultvar);设置errno为-resultvar
resultvar = 0xffffffff; //-1
}
__errno_location的定义是
sysdeps/generic/errno-loc.c
int * __errno_location (void)
{
return &errno;
}
5.也可使用如下宏生成调用系统调用execve的代码
linux/include/asm-i386/unistd.h
#define _syscall3(type,name,type1,arg1,type2,arg2,type3,arg3) \
type name(type1 arg1,type2 arg2,type3 arg3) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
: "=a" (__res) \
: "0" (__NR_##name),"b" ((long)(arg1)),"c" ((long)(arg2)), \
"d" ((long)(arg3))); \
__syscall_return(type,__res); \
}
例如
_syscall3(int,execve,const char *,file,char *const,argv[],char *const,envp[])
能生成和glibc相似的代码
6.sys_execve
linux/arch/i386/kernel/process.c
/*
* sys_execve() executes a new program.
*/
asmlinkage int sys_execve(struct pt_regs regs)
{
int error;
char * filename;
filename = getname((char *) regs.ebx);
error = PTR_ERR(filename);
if (IS_ERR(filename))
goto out;
//do_execve成功替换掉执行影像后,在返回到用户空间时,执行权才交给新的影像
error = do_execve(filename, (char **) regs.ecx, (char **) regs.edx, ®s);
if (error == 0)
current->ptrace &= ~PT_DTRACE;//取消单步跟踪
putname(filename);
out:
return error;
}
7.do_execve(sys_execve->do_execve)
fs/exec.c
/*
* sys_execve() executes a new program.
*/
int do_execve(char * filename, char ** argv, char ** envp, struct pt_regs * regs)
{
struct linux_binprm bprm;
struct file *file;
int retval;
int i;
file = open_exec(filename);
retval = PTR_ERR(file);
if (IS_ERR(file))
return retval;
bprm.p = PAGE_SIZE*MAX_ARG_PAGES-sizeof(void *);//参数最多占32个页面,最后一个字存放NULL
memset(bprm.page, 0, MAX_ARG_PAGES*sizeof(bprm.page[0]));//清空页指针
bprm.file = file;
bprm.filename = filename;
bprm.sh_bang = 0;
bprm.loader = 0;
bprm.exec = 0;
//计算argv数组的长度,该数组是0结束
if ((bprm.argc = count(argv, bprm.p / sizeof(void *))) < 0) {
allow_write_access(file);
fput(file);
return bprm.argc;
}
//计算envp数组的长度
if ((bprm.envc = count(envp, bprm.p / sizeof(void *))) < 0) {
allow_write_access(file);
fput(file);
return bprm.envc;
}
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);//复制envp
if (retval < 0)
goto out;
retval = copy_strings(bprm.argc, argv, &bprm);//复制argv
if (retval < 0)
goto out;
retval = search_binary_handler(&bprm,regs);
if (retval >= 0)
/* execve success */
return retval;
out:
/* Something went wrong, return the inode and free the argument pages*/
allow_write_access(bprm.file);
if (bprm.file)
fput(bprm.file);
for (i = 0 ; i < MAX_ARG_PAGES ; i++) {
struct page * page = bprm.page[i];
if (page)
__free_page(page);
}
return retval;
}
8.copy_strings(sys_execve->do_execve->copy_strings)
fs/exec.c
/*
* 'copy_strings()' copies argument/envelope strings from user
* memory to free pages in kernel mem. These are in a format ready
* to be put directly into the top of new user memory.
*/
//从用户空间拷贝数据到空闲页
int copy_strings(int argc,char ** argv, struct linux_binprm *bprm)
{
while (argc-- > 0) {//argc--
char *str;
int len;
unsigned long pos;
//上面argc--
if (get_user(str, argv+argc) || !str || !(len = strnlen_user(str, bprm->p)))
return -EFAULT;
if (bprm->p < len) //空间不够
return -E2BIG;
bprm->p -= len;//从后往前考
/* XXX: add architecture specific overflow check here. */
pos = bprm->p;
while (len > 0) {
char *kaddr;
int i, new, err;
struct page *page;
int offset, bytes_to_copy;
offset = pos % PAGE_SIZE;//页内偏移
i = pos/PAGE_SIZE;//页号
page = bprm->page[i];
new = 0;
if (!page) {
page = alloc_page(GFP_HIGHUSER);
bprm->page[i] = page;
if (!page)
return -ENOMEM;
new = 1;
}
kaddr = kmap(page);
if (new && offset)//是新页,offset>0,清[0,offset)
memset(kaddr, 0, offset);
bytes_to_copy = PAGE_SIZE - offset;
if (bytes_to_copy > len) {
bytes_to_copy = len;
if (new)//清[offset+len,PAGE_SIZE)
memset(kaddr+offset+len, 0, PAGE_SIZE-offset-len);
}
err = copy_from_user(kaddr + offset, str, bytes_to_copy);
kunmap(page);
if (err)
return -EFAULT;
pos += bytes_to_copy;//可能跨页
str += bytes_to_copy;
len -= bytes_to_copy;
}
}
return 0;
}
执行到这里bprm->p内存空间布局如下
[ argument ASCIIZ strings ] >= 0
[ environment ASCIIZ str. ] >= 0
[filename]
(0xbffffffc) [ end marker ] 4 (= NULL)
(0xc0000000) < top of stack > 0 (virtual)
写一个程序验证一下
系统redhat7.2
[root@proxy ~]# uname -a
Linux proxy 2.4.7-10smp #1 SMP Thu Sep 6 17:09:31 EDT 2001 i686 unknown
[root@proxy ~]#
root@proxy ~]# cat 1.c
#include
int main(int argc,char * argv[],char * envp[])
{
unsigned char * p;
printf("%d,%p,%p\n",argc,argv,envp);
p=(unsigned char *)argv;
for(;p<(unsigned char *)0xc0000000;p++)
if(isprint(*p))
printf("%c",*p);
else
printf("\\%x",*p);
return 0;
}
[root@proxy ~]# ./a.out
1,0xbffffb04,0xbffffb0c
\3\fc\ff\bf\0\0\0\0\b\fc\ff\bf\15\fc\ff\bf$\fc\ff\bf<\fc\ff\bf^\fc\ff\bfj\fc\ff\bft\fc\ff\bf7\fe\ff\bfV\fe\ff\bfp\fe\ff\bf\85\fe\ff\bf\9c\fe\ff\bf\a7\fe\ff\bf\b4\fe\ff\bf\bc\fe\ff\bf\cc\fe\ff\bf\da\fe\ff\bf\e8\fe\ff\bf\f9\fe\ff\bf\7\ff\ff\bf\12\ff\ff\bf\1d\ff\ff\bfI\ff\ff\bf|\ff\ff\bf\d7\ff\ff\bf\ea\ff\ff\bf\0\0\0\0\10\0\0\0\ff\fb\83\3\6\0\0\0\0\10\0\0\11\0\0\0d\0\0\0\3\0\0\04\80\4\8\4\0\0\0 \0\0\0\5\0\0\0\6\0\0\0\7\0\0\0\0\0\0@\8\0\0\0\0\0\0\0\9\0\0\0\90\83\4\8\b\0\0\0\0\0\0\0\c\0\0\0\0\0\0\0\d\0\0\0\0\0\0\0\e\0\0\0\0\0\0\0\f\0\0\0\fe\fb\ff\bf\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0i686\0./a.out\0PWD=/root\0HOSTNAME=proxy\0QTDIR=/usr/lib/qt-2.3.1\0LESSOPEN=|/usr/bin/lesspipe.sh %s\0KDEDIR=/usr\0USER=root\0LS_COLORS=no=00:fi=00:di=01;34:ln=01;36:pi=40;33:so=01;35:bd=40;33;01:cd=40;33;01:or=01;05;37;41:mi=01;05;37;41:ex=01;32:*.cmd=01;32:*.exe=01;32:*.com=01;32:*.btm=01;32:*.bat=01;32:*.sh=01;32:*.csh=01;32:*.tar=01;31:*.tgz=01;31:*.arj=01;31:*.taz=01;31:*.lzh=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.gz=01;31:*.bz2=01;31:*.bz=01;31:*.tz=01;31:*.rpm=01;31:*.cpio=01;31:*.jpg=01;35:*.gif=01;35:*.bmp=01;35:*.xbm=01;35:*.xpm=01;35:*.png=01;35:*.tif=01;35:\0MACHTYPE=i386-redhat-linux-gnu\0MAIL=/var/spool/mail/root\0INPUTRC=/etc/inputrc\0BASH_ENV=/root/.bashrc\0LANG=en_US\0LOGNAME=root\0SHLVL=1\0SHELL=/bin/bash\0USERNAME=root\0HOSTTYPE=i386\0OSTYPE=linux-gnu\0HISTSIZE=1000\0HOME=/root\0TERM=linux\0SSH_AUTH_SOCK=/tmp/ssh-XXi40Qtw/agent.23262\0SSH_ASKPASS=/usr/libexec/openssh/gnome-ssh-askpass\0PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/usr/X11R6/bin:/root/bin\0SSH_TTY=/dev/pts/0\0_=./a.out\0./a.out\0\0\0\0\0
9.search_binary_handler(sys_execve->do_execve->search_binary_handler)
fs/exec.c
/*
* cycle the list of binary formats handler, until one recognizes the image
*/
int search_binary_handler(struct linux_binprm *bprm,struct pt_regs *regs)
{
int try,retval=0;
struct linux_binfmt *fmt;
#ifdef __alpha__
/* handle /sbin/loader.. */
{
struct exec * eh = (struct exec *) bprm->buf;
if (!bprm->loader && eh->fh.f_magic == 0x183 &&
(eh->fh.f_flags & 0x3000) == 0x3000)
{
char * dynloader[] = { "/sbin/loader" };
struct file * file;
unsigned long loader;
allow_write_access(bprm->file);
fput(bprm->file);
bprm->file = NULL;
loader = PAGE_SIZE*MAX_ARG_PAGES-sizeof(void *);
file = open_exec(dynloader[0]);
retval = PTR_ERR(file);
if (IS_ERR(file))
return retval;
bprm->file = file;
bprm->loader = loader;
retval = prepare_binprm(bprm);
if (retval<0)
return retval;
/* should call search_binary_handler recursively here,
but it does not matter */
}
}
#endif
for (try=0; try<2; try++) {
read_lock(&binfmt_lock);
for (fmt = formats ; fmt ; fmt = fmt->next) {
int (*fn)(struct linux_binprm *, struct pt_regs *) = fmt->load_binary;
if (!fn)
continue;
if (!try_inc_mod_count(fmt->module))
continue;
read_unlock(&binfmt_lock);
retval = fn(bprm, regs);//调用该文件格式的load_binary
if (retval >= 0) {//成功
put_binfmt(fmt);
allow_write_access(bprm->file);//allow write
if (bprm->file)
fput(bprm->file);
bprm->file = NULL;
current->did_exec = 1;//可以执行了
return retval;
}
read_lock(&binfmt_lock);
put_binfmt(fmt);
if (retval != -ENOEXEC)
break;
if (!bprm->file) {
read_unlock(&binfmt_lock);
return retval;
}
}
read_unlock(&binfmt_lock);
if (retval != -ENOEXEC) {
break;
#ifdef CONFIG_KMOD
}else{
#define printable(c) (((c)=='\t') || ((c)=='\n') || (0x20<=(c) && (c)<=0x7e))
char modname[20];
if (printable(bprm->buf[0]) &&
printable(bprm->buf[1]) &&
printable(bprm->buf[2]) &&
printable(bprm->buf[3]))
break; /* -ENOEXEC 不允许都是可打印字符*/
sprintf(modname, "binfmt-%04x", *(unsigned short *)(&bprm->buf[2]));
request_module(modname);
#endif
}
}
return retval;
}
elf文件的相关处理结构在fs/binfmt_elf.c中
static int __init init_elf_binfmt(void)
{
return register_binfmt(&elf_format);
}
static struct linux_binfmt elf_format = {
NULL, THIS_MODULE, load_elf_binary, load_elf_library, elf_core_dump, ELF_EXEC_PAGESIZE
};
因此elf的load_binary函数是load_elf_binary
阅读(1939) | 评论(0) | 转发(0) |