非侵入式观测进程里的某个线程的tls数据

一、背景

在之前的 线程局部存储tls的原理和使用_linux tls存放在堆区-CSDN博客 博客里,我们讲到了glibc提供的tls机制及tls的原理及与内核的配合逻辑。在之前的 非gdb方式观察应用程序的运行时的变量状态-CSDN博客 博客里,我们讲到了如何非侵入式观测进程里的某个变量的方法。这篇博客,我们把两个课题结合,来用非侵入式观测的方法来查看一个进程里的某个线程的tls数据。

我们在第二章里先贴出源码并给出运行效果,在第三章里,我们对相关代码和步骤的原理进行分析。

二、源码及运行效果

需要说明的是,本文观测的tls变量只涉及编译出的程序的源码里直接使用的tls变量,并不涉及观测程序所依赖的so库里使用到的tls变量。

2.1 用户态的tls例程

#include <iostream>
#include <thread>
#include <vector>
#include <chrono>
#include <unistd.h>

thread_local int threadStatus; // 每个线程的局部状态变量

void threadFunction(int threadID) {
    threadStatus = threadID;
    std::cout << threadID << ": gettid = " << gettid() << std::endl;
    while (true) {
        threadStatus += 100; // 增加状态值
        //std::cout << "Thread " << threadID << ": Status = " << threadStatus << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(1)); // 暂停1秒
    }
}

int main() {
    const int numThreads = 10;
    std::vector<std::thread> threads;

    // 创建并启动线程
    for (int i = 0; i < numThreads; ++i) {
        threads.emplace_back(threadFunction, i);
    }

    // 等待线程结束(在这个例子中,线程将永远运行,因此我们不会在这里join线程)
    for (auto& thread : threads) {
        thread.join();
    }

    return 0;
}

2.2 用于观测tls变量的内核模块源码

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/uaccess.h>

MODULE_LICENSE("GPL");

static int watchpid = 0;
module_param(watchpid, int, 0);

//static unsigned long va = 0;  // 要观测的另外一个进程里的虚拟地址
static unsigned long fsbaseoffset = 0;
module_param(fsbaseoffset, ulong, 0);

static int __init my_module_init(void) {
    struct task_struct *task;
    struct mm_struct *mm;
    struct page **pages;
    int nr_pages = 1;
    long offset;
    char *data;
    struct vm_area_struct *vma;
    int ret = 0;
    unsigned long watch_va;
    unsigned long fsbase;
    unsigned long va;

    pid_t pid = watchpid;

    task = pid_task(find_vpid(pid), PIDTYPE_PID);
    if (!task) {
        printk(KERN_ERR "Task not found\n");
        return -1;
    }
    mm = task->mm;
    if (!mm) {
        printk(KERN_ERR "No memory map\n");
        return -1;
    }
    data = kzalloc(4096, GFP_KERNEL);
    if (!data) {
        printk(KERN_ERR "Memory allocation failed\n");
        return -1;
    }

    if (fsbaseoffset == 0) {
        printk(KERN_ERR "fsbaseoffset no input!\n");
        return -1;
    }

    fsbase = task->thread.fsbase;
    va = fsbase - fsbaseoffset;

    vma = find_vma(mm, va);
	if (!vma)
		return -EFAULT;
    if (!(vma->vm_flags & VM_MAYEXEC))
		return -EACCES;
    pages = kmalloc_array(nr_pages, sizeof(struct page*), GFP_KERNEL);
    offset = (va - (va & PAGE_MASK));
    ret = pin_user_pages_remote(mm, va & PAGE_MASK, nr_pages, FOLL_FORCE, pages, NULL);
    if (ret < 0) {
        printk(KERN_ERR "Failed to pin_user_pages_remote, ret[%d]\n", ret);
        goto label_free;
    }
    else {
        ret = 0;
    }
    watch_va = page_address(pages[0]);
    if (!watch_va) {
        printk(KERN_ERR "Fail to page_address\n");
        ret = -1;
        goto label_free;
    }
    memcpy(data, watch_va + offset, 4);
    printk(KERN_INFO "Data: %d\n", *((int*)data));
    unpin_user_page(pages[0]);

label_free:
    kvfree(data);
    kvfree(pages);

    return ret;
}

static void __exit my_module_exit(void) {
    printk(KERN_INFO "Module exited\n");
}

module_init(my_module_init);
module_exit(my_module_exit);

2.3 运行效果

为了方便验证,我们用vs2019的ssh远程gdb方式断点在某个线程,看到即时的变量值状态,方便和用内核模块获取到的数值做对比验证。

2.3.1 使用vs2019的ssh远程gdb方式断点在某个线程,确定要观察的线程的tid和对应的tls变量的数值

(有关如何使用vs2019的ssh远程gdb方式调试linux程序,见之前的博客 vs2019进行远程linux用户态调试_vs2019远程调试linux-CSDN博客)

先运行起来 2.1 里的用户态的tls例程的程序,然后用断点断在下截图处,我们看到这时候线程6的tid是1001463,线程6的这时候的threadStatus变量是106

2.3.2 通过objdump -t 程序 | grep tls的变量,来得到tls变量相较于线程局部存储基址的偏移

再通过objdump -t 程序 | grep tls的变量,来得到tls变量相较于线程局部存储基址的偏移:

2.3.3 通过内核模块获取用户态tls例程程序里某个线程的tls变量的数值,传入线程的tid和刚拿到的要观测的tls变量相对于线程局部存储基址的偏移

通过内核模块获取用户态tls例程程序里某个线程的tls变量的数值,传入线程的tid和刚拿到的要观测的tls变量相对于线程局部存储基址的偏移:

