第三章 程序的机器级表示

本文详细介绍了程序从源代码到机器代码的转换过程,重点讲解了GCC编译器的工作流程,包括预处理器、编译器、汇编器和链接器的作用。同时,深入探讨了机器级代码,特别是IA32架构中的数据格式、寄存器使用、指令操作和控制流程,如跳转指令、条件分支、循环和过程调用。此外,文章还提到了x86-64架构的扩展和性能提升,以及如何使用GDB和DDD进行程序调试,以及防止缓冲区溢出的措施。

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 GDBDBX, 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、浮点程序的机器级表示

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值