【第 3 周】深入理解计算机系统共读心得体会

所在小组

六组

组内昵称

钟荣荣

你的心得体会

1.从程序编码到机器代码:源码经过预处理,编译,汇编,链接四个步骤得到机器代码,使用gcc -Og -s命令可以得到汇编代码,objdump -d反汇编查看机器代码,书上的示例中,汇编代码push %rbx对应的十六进制机器代码是53,对此处有个疑问,x86 -64指令长度从1到15字节不等,是如何执行复杂操作的。
2.关于第三章:囫囵吞枣地读了几个章节,对源码到汇编,机器码的过程有了大概的了解,但是还有很多细节有很多疑惑,还得再精读几遍。

所在小组

第一组

组内昵称

盆栽Charming

心得体会

这里

所在小组

第七组

组内昵称

jinmiaoluo

你的心得体会

见: https://github.com/jinmiaoluo/blog/tree/main/example-8-reading-notes/csapp/chapter-3

所在小组

第三组

组内昵称

uucloud

心得体会

  • 编译器到汇编可能会插入一些没什么意义的指令,为的是补全为16字节
  • double long是一种x86历史上出现的一种10字节的特殊浮点格式,移植性不好,硬件实现也不如单/双精度算术运算高效
  • movb(传送字节)、movw(传送字)、movl(传送双字)、movq(传送四字),'l’是4字节整数或者8字节双精度浮点,不会有歧义的原因是指令集和寄存器都不一样。

所在小组:静默组
组内昵称:黄小黄
你的心得体会:

  • 缓冲区溢出
    C对于数组引用不进行任何边界检查,而且局部变量和状态信息,都存在栈中。对越界的数组元素的写操作会破坏存储在栈中的状态信息。当程序使用这个被破坏的状态,试图重新加载寄存器或执行ret指令时,就会出现很严重的错误。
    缓冲区溢出的一个更加致命的使用就是让程序执行它本来不愿意执行的函数。这是一种最常见的通过计算机网络攻击系统安全的方法。通常,输入给程序一个字符串,这个字符串包含一些可执行代码的字节编码,称为攻击代码,另外还有一些字节会用一个指向攻击代码的指针覆盖返回地址。那么,执行ret指令的效果就是跳转到攻击代码。
    通常,使用gets或其他任何能导致存储溢出的函数,都不是好的编程习惯。不幸的是,很多常用库函数,包括strcpy、strcat、sprintf,都有一个属性——不需要告诉它们目标缓冲区的大小,就产生一个字节序列。

  • 对抗缓冲区溢出攻击
    1、栈随机化
    实现方式:程序开始时,在栈上分配一段0–n字节之间的随机大小空间。程序不使用这段空间,但是它会导致程序每次执行时后续的栈位置发生了变化。
    2、栈破坏检测
    最近的GCC版本在产生的代码中加入了一种栈保护者机制,用来检测缓冲区越界,其思想是在栈中任何局部缓冲区与栈状态之间存储一个特殊的金丝雀值。这个金丝雀值是在程序每次运行时随机产生的,因此,攻击者没有简单的办法知道它是什么。在恢复寄存器状态和从函数返回之前,程序检查这个金丝雀值是否被该函数的某个操作或者函数调用的某个操作改变了。如果是,那么程序异常终止。
    3、限制可执行代码区域
    限制那些能够存放可执行代码的存储器区域。在典型的程序中,只有保存编译器产生的代码的那部分存储器才需要是可执行的,其他部分可以被限制为只允许读和写。现在的64位处理器的内存保护引入了”NX”(不执行)位。有了这个特性,栈可以被标记为可读和可写,但是不可执行,检查页是否可执行由硬件来完成,效率上没有损失。

所在小组

第六组

组内昵称

黄永平

心得体会

  • 大多数指令,既可以用于无符号运算,也可以用于补码运算。只有右移操作要求区分有符号与无符号数。这是采用补码实现有符号数的原因之一。

  • 乘法与除法运算区分有符号与无符号数,乘法指令:imulq、mulq,除法指令:idivq、divq。

  • x86-64中,通过寄存器最多传递6个整型(例如整型和指针)参数。如果一个函数大于6个整型参数,超过6个的部分就要通过栈来传递。显然的,通过栈传递的开销肯定大于寄存器方式。CleanCode关于函数入参数个数的控制,这也是一个原因吧。

  • 疑问待查资料:各汇编指令的CPU周期开销是多少呢?

所在小组
第七组
组内昵称
Hayden
心得体会

  1. 预处理器、编译器汇编器和链接器一起构成了编译系统。
  2. C预处理器拓展源代码,插入所有用#include命令指定的文件,并拓展所有用#define声明指定的宏。
  3. 编译器产生两个源文件的汇编代码。
    4.汇编器将汇编代码转化成二进制目标代码文件。
    4.链接器将两个目标文件与实现库函数代码合并,产生可执行文件。
  • 通用寄存器主要用来存储整数和指针。

  • 摩尔定律:集成电路上可以容纳的集体管数目在大约每26个月会增加一倍。

  • 栈是一种数据结构,可以压入和弹出数据,遵循后进先出规则。通过push压栈,通过pop出栈。栈是向下增长,向低地址方向增长,栈顶元素的地址是所有栈中元素地址中最低的。数据的参数传递、局部变量的分配和释放都是通过栈来实现的。

所在小组

第2组

组内昵称

叶王

心得体会

  • 汇编语言是机器语言的文本表示,了解汇编语言对我们编程高级语言代码有很大的帮助,但并不意味着我们需要手写汇编代码
  • 可以使用 objdump 进行反汇编,从二进制文件生成汇编代码
  • 常见的汇编代码是 ATT 格式的,另外还有微软和 Intel 的汇编表示(省略操作指令后缀,寄存器前面省略 % 等)
  • x86 是 32 位 CPU,x86_64, x64, AMD64 都是一样的,是 64 位 CPU
  • 简单函数的局部变量可以直接使用寄存器保存,而不用内存,可以加快程序运行速度。当参数太多时,就需要存储到函数的栈空间
  • 按照惯例,栈空间是从下往上
  • 字是16位,32位是双字
  • 可以通过名字来访问寄存器,如 %rax
  1. 所在小组:第五组
  2. 组内昵称:张学广
  3. 心得体会:

第三章重点来了,我们知道程序的执行是由高级语言转换成机器语言,然后机器识别这些10字符后进行运算的,而这个转换的过程中代码也是经历了一系列编译链接,代码优化等操作,我们想要深入的了解程序的效率执行,就需要对这个过程有一定的认知,才能写出更高效的代码,或者找出代码中的问题,这一章主要内容就是这个。

汇编是这一章的重点,通过对我们编写的代码转换为对应的汇编理解程序在机器中的执行过程,这一部分暂时的收获如下:

  • 多个if转换成switch更加高效(可读性高),多次判断转换成一次比较跳转
  • case在一些跨度大数量少的情况会被优化为if-else(一切为了效率)

继续研读中,进度略慢。。

所在小组

第六组

组内昵称

蒋权

你的心得体会

  • 汇编代码表示非常接近于机器代码。与机器代码的二进制格式相比,汇编代码的主要特点是它用可读性更好的文本格式表示,能够理解汇编代码以及它与原始代码的联系,是理解九三级如何执行程序的关键一步。x96-64的机器代码和原始的C代码差别非常大。一些通常对C语言程序员隐藏的处理器状态都是可见的:
  1. 程序计数器(通常称为“PC”,在x86-64中用%rip表示)给出将要执行的下一条指令在内存中的地址。

  2. 整数寄存器文件包含16个命名的位置,分别存储64位的值。这些寄存器可以存储地址(对应于C语言的指针)或整数数据。有的寄存器被用来记录某些重要的程序状态,而其他的寄存器用来保存临时数据,例如过程的参数和局部变量,以及函数的返回值。

  3. 条件码寄存器保存着最近执行的算术或逻辑指令的状态信息。它们用来实现控制或数据流中的条件变化,比如说用来实现if和while语旬。

  4. 一组向量寄存器可以存放一个或多个整数或浮点数值。

  • 操作数被分为三种类型:立即数、寄存器和内存引用。

尚义龙:尚义龙的第三周总结
史素佳:史素佳的第三周总结
安梦飞:安梦飞的第三周总结
张旭辉:张旭辉的第三周总结
顾思明:顾思明的第三周总结
卢国华:卢国华的第三周总结
魏琮:魏琮的第三周总结
志林:志林的第三周总结

所在小组

第五组

组内昵称

郑伟钊
###心得体会
1.机器级编程的2种抽象:指令集结构,虚拟地址

2.使用反汇编器,64位系统下指定-m32生成32位的,和书中给出的代码不一样,所以阅读本章的目的是读懂汇编代码。

3.链接:就是将不同部分的代码和数据收集和组合成为一个单一文件的过程。链接可以在编译时由静态编译器来完成,也可以在加载时和运行时由动态链接器来完成。链接器处理称为目标文件的二进制文件,它有三种不同的形式:可重定位的、可执行的和共享的。可重定位的目标文件由静态链接器组合成一个可执行的目标文件,它可以加载到存储器中并执行。共享目标文件(共享库)是在运行时由动态链接器链接和加载的,或者隐含地在调用程序被加载和开始执行时,或者根据需要在程序调用dlopen库的函数时。

