哈尔滨工业大学计算机系统HIT-ICS大作业:程序人生-Hello’s P2P ...

打印 上一主题 下一主题

主题 1503|帖子 1503|积分 4509

摘  要

本陈诉以老师提供的非常具有代表性的hello.c程序为例,分析了一个C语言程序从源代码到进程实行的完备生命周期。通过深入分析程序的预处理惩罚、编译、汇编、链接、进程管理和存储管理等关键环节,探究了计算机系统底层的工作机制。撰写本陈诉的过程中采取Ubuntu 20.04作为实行环境,结合GCC工具链和多种调试分析工具,详细记载了程序在各阶段的转换过程与中间结果。特别探讨了ELF文件格式、捏造地址空间管理、动态链接机制、进程调理计谋以及内存管理技术等核心概念。实践探究结果印证了当代计算机系统通过精妙的分层抽象和高效的资源管理机制,来实现程序的可靠实行和系统资源的高效利用,不但加深了对计算机系统工作原理的明确,也为后续的系统级编程和性能优化提供了理论底子和实践引导。
关键词:计算机系统;程序生命周期;ELF格式;进程管理;存储管理;动态链接
第1章 概述

1.1 Hello简介

Hello程序是一个简单的C语言程序,它展示了程序从源代码到进程实行的完备生命周期,即"From Program to Process"(P2P)的过程,以及从零开始到终极结束的"From Zero-0 to Zero-0"(020)过程。
P2P过程分析:
Program阶段:

  • 程序员创建hello.c源文件,包含main函数和必要的头文件。
  • 预处理惩罚阶段:处理惩罚#include和#define等预处理惩罚指令,生成hello.i文件。
  • 编译阶段:将预处理惩罚后的C代码转换为汇编代码(hello.s)。
  • 汇编阶段:将汇编代码转换为机器码,生成可重定位目标文件(hello.o)。
  • 链接阶段:将目标文件与系统库链接,生成可实行文件(hello)。
Process阶段:

  • Shell通过fork()创建新进程。
  • execve()加载并实行hello程序。
  • CPU实行程序指令,输出问候信息。
  • 程序终止,进程被操作系统回收。
020过程分析:

  • From Zero:程序从无到有,经过编译链接过程生成可实行文件。
  • 实行阶段:程序运行时创建进程,在屏幕上输出指定格式的信息。
  • To Zero:程序实行完毕,进程终止,所有资源被系统回收,回归初始状态。
1.2 环境与工具

硬件环境:
处理惩罚器:AMD Ryzen 7 7745HX with Radeon Graphics-3.60 GHz
内存:16.0GB(机器的全部内存);4GB RAM(Ubuntu捏造机可用的内存空间)
存储:2TB(机器的全部硬盘空间)20GB SSD(分配给Ubuntu捏造机的硬盘空间)
软件环境:
操作系统:Ubuntu 20.04.6 LTS
内核版本:Linux 5.15.0-134-generic
开辟与调试工具:
GCC工具链:
gcc 11.3.0:用于编译和链接程序
编译下令:gcc -m64 -Og -no-pie -fno-stack-protector -fno-PIC hello.c -o hello
调试工具:

  • GDB 12.1:用于程序调试
  • EDB调试器:用于分析程序实行过程
分析工具:

  • objdump:用于反汇编和目标文件分析
  • readelf:用于分析ELF格式文件
  • strace:用于跟踪系统调用
1.3 中间结果

在Hello程序的生成和实行过程中,会产生以下中间文件:
hello.i:预处理惩罚后的源代码文件
作用:展示宏展开和头文件包含后的完备代码
生成下令:gcc -E hello.c -o hello.i
hello.s:汇编代码文件
作用:展示C代码转换后的汇编语言表示
生成下令:gcc -S hello.i -o hello.s
hello.o:可重定位目标文件
作用:包含机器码但未完成终极地址重定位
生成下令:gcc -c hello.s -o hello.o
hello:终极可实行文件
作用:可直接在Linux系统上运行的程序
生成下令:gcc hello.o -o hello
1.4 本章小结

本章概述了Hello程序的生命周期(P2P和020过程),先容了完成本大作业所使用的软硬件环境和工具链,并枚举了在程序构建过程中生成的中间文件及其作用。通过本章的先容,我们对Hello程序有了整体熟悉,为后续章节深入分析程序的预处理惩罚、编译、汇编、链接、进程管理和存储管理等细节奠定了底子。接下来的章节将逐步深入分析Hello程序在计算机系统中的完备实行过程。
第2章 预处理惩罚

2.1 预处理惩罚的概念与作用

预处理惩罚(Preprocessing)是C程序编译的第一个阶段,由预处理惩罚器(cpp)负责处理惩罚源代码中的预处理惩罚指令(以#开头的指令)。其主要作用包罗:

  • 宏展开:更换#define定义的宏。
  • 头文件包含:将#include指定的头文件内容插入到源文件中。
  • 条件编译:根据#if、#ifdef、#ifndef等条件编译指令选择性地包含或排除代码。
  • 删除解释:移除所有解释(/* ... */ 和 // ...)。
  • 添加行号和文件名标志:便于后续编译阶段的错误定位。
预处理惩罚后的文件(.i后缀的文件)仍旧是纯文本文件,但已经去除了预处理惩罚指令,并完成了宏更换和头文件展开。
2.2在Ubuntu下预处理惩罚的下令

在Ubuntu中,可以使用GCC的-E选项进行预处理惩罚:
gcc -E hello.c -o hello.i
此中,-E表示仅实行预处理惩罚,不进行编译、汇编和链接;-o hello.i表示指定输出文件名为hello.i。
在Ubuntu终端实行此下令的结果如下图所示:

图2.1:Ubuntu下实行预处理惩罚下令前后截图

可以看到,在没有实行下令行时,该目录下只有hello.c一个文件;实行过预处理惩罚下令后,便多了生成的预处理惩罚文件:hello.i。同时也可以看到在当前目录的文件夹里出现了所预期的hello.i文件。
2.3 Hello的预处理惩罚结果分析

打开生成的hello.i文件,可见原来的c程序被预处理惩罚成为了3060行代码:

图2.2:查看hello.i的结果

可以观察到,c程序里的代码部分在预处理惩罚后并没有变革,只占了3060行预处理惩罚代码中的14行。前面的大量代码,都源于头文件<stdio.h>  <unistd.h>  <stdlib.h> 的依次展开。
以 stdio.h 的展开为例:预处理惩罚过程中,#include指令的作用是把指定的头文件的内容包含到源文件中。stdio.h是标准输入输出库的头文件,它包含了用于读写文件、标准输入输出的函数原型和宏定义等内容。
当预处理惩罚器遇到#include<stdio.h>时,它会在系统的头文件路径下查找stdio.h文件,一般在/usr/include目录下,然后把stdio.h文件中的内容复制到源文件中。stdio.h文件中可能另有其他的#include指令,比如#include<stddef.h>或#include<features.h>等,这些头文件也会被递归地展开到源文件中。
预处理惩罚器不会对头文件中的内容做任何计算或转换,只是简单地复制和更换。
在阅读预处理惩罚得来的hello.i的内容时间,也可以发现并印证一些预处理惩罚过程中会对原始代码做出的改变。下面以截图举例说明:
首先就是头文件的展开:

图2.3:头文件展开截图示例

可以看到,一些库函数里的功能函数,比如fflush和相干的FILE操作函数,都被展开成了如上图所示的以extern为开头的声明格式。
然后就是解释删除:

图2.4:解释删除截图示例

可以看到,预处理惩罚文件在头文件展开代码结束后紧跟着hello.c中的代码部分,原有的一些解释被全部删除了,这符合我们对预处理惩罚过程的认知。
2.4 本章小结

本章讲述了在Linux环境中,如何用下令对c程序进行预处理惩罚,以及预处理惩罚的寄义和作用。然后用一个简单的hello程序演示了从hello.c到hello.i的过程,并对所生成的hello.i文件进行了查看和分析。通过对比观察,可以分析得到预处理惩罚过程会将引用的头文件全部展开为所引用的库文件内容,而且预处理惩罚文件会包含一些宏和常量的定义,另有一些行号信息和条件编译指令,同时也会把c程序中的解释部分全部删除。
第3章 编译

3.1 编译的概念与作用

编译(Compilation)是将预处理惩罚后的C代码(.i文件)转换为汇编代码(.s文件)的过程。编译器(如GCC)在此阶段实行以下关键操作:

  • 词法分析:将源代码分解为词法单位(tokens)。
  • 语法分析:构建抽象语法树(AST),检查语法正确性。
  • 语义分析:验证类型、作用域等语义规则。
  • 中间代码生成:生成与机器无关的中间表示(如RTL)。
  • 代码优化:对中间代码进行优化(如常量传播、死代码消除)。
  • 目标代码生成:将优化后的中间代码转换为目标机器的汇编指令。
3.2 在Ubuntu下编译的下令

在Ubuntu中使用GCC生成汇编代码的下令如下:
gcc -S hello.i -o hello.s
在Ubuntu终端实行此下令的结果如下图所示:

图3.1:Ubuntu下实行编译下令前后截图

可以看到,在没有实行下令行时,该目录下只有hello.c和hello.i两个文件;实行过编译下令后,便多了生成的汇编文件:hello.s。同时也可以看到在当前目录的文件夹里出现了所预期的hello.s文件。
3.3 Hello的编译结果分析

3.3.1 程序元信息和字符串常量

图3.2:汇编代码中的程序元信息和字符串常量部分

首先开头标识了源文件为hello.c,之后定义了两个字符串常量,存储在.rodata只读数据段:.LC0是参数错误提示信息,.LC1是printf的格式字符串。.LC0应为"用法: Hello 学号 姓名 手机号 秒数!",此处是汉字,呈现为UTF-8编码的中笔墨符串的八进制转义序列表示,由于在汇编文件(.s)中,GCC编译器会将非ASCII字符(如中文)转换为八进制转义序列(\后跟3位八进制数字),以确保汇编器能正确分析。
3.3.2 函数框架

图3.3:汇编代码中的函数框架部分

此部分设置了函数栈帧,分配32字节栈空间;endbr64是安全特性指令,防止控制流挟制。
3.3.3 参数检查

图3.4:汇编代码中的参数检查部分

此部分使用cmpl指令比较参数数量,并通过PLT(过程链接表)调用库函数。
3.3.4 主循环结构
此部分使用jmp和条件跳转实现for循环,通过指针算术运算访问argv数组元素,并在每次循环调用printf和sleep函数。对于详细代码的分析见下图:

图3.5:汇编代码中的主循环结构部分

3.3.5 函数返回

图3.6:汇编代码中的函数返回部分

此部分调用getchar等待输入,设置返回值0,然后恢复栈帧并返回。
以上我们是从汇编代码的整体结构入手,分析每一段汇编代码对应什么作用,下面我们将进一步把汇编代码按着数据处理惩罚,函数调用,赋值、算术、关系等运算以及控制跳转和类型转换等详细操作进行分析。
3.3.6 数据与类型处理惩罚
(1)常量处理惩罚

图3.7:汇编代码中的常量处理惩罚部分

字符串常量使用.string指令定义,存储在只读数据段(.rodata),编译器将中笔墨符转换为UTF-8编码的八进制转义序列。
(2)变量处理惩罚

图3.8:汇编代码中的变量处理惩罚部分

这两行汇编代码分别将argc和argv存储到栈。
(3)类型转换

图3.9:汇编代码中的类型转换部分

atoi实现显式的字符串到整数转换,movl指令处理惩罚32位到64位的隐式扩展。
3.3.7 表达式与操作符
(1)算数运算

图3.10:汇编代码中的算数运算(其一)

此条语句是通过addq实现指针运算;

图3.11:汇编代码中的算数运算(其二)

而此条语句是使用addl实现整数自增,他们都是算术运算的表现。
(2)关系运算

图3.12:


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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

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

本版积分规则

张裕

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表