程序的机器级表示

程序的机器级表示

程序编码

    ### 机器级代码

        机器代码==汇编代码

        计算机系统总存在大量抽象,屏蔽了实现的细节

        - 指令集架构ISA 定义机器语言的格式和行为 具体实现是并发的

        - 虚拟内存地址 内存模型为一个大字节数组

        一些对c隐藏的寄存器在汇编可见

        - 程序计数器 PC

        - 整数寄存器 16个

        - 条件寄存器

        - 一组向量寄存器

        汇编代码不区分有无符号、指针类型 甚至不区分指针与整数

    ### 寄存器

        %r 64位

        %e 32位 (为%r的低32位)

        寄存器通过名字存取

        汇编会丢失变量名等执行 无关的信息,反汇编时不能恢复

         指令可以对16个寄存器的低位字节中存放的不同大小的数据进行操作

        - 1字节、2字节指令会保持剩下的字节不变

        - 4字节指令会把高4字节置0

        - 以立即数位目的无意义,不允许直接从内存复制到内存(mem→mem)

        - Imm(rb,ri,s) == Imm+R[rb]+R[ri]*s

        - 指针就是地址

        - 局部变量存在寄存器中,如果寄存器数量不足,则局部变量被存储在称栈段的内存中。

        - 类型转换时

            - 大转小:读取%rax低字节

            - 小转大:零扩展、符号扩展

            - 不存在movzlq,可以直接使用movl。因为当使用movl传输数据到32位目的寄存器中时, 会自动将目的寄存器的高位4字节置零。

            - cltq没有操作数,总是以%eax作为源寄存器,以%rax作为目的寄存器,等价于movslq %eax,%rax

        - 一些惯例

            %rsp 指向栈顶

            %rax 为返回值

            位移运算只能用%cl地位 即完成ch2中的取模

    ### 加载有效地址 lea

        load effect addr

        lea是计算内存地址,然后把内存地址本身放进寄存器里。

        因为计算地址是Imm(rb,ri,s) == Imm+R[rb]+R[ri]*s 的形式

        所以c编译器喜欢用于加速计算

             e.g.计算2的幂次 x+n*x

    - 目的操作数只能是寄存器。

    ## 控制

        ### 条件码

            CF 进位(无符号) ZF 零 SF 符号 OF 溢出(有符号)

            leaq不改变条件码,因为是用于进行地址计算的

            访问条件码

            1. set 根据条件码的组合,将某个字节设置 为0或1

            2. 条件跳转到程序的某个其他部分

            3. 有条件地传输数据

        ### 跳转

            jmp 无条件跳转

            jmp *Operand 间接跳转

            跳转指令的编码

            - PC相对地址

            - 绝对地址

        ### 条件分支

            使用条件控制来实现条件分支

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

            - 传统方法:使用控制的条件转移

            - 使用数据的条件转移 cmov* 也叫条件传送

                计算一个条件操作的两种结果,根据条件是否满足再选择一个

                这样可以使得处理器流水线效率最高

                但并不是所有条件表达式都可以用条件传送

                    return (xp? *xp : 0);

        ### 循环

            #### do-while

                jg 是实现循环的关键指令,决定了是继续重复还是退出循环

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

            #### while

                jump-to-middle

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

                guarded-do

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

            #### for

                在while循环的基础上加上初始化表达式和更新表达式

                jump-to-middle

  **init-expr;** 
  goto test;
loop:
  body-statement
 ** update-expr;** 
test:
  t=test-expr;
  if(t)
    goto loop;

                guarded-do

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

        ### switch

            根据整数索引 进行多重分支

            使用跳转表 使得实现更加高效:执行开关语句的时间与开关情况的数量无关

            && 创建一个指向代码位置的指针

过程 Procedure

    机制: P调用过程Q

    - 传递控制

        PC设置为Q的起始地址,返回时PC设置为P中调用Q的下一条指令的地址

    - 传递数据

        P向Q提供参数 Q向P返回一个值

        可以通过寄存器传递最多6个整型参数(整型或指针)

        超出6个的部分通过栈来传递,数据向8的倍数对齐

    - 分配和释放内存

    #### 栈上的局部存储

    必须将数据放在内存中的情况

    - 寄存器不足

    - 使用了地址运算符&

    - 局部变量是数组或结构

    通过减小栈指针在栈上分配空间

    #### 寄存器中的局部存储

    寄存器是唯一被所有过程共享的资源。

    被调用者保存寄存器6个:%rbx、%rbp和%r12—%r15

    其他的 除了%rsp,均为调用者保存寄存器

    #### 递归

    每个过程调用在栈中都会有自己的私有空间,因此多个未完成调用的局部变量不会相互影响。

数组

     一种将标量数据聚集成更大数据类型的方式(同质)

    C语言可以产生指向数组中元素的指针,并对这些指针进行运算

    ### 基本原则

    通过声明,在内存中分配L*N字节的连续区域,并引标识符,作为指向数组开头的指针

    指针数组每个元素都是8字节的指针

    C语言会根据指针引用的数据类型的大小进行伸缩

    ### 嵌套数组

    按照“行优先”的原则在内存中排列

T D[R][C];
元素D[i][j]的内存地址为:&D[i][j]=Xd+L(C*i+j);

    ### 变长&定长

    C99允许数组的维度是表达式,在数组分配时才被计算出来

    - 增加了参数n,寄存器的使用变化了

    - 使用了乘法指令计算n*i,而不是leaq,会带来严重的性能处罚

    如果允许优化,GCC能识别出程序访问多维数组的元素的步长

异质的数据结构

    ### 结构

    将可能不同类型的对象聚合到一个对象中

    编译器维护关于每个结构类型的信息,指示每个字段的字节偏移。

    结构的各个字段的选取完全是在编译时处理的,机器代码不包含关于字段声明或字段名字的信息

    ### 联合体

    规避C语言的类型系统,允许以多种类型来引用一个对象。

    用不同的字段来引用相同的内存块

    ### 数据对齐

    简化了处理器和内存系统之间接口的设计

控制&数据的结合

    - 代码注入

        - 编写更健壮的代码

        - 系统完成

            - ASLR 随机化栈初始位置

            - 将区域标记为不可执行

            - 使用金丝雀canary检测溢出

    - 面向返回编程

浮点代码

posted @ 2021-12-13 22:26  紫羊  阅读(15)  评论(0)    收藏  举报