/* * my_mmap - Chamada de sistema mmap implementada em modulo * * Esta e uma implementacao simples da chamada de sistema mmap. * Nos adicionamos a nossa chamada de sistema na posicao 228 da sys_call_table * * Para voce compreender melhor este modulo, vamos a uma rapida visao sobre VM no kernel * * Antes de comecar quero lembrar que este modulo so vai funcionar se voce adicionar o * simbolo vm_area_cachep e insert_vm_struct do kernel para serem exportados. Para isto adicione * as seguintes linhas em /usr/src/linux/kernel/ksyms.c e recompile o seu kernel. * * EXPORT_SYMBOL(vm_area_cachep); * EXPORT_SYMBOL(insert_vm_struct); * * O vm_area_cachep, eu nao vou explicar em detalhes aqui, mas ele e um objeto do * slab allocator para VMA, ou seja, uma cache do kernel para a estrutura vm_area_struct. * O insert_vm_struct, e a funcao que insere a VMA que nos criamos na lista de VMAs do usuario * Para conferir que os simbolos estao exportados procure-os em /proc/ksyms * * Todas as informacoes relacionados com o espaco de enderecamento do processo estao incluidas * na estrutura referenciada pelo campo mm no process descriptor. Esta estrutura e a * mm_struct, que e encontrada em include/linux/sched.h, ou seja, ela e o memory descriptor. * Deste modo cada processo tem _uma_ mm_struct. * O process descriptor eh a task_struct que tambem pode ser encontrada em include/linux/sched.h * * O linux implementa a area virtual de memoria (VMA) atraves da estrutura vm_area_struct, * que pode ser encontrada em include/linux/mm.h * Uma VMA e um intervalo de endereco linear e que contem as mesmas FLAGs de permissoes, ou seja, uma * regiao homogenea * Assim, cada processo tem uma lista de VMA. * * Todas as regioes de memoria do processo sao lincadas. O kernel encontra uma area de memoria atraves * do campo mmap da mm_struct do processo. Para evitar uma pesquisa linear nesta lista de VMAs, o kernel * utiliza uma red black tree. Isto ocorreu na versao 2.4.10 do kernel, as versoes anteriores utilizavam * a lista encadeada simples para manter a lista das VMAs, e quando o numero de VMAs alcançava 32 * ele usava uma AVL tree. Nas versoes acima da 2.4.10, ele sempre utiliza uma rb tree. A qual * tem um algoritmo mais eficiente de pesquisa, insert... Assim, quando o kernel tem um endereco e quer * encontrar qual VMA este endereco pertence ele usa a funcao find_vma, que pode ser encontrada em * mm/mmap.c * * As regioes de memoria de cada processo podem ser acessadas atraves /proc/pid/maps onde pid e o pid * do processo. * De uma olhada na mm_struct e vm_area_struct que voce vai compreender melhor. * * Exitem um campo na vm_area_struct que e struct vm_operations_struct * vm_ops, esta e uma estrutura * para certas funcoes que podem ser especificas para cada VMA. No nosso modulo, nos utilizamos o campo * nopage desta estrutura, o qual vai ser chamado cada vez que o processo acessar uma pagina e esta * nao estiver presente em memoria, ou seja, um page fault. * * OBS: o nosso modulo sempre aloca memoria anonima, ou seja, memoria que nao esta vinculada * com nenhum arquivo * * -------------------------------------------------------------------- * Parte do usuario. Este programa chama a nossa nova chamada de sistema: #include <stdio.h> #include <stdlib.h> #include <asm/unistd.h> // _syscallX macro #include <errno.h> #define __NR_my_mmap 228 // numbero da nossa chamada de sistema _syscall3(int,my_mmap,int,x,int,y,int,z) // 3 args //terceiro campo nao e usado para nada int main(void) { int n; char *i, *page; i=(char *)my_mmap(0,40960,2); //alocamos 10 pagina printf("return the value %d\n",i); // Vamos fazer o page fault para a primeira pagina // vamos copiar o conteudo desta pagina para page // imprimir o conteudo de page // isto pq quando o page fault ocorre nos escrevemos alguma coisa // na pagina antes de retornar para o usuario page = (char *)malloc(4096); bcopy((void *)i, (void *)page, 4096); printf("\npage = %s\n", page); printf("liberar a memoria com o munmap\n"); n=munmap(i,40960); if (n<0) printf("ERRO munmap = %d\n"); else printf("munmap OK\n"); } * * -------------------------------------------------------------------- * * Agora que ja temos uma visao rapidao vamos ao codigo :) * * 14/11/2001 */ #include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/unistd.h> #include <linux/mm.h> #include <linux/sched.h> #include <linux/slab.h> #include <linux/highmem.h> #include <linux/string.h> #include <asm/uaccess.h> #include <asm/errno.h> #include <asm/mman.h> #include <asm/page.h> #include <asm/pgalloc.h> #include <asm/processor.h> #define NUM_SYSCALL_mmap 228 //entry on sys_call_table to hijack (entry not used) /* guara o endereco original da chamada de sistema */ asmlinkage long (*original_call_mmap)(void); /* Combine the mmap "prot" and "flags" argument into one "vm_flags" used * internally. Essentially, translate the "PROT_xxx" and "MAP_xxx" bits * into "VM_xxx". */ static inline unsigned long calc_vm_flags(unsigned long prot, unsigned long flags) { #define _trans(x,bit1,bit2) \ ((bit1==bit2)?(x&bit1):(x&bit1)?bit2:0) unsigned long prot_bits, flag_bits; prot_bits = _trans(prot, PROT_READ, VM_READ) | _trans(prot, PROT_WRITE, VM_WRITE) | _trans(prot, PROT_EXEC, VM_EXEC); flag_bits = _trans(flags, MAP_GROWSDOWN, VM_GROWSDOWN) | _trans(flags, MAP_DENYWRITE, VM_DENYWRITE) | _trans(flags, MAP_EXECUTABLE, VM_EXECUTABLE); return prot_bits | flag_bits; #undef _trans } pgprot_t protection_map[16] = { __P000, __P001, __P010, __P011, __P100, __P101, __P110, __P111, __S000, __S001, __S010, __S011, __S100, __S101, __S110, __S111 }; /* * Function nopage a qual e chamada toda vez que o usuario tiver um page fault */ struct page *my_nopage(struct vm_area_struct *vma, unsigned long address, int write_access) { struct page *page = NOPAGE_SIGBUS; char *kaddr; printk(KERN_ALERT "my_nopage : nopage chamado \n"); page = alloc_page(GFP_USER); if (!page) return NOPAGE_SIGBUS; printk(KERN_ALERT "my_nopage : page zone = %s\n",page->zone->name); printk(KERN_ALERT "my_nopage : zone free pages = %lu\n",page->zone->free_pages); kaddr = (char *) kmap(page); /* Escreve esta string na pagina */ strcpy((char *)kaddr, "My mmap cool"); return page; } /* * Troca a o ponteiro da vma->vm_ops da VMA * ops nopage, agora aponta para function my_nopage */ struct vm_operations_struct my_vm_ops = { nopage: my_nopage, }; /* * Function que realmente faz o mmap e insere ela na lista de VMAs do * processo do usuário */ unsigned long do_my_mmap(unsigned long addr, unsigned long len, unsigned long type) { struct mm_struct * mm = current->mm; struct vm_area_struct * vma; unsigned long flags, prot; unsigned int vm_flags; int error; if ((len = PAGE_ALIGN(len)) == 0) { printk(KERN_ALERT "ERRO do_my_mmap : PAGE_ALIGN \n"); return addr; } //printk(KERN_ALERT "do_my_mmap: len apos PAGE_ALIGN = %lu\n",len); if (len > TASK_SIZE || addr > TASK_SIZE-len) { printk(KERN_ALERT "ERRO do_my_mmap : len > TASK_SIZE ou addr > TASK_SIZE-len\n"); return -EINVAL; } if (mm->map_count > MAX_MAP_COUNT) { printk(KERN_ALERT "ERRO do_my_mmap : mm->map_count = %d\n",mm->map_count); return -ENOMEM; } prot = PROT_READ | PROT_WRITE; //permissao para ler e escrever flags = MAP_ANON; //memoria anonima addr = get_unmapped_area(NULL, addr, len, 0, flags); if (addr & ~PAGE_MASK) return addr; printk(KERN_ALERT "addr retornado pelo get_unmapped_area = %lu\n",addr); vm_flags = calc_vm_flags(prot,flags) | mm->def_flags | VM_MAYREAD | VM_MAYWRITE | VM_MAYEXEC; /* Esta funcao testa o numero de paginas locked que o processo pode ter */ if (vm_flags & VM_LOCKED) { unsigned long locked = mm->locked_vm << PAGE_SHIFT; locked += len; if (locked > current->rlim[RLIMIT_MEMLOCK].rlim_cur) return -EAGAIN; } /* Clear old maps */ error = -ENOMEM; if (do_munmap(mm, addr, len)) return -ENOMEM; /* Check against address space limit. */ if ((mm->total_vm << PAGE_SHIFT) + len > current->rlim[RLIMIT_AS].rlim_cur) return -ENOMEM; /* Aloca uma VMA da cache do kernel, este simbolo tem que ser * exportado como mencionado antes */ vma = kmem_cache_alloc(vm_area_cachep, SLAB_KERNEL); if (!vma) { printk(KERN_ALERT "ERRO do_my_mmap : kmem_cache_alloc\n"); return -ENOMEM; } /* Preenchemos os campos da nossta VMA */ vma->vm_mm = mm; //ponteiro para a mm_struct do processo vma->vm_start = addr; vma->vm_end = addr + len; vma->vm_flags = vm_flags; vma->vm_page_prot = protection_map[vm_flags & 0x0f]; vma->vm_ops = &my_vm_ops; //trocamos o ponteiro da vm_ops, para depois trocar o nopage vma->vm_pgoff = 0; vma->vm_file = NULL; // memoria anonima, tb setado nas flags vma->vm_private_data = NULL; vma->vm_raend = 0; /* insere a VMA na lista de VMAs do processo */ insert_vm_struct(mm, vma); /* * Aumentamos o numero total de paginas que tem o espaco de * enderecamento deste processo */ mm->total_vm += len >> PAGE_SHIFT; return addr; //retorna o endereco } /* * Function que é execudata quando o usuario chamar a syscall my_mmap */ asmlinkage long my_syscall_mmap(unsigned long addr,unsigned long len,unsigned long type) { int error; MOD_INC_USE_COUNT; printk(KERN_ALERT "my_mmap : addr = %lu, size = %lu, type = %lu\n",addr,len,type); printk(KERN_ALERT "my_mmap : current->comm is = %s\n",current->comm); down_write(¤t->mm->mmap_sem); error = do_my_mmap(addr, len, type); up_write(¤t->mm->mmap_sem); MOD_DEC_USE_COUNT; return error; } int mmap_init_module(void) { extern long sys_call_table[]; original_call_mmap = (long (*)(void))(sys_call_table[NUM_SYSCALL_mmap]); sys_call_table[NUM_SYSCALL_mmap] = (unsigned long)my_syscall_mmap; printk(KERN_ALERT "Chamada de sistema %d sequestrada \n",NUM_SYSCALL_mmap); return 0; } void mmap_cleanup_module(void) { extern long sys_call_table[]; sys_call_table[NUM_SYSCALL_mmap] = (unsigned long)original_call_mmap; printk(KERN_ALERT "Chamada de sistema %d restaurada\n",NUM_SYSCALL_mmap); } module_init(mmap_init_module); module_exit(mmap_cleanup_module); MODULE_AUTHOR("Thobias Salazar Trevisan"); MODULE_DESCRIPTION("Implementa uma chamada de sistema mmap"); MODULE_LICENSE("GPL"); EXPORT_NO_SYMBOLS;