免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
12下一页
最近访问板块 发新帖
查看: 24289 | 回复: 12

[原创]QEMU小实验:手工遍历所有进程的方法 [复制链接]

论坛徽章:
0
发表于 2010-01-28 06:02 |显示全部楼层
本帖最后由 accessory 于 2010-02-04 09:29 编辑

QEMU小实验:手工遍历所有进程的方法

在内核中已经提供了遍历所有进程的方法,比如用for_each_process宏。但是如果你想加深对这部分的了解,那么可以不用这个宏,完全手工遍历一遍。下面介绍了在QEMU中,利用QEMU MONITOR,手工找出所有PROCESS的方法

环境:
QEMU 0.9.1
QEMU VM: CENT OS 5.3 (LINUX 2.6.18  )
ARCH :X86-32

主要思路:
我们已经知道LINUX中的所有进程都对应一个TASK_STRUCT. 同时这些TASK_STRUCT里面有一个成员叫做TASKS。它的类型是一个LIST_HEAD(有NEXT, PRE2个成员,构成一个双向链表)。所有的进程就是通过这个TASK_STRUCT里的TASKS连接在一起的。如最后的图所示:(图片来自ULK3)


想遍历进程的话,可以找到一个上面说的结构,然后顺着链表一个一个找下去。另外,还有一些重要的信息在TASK_STRUCT中。常用的有PID(线程ID), TGID(进程ID), COMM(进程的命令参数)。
(关于PID, TGID,可以看下面这个帖子:http://linux.chinaunix.net/bbs/thread-1155667-1-1.html

如果是在GDB里,那么找到了TASK_STRUCT之后,可以直接用  p task->pid 的方法来把PID之类的打印出来。但是我们的目的就是不用GDB的功能,而只用QEMU MONITOR的功能,纯手工的找出PID这些成员。

这样一来,我们需要自己计算下PID, TGID等成员在TASK_STRUCT中的偏移量。怎么算呢?最天真的办法是按照 .H 文件里TASK_STRUCT的声明,自己一个一个算过去,比如一个CHAR占一个字节,一个INT 4个字节等。但是由于TASK_STRUCT是一个很大的结构(包括几十或者上百个成员),同时考虑到编译的时候还有对齐的问题。这样手工算可以说既费力又不一定对。

最厉害的办法是自己写个类似的C编译器,直接把编译后的输出打印出来。感兴趣的可以看看CIL(C Intermediate Language)。但是CIL还是有点麻烦的。它是用OCAML语言写的。我不是很熟悉。

于是我采用了一个比较折中的办法:自己写个KERNEL MODULE, 在这个MODULE里直接定义一个TASK_STRUCT,然后把感兴趣的那些成员的地址和基地址都打印出来。或者直接把偏移量打印出来。

我就是用上述办法得到了几个关键成员的偏移量:
(前面是TASK_STRUCT里的成员,后面是它距离TASK_STRUCT基地址的偏移量)
pid = 0Xbc
tgid = 0Xc0
comm = 0X1AC
tasks = 0X80

还有一点,我们怎么找到第一个TASK_STRUCT呢?可以看这篇文章(如何找出CURRENT):
http://linux.chinaunix.net/bbs/v ... =1147973&extra=

好了,万事具备,开始行动吧。
1)        运行一个QEMU VM。我是在WINDOWS HOST上跑的QEMU.  在LINUX上也可以跑QEMU。但是LINUX上的QEMU有一点不好,那就是它的QEMU MONITOR的大小是固定的。太小了。而在WINDOWS上面,QEMU MONITOR可以变得很大。

2)        按ALT+CTRL+2, 切换到QEMU MONITOR. 输入STOP. 这样QEMU 就会暂停下来了。这样的好处是你可以花很多时间来遍历进程。而不用担心某个进程结束后,带来的地址无效的问题。

3)        p $esp :找出当前的ESP。我实验的时的输出是 0xc3a94f78

4)        找出THREAD_INFO:也就是把ESP与上0XFFFFE000。得到0xc3a94000 (注意:有时这里会得到一个小于0XC000,0000的数,这是因为此时CPU是在运行用户态程序,ESP是指向的用户态堆栈。在这种情况下,无法进行下面的步骤。需要按c让QEMU跑一阵,再stop停下来。)

5)        x /20w 0xc3a94000。显示的这个内容的第一个指针(前4个字节)就是当前进程的TASK_STRUCT。因为THREAD_INFO里的第一个成员就是struct task_struct        *task
我这里得到的结果是0xc3aaa370。

(注意:有的时候这个地方显示的数据全都是0。我怀疑是因为进程正在切换中,内核栈刚刚清空。碰到这种情况,可以在QEMU MONITOR里输入c, 让QEMU继续跑一段时间,然后输入stop. 从头开始。有时要多试几次)

