From 931847828060842fb3ce114f24e454690db372f3 Mon Sep 17 00:00:00 2001 From: chai2010 Date: Sat, 19 May 2018 08:13:11 +0800 Subject: [PATCH 1/5] =?UTF-8?q?ch3:=20=E5=89=8D=E8=A8=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SUMMARY.md | 2 +- ch3-asm/readme.md | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/SUMMARY.md b/SUMMARY.md index 1e53f03..aeb9335 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -22,7 +22,7 @@ * [2.10. Go实现Python模块](ch2-cgo/ch2-10-py-module.md) * [2.11. 编译和链接参数](ch2-cgo/ch2-11-link.md) * [2.12. 补充说明](ch2-cgo/ch2-12-faq.md) -* [第三章 汇编语言(TODO)](ch3-asm/readme.md) +* [第三章 汇编语言](ch3-asm/readme.md) * [3.1. 汇编基础(TODO)](ch3-asm/ch3-01-basic.md) * [3.2. 控制流(TODO)](ch3-asm/ch3-02-control-flow.md) * [3.3. 包变量(TODO)](ch3-asm/ch3-03-var.md) diff --git a/ch3-asm/readme.md b/ch3-asm/readme.md index 0734dba..eda2cde 100644 --- a/ch3-asm/readme.md +++ b/ch3-asm/readme.md @@ -1,3 +1,10 @@ -# 第三章 Go汇编语言(TODO) +# 第三章 Go汇编语言 + +Go语言中很多设计思想和工具都是传承自Plan9操作系统,Go汇编语言也是基于Plan9汇编演化而来。根据Rob Pike的介绍,大神Ken Thompson在1986年为Plan9系统编写的C语言编译器输出的汇编伪代码就是Plan9汇编的前身。所谓的Plan9汇编语言只是便于以手工方式书写该C语言编译器输出的汇编伪代码而已。 + +无论高级语言如何发展,作为最接近CPU的汇编语言的地方依然是无法彻底被替代的。只有通过汇编语言才能彻底挖掘CPU芯片的全部功能,因此操作系统的引导过程必须要依赖汇编语言的帮助。只有通过汇编语言才能彻底榨干CPU芯片的性能,因此很多底层的加密解密等对性能敏感的算法会考虑通过汇编语言进行性能优化。 + +对于每一个严肃的Gopher,Go汇编语言都是一个不可忽视的技术。因为哪怕只懂一点点汇编,也便于更好地理解计算机,将更容易理解Go语言中动态栈/接口等高级特性的实现原理。而且掌握了Go汇编语言之后,你将不用担心再被其它所谓的任何高级编程语言用户鄙视。 + +本章我们将简单地探讨Go汇编语言的基础用法。 -TODO From d3f661f67e4e45d2be4769d199b4a8defaacc278 Mon Sep 17 00:00:00 2001 From: chai2010 Date: Sat, 19 May 2018 16:35:10 +0800 Subject: [PATCH 2/5] =?UTF-8?q?ch3-01:=20=E5=BC=80=E5=A7=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SUMMARY.md | 2 +- ch3-asm/ch3-01-basic.md | 118 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 117 insertions(+), 3 deletions(-) diff --git a/SUMMARY.md b/SUMMARY.md index aeb9335..a7cac23 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -23,7 +23,7 @@ * [2.11. 编译和链接参数](ch2-cgo/ch2-11-link.md) * [2.12. 补充说明](ch2-cgo/ch2-12-faq.md) * [第三章 汇编语言](ch3-asm/readme.md) - * [3.1. 汇编基础(TODO)](ch3-asm/ch3-01-basic.md) + * [3.1. 快速入门(Doing)](ch3-asm/ch3-01-basic.md) * [3.2. 控制流(TODO)](ch3-asm/ch3-02-control-flow.md) * [3.3. 包变量(TODO)](ch3-asm/ch3-03-var.md) * [3.4. 叶子函数(TODO)](ch3-asm/ch3-04-leaf-func.md) diff --git a/ch3-asm/ch3-01-basic.md b/ch3-asm/ch3-01-basic.md index a04ee5a..a16a08e 100644 --- a/ch3-asm/ch3-01-basic.md +++ b/ch3-asm/ch3-01-basic.md @@ -1,3 +1,117 @@ -# 3.1. 汇编基础(TODO) +# 3.1. 快速入门 + +在第一章的“Hello, World 的革命”一节中,我们已经见过一个Go汇编程序。本节我们将通过由浅入深的一系列小例子来快速掌握Go汇编的简单用法。 + +## Go语言版本 + +```go +package pkg + +var helloworld = "Hello World!" + +func HelloWorld() { + println(helloworld) +} +``` + +## Go汇编版本 + +``` +#include "textflag.h" + +// var helloworld string +GLOBL ·helloworld(SB),NOPTR,$32 // var helloworld [32]byte + DATA ·helloworld+0(SB)/8,$·helloworld+16(SB) // StringHeader.Data + DATA ·helloworld+8(SB)/8,$12 // StringHeader.Len + DATA ·helloworld+16(SB)/8,$"Hello Wo" // ...string data... + DATA ·helloworld+24(SB)/8,$"rld!" // ...string data... + +// func HelloWorld() +TEXT ·HelloWorld(SB), $16-0 + MOVQ ·helloworld+0(SB), AX; MOVQ AX, 0(SP) + MOVQ ·helloworld+8(SB), BX; MOVQ BX, 8(SP) + CALL runtime·printstring(SB) + CALL runtime·printnl(SB) + RET +``` + +## 汇编语法 + +- 变量要在Go语言中声明, 但不能赋值 +- 函数要在Go语言中声明, 但不包含函数实现 + +- Go语言中的标识符x对应汇编语言中的·x + +- GLOBL: 定义全局标识符, 分配内存空间 +- DATA: 初始化对应内存空间 +- TEXT: 定义函数 + +## 字符串的结构 + +```go +var helloworld string // 只能声明, 不能赋值 +``` + +``` +// +---------------------------+ ·helloworld+0(SB) +// | reflect.StringHeader.Data | ----------\ $·helloworld+16(SB) +// +---------------------------+ | +// | reflect.StringHeader.Len | | +// +---------------------------+ <---------/ ·helloworld+16(SB) +// | "Hello World!" | +// +---------------------------+ +``` + +- 字符串的数据紧挨字符串头结构体 +- $·helloworld+16(SB) 表示符号地址 +- ·helloworld+16(SB) 表示符号地址内的数据 + +## HelloWorld函数 + +```go +func HelloWorld() // 只能声明, 不能定义 +``` + +``` +TEXT ·HelloWorld(SB), $16-0 + MOVQ ·helloworld+0(SB), AX; MOVQ AX, 0(SP) + MOVQ ·helloworld+8(SB), BX; MOVQ BX, 8(SP) + CALL runtime·printstring(SB) + CALL runtime·printnl(SB) + RET +``` + +- $16-0中的16: 表示函数内部有16字节用于局部变量 +- $16-0中的0: 表示函数参数和返回值总大小为0 + +- printstring的参数类型为StringHeader +- 0(SP)为StringHeader.Data +- 8(SP)为StringHeader.Len + +## 简化: 在Go中定义变量 + +```go +var helloworld string = "你好, 中国!" + +func HelloWorld() +``` + +``` +TEXT ·HelloWorld(SB), $16-0 + MOVQ ·helloworld+0(SB), AX; MOVQ AX, 0(SP) + MOVQ ·helloworld+8(SB), BX; MOVQ BX, 8(SP) + CALL runtime·printstring(SB) + CALL runtime·printnl(SB) + RET +``` + +- 汇编定义变量没有太多优势, 性价比较低 +- 汇编的优势是挖掘芯片的功能和性能 + +## 没有分号 + +- 分号用于分隔多个汇编语句 +- 行末尾自动添加分号 + + -TODO From 32865f9369b50511e222493e18a3b160277126fb Mon Sep 17 00:00:00 2001 From: chai2010 Date: Sun, 20 May 2018 08:09:39 +0800 Subject: [PATCH 3/5] =?UTF-8?q?ch3:=20=E7=9B=AE=E5=BD=95=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SUMMARY.md | 20 +++++++++++++------- ch3-asm/ch3-01-basic.md | 4 ++++ ch3-asm/ch3-02-arch.md | 3 +++ ch3-asm/ch3-02-control-flow.md | 3 --- ch3-asm/ch3-03-const-and-var.md | 3 +++ ch3-asm/ch3-03-var.md | 3 --- ch3-asm/ch3-04-func.md | 3 +++ ch3-asm/ch3-04-leaf-func.md | 3 --- ch3-asm/ch3-05-control-flow.md | 3 +++ ch3-asm/ch3-05-more-stack.md | 3 --- ch3-asm/ch3-06-func-again.md | 3 +++ ch3-asm/ch3-06-gc.md | 3 --- ch3-asm/ch3-07-funcdata-pcdata.md | 3 +++ ch3-asm/ch3-07-pyrdown.md | 3 --- ch3-asm/ch3-08-c-preprocessors.md | 3 +++ ch3-asm/ch3-08-faq.md | 3 --- ch3-asm/ch3-09-core-type.md | 3 +++ ch3-asm/ch3-10-runtime-func.md | 5 +++++ ch3-asm/ch3-11-call-c-leaf-func.md | 3 +++ ch3-asm/ch3-12-avx-sse-jit.md | 3 +++ ch3-asm/ch3-13-arm.md | 3 +++ ch3-asm/ch3-14-faq.md | 3 +++ 22 files changed, 58 insertions(+), 28 deletions(-) create mode 100644 ch3-asm/ch3-02-arch.md delete mode 100644 ch3-asm/ch3-02-control-flow.md create mode 100644 ch3-asm/ch3-03-const-and-var.md delete mode 100644 ch3-asm/ch3-03-var.md create mode 100644 ch3-asm/ch3-04-func.md delete mode 100644 ch3-asm/ch3-04-leaf-func.md create mode 100644 ch3-asm/ch3-05-control-flow.md delete mode 100644 ch3-asm/ch3-05-more-stack.md create mode 100644 ch3-asm/ch3-06-func-again.md delete mode 100644 ch3-asm/ch3-06-gc.md create mode 100644 ch3-asm/ch3-07-funcdata-pcdata.md delete mode 100644 ch3-asm/ch3-07-pyrdown.md create mode 100644 ch3-asm/ch3-08-c-preprocessors.md delete mode 100644 ch3-asm/ch3-08-faq.md create mode 100644 ch3-asm/ch3-09-core-type.md create mode 100644 ch3-asm/ch3-10-runtime-func.md create mode 100644 ch3-asm/ch3-11-call-c-leaf-func.md create mode 100644 ch3-asm/ch3-12-avx-sse-jit.md create mode 100644 ch3-asm/ch3-13-arm.md create mode 100644 ch3-asm/ch3-14-faq.md diff --git a/SUMMARY.md b/SUMMARY.md index a7cac23..6155b8c 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -24,13 +24,19 @@ * [2.12. 补充说明](ch2-cgo/ch2-12-faq.md) * [第三章 汇编语言](ch3-asm/readme.md) * [3.1. 快速入门(Doing)](ch3-asm/ch3-01-basic.md) - * [3.2. 控制流(TODO)](ch3-asm/ch3-02-control-flow.md) - * [3.3. 包变量(TODO)](ch3-asm/ch3-03-var.md) - * [3.4. 叶子函数(TODO)](ch3-asm/ch3-04-leaf-func.md) - * [3.5. 动态栈(TODO)](ch3-asm/ch3-05-more-stack.md) - * [3.6. 垃圾回收(TODO)](ch3-asm/ch3-06-gc.md) - * [3.7. 例子: 图像降采样(TODO)](ch3-asm/ch3-07-pyrdown.md) - * [3.8. 补充说明(TODO)](ch3-asm/ch3-08-faq.md) + * [3.2. 冯·诺伊曼计算机(TODO)](ch3-asm/ch3-02-arch.md) + * [3.3. 常量和变量(TODO)](ch3-asm/ch3-03-const-and-var.md) + * [3.4. 函数(TODO)](ch3-asm/ch3-04-func.md) + * [3.5. 控制流(TODO)](ch3-asm/ch3-05-control-flow.md) + * [3.6. 再论函数(TODO)](ch3-asm/ch3-06-func-again.md) + * [3.7. FUNCDATA和PCDATA(TODO)](ch3-asm/ch3-07-funcdata-pcdata.md) + * [3.8. C预处理器(TODO)](ch3-asm/ch3-08-c-preprocessors.md) + * [3.9. Go核心对象结构(TODO)](ch3-asm/ch3-09-core-type.md) + * [3.10. runtime内置函数(TODO)](ch3-asm/ch3-10-runtime-func.md) + * [3.11. 调用C函数(TODO)](ch3-asm/ch3-11-call-c-leaf-func.md) + * [3.12. AVX/SSE/JIT高级优化(TODO)](ch3-asm/ch3-12-avx-sse-jit.md) + * [3.13. ARM汇编(TODO)](ch3-asm/ch3-13-arm.md) + * [3.14. 补充说明(TODO)](ch3-asm/ch3-14-faq.md) * [第四章 移动平台(TODO)](ch4-mobile/readme.md) * [第六章 Go和Web](ch6-web/readme.md) * [6.1. Web开发简介](ch6-web/ch6-01-introduction.md) diff --git a/ch3-asm/ch3-01-basic.md b/ch3-asm/ch3-01-basic.md index a16a08e..a9c7b69 100644 --- a/ch3-asm/ch3-01-basic.md +++ b/ch3-asm/ch3-01-basic.md @@ -108,6 +108,10 @@ TEXT ·HelloWorld(SB), $16-0 - 汇编定义变量没有太多优势, 性价比较低 - 汇编的优势是挖掘芯片的功能和性能 +## 特殊字符 + +TODO + ## 没有分号 - 分号用于分隔多个汇编语句 diff --git a/ch3-asm/ch3-02-arch.md b/ch3-asm/ch3-02-arch.md new file mode 100644 index 0000000..9f440c9 --- /dev/null +++ b/ch3-asm/ch3-02-arch.md @@ -0,0 +1,3 @@ +# 3.2. 冯·诺伊曼 计算机(TODO) + +TODO diff --git a/ch3-asm/ch3-02-control-flow.md b/ch3-asm/ch3-02-control-flow.md deleted file mode 100644 index 9278cc2..0000000 --- a/ch3-asm/ch3-02-control-flow.md +++ /dev/null @@ -1,3 +0,0 @@ -# 3.2. 控制流(TODO) - -TODO diff --git a/ch3-asm/ch3-03-const-and-var.md b/ch3-asm/ch3-03-const-and-var.md new file mode 100644 index 0000000..7890044 --- /dev/null +++ b/ch3-asm/ch3-03-const-and-var.md @@ -0,0 +1,3 @@ +# 3.2. 常量和变量(TODO) + +TODO diff --git a/ch3-asm/ch3-03-var.md b/ch3-asm/ch3-03-var.md deleted file mode 100644 index 3e6272e..0000000 --- a/ch3-asm/ch3-03-var.md +++ /dev/null @@ -1,3 +0,0 @@ -# 3.3. 包变量(TODO) - -TODO diff --git a/ch3-asm/ch3-04-func.md b/ch3-asm/ch3-04-func.md new file mode 100644 index 0000000..08a0efd --- /dev/null +++ b/ch3-asm/ch3-04-func.md @@ -0,0 +1,3 @@ +# 3.4. 函数(TODO) + +TODO diff --git a/ch3-asm/ch3-04-leaf-func.md b/ch3-asm/ch3-04-leaf-func.md deleted file mode 100644 index 67c3d1a..0000000 --- a/ch3-asm/ch3-04-leaf-func.md +++ /dev/null @@ -1,3 +0,0 @@ -# 3.4. 叶子函数(TODO) - -TODO diff --git a/ch3-asm/ch3-05-control-flow.md b/ch3-asm/ch3-05-control-flow.md new file mode 100644 index 0000000..f0e8d3b --- /dev/null +++ b/ch3-asm/ch3-05-control-flow.md @@ -0,0 +1,3 @@ +# 3.5. 控制流(TODO) + +TODO diff --git a/ch3-asm/ch3-05-more-stack.md b/ch3-asm/ch3-05-more-stack.md deleted file mode 100644 index b980e66..0000000 --- a/ch3-asm/ch3-05-more-stack.md +++ /dev/null @@ -1,3 +0,0 @@ -# 3.5. 动态栈(TODO) - -TODO diff --git a/ch3-asm/ch3-06-func-again.md b/ch3-asm/ch3-06-func-again.md new file mode 100644 index 0000000..be92c39 --- /dev/null +++ b/ch3-asm/ch3-06-func-again.md @@ -0,0 +1,3 @@ +# 3.6. 再论函数(TODO) + +TODO diff --git a/ch3-asm/ch3-06-gc.md b/ch3-asm/ch3-06-gc.md deleted file mode 100644 index 6b7748a..0000000 --- a/ch3-asm/ch3-06-gc.md +++ /dev/null @@ -1,3 +0,0 @@ -# 3.6. 垃圾回收(TODO) - -TODO diff --git a/ch3-asm/ch3-07-funcdata-pcdata.md b/ch3-asm/ch3-07-funcdata-pcdata.md new file mode 100644 index 0000000..85044dc --- /dev/null +++ b/ch3-asm/ch3-07-funcdata-pcdata.md @@ -0,0 +1,3 @@ +# 3.6. FUNCDATA和PCDATA(TODO) + +TODO diff --git a/ch3-asm/ch3-07-pyrdown.md b/ch3-asm/ch3-07-pyrdown.md deleted file mode 100644 index 4cde048..0000000 --- a/ch3-asm/ch3-07-pyrdown.md +++ /dev/null @@ -1,3 +0,0 @@ -# 3.7. 例子: 图像降采样(TODO) - -TODO diff --git a/ch3-asm/ch3-08-c-preprocessors.md b/ch3-asm/ch3-08-c-preprocessors.md new file mode 100644 index 0000000..15cb2ae --- /dev/null +++ b/ch3-asm/ch3-08-c-preprocessors.md @@ -0,0 +1,3 @@ +# 3.8. C预处理器(TODO) + +TODO diff --git a/ch3-asm/ch3-08-faq.md b/ch3-asm/ch3-08-faq.md deleted file mode 100644 index f89b61a..0000000 --- a/ch3-asm/ch3-08-faq.md +++ /dev/null @@ -1,3 +0,0 @@ -# 3.8. 补充说明(TODO) - -TODO diff --git a/ch3-asm/ch3-09-core-type.md b/ch3-asm/ch3-09-core-type.md new file mode 100644 index 0000000..c5ee6a5 --- /dev/null +++ b/ch3-asm/ch3-09-core-type.md @@ -0,0 +1,3 @@ +# 3.9. Go核心对象结构(TODO) + +TODO diff --git a/ch3-asm/ch3-10-runtime-func.md b/ch3-asm/ch3-10-runtime-func.md new file mode 100644 index 0000000..59c32d0 --- /dev/null +++ b/ch3-asm/ch3-10-runtime-func.md @@ -0,0 +1,5 @@ +# 3.10. runtime内置函数(TODO) + +TODO + + diff --git a/ch3-asm/ch3-11-call-c-leaf-func.md b/ch3-asm/ch3-11-call-c-leaf-func.md new file mode 100644 index 0000000..893a4d6 --- /dev/null +++ b/ch3-asm/ch3-11-call-c-leaf-func.md @@ -0,0 +1,3 @@ +# 3.11. 调用C函数(TODO) + +TODO diff --git a/ch3-asm/ch3-12-avx-sse-jit.md b/ch3-asm/ch3-12-avx-sse-jit.md new file mode 100644 index 0000000..5933285 --- /dev/null +++ b/ch3-asm/ch3-12-avx-sse-jit.md @@ -0,0 +1,3 @@ +# 3.12. AVX/SSE/JIT高级优化(TODO) + +TODO diff --git a/ch3-asm/ch3-13-arm.md b/ch3-asm/ch3-13-arm.md new file mode 100644 index 0000000..67c94f4 --- /dev/null +++ b/ch3-asm/ch3-13-arm.md @@ -0,0 +1,3 @@ +# 3.14. ARM汇编(TODO) + +TODO diff --git a/ch3-asm/ch3-14-faq.md b/ch3-asm/ch3-14-faq.md new file mode 100644 index 0000000..883844f --- /dev/null +++ b/ch3-asm/ch3-14-faq.md @@ -0,0 +1,3 @@ +# 3.14. 补充说明(TODO) + +TODO From 537decf3bde118930f7803c495275464f43d2f9c Mon Sep 17 00:00:00 2001 From: chai2010 Date: Mon, 21 May 2018 06:51:38 +0800 Subject: [PATCH 4/5] =?UTF-8?q?ch3-01:=20=E5=AE=8C=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ch3-asm/ch3-01-basic.md | 104 +++++++++++++++++- ch3-asm/pkg.go | 3 + ch3-asm/readme.md | 2 +- examples/ch3-01-quick-guide/id-01/pkg.go | 3 + examples/ch3-01-quick-guide/id-01/runme.go | 9 ++ examples/ch3-01-quick-guide/id-02/pkg.go | 3 + examples/ch3-01-quick-guide/id-02/pkg_amd64.s | 11 ++ examples/ch3-01-quick-guide/id-02/runme.go | 11 ++ 8 files changed, 144 insertions(+), 2 deletions(-) create mode 100644 ch3-asm/pkg.go create mode 100644 examples/ch3-01-quick-guide/id-01/pkg.go create mode 100644 examples/ch3-01-quick-guide/id-01/runme.go create mode 100644 examples/ch3-01-quick-guide/id-02/pkg.go create mode 100644 examples/ch3-01-quick-guide/id-02/pkg_amd64.s create mode 100644 examples/ch3-01-quick-guide/id-02/runme.go diff --git a/ch3-asm/ch3-01-basic.md b/ch3-asm/ch3-01-basic.md index a9c7b69..fb447e6 100644 --- a/ch3-asm/ch3-01-basic.md +++ b/ch3-asm/ch3-01-basic.md @@ -1,6 +1,108 @@ # 3.1. 快速入门 -在第一章的“Hello, World 的革命”一节中,我们已经见过一个Go汇编程序。本节我们将通过由浅入深的一系列小例子来快速掌握Go汇编的简单用法。 +在第一章的“Hello, World 的革命”一节中,我们已经见过一个Go汇编程序。本节我们将通过分析简单的Go程序输出的汇编代码,然后照猫画虎用汇编实现一个简单的输出程序。 + +## 实现和声明 + +Go汇编语言并不是一个独立的语言,主要原因是因为Go汇编程序无法独立使用。Go汇编代码必须以Go包的方式被组织,同时包中至少要有一个Go语言文件。如果Go汇编代码中定义的变量和函数要被其它Go语言代码引用,还需要通过Go语言代码将汇编中定义的符号声明出来。用于变量的定义和函数的定义Go汇编文件类似于C语言中的.c文件。而用于导出汇编中定义符号的Go源文件类似于C语言的.h文件。 + +## 定义整数变量 + +为了简单,我们先用Go语言定义并赋值一个整数变量,然后查看生成的汇编代码。 + +创建pkg.go文件,内容如下: + +```go +package pkg + +var Id = 9527 +``` + +然后用以下命令查看的Go语言程序对应的伪汇编代码: + +``` +$ go tool compile -S pkg.go +"".Id SNOPTRDATA size=8 + 0x0000 37 25 00 00 00 00 00 00 '....... +``` + +输出的汇编比较简单,其中`"".Id`对应Id变量符号,变量的内存大小为8个字节。变量的初始化内容为`37 25 00 00 00 00 00 00`,对应十六进制格式的0x2537,对应十进制为9527。SNOPTRDATA是相关的标志,暂时忽略。 + +以上的内容只是目标文件对于的汇编,和Go汇编语言虽然相似当并不完全等价。Go语言官网自带了一个Go汇编语言的入门教程,地址在:https://golang.org/doc/asm。 + +Go汇编语言提供了DATA命令用于初始化变量,DATA命令的语法如下: + +``` +DATA symbol+offset(SB)/width, value +``` + +其中symbol为变量在汇编语言中对应的符号,offset是符号开始地址的偏移量,width是要初始化内存的宽度大小,value是要初始化的那天。其中当前包中Go语言定义的符号symbol,在汇编代码中对应`·symbol`,其中·为一个特殊的unicode符号。 + +采用以下命令可以给Id变量初始化为十六进制的0x2537,对应十进制的9527,常量需要以美元符号$开头表示: + +``` +DATA ·Id+0(SB)/1,$0x37 +DATA ·Id+1(SB)/1,$0x25 +``` + +变量定义好之后需要导出以共其它代码引用。Go汇编语言提供了GLOBL命令用于将符号导出: + +``` +GLOBL symbol(SB), width +``` + +其中symbol对应汇编中符号的名字,width为符号对应内存的大小。用以下命令将汇编中的·Id变量导出: + +``` +GLOBL ·Id, $8 +``` + +现在已经出版完成了用汇编定义一个整数变量的工作。 + +为了便于其它包使用该Id变量,我们还需要在Go代码中声明该变量,同时也给变量指定一个合适的类型。修改pkg.go的内容如下: + +```go +package pkg + +var Id int +``` + +表示声明一个一个int类型的Id变量。因为该变量已经在汇编中定义,因此Go语言部分只是声明变量,声明的变量不能含义初始化的操作。 + +完整的汇编代码在pkg_amd64.s中: + +``` +GLOBL ·Id(SB),$8 + +DATA ·Id+0(SB)/1,$0x37 +DATA ·Id+1(SB)/1,$0x25 +DATA ·Id+2(SB)/1,$0x00 +DATA ·Id+3(SB)/1,$0x00 +DATA ·Id+4(SB)/1,$0x00 +DATA ·Id+5(SB)/1,$0x00 +DATA ·Id+6(SB)/1,$0x00 +DATA ·Id+7(SB)/1,$0x00 +``` + +文件名pkg_amd64.s表示为AMD64环境下的汇编代码文件。 + +虽然pkg包改用汇编实现,但是用法和之前完全一样: + +```go +package main + +import pkg "pkg包的路径" + +func main() { + println(pkg.Id) +} +``` + +对于Go包的用户来说,用Go汇编语言或Go语言实现并无区别。 + +## 定义字符串变量 + +TODO ## Go语言版本 diff --git a/ch3-asm/pkg.go b/ch3-asm/pkg.go new file mode 100644 index 0000000..197a171 --- /dev/null +++ b/ch3-asm/pkg.go @@ -0,0 +1,3 @@ +package pkg + +var id int = 9527 diff --git a/ch3-asm/readme.md b/ch3-asm/readme.md index eda2cde..d33e4c5 100644 --- a/ch3-asm/readme.md +++ b/ch3-asm/readme.md @@ -6,5 +6,5 @@ Go语言中很多设计思想和工具都是传承自Plan9操作系统,Go汇 对于每一个严肃的Gopher,Go汇编语言都是一个不可忽视的技术。因为哪怕只懂一点点汇编,也便于更好地理解计算机,将更容易理解Go语言中动态栈/接口等高级特性的实现原理。而且掌握了Go汇编语言之后,你将不用担心再被其它所谓的任何高级编程语言用户鄙视。 -本章我们将简单地探讨Go汇编语言的基础用法。 +本章我们将以AMD64为主要开发环境,简单地探讨Go汇编语言的基础用法。 diff --git a/examples/ch3-01-quick-guide/id-01/pkg.go b/examples/ch3-01-quick-guide/id-01/pkg.go new file mode 100644 index 0000000..93bfda6 --- /dev/null +++ b/examples/ch3-01-quick-guide/id-01/pkg.go @@ -0,0 +1,3 @@ +package pkg + +var Id = 9527 diff --git a/examples/ch3-01-quick-guide/id-01/runme.go b/examples/ch3-01-quick-guide/id-01/runme.go new file mode 100644 index 0000000..e9b9965 --- /dev/null +++ b/examples/ch3-01-quick-guide/id-01/runme.go @@ -0,0 +1,9 @@ +// +build ignore + +package main + +import pkg "." + +func main() { + println(pkg.Id) +} diff --git a/examples/ch3-01-quick-guide/id-02/pkg.go b/examples/ch3-01-quick-guide/id-02/pkg.go new file mode 100644 index 0000000..8a207cb --- /dev/null +++ b/examples/ch3-01-quick-guide/id-02/pkg.go @@ -0,0 +1,3 @@ +package pkg + +var Id int diff --git a/examples/ch3-01-quick-guide/id-02/pkg_amd64.s b/examples/ch3-01-quick-guide/id-02/pkg_amd64.s new file mode 100644 index 0000000..09171c7 --- /dev/null +++ b/examples/ch3-01-quick-guide/id-02/pkg_amd64.s @@ -0,0 +1,11 @@ + +DATA ·Id+0(SB)/1,$0x37 +DATA ·Id+1(SB)/1,$0x25 +DATA ·Id+2(SB)/1,$0x00 +DATA ·Id+3(SB)/1,$0x00 +DATA ·Id+4(SB)/1,$0x00 +DATA ·Id+5(SB)/1,$0x00 +DATA ·Id+6(SB)/1,$0x00 +DATA ·Id+7(SB)/1,$0x00 + +GLOBL ·Id(SB),$8 diff --git a/examples/ch3-01-quick-guide/id-02/runme.go b/examples/ch3-01-quick-guide/id-02/runme.go new file mode 100644 index 0000000..c2fbf86 --- /dev/null +++ b/examples/ch3-01-quick-guide/id-02/runme.go @@ -0,0 +1,11 @@ +// +build ignore + +package main + +import ( + pkg "." +) + +func main() { + println(pkg.Id) +} From f26f0f255af83506e4b26c0614cea97ca7cbb5b7 Mon Sep 17 00:00:00 2001 From: chai2010 Date: Mon, 21 May 2018 18:31:38 +0800 Subject: [PATCH 5/5] ch03-1: done --- SUMMARY.md | 2 +- ch3-asm/ch3-01-basic.md | 241 +++++++++++------- examples/ch3-01-quick-guide/main-01/Makefile | 10 + examples/ch3-01-quick-guide/main-01/main.go | 5 + .../ch3-01-quick-guide/main-01/main_amd64.s | 7 + examples/ch3-01-quick-guide/str-01/pkg.go | 3 + examples/ch3-01-quick-guide/str-02/pkg.go | 5 + .../ch3-01-quick-guide/str-02/pkg_amd64.s | 8 + examples/ch3-01-quick-guide/str-02/runme.go | 14 + examples/ch3-01-quick-guide/str-03/pkg.go | 5 + .../ch3-01-quick-guide/str-03/pkg_amd64.s | 5 + examples/ch3-01-quick-guide/str-03/runme.go | 11 + 12 files changed, 226 insertions(+), 90 deletions(-) create mode 100644 examples/ch3-01-quick-guide/main-01/Makefile create mode 100644 examples/ch3-01-quick-guide/main-01/main.go create mode 100644 examples/ch3-01-quick-guide/main-01/main_amd64.s create mode 100644 examples/ch3-01-quick-guide/str-01/pkg.go create mode 100644 examples/ch3-01-quick-guide/str-02/pkg.go create mode 100644 examples/ch3-01-quick-guide/str-02/pkg_amd64.s create mode 100644 examples/ch3-01-quick-guide/str-02/runme.go create mode 100644 examples/ch3-01-quick-guide/str-03/pkg.go create mode 100644 examples/ch3-01-quick-guide/str-03/pkg_amd64.s create mode 100644 examples/ch3-01-quick-guide/str-03/runme.go diff --git a/SUMMARY.md b/SUMMARY.md index 6155b8c..e538bcb 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -23,7 +23,7 @@ * [2.11. 编译和链接参数](ch2-cgo/ch2-11-link.md) * [2.12. 补充说明](ch2-cgo/ch2-12-faq.md) * [第三章 汇编语言](ch3-asm/readme.md) - * [3.1. 快速入门(Doing)](ch3-asm/ch3-01-basic.md) + * [3.1. 快速入门](ch3-asm/ch3-01-basic.md) * [3.2. 冯·诺伊曼计算机(TODO)](ch3-asm/ch3-02-arch.md) * [3.3. 常量和变量(TODO)](ch3-asm/ch3-03-const-and-var.md) * [3.4. 函数(TODO)](ch3-asm/ch3-04-func.md) diff --git a/ch3-asm/ch3-01-basic.md b/ch3-asm/ch3-01-basic.md index fb447e6..74d6fc6 100644 --- a/ch3-asm/ch3-01-basic.md +++ b/ch3-asm/ch3-01-basic.md @@ -102,122 +102,185 @@ func main() { ## 定义字符串变量 -TODO +在前一个例子中,我们通过汇编定义了一个整数变量。现在我们尝试通过汇编定义一个字符串变量。 -## Go语言版本 +虽然从Go语言角度看,定义字符串和整数变量的写法基本相同,但是字符串底层却有着比单个整数更复杂的数据结构。 + + +创建pkg.go文件,内容如下: ```go package pkg -var helloworld = "Hello World!" +var Name = "gopher" +``` -func HelloWorld() { - println(helloworld) +然后用以下命令查看的Go语言程序对应的伪汇编代码: + +``` +$ go tool compile -S pkg.go +go.string."gopher" SRODATA dupok size=6 + 0x0000 67 6f 70 68 65 72 gopher +"".Name SDATA size=16 + 0x0000 00 00 00 00 00 00 00 00 06 00 00 00 00 00 00 00 ................ + rel 0+8 t=1 go.string."gopher"+0 +``` + +输出中出现了一个新的符号go.string."gopher",根据其长度和内容分析可以猜测是对应底层的"gopher"字符串数据。因为Go语言的字符串并不是值类型,Go字符串只是一中只读的引用类型。假设多个代码中出现了相同的"gopher"字符串时,程序链接后其实都是引用的同一个符号go.string."gopher"。因此,该符号有一个SRODATA标志表示这个数据在只读内存段,dupok表示出现多个相同符号时只保留一个就可以了。 + +而真正的Go字符串变量Name对应的大小却只有16个字节了。其实Name变量并没有直接对应“gopher”字符串,而是对应reflect.StringHeader结构体: + +```go +type reflect.StringHeader struct { + Data uintptr + Len int } ``` -## Go汇编版本 +从汇编角度看,Name变量其实对应的是reflect.StringHeader结构体类型。前8个字节对应底层真实字符串数据的指针,也就是符号go.string."gopher"对应的地址。后8个字节对应底层真实字符串数据的有效长度,这里是6个字节。 + +创建pkg_amd64.s文件,我们尝试通过汇编代码重新定义并初始化Name字符串: + +``` +GLOBL ·NameData(SB),$8 +DATA ·NameData(SB)/8,$"gopher" + +GLOBL ·Name(SB),$16 +DATA ·Name+0(SB)/8,$·NameData(SB) +DATA ·Name+8(SB)/8,$6 +``` + +因为在Go汇编语言中,go.string."gopher"不是一个合法的符号,我们无法手工创建(这是给编译器保留的部分特权,因为手工创建类似符号可能打破编译器输出代码的某些规则)。因此我们新创建了一个·NameData符号表示底层的字符串数据。 + +然后定义·Name符号为两个16字节,其中前8个字节用·NameData符号对应的地址初始化,后8个字节为常量6表示字符串长度。 + +通过以下代码测试输出Name变量: + +```go +package main + +import pkg "pkg包的路径" + +func main() { + println(pkg.Name) +} +``` + +在运行时将会产生类似以下错误: + +``` +pkgpath.NameData: missing Go //type information for global symbol: size 8 +``` + +提示汇编中定义的NameData符号没有类型信息。其实Go汇编语言中定义的数据并没有所谓的类型,每个符号只不过是对应一个内存而且。出现这种错误的原因是,Go语言的垃圾回收器在扫描NameData变量的时候,无法知晓该变量内部是否包含指针。因此,真正错误的原因并不是NameData没有类型,二是NameData变量没有标注是否会含有指针信息。 + +通过给NameData变量增加一个标志,表示其中不会包含指针数据可以修复该错误: ``` #include "textflag.h" -// var helloworld string -GLOBL ·helloworld(SB),NOPTR,$32 // var helloworld [32]byte - DATA ·helloworld+0(SB)/8,$·helloworld+16(SB) // StringHeader.Data - DATA ·helloworld+8(SB)/8,$12 // StringHeader.Len - DATA ·helloworld+16(SB)/8,$"Hello Wo" // ...string data... - DATA ·helloworld+24(SB)/8,$"rld!" // ...string data... +GLOBL ·NameData(SB),NOPTR,$8 +``` -// func HelloWorld() -TEXT ·HelloWorld(SB), $16-0 +通过给·NameData增加NOPTR,表示其中不含指针数据。那么垃圾回收器在遇到该变量的时候就会停止内部数据的扫描。 + +我们也可以通过给·NameData变量在Go语言中增加一个不含指针并且大小为8个字节的类型来修改该错误: + +```go +package pkg + +var NameData [8]byte +var Name string +``` + +我们将NameData声明为长度为8的字节数组。因为编译器可以通过类型分析出该变量不会包含指针,因此汇编代码中可以NOPTR标志信息。 + +在这个实现中,Name字符串底层其实引用的是NameData内存对应的“gopher”字符串数据。因此,如果NameData发生变化的化,Name字符串的数据也会跟着变化的。 + +```go +func main() { + println(pkg.Name) + + pkg.NameData[0] = '?' + println(pkg.Name) +} +``` + +当然这和字符串的只读定义是冲突的,正常的代码需要避免出现这种情况。最好的方法是不要导出内部的NameData变量,这样可以避免内部数据被无意破坏。 + +在用汇编定义字符串时,我们完全一个换一种思维:将底层的字符串数据和字符串头结构体定义在一起,这样可以避免引入NameData符号: + +``` +GLOBL ·Name(SB),$24 + +DATA ·Name+0(SB)/8,$·Name+16(SB) +DATA ·Name+8(SB)/8,$6 +DATA ·Name+16(SB)/8,$"gopher" +``` + +在新的结构中,Name符号对应的内存从16字节变为24字节,多出的8个字节用户存放底层的“gopher”字符串。·Name符号前16个字节依然对应reflect.StringHeader结构体:Data部分对应`$·Name+16(SB)`,表示数据的地址为Name符号往后偏移16个字节的位置;Len部分依然对应6个字节的长度。 + + +## 定义main函数 + +前面的例子已经展示的如何通过汇编定义整型和字符串类型变量。我们现在将尝试用汇编实现函数,然后输出一个字符串。 + +先创建main.go文件,创建并初始化字符串变量,同时声明main函数: + +```go +package main + +var helloworld = "你好, 世界" + +func main() +``` + +然后创建main_amd64.s文件,里面对应main函数的实现: + +``` +TEXT ·main(SB), $16-0 MOVQ ·helloworld+0(SB), AX; MOVQ AX, 0(SP) MOVQ ·helloworld+8(SB), BX; MOVQ BX, 8(SP) CALL runtime·printstring(SB) CALL runtime·printnl(SB) RET ``` +`TEXT ·main(SB), $16-0`用于定义`main`函数,其中`$16-0`表示`main`函数的帧大小是16个字节(对应string头的大小,用于给`runtime·printstring`函数传递参数),`0`表示`main`函数没有参数和返回值。`main`函数内部通过调用运行时内部的`runtime·printstring(SB)`函数来打印字符串。然后调用runtime·printnl打印换行符号。 -## 汇编语法 +Go语言函数在函数调用时,完全通过栈传递调用参数和返回值。先通过MOVQ指令,将helloworld对应的字符串头部结构体的16个字节复制到栈指针SP对应的16字节的空间,然后通过CALL指令调用对应函数。最后使用RET指令表示当前函数返回。 -- 变量要在Go语言中声明, 但不能赋值 -- 函数要在Go语言中声明, 但不包含函数实现 - -- Go语言中的标识符x对应汇编语言中的·x - -- GLOBL: 定义全局标识符, 分配内存空间 -- DATA: 初始化对应内存空间 -- TEXT: 定义函数 - -## 字符串的结构 - -```go -var helloworld string // 只能声明, 不能赋值 -``` - -``` -// +---------------------------+ ·helloworld+0(SB) -// | reflect.StringHeader.Data | ----------\ $·helloworld+16(SB) -// +---------------------------+ | -// | reflect.StringHeader.Len | | -// +---------------------------+ <---------/ ·helloworld+16(SB) -// | "Hello World!" | -// +---------------------------+ -``` - -- 字符串的数据紧挨字符串头结构体 -- $·helloworld+16(SB) 表示符号地址 -- ·helloworld+16(SB) 表示符号地址内的数据 - -## HelloWorld函数 - -```go -func HelloWorld() // 只能声明, 不能定义 -``` - -``` -TEXT ·HelloWorld(SB), $16-0 - MOVQ ·helloworld+0(SB), AX; MOVQ AX, 0(SP) - MOVQ ·helloworld+8(SB), BX; MOVQ BX, 8(SP) - CALL runtime·printstring(SB) - CALL runtime·printnl(SB) - RET -``` - -- $16-0中的16: 表示函数内部有16字节用于局部变量 -- $16-0中的0: 表示函数参数和返回值总大小为0 - -- printstring的参数类型为StringHeader -- 0(SP)为StringHeader.Data -- 8(SP)为StringHeader.Len - -## 简化: 在Go中定义变量 - -```go -var helloworld string = "你好, 中国!" - -func HelloWorld() -``` - -``` -TEXT ·HelloWorld(SB), $16-0 - MOVQ ·helloworld+0(SB), AX; MOVQ AX, 0(SP) - MOVQ ·helloworld+8(SB), BX; MOVQ BX, 8(SP) - CALL runtime·printstring(SB) - CALL runtime·printnl(SB) - RET -``` - -- 汇编定义变量没有太多优势, 性价比较低 -- 汇编的优势是挖掘芯片的功能和性能 ## 特殊字符 -TODO +Go语言函数或方法符号在编译为目标文件后,目标文件中的每个符号均包含对应包的觉得导入路径。因此目标文件的符号可能非常复杂,比如“path/to/pkg.(*SomeType).SomeMethod”或“go.string."abc"”。目标文件的符号名中不仅仅包含普通的字母,还可能包含诸多特殊字符。而Go语言的汇编器是从plan9移植过来的二把刀,并不能处理这些特殊的字符,导致了用Go汇编语言手工实现Go诸多特性时遇到种种限制。 + +Go汇编语言同样遵循Go语言少即是多的哲学,它只保留了最基本的特性:定义变量和全局函数。同时为了简化Go汇编器的词法扫描程序的实现,特别引入了Unicode中的中点`·`和大写的除法`/`,对应的Unicode码点为`U+00B7`和`U+2215`。汇编器编译后,中点`·`会被替换为ASCII中的点“.”,大写点除法会被替换为ASCII码中的除法“/”,比如`math/rand·Int`会被替换为`math/rand.Int`。这样可以将点和浮点数中的小数点、大写的除法和表达式中的除法符号分开,可以简化汇编程序此法分析部分的实现。 + +即使暂时抛开Go汇编语言设计取舍的问题,中点`·`和除法`/`两个字符的如何输入就是一个挑战。这两个字符在 https://golang.org/doc/asm 文档中均有描述,因此直接从该页面复制是最简单可靠的方式。 + +如果是macOS系统,则有以下几种方法输入中点`·`:在不开输入法时,可直接用 option+shift+9 输入;如果是自带的简体拼音输入法,输入左上角`~`键对应`·`,如果是自带的Unicode输入法,则可以输入对应的Unicode码点。 + ## 没有分号 -- 分号用于分隔多个汇编语句 -- 行末尾自动添加分号 +Go汇编语言中分号可以用于分隔同一行内的多个语句。下面是用分号混乱排版的汇编代码: - +``` +TEXT ·main(SB), $16-0; MOVQ ·helloworld+0(SB), AX; MOVQ ·helloworld+8(SB), BX; +MOVQ AX, 0(SP);MOVQ BX, 8(SP);CALL runtime·printstring(SB); +CALL runtime·printnl(SB); +RET; +``` +和Go语言一样,也可以省略行尾的分号。当遇到末尾时,汇编器会自动插入分号。下面是省略分号后的代码: + +``` +TEXT ·main(SB), $16-0 + MOVQ ·helloworld+0(SB), AX; MOVQ AX, 0(SP) + MOVQ ·helloworld+8(SB), BX; MOVQ BX, 8(SP) + CALL runtime·printstring(SB) + CALL runtime·printnl(SB) + RET +``` + +和Go语言一样,语句之间多个连续的空白字符和一个空格是等价的。 diff --git a/examples/ch3-01-quick-guide/main-01/Makefile b/examples/ch3-01-quick-guide/main-01/Makefile new file mode 100644 index 0000000..47a61b4 --- /dev/null +++ b/examples/ch3-01-quick-guide/main-01/Makefile @@ -0,0 +1,10 @@ +# Copyright 2018 . All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +default: + -go build -o a.out && ./a.out + -@rm a.out + +clean: + -@rm a.out diff --git a/examples/ch3-01-quick-guide/main-01/main.go b/examples/ch3-01-quick-guide/main-01/main.go new file mode 100644 index 0000000..3c37524 --- /dev/null +++ b/examples/ch3-01-quick-guide/main-01/main.go @@ -0,0 +1,5 @@ +package main + +var helloworld = "你好, 世界" + +func main() diff --git a/examples/ch3-01-quick-guide/main-01/main_amd64.s b/examples/ch3-01-quick-guide/main-01/main_amd64.s new file mode 100644 index 0000000..e675054 --- /dev/null +++ b/examples/ch3-01-quick-guide/main-01/main_amd64.s @@ -0,0 +1,7 @@ + +TEXT ·main(SB), $16-0 + MOVQ ·helloworld+0(SB), AX; MOVQ AX, 0(SP) + MOVQ ·helloworld+8(SB), BX; MOVQ BX, 8(SP) + CALL runtime·printstring(SB) + CALL runtime·printnl(SB) + RET diff --git a/examples/ch3-01-quick-guide/str-01/pkg.go b/examples/ch3-01-quick-guide/str-01/pkg.go new file mode 100644 index 0000000..3886146 --- /dev/null +++ b/examples/ch3-01-quick-guide/str-01/pkg.go @@ -0,0 +1,3 @@ +package pkg + +var Name = "gopher" diff --git a/examples/ch3-01-quick-guide/str-02/pkg.go b/examples/ch3-01-quick-guide/str-02/pkg.go new file mode 100644 index 0000000..72c0da0 --- /dev/null +++ b/examples/ch3-01-quick-guide/str-02/pkg.go @@ -0,0 +1,5 @@ +package pkg + +var NameData [8]byte + +var Name string diff --git a/examples/ch3-01-quick-guide/str-02/pkg_amd64.s b/examples/ch3-01-quick-guide/str-02/pkg_amd64.s new file mode 100644 index 0000000..a5306b2 --- /dev/null +++ b/examples/ch3-01-quick-guide/str-02/pkg_amd64.s @@ -0,0 +1,8 @@ + + +GLOBL ·NameData(SB),$8 +DATA ·NameData(SB)/8,$"gopher" + +GLOBL ·Name(SB),$16 +DATA ·Name+0(SB)/8,$·NameData(SB) +DATA ·Name+8(SB)/8,$6 diff --git a/examples/ch3-01-quick-guide/str-02/runme.go b/examples/ch3-01-quick-guide/str-02/runme.go new file mode 100644 index 0000000..b697f78 --- /dev/null +++ b/examples/ch3-01-quick-guide/str-02/runme.go @@ -0,0 +1,14 @@ +// +build ignore + +package main + +import ( + pkg "." +) + +func main() { + println(pkg.Name) + + pkg.NameData[0] = '?' + println(pkg.Name) +} diff --git a/examples/ch3-01-quick-guide/str-03/pkg.go b/examples/ch3-01-quick-guide/str-03/pkg.go new file mode 100644 index 0000000..72c0da0 --- /dev/null +++ b/examples/ch3-01-quick-guide/str-03/pkg.go @@ -0,0 +1,5 @@ +package pkg + +var NameData [8]byte + +var Name string diff --git a/examples/ch3-01-quick-guide/str-03/pkg_amd64.s b/examples/ch3-01-quick-guide/str-03/pkg_amd64.s new file mode 100644 index 0000000..a2ef15d --- /dev/null +++ b/examples/ch3-01-quick-guide/str-03/pkg_amd64.s @@ -0,0 +1,5 @@ +GLOBL ·Name(SB),$24 + +DATA ·Name+0(SB)/8,$·Name+16(SB) +DATA ·Name+8(SB)/8,$6 +DATA ·Name+16(SB)/8,$"gopher" diff --git a/examples/ch3-01-quick-guide/str-03/runme.go b/examples/ch3-01-quick-guide/str-03/runme.go new file mode 100644 index 0000000..c43ad67 --- /dev/null +++ b/examples/ch3-01-quick-guide/str-03/runme.go @@ -0,0 +1,11 @@ +// +build ignore + +package main + +import ( + pkg "." +) + +func main() { + println(pkg.Name) +}