信息安全系统设计基础第四周学习总结

#第三章 程序的机器级表示

1. 寻址方式历史

(1)DOS时代的平坦模式

不区分用户空间和内核空间,很不安全html

(2)8086的分段模式

(3)IA32的带保护模式的平坦模式

2. 程序编码

(1)编译

gcc -01 -o p p1.c

-01:表示使用第一级优化。一般提升优化级别会使最终程序运行得更快,可是编译时间可能会变长,用调试工具对代码进行调试会更困难。(实际中,第二级优化-02被认为是较好的选择)程序员

gcc命令调用一系列程序将源代码转换成可执行代码:
    - C预处理器 扩展源代码,插入#include命令指定的文件,扩展#define声明指定的宏。(.i)
    - 编译器 产生两个源代码的汇编代码。(.s)
    - 汇编器 将汇编代码转化成二进制目标代码(.o)
    - 链接器 将两个目标代码与实现库函数的代码合并,并产生最终的可执行代码文件。

(2)两种抽象

参考资料3:体系结构-指令集结构编程

计算机系统使用了多种不一样形式的抽象,利用更简单的抽象模型来隐藏实现的细节。对于机器级编程来讲,两种抽象尤其重要:数组

  • 机器级程序的格式和行为,定义为 ISA :指令集体系结构。安全

    ISA,定义了处理器状态,指令格式,以及每条指令对状态的影响。
      处理器的硬件并发的执行许多指令,可是能够采起措施保证总体行为与ISA指定的顺序整形彻底一致。
    
      解决的问题:
          指令的编码方式(即如何编码)
          操做数和操做结构的存放位置
          数据的类型和大小
          支持哪些操做
          下一条指令的地址
  • 机器级程序使用的存储器地址是虚拟地址,提供的存储器模型看上去是一个很是大的字节数组。数据结构

(3)IA32机器代码

一些一般对C语言程序员隐藏的机器代码在IA32中是可见的:并发

  • 程序计数器(在IA32中,一般称为“PC”,用%eip表示)

指示将要执行的下一条指令在存储器中的地址。less

  • 整数寄存器

包含8个命名的位置,分别存储32位的数值,这些寄存器能够存储地址(对应C语言的指针)或整数数据,有的寄存器被用来记录某些重要的程序状态,其余的寄存器用来保存临时数据,例如过程的局部变量和函数的返回值。函数

  • 条码寄存器

保存着最近执行的算术或逻辑指令的状态信息,他们用来实现控制或数据流中的条件变化。工具

  • 浮点寄存器

一组浮点寄存器存放浮点数据

一条机器指令只执行一个很是基本的操做

(4)代码示例

参考资料4:函数调用过程栈帧变化详解

教材代码:

  • 得到汇编代码:

    gcc -S xxx.c -o xxx.s
  • 反汇编:

    objdump -d xxx

  • 汇编代码(函数前两条和后两条汇编代码,全部函数都有,创建函数调用栈帧):

    前两条:
          pushl %ebp          将寄存器%ebp的内容压入程序栈
          movl %esp,%ebp      获得新栈低,将当前栈顶赋予栈低
      后两条:
          popl %ebp            过程调用结束,恢复旧栈低
          ret                 子程序的返回指令

注意

  • 64位机器上想要获得32代码:gcc -m32 -S xxx.c
  • MAC OS中没有objdump, 有基本等价的命令otool
  • Ubuntu中 gcc -S code.c (不带-O1) 产生的代码更接近教材中代码(删除"."开头的语句)
  • 实验楼中:

  • 二进制文件能够用od 命令查看,也能够用gdb的x命令查看。
    有些输出内容过多,可使用 more或less命令结合管道查看,也可使用输出重定向来查看。

    od code.o | more
      od code.o > code.txt

(5)机器代码和它的反汇编表示的一些特性

  • IA32指令长度从1到15个字节不等
  • 设计指令格式的方式是,从某个给定位置开始,能够将字节惟一的解码成机器指令
  • 反汇编器只是基于机器代码文件中的字节序列来肯定汇编代码,不须要访问程序的源代码或汇编代码
  • 反汇编器使用的指令命名规则与GCC生成的汇编代码使用的有些差异

3. 数据格式

(1)Intel(由16位体系扩展为32位)

字(word)              16位
双字(double words)    32位
四字(quad words)      64位

(2)C语言数据类型在IA32中的大小

  • IA32不支持64位整数运算
  • 大多数GCC生成的汇编代码指令都有一个字符后缀,代表操做数的大小。

