当前位置: 首页 > news >正文

vdso内核与glibc配合的相关逻辑分析

一、背景

在之前的 vdso概念及原理,vdso_fault缺页异常,vdso符号的获取-CSDN博客 博客里,我们分析了vdso的概念及内核部分的原理,并通过讲vdso_fault和vvar_fault两个vdso相关的缺页异常的处理函数来加深vdso实现的细节处的理解,也同时对内存缺页异常处理的逻辑细节进行了一定的拓展,最后也讲解了vdso符号要如何获取,涉及到了vdso代码段还有数据段,还有vsyscall的段这些内容对应的符号的获取。

这篇博客我们继续vdso的原理介绍,侧重于vdso与glibc的配合部分,也会涉及相关的与内核部分配合的逻辑。

二、clock_gettime的实现的调试跟踪

我们在用户态代码里调用clock_gettime函数:

单步+反汇编窗口通过vs2019的gdb调试(具体vs2019的gdb调试见之前的博客 linux上对于so库的调试——包含通过vs2019远程ssh调试so库_vs linux 远程调试so文件-CSDN博客):

调用到的glibc里的sysdeps/unix/sysv/linux/clock_gettime.c里:

然后走到了clock_gettime.c里的42行,如下图:

所以,说明HAVE_CLOCK_GETTIME64_VSYSCALL是有值的,x86下定义的是:

且vdso_time64也是有值的,它是通过GLRO(dl_vdso_clock_gettime64)来得到:

而GLRO(dl_vdso_clock_gettime64)是在dl-vdso-setup.h里的setup_vdso_pointers里进行的赋值:

是通过dl_vdso_vsym函数来拿HAVE_CLOCK_GETTIME64_VSYSCALL也就是__vdso_clock_gettime的符号:

三、auxiliary vector机制与AT_SYSINFO_EHDR

auxiliary vector是用户态和内核态通讯的一种机制,它是一系列键值对的列表,在内核加载应用程序时会将其存储在与用户栈临近的地址空间上。如下图:

这个auxiliary vector数组里有一项与vdso有关,即AT_SYSINFO_EHDR,内核代码里有关于它的注释,如下图:

3.1 内核里与auxiliary vector机制的相关细节

在mm_types.h里定义mm_struct结构体里有如下定义saved_auxv数组:

如上图,saved_auxv数组对应于/proc/PID/auxv。

3.1.1 create_elf_tables函数设置了该saved_auxv数组,用了ARCH_DLINFO宏

在binfmt_elf.c里定义的create_elf_tables函数里有该saved_auxv数组的赋值,如下图:

先是定义了NEW_AUX_ENT宏,然后用ARCH_DLINFO宏,而ARCH_DLINFO宏用了NEW_AUX_ENT宏设置了AT_SYSINFO_EHDR的值。

ARCH_DLINFO宏如下图:

3.1.2 设置AT_SYSINFO_EHDR的值为current->mm->context.vdso

如上图,设置AT_SYSINFO_EHDR的值为current->mm->context.vdso。

而该current->mm->context.vdso的值在之前的博客 vdso概念及原理,vdso_fault缺页异常,vdso符号的获取-CSDN博客 里的 2.6一节里有相关调用链的介绍里的map_vdso函数里做了该current->mm->context.vdso的设置:

exec_binprm->do_execveat_common->bprm_execve->exec_binprm->search_binary_handler->load_elf_binary->ARCH_SETUP_ADDITIONAL_PAGES宏->arch_setup_additional_pages->map_vdso_randomized->map_vdso->_install_special_mapping

map_vdso里设置current->mm->context.vdso的截图:

3.1.3 create_elf_tables函数是在load_elf_binary里的ARCH_SETUP_ADDITIONAL_PAGES宏之后运行

而 3.1.1 里讲的 create_elf_tables 函数在下面的调用链里的下面调用链里的load_elf_binary函数里的ARCH_SETUP_ADDITIONAL_PAGES宏之后运行的。

exec_binprm->do_execveat_common->bprm_execve->exec_binprm->search_binary_handler->load_elf_binary->ARCH_SETUP_ADDITIONAL_PAGES宏->arch_setup_additional_pages->map_vdso_randomized->map_vdso->_install_special_mapping

3.2 execve系统调用的内核逻辑里设置新程序入口和载入解释器逻辑

execve系统调用在load_elf_binary里执行完ARCH_SETUP_ADDITIONAL_PAGES和create_elf_tables之后,还执行了两个重要逻辑,即设置新程序入口,和载入解释器逻辑。

