当前位置:首页 » 《资源分享》 » 正文

曹大带我学 Go(3)—— 如何用汇编打同事的脸_梦醒人间

29 人参与  2021年06月09日 08:43  分类 : 《资源分享》  评论

点击全文阅读


你好,我是小X。

曹大最近开 Go 课程了,小X 正在和曹大学 Go。

这个系列会讲一些从课程中学到的让人醍醐灌顶的东西,拨云见日,带你重新认识 Go。

今天介绍几个常用的查看 Go 汇编代码、调试 Go 程序的命令和工具,既可以在平时和同事、网友抬杠时使用,还能在关键时刻打他们的脸。

比如,有同事说这段代码:

package main

type Student struct {
 Class int
}

func main() {
 var a = &Student{1}
 println(a)
}

的执行效率要高于下面这段代码:

package main

type Student struct {
 Class int
}

func main() {
 var a = Student{1}
 var b = &a
 println(b)
}

并且给你讲了一通道理,你好像没法辩赢他。怎么办?

直接用一行命令生成汇编代码,马上可以戳穿他,打他的脸。

go tool 生成汇编

其实很简单,有两个命令可以做到:

go tool compile -S main.go

和:

go build main.go && go tool objdump ./main

前者是编译,即将源代码编译成 .o 目标文件,并输出汇编代码。

后者是反汇编,即从可执行文件反编译成汇编,所以要先用 go build 命令编译出可执行文件。

二者不尽相同,但都能看到前面两个示例代码对应的汇编代码是一致的。同事的“谣言”不攻自破,脸都被你打疼了。

找到 runtime 源码

Go 是一门有 runtime 的语言,什么是 runtime?其实就是一段辅助程序,用户没有写的代码,runtime 替我们写了,比如 Go 调度器的代码。

我们只需要知道用 go 关键字创建 goroutine,就可以疯狂堆业务了。至于 goroutine 是怎么被调度的,根本不需要关心,这些是 runtime 调度器的工作。

那我们自己写的代码如何和 runtime 里的代码对应起来呢?

前面介绍的方法就可以做到,只需要加一个 grep 就可以。

例如,我想知道 go 关键字对应 runtime 里的哪个函数,于是写了一段测试代码:

package main

func main() {
 go func() {
  println(1+2)
 }()
}

因为 go func(){}() 那一行代码在第 4 行,所以,grep 的时候加一个条件:

go tool compile -S main.go | grep "main.go:4"

// 或

go build main.go && go tool objdump ./main | grep "main.go:4"
go func

马上就能看到 go func(){}() 对应 newproc() 函数,这时再深入研究下 newproc() 函数就大概知道 goroutine 是如何被创建的。

用 dlv 调试

那有同学问了,有没有其他可以调试 Go、以及和 Go 程序互动的方法呢?其实是有的!这就是我们要介绍的 dlv 调试工具,目前它对调试 Go 程序的支持是最好的。

之前没我怎么研究它,只会一些非常简单的命令,这次学会了几个进阶的指令,威力挺大,也进一步加深了对 Go 的理解。

下面我们带着一个任务来讲解 dlv 如何使用。

我们知道,向一个 nil 的 slice append 元素,不会有任何问题。但是向一个 nil 的 map 插入新元素,马上就会报 panic。这是为什么呢?又是在哪 panic 呢?

首先写出让 map 产生 panic 的示例程序:

package main

func main() {
 var m map[int]int
 m[1] = 1
}

接着用 go build 命令编译生成可执行文件:

go build a.go

然后,使用 dlv 进入调试状态:

dlv exec ./a

使用 b 这个命令打断点,有三种方法:

  1. b + 地址

  2. b + 代码行数

  3. b + 函数名

我们要在对 map 赋值的地方加个断点。先找到代码位置:

cat -n a.go

看到:

hello.go

赋值的地方在第 5 行,加断点:

(dlv) b a.go:5
Breakpoint 1 set at 0x45e55d for main.main() ./a.go:5

