csapp-Machine-Level Representation of Program-review

news2024/10/7 12:26:44

Machine-Level Representation of Program收获和思考

Basics

Machine-Level Programming可以看成是机器执行对于上层代码的一种翻译,即硬件是如何通过一个个的指令去解释每一行代码,然后操纵各种硬件执行出对应的结果。
Machine-Level Programming有2种表现形式,一种是text格式的汇编代码;一种是由字节序列构成的机器码,可以理解汇编是机器码的一种文本表示(毕竟机器码是一组字节序列,很难读懂),Machine-Level Program可以看成是C、Java等高级语言和机器执行之间的桥梁。对于一个程序员而言,我们对于Machine-Level Programming更多的是能够读懂各种代码的汇编级别,通过阅读大量的汇编代码去掌握低级别的优化手段以及对代码常见错误的原因有更深刻的理解。

历史

csapp是基于Intel-64指令集讲述的Machine-Level Representation of Program。Intel采用的是CISC(Complex Instruction Set Computer),与Intel对应的AMD采用的则是RISC(Reduced Instruction Set Computer);与X86-64相对应的是IA32,X86-64相对于IA32添加了以下功能:

  1. 提供更高效的条件操作指令
  2. 从32bit扩展到64bit
  3. 支持多核

C、汇编和机器码

汇编可以看成是机器码的文本表示。对于C语言,其执行过程就是首先编译成汇编文件,然后再翻译成对应的可执行文件,当然我们也可以使用反汇编工具对可执行文件进行反汇编。
在这里插入图片描述

汇编构成

在Machine-Level Programming中,其主要操纵的是CPU和内存。
在这里插入图片描述
对于Assemble Code,与C Code不同的是它的数据类型与操作指令,在Assemble Code中,整数只有1、2、4、8字节大小的数据,浮点数只有4、8、10字节大小的数据,没有C语言中诸如数组、结构体等数据结构;操作指令则只包含数据在Register和Memory中的传输和存储以及控制指令(if、switch、for、while、do-while等)。
在X86-64架构中,Register一共有16个,除了%rsp Register以外,其他的Register中均可以存储数据和地址,%rsp表示栈顶指针。
在这里插入图片描述
与%rxx对应的%exx表示的是32bit的数据和地址,因此如果存储long型的数据,使用%rxx类型的Register,否则可以使用%exx类型的Register。
通常%rsi、%rdi用来存储函数形参中的第一个参数和第二个参数。

汇编运算

通常我们用%rxx表示该Register中存储的是数据,用(%rxx)表示该Register中存储的是地址。通过D(Rb,Rr,S)<---->Mem(Reg[Rb]+S*Reg[Rr]+D]来表示Register和Memory中的地址映射。
在汇编运算中,一个最重要的指令是movq,它表示将某个位置中的数据移动到另一个位置中,如:movq %rbx %rax 表示将%rbx中存储的数据移动到%rax中,注意在汇编代码中,参数的顺序和C语言中的参数顺序是相反的,即第一个表示source,第二个表示dest。在汇编中,数据一共有3种形式:Immediate(常量)、存储在Register中的数据,存储在Memory中的数据,三种数据的传输关系如下:在这里插入图片描述
除了movq指令外,还有一个很重要的指令leaq(load effective address) src dest,其用来更高效的进行地址运算(注意:leaq指令是为了更方便的计算地址,而不是存储在其中的值),其中src通常是expression mode,如在编译t = 12 x s时,可以通过leaq (%rbx,%rbx,2) %rax和salq $2 %rax来表示。除此之外,还有一系列的汇编运算指令:

在这里插入图片描述
在这里插入图片描述

Control

Control描述的是通过一系列条件指令去控制代码进行非线性执行。

Condition Codes

Condition Codes中常用的4个为CF、ZF、SF、OF,4个条件码分别表示无符号整数运算是否溢出、运算结果是否为0,运算结果是否为负数、有符号整数运算是否溢出。
Condition Codes通常并不会直接去设置,而是作为某些指令的结果去设置对应的Condition Codes,如cmp指令。通过setx指令去实现Condition Codes的设置,setx修改的是对应寄存器的低位字节(%rxx对应的低位字节为%xl)
在这里插入图片描述

Conditional Branch

