打印虚拟地址空间中的字段

以test.c为例,分析一个进程生成一个可执行文件、装入虚拟内存的过程:

test.c代码如下:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int  a;
int  b=1;
 
int main()
{
    int  n = 0;
    char *p1 = NULL;
    char *p2 = NULL;
    const int s = 10;
    p1 = (char*)malloc(200);
    p2 = "zxy";
    printf("PID is:%d\n\n",getpid());
    printf("main  %p\n",main);
    printf("未初始化 a   %p\n",&a);
    printf("初始化   b   %p\n",&b);
    printf("局部变量 n   %p\n",&n);
    printf("动态内存 p1  %p\n",p1);
    printf("常量    s    %p\n",&s);
    printf("常字符串 p2  %p\n",p2);
    pause();
    return 0;	
}

生成可执行文件的过程:

预处理(Prepressing)—>编译(Compilation)—>汇编(Assembly)—>链接(Linking)

  • gcc -E test.c -o test.i命令,可以查看预处理之后的test.i文件,预处理器把所有的头文件替换为实际文件的内容,并去掉了注释等等。

  • gcc -S test.c -o test.s命令,可以查看编译后的汇编代码:在这里插入图片描述

  • 接下来是汇编,将编译后的汇编代码翻译为机器码,几乎每一条汇编指令对应一句机器码。下图为整个过程:
    在这里插入图片描述

64位系统下进程的内存布局

对于64位的系统,理论上,64bit内存地址可用空间为0x0000000000000000 ~ 0xFFFFFFFFFFFFFFFF(16位十六进制数),这是个相当庞大的空间,Linux实际上只用了其中一小部分(256T)。

  • Linux64位操作系统仅使用低47位,高17位做扩展(只能是全0或全1)。所以,实际用到的地址为空间为0x0000000000000000 ~ 0x00007FFFFFFFFFFF(user space)和0xFFFF800000000000 ~ 0xFFFFFFFFFFFFFFFF(kernel space),其余的都是unused space,user space 也就是用户区由以下几部分组成:代码段,数据段,BSS段,heap,stack。

查看该进程的内存映射信息:

cat /proc/pid/maps

在这里插入图片描述

  • 可以看到在heap之前有五个内存区域,通过查看其权限,不难看出第二个是可执行的,所以存放的是代码段,可以看到代码段的存放起始地址。最后一个可写可读,所以存放的是数据段,但只存放全局和静态变量,局部变量存放在栈里。

查看该进程的节信息:

readelf -S test

在这里插入图片描述

查看该进程的段信息:

readelf -l test

在这里插入图片描述
在这里插入图片描述

  • 在这里可以看到各个load段中对应的节。

  • 在映射时分配给数据段的大小会大于实际大小,最后一个load段多出来的部分分给了bss段0x288-0x27c=0xc 与bss节的大小相等,不专门给bss一个专门的段去映射。

图解:

在这里插入图片描述

  • test.c运行结果分析:

在这里插入图片描述

  • 从程序的入口地址main 0x55ee7bfe81c9 可以看出,maps文件的第二行确实是程序的代码段。

  • a、b都是全局变量,a未定义(bss),b已定义(data),查看其地址存储在数据段,maps文件的第二行确实是程序的数据段。

  • 局部变量n,存放在stack内。

  • 动态内存p1,存放在堆段。

  • const常量s存放在栈里。

打印该进程虚拟空间字段的模块:hh.c

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

static int pid = -1;
module_param(pid, int, 0644); // 模块参数,表示目标进程的PID

// 打印目标进程的虚拟空间字段
static int print_vm_areas(void) {
  struct task_struct *task;
  struct mm_struct *mm;
  struct vm_area_struct *vma;

  printk(KERN_INFO "print_vm_areas: begin.\n");

  // 通过进程 PID 找到对应的进程的 task_struct 结构
  task = pid_task(find_vpid(pid), PIDTYPE_PID);
  if (!task) {
      printk(KERN_ERR "print_vm_areas: cannot find process with PID %d\n", pid);
      return -EINVAL;
  }

  // 找到进程的 mm_struct 结构,即内存描述符
  mm = task->mm;
  if (!mm) {
      printk(KERN_ERR "print_vm_areas: failed to get mm_struct of process with PID %d\n", pid);
      return -EINVAL;
  }

  // 打印进程的名称和 PID
  printk(KERN_INFO "Executable name: %s, PID: %d\n", task->comm, pid);

  // 打印进程的代码段、数据段和堆栈地址范围
  printk(KERN_INFO "Code: 0x%lx - 0x%lx\n", mm->start_code, mm->end_code);
  printk(KERN_INFO "Data: 0x%lx - 0x%lx\n", mm->start_data, mm->end_data);
  printk(KERN_INFO "Heap: 0x%lx-0x%lx\n", mm->start_brk, mm->brk);
  printk(KERN_INFO "Stack: 0x%lx\n", mm->start_stack);

  // 遍历进程的内存映射区间并打印
  printk(KERN_INFO "Virtual memory areas:\n");
  for (vma = mm->mmap; vma; vma = vma->vm_next) {
      printk(KERN_INFO "%16lx-%16lx %c%c%c%c\n",
          vma->vm_start,
          vma->vm_end,
          (vma->vm_flags & VM_READ) ? 'r' : '-',
          (vma->vm_flags & VM_WRITE) ? 'w' : '-',
          (vma->vm_flags & VM_EXEC) ? 'x' : '-',
          (vma->vm_flags & VM_SHARED) ? 's' : 'p');
  }

  printk(KERN_INFO "print_vm_areas: end.\n");
  return 0;
}

// 模块初始化函数,在加载模块时调用
static int __init print_init(void) {
  printk(KERN_INFO "print_vm_areas: module loaded.\n");
  print_vm_areas(); // 打印目标进程的虚拟空间字段
  return 0;
}

// 模块退出函数,在卸载模块时调用
static void __exit print_exit(void) {
  printk(KERN_INFO "print_vm_areas: module unloaded.\n");
}

module_init(print_init);
module_exit(print_exit);
MODULE_LICENSE("GPL");

```csharp
obj-m:=hh.o
CURRENT_PATH:=$(shell pwd)
LINUX_KERNEL:=$(shell uname -r)
LINUX_KERNEL_PATH:=/usr/src/linux-headers-5.15.0-86-generic
all:
  make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
clean:
  make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean

运行命令:

make
sudo insmod hh.ko pid=XX
dmesg

运行截图:

在这里插入图片描述

可以看出,与我们test.c的运行结果相对应。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值