程序人生-Hello’s P2P

打印 上一主题 下一主题

主题 649|帖子 649|积分 1947





计算机系统


大作业



题     目  程序人生-Hellos P2P 
专       业  人工智能方向(2+x)   
学     号   2021113659   
班   级     21WL024       
学       生        严铮       
指 导 教 师         郑贵滨         






计算机科学与技能学院

2023年5月

摘  要

本文通过一个简单的hello程序,展示了计算机系统在程序从文本到可执行文件再到进程的整个过程中所饰演的脚色。在这个过程中,我们了解了程序预处理、编译、汇编和链接等过程,同时也先容了与进程、存储相关的知识。这篇文章深入探究了计算机系统的软硬件团结,为我们提供了一个系统和广泛的视角。


关键词:计算机系统;进程;软硬件团结;                            









目  录


第1章 概述

1.1 Hello简介

1.2 环境与工具

1.3 中间结果

1.4 本章小结

第2章 预处理

2.1 预处理的概念与作用

2.2在Ubuntu下预处理的下令

2.3 Hello的预处理结果解析

2.4 本章小结

第3章 编译

3.1 编译的概念与作用

3.2 在Ubuntu下编译的下令

3.3 Hello的编译结果解析

3.4 本章小结

第4章 汇编

4.1 汇编的概念与作用

4.2 在Ubuntu下汇编的下令

4.3 可重定位目的elf格式

4.4 Hello.o的结果解析

4.5 本章小结

5.1 链接的概念与作用

5.2 在Ubuntu下链接的下令

5.3 可执行目的文件hello的格式

5.4 hello的假造地点空间

5.5 链接的重定位过程分析

5.6 hello的执行流程

5.7 Hello的动态链接分析

5.8 本章小结

6hello进程管理

6.1 进程的概念与作用

6.2 简述壳Shell-bash的作用与处理流程

6.3 Hello的fork进程创建过程

6.4 Hello的execve过程

6.5 Hello的进程执行

6.6 hello的异常与信号处理

6.7本章小结

7hello的存储管理

7.1 hello的存储器地点空间

7.2 Intel逻辑地点到线性地点的变换-段式管理

7.3 Hello的线性地点到物理地点的变换-页式管理

7.4 TLB与四级页表支持下的VA到PA的变换

7.5 三级Cache支持下的物理内存访问

7.6 hello进程fork时的内存映射

7.7 hello进程execve时的内存映射

7.8 缺页故障与缺页中断处理

7.9本章小结

结论

附件

参考文献



第1章 概述

(0.5分)


1.1 Hello简介

1.1.1 Hello的P2P过程:
Hello程序的生命周期始于高级C语言程序。在系统上运行hello.c程序时,需要经过一系列步调。首先,预处理器(cpp)对hello.c程序举行处理,生成修改后的源程序hello.i。接下来,编译器(cc1)将hello.i翻译为汇编程序hello.s,然后,汇编器(as)将hello.s转化为机器语言指令,并打包成可重定位目的程序hello.o。此时,链接器将调用的尺度C库中的函数(如printf)对应的预编译好的目的文件合并到hello.o文件中,生成可执行目的程序hello。当我们在shell中运行hello程序时,使用fork()函数创建子进程,然后使用execve函数加载hello程序。此时,hello程序由一个程序(program)转变为一个进程(process),完成了P2P的过程。

1.1.2 Hello的020过程:
在020过程中,我们使用shell中的fork()函数创建子进程,然后使用execve函数加载可执行目的程序hello。在程序开始时,假造内存被映射到物理内存中,并将程序载入物理内存,接着进入CPU处理。CPU为执行文件hello分配时间片,并举行取指、译码、执行等流水线操纵。在执行过程中,内存管理器和CPU使用L1、L2、L3三级缓存和TLB多级页表从物理内存中获取数据,并通过I/O系统根据代码指令举行输出。在程序运行结束后,父进程会对其举行接纳,内核将其从系统中清除,此时hello就从0转换为0,完成了020过程。
1.2 环境与工具

1.2.1 硬件环境
硬件型号:Lenovo Yoga 14sACH 2021 
内存:16.0 GiB
处理器:AMD® Ryzen 7 5800h with radeon graphics × 16
磁盘容量:512.1 GB
1.2.2 软件环境
Ubuntu 22.04.2 LTS 64位
1.2.3 开发工具
Visual Studio Code (Version 1.76)

edb
1.3 中间结果

以下是中间产物的文件名及其作用的说明:

- hello.i:这是一个由C预处理器生成的ASCII码中间文件,用于分析预处理过程。预处理器会将源代码中的宏界说、条件编译指令等举行处理,并将结果输出到该文件中。
- hello.s:这是一个由C编译器生成的ASCII汇编语言文件,用于分析编译过程。编译器会将预处理后的代码转换成汇编语言,并将结果输出到该文件中。
- hello.o:这是一个由汇编器生成的可重定位目的程序,用于分析汇编过程。汇编器会将汇编语言代码转换成机器码,并生成可重定位目的文件,该文件可以被链接器用于生成可执行目的文件。
- hello:这是一个由链接器生成的可执行目的文件,用于分析链接过程。链接器会将多个可重定位目的文件合并成一个可执行目的文件,并解决符号引用、重定位等问题。
- obj_hello.o.text:这是hello.o的反汇编文件,用于分析可重定位目的文件hello.o。反汇编器会将机器码转换成汇编语言代码,并输出到该文件中。
- obj_hello.text:这是hello的反汇编文件,用于分析可执行目的文件hello。反汇编器会将机器码转换成汇编语言代码,并输出到该文件中。
- hello_elf.text:这是hello的ELF格式,用于分析可执行目的文件hello。ELF是一种可执行文件格式,包含了程序的代码、数据、符号表等信息。该文件可以被调试器用于调试程序。
1.4 本章小结

本章概述了hello的生命周期,包括从程序到进程再到运行的经历,以及经历的P2P和020两个过程。同时,还列出了测试环境和工具,以及生成的中间结果的名称和作用,从整体上对hello举行了简要先容。



第2章 预处理

(0.5分)


2.1 预处理的概念与作用

2.1.1 预处理的概念:
C语言程序预处理是C编译过程中的第一步,其作用是对源程序举行一些文本更换和宏展开等操纵,以便更好地为编译器后续的处理做准备。
2.2.1 预处理的作用
预处理器会扫描源程序中以#开头的预处理指令,并根据指令的不同作用举行处理。例如,预处理指令#include可以将指定的头文件内容插入到当前文件中,预处理指令#define可以界说宏,宏展开可以将宏更换成实际的代码等。
预处理器的另一个作用是举行条件编译。通过#if、#ifdef、#ifndef等指令,可以根据条件判定是否编译某些代码块,从而实当代码的可移植性。
预处理器还可以通过#pragma指令向编译器传递一些特殊的信息,如编译器的优化等级。
综上,预处理器为编译器提供了更好的源程序,使得编译器能够更好地举行后续处理,也进步了代码的可读性和可移植性。
2.2在Ubuntu下预处理的下令