4. 访问信息

(1)IA32的整数寄存器

一个IA32的中央处理器单元包含一组8个存储32位数值的寄存器。全部八个寄存器均可以做为16位(字)或32位(双字)来访问。

  • %esi,%edi能够用来操纵数组
  • %esp,%ebp用来操纵栈帧。
  • 能够独立访问前四个寄存器的两个低位字节(后向兼容)。
  • 32位的%eax,16位的%ax,8位的%ah,%al都是独立的

(2)操做数指示符

  • 操做数三种类型
  • 当即数,即常数值
  • 寄存器,表示某个寄存器的内容
  • 存储器,根据计算出来的地址(有效地址)访问某个存储器位置。

  • 操做数格式:

  • 有效地址的计算方式

    Imm(Eb,Ei,s) = Imm + R[Eb] + R[Ei]*s

(3)数据传送指令

参考资料5:汇编(五)——数据传送指令一
参考资料6:汇编(八)——数据传送类指令三

符号                      意义
r8          任意一个8位通用寄存器AH/AL/BH/BL/CH/CL/DH/DL
r16         任意一个16通用寄存器AX/BX/CX/DX/SI/DI/BP/SP
reg         表明r8或r16
seg         段寄存器CS/DS/ES/SS
m8          一个8位存储器操做数单元(包括全部主存寻址方式)
m16         一个16位存储器操做数单元(包括全部主存寻址方式)
mem         表明m8或m16
i8          一个8位当即数
i16         一个16位当即数
imm         表明i8或i16
dest        目的操做数
src         源操做数
  • mov指令(指令的第一个是源操做数,第二个是目的操做数)

  • MOV reg/mem,imm     当即数送寄存器或是存储器
         MOV reg/mem/seg,reg 寄存器送寄存器(包括段寄存器)或贮存
         MOV reg/seg,mem     主存送寄存器(包括段寄存器)
         MOV reg/mem,seg     段寄存器送主存或寄存器
  • IA32的限制:两个操做数都不能指向存储器。

  • 不能从内存地址直接MOV到另外一个内存地址,要用寄存器中转一下。

  • push与pop

    • 先进后出:push将数据压入栈中,pop弹出,弹出的永远是最近被压入的。用数组实现栈,进行操做的一端为栈顶。
    • 栈向下增加,栈顶元素的地址是全部栈中元素地址中最低的。栈指针%esp保存栈顶元素的地址。

      进栈指令PUSH:
               PUSH reg/mem/seg       SP←SP-2,SS←reg/mem/seg
          - 进栈指令先使堆栈指令SP减2,而后把一个字操做数存入堆栈顶部。
          - 堆栈操做的对象只能是字操做数。
          - 进栈时,底字节存放于低地址,高字节存放在高地址,SP相应向低地址移动两个字节单元。
      
          出栈指令POP:
              POP reg/seg/mem         reg/seg/mem←SS:[SP],SP←SP+2
          - 出栈指令把栈顶的一个字传送至指定的目的操做数,而后堆栈指针SP加2。
          - 目的操做数应为字操做数。
          - 字从栈顶弹出时,低地址字节送低字节,高地址字节送高字节。

(4)数据传送与C语言

  • C语言中“指针”其实就是地址。间接引用指针就是将该指针放在一个寄存器中,而后在存储器引用中使用这个寄存器。
  • 局部变量一般是保存在寄存器中,而不是存储器中。寄存器访问比存储器访问要快得多。

5. 算术和逻辑操做

(1)加载有效地址指令

是movl指令的变形

  • 指令形式:从存储器读取到寄存器
  • 其实是将有效地址写入目的操做数。
  • leal指令一般用来执行简单的算术操做
  • 整数算术操做:

(2)一元操做和二元操做

  • 一元操做

    - INC       加1
      - DEC       减1
      - NEG       取负
      - NOT       取补
  • 只有一个操做数,既是源又是目的,能够是一个寄存器,或者存储器位置。
  • 二元操做

    - ADD       加
      - SUB       减
      - IMUL      乘
      - XOR       异或
      - OR        或
      - AND       与
  • 第一个操做数能够是当即数、寄存器或者存储器位置
  • 第二个操做数既是源也是又是目的。能够是寄存器或者存储器位置,可是不能同时是存储器位置。
  • 注意操做的顺序:

    第二个操做数 操做符 第一个操做数

