您的位置:首页 > 其它

第三章 程序的机器级表示

2015-10-11 11:43 141 查看
1、历史观点

2、程序编码

命令gcc---GCC C、C++编译器。是Linux上默认的编译器。

gcc命令调用一系列程序,将源代码转化成可执行代码:

Ø 首先,C预处理器扩展源代码*.c,插入所有用#include命令指定的文件,并扩展所有用#define声明指定的宏。

Ø 然后,编译器产生两个源代码的汇编代码,名字为*.s

Ø 接下来,汇编器将汇编代码转化成二进制目标代码(机器代码的一种形式)文件名为*.o

Ø 最后,衔接器将两个目标代码文件与实现库函数的代码合并,并产生最终的可执行代码文件。

2.1、机器级代码

机器代码:二进制格式

汇编代码:用可读性更好的文本格式来表示。

一条机器指令值执行一个非常基本的操作。

2.2、代码示例

code.c

在命令行上使用“-S”选项,gcc将*.c文件编译器产生汇编代码,产生*.s的可读汇编代码文件

gcc -o1 –S code.c

在命令行上使用“-C”选项,gcc将*.c文件编译并汇编该代码,产生目标代码文件*.o的二进制格式。

gcc –o1 –C code.c

反汇编器(disassembler)用于查看目标代码文件的内容,将目标文件反汇编为汇编代码文件*.s

objdump –d code.o

生成可执行文件

gcc –o1 –o main.c prog

2.3、关于格式的注解

3、数据格式

Intel 用术语“字”(word)表示16位数据类型。

32位数为“双字”(double words),64位数为“四字”(quadwords)。

汇编代码指令都有一个字符后缀,表明操作数的大小。

数据传送指令有三个变种:movb传送字节、movw传送字、movl传送双字。

4、访问信息

寄存器:用来存储整数数据和指针。

IA32CPU包含一组8个存储32位值的寄存器。以%e开头。

前6个寄存器是通用寄存器,最后两个寄存器保存指向程序栈中重要位置的指针。

4.1、操作数指示符

操作数operand

三种类型:

Ø 立即数(immediate)也就是常数值。

Ø 寄存器(register)表示某个寄存器的内容。

Ø 存储器引用(memory)根据计算出来的地址(寻址方式),访问某个存储器位置。

4.2、数据传送指令

MOV类中的指令,将源操作数的值复制到目的操作数中。

源操作数:指定的值是一个立即数,存储在寄存器或者存储器中。

目的操作数:指定一个位置,寄存器或者存储器地址。

程序栈:通过push和pop操作,程序栈存放在存储器中的某个区域。

在C语言中:

Ø “指针”其实就是地址

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

Ø 局部变量通常保存着寄存器中,而不是存储器中。寄存器访问比存储器访问快的多。

5、算术和逻辑操作

5.1、加载有效地址

加载有效地址指令leal:从存储器读数据到寄存器。

目的操作数必须是一个寄存器。

5.2、一元操作和二元操作

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

二元操作,第二个操作数既是源又是目的。

5.3、移位操作

<< >> 移位量、要移位的数值

5.4、特殊的算术操作

6、控制

顺序执行。

jump指令:改变一组机器代码指令的执行顺序。

6.1、条件码

CPU维护一组单个位的条件码寄存器:描述最近的算术或逻辑操作的属性。

常用的条件码:

Ø CF:进位标志

Ø ZF:零标志

Ø SF:符号标志

Ø OF:溢出标志

6.2、访问条件码:条件码的使用方法:

² 根据条件码的某个组合,将一个字节设置为0或者1。---SET指令

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

² 有条件的传送数据

6.3、跳转指令及其编码

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

跳转的目的地通常用一个标号(label)指明。

6.4、条件分支

if-else

6.5、循环

Ø do-while

Ø while

Ø for

6.6、条件传送指令

6.7、switch语句

根据一个整数索引值进行多重分支。

7、过程

过程调用:将数据(参数和返回值)和控制从代码的一部分传递到另一部分。

数据传递、局部变量的分配和释放---通过操纵程序栈实现。

7.1、栈帧

栈帧(stack frame):为某个过程分配的那部分栈。(见有道云笔记)

递归过程

过程能够递归的调用它们自身。因为每个调用在栈中都有自己的私有空间,多个未完成调用的局部变量不会相互影响。

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

8、数组的分配和访问

9、结构struct、联合union

9.1、struct

9.2、union

9.3、数据对齐

Linux沿用的对齐策略是:2字节数据类型(例如short)的地址必须是2的倍数,而较大的数据类型(例如int、int*、float、double)的地址必须是4的倍数。

10、理解指针

Ø 每个指针都对应一个类型