Conditional Branch可以狭义的理解为if else控制语句,通过一系列%jmp(无条件跳转)/%jx(有条件跳转)指令实现。
在这里插入图片描述
这里涉及到Conditional move的优化:对于两个相对简单的分支运算,提前计算每个分支的结果再判断的效率要优于先判断再计算对应的分支结果。这里的本质我理解的其实编译器会去衡量提前预测的代价和预测失败的代价,当操作相对简单时,提前预测的代价(即提前计算每个分支的结果)要小于预测失败的代价(即在预测进入某个分支失败时重新进行判断),因此提前计算每个分支的效果更高;当操作比较复杂时,首先我们提前计算每个分支的代价会增大,其次我们也大概率不会对每个分支的结果进行计算。
因此Condition move的前提是每个分支的计算相对简单,当然Condition move可能还会涉及到一些异常错误,如空指针异常等(因为它会提前计算每个分支的结果,可能某个分支本身不会被执行到,但因为提前计算的原因触发了空指针异常)

Loop

常见的Loop有3种,分别为while、for、do-while。Loop总体来说是通过Condition Branch和Goto指令实现。三种Loop的差异在于:for对于while而言,需要提前初始化某些变量;do-while对于while而言,需要在条件判断前提前执行一次循环体。

Switch Statement

对于C Code而言,switch可以用一系列if else判断,但在汇编层面,switch并不是通过简单的if else堆积去实现。在switch的汇编实现中,编译器放弃了O(n)的if else代替,而是通过Jump Table和Conditional Tree实现。
Jump Table可以看成是一个索引数组,其中每一个索引对应switch statement中的case value。当然对于非法的case value(即负数or过大的value),可以通过对所有case value添加一个bias来将case value演绎成一个合理的值,当然也可以通过其他技巧去将不合理的case value等价替换掉。
由于数组是可以通过索引直接访问对应的值,因此Jump Table为O(1),但对于较为稀疏的case value而言,将其设置为一个0-n-1的jump table会浪费较大的空间,因此通过conditional tree(本质上是二分查找)来实现,其时间复杂度为O(log n)

Procedures

Procedures即为函数,其执行规则与栈规则一致:if p call q,then q return before q <—> last in, first out

Stack

对于Procedures而言,其重要特性都和Stack相关,如函数调用,参数传递、数据存储。因此理解Stack的结构对于Procedures的理解至关重要。
Stack是一个后进先出的数据结构,在汇编层面,Address从高到低对应Stack从栈底到栈顶,而数据增长的方向则是从栈底到栈顶,%rsp寄存器指向栈顶地址
在这里插入图片描述

Calling Conventions

Passing Control

对于Procedures而言,其通过call label指令和ret指令实现控制权的传递,对应的call label即当p call q时将控制权从p传递给q,当q执行完后通过ret返回结果,将控制权还给p。

Passing Data

Passing Data主要是参数的传递,对于前6个参数,会存储在特定的Register中(%rsi、%rdi…),从第7个参数开始,其都会存储在stack的特定区域(stack frame)中。

Managing Local Data

在Procedure中,当调用其他函数时,如p call q,那么对于q执行过程中产生的一些中建信息,对于p而言其实是无用的,所以这部分信息不应该占用公共的空间,而是应该随着q的生命周期结束而释放,在stack中,通过设定stack frame区域来存储这一部分数据,包括返回信息、本地的存储信息、临时空间。当然返回信息式存储在caller stack frame中,而剩下两者则是存储在callee stack frame中。
当然,由于寄存器是公用的,即p和q使用的%rbx是同一个rbx。在某些场景下我们可能需要临时保存某些特殊值而不被改变,因此在16个Register中,使用特殊的几个Registers(%r10和%r11)来保存临时值
在这里插入图片描述

Data

Data除了Integer和Floating Point这些基本数据类型外,还有Array、Structure、Union等组合数据。在内存中,诸如Array、Structure、Union这种类型的数据其实是一段连续的字节,他们是某些基本数据类型的集合。

Array

Array可以看成是同一种数据类型的集合,常见的有一维数组和二维数组两种类型。对于一个 A T[L],其在内存中所占用的空间大小为L * sizeof(A)。对于二维数组 A T[m][n],可以将其理解为每个元素均为一个长度为n的一维A类型数组。
在这里插入图片描述
在这里插入图片描述

数组和指针

