《微机系统》实验
实验1:实验环境安装与测试
- 实验目的
- 掌握安装本实验的环境并以一简单程序“hello.c”进行测试。
- 熟悉掌握gcc编译命令和objdump反汇编命令
二、实验报告要求
- 说明你做实验的过程(重要步骤用屏幕截图表示)。
- 分析或回答问题。
三、实验内容
(1)在所使用的电脑平台上安装linux系统与环境
(提示:具体安装步骤可参考讲义的附录文件:实验环境安装指引)
1. 下载并安装VMware Workstation Pro
2. 安装成功之后打开VMware
3.下载Ubuntu
4.在虚拟机上安装Ubuntu
5. 打开Ubuntu系统
6. 打开终端
7. 安装gcc,输入命令:sudo apt-get install gcc,在安装时可能会出错,以下是一些解决方案,根据不同错误提示,可以百度解决
https://www.freesion.com/article/61581305747/
https://blog.csdn.net/qq_40467670/article/details/120896369
(2)成功在linux系统中使用gcc完成简单c语言的编译和执行
Hello.c的源代码如下
#include "stdio.h"
void main()
{ printf("hello world!\n");
}
1.使用命令在主文件夹下创建一个hello.c文件,可以通过gedit命令,输入源代码,点击保存,关闭。(也可以通过vim命令编辑)
2.按上图,使用下列gcc命令将hello.c程序依次转换为相应的程序文件。
依次输入以下命令:
gcc -E hello.c -o hello.i
gcc -S hello.i -o hello.s
gcc -c hello.s -o hello.o
gcc hello.o -o hello
gcc -o0 -m32 -g -no-pie -fno-pic hello.c -o hello2 //直接把hello.c编译为可执行文件hello2
最后一行可能会提出出错,可以输入
sudo apt-get install gcc-multilib
gcc命令的学习总结:
当使用 GCC 编译器编译一个 C 语言程序时,通常会经历以下几个步骤:
-
预处理(Preprocessing):在这一步中,预处理器会处理源文件,执行诸如宏替换、文件包含等预处理指令。通过执行以下命令进行预处理:
gcc -E hello.c -o hello.i
这将对
hello.c
文件进行预处理,并将结果保存为hello.i
文件。hello.i
文件包含了经过预处理后的 C 代码。gcc -S hello.i -o hello.s
这将把经过预处理的
hello.i
文件编译成汇编代码,并将结果保存为hello.s
文件。hello.s
文件包含了汇编代码。 -
汇编(Assembling):接下来,汇编器将汇编代码转换为目标文件(
.o
文件)。执行以下命令进行汇编:gcc -c hello.s -o hello.o
这将把汇编代码
hello.s
文件汇编成目标文件hello.o
。 -
链接(Linking):最后一步是链接,将目标文件和必要的库文件链接在一起生成可执行文件。执行以下命令进行链接:
gcc hello.o -o hello
这将把目标文件
hello.o
链接为可执行文件hello
。 -
特定选项编译:最后一个命令是用于演示一些特定的编译选项:
gcc -o0 -m32 -g -no-pie -fno-pic hello.c -o hello2
-o0
: 关闭优化选项。-m32
: 生成 32 位的目标文件。-g
: 生成调试信息。-no-pie
: 禁用位置无关执行。-fno-pic
: 禁用位置无关代码生成。hello2
: 最终生成的可执行文件名。
3.分别执行hello和hello2
./hello
./hello2
4.用记事本分别打开hello.i、hello.s、hello.o、hello文件,查看文件内容,体会文本文件和二进制文件差异。
学习总结:
-
hello.i:
hello.i
文件是经过预处理后的 C 代码。- 在这个文件中,所有的
#include
指令被替换为对应的文件内容,所有的宏替换也被执行了。 - 文件的内容是文本格式,可以在文本编辑器中打开和查看。
-
hello.s:
hello.s
文件是经过编译器转换的汇编代码。- 在这个文件中,每行代码对应一个汇编指令,用助记符来表示操作码和操作数。例如,
movl $0, %eax
表示将 0 存储到寄存器%eax
中。 - 文件的内容是文本格式,可以在文本编辑器中打开和查看。
-
hello.o:
hello.o
文件是经过汇编器转换的二进制代码。- 在这个文件中,每个指令被转换成机器码,并按照特定格式排列,以便操作系统将它们加载到内存中并执行。
- 文件的内容是二进制格式,不适合直接在文本编辑器中打开和查看,因为它包含大量的非文本数据。
-
hello:
hello
文件是经过链接器组合的可执行文件。- 在这个文件中,所有的指令和数据已经按照正确的顺序组合在一起,并被编译成二进制代码,以便操作系统能够加载和执行它们。
- 文件的内容是二进制格式,不适合直接在文本编辑器中打开和查看。
-
hello2:
hello2
文件是通过特定的编译选项编译的可执行文件。- 在这个文件中,使用了一些特定的编译选项,如禁用位置无关执行和位置无关代码生成等。
- 文件的内容也是二进制格式,不适合直接在文本编辑器中打开和查看。
(3)学习使用objdump命令实现反汇编
1.用objdump命令对hello.o和hello反汇编,并保存到文本文件;比较hello.o 和hello的反汇编内容差异。
输入以下命令:
objdump -S hello.o
objdump -S hello
objdump -S hello.o >helloo.txt
objdump -S hello >hello.txt
学习总结:相同
2.用objdump命令对hello2反汇编,比较hello 和hello2的反汇编内容差异。
objdump -S hello2 >hello2.txt
hello 和hello2的反汇编内容差异:
第一个区别涉及到文件格式,一个文件格式是elf64-x86-64,另一个是elf32-i386,这表示这两个文件分别是64位和32位的可执行文件。
第二个区别是在不同的地址上有不同的指令序列,这些指令序列可能是函数的开头部分,比如_init、plt等。
第三个区别是程序的入口点(_start),以及一些其他函数(_fini、deregister_tm_clones、register_tm_clones、__do_global_dtors_aux、frame_dummy等)的定义和内容。
这两个文件是不同平台上的不同版本的可执行文件。一个是64位架构的,另一个是32位架构的。
课后学习:。
实验内容: 1、以下程序实现了排序和求和算法,程序源码如下图所示。请根据提供的图片输入源程序文件,并保存为相应的.c 和.h 文件。 bubblesort.h: bubblesort.c: add.h: add.c: printresult.h: printresult.c:
2、分析理解源代码,将源程序文件进行预处理、编译、汇编和链接,以生成可执行文件。 使用gcc 直接生成可执行文件,并执行程序 gcc -o main main.c bubblesort.c add.c printresult.c ./main 令进行反汇编(请自行查阅OBJDUMP命令的使用方法) 例如,可使用“objdump –S”命令进行反汇编 objdump –S main>main.txt:将main进行反汇编
4、使用GDB命令进行各种调试(GDB命令参见教材附录C,也可自行查阅网上相关文档) 调试之前首先用“gcc –g”命令生成调试信息,否则调试失败。 gcc -m32 -g -o main main.c bubblesort.c add.c printresult.c gdb main 要求用各种GDB命令对程序进行调试(例如用info registers 查看寄存器内容)。 举例: 在主函数设置断点: 输入run命令,运行程序,停在源程序第8行 输入i r eip,查看eip寄存器,eip内存储的是当前执行到指令地址 输入s命令,执行一条指令 输入i r eip,查看eip寄存器,eip内存储的是当前执行到的指令的地址 下一个指令如下 | |||
四、思考题 (1)分析同一个源程序在不同机器上生成的可执行目标代码是否相同。 提示:从多个方面(如ISA、OS 和编译器)来分析。 在不同机器上使用不同的编译器或者设置不同的编译选项来编译同一个源程序时,生成的可执行目标代码可能会有差异: 指令集架构(ISA)差异: 如果源程序在不同的机器上使用了不同的指令集架构(比如x86、ARM等),则生成的可执行目标代码会有明显的不同,因为不同的架构对应着不同的指令集和指令格式。 操作系统(OS)差异: 不同的操作系统提供不同的系统调用接口、ABI(应用程序二进制接口)等,这些差异会影响可执行目标代码与操作系统的交互方式。因此,在不同操作系统上生成的可执行目标代码可能会有差异。 编译器差异: 不同的编译器可能采用不同的优化策略,生成的机器码结构和执行效率可能会有所不同。比如,一些编译器可能会进行函数内联、循环展开等优化,而另一些编译器可能不会执行这些优化。 编译器版本差异: 不同版本的编译器可能实现了不同的优化算法或者修复了不同的bug,这也会导致生成的可执行目标代码存在一定的差异。 即使是相同的源程序,在不同机器上生成的可执行目标代码也可能存在上述差异
在GDB 里面使用 file main 命令加载可执行文件 main,然后使用 disassemble printf 命令来查看函数 printf() 对应的机器代码段。 可以看到 printf@plt 函数的机器代码段。这里的 @plt 表示该函数属于过程链接表(Procedure Linkage Table)。分析了一下,这里显示的是汇编指令,而不是完整的机器码 使用 GDB 的 x/d 命令来查看内存地址中的机器指令: 在 GDB 中设置断点在 printf() 函数的入口处: break printf 接着运行程序直到断点处: run x/10i $pc 目前使用指令查看汇编指令,以为题目意图得到二进制码 机器码是否是二进制码?如果有,用什么指令? (3)为什么源程序文件的内容和可执行目标文件的内容完全不同? 因为它们代表了不同的阶段和形式。 源程序文件是以文本形式编写的,其中包含了程序的源代码,使用的是高级编程语言(如C、C++、Python等)的语法和结构。源程序文件是开发人员编写和理解代码的基础,它们通常包含变量、函数、控制流语句等高级抽象。 可执行目标文件是经过编译、链接和优化处理的二进制文件,其中包含了机器代码、符号表和其他相关信息。这些机器代码是计算机可以直接执行的底层指令,它们是通过将源代码转换为二进制表示形式生成的。可执行目标文件被操作系统加载到内存中,并由处理器执行其中的机器指令,实现程序的运行。 在编译过程中,源程序文件首先被编译器转换为汇编代码(Assembly code),然后通过汇编器将汇编代码转换为机器代码(Machine code)。最后,链接器将多个目标文件和库文件组合在一起,生成最终的可执行目标文件。 源程序文件和可执行目标文件之间的内容完全不同,这是因为它们分别代表了高级语言的源代码和底层机器代码的不同表示形式。 |