20145201 《信息安全系统设计基础》第5周学习总结
20145201 《信息安全系统设计基础》第5周学习总结
教材学习内容总结
1、X86(Intel处理器系列) 寻址方式经历三代:
1 DOS时代的平坦模式,不区分用户空间和内核空间,很不安全
2 8086的分段模式
3 IA32的带保护模式的平坦模式
2、对于机器级编程的两种重要抽象
1) 机器级程序的格式和行为,定义为指令集体系结构(ISA):
定义了处理器状态,指令的格式,以及每条指令对状态的影响。将程序的行为描述成好像每条指令是按顺序执行的。
2)机器级程序使用的存储器地址是虚拟地址,存储器系统的实际实现是将多个硬件存储器和操作系统软件组合起来。
一些处理器状态:
①程序计数器PC:指示将要执行的下一条指令在存储器中的地址。
②整数寄存器,存储数据;条件码寄存器,保存逻辑指令状态信息;等
3、gcc -S xxx.c 使GCC运行编译器,可以得到C语言编译器产生的汇编代码,但不会做进一步工作;使用‘-c’命令,GCC就会编译并汇编该代码,会产生二进制文件xxx.o。由此可见,机器执行的实际上是对一系列指令进行编码的字节序列。
也可以用objdump -d xxx.o 反汇编; 注意函数前两条(push,mov )和后两条(pop,ret)汇编代码,所有函数都有,建立函数调用栈帧。
注意: 64位机器上想要得到32代码:gcc -m32 -S xxx.c
MAC OS中没有objdump, 有个基本等价的命令otool
Ubuntu中 gcc -S code.c (不带-O1) 产生的代码更接近教材中代码(删除"."开头的语句)
4、二进制文件可以用od 命令查看,也可以用gdb的x命令查看。有些输出内容过多,我们可以使用 more或less命令结合管道查看,也可以使用输出重定向来查看:
od code.o | more
od code.o > code.txt
5、gcc -S 产生的汇编代码中可以把 以”.“开始的语句都删除了再阅读(这些是指导汇编器和链接器的命令,可以忽略)
6、Linux和Windows的汇编格式的区别:ATT格式和Intel格式
1) Intel代码省略了指示大小的后缀
2)Intel代码省略了寄存器名字前面的‘%’符号
3)Intel代码用不同的方式来描述存储器中位置
4) 在带有多个操作数的指令情况下,列出操作数的顺序相反
7、不同数据的汇编代码后缀
8、一个 IA32中央处理单元CPU包含一组8个存储32位值的寄存器,用来存储整数数据和指针,名字都以%e开头。
esi edi可以用来操纵数组,esp ebp用来操纵栈帧。
多数情况下,前6个寄存器为通用寄存器,最后两个寄存器保存着指向程序栈中重要位置的指针。
另外,字节操作指令可以独立的读或者写前4个寄存器的2个低位字节。
对于寄存器,特别是通用寄存器中的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位的不会发生溢出.
9、操作数的三种类型
立即数:常数值
寄存器:表示某个寄存器的内容
存储器:根据计算出来的地址(通常称为有效地址)访问某个存储器位置
10、寻址模式
11、数据传送指令:将数据从一个位置复制到另一个位置的指令
MOV相当于C语言的赋值"=",注意ATT格式中的方向,注意不能从内存地址直接MOV到另一个内存地址,要用寄存器中转一下。MOVS和MOVZ指令类都是将一个较小的源数据复制到一个较大的数据位置,高位用符号位扩展(MOVS)或者零扩展(MOVZ)进行填充。
最后两个数据传送操作可以将数据压入程序栈中,以及从程序栈中弹出数据。
12、栈
1) 遵循“后进先出”的原则
2) push压栈 把数据压入栈中,pop出栈 删除数据
3) 栈顶:总是从这端插入和删除元素
4) 栈顶元素的地址是最低的
5) 栈指针%esp保存着栈顶元素的地址
13、指针
1) C语言中所谓的指针就是地址
2) 间接引用指针是将该指针放在寄存器中然后在存储器引用中使用这个寄存器
3) 局部变量通常保存在寄存器中。寄存器访问要比存储器快很多。
14、算术和逻辑操作
四组操作:
①加载有效地址:实际是将有效地址写入目的操作数,目的操作数必须是寄存器。
②一元操作:只有一个操作数,可以是寄存器也可是存储器位置。
③二元操作:源操作数是第一个,可以是立即数、寄存器、存储器
目的操作数是第二个,可以是寄存器、存储器
两个不能同时为存储器。
④移位操作:第一个是移位量,用单个字节编码(只允许0-31位的移位),可以是立即数或者放在单字节寄存器%cl中
算术右移SAR,填上符号位;逻辑右移SHR,填上0。
目的操作数可以是一个寄存器或存储器。
15、控制
控制中最核心的是跳转语句:
有条件跳转(实现if,switch,while,for)
无条件跳转jmp(实现goto)
1)条件码寄存器
描述了最近的算术或逻辑操作的属性,可以检测这些寄存器来执行条件分支指令
常用条件码:CF ZF SF OF
CF:进位标志
ZF:零标志
SF:符号标志
OF:溢出标志
注意leal不改变任何条件码
2)访问条件码
① 根据条件码的某个组合,将一个字节设置为0或1。SET指令根据t=a-b的结果设置条件码
② 可以条件跳转到程序的某个其他部分
③ 可以有条件的传送数据
3)跳转指令
会导致执行切换到程序中一个全新的位置,跳转的目的地通常用一个标号指明。
控制中最核心的是跳转语句:有条件跳转(实现if,switch,while,for),无条件跳转jmp(实现goto)
无条件跳转:JMP 可以是直接跳转也可以是间接跳转(写法是*后面加操作数指示符)
有条件跳转:根据条件码的某个组合,或者跳转或者继续执行下一条指令。
16、if-else汇编结构
通用形式会在两个分支语句中选择执行一个,汇编实现通过goto,就是汇编器为两个分支产生各自的代码块,它会插入条件和无条件分支,以保证能执行正确的代码块。
17、循环结构
p132/p133: do-while
p134/p135: while
p137/p138: for
汇编中用条件测试和跳转组合实现循环的效果。大多数汇编器根据do-while形式来产生循环代码,其他的循环会首先转换成do-while形式,然后再编译成机器代码。
18、switch语句
p144/p145: switch
根据一个整数索引值进行多重分支。通过使用跳转表这种数据结构实现更加高效。跳转表是一个数组,表项i是一个代码段的地址,这个代码段实现当开关索引值为i时程序该做的。
这里的跳转可以用到goto/jmp
19、过程
一个过程调用包括将数据和控制从代码的一部分传递到另一部分,需要在进入时为过程的局部变量分配空间并在退出时释放空间,这通过程序栈实现。
1)IA32通过程序栈来实现过程调用。栈可以用来:
①传递过程参数
②存储返回信息
③保存寄存器
④本地存储
栈帧:为单个过程分配的那部分栈。
最顶端的栈帧以两个指针界定,寄存器%ebp为帧指针,寄存器%esp为栈指针。程序执行时,栈指针可以移动,大多数信息的访问都是相对于帧指针的。
栈向低地址方向增长。
20、call指令有一个目标,即指明被调用过程起始的指令地址,效果是将返回地址入栈,并跳转到被调用过程的起始处。
ret指令从栈中弹出地址,并跳转到这个位置,使用这个指令栈指针要指向call指令存储返回地址的位置。
函数返回值存在%eax中
21、bt/frame/up/down :关于栈帧的gdb命令
实验练习
-
使用gcc –S –o lzx.s lzx.c -m32编译
-
编译之后打开.s找到汇编文件
代码如下:
.file "lzx.c"
.text
.globl add
.type add, @function
add:
.LFB0:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
movl 8(%ebp), %eax
addl $3, %eax
popl %ebp
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE0:
.size add, .-add
.globl call
.type call, @function
call:
.LFB1:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
subl $4, %esp
movl 8(%ebp), %eax
movl %eax, (%esp)
call add
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE1:
.size call, .-call
.globl main
.type main, @function
main:
.LFB2:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
subl $4, %esp
movl $8, (%esp)
call call
addl $1, %eax
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE2:
.size main, .-main
.ident "GCC: (Ubuntu 4.8.4-2ubuntu1~14.04) 4.8.4"
.section .note.GNU-stack,"",@progbits
整理汇编文件:删除gcc产生代码中以"."开头的编译器指令后得到的汇编代码:
add:
pushl %ebp ;将%ebp入栈,为帧指针
movl %esp, %ebp ;建立空帧栈
movl 8(%ebp), %eax ;空出地址存变量
addl $3, %eax;ax中值加3
popl %ebp;%ebp出栈
ret
call:
pushl %ebp;将%ebp入栈,为帧指针
movl %esp, %ebp;为call建立空帧栈
subl $4, %esp
movl 8(%ebp), %eax
movl %eax, (%esp)
call add;调用add函数
leave ;为返回准备栈,相当于%ebp出栈
ret
main:
pushl %ebp;将%ebp入栈,为帧指针
movl %esp, %ebp;为main建立空帧栈
subl $4, %esp
movl $8, (%esp)
call call;调用call函数
addl $1, %eax;值加1
leave
ret
相应帧栈图:
教材学习中的问题和解决过程
p131 3.16
为什么C语言只有一个条件语句;而汇编中有两个分支呢?
第一个条件分支是&&表达式实现的一部分;如果对p非空的测试失败,代码会跳过对a的测试
3.30
call next
next:
popl %eax
为什么说它是将程序计数器放到整数寄存器中的唯一办法。还是不太理解这段代码。
部分问题根据答案反推是会的,直接做有些犯难。
本周代码托管截图
其他(感悟、思考等,可选)
这周学习的的一些内容都是在上学期的汇编课上学过的,这本书讲的更为深入了,但有学内容还是要静下心学习的。将这次所学和上学期的内容进行对比学习,用新的视角去看知识和问题,发现学习同一个知识还是可以从很多方面去理解。
学习进度条
代码行数(新增/累积) | 博客量(新增/累积) | 学习时间(新增/累积) | 重要成长 | |
---|---|---|---|---|
目标 | 5000行 | 30篇 | 400小时 | |
第一周 | 100/100 | 2/2 | 25/25 | 安装了虚拟机并学习掌握核心的linux命令 |
第二周 | 100/200 | 1/3 | 30/55 | 虚拟机上的C语言编程 |
第三周 | 150/350 | 1/4 | 10/65 | 计算机中信息的表示和运算 |
第四周 | 0/350 | 0/4 | 3/68 | 复习前几周内容 |
第五周 | 75/420 | 1/5 | 20/88 | 程序的机器级表示 |