(tid用 2.3.1 里断点下来的那个线程1001463,该线程如 2.3.1 看到该线程上的threadStatus的即时值是106

insmod progtlswatch.ko watchpid=1001463 fsbaseoffset=4

目前的实现获取到的数值是打印到printk里的:

如下图的printk的打印,确实得到了106

三、原理分析

如第二章里描述的方法,在运行了程序之后,先执行objdump -t 程序 | grep tls的变量名,来得到tls变量相较于线程局部存储基址的偏移,然后再通过内核模块获取到线程局部存储基址并计算出要观测的变量在要观测进程上下文下的虚拟地址,最后在通过内核模块获取该进程上下文的虚拟地址上的值。

我们按照这个步骤一一进行分析。在进行这些步骤的分析之前,我们先对使用tls变量的用户态例程做简要说明。

3.1 用户态例程使用了C++的tls变量修饰声明的方式

例程里使用了thread_local这个C++的修饰符来表示变量是tls变量:

使用thread_local修饰的变量,它可以是POD变量,也可以是一个类结构,如果线程里使用到了该tls变量,那么它对应的构造函数是在其线程里的第一次使用时触发,其析构函数是在线程退出时触发,如果线程里没有使用到该变量,它对应的构造函数和析构函数都不会执行。

关于tls机制的原理及tls的详细的使用及注意事项见之前的博客 线程局部存储tls的原理和使用_linux tls存放在堆区-CSDN博客。

3.2 objdump -t 程序 | grep tls的变量名,得到tls变量相较于线程局部存储基址的偏移

3.2.1 grep出来的内容里两个数字相加,就是tls变量相较于线程局部存储基址的负偏移

我们的程序名字叫tlssample.out,程序里用的tls变量名是threadStatus,grep出来的情况如下:

我们把上图里红色框出的两个数字相加,就是4,4就是例程的tls变量相较于线程局部存储基址的偏移。

3.2.2 把例程里的tls变量的使用改得相对复杂一些,来确定tls变量相较于线程局部存储基址的偏移的计算逻辑

为了更加确定上面的格式的实际含义,我们把例程里的thread_local变量增多,定义的地方前面和后面各加两个int大小的tls变量,另外再把grep的tls变量名对应的size增大,增大到8字节:

修改过后的程序完整源码如下:

#include <iostream>
#include <thread>
#include <vector>
#include <chrono>
#include <unistd.h>

thread_local int aaa;
thread_local int bbb;
struct teststatus {
    int a;
    int b;
    int c;
    int d;
    int e;
};
thread_local teststatus threadStatus = { 0 }; // 每个线程的局部状态变量
thread_local int ccc;
thread_local int ddd;

void threadFunction(int threadID) {
    threadStatus.a = threadID;
    std::cout << threadID << ": gettid = " << gettid() << std::endl;
    while (true) {
        threadStatus.a += 100; // 增加状态值
        //std::cout << "Thread " << threadID << ": Status = " << threadStatus << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(1)); // 暂停1秒
    }
}

int main() {
    const int numThreads = 10;
    std::vector<std::thread> threads;

    // 创建并启动线程
    for (int i = 0; i < numThreads; ++i) {
        threads.emplace_back(threadFunction, i);
    }

    // 等待线程结束(在这个例子中,线程将永远运行,因此我们不会在这里join线程)
    for (auto& thread : threads) {
        thread.join();
    }

    return 0;
}

如上图这样的修改过后,再进行objdump -t 程序 | grep tls的变量名,如下图:

看上图的.tbss后面的数值是0x14,是20字节,正好对应于struct teststatus的大小。

而第一个数字0x8加上上面说的.tbss后面的数值0x14,和是0x1c,则对应于线程局部存储基址的负偏移的绝对值,如下图里展示的反汇编的那句汇编的数值:

如上面的实验也可以得到另外一个thread_local的使用上的细节,就是对于我们在程序里并没有使用的thread_local变量,程序在编译时也会给它腾出空间。与之有关的另外一个细节需要再强调一下,就是虽然腾出了空间,但是不使用thread_local声明的变量,它就不会触发thread_local变量的构造函数(如果thread_local变量有构造函数的话),关于这个细节的说明见之前的博客 线程局部存储tls的原理和使用_linux tls存放在堆区-CSDN博客 里的 2.2.1 一节,再拓展一下,用C方式__thread声明tls变量,并调用过pthread_setspecific的线程,无论是否真正使用它,都是会触发注册的清理函数回调的,细节见 线程局部存储tls的原理和使用_linux tls存放在堆区-CSDN博客 里的 2.1.1 一节。

3.3 内核模块根据线程的tid获取到线程的局部存储基址

当前编写的内核模块是用的比较粗暴和直接的方式,当前基于的实验平台是x86_64平台,也确定打开了X86_FEATURE_FSGSBASE,关于该特性是否打开的验证在之前的博客 线程局部存储tls的原理和使用_linux tls存放在堆区-CSDN博客 的 3.3.1 一节里有做过验证:

所以有关的获取fsbase的实现里的部分可以直接挪到内核模块里作为获取线程局部存储基址的方式。

内核里的实现:

挪到内核模块里简单粗暴的获取:

3.4 计算出要观测的tls变量在要观测的进程上下文下的虚拟地址

有关的逻辑如下:

上图里的fsbaseoffset是通过insmod传参传入的:

3.5 得到要观测的tls变量的数值

上面的 3.4 这一步已经拿到了要观测的tls变量在要观测的进程上下文下的虚拟地址,接下来我们只需要用之前的博客 非gdb方式观察应用程序的运行时的变量状态-CSDN博客 里一样的方法,来拿到这个地址上的值。

核心函数是pin_user_pages_remote函数,逻辑如下:

有关pin_user_pages_remote的相关函数get_user_pages和pin_user_pages的详细说明见之前的博客 内存管理之——get_user_pages和pin_user_pages及缺页异常_get user page-CSDN博客。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/970470.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

平面与平面相交算法杂谈

1.前言 空间平面方程&#xff1a; 空间两平面如果不平行&#xff0c;那么一定相交于一条空间直线&#xff0c; 空间平面求交有多种方法&#xff0c;本文进行相关讨论。 2.讨论 可以联立方程组求解&#xff0c;共有3个变量&#xff0c;2个方程&#xff0c;而所求直线有1个变量…

C#(Winform)通过添加AForge添加并使用系统摄像机

先展示效果 AForge介绍 AForge是一个专门为开发者和研究者基于C#框架设计的, 也是NET平台下的开源计算机视觉和人工智能库 它提供了许多常用的图像处理和视频处理算法、机器学习和神经网络模型&#xff0c;并且具有高效、易用、稳定等特点。 AForge主要包括: 计算机视觉与人…

Golang并发编程最佳实践:协程与通道

Golang并发编程最佳实践&#xff1a;协程与通道 本文旨在介绍Golang并发编程的最佳实践&#xff0c;重点讨论协程和通道的使用方法&#xff0c;以及相关的实际案例和代码示例。 一、Golang并发编程简介 又称Go语言&#xff09;是一种由Google开发的编程语言&#xff0c;旨在提供…

Uniapp 短视频去水印解析工具开发实现

最近搞了一个有意思的小工具——短视频去水印解析器&#xff01;这玩意儿可以把短视频中的水印给抹掉&#xff0c;还能提取视频、封面等资源。整个项目用了 Uniapp 开发&#xff0c;做完后体验了一下&#xff0c;发现还挺顺手。今天就来跟大家聊聊实现思路和代码细节~ 需求分析…

LeapMotion第2代 Unity示范代码(桌面开发)

一、官方地址&#xff1a; 官网&#xff1a;https://www.ultraleap.com/ 驱动下载&#xff1a;https://leap2.ultraleap.com/downloads/leap-motion-controller-2/ docs地址&#xff1a;https://docs.ultraleap.com/xr-and-tabletop/tabletop/unity/getting-started/index.html…

【Qt】模型/视图(Model/View)框架详解(一):基本概念

1、简述 1.1 框架 Qt的模型/视图(Model/View)框架 源自 模型-视图-控制器 (MVC) ; 模型 提供从数据集合(比如,数据库)中获取数据;视图 提供显示数据的界面;控制器 提供用户通过界面修改数据的接口;在Qt模型/视图框架中,称之为“委托Delegate”1.2 通信 模型,视图…

如何在Spring Boot中使用Profiles实现环境隔离

文章目录 如何在Spring Boot中使用Profiles实现环境隔离什么是Spring Profiles1.基本概念2.配置管理3.使用场景4.条件化配置5.优点Spring Profiles的基础知识1.Profile的定义2.配置文件3.激活Profiles4.条件化配置5.Profile的优先级与合并6.Profiles的最佳实践配置文件的组织1.…

《pytorch》——优化器的解析和使用

优化器简介 在 PyTorch 中&#xff0c;优化器&#xff08;Optimizer&#xff09;是用于更新模型参数以最小化损失函数的关键组件。在机器学习和深度学习领域&#xff0c;优化器是一个至关重要的工具&#xff0c;主要用于在模型训练过程中更新模型的参数&#xff0c;其目标是最…

应用层优秀的共享民宿物联网框架该怎么选?

有一说一&#xff0c;应用层优秀的物联网框架通常能帮助提升用户体验、提高运营效率、节能减排等等优势&#xff0c;很多老板也很注重这个层面的设计和打磨&#xff0c;那么对于选择应用层优秀的共享民宿物联网框架时&#xff0c;大家可以从哪几个关键因素进行考量呢&#xff1…

DeepSeek自动化写作软件

DeepSeek写作软件的三大核心功能 对于内容创作者来说&#xff0c;写作不仅是表达思想的过程&#xff0c;更是一项需要投入大量时间和精力的任务。面对日益增长的内容需求&#xff0c;写作效率低下、内容质量不高等问题&#xff0c;常常让创作者感到焦虑。而 DeepSeek 写作软件…

CPT205 计算机图形学 OpenGL 3D实践(CW2)

文章目录 1. 介绍2. 设计3. 准备阶段4. 角色构建5. 场景构建6. 交互部分6.1 键盘交互6.2 鼠标交互6.3 鼠标点击出多级菜单进行交互 7. 缺点与问题7.1 程序bug7.2 游戏乐趣不足7.3 画面不够好看 8. 完整代码 1. 介绍 前面已经分享过了关于CPT205的CW1的2D作业&#xff0c;这次C…

Matlab离线安装硬件支持包的方法

想安装支持树莓派的包&#xff0c;但是发现通过matlab安装需要续订维护服务 可以通过离线的方式安装。 1. 下载SupportSoftwareDownloader Support Software Downloader - MATLAB & Simulink 登录账号 选择对应的版本 2. 选择要安装的包 3.将下载的包copy到安装目录下 …

在蓝耘平台使用4090显卡跑一下深度学习算法-教学文章

本次项目展示了如何使用线性回归模型完成房价预测。尽管线性回归简单有效&#xff0c;但在实际问题中&#xff0c;特征与目标变量可能呈现复杂的非线性关系。这时&#xff0c;可以考虑改用其他模型&#xff0c;如决策树、随机森林或深度学习。 文章目录 前言1. 数据集成与管理2…

springboot整合mybatis-plus(保姆教学) 及搭建项目

一、Spring整合MyBatis (1)将MyBatis的DataSource交给Spring IoC容器创建并管理&#xff0c;使用第三方数据库连接池(Druid&#xff0c;C3P0等)代替MyBatis内置的数据库连接池 (2)将MyBatis的SqlSessionFactory交给Spring IoC容器创建并管理&#xff0c;使用spring-mybatis整…

景联文科技:以精准标注赋能AI未来,打造高质量数据基石

在人工智能蓬勃发展的时代&#xff0c;数据已成为驱动技术革新的核心燃料&#xff0c;而高质量的数据标注则是让AI模型从“感知”走向“认知”的关键桥梁。作为深耕数据服务领域的创新者&#xff0c;景联文科技始终以“精准、高效、安全”为核心理念&#xff0c;为全球AI企业提…

云创智城充电系统:基于 SpringCloud 的高可用、可扩展架构详解-多租户、多协议兼容、分账与互联互通功能实现

在新能源汽车越来越普及的今天&#xff0c;充电基础设施的管理和运营变得越来越重要。云创智城充电系统&#xff0c;就像一个超级智能管家&#xff0c;为新能源充电带来了全新的解决方案&#xff0c;让充电这件事变得更方便、更高效、更安全。 一、厉害的技术架构&#xff0c;让…

LlamaFactory可视化模型微调-Deepseek模型微调+CUDA Toolkit+cuDNN安装

LlamaFactory https://llamafactory.readthedocs.io/zh-cn/latest/ 安装 必须保证版本匹配&#xff0c;否则到训练时&#xff0c;找不到gpu cuda。 否则需要重装。下面图片仅供参考。因为cuda12.8装了没法用&#xff0c;重新搞12.6 cudacudnnpytorch12.69.612.6最新&#xf…

Django 美化使用ModelForm的输入框

在初次使用ModelForm时&#xff0c;我的html文件代码如下&#xff0c;主要内容是显示一个卡片式表单&#xff0c;通过循环遍历 form 对象动态生成表单字段 {% extends layout.html %}{% block content %} <div class"container"><div class"c1"&g…

深度学习框架探秘|Keras:深度学习的魔法钥匙

一、引言&#xff1a;深度学习浪潮中的 Keras 前面的文章我们探秘了深度学习框架中的两大明星框架 —— TensorFlow 和 PyTorch 以及 两大框架的对比 在深度学习的众多框架中&#xff0c;还有一款框架备受开发者们的喜爱 —— Keras 。它就像是一位贴心的助手&#xff0c;为我…

深入解析SVG图片原理:从基础到高级应用

文章目录 引言一、SVG基础概念1.1 什么是SVG&#xff1f;1.2 SVG的优势 二、SVG的基本结构2.1 SVG文档结构2.2 常用SVG元素 三、SVG的工作原理3.1 坐标系与变换3.2 路径与曲线3.3 渐变与滤镜 四、SVG的高级应用4.1 动画与交互4.2 数据可视化4.3 响应式设计 五、SVG的优化与性能…