pr_debug、dev_dbg等动态调试二

内核版本:Linux-3.14

作者:彭东林

邮箱:[email protected]

下面我们简要分析

   1: echo -n "file demo.c +p" > /sys/kernel/debug/dynamic_debug/control

的实现。

首先看一下dynamic_dedbg/control是如何生成的?

代码位置 lib/dynamic_debug.c

   1: static int __init dynamic_debug_init_debugfs(void)
   2: {
   3:     struct dentry *dir, *file;
   4:  
   5:     if (!ddebug_init_success)
   6:         return -ENODEV;
   7:  
   8:     dir = debugfs_create_dir("dynamic_debug", NULL);
   9:     if (!dir)
  10:         return -ENOMEM;
  11:     file = debugfs_create_file("control", 0644, dir, NULL,
  12:                     &ddebug_proc_fops);
  13:     if (!file) {
  14:         debugfs_remove(dir);
  15:         return -ENOMEM;
  16:     }
  17:     return 0;
  18: }
  19:  
  20: fs_initcall(dynamic_debug_init_debugfs);

这个函数会在kernel启动的时候执行,在/sys/kernel/debug下创建目录dynamic_debug,然后在dynamic_debug下面创建control节点,并将这个节点的操作函数集设置为ddebug_proc_fops。

   1: static const struct file_operations ddebug_proc_fops = {
   2:     .owner = THIS_MODULE,
   3:     .open = ddebug_proc_open,
   4:     .read = seq_read,
   5:     .llseek = seq_lseek,
   6:     .release = seq_release_private,
   7:     .write = ddebug_proc_write
   8: };

这里涉及到了顺序文件seq_file,关于这部分知识可以参考:

序列文件(seq_file)接口

这里我们需要看一下ddebug_proc_open:

   1: /*
   2:  * File_ops->open method for <debugfs>/dynamic_debug/control.  Does
   3:  * the seq_file setup dance, and also creates an iterator to walk the
   4:  * _ddebugs.  Note that we create a seq_file always, even for O_WRONLY
   5:  * files where it‘s not needed, as doing so simplifies the ->release
   6:  * method.
   7:  */
   8: static int ddebug_proc_open(struct inode *inode, struct file *file)
   9: {
  10:     struct ddebug_iter *iter;
  11:     int err;
  12:  
  13:     vpr_info("called\n");
  14:  
  15:     iter = kzalloc(sizeof(*iter), GFP_KERNEL);
  16:     if (iter == NULL)
  17:         return -ENOMEM;
  18:  
  19:     err = seq_open(file, &ddebug_proc_seqops);
  20:     if (err) {
  21:         kfree(iter);
  22:         return err;
  23:     }
  24:     ((struct seq_file *)file->private_data)->private = iter;
  25:     return 0;
  26: }

其中:

   1: static const struct seq_operations ddebug_proc_seqops = {
   2:     .start = ddebug_proc_start,
   3:     .next = ddebug_proc_next,
   4:     .show = ddebug_proc_show,
   5:     .stop = ddebug_proc_stop
   6: };

从上面的代码可以看出,我们需要分析的函数主要是ddebug_proc_seqops中的以及ddebug_proc_write。

其中,对于 echo –n “file demo.c +p”> /sys/kernel/debug/dynamic_debug/control来说,函数调用:ddebug_proc_open ---> ddebug_proc_write

下面我们一个一个看。

ddebug_proc_write:

   1: /*
   2:  * File_ops->write method for <debugfs>/dynamic_debug/conrol.  Gathers the
   3:  * command text from userspace, parses and executes it.
   4:  */
   5: #define USER_BUF_PAGE 4096
   6: static ssize_t ddebug_proc_write(struct file *file, const char __user *ubuf,
   7:                   size_t len, loff_t *offp)
   8: {
   9:     char *tmpbuf;
  10:     int ret;
  11:  
  12:     if (len == 0)
  13:         return 0;
  14:     if (len > USER_BUF_PAGE - 1) {
  15:         pr_warn("expected <%d bytes into control\n", USER_BUF_PAGE);
  16:         return -E2BIG;
  17:     }
  18:     tmpbuf = kmalloc(len + 1, GFP_KERNEL);
  19:     if (!tmpbuf)
  20:         return -ENOMEM;
  21:     if (copy_from_user(tmpbuf, ubuf, len)) {
  22:         kfree(tmpbuf);
  23:         return -EFAULT;
  24:     }
  25:     tmpbuf[len] = ‘\0‘;
  26:     vpr_info("read %d bytes from userspace\n", (int)len);
  27:  
  28:     ret = ddebug_exec_queries(tmpbuf, NULL);
  29:     kfree(tmpbuf);
  30:     if (ret < 0)
  31:         return ret;
  32:  
  33:     *offp += len;
  34:     return len;
  35: }

如果以 echo –n “file demo.c +p” > /sys/kernel/debug/dynamic_debug/control 为例:

上面的代码第28行传给ddebug_exec_queries的参数tembuf中存放的就是 “file demo.c +p”,函数ddebug_exec_queries的分析在上一篇博客中已经分析过了。

此外,如何查看某个文件中pr_debug或者dev_dbg的设置情况呢?

   1: [[email protected] /]# cat /sys/kernel/debug/dynamic_debug/control 
   2: # filename:lineno [module]function flags format
   3: init/main.c:679 [main]do_one_initcall_debug =p "calling  %pF @ %i\012"
   4: init/main.c:686 [main]do_one_initcall_debug =p "initcall %pF returned %d after %lld usecs\012"
   5: arch/arm/kernel/unwind.c:162 [unwind]unwind_find_origin =_ "%s(%p, %p)\012"
   6: arch/arm/kernel/unwind.c:173 [unwind]unwind_find_origin =_ "%s -> %p\012"
   7: arch/arm/kernel/unwind.c:461 [unwind]unwind_table_add =_ "%s(%08lx, %08lx, %08lx, %08lx)\012"
   8: arch/arm/kernel/unwind.c:117 [unwind]search_index =_ "%s(%08lx, %p, %p, %p)\012"
   9: arch/arm/kernel/unwind.c:341 [unwind]unwind_frame =_ "%s(pc = %08lx lr = %08lx sp = %08lx)\012"
  10: arch/arm/kernel/unwind.c:182 [unwind]unwind_find_idx =_ "%s(%08lx)\012"
  11: ......

上面每一行对应的就是一个pr_debug或者dev_dbg,以第5行为例

arch/arm/kernel/unwind.c:162 [unwind]unwind_find_origin =_ "%s(%p, %p)\012"

表示在文件unwind.c的第162行,modname是unwind,function是unwind_find_origin,”=_”表示不打印, 最后一个表示的是pr_debug要打印的内容。

如果执行

echo -n "file unwind.c +pfmlt" > /sys/kernel/debug/dynamic_debug/control

然后再次执行

cat /sys/kernel/debug/dynamic_debug/control | grep “unwind.c:162”

得到的结果就是:

arch/arm/kernel/unwind.c:162 [unwind]unwind_find_origin =pmflt "%s(%p, %p)\012"

然后我们分析一下 cat /sys/kernel/debug/dynamic_debug/control 的函数调用:

ddebug_proc_open ---> seq_read,在seq_read中又依次调用了ddebug_proc_seqops中的:

ddebug_proc_start/ddebug_proc_next/ddebug_proc_show/ddebug_proc_stop函数。

ddebug_proc_start:

   1: /*
   2:  * Seq_ops start method.  Called at the start of every
   3:  * read() call from userspace.  Takes the ddebug_lock and
   4:  * seeks the seq_file‘s iterator to the given position.
   5:  */
   6: static void *ddebug_proc_start(struct seq_file *m, loff_t *pos)
   7: {
   8:     struct ddebug_iter *iter = m->private;
   9:     struct _ddebug *dp;
  10:     int n = *pos;
  11:  
  12:     vpr_info("called m=%p *pos=%lld\n", m, (unsigned long long)*pos);
  13:  
  14:     mutex_lock(&ddebug_lock);
  15:  
  16:     if (!n)
  17:         return SEQ_START_TOKEN;
  18:     if (n < 0)
  19:         return NULL;
  20:     dp = ddebug_iter_first(iter);
  21:     while (dp != NULL && --n > 0)
  22:         dp = ddebug_iter_next(iter);
  23:     return dp;
  24: }

