只有在天足够黑的时候你才能看到星星。

BY WAY GK 加油


一、书本第七章知识总结【可执行程序工作原理】

1. ELF目标文件格式

  • ELF全称Executable and Linkable Format,可执行连接格式,ELF格式的文件用于存储Linux程序。ELF文件(目标文件)格式主要三种:
    1)可重定向文件:文件保存着代码和适当的数据,用来和其他的目标文件一起来创建一个可执行文件或者是一个共享目标文件。(目标
    文件或者静态库文件,即linux通常后缀为.a和.o的文件)
    2)可执行文件:文件保存着一个用来执行的程序。(例如bash,gcc等)
    3)共享目标文件:共享库。文件保存着代码和合适的数据,用来被下连接编辑器和动态链接器链接。(linux下后缀为.so的文件。)

  • 如下图:一般的 ELF 文件包括三个索引表:
    1)ELF header:在文件的开始,保存了路线图,描述了该文件的组织情况。
    2)Program header table:告诉系统如何创建进程映像。用来构造进程映像的目标文件必须具有程序头部表,可重定位文件不需要这个表。
    3)Section header table :包含了描述文件节区的信息,每个节区在表中都有一项,每一项给出诸如节区名称、节区大小这类信息。用于链接
    的目标文件必须包含节区头部表,其他目标文件可以有,也可以没有这个表。

  • 分析ELF文件头(ELF header)
    进入终端输入:cd /usr/include/elf.h,查看ELF的文件头包含整个文件的控制结构:

  • 取一段简单的代码进行分析:

hello.c

#include<stdio.h>
void main()
{
        printf("hello");
}

输入指令 readelf -h hello 得到ELF文件头信息:

由图可看出ELF文件头大小为64字节。

2. 预处理、编译、汇编、链接【c语言】

  • 编译器预处理、编译成汇编代码、汇编器编译成目标代码,然后链接成可执行文件,再将可执行程序加载到内存中执行,的过程可以通过下图展示(其中预处理已省略):
cd Code
vi hello.c
gcc –E –o hello.cpp hello.c –m32             //预处理,把include的文件包含进来及宏替换等工作
vi hello.cpp                                 //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                                      //得到二进制.o文件,ELF格式
gcc -o hello hello.o -m32                    //链接成可执行文件hello
vi hello                                     //也是二进制文件,ELF格式
gcc -o hello.static hello.o -m32 -static     //静态编译,占用内存较大
ls -l

gcc –E hello.c -o hello.i //预处理
gcc -S hello.i -o hello.s -m32//编译
gcc -c hello.s -o hello.o -m32 //汇编
gcc hello.o -o hello -m32 //链接

3. 可执行程序、共享库和动态加载

  • 当elf文件加载到内存的时候,他把代码的数据加载到一块内存中来,其中有很多段代码。加载进来之后默认从0x8048000开始加载,前面是elf头部的一些信息,一般头部的大小会有不同,加载的入口点的位置可能是0x8048300,即程序的实际入口。当启动一个刚加载过可执行文件的进程的时候,开始执行的入口点。文件是一个elf的静态连接文件,链接的时候已经链接好了。从这(0x8048300)开始执行,压栈出栈,从main函数到结束,所有的链接在静态链接时候已经设定好了。正常需要用到共享库或动态链接的时候,情况会更复杂一点。
  • 装载可执行程序之前,先了解一下可执行程序的执行环境。一般我们执行一个程序的shell环境,它本身不限制命令行参数的个数,命令行参数的个数受限于命令自身,比如 int main(int argc,char *argv[]) ,shell会调用execve将命令行参数和环境参数传递给可执行程序的main函数。命令行参数和环境串都放在用户态的堆栈中。Shell程序->execve -> sys_execve,然后在初始化新程序堆栈时拷贝进去。

  • 动态链接有可执行装载时的动态链接和运行时的动态链接,下面演示了两种动态链接:

1)共享库shilibexample.c实现SharedLibApi()函数
shilibexample.h:

#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_ */

SharedLibApi()函数:

#include <stdio.h>
#include "shlibexample.h"

/*
 * Shared Lib API Example
 * input    : none
 * output   : none
 * return   : SUCCESS(0)/FAILURE(-1)
 *
 */
int SharedLibApi()
{
printf("This is a shared libary!\n");
return SUCCESS;
}

通过gcc -shared shlibexaple.c -o libshlibexample.so -m32编译成一个共享库文件,
下面是同样使用 gcc -shared dllibexample.c -o libdllibexample.so -m32 得到动态加载共享库。

2) 共享库dellibexample.c实现DynamicalLoadingLibApi()函数:
dellibexample.h:

#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_ */

DynamicalLoadingLibApi()函数:

#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;
}

3)main.c()函数:

