积分300 / 贡献0

提问5答案被采纳4文章45

[经验分享] OpenHarmony TracePoint Demo实现机制 原创

润开鸿_闻飞 显示全部楼层 发表于 2024-6-3 13:50:34

itopen组织 1、提供OpenHarmony优雅实用的小工具 2、手把手适配riscv + qemu + linux的三方库移植 3、未来计划riscv + qemu + ohos的三方库移植 + 小程序开发 4、一切拥抱开源,拥抱国产化

一、实现原理机制介绍

内核利用TracePoint机制实现内核和芯片特性功能解耦,根据TracePoint的代码实现来看,其主要原理是在内核代码中插入一个桩点,芯片厂家自己功能代码实现,然后注册到内核中即可,具体的流程可见下图:

1、在内核中定义一个桩点的宏,该宏的展开是产生三个函数:钩子执行函数、钩子注册函数和钩子注销函数

2、芯片厂家需要做的事是在功能实现代码函数中将需要完成的功能写进去,然后调用上一步的钩子注册函数将实现代码函数注册进去,最后将该模块加载到内核中

3、内核在特定的调用点加入插桩函数即步骤一中的钩子执行函数

4、测试用例:想办法调用内核接口函数,验证运行插桩函数是否执行了芯片厂家的功能实现代码的函数

img

二、宏定义

宏定义的展开过程

#define DECLARE_TRACE(name, proto, args) \
    __DECLARE_TRACE(name, PARAMS(proto), PARAMS(args), \
        cpu_online(raw_smp_processor_id()), \
        PARAMS(void *__data, proto))
#define DECLARE_HOOK DECLARE_TRACE
#define PARAMS(args...) args
#define TP_PROTO(args...)   args
#define TP_ARGS(args...)    args

DECLARE_HOOK(vendor_tracepoint_demo,
    TP_PROTO(void *arg), 
    TP_ARGS(arg));
    |||||
    |||||
    |||||
    \|||/
     \|/
      |
__DECLARE_TRACE(vendor_tracepoint_demo, \
    PARAMS(void *arg), \
    PARAMS(arg), \
    cpu_online(raw_smp_processor_id()), \
    PARAMS(void *__data, void *arg))

进一步解析

#define raw_smp_processor_id() (current_thread_info()->cpu) // 获取运行当前线程的CPUID
#define cpu_online(cpu)     cpumask_test_cpu((cpu), cpu_online_mask) // 测试 CPU 掩码中的 CPU
#define __DECLARE_TRACE(name, proto, args, cond, data_proto)            \
    extern int __traceiter_##name(data_proto);                          \
    DECLARE_STATIC_CALL(tp_func_##name, __traceiter_##name);            \
    extern struct tracepoint __tracepoint_##name;                       \
    static inline void trace_##name(proto)                              \
    {                                                                   \
        if (static_key_false(&__tracepoint_##name.key))                 \
            __DO_TRACE(name,                                            \
                TP_ARGS(args),                                          \
                TP_CONDITION(cond), 0);                                 \
        if (IS_ENABLED(CONFIG_LOCKDEP) && (cond)) {                     \
            rcu_read_lock_sched_notrace();                              \
            rcu_dereference_sched(__tracepoint_##name.funcs);           \
            rcu_read_unlock_sched_notrace();                            \
        }                                                               \
    }                                                                   \
    __DECLARE_TRACE_RCU(name, PARAMS(proto), PARAMS(args),              \
                PARAMS(cond))                                           \
    static inline int                                                   \
    register_trace_##name(void (*probe)(data_proto), void *data)        \
    {                                                                   \
        return tracepoint_probe_register(&__tracepoint_##name,          \
                        (void *)probe, data);                           \
    }                                                                   \
    static inline int                                                   \
    register_trace_prio_##name(void (*probe)(data_proto), void *data,   \
                   int prio)                                            \
    {                                                                   \
        return tracepoint_probe_register_prio(&__tracepoint_##name,     \
                          (void *)probe, data, prio);                   \
    }                                                                   \
    static inline int                                                   \
    unregister_trace_##name(void (*probe)(data_proto), void *data)      \
    {                                                                   \
        return tracepoint_probe_unregister(&__tracepoint_##name,        \
                        (void *)probe, data);                           \
    }                                                                   \
    static inline void                                                  \
    check_trace_callback_type_##name(void (*cb)(data_proto))            \
    {                                                                   \
    }                                                                   \
    static inline bool                                                  \
    trace_##name##_enabled(void)                                        \
    {                                                                   \
        return static_key_false(&__tracepoint_##name.key);              \
    }

最后展开结果

__DECLARE_TRACE( \
    vendor_tracepoint_demo, \
    PARAMS(void *arg), \
    PARAMS(arg), \
    cpu_online(raw_smp_processor_id()), \
    PARAMS(void *__data, void *arg) \
)
    |||||
    |||||
    |||||
    \|||/
     \|/
      |
extern int __traceiter_vendor_tracepoint_demo(
    void *__data, void *arg);
DECLARE_STATIC_CALL(tp_func_vendor_tracepoint_demo, __traceiter_vendor_tracepoint_demo);
extern struct tracepoint __tracepoint_vendor_tracepoint_demo;

static inline void trace_vendor_tracepoint_demo(
    void *arg)
{
    if (static_key_false(&__tracepoint_vendor_tracepoint_demo.key))
        __DO_TRACE(vendor_tracepoint_demo,
            TP_ARGS(arg),
            TP_CONDITION(cpu_online(raw_smp_processor_id())), 0);
    if (IS_ENABLED(CONFIG_LOCKDEP) && (cpu_online(raw_smp_processor_id()))) {
        rcu_read_lock_sched_notrace();
        rcu_dereference_sched(__tracepoint_vendor_tracepoint_demo.funcs);
        rcu_read_unlock_sched_notrace();
    }
}

__DECLARE_TRACE_RCU( \
    vendor_tracepoint_demo, \
    PARAMS(void *arg), \
    PARAMS(arg), \
    PARAMS(cpu_online(raw_smp_processor_id()))
)

static inline int register_trace_vendor_tracepoint_demo(
    void (*probe)(void *__data, void *arg),
    void *data)
{
    return tracepoint_probe_register(&__tracepoint_vendor_tracepoint_demo, (void *)probe, data);
}

static inline int register_trace_prio_vendor_tracepoint_demo(
    void (*probe)(void *__data, void *arg),
    void *data, int prio)
{
    return tracepoint_probe_register_prio(&__tracepoint_vendor_tracepoint_demo, (void *)probe, data, prio);
}

static inline int unregister_trace_vendor_tracepoint_demo(
    void (*probe)(void *__data, void *arg),
    void *data)
{
    return tracepoint_probe_unregister(&__tracepoint_vendor_tracepoint_demo, (void *)probe, data);
}

static inline void check_trace_callback_type_vendor_tracepoint_demo(
    void (*cb)(void *__data, void *arg))
{
}

static inline bool trace_vendor_tracepoint_demo_enabled(void)
{
    return static_key_false(&__tracepoint_vendor_tracepoint_demo.key);
}

2.1 宏展开的结果说明

通过 DECLARE_HOOK(vendor_tracepoint_demo, TP_PROTO(void *arg), TP_ARGS(arg));展开最核心是为了得到以下函数:

  • 钩子执行函数:register_trace_vendor_tracepoint_demo

    调用地方使用

  • 钩子注册函数:register_trace_prio_vendor_tracepoint_demo

    芯片厂家实现功能函数后要调用注册函数

  • 钩子注销函数:unregister_trace_vendor_tracepoint_demo

    同注册函数

  • 钩子回调函数:check_trace_callback_type_vendor_tracepoint_demo

    如果没有调用过注册函数,那么执行钩子函数的时候默认执行该回调函数

  • 钩子使能函数:trace_vendor_tracepoint_demo_enabled

    暂时不清楚,没来的及查看

  • 可通过如下命令看出符号表是否加载成功

    cat /proc/kallsyms | grep vendor_tracepoint_demo

2.2 实现代码

  • drivers/hooks里面的实现
  • include/trace/hooks里面的代码实现

三、芯片厂家实现的功能

芯片厂家需要定义一个和钩子回调函数类型相同的函数来实现自己的功能代码,然后通过调用钩子注册函数将实现代码注册进去。
为了将芯片部分代码成功加载到内核,我们一般使用内核模块实现,即通过module_init和module_exit实现。
DECLARE_HOOK宏的第二个参数是回调函数除了第一个以外的参数,可以一个,也可以两个三个等多个,第三个参数是要传入的参数值,从宏定义上来看应该很好理解的。
模块是否加载成功可通过命令

cat /proc/kallsyms | grep vendor_demo_init查看

3.1 参考实现代码

// drivers/test_hook/test_hook.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <asm/io.h>
#include <linux/slab.h>
#include <trace/hooks/demohook.h>

static void vendor_foo(void *data, void *arg)
{
        printk("******%s************\n", __func__);
}

static int __init vendor_demo_init(void)
{
      return register_trace_vendor_tracepoint_demo(&vendor_foo, NULL);
}

static void __exit vendor_demo_exit(void)
{
        unregister_trace_vendor_tracepoint_demo(&vendor_foo, NULL);
}

module_init(vendor_demo_init);
module_exit(vendor_demo_exit);
MODULE_LICENSE("GPL");

四、内核调用参考demo

这里是自己实现一个简单符号表中去调用插桩点,目的是为了测试方便,内核加载后可通过如下命令查看该符号表是否加载成功

cat /proc/kallsyms | grep test_symbol

// drivers/test_symbol/test_symbol.c

#include <linux/module.h>
#include <linux/init.h>
#include <trace/hooks/demohook.h>
#include <trace/hooks_test/test_symbol.h>

void test_symbol(void)
{
    printk("-----%s-----\n", __FUNCTION__);
    trace_vendor_tracepoint_demo(NULL);
}

EXPORT_SYMBOL(test_symbol);
MODULE_LICENSE("GPL");

五、测试用例

5.1 测试代码

先实现一个设备驱动调用符号表,然后在应用层打开设备从而调用符号表,这里主要考虑到RK3568编译ko文件不方便

// drivers/test_demo/test_demo.c
// 驱动侧代码实现
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <asm/io.h>
#include <linux/slab.h>
#include <trace/hooks_test/test_symbol.h>

#define CHRNAME     "chrdev_demo"
#define CLASSNAME   "demo_class"
#define DEVICENAME  "demo_device"

struct demo_desc {
    unsigned int dev_major;
    struct class *pcls;
    struct device *pdev;
};


static struct demo_desc *demo_dev = NULL;

int demo_open(struct inode *inode, struct file *filep)
{
    printk("-----%s-----\n", __FUNCTION__);
    /**********************************************************/
    test_symbol();
    /**********************************************************/
    return 0;
}

int demo_release(struct inode *inode, struct file *filep)
{
    printk("-----%s-----\n", __FUNCTION__);
    return 0;
}

static const struct file_operations demo_ops = {
    .open = demo_open,
    .release = demo_release,
};

static int __init test_demo_init(void)
{
    int ret;
    printk("-----%s-----\n", __FUNCTION__);
    demo_dev = kzalloc(sizeof(struct demo_desc), GFP_KERNEL);
    if (IS_ERR(demo_dev)) {
        printk(KERN_ERR "kmalloc error!\n");
        ret = -ENOMEM;
        goto err0;
    }

    demo_dev->dev_major = register_chrdev(0, CHRNAME, &demo_ops);
    if (demo_dev->dev_major < 0) {
        printk(KERN_ERR "register chrdev faidemo!\n");
        ret = -ENODEV;
        goto err1;
    }

    demo_dev->pcls = class_create(THIS_MODULE, CLASSNAME);
    if (IS_ERR(demo_dev->pcls)) {
        printk(KERN_ERR "create demo class faidemo!\n");
        ret = PTR_ERR(demo_dev->pcls);
        goto err2;
    }

    demo_dev->pdev = device_create(demo_dev->pcls, NULL, MKDEV(demo_dev->dev_major, 0), NULL, DEVICENAME);
    if (IS_ERR(demo_dev->pdev)) {
        printk(KERN_ERR "create demo device faidemo!\n");
        ret = PTR_ERR(demo_dev->pdev);
        goto err3;
    }

    return 0;
err3:
    class_destroy(demo_dev->pcls);
err2:
    unregister_chrdev(demo_dev->dev_major, CHRNAME);
err1:
    kfree(demo_dev);
err0:
    return ret;
}

static void __exit test_demo_exit(void)
{
    printk("-----%s-----\n", __FUNCTION__);
    device_destroy(demo_dev->pcls, MKDEV(demo_dev->dev_major, 0));
    class_destroy(demo_dev->pcls);
    unregister_chrdev(demo_dev->dev_major, CHRNAME);
    kfree(demo_dev);
}

module_init(test_demo_init);
module_exit(test_demo_exit);
MODULE_LICENSE("GPL");

// kernel/linux/build/test/unittest/accesstokenid
// 应用层代码实现,这里借助内核的unittest测试框架,主要不用自己去增加配置文件,方便
#include "accesstokenid_test.h"
#include <cstdio>
#include <cstdlib>
#include <fcntl.h>
#include <cerrno>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <sys/ioctl.h>
#include <ctime>
#include <climits>
#include <pthread.h>
#include <sys/syscall.h>
#include <grp.h>


namespace {
}

using namespace testing::ext;
using namespace std;

void AccesstokenidTest::SetUp() {}

void AccesstokenidTest::TearDown() {}

void AccesstokenidTest::SetUpTestCase() {}

void AccesstokenidTest::TearDownTestCase() {}

/**
 * @tc.name: CheckInitToken
 * @tc.desc: Test init value of tokenid and ftokenid
 * @tc.desc: tokenid equals to the father(hdcd) and ftokenid equals to 0
 * @tc.type: FUNC
 */
HWTEST_F(AccesstokenidTest, CheckInitToken, Function | MediumTest | Level1)
{
    printf("----------------------------trace point test-----------------------------------------\n");
    int fd = open("/dev/demo_device", O_RDWR);
    if (fd < 0) {
        printf("open demo_device failed\n");
        return;
    }
    close(fd);
}

5.2 用例测试

将版本编译后烧录可以看到在/dev目录下有个demo_device设备,然后将编译好的accesstokenid_test执行后内核中会有如下打印,表示我们插桩点成功

# shell终端运行结果(不重要)
# ./accesstokenid_test
Running main() from ../../third_party/googletest/googletest/src/gtest_main.cc
[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from AccesstokenidTest
[ RUN      ] AccesstokenidTest.CheckInitToken
----------------------------trace point test-----------------------------------------
[       OK ] AccesstokenidTest.CheckInitToken (0 ms)
[----------] 1 test from AccesstokenidTest (0 ms total)

[----------] Global test environment tear-down
Gtest xml output finished
[==========] 1 test from 1 test case ran. (0 ms total)
[  PASSED  ] 1 test.

# 内核侧的log打印很重要
[52744.227764] -----demo_open-----
[52744.227800] -----test_symbol-----
[52744.227809] ******vendor_foo************
[52744.227827] -----demo_release-----

©著作权归作者所有,转载或内容合作请联系作者

您尚未登录,无法参与评论,登录后可以:
参与开源共建问题交流
认同或收藏高质量问答
获取积分成为开源共建先驱

Copyright   ©2023  OpenHarmony开发者论坛  京ICP备2020036654号-3 |技术支持 Discuz!

返回顶部