执行 c 命令,直接运行到断点处:

运行到断点处

执行 disass 命令,可以看到汇编指令:

disass

这时使用 si 命令,执行单条指令,多次执行 si,就会执行到 map 赋值函数 mapassign_fast64

mapassign_fast64

这时再用单步命令 s,就会进入判断 h 的值为 nil 的分支,然后执行 panic 函数:

panic

至此,向 nil 的 map 赋值时,产生 panic 的代码就被我们找到了。接着,按图索骥找到对应 runtime 源码的位置,就可以进一步探索了。

除此之外,我们还可以使用 bt 命令看到调用栈:

调用栈

使用 frame 1 命令可以跳转到相应位置。这里 1 对应图中的 a.go:5,也就是我们前面打断点的地方,是不是非常酷炫。

上面这张图里我们也能清楚地看到,用户 goroutine 其实是被 goexit 函数一路调用过来的。当用户 goroutine 执行完毕后,就会回到 goexit 函数做一些收尾工作。当然,这是题外话了。

另外,用 dlv 也能干第二部分“找到 runtime 源码”活。

总结

今天系统地讲了几招通过命令和工具查看用户代码对应的 runtime 源码或者汇编代码的方法,非常实用。最后再汇总一下:

  1. go tool compile

  2. go tool objdump

  3. dlv

使用这些命令和工具,可以让你在看 Go 源码的过程中事半功倍。

好了,这就是今天全部的内容了~ 我是小X,我们下期再见~


欢迎关注曹大的 TechPaper 以及码农桃花源~


点击全文阅读


本文链接:http://m.zhangshiyu.com/post/21280.html

代码  命令  汇编  
<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

最新文章

  • 清风明月无念秋(沈念秋谢敬闵)_清风明月无念秋沈念秋谢敬闵
  • 重生后,我逆光而行+后续+结局(沈墨寒陆瑶光)列表_重生后,我逆光而行+后续+结局
  • [老公为兼祧两房送我去拍喜,悔疯了]后续已完结_「陆镜白晚晚婆婆」反转剧情碎片化试读
  • (番外)+(全书)简宁傅淮京结局+番外(简宁傅淮京)_简宁傅淮京结局+番外免费_笔趣阁(简宁傅淮京)
  • (番外)+(结局)当爱恨如潮生(裴叙白乔若梨)_(当爱恨如潮生)列表_笔趣阁(裴叙白乔若梨)
  • 冯若琳杜伟(发现祠堂的秘密后,全村无一活口全书+结局)_冯若琳杜伟列表_笔趣阁(发现祠堂的秘密后,全村无一活口全书+结局)
  • 当爱恨如潮生在线赏析(裴叙白乔若梨)全书浏览_当爱恨如潮生在线赏析全书浏览
  • 流光易散尽成空全书+后续+结局(林叙白孟知雪)列表_流光易散尽成空全书+后续+结局(林叙白孟知雪)流光易散尽成空全书+后续+结局在线
  • 满级杀手重生!别惹,她是真大佬夕颜春言番外笔趣阁_满级杀手重生!别惹,她是真大佬夕颜春言番外笔趣阁
  • 沈朝朝沈廷禛宝藏文完本_完本沈朝朝沈廷禛宝藏文
  • (番外)+(全书)出国五年归来后,我为被拍卖小视频的妹妹点天灯_(陆昀昭秦半夏全书+后续)出国五年归来后,我为被拍卖小视频的妹妹点天灯列表_笔趣阁(陆昀昭秦半夏全书+后续)
  • 出国五年归来后,我为被拍卖小视频的妹妹点天灯全书+后续+结局(陆昀昭秦半夏)_出国五年归来后,我为被拍卖小视频的妹妹点天灯全书+后续+结局列表_笔趣阁(出国五年归来后,我为被拍卖小视频的妹妹点天灯全书+后续+结局)

    关于我们 | 我要投稿 | 免责申明

    Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1