数组和指针一直是一个容易被混淆的概念。但其实如果站在内存的角度去看就很容易区分这两种概念,在内存中,数组是一段连续的字节序列,存储的是同一种数据类型;而指针在内存中则是一个大小为8byte的地址空间。对于A *而言,其表示A作为一个pointer,指向内存中的某个存储的value为A类型的内存块,因此,A *既可以表示一个A value,也可以表示一个A array。当A *表示一个A类型的array时,其代表的是该array的首地址

数组指针和指针数组

很多同学分不清这两个概念:数组指针和指针数组。同样的,从内存方面去考虑二者能够很轻松的区分。如int * A[3],它表示的是一个长度为3,每个元素为一个指针,每个指针指向一个int类型元素的数组,int (* A)[3],它表示的是一个指向长度为3的int数组的指针。
在这里插入图片描述
还有一点需要注意的是在使用指针时,对于 T val[L]而言,val+2表示val数组的第三个元素的地址(0表示首地址),但是2+val则无法通过编译,这是因为在汇编层面规定了参数的顺序,第一个为 void *,第二个为常数,因此2+val无法被有效处理。

Structure

Structure可以看成是一组不同类型元素的集合。

struct{
	char a;
	int b[2];
	long c;
} S;

Structure中需要注意的是一个Structure所占用的内存空间并不总是等于其内部所有元素占用大小的和,因为这里还涉及到一个对其的问题。
对于X86-64而言,在从内存中取数据时会一次性取出64byte大小的数据,因此对于一个Structure而言,如果其所占用的内存地址横跨两个内存块时,还需要硬件和os去采取额外的操作去弥补这种横跨问题,因此会造成性能的下降。
而对齐则是为了解决这种横跨内存块造成的性能下降。所谓对齐就是尽可能让变量的首地址是该变量占用大小的k倍,通常是按照一个Structure中占用空间最大的变量对齐,拿上述例子来说,占用空间最大的是long类型的c(占用8个byte),因此需要按照8对齐(注意:对齐只针对基本数据类型,即使是一个array,也只需要看array中元素的大小,这一点很好理解)。为了尽可能减少对齐造成的内存消耗,这里涉及到一个变量重排序的过程,通常将占用空间最大的元素放在地址首部对齐占用的内存空间最小(这一点大家可以实际实践下)

Union

Union和Structure是非常类似的一种数据类型,都是一组不同类型数据的集合,但Union与Structure不同的是,Union中所有数组共享同一份内存空间,也就是说Structure的大小等于其内所有元素占用空间之和(不考虑对齐问题),而Union的大小等于其内最大元素占用空间。
在这里插入图片描述
因此在使用Union时,不能在同一时刻同时使用Union中的多个数据,而只能使用一个数据

Memory Layout

对于X86-64机器来说,其可用的地址空间有48位,对应即为256TB,在其中又只有128TB可以供用户使用,所以寻址空间一共有128TB(0->0x0007FFFFFFFFFFFF)
在这里插入图片描述

在内存区域中,Stack负责存储一些局部变量以及运行时数据;Heap则是动态产生,通过malloc()、calloc()、new()等方式创建;Data则是负责存储一些静态变量、全局变量;Text/Shared Libraries存储可执行的机器指令,并且是只可读的。

Buffer Overflow

Buffer Overflow指的是输入的内容由于没有做限制而导致其覆盖了内存中其他指令的地址从而对其他指令造成破坏,同样,这也是hacker攻击程序的主要手段。
在C语言中,诸如gets()这样没有对输入内容作大小限制的函数则存在Buffer Overflow的风险。

避免手段

  1. 禁止使用有Buffer Overflow风险的函数,使用等价的fgets代替gets,fgets通过提供最大长度限制参数来避免Buffer Overflow
  2. OS提供的避免Buffer Overflow的保护手段
    1. ASL:每次运行时将栈地址随机化,Buffer Overflow生效的前提是你明确知道栈地址的位置,然后通过覆盖的形式破坏程序执行,而ASL通过随机栈地址可以有效避免Buffer Overflow
    2. 提供可执行标记,通过标记栈不可执行来避免Buffer Overflow
    3. 金丝雀:在Stack中添加测试代码,从代码执行前从指定寄存器A中取出8byte的value存储到另一个寄存器B中,然后执行栈上代码,随后比较寄存器B中的value和寄存器A中的value是否相等,若不相等,说明栈上地址被破坏、

总结

