程序的机器级表示

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命令

 

 

 

 

 

         

 

posted on 2015-10-11 10:30  20135314朱恒志  阅读(547)  评论(1编辑  收藏  举报