下令:gcc hello.c -E -o hello.i
结果:生成了hello.i文件(如下图所示)

 

图2-1:hello.i文件生成

2.3 Hello的预处理结果解析

预处理器会将源程序中所有的预处理指令(以#开头的指令)和头文件(以#include开头的指令)举行处理,将它们展开为详细的代码或内容。

在本程序中,预处理器会将三个头文件(stdio.h、unistd.h和stdlib.h)中的内容展开到hello.i文件。

如 1."/usr/include/stdio.h" 是对stdio.h头文件的目录标注。2.hello.i文件出现大量typedef,enum,struct语句,均为头文件内部界说。3.出现了大量extern外部变量。

文件的末了是hello.c的其他内容,在这里是去除头文件后的其他语句。


2.4 本章小结

    通过 gcc hello.c -E -o hello.i下令生成了预处理文件hello.i,方便后续的编译和链接操纵。




第3章 编译


(2分)


3.1 编译的概念与作用

3.1.1编译的概念:
概念:将高级语言转化为机器语言,让计算性能够理解和执行程序。高级语言相对于机器语言来说更加易于理解和编写,但是计算机并不直接能够辨认高级语言,因此需要将其转化为机器语言。在这里指编译器(cc1)将文本文件hello.i翻译成汇编语言程序hello.s的过程。
3.1.2编译的作用:
作用:编译器在编译的过程中,会对程序举行词法分析、语法分析、语义分析等操纵,以便生成正确的汇编语言程序。这些操纵会查抄程序中的错误和不符合规范的代码,并给出相应的提示,帮助程序员在编写代码时避免一些常见的错误。别的,编译器还可以根据编译选项举行一些适当的优化,以进步程序的性能和服从。例如,可以对循环举行展开、对常量表达式举行求值等等。这些优化可以使程序更加高效,从而进步计算机的响应速度和运行服从。
        

3.2 在Ubuntu下编译的下令

下令:gcc -S hello.i -o hello.s
结果:生成了hello.s文件(如下图所示)

 

图3-1:hello.s文件的生成

3.3 Hello的编译结果解析


3.3.1文件声明
.file 源文件名,在汇编语言中,使用指令 ".file" 可以指定当前的源文件名
.text是用来存放程序执行代码的一块内存区域,也被称为代码段或文本段。
.section 可以将代码划分成若干个段,方便管理和维护。
.rodata 是用于维护只读数据的一块内存区域。
.align 8 是一条地点对齐的伪指令,用来指定符号的对齐方式,将符号的地点按照 8 字节对齐。
.string 可以指定字符串的存储位置,将字符串的内容存储在内存中。
.global是用来让一个符号对链接器可见的指令,可以供其他链接对象模块使用,告诉编译器后续跟的是一个全局可见的名字。
.type 可以指定一个符号的类型,是对象类型照旧函数类型。


3.3.2数据
(1) 字符串常量
源程序中有两个字符串常量,这两个常量都出现在输出函数函数中。这两个字符串之前都由声明为.rodata这意味着这两个串都将存储在只读数据区中。

LC0部分和.LC1部分为两个字符串提示符,放在静态存储区,不可改变,一直存在。表示"用法: Hello 学号 姓名 秒数!\n"和"Hello %s %s\n"。在源程序中的位置如图所示


 


图3-2:静态字符串在静态存储区中的位置

 

(2)变量


  • 局部变量
局部变量是在函数内部声明的变量,只能在函数内部使用。局部变量的作用域仅限于声明它的函数内部,在函数执行完毕后,局部变量的内存空间会被开释,在hello.c程序中,主要的局部变量为i。


 

图3-3:通过movl指令将0赋值到i,i的存储位置为-4(%rbp)。



 
图3-4:在for循环中,程序再次使用局部变量i的值举行循环条件的判定,并且对i的值举行运算。



 
图3-5:局部变量i在源程序中的位置




  • 函数参数
           函数参数是在函数界说时声明的变量,用于接收函数调用时传递的值。函数参数可以在函数内部使用,但只能在函数内部使用。函数开始时被存储在栈中,函数返回时,所占空间被开释。

在hello.c程序中,main函数将argc和argv作为局部变量存储在栈中,此中:argc为main函数下令行参数的个数,argv[]为存放参数字符串指针的数组。


图3-6:函数参数对应的汇编语言

        
图3-7:函数参数在源程序中的位置


  • 表达式
表达式是由操纵数和运算符组成的式子,可以用来计算出一个值。操纵数可以是变量、常量、函数调用等,运算符可以是算术运算符、逻辑运算符、关系运算符等。


  • 算术表达式
算数表达式是一种常见的C语言表达式,由算术运算符和操纵数组成,用于举行数值计算。常用的算术运算符包括加法运算符+、减法运算符-、乘法运算符*、除法运算符/和求余运算符%。

在hello程序中,i++即为算数表达式,作用是使i每次循环自增1。每次循环结束使用addl指令将存储在栈中位置-4(%rbp)处的局部变量i的值加1,其对应的汇编语言如图所示:


图3-8:i++算数表达式对应的汇编语言

    
图3-9:i++在源程序中的表示



  • 赋值表达式
  赋值表达式是一种常见的C语言表达式,用于将一个值赋给一个变量。在汇编中将mov指令用于将一个值存储到指定的内存地点或寄存器中。


图3-10:赋值表达式对应的汇编语言

  
图3-11:赋值表达式在源程序中的位置



  • 关系判定表达式
关系表达式是一种常见的C语言表达式,用于比力两个值的巨细关系,返回一个布尔值(true或false)。常用的关系运算符包括小于<、大于>、小于即是<=、大于即是>=、即是==和不即是!=。                                         汇编中,关系表达式对应的是比力两个值的巨细关系,并将比力结果存储到标记寄存器中。

Hello程序中关系判定表达式有两个,分别为if条件判定中对argv即main函数参数个数举行判定,和for循环中对i的值举行的循环条件判定。这两地方对应的汇编代码分别如图3-14和3-15所示。


图3-12:argv参数关系表达式对应的汇编语言


图3-13:变量i的值(循环条件)关系表达式对应的汇编语言


图3-14:关系判定表达式在源程序中的位置

3.2.2各类操纵

1.非函数操纵

(1)赋值操纵:

赋值操纵是对变量举行赋值的一种常见操纵,可以将一个值赋给一个变量。在程序开始运行时,常量、全局变量和静态变量就已经被赋值,而对于局部变量,程序在运行过程中对其举行赋值。赋值操纵和赋值表达式密不可分,赋值表达式由变量、运算符和操纵数组成,用于计算出一个值并将其赋给变量。

(2)算数操纵:

算数操纵是一种常见的操纵,用于举行数值计算。常见的算术运算符包括加法运算符+、减法运算符-、乘法运算符*、除法运算符/和求余运算符%。算数操纵和算数表达式密不可分,算数表达式由操纵数和运算符组成,用于计算出一个值。详见3.3.1

(3)关系操纵:

关系操纵是一种常见的操纵,用于比力两个值的巨细关系,返回一个布尔值(true或false)。常用的关系运算符包括小于<、大于>、小于即是<=、大于即是>=、即是==和不即是!=。关系操纵和关系表达式密不可分,关系表达式由操纵数和运算符组成,用于比力两个值的巨细关系。详见3.3.1

(4)控制转移操纵:

控制转移操纵是一种常见的操纵,用于改变程序的执行顺序。在汇编语言中,一般使用jump类指令举行控制转移,多数出现在条件判定、循环、switch语句中。编译器使用cmp类指令更新条件码寄存器后,使用相应的jump类指令跳转到相应位置。控制转移操纵可以是条件跳转或无条件跳转。

例如,在图中,控制转移对应源程序中的if(argc!=4)。在图中,控制转移发生在将i赋值为0后跳入for循环,此部分为无条件跳转。在图中,控制转移对应源程序中for循环中的i<8,即如果i大于即是8,则跳出循环。




图3-15:hello.s中的控制转移


  • 数组操纵
      Argv是字符串指针数组,其有三个元素,图中圈出的汇编代码分别对应argv[1]、argv[2]、argv[3]。

图3-16:argv数组对应的元素

数组元素存储在堆栈中,数组首地点存储在栈中位置-32(%rbp)处,指针巨细为8字节,每个字符串地点相差8个字节。编译器以数组首地点-32(%rbp)为基址,以偏移量为索引,寻找各个字符串指针的地点。

2.函数操纵

函数操纵包括三个过程:参数传递、函数调用和函数返回。在调用函数之前,编译器会将函数参数存储在寄存器中,以供调用的函数使用。参数传递遵照肯定的规则,根据参数个数的不同,接纳不同的传递方式。

(1)参数传递
  在函数参数个数小于即是六个时,按照以下优先级顺序使用寄存器传递参数:%rdi、%rsi、%rdx、%rcx、%r8、%r9。当参数个数大于六个时,前六个参数使用寄存器存放,其他参数压入栈中存储。
  在Hello程序中,多次调用函数并涉及到参数传递。在第一次调用printf函数时,如果输入参数不为4,程序会打印提示信息,将提示字符串的地点赋给%rdi,然后调用puts函数输出。这个过程可以在图3-21中看到。


图3-17:第一次调用printf函数传参对应的汇编语言

第二次调用printf函数:即printf(“Hello %s %s\n”,argv[1],argv[2]);其汇编语言如图3-22所示


图3-18:第二次调用printf函数传参对应的汇编语言

调用exit函数 :将1存入%edi中,然后传入exit函数,对应的汇编语言如图3-23所示


图3-19:参数传入exit

调用atoi函数:将argv[3]的值从栈中取出到%rdi中,再传入atoi函数


图3-20:参数传入atoi


调用sleep函数:将atoi函数的返回值从%eax中存到%edi中,作为参数传递给sleep。



图3-21:参数传入sleep


(2)函数调用
在程序中,使用汇编指令call+函数地点来调用函数。但是由于没有重定位,无法得知函数地点,因此需要使用函数名作为助记符取代。在调用函数之前,call指令将返回地点压入栈中,为局部变量和函数参数创建栈帧,然后转移到调用函数地点。
在调用函数时,程序会跳转到函数的入口处开始执行。在函数执行过程中,将会使用栈来存储函数的局部变量和传递的参数。函数的执行完毕后,使用汇编指令ret从调用的函数中返回,并且还原栈帧。此时,函数的返回值存放在寄存器%rax中,供调用函数使用。

例如:call puts@PLT、call exit@PLT、call printf@PLT、call atoi@PLT、call sleep@PLT、call getchar@PLT

(3)函数返回
在函数执行完毕后,使用汇编指令ret从调用的函数中返回,并且还原栈帧。在返回之前,函数的返回值存放在寄存器%rax中。如果函数没有返回值,则%rax寄存器的值为0。
在返回过程中,程序会将栈顶指针移动到之宿世存的返回地点处,然后将返回地点弹出栈。此时,程序会跳转到返回地点处继续执行。如果函数返回后需要举行一些清理工作,程序会在返回之前执行这些清理工作。
需要留意的是,函数返回后需要确保栈的状态正确,以免影响其他函数的执行。因此,在编写函数时需要留意栈的使用方式和状态的管理。







3.4 本章小结

本章以hello程序为例,先容了编译这一过程,并对编译生成的.s文件中的内容举行了详细而细致的分析。偏重解析了.s文件中的数据和操纵,对汇编语言举行了初步的研究。


第4章 汇编


2分)


