不出意外的,之前提到的 ELF 文件解析内容又拖延了。目前还不知道什么时候有时间能够把希望完成的几篇文章给搞完。翻一翻目前的博客,已经有很久没有更新了。那就水一篇文章吧。目前算是项目里的低谷期,希望能够重拾程序员的意义。
在bpftrace 无侵入遍历golang链表里,笔者展示了使用bpftrace
来遍历golang
链表的方法。由于go-17
和go-16
的函数调用规约存在不同,因此bpftrace 无侵入遍历golang链表并不适用于go-17
。其实这个问题在go-1.17+ 调用规约已经提到了解决方案。本文给一个实例,算是更进一步的延伸这个话题,希望能够起到一些效果。
#
一、执行效果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| $ sudo bpftrace ./link.bt
Attaching 1 probe... // 在触发目标程序前,停止在这里
// 触发目标程序后,输出
== enter main.showNode
name: Alice, age: 11
name: Bob, age: 12
name: Claire, age: 13
== end
// 目标程序执行结果
$ ./link
name: Alice, age: 11
name: Bob, age: 12
name: Claire, age: 13
|
需要注意的是,笔者的验证环境为:
1
2
3
| Linux 4.18.0-193.el8.x86_64
go version go1.17 linux/amd64
bpftrace v0.14.0-72-g6761-dirty
|
由于不同的CPU
架构下,寄存器的信息会有所不同。本文中所涉及的代码示例仅在amd64
里有效。
#
二、代码
本文涉及两部分代码:目标的go
代码以及bpftrace
代码。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
| // link/main.go
package main
import "fmt"
type Node struct {
Name string
Age int64
Next *Node
}
//go:noinline
func showNode(head *Node) {
var cur = head
for cur != nil {
fmt.Printf("name: %s, age: %d
", cur.Name, cur.Age)
cur = cur.Next
}
return
}
func main() {
var node = &Node{
Name: "Alice",
Age: 11,
Next: &Node{
Name: "Bob",
Age: 12,
Next: &Node{
Name: "Claire",
Age: 13,
Next: nil,
},
},
}
showNode(node)
}
|
bpftrace
代码为:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| // link/link.bt
// 这里,符号使用双引号包裹起来是个好习惯
uprobe:./link:"main.showNode"
{
printf("== enter main.showNode
");
$head_ptr = reg("ax");
unroll(10){
$name_ptr = *(uint64*)($head_ptr+0);
$name_len = *(uint64*)($head_ptr+8);
$age_v = *(int64*)($head_ptr+16);
printf("name: %s, age: %d
", str($name_ptr, $name_len), $age_v);
// set head = next
$head_ptr = *(uint64*)($head_ptr+24);
if ($head_ptr == 0){
printf("== end
");
return;
}
}
}
|
以上。周末愉快。