#include <stdio.h>
#include "shlibexample.h"
#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();
/* 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);
return SUCCESS;
}

4)我们发现main函数中含有include "shlibexample.h" 以及include dlfcn,而没有include dllibexample(动态加载共享库)。当需要调用动态加载共享库时,使用定义在dlfcn.h中的dlopen。
最后通过 gcc main.c -o main -L/path/to/your/dir -lshlibexample -ldl -m32
接下来编译main()函数,注意这里只提供shlibexample的-L(库对应的接口头文件所在目录)和-l(库名,如libshlibexample.so去掉lib和.so的部分),并没有提供dllibexample的相关信息,只是指明了-ldl,然后我们继续执行:

$ export LD_LIBRARY_PATH=$PWD #将当前目录加入默认路径,否则main找不到依赖的库文件,当然也可以将库文件copy到默认路径下。
$ ./main 

二、实验部分【使用gdb跟踪sys_execve内核函数的处理过程】

1)首先还是将menu目录删除,用git命令复制一个新的menu目录,用test_exec.c将test.c覆盖,然后重新编译rootfs

2)打开test.c文件,可以发现增加了一句,MenuConfig("exec","Execute a program",Exec)

*********************************************

3)看一下这段代码,和fork()函数类似,增加了一个fork,子进程增加了一个execlp("/hello","hello",NULL); 启动hello,看一下hello.c

4)看一下Makefile文件,静态的方式编译了hello.c,并在生成根文件系统时把init 和hello都放在rootfs里面

5)输入命令 make rootfs ,在qemu窗口中输入help 执行一下exec

发现在MenuOS中使用help命令可以看到增加了exec命令,执行exec指令发现比fork指令增加了一行输出“helloworld!”,实际上是新加载了一个可执行程序来输出了一行语句。

6)先cd .. 返回到上一级,qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S启动,水平分割,gdb。然后把符号表文件加载进来,gdb服务器使用默认端口号1234来连接,并通过gdb跟踪调试,设置断点,可以先停在sys_execve然后再设置其他断点。

*********************************************

7)调试执行

  • 按c执行

  • 连续c执行三次,在menuos窗口输入exec,发现执行到 This is child process! 停下

    *********************************************

  • 进入sys_execve系统调用,list列出来,跟踪,接下来通过s进入sys_execve内部

    *********************************************

  • 按c继续执行到load_elf_binary,list查看;再按c执行,执行到start_thread,想知道new_ip到底指向哪里,new_ip是返回到用户态的第一条指令的地址。再水平分割一个控制台出来,使用命令readelf -h hello,可以看到入口点地址和上面po new_ip所显示的地址一样:
  • 然后我们继续执行s步骤,可以看到在进行修改内核堆栈的位置,发现原来压栈的ip和sp都被改成了新的ip(程序hello的入口点地址)和新的sp,这样在返回到用户态的时候程序就有一个新的可执行上下文环境。最后按一下c,exec的执行结束:

三、实验收获

1. 关于execve

  • 当可执行程序在执行到execve的时候陷入到内核态,当前进程的可执行程序被execve的加载的可执行文件覆盖,当execve的系统调用返回时,返回的不是原来的可执行程序,而是新的可执行程序的起点(main函数)。shell环境会执行execve,把命令行参数和环境变量都加载进来,当系统调用陷入到内核里面的时候,system call调用sys_execve。sys_execve中调用了do_execve。

2. fork和execve的区别

  • man exec就可以知到:
    The exec() family of functions replaces the current process image with a new process image
    exec是没有创建新进程的,而是把当前进程对应的应用换成新的应用。因此,它里头当前不会去fork了。
    举个例,如果PID=1000的进程A, 执行ecec B, 那就PID=1000的进程就会变为B,A的资源会被系统回收。对Exec函数来说,没所谓父子进程,只有当前进程,当前执行exec函数的进程。新进程 是在用户调用fork时生成的。
  • fork和exec不一样,它的作用是复制一个进程,但两个进程都运行相同的程序。task_struct也是fork的时间新建的。一般这两函数是联用的,先fork,再在子进程里exec。在内核态并没有相互调用关系。

3. gdb调试若干问题

点击

2018-2019-1 20189201 《LInux内核原理与分析》第八周作业的更多相关文章

  1. 2019-2020-1 20199329《Linux内核原理与分析》第九周作业

    <Linux内核原理与分析>第九周作业 一.本周内容概述: 阐释linux操作系统的整体构架 理解linux系统的一般执行过程和进程调度的时机 理解linux系统的中断和进程上下文切换 二 ...

  2. 2019-2020-1 20199329《Linux内核原理与分析》第二周作业

    <Linux内核原理与分析>第二周作业 一.上周问题总结: 未能及时整理笔记 Linux还需要多用 markdown格式不熟练 发布博客时间超过规定期限 二.本周学习内容: <庖丁解 ...

  3. 20169212《Linux内核原理与分析》第二周作业

    <Linux内核原理与分析>第二周作业 这一周学习了MOOCLinux内核分析的第一讲,计算机是如何工作的?由于本科对相关知识的不熟悉,所以感觉有的知识理解起来了有一定的难度,不过多查查资 ...

  4. 20169210《Linux内核原理与分析》第二周作业

    <Linux内核原理与分析>第二周作业 本周作业分为两部分:第一部分为观看学习视频并完成实验楼实验一:第二部分为看<Linux内核设计与实现>1.2.18章并安装配置内核. 第 ...

  5. 2018-2019-1 20189221 《Linux内核原理与分析》第九周作业

    2018-2019-1 20189221 <Linux内核原理与分析>第九周作业 实验八 理理解进程调度时机跟踪分析进程调度与进程切换的过程 进程调度 进度调度时机: 1.中断处理过程(包 ...

  6. 2017-2018-1 20179215《Linux内核原理与分析》第二周作业

    20179215<Linux内核原理与分析>第二周作业 这一周主要了解了计算机是如何工作的,包括现在存储程序计算机的工作模型.X86汇编指令包括几种内存地址的寻址方式和push.pop.c ...

  7. 2019-2020-1 20209313《Linux内核原理与分析》第二周作业

    2019-2020-1 20209313<Linux内核原理与分析>第二周作业 零.总结 阐明自己对"计算机是如何工作的"理解. 一.myod 步骤 复习c文件处理内容 ...

  8. 2018-2019-1 20189221《Linux内核原理与分析》第一周作业

    Linux内核原理与分析 - 第一周作业 实验1 Linux系统简介 Linux历史 1991 年 10 月,Linus Torvalds想在自己的电脑上运行UNIX,可是 UNIX 的商业版本非常昂 ...

  9. 《Linux内核原理与分析》第一周作业 20189210

    实验一 Linux系统简介 这一节主要学习了Linux的历史,Linux有关的重要人物以及学习Linux的方法,Linux和Windows的区别.其中学到了LInux中的应用程序大都为开源自由的软件, ...

  10. 2018-2019-1 20189221《Linux内核原理与分析》第二周作业

    读书报告 <庖丁解牛Linux内核分析> 第 1 章 计算工作原理 1.1 存储程序计算机工作模型 1.2 x86-32汇编基础 1.3汇编一个简单的C语言程序并分析其汇编指令执行过程 因 ...

随机推荐

  1. JMeter测试(菜鸟级,高手莫点)

    干了这么多年开发,多线程测试都是自己写程序,都不知道还有JMeter这玩意,莫笑莫笑 一个简单的测试计划如下: 在[测试计划]下创建[线程组],线程组下[HTTP请求]和[查看结果树]是必须的,[HT ...

  2. Day038--Python--Gevent , IO多路复用

    1. 协程: gevent  (遇到IO自动切换) import gevent import time from gevent import monkey; monkey.patch_all() # ...

  3. SpringBoot使用消息队列RabbitMQ

    RabbitMQ 即一个消息队列,主要是用来实现应用程序的异步和解耦,同时也能起到消息缓冲.消息分发的作用.RabbitMQ是实现AMQP(高级消息队列协议)的消息中间件的一种,AMQP,即Advan ...

  4. HDU 1426(数独 DFS)

    题意是完成数独. 记录全图,将待填位置处填 0,记录下所有的待填位置,初始化结束.在每个待填位置处尝试填入 1 - 9,若经过判断后该位置可以填入某数字,则继续向下填下一个位置, 回溯时把待填位置重新 ...

  5. freemarker和thymeleaf的使用样例

    最近需要对公司项目首页使用Java模板重做,以提高首屏加载速度和优化SEO. 在选择模板时发现freemarker和thymeleaf最为常用. 两者最大的区别在于语法,对性能方面未作测试,具体性能测 ...

  6. 版本控制工具 - Git

    版本控制工具 - Git 安装完成后,打开Git Bash,这是一个命令行工具,用于操作仓库和仓库的文件.你可以通过命令将已经存在的项目变成仓库,也可以重新创建一个新项目再通过命令将其变成仓库,还可以 ...

  7. [转] Python 字符编码判断

    转自:http://www.cnblogs.com/dkblog/archive/2011/03/02/1980644.html 法一: isinstance(s, str) 用来判断是否为一般字符串 ...

  8. zabbix3.2监控rabbitmq集群

    监控模板和脚本github地址:https://github.com/jasonmcintosh/rabbitmq-zabbix/tree/master/scripts/rabbitmq .将rabb ...

  9. Python-Django 路由控制器

    1 路由的基本使用: # url是个函数,有四个参数,第一个参数要传正则表达式,第二参数传函数内存地址,第三个参数传默认参数,第四个是路由的别名 url(r'^liuqingzheng/article ...

  10. web页面实现文件下载的几种方法

    今天碰到文件下载的一些问题,本着知其然也要知其所以然的精神,站在巨人的肩膀上深入学习和测试了一下,抛砖引玉,现在总结结论如下: 1)标准URL下载方式可以通过在web页面中嵌入 url超级链接,标准的 ...