您的位置:首页 > 运维架构 > Linux

VMware对挂起linux快照文件的进程分析

2017-05-06 19:24 190 查看
写作业,记录一下心得。致敬阳神。

以下内容都是我的个人理解,可能有各种偏差。。。orz

虚拟机在进行挂起时,为了在下一次打开时能够直接使用,它保存了当前状态的内存信息!

我猜是虚拟机突然剥夺了什么资源吧,然后整个系统就在等资源了,然后趁机拷贝内存。。。

内存拷贝好后是一个*.vmem文件,大小应该是等于你分配的内存大小。

把它复制到一个风水宝地,比如你外界的的c++工程目录。



然后就开始分析吧:

首先要看进程信息,就要知道进程的管理是靠PCB控制块,那么,内存里的PCB控制块放在哪里呢?

讲道理我是不知道的,但是,所有的总得有个头吧。也就是传说中的0号进程——init_task

0号进程是linux初始化的第一个进程,直接由系统生成,他被定义在Syetem.map里,这个文件在linux的boot里



无法直接查看的,只有用命令行,要root才能cat



这样你生成一个xxxx.txt的文件,其实就是把这个玩意复制了一份,现在你可以查看xxxx.txt了

通过简单的搜索,可以找到init_task的位置。

我的在56410行



我还是不太习惯linux,所以大部分操作都在windows(其实现在已经对linux有些好感了)

好了,我们找到了它对应的地址,就是c1863200,这个是个虚拟地址,映射到物理地址就是直接减去0xc0000000.

因为我们查询的文件就是物理的内存条,所以,所有地址都是物理地址。但在指针的寻址时,却是虚拟地址,反正要有个转换

找到了这个地址的物理地址 就是0x01863200.至此我们找到了0号进程的地址。

我们要访问进程的信息,就要知道PCB中各种信息的位置偏移。

怎么找偏移呢?难点来了

我们要用到一个函数

offsetof(类型名,成员名);

这个函数是查看指定成员在指定类型里的偏移。

但是我们却要在linux里编写程序,而且还是内核程序!因为我们要用到内核的数据结构。。反正我之前是想的太简单,耽误很久时间,还是黄巨指导说要在内核编程。

怎么进行内核编程呢,百度各种博客,,我就直接抄了一篇,http://blog.csdn.net/chang198932/article/details/17006537

我也忘了当时抄的那一篇了。

//必要的头文件
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
//模块许可证声明(必须)
MODULE_LICENSE("Dual BSD/GPL");
//模块加载函数(必须)
static int hello_init(void)
{
printk(KERN_ALERT "Hello World enter/n");
return 0;
}
//模块卸载函数(必须)
static void hello_exit(void)
{
printk(KERN_ALERT "Hello World exit/n");
}
//模块的注册
module_init(hello_init);
module_exit(hello_exit);


直接抄,然后改。

这个的意思是内核加载时候,就执行hello_init,退出时候加载exit那个

你的代码写在init里面就好; 

这里的代码写你需要查询的偏移量啊,验证一些数据结构的大小什么的。

我的查询,查询了pid,comm,tasks,uid,gid这几样

//hello.c 源码
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/sched.h>

static int __init mini2440_hello_module_init(void)
{
/* Set up the anchor point */
struct task_struct *task = &init_task;
printk (KERN_WARNING "===OPEN===\ntask_struct-size:%d\n\n",sizeof(struct task_struct));

printk (KERN_WARNING "task->pid-size:%d\n",sizeof(task->pid));
printk (KERN_WARNING "task->comm-size:%d\n",sizeof(task->comm));
printk (KERN_WARNING "task->tasks-size:%d\n",sizeof(task->tasks));
printk (KERN_WARNING "struct cred-size:%d\n",sizeof(struct cred));
printk (KERN_WARNING "task->real_cred-size:%d\n\n",sizeof(task->real_cred));
printk (KERN_WARNING "task->real_cred->uid-size:%d\n\n",sizeof(task->real_cred->uid));

printk (KERN_WARNING "pid:%d\n",offsetof(struct task_struct,pid));
printk (KERN_WARNING "comm:%d\n",offsetof(struct task_struct,comm));
printk (KERN_WARNING "tasks:%d\n",offsetof(struct task_struct,tasks));
printk (KERN_WARNING "real_cred:%d\n",offsetof(struct task_struct,real_cred));
printk (KERN_WARNING "tasks.next:%d\n",offsetof(struct list_head,next));
printk (KERN_WARNING "cred.uid:%d\n",offsetof(struct cred,uid));
printk (KERN_WARNING "cred.gid:%d\n",offsetof(struct cred,gid));

return 0;
}

