阅读:3912回复:15
linux内核模块和驱动程序的编写
linux内核模块和驱动程序的编写
本文出自:http://os.silversand.net 作者: sunmoon (2001-08-30 15:00:00) linux内核是一个整体是结构.因此向内核添加任何东西.或者删除某些功能 ,都十分困难.为了解决这个问题. 引入了内核机制.从而可以动态的想内核中添加或者删除模块. 模块不被编译在内核中,因而控制了内核的大小.然而模块一旦被插入内核,他就和内核其他部分一样.这样一来 就会曾家一部分系统开销.同时,如果模块出现问题.,也许会带来系统的崩溃. 1.1模块的实现机制: 启动时,由函数 void inti_modules() 来初始化模块,.因为启动事很多时候没有模块.这个函数往往把内核自 身当作一个虚模块. 如由系统需要,则调用一系列以sys 开头的函数,对模块进行操作. 如:sys_creat_modules(),sys_inti_modules() , sys_deldte_modules()等等. 这里会用到一些模块的数据就结构,在/usr/scr/linux/include/linux/module.h 中,有兴趣的朋友可以找出来一看 块的加入有两种方法:一是手动加入:如:insmod modulename.另一种是根据需要,动态的加载模块.如你执行命令: $mount -t msdos /dev/hdd /mnt/d 时.系统便自动加载 FAT模块,以支持MSDOS的文件系统. 1.2 模块编程 写一个模块,必须有一定的多进程编程基础.因为你变得程序不是以一个独立的程序的来运行的.另外,因为,模块需要 在内核模式下运行,会遇到在内和空间和用户空间数据交换的问题.一般的数据复制函数无法完成这一个过程.因此系 统已入了一些特殊的函数以用来完成内核空间和用户空间数据的交换. 这些函数有:void put _user (type valude,type *u_addr) memcpy_tofs() 等等,有兴趣的朋友可以仔细的看看所有的函数,以及他们的用法.需要说明的是.模块编程河内核的版本有很大的关系. 如果版本不通可能造成,内核模块不能编译,或者.在运行这个模块时,出现不可测结果.如:系统崩溃等. 明白了这些以后.你就可以尝试着编写内核模块了.对于每一个内核模块来说.必定包含两个函数 int init_module() 这个函数在插入内核时启动,在内核中注册一定的功能函数,或者用他的代码代替内和中某些函数 的内容(估计这些函数是空的).因此,内和可以安全的卸载.(个人猜测) int cleanup_module() 当内核模块谢载时,调用.将模块从内核中清除. 同其他的程序设计教程一样 ,我们给出一个hello world 的例子 /*hello.c a module programm*/ /* the program runing under kernel mod and it is a module*/ #include\" linux/kernerl.h\" #include\"llinux/module.h\" /* pross the CONFIG_MODVERSIONS*/ #if CONFIG_MODVERSIONS==1 #define MODVERSIONS #include\"\"linux/modversions.h\" #end if /* the init function*/ int init_module() { printk(\" hello world !\\n\'); printd(\" I have runing in a kerner mod@!!\\n\"); return 1; } /* the distory function*/ int cleanup_module() { printk(\" I will shut down myself in kernerl mod /n)\"; retutn 0; } 这样一个例子就完成了.我们也写一个makefile 的例子,以适于我们在大程序重的应用.一下是makfile 文件的内容 # a makefile for a module CC=gcc MODCFLAGS:= -Wall _DMODULE -D_KERNEL_ -Dlinux hello.o hello.c /usr/inculde?linux/version.h CC $(MODCFLAGS) 0c hello.c echo the module is complie completely 然后你运行make 命令 得到hello.o 这个模块.运行 $insmod hello.o hello world! I will shut down myself in kernerl mod $lsmod hello (unused) …. $remmod I will shut down myself in kernerl mod 这样你的模块就可以随意的插入和删除了. linux中的大部分驱动程序,是以模块的形式编写的.这些驱动程序源码可以修改到内核中,也可以把他们编译成模 块形势,在需要的时候动态加载. 一个典型的驱动程序,大体上可以分为这么几个部分: 1,注册设备 在系统初启,或者模块加载时候,必须将设备登记到相应的设备数组,并返回设备的主驱动号,例如:对快设备来说调 用 refister_blkdec()将设备添加到数组blkdev中.并且获得该设备号.并利用这些设备号对此数组进行索引.对于 字符驱动设备来说,要使用 module_register_chrdev()来获得祝设备的驱动号.然后对这个设备的所有调用都用这 个设备号来实现 2,定义功能函数 对于每一个驱动函数来说.都有一些和此设备密切相关的功能函数.那最常用的块设备或者字符设备来说.都存在着 诸如 open() read() write() ioctrol()这一类的操作.当系统社用这些调用时.将自动的使用驱动函数中特定的模 块.来实现具体的操作.而对于特定的设备.上面的系统调用对应的函数是一定的. 如:在块驱动设备中.当系统试图读取这个设备(即调用read()时),就会运行驱动程序中的block_read() 这个函数. 打开新设备时会调用这个设备驱动程序的device_open() 这个函数. 3,谢载模块 在不用这个设备时,可以将他卸载.主要是从/proc 中取消这个设备的特殊文件.可用特定的函数实现. 下面我们列举一个字符设备驱动程序的框架.来说明这个过程. /* a module of a character device */ /* some include files*/ #include\"param.h\" #include\"user.h\" #include\"tty.h\" #include\"dir.h\" #include”fs.h\" /* the include files modules need*/ #include\"linux/kernel.h\" #include\"linux/module.h\" #if CONFIG_MODBERSIONS==1 degine MODBERSIONS #include\" linux.modversions.h\" #endif #difine devicename mydevice /* the init funcion*/ int init_module() { int tag=module_register_chrdev(0,mydevice,&Fops); if (tag<0) { printk(\"the device init is erro!\\n\"); return 1; } return 0; } /*the funcion which the device will be used */ int device_open () { ……. } int device_read () { ……. } int device_write () { ……. } int device_ioctl () { ……. } …… /* the deltter function of this module*/ int cleanup_module() { int re=module_unregister_chrdev(tag,mydevice); if( re<0) { printk(\"erro unregister the module !!\\n\"); return 1; } return 0; } |
|
最新喜欢:hughwe...
|
沙发#
发布于:2003-11-12 21:38
好
|
|
板凳#
发布于:2003-12-12 21:57
UP
|
|
地板#
发布于:2003-12-15 17:22
学到了很多阿
|
|
|
地下室#
发布于:2003-12-17 14:47
基础,好文!
顶! |
|
5楼#
发布于:2003-12-20 00:38
建议对Linux kernel不熟的人,可以学习一下。熟悉的人也可以温故而知新。总之谢谢!
|
|
6楼#
发布于:2004-04-01 10:54
insmod hello.o结果显示内核不匹配,怎么办?
我的内核为2.4.20-8 hello.o支持的内核为2.4.20 |
|
7楼#
发布于:2004-04-01 14:35
insmod hello.o结果显示内核不匹配,怎么办? 我的也是这个问题,不知道怎么办? |
|
8楼#
发布于:2004-04-02 18:14
其实有些地方确实有点老了.可以修改以下
module_register改为register就可以了 还有就是如果用户和KERNEL交换大块数据用 memcpy_from_user() memcpy_to_user() 用法和memcpy一样, |
|
|
9楼#
发布于:2004-04-02 18:31
作者:jbtzhm <jbtzhm@nsfocus.com>
主页:http://www.nsfocus.com 日期:2002-08-16 问题的提出是前一阵和lgx聊天发现,一个被strip的module也可以被成功的insmod,当时知道一些insmod 的原理觉得不太可能,因为一个正常的module文件其实就是标准的ELF格式object文件,如果将他的 symtab strip掉的话,那些printk这类的symbol将不能被正常的解析,理论上是不可能加载成功的,于是 做了一个简单的module在turbo7上测试了一把,modutils的版本是2.4.6,出人意料的是竟然成功的被加 载,真是觉得真是不可思议,因此觉得有必要研究一下insmod的具体实现,最好的方法当然是go to the source 先说说关于module的几个系统调用,主要有 sys_create_module,sys_init_module,sys_query_module,sys_delete_module。我们简单的分析一 下module的创建过程,有一个重要地数据结构必然要提到,那就是struct module,定义如下。 struct module { unsigned long size_of_struct; /* == sizeof(module) */ struct module *next; const char *name; unsigned long size; union { atomic_t usecount; long pad; } uc; /* Needs to keep its size - so says rth */ unsigned long flags; /* AUTOCLEAN et al */ unsigned nsyms; symbol的个数 unsigned ndeps; struct module_symbol *syms; 此module实现的对外输出的所有symbol struct module_ref *deps; struct module_ref *refs; int (*init)(void); void (*cleanup)(void); const struct exception_table_entry *ex_table_start; const struct exception_table_entry *ex_table_end; #ifdef __alpha__ unsigned long gp; #endif /* Members past this point are extensions to the basic module support and are optional. Use mod_opt_member() to examine them. */ const struct module_persist *persist_start; const struct module_persist *persist_end; int (*can_unload)(void); }; 而kernel中有一个全局变量module_list为所有的module的list,在系统初始化是module_list只有一项为 struct module *module_list = &kernel_module; kernel_module中的syms为__start___ksymtab即为kernel对外输出的symbol的所有项。 sys_create_module负责分配空间生成一个module结构,加入module_list中。而sys_init_module是将insmod 在用户空间地module结构复制到由sys_create_module创建地空间中。 好了,我们现在分析一下insmod的主要工作流程(modtuils-2.4.6版) 1.get_kernel_info函数负责取得kernel中先以注册的modules,放入module_stat中,并将kernel实现的各个 symbol放入ksyms中,个数为ksyms。 最终调用new_get_kernel_info //type的三种类型,影响get_kernle_info的流程。 #define K_SYMBOLS 1 /* Want info about symbols */ #define K_INFO 2 /* Want extended module info */ #define K_REFS 4 /* Want info about references */ static int new_get_kernel_info(int type) { struct module_stat *modules; struct module_stat *m; struct module_symbol *syms; struct module_symbol *s; size_t ret; size_t bufsize; size_t nmod; size_t nsyms; size_t i; size_t j; char *module_names; char *mn; drop(); /* * Collect the loaded modules */ module_names = xmalloc(bufsize = 256); //取得现有module的名称,ret返回个数,module_names返回各个module名称,字符0分割 while (query_module(NULL, QM_MODULES, module_names, bufsize, &ret)) { if (errno != ENOSPC) { error("QM_MODULES: %m\n"); return 0; } module_names = xrealloc(module_names, bufsize = ret); } module_name_list = module_names; l_module_name_list = bufsize; n_module_stat = nmod = ret; module_stat = modules = xmalloc(nmod * sizeof(struct module_stat)); memset(modules, 0, nmod * sizeof(struct module_stat)); /* Collect the info from the modules */ //循环取得各个module的信息,QM_INFO的使用。 for (i = 0, mn = module_names, m = modules; i < nmod; ++i, ++m, mn += strlen(mn) + 1) { struct module_info info; //info包括module的地址,大小,flag和使用计数器。 m->name = mn; if (query_module(mn, QM_INFO, &info, sizeof(info), &ret)) { if (errno == ENOENT) { /* The module was removed out from underneath us. */ m->flags = NEW_MOD_DELETED; continue; } /* else oops */ error("module %s: QM_INFO: %m", mn); return 0; } m->addr = info.addr; if (type & K_INFO) {//取得module的信息 m->size = info.size; m->flags = info.flags; m->usecount = info.usecount; m->modstruct = info.addr; }//将info值传给module_stat结构 if (type & K_REFS) {//取得module的引用关系 int mm; char *mrefs; char *mr; mrefs = xmalloc(bufsize = 64); while (query_module(mn, QM_REFS, mrefs, bufsize, &ret)) { if (errno != ENOSPC) { error("QM_REFS: %m"); return 1; } mrefs = xrealloc(mrefs, bufsize = ret); } for (j = 0, mr = mrefs; j < ret; ++j, mr += strlen(mr) + 1) { for (mm = 0; mm < i; ++mm) { if (strcmp(mr, module_stat[mm].name) == 0) { m->nrefs += 1; m->refs = xrealloc(m->refs, m->nrefs * sizeof(struct module_stat **)); m->refs[m->nrefs - 1] = module_stat + mm; break; } } } free(mrefs); } if (type & K_SYMBOLS) { /* 取得symbol信息,正是我们要得*/ syms = xmalloc(bufsize = 1024); while (query_module(mn, QM_SYMBOLS, syms, bufsize, &ret)) { if (errno == ENOSPC) { syms = xrealloc(syms, bufsize = ret); continue; } if (errno == ENOENT) { /* * The module was removed out * from underneath us. */ m->flags = NEW_MOD_DELETED; free(syms); goto next; } else { error("module %s: QM_SYMBOLS: %m", mn); return 0; } } nsyms = ret; //syms是module_symbol结构,ret返回symbol个数 m->nsyms = nsyms; m->syms = syms; /* Convert string offsets to string pointers */ for (j = 0, s = syms; j < nsyms; ++j, ++s) s->name += (unsigned long) syms; } next: } if (type & K_SYMBOLS) { /* Want info about symbols */ /* Collect the kernel's symbols. */ syms = xmalloc(bufsize = 16 * 1024); //name为NULL,返回kernel_module的symbol。 while (query_module(NULL, QM_SYMBOLS, syms, bufsize, &ret)) { if (errno != ENOSPC) { error("kernel: QM_SYMBOLS: %m"); return 0; } syms = xrealloc(syms, bufsize = ret); } //将值返回给nksyms和ksyms两个全局变量存储。 nksyms = nsyms = ret; ksyms = syms; /* name原来只是一个结构内的偏移,加上结构地址为真正的字符串地址 */ for (j = 0, s = syms; j < nsyms; ++j, ++s) s->name += (unsigned long) syms; } return 1; } 2.set_ncv_prefix(NULL);判断symbolname中是否有前缀,象_smp之类,这里没有。 3.检查是否已有同名的module。 ... for (i = 0; i < n_module_stat; ++i) { if (strcmp(module_stat.name, m_name) == 0) { error("a module named %s already exists", m_name); goto out; } }//判断是否已经存在。 ... 4.obj_load,将。o文件读入到struct obj_file结构f中。 struct obj_file *obj_load (int fp, Elf32_Half e_type, const char *filename) { struct obj_file *f; ElfW(Shdr) *section_headers; int shnum, i; char *shstrtab; /* Read the file header. */ f = arch_new_file();//生成新的obj_file结构 memset(f, 0, sizeof(*f)); f->symbol_cmp = strcmp;//设置symbol名的比较函数就是strcmp f->symbol_hash = obj_elf_hash;//设置计算symbol hash值的函数 f->load_order_search_start = &f->load_order;//?? gzf_lseek(fp, 0, SEEK_SET); if (gzf_read(fp, &f->header, sizeof(f->header)) != sizeof(f->header)) {//取得object文件的ELF头结构。 error("cannot read ELF header from %s", filename); return NULL; } if (f->header.e_ident[EI_MAG0] != ELFMAG0 || f->header.e_ident[EI_MAG1] != ELFMAG1 || f->header.e_ident[EI_MAG2] != ELFMAG2 || f->header.e_ident[EI_MAG3] != ELFMAG3) {//判断ELF的magic,是否是ELF文件 error("%s is not an ELF file", filename); return NULL; } if (f->header.e_ident[EI_CLASS] != ELFCLASSM//i386的机器上为ELFCLASS32,表示32bit || f->header.e_ident[EI_DATA] != ELFDATAM//此处值为ELFDATA2LSB,表示编码方式 || f->header.e_ident[EI_VERSION] != EV_CURRENT//此值固定,表示版本 || !MATCH_MACHINE(f->header.e_machine))//机器类型 { error("ELF file %s not for this architecture", filename); return NULL; } if (f->header.e_type != e_type && e_type != ET_NONE)//类型必为ET_REL { switch (e_type) { case ET_REL: error("ELF file %s not a relocatable object", filename); break; case ET_EXEC: error("ELF file %s not an executable object", filename); break; default: error("ELF file %s has wrong type, expecting %d got %d", filename, e_type, f->header.e_type); break; } return NULL; } /* Read the section headers. */ if (f->header.e_shentsize != sizeof(ElfW(Shdr))) { error("section header size mismatch %s: %lu != %lu", filename, (unsigned long)f->header.e_shentsize, (unsigned long)sizeof(ElfW(Shdr))); return NULL; } shnum = f->header.e_shnum;//section个数 f->sections = xmalloc(sizeof(struct obj_section *) * shnum); memset(f->sections, 0, sizeof(struct obj_section *) * shnum); section_headers = alloca(sizeof(ElfW(Shdr)) * shnum);//section表的大小 gzf_lseek(fp, f->header.e_shoff, SEEK_SET); if (gzf_read(fp, section_headers, sizeof(ElfW(Shdr))*shnum) != sizeof(ElfW(Shdr))*shnum) {//获得section表内容 error("error reading ELF section headers %s: %m", filename); return NULL; } /* Read the section data. */ for (i = 0; i < shnum; ++i) { struct obj_section *sec; f->sections = sec = arch_new_section();//分配内存给每个section memset(sec, 0, sizeof(*sec)); sec->header = section_headers;//设置section表项地址 sec->idx = i;//section表中第几个 switch (sec->header.sh_type)//section的类型 { case SHT_NULL: case SHT_NOTE: case SHT_NOBITS: /* ignore */ break; case SHT_PROGBITS: case SHT_SYMTAB: case SHT_STRTAB: case SHT_RELM://将以上各种类型的section内容读到结构中。 if (sec->header.sh_size > 0) { sec->contents = xmalloc(sec->header.sh_size); gzf_lseek(fp, sec->header.sh_offset, SEEK_SET); if (gzf_read(fp, sec->contents, sec->header.sh_size) != sec->header.sh_size) { error("error reading ELF section data %s: %m", filename); return NULL; } } else sec->contents = NULL; break; //描述relocation的section #if SHT_RELM == SHT_REL case SHT_RELA: if (sec->header.sh_size) { error("RELA relocations not supported on this architecture %s", filename); return NULL; } break; #else case SHT_REL: if (sec->header.sh_size) { error("REL relocations not supported on this architecture %s", filename); return NULL; } break; #endif default: if (sec->header.sh_type >= SHT_LOPROC) { if (arch_load_proc_section(sec, fp) < 0) return NULL; break; } error("can't handle sections of type %ld %s", (long)sec->header.sh_type, filename); return NULL; } } /* Do what sort of interpretation as needed by each section. */ //shstrndx存的是section字符串表的索引值,就是第几个section //shstrtab就是那个section了。 shstrtab = f->sections[f->header.e_shstrndx]->contents; for (i = 0; i < shnum; ++i) { struct obj_section *sec = f->sections; sec->name = shstrtab + sec->header.sh_name; }//根据strtab,取得每个section的名字 for (i = 0; i < shnum; ++i) { struct obj_section *sec = f->sections; /* .modinfo and .modstring should be contents only but gcc has no * attribute for that. The kernel may have marked these sections as * ALLOC, ignore the allocate bit. */也就是说即使modinfo和modstring有此标志位,也去掉。 if (strcmp(sec->name, ".modinfo") == 0 || strcmp(sec->name, ".modstring") == 0) sec->header.sh_flags &= ~SHF_ALLOC;//ALLOC表示此section是否占用内存 if (sec->header.sh_flags & SHF_ALLOC) obj_insert_section_load_order(f, sec);//确定section load的顺序 //根据的是flag的类型加权得到优先级 switch (sec->header.sh_type) { case SHT_SYMTAB://符号表 { unsigned long nsym, j; char *strtab; ElfW(Sym) *sym; if (sec->header.sh_entsize != sizeof(ElfW(Sym))) { error("symbol size mismatch %s: %lu != %lu", filename, (unsigned long)sec->header.sh_entsize, (unsigned long)sizeof(ElfW(Sym))); return NULL; } //计算符号表表项个数,nsym也就是symbol个数 nsym = sec->header.sh_size / sizeof(ElfW(Sym)); //sh_link是符号字符串表的索引值 strtab = f->sections[sec->header.sh_link]->contents; sym = (ElfW(Sym) *) sec->contents; /* Allocate space for a table of local symbols. */ //为所有符号分配空间 j = f->local_symtab_size = sec->header.sh_info; f->local_symtab = xmalloc(j *= sizeof(struct obj_symbol *)); memset(f->local_symtab, 0, j); /* Insert all symbols into the hash table. */ for (j = 1, ++sym; j < nsym; ++j, ++sym) { const char *name; if (sym->st_name)//有值就是strtab的索引值 name = strtab+sym->st_name; else//如果为零,此symbolname是一个section的name,比如.rodata之类的 name = f->sections[sym->st_shndx]->name; //obj_add_symbol将符号加入到f->symbab这个hash表中 obj_add_symbol(f, name, j, sym->st_info, sym->st_shndx, sym->st_value, sym->st_size); } } break; } } /* second pass to add relocation data to symbols */ for (i = 0; i < shnum; ++i) { struct obj_section *sec = f->sections; switch (sec->header.sh_type) { case SHT_RELM: {//找到描述重定位的section unsigned long nrel, j; ElfW(RelM) *rel; struct obj_section *symtab; char *strtab; if (sec->header.sh_entsize != sizeof(ElfW(RelM))) { error("relocation entry size mismatch %s: %lu != %lu", filename, (unsigned long)sec->header.sh_entsize, (unsigned long)sizeof(ElfW(RelM))); return NULL; } //算出rel有几项,存到nrel中 nrel = sec->header.sh_size / sizeof(ElfW(RelM)); rel = (ElfW(RelM) *) sec->contents; //rel的section中sh_link相关值是符号section的索引号 symtab = f->sections[sec->header.sh_link]; //而符号section中sh_link是符号字符串section的索引号 strtab = f->sections[symtab->header.sh_link]->contents; /* Save the relocate type in each symbol entry. */ //存储需要relocate的符号的rel的类型 for (j = 0; j < nrel; ++j, ++rel) { ElfW(Sym) *extsym; struct obj_symbol *intsym; unsigned long symndx;//取得这个需relocate的符号索引值 symndx = ELFW(R_SYM)(rel->r_info); if (symndx) { extsym = ((ElfW(Sym) *) symtab->contents) + symndx; if (ELFW(ST_BIND)(extsym->st_info) == STB_LOCAL) {//local类型 /* Local symbols we look up in the local table to be sure we get the one that is really intended. */ intsym = f->local_symtab[symndx]; } else {//其他类型,从hash表中取 /* Others we look up in the hash table. */ const char *name; if (extsym->st_name) name = strtab + extsym->st_name; else name = f->sections[extsym->st_shndx]->name; intsym = obj_find_symbol(f, name); } intsym->r_type = ELFW(R_TYPE)(rel->r_info); } } } break; } } f->filename = xstrdup(filename); return f; } ... struct obj_symbol * obj_add_symbol (struct obj_file *f, const char *name, unsigned long symidx, int info, int secidx, ElfW(Addr) value, unsigned long size) { struct obj_symbol *sym;//计算符号的hash值 unsigned long hash = f->symbol_hash(name) % HASH_BUCKETS; int n_type = ELFW(ST_TYPE)(info); int n_binding = ELFW(ST_BIND)(info); //开始symtab为空的所以肯定找不到,一项一项向里加。 for (sym = f->symtab[hash]; sym; sym = sym->next) if (f->symbol_cmp(sym->name, name) == 0) {//找到符号对应的值,保存原有的值。 int o_secidx = sym->secidx; int o_info = sym->info; int o_type = ELFW(ST_TYPE)(o_info); int o_binding = ELFW(ST_BIND)(o_info); /* A redefinition! Is it legal? */ if (secidx == SHN_UNDEF) return sym; else if (o_secidx == SHN_UNDEF) goto found; else if (n_binding == STB_GLOBAL && o_binding == STB_LOCAL) { /* Cope with local and global symbols of the same name in the same object file, as might have been created by ld -r. The only reason locals are now seen at this level at all is so that we can do semi-sensible things with parameters. */ struct obj_symbol *nsym, **p; nsym = arch_new_symbol();//生成一个新的sym加到hash表中 nsym->next = sym->next; nsym->ksymidx = -1; /* Excise the old (local) symbol from the hash chain. */ for (p = &f->symtab[hash]; *p != sym; p = &(*p)->next) continue; *p = sym = nsym; goto found; } else if (n_binding == STB_LOCAL) { /* Another symbol of the same name has already been defined. Just add this to the local table. */ sym = arch_new_symbol(); sym->next = NULL; sym->ksymidx = -1;//加到本地结构中 f->local_symtab[symidx] = sym; goto found; } else if (n_binding == STB_WEAK) return sym; else if (o_binding == STB_WEAK) goto found; /* Don't unify COMMON symbols with object types the programmer doesn't expect. */ else if (secidx == SHN_COMMON && (o_type == STT_NOTYPE || o_type == STT_OBJECT)) return sym; else if (o_secidx == SHN_COMMON && (n_type == STT_NOTYPE || n_type == STT_OBJECT)) goto found; else { /* Don't report an error if the symbol is coming from the kernel or some external module. */ if (secidx <= SHN_HIRESERVE) error("%s multiply defined", name); return sym; } } /* Completely new symbol. */ //开始的时候都会走到这里来 sym = arch_new_symbol();//创建新的sym结构加入到hash表中 sym->next = f->symtab[hash]; f->symtab[hash] = sym; sym->ksymidx = -1; if (ELFW(ST_BIND)(info) == STB_LOCAL && symidx != -1) { if (symidx >= f->local_symtab_size) error("local symbol %s with index %ld exceeds local_symtab_size %ld", name, (long) symidx, (long) f->local_symtab_size); else f->local_symtab[symidx] = sym;//如果是文件内使用的加入到此结构中 } found://以后用kernel中的symbol解析时会走到此处,value会添上正确的值 sym->name = name; sym->value = value; sym->size = size; sym->secidx = secidx; sym->info = info; sym->r_type = 0; /* should be R_arch_NONE for all arch */ return sym; } ... 5.比较kernel版本和module的版本。 ... /* Version correspondence? */ k_version = get_kernel_version(k_strversion); m_version = get_module_version(f, m_strversion); if (m_version == -1) { error("couldn't find the kernel version the module was compiled for"); goto out; } k_crcs = is_kernel_checksummed();//kernel的symbol是否含有RXXXXXXX的校验 m_crcs = is_module_checksummed(f);//module的symbol是否含有RXXXXXX的校验 if ((m_crcs == 0 || k_crcs == 0) &&//如果都含有,就不必比较version了 strncmp(k_strversion, m_strversion, STRVERSIONLEN) != 0) { if (flag_force_load) {//-f选项会强行加载 lprintf("Warning: kernel-module version mismatch\n" "\t%s was compiled for kernel version %s\n" "\twhile this kernel is version %s", filename, m_strversion, k_strversion); } else { if (!quiet) error("kernel-module version mismatch\n" "\t%s was compiled for kernel version %s\n" "\twhile this kernel is version %s.", filename, m_strversion, k_strversion); goto out; } } if (m_crcs != k_crcs)//两者比一样 //就不使用strcmp比较符号名字,而用ncv_strcmp比较使得printk和pirntk_RXXXXX //是相同的。还有重新创建hash表 obj_set_symbol_compare(f, ncv_strcmp, ncv_symbol_hash); ... 6.add_kernel_symbols替换。o中的symbol为ksyms中的符号值 ... static void add_kernel_symbols(struct obj_file *f) { struct module_stat *m; size_t i, nused = 0; /* 使用已有的module中的symbol */ for (i = 0, m = module_stat; i < n_module_stat; ++i, ++m) if (m->nsyms && add_symbols_from(f, SHN_HIRESERVE + 2 + i, m->syms, m->nsyms)) m->status = 1 /* used */, ++nused; n_ext_modules_used = nused; /* 使用kernel export的symbol */ if (nksyms) add_symbols_from(f, SHN_HIRESERVE + 1, ksyms, nksyms); } /* * Conditionally add the symbols from the given symbol set * to the new module. */ static int add_symbols_from(struct obj_file *f, int idx, struct module_symbol *syms, size_t nsyms) { struct module_symbol *s; size_t i; int used = 0; for (i = 0, s = syms; i < nsyms; ++i, ++s) { /* * Only add symbols that are already marked external. * If we override locals we may cause problems for * argument initialization. * We will also create a false dependency on the module. */ struct obj_symbol *sym; //表中是否有需要此名字的的symbol sym = obj_find_symbol(f, (char *) s->name); if (sym && !ELFW(ST_BIND) (sym->info) == STB_LOCAL) { sym = obj_add_symbol(f, (char *) s->name, -1, ELFW(ST_INFO) (STB_GLOBAL, STT_NOTYPE), idx, s->value, 0); /*将hash表中的待解析的symbol的value添成正确的值 * Did our symbol just get installed? * If so, mark the module as "used". */ if (sym->secidx == idx) used = 1; } } return used; } ... 7.create_this_module(f, m_name)生成module结构,加入module_list中。 ... static int create_this_module(struct obj_file *f, const char *m_name) { struct obj_section *sec; //创建一个.this节,节大小为struct module的大小 sec = obj_create_alloced_section_first(f, ".this", tgt_sizeof_long, sizeof(struct module)); memset(sec->contents, 0, sizeof(struct module)); //向hash表中添加一个__this_module的符号 obj_add_symbol(f, "__this_module", -1, ELFW(ST_INFO) (STB_LOCAL, STT_OBJECT), sec->idx, 0, sizeof(struct module)); //创建.kstrtab节 obj_string_patch(f, sec->idx, offsetof(struct module, name), m_name); return 1; } ... 8.obj_check_undefineds检查是否还有un_def的symbol ... obj_check_undefineds(struct obj_file *f, int quiet) { unsigned long i; int ret = 1; for (i = 0; i < HASH_BUCKETS; ++i) { struct obj_symbol *sym; for (sym = f->symtab; sym ; sym = sym->next) if (sym->secidx == SHN_UNDEF) { if (ELFW(ST_BIND)(sym->info) == STB_WEAK) { sym->secidx = SHN_ABS; sym->value = 0; } else if (sym->r_type) /* assumes R_arch_NONE is 0 on all arch */ { if (!quiet) error("unresolved symbol %s", sym->name); ret = 0; } } } return ret; } ... 9.add_archdata添加结构相关的section,不过i386没什么用。 10.add_kallsyms如果symbol使用的都是kernel提供的,就添加一个.kallsyms section 11.obj_load_size计算module的大小 ... /* Module has now finished growing; find its size and install it. */ m_size = obj_load_size(f); /* DEPMOD */ ... obj_load_size (struct obj_file *f) { unsigned long dot = 0; struct obj_section *sec; /* Finalize the positions of the sections relative to one another. */ for (sec = f->load_order; sec ; sec = sec->load_next) {//按照装载的顺序,计算module的大小。 ElfW(Addr) align; align = sec->header.sh_addralign; if (align && (dot & (align - 1))) dot = (dot | (align - 1)) + 1; sec->header.sh_addr = dot; dot += sec->header.sh_size; } return dot; } ... 12.create_module调用sys_create_module系统调用创建模块,分配module的空间 13.obj_relocate ... int obj_relocate (struct obj_file *f, ElfW(Addr) base) {//base就是create_module时分配的地址m_addr int i, n = f->header.e_shnum; int ret = 1; /* Finalize the addresses of the sections. */ //将各个section的sh_addr(原来是相对地址)加上此基地址 arch_finalize_section_address(f, base); /* And iterate over all of the relocations. */ for (i = 0; i < n; ++i) { struct obj_section *relsec, *symsec, *targsec, *strsec; ElfW(RelM) *rel, *relend; ElfW(Sym) *symtab; const char *strtab; relsec = f->sections;//找到重定位section if (relsec->header.sh_type != SHT_RELM) continue; //relse的sh_link指向.symtab节,sh_info指向.text节 symsec = f->sections[relsec->header.sh_link]; targsec = f->sections[relsec->header.sh_info]; //symtab节的sh_link指向符号字符串那节。 strsec = f->sections[symsec->header.sh_link]; //获得相应section的内容 rel = (ElfW(RelM) *)relsec->contents; relend = rel + (relsec->header.sh_size / sizeof(ElfW(RelM))); symtab = (ElfW(Sym) *)symsec->contents; strtab = (const char *)strsec->contents; for (; rel < relend; ++rel) { ElfW(Addr) value = 0; struct obj_symbol *intsym = NULL; unsigned long symndx; ElfW(Sym) *extsym = 0; const char *errmsg; /* Attempt to find a value to use for this relocation. */ //根据rel的描述找到需要重定位的符号索引值 symndx = ELFW(R_SYM)(rel->r_info); if (symndx) {//同上,找到符号的描述,存入intsym变量中。 /* Note we've already checked for undefined symbols. */ extsym = &symtab[symndx]; if (ELFW(ST_BIND)(extsym->st_info) == STB_LOCAL) { /* Local symbols we look up in the local table to be sure we get the one that is really intended. */ intsym = f->local_symtab[symndx]; } else { /* Others we look up in the hash table. */ const char *name; if (extsym->st_name) name = strtab + extsym->st_name; else name = f->sections[extsym->st_shndx]->name; intsym = obj_find_symbol(f, name); } //虽然有些函数的symbol的value已经被填写过了,但是rel中还有象.rodata节这样的需要 //relocate的符号,因此他的value是0+section[.rodata]->header->sh_addr的值 value = obj_symbol_final_value(f, intsym); } #if SHT_RELM == SHT_RELA #if defined(__alpha__) && defined(AXP_BROKEN_GAS) /* Work around a nasty GAS bug, that is fixed as of 2.7.0.9. */ if (!extsym || !extsym->st_name || ELFW(ST_BIND)(extsym->st_info) != STB_LOCAL) #endif value += rel->r_addend; #endif /* Do it! *///此函数将.text中需要relocate的地方都填写正确value //非常重要的一个函数 switch (arch_apply_relocation(f,targsec,symsec,intsym,rel,value)) {//f:objfile结构,targsec:.text节,symsec:.symtab节,rel:.rel结构,value:绝对地址 case obj_reloc_ok: break; case obj_reloc_overflow: errmsg = "Relocation overflow"; goto bad_reloc; case obj_reloc_dangerous: errmsg = "Dangerous relocation"; goto bad_reloc; case obj_reloc_unhandled: errmsg = "Unhandled relocation"; goto bad_reloc; case obj_reloc_constant_gp: errmsg = "Modules compiled with -mconstant-gp cannot be loaded"; goto bad_reloc; bad_reloc: if (extsym) { error("%s of type %ld for %s", errmsg, (long)ELFW(R_TYPE)(rel->r_info), strtab + extsym->st_name); } else { error("%s of type %ld", errmsg, (long)ELFW(R_TYPE)(rel->r_info)); } ret = 0; break; } } } /* Finally, take care of the patches. */ if (f->string_patches) { struct obj_string_patch_struct *p; struct obj_section *strsec; ElfW(Addr) strsec_base; strsec = obj_find_section(f, ".kstrtab"); strsec_base = strsec->header.sh_addr; for (p = f->string_patches; p ; p = p->next) { struct obj_section *targsec = f->sections[p->reloc_secidx]; *(ElfW(Addr) *)(targsec->contents + p->reloc_offset) = strsec_base + p->string_offset; } } if (f->symbol_patches) { struct obj_symbol_patch_struct *p; for (p = f->symbol_patches; p; p = p->next) { struct obj_section *targsec = f->sections[p->reloc_secidx]; *(ElfW(Addr) *)(targsec->contents + p->reloc_offset) = obj_symbol_final_value(f, p->sym); } } return ret; } ... 14.init_module初始化有create_module生成的module,是由sys_init_module系统调用实现的。 ... static int init_module(const char *m_name, struct obj_file *f, unsigned long m_size, const char *blob_name, unsigned int noload, unsigned int flag_load_map) {//一般情况下后三个参数都是0 struct module *module; struct obj_section *sec; void *image; int ret = 0; tgt_long m_addr; //找到原来用create_this_module生成的.this节 sec = obj_find_section(f, ".this"); module = (struct module *) sec->contents; m_addr = sec->header.sh_addr;//base基地址 module->size_of_struct = sizeof(*module);//module结构大小 module->size = m_size;//module总共的大小 module->flags = flag_autoclean ? NEW_MOD_AUTOCLEAN : 0; sec = obj_find_section(f, "__ksymtab"); if (sec && sec->header.sh_size) {//模块自己export的symbol module->syms = sec->header.sh_addr; module->nsyms = sec->header.sh_size / (2 * tgt_sizeof_char_p); } if (n_ext_modules_used) {//填写此module依靠的模块 sec = obj_find_section(f, ".kmodtab"); module->deps = sec->header.sh_addr; module->ndeps = n_ext_modules_used; } //填写init_module,cleanup_module的地址 module->init = obj_symbol_final_value(f, obj_find_symbol(f, "init_module")); module->cleanup = obj_symbol_final_value(f, obj_find_symbol(f, "cleanup_module")); //exception_table_entry的地址,一般没有 sec = obj_find_section(f, "__ex_table"); if (sec) { module->ex_table_start = sec->header.sh_addr; module->ex_table_end = sec->header.sh_addr + sec->header.sh_size; } sec = obj_find_section(f, ".text.init"); if (sec) {//这个runsize不知道是什么东西,一般没有 module->runsize = sec->header.sh_addr - m_addr; } sec = obj_find_section(f, ".data.init"); if (sec) { if (!module->runsize || module->runsize > sec->header.sh_addr - m_addr) module->runsize = sec->header.sh_addr - m_addr; } sec = obj_find_section(f, ARCHDATA_SEC_NAME); if (sec && sec->header.sh_size) { module->archdata_start = sec->header.sh_addr; module->archdata_end = module->archdata_start + sec->header.sh_size; } sec = obj_find_section(f, KALLSYMS_SEC_NAME); if (sec && sec->header.sh_size) { module->kallsyms_start = sec->header.sh_addr; module->kallsyms_end = module->kallsyms_start + sec->header.sh_size; } if (!arch_init_module(f, module))//i386此函数直接返回 return 0; /* * Whew! All of the initialization is complete. * Collect the final module image and give it to the kernel. */ image = xmalloc(m_size); obj_create_image(f, image);//现在用户空间分配module的image的内存,然后将 //各个section的内容拷入image中,包括原文件中的section和后来构造的section if (flag_load_map) print_load_map(f); if (blob_name) { int fd, l; fd = open(blob_name, O_WRONLY|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); if (fd < 0) { error("open %s failed %m", blob_name); ret = -1; } else { if ((l = write(fd, image, m_size)) != m_size) { error("write %s failed %m", blob_name); ret = -1; } close(fd); } } if (ret == 0 && !noload) {//调用系统调用完成最后的insmod工作。 fflush(stdout); /* Flush any debugging output */ ret = sys_init_module(m_name, (struct module *) image); if (ret) { error("init_module: %m"); lprintf("Hint: insmod errors can be caused by incorrect module parameters, " "including invalid IO or IRQ parameters"); } } free(image); return ret == 0; } ... 最后清理一下思路。 1.get_kernel_info函数负责取得kernel中先以注册的modules,放入module_stat中. 2.set_ncv_prefix(NULL);判断symbolname中是否有前缀,象_smp之类,一般没有。 3.检查是否已有同名的module。 4.obj_load,将。o文件读入到struct obj_file结构f中。 5.比较kernel版本和module的版本,在版本的判断中,不是想象中的那样简单,还有是否有checksum的逻 辑关系。 6.add_kernel_symbols替换.o中的symbol为ksyms中的符号值 7.create_this_module(f, m_name)生成module结构,加入module_list中。 8.obj_check_undefineds检查是否还有un_def的symbol 9.add_archdata添加结构相关的section,不过i386没什么用。 10.add_kallsyms如果symbol使用的都是kernel提供的,就添加一个.kallsyms section 11.obj_load_size计算module的大小 12.create_module调用sys_create_module系统调用创建模块,分配module的空间 13.obj_relocate重定位module文件中.text中的地址 14.init_module先在用户空间创建module结构的image影响,是由sys_init_module系统调用实现向kernel的 copy。 经过对insmod主要流程的分析,发现原来的理解没有偏差,那问什么会被成功加载呢?后来仔细用gdb跟 了一把,发现所有对symtab的操作都被调过,因为它一直为NULL嘛,可是发现在没有symtab的情况 下,2.4.6竟然一个判断都没有,一路平稳的跑了下来,然后在module结构init和cleanup指针都为NULL的情 况下被加载成功。 而2.4.10的modutils中的insmod对strip后的module加载后有除零的异常.在obj_load函数实现中有 278 nsyms = symtab->header.sh_size / symtab->header.sh_entsize; 由于module中根本没有symtab所以这个结构中的所有值都为0。 后话: 其实将这篇文章加到backdoor研究中有些牵强,但是前一阵和backend讨论内核方式后门实现时讨论 了kernel image的方式,但只是限于理论上的讨论,考虑到以后有这方面实现的可能,那时有可能要自己 实现一个简单的insmod流程完成对symbol的解析和重定位,关于那三个module方面的系统调用在《情景 分析》中有较详细的讨论,就不班门弄斧了。 |
|
|
10楼#
发布于:2004-04-03 11:11
分析的很深入呀,尽管偶现在还看不懂!不过有版主这样热心的高手,偶还是比较有信心的
|
|
11楼#
发布于:2004-04-11 11:29
首先当然还是要谢谢楼主在技术上的无私贡献,不过楼主发的第一个驱动只是个hello wold,教教大家如何编译驱动而已,而第二个程序相信能看懂的不多.我个人觉得楼主如果能系统的写一系列教程循序渐进地教大家那就好了.毕竟中国搞LINUX的人比例还是相当少的,对内核编程根本没概念上的认识.例如在内核中如何创建进程(kernel_thread()),进程休眠(schedule())等等,这些函数不单单是调用这么简单,实际上,深入认识这些函数后,对内核的工作模式就会有相当深入的了解了.内核编程之后以困难,是因为内核中所有的函数都不能用man来查看的.另外,内核模式的程序调式也是个问题,我做了三年的LINUX驱动,都是用printk来调程序.累.
|
|
12楼#
发布于:2004-07-30 16:51
没太看懂,不过这种精神是可嘉的。还希望版主能多多赐教!
|
|
13楼#
发布于:2004-08-03 09:38
分析的很深,我没看懂,多谢版主的讲解,谢谢大家
|
|
14楼#
发布于:2004-08-18 08:43
有人把应用程序打到内核运行,请问这样有哪些好处?
会提高效率吗?(减少内核与用户运行环境切换) 会不会因为应用不稳定,导致OS系统不稳定 |
|
15楼#
发布于:2004-10-20 18:29
谢谢!!!
|
|