程序的机器级表示

本章学习内容:汇编代码

高级语言屏蔽了程序的机器级实现。
用高级语言编写的程序可以在不同的机器上运行,汇编代码则于特定机器密切相关

学习汇编代码能理解编译器优化能力,并分析代码中隐含的低效率
此外,高级语言提供的抽象层会隐藏我们想要了解的程序的运行时行为
此外,很多攻击都涉及到程序存储运行时控制信息的方式的细节

逆向工程:通过研究系统和逆向工作,试图了解系统的创建过程

IA32:x86-64的32位前身(机器可以向后兼容IA32程序)

  1. C语言、汇编代码以及机器代码之间的关系
  2. x86-64的细节
  3. C语言的控制结构的实现
  4. 过程的实现
  5. 数组、结构、联合等数据结构的实现
  6. 内存越界问题
  7. 系统易遭受缓冲区溢出攻击的问题
  8. GDB调试器检查机器级程序运行时的行为技巧

历史观点

Intel处理器俗称x86,最开始的时候是单芯片

每个后继处理器都是向后兼容的:较早版本的代码可以在较新的机器上运行

  • 摩尔定律
    晶体管数量以每年大约37%的速率增加=>每26个月翻倍
    摩尔定律:1965年,摩尔预测在未来10年,芯片上的晶体管数量每年都会翻倍。在超过50年的事件里,半导体工业能使得晶体管数目每18个月翻倍

这些年出现了很多与Intel处理器兼容的处理器,例如AMD

经过了数十年的发展,曾经的晦涩难懂的特性已经不再会出现了

数据格式

byte:8位
word:16位
double word:32位
quad word:64位
标准int为双字,64位的指针为四字

x86-64指令集包括对完整的针对字节、字、双字的指令

C Intel数字精度 汇编代码后缀 大小(字节)
char 字节 b 1
short w 2
int 双字 l(long word) 4
long 四字 q 8
char * 四字 q 8
float 单精度 s 4
double 双精度 l 8

大多数GCC生成的汇编代码指令带有一个字符的后缀,表明操作数的大小
例如:数据传送指令有四种:

  1. movb
  2. movw
  3. movl
  4. movq

浮点数的双精度也是使用l代表的,但是并不会引起歧义,因为浮点数使用一组完全不同的指令与寄存器


假设有两个C程序,使用以下命令进行编译:
gcc -Og -o p p1.c p2.c

  1. gcc:GCC编译器
  2. -Og:生成复合原始C代码整体结构的机器代码的优化等级(使用高等级编译产生的代码会严重变形,以至于产生的机器代码与源代码之间的关系难以理解)

编译流程(见第一章):
.c -> .i -> .s -> .o -> 可执行
.o:目标代码文件,是机器代码的一种形式,包含所有指令的二进制表示,但是未填入全局值的地址

机器级代码

计算机系统使用了多种不同形式的抽象,利用更简单的抽象模型来隐藏实现的细节
两种最重要的抽象:

  1. 指令集体系结构或指令集架构(ISA)定义机器级别程序的格式与行为。它定义了处理器状态、指令的格式以及每条指令对状态的影响。大多数ISA将程序的运行描述得好像是一条一条执行的,但是事实上它们是并发执行的
  2. 机器级程序使用的内存地址是虚拟地址,提供的内存模型看上去像一个很大的字节数组

存储器系统的实际实现是将多个硬件存储器和操作系统软件组合起来的

汇编代码表示非常接近机器代码,且比起二进制格式,文本格式可读性更好

一些通常对C语言程序员隐藏的处理器状态在机器代码中都是可见的

  1. 程序计数器(PC):在x86-64中使用%rip表示
  2. 整数寄存器文件:包含16个命名的位置,可以存储指针或整数数据,也可以用来记录某些重要的程序状态。其它寄存器用来保存临时数据,例如过程参数、局部变量、函数返回值
  3. 条件码寄存器:最近执行的算术或逻辑指令的状态信息。实现数据流中的条件变化。
  4. 一组向量寄存器:存放一个或多个整数或浮点数值

机器代码只是简单地将内存看成一个很大的、按字节寻址的数组,C语言的各种结构在机器代码中用一组连续的字节表示,不区分有符号或者无符号的整数、指针。

程序内存:

  1. 可执行机器代码
  2. 操作系统需要的信息
  3. 管理过程调用和返回的栈
  4. 用户分配的内存块

在任意的给定的时刻,只有一部分虚拟地址被认为是合法的。

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

代码示例

在命令行上使用-S选项就能看到C语言编译器产生的汇编代码

  • helloWorld的编译结果:
 .file   "hello.c".text.def    __main; .scl    2;  .type   32; .endef.section .rdata,"dr"
