/* ** Copyright (C) 2011 EADS France, Fabrice Desclaux ** ** This program is free software; you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation; either version 2 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License along ** with this program; if not, write to the Free Software Foundation, Inc., ** 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "vm_mngr.h" #include #include #include #include "queue.h" /****************memory manager**************/ #define MIN(a,b) (((a)<(b))?(a):(b)) #define MAX(a,b) (((a)>(b))?(a):(b)) // #define DEBUG_MIASM_AUTOMOD_CODE void memory_access_list_init(struct memory_access_list * access) { access->array = NULL; access->allocated = 0; access->num = 0; } void memory_access_list_reset(struct memory_access_list * access) { if (access->array) { free(access->array); access->array = NULL; } access->allocated = 0; access->num = 0; } void memory_access_list_add(struct memory_access_list * access, uint64_t start, uint64_t stop) { if (access->num >= access->allocated) { if (access->allocated == 0) access->allocated = 1; else access->allocated *= 2; access->array = realloc(access->array, access->allocated * sizeof(struct memory_access)); if (access->array == NULL) { fprintf(stderr, "cannot realloc struct memory_access access->array\n"); exit(EXIT_FAILURE); } } access->array[access->num].start = start; access->array[access->num].stop = stop; access->num += 1; } uint16_t set_endian16(vm_mngr_t* vm_mngr, uint16_t val) { if (vm_mngr->sex == __BYTE_ORDER) return val; else return Endian16_Swap(val); } uint32_t set_endian32(vm_mngr_t* vm_mngr, uint32_t val) { if (vm_mngr->sex == __BYTE_ORDER) return val; else return Endian32_Swap(val); } uint64_t set_endian64(vm_mngr_t* vm_mngr, uint64_t val) { if (vm_mngr->sex == __BYTE_ORDER) return val; else return Endian64_Swap(val); } void print_val(uint64_t base, uint64_t addr) { uint64_t *ptr = (uint64_t *) (intptr_t) addr; fprintf(stderr, "addr 0x%"PRIX64" val 0x%"PRIX64"\n", addr-base, *ptr); } int midpoint(int imin, int imax) { return (imin + imax) / 2; } int find_page_node(struct memory_page_node * array, uint64_t key, int imin, int imax) { // continue searching while [imin,imax] is not empty while (imin <= imax) { // calculate the midpoint for roughly equal partition int imid = midpoint(imin, imax); if(array[imid].ad <= key && key < array[imid].ad + array[imid].size) // key found at index imid return imid; // determine which subarray to search else if (array[imid].ad < key) // change min index to search upper subarray imin = imid + 1; else // change max index to search lower subarray imax = imid - 1; } // key was not found return -1; } struct memory_page_node * get_memory_page_from_address(vm_mngr_t* vm_mngr, uint64_t ad, int raise_exception) { struct memory_page_node * mpn; int i; i = find_page_node(vm_mngr->memory_pages_array, ad, 0, vm_mngr->memory_pages_number - 1); if (i >= 0) { mpn = &vm_mngr->memory_pages_array[i]; if ((mpn->ad <= ad) && (ad < mpn->ad + mpn->size)) return mpn; } if (raise_exception) { fprintf(stderr, "WARNING: address 0x%"PRIX64" is not mapped in virtual memory:\n", ad); vm_mngr->exception_flags |= EXCEPT_ACCESS_VIOL; } return NULL; } static uint64_t memory_page_read(vm_mngr_t* vm_mngr, unsigned int my_size, uint64_t ad) { struct memory_page_node * mpn; unsigned char * addr; uint64_t ret = 0; struct memory_breakpoint_info * b; mpn = get_memory_page_from_address(vm_mngr, ad, 1); if (!mpn) return 0; if ((mpn->access & PAGE_READ) == 0){ fprintf(stderr, "access to non readable page!! %"PRIX64"\n", ad); vm_mngr->exception_flags |= EXCEPT_ACCESS_VIOL; return 0; } /* check read breakpoint */ LIST_FOREACH(b, &vm_mngr->memory_breakpoint_pool, next){ if ((b->access & BREAKPOINT_READ) == 0) continue; if ((b->ad <= ad) && (ad < b->ad + b->size)) vm_mngr->exception_flags |= EXCEPT_BREAKPOINT_MEMORY; } addr = &((unsigned char*)mpn->ad_hp)[ad - mpn->ad]; /* read fits in a page */ if (ad - mpn->ad + my_size/8 <= mpn->size){ switch(my_size){ case 8: ret = *((unsigned char*)addr)&0xFF; break; case 16: ret = *((unsigned short*)addr)&0xFFFF; ret = set_endian16(vm_mngr, (uint16_t)ret); break; case 32: ret = *((unsigned int*)addr)&0xFFFFFFFF; ret = set_endian32(vm_mngr, (uint32_t)ret); break; case 64: ret = *((uint64_t*)addr)&0xFFFFFFFFFFFFFFFFULL; ret = set_endian64(vm_mngr, ret); break; default: fprintf(stderr, "Bad memory access size %d\n", my_size); exit(EXIT_FAILURE); break; } } /* read is multiple page wide */ else{ unsigned int new_size = my_size; int index = 0; while (new_size){ mpn = get_memory_page_from_address(vm_mngr, ad, 1); if (!mpn) return 0; addr = &((unsigned char*)mpn->ad_hp)[ad - mpn->ad]; ret |= ((uint64_t)(*((unsigned char*)addr)&0xFF))<<(index); index +=8; new_size -= 8; ad ++; } switch(my_size){ case 8: ret = ret; break; case 16: ret = set_endian16(vm_mngr, (uint16_t)ret); break; case 32: ret = set_endian32(vm_mngr, (uint32_t)ret); break; case 64: ret = set_endian64(vm_mngr, ret); break; default: fprintf(stderr, "Bad memory access size %d\n", my_size); exit(EXIT_FAILURE); break; } } return ret; } static void memory_page_write(vm_mngr_t* vm_mngr, unsigned int my_size, uint64_t ad, uint64_t src) { struct memory_page_node * mpn; unsigned char * addr; struct memory_breakpoint_info * b; mpn = get_memory_page_from_address(vm_mngr, ad, 1); if (!mpn) return; if ((mpn->access & PAGE_WRITE) == 0){ fprintf(stderr, "access to non writable page!! %"PRIX64"\n", ad); vm_mngr->exception_flags |= EXCEPT_ACCESS_VIOL; return ; } /* check read breakpoint*/ LIST_FOREACH(b, &vm_mngr->memory_breakpoint_pool, next){ if ((b->access & BREAKPOINT_WRITE) == 0) continue; if ((b->ad <= ad) && (ad < b->ad + b->size)) vm_mngr->exception_flags |= EXCEPT_BREAKPOINT_MEMORY; } addr = &((unsigned char*)mpn->ad_hp)[ad - mpn->ad]; /* write fits in a page */ if (ad - mpn->ad + my_size/8 <= mpn->size){ switch(my_size){ case 8: *((unsigned char*)addr) = src&0xFF; break; case 16: src = set_endian16(vm_mngr, (uint16_t)src); *((unsigned short*)addr) = src&0xFFFF; break; case 32: src = set_endian32(vm_mngr, (uint32_t)src); *((unsigned int*)addr) = src&0xFFFFFFFF; break; case 64: src = set_endian64(vm_mngr, src); *((uint64_t*)addr) = src&0xFFFFFFFFFFFFFFFFULL; break; default: fprintf(stderr, "Bad memory access size %d\n", my_size); exit(EXIT_FAILURE); break; } } /* write is multiple page wide */ else{ switch(my_size){ case 8: src = src; break; case 16: src = set_endian16(vm_mngr, (uint16_t)src); break; case 32: src = set_endian32(vm_mngr, (uint32_t)src); break; case 64: src = set_endian64(vm_mngr, src); break; default: fprintf(stderr, "Bad memory access size %d\n", my_size); exit(EXIT_FAILURE); break; } while (my_size){ mpn = get_memory_page_from_address(vm_mngr, ad, 1); if (!mpn) return; addr = &((unsigned char*)mpn->ad_hp)[ad - mpn->ad]; *((unsigned char*)addr) = src&0xFF; my_size -= 8; src >>=8; ad ++; } } } // ################## void dump_code_bloc(vm_mngr_t* vm_mngr) { struct code_bloc_node * cbp; LIST_FOREACH(cbp, &vm_mngr->code_bloc_pool, next){ fprintf(stderr, "%"PRIX64"%"PRIX64"\n", cbp->ad_start, cbp->ad_stop); } } void add_range_to_list(struct memory_access_list * access, uint64_t addr1, uint64_t addr2) { if (access->num > 0) { /* Check match on upper bound */ if (access->array[access->num-1].stop == addr1) { access->array[access->num-1].stop = addr2; return; } /* Check match on lower bound */ if (access->array[0].start == addr2) { access->array[0].start = addr1; return; } } /* No merge, add to the list */ memory_access_list_add(access, addr1, addr2); } void add_mem_read(vm_mngr_t* vm_mngr, uint64_t addr, uint64_t size) { add_range_to_list(&(vm_mngr->memory_r), addr, addr + size); } void add_mem_write(vm_mngr_t* vm_mngr, uint64_t addr, uint64_t size) { add_range_to_list(&(vm_mngr->memory_w), addr, addr + size); } void check_invalid_code_blocs(vm_mngr_t* vm_mngr) { int i; struct code_bloc_node * cbp; for (i=0;imemory_w.num; i++) { if (vm_mngr->exception_flags & EXCEPT_CODE_AUTOMOD) break; if (vm_mngr->memory_w.array[i].stop <= vm_mngr->code_bloc_pool_ad_min || vm_mngr->memory_w.array[i].start >=vm_mngr->code_bloc_pool_ad_max) continue; LIST_FOREACH(cbp, &vm_mngr->code_bloc_pool, next){ if ((cbp->ad_start < vm_mngr->memory_w.array[i].stop) && (vm_mngr->memory_w.array[i].start < cbp->ad_stop)){ #ifdef DEBUG_MIASM_AUTOMOD_CODE fprintf(stderr, "**********************************\n"); fprintf(stderr, "self modifying code %"PRIX64" %"PRIX64"\n", vm_mngr->memory_w.array[i].start, vm_mngr->memory_w.array[i].stop); fprintf(stderr, "**********************************\n"); #endif vm_mngr->exception_flags |= EXCEPT_CODE_AUTOMOD; break; } } } } void check_memory_breakpoint(vm_mngr_t* vm_mngr) { int i; struct memory_breakpoint_info * memory_bp; /* Check memory breakpoints */ LIST_FOREACH(memory_bp, &vm_mngr->memory_breakpoint_pool, next) { if (vm_mngr->exception_flags & EXCEPT_BREAKPOINT_MEMORY) break; if (memory_bp->access & BREAKPOINT_READ) { for (i=0;imemory_r.num; i++) { if ((memory_bp->ad < vm_mngr->memory_r.array[i].stop) && (vm_mngr->memory_r.array[i].start < memory_bp->ad + memory_bp->size)) { vm_mngr->exception_flags |= EXCEPT_BREAKPOINT_MEMORY; break; } } } if (memory_bp->access & BREAKPOINT_WRITE) { for (i=0;imemory_w.num; i++) { if ((memory_bp->ad < vm_mngr->memory_w.array[i].stop) && (vm_mngr->memory_w.array[i].start < memory_bp->ad + memory_bp->size)) { vm_mngr->exception_flags |= EXCEPT_BREAKPOINT_MEMORY; break; } } } } } PyObject* get_memory_pylist(vm_mngr_t* vm_mngr, struct memory_access_list* memory_list) { int i; PyObject *pylist; PyObject *range; pylist = PyList_New(memory_list->num); for (i=0;inum;i++) { range = PyTuple_New(2); PyTuple_SetItem(range, 0, PyLong_FromUnsignedLongLong((uint64_t)memory_list->array[i].start)); PyTuple_SetItem(range, 1, PyLong_FromUnsignedLongLong((uint64_t)memory_list->array[i].stop)); PyList_SetItem(pylist, i, range); } return pylist; } PyObject* get_memory_read(vm_mngr_t* vm_mngr) { return get_memory_pylist(vm_mngr, &vm_mngr->memory_r); } PyObject* get_memory_write(vm_mngr_t* vm_mngr) { return get_memory_pylist(vm_mngr, &vm_mngr->memory_w); } void vm_MEM_WRITE_08(vm_mngr_t* vm_mngr, uint64_t addr, unsigned char src) { add_mem_write(vm_mngr, addr, 1); memory_page_write(vm_mngr, 8, addr, src); } void vm_MEM_WRITE_16(vm_mngr_t* vm_mngr, uint64_t addr, unsigned short src) { add_mem_write(vm_mngr, addr, 2); memory_page_write(vm_mngr, 16, addr, src); } void vm_MEM_WRITE_32(vm_mngr_t* vm_mngr, uint64_t addr, unsigned int src) { add_mem_write(vm_mngr, addr, 4); memory_page_write(vm_mngr, 32, addr, src); } void vm_MEM_WRITE_64(vm_mngr_t* vm_mngr, uint64_t addr, uint64_t src) { add_mem_write(vm_mngr, addr, 8); memory_page_write(vm_mngr, 64, addr, src); } unsigned char vm_MEM_LOOKUP_08(vm_mngr_t* vm_mngr, uint64_t addr) { unsigned char ret; add_mem_read(vm_mngr, addr, 1); ret = (unsigned char)memory_page_read(vm_mngr, 8, addr); return ret; } unsigned short vm_MEM_LOOKUP_16(vm_mngr_t* vm_mngr, uint64_t addr) { unsigned short ret; add_mem_read(vm_mngr, addr, 2); ret = (unsigned short)memory_page_read(vm_mngr, 16, addr); return ret; } unsigned int vm_MEM_LOOKUP_32(vm_mngr_t* vm_mngr, uint64_t addr) { unsigned int ret; add_mem_read(vm_mngr, addr, 4); ret = (unsigned int)memory_page_read(vm_mngr, 32, addr); return ret; } uint64_t vm_MEM_LOOKUP_64(vm_mngr_t* vm_mngr, uint64_t addr) { uint64_t ret; add_mem_read(vm_mngr, addr, 8); ret = memory_page_read(vm_mngr, 64, addr); return ret; } int vm_read_mem(vm_mngr_t* vm_mngr, uint64_t addr, char** buffer_ptr, uint64_t size) { char* buffer; uint64_t len; struct memory_page_node * mpn; buffer = malloc(size); *buffer_ptr = buffer; if (!buffer){ fprintf(stderr, "Error: cannot alloc read\n"); exit(EXIT_FAILURE); } /* read is multiple page wide */ while (size){ mpn = get_memory_page_from_address(vm_mngr, addr, 1); if (!mpn){ free(*buffer_ptr); PyErr_SetString(PyExc_RuntimeError, "Error: cannot find address"); return -1; } len = MIN(size, mpn->size - (addr - mpn->ad)); memcpy(buffer, (char*)mpn->ad_hp + (addr - mpn->ad), len); buffer += len; addr += len; size -= len; } return 0; } int vm_write_mem(vm_mngr_t* vm_mngr, uint64_t addr, char *buffer, uint64_t size) { uint64_t len; struct memory_page_node * mpn; /* write is multiple page wide */ while (size){ mpn = get_memory_page_from_address(vm_mngr, addr, 1); if (!mpn){ PyErr_SetString(PyExc_RuntimeError, "Error: cannot find address"); return -1; } len = MIN(size, mpn->size - (addr - mpn->ad)); memcpy((char*)mpn->ad_hp + (addr-mpn->ad), buffer, len); buffer += len; addr += len; size -= len; } return 0; } int is_mapped(vm_mngr_t* vm_mngr, uint64_t addr, uint64_t size) { uint64_t len; struct memory_page_node * mpn; /* test multiple page wide */ while (size){ mpn = get_memory_page_from_address(vm_mngr, addr, 0); if (!mpn) return 0; len = MIN(size, mpn->size - (addr - mpn->ad)); addr += len; size -= len; } return 1; } struct memory_page_node * create_memory_page_node(uint64_t ad, unsigned int size, unsigned int access, char* name) { struct memory_page_node * mpn; void* ad_hp; mpn = malloc(sizeof(*mpn)); if (!mpn){ fprintf(stderr, "Error: cannot alloc mpn\n"); return NULL; } ad_hp = malloc(size); if (!ad_hp){ free(mpn); fprintf(stderr, "Error: cannot alloc %d\n", size); return NULL; } mpn->name = malloc(strlen(name) + 1); if (!mpn->name){ free(mpn); free(ad_hp); fprintf(stderr, "Error: cannot alloc\n"); return NULL; } mpn->ad = ad; mpn->size = size; mpn->access = access; mpn->ad_hp = ad_hp; strcpy(mpn->name, name); return mpn; } struct code_bloc_node * create_code_bloc_node(uint64_t ad_start, uint64_t ad_stop) { struct code_bloc_node * cbp; cbp = malloc(sizeof(*cbp)); if (!cbp){ fprintf(stderr, "Error: cannot alloc cbp\n"); exit(EXIT_FAILURE); } cbp->ad_start = ad_start; cbp->ad_stop = ad_stop; return cbp; } void add_code_bloc(vm_mngr_t* vm_mngr, struct code_bloc_node* cbp) { LIST_INSERT_HEAD(&vm_mngr->code_bloc_pool, cbp, next); if (vm_mngr->code_bloc_pool_ad_min> cbp->ad_start) vm_mngr->code_bloc_pool_ad_min = cbp->ad_start; if (vm_mngr->code_bloc_pool_ad_max< cbp->ad_stop) vm_mngr->code_bloc_pool_ad_max = cbp->ad_stop; } void dump_code_bloc_pool(vm_mngr_t* vm_mngr) { struct code_bloc_node * cbp; LIST_FOREACH(cbp, &vm_mngr->code_bloc_pool, next){ printf("ad start %"PRIX64" ad_stop %"PRIX64"\n", cbp->ad_start, cbp->ad_stop); } } void init_memory_page_pool(vm_mngr_t* vm_mngr) { vm_mngr->memory_pages_number = 0; vm_mngr->memory_pages_array = NULL; } void init_code_bloc_pool(vm_mngr_t* vm_mngr) { LIST_INIT(&vm_mngr->code_bloc_pool); vm_mngr->code_bloc_pool_ad_min = 0xffffffffffffffffULL; vm_mngr->code_bloc_pool_ad_max = 0; memory_access_list_init(&(vm_mngr->memory_r)); memory_access_list_init(&(vm_mngr->memory_w)); } void init_memory_breakpoint(vm_mngr_t* vm_mngr) { LIST_INIT(&vm_mngr->memory_breakpoint_pool); } void reset_memory_page_pool(vm_mngr_t* vm_mngr) { struct memory_page_node * mpn; int i; for (i=0;imemory_pages_number; i++) { mpn = &vm_mngr->memory_pages_array[i]; free(mpn->ad_hp); free(mpn->name); } free(vm_mngr->memory_pages_array); vm_mngr->memory_pages_array = NULL; vm_mngr->memory_pages_number = 0; } void reset_code_bloc_pool(vm_mngr_t* vm_mngr) { struct code_bloc_node * cbp; while (!LIST_EMPTY(&vm_mngr->code_bloc_pool)) { cbp = LIST_FIRST(&vm_mngr->code_bloc_pool); LIST_REMOVE(cbp, next); free(cbp); } vm_mngr->code_bloc_pool_ad_min = 0xffffffffffffffffULL; vm_mngr->code_bloc_pool_ad_max = 0; } void reset_memory_access(vm_mngr_t* vm_mngr) { memory_access_list_reset(&(vm_mngr->memory_r)); memory_access_list_reset(&(vm_mngr->memory_w)); } void reset_memory_breakpoint(vm_mngr_t* vm_mngr) { struct memory_breakpoint_info * mpn; while (!LIST_EMPTY(&vm_mngr->memory_breakpoint_pool)) { mpn = LIST_FIRST(&vm_mngr->memory_breakpoint_pool); LIST_REMOVE(mpn, next); free(mpn); } } /* We don't use dichotomy here for the insertion */ int is_mpn_in_tab(vm_mngr_t* vm_mngr, struct memory_page_node* mpn_a) { struct memory_page_node * mpn; int i; for (i=0;imemory_pages_number; i++) { mpn = &vm_mngr->memory_pages_array[i]; if (mpn->ad >= mpn_a->ad + mpn_a->size) continue; if (mpn->ad + mpn->size <= mpn_a->ad) continue; fprintf(stderr, "Error: attempt to add page (0x%"PRIX64" 0x%"PRIX64") " "overlapping page (0x%"PRIX64" 0x%"PRIX64")\n", mpn_a->ad, mpn_a->ad + mpn_a->size, mpn->ad, mpn->ad + mpn->size); return 1; } return 0; } /* We don't use dichotomy here for the insertion */ void add_memory_page(vm_mngr_t* vm_mngr, struct memory_page_node* mpn_a) { struct memory_page_node * mpn; int i; for (i=0; i < vm_mngr->memory_pages_number; i++) { mpn = &vm_mngr->memory_pages_array[i]; if (mpn->ad < mpn_a->ad) continue; break; } vm_mngr->memory_pages_array = realloc(vm_mngr->memory_pages_array, sizeof(struct memory_page_node) * (vm_mngr->memory_pages_number+1)); if (vm_mngr->memory_pages_array == NULL) { fprintf(stderr, "cannot realloc struct memory_page_node vm_mngr->memory_pages_array\n"); exit(EXIT_FAILURE); } memmove(&vm_mngr->memory_pages_array[i+1], &vm_mngr->memory_pages_array[i], sizeof(struct memory_page_node) * (vm_mngr->memory_pages_number - i) ); vm_mngr->memory_pages_array[i] = *mpn_a; vm_mngr->memory_pages_number ++; } /* Return a char* representing the repr of vm_mngr_t object */ char* dump(vm_mngr_t* vm_mngr) { char buf[0x100]; int length; char *buf_final; int i; char buf_addr[0x20]; char buf_size[0x20]; struct memory_page_node * mpn; /* 0x1234567812345678 0x1234567812345678 */ char* intro = "Addr Size Access Comment\n"; size_t total_len = strlen(intro) + 1; buf_final = malloc(total_len); if (buf_final == NULL) { fprintf(stderr, "Error: cannot alloc char* buf_final\n"); exit(EXIT_FAILURE); } strcpy(buf_final, intro); for (i=0; i< vm_mngr->memory_pages_number; i++) { mpn = &vm_mngr->memory_pages_array[i]; snprintf(buf_addr, sizeof(buf_addr), "0x%"PRIX64, (uint64_t)mpn->ad); snprintf(buf_size, sizeof(buf_size), "0x%"PRIX64, (uint64_t)mpn->size); length = snprintf(buf, sizeof(buf) - 1, "%-18s %-18s %c%c%c %s", buf_addr, buf_size, mpn->access & PAGE_READ? 'R':'_', mpn->access & PAGE_WRITE? 'W':'_', mpn->access & PAGE_EXEC? 'X':'_', mpn->name ); strcat(buf, "\n"); total_len += length + 1 + 1; buf_final = realloc(buf_final, total_len); if (buf_final == NULL) { fprintf(stderr, "cannot realloc char* buf_final\n"); exit(EXIT_FAILURE); } strcat(buf_final, buf); } return buf_final; } void dump_memory_breakpoint_pool(vm_mngr_t* vm_mngr) { struct memory_breakpoint_info * mpn; LIST_FOREACH(mpn, &vm_mngr->memory_breakpoint_pool, next){ printf("ad %"PRIX64" size %"PRIX64" access %"PRIX64"\n", mpn->ad, mpn->size, mpn->access ); } } void add_memory_breakpoint(vm_mngr_t* vm_mngr, uint64_t ad, uint64_t size, unsigned int access) { struct memory_breakpoint_info * mpn_a; mpn_a = malloc(sizeof(*mpn_a)); if (!mpn_a) { fprintf(stderr, "Error: cannot alloc\n"); exit(EXIT_FAILURE); } mpn_a->ad = ad; mpn_a->size = size; mpn_a->access = access; LIST_INSERT_HEAD(&vm_mngr->memory_breakpoint_pool, mpn_a, next); } void remove_memory_breakpoint(vm_mngr_t* vm_mngr, uint64_t ad, unsigned int access) { struct memory_breakpoint_info * mpn; LIST_FOREACH(mpn, &vm_mngr->memory_breakpoint_pool, next){ if (mpn->ad == ad && mpn->access == access) LIST_REMOVE(mpn, next); } } /********************************************/ void hexdump(char* m, unsigned int l) { unsigned int i, j, last; last = 0; for (i=0;iexception_flags; }