第8行中的iter指向的内存是在ddebug_proc_open中调用kzalloc分配的。

第20行函数的目的是获取ddebug_tables中的第一项

ddebug_iter_first:

   1: /*
   2:  * Set the iterator to point to the first _ddebug object
   3:  * and return a pointer to that first object.  Returns
   4:  * NULL if there are no _ddebugs at all.
   5:  */
   6: static struct _ddebug *ddebug_iter_first(struct ddebug_iter *iter)
   7: {
   8:     if (list_empty(&ddebug_tables)) {
   9:         iter->table = NULL;
  10:         iter->idx = 0;
  11:         return NULL;
  12:     }
  13:     iter->table = list_entry(ddebug_tables.next,
  14:                  struct ddebug_table, link);
  15:     iter->idx = 0;
  16:     return &iter->table->ddebugs[iter->idx];
  17: }

这里ddebug_tables是在解析__verbose段的时候填充,这里是获取第一项;注意这里每个文件中的pr_debug和dev_dbg的modname都相同,也就是文件名,每个modname在ddebug_tables中只有一项,每个ddebug_table中的ddebugs指向了模块名为modname的descriptor的第一个,因为这些descriptor在内存中是连续存放的,所以通过ddebugs就可索引了,个数是num_ddebugs.

ddebug_iter_next:

   1: /*
   2:  * Advance the iterator to point to the next _ddebug
   3:  * object from the one the iterator currently points at,
   4:  * and returns a pointer to the new _ddebug.  Returns
   5:  * NULL if the iterator has seen all the _ddebugs.
   6:  */
   7: static struct _ddebug *ddebug_iter_next(struct ddebug_iter *iter)
   8: {
   9:     if (iter->table == NULL)
  10:         return NULL;
  11:     if (++iter->idx == iter->table->num_ddebugs) {
  12:         /* iterate to next table */
  13:         iter->idx = 0;
  14:         if (list_is_last(&iter->table->link, &ddebug_tables)) {
  15:             iter->table = NULL;
  16:             return NULL;
  17:         }
  18:         iter->table = list_entry(iter->table->link.next,
  19:                      struct ddebug_table, link);
  20:     }
  21:     return &iter->table->ddebugs[iter->idx];
  22: }

ddebug_proc_next:

   1: /*
   2:  * Seq_ops next method.  Called several times within a read()
   3:  * call from userspace, with ddebug_lock held.  Walks to the
   4:  * next _ddebug object with a special case for the header line.
   5:  */
   6: static void *ddebug_proc_next(struct seq_file *m, void *p, loff_t *pos)
   7: {
   8:     struct ddebug_iter *iter = m->private;
   9:     struct _ddebug *dp;
  10:  
  11:     vpr_info("called m=%p p=%p *pos=%lld\n",
  12:          m, p, (unsigned long long)*pos);
  13:  
  14:     if (p == SEQ_START_TOKEN)
  15:         dp = ddebug_iter_first(iter);
  16:     else
  17:         dp = ddebug_iter_next(iter);
  18:     ++*pos;
  19:     return dp;
  20: }

下面是我画的一张图,大概表示出了ddebug_tables/ddebug_tables/_ddebug的关系:

上面的函数返回的类型是struct _ddebug,用于遍历所有的_ddebug.

