• Linux 驱动扫描所有线程调用栈


    测试环境

    root@:curtis# uname -a
    Linux curtis-Aspire-E5-471G 5.15.0-52-generic #58~20.04.1-Ubuntu SMP Thu Oct 13 13:09:46 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux
    root@:curtis# lsb_release -a
    No LSB modules are available.
    Distributor ID: Ubuntu
    Description:    Ubuntu 20.04.3 LTS
    Release:        20.04
    Codename:       focal
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    Linux 通过proc文件系统,将进程的栈信息透给用户态,调用链如下所示。

    root@:curtis# cat /proc/self/stack
    [<0>] proc_pid_stack+0x9a/0xf0
    [<0>] proc_single_show+0x52/0xc0
    [<0>] seq_read_iter+0x124/0x450
    [<0>] seq_read+0xfd/0x150
    [<0>] vfs_read+0xa0/0x1a0
    [<0>] ksys_read+0x67/0xf0
    [<0>] __x64_sys_read+0x1a/0x20
    [<0>] do_syscall_64+0x5c/0xc0
    [<0>] entry_SYSCALL_64_after_hwframe+0x61/0xcb
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    从调用栈上来看,最终调用的是函数proc_pid_stack

    #ifdef CONFIG_STACKTRACE
    
    #define MAX_STACK_TRACE_DEPTH	64
    
    static int proc_pid_stack(struct seq_file *m, struct pid_namespace *ns,
    			  struct pid *pid, struct task_struct *task)
    {
    	unsigned long *entries;
    	int err;
    
    	/*
    	 * The ability to racily run the kernel stack unwinder on a running task
    	 * and then observe the unwinder output is scary; while it is useful for
    	 * debugging kernel issues, it can also allow an attacker to leak kernel
    	 * stack contents.
    	 * Doing this in a manner that is at least safe from races would require
    	 * some work to ensure that the remote task can not be scheduled; and
    	 * even then, this would still expose the unwinder as local attack
    	 * surface.
    	 * Therefore, this interface is restricted to root.
    	 */
    	if (!file_ns_capable(m->file, &init_user_ns, CAP_SYS_ADMIN))
    		return -EACCES;
    
    	entries = kmalloc_array(MAX_STACK_TRACE_DEPTH, sizeof(*entries),
    				GFP_KERNEL);
    	if (!entries)
    		return -ENOMEM;
    
    	err = lock_trace(task);
    	if (!err) {
    		unsigned int i, nr_entries;
    
    		nr_entries = stack_trace_save_tsk(task, entries,
    						  MAX_STACK_TRACE_DEPTH, 0);
    
    		for (i = 0; i < nr_entries; i++) {
    			seq_printf(m, "[<0>] %pB\n", (void *)entries[i]);
    		}
    
    		unlock_trace(task);
    	}
    	kfree(entries);
    
    	return err;
    }
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    从函数的定义来看需要将内核调试选项CONFIG_STACKTRACE打开,核心程序调用的是stack_trace_save_tsk函数,为非导出函数,如何使用未导出函数之前的文章有介绍过。

    /**
     * stack_trace_save_tsk - Save a task stack trace into a storage array
     * @task:	The task to examine
     * @store:	Pointer to storage array
     * @size:	Size of the storage array
     * @skipnr:	Number of entries to skip at the start of the stack trace
     *
     * Return: Number of trace entries stored
     */
    unsigned int stack_trace_save_tsk(struct task_struct *task,
    				  unsigned long *store, unsigned int size,
    				  unsigned int skipnr)
    {
    	struct stack_trace trace = {
    		.entries	= store,
    		.max_entries	= size,
    		/* skip this function if they are tracing us */
    		.skip	= skipnr + (current == task),
    	};
    
    	save_stack_trace_tsk(task, &trace);
    	return trace.nr_entries;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    主要代码逻辑

    #include 
    #include 
    #include 
    #include 
    
    #include "trace.h"
    
    #define MAX_STACK_TRACE_DEPTH 64
    
    unsigned int (*stack_trace_save_tsk_ptr)(struct task_struct *task,
    		unsigned long *store, unsigned int size,
    		unsigned int skipnr);
    
    int print_stack(struct task_struct *task)
    {
    	unsigned long *entries;
    	unsigned int i, nr_entries;
    
    	entries = kmalloc_array(MAX_STACK_TRACE_DEPTH, sizeof(*entries), GFP_KERNEL);
    	if (!entries)
    		return -ENOMEM;
    
    	nr_entries = stack_trace_save_tsk_ptr(task, entries,
    			MAX_STACK_TRACE_DEPTH, 0);
    
    	printk("PID = %d, COMM = %s\n", task->pid, task->comm);
    	for (i = 0; i < nr_entries; i++) {
    		printk(" [<0>] %pB\n", (void *)entries[i]);
    	}
    
    	kfree(entries);
    	return 0;
    }
    
    int query_stack(void)
    {
    	int ret = 0;
    	struct task_struct *g, *t;
    
    	do_each_thread(g, t) {
    		print_stack(t);	
    	} while_each_thread(g, t);
    
    	return ret;
    }
    
    static int __init stack_trace_init(void)
    {
    	int ret = 0;
    
    	ret = init_kallsyms_lookup_func();
    	if (ret < 0) {
    		printk("get kallsyms_lookup_name addr failed\n");
    		return -1;
    	}
    
    	stack_trace_save_tsk_ptr = find_func("stack_trace_save_tsk");
    	if (stack_trace_save_tsk_ptr == NULL) {
    		printk("get stack_trace_save_tsk addr failed\n");
    		return -1;
    	}
    
    	ret = query_stack();
    	if (ret < 0) {
    		printk("query stack failed\n");
    		return ret;
    	}
    	printk("stack trace init\n");
    	return 0;
    }
    
    static void __exit stack_trace_exit(void)
    {
    	printk("stack trace exit\n");	
    }
    
    module_init(stack_trace_init);
    module_exit(stack_trace_exit);
    MODULE_LICENSE("GPL");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79

    调用栈打印示例。

    [781162.407668] PID = 107085, COMM = sudo
    [781162.407670]  [<0>] do_sys_poll+0x486/0x610
    [781162.407675]  [<0>] __x64_sys_ppoll+0xac/0xe0
    [781162.407679]  [<0>] do_syscall_64+0x5c/0xc0
    [781162.407684]  [<0>] entry_SYSCALL_64_after_hwframe+0x61/0xcb
    [781162.407696] PID = 107086, COMM = insmod
    [781162.407698]  [<0>] print_stack+0x58/0x90 [trace]
    [781162.407705]  [<0>] query_stack+0x2d/0x70 [trace]
    [781162.407712]  [<0>] stack_trace_init+0x55/0x1000 [trace]
    [781162.407719]  [<0>] do_one_initcall+0x48/0x1e0
    [781162.407726]  [<0>] do_init_module+0x52/0x230
    [781162.407733]  [<0>] load_module+0x138d/0x1610
    [781162.407739]  [<0>] __do_sys_finit_module+0xbf/0x120
    [781162.407746]  [<0>] __x64_sys_finit_module+0x1a/0x20
    [781162.407752]  [<0>] do_syscall_64+0x5c/0xc0
    [781162.407757]  [<0>] entry_SYSCALL_64_after_hwframe+0x61/0xcb
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
  • 相关阅读:
    拿捏Fiddler抓包教程(10)-Fiddler如何设置捕获Firefox浏览器的Https会话
    人工智能大会爆火的“数字员工”究竟是什么?
    Go语言入门心法(三): 接口
    金九银十,刷完这个笔记,17K不能再少了....
    nodejs+vue+elementui房屋租赁网站系统 python协同过滤推荐系统
    基于vue3 + ant-design 自定义SVG图标iconfont的解决方案;ant-design加载本地iconfont.js不显示图标问题
    PGRouting导航规划-AStar算法
    在CentOS中开启mysql服务
    Spring框架系列(13) - SpringMVC实现原理之DispatcherServlet的初始化过程
    kubernetes部署和运行维护中的错误汇总(不定时更新)
  • 原文地址:https://blog.csdn.net/qq_42931917/article/details/133103704