为什么要在分析这两个逻辑,因为这两个逻辑决定了用户态的代码在execve系统调用返回用户态之后从哪里开始执行,如何执行。

3.2.1 在load_elf_binary里执行START_THREAD宏用新的ip和sp

在load_elf_binary里执行完ARCH_SETUP_ADDITIONAL_PAGES和create_elf_tables之后,执行了START_THREAD宏:

在elf.h里有如下定义:

看一下start_thread_common的实现:

如上图,会用new_ip和new_sp来替换当前的ip和sp。这样execve系统调用返回用户态时就能进入新的程序入口了。

而new_ip和new_sp即传入START_THREAD宏里的elf_entry和bprm->p:

3.2.2 load_elf_binary里载入解释器的逻辑

上图里的elf_entry是程序的入口地址,在有用到动态链接库时是解释器的入口地址,如果没有用到动态链接器elf_entry则是程序本身的入口地址。

load_elf_binary里与载入解释器相关的逻辑如下:

即遍历elf里程序头表条目,找到是PT_INTERP类型的,就是解释器段的类型。

如果是解释器段,就读取这个条目的内容到elf_interpreter数组里:

然后调用open_exec(传入解释器文件的路径,解释器文件路径如/lib64/ld-linux-x86-64.so.2),得到打开文件后的file指针。这个open_exec函数里会通过do_open_execat打开,do_open_execat函数实现里会设置一些flags属性再打开,再设置file的deny write access:

通过open_exec(elf_interpreter)得到解释器的file指针赋值给interpreter后,通过load_elf_interp函数通过解释器来加载elf,如果没有用到动态链接库,即解释器file指针为NULL时则直接把elf的entry给到elf_entry:

四、glibc里的dynamic linker的逻辑和_dl_non_dynamic_init

为什么看glibc里的dynamic linker的逻辑和_dl_non_dynamic_init的逻辑是因为vdso的base地址以及如何拿到vdso里的一些函数地址和vdso数据的共享内存地址都是由这一章里介绍的dynamic linker的逻辑和_dl_non_dynamic_init的逻辑调用到的。

4.1 glibc里的dynamic linker逻辑

4.1.1 从_dl_start开始到setup_vdso的完整的调用链

从_dl_start开始到vdso的base地址获取的完整的调用链:

_dl_start->_dl_start_final->_dl_sysdep_start->dl_main->setup_vdso

dynamic linker逻辑由elf/rtld.c里的_dl_start开始:

调用了_dl_start_final函数:

在_dl_start_final函数里调用了_dl_sysdep_start:

在_dl_sysdep_start函数里调用了传入给_dl_sysdep_start函数的cb:dl_main

在dl_main中,会调用与vdso相关的两个函数setup_vdso和setup_vdso_pointers:

我们会在下面 4.1.3 里继续分析这两个vdso相关的函数,setup_vdso和setup_vdso_pointers。在分析setup_vdso和setup_vdso_pointers之前,我们先分析一下这两个函数会用到的GLRO(dl_sysinfo_dso)值的获取:

4.1.2 GLRO(dl_sysinfo_dso)值的设置

GLRO(dl_sysinfo_dso)值在setup_vdso函数里会使用到,这个dl_sysinfo_dso是有关vdso这个so的一些具体信息。这些信息属于so的elf的header里的信息,在内核的工具里有相关信息的设置,这些信息最终都是在编译内核的步骤里完成这个vdso的这个so的完整二进制的组装。相关的如e_phoff和e_phnum如下图,在内核代码arch/x86/tools/relocs.c里进行了设置:

内核逻辑把vdso代码及vvar数据映射到用户态之后(之前的博客  vdso概念及原理,vdso_fault缺页异常,vdso符号的获取_x86架构的vdso-CSDN博客 2.6 一节),通过auxv辅助数组传递给用户态,最终在上面 4.1.1 里介绍的下面的调用链里的_dl_sysdep_start函数里设置的dl_sysinfo_dso:

_dl_start->_dl_start_final->_dl_sysdep_start->dl_main->setup_vdso

dl_sysinfo_dso这个数值其实是一个地址,这个地址是一个基地址,根据这个基地址就可以找到与vdso这个so相关的更具体的信息。

