由于格式不够美观,我重新将博客换了一种编译器编写,但因为编译器的修改必须重新开一篇博客。留此截图以示提交日期,此博客为格式修改过的新随笔,以此为准。
DOS时代的平坦模式,不区分用户空间和内核空间,很不安全;8086的分段模式;IA32的带保护模式的平坦模式
特别注意:i386增加了平坦寻址模式,Linux和最近版本的windows系列操作系统都是使用这种模式,这是Intel系列中第一台Unix操作系统的机器。
①ISA:机器级程序的格式和行为,定义为指令集体系机构,它定义了处理器状态指令的格式,以及每条指令对状态的影响。
机器级程序使用的存储器地址是虚拟地址,提供的存储器模型看上去是一个非常大的字符数组。
②PC:程序计数器。在IA32中,用%eip表示,指示将要执行的下一条指令在存储器中的地址。
③程序存储器:包含程序的可执行机器代码,操作系统需要的一些信息,用来管理过程调用和返回的运行时栈,以及用户分配的存储器块。
④ARM(举一反三部分)参考链接:
http://blog.chinaunix.net/uid-25067956-id-398205.html
①64位机上想得到32位代码:
gcc -m32 -S xxx.c
②编译并产生汇编目标文件xxx.o:
gcc -O1 -c xxx.c
③获得汇编代码:
gcc -S xxx.c -o xxx.s
④Ubuntu中获得汇编代码:(更接近教材)
gcc -S xxx.c
⑤教材中获得汇编代码:(编译器使用的是第一级优化)
gcc -O1 -S xxx.c
⑥汇编代码中需要牢记以下几句:
pushl %ebp movl %esp,%ebp …… popl %ebp ret
⑦二进制文件可用od命令查看,也可以用gdb的x查看:
(gdb)x/17xb sum
(表示检查17个十六进制的字节)
⑧显示代码过多或过少可用more、less结合管道查看,也可以用输出重定向:
od xxx.o | more od xxx.o > xxx.txt
⑨反汇编:
objdump -d xxx.o
⑩MAC OS中没有objdump,可用otool代替;
***注意:汇编代码中另类结尾有'l'指的是大小指示符。
当带选项-S和-O1运行gcc时,会产生xxx.s文件,其中带有'.'开头的行是指导汇编器和链接器的命令。 gcc -S产生的汇编代码中可以把以'.'开头的语句删除再使用也没关系。 了解Linux和windows的汇编格式的区别:Intel代码省略了指示大小的后缀,即'l';Intel代码省略了寄存器名字前面的'%'符号,用的是esp,而不是%esp。
1、IA32的整数寄存器:
其中,esi、edi可以用来操纵数组,esp、ebp用来操纵栈帧。
通用寄存器中的eax,ebx,ecx,edx中,32位的eax,16位的ax,8位的ah,al都是独立的。例如: 假定当前是32位x86机器,eax寄存器的值为0x8226,执行完addw $0x8266, %ax指令后eax的值是多少? 解析:0x8226+0x826=0x1044c, ax是16位寄存器,出现溢出,最高位的1会丢掉,剩下0x44c,不要以为eax是32位的不会发生溢出。
操作数的三种类型:立即数、寄存器、存储器。
有效地址的计算方式: Imm(Eb,Ei,s) = Imm + R[Eb] + R[Ei]*s
不能从内存地址直接MOV到另一个内存地址,要用寄存器中转一下,因此需要两个指令。
MOV:将源操作数的值复制到目的操作数中; MOVS:将一个较小的源数据复制到一个较大的数据位置,高位用位扩展; MOVZ:将一个较小的源数据复制到一个较大的数据位置,高位用零扩展。 push:把数据压入栈中; pop:删除数据。
栈顶元素的地址是所有栈中元素地址中最低的,栈指针%esp保存着栈顶元素的地址。
指针是地址。
局部变量通常是保存在寄存器中,而不是存储器中。寄存器访问比存储器访问要快得多。
第一类:目的操作数必须是一个寄存器 第二类:一元操作。操作数既是源又是目的。可以是寄存器也可以是存储器。 第三类:二元操作。第二个操作数既是源又是目的。但两个操作数不能同时是存储器。 第四类:移位操作。位移量是一个立即数或放在单字节寄存器%cl中。移位操作的目的操作数可以是一个寄存器或是一个存储器位置。
机器代码提供两种基本的低级机制来实现有条件的行为:测试数据值,然后根据测试的结果来改变控制流或者数据流。
控制部分运用分支、循环语句实现。
最核心的是跳转语句、有条件跳转、无条件跳转。
重点关注:CF、ZF、SF、OF
leal指令不改变任何条件码,因为是用来进行地址计算的。
>>有两类指令只设置条件码而不改变任何其他寄存器。
>>CMP指令根据他们的两个操作数之差来设置条件码。除了至设置条件码而不更新目标寄存器之外。CMP与SUB行为是一样的。
SET命令的描述都适用的情况是:执行比较指令,根据计算t=a-b设置条件码。
当t=a-b中,当a-b<0时,CMP指令会设置仅为标志,因此无符号比较使用的是进位标志和零标志的组合。
跳转指令会导致执行切换到程序中一个全新的位置。这些跳转的目的地通常用一个标号指明。
跳转语句主要有有条件跳转(if,switch,while,for)和无条件跳转jmp(实现goto)。
主要查看书本包括例子:
p130/p131: if-else p132/p133: do-while p134/p135: while p137/p138: for p144/p145: switch
七、过程
IA32通过栈帧来实现过程调用。数据传递、局部变量的分配和释放通过操纵程序栈来实现。
机器用栈来传递过程参数、存储返回信息、保存寄存器用于以后恢复以及本地存储。
最顶端的栈帧以两个指针界定,寄存器%ebp为帧指针,而寄存器%esp为栈指针。
call指令有一个目标,即指明被调用过程起始的指令地址。同跳转一样,调用可以是直接的,也可以是间接地。直接调用的目标是一个标号,而间接调用的目标是*后面跟一个操作数指示符。
call指令的效果是将返回地址入栈,并跳转到被调用过程的起始处。返回地址是在程序中紧跟在call后面的那条指令的地址。
ret指令从栈中弹出地址,并跳转到这个位置。栈指针要指向前面call指令存储返回地址的位置。
返回值存在%eax中。
1、启动GDB:
gdb xxx
2、补充知识点:
<<<<查看函数调用栈信息的GDB命令 ①backtrace/bt n n是一个正整数,表示只打印栈顶上n层的栈信息。 -n表一个负整数,表示只打印栈底下n层的栈信息。 ②frame n n是一个从0开始的整数,是栈中的层编号。比如:frame 0,表示栈顶,frame 1,表示栈的第二层。 这个指令的意思是移动到n指定的栈帧中去,并打印选中的栈的信息。若不输入数字则默认打印当前帧的信息。
③up n 表示向栈的上面移动n层,若不输入数字则默认向上移动一层。
④down n 表示向栈的下面移动n层,若不输入数字则默认向下移动一层。
起先做实验练习的时候,在编译week4.c时输入的编译代码是:
gcc -O1 -S week4.c
导致编译所得的汇编代码很奇怪,与应该有的代码差之甚远,如图:
将其删除为纯汇编代码后非常简短,无法直观看出栈帧使用情况。原因是老师给出的编译代码、书上的编译代码没有将功能区分清楚,才导致了这样的情况。
原本以为是系统环境的差别导致的,但是在MAC OS中编译也是十分简短,该有的代码没有出现。后来仔细尝试和再次查阅实验指导材料才发现是没有在64位环境下将代码转换为32位的汇编代码。
或者使用cat xxx.s查看汇编文件效果一致。
⑤修改汇编代码并另存为week4_final_.s:
代码操作完成。
虚拟机Ubuntu上操作截图:
纯汇编代码:
堆栈使用情况:
十一、参考文献
1、《深入理解计算机系统》pdf
2、ARM知识拓展博客:http://blog.chinaunix.net/uid-25067956-id-398205.html
3、实验楼实验指导书:https://www.shiyanlou.com/courses/413 实验四