4.链接器的两个主要任务是符号解析和重定位。符号解析将目标文件中的每个全局符号都绑定到一个惟一的定义,而重定位确定每个符号的最终存储器地址,并修改对那些目标的引用。静态链接器是由像GCC这样的编译器调用的.

5.C语言中的指针其实就是地址,引用指针就是将指针取到寄存器中,然后在存储器访问中使用这个寄存器

6.函数体中的局部变量x存在寄存器,而非存储器中

7.移位指令中,移位量是单字节编码,移位量是立即数或者放在单字节寄存器%cl中,注意只能是这个寄存器

8.和使用一组很长的if-else相比,使用跳转表的优点是执行switch语句的时间与case的数量无关。当case数据量比较多,并且值得取值范围较小时就会使用jump table。

  1. 循环:汇编中没有相应的循环指令,将条件测试和跳转组合起来可以实现循环的效果

10.一个过程调用包括将数据和控制从代码的一部分传递到另一部分。另外,它还必须在进入时为过程的局部变量分配空间,并在退出时释放这些空间。大多数机器,包括 IA32,只提供转移控制到过程和从过程转移出控制这种简单的指令。数据传递、局部变量的分配和释放通过操纵程序栈来实现。

所在小组

第五组

组内昵称

孙恒

心得

github.com/SHenry07/roadmap/blob/master/computer/csapp/basic.md

第一个问题: 请再看下c语言代码
函数内的第一行是long x = *xpxp是个指针直接放入内存中 所以"指令2从内存中读出x"
xp已经在寄存器%rdi中了
第二个问题: ** 请往前看两行
*
“一个字节的移位量是的移位量的编码范围可以达到28-1=255…移位量是由%cl寄存器的低m位决定的,这里2m=w, 所以例如当寄存器%cl的十六进制值为0xFF时…”
所以我的理解是: char的后缀是b会移动7位即对应salb 其他同理,毕竟后缀是什么是看type的

所在小组

第六组

组内昵称

慎思明辨笃行

你的心得体会

读书内容

程序的机器表示 章节 3.8~3.12

3.8 数组访问与分配

数组定义

数组是某种基本数据类型数据的集合

对于数据类型 T 和整型常数 N,数组的声明如下:


T A[N]

上面的 A 称为数组名称。它有两个效果:

①、它在存储器中分配一个 L*N 字节的连续区域,这里 L 是数据类型 T 的大小(单位为字节)

②、A 作为指向数组开头的指针,如果分配的连续区域的起始地址为 xa,那么这个指针的值就是xa

即当用 A[i] 去读取数组元素的时候,其实我们访问的是 xa+i*sizeof(T)。sizeof(T)是获得数据类型T的占用内存大小,以字节为单位,比如如果T为int,那么sizeof(int)就是4。因为数组的下标是从0开始的,当 i等于0时,我们访问的地址就是 xa


package main

import "fmt"

func test1() {

a := [10]int{}

for i := 0; i < 10; i++ {

fmt.Println(i, &a[i])

}

}

运行结果:


0 0xc0000e4000

1 0xc0000e4008

2 0xc0000e4010

3 0xc0000e4018

4 0xc0000e4020

5 0xc0000e4028

6 0xc0000e4030

7 0xc0000e4038

8 0xc0000e4040

9 0xc0000e4048

数组嵌套


package main

import "fmt"

func test2() {

a := [5][5]int{}

for i := 0; i < 5; i++ {

for j := 0; j < 5; j++ {

fmt.Println(i, j, &a[i][j])

}

}

}

运行结果:


0 0 0xc0000e4000

0 1 0xc0000e4008

0 2 0xc0000e4010

0 3 0xc0000e4018

0 4 0xc0000e4020

1 0 0xc0000e4028

1 1 0xc0000e4030

1 2 0xc0000e4038

1 3 0xc0000e4040

1 4 0xc0000e4048

2 0 0xc0000e4050

2 1 0xc0000e4058

2 2 0xc0000e4060

2 3 0xc0000e4068

2 4 0xc0000e4070

3 0 0xc0000e4078

3 1 0xc0000e4080

3 2 0xc0000e4088

3 3 0xc0000e4090

3 4 0xc0000e4098

4 0 0xc0000e40a0

4 1 0xc0000e40a8

4 2 0xc0000e40b0

4 3 0xc0000e40b8

4 4 0xc0000e40c0

3.9 异质的数据结构

  • 结构(structure),用关键词struct来声明,将多个对象集合到一个单位中

  • 联合(union),用关键词union来声明,允许用几种不同的类型来引用一个对象


const (

N = 16

)

type fix_matrix [N][N]int

type People struct {

Name string

Sex string

Age int

Address string

}

type Student struct {

People

Attribute int

}

3.10 在机器级程序中将控制与数据结合起来

  • 指针是C语言的一个核心特色,它提供了一种统一方式,对不同数据结构中的元素产生引用。

  • 每个指针都对应一个类型,void *类型代表通用指针,它通过强制或隐式类型转换变成一个有类型的指针。

  • 每个指针都有一个值,NULL(0)值表示指针没有指向任何地方。

  • 指针用&操作符创建。

  • *操作符用于间接引用指针。

  • 数组与指针紧密联系。

  • 将指针从一种类型强制转换为另一种类型,只改变它的类型而不改变它的值,效果只是改变指针- 运算的伸缩。

  • 指针也可以指向函数。


package main

import "net/http"

type starInt *int

type handler *func(http.ResponseWriter, *http.Request)

3.11 浮点代码

处理器的浮点体系结构包括多个方面,会影响对浮点数据操作的程序如何被映射到机器上,包括:

1) 如何存储和访问浮点数据。通常是通过某种寄存器方式来完成。

2) 对浮点数据操作的指令。

3) 想函数传递浮点数参数和从函数返回浮点数结构的规则。

4) 函数调用过程保持寄存器的规则——例如,一些寄存器被指定为调用者保存,而其他的被指定为被调用者保存。


package main

func float_mov(v1 float32, src, dest *float32) float32 {

v2 := *src

dest = &v1

return v2

}

把浮点值转换成整数时,指令会执行截断(truncation)

所在小组

第六组

组内昵称

之昂

心得体会

  • 指针运算:
    C语言允许对指针进行运算,值会根据该指针引用的数据类型的大小进行伸缩。但操作数操作符‘&’和‘*’可以产生指针和间接引用指针。对于一个表示某个现象的表达式Expr,&Expr是给出该对象地址的一个指针。对于一个表示地址的表达式AExpr, 该地址值和&Expr是等价的。

  • 变长数组:
    c语言只支持大小在编译时就能确定的多维数组,需要变长数组时不得不是用malloc和calloc这样的函数为这些数组分配存储空间,而且必须显示编码,用行有限索引将多为数组映射到一维数组。

  • 异质的数据结构
    c语言提供了两种将不同类型的对象组合到一起创建数据类型的机制:结构(structure),用关键字struct来声明,将多个对象集合到一个单位中;联合(union),用关键字union声明,允许几个不同的类型来引用一个对象。

  • 指针:

    指针是c语言的一个特色,以一种统一方式,对不同的数据结构中的元素产生引用:

    1. 每个指针都对应一个类型;
    2. 每个指针都有一个值;
    3. 指针用&运算符创建;
    4. *操作符用于间接引用指针;
    5. 数组与指针联系紧密;
    6. 将指针从一种数据类型强制转换为另一种,只改变他的类型,不改变值;
    7. 指针可以指向函数。

[quote=“feichenxue, post:51, topic:1064, full:true”]

所在小组

第六组

组内昵称

蒋权

你的心得体会

  • 汇编代码表示非常接近于机器代码。与机器代码的二进制格式相比,汇编代码的主要特点是它用可读性更好的文本格式表示,能够理解汇编代码以及它与原始代码的联系,是理解九三级如何执行程序的关键一步。x96-64的机器代码和原始的C代码差别非常大。一些通常对C语言程序员隐藏的处理器状态都是可见的:
  1. 程序计数器(通常称为“PC”,在x86-64中用%rip表示)给出将要执行的下一条指令在内存中的地址。

  2. 整数寄存器文件包含16个命名的位置,分别存储64位的值。这些寄存器可以存储地址(对应于C语言的指针)或整数数据。有的寄存器被用来记录某些重要的程序状态,而其他的寄存器用来保存临时数据,例如过程的参数和局部变量,以及函数的返回值。

  3. 条件码寄存器保存着最近执行的算术或逻辑指令的状态信息。它们用来实现控制或数据流中的条件变化,比如说用来实现if和while语旬。

  4. 一组向量寄存器可以存放一个或多个整数或浮点数值。

  • 操作数被分为三种类型:立即数、寄存器和内存引用。
  • 条件码

除了整数寄存器, CPU还维护着一组单个位的条件码(condition code)寄存器,它们描述了最近的算术或逻辑操作的属性。可以检测这些寄存器来执行条件分支指令。最常用的条件码有:

1.CF:进位标志。最近的操作使最高位产生了进位。可用来检查无符号操作的溢出。
2.ZF:零标志。最近的操作得出的结果为0。
3.SF:符号标志。最近的操作得到的结果为负数。
4.OF:溢出标志。最近的操作导致一个补码溢出一正溢出或负溢出。
对于逻辑操作,例如xoR,进位标志和溢出标志会设置成0。对于移位操作,进位标志将设置为最后一个被移出的位,而溢出标志设置为0。INC和DEC指令会设置溢出和零标志,但是不会改变进位标志。