Skip to content

Commit

Permalink
ch3: 规划图像编号
Browse files Browse the repository at this point in the history
  • Loading branch information
chai2010 committed Dec 14, 2018
1 parent 67ed19b commit bcf5605
Show file tree
Hide file tree
Showing 28 changed files with 26 additions and 26 deletions.
12 changes: 6 additions & 6 deletions ch3-asm/ch3-02-arch.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,9 @@

下图是某一层的任务:将输入数据的0剔除,非0的数据依次输出,右边部分是解决方案。

![](../images/ch3.2-1-arch-hsm-zero.jpg)
![](../images/ch3-1-arch-hsm-zero.jpg)

*图 3.2-1 人力资源机器*
*图 3-1 人力资源机器*


整个程序只有一个输入指令、一个输出指令和两个跳转指令共四个指令:
Expand All @@ -89,9 +89,9 @@ X86其实是是80X86的简称(后面三个字母),包括Intel 8086、80286

在使用汇编语言之前必须要了解对应的CPU体系结构。下面是X86/AMD架构图:

![](../images/ch3.2-2-arch-amd64-01.ditaa.png)
![](../images/ch3-2-arch-amd64-01.ditaa.png)

*图 3.2-2 AMD64架构*
*图 3-2 AMD64架构*


左边是内存部分是常见的内存布局。其中text一般对应代码段,用于存储要执行指令数据,代码段一般是只读的。然后是rodata和data数据段,数据段一般用于存放全局的数据,其中rodata是只读的数据段。而heap段则用于管理动态的数据,stack段用于管理每个函数调用时相关的数据。在汇编语言中一般重点关注text代码段和data数据段,因此Go汇编语言中专门提供了对应TEXT和DATA命令用于定义代码和数据。
Expand All @@ -107,9 +107,9 @@ Go汇编为了简化汇编代码的编写,引入了PC、FP、SP、SB四个伪

四个伪寄存器和X86/AMD64的内存和寄存器的相互关系如下图:

![](../images/ch3.2-3-arch-amd64-02.ditaa.png)
![](../images/ch3-3-arch-amd64-02.ditaa.png)

*图 3.2-3 Go汇编的伪寄存器*
*图 3-3 Go汇编的伪寄存器*


在AMD64环境,伪PC寄存器其实是IP指令计数器寄存器的别名。伪FP寄存器对应的是函数的帧指针,一般用来访问函数的参数和返回值。伪SP栈指针对应的是当前函数栈帧的底部(不包括参数和返回值部分),一般用于定位局部变量。伪SP是一个比较特殊的寄存器,因为还存在一个同名的SP真寄存器。真SP寄存器对应的是栈的顶部,一般用于定位调用其它函数的参数和返回值。
Expand Down
16 changes: 8 additions & 8 deletions ch3-asm/ch3-03-const-and-var.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,9 @@ DATA ·num+8(SB)/8,$0

下图是Go语句和汇编语句定义变量时的对应关系:

![](../images/ch3.3-1-pkg-var-decl-01.ditaa.png)
![](../images/ch3-4-pkg-var-decl-01.ditaa.png)

*图 3.3-1 变量定义*
*图 3-4 变量定义*


汇编代码中并不需要NOPTR标志,因为Go编译器会从Go语言语句声明的`[2]int`类型中推导出该变量内部没有指针数据。
Expand Down Expand Up @@ -174,9 +174,9 @@ Go汇编语言通常无法区分变量是否是浮点数类型,与之相关的

IEEE754标准中,最高位1bit为符号位,然后是指数位(指数为采用移码格式表示),然后是有效数部分(其中小数点左边的一个bit位被省略)。下图是IEEE754中float32类型浮点数的bit布局:

![](../images/ch3.3-2-ieee754.jpg)
![](../images/ch3-5-ieee754.jpg)

*图 3.3-2 IEEE754浮点数结构*
*图 3-5 IEEE754浮点数结构*


IEEE754浮点数还有一些奇妙的特性:比如有正负两个0;除了无穷大和无穷小Inf还有非数NaN;同时如果两个浮点数有序那么对应的有符号整数也是有序的(反之则不一定成立,因为浮点数中存在的非数是不可排序的)。浮点数是程序中最难琢磨的角落,因为程序中很多手写的浮点数字面值常量根本无法精确表达,浮点数计算涉及到的误差舍入方式可能也的随机的。
Expand Down Expand Up @@ -306,18 +306,18 @@ func makechan(chanType *byte, size int) (hchan chan any)

首先查看前面已经见过的`[2]int`类型数组的内存布局:

![](../images/ch3.3-3-pkg-var-decl-02.ditaa.png)
![](../images/ch3-6-pkg-var-decl-02.ditaa.png)

*图 3.3-3 变量定义*
*图 3-6 变量定义*


变量在data段分配空间,数组的元素地址依次从低向高排列。

然后再查看下标准库图像包中`image.Point`结构体类型变量的内存布局:

![](../images/ch3.3-4-pkg-var-decl-03.ditaa.png)
![](../images/ch3-7-pkg-var-decl-03.ditaa.png)

*图 3.3-4 结构体变量定义*
*图 3-7 结构体变量定义*


变量也时在data段分配空间,变量结构体成员的地址也是依次从低向高排列。
Expand Down
20 changes: 10 additions & 10 deletions ch3-asm/ch3-04-func.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ TEXT ·Swap(SB), NOSPLIT, $0

下图是Swap函数几种不同写法的对比关系图:

![](../images/ch3.4-1-func-decl-01.ditaa.png)
![](../images/ch3-8-func-decl-01.ditaa.png)

*图 3.4-1 函数定义*
*图 3-8 函数定义*


第一种是最完整的写法:函数名部分包含了当前包的路径,同时指明了函数的参数大小为32个字节(对应参数和返回值的4个int类型)。第二种写法则比较简洁,省略了当前包的路径和参数的大小。如果有NOSPLIT标注,会禁止汇编器为汇编函数插入栈分裂的代码。NOSPLIT对应Go语言中的`//go:nosplit`注释。
Expand Down Expand Up @@ -82,9 +82,9 @@ TEXT ·Swap(SB), $0-32

下图是Swap函数中参数和返回值在内存中的布局图:

![](../images/ch3.4-2-func-decl-02.ditaa.png)
![](../images/ch3-9-func-decl-02.ditaa.png)

*图 3.4-2 函数定义*
*图 3-9 函数定义*

下面的代码演示了如何在汇编函数中使用参数和返回值:

Expand Down Expand Up @@ -150,9 +150,9 @@ func Foo(FP *SomeFunc_args, FP_ret *SomeFunc_returns) {

Foo函数的参数和返回值的大小和内存布局:

![](../images/ch3.4-3-func-arg-01.ditaa.png)
![](../images/ch3-10-func-arg-01.ditaa.png)

*图 3.4-3 函数的参数*
*图 3-10 函数的参数*


下面的代码演示了Foo汇编函数参数和返回值的定位:
Expand Down Expand Up @@ -224,9 +224,9 @@ func Foo() {

下面是Foo函数的局部变量的大小和内存布局:

![](../images/ch3.4-4-func-local-var-01.ditaa.png)
![](../images/ch3-11-func-local-var-01.ditaa.png)

*图 3.4-4 函数的局部变量*
*图 3-11 函数的局部变量*


从图中可以看出Foo函数局部变量和前一个例子中参数和返回值的内存布局是完全一样的,这也是我们故意设计的结果。但是参数和返回值是通过伪FP寄存器定位的,FP寄存器对应第一个参数的开始地址(第一个参数地址较低),因此每个变量的偏移量是正数。而局部变量是通过伪SP寄存器定位的,而伪SP寄存器对应的是第一个局部变量的结束地址(第一个局部变量地址较大),因此每个局部变量的偏移量都是负数。
Expand Down Expand Up @@ -258,9 +258,9 @@ func sum(a, b int) int {

下图展示了三个函数逐级调用时内存中函数参数和返回值的布局:

![](../images/ch3.4-5-func-call-frame-01.ditaa.png)
![](../images/ch3-12-func-call-frame-01.ditaa.png)

*图 3.4-5 函数帧*
*图 3-12 函数帧*


为了便于理解,我们对真实的内存布局进行了简化。要记住的是调用函数时,被调用函数的参数和返回值内存空间都必须由调用者提供。因此函数的局部变量和为调用其它函数准备的栈空间总和就确定了函数帧的大小。调用其它函数前调用方要选择保存相关寄存器到栈中,并在调用函数返回后选择要恢复的寄存器进行保存。最终通过CALL指令调用函数的过程和调用我们熟悉的调用println函数输出的过程类似。
Expand Down
4 changes: 2 additions & 2 deletions ch3-asm/ch3-06-func-again.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@

和C语言函数不同,Go语言函数的参数和返回值完全通过栈传递。下面是Go函数调用时栈的布局图:

![](../images/ch3.6-1-func-stack-frame-layout-01.ditaa.png)
![](../images/ch3-13-func-stack-frame-layout-01.ditaa.png)

*图 3.6-1 函数调用参数布局*
*图 3-13 函数调用参数布局*


首先是调用函数前准备的输入参数和返回值空间。然后CALL指令将首先触发返回地址入栈操作。在进入到被调用函数内之后,汇编器自动插入了BP寄存器相关的指令,因此BP寄存器和返回地址是紧挨着的。再下面就是当前函数的局部变量的空间,包含再次调用其它函数需要准备的调用参数空间。被调用的函数执行RET返回指令时,先从栈恢复BP和SP寄存器,接着取出的返回地址跳转到对应的指令执行。
Expand Down
File renamed without changes
File renamed without changes
File renamed without changes.
File renamed without changes
File renamed without changes.
File renamed without changes
File renamed without changes.
File renamed without changes
File renamed without changes.
File renamed without changes
File renamed without changes.
File renamed without changes
File renamed without changes.
File renamed without changes
File renamed without changes
File renamed without changes.
File renamed without changes
File renamed without changes.
File renamed without changes
File renamed without changes.
File renamed without changes
File renamed without changes.

0 comments on commit bcf5605

Please sign in to comment.