zoukankan      html  css  js  c++  java
  • /proc/kcore失效,调试其文件系统相关模块,使重新正常工作

    为分析内核,在有限的机器上用虚拟机装了CentOS.6.9.i386.minimal,重新编译了3.19.8内核并克隆。当使用/proc/kcore进行内核动态映像调试时,发现与kgdb远程调试端读到的内存数据不一样。运行内核的测试机上的/proc/kcore里面的数据大多都为0,几乎没有一处用途。不管我进行多少次core-file去刷新/proc/kcore,结果也是无功而返。开始以为gdb与新内核不兼容,但并不是,用hexdump去读取/proc/kcore的数据,只有开头一小段是有数据迹象,其余都基本为0。/proc/kcore大小约为1G,内核arch为x86。
    新内核是按默认配置进行编译,不知道是否其中有配置影响了/proc/kcore文件系统,于是我切换到随CentOS.6.9发布的内核2.6.32-696.el6.i686。结果出来了,在CentOS官方编译的32位内核中,hexdump同样只可以读到/proc/kcore第一个页面大小的数据内容,当读取到其余空间的内容时,只返回000100(也就是一个页面的大小数值)。
    那就可能跟编译选项有关,于是找遍了menuconfig,和比较/boot目录里的config-$(uname -r),再参考kconfig里面的参考说明,好像也没有找到相关的选项。最后手段就是去调试文件系统下的kcore了。

    kcore模块的代码在$YourSrcDir/fs/proc/kcore.c。先去阅读代码找出有用的函数作为断点切入分析。kcore.c文件不大,只有600不到700行,只要稍为熟识文件系统就可以分析。
    kcore特殊文件只实现了(虚)文件系统中的 file_operations 接口,系统调用文件IO,映射相关的接口。

    static const struct file_operations proc_kcore_operations = {
        .read        = read_kcore,
        .open        = open_kcore,
        .llseek        = default_llseek,
    };

    kcore只为文件系统提供了read, open, seek三种操作服务。也就是说我们只可以通过系统调用进行kcore的这三种操作。

    下面是kcore.c模块相关的数据结构和静态变量:

    // kcore.h
    enum kcore_type {
        KCORE_TEXT,
        KCORE_VMALLOC,
        KCORE_RAM,
        KCORE_VMEMMAP,
        KCORE_OTHER,
    };
    
    struct kcore_list {
        struct list_head list;
        unsigned long addr;
        size_t size;
        int type;
    };
    
    struct vmcore {
        struct list_head list;
        unsigned long long paddr;
        unsigned long long size;
        loff_t offset;
    };
    // kcore.c模块的静态变量:
    static LIST_HEAD(kclist_head);
    static DEFINE_RWLOCK(kclist_lock);
    static int kcore_need_update = 1;

    这里主要有两类地址,KCORE_VMALLOC和KCORE_RAM。KCORE_RAM,在32位内核中内核地址与物理地址存在一种简单的对应关系,内核地址=物理地址+3G,所以叫KCORE_RAM。但是3G到4G的地址空间中,也不是全部应用这一简单映射关系的,还有一块特殊的区域,就是KCORE_VMALLOC,它位于4G地址空间的最高256M,即0xf0000000到0xfffff000内。

    VMALLOC这部分虚拟地址是用在ioremamp,也就是外部设备存储单元使用到的I/O mapped地址。这区域的VMALLOC要与内存管理mm中的vm_area区分好。vmalloc是从内核地址空间,0xc0000000到high_memory(即VMALLOC_START - VMALLOC_OFFSET)为止,进行虚拟空间分配,使用vm_struct结构和vmlist全局队列管理。进程的内存空间由mm_struct结构和vm_area_struct结构进行管理。

    // pgtable_32.c
    unsigned long __FIXADDR_TOP = 0xfffff000;
    EXPORT_SYMBOL(__FIXADDR_TOP);
    // fixmap.h
    #define FIXADDR_TOP    ((unsigned long)__FIXADDR_TOP)
    #define FIXADDR_START        (FIXADDR_TOP - FIXADDR_SIZE)
    #define FIXADDR_SIZE    (__end_of_permanent_fixed_addresses << PAGE_SHIFT)
    // pgtable_32_types.h
    #define VMALLOC_START    ((unsigned long)high_memory + VMALLOC_OFFSET)
    #ifdef CONFIG_HIGHMEM
    # define VMALLOC_END    (PKMAP_BASE - 2 * PAGE_SIZE)
    #else
    # define VMALLOC_END    (FIXADDR_START - 2 * PAGE_SIZE)
    #endif
    #define MODULES_VADDR    VMALLOC_START
    #define MODULES_END    VMALLOC_END
    #define MODULES_LEN    (MODULES_VADDR - MODULES_END)

    变量high_memory标志着具体物理内存的上限所对应的虚拟地址,这是在系统初始化时设置好的。通过kgdb调试,查看到high_memory为0xf0000000。

    为什么我的内核只可以读到/proc/kcore的一个页面的数据,其它地址上的数据全为0呢,那么参考读操作函数read_kcore。
    这个函数主要是将/proc/kcore文件尺寸的线性地址空间映射到不同的区域进行读操作,与内存(文件)映射类似。
    1.read_kcore将第一个页面的地址空间的读操作,映射为一个ELF core header头结构的数据内容。
    2.从这个头结构偏移后的地址,使用内核地址的简单映射规则,进行映射,即 addr + 3G - sizeof(ELF core header)。
    3.分别分派到kcore_list区域进行读操作。
    4.如果访问地址满足is_vmalloc_or_module_addr,则进行vread,并copy_to_user。
    5.如果访问地址满足kern_addr_valid,则直接进行copy_to_user。
    6.其余情况clear_user。

    先来看kcore是如何将kcore文件的线性地址映射到内核地址的,下面的相关函数的反汇编代码:

    这里要注意的是,代码是被-O2优化过的,反汇编并不能与代码很好地对应,开头两行可能是承着某处的跳转的。从行文来看%esi优化为变量elf_buflen。

    将图的下面4行汇编逆向为 0xc0000000 - elf_buflen + *fpos。 也就是 offset + 3G - sizeof(ELF core header)。

    再来看kcore一共有几个映射区间,只要walk through一下kclist_head队列就可以知道。自己写一下调试脚本就可以得到。

    kclist_head有两个区间,地址区间与info files查看到映像区间一致。分别是0xf0800000开始的KCORE_VMALLOC区间,和0xc0000000开始的KCORE_RAM区间。 这两个区间下面还会进行说明。

    这就好办了,断点设置在read_kcore跑一次。结果发现在头部分偏移之后的地址量的读操作一切都被clear_user,也就是填0。对应高地址(即vmalloc,ioremap)的读操作则是正常的。代码已经被-O2优化过,用混合反汇编出来的代码和汇编对应也是乱七八糟的。不过好在符号信息还在,那就设断点到kern_addr_valid,却发现找不到符号。经过汇编分析后发现,代码在判断is_vmalloc_or_module_addr失败后直接进行clear_user。按代码的逻辑应该是先进行kern_addr_valid判断。也就是访问地址不在最高 处的vamlloc,再判断是否在内核有效的地址空间内。但实际的反汇编代码却根本找不到这个判断逻辑。查阅kern_addr_valid在不同arch下实现后,发现x86下这个函数只是一个开关。

    // arch/x86/include/asm/pgtable_32.h
    #ifdef CONFIG_FLATMEM
    #define kern_addr_valid(addr)    (1)
    #else
    #define kern_addr_valid(kaddr)    (0)
    #endif

    终于发现在arch x86中,内存管理模型影响了/proc/kcore特殊文件的运作。只有在FLAT模型下的内存管理,/proc/kcore才能提供正常的服务。内核中默认配置是使用SPARSE模型的,不管理是什么arch的处理器。CentOS的32位内核也使用了这一默认配置,经过/boot/config-2.6.32-696.el6.i686核实。

    内核3.19.8重新进行menuconfig配置,选用FLAT内存管理模型,再次编译后。OK!/proc/kcore特殊文件正常读出内核内存映像的数据。x86 32位的内核必须使用FLAT内存管理模型,即CONFIG_FLATMEM_ENABLE=y,才可以正常使用/proc/kcore。

    在重编译后,我懒得编译modules,使用原来SPARSE内存模型编译出来的modules进行安装。安装时提示许多mm_section符号缺失的warning,实际运行内核时也就杯具了。幸好是虚拟机,回退重新全部编译就是了,机器配置有限,至少就一个半小时。

    现在大家都用arch x86_64的内核,内核不会出现这个问题,arch arm和arch arm64也不会有这个问题,只是我机器资源有限只能使用32位的内核,所以碰上了这个问题。
    x86 32位的内核必须使用FLAT内存管理模型,即CONFIG_FLATMEM_ENABLE=y,才可以正常使用/proc/kcore。

  • 相关阅读:
    hdu 4686 Arc of Dream
    监测系统的情况,你必须知道的两三事!
    [置顶] access函数-linux
    IE9 "CSS 因 Mime 类型不匹配而被忽略“问题
    [置顶] mkdir函数-linux
    adb 异常报错----adb server is out of date. killing... ADB server didn't ACK * failed to start daemon *
    Hbase深入学习(一) 什么是hbase
    Qt5 基于TCP传输的发送/接收文件服务器(支持多客户端)
    组队练习赛(Regionals 2012, North America
    CSS 注意事项
  • 原文地址:https://www.cnblogs.com/bbqzsl/p/6895833.html
Copyright © 2011-2022 走看看