6)        找到了TASK_STRUCT之后,根据前面找到的偏移量,可以方便的找到PID, TGID, COMM, TASKS 等成员的值。比如PID的位置在0xc3aaa42c (TASK+0XBC)。
注意,在看COMM的时候,要用下面这个命令: x /20b 0xc3aaa51c. 否则的话,QEMU会把数据当成DWORD处理,并自动转换Endian. 使得看到的字符串的顺序是反过来的。

7)        这样一来,当前进程的信息已经知道了 。在我的输出中,PID,TGID=0,COMM=SWAPPER.  我们开始找下一个。下一个进程是通过TASK_STRUCT->TASKS 连接在一起的。有一点要注意,TASK_STRUCT->TASKS->NEXT中的地址不是下个TASK_STRUCT的起始地址,而是下个TASK_STRUCT中的TASKS的地址。所以有了这个地址后,要先减去0X80才得到基地址。然后就可以用上面的方法找出PID, TGID, COMM来了。

[ 本帖最后由 accessory 于 2010-1-28 06:24 编辑 ]
proc.GIF

论坛徽章:
0
发表于 2010-01-28 09:35 |显示全部楼层
很好,挺有意思的。看来qemu是个很多的学习linux内核的东西
只是看起来都是二进制。

论坛徽章:
0
发表于 2010-01-28 10:07 |显示全部楼层
没错。我就是把QEMU当成一个方便好用的内核调试器。很多底层信息可以方便的获得。

论坛徽章:
36
IT运维版块每日发帖之星
日期:2016-04-10 06:20:00IT运维版块每日发帖之星
日期:2016-04-16 06:20:0015-16赛季CBA联赛之广东
日期:2016-04-16 19:59:32IT运维版块每日发帖之星
日期:2016-04-18 06:20:00IT运维版块每日发帖之星
日期:2016-04-19 06:20:00每日论坛发贴之星
日期:2016-04-19 06:20:00IT运维版块每日发帖之星
日期:2016-04-25 06:20:00IT运维版块每日发帖之星
日期:2016-05-06 06:20:00IT运维版块每日发帖之星
日期:2016-05-08 06:20:00IT运维版块每日发帖之星
日期:2016-05-13 06:20:00IT运维版块每日发帖之星
日期:2016-05-28 06:20:00每日论坛发贴之星
日期:2016-05-28 06:20:00
发表于 2010-01-28 11:22 |显示全部楼层
好文章。accessory兄把QEMU玩的很熟啊

论坛徽章:
0
发表于 2010-01-29 04:26 |显示全部楼层
原帖由 accessory 于 2010-1-28 10:07 发表
没错。我就是把QEMU当成一个方便好用的内核调试器。很多底层信息可以方便的获得。


用这种方法看stack和重要的数据结构来理解内核怎么工作是很有效率的。
偶以前就是这样学Solaris内核的。

论坛徽章:
0
发表于 2010-01-29 04:31 |显示全部楼层
原帖由 accessory 于 2010-1-28 06:02 发表
Q如果是在GDB里,那么找到了TASK_STRUCT之后,可以直接用  p task->pid 的方法来把PID之类的打印出来。但是我们的目的就是不用GDB的功能,而只用QEMU MONITOR的功能,纯手工的找出PID这些成员。 ...


gdb不能查数据类型和偏移吗?

这个在Solaris的内核调试器里很容易查某个数据结构的类型定义和成员的偏移,下面是Solaris的进程控制结构的例子:

> ::print -at proc_t
{
    0 struct vnode *p_exec
    8 struct as *p_as
    10 struct plock *p_lockp
    18 kmutex_t p_crlock {
        18 void *[1] _opaque
    }
    20 struct cred *p_cred
    28 int p_swapcnt
    2c char p_stat
    2d char p_wcode
    2e ushort_t p_pidflag
    30 int p_wdata
    34 pid_t p_ppid
    38 struct proc *p_link
    40 struct proc *p_parent
    48 struct proc *p_child
    50 struct proc *p_sibling
    58 struct proc *p_psibling
    60 struct proc *p_sibling_ns
    68 struct proc *p_child_ns
    70 struct proc *p_next
    78 struct proc *p_prev
>> More [<space>, <cr>, q, n, c, a] ?

论坛徽章:
0
发表于 2010-01-29 23:59 |显示全部楼层
LS的方法我还是头一次见到。下次在GDB里试试

论坛徽章:
0
发表于 2010-01-30 02:52 |显示全部楼层
原帖由 accessory 于 2010-1-29 23:59 发表
LS的方法我还是头一次见到。下次在GDB里试试


Solaris通过一种CTF的机制实现了在二进制文件中嵌入数据类型的描述,这个如果在Linux里没有的话,可以去搞一搞,算是不错的一个特性。

论坛徽章:
0
发表于 2010-01-30 06:41 |显示全部楼层
刚刚在GDB里试了下,似乎不行。 -at 是print的参数吧?在GDB里根本就不认识-at. 换成/at也不对。

论坛徽章:
0
发表于 2010-02-01 12:37 |显示全部楼层
学习了
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP