程序的机器级表示

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.txtP109:  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命令

 

 
				
时间: 2024-10-05 07:56:38

程序的机器级表示的相关文章

深入理解计算机系统之程序的机器级表示部分学习笔记

不论我们是在用C语言还是用JAVA或是其他的语言编程时,我们会被屏蔽了程序的机器级的实现.机器语言不需要被编译,可以直接被CPU执行,其执行速度十分  快.但是机器语言的读写性与移植性较高级语言低.高级语言被编译后便成为了汇编语言,汇编语言十分接近机器语言.之后汇编代码会转化为机器语言.虽然现代  的编译器能帮助我们将高级语言转化为汇编语言,解决了不少问题,但是对于一个严谨的程序员来说,需要做到能够阅读和理解汇编语言.我们主要围绕Intel来讲  解. 一  Intel处理器的历史演变 Inte

第三章 程序的机器级表示

程序的机器级表示 3.1历史观点 8086—〉80286—〉i386—〉i486—〉Pentium—〉PentiumPro—〉Pentium—〉Pentium—〉Pentium4—〉Pentium4e—〉Core 2 Duo —〉Core i7 3.2程序编码 1.gcc -01 –o p p1.c p2.c      使用第一级优化 2.程序计数器(%eip)指示将要执行的下一条指令在存储器中的地址. 3.寄存器文件 4.-S:C语言编译器产生的汇编代码 例:gcc -01 –S code.c

深入理解计算机系统(第二版)----之三:程序的机器级表示

计算机执行机器代码,用字节编码低级的操作,包括处理数据.管理存储器.读写存储设备上的数据,利用网络通信,编译器基于变成语言的原则, 目标机器的指令集合操作系统遵循的原则,经过一系列阶段产生机器代码,gcc c语言编辑器以汇编代码的形式输出,汇编代码是机器代码的文本表示,给出程序的每一条指令.然后gcc调用汇编器和链接器,根据汇编代码生成可执行的机器代码. 本章,近距离观察机器代码和汇编代码. 机器级的实现,被高级语言屏蔽了,用高级语言编写的程序可以在很多不同的机器上编译和执行,而汇编代码则是与特

第三章程序的机器级表示 学习报告

第三章 程序的机器级表示 3.1 历史观点 Intel处理器系列俗称x86,开始时是第一代单芯片.16位微处理器之一. 每个后继处理器的设计都是后向兼容的——较早版本上编译的代码可以在较新的处理器上运行. X86 寻址方式经历三代: 1  DOS时代的平坦模式,不区分用户空间和内核空间,很不安全 2  8086的分段模式 3  IA32的带保护模式的平坦模式 3.2 程序编码 gcc -01 -o p p1.c -01 表示使用第一级优化.优化的级别与编译时间和最终产生代码的形式都有关系,一般认

CSAPP:第三章程序的机器级表示2

CSAPP:程序的机器级表示2 关键点:算术.逻辑操作 算术逻辑操作1.加载有效地址2.一元二元操作3.移位操作 算术逻辑操作 ??如图列出了x86-64的一些整数和逻辑操作,大多数操作分成了指令类(只有leaq没有其他的变种,addb.addw.addl.addq分别是字节加法.字加法.双字加法和四字加法),这些操作通常分为四组:加载有效地址.一元操作.二元操作和移位操作. 1.加载有效地址 leaq S,D;D = &S??加载有效地址指令leag实际上是movq指令的变形,它的指令形式上是

程序的机器级表示——基础

计算机执行的是机器代码,机器代码是二进制文件,既程序.机器代码用字节(1Byte=8bit)序列编码低级的操作,例如数据处理,管理存储器,从存储设备取数据等.使用高级语言(c,c++等)编写的程序(文本形式)最终需要被编译成机器代码才可以被计算机执行.当使用GCC c编译器来编译c语言代码时,会首先将其编译成汇编语言形式的内容,然后c编译器调用汇编器和连接器来最终形成计算机可执行的机器代码.汇编语言代码是机器码的文本形式,是机器码(字节序列)的文本助记形式.现在的要求是可以理解经过编译器优化过的

六星经典CSAPP-笔记(3)程序的机器级表示

1.前言 IA32机器码以及汇编代码都与原始的C代码有很大不同,因为一些状态对于C程序员来说是隐藏的.例如包含下一条要执行代码的内存位置的程序指针(program counter or PC)以及8个寄存器.还要注意的一点是:汇编代码的ATT格式和Intel格式.ATT格式是GCC和objdump等工具的默认格式,在CSAPP中一律使用这种格式.而Intel格式则通常会在Intel的IA32架构文档以及微软的Windows技术文档中碰到.两者的主要区别有: Intel格式忽略指令中暗示操作数长度

C语言程序的机器级表示

过程调用的机器级表示 特别说明该表示是基于IA-32指令系统,x86 64指令系统不同于IA-32 机器级表示 可执行文件的存储器映像 调用过程 IA-32的寄存器使用约定 – 调用者保存寄存器:EAX.EDX.ECX 当过程P调用过程Q时,Q可以直接使用这三个寄存器,不用 将它们的值保存到栈中.如果P在从Q返回后还要用这三个寄 存器的话,P应在转到Q之前先保存,并在从Q返回后先恢复 它们的值再使用. – 被调用者保存寄存器:EBX.ESI.EDI Q必须先将它们的值保存到栈中再使用它们,并在返

程序的机器级表示 (2)

3.6.5 循环 据说大多数汇编器会根据do-while循环来产生代码, 所以其他循环可能会先转化为do-while形式再编译成机器代码, 所以我们首先介绍do-while循环... 1. do-while 循环 do-while的通用形式如图所示 : loop: body-statement t = test-expr; if(t) goto loop; 这里给出一个实际的例子 : 2. while循环 while循环有多种翻译方法, gcc使用了的是 先用if 进行检测第一次循环, 这样就将