程序人生-Hello’s P2P

 

 

 

计算机系统

 

大作业

 

 

题     目  程序人生-Hellos P2P  

专       业      计算机科学与技术    

学     号       2021112239         

班    级         2103102          

学       生          陈羿桥    

指 导 教 师           刘宏伟       

 

 

 

 

 

 

计算机科学与技术学院

2022年11月

摘  要

分析了hello world程序从编程为.c文件后经历预处理,编译,汇编,链接,最终形成可执行目标文件,并在shell中运行的具体处理过程与细节,讲述了hello world一生从无到有,从有到无的整个过程。

关键词:CSAPP,程序,编程,系统过程                         

 

 

 

 

 

 

 

 

目  录

 

第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章 链接

5.1 链接的概念与作用

5.2 在Ubuntu下链接的命令

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

5.4 hello的虚拟地址空间

5.5 链接的重定位过程分析

5.6 hello的执行流程

5.7 Hello的动态链接分析

5.8 本章小结

第6章 HELLO进程管理

6.1 进程的概念与作用

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

6.3 Hello的fork进程创建过程

6.4 Hello的execve过程

6.5 Hello的进程执行

6.6 hello的异常与信号处理

6.7本章小结

结论

附件

参考文献

 

 


第1章 概述

1.1 Hello简介

1. P2P

P2P全称为‘From Program to Process’,从程序到进程。而我们的Hello,在IDE里编写为hello.c,通过cpp的预处理为hello.i,ccl的编译下编程hello.s,由as汇编出可重定位目标文件hello.o,最终由ld链接得到可执行目标文件hello。我们打开hello,shell则开始命令行参数解析,初始化环境变量。调用fork、execve执行函数,映射内存分配空间,这样完成了Program to Process的过程

2. 020

020全称为‘From Zero to Zero’,我们在shell中创建了hello的进程并使用他,而从main函数中返回时,进程结束,他的父进程就会回收他,在内存中就是‘From Zero to Zero’,灰飞烟灭。

1.2 环境与工具

硬件环境:11th Gen Intel(R) Core(TM) i5-1135G7

软件环境:Windows Subsystem for Linux 2 For Ubuntu20.04

Windows 11 专业版

开发工具:Clion2022.02.4

EDB

GCC/GDB/OBJDUMP

 

1.3 中间结果

hello.i

hello.c预编译的结果,用于研究预编译的作用以及进行编译器的下一步编译操作。

hello.s

hello.i编译后的结果,用于研究汇编语言以及编译器的汇编操作,可以与hello.c对应,分析底层的实现。

hello.o

hello.s汇编后的结果,可重定位目标程序,没有经过链接,用于链接器或编译器链接生成最终可执行程序。

hello

hello.o链接后生成的可执行文件,可以用来反汇编或者通过EDB、GDB等工具分析链接过程以及程序运行过程。

hello.elf

可重定位目标文件的elf,用来看hello.o的Section information

elf.txt

可执行目标文件的elf

disasm_hello.s

.o经过反汇编生成的汇编语言文件

disasm.txt

可执行文件的反汇编文件

 

1.4 本章小结

本章介绍了Hello从编译到生成、再到运行与终止的全过程,大体上描述了本论文内容的主要框架,最后列出了环境与工具、中间结果。

 


第2章 预处理

2.1 预处理的概念与作用

预处理概念:

预处理是指是指在进行编译的第一遍扫描(词法和语法)之前所做的工作。预处理由预处理程序负责完成。当对一个源文件进行编译时,系统将自动引用预处理程序对程序中的预处理部分作处理,处理完毕自动进入对源程序的编译。

预处理器(cpp)根据以字符#开头的命令,修改原始的C程序。比如 hello.c中第1行的#include <stdio.h>命令告诉预处理器读取系统头文件 stdio.h的内容,并把它直接插人程序文本中。结果就得到了另一个C程序,通常是以.i作为文件扩展名。

预处理的任务:

1. 删除注释

2. 插入被#include指令包含的文件内容

3. 定义和替换由#define指令定义的符号(宏替换或宏展开)

4. 条件编译

2.2在Ubuntu下预处理的命令

 

 

2.3 Hello的预处理结果解析

 

进行了三千余行的宏替换,头文件展开,预编译指令的处理。正因为头文件展开,导致了待编译文件快速膨胀。在文件末尾为主内容。

2.4 本章小结

本章介绍了预处理的概念内容作用还有预处理的目的任务,给出了预处理命令和结果

第3章 编译

3.1 编译的概念与作用

编译的概念:

 编译程序的工作就是通过编译器的词法分析和语法分析,先行确认是否存在不符合语法规则的情况,而后进行语义分析,将上述的预处理结果翻译成汇编代码。

编译的流程:

1. 词法分析:词法分析的任务是对由字符组成的单词进行处理,从左至右逐个字符地对源程序进行扫描,产生一个个的单词符号,把作为字符串的源程序改造成为单词符号串的中间程序。

2. 语法分析:语法分析器以单词符号作为输入,分析单词符号串是否形成符合语法规则的语法单位,如表达式、赋值、循环等,最后看是否构成一个符合要求的程序,按该语言使用的语法规则分析检查每条语句是否有正确的逻辑结构,程序是最终的一个语法单位。

3. 语义分析:有语义分析器完成,指示判断是否合法,不判断对错。

4. 目标代码的生成与优化:目标代码生成器把语法分析后或优化后的中间代码经汇编程序汇编生成汇编语言代码,成为可执行的机器语言代码。

 

3.2 在Ubuntu下编译的命令

3.3 Hello的编译结果解析

3.3.1编译结果

 

3.3.1数据

(1)常量

 

两个字符串常量,对应printf中的标准格式串

(2)变量

 

在hello.c中循环计数器i变量储存在栈-4(%rbp)中

 

Argv参数数组和argc参数数组大小也保存在栈中-20(%rbp)和-32(%rbp)

3.3.2操作

(1)赋值

 

汇编中常规使用mov进行赋值操作,图中例子展示了栈指针的保存过程。movl还是一个例外的,movl指令以寄存器作为目的时,它会把该寄存器的高位4字节设置为00。

(2)算数操作

 

图中展示了使用addl给循环计数器i进行加一,对应for中的i++。

(3)关系操作

 

图中cmpl操作对应循环中比较i是否小于9,是循环结束的判定

 

这一次cmpl操作时判断argc和4的是否相等,对应if(argc!=4)语句

(4)控制转移

1. if语句

 

对应hello.c中

 

使用cmpl和je进行跳转

2. for循环语句的循环部分跳转

 

使用cmpl和jle进行,若计数器小于等于8则执行循环内容。

(5)函数操作

1. prinft函数

 

直接使用call指令调用

2. exit函数

 

直接使用call函数调用

3. atoi函数

 

该函数是将字符串转换为整数,使用call调用

4. sleep函数

 

使用call调用,参数为休眠秒数。

3.4 本章小结

本章首先介绍了编译的概念与流程,其次展示了编译后得到的汇编内容,对编译内容各部分进行详尽的分析说明。

第4章 汇编

4.1 汇编的概念与作用

汇编的概念:

  把汇编代码翻译成及其语言指令,把这些指令打包成为可重定位目标程序的格式,并形成.o文件的过程就是汇编。

汇编的作用:

汇编的过程将汇编代码转换为计算机能够理解并执行的二进制机器代码,这个二进制机器代码是程序在本机器上的机器语言的表示,从而程序真正可以被执行。

4.2 在Ubuntu下汇编的命令

 

4.3 可重定位目标elf格式

  4.3.1 ELF HEADER

 

elf头以一个16字节的序列开始,这个序列是对于声称该文件系统下一些字的大小等信息的描述。而后包含一些能够帮助链接器语法分析并解释目标文件的信息,包括elf头的大小、目标文件的版本、机器类型、节头部表的文件偏移、以及节头部表中条目的大小和数量

4.3.2Seciton Headers:

 

section header用来描述每个section的特性,如大小、类型、名称等等

4.3.3Relocation section

 

重定位是连接符号引用与符号定义的过程。例如,程序调用函数时,关联的调用指令必须在执行时将控制权转移到正确的目标地址。可重定位文件必须包含说明如何修改其节内容的信息。通过此信息,可执行文件和共享目标文件可包含进程的程序映像的正确信息。重定位项即是这些数据。

4.3.4Symbol table

 

目标文件的符号表包含定位和重定位程序的符号定义和符号引用所需的信息。

4.4 Hello.o的结果解析

使用指令objdump -d -r hello.o > disasm_hello.s得到反汇编文件

 

4.4.1分支跳转

 

在反汇编中发现分支跳转不再使用节而是使用相对地址进行跳转

4.4.2函数调用

 

在hello.s中函数的调用是使用call + 函数名直接调用

而在反汇编文件中,则是使用call相对地址进行跳转

4.4.3全局变量访问

 

在hello.s中 使用段名称(%rip)即可访问全局变量,而在disasm_hello.s并不知道rodata的地址,所以全部写成了

进行占位

4.4.3操作数表示

 

在disasm_hello.s中使用16进制

 

在hello.s中使用10进制

 

说明机器语言的构成,与汇编语言的映射关系。特别是机器语言中的操作数与汇编语言不一致,特别是分支转移函数调用等。

4.5 本章小结

本章首先介绍了汇编的概念和作用,汇编语言最终形成了机器语言。其次使用readelf读取了ELF文件信息,解析了各段内容。最后使用objdump得到反汇编内容,对比hello.s分析了内容的不同。

5章 链接

5.1 链接的概念与作用

链接的概念:

链接是指将文件中调用的各种函数跟静态库及动态库链接,并将它们打包合并形成目标文件,即可执行文件。

链接的作用:

首先,它可以避免构造大型程序时写一个超级大的源文件,我们可以分成更小的模块进行编写,修改某一个模块只需要这个模块重新链接到应用上,其他模块不用重新编译。
其次,它可以帮助我们理解一些编程中的链接错误,全局变量局部变量意义,其他系统概念,开发共享库等。

5.2 在Ubuntu下链接的命令

 ld -o hello -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/x86_64-linux-gnu/libc.so /usr/lib/x86_64-linux-gnu/crtn.o  hello.o

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

5.3.1 ELF HEADER

 

在该ELF HEADER中我们可以获得Entry point address

5.3.2Section Headers

 

我们可以得知各个节的偏移量和大小。

5.3.3 Program Headers

 

 描述了系统准备程序执行所需的段或者其他信息,描述了可执行目标文件的连续的片与连续的虚拟内存段之间的映射关系。

5.3.4 Relocation Section

 

现在所有的value和addend都为0说明我们已经链接成功,实现了 重定位效果

5.3.5Symbol table

 

与之前不同,其他符号也有了Type,说明了链接的作用

5.4 hello的虚拟地址空间

    

程序的虚拟地址从从0x401000开始到0x402000结束

5.5 链接的重定位过程分析

 

Hello.o中地址是从0开始,而链接后我们有了具体的虚拟地址

 

链接后出现了所需要的函数代码,而且有了确切的地址。说明实现了重定位

5.5.1重定位过程分析

当链接器完成符号解析的步骤后,每个符号引用和符号定义都对应到了一起。此时,链接器已经知道了输入目标模块中的代码和数据段的大小,准备开始重定位工作,在重定位阶段链接器会将输入模块整合到一起并为每个符号赋予运行时地址。重定位分为两个步骤:

重定位段和符号定义:在这个步骤,链接器将所有相同类型的段都整合进入一个新的聚合的段中。例如,所有的输入模块中的.data段都会被整合进入输出的可执行文件的.data段中。链接器接下来为新生成的段,原模块中的段以及原模块中的符号赋予运行时地址。当这步完成后,程序中的每一个指令和全局变量都有了一个独一无二的运行时地址内存地址。

用段重定位符号引用:在这步中,链接器会修改代码和数据段中的每个符号引用,这时符号引用会指向正确的运行时地址。为了执行这步,链接器依赖一种在可重定位目标文件中的数据结构,称为重定位实体(relocation entries)

链接器把 hello.o 中的偏移量加上程序在虚拟内存中的起始地址得到了可直接访问的地址。

5.6 hello的执行流程

 

5.7 Hello的动态链接分析

 

动态链接内容为.got .got.plt

寻找所在地址为

 

 

 

.got变化

.got.plt变化

函数第一次被调用时才会被绑定地址位置并被存储于GOT表和PLT表中,故运行后二者皆会改变。

 

5.8 本章小结

本章主要内容为将hello程序进行链接,对得到的文件格式以及虚拟地址空间进行了分析,本章主要使用的工具为edb,通过使用edb以及阅读文件的elf可以看到程序的执行过程以及在其中涉及到的程序地址动态链接内容与相关函数调用,同时我们还分析了动态链接前后程序的变化

6章 hello进程管理

6.1 进程的概念与作用

进程的概念:

进程(Process)是指计算机中已运行的程序,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。进程是程序真正运行的实例,若干进程可能与同一个程序相关,且每个进程皆可以同步或异步的方式独立运行。

进程的作用:

向用户提供了一种假象,似乎我们的程序是当前系统运行的唯一程序,我们的程序好像独占处理器和内存

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

Shell 是一种交互型的应用级程序,用户能够通过 Shell 与操作系统内核进行交互

处理流程

1. Shell 处理流程:

2. 在 Shell 中输入 hello 程序的路径

3. Shell 判断用户输入的是否为内置命令,如果不是,就认为它是一个可执行目标文件

4. Shell 构造 argv 和 envp

5. Shell 使用 fork() 创建子进程,调用 execve() 函数在新创建的子基础南横的上下文中加载并运行 hello 程序。将 hello 中的 .text 节、.data 节、.bss 节等内容加载到当前进程的虚拟地址空间

6. execve() 函数调用加载器,跳转到程序的入口点,开始执行 _start 函数,我们的 hello 程序便正式开始执行了

 

6.3 Hello的fork进程创建过程

因为hello不是一个内部命令,因此shell会从文件系统中找到当前目录下的hello文件进行执行。调用fork创建一个新进程并在其中执行。子进程获取了父进程的上下文,包括栈信息、通用寄存器、程序计数器、环境变量和打开的文件相同的一份副本。两个进程本质上的不同就是子进程与父进程有着不同的PID。由于这种父进程与子进程的从属关系,子进程可以读取父进程打开的任何文件。当子进程运行结束的时候,对其执行回收。若其父进程仍存在,则由其父进程执行回收,否则由init进程执行回收。

6.4 Hello的execve过程

通过调用 fork 创建新的子进程之后,子进程会调用 execve 函数。子进程通过execve系统调用加载器删除子进程现有的虚拟内存段,并创建一组新的关于hello程序的代码、数据、堆和栈段。新的栈和堆段被初始化成0.通过将虚拟地址空间中的页映射到hello的页大小的片,新的代码和数据段被初始化为可执行文件的内容。最后跳转至hello的开始地址,开始执行main函数。

6.5 Hello的进程执行

6.5.1用户模式和内核模式

为了保证系统安全,处理器需要限制应用程序所能访问的地址空间范围。因而存在用户态与核心态的划分,内核态拥有最高的访问权限,而用户态的访问权限会受到一些限制。处理器使用一个寄存器作为模式位来描述当前进程的特权。进程只有故障、中断或陷入系统调用时才会得到内核访问权限,其他情况下始终处于用户权限之中。

6.5.2上下文切换

上下文就是内核重新启动一个被抢占的进程所需要恢复的原来的状态,由寄存器、程序计数器、用户栈、内核栈和内核数据结构等对象的值构成。当内核调度一个新的进程抢占当前进程后,我们需要进行上下文切换,保存以前进程的上下文,恢复新恢复进程被保存的上下文。

6.5.3逻辑控制流

操作系统将一个 CPU 物理控制流,分成多个逻辑控制流,每个进程独占一个逻辑控制流。当一个逻辑控制流执行的时候,其他的逻辑控制流可能会临时暂停执行。下图展示了一对进程 A和 B之间上下文切换的示例。在这个例子中,进程 A初始运行在用户模式中,直到它通过执行系统调用 read 陷入到内核。内核中的陷阱处理程序 请求来自磁盘控制器的 DMA传输,并且安排在磁盘控制器完成从磁盘到内存的数据传输后,磁盘中断处理器。

 

 

处理器在多个进程中来回切换称为多任务,每个时间当处理器执行一段控制流称为时间片。因此多任务也指时间分片。

6.5.4 hello的执行

运行 hello 时,它运行在用户模式,运行过程中,内核不断切换上下文,使运行过程被切分成时间片,进程交替执行,进程调度。如果在运行过程中收到信号等,那么就会进入内核模式,运行信号处理程序,之后再返回用户模式。

6.6 hello的异常与信号处理

SIGSTP:

 

 

使用ps,jobs命令

 

pstree指令

 

kill命令杀死进程

在运行过程中使用Ctrl+Z,发送 SIGSTP 信号,这个进程就会暂时挂起。

使用fg命令使其重新在前台工作

SIGINT:

 

 

6.7本章小结

本章主要阐述了 hello 的进程管理,创建一个进程的全过程。其次介绍了异常信号处理过程

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

结论

输出helloword这一行字符串需要若干过程,其背后的内容远超printf(“hello world”);这一行代码。

1.  预处理:去除注释,对hello.c的预处理指令进行处理,导入外部文件并整合源码。

2. 编译:编译器通过词法分析和语法分析,将C语言代码翻译成等价汇编代码。通过编译过程,编译器将 hello.i 翻译成汇编语言文件 hello.s

3. 汇编:将 hello.s 汇编程序翻译成机器语言指令,并把这些指令打包成可重定位目标程序格式。

4. 链接:通过链接器ld处理合并、链接动态库,生成最终的可执行文件。

5. Shell中运行:fork、execve后,创建一组新的关于hello程序的代码、数据、堆和栈段来让hello展现功能


附件

列出所有的中间产物的文件名,并予以说明起作用。

hello.i

hello.c预编译的结果,用于研究预编译的作用以及进行编译器的下一步编译操作。

hello.s

hello.i编译后的结果,用于研究汇编语言以及编译器的汇编操作,可以与hello.c对应,分析底层的实现。

hello.o

hello.s汇编后的结果,可重定位目标程序,没有经过链接,用于链接器或编译器链接生成最终可执行程序。

hello

hello.o链接后生成的可执行文件,可以用来反汇编或者通过EDB、GDB等工具分析链接过程以及程序运行过程。

hello.elf

可重定位目标文件的elf,用来看hello.o的Section information

elf.txt

可执行目标文件的elf

disasm_hello.s

.o经过反汇编生成的汇编语言文件

disasm.txt

可执行文件的反汇编文件

 

参考文献

[1]  CSDN.C预处理,2021[2021-03-31]. https://blog.csdn.net/weixin_44342705/article/details/115358254

[2]  CSDN.C语言编译过程,2022[2022-07-17].

https://blog.csdn.net/qq_61672347/article/details/125828033

[3]  博客园.ELF 可重定位目标文件简析,2019[2019-10-03].

https://www.cnblogs.com/lyw-hunnu/p/11619512.html

[4]  简书. 程序的链接——重定位,2019[2019-5-27].

程序的链接——重定位 - 简书

[5] Randal E. Bryant,Computer Systems: A Programmer's Perspective, 3rd edition[M] .America.Pearson,2016:736.

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值