linux内核分析 第七周 Linux内核如何装载和启动一个可执行程序
一、编译链接的过程和ELF可执行文件格式
vi hello.c
gcc -E -o hello.cpp hello.c -m32 //预处理.c文件,预处理包括把include的文件包含进来以及宏替换等工作 vi hello.cpp
gcc -x cpp-output -S -o hello.s hello.cpp -m32 //编译 vi hello.s
gcc -x assembler -c hello.s -o hello.o -m32 //汇编 vi hello.o
gcc -o hello hello.o -m32 //链接 vi hello
gcc -o hello.static hello.o -m32 -static
ELF文件中有三种文件
可重定位文件:保存着代码和适当的数据,用来和其他的object文件一起来创建一个可执行文件或者是一个共享文件。
可执行文件:保存着一个用来执行的程序;该文件指出了exec(BA_OS)如何来创建程序进程映象。
共享文件:保存着代码和合适的数据,用来被下面的两个链接器链接。
第一个是连接编辑器[请参看ld(SD_CMD)],可以和其他的可重定位和共享object文件来创建其他的object。
第二个是动态链接器,联合一个可执行文件和其他的共享object文件来创建一个进程映象。
object文件参与程序的链接(创建)和执行。
ELF文件头
用readelf查看
在文件开始保存了:路线图:描述该文件组织情况,程序头表:告诉系统如何创建一个进程的内存映像,section头表:描述文件的section信息。(每个section在这个表中有一个入口,给出该section信息)
静态链接的ELF可执行文件和进程的地址空间
程序从0x804800开始,正式开始是在头部结束之后。
可执行文件加载到内存中开始执行的第一行代码。
一般静态链接将会把所有代码放在同一个代码段。
动态连接的进程会有多个代码段。
二、可执行程序、共享库和动态链接
可执行程序的执行环境
一般执行一个程序的Shell环境,实验中直接使用execve系统调用
Shell本身不限制命令行参数的个数,命令行参数的个数受限于命令自身,如:
int main(int argc, char *argv[]) int main(int argc, char argv[], char envp[])//envp是shell的执行环境
Shell会调用execve将命令行参数和环境参数传递给可执行程序的main函数
int execve(const char * filename,char * const argv[ ],char * const envp[ ]);
命令行参数和环境串都放在用户态堆栈中
可执行程序动态链接
共享库的动态链接
准备.so文件(在Linux下动态链接文件格式,在Windows中是.dll)
#ifndef _SH_LIB_EXAMPLE_H_
#define _SH_LIB_EXAMPLE_H_ #define SUCCESS 0
#define FAILURE (-1) #ifdef __cplusplus
extern "C" {
#endif
/*
* Shared Lib API Example
* input : none
* output : none
* return : SUCCESS(0)/FAILURE(-1)
*
*/
int SharedLibApi();//内容只有一个函数头定义 #ifdef __cplusplus
}
#endif
#endif /* _SH_LIB_EXAMPLE_H_ */
/*------------------------------------------------------*/ #include <stdio.h>
#include "shlibexample.h" int SharedLibApi()
{
printf("This is a shared libary!\n");
return SUCCESS;
}/* _SH_LIB_EXAMPLE_C_ */
编译成.so文件
gcc -shared shlibexample.c -o libshlibexample.so -m32
3. 动态加载库
#ifndef _DL_LIB_EXAMPLE_H_
#define _DL_LIB_EXAMPLE_H_
#ifdef __cplusplus
extern "C" {
#endif
/*
* Dynamical Loading Lib API Example
* input : none
* output : none
* return : SUCCESS(0)/FAILURE(-1)
*
*/
int DynamicalLoadingLibApi(); #ifdef __cplusplus
}
#endif
#endif /* _DL_LIB_EXAMPLE_H_ */
/*------------------------------------------------------*/ #include <stdio.h>
#include "dllibexample.h" #define SUCCESS 0
#define FAILURE (-1) /*
* Dynamical Loading Lib API Example
* input : none
* output : none
* return : SUCCESS(0)/FAILURE(-1)
*
*/
int DynamicalLoadingLibApi()
{
printf("This is a Dynamical Loading libary!\n");
return SUCCESS;
}
4. main.c
#include <stdio.h>
#include "shlibexample.h" //只include了共享库
#include <dlfcn.h>
/*
* Main program
* input : none
* output : none
* return : SUCCESS(0)/FAILURE(-1)
*
*/
int main()
{
printf("This is a Main program!\n");
/* Use Shared Lib */
printf("Calling SharedLibApi() function of libshlibexample.so!\n");
SharedLibApi();//可以直接调用,因为include了这个库的接口
/* Use Dynamical Loading Lib */
void * handle = dlopen("libdllibexample.so",RTLD_NOW);//先打开动态加载库
if(handle == NULL)
{
printf("Open Lib libdllibexample.so Error:%s\n",dlerror());
return FAILURE;
}
int (*func)(void);
char * error;
func = dlsym(handle,"DynamicalLoadingLibApi");
if((error = dlerror()) != NULL)
{
printf("DynamicalLoadingLibApi not found:%s\n",error);
return FAILURE;
}
printf("Calling DynamicalLoadingLibApi() function of libdllibexample.so!\n");
func();
dlclose(handle);//与dlopen函数配合,用于卸载链接库
return SUCCESS;
}
dlsym函数与上面的dlopen函数配合使用,通过dlopen函数返回的动态库句柄(由dlopen打开动态链接库后返回的指针handle)以及对应的符号返回符号对应的指针。
三、使用gdb跟踪分析一个execve系统调用内核处理函数sys_execve
四、Linux内核如何装载和启动一个可执行程序
创建新进程
新进程调用execve()系统调用执行指定的ELF文件
调用内核的入口函数sys_execve(),sys_execve()服务例程修改当前进程的执行上下文;当ELF被load_elf_binary()装载完成后,函数返回至do_execve()在返回至sys_execve()。ELF可执行文件的入口点取决于程序的链接方式:
静态链接:elf_entry就是指向可执行文件里边规定的那个头部,即main函数处。
动态链接:可执行文件是需要依赖其它动态链接库,elf_entry就是指向动态链接器的起点。
多进程、多用户、虚拟存储的操作系统出现以后,可执行文件的装载过程变得非常复杂。引入了进程的虚拟地址空间;然后根据操作系统如何为程序的代码、数据、堆、栈在进程地址空间中分配,它们是如何分布的;最后以页映射的方式将程序映射进程虚拟地址空间。
五、分析sys_execve
当sys_execve
被调用后,涉及的主要函数为:do_execve -> do_execve_common -> exec_binprm
syscall
SYSCALL_DEFINE3(execve,
const
char
__user *, filename,
const
char
__user *
const
__user *, argv,
const
char
__user *
const
__user *, envp)
{
//真正执行程序的功能exec.c文件中的do_execve函数中实现
return
do_execve(getname(filename), argv, envp);
}
do_execve
int
do_execve(
struct
filename *filename,
const
char
__user *
const
__user *__argv,
const
char
__user *
const
__user *__envp)
{
struct
user_arg_ptr argv = { .ptr.native = __argv };
struct
user_arg_ptr envp = { .ptr.native = __envp };
//调用do_execve_common
return
do_execve_common(filename, argv, envp);
}
do_execve_common
static
int
do_execve_common(
struct
filename *filename,
struct
user_arg_ptr argv,
struct
user_arg_ptr envp)
{
struct
linux_binprm *bprm;
struct
file *file;
struct
files_struct *displaced;
int
retval;
..
//打开要执行的文件,并检查其有效性
file = do_open_exec(filename);
retval = PTR_ERR(file);
if
(IS_ERR(file))
goto
out_unmark;
sched_exec();
// 填充linux_binprm结构
bprm->file = file;
bprm->filename = bprm->interp = filename->name;
...
//将文件名、环境变量和命令行参数拷贝到新分配的页面中
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
;
//调用exec_binprm,保存当前的pid并且调用 search_binary_handler
retval = exec_binprm(bprm);
if
(retval < 0)
goto
out
;
/* execve succeeded */
current->fs->in_exec = 0;
current->in_execve = 0;
acct_update_integrals(current);
task_numa_free(current);
free_bprm(bprm);
putname(filename);
if
(displaced)
put_files_struct(displaced);
return
retval;
}
linux内核分析 第七周 Linux内核如何装载和启动一个可执行程序的更多相关文章
- linux内核分析第七周-Linux内核如何装载和启动一个可执行程序
一.可执行文件的创建 可执行文件的创建就是三步:预处理.编译和链接. cd Code vi hello.c #写入最简单的helloworld的c程序 gcc -E -o hello.cpp hell ...
- LINUX内核分析第七周学习总结:可执行程序的装载
LINUX内核分析第七周学习总结:可执行程序的装载 韩玉琪 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/cours ...
- Linux内核分析 第七周 可执行程序的装载
张嘉琪 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 Linux内核分析 第七 ...
- 20135327郭皓--Linux内核分析第七周 可执行程序的装载
第七周 可执行程序的装载 郭皓 原创作品转载请注明出处 <Linux内核分析>MOOC课程 http://mooc.study.163.com/course/USTC-1000029000 ...
- LINUX内核分析第七周学习总结
LINUX内核分析第七周学习总结 标签(空格分隔): 20135328陈都 陈都 原创作品转载请注明出处 <Linux内核分析>MOOC课程 http://mooc.study.163.c ...
- Linux内核分析第七周———可执行程序的装载
Linux内核分析第七周---可执行程序的装载 李雪琦+原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/US ...
- Linux内核分析第七周学习笔记——Linux内核如何装载和启动一个可执行程序
Linux内核分析第七周学习笔记--Linux内核如何装载和启动一个可执行程序 zl + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study. ...
- LINUX内核分析第七周学习总结——可执行程序的装载
LINUX内核分析第六周学习总结——进程的描述和进程的创建 张忻(原创作品转载请注明出处) <Linux内核分析>MOOC课程http://mooc.study.163.com/cours ...
- Linux内核分析第七周总结
第七章 可执行程序的装载 可执行程序的生成 可执行程序的生成: c语言代码--->经过编译器的预处理--->编译成汇编代码--->由汇编器编译成目标代码--->链接成可执行文件 ...
随机推荐
- 帝国cms后台集成ueditor编辑器
我更换成百度编辑器的原因有以下几点:1.使用百度编辑器的图片粘贴上传功能,这个功能实在是太有必要了,有开发的过程中或上传的过程中,通常用qq直接截图,直接放到文章上面,避免了再放到本地保存的情况,真是 ...
- hadoop组件概念理解
一.HADOOP 二.HIVE 三.SQOOP 1.来由和作用 sqoop由一些封装好的MR程序的jar包构成,后演变成框架,但sqoop只有map任务没有reduce任务. 用于 hdfs.hive ...
- 记录一个IIS的服务器错误问题的解决方案
部署一个mvc项目到iis的时候提示有下面这样的错误, 看提示是Microsoft.CodeDom.Providers.DotNetCompilerPlatform,权限问题. 我是第一次遇到,所以只 ...
- 匹配追踪算法(MP)简介
图像的稀疏表征 分割原始图像为若干个\[\sqrt{n} \times \sqrt{n}\]的块. 这些图像块就是样本集合中的单个样本\(y = \mathbb{R}^n\). 在固定的字典上稀疏分解 ...
- GIT rebase讲解
对分支进行rebase 从master分支checkout出fork分支,并在master和fork上都进行了一些修改 现在fork分支想要及时的同步master分支上的修改,避免在已经失效的代码上继 ...
- eclipse创建spring boot项目加载不到application.properties配置文件
在配置文件application.properties中修改了端口号,但重启服务后发现端口号并没有跟着改变,发现是项目启动时没有加载application.properties文件导致 解决:项目-& ...
- telnet命令详解
基础命令学习目录 原文链接:https://www.cnblogs.com/PatrickLiu/p/8556762.html telnet命令用于登录远程主机,对远程主机进行管理.telnet因为采 ...
- unset命令详解
基础命令学习目录首页 功能说明:unset是一个内建的Unix shell命令,在Bourne shell家族(sh.ksh.bash等)和C shell家族(csh.tcsh等)都有实现.它可以取消 ...
- Java-URLEncoder.encode 什么时候才是必须的
当你希望把一段 URL 当成另一个 URL 的参数时,比如:当用户点击交易的按钮时你发现未登录就跳转到 login 页面同时带上一个参数记录在登录之前用户是希望访问的那个交易页面,这样在登录完成之后再 ...
- 基于Promise规范的fetch API的使用
基于Promise规范的fetch API的使用 fetch的使用 作用:fetch 这个API,是专门用来发起Ajax请求的: fetch 是由原生 JS 提供的 API ,专门用来取代 XHR 这 ...