ddebug_proc_show:

   1: /*
   2:  * Seq_ops show method.  Called several times within a read()
   3:  * call from userspace, with ddebug_lock held.  Formats the
   4:  * current _ddebug as a single human-readable line, with a
   5:  * special case for the header line.
   6:  */
   7: static int ddebug_proc_show(struct seq_file *m, void *p)
   8: {
   9:     struct ddebug_iter *iter = m->private;
  10:     struct _ddebug *dp = p;
  11:     char flagsbuf[10];
  12:  
  13:     vpr_info("called m=%p p=%p\n", m, p);
  14:  
  15:     if (p == SEQ_START_TOKEN) {
  16:         seq_puts(m,
  17:              "# filename:lineno [module]function flags format\n");
  18:         return 0;
  19:     }
  20:  
  21:     seq_printf(m, "%s:%u [%s]%s =%s \"",
  22:            trim_prefix(dp->filename), dp->lineno,
  23:            iter->table->mod_name, dp->function,
  24:            ddebug_describe_flags(dp, flagsbuf, sizeof(flagsbuf)));
  25:     seq_escape(m, dp->format, "\t\r\n\"");
  26:     seq_puts(m, "\"\n");
  27:  
  28:     return 0;
  29: }

我们最终看到的结果:

   1: [[email protected] /]# cat /sys/kernel/debug/dynamic_debug/control | head -n 10
   2: # filename:lineno [module]function flags format
   3: init/main.c:679 [main]do_one_initcall_debug =p "calling  %pF @ %i\012"
   4: init/main.c:686 [main]do_one_initcall_debug =p "initcall %pF returned %d after %lld usecs\012"
   5: arch/arm/kernel/unwind.c:162 [unwind]unwind_find_origin =_ "%s(%p, %p)\012"
   6: arch/arm/kernel/unwind.c:173 [unwind]unwind_find_origin =_ "%s -> %p\012"

上面的内容就是ddebug_proc_show打印出来的。

trim_prefix:

   1: /* Return the path relative to source root */
   2: static inline const char *trim_prefix(const char *path)
   3: {
   4:     int skip = strlen(__FILE__) - strlen("lib/dynamic_debug.c");
   5:  
   6:     if (strncmp(path, __FILE__, skip))
   7:         skip = 0; /* prefix mismatch, don‘t skip */
   8:  
   9:     return path + skip;
  10: }

这个函数的目的是将文件名的绝对路径转换为相对路径,相对于kernel源码的根目录。

ddebug_describe_flags:

   1: static struct { unsigned flag:8; char opt_char; } opt_array[] = {
   2:     { _DPRINTK_FLAGS_PRINT, ‘p‘ },
   3:     { _DPRINTK_FLAGS_INCL_MODNAME, ‘m‘ },
   4:     { _DPRINTK_FLAGS_INCL_FUNCNAME, ‘f‘ },
   5:     { _DPRINTK_FLAGS_INCL_LINENO, ‘l‘ },
   6:     { _DPRINTK_FLAGS_INCL_TID, ‘t‘ },
   7:     { _DPRINTK_FLAGS_NONE, ‘_‘ },
   8: };
   9:  
  10: /* format a string into buf[] which describes the _ddebug‘s flags */
  11: static char *ddebug_describe_flags(struct _ddebug *dp, char *buf,
  12:                     size_t maxlen)
  13: {
  14:     char *p = buf;
  15:     int i;
  16:  
  17:     BUG_ON(maxlen < 6);
  18:     for (i = 0; i < ARRAY_SIZE(opt_array); ++i)
  19:         if (dp->flags & opt_array[i].flag)
  20:             *p++ = opt_array[i].opt_char;
  21:     if (p == buf)
  22:         *p++ = ‘_‘;
  23:     *p = ‘\0‘;
  24:  
  25:     return buf;
  26: }

这个函数是将flags的值转换为字符串,存放到buf中,并将buf返回。

ddebug_proc_stop:

   1: /*
   2:  * Seq_ops stop method.  Called at the end of each read()
   3:  * call from userspace.  Drops ddebug_lock.
   4:  */
   5: static void ddebug_proc_stop(struct seq_file *m, void *p)
   6: {
   7:     vpr_info("called m=%p p=%p\n", m, p);
   8:     mutex_unlock(&ddebug_lock);
   9: }

上一篇博客中pr_debug展开结果:

   1: do {
   2:     static struct _ddebug  __aligned(8)            \
   3:     __attribute__((section("__verbose"))) descriptor = {        \
   4:         .modname = KBUILD_MODNAME,            \
   5:         .function = __func__,                \
   6:         .filename = __FILE__,                \
   7:         .format = (fmt),                \
   8:         .lineno = __LINE__,                \
   9:         .flags =  _DPRINTK_FLAGS_DEFAULT,        \
  10:     }
  11:     if (unlikely(descriptor.flags & _DPRINTK_FLAGS_PRINT))    \
  12:         __dynamic_pr_debug(&descriptor, fmt,    \
  13:                    ##__VA_ARGS__);        \
  14: } while (0)

第11行就是判断descriptor.flags的值,看是否能够打印。

__dynamic_pr_debug:

   1: int __dynamic_pr_debug(struct _ddebug *descriptor, const char *fmt, ...)
   2: {
   3:     va_list args;
   4:     int res;
   5:     struct va_format vaf;
   6:     char buf[PREFIX_SIZE];
   7:  
   8:     BUG_ON(!descriptor);
   9:     BUG_ON(!fmt);
  10:  
  11:     va_start(args, fmt);
  12:  
  13:     vaf.fmt = fmt;
  14:     vaf.va = &args;
  15:  
  16:     res = printk(KERN_DEBUG "%s%pV",
  17:              dynamic_emit_prefix(descriptor, buf), &vaf);
  18:  
  19:     va_end(args);
  20:  
  21:     return res;
  22: }

dynamic_emit_prefix:

   1: static char *dynamic_emit_prefix(const struct _ddebug *desc, char *buf)
   2: {
   3:     int pos_after_tid;
   4:     int pos = 0;
   5:  
   6:     *buf = ‘\0‘;
   7:  
   8:     if (desc->flags & _DPRINTK_FLAGS_INCL_TID) {
   9:         if (in_interrupt())
  10:             pos += snprintf(buf + pos, remaining(pos), "<intr> ");
  11:         else
  12:             pos += snprintf(buf + pos, remaining(pos), "[%d] ",
  13:                     task_pid_vnr(current));
  14:     }
  15:     pos_after_tid = pos;
  16:     if (desc->flags & _DPRINTK_FLAGS_INCL_MODNAME)
  17:         pos += snprintf(buf + pos, remaining(pos), "%s:",
  18:                 desc->modname);
  19:     if (desc->flags & _DPRINTK_FLAGS_INCL_FUNCNAME)
  20:         pos += snprintf(buf + pos, remaining(pos), "%s:",
  21:                 desc->function);
  22:     if (desc->flags & _DPRINTK_FLAGS_INCL_LINENO)
  23:         pos += snprintf(buf + pos, remaining(pos), "%d:",
  24:                 desc->lineno);
  25:     if (pos - pos_after_tid)
  26:         pos += snprintf(buf + pos, remaining(pos), " ");
  27:     if (pos >= PREFIX_SIZE)
  28:         buf[PREFIX_SIZE - 1] = ‘\0‘;
  29:  
  30:     return buf;
  31: }

这个函数会根据flags(如fmtl)的值向buf中填充内容,然后将buf返回。

先分析到这里,下一篇总结一些开启pr_debug的一些方法。

完。

时间: 2024-07-30 22:01:50

pr_debug、dev_dbg等动态调试二的相关文章

pr_debug、dev_dbg等动态调试三

内核版本:Linux-3.14 作者:彭东林 邮箱:[email protected] 如果没有使用CONFIG_DYNAMIC_DEBUG,那么就需要定义DEBUG,那么此时pr_debug就退化为了printk. 如果定义了CONFIG_DYNAMIC_DEBUG,下面有几种方法: 参考内核文档:Documentation/dynamic-debug-howto.txt Introduction ============ This document describes how to use

pr_debug、dev_dbg等动态调试一

内核版本:Linux-3.14 pr_debug: #if defined(CONFIG_DYNAMIC_DEBUG) /* dynamic_pr_debug() uses pr_fmt() internally so we don't need it here */ #define pr_debug(fmt, ...) \ dynamic_pr_debug(fmt, ##__VA_ARGS__) #elif defined(DEBUG) #define pr_debug(fmt, ...) \

安卓程序动态调试方法

一.使用DDMS 1.将程序使用apktool转化为smali代码,在关键代码左右注入log代码(的smali形式,如输出寄存器v0的值),之后重新打包apk,再执行时可以从 logcat 中看到输出. 2.栈跟踪法:如,在关键代码左右插入new Exception("print trace").printStackTrace();的smali代码形式,之后重新打包签名,再次运行可以在logcat窗口中得到栈跟踪信息. 3.method profiling: 可以用ddms中的meth

Android动态方式破解apk前奏篇(Eclipse动态调试smail源码)

一.前言 今天我们开始apk破解的另外一种方式:动态代码调试破解,之前其实已经在一篇文章中说到如何破解apk了: Android中使用静态方式破解Apk  主要采用的是静态方式,步骤也很简单,首先使用apktool来反编译apk,得到smail源码,然后分析smail代码,采用代码注入技术来跟踪代码,然后找到关键方法进行修改,进而破解,同时还可以使用一些开源的hook框架,比如:Xposed和Cydia Substrate,来进行关键方法的hook.所以这里我们可以看到我们破解的第一步是使用ap

【动态调试so文件】 + AliCrackMe_2分析记录

时不时要用到动态调试so, 但总是不记得,这里记录一下. 步骤很简单,就是参考看雪论坛,非虫大哥的介绍. http://www.kanxue.com/bbs/showthread.php?p=1111471 调试so必须可以. Debugger→Attach→Remote ArmLinux/Android debugger Attach进程 CTRL+S找so 计算偏移找地址,F2下断后就OK 触发断点 以及luyangliu 的http://bbs.pediy.com/showthread.p

【转】Android逆向之动态调试总结

一.在SO中关键函数上下断点 刚学逆向调试时.大多都满足于在SO中某关键函数上下断点.然后通过操作应用程序,去触发这个断点,然后进行调试 详细的步骤可以参见非虫大大的<Android软件安全与逆向分析> 简单说:在libsyclover.so文件中有一个函数jnicall1.每次单击按钮的时候,便会调用此函数. 1.静态载入此so文件,找到函数的偏移地址为:0x132C <ignore_js_op> 2.执行android_server3.端口转发 adb forward tcp:

Android逆向之旅---动态方式破解apk前奏篇(Eclipse动态调试smail源码)

一.前言 今天我们开始apk破解的另外一种方式:动态代码调试破解,之前其实已经在一篇文章中说到如何破解apk了: Android中使用静态方式破解Apk  主要采用的是静态方式,步骤也很简单,首先使用apktool来反编译apk,得到smail源码,然后分析smail代码,采用代码注入技术来跟踪代码,然后找到关键方法进行修改,进而破解,同时还可以使用一些开源的hook框架,比如:Xposed和Cydia Substrate,来进行关键方法的hook.所以这里我们可以看到我们破解的第一步是使用ap

阿里ctf-2014 android 第三题——so动态调试及破解加固

通过做题来学习android逆向是一个比较不错的方法. 虽然有投机取巧的方法解决这题,但是对这个题目的深入研究,学习到了dex的动态调试.破解加固等技术. 要感谢各位android大牛对此题的详细介绍.我只是照着做了一遍,对有些不明确的地方自己演练了一下,并对android的so调试进行了一下简单的归纳. 从零开始进行android的so代码调试: 一.工具准备: jre--java runtime enveroment    //下面的一切工具都要这个支持 jdk--java developm

Android逆向 Android Studio动态调试smali代码

工具: Android Studio版本: 3.0.1 smalidea插件: https://github.com/JesusFreke/smali/wiki/smalidea. 反编译工具:本节先用Android Killer,后面介绍apktool. 一 配置插件 下载smalidea插件,然后打卡Android Studio,点击File->Setting->Plugins->Install plugin from disk,选择下载的smalidea.zip文件,安装成功后显示