.LC0:.ascii "Hello, world!\0".text.globl  main.def    main;   .scl    2;  .type   32; .endef.seh_proc main
main:subq   $40, %rsp.seh_stackalloc    40.seh_endprologuecall  __mainleaq  .LC0(%rip), %rcxcall    putsmovl    $0, %eaxaddq    $40, %rspret.seh_endproc.ident  "GCC: (x86_64-win32-seh-rev0, Built by MinGW-W64 project) 8.1.0".def  puts;   .scl    2;  .type   32; .endef

每一个缩进去的行都相当于一条机器指令
机器执行的只是一个字节序列,它对于产生这些序列的源代码一无所知

  • 反汇编器:用于查看机器代码的文件内容
    在Linux中objdump可以作为反汇编器使用
    linux> objdump -d hello.o

  • 使用反汇编器反汇编hello.o的结果

0000000000000000 <main>:0: 48 83 ec 08             sub    $0x8,%rsp4:  48 8d 35 00 00 00 00    lea    0x0(%rip),%rsi        # b <main+0xb>b:    bf 01 00 00 00          mov    $0x1,%edi10: b8 00 00 00 00          mov    $0x0,%eax15: e8 00 00 00 00          callq  1a <main+0x1a>1a: b8 00 00 00 00          mov    $0x0,%eax1f: 48 83 c4 08             add    $0x8,%rsp23: c3                      retq
  • 每组都是一个指令。右边是等价的汇编语言

  • 机器代码与反汇编表现的特性:

  1. x86-64的指令长度从1到15个字节不等,常用的指令以及操作数较少的指令的字节数较少
  2. (没看懂)设计指令格式的方式是,从某个给定位置开始,可以将字节唯一地解码成机器指令。例如只有pushq %rbx是以字节53开头的
  3. 反汇编器只是基于机器代码文件中的字节序列来确定汇编代码的。
  4. 反汇编器使用的指令命名规则与GCC生成的汇编代码使用的有细微差别。它省略了很多指令结尾的q。
  • 生成可执行的代码需要对一组目标代码文件运行链接器
    进行链接后文件变大了,因为它不仅包含着两个过程的代码,还包含着用来启动和终止程序的代码以及与操作系统交互的代码

关于格式的注解

GCC产生的汇编代码一方面包含一些我们不关心的信息,另一方面不提供任何程序的描述
所有以.开头的都是指导汇编器与链接器工作的伪指令,可以忽略

对于一些应用程序,程序员必须用汇编代码来访问机器的低级特性
一种方法是用汇编代码编写整个函数
另一种方法是利用GCC的支持,直接在C程序中嵌入汇编代码

ATT与Intel汇编代码格式

ATT是GCC和一些其它工具的默认格式
其它工具,包括Microsoft的工具,使用Intel格式
GCC可以产生Intel格式的代码:
linux> gcc -Og -S -masm=intel mstore.c

Intel与ATT在格式方面的不同:

  • Intel代码省略了指示大小的后缀
  • Intel代码省略了寄存器名字前的%
  • Intel代码使用不同的方式描述内存中的位置:
    使用QWORD PTR [rbx]而不是(%rbx)
  • 在带有多个操作数时,列出的操作数顺序相反

将C程序与汇编代码结合起来(找一个时间进行试验)

有一些机器特性是C程序访问不到的
例如:每次x86-64处理器执行算术或者逻辑运算时,若得到的计算结果的低8位中有偶数个1,则将一个名为PF(parity flag)的1位条件码设为1,否则设为0
而在C语言中,需要得到这些信息至少需要7次移位、掩码或者异或计算

在程序中插入汇编代码指令就可以轻松完成该任务

在C程序中插入汇编代码:

  • 编写完整的函数,放入一个独立的汇编代码文件,让汇编器与链接器将其与C语言代码合并
  • 使用GCC内联汇编的特性,使用asm伪指令在C程序中包含简短的汇编代码(好处:减少了与机器相关的代码量)

C语言内联汇编例子:

#include <stdio.h>void main(void)
{asm("mov $0x49, %edi;""call putchar@PLT;""mov $0x20, %edi;""call putchar@PLT;""mov $0x4c, %edi;""call putchar@PLT;""mov $0x4f, %edi;""call putchar@PLT;""mov $0x56, %edi;""call putchar@PLT;""mov $0x45, %edi;""call putchar@PLT;""mov $0x20, %edi;""call putchar@PLT;""mov $0x59, %edi;""call putchar@PLT;""mov $0x4f, %edi;""call putchar@PLT;""mov $0x55, %edi;""call putchar@PLT;""mov $0x0a, %edi;""call putchar@PLT;");
}

运行结果:

$ gcc -O3 love.c -o love
$ ./love
I LOVE YOU
$

北邮CSAPP第三章之数据格式与程序编码相关推荐

  1. Csapp 第三章阅读以及思考

    前言 csapp 第三章从 CPU 的架构入手,了解处理器如何处理指令,并将C程序翻译成汇编,根据汇编和cpu结构讲解你的程序是如何运行的,并讲解常用的数据结构是如何在内存中存储和表示的,通过这些了解 ...

  2. CSAPP第三章——程序的机器级表示:学习笔记总结

    花了半个多月,补完王爽老师的汇编语言后,跟着CMU的视频课+课本,学完了第三章的知识,最深的感触就是CSAPP无论是视频还是书的质量都非常的硬,不愧它的盛名.(lab6和课后的家庭作业还没做,之后再补 ...

  3. 软件项目管理MOOC(北邮)——第十一章测试答案

    第十一章 软件项目风险计划 第十一章 单元测试 1.不属于项目风险三要素的是哪项? A.一个事件 B.事件的产生原因 C.事件发生的概率 D.事件的影响 2.下列属于可预测风险的是哪项? A.不现实的 ...

  4. 软件项目管理MOOC(北邮)——第七章测试答案

    第七章 软件项目进度计划 第七章单元测验 1.在项目进行过程中,关键路径是不变的. A.对 B.错 2.浮动是在不增加项目成本的条件下,一个活动可以延迟的时间量. A.对 B.错 3.在使用应急法压缩 ...

  5. 北邮矩阵论- 第1章 1.1线性空间

    1.1 线性空间 1.1.1 集合与映射 1.数环和数域的定义 1.1.2 线性空间及其性质 1.线性空间的定义 2.线性空间的性质 线性空间VVV有唯一零元素 任一元素也有唯一的负元素 若 x∈V, ...

  6. CSAPP第三章(续)

    目录 联合 union 从来没见过捏 联合(union)是一种数据类型,特点是: 数据对齐 对齐规则 在机器级程序中将控制与数据结合起来 void* 特殊指针 指针类型的强制转换 函数指针 要记得东西 ...

  7. CSAPP第三章之条件分支的两种实现方式

    有关博文请移步:为什么我不喜欢用if语句 使用条件控制来实现条件分支 将条件表达式和语句从C语言翻译成机器代码,最常用的就是结合有条件和无条件的跳转 (有些条件可以用数据的条件转移来实现,而不是用控制 ...

  8. 【仅供参考】csapp第三章课后习题答案(欢迎批评指正)

    3.67 [答案] A. %rsp + 24 z              %rsp + 16 &z            %rsp + 8 y             %rsp     x  ...

  9. 第三章 Hello World入门程序

    源代码: .java--通过javac编译--字节码文件: .class 注释格式: // 单行注释(不需要完整占一行)/*无论这里占多少行,只要是在这个范围之内都算多行注释(区块注释)*/ 3.1 ...

最新文章

  1. Java学习-jsp内置对象Session
  2. 实现简书个人中心UI效果
  3. 男人必看:最近在网上流传甚广的一个帖子
  4. vc新线程、类外控制EDIT控件
  5. Excel的加密和解密
  6. python完全背包最优_python 完全背包问题_遗传算法Python实战 009.背包问题
  7. linux系统分区不,其中,不属于Linux系统分区的是()。
  8. 重构,体现一个工程师的基本素养和底蕴(细节篇)
  9. 信息如何实现病毒式传播?一文看懂Gossip协议
  10. JVM专题之类加载机制
  11. 解决font-weight:600在安卓机不生效的方法
  12. H5桌面通知: Notification API 的应用
  13. 章节十一、1-Junit介绍
  14. .net分层架构思想(彻底分离每个层)
  15. JEB配合IDA神器逆向
  16. IT行业市场人才需求
  17. 一文理解CPU进行简单加法(计算机组成原理5.1CPU的功能和基本结构)
  18. hadoop集群配置时hosts配置导致的错误(datanode节点只显示一个)
  19. ZigBee协议栈安装失败解决方法
  20. 什么是函数指针?(理解及使用)

热门文章

  1. 淘宝根据商品ID免费获取详细信息(实时价格、尺码、库存等) 超详细
  2. origin干嘛用的
  3. flyway常用配置_flyway解决
  4. DFS 深度优化搜索
  5. js inArray() jQuery.inArray( value, array )
  6. Gallery的简单功能和用法(画廊视图)
  7. java response 图片_SpringBoot使用@ResponseBody返回图片的实现
  8. Java - 保留小数点后两位
  9. c++之通讯录管理系统
  10. 怎样让Eclipse的智能提示像VS一样霸气