啥时候我能写一个安全行业人手一个工具

go 有个恶心的地方,就是不能捕获子进程的 panic, 每个子进程都使用 recover()去捕获,太麻烦,不现实,而且你也没办法确保你引入的第三方包会捕获子进程; 这就导致我在写扫描器时,某个进程发生 panic , 屏幕输出很长很长的堆栈信息,一屏幕都滚动不完,而且我使用 screen 放到后台,导致我就只能看到非常小的一部分。

所以只能找其他路子了,下面记录一下使用 dlv 来排查,寻找导致 panic 的地方

panic 错误

使用 dlv 调试,前提二进制文件再生成时,不能指定-ldflags "-s -w"
启动前,先执行

1
2
3
ulimit -c unlimited

export GOTRACEBACK=crash

ulimit命令用于控制shell程序的资源

-c <core文件上限> 设定core文件的最大值,单位为区块

unlimited标识不做限制

GOTRACEBACK 来控制Golang panic stack trace输出的信息

1
2
3
4
5
6
7
8
9
10
11
12
GOTRACEBACK的设置值有几种,下面分别说明:
export GOTRACEBACK=none :完全省略panic的 stack traces 。
export GOTRACEBACK=single (默认值)只打印当前goroutine的部分stack traces。
当不存在当前goroutine或者是由于runtime内部的错误导致的panic,则会打印出所有goroutine的
堆栈。
export GOTRACEBACK=all :打印用户创建的所有goroutine的stack trace。
export GOTRACEBACK=system :与all的行为很像,只不过会将runtime的goroutine的stace trace也打出来,并且还会显示出runtime内部创建的所有goroutine。
export GOTRACEBACK=crash:与“system”的行为很像,只不过当程序crash的时候不是直接退出,而是可以按照操作系统指定的方式进行后续处理。

例如,在Unix操作系统中,crash会发送一个SIGABRT信号触发core dump。由于历史原因,当将系统环境变量GOTRACEBACK设置为:0, 1, 和 2 的时候,分别代表none, all和system。

通过包runtime/debug package中的方法SetTraceback,也可以设置相应的GOTRACEBACK的值,进而控制输出的stack trace的内容的多少,但是通过该方法,不能够设置比系统环境变量的level更低的值。而level的高低顺序为:none<single<all<system<crash

加上这两句,当程序异常终止时,就是打印堆栈信息,然后会在当前目录下生成一个`core文件

  1. 安装dlv
  2. 调试 coredump 文件
    dlv core {这里是你的程序} {这里是coredump文件} --check-go-version=false

然后使用 goroutines -with running, 查看运行的协程

20230413205255.png

看到编号 23794 有个 * 标记
切换到对应的协程goroutine 23794 ,再试用 bt 子命令分析堆栈的错误

20230413205758.png

emmm,我看代码没看出来为啥会发生panic
20230413211354.png

然后求助ChatGPT

20230413211428.png

按照给出的修复方式,然后运行一段时间又出现 panic ,还是这里,emmmmmmm

20230413220608.png

我大概知道了,我将 AfrogFingerPrintMap 变量定义到了外部,是一个全局变量, 当多协程使用时,就会发生他说的情况。修复方案就是将变量定义为局部变量,互不干扰。

教训就是除了一些常量外,尽量不要将变量定义为全局的,不然多协程下修改值必出问题

还有就是这里重复初始化,可能会导致内存分配出现问题而导致程序崩溃

oom-killer

运行一段时间,整个程序被 killed ,小机器不行啊, 又没钱升级,只能找找程序的原因,看看哪里占用了过多的内存,调优性能

一开始使用保存文件的方式

1
2
3
f, _ := os.Create("mem.prof") 
pprof.WriteHeapProfile(f)
defer f.Close()

go tool pprof -http=0.0.0.0:80 SScan-agent mem.prof

但是看到的信息好像不是很全,之后使用

1
2
3
go func() {  
http.ListenAndServe("0.0.0.0:80", nil)
}()

通过 http://xxx.xxx.xx.xx/debug/pprof/ 查看,不好的地方就是要在程序被 killed 前看数据,不过也还好,这个可以监控内存使用率,超过一定阈值通知,然后去看debug/pprof/ 相关信息就好了,最终也找到了原因。

比如 goroutine 居高不下,可能就是哪里发生了死锁,比如 ratelimit 用完不释放

还有就是 heap 也是居高不下,比如我用到了 httpx wappalyzergofastdialer 重复初始化,内存没有释放掉,导致了被 killed

看代码是因为 fastdialer 用完没有调用 close()

参考

https://zhuanlan.zhihu.com/p/413527587

https://github.com/go-delve/delve/blob/master/Documentation/cli/README.md#goroutines