static int __exit mini2440_bye_module_exit(void)
{
printk (KERN_WARNING "bye , mini2440\n");
return 0;
}

module_init(mini2440_hello_module_init);
module_exit(mini2440_bye_module_exit);

MODULE_LICENSE("GPL");


然后,你如果每次按照常规的方法编译,很麻烦,每次提供版本号啊各种神奇的东西,网上提供了一个简易方法,就是写一个Makefile文件

Makefile文件内容

obj-m := hello.o

KERNEL_DIR := /lib/modules/$(shell uname -r)/build
#KERNEL_DIR := /opt/FriendlyARM/mini2440/linux-2.6.32.2

PWD := $(shell pwd)

all:
make -C $(KERNEL_DIR) M=$(PWD) modules

clean:
rm *.o *.ko *.mod.c  Module.symvers modules.order


如果你要改文件名,两个要一起改。。

现在你有两个文件

放一起



然后打开命令行里cd到这个目录

直接make



然后加载这个.ko文件



加载的时候还是要root

加载成功没有任何提示,每错,,,并么有标准的输出,,但是我们可以查看日志





我们刚刚写的东西,都出来了。

最后,别忘了卸载



找到了偏移,我就就可以愉快的写程序了

直接上代码吧:

#define _CRT_SECURE_NO_WARNINGS
#include<bits/stdc++.h>
using namespace std;

unsigned int charToUint(char* ch)
{
unsigned int tmp = 0;
for (int i = 0; i < 4; ++i) {
tmp |= (0x000000ff & ch[i]) << 8 * i;
}
return tmp;
}

int main()
{
ifstream ifile("mem.vmem",ios::binary);
ofstream ofile("Print.txt");
int count = 0;
char comm_c[16],pid_c[4],taskN_c[4],uidc[4],gidc[4];
unsigned int pid = -1, next = 0x01863200 + 440, uid, gid;
ofile << "pid " << "\t" << "name" << "\t" << "uid " << "\t" << "gid " << endl;
while (1) {
ifile.seekg(next + 80);
ifile.read(pid_c, 4);
pid = charToUint(pid_c);
if (pid == 0 && count != 0)break;
ifile.seekg(next + 300);
ifile.read(comm_c, 16);
ifile.seekg(next + 292);
ifile.read(uidc, 4);
uid = charToUint(uidc);
ifile.seekg(uid - 0xc0000000 + 4);
ifile.read(uidc, 4);
uid = charToUint(uidc);
ifile.seekg(next + 292);
ifile.read(gidc, 4);
gid = charToUint(gidc);
ifile.seekg(gid - 0xc0000000 + 8);
ifile.read(gidc, 4);
gid = charToUint(gidc);
ifile.seekg(next);
ifile.read(taskN_c, 4);
next = charToUint(taskN_c);
next -= 0xc0000000;
ofile << pid << "\t" << comm_c << "\t" << uid << "\t" << gid << endl;
++count;
}

}


很简单,,就是读入文件,找到对应的偏移量。

对了忘了很重要的一点

就是循环的读取进程控制块

在task_struct里面

tasks这个结构就是进行连接用的指针,这个结构有两个指针,一个后,一个前,不停往后遍历就好了。。

其实主要就是内核找偏移麻烦点,搞定了那里,C++的部分简单了。

其实要查看什么信息,直接网上搜一搜,很多结构啊什么的都有了,第一篇博客,记录一下实验心得,结束。

(博客贴图差评啊!为啥不能直接粘贴!!)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: