【计算机系统基础】揭秘C语言到机器码的完整编译之旅
主要任务明白C语言--汇编程序--机器码以及机器码存储和CPU处理机器码的过程。目录一、获取范例程序的汇编代码与机器码二、程序的编译1. 选择编译器(选择指令集)2. 编译流程3. 编译实现4. objdump工具的作用5. 如何获取函数与变量的地址三、指令和数据在存储器中的存储方式1. 存储器的基本分类2. 程序的变量数据与函数指令在计算机中的存储方式一、获取范例程序的汇编代码与机器码1第1步编译将范例程序源码编译成可执行文件。2第2步分析工具提取采用可执行文件分析工具从编译后生成的可执行文件中提取出汇编代码和机器码。二、程序的编译1. 选择编译器(选择指令集)不同的编译器将编译生成对应不同指令集的汇编语言和机器码指令。因此编译器的选择实际上就是指令集的选择。常见的指令集有x86、ARM、RISC-V、MIPS等。RISC-V中bin目录包含-riscv-nuclei-elf-gcc.exe编译器可用于编译源代码生成可执行文件。-riscv-nuclei-elf-size.exe大小分析工具可用于分析编译后可执行文件所需消耗的资源空间大小。-riscv-nuclei-elf-objdump.exe反汇编工具可用于从可执行文件中提取源程序的汇编代码和机器码。2. 编译流程3. 编译实现编译实现有两种方法分别为一步到位编译法和两步到位编译法。一步到位编译法仅执行一条命令来完成从“预处理 编译 汇编 链接”的全过程。riscv-nuclei-elf-gcc.exe-marchrv32imac -mabiilp32 -Wall -O1 -g -nostartfiles-Tlinker_script.ld-Wl,-Mapexample.map-oexample.elfstartup.smain.cfun.c分别对应编译器、编译控制选项、范例程序的源代码两步到位编译法•第1步先执行一条指令完成从“预处理à编译à汇编”的编译过程生成各个目标文件但不链接。•第2步再执行一条指令对各个目标文件进行“链接”生成可执行文件。作者在实际操作中使用一步到位编译法。注意最后的启动文件、源文件一定要路径正确作者在它们所在的文件夹中执行的cmd这样直接写文件名就可以搜索到方便一点。第一句指令就是一步编译产生可执行文件example.elf但是它是机器码文件转换了但是没有完全转换因为初学者完全看不懂鸭那就使用objdump工具吧4. objdump工具的作用编译生成的可执行文件中含有大量的机器码无法直接阅读。采用objdump工具可直接从执行文件中提取出源代码的汇编代码和机器码。分析出来的内容如下•黑色部分为源代码•蓝色部分为源代码编译后生成的汇编代码•红色部分为汇编编译后生成的机器码16进制•绿色部分为机器码在存储器中的存储地址•灰色部分为注释5. 如何获取函数与变量的地址该问题涉及到编程中的一些基础概念如数据的内存表示和端序。为了回答你的问题我们首先要明确一些概念。端序计算机存储多字节数据如int, float时有两种主要的方法这两种方法称为大端序Big Endian和小端序Little Endian。大端序最高有效字节在最低地址处最低有效字节在最高地址处。小端序最低有效字节在最低地址处最高有效字节在最高地址处。举个例子整数18在内存中的二进制表示为0001 0010这是8位表示实际上一个整数在大多数现代计算机上是32位或4字节。在大端序系统中它会这样存储0000 0000 0000 0000 0000 0000 0001 0010在小端序系统中它会这样存储0001 0010 0000 0000 0000 0000 0000 0000内存表示当你使用objdump -d或其他类似的工具查看内存时你通常会看到内存中的实际字节值。为什么s[0] 18输出的是1218在十六进制中表示为12所以你在内存中看到的是12。为什么输出的数值形式是00001200这是因为计算机很可能使用小端序来存储数据。如前所述小端序存储将最低有效字节放在最低地址处。所以整数18十六进制中的12在内存中的表示形式是0012 0000 0000 0000。三、指令和数据在存储器中的存储方式1. 存储器的基本分类2. 程序的变量数据与函数指令在计算机中的存储方式注意s[2]是未赋值的变量放在.bss部分不是data区注意在ROM存储指令时首先存储startup启动函数部分再存储main函数再按照定义顺序存放其他函数如图所示RISC-V程序假设程序的指令和数据分别存放在ROM和RAM中则:1指针p与指针k有什么区别?2如何理解p max语句?3(*p)(a, k)语句执行何种操作?解1指针p•指针p是一个函数指针而非变量指针。•指针p指向一个“参数类型为(int,int*)返回值类型为int”的函数的首地址。•注意int(*p)(int,int*)不是函数声明函数名不会带*号。•指针p指向ROM中某个地址处而不是SRAM中。•指针k指针k是一个数据指针指向SRAM中某个变量的存储首地址。通过objdump工具可以获取各个变量的地址如下•a0x20000000a初始值为0x46•p0x20000004p初始值为0•k0x20000008k初始值为0各个函数的地址如下•max0x8000060注意函数名表示函数地址•main0x800006c注意函数名表示函数地址FLASH是闪速存储器属于ROM的新的发展器件哦2pmax操作将FLASH中存储的max函数地址值赋值给函数指针p故p0x8000060ka操作将SRAM中变量a地址值赋值给变量指针k故k0x20000000执行上述语句中SRAM中变化如图所示貌似老师将小端存储作为默认方式了是低字节存在低地址中哦解3*k与 *pk为数据指针故 *k表示获取数据指针k所指向的SRAM中变量的值。p为指令指针故 *p表示获取指令指针p所指向的FLASH中的指令并运行。由于pmax故*p即表示从max函数处取指执行也即调用函数max。因此(*p)(a,k)等效于max(a,k)函数指针的“值”相当于直接调用该函数获得返回值