一、 预处理、编译、链接

gcc hello.c -o hello.
  • gcc编译源代码生成最终可执行的二进制程序,GCC后台隐含执行了四个阶段步骤。

    预处理 → 编译 汇编 链接
  • 预处理:编译器将C源代码中包含的头文件编译进来和执行宏替换等工作。

gcc -E hello.c -o hello.i

  编译:gcc首先要检查代码的规范性、是否有语法错误等,以确定代码的实际要做的工作,在检查无误后,gcc把代码翻译成汇编语言。

gcc –S hello.i –o hello.s
-S:该选项只进行编译而不进行汇编,生成汇编代码。

  汇编:把编译阶段生成的.s文件转成二进制目标代码.

gcc –c hello.s –o hello.o

  链接:将编译输出.o文件链接成最终的可执行文件。

gcc hello.o –o hello

  运行:若链接没有-o指明,则生成可执行文件默认为a.out

./hello

二、可执行文件

  1、在windows环境下,只要双击一个.exe的文件就可以执行一个程序,这个以.exe结尾的文件就是一个可执行文件。在andriod系统下,一个.apk的文件就是一个可执行文件,在linux系统下,可执行文件在linux环境下并没有什么特殊的后缀标记,只是在生成该文件时,它的属性设置了可执行(就是‘x’),那么他就是属于可执行文件。

  2、linux系统中,可执行文件的格式为elf(Executable and Linking Format)格式

    ELF文件有三种类型:

  • 可重定位文件 :也就是通常称的目标文件,后缀为.o。链接器将它作为输入,经链接处理后,生成一个可执行的对象文件 (Executable file) 或者一个可被共享的对象文件。
  • 共享文件 :这些就是所谓的动态库文件,也即 .so 文件。如果拿前面的静态库来生成可执行程序,那每个生成的可执行程序中都会有一份库代码的拷贝。如果在磁盘中存储这些可执行程序,那就会占用额外的磁盘空间;另外如果拿它们放到Linux系统上一起运行,也会浪费掉宝贵的物理内存。如果将静态库换成动态库,那么这些问题都不会出现。
  • 可执行文件
    • 第一个是连接编辑器,可以和其他的可重定位和共享object文件来创建其他的object。
    • 第二个是动态链接器,联合一个可执行文件和其他的共享object文件来创建一个进程映象。
  3、查看一个可执行文件头部结构:

readelf -h
    可知ELF头是程序表

typedef struct {
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;

4、可执行程序动态链接

(1)动态链接

load_elf_binary(...)
{
...
kernel_read();//其实就是文件解析
...
//映射到进程空间 0x804 8000地址
elf_map();//
...
if(elf_interpreter) //依赖动态库的话
{
...
//装载ld的起点 #获得动态连接器的程序起点
elf_entry=load_elf_interp(...);
...
}
else //静态链接
{
...
elf_entry = loc->elf_ex.e_entry;
...
}
...
//static exe: elf_entry: 0x804 8000
//exe with dyanmic lib: elf_entry: ld.so addr
start_thread(regs,elf_entry,bprm->p);
}

    • 实际上,装载过程是一个广度遍历,遍历的对象是“依赖树”。
    • 主要过程是动态链接器完成、用户态完成。

(2)装载时动态链接

/*准备.so文件*/
shlibexample.h (1.3 KB) - Interface of Shared Lib Example
shlibexample.c (1.2 KB) - Implement of Shared Lib Example

/*编译成libshlibexample.so文件*/
$ gcc -shared shlibexample.c -o libshlibexample.so -m32

/*使用库文件(因为已经包含了头文件所以可以直接调用函数)*/
SharedLibApi();

  (3)运行时动态链接

dllibexample.h (1.3 KB) - Interface of Dynamical Loading Lib Example
dllibexample.c (1.3 KB) - Implement of Dynamical Loading Lib Example

/*编译成libdllibexample.so文件*/
$ gcc -shared dllibexample.c -o libdllibexample.so -m32

/*使用库文件*/
void * handle = dlopen("libdllibexample.so",RTLD_NOW);//先加载进来
int (*func)(void);//声明一个函数指针
func = dlsym(handle,"DynamicalLoadingLibApi");//根据名称找到函数指针
func(); //调用已声明函数

  (4)运行

$ gcc main.c -o main -L/path/to/your/dir -lshlibexample -ldl -m32
$ export LD_LIBRARY_PATH=$PWD
/*将当前目录加入默认路径,否则main找不到依赖的库文件,当然也可以将库文件copy到默认路径下。*/

三、可执行程序的装载

1、sys_execve内核处理过程
  (1)新的可执行程序

  • 一般是地址空间为0x8048000或0x8048300

(2)execve与fork

  • execve和fork都是特殊一点的系统调用:一般的都是陷入到内核态再返回到用户态。
  • fork两次返回,第一次返回到父进程继续向下执行,第二次是子进程返回到ret_from_fork然后正常返回到用户
  • execve执行的时候陷入到内核态,用execve中加载的程序把当前正在执行的程序覆盖掉,当系统调用返回的时候也就返回到新的可执行程序起点。

  (3)execve

  • 执行到可执行程序 -> 陷入内核
  • 构造新的可执行文件 -> 覆盖掉原可执行程序
  • 返回到新的可执行程序,作为起点(也就是main函数)
  • 需要构造其执行环境;

Shell会调用execve将命令行参数和环境参数传递给可执行程序的main函数,先函数调用参数传递,再系统调用参数传递。
(4)静态链接的可执行程序和动态链接的可执行程序execve系统调用返回时不同

  • 静态链接:elf_entry指向可执行文件的头部,一般是main函数,是新程序执行的起点。
  • 动态链接:elf_entry指向ld(动态链接器)的起点,加载load_elf_interp
四、实验

1、在实验楼虚拟机下,键入以下指令更新 MenuOS

cd LinuxKernel
rm menu -rf
git config --global user.name "Scott Chacon"
git config --global user.mail "schacon@gmail.com"
git clone https://github.com/mengning/menu.git
cd menu
mv test_exec.c test.c
make rootfs

2、MenuOS 系统完毕之后,在 MenuOS 中运行 help 指令与 exec 指令

3、以跟踪模式启动MenuOS
  关闭 MenuOS,以如下指令重启 MenuOS:

qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S 

  水平分割终端模拟器窗口,运行gdb,让 MenuOS 完全启动,处于接收命令状态:

cd ~/LinuxKernel
gdb
(gdb) file linux-3.18.6/vmlinux
(gdb) target remote:1234
(gdb) c # 让 MenuOS 完全启动,处于接收命令状态

 按“Ctrl +C", 让 gdb 处于 gdb 命令行模式,设置断点
4、跟踪断点
在 MenuOS 命令行执行 exec 指令,gdb 自动捕捉到 1#断点
 
 
五、总结
  Linux 系统通过 execve API 启动一个新进程,该 API 又呼叫 sys_execve 系统调用,负责将新的程序代码和数据替换到新的进程中,打开可执行文件,载入依赖的库文件,申请新的内存空间,最后执行 start_thread,设置 new_ip、new_sp,完成新进程的代码和数据替换,然后返回,接下来就是执行新的进程代码了。

Linux内核分析作业第七周的更多相关文章

  1. 《Linux内核分析》第七周学习总结

    <Linux内核分析>第七周学习总结                         ——可执行程序的装载 姓名:王玮怡  学号:20135116 一.理论部分总结 (一)可执行程序的装载 ...

  2. 《Linux内核分析》第七周学习笔记

    <Linux内核分析>第七周学习笔记 可执行程序的装载 郭垚 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/co ...

  3. 《Linux内核分析》第七周: 可执行程序的装载

    LINUX内核分析第七周学习总结--可执行程序的装载 杨舒雯(原创作品转载请注明出处) <Linux内核分析>MOOC课程http://mooc.study.163.com/course/ ...

  4. 《Linux内核分析》第七周 可执行程序的装载

    [刘蔚然 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000] WEEK SEVEN ...

  5. 《Linux内核分析》第七周笔记 可执行程序的装载

    20135132陈雨鑫 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000  ...

  6. 《Linux内核分析》第七周学习总结 可执行程序的装载

    第七周.可执行程序的装载 一.可执行程序是如何产生的? (1).c文件gcc汇编形成.s和.asm汇编代码: (2)汇编代码经过gas变成.o目标文件: (3)目标文件变成可执行文件: (4)可执行文 ...

  7. Linux内核分析作业第五周

    系统调用的三个层次(下) 一.给MenuOS增加time和time-asm命令 1.克隆并自动编译 MenuOS rm menu -rf 强制删除原menu文件 git clone https://g ...

  8. Linux内核分析(第七周)

    可执行程序的装载 一.预处理.编译.链接和目标文件的格式 1.可执行程序怎么来的? 预处理: gcc -E -o hello.cpp hello.c -m32 *负责把include的文件包含进来及宏 ...

  9. Linux内核分析作业第三周

    一.实验楼实验 使用实验楼的虚拟机打开shell 1 cd LinuxKernel/ 2 qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd ...

随机推荐

  1. Jenkins 自动发布 Spring Boot 项目(Gitee)

    1.下载 wget http://mirrors.jenkins.io/war-stable/latest/jenkins.war,并部署到tomcat下 2.机器安装好 java ,maven ,g ...

  2. ccf--20150303--节日

    本题思路:首先,计算a月1日是星期几,然后再通过b和c得出日期monday,最后判断monday是否合法. 题目与代码如下: 问题描述 试题编号: 201503-3 试题名称: 节日 时间限制: 1. ...

  3. Orcale日期函数to_date(),to_char()

    日期转换的两个函数分别是to_date()和to_char(),to_date() 作用将字符类型按一定格式转化为日期类型, to_char() 将日期转按一定格式换成字符类型 其中当时间需要精确的时 ...

  4. Java设计模式之九 ----- 解释器模式和迭代器模式

    前言 在上一篇中我们学习了行为型模式的责任链模式(Chain of Responsibility Pattern)和命令模式(Command Pattern).本篇则来学习下行为型模式的两个模式, 解 ...

  5. jquery easyui datagrid js获取记录数 页数 当前页

    首先要吐槽的是 easyui竟然找不到未压缩的版本(1 也许它是藏在某个个几角旮旯; 2 压缩的版本想看懂? 大概你得在你脑袋上外接个CPU), 而且官方的文档简陋的不能再简陋了, 想实现个稍微复杂点 ...

  6. centos7下安装docker(3.1创建镜像commit)

    docker commit创建镜像 步骤:1.运行容器 2.修改容器 3.将容器保存为镜像 1. 注:-it是以交互模式进入容器,并打开终端 2.安装一个vim进行修改镜像 yum install - ...

  7. 3902-luogu 最长不下降子区间

    题目 现有数列A1,A2,…An ,修改最少的数字,使得数列严格单调递增. 依旧是书上的题 但是书上的范围比较小 而 lg上的数据范围很大 按书上的 方法 是会超时 只能过一半的数据 但是 算法思路还 ...

  8. 18核心的Intel i9将在2019年夏发布

    受工艺和架构限制,Intel HEDT发烧级桌面平台面对AMD早已经优势不再,但升级仍然在继续. 去年10月份,Intel一方面发布了第二代酷睿i9 X系列,仍然基于14nm Skylake-X架构, ...

  9. gitlab分支代码本地拉取及jenkins关联gitlab分支

    git本地拉取 git init git remote add origin http://47.*.*.*:8089/back_dev/claimeureka.git git fetch origi ...

  10. Objective-C 图片处理

    图片处理 编码 解码 imageNamed