通过学习Machine-Level Representation这一章节,我觉得最大的收获就是学会了去阅读汇编代码,能够从汇编级别上去调试代码,掌握汇编层面的一些优化手段(Condition Move、对齐等等),同时也进一步加深了我对指针的理解。最后,我想用自己总结的一句话来收尾:自己从汇编层面、内存存储层面去理解代码原理、分析代码问题要比Google更加有效,cs is easy,if not easy,then try back to the source.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1117329.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Jprofiler V14中文使用文档

JProfiler介绍 什么是JProfiler? JProfiler是一个用于分析运行JVM内部情况的专业工具。 在开发中你可以使用它,用于质量保证,也可以解决你的生产系统遇到的问题。 JProfiler处理四个主要问题: 方法调用 这通常被称为"CPU分析"。方法调用可以通过不同的方式进行测…

【剑指Offer】33.二叉搜索树的后序遍历序列

题目 输入一个整数数组&#xff0c;判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则返回 true ,否则返回 false 。假设输入的数组的任意两个数字都互不相同。 数据范围&#xff1a; 节点数量 0≤n≤1000 &#xff0c;节点上的值满足 1≤val≤10^5 &#xff0c;保证节…

Xcode报错“compact unwind compressed function offset doesn‘t fit in 24 bits

Assertion failed: (false && “compact unwind compressed function offset doesn’t fit in 24 bits”), function operator(), file Layout.cpp, line 5758. 解决方案&#xff1a;targerts->build settings->other linker Flages增加-ld64

企业数据防泄密软件-文件外发管理,文件,文档,图纸不外泄

企业数据防泄密软件可以帮助保护企业的重要数据和知识产权&#xff0c;其中文件外发管理是一个重要的环节。 PC访问地址&#xff1a;https://isite.baidu.com/site/wjz012xr/2eae091d-1b97-4276-90bc-6757c5dfedee 以下是一些关键功能&#xff1a; 透明加密&#xff1a;加密软件…

【框架源码篇 01】Spring源码-手写IOC

Spring源码手写篇-手写IoC 一、IoC分析 1.Spring的核心 在Spring中非常核心的内容是 IOC和 AOP. 2.IoC的几个疑问? 2.1 IoC是什么&#xff1f; IoC:Inversion of Control 控制反转&#xff0c;简单理解就是&#xff1a;依赖对象的获得被反转了。 2.2 IoC有什么好处? IoC带…

浏览器的四种缓存协议

❤️浏览器缓存 在HTTP里所谓的缓存本质上只是浏览器和业务侧根据不同的报文字段做出不同的缓存动作而已 四种缓存协议如下 Cache-ControlExpiresETag/If-None-MatchLast-Modified/If-Modified-Since &#x1f3a1;Cache-Control 通过响应头设置Cache-Control和max-age&…

【必须安排】书单|1024程序员狂欢节充能书单!

注&#xff1a;以上书单可从京东商城优惠购买&#xff0c;点击以下链接进入图书专题&#xff01;1024程序员狂欢节充能书单 一年一度的1024程序员狂欢节又到啦&#xff01;成为更卓越的自己&#xff0c;坚持阅读和学习&#xff0c;别给自己留遗憾&#xff0c;行动起来吧&#x…

UniApp百度人脸识别插件YL-FaceDetect

插件地址&#xff1a;https://ext.dcloud.net.cn/plugin?id15061 插件说明&#xff1a; 百度离线人脸识别&#xff0c;人脸收集&#xff0c;属性&#xff08;性别年龄&#xff09;识别等&#xff0c;目前只支持安卓端&#xff01; 另&#xff1a;该插件支持的功能为属性识别…

win10下yolox tensorrt模型部署

TensorRT系列之 Win10下yolov8 tensorrt模型加速部署 TensorRT系列之 Linux下 yolov8 tensorrt模型加速部署 TensorRT系列之 Linux下 yolov7 tensorrt模型加速部署 TensorRT系列之 Linux下 yolov6 tensorrt模型加速部署 TensorRT系列之 Linux下 yolov5 tensorrt模型加速部署…

51系列—基于51单片机的数字频率计(代码+文档资料)

本文主要说明基于51单片机的数字频率计设计&#xff0c;完整资料见文末链接 数字频率计概述 数字频率计是计算机、通讯设备、音频视频等科研生产领域不可缺少的测量仪器。它是一种用十进制数字显示被测信号频率的数字测量仪器。它的基本功能是测量正弦信号&#xff0c;方波信…