在上图的这个设置之后,在_dl_sysdep_start里的最后,调用了dl_main,在dl_main里调用了setup_vdso。在下一节,我们讲setup_vdso和setup_vdso_pointers两个函数,然后在 4.1.4 一节,我们讲上图里GLRO(dl_sysinfo_dso)的设置所通过的下图里的循环里的GLRO(dl_auxv)是如何获取正确的值的:

4.1.3 setup_vdso和setup_vdso_pointers

setup_vdso函数是根据上面 4.1.2 里说的已经完成初始化了的dl_sysinfo_dso,来进行vdso这个so的link_map的初始化,初始化完成之后,设置到GLRO(dl_sysinfo_map)里去,如下图:

我们看一下这个struct link_map *l是如何创建和初始化的:

如果dl_sysinfo_dso非NULL,则创建一个link_map对象,并设置下图里的__RTLD_VDSO标志位,表示这个so是system_loaded的。

然后把dl_sysinfo_dso这个vdso的base地址根据数据协议找elf header里的信息,填充到刚创建的这个link_map对象里去:

然后遍历这些phnum项,进行link_map里更多信息的设置:

接下来,我们看一下setup_vdso_pointers这个函数:

setup_vdso_pointers函数主要就是用dl_vdso_vsym去根据符号名找函数指针。

我们看dl_vdso_vsym函数的实现:

其实,有了上面的分析,我们其实可以看得懂下面这个dl_vdso_vsym函数的实现的:

就是通过之前完成初始化赋值的这个link_map的对象dl_sysinfo_map,去根据里面的信息,通过dl_lookup_symbol_x函数去根据名字去找里面的符号的函数地址。

4.1.4 START_THREAD宏传入的bprm->p到GLRO(dl_auxv)

在上面 3.2.1 一节里我们介绍了START_THREAD宏,内核里使用START_THREAD宏来设置pt_regs里的ip和sp的调用地方load_elf_binary函数里:

传入的bprm->p的设置的地方是,在create_elf_tables里:

这个传给用户态的这个bprm->p就是arg的地址。

再来看用户态的从_dl_start的入参(void *arg)到到_dl_start_final的入参(void *arg)再到_dl_sysdep_start的入参(void **start_argptr)也是一级级传下来的。

在_dl_sysdep_start函数里,会根据传入的start_argptr,得到计算出_dl_argc,_dl_argv,_environ等与进程上下文相关的一些关键数据:

如上图,这里面也包含了dl_auxv这个包含了vdso的base地址信息的辅助数组:

这里,GLRO宏仅仅是加了一个下划线:

4.2 glibc里的_dl_non_dynamic_init逻辑

上面 4.1 一节,我们其实已经把vdso相关的细节在介绍更常见的带so库运行程序的情景时全部讲到了。

对于不带so库运行程序的场景,vdso的两个相关初始化函数setup_vdso和setup_vdso_pointers也是会被执行到的,如下图:

相关文章:

  • IDEA打不开、打开报错
  • 【Easylive】手动实现分布式事务解决方案流程解析
  • 【Flask】Explore-Flask:早期 Flask 生态的实用指南
  • 多模态大语言模型arxiv论文略读(三十三)
  • 【产品经理思维】
  • 多级缓存架构,让系统更快的跑起来!
  • 特伦斯智慧钢琴评测:如何用科技重塑钢琴学习新体验
  • Cribl 利用表向event 中插入相应的字段-example-01
  • C++入门语法
  • FreeRTOS中的优先级翻转问题及其解决方案:互斥信号量详解
  • 第十四届蓝桥杯 2023 C/C++组 平方差
  • 设计模式 建造者模式
  • Pycharm(九)函数的闭包、装饰器
  • compat-openssl10和libnsl下载安装
  • Java高效合并Excel报表实战:GcExcel让数据处理更简单
  • 靠华为脱胎换骨,但赛力斯仍需要Plan B
  • MySQL访问权限授权问题
  • 二分查找、分块查找、冒泡排序、选择排序、插入排序、快速排序
  • SPL 量化 序言
  • 【FFmpeg从入门到精通】第四章-FFmpeg转码
  • 高架上2名儿童从轿车天窗探出身来,驾驶员被记3分罚200元
  • 我国与沙特签署《核能发展安全与安保合作谅解备忘录》
  • 《“四有”好老师系列丛书》发布,由顾明远总主编
  • 官方披露:定西民政局原局长将收受烟酒高价“倒卖”给单位,用于违规接待
  • 中央宣传部原副部长张建春被提起公诉
  • 9部门发文促进家政服务消费扩容升级