zoukankan      html  css  js  c++  java
  • 程序的机器级表示

    P104,p105:

        X86,经历了一个长期的的不断发展的过程。开始时他是第一代单芯片和16位微处理器之一,由于当时集成电路技术水平十分有限,其中做了很多妥协。此后,他不断地成长,利用进步的技术满足更高性能和支持更高级的操作系统的需求。

       8086(1978)

       80286(1982)

       i386(1985)

       i486(1989)

       Pentium(1993)

       PentiumPro(1995)

       Pentium II(1997)

       Pentium III(1999)

       Pentium 4(2000)

       Pentium 4E(2004)

       Core 2(2006)

       Core i7(2008)

    X86寻址方式经历三代:

                1. DOS时代的平坦模式,不区分用户空间和内核空间,很不安全

                2. 8086的分段模式

                3. IA32的带保护模式的平坦模式

    摩尔定律:

                 随着Intel微处理器复杂性的复杂度提高,晶体管的数量不断增长。

    P106:

        ISA的定义:

              计算级程序的格式和行为,定义为指令集体系结构,它定义了处理器状态指令的格式,以及每条指令对状态的影响。

        一些对C语言程序员隐藏的处理器的状态是可见的:

                  程序计数器:

                         通常称为PC,指示将要执行的下一条指令在存储器的地址。

                  整数寄存器:

                         文件包含8个命名的位置,分别存储32位的值。

                  条件码寄存器: 

                         保存最近执行的算术或者逻辑的状态和信息。它们用来实现控制或者数据流中的条件变化。

                  一组浮点寄存器:

                          用来存放浮点数据。

    P107:

         在命令行使用-s选项,就能得到C语言编译器产生的汇编代码:

                 unix>gcc -01 -s code.c

         如果我们使用-c命令行选项,GCC会编译并汇编该代码:

                unix>gcc -01 -c code.c

    gcc -S xxx.c -o xxx.s 获得汇编代码,也可以用objdump -d xxx 反汇编。

    注意:

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

    P108:

     如何找到程序的字节表示:

                 在文件code.o上运行GNU调试工具GDB输入命令:

                            (gdb)  x/17xb  sum

                查看目标代码文件的内容输入命令:

                            unix>objdump -d code.o

                  生成可执行的文件prog:

                            unix>gcc -01 -o prog code.o main.c

                 反汇编文件prog:

                           unix>objdump -d prog

     进制文件可以用od 命令查看,也可以用gdb的x命令查看。

     有些输出内容过多,我们可以使用 more或less命令结合管道查看,也可以使用输出重定向来查看

                od code.o | more
                od code.o > code.txt
    P109:
    gcc -S 产生的汇编中以“.”开始的语句都删除
    所有是我以“.”开头的行都是知道汇编器和链接器的命令。
    我们通常可以忽略这些行。另一方面,没有关于这些指令的用途以及它们与源代码之间的关系的解释说明。

    P110:

    了解Linux和Windows的汇编格式有点区别:ATT格式和Intel格式

              GCC采用的是AT&T的汇编格式, 也叫GAS格式(Gnu ASembler GNU汇编器), 而微软采用Intel的汇编格式. 
                   一 基本语法 
                   语法上主要有以下几个不同. 
                   1、寄存器命名原则

    
    
    AT&T Intel 说明
    %eax eax Intel的不带百分号
    
    


                   2、源/目的操作数顺序

    
    
    AT&T Intel 说明
    movl %eax, %ebx mov ebx, eax Intel的目的操作数在前,源操作数在后
    
    


                   3、常数/立即数的格式

    
    
    AT&T Intel 说明
    movl $_value,%ebx mov eax,_value Intel的立即数前面不带$符号
    movl $0xd00d,%ebx mov ebx,0xd00d 规则同样适用于16进制的立即数
    
    


                  4、操作数长度标识

    
    
    AT&T Intel 说明
    movw %ax,%bx mov bx,ax Intel的汇编中, 操作数的长度并不通过指令符号来标识
    
    

    在AT&T的格式中, 每个操作都有一个字符后缀, 表明操作数的大小. 例如:mov指令有三种形式:

    movb  传送字节

    movw  传送字

    movl   传送双字

    因为在许多机器上, 32位数都称为长字(long word), 这是沿用以16位字为标准的时代的历史习惯造成的.
    果没有指定操作数长度的话,编译器将按照目标操作数的长度来设置。比如指令“mov %ax, %bx”,由于目标操作数bx的长度为word,那么编译器将把此指令等同于“movw %ax, %bx”。同样道理,指令“mov $4, %ebx”等同于指令“movl $4, %ebx”,“push %al”等同于“pushb %al”。对于没有指定操作数长度,但编译器又无法猜测的指令,编译器将会报错,比如指令“push $4”。

    
    


                     5、寻址方式

    
    
    AT&T Intel
    imm32(basepointer,indexpointer,indexscale) [basepointer + indexpointer*indexscale + imm32)
    
    

                      两种寻址的实际结果都应该是

    
    

                      imm32 + basepointer + indexpointer*indexscale

    P111:
    表中不同数据的汇编代码后缀




    P112:

       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位的不会发生溢出.

    P113:
    操作数指令符:
    立即数:
    在ATT格式的汇编代码中,立即数的书写方式$后面跟一个用标准C表示法表示的整数。任何能放进一个32位的字里的数值都可以用作立即数。
    寄存器:
    它表示某个寄存器的内容,对双操作数来说,可以是8个32位寄存器中的一个。
    对于字操作数来说,可以是8个16位寄存器中的一个。
    我们用Ea来表示任意的寄存器a,用引用R【Ea】来表示它的值
    存储器:
    它会根据计算出来的地址访问某个存储器的位置。

    有效地址的计算方式 Imm(Eb,Ei,s) = Imm + R[Eb] + R[Ei]*s

    P114:

        MOV相当于C语言的赋值”=“

        注意ATT格式中的方向

     另外注意不能从内存地址直接MOV到另一个内存地址,要用寄存器中转一下。
    MOV:
    传送
    MOVS:
    传送符号扩展字节
    MOVZ:
    传送零扩展的字节

    掌握push,pop:
    (1)进栈指令push
    push reg/mem/seg;sp<-sp-2,ss<-reg/mem/seg
    进栈指令先使堆栈指令sp减2,然后把一个字操作数存入堆栈顶部。堆栈操作的对象只能是字操作数,进栈时底字节存放于低地址,高字节存放于高地址,sp相应向低地址移动两个字节单元。
    push AX
    PUSH [2000H]
    PUSH CS
    (2)、出栈指令pop
    pop reg/seg/mem;reg/seg/mem<-ss:[sp],sp<-sp+2
    出栈指令把栈顶的一个字传送至指定的目的操作数,然后堆栈指针sp加2。目的操作数应为字操作数,字从栈顶弹出时,低地址字节送低字节,高地址字节送高字节。
    pop AX
    POP [2000H]
    POP SS堆栈可以用来临时存放数据,以便随时恢复它们。也常用于子程序见传递参数。
    
    

    p115/p116:

     push :

     ax是把ax里的值压入堆栈。即当前esp-4出的值变为ax的值,ax本身的值不变。

     pop :
     dx是把当esp指向的栈中的值(即之前push ax进栈的ax的值)赋给dx
     并且esp+4(dx的值改变,esp在pop之前指向的地方的值不变,还是之前ax进栈后的值,即堆栈里的那个值不会自动清零)

     注意栈顶元素的地址是所有栈中元素地址中最低的。

    
    

    p117:

          指针就是地址;局部变量保存在寄存器中。

          C语言中所谓的“指针”其实就是地址。

          间接地引用指针就是将该指针放在一个寄存器中,然后在存储器中引用中使用这个寄存器。

          其次,像X这样的局部变量通常是保存在寄存器中,而不是存储器中。寄存器访问比存储器访问要快的多。

    
    

    p119:

         结合表理解一下算术和逻辑运算

         注意目的操作数都是什么类型:

                   加载有效地址:

                                指令leal实际上是movl指令的变形。它的指令形式是从存储器读数据到寄存器,但实际上它根本就没有引用存储器。

                   一元操作:

                                它只有一个操作数,既是源又是目的。

                   二元操作:

                                既是源又是目的。

                   移位:

                               先给出移位量,然后第二项给出的是要移位的位数。

      特别注意:

                 1. 减法是谁减去谁

                 2.移位操作移位量可以是立即数或%cl中的数

    
    

    p123:

     条件码:

           CF:进位标志

           ZF:零标志

           SF:符号标志

           OF:溢出标志

           CF:(unsigned)t < (unsiged)a  无符号溢出

           ZF:  (t==0) 零

           SF:   (t < 0)  负数

           OF:(a <0 == b< 0)&& (t < 0 != a < 0) 有符号溢出

       控制中最核心的是跳转语句:有条件跳转

    
    

    p124:

    有条件跳转的条件看状态寄存器(教材上叫条件码寄存器)

    注意leal不改变条件码寄存器

    思考一下:CMP和SUB用在什么地方

         CMP指令根据它们的两个操作数之差来设置条件码

         除了只设置条件码而不更新目标寄存器之外,CMP指令与SUB指令的行为是一样的。

    p125:

         条件码通常不会直接读取,常用的方法有三种:

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

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

                  3.可以有条件的传送数据

    setl和setb:

                 表示”小于时设置“和”低于时设置“

    SET指令根据t=a-b的结果设置条件码

    
    

    p127:

           正常情况下执行下,指令按照它们出现的顺序一条一条的执行。

           跳转指令会导致执行切换到程序中的一个全新的位置。

           这些跳转的目的通常用一个标号指明。

    p128:

          (实现if,switch,while,for),

           无条件跳转jmp(实现goto)

    
    

    p130/p131:

           if-else 的汇编结构:

                  t=test-expr;

                  if(!t)

                       goto false;

                  then-statement

                  goto done;

           false:

                else-statement

           done:

     汇编器为then-statement和else-statement产生各自的代码块。

     它会插入条件和无条件分支,以保证执行正确的代码块。

    
    

    p132/p133:

          do-while:

               loop:

                  body-statement

                 t=test-expr;

                  if(t)

                      goto loop;

    也就是说,每次循环,程序会执行循环体里的语句,然后执行测试表达式。

    如果测试为真,则回去再执行一个循环。

    
    

    p134/p135:

         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:

    
    

    p137/p138:

             for:

                init-expr;

                     if(!test-expr)

                           goto done;

               do{

                     body-statement

                    update-expr;

                 }while(test-expr);

              done:

    
    

    p144/p145:

               switch:

                      

    
    

    p149:

             IA32通过栈来实现过程调用。掌握栈帧结构,注意函数参数的压栈顺序.

    
    

    p150/p151:

            转移控制:

              call 指令有一个指令目标,即指明呗调用过程起始的指令地址。同跳转一样,调用可以是直接的,也可以是间接的。

              call指令的效果是将返回的地址入栈,并跳转到被调用过程的起始处

               call/ret; 函数返回值存在%eax中

    
    

    p174:

             bt/frame/up/down :关于栈帧的gdb命令

     

     
    
    
    

     

             

  • 相关阅读:
    redis安装以及php扩展
    Linux下php安装Redis扩展
    正则验证邮箱
    常用方法
    PHPExcel说明
    冒泡排序
    CURL post请求
    PHP生成随机字符串
    PHP中的字符串函数
    PHP中的数组函数
  • 原文地址:https://www.cnblogs.com/20135314ZHU/p/4864530.html
Copyright © 2011-2022 走看看