安达发|AI在APS生产计划排程系统中的应用与优势

随着科技的不断发展&#xff0c;人工智能&#xff08;AI&#xff09;已经在许多领域取得了显著的成果。在生产管理计划系统中&#xff0c;AI技术的应用也日益受到关注。本文将探讨如何将AI人工智能用在生产管理计划系统上&#xff0c;以提高生产效率、降低成本并优化资源配置。…

Qt之使用bitblt抓取bitmap(位图)并转QImage

一.效果 点击按钮抓取窗口自身并显示到QLable中 二.实现 pro文件 QT += core guigreaterThan(QT_MAJOR_VERSION, 4): QT += widgetsCONFIG += c++11SOURCES += \main.cpp \mainwindow.cppHEADERS += \mainwindow.hFORMS += \mainwindow.uiLIBS += -lgdi32 -luser32 -l…

前端数据可视化之【Echarts下载使用】

目录 &#x1f31f;下载&#x1f31f;浏览器引入&#x1f31f;模块化引入 &#x1f31f;使用&#x1f31f;基本使用步骤 &#x1f31f;绘制一个简单的图表&#x1f31f;写在最后 &#x1f31f;下载 &#x1f31f;浏览器引入 官网下载界面&#xff1a;官方网站 或 Echarts中文…

旺店通企业版与金蝶云星辰数据集成方案分享

今天我们将深入介绍旺店通企业版与金蝶云星辰业财一体化数据集成方案&#xff0c;以丰富的业务场景示例展示该平台如何以无缝的方式连接不同系统&#xff0c;实现数据同步、提高效率&#xff0c;同时突显轻易云的出色能力。 概述 旺店通企业版与金蝶云星辰业财一体化数据集成方…

【一周安全资讯1021】工业和信息化部等六部门印发《算力基础设施高质量发展行动计划》;思科未修补的零日漏洞正被积极利用

要闻速览 1、工业和信息化部等六部门印发《算力基础设施高质量发展行动计划》 2、香港芭蕾舞团电脑遭勒索软件入侵读取个人及内部资料 3、日本最大通信运营商九百万条数据被盗&#xff0c;泄露时间长达十年 4、疑似迪卡侬 8000 名员工个人信息暴露暗网上 5、Kwik Trip遭遇“神…

【算法题】统计无向图中无法互相到达点对数

题目&#xff1a; 给你一个整数 n &#xff0c;表示一张 无向图 中有 n 个节点&#xff0c;编号为 0 到 n - 1 。同时给你一个二维整数数组 edges &#xff0c;其中 edges[i] [ai, bi] 表示节点 ai 和 bi 之间有一条 无向 边。 请你返回 无法互相到达 的不同 点对数目 。 示…

美格智能出席无锡智能网联汽车生态大会,共话数字座舱新势力

10月20日&#xff0c;2023世界物联网博览会期间&#xff0c;以“智 行天下 启未来”为主题的2023无锡智能网联汽车生态大会暨域控制器及智能座舱论坛在无锡举行。大会邀请行业权威专家&#xff0c;多家知名企业重磅嘉宾出席&#xff0c;融汇智能网联汽车思想智慧、创新技术、产…

系统架构设计之微内核架构(Microkernel Architecture)

微内核架构&#xff08;Microkernel Architecture&#xff09; 一. 什么是微内核架构二. 微内核架构风格-拓扑结构三. 微内核的核心系统设计的三个关键点3.1 插件管理3.2 插件连接3.3 插件通信 四. 微内核架构的优缺点 一. 什么是微内核架构 微内核架构是一种面向功能进行拆分的…

Spring framework day 02:Spring 整合 Mybatis

前言 在现代软件开发中&#xff0c;数据持久化是一个重要的环节。为了高效、可维护地管理和操作数据库&#xff0c;许多开发者采用了Spring框架和Mybatis持久化框架的组合。Spring提供了依赖注入和面向切面编程等特性&#xff0c;而Mybatis则是一个优秀的对象关系映射&#xf…

JS类的继承和实现原理详解

一&#xff1a;前言 各位小伙伴在日常开发中&#xff0c;相信一定遇到过Class这种写法。这代表在JS中创建了一个类&#xff0c;并且可以通过这个类去 new 出一个新的对象。其实在JS中&#xff0c;这个类和java中的类是没有区别的&#xff0c;同样具有属性&#xff0c;方法&…