没有消灭一切的银弹,也没有可以保证永不出错的程序。我们应当如何捕捉 Go 程序错误?我想同学们的第一反应是:打日志。
但错误日志的能力是有限的。第一,日志是开发者在代码中定义的打印信息,我们没法保证日志信息能包含所有的错误情况。第二,在 Go 程序中发生 panic 时,我们也并不总是能通过 recover 捕获(没法插入日志代码)。
那线上 Go 程序突然莫名崩溃后,当日志记录没有覆盖到错误场景时,还有别的方法排查吗?
core dump 又即核心转储,简单来说它就是程序意外终止时产生的内存快照。我们可以通过 core dump 文件来调式程序,找出其崩溃原因。
在 linux 平台上,可通过ulimit -c
命令查看核心转储配置,系统默认为 0,表明未开启 core dump 记录功能。
$ ulimit -c
0
可以使用ulimit -c [size]
命令指定记录 core dump 文件的大小,即是开启 core dump 记录。当然,如果电脑资源足够,避免 core dump 丢失或记录不全,也可执行ulimit -c unlimited
而不限制 core dump 文件大小。
那在 Go 程序中,如何开启 core dump 呢?
我们在[你真的懂string与[]byte的转换了吗] 一文中探讨过 string 转 []byte 的黑魔法,如下例所示。
package main
import (
"reflect"
"unsafe"
)
func String2Bytes(s string) []byte {
sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
bh := reflect.SliceHeader{
Data: sh.Data,
Len: sh.Len,
Cap: sh.Len,
}
return *(*[]byte)(unsafe.Pointer(&bh))
}
func Modify() {
a := "hello"
b := String2Bytes(a)
b[0] = 'H'
}
func main() {
Modify()
}
string 是不可以被修改的,当我们将 string 类型通过黑魔法转为 []byte 后,企图修改其值,程序会发生一个不能被 recover 捕获到的错误。
$ go run main.go
unexpected fault address 0x106a6a4
fatal error: fault
[signal SIGBUS: bus error code=0x2 addr=0x106a6a4 pc=0x105b01a]
goroutine 1 [running]:
runtime.throw({0x106a68b, 0x0})
/usr/local/go/src/runtime/panic.go:1198 +0x71 fp=0xc000092ee8 sp=0xc000092eb8 pc=0x102bad1
runtime.sigpanic()
/usr/local/go/src/runtime/signal_unix.go:732 +0x1d6 fp=0xc000092f38 sp=0xc000092ee8 pc=0x103f2f6
main.Modify(...)
/Users/slp/github/PostDemo/coreDemo/main.go:21
main.main()
/Users/slp/github/PostDemo/coreDemo/main.go:25 +0x5a fp=0xc000092f80 sp=0xc000092f38 pc=0x105b01a
runtime.main()
/usr/local/go/src/runtime/proc.go:255 +0x227 fp=0xc000092fe0 sp=0xc000092f80 pc=0x102e167
runtime.goexit()
/usr/local/go/src/runtime/asm_amd64.s:1581 +0x1 fp=0xc000092fe8 sp=0xc000092fe0 pc=0x1052dc1
exit status 2
这些堆栈信息是由 GOTRACEBACK 变量来控制打印粒度的,它有五种级别。
如果我们将 GOTRACEBACK 设置为 system ,我们将看到程序崩溃时所有 goroutine 状态信息
$ GOTRACEBACK=system go run main.go
unexpected fault address 0x106a6a4
fatal error: fault
[signal SIGBUS: bus error code=0x2 addr=0x106a6a4 pc=0x105b01a]
goroutine 1 [running]:
runtime.throw({0x106a68b, 0x0})
...
goroutine 2 [force gc (idle)]:
runtime.gopark(0x0, 0x0, 0x0, 0x0, 0x0)
...
created by runtime.init.7
/usr/local/go/src/runtime/proc.go:294 +0x25
goroutine 3 [GC sweep wait]:
runtime.gopark(0x0, 0x0, 0x0, 0x0, 0x0)
...
created by runtime.gcenable
/usr/local/go/src/runtime/mgc.go:181 +0x55
goroutine 4 [GC scavenge wait]:
runtime.gopark(0x0, 0x0, 0x0, 0x0, 0x0)
...
created by runtime.gcenable
/usr/local/go/src/runtime/mgc.go:182 +0x65
exit status 2
如果想获取 core dump 文件,那么就应该把 GOTRACEBACK 的值设置为 crash 。当然,我们还可以通过 runtime/debug 包中的 SetTraceback
方法来设置堆栈打印级别。
delve 是 Go 语言编写的 Go 程序调试器,我们可以通过 dlv core
命令来调试 core dump。
首先,通过以下命令安装 delve
go get -u github.com/go-delve/delve/cmd/dlv
还是以上文中的例子为例,我们通过设置 GOTRACEBACK 为 crash 级别来获取 core dump 文件
$ tree
.
└── main.go
$ ulimit -c unlimited
$ go build main.go
$ GOTRACEBACK=crash ./main
...
Aborted (core dumped)
$ tree
.
├── core
├── main
└── main.go
$ ls -alh core
-rw------- 1 slp slp 41M Oct 31 22:15 core
此时,在同级目录得到了 core dump 文件 core(文件名、存储路径、是否加上进程号都可以配置修改)。
通过 dlv 调试器来调试 core 文件,执行命令格式 dlv core 可执行文件名 core文件
$ dlv core main core
Type 'help' for list of commands.
(dlv)
命令 goroutines
获取所有 goroutine 相关信息
(dlv) goroutines
* Goroutine 1 - User: ./main.go:21 main.main (0x45b81a) (thread 18061)
Goroutine 2 - User: /usr/local/go/src/runtime/proc.go:367 runtime.gopark (0x42ed96) [force gc (idle)]
Goroutine 3 - User: /usr/local/go/src/runtime/proc.go:367 runtime.gopark (0x42ed96) [GC sweep wait]
Goroutine 4 - User: /usr/local/go/src/runtime/proc.go:367 runtime.gopark (0x42ed96) [GC scavenge wait]
[4 goroutines]
(dlv)
Goroutine 1 是出问题的 goroutine (带有 * 代表当前帧),通过命令 goroutine 1
切换到其栈帧
(dlv) goroutine 1
Switched from 1 to 1 (thread 18061)
(dlv)
执行命令 bt
(breakpoints trace) 查看当前的栈帧详细信息
(dlv) bt
0 0x0000000000454bc1 in runtime.raise
at /usr/local/go/src/runtime/sys_linux_amd64.s:165
1 0x0000000000452f60 in runtime.systemstack_switch
at /usr/local/go/src/runtime/asm_amd64.s:350
2 0x000000000042c530 in runtime.fatalthrow
at /usr/local/go/src/runtime/panic.go:1250
3 0x000000000042c2f1 in runtime.throw
at /usr/local/go/src/runtime/panic.go:1198
4 0x000000000043fa76 in runtime.sigpanic
at /usr/local/go/src/runtime/signal_unix.go:742
5 0x000000000045b81a in main.Modify
at ./main.go:21
6 0x000000000045b81a in main.main
at ./main.go:25
7 0x000000000042e9c7 in runtime.main
at /usr/local/go/src/runtime/proc.go:255
8 0x0000000000453361 in runtime.goexit
at /usr/local/go/src/runtime/asm_amd64.s:1581
(dlv)
通过 5 0x000000000045b81a in main.Modify
发现了错误代码所在函数,执行命令 frame 5
进入函数具体代码
(dlv) frame 5
> runtime.raise() /usr/local/go/src/runtime/sys_linux_amd64.s:165 (PC: 0x454bc1)
Warning: debugging optimized function
Frame 5: ./main.go:21 (PC: 45b81a)
16: }
17:
18: func Modify() {
19: a := "hello"
20: b := String2Bytes(a)
=> 21: b[0] = 'H'
22: }
23:
24: func main() {
25: Modify()
26: }
(dlv)
自此,破案了,问题就出在了擅自修改 string 底层值。
有一点需要注意,上文 core dump 生成的例子,我是在 linux 系统下完成的,mac amd64 系统没法弄(很气,害我折腾了两个晚上)。
这是由于 mac 系统下的 Go 限制了生成 core dump 文件,这个在 Go 源码 src/runtime/signal_unix.go 中有相关说明。
//go:nosplit
func crash() {
// OS X core dumps are linear dumps of the mapped memory,
// from the first virtual byte to the last, with zeros in the gaps.
// Because of the way we arrange the address space on 64-bit systems,
// this means the OS X core file will be >128 GB and even on a zippy
// workstation can take OS X well over an hour to write (uninterruptible).
// Save users from making that mistake.
if GOOS == "darwin" && GOARCH == "amd64" {
return
}
dieFromSignal(_SIGABRT)
}
core dump 文件是操作系统提供给我们的一把利器,它是程序意外终止时产生的内存快照。利用 core dump,我们可以在程序崩溃后更好地恢复事故现场,为故障排查保驾护航。
当然,core dump 文件的生成也是有弊端的。core dump 文件较大,如果线上服务本身内存占用就很高,那在生成 core dump 文件上的内存与时间开销都会很大。另外,我们往往会布置服务守护进程,如果我们的程序频繁崩溃和重启,那会生成大量的 core dump 文件(设定了core+pid 命名规则),产生磁盘打满的风险(如果放开了内核限制 ulimit -c unlimited)。
最后,如果担心错误日志不能帮助我们定位 Go 代码问题,我们可以为它开启 core dump 功能,在 hotfix 上增加奇兵。对于有守护进程的服务,建议设置好 ulimt -c 大小限制。
本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/RktnMydDtOZFwEFLLYzlCA
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。