(3)移位操做

  • 先给出移位量,第二项给出要移位的数值。

    - SAL       左移
      - SHL       左移(等同于SAL)
      - SAR       算术右移    
      - SHR       逻辑右移
  • 源操做数(移位量):当即数或者放在单字节寄存器元素%cl中。
  • 目的操做数:一个寄存器或是一个存储器位置。

(4)特殊操做

  • 乘法
  • 乘积截断

    imull   双操做数
         - 从两个32位操做数产生一个32位的乘积。
  • 乘积不截断

    mull    无符号数乘法
         imull   有符号数乘法
         - 要求一个参数必须在寄存器%eax中,另外一个做为指令的源操做数给出。
         - 乘积的高32位在%edx中,低32位在%eax中。
  • 除法
  • 有符号除法

    idivl 操做数
         - 将DX:AX中的64位数做为被除数,操做数中为除数
         - 结果:商在AX中,余数在DX中。
  • 无符号除法

    divl指令
         - 一般会事先设定寄存器%edx为0.

6. 控制

(1)状态寄存器(条件码寄存器)

  • 经常使用条件码:

    • CF:进位标志。最近操做使最高位产生进位。用于检查无符号操做数的溢出。
    • ZF:零标志。最近操做结果为0。
    • SF:符号标志。最近操做获得的结果为负数。
    • OF:溢出标志。最近操做致使一个补码溢出。
  • leal 不改变条件码寄存器
  • 比较和测试指令:不修改任何寄存器的值,只设置条件码

    比较指令cmp和减法指令sub有何不一样?
      - sub d,s   是d-s,结果送回d中,即送回目的操做数中。
      - cmp d,s   也是d-s,但结果不送回目的操做数中,是利用减法进行两个数值的比较。

(2)访问条件码

  • 经常使用的使用方法:
  • 根据条件码的某个组合,将一个字节设置为0或1。

    SET指令:执行比较指令,根据计算t=a-b的结果设置条件码

  • 能够条件跳转到程序的某个其余部分
  • 能够有条件的传送数据

(3)跳转指令

  • 无条件跳转
  • 直接跳转:跳转目标是做为指令的一部分编码的。
  • 间接跳转:跳转目标是从寄存器或存储器位置中读出的。

    例:
             jmp *%eax   用寄存器%eax中的值做为跳转目标。
             jmp *(%eax) 以%eax中的值做为读地址,从存储器中读出跳转目标。
  • 其它跳转指令:
  • 一些底层的机器指令有多个名字,条件跳转只能是直接跳转。

  • 跳转指令编码
  • 最经常使用的是PC相关的,它们会将目标指令的地址与紧跟在跳转指令后面那条指令的地址之间的差做为编码。
  • 第二种编码方法是给出“绝对”地址,用四个字节直接指定目标。
  • 执行与PC相关的寻址时,程序计数器的值是跳转指令后面的那条指令的地址,而不是跳转指令自己的地址。

(4)翻译条件分支

参考资料7:20135202闫佳歆——信息安全系统设计基础第四周学习总结

  • 将条件表达式和语句从c语言翻译成机器语言,最经常使用的方式就是结合有条件和无条件跳转。
  • if-else 的汇编结构
  • 通用形式模板

    if(test-expr)
             then-statement
         else
             else-statement
    
         (注:test-expr    整数表达式[假/真])
  • 汇编实现形式

    t = test-expr;
         if (!t)
             goto false;
         then-statement
         goto done;
         false: 
             else-statement
         done:

(5)循环

  • do-while循环
  • 通用形式:

    do
             body-statement
             while(test-expr);
  • 翻译成以下条件和goto语句:

    loop:
             body-statement
             t = test-expr;
             if(t)
                 goto loop;
  • while循环
  • 通用形式:

    while (test-expr)
             body-statement
  • 转换成 do-while 形式:

    if(!test-expr)
             goto done;
         do
                 body-statement
                 while(test-expr);
         done:
  • 翻译成 goto 形式:

    t = test-expr;
             if(!t)
                 goto done:
         loop:
             body-statement
             t = test-expr;
             if(t)
                 goto loop;
         done:
  • for循环
  • 通用形式

    for(init-expr;test-expr;update-expr)
             body-statement
  • 同 while:

    init-expr;
         while(test-expr){
             body-statement
             update-expr;
         }
  • 对应 do-while 形式:

    init-expr;
         if(!test-expr)
             goto done;
         do{
             body-statement
             update-expr;
         }while(test-expr);
         done;
  • 转换成 goto 代码:

    init-expr
             t = test-expr;
             if(!t)
                 goto done:
         loop:
             body-statement
             t = test-expr;
             if(t)
                 goto loop;
         done:

