当前位置: 首页 > 工具软件 > DTrace > 使用案例 >

solaris dtrace系列三:dtrace应用进阶-在应用中添加自定义的探测器

沃弘图
2023-12-01

上面两篇讲解了dtrace的基本概念,这次来看下dtrace的进阶应用,在源码中添加自定义的探测器

一:为什么需要自定义探测器?

Solaris(包括 OpenSolaris)、FreeBSD 和 Mac OS X都支持使用标准的 DTrace 探测。这包括在代码中不同函数边界由操作系统实现的那些探测。这些探测称为 Function Boundary Tracing (FBT),可以通过它们探测特定函数的启动或停止。这个功能的局限是,只能使用它探测函数,而不能探测应用程序的功能性片段。如果想探测组成同一操作的多个函数,或者想检查某一函数的片段,FBT 就无能为力了。

对于自己的应用程序,可以使用 User-land Statically Defined Tracing (USDT) 解决这个问题。USDT 让开发人员可以在代码中重要的位置添加特定的探测。还可以使用 USDT 从正在运行的应用程序获取数据,这些数据可作为跟踪应用程序的探测的参数而被访问。

总而言之,标准探测只能告诉你什么时候调用了哪些函数,传了哪些参数,至于函数内部用户自定义了哪些算法,具体在干什么,只能通过在源码中添加USDT来暴露给dtrace

二:实验

1.创建探测定义文件;其中包含希望插入代码中的每个探测,包括希望通过每个探测共享的参数的定义。这个文件的格式与 C 语言相似。必须指定一个或多个提供者,在每个提供者中指定希望在代码中支持的探测,见provider.d

root@IPS_TEST # cat provider.d

provider ipstest {
/* test */
  probe a1__done(char *p1,int p2);

};

provider 是在应用程序中安装探测之后提供者的名称。DTrace 中的探测由提供者、模块、函数和探测名标识:provider:module:function:name。对于 USDT 探测,可以只指定其中的 provider 和 name 部分。

探测的名称取自 probe 关键字后面的字符串。可以用双下划线分隔探测名中的单词。在跟踪期间希望使用探测时,把双下划线改为单一连字符。例如,这个文件中的探测名 a1__done() 可以表示为ipstest::a1-done(提供者和探测名的组合)。

定义中每个探测的参数用来标识 C 代码中参数的数据类型。在跟踪期间用 arg0、arg1、argN 等引用参数。

2.用 dtrace 命令把探测定义转换为头文件, 见provider.h

root@IPS_TEST # dtrace -o provider.h -h -s provider.d
root@IPS_TEST # ls -lt
total 134
-rw-r–r-- 1 root root 567 Sep 18 17:10 provider.h
-rw-r–r-- 1 root root 68 Sep 18 17:10 provider.d

provider.h

/*
 * Generated by dtrace(1M).
 */

#ifndef _PROVIDER_H
#define _PROVIDER_H

#include <unistd.h>

#ifdef  __cplusplus
extern "C" {
#endif

#if _DTRACE_VERSION

#define IPSTEST_A1_DONE(arg0, arg1) \
        __dtrace_ipstest___a1__done(arg0, arg1)
#define IPSTEST_A1_DONE_ENABLED() \
        __dtraceenabled_ipstest___a1__done()


extern void __dtrace_ipstest___a1__done(char *, int);
extern int __dtraceenabled_ipstest___a1__done(void);

#else

#define IPSTEST_A1_DONE(arg0, arg1)
#define IPSTEST_A1_DONE_ENABLED() (0)

#endif


#ifdef  __cplusplus
}
#endif

#endif  /* _PROVIDER_H */

上面的命令指定输出文件名 (-o)、希望生成头文件 (-h) 以及源探测定义文件名 (-s)。

产生的头文件包含宏,可以通过在代码中放置这些宏插入探测。可以在代码中希望触发探测的任何地方使用它们,次数不限。

探测宏的名称取决于您定义的探测名。例如,a1__done 的宏是 IPSTEST_A1_DONE。既然有了探测定义文件(在构建应用程序时还要使用它)和头文件,现在就该在 C 源代码中插入探测了。

3.修改应用主程序源码,添加探测器,见mydtrace.c

#include <stdio.h>
#include <string.h>
#include "provider.h"

void a(char *p1, int p2);
int  a_1(char *p1, int p2);
int  a_1_1(char *p1, int p2);
void b(char *p1, int p2);
int  b_1(char *p1, int p2);
void c(char *p1, int p2);
 
int main ()
{
   while (1)
   {
   a("dtrace-test-in-a",100);
   b("dtrace-test-in-b",200);
   c("dtrace-test-in-c",300);
   }
}
 

void a(char *p1, int p2)
{  char str[100];
   int rst;
   printf("a(%s,%d)\n",p1,p2);
   strcpy(str,p1);
   strcat(str,"&a_1");
   rst=a_1(str,p2+10);  
   printf("a_1=%d\n",rst);
}

int a_1(char *p1, int p2)
{
   int rst;
   char str[100];
   printf("a_1(%s,%d)\n",p1,p2);
   
   //backup&change p1
   strcpy(str,p1);
   strcpy(p1,"MyProbe-In-A_1");
   p2=p2-1;
   
   IPSTEST_A1_DONE(p1, p2);
   
   //recover p1
   strcpy(p1,str);
   rst=a_1_1(strcat(p1,"&a_1_1"),p2+1);
   printf("a_1_1=%d\n",rst);
   sleep(2);
   return rst;
}

int a_1_1(char *p1, int p2)
{
   int rst;
   rst=p2+1;
   printf("a_1_1(%s,%d)\n",p1,p2);   
   return rst;
}

void b(char *p1, int p2)
{  char str[100];
   int rst;
   strcpy(str,p1);
   strcat(str,"&b_1");
   printf("b(%s,%d)\n",p1,p2);
   rst=b_1(str,p2+10);
   printf("b_1=%d\n",rst);  
}

int b_1(char *p1, int p2)
{
   int rst;
   rst=p2+1;
   printf("b_1(%s,%d)\n",p1,p2);
   sleep(3);  
   return rst;
}

void c(char *p1, int p2)
{
   printf("c(%s,%d)\n",p1,p2);
}

4.把主程序源文件编译为对象文件

root@IPS_TEST # gcc -c mydtrace.c
root@IPS_TEST # ls -lt
-rw-r–r-- 1 root root 2568 Sep 18 17:15 mydtrace.o
-rw-r–r-- 1 root root 1367 Sep 18 17:13 mydtrace.c
-rw-r–r-- 1 root root 567 Sep 18 17:10 provider.h
-rw-r–r-- 1 root root 68 Sep 18 17:10 provider.d

5.链接主程序对象文件,生成探测对象文件

root@IPS_TEST # dtrace -G -s provider.d -o provider.o mydtrace.o
root@IPS_TEST # ls -lt
-rw-r–r-- 1 root root 8672 Sep 18 17:16 provider.o
-rw-r–r-- 1 root root 2568 Sep 18 17:15 mydtrace.o
-rw-r–r-- 1 root root 1367 Sep 18 17:13 mydtrace.c
-rw-r–r-- 1 root root 567 Sep 18 17:10 provider.h
-rw-r–r-- 1 root root 68 Sep 18 17:10 provider.d

6.链接主程序对象文件和探测对象文件,生成最终的模拟守护进程的应用程序mydtraced(这个程序等同以后生成环境debug的实际应用mysqld或oracle进程)

root@IPS_TEST # gcc -o mydtraced mydtrace.o provider.o
root@IPS_TEST # ls -lt
-rwxr-xr-x 1 root root 13832 Sep 18 17:17 mydtraced
-rw-r–r-- 1 root root 8672 Sep 18 17:16 provider.o
-rw-r–r-- 1 root root 2568 Sep 18 17:15 mydtrace.o
-rw-r–r-- 1 root root 1367 Sep 18 17:13 mydtrace.c
-rw-r–r-- 1 root root 567 Sep 18 17:10 provider.h
-rw-r–r-- 1 root root 68 Sep 18 17:10 provider.d

7.编写脚本使用探测,见mydtrace.sh

root@IPS_TEST # cat mydtrace.sh

#!/usr/sbin/dtrace -s

#pragma D option quiet

ipstest*:::a1-done
{
 printf("parameters pass into a1_done: %s,%d\n",copyinstr(arg0),arg1);
}

8.运行编译好的模拟守护进程mydtraced

root@IPS_TEST # ./mydtraced
a(dtrace-test-in-a,100)
a_1(dtrace-test-in-a&a_1,110)
a_1_1(dtrace-test-in-a&a_1&a_1_1,110)
a_1_1=111
a_1=111
b(dtrace-test-in-b,200)
b_1(dtrace-test-in-b&b_1,210)
b_1=211
c(dtrace-test-in-c,300)
a(dtrace-test-in-a,100)
a_1(dtrace-test-in-a&a_1,110)

9.运行探测脚本,查看输出结果

root@IPS_TEST # ./mydtrace.sh
parameters pass into a1_done: MyProbe-In-A_1,109
parameters pass into a1_done: MyProbe-In-A_1,109
^C

 类似资料: