本文主要是介绍59 多次 mmap 虚拟地址的关系,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
前言
这是来自于网友的一篇帖子
然后 我们这里来探究一下这个问题
主要是 多次连续的 mmap 获取到的 虚拟地址区域 是否连续
以及 衍生出的一些其他的问题
从 mmap 的实现 我们可以知道, mmap 的空间是 自顶向下 分配的, 因此 两块空间应该是连续的, 第一块在上面, 第二块在下面
测试用例
测试用例很简单, 就是多次 mmap 同一个文件, 然后 观察一下 其地址信息
#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>#define LEN (10*4096)int main(void) {int fd;char *vadr;if ((fd = open("./1.txt", O_RDWR)) < 0) {return 0;}vadr = mmap(0, LEN, PROT_READ, MAP_PRIVATE | MAP_LOCKED, fd, 0);printf("vaddr = 0x%x\n", vadr);printf(" ch = %c \n", vadr[10]);if ((fd = open("./1.txt", O_RDWR)) < 0) {return 0;}vadr = mmap(0, LEN, PROT_READ, MAP_PRIVATE | MAP_LOCKED, fd, 0);printf("vaddr = 0x%x\n", vadr);printf(" ch = %c \n", vadr[10]);return 0;
}
程序执行输出如下, 可以证实我们上面的猜想, 但是还有一些 细节我们需要去了解
root@ubuntu:~/Desktop/linux/HelloWorld# ./startGdbServer.sh
Test07MmapRead.c: In function ‘main’:
Test07MmapRead.c:19:12: warning: format ‘%x’ expects argument of type ‘unsigned int’, but argument 2 has type ‘char *’ [-Wformat=]printf("vaddr = 0x%x\n", vadr);^
Test07MmapRead.c:27:12: warning: format ‘%x’ expects argument of type ‘unsigned int’, but argument 2 has type ‘char *’ [-Wformat=]printf("vaddr = 0x%x\n", vadr);^
Process /root/Desktop/linux/HelloWorld/Test07MmapRead created; pid = 52114
Listening on port 1235
Remote debugging from host ::ffff:192.168.220.132, port 44444
vaddr = 0xf7fea000ch = f
vaddr = 0xf7fe0000ch = f
基于内核的调试多次 mmap 的调用
我们这里 先放出一部分调试结果信息作为参照
如下调试会分为两个部分, 一个是 内核的调试, 一个是 glibc的调试, 这两部分 完整的解释日志的信息
我们这里核心关注的是 1.txt 对应的两段空间, 以及其到 堆栈底部 的空间
所以 ld-2.23.so, libc-2.23.so 对应的空间应该是 mmap 映射了之后 进行了 munmap 因此才有如下奇怪的 mmap 空间的申请, 然后 我们这里需要 大致梳理清楚 如下日志的具体情况
2260992 - 0x228000 - 0x7ffff7dd7000 - 0x7ffff7fff000 - ld-2.23.so
16384 - 0x4000 - 0x7ffff7ff8000 - vdso_image
4096 - 0x1000 - 0x7ffff7ff7000 - system_dirs
4096 - 0x1000 - 0x7ffff7ff6000 - realname libc.so.6
3973120 - 0x3ca000 - 0x7ffff7a00000 - libc-2.23.so0x7ffff7dcf000 - dl_main - openaux - dl_map_segments 0x7ffff7dd5000 - dl_main - openaux - dl_map_segments
4096 - 0x1000 - 0x7ffff7ff5000 - dl_main - init_tls - dl_allocate_tls_storage - libc_memalign
4096 - 0x1000 - 0x7ffff7ff4000 - dl_main - init_tls - dl_allocate_tls_storage - allocate_dtv
40960 - 0xa000 - 0x7ffff7fea000 - 1.txt
40960 - 0xa000 - 0x7ffff7fe0000 - 1.txt
第一个 mmap 调用如下, 主要是 加载 ld-2.23.so, 这个纯粹是在内核的调用, 所以 待会儿 glibc 的调试不会碰到它
ld-2.23.so 文件大小接近 160kb 左右
接着 munmap 了没有使用的空间, 现在的情况是 ld-2.23.so 占用的空间是 0x7ffff7dd7000 - 0x7ffff7dfd000
释放掉了空间 0x7ffff7dfd000 - 0x7ffff7fff000
第二个 mmap 调用如下,映射的是 vdso 的镜像, 映射的区间是 0x7ffff7ff8000 - 0x7ffff7ffc000
第三个 mmap 调用如下, 映射的是 “/root/Desktop/linux/glibc-2.23/install/lib” 的字符串, 映射的区间是 0x7ffff7ff7000 - 0x7ffff7ff8000
第四个 mmap 调用如下, 映射的是 “libc.so.6” 对应的一个 linkmap 的一个结构体, 映射的区间是 0x7ffff7ff6000 - 0x7ffff7ff7000
第五个 mmap 调用如下,映射的是 “libc-2.23.so” 的动态库, 映射的区间是 0x7ffff7a0d000 - 0x7ffff7dd7000
空间之所以 在这里是因为 ld-2.23.so 的 mmap 映射区只有 2m, 这里需要 3m, 因此 继续向前寻找空间, 找到的 ld-2.23.so 前面的一块空间
第六个 mmap 调用如下, dl_main - init_tls - dl_allocate_tls_storage - libc_memalign 映射了 4k 的映射区, 映射的区间是 0x7ffff7ff5000 - 0x7ffff7ff6000
第七个 mmap 调用如下, dl_main - init_tls - dl_allocate_tls_storage - allocate_dtv 映射了 4k 的映射区, 映射的区间是 0x7ffff7ff4000 - 0x7ffff7ff5000
接下来就是我们需要关心的业务 mmap 映射了
目前 mmap 映射区已经使用的空间如下, 接着需要申请两个 40kb 的空间, 在 0x7ffff7dfd000 - 0x7ffff7ff4000 的空间是可以容纳这需求的 2 * 40kb 的
0x7ffff7a0d000 - 0x7ffff7dd7000 = libc-2.23.so
0x7ffff7dd7000 - 0x7ffff7dfd000 = ld-2.23.so
0x7ffff7ff4000 - 0x7ffff7ff5000 = dl_main - init_tls - dl_allocate_tls_storage - allocate_dtv
0x7ffff7ff5000 - 0x7ffff7ff6000 = dl_main - init_tls - dl_allocate_tls_storage - libc_memalign
0x7ffff7ff6000 - 0x7ffff7ff7000 = system_dirs
0x7ffff7ff7000 - 0x7ffff7ff8000 = realname libc.so.6
0x7ffff7ff8000 - 0x7ffff7ffc000 = vdso_image
第八个 mmap 调用申请了 40kb, 申请的空间为 0x7ffff7fea000 - 0x7ffff7ff4000
第九个 mmap 调用申请了 40kb, 申请的空间为 0x7ffff7fe0000 - 0x7ffff7fea000
最终整个 mmap 映射区使用空间如下
0x7ffff7a0d000 - 0x7ffff7dd7000 = libc-2.23.so
0x7ffff7dd7000 - 0x7ffff7dfd000 = ld-2.23.so
0x7ffff7fe0000 - 0x7ffff7fea000 = mmap("1.txt")
0x7ffff7fea000 - 0x7ffff7ff4000 = mmap("1.txt")
0x7ffff7ff4000 - 0x7ffff7ff5000 = dl_main - init_tls - dl_allocate_tls_storage - allocate_dtv
0x7ffff7ff5000 - 0x7ffff7ff6000 = dl_main - init_tls - dl_allocate_tls_storage - libc_memalign
0x7ffff7ff6000 - 0x7ffff7ff7000 = system_dirs
0x7ffff7ff7000 - 0x7ffff7ff8000 = realname libc.so.6
0x7ffff7ff8000 - 0x7ffff7ffc000 = vdso_image
基于glibc的调试多次 mmap 的调用
这里主要是基于 glibc 进行调试, 这里的断点是上面的 内核调试断点的一部分
然后 上面各个 mmap 映射区的 大部分映射空间的逻辑意义 也可以在这里找到, 上面给出了各个 mmap 映射区的逻辑意义信息, 但是没有给为什么
这里 基于 glibc 的调试就是 给出为什么
第一个 mmap 调用如下, 映射的是 “/root/Desktop/linux/glibc-2.23/install/lib” 的字符串, 映射的区间是 0x7ffff7ff7000 - 0x7ffff7ff8000
分配空间的大小参照, 以及具体使用如下, 注意这里的 malloc 不同于我们不同的应用程序的 malloc 调用
第二个 mmap 调用如下,映射的是 “libc.so.6” 对应的一个 linkmap 的一个结构体, 映射的区间是 0x7ffff7ff6000 - 0x7ffff7ff7000
分配空间的大小参照, 以及具体使用如下, 主要是新建了一个 linkmap 结构体, 然后存储它的各个属性
第三个 mmap 调用如下,映射的是 “libc-2.23.so” 的动态库, 映射的区间是 0x7ffff7a38000 - 0x7ffff7dd7000
因为在宿主机 和 在 qemu 虚拟机中 libc-2.23.so 大小不太一样, 因此这里 libc-2.23.so 对应的 vma 区间不太一致
第四个 mmap 调用如下, 这里我们不太关心, 直接跳过
内核中没有该断点, 因为这里是传入的地址, 内核的断点是在 申请空间的地方
第五个 mmap 调用如下, 这里我们不太关心, 直接跳过
内核中没有该断点, 因为这里是传入的地址, 内核的断点是在 申请空间的地方
第六个 mmap 调用如下, dl_main - init_tls - dl_allocate_tls_storage - libc_memalign 映射了 4k 的映射区, 映射的区间是 0x7ffff7ff5000 - 0x7ffff7ff6000
第七个 mmap 调用如下, dl_main - init_tls - dl_allocate_tls_storage - allocate_dtv 映射了 4k 的映射区, 映射的区间是 0x7ffff7ff4000 - 0x7ffff7ff5000
第八个 mmap 调用申请了 40kb, 申请的空间为 0x7ffff7fea000 - 0x7ffff7ff4000
从 rax 寄存器中可以看到 mmap 申请的空间的首地址, 从上层堆栈信息中可以看到当前执行的代码位置
第九个 mmap 调用申请了 40kb, 申请的空间为 0x7ffff7fea000 - 0x7ffff7fe0000
从 rax 寄存器中可以看到 mmap 申请的空间的首地址, 从上层堆栈信息中可以看到当前执行的代码位置
main 中 第一个 mmap 和 第二个 mmap 调用如下, 你也可以对一下 上面 glibc 上层调用 mmap 的代码的地址信息
同一个文件, 两个虚拟地址空间怎么关联的?
虚拟地址空间映射物理页的时候是从 pagecache 中获取的 给定的文件对应的物理页
两个虚拟地址空间 对应的文件一样, 因此是映射到了相同的物理页
第一个 mmap 映射区的第一个物理页如下
第二个 mmap 映射区的第一个物理页如下, 可以发现 两个虚拟地址空间映射的物理页相同
完
这篇关于59 多次 mmap 虚拟地址的关系的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!