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

被折叠的 条评论
为什么被折叠?



