免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 1804 | 回复: 0
打印 上一主题 下一主题

linux 1.0 内核注解 linux/fs/binfmt_elf.c [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2009-05-23 20:45 |只看该作者 |倒序浏览
/********************************************
*Created By: Prometheus
*Date        : 2009-5-23   
********************************************/
/*
* linux/fs/binfmt_elf.c
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
asmlinkage int sys_exit(int exit_code);
asmlinkage int sys_close(unsigned fd);
asmlinkage int sys_open(const char *, int, int);
asmlinkage int sys_brk(unsigned long);
#define DLINFO_ITEMS 8
#include
/* We need to explicitly zero any fractional pages
   after the data section (i.e. bss).  This would
   contain the junk from the file that should not
   be in memory */
//同coff的clear_memory相似的作用
static void padzero(int elf_bss){
  unsigned int fpnt, nbyte;
  
  if(elf_bss & 0xfff) {    //没有页对齐
   
    nbyte = (PAGE_SIZE - (elf_bss & 0xfff)) & 0xfff;
    if(nbyte){
      verify_area(VERIFY_WRITE, (void *) elf_bss, nbyte);
      
      fpnt = elf_bss;
      while(fpnt & 0xfff) put_fs_byte(0, fpnt++);
    };
  };
}
//对于标准的Linux程序来说,命令行字符串表指针argv和环境字符串表指针envp并不压入argc之上,
//就是说ELF的入口函数并不是以 main(argc,argv,envp)参数形式调用,而是用
//main(argc,argv[0],argv[1],...,NULL,envp[0],envp[1],...,NULL)这样的形式
unsigned long * create_elf_tables(char * p,int argc,int envc,struct elfhdr * exec, unsigned int load_addr, int ibcs)
{
    unsigned long *argv,*envp, *dlinfo;
    unsigned long * sp;
    struct vm_area_struct *mpnt;
    mpnt = (struct vm_area_struct *)kmalloc(sizeof(*mpnt), GFP_KERNEL);
    if (mpnt) {
        mpnt->vm_task = current;
        mpnt->vm_start = PAGE_MASK & (unsigned long) p;
        mpnt->vm_end = TASK_SIZE;        //实际最大的参数空间可能是32*4K=128K大小
        mpnt->vm_page_prot = PAGE_PRIVATE|PAGE_DIRTY;
        mpnt->vm_share = NULL;
        mpnt->vm_inode = NULL;
        mpnt->vm_offset = 0;
        mpnt->vm_ops = NULL;
        insert_vm_struct(current, mpnt);
        current->stk_vma = mpnt;
    }
    sp = (unsigned long *) (0xfffffffc & (unsigned long) p);
    if(exec) sp -= DLINFO_ITEMS*2;    //8*2
    dlinfo = sp;
   
    //这里的+1可能是填null的原因,记录envp argv 载堆栈中的偏移
    sp -= envc+1;
    envp = sp;
    sp -= argc+1;
    argv = sp;
    if (!ibcs) {
        put_fs_long((unsigned long)envp,--sp);
        put_fs_long((unsigned long)argv,--sp);
    }
    /* The constant numbers (0-9) that we are writing here are
       described in the header file sys/auxv.h on at least
       some versions of SVr4 */
    //INTERPRETER_ELF类型的连接器
    if(exec) { /* Put this here for an ELF program interpreter */
      struct elf_phdr * eppnt;
      eppnt = (struct elf_phdr *) exec->e_phoff;
      put_fs_long(3,dlinfo++); put_fs_long(load_addr + exec->e_phoff,dlinfo++);
      put_fs_long(4,dlinfo++); put_fs_long(sizeof(struct elf_phdr),dlinfo++);
      put_fs_long(5,dlinfo++); put_fs_long(exec->e_phnum,dlinfo++);
      put_fs_long(9,dlinfo++); put_fs_long((unsigned long) exec->e_entry,dlinfo++);
      put_fs_long(7,dlinfo++); put_fs_long(SHM_RANGE_START,dlinfo++);
      put_fs_long(8,dlinfo++); put_fs_long(0,dlinfo++);
      put_fs_long(6,dlinfo++); put_fs_long(PAGE_SIZE,dlinfo++);
      put_fs_long(0,dlinfo++); put_fs_long(0,dlinfo++);
    };
    put_fs_long((unsigned long)argc,--sp);
    current->arg_start = (unsigned long) p;
    while (argc-->0) {    //填充参数
        put_fs_long((unsigned long) p,argv++);
        while (get_fs_byte(p++)) /* nothing */ ;    //一方面校正p,不知道是不是每个参数之间有空字符间隔
    }
    put_fs_long(0,argv);    //最后一个参数使用0结尾
    current->arg_end = current->env_start = (unsigned long) p;    //参数结束,环境变量开始
    while (envc-->0) {
        put_fs_long((unsigned long) p,envp++);
        while (get_fs_byte(p++)) /* nothing */ ;
    }
    put_fs_long(0,envp);
    current->env_end = (unsigned long) p;
    return sp;
}
/* This is much more generalized than the library routine read function,
   so we keep this separate.  Techincally the library read function
   is only provided so that we can read a.out libraries that have
   an ELF header */
static unsigned int load_elf_interp(struct elfhdr * interp_elf_ex,
                 struct inode * interpreter_inode)
{
        struct file * file;
    struct elf_phdr *elf_phdata  =  NULL;
    struct elf_phdr *eppnt;
    unsigned int len;
    unsigned int load_addr;
    int elf_exec_fileno;
    int elf_bss;
    int old_fs, retval;
    unsigned int last_bss;
    int error;
    int i, k;
   
    elf_bss = 0;
    last_bss = 0;
    error = load_addr = 0;
   
    /* First of all, some simple consistency checks */
    if((interp_elf_ex->e_type != ET_EXEC &&         //可执行文件或者是动态共享文件
        interp_elf_ex->e_type != ET_DYN) ||
       (interp_elf_ex->e_machine != EM_386 && interp_elf_ex->e_machine != EM_486) ||
       (!interpreter_inode->i_op || !interpreter_inode->i_op->bmap ||
        !interpreter_inode->i_op->default_file_ops->mmap)){
        return 0xffffffff;        //-1
    };
   
//typedef struct {
//      Elf32_Word  p_type;            /* 段类型 */
//      Elf32_Off   p_offset;              /* 段位置相对于文件开始处的偏移量 */
//      Elf32_Addr  p_vaddr;               /* 段在内存中的地址 */
//      Elf32_Addr  p_paddr;               /* 段的物理地址 */
//      Elf32_Word  p_filesz;            /* 段在文件中的长度 */
//      Elf32_Word  p_memsz;            /* 段在内存中的长度 */
//      Elf32_Word  p_flags;            /* 段的标记 */
//      Elf32_Word  p_align;            /* 段在内存中对齐标记 */
// }  Elf32_Phdr;
    /* Now read in all of the header information */
   
    if(sizeof(struct elf_phdr) * interp_elf_ex->e_phnum > PAGE_SIZE) //程序头部表格的表项总占空间大小   
        return 0xffffffff;
   
    elf_phdata =  (struct elf_phdr *)
        kmalloc(sizeof(struct elf_phdr) * interp_elf_ex->e_phnum, GFP_KERNEL);    //加载所有的程序头结构所需要的内存空间
    if(!elf_phdata) return 0xffffffff;
   
    old_fs = get_fs();
    set_fs(get_ds());            //程序头部表格(Program Header Table)的偏移量
    retval = read_exec(interpreter_inode, interp_elf_ex->e_phoff, (char *) elf_phdata,
               sizeof(struct elf_phdr) * interp_elf_ex->e_phnum);
    set_fs(old_fs);
   
    elf_exec_fileno = open_inode(interpreter_inode, O_RDONLY);
    if (elf_exec_fileno filp[elf_exec_fileno];
    eppnt = elf_phdata;
    for(i=0; ie_phnum; i++, eppnt++)    //遍历程序头部表格
    //此数组元素给出一个可加载的段,可加载
        //的段在程序头部表格中根据 p_vaddr 成员按升序排列
    if(eppnt->p_type == PT_LOAD)
    {
        error = do_mmap(file,
                eppnt->p_vaddr & 0xfffff000,
                eppnt->p_filesz + (eppnt->p_vaddr & 0xfff),
                PROT_READ | PROT_WRITE | PROT_EXEC,
                MAP_PRIVATE | (interp_elf_ex->e_type == ET_EXEC ? MAP_FIXED : 0),
                eppnt->p_offset & 0xfffff000);
        
        if(!load_addr && interp_elf_ex->e_type == ET_DYN)
          load_addr = error;    //先前的do_mmap返回的就是映射的地址,这里就是如果是动态共享的话就更新load_addr
        k = load_addr + eppnt->p_vaddr + eppnt->p_filesz;
        if(k > elf_bss) elf_bss = k;    //相应的移动elf_bss
        if(error  -1024) break;  /* Real error */
        k = load_addr + eppnt->p_memsz + eppnt->p_vaddr;
        if(k > last_bss) last_bss = k;
      }
   
    /* Now use mmap to map the library into memory. */
   
    sys_close(elf_exec_fileno);
    if(error  -1024) {
            kfree(elf_phdata);
        return 0xffffffff;
    }
    padzero(elf_bss);
    len = (elf_bss + 0xfff) & 0xfffff000; /* What we have mapped so far */    //页取整
    /* Map the last of the bss segment */
    if (last_bss > len)
      do_mmap(NULL, len, last_bss-len,
          PROT_READ|PROT_WRITE|PROT_EXEC,
          MAP_FIXED|MAP_PRIVATE, 0);
    kfree(elf_phdata);
    return ((unsigned int) interp_elf_ex->e_entry) + load_addr;
}
//这里是对a.out格式进行加载,因为a.out有几种组织格式,这里分别对待了
static unsigned int load_aout_interp(struct exec * interp_ex,
                 struct inode * interpreter_inode)
{
  int retval;
  unsigned int elf_entry;
  
  current->brk = interp_ex->a_bss +
    (current->end_data = interp_ex->a_data +
     (current->end_code = interp_ex->a_text));
  elf_entry = interp_ex->a_entry;
  
//OMAGIC
//The text and data segments immediately follow the header and are contiguous.
//The kernel loads both text and data segments into writable memory.
//ZMAGIC
//The kernel loads individual pages on demand from the binary. The header, text segment
//and data segment are all padded by the link editor to a multiple of the page size.
// Pages that the kernel loads from the text segment are read-only, while pages from the data segment are writable.
// 88 #define _N_HDROFF(x) (1024 - sizeof (struct exec))        //说是页的倍数,但这里怎么是1k?不过至少填充了
// 91 #define N_TXTOFF(x) \
// 92  (N_MAGIC(x) == ZMAGIC ? _N_HDROFF((x)) + sizeof (struct exec) : \
// 93   (N_MAGIC(x) == QMAGIC ? 0 : sizeof (struct exec)))
  if (N_MAGIC(*interp_ex) == OMAGIC) {        //数据和代码段直接跟在头部后面,并且数据代码加载到同一个可写段中
    do_mmap(NULL, 0, interp_ex->a_text+interp_ex->a_data,
        PROT_READ|PROT_WRITE|PROT_EXEC,
        MAP_FIXED|MAP_PRIVATE, 0);
                    //a.out执行格式的头部固定是32个字节的偏移
    retval = read_exec(interpreter_inode, 32, (char *) 0,
               interp_ex->a_text+interp_ex->a_data);
  } else if (N_MAGIC(*interp_ex) == ZMAGIC || N_MAGIC(*interp_ex) == QMAGIC) {    //Z类型的头部被填充了
    do_mmap(NULL, 0, interp_ex->a_text+interp_ex->a_data,
        PROT_READ|PROT_WRITE|PROT_EXEC,
        MAP_FIXED|MAP_PRIVATE, 0);
    retval = read_exec(interpreter_inode,
               N_TXTOFF(*interp_ex) ,
               (char *) N_TXTADDR(*interp_ex),
               interp_ex->a_text+interp_ex->a_data);
  } else
    retval = -1;
  
  if(retval >= 0)    //这里页对对齐后再映射bss段
    do_mmap(NULL, (interp_ex->a_text + interp_ex->a_data + 0xfff) &
        0xfffff000, interp_ex->a_bss,
        PROT_READ|PROT_WRITE|PROT_EXEC,
        MAP_FIXED|MAP_PRIVATE, 0);
  if(retval buf);      /* exec-header */
   
    //执行文件魔数 0x7f ELF
    if (elf_ex.e_ident[0] != 0x7f ||
        strncmp(&elf_ex.e_ident[1], "ELF",3) != 0)
        return  -ENOEXEC;
   
   
    /* First of all, some simple consistency checks */
    //检查是否是执行文件并所在的文件系统是否具有文件映射功能
    if(elf_ex.e_type != ET_EXEC ||
       (elf_ex.e_machine != EM_386 && elf_ex.e_machine != EM_486) ||
       (!bprm->inode->i_op || !bprm->inode->i_op->default_file_ops ||
        !bprm->inode->i_op->default_file_ops->mmap)){
        return -ENOEXEC;
    };
   
    /* Now read in all of the header information */
    //根据程序段表总长度分配空间            //程序段头部的表项大小 * 表项数
    elf_phdata = (struct elf_phdr *) kmalloc(elf_ex.e_phentsize *
                         elf_ex.e_phnum, GFP_KERNEL);
   
    old_fs = get_fs();
    set_fs(get_ds());
    retval = read_exec(bprm->inode, elf_ex.e_phoff, (char *) elf_phdata,
               elf_ex.e_phentsize * elf_ex.e_phnum);
    set_fs(old_fs);
    if (retval inode, O_RDONLY);
    if (elf_exec_fileno filp[elf_exec_fileno];
   
    elf_stack = 0xffffffff;
    elf_interpreter = NULL;
    start_code = 0;
    end_code = 0;
    end_data = 0;
   
    old_fs = get_fs();
    set_fs(get_ds());
   
    //扫描ELF程序段表,搜寻动态链接器定义
    for(i=0;i p_type == PT_INTERP) {
            /* This is the program interpreter used for shared libraries -
               for now assume that this is an a.out format binary */
            
            //为动态链接器名称字符串分配空间
            elf_interpreter = (char *) kmalloc(elf_ppnt->p_filesz,
                               GFP_KERNEL);
                                        //加载进连接器名字了
            retval = read_exec(bprm->inode,elf_ppnt->p_offset,elf_interpreter,
                       elf_ppnt->p_filesz);
#if 0
            printk("Using ELF interpreter %s\n", elf_interpreter);
#endif
            if(retval >= 0)
                retval = namei(elf_interpreter, &interpreter_inode);
            if(retval >= 0)
                retval = read_exec(interpreter_inode,0,bprm->buf,128);
            
            if(retval >= 0){
                interp_ex = *((struct exec *) bprm->buf);        /* exec-header */
                interp_elf_ex = *((struct elfhdr *) bprm->buf);      /* exec-header */
               
            };
            if(retval sh_bang) {
        char * passed_p;
        
        if(interpreter_type == INTERPRETER_AOUT) {
          sprintf(passed_fileno, "%d", elf_exec_fileno);
          passed_p = passed_fileno;
        
        //将程序的文件描述符压入参数堆栈,准备传递给动态链接器
          if(elf_interpreter) {
            bprm->p = copy_strings(1,&passed_p,bprm->page,bprm->p,2);
            bprm->argc++;        //bprm->page[]中参数的数目
          };
        };
        if (!bprm->p) {
                if(elf_interpreter) {
                  kfree(elf_interpreter);
            }
                kfree (elf_phdata);
            return -E2BIG;
        }
    }
   
    /* OK, This is the point of no return */
    flush_old_exec(bprm);
    current->end_data = 0;
    current->end_code = 0;
    current->start_mmap = ELF_START_MMAP;    //2G?
    current->mmap = NULL;
    elf_entry = (unsigned int) elf_ex.e_entry;    //应用程序的入口地址
   
    /* Do this so that we can load the interpreter, if need be.  We will
       change some of these later */
    current->rss = 0;
    bprm->p += change_ldt(0, bprm->page);
    current->start_stack = bprm->p;
   
    /* Now we do a little grungy work by mmaping the ELF image into
       the correct location in memory.  At this point, we assume that
       the image should be loaded at fixed address, not at a variable
       address. */
   
    old_fs = get_fs();
    set_fs(get_ds());
   
    elf_ppnt = elf_phdata;
    for(i=0;i p_type == PT_INTERP) {
            /* Set these up so that we are able to load the interpreter */
              /* Now load the interpreter into user address space */
          set_fs(old_fs);
            //这里的链接器的名字interpreter_inode已经被初始化了
          if(interpreter_type & 1) elf_entry =             //AOUT
            load_aout_interp(&interp_ex, interpreter_inode);
          if(interpreter_type & 2) elf_entry =             //ELF
            load_elf_interp(&interp_elf_ex, interpreter_inode);
          old_fs = get_fs();
          set_fs(get_ds());
          iput(interpreter_inode);
          kfree(elf_interpreter);
            
          if(elf_entry == 0xffffffff) {
            printk("Unable to load interpreter\n");
            kfree(elf_phdata);
            send_sig(SIGSEGV, current, 0);
            return 0;
          };
        };
        
        // 此数组元素给出一个可加载的段,段的大小由 p_filesz 和 p_memsz
        // 描述。文件中的字节被映射到内存段开始处。如果 p_memsz 大于
        // p_filesz,“剩余”的字节要清零。p_filesz 不能大于 p_memsz。
        if(elf_ppnt->p_type == PT_LOAD) {        //可加载
            error = do_mmap(file,
                    elf_ppnt->p_vaddr & 0xfffff000,
                    elf_ppnt->p_filesz + (elf_ppnt->p_vaddr & 0xfff),
                    PROT_READ | PROT_WRITE | PROT_EXEC,
                    MAP_FIXED | MAP_PRIVATE,
                    elf_ppnt->p_offset & 0xfffff000);
            
#ifdef LOW_ELF_STACK
            if(elf_ppnt->p_vaddr & 0xfffff000 p_vaddr & 0xfffff000;
#endif
            
            if(!load_addr)
              load_addr = elf_ppnt->p_vaddr - elf_ppnt->p_offset;
            k = elf_ppnt->p_vaddr;
            if(k > start_code) start_code = k;
            k = elf_ppnt->p_vaddr + elf_ppnt->p_filesz;
            if(k > elf_bss) elf_bss = k;
            if((elf_ppnt->p_flags | PROT_WRITE) && end_code p_vaddr + elf_ppnt->p_memsz;
            if(k > elf_brk) elf_brk = k;            
              };
        elf_ppnt++;
    };
    set_fs(old_fs);
   
    kfree(elf_phdata);
   
    if(!elf_interpreter) sys_close(elf_exec_fileno);
    current->elf_executable = 1;
    current->executable = bprm->inode;
    bprm->inode->i_count++;
#ifdef LOW_ELF_STACK
    current->start_stack = p = elf_stack - 4;
#endif
    bprm->p -= MAX_ARG_PAGES*PAGE_SIZE;
    bprm->p = (unsigned long)         
        //拷贝执行参数
      create_elf_tables((char *)bprm->p,
            bprm->argc,
            bprm->envc,
            (interpreter_type == INTERPRETER_ELF ? &elf_ex : NULL),
            load_addr,   
            (interpreter_type == INTERPRETER_AOUT ? 0 : 1));
    if(interpreter_type == INTERPRETER_AOUT)
      current->arg_start += strlen(passed_fileno) + 1;    //AOUT多的一个文件描述符参数
    current->start_brk = current->brk = elf_brk;
    current->end_code = end_code;
    current->start_code = start_code;
    current->end_data = end_data;
    current->start_stack = bprm->p;
    current->suid = current->euid = bprm->e_uid;
    current->sgid = current->egid = bprm->e_gid;
    /* Calling sys_brk effectively mmaps the pages that we need for the bss and break
       sections */
    current->brk = (elf_bss + 0xfff) & 0xfffff000;
    sys_brk((elf_brk + 0xfff) & 0xfffff000);
    padzero(elf_bss);
    /* Why this, you ask???  Well SVr4 maps page 0 as read-only,
       and some applications "depend" upon this behavior.
       Since we do not have the power to recompile these, we
       emulate the SVr4 behavior.  Sigh.  */
    error = do_mmap(NULL, 0, 4096, PROT_READ | PROT_EXEC,
            MAP_FIXED | MAP_PRIVATE, 0);
    //呵呵
    regs->eip = elf_entry;        /* eip, magic happens :-) */
    regs->esp = bprm->p;            /* stack pointer */
    if (current->flags & PF_PTRACED)
        send_sig(SIGTRAP, current, 0);
    return 0;
}
/* This is really simpleminded and specialized - we are loading an
   a.out library that is given an ELF header. */
int load_elf_library(int fd){
        struct file * file;
    struct elfhdr elf_ex;
    struct elf_phdr *elf_phdata  =  NULL;
    struct  inode * inode;
    unsigned int len;
    int elf_bss;
    int old_fs, retval;
    unsigned int bss;
    int error;
    int i,j, k;
   
    len = 0;
    file = current->filp[fd];
    inode = file->f_inode;
    elf_bss = 0;
   
    set_fs(KERNEL_DS);                //what?
    if (file->f_op->read(inode, file, (char *) &elf_ex, sizeof(elf_ex)) != sizeof(elf_ex)) {
        sys_close(fd);
        return -EACCES;
    }
    set_fs(USER_DS);
   
    if (elf_ex.e_ident[0] != 0x7f ||
        strncmp(&elf_ex.e_ident[1], "ELF",3) != 0)
        return -ENOEXEC;
   
    /* First of all, some simple consistency checks */
    if(elf_ex.e_type != ET_EXEC || elf_ex.e_phnum > 2 ||
       (elf_ex.e_machine != EM_386 && elf_ex.e_machine != EM_486) ||
       (!inode->i_op || !inode->i_op->bmap ||
        !inode->i_op->default_file_ops->mmap)){
        return -ENOEXEC;
    };
   
    /* Now read in all of the header information */
   
    if(sizeof(struct elf_phdr) * elf_ex.e_phnum > PAGE_SIZE)
        return -ENOEXEC;
   
    elf_phdata =  (struct elf_phdr *)
        kmalloc(sizeof(struct elf_phdr) * elf_ex.e_phnum, GFP_KERNEL);
   
    old_fs = get_fs();
    set_fs(get_ds());
    retval = read_exec(inode, elf_ex.e_phoff, (char *) elf_phdata,
               sizeof(struct elf_phdr) * elf_ex.e_phnum);
    set_fs(old_fs);
   
    j = 0;
    for(i=0; ip_type == PT_LOAD) j++;        //can only be one!
   
    if(j != 1)  {
        kfree(elf_phdata);
        return -ENOEXEC;
    };
   
    while(elf_phdata->p_type != PT_LOAD) elf_phdata++;    //校准到那个位置
   
    /* Now use mmap to map the library into memory. */
    error = do_mmap(file,
            elf_phdata->p_vaddr & 0xfffff000,
            elf_phdata->p_filesz + (elf_phdata->p_vaddr & 0xfff),
            PROT_READ | PROT_WRITE | PROT_EXEC,
            MAP_FIXED | MAP_PRIVATE,
            elf_phdata->p_offset & 0xfffff000);
    k = elf_phdata->p_vaddr + elf_phdata->p_filesz;
    if(k > elf_bss) elf_bss = k;
   
    sys_close(fd);
    if (error != elf_phdata->p_vaddr & 0xfffff000) {
            kfree(elf_phdata);
        return error;
    }
    padzero(elf_bss);
    len = (elf_phdata->p_filesz + elf_phdata->p_vaddr+ 0xfff) & 0xfffff000;
    bss = elf_phdata->p_memsz + elf_phdata->p_vaddr;
    if (bss > len)
      do_mmap(NULL, len, bss-len,
          PROT_READ|PROT_WRITE|PROT_EXEC,
          MAP_FIXED|MAP_PRIVATE, 0);
    kfree(elf_phdata);
    return 0;
}
文档地址:http://blogimg.chinaunix.net/blog/upfile2/090523204406.pdf
               
               
               

本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u3/90306/showart_1939689.html
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP