您的位置 首页 golang

bpftrace动态追踪golang应用-string参数

在前面文章的例子中,函数参数都是整数类型,对于 string 数据类型,bpftrace的追踪方式也是一样的吗?

例子

golang程序中定义了一个函数,实现两个 字符串 的拼接。其参数数据类型为string。代码如下:

 # cat string.go
package main
 import  (
  "fmt"
)
//go:noinline
func join(s1, s2 string) string {
  return s1 + s2
}
func main() {
  s1 := "world"
  ss := join("hello,", s1)
  fmt.Println(ss)
}

# ./string
hello,world  

使用bpftrace追踪 join 函数,代码如下:

 #!/usr/bin/bpftrace
uprobe:./string:main.join
{
     printf ("arg1:%s\n", str(sarg0));
    printf("arg2:%s\n", str(sarg1));
}  

执行bpftrace代码后,可以看到,输出结果为:

 # bpftrace string.bt
Attaching 1 probe...
arg1:hello,objectpopcntselectstring struct sweep sysmontimersuint16uin
arg2:  

从输出结果来看,显然bpftrace追踪代码的实现是不对的。因此,对golang程序进一步分析。

分析

使用 gdb 调试golang程序,查看 join 函数的参数传递。在 ss := join(“hello,”, s1) 处设置断点,运行后,单步跟踪至函数调用处,查看对应汇编代码。

 (gdb) b 13
(gdb) r
(gdb) si 6
(gdb) disassemble
Dump of assembler code for function main.main:
   0x0000000000498e80 <+0>: mov    %fs:0xfffffffffffffff8,%rcx
   0x0000000000498e89 <+9>: cmp    0x10(%rcx),%rsp
   0x0000000000498e8d <+13>:  jbe    0x498f47 <main.main+199>
   0x0000000000498e93 <+19>:  sub    $0x58,%rsp
   0x0000000000498e97 <+23>:  mov    %rbp,0x50(%rsp)
   0x0000000000498e9c <+28>:  lea    0x50(%rsp),%rbp
   0x0000000000498ea1 <+33>:  lea    0x2309d(%rip),%rax        # 0x4bbf45
   0x0000000000498ea8 <+40>:  mov    %rax,(%rsp)
   0x0000000000498eac <+44>:  movq   $0x6,0x8(%rsp)
   0x0000000000498eb5 <+53>:  lea    0x22f35(%rip),%rax        # 0x4bbdf1
   0x0000000000498ebc <+60>:  mov    %rax,0x10(%rsp)
   0x0000000000498ec1 <+65>:  movq   $0x5,0x18(%rsp)
=> 0x0000000000498eca <+74>:  callq  0x498e00 <main.join>
   0x0000000000498ecf <+79>:  mov    0x20(%rsp),%rax
   0x0000000000498ed4 <+84>:  mov    0x28(%rsp),%rcx
   0x0000000000498ed9 <+89>:  mov    %rax,(%rsp)
   0x0000000000498edd <+93>:  mov    %rcx,0x8(%rsp)
   0x0000000000498ee2 <+98>:  callq  0x40a120 < runtime .convTstring>
   0x0000000000498ee7 <+103>: mov    0x10(%rsp),%rax
   0x0000000000498eec <+108>: xorps  %xmm0,%xmm0
   0x0000000000498eef <+111>: movups %xmm0,0x40(%rsp)
   0x0000000000498ef4 <+116>: lea    0xaa05(%rip),%rcx        # 0x4a3900  

结合如下 join 函数的汇编代码,

    0x0000000000498ea8 <+40>:  mov    %rax,(%rsp)
   0x0000000000498eac <+44>:  movq   $0x6,0x8(%rsp)
   0x0000000000498eb5 <+53>:  lea    0x22f35(%rip),%rax        # 0x4bbdf1
   0x0000000000498ebc <+60>:  mov    %rax,0x10(%rsp)
   0x0000000000498ec1 <+65>:  movq   $0x5,0x18(%rsp)  

可以得出:

  • join 函数的参数是通过栈进行传递的。因此,可以通过 sargN 变量访问到函数的参数。( argN sargN 是有区别的。)
  • main 函数中,一共向 join 函数传递了4个参数,在栈中的位置分别为: $rsp $rsp+0x8 $rsp+0x10 $rsp+0x18

为了进一步验证,查看栈中保存的内容:

 (gdb) p ($rsp)
$4 = ( void  *) 0xc000064f28
(gdb) x/10c *0xc000064f28
0x4bbf45: 104 'h' 101 'e' 108 'l' 108 'l' 111 'o' 44 ','  111 'o' 98 'b'
0x4bbf4d: 106 'j' 101 'e'

(gdb) p *($rsp+0x10)
Attempt to dereference a generic pointer.
(gdb) p ($rsp+0x10)
$3 = (void *) 0xc000064f38
(gdb) x/10c *0xc000064f38
0x4bbdf1: 119 'w' 111 'o' 114 'r' 108 'l' 100 'd' 119 'w' 114 'r' 105 'i'
0x4bbdf9: 116 't' 101 'e
  

同时, $rsp+0x8 为字符串”hello,”的长度6, $rsp+0x18 为字符串”world”的长度5。

验证

通过前面的分析,将 bpftrace 代码修改为:

 #!/usr/bin/bpftrace

uprobe:./string:main.join
{
    printf("arg1[%d]:%s\n", sarg1, str(sarg0, sarg1));
    printf("arg2[%d]:%s\n", sarg3, str(sarg2, sarg3));
}  

运行之后,可以看到结果:

 # bpftrace string.bt
Attaching 1 probe...
arg1[6]:hello,
arg2[5]:world
  

指针参数

上文中,函数 join 的两个参数均为string类型,现将其定义为string指针,对应函数调用的汇编代码也发生了变化,如下所示:

 //go:noinline
func join(s1, s2 *string) string {
  return *s1 + *s2
}

ss1 := join(&s1, &s1)对应的汇编代码:

498fd0:   48 89 04 24             mov    %rax,(%rsp)
498fd4:   48 89 44 24 08          mov    %rax,0x8(%rsp)
498fd9:   e8 a2 fe ff ff          callq  498e80 <main.join>
  

可以看到,传递给 join 函数的参数只有两个,即 s1 的地址。因此,在uprobe中 sargN 存储的内容为字符串的地址。此时,需要将该地址转换为string结构体才能可视化输出。

查阅golang中string结构的存储结构,在bpftrace程序中定义一个 GoString 的结构体:

 struct GoString {
     char * str;
    int len;
};  

uprobe的代码可以写成:

 uprobe:./string:main.join
{
    $p1 = (struct GoString*) sarg0;
    printf("arg1[%d]:%s\n", $p1->len, str($p1->str, $p1->len));
    $p2 = (struct GoString*) sarg1;
    printf("arg2[%d]:%s\n", $p2->len, str($p2->str, $p2->len));
}  

通过这种方式,有效地解决了地址参数的可视化输出的问题。同样的原理,如果参数为结构体或其他数据类型,也可以通过同样的方式进行解决。

总结

使用bpftrace分析应用程序的输入参数,重点在于,需要弄清楚:

  • 参数的传递方式:是栈进行传递?还是 寄存器 进行传递?这决定了在uprobe中,使用 sargN 还是 argN 来获取参数。
  • 参数的存储结构:如果参数是一个地址或结构体,需要将该地址强转到对应的数据结构,才能正常解析。

文章来源:智云一二三科技

文章标题:bpftrace动态追踪golang应用-string参数

文章地址:https://www.zhihuclub.com/86933.shtml

关于作者: 智云科技

热门文章

网站地图