4.1 汇编的概念与作用

在这里指汇编器(as)将文本文件hello.s转换为可重定位目的程序hello.o的过程。作用是产生气器能读懂的代码,使得程序能被机器执行。

4.2 在Ubuntu下汇编的下令

下令:gcc -c hello.s -o hello.o
结果:如图4-1所示生成hello.o文件。


图4-1:hello.o文件生成

4.3 可重定位目的elf格式

在下令行输入readelf -a hello.o检察各节的基本信息

4.3.1ELF头

图4-2:ELF头的信息




1.魔数(Magic Number):用于标识文件是否为ELF格式,其值为0x7F, 'E', 'L', 'F'。
2.类型(Type):表示文件的类型,如可执行文件(ET_EXEC)、共享对象(ET_DYN)、目的文件(ET_REL)等。
3.机器架构(Machine):表示目的机器的架构类型,如x86、ARM、MIPS等。
4.版本(Version):表示ELF文件的版本号。
5.入口地点(Entry Point):表示程序的入口地点。
6.程序头表偏移量(Program Header Table Offset):表示程序头表在文件中的偏移量。
7.节头表偏移量(Section Header Table Offset):表示节头表在文件中的偏移量。
8.标记(Flags):用于标识ELF文件的特性。
9.ELF头巨细(ELF Header Size):表示ELF头的巨细。
10.程序头表项巨细(Program Header Table Entry Size):表示程序头表中每个表项的巨细。
11.程序头表项数量(Program Header Table Entry Count):表示程序头表中表项的数量。
12.节头表项巨细(Section Header Table Entry Size):表示节头表中每个表项的巨细。
13.节头表项数量(Section Header Table Entry Count):表示节头表中表项的数量。
14.字符串表索引(String Table Index):表示字符串的节在节头表中的索引。
15.ELF头的巨细(ELF Header Size):表示ELF头的巨细。

4.3.2节头部表
  包含文件中出现的各个节的语义,包括节的类型、位置和巨细等信息。

图4-2:节头部表



4.3.3节

  在ELF头和节头部表之间都是节,此中:
.text:已编译程序的机器代码。
.rodata:只读数据,例如printf语句的格式串和switch语句中的跳转表。
.data:已初始化的全局变量和静态C变量。局部C变量在运行时生存在栈中,既不出现在.data节中,也不出现在.bss节中。
.bss:未初始化的全局变量和静态C变量,以及所有被初始化为0的全局或静态变量。在目的文件中这个节不占据实际的空间,它仅仅是一个占位符。
.symtab:一个符号表,它存放在程序中界说和引用的全局变量和静态C变量的信息。
.rela.text:一个.text节中位置的列表,当链接器把这个目的文件和其他文件组适时,需要修改这些位置。重定位的信息包括了类型和偏移量,在链接阶段会用到这些信息来举行地点的计算,需要举行重定位的信息包括了.rodata中的模式串,puts,exit,printf,slepsecs,sleep,getchar等函数

图4-3:节



4.4 Hello.o的结果解析

输入objdump -d -r hello.o  分析hello.o的反汇编


图4-4:hello的反汇编

左侧为16进制机器指令,右侧为对应的汇编指令,与hello.s对比发现两者大体一致,只有以下几点区别。
1.伪指令:
hello.s中的开头和结尾有着引导汇编器和连接器的伪指令,以.开头的如.file等。而hello.o代码只有开头的文件格式说明。

图4-5:.s文件中的伪指令

2.分支转移:
  只有hello.s代码使用助记符如.L1来誊写代码(如图4-6所示),而hello.o代码没有使用助记符举行转移,而是间接地点,是每条语句之间相对偏移的地点。

图4-6:.s文件中的代码助记符

3.函数调用:
  调用函数时,hello.s代码直接使用函数名称作为助记符(如图4-7所示),而hello.o代码中,由于hello.o文件还未举行重定位,所以其接纳符号+数字表示信息,函数的详细地点先使用一串‘0’来取代。

图4-7:.s文件中的函数助记符
 通过上述例子我们可以看出,因为hello.o本质上是二进制文件,是机器语言,而hello.s本质上是ASCII文件,是汇编语言,所以二者存在着差异。相较而言,hello.o更贴近机器的运行机制。
4.5 本章小结

在本章中,我们详细讲解了将汇编后程序的文本文件转化为可重定位目的程序的过程。我们通过ELF文件格式的分析,深入了解了.o文件的布局和内容。接着,我们举行了反汇编操纵,发现了汇编后程序的改动,即使用逻辑地点的相对偏移来约定跳转指令和call指令。这种改动使得程序更加高效和机动,同时也为后续的链接和加载操纵提供了便利。总的来说,本章的内容对于理解汇编语言的编译和执行过程具有重要意义。
第5章 链接

(1分)


5.1 接的概念与作用

5.1.1 链接的概念:

链接(Linking)是指将多个目的文件(Object File)或库文件(Library File)合并成一个可执行文件(Executable File)的过程。链接器(Linker)是完成链接操纵的程序。链接器的主要作用是将目的文件中的符号(Symbol)与其他目的文件或库文件中的符号举行关联,并生成可执行文件。
5.1.2 链接的概念:
链接的主要作用有:
1.解决符号引用问题:在多个目的文件中,有些符号是被引用但未界说的,有些符号是被界说但未被引用的。链接器通过将这些符号举行关联,解决符号引用问题,使程序能够正确地执行。
2.合并代码和数据段:在多个目的文件中,有些代码和数据段是相同的,链接器将这些相同的代码和数据段举行合并,减小可执行文件的巨细。
3.优化代码布局:链接器可以对代码举行优化,使得程序的执行服从更高。
4.加载库文件:链接器可以将程序所需的库文件链接到可执行文件中,使得程序能够使用库文件中的函数和变量。
链接是将多个目的文件或库文件合并成一个可执行文件的重要过程,是程序编译和执行过程中不可或缺的一环。
5.2 在Ubuntu下链接的下令

 下令:ld -dynamic-linker /lib64/ld-linux-x86-64.so.2 /usr/lib/x86_64-linux-gnu/crt1.o /usr/lib/x86_64-linux-gnu/crti.o /usr/lib/gcc/x86_64-linux-gnu/11/crtbegin.o hello.o -lc /usr/lib/gcc/x86_64-linux-gnu/11/crtend.o /usr/lib/x86_64-linux-gnu/crtn.o -z relro -o hello
结果:生成hello文件


图5-1:hello文件的生成

5.3 可执行目的文件hello的格式

在下令行中键入下令readelf -a hello > hello_elf.text得到hello的ELF格式。
5.3.1ELF头
ELF头以一个序列开始,这个序列描述了生成该文件的系统的字的巨细和字节顺序。ELF头剩下的部分包含帮助链接器语法分析和解释目的文件的信息。此中包括ELF头的巨细、目的文件的类型、机器类型、节头部表的文件偏移,以及节头部表中条目的巨细和数量。


图5-2:hello的ELF头信息

   ELF头部以16进制序列7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00开头,如图5-2所示。该序列指示生成文件的系统中字节巨细为8字节,并且字节顺序为小端序。别的,该序列还提供了以下信息:ELF头部巨细为64字节;目的文件类型为REL(可重定位文件);机器类型为Advanced Micro Devices X86-64(即AMD X86-64);节头表的文件偏移为0;节头表中条目的巨细为25。

5.3.2节头部表
如图5-3所示,hello的节头部表包含了25个节的相关信息。与hello.o的节头部表相比,多出来的部分有以下几个:


  • .interp:该段生存了一个字符串,用于指定可执行文件所需要的动态链接器的位置,通常位于/lib/ld-linux.so.2。
  • .dynamic:该段生存了动态链接器所需要的基本信息,可以看作动态链接下ELF文件的“文件头”,存储了动态链接会用到的各个表的位置等信息。
  • .dynsym:该段雷同于.symtab段,但只生存了与动态链接相关的符号,记载了动态链接符号在动态符号字符串表中的偏移,与.symtab中记载对应。
  • .dynstr:该段是.dynsym段的辅助段,与.dynsym的关系雷同于.symtab与.strtab的关系。
  • .hash:该段是一个辅助的符号哈希表,在动态链接下,需要在程序运行时查找符号,为了加快符号查找过程而增加。
  • .rel.dyn:该段用于对数据引用的修正,修正位置位于.got以及数据段,雷同于重定位段“rel.data”。
  • .rel.plt:该段用于对函数引用的修正,修正位置位于.got.plt。


图 5-3: hello的节头部表

5.3.3符号表
符号表存储程序中界说和引用的函数和全局变量的信息,包括编号Num、Value、Size、Type、Bind、Vis、Ndx和Name字段。
与hello.o文件相比,符号表中的函数名被更换为更为详细的内容,如puts被更换为puts@GLIBC_2.2.5,说明已经找到了该函数的界说。别的,与hello.o相比,符号表中多了一个.dynsym节(如图5-4所示)。

图 5-4 :dynsym节

另外,此中还含有.symtab节。(如图5-5所示)

图 5-5 :symtab节

5.3.4重定位信息
程序中新增了一个名为.rela.dyn的节,用于存储与动态链接相关的信息,并对数据引用举行修正,修正的位置位于.got及数据段。别的,与原始版本hello.o相比,程序中许多函数名称的情势发生了变化,以puts@GLIBC_2.2.5+0的情势呈现,这表明相关的库已经找到了这些函数,并对函数引用举行了修正,以便修正.got.plt。

图 5-6: hello的重定位信息

5.4 hello的假造地点空间


                      图 5-7: 用edb加载hello

5.4.1.text

根据5.3中节头部表的相关信息,.text段的地点从0x4010f0开始,偏移量为0x10f0,巨细为0x1e5,对齐要求为16。通过检察0x4010f0处的内存(如图5-8所示),可以发现其与反汇编代码中的机器码完全一致。




图 5-8 :用edb检察hello的.data部分

5.4.2.rodata

  对照5.3中节头部表的相关内容,.rodata段地点从0x402000开始,偏移量是0x2000,巨细为0x3b,对齐要求为8,据此检察0x402000处的内存,与反汇编代码中拥有的机器码比力发现是一致的。



5.5 链接的重定位过程分析

在下令行输入objdump -d -r hello。

图 5-9 :用objdump指令检察hello的反汇编

完成符号解析后,链接器会将代码中的每个符号引用和对应的符号界说关联起来。接着,链接器会获取输入目的模块中代码节和数据节的确切巨细,并举行重定位步调。在此过程中,链接器会合并输入模块,并为每个符号分配运行时地点。然后,链接器会修改hello中代码节和数据节中的符号引用,使它们指向正确的运行地点。
   观察图5-9,可以发现hello的反汇编程序相比hello.o的反汇编程序多了许多内容,也产生了许多不同之处。

5.5.1代码起始位置
hello的反汇编代码段从0x401000开始;而hello.o的反汇编代码段从0x0开始。(如图5-10所示)


图 5-10: hello.o的反汇编代码段

5.5.2引入函数
在hello.o的反汇编代码中,首先是.text段,然后是main函数,没有其他内容。此中,puts函数的重定位信息如图5-11所示。相比之下,hello的反汇编代码包含了许多引用,这是由于重定位过程中添加的,例如puts@plt、exit@plt、__printf_chk@plt等等。puts函数的引用如图5-12所示。

图 5-11: hello.o反汇编代码中puts的重定位信息


图 5-12: hello反汇编代码中的puts函数部分


5.5.3接纳假造地点

当反汇编hello.o和hello的代码时,它们在处理函数跳转时接纳了不同的方法。在hello.o中,由于尚未举行重定位,因此只能使用0来取代main函数的位置(见图5-13)。相比之下,在反汇编hello的代码时,使用了假造地点来处理函数跳转,此中puts函数的假造地点如图5-14所示。


图 5-13: hello.o反汇编代码中puts函数地点信息


图 5-14: hello反汇编代码中pus函数的假造地点

5.5.4调用.rodata的数据

在hello程序中,printf函数的静态字符串是存储在只读内存段中的。在hello.o文件中,0x0(%rip)被用来取代这些静态字符串(详见图5-15)。不过,在hello程序经过重定位之后,静态字符串的位置已经被明确标识出来了(详见图5-16)。


图 5-15: hello.o反汇编代码中的printf


图 5-16: hello反汇编代码中的printf




5.6 hello的执行流程

使用edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程。请列出其调用与跳转的各个子程序名或程序地点。

  • ld-2.23.so!_dl_start
  • ld-2.23.so! dl_init
  • hello!_start
  • hello!__libc_start_main
  • libc-2.23.so!__libc_start_main
  • libc-2.23.so! cxa_atexit
  • hello!__libc_csu_init
  • hello!_init
  • libc-2.23.so!_setjmp
  • libc-2.23.so!_sigsetjmp
  • hello!main
  • hello!puts@plt
  • hello!exit@plt
  • hello!printf@plt
  • hello!sleep@plt
  • hello!getchar@plt
  • ld-2.23.so!_dl_runtime_resolve_avx
  • libc-2.23.so!exit
5.7 Hello的动态链接分析

当程序调用一个共享库的函数时,编译器不能猜测这个函数在什么地点,因为界说它的共享模块在运行时可以加载到任何位置。这时,编译系统提供了延迟绑定的方法,即:将过程地点的加载推迟到第一次调用该过程时。通过观察edb对hello的执行环境,便可发现dl_init前后后.got.plt节发生的变化。
首先,通过readelf找到.got.plt节在地点为0x404000的地方开始,巨细为0x48。因此,结束地点为0x40400047,这两个地点之间部分便是.got.plt的内容。
在edb中的Data Dump中找到这个地点,观察.got.plt节的,发现在dl_init前后,.got.plt的第8到15个字节发生了变化。
在这里,这些变化的字节分别对应GOT[1]和GOT[2]的位置。此中, GOT[1]包括动态链接器在解析函数地点时使用的信息,而GOT[2]则是动态链接器ld-linux.so模块中的入口点。加载时,动态链接器将重定位GOT中的这些条目,使它们包含正确的地点。内存的变化如下图所示:



图 5-17: got.plt节在假造内存中的位置




图 5-18: dl_init之前


图 5-19: dl_init之后



5.8 本章小结


本章主要先容了链接的概念和作用。它将预编译好的一个或多个目的文件以及链接库合并成为一个可执行目的文件。在Ubuntu下,我们使用链接器(ld)将hello.o文件生成了可执行目的文件hello,并分析了它的ELF格式和各节的基本信息。使用edb检察了hello的假造地点空间,发现各节的名称都与相应的假造地点相对应,并检察了各节的起始位置与巨细。对hello举行反汇编,得到了反汇编程序,并与hello.o的反汇编程序举行比力,发现经过链接后,hello的反汇编程序代码量增加,插入了C尺度库中的函数代码,指令都分配了假造地点,字符串常量的引用、函数调用以及跳转指令的地点都更换为了假造地点。我们还分析了链接的过程,包括符号解析和重定位。末了,使用edb执行hello,说明白从加载hello到_start,到call main,以及程序终止的所有过程,并列出了调用和跳转的各个子程序名及地点。我们还分析了hello程序的动态链接项目,并通过edb调试分析了在dl_init前后,这些.got.plt节的内容变化,是由动态链接的延迟绑定造成的。


6hello进程管理


(1分)

6.1 进程的概念与作用

6.1.1进程的概念:

进程是计算机中正在运行的程序的实例。每个进程都有自己的地点空间、数据栈和代码段,它们在内存中被分配肯定的空间,可以独立地运行和执行任务。
6.1.2进程的作用:
进程具有以下几个主要的作用:
    实现并发执行:一个操纵系统可以同时运行多个进程,每个进程都有自己的独立运行空间,可以独立执行任务。
    提供资源分配:进程可以请求和开释系统资源,如 CPU 时间、内存、文件、网络连接等。操纵系统通过调理算法来分配资源,保证每个进程都能获得必要的资源以完成任务。
    提供进程间通信:多个进程之间可以通过进程间通信机制(如管道、消息队列、共享内存等)来交换数据和信息,从而实现协作完成任务。
    实现程序的动态加载:进程可以在运行过程中动态加载库文件和插件,从而进步系统的机动性和可扩展性。
进程是计算机系统中最基本的运行单元,是操纵系统实现并发、资源分配、进程间通信和程序动态加载等功能的基础。
6.2 简述壳Shell-bash的作用与处理流程

壳(Shell)是指在操纵系统上举行交互式操纵的下令行解析器,它是用户与操纵系统内核之间的接口。Bash是常见的壳程序之一,它是GNU操纵系统的默认壳,也是Linux和macOS等操纵系统的默认壳。

Bash的作用是接收用户输入的下令并将其解释为相应的操纵。它会将输入的下令分解成若干个词语,然后根据下令的语法规则举行解析和执行。Bash支持许多常用的下令、变量、环境变量、函数、管道、重定向等功能。用户也可以通过编写脚本来实现自动化任务。

Bash的处理流程如下:

1. 接收用户输入的下令,并将其传递给解释器。
2. 解释器将下令分解成若干个词语,包括下令名、参数、选项等。
3. 解释器会先查找是否存在与下令名相对应的可执行文件,如果存在则执行该可执行文件,否则查找是否存在与下令名相对应的内置下令。
4. 如果下令是通过管道连接的,则将多个下令通过管道举行连接,并按照肯定的顺序执行。
5. 执行完下令后,Bash会返回执行结果,并表现在终端上。
总之,Bash是一个非常强盛的下令行解析器,它可以让用户通过下令行完成许多操纵,提供了许多方便的功能和工具,使得操纵系统的使用更加高效和便捷。
6.3 Hello的fork进程创建过程

当我们在shell上输入“./hello”时,shell会将其视为一个可执行目的文件而不是内置下令,因此会调用加载器的操纵系统代码来运行该文件(拜见图6-1)。根据shell的处理流程,我们可以得出这样的结论:如果父进程判定输入的下令不是内部指令,那么它就会通过fork函数创建一个子进程。子进程和父进程雷同,它们都拥有一份与父进程用户级假造空间相同且独立的副本,此中包括数据段、代码、共享库、堆和用户栈。子进程可以读写父进程打开的文件。父进程和子进程之间最大的不同可能就是PID的不同了。fork函数只会被调用一次,但它会返回两次。在父进程中,fork返回子进程的PID,在子进程中,fork返回0。由于子进程的PID总是非零的,因此返回值可以明确地域分程序是在父进程照旧子进程中执行。

图 6-1: 运行hello


6.4 Hello的execve过程

execve函数会加载并运行可执行目的文件Hello,同时传入列表argv和环境变量列表envp。其作用是在当进步程上下文中加载并运行一个新的程序。只有在出现错误时,如找不到Hello时,execve才会返回到调用程序,不同于fork调用一次返回两次的环境。在execve加载了Hello后,它会调用启动代码。启动代码会设置栈,并将控制传递给新程序的主函数,该函数原型为int main(int argc, char **argv, char *envp)。团结假造内存和内存映射过程,可以更详细地说明execve函数怎样实际加载和执行程序Hello:
1. 首先会删除已存在的用户区域(与父进程独立)。
2. 然后会举行共享区映射,例如Hello程序与尺度C库libc.so链接,这些对象都是动态链接到Hello的,然后再用户假造地点空间中的共享区域内。
3. 接下来会举行私有区映射,为Hello的代码、数据、.bss和栈区域创建新的区域布局,所有这些区域都是私有的、写时复制的。
4. 末了会设置PC,execve会设置当进步程上下文中的程序计数器,指向代码区的入口。
6.5 Hello的进程执行

系统上同时存在多个任务并行执行,操纵系统内核接纳上下文切换的方式来实现多任务,这是一种较高条理的异常控制流。上下文指的是内核重新启动一个被抢占的进程所需的状态。
在执行hello程序时,内核可以决定抢占当进步程并重新开始先前被抢占的进程,这个决策称为调理。调理器完成调理过程,当内核调理新的进程运行后,它会抢占当进步程并举行以下操纵:
1. 生存以进步程的上下文。
2. 规复新进程被生存的上下文。
3. 将控制传递给这个新规复的进程,完成上下文切换。
6.6 hello的异常与信号处理

在hello程序执行过程中,可能会发生四种异常,包括中断、陷阱、故障和终止。
中断是异步发生的,由外部I/O装备信号引起,中断处理程序会对其举行处理,处理完后继续执行调用前待执行的下一条代码。
陷阱是同步发生的,是有意的异常,由执行指令引起,调用后会返回到下一条指令,用于调用内核服务举行操纵,帮助程序从用户模式切换到内核模式。
故障是同步发生的,由错误环境引起,可能会被故障处理程序修正,如果修正乐成,则将控制返回到引起故障的指令,否则将终止程序。
终止是同步发生的,是由不可规复的致命错误引起的,通常是硬件错误,处理程序会将控制返回给一个abort例程,该例程会终止应用程序。

6.6.1乱按键盘(不回车)

并不影响源程序输出结果,屏幕上会缓存输入的字符串
6.6.2乱按键盘(有回车)

shell会把回车前的字符串默认为输入的下令

6.6.3 Ctrl+C

输入Ctrl+C会让内核发送一个SIGINT信号给到前台进程组中的每个进程,结果是终止前台进程——即hello程序,通过ps下令发现这时hello进程已经被接纳
6.6.4 Ctrl+Z

输入Ctrl+Z会发送一个SIGTSTP信号给前台进程组的每个进程,结果是停止前台进程——即hello程序
6.6.5ps(Ctrl+Z之后)

ps会列出当前正在执行的进程(包括暂停的进程),此时可以看到进程的名称和PID号。
6.6.6jobs(Ctrl+Z之后)

表现了已经停止的进程以及参数。
6.6.7pstree(Ctrl+Z之后)

表现了进程树,体现了父子进程附属关系。

6.6.7 fg

fg的功能是使第一个后台作业变为前台,而第一个后台作业是hello,所以输入fg 后hello程序又在前台开始运行,并且是继续刚才的进程

6.6.8 kill





6.7本章小结

本章重点先容了hello进程管理的相关内容,通过shell可以对计算机运行的进程举行管理。同时,我们详细讲解了hello程序的fork和execve过程,以及在程序运行中可能会遇到的异常环境。末了,我们还探究了系统怎样处来由异常引发的信号。



7hello的存储管理


( 2分)


7.1 hello的存储器地点空间

   逻辑地点是程序hello生成的与段相关的偏移地点部分,hello.o文件中的地点即为逻辑地点。
线性地点则是逻辑地点到物理地点变换之间的中间层。程序hello的代码会产生逻辑地点,也就是段中的偏移地点,加上相应段的基地点就会生成线性地点。
CPU生成的假造地点会通过内存管理单元MMU转换成适当的物理地点,查询表的内容由操纵系统管理。在linux系统中,只有分页而不是分段,因此hello程序的逻辑地点几乎就是假造地点。
物理地点是主存上由M个连续的字节巨细的单元组成的数组,每个字节都有唯一的物理地点。hello的物理地点来自于假造地点的地点转换,它也是程序运行的最终地点。

7.2 Intel逻辑地点到线性地点的变换-段式管理

(1) 一个逻辑地点包括段标识符和段内偏移量。
(2) 段标识符是16位长的字段,此中前13位可以直接对应到全局段描述符表或局部段描述符表中的一个详细的段描述符。
(3) 全局段描述符表存储全局的段描述符,局部段描述符表存储局部的段描述符。

确定给定逻辑地点的线性地点的步调如下:
(1) 判定段选择符的T1位是0照旧1,确定所要转换的是GDT中的段照旧LDT中的段。
(2) 根据相应的寄存器获取段的地点和巨细。
(3) 使用段选择符中的前13位在相应的描述符表中查找对应的段描述符,获取该段的基地点。
(4) 将基地点和段内偏移量相加得到线性地点。
7.3 Hello的线性地点到物理地点的变换-页式管理

页式管理是一种用于内存空间存储管理的技能,它分为静态页式管理和动态页式管理。该技能将每个进程的假造空间分割成相称长度的页(page),将内存空间按页的巨细分割成片,并与页式假造地点一一对应,存储在页表中。为了处理离散地点变换问题,它需要调用相应的硬件地点变换构件。页式管理通过请求调页或预调页技能实现了内外存的同一管理与调用。
该技能的优点是有效解决了碎片问题,因为它不需要进程的程序段和数据在内存中连续存放,从而提供了内外存同一管理的假造内存实现方式,大大增加了用户可以使用的碎片化空间,也非常有利于多个进程同时执行。
然而,页式管理需要相应的硬件支持,增加了机器本钱,并增加了系统开销。例如,缺页中断的产生和选择镌汰页面等都要求有相应的硬件支持;缺页中断处理时,请求调页的算法如果不够恰当,有可能会产生抖动征象。别的,碎片式的管理也会导致每个进程内总有一部分空间得不到使用,当页面比力大时,这一部分的丧失会非常显著。
7.4 TLB与四级页表支持下的VA到PA的变换

TLB的支持是指在MMU中包含一个PTE缓存,即翻译后备缓冲器。TLB是一个小的、假造寻址的缓存,每一行生存着一个由单个PTE组成的块。在VA到PA的转换过程中,需要使用VPN确定相应的页表条目,因此TLB需要通过VPN来寻找PTE。如果TLB有2t个组,则TLB的索引TLBI由VPN的t个最低位组成,TLB标记TLBT由VPN中剩余的位组成,需要举行组索引和行匹配。当MMU举行地点翻译时,会先将VPN传给TLB,看TLB中是否已经缓存了需要的PTE,如果TLB命中,可以直接从TLB中获取PTE,将PTE中的物理页号和假造地点中的VPO连接起来就得到相应的物理地点。如果TLB不命中,则需要从cache或者内存中取出相应的PTE,这时所有的地点翻译步调都是在芯片上的MMU中执行的,因此非常快。另外,多级页表可以用来压缩页表,对于k级页表条理布局,假造地点的VPN被分为k个,每个VPNi是一个到第i级页表的索引。当1≤j≤k-1时,第j级页表中的每个PTE指向某个第j+1级页表的基址。末了得到的L4 PTE包含了需要的物理页号,和假造地点中的VPO连接起来就得到相应的物理地点。在Intel Core i7中,使用了4级页表,每个VPNi有9位。

图 7-1 使用四级页表的地点翻译


7.5 三级Cache支持下的物理内存访问

一旦MMU完成了假造地点到物理地点的转换,就可以使用物理地点来访问内存。Intel Core i7接纳了三级缓存来加快物理内存访问,此中L1级缓存作为L2级缓存的缓存,L2级缓存作为L3级缓存的缓存,而L3级缓存则作为内存(DRAM)的缓存。
在举行物理内存访问时,首先将物理地点发送给L1级缓存,以检察是否缓存了所需数据。L1级缓存共有64组,每组8行,块巨细为64B。因此,物理地点被分为三部分:块偏移(6位)、组索引(6位)和标记位(40位)。首先使用组索引位找到相应的组,然后在组中举行行匹配。对于组中的8个行,分别检察有效位并将行的标记位与物理地点的标记位举行匹配。当标记位匹配且有效位为1时,缓存命中,可以直接将缓存中的数据传送给CPU。如果缓存未命中,则需要从存储条理布局的下一层中取出所请求的块,并将新块存储在相应组的某个行中,可能会更换某个缓存行。
当L1级缓存未命中时,会向L2级缓存发送数据请求。与L1级缓存的过程相同,需要举行组索引、行匹配和字选择,将数据传送给L1级缓存。同样,当L2级缓存未命中时,会继续向L3级缓存发送数据请求。末了,当L3级缓存未命中时,只能从内存中请求数据了。
值得留意的是,三级缓存不但支持数据和指令的访问,也支持页表条目的访问。在MMU举行假造地点到物理地点的转换过程中,三级缓存也会发挥作用。

 图 7-2 三级cache下的物理内存访问


7.6 hello进程fork时的内存映射

调用fork函数时,内核会为新进程创建各种数据布局,并分配唯一的PID。新进程会拥有与当进步程相同的假造内存,包括mm_struct、区域布局和页表的原样副本。为了保持私有空间地点的抽象概念,内核会将两个进程中的每个页面标记为只读,并将每个区域布局标记为私有的写时复制。当fork返回时,新进程的假造内存与调用fork时的假造内存相同。如果任何一个进程举行写操纵,写时复制机制会创建新页面,从而为每个进程保持私有空间地点的抽象概念。
7.7 hello进程execve时的内存映射

函数声明为int execve(char *filename, char *argv[], char *envp[]),execve函数可以在当进步程中加载并运行filename程序,有效地替代当出息序。这个过程需要以下几个步调:
1. 删除已存在的用户区域。删除当进步程假造地点的用户部分中已存在的区域布局。
2. 映射私有区域。为新程序的代码、数据、bss和栈区域创建新的区域布局。所有这些新的区域都是私有的、写时复制的。
3. 映射共享区域。如果filename程序与共享对象(或目的)链接,那么这些对象都是动态链接到这个程序的,然后再映射到用户假造地点空间中的共享区域内。
4. 设置程序计数器(PC)。execve函数末了一步是设置当进步程上下文中的程序计数器,使之指向代码区域的入口点。当再次调理这个进程时,它将从入口点开始执行。Linux将根据需要换入代码和数据页面。
7.8 缺页故障与缺页中断处理

在假造内存的常用术语中,DRAM缓存未命中被称为缺页。以图7-3为例,当CPU引用VP3中的一个字时,VP3并未被缓存在DRAM中。此时会读取PTE3,通过有效位可以判定VP3未被缓存,从而触发一个缺页异常。此时会调用内核中的缺页异常处理程序(如图7-4所示),该程序会选择一个牺牲页(这里是VP4)。如果该牺牲页被修改过,内核就会将它复制回磁盘。然后内核会将VP3从磁盘复制到内存中原先VP4的位置上,并更新PTE3,随后返回。当异常处理子程序返回时,它会重新启动导致缺页的指令。该指令会重新发送导致缺页的假造地点到地点翻译硬件,但现在VP3已经在主存中,可以正常处理了。异常处理后的结果
如图7-5所示。

图 7-3 VP3缺页异常



图 7-4 缺页异常处理程序



图 7-5 异常处理后的结果


7.9本章小结

在本章中,我们探究了储存器的地点空间,详细先容了假造地点、物理地点、线性地点和逻辑地点的概念。别的,我们还讨论了进程fork和execve时的内存映射,并描述了系统怎样处理缺页异常。

结论


(0分,必要项,如缺失扣1分,根据内容酌情加分)


hello程序从诞生到结束,经历了多个阶段:
首先,程序员在文本编辑器或IDE中编写代码,得到最初的源程序。
接着,预处理器解析宏界说、文件包含、条件编译等,生成中间文件。
编译器将代码翻译成汇编指令,生成汇编语言文件。
汇编器将汇编指令翻译成机器语言,并生成可重定位目的文件。
链接器举行符号解析、重定位、动态链接等创建一个可执行目的文件。
在shell中运行程序时,shell会调用fork函数创建子进程,供之后程序的运行。
子进程中调用execve函数,加载程序,进入程序入口点,程序终于要开始运行了。
在运行阶段,内核负责调理进程,并对可能产生的异常及信号举行处理。硬件和软件相互协作,使得程序与文件举行交互。
末了,程序运行结束,shell负责接纳终止的进程,内核删除为进程创建的所有数据布局。程序的一生到此结束。
通过分析程序的一生,我认识到,一个复杂的系统需要多方面的协作配合才气更好地实现功能。同时,计算机系统提供的一系列抽象使得实际应用与详细实现相互分离,可以很好地隐藏实现的复杂性,降低了程序员的负担,使得程序更加容易地编写、分析、运行。抽象是十分重要的,是计算机科学中最为重要的概念之一。
附件

以下是中间产物的文件名及其作用的说明:

- hello.i:这是一个由C预处理器生成的ASCII码中间文件,用于分析预处理过程。预处理器会将源代码中的宏界说、条件编译指令等举行处理,并将结果输出到该文件中。
- hello.s:这是一个由C编译器生成的ASCII汇编语言文件,用于分析编译过程。编译器会将预处理后的代码转换成汇编语言,并将结果输出到该文件中。
- hello.o:这是一个由汇编器生成的可重定位目的程序,用于分析汇编过程。汇编器会将汇编语言代码转换成机器码,并生成可重定位目的文件,该文件可以被链接器用于生成可执行目的文件。
- hello:这是一个由链接器生成的可执行目的文件,用于分析链接过程。链接器会将多个可重定位目的文件合并成一个可执行目的文件,并解决符号引用、重定位等问题。
- obj_hello.o.text:这是hello.o的反汇编文件,用于分析可重定位目的文件hello.o。反汇编器会将机器码转换成汇编语言代码,并输出到该文件中。
- obj_hello.text:这是hello的反汇编文件,用于分析可执行目的文件hello。反汇编器会将机器码转换成汇编语言代码,并输出到该文件中。
- hello_elf.text:这是hello的ELF格式,用于分析可执行目的文件hello。ELF是一种可执行文件格式,包含了程序的代码、数据、符号表等信息。该文件可以被调试器用于调试程序。
(附件0分,缺失 -1分)


参考文献


为完本钱次大作业你翻阅的书籍与网站等

[1]  qq_65228171. (2021, 3.28). 【C语言】预处理超级详细解析
.https://blog.csdn.net/qq_65228171/article/details/127464938.
[2]  是星星鸭. (2020, 2.12). C语言汇编分析
.http://www.qb5200.com/article/549765.html
[3]  CS:APP3e Lab Assignments." Computer Systems: A Programmer's Perspective, Third Edition. Accessed on November 23, 2021. CS:APP3e, Bryant and O'Hallaron.
[4]   我想吹水. (2024, 3.15). linux 动态连接
https://blog.csdn.net/god_wen/article/details/126842757.


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

石小疯

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表