(6)条件传送指令

(7)switch语句

  • switch语句能够根据一个整数索引值进行多重分支。处理具备多种可能结果的测试时,这种语句特别有用。
  • 优势:提升了代码的可读性;使用跳转表这个数据结构使用实现更加高效。
  • 跳转表:是一个数组,表项i是一个代码段的地址,这个代码段实现当switch索引值等于i时程序应该执行的动做。程序代码用于索引值来执行一个跳转表内的数组引用,肯定跳转指令的目标。和使用一组很长的if-else相比,使用跳转表的优势是执行switch语句的时间与switch的case数量无关。GCC根据switch语句中case的数量和case中值的稀少程序来翻译开关语句。当case数据比较多(例如4个以上),而且值的范围跨度比较小时,就会使用跳转表。
  • 执行switch语句的关键步骤是经过跳转表来访问代码位置。

7. 过程

(1)概述

  • 一个过程调用包括将数据和控制从代码的一部分传递到另外一部分,须要在进入时为过程的局部变量分配空间,并在退出时释放这些空间。
  • 数据传递、局部变量的分配和释放经过操纵程序栈来实现。

(2)栈帧结构

栈帧: 为单个过程分配的那部分栈

  • 最顶端的栈帧以两个指针界定:

    - 寄存器%ebp为帧指针
      - 寄存器%esp为栈指针
  • 程序执行时,栈指针能够移动,大多数信息的访问都是相对于帧指针的。
  • 栈向低地址方向增加,栈指针%esp指向栈顶元素:

    - 栈指针值适当减少能够分配没有指定初始值的数据的空间
      - 相似的,能够经过增长栈指针来释放空间

(3)转移控制

  • 支持过程调用和返回的指令:

  • call指令
  • 目标:指明被调用过程起始的指令地址。
  • 效果:将返回地址入栈,并跳转到被调用过程的起始处。

  • ret指令
  • 从栈中弹出地址,并跳转到这个位置.
  • 使用这个指令时,栈指针要指向前面call指令存储返回地址的位置。

  • leave
  • 这个指令可使栈作好返回的准备
  • 等价于:

    movl %ebp,%esp
         popl %ebp

(4)寄存器使用惯例

  • 程序寄存器组是惟一能被全部过程共享的资源。
  • 为何必须遵照惯例:必须保证一个过程(调用者)在调用另外一个过程(被调用者)时,被调用者不会覆盖某个调用者寄存器中的值。
  • 惯例:

    - %eax,%edx,%ecx        划分为调用者保存寄存器
      - %ebx,%esi,%edi        划分为被调用者保存寄存器
      - %ebp,%esp             保持寄存器
      - %eax                  保存函数返回值

(5)递归过程

  • 当过程被调用时分配局部存储,返回时释放存储。

8. GDB调试器

  • GDB命令:

#做业

  • main.c:

  • 汇编代码:

  • 用vi查看编译器指令:

  • 删除gcc产生代码中以"."开头的编译器指令后:

  • 分析:
  • main函数保存%ebp,并设置新的帧指针。

    pushl   %ebp
         movl    %esp,%ebp
  • 分配4字节的栈空间

    subl    $4,%esp
  • 设置 arg1=8

    movl    $8,(%esp)
  • call调用fh
  • fh被调用,初始化帧指针,分配栈空间。
  • 将(%esp)中的8给 %eax,即存入栈中

    movl    %eax,(%esp)
  • call调用gh
  • gh被调用,初始化栈指针,分配栈空间
  • 将 %eax 与当即数 3 相加

    add     $3,%eax
  • 在gh结束前弹栈

    popl    %ebp
  • ret返回fh中call的调用位置
  • fh也结束,return返回main中call调用的位置
  • main继续 %eax 加1的操做

    addl    $1,%eax
  • leave为返回准备栈,至关于%ebp出栈,最后ret结束。

参考资料


参考资料1:深刻理解计算机系统(第二版)
参考资料2:教材导读与每周考试重点---不断更新
参考资料3:体系结构-指令集结构
参考资料4:函数调用过程栈帧变化详解
参考资料5:汇编(五)——数据传送指令一
参考资料6:汇编(八)——数据传送类指令三
参考资料7:20135202闫佳歆——信息安全系统设计基础第四周学习总结

相关文章
相关标签/搜索