摘 要
hello程序从最初的hello.c开始,经过预处理生成hello.i文件,在经过编译、汇编生成hello.o可重定位目标文件,最后通过链接器与printf.o链接得到hello文件。它是一个可执行目标文件,可以被加载到内存中,由系统执行。
本文以系统的角度出发,分析C语言文件执行的整个程序生命周期,包括预处理、编译、汇编、链接、加载、运行、结束等阶段,从进程管理、存储管理、IO管理等方面较为深入地分析计算机系统运行程序时的工作原理。
关键词:计算机系统;程序的生命周期;
目 录
第1章 概述 - 4 -
1.1 HELLO简介 - 4 -
1.2 环境与工具 - 4 -
1.3 中间结果 - 4 -
1.4 本章小结 - 4 -
第2章 预处理 - 5 -
2.1 预处理的概念与作用 - 5 -
2.2在UBUNTU下预处理的命令 - 5 -
2.3 HELLO的预处理结果解析 - 5 -
2.4 本章小结 - 5 -
第3章 编译 - 6 -
3.1 编译的概念与作用 - 6 -
3.2 在UBUNTU下编译的命令 - 6 -
3.3 HELLO的编译结果解析 - 6 -
3.4 本章小结 - 6 -
第4章 汇编 - 7 -
4.1 汇编的概念与作用 - 7 -
4.2 在UBUNTU下汇编的命令 - 7 -
4.3 可重定位目标ELF格式 - 7 -
4.4 HELLO.O的结果解析 - 7 -
4.5 本章小结 - 7 -
第5章 链接 - 8 -
5.1 链接的概念与作用 - 8 -
5.2 在UBUNTU下链接的命令 - 8 -
5.3 可执行目标文件HELLO的格式 - 8 -
5.4 HELLO的虚拟地址空间 - 8 -
5.5 链接的重定位过程分析 - 8 -
5.6 HELLO的执行流程 - 8 -
5.7 HELLO的动态链接分析 - 8 -
5.8 本章小结 - 9 -
第6章 HELLO进程管理 - 10 -
6.1 进程的概念与作用 - 10 -
6.2 简述壳SHELL-BASH的作用与处理流程 - 10 -
6.3 HELLO的FORK进程创建过程 - 10 -
6.4 HELLO的EXECVE过程 - 10 -
6.5 HELLO的进程执行 - 10 -
6.6 HELLO的异常与信号处理 - 10 -
6.7本章小结 - 10 -
第7章 HELLO的存储管理 - 11 -
7.1 HELLO的存储器地址空间 - 11 -
7.2 INTEL逻辑地址到线性地址的变换-段式管理 - 11 -
7.3 HELLO的线性地址到物理地址的变换-页式管理 - 11 -
7.4 TLB与四级页表支持下的VA到PA的变换 - 11 -
7.5 三级CACHE支持下的物理内存访问 - 11 -
7.6 HELLO进程FORK时的内存映射 - 11 -
7.7 HELLO进程EXECVE时的内存映射 - 11 -
7.8 缺页故障与缺页中断处理 - 11 -
7.9动态存储分配管理 - 11 -
7.10本章小结 - 12 -
第8章 HELLO的IO管理 - 13 -
8.1 LINUX的IO设备管理方法 - 13 -
8.2 简述UNIX IO接口及其函数 - 13 -
8.3 PRINTF的实现分析 - 13 -
8.4 GETCHAR的实现分析 - 13 -
8.5本章小结 - 13 -
结论 - 14 -
附件 - 15 -
参考文献 - 16 -
第1章 概述
1.1 Hello简介
From Program to Process:
hello.c文件通过上述过程后得到可执行目标程序hello。在linux shell中输入命令运行程序,shell通过fork产生子进程。
From Zero to Zero:
fork产生的子进程调用execve执行hello,运行结束后终止并回收进程。程序开始运行前和程序运行结束后内存中均无改程序。
1.2 环境与工具
软件环境:Windows 10 64位;Vmware 14.11;Ubuntu 18.04 64位
硬件环境:Intel Core i7-4712MQ CPU 2.30GHz;16GB内存
开发与调试工具:CodeBlocks 64位;gcc;objdump;edb;readelf
1.3 中间结果
hello.i hello.c预处理后的文本文件
hello.s hello.i编译后的汇编文件
hello.o hello.s汇编后的可重定位目标文件
hello 链接后的可执行目标文件
helloAsm.txt hello.o的反汇编代码
helloEAsm.txt hello的反汇编代码
1.4 本章小结
本章简单概述了hello程序的执行过程,介绍了P2P、O2O,描述了软硬件环境和形成的中间文件。
第2章 预处理
2.1 预处理的概念与作用
预处理的概念:在编译之前进行的处理。C语言的预处理主要有三个方面的内容: 1.宏定义;2.文件包含;3.条件编译。预处理命令以符号“#”开头。
预处理器根据以字符#为开头的命令,修改原始的C程序。字符#为开头的命令告诉预处理器读取相应的内容(例如 头文件、宏定义、重定义),并将头文件直接插入到程序中,将宏直接进行宏展开。结果得到的是另一个C程序,通常以 .i 作为文件的扩展名。
2.2在Ubuntu下预处理的命令
gcc cpp hello.c > hello.i
2.3 Hello的预处理结果解析
使用cat -n hello.i将该文件输出到屏幕。
出此时程序中共有3118行,hello.c中的 main函数从第3102行才开始。hello.i文件对stdio.h unistd.h stdlib.h三个库进行了展开。hello.i文件中已经没有#include语句。
2.4 本章小结
简单讲述了预处理过程的概念和作用以及Linux下预处理命令,主要解析了C语言预处理的结果对程序的影响。
第3章 编译
3.1 编译的概念与作用
编译是指利用编译程序将用高级语言编写的源程序产生汇编语言程序的过程。编译器将hello.i文件翻译成文本文件hello.s,这是一个汇编语言程序。编译程序把一个源程序翻译成目标程序的工作过程分为五个阶段:词法分析;语法分析;语义检查和中间代码生成;代码优化;目标代码生成。主要是进行词法分析和语法分析,又称为源程序分析,分析过程中发现有语法错误,给出提示信息。
注意:这儿的编译是指从 .i 到 .s 即预处理后的文件到生成汇编语言程序
3.2 在Ubuntu下编译的命令
3.3 Hello的编译结果解析
3.3.1数据操作
全局变量sleepsecs定义时向int类型赋值2.5,编译器自动进行隐式类型转化将sleepsecs的初值改为2:
局部变量i和参数argc存储在运行时堆栈中,其中argc保存在 -20(%rbp)中,i在 -4(%rbp)中。
Hello.c中的两个字符串经过编译处理后如下:
3.3.2赋值
对于全局变量sleepsecs的赋值将数据存储在.data节中,局部变量i的赋值采用汇编语句mov:
3.3.3类型转换
全局变量sleepsecs定义时向int类型赋值2.5,编译器自动进行隐式类型转化将sleepsecs的初值改为2:
3.3.4算术操作
,在汇编代码中
3.3.5关系运算
, ,比较argc和3并设置条件码,符合则跳转至.L2。
, ,比较i和8并设置条件码,符合则跳转至.L4。
3.3.6数组操作
程序中argv参数是字符串指针数组,其各元素值为命令行中各字符串的首地址,将指针数组的首地址压入栈中,指针长度为8字节,所以将地址+8就能得到下一个指针存储的的位置。而rdi中保存第一个参数,由leaq .LC1一行可知,printf第一个参数为保存的字符串,rsi中保存第二个参数,rdx中保存第三个参数。
3.3.7控制转移
跳转指令根据条件码的某种组合,或者跳转,或者继续执行代码序列中下一条指令。这些指令的名字和跳转条件与SET指令的名字和设置条件是相匹配的。
3.3.8函数操作
在调用函数之前,先把需要的参数依次传入%rdi,%rsi,%rdx,%rcx,%r8,%r9等6个寄存器中,如果参数大于6,就会把参数放在栈中。
进行函数调用的时候,程序计数器必须设置为所调用函数的代码的起始地址,然后在返回时,要把程序计数器设置为调用函数的指令后面那条指令的地址。
函数执行完之后有ret操作,如果函数有返回值,就将寄存器%rax中的数返回,只有寄存器%rax可以存放返回值,在ret之前,还要将栈空间恢复为调用之前的状态,将栈指针回到调用函数的指令的下一条指令的位置。
main函数通过系统启用函数__libc_start_main使用call语句调用main函数,通过rdi和rsi传递参数argc和argv, 最后将返回值放到rax中返回零即return 0。
printf函数的参数传递在3.3.6中已经说明。
以edi中的值为第一个函数通过call调用sleep函数
3.4 本章小结
本章阐述了C语言编译器将C语言数据和操作翻译成汇编代码的结果,以及编译器优化的方式和结果。
第4章 汇编
4.1 汇编的概念与作用
把汇编语言翻译成机器语言的过程称为汇编。汇编程序将.s汇编文件转化为机器语言程序,并生成.o可重定向目标程序。
4.2在Ubuntu下汇编的命令
4.3 可重定位目标elf格式
ELF Header:以 16B 的序列 Magic 开始,Magic 描述了生成该文件的系统 的字的大小和字节顺序,ELF 头剩下的部分包含帮助链接器语法分析和解 释目标文件的信息。
Section Headers:节头部表,包含了文件中出现的各个节的语义。
节头部分:文件中各个出现的节的具体信息,类型、位置、大小等。
重定位节和存放程序中定义和引用的函数和全局变量的信息的符号表:
offset是重定向代码在.text或.data节中的偏移位置。
symbol代表重定位到的目标在.symtab中的偏移量
type代表重定位的类型
Addend是计算重定位位置的辅助信息
Type是重定位到的目标的类型
Name是重定向到的目标的名称
4.4 Hello.o的结果解析
将反汇编代码与汇编代码比较可得,反汇编代码跳转指令的操作数使用的不是段名称,反汇编代码中有机器指令及每个函数的相对地址,在汇编代码中,函数调用时直接call+函数名,而在反汇编代码中call指令后直接跟函数的相对地址。如果程序调用的函数没有自己定义,而是库函数或者外部函数的话就将偏置值设为0,然后在.rela.text 节中为其添加重定位条目,等待链接时再确定。只读数据中数据地址也是在运行时确定,故访问也需要重定位。
4.5 本章小结
本章介绍从 hello.s 到 hello.o 的汇编过程,通过查看 hello.o 的 elf 格式 和使用 objdump 得到反汇编代码并与hello.s 进行比较,了解了从汇编语言映射到机器代码时汇编器实现的转换和定位,跳转信息已经转换成了具体的地址值。介绍了从汇编指令到机器指令的变化。
第5章 链接
5.1 链接的概念与作用
链接是将各种代码和数据片段收集并组合成一个单一文件的过程,这个文件被加载到内存并执行。链接可以执行于编译时,也就是在源代码被编译成机器代码时;也可以执行于加载时,也就是在程序被加载器加载到内存并执行时;甚至于运行时,也就是由应用程序来执行。链接是由叫做链接器的程序执行的。链接器使得分离编译成为可能。
5.2 在Ubuntu下链接的命令
5.3 可执行目标文件hello的格式
其中,ELF头描述文件的总体格式,包括程序的入口点;段头部表将连续的文件映射到运行时内存段;.init定义了一个小函数,叫做_init;.text存储程序的机器代码;.rodata存储只读数据;.data存储已初始化的全局和静态C变量;.bss存放未初始化的全局和静态C变量;.symtab是一个符号表,存放在程序中定义和引用的函数和全局变量的信息。
5.4 hello的虚拟地址空间
虚拟地址空间是从0x400000开始到0x400ff0结束的
通过程序头部分可以看出各段在虚拟地址空间和物理地址空间的大小、位置、标志、访问权限和对齐方面的信息。从此也可以看出程序每个段的信息。
5.5 链接的重定位过程分析
Hello反汇编文件与hello.o反汇编文件最大的区别在于main函数前多了很多函数。链接之后不再是相对地址,而是虚拟地址。程序调用的库函数都被复制到了程序代码中。
存储在data及.rodata节中的信息,已经被存放在虚拟内存中,直接从虚拟内存的相应位置读取。
5.6 hello的执行流程
_dl_start
_dl_init
_stat
_cax_atexit
_new_exitfn
_libc_start_main
_libc_csu_init
_main
_printf
_exit
_sleep
_getchar
_dl_runtime_resolve_xsave
_dl_fixup
_dl_lookup_symbol_x
exit
5.7 Hello的动态链接分析
hello调用.so共享库函数是通过动态链接来完成的。现代系统采用位置无关代码(PIC)处理地址空间中的分配问题。位置无关代码包括数据引用和函数调用的部分,程序执行时仍然可以通过地址偏移来找到这些代码。
动态连接的实现是通过过程连接表+全局偏移量表来实现的,过程连接表通过全局偏移量表可以得知调用函数的地址。而由于延迟绑定的现象,只有到了调用这个函数的时候才会解析这个函数的地址,所以只有第一次执行这个函数的时候才会将函数序号压栈,然后跳转到PLT[0],在PLT[0]中将重定位表地址压栈,然后访问动态链接器,在动态链接器中使用函数序号和重定位表确定函数运行时地址,重写GOT,之后的每次执行直接访问就可以,这种策略有效的节省了大量的开销,加快了程序的执行效率。
通过Data Dump 分析可以看出got的变化,0x6008c0行多出的内容就是动态连接之后加入的。
执行init前的got
执行init后的got
5.8 本章小结
本章介绍了链接的概念与作用,并分析了hello程序的 ELF 格式各部分的意义和作用,阐述了hello程序的虚拟地址空间分配的过程,重定位的过程和动态链接的过程。
第6章 hello进程管理
6.1 进程的概念与作用
进程是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。进程的一个经典的定义就是一个执行中程序的实例,系统中的每个程序都运行在某个进程的上下文中。也可以说成,程序是指令、数据及其组织形式的描述,而进程是程序的实体。
在现代系统上运行一个程序时,我们会得到一个假象,就好像我们的程序是系统中当前运行的唯一的程序一样。我们的程序好像是独占地使用处理器和内存。处理器就好像是无间断地一条接一条地执行我们程序中的指令。最后,我们程序中的代码和数据好像是系统内存中唯一的对象。这些都是进程带给我们的。
6.2 简述壳Shell-bash的作用与处理流程
Shell本身是一个用C语言编写的程序,它是用户使用Unix/Linux的桥梁,用户的大部分工作都是通过Shell完成的。Shell既是一种命令语言,又是一种程序设计语言。作为命令语言,它交互式地解释和执行用户输入的命令;作为程序设计语言,它定义了各种变量和参数,并提供了许多在高级语言中才具有的控制结构,包括循环和分支。
Shell应用程序提供了一个界面,用户通过这个界面访问操作系统内核的服务。
处理流程:(1)从终端读入输入的命令。(2)将输入字符串切分获得所有的参数。(3)如果是内置命令则立即执行。(4)否则调用相应的程序执行。(5)shell应该接受键盘输入信号,并对这些信号进行相应处理。
6.3 Hello的fork进程创建过程
终端中输入 ./hello 1161910117 肖亮,终端对输入的命令行进行解析。
hello不是一个内置的 shell 命令。所以解析之后终端程序判断./hello 的语义为执行当前目录下的可执行目标文件,
终端程序调用 fork函数创建一个新的子进程,子进程得到与父进程用户级虚拟地址空间相同的(但是独立的)一份副本,包括代码和数据段、堆、共享库及用户栈。但父进程与子进程之拥有不同的 PID。
6.4 Hello的execve过程
execve函数加载并运行hello,且带参数列表argv和环境变量envp。在execve加载了hello之后,调用启动代码。启动代码设置栈,并将控制传递给新程序的主函数。
6.5 Hello的进程执行
Hello程序刚执行是处于用户模式,调用sleep后进入内核模式,内核处理休眠请求并释放当前进程,将hello进程从运行队列中加入等待队列,内核进行上下文切换,将当前进程的控制权交给其他进程。睡眠2秒后发送一个中断信号,此时再次发生上下文切换,陷入内核态进行中断处理,将hello进程从等待队列中移出重新加入到运行队列,成为就绪状态,hello进程就可以继续进行自己的控制逻辑流了。
6.6 hello的异常与信号处理
按下ctrl+z后,父进程收到SIGSTP信号,将hello进程挂起。
按下ctrl+c后,父进程收到SIGINT信号,结束hello进程。
乱按只是将屏幕的输入缓存到缓冲区。
6.7本章小结
介绍了进程的基本概念和Shell程序的一般处理流程,以及shell是如何具体地通过调用fork 创建新进程和调用 execve 执行hello。还介绍了进程的异常与信号处理。
第7章 hello的存储管理
7.1 hello的存储器地址空间
物理地址:CPU 通过地址总线的寻址,找到真实的物理内存对应地址。
逻辑地址:程序代码经过编译后出现在汇编程序中地址。
线性地址:逻辑地址经过段机制后转化为线性地址,为描述符:偏移量组合形式。
7.2 Intel逻辑地址到线性地址的变换-段式管理
逻辑地址中的段选择符选中的段描述符先被送至描述符cache,每次从描述符cache中取32位段基址,与32位段内偏移量(有效地址)相加得到线性地址。
7.3 Hello的线性地址到物理地址的变换-页式管理
翻译时将线性地址分为VPN(虚拟页号)+VPO(虚拟页偏移)的形式, VPN又可以拆分成TLBT(TLB标记)+TLBI(TLB索引)。如果页面命中,高速缓存或主存直接返回PTE,MMU构造物理地址,并传回。如果页面不命中,触发异常,启动缺页异常处理程序。缺页异常处理程序确定物理内存中的牺牲页,如果这个页面已被修改,则直接把它换出硬盘,然后把程序页面换入新的页面,更新PTE。
7.4 TLB与四级页表支持下的VA到PA的变换
线性地址分为VPN(虚拟页号)+VPO(虚拟页偏移), VPN又可以拆分成TLBT(TLB标记)+TLBI(TLB索引)。当TLB不命中时,需要在页表中查询PPN。CR3确定第一级页表的起始地址,VPN1确定在第一级页表中的偏移量,查询出PTE,确定第二级页表的起始地址,以此类推,最终在第四级页表中查询到PPN,与VPO组合成PA。
7.5 三级Cache支持下的物理内存访问
将PA分解成CT、CI、CO,根据CI找到对应的组别,根据CT找是否有一致的tag,根据CO找到块偏移,如果没有找到,就去下一级cache之中找,以此类推,如果三级cache都没有找到,就在主存中找。
7.6 hello进程fork时的内存映射
当shell进程调用fork函数时,它创建了当前进程的 mm_struct、区域结构和页表的原样副本。它将这两个进程的每个页面都标记为只读,并将两个进程中的每个区域都标记为私有写时复制。
7.7 hello进程execve时的内存映射
execve在当前进程中加载并运行包含在可执行目标文件hello中的程序,用 hello程序有效地替代了当前程序。加载并运行 hello 有以下几个步骤:删除已存在的用户区域;映射私有区域,为新程序的代码、数据、.bss和栈区域创建新的区域结构;hello 程序与共享对象 libc.so 链接;设置程序计数器PC。
7.8 缺页故障与缺页中断处理
缺页中断就是要访问的页不在主存,需要操作系统将其调入主存后再进行访问。在这个时候,被内存映射的文件实际上成了一个分页交换文件。缺页处理程序选择一个牺牲页面,换入新的页面并更新页表。当缺页处理程序返回时,CPU 重新启动引起缺页的指令,这条指令再次发送虚拟地址到MMU。
7.9动态存储分配管理
动态内存分配器维护着一个进程的虚拟内存区域,称为堆。它紧接在未初始化的数据区域后开始,并向上生长。对于每个进程,内核维护着一个变量brk,它指向堆的顶部。
分配器有两种基本风格:
显式分配器,要求应用显式地释放任何已分配的块,比如C、C++中的free函数释放。
隐式分配器,要求分配器检测一个已分配块何时不再被程序所使用,那么就释放这个块。诸如Java之类的高级语言就依赖垃圾收集来释放已分配的块。
7.10本章小结
介绍了程序的存储器地址空间,讲述了物理地址、线性地址、逻辑地址的概念,介绍了逻辑地址到线性地址、线性地址到物理地址的转换过程。本章还讲述了进程运行时使用fork函数创建新的进程,以及execve函数加载新进程时系统对内存空间的操作。讲述了计算机系统进行缺页处理以及动态存储分配的管理方式。
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
所有的I/O设备都被模型化为文件,而所有的输入和输出都被当作对相应文件的读和写来执行。这种将设备映射为文件的方式,允许Linux内核引出一个简单、低级的应用接口,成为Unix I/O,这使得所有的输入和输出都能以一种统一且一致的方式来执行。
8.2 简述Unix IO接口及其函数
打开和关闭文件:open() close(),读写文件:read() write(),改变当前的文件位置:lseek()。
打开文件open():返回一个文件描述符。返回的描述符总是在进程中当前没有打开的最小描述符。Linux内核创建的每个进程都以与一个终端相关联的三个打开的文件开始:0: 标准输入 (stdin)、1: 标准输出 (stdout)、2: 标准错误 (stderr)。
关闭文件:close(),若成功关闭返回0;否则返回-1。
读文件read():从当前文件位置复制字节到内存位置,然后更新文件位置,返回值表示的是实际传送的字节数量。
写文件write():写文件从内存复制字节到当前文件位置,然后更新文件位置。返回值表示的是从内存向文件fd实际传送的字节数量。
8.3 printf的实现分析
Printf:
int printf(const char fmt, …)
{
int i;
char buf[256];
va_list arg = (va_list)((char)(&fmt) + 4);
i = vsprintf(buf, fmt, arg);
write(buf, i);
return i;
}
vsprintf:
int vsprintf(char *buf, const char fmt, va_list args) {
char p;
char tmp[256];
va_list p_next_arg = args;
for (p = buf; *fmt; fmt++){
if (*fmt != ‘%’){
*p++ = *fmt;
continue;
}
fmt++;
switch (fmt){
case ‘x’:
itoa(tmp, ((int)p_next_arg));
strcpy(p, tmp);
p_next_arg += 4;
p += strlen(tmp);
break;
case ‘s’:
break;
default:
break;
}
}
return (p - buf);
}
在 printf 中调用系统函数write(buf,i)输出长度为i的buf。write 函数中,将栈中参数放入寄存器,%ecx中存放字符个数,%ebx 中存放第一个字符地址,int INT_VECTOR_SYS_CALLA 代表通过系统调用 sys_call,查看 sys_call 的实现:
sys_call:
call save
push dword [p_proc_ready]
sti
push ecx
push ebx
call [sys_call_table + eax * 4]
add esp, 4 * 3
mov [esi + EAXREG - P_STACKBASE], eax
cli
ret
8.4 getchar的实现分析
getchar函数的代码:
int getchar(void){
static char buf[BUFSIZ];
static char bb = buf;
static int n = 0;
if(n == 0){
n = read(0, buf, BUFSIZ);
bb = buf;
}
return(–n>=0) ? (unsigned char)*bb++ : EOF;
}
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。read函数由三个参数,分别为文件描述符fd、输入内容的指针和读入字符的个数。read函数的返回值是读入字符的个数,若出错则返回-1。
8.5本章小结
本章简单讲述了Linux的IO设备管理方式,介绍了文件在linux中的形式和UNIX I/O接口,介绍了UNIX I/O接口函数并结合hello分析了printf函数以及getchar函数的实现。
结论
hello程序的生命周期是从一个高级C语言程序开始的。为了在系统上运行hello.c程序,每条C语句都必须被其他程序转化为一系列的低级机器语言指令。然后这些指令按照一种称为可执行目标程序的格式打好包,并以二进制磁盘文件的形式存放起来。
在shell中输入相关命令后,GCC编译器从磁盘中读取hello.c,通过编译系统把它翻译成一个可执行目标文件hello存储在磁盘上。
在shell中输入相关命令后,shell加载并运行hello程序,然后等待程序终止。hello程序在屏幕上输出它的消息,然后终止。shell随后输出一个提示符,等待下一个输入的命令行。
在hello的执行过程中,shell先fork一个进程,在这个新的进程中通过调用execve执行hello,execve会加载hello,然后把栈指针指向hello的程序入口,开始执行main()。hello执行的过程中可能收到来自键盘或者其它进程的信号,当收到信号时hello会调用信号处理程序来进行处理,终止程序、挂起程序,也可能暂时忽略继续执行hello。hello要访问内存,需要经历逻辑地址到线性地址再到物理地址的变换过程。
hello输出时需要调用printf和getchar函数,printf和getchar的实现需要调用Unix I/O中的write和read函数,这时程序会和I/O接口进行交互。
(结论0分,缺失 -1分,根据内容酌情加分)
附件
hello.i hello.c预处理后的文本文件
hello.s hello.i编译后的汇编文件
hello.o hello.s汇编后的可重定位目标文件
hello 链接后的可执行目标文件
helloAsm.txt hello.o的反汇编代码
helloEAsm.txt hello的反汇编代码
(附件0分,缺失 -1分)
参考文献
为完成本次大作业你翻阅的书籍与网站等
[1] 林来兴. 空间控制技术[M]. 北京:中国宇航出版社,1992:25-42.
[2] 辛希孟. 信息技术与信息服务国际研讨会论文集:A集[C]. 北京:中国科学出版社,1999.
[3] 赵耀东. 新时代的工业工程师[M/OL]. 台北:天下文化出版社,1998 [1998-09-26]. http://www.ie.nthu.edu.tw/info/ie.newie.htm(Big5).
[4] 谌颖. 空间交会控制理论与方法研究[D]. 哈尔滨:哈尔滨工业大学,1992:8-13.
[5] KANAMORI H. Shaking Without Quaking[J]. Science,1998,279(5359):2063-2064.
[6] CHRISTINE M. Plant Physiology: Plant Biology in the Genome Era[J/OL]. Science,1998,281:331-332[1998-09-23]. http://www.sciencemag.org/cgi/ collection/anatmorp.
(参考文献0分,缺失 -1分)