Ø 每个指针都有一个值

Ø 指针用&运算符创建

Ø 运算符*用于指针的间接引用

Ø 将指针从一种类型强制转换成另一种类型,只改变它的类型,而不改变它的值。

Ø 指针可以指向函数。

Ø

给C语言初学者:函数指针

int (*f)(int *p)

区别于

int * f(int *p)

11、使用GDB调试器

What is GDB?

GDB, the GNUProject debugger, allows you to see what is going on `inside' another programwhile it executes -- or what another program was doing at the moment itcrashed.

GDB can dofour main kinds of things (plus other things in support of these) to help youcatch bugs in the act:

Start your program, specifying anything that might affect its behavior.
Make your program stop on specified conditions.
Examine what has happened, when your program has stopped.
Change things in your program, so you can experiment with correcting the effects of one bug and go on to learn about another.

相对于使用命令行接口访问GDB,许多程序员更愿意使用DDD,它是GDB的一个扩展,提供了图形用户界面。

Whatis DDD?

GNU DDD is a graphical front-end for command-linedebuggers such as GDB, DBX,
WDB, Ladebug, JDB, XDB, the
Perl debugger, the bash debugger bashdb, the GNU Make debugger remake,
or the Python debugger pydb. Besides ``usual'' front-end features such as viewing sourcetexts, DDD has become famous
through its interactive graphical data display,where data structures are displayed as graphs.

12、存储器的越界引用和缓冲区溢出

数组---边界检查

缓冲区溢出(bufferoverflow):通常,在栈中分配某个字节数组来保存一个字符串,但是字符串的长度超出了为数组分配的空间。

缓冲区溢出会覆盖栈上存储的某些信息,使得这些信息被破坏。

缓冲区溢出的一个更加致命的使用是:让程序执行它本来不愿意执行的函数。通过计算机网络攻击系统安全的方法。

通常,输入给程序一个字符串,这个字符串包含一些可执行代码的字节编码,称为“攻击代码”(exploit code),还有一些字节会用一个指向攻击代码的指针覆盖返回地址,那么,执行ret指令(即返回指令)的效果就是转到攻击代码。

对抗缓冲区溢出攻击的方法:

Ø 栈随机化

Ø 栈破坏检测

Ø 限制可执行代码区域

13、x86-64:将IA32扩展到64位

IA32:指代基于Intel的机器上运行传统32位Linux版本时,硬件和GCC代码的组合。

x86-64:指代在AMD和Intel 的较新的64位机器上运行的硬件和代码的组合。

用Linux和GCC的话来说,这两个平台分别称为“i386”和“x86-64”

x86-64 主要特性:

l 指针和长整数是64位长。整数运算支持8/16/32/64位数据类型。

l 通用目的寄存器组 从8个扩展到16个。

l 许多 程序状态 都保存在寄存器中,而不是栈上。整型和指针类型的过程参数(最多6个)通过寄存器传递。有些过程根本不需要

l 如果可能,条件操作 用 条件传送指令 实现,会得到比传统分支代码更好的性能。

l 浮点操作 用 面向寄存器的指令集来实现,而不是IA32支持的基于栈的方法来实现。

(1) 数据类型

大多数机器并不是真的支持 完全地址范围,当前的AMD和Intel x86-64 机器只支持256TB(248字节)虚拟存储器,但为指针分配完全的64位。

数据大小

数据类型的准确字节数依赖于机器和编译器。

图 C语言中数字数据类型的字节(byte)数
C声明

32位机器

64位机器

char

1

1

short int

2

2

int

4

4

long int

4

8

long long int

8

8

char *(指针使用机器的全字长)

4

8

float

4

4

double

8

8

在x86-64机器上,“long”将整数变成了64位,可以表示的值的范围大了很多。

“long long”与“long”变成一样的了。

(2) 汇编代码示例

通常,x86-64代码更简洁,需要较少的存储器访问,运行起来比相应的IA32代码更有效率。

(3) 访问信息

通用寄存器组对比
区别

x86-64

IA32

寄存器的数量

16个

8个

寄存器长度

64位

32位

寄存器命名

以(%r)开头:

%rax

%rcx

%rdx

%rbx

%rsi

%rdi

%rsp

%rbp

新增加的寄存器命名为:

%r8~%r15

以(%e)开头:

%eax

%ecx

%edx

%ebx

%esi

%edi

%esp

%ebp

可以直接访问每个寄存器的

低32位

低16位

低8位

低16位

(4) 控制

(5) 数据结构

数组、结构、联合

数据对齐要求:对于任何需要K字节的标量数据类型来说,它的起始地址必须是K的倍数。

(6) 关于2x86-64的总结性评论

14、浮点程序的机器级表示
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: