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

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

例子

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

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

# cat string.gopackage mainimport (  "fmt")//go:noinlinefunc join(s1, s2 string) string {  return s1 + s2}func main() {  s1 := "world"  ss := join("hello,", s1)  fmt.Println(ss)}# ./stringhello,world

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

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

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

# bpftrace string.btAttaching 1 probe...arg1:hello,objectpopcntselectstringstructsweep sysmontimersuint16uinarg2:

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

分析

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

(gdb) b 13(gdb) r(gdb) si 6(gdb) disassembleDump 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 *0xc000064f280x4bbf45: 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 *0xc000064f380x4bbdf1: 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/bpftraceuprobe:./string:main.join{    printf("arg1[%d]:%s\n", sarg1, str(sarg0, sarg1));    printf("arg2[%d]:%s\n", sarg3, str(sarg2, sarg3));}

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

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

指针参数

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

//go:noinlinefunc 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来获取参数。
  • 参数的存储结构:如果参数是一个地址或结构体,需要将该地址强转到对应的数据结构,才能正常解析。

版权声明

1 本文地址:https://www.sunlonger.com/jijin/1237389.html 转载请注明出处。
2 本站内容除隼龙儿财经签约编辑原创以外,部分来源网络由互联网用户自发投稿及AIGC生成仅供学习参考。
3 文章观点仅代表原作者本人不代表本站立场,并不完全代表本站赞同其观点和对其真实性负责。
4 文章版权归原作者所有,部分转载文章仅为传播更多信息服务用户,如信息标记有误请联系管理员。
5 本站禁止以任何方式发布转载违法违规相关信息,如发现本站有涉嫌侵权/违规及任何不妥内容,请第一时间联系我们申诉反馈,经核实立即修正或删除。


本站仅提供信息存储空间服务,部分内容不拥有所有权,不承担相关法律责任。
上一篇 2024年06月18日
下一篇 2024年06月18日

相关推荐

  • 新手选好基金的关键与投资技巧 |基金宝典

    对于新手来说,选择基金是一个需要谨慎考虑的过程,首先,要明确自己的投资目标,是追求短期收益还是长期增值,或者是为了特定的财务目标(如子女教育、退休养老等)。同时,评估自己对投资风险的承受能力,风险承受能力较低的投资者可以选择低风险的货币市场基金或债券型基金

    2024-09-14 21:32:10
    0 0
  • 基金基础篇:一文讲透指数基金,干货满满

    投资千万条,风险第一条。前言:对于基金投资而言,绕不开的一个话题就是指数基金,那么到底什么是指数基金呢?它有哪些魅力呢?指数基金的优缺点是啥呢?等一系列问题,本篇就来展开详解,一起往下!对于基金老手而言,可以选择性阅读!01一、什么是指数?在说指数基金之前,

    2024-09-14 21:32:07
    0 0
  • 基金在什么平台上买比较合理?

    购买基金的平台,各有特点和优势,你可以根据自己的需求进行选择,以下是一些基金购买平台:1. 基金公司官方网站或APP:- 优点:直接从基金公司购买,能获取最准确的产品信息和相关服务,有时还可能享受费率优惠,比如申购费折扣等;对自家基金产品的研究和理解更深入,能提供

    2024-09-14 21:32:02
    0 0
  • 购买货币基金有哪些技巧?

    来源:龙老解股以下是购买货币基金的一些技巧: **一、选择规模适中的基金** 一般来说,规模适中的货币基金在市场上的议价能力较强,同时也能在操作上保持灵活性。如果规模过小,可能在应对大额赎回时面临流动性风险,并且在与银行等机构谈判获取更高利率时处于劣势;而规模过

    2024-09-14 21:31:59
    0 0
  • 【理财狮talk秀】债市也波动?别急,先看看这五要素

    据wind数据显示,今年开年以来债券市场吸引了大量资金的关注,走出了非常亮眼的行情,不过也引发了市场和监管对于持续上涨背后潜在风险的担忧。自8月5日以来,债券市场出现了明显的波动,债券价格持续下跌。走势相对较稳的债券为什么出现了较大的波动呢?今天,就让我们来聊一

    2024-09-14 21:31:58
    0 0
  • 几种止损的方式

    XX股票跌了好多,需要止损吗?关于这个问题,相信炒股的朋友都遇到过。买入被套后,不少人会选择原地躺下,等待股价回归。但事实上,随着A股越来越成熟,国际化进程越来越快,以往很多老股民的这个解套“经验”可能不那么灵了。运气不好的,甚至可能被套到退市!众所周知,投

    2024-09-14 21:31:56
    0 0

发表回复

8206

评论列表(0条)

    暂无评论