#define _GNU_SOURCE #include "dw-disassembly.h" #include "dw-log.h" #include "dw-protect.h" #include "dw-registers.h" #include <capstone/capstone.h> #include <sys/mman.h> #include <sys/user.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <string.h> // When an instruction accesses a protected object, we need to create an entry to // tell us the affected registers, a buffer to emulate the instruction, and an // epilogue to reprotect the registers if needed. struct insn_table { size_t size; struct insn_entry *entries; csh handle; cs_insn *insn; }; // Allocate the instruction hash table and initialize libcapstone instruction_table* dw_init_instruction_table(size_t size) { instruction_table *table = malloc(sizeof(instruction_table)); table->size = 2 * size - 1; // have a hash table about twice as large, and a power of two -1 table->entries = calloc(sizeof(struct insn_entry), table->size); cs_err csres = cs_open(CS_ARCH_X86, CS_MODE_64, &(table->handle)); if(csres != CS_ERR_OK) dw_log(ERROR, DISASSEMBLY, "cs_open failed, returned %d\n", csres); csres = cs_option(table->handle, CS_OPT_DETAIL, CS_OPT_ON); table->insn = cs_malloc(table->handle); return table; } // Deallocate the instruction hash table and close libcapstone void dw_fini_instruction_table(instruction_table *table) { free(table->entries); cs_free(table->insn, 1); cs_close(&(table->handle)); free(table); } // Get the entry for this instruction address struct insn_entry* dw_get_instruction_entry(instruction_table *table, uintptr_t fault) { size_t hash = fault % table->size; size_t cursor = hash; while((void *)table->entries[cursor].insn != NULL) { if(table->entries[cursor].insn == fault) return &(table->entries[cursor]); cursor = (cursor + 1) % table->size; if(cursor == hash) break; } return NULL; } // Is that register in the list or registers modified by the instruction? // There is a lot of aliasing between registers, e.g. al, ax, eax, rax, // all refer to the same register or portion thereof. static bool dw_reg_written(struct insn_entry *entry, unsigned reg) { if(reg == X86_REG_INVALID) return false; for(int i = 0; i < entry->gregs_write_count; i++) if(dw_get_reg_entry(entry->gregs_written[i])->ucontext_index == dw_get_reg_entry(reg)->ucontext_index) return true; return false; } static bool dw_reg_si_di(struct insn_entry *entry, unsigned reg) { if((reg == X86_REG_RSI || reg == X86_REG_RDI) && entry->repeat) return true; else return false; } // Create a new entry for this instruction address #define UNW_LOCAL_ONLY #include <libunwind.h> struct insn_entry* dw_create_instruction_entry(instruction_table *table, uintptr_t fault, uintptr_t *next, ucontext_t *uctx) { size_t hash = fault % table->size; size_t cursor = hash; while((void *)table->entries[cursor].insn != NULL) { if(table->entries[cursor].insn == fault) dw_log(ERROR, DISASSEMBLY, "Trying to add existing instruction in hash table\n"); cursor = (cursor + 1) % table->size; if(cursor == hash) dw_log(ERROR, DISASSEMBLY, "Instruction hash table full\n"); } // We insert the new entry at the first empty location following the hash code index table->entries[cursor].insn = fault; struct insn_entry *entry = &(table->entries[cursor]); size_t sizeds = 100; const uint8_t *code = (uint8_t *)fault; uint64_t instr_addr = (uint64_t) fault; unsigned reg, base, index; uintptr_t addr, scale, displacement, base_addr = 0, index_addr = 0; unsigned i, j; unsigned arg_m = 0, arg_r = 0; bool success; int error_code; struct reg_entry *re; // Disassemble the instruction with Capstone success = cs_disasm_iter(table->handle, &code , &sizeds, &instr_addr, table->insn); error_code = cs_errno(table->handle); if(!success) dw_log(ERROR, DISASSEMBLY, "Capstone cannot decode instruction 0x%llx, error %d\n", fault, error_code); // Get the symbol of the containing function, to help in debugging unw_cursor_t cur; unw_context_t context; unw_getcontext(&context); unw_init_local(&cur, &context); unw_word_t offset; unw_word_t pc = fault; unw_set_reg(&cur, UNW_REG_IP, pc); char proc_name[256]; if (unw_get_proc_name(&cur, proc_name, sizeof(proc_name), &offset) != 0) { strcpy(proc_name, "-- no symbol --"); offset = 0; } entry->insn_length = table->insn->size; entry->next_insn = *next = instr_addr; entry->post_handler = true; snprintf(entry->disasm_insn, sizeof(entry->disasm_insn), "%.11s %.51s", table->insn->mnemonic, table->insn->op_str); code = (uint8_t *)fault; char insn_code[256], *c = insn_code; int ret, length = 256; for(int i = 0; i < entry->insn_length; i++) { ret = snprintf(c, length, "%02x ", *code); c += ret; length -= ret; code++; } // This is info, not a warning, but we need it for debug purposes for now dw_log(WARNING, DISASSEMBLY, "Instruction 0x%llx (%s+0x%lx), entry %lu (%llx + %lx * %lx = %llx), %d, %d, 0x%lx: %s %s, (%hu) %s\n", fault, proc_name, offset, cursor, table->entries, cursor, sizeof(struct insn_entry), entry, success, error_code, table->insn->address, table->insn->mnemonic, table->insn->op_str, table->insn->size, insn_code); cs_detail *detail = table->insn->detail; cs_x86 *x86 = &(detail->x86); // On control transfer instructions, the post handler hooks do not work. // Thus, the tainted pointer will not be retainted after the execution. // This is not a problem if the register value is not reused. Otherwise, // a subsequent access may not be checked, or the program logic may be // compromised if the untainted pointer is compared with a tainted pointer. if(detail->groups_count > 0) { for(i = 0; i < detail->groups_count; i++) { if(detail->groups[i] < X86_GRP_VM) { dw_log(WARNING, DISASSEMBLY, "Trap on control transfer instruction, post handler cannot be used %llx\n", fault); entry->post_handler = false; break; } } } cs_regs regs_read, regs_write; uint8_t read_count = 0, write_count = 0; error_code = cs_regs_access(table->handle, table->insn, regs_read, &read_count, regs_write, &write_count); if(error_code != CS_ERR_OK) dw_log(ERROR, DISASSEMBLY, "Capstone cannot give register accesses\n"); if(read_count > MAX_MOD_REG) dw_log(ERROR, DISASSEMBLY, "More registers read %d than expected %d\n", read_count, MAX_MOD_REG); if(write_count > MAX_MOD_REG) dw_log(ERROR, DISASSEMBLY, "More registers written %d than expected %d\n", write_count, MAX_MOD_REG); entry->gregs_read_count = read_count; entry->gregs_write_count = write_count; if(x86->prefix[0] == X86_PREFIX_REP || x86->prefix[0] == X86_PREFIX_REPE || x86->prefix[0] == X86_PREFIX_REPNE) entry->repeat = true; else entry->repeat = false; for (i = 0; i < min(read_count, MAX_MOD_REG); i++) { reg = entry->gregs_read[i] = regs_read[i]; dw_log(INFO, DISASSEMBLY, "read: %s; (%d)\n", cs_reg_name(table->handle, reg), reg); } for (i = 0; i < min(write_count, MAX_MOD_REG); i++) { reg = entry->gregs_written[i] = regs_write[i]; dw_log(INFO, DISASSEMBLY, "write: %s; (%d)\n", cs_reg_name(table->handle, reg), reg); } // Loop over all the instruction arguments unsigned nb_protected = 0; for (i = 0; i < x86->op_count; i++){ switch(x86->operands[i].type) { // We need to know the overwritten registers to avoid retainting them case X86_OP_REG: reg = x86->operands[i].reg; re = dw_get_reg_entry(reg); if(re->ucontext_index >= 0) { if(arg_r >= MAX_REG_ARG) dw_log(ERROR, DISASSEMBLY, "Too many destination register arguments\n"); entry->arg_r[arg_r].reg = reg; entry->arg_r[arg_r].length = re->size; entry->arg_r[arg_r].access = x86->operands[i].access; arg_r++; } dw_log(INFO, DISASSEMBLY, "Register operand %lu, reg %s, access %hhu\n", i, cs_reg_name(table->handle, x86->operands[i].reg), x86->operands[i].access); break; // The memory address is given by base + (index * scale) + displacement case X86_OP_MEM: if(arg_m >= MAX_MEM_ARG) dw_log(ERROR, DISASSEMBLY, "Too many memory arguments\n"); // Check if we have a base register and if it is a general purpose register entry->arg_m[arg_m].base = base = x86->operands[i].mem.base; if(base == X86_REG_INVALID) base_addr = 0; // no base register else { re = dw_get_reg_entry(base); if(re->ucontext_index < 0) dw_log(ERROR, DISASSEMBLY, "Base register %s not general register\n", re->name); else base_addr = dw_get_register(uctx, re->ucontext_index); } // Check if we have an index register and if it is a general purpose register entry->arg_m[arg_m].index = index = x86->operands[i].mem.index; if(index == X86_REG_INVALID) index_addr = 0; else { re = dw_get_reg_entry(index); if(re->ucontext_index < 0) dw_log(ERROR, DISASSEMBLY, "Index register %s not general register\n", re->name); else index_addr = dw_get_register(uctx, re->ucontext_index); } entry->arg_m[arg_m].scale = scale = x86->operands[i].mem.scale; entry->arg_m[arg_m].displacement = displacement = x86->operands[i].mem.disp; entry->arg_m[arg_m].length = x86->operands[i].size; entry->arg_m[arg_m].access = x86->operands[i].access; addr = base_addr + (index_addr * scale) + displacement; entry->arg_m[arg_m].base_taint = entry->arg_m[arg_m].index_taint = 0; dw_log(INFO, DISASSEMBLY, "Memory operand %lu, segment %d, base %s (0x%llx) + (index %s (0x%llx) x scale 0x%llx) + disp 0x%llx = 0x%llx, access %hhu\n", i, x86->operands[i].mem.segment, cs_reg_name(table->handle, base), base_addr, cs_reg_name(table->handle, index), index_addr, scale, displacement, addr, x86->operands[i].access); // Check that the segmentation violation is related to a tainted pointer if(dw_is_protected((void *)base_addr)) { nb_protected++; entry->arg_m[arg_m].base_taint = base_addr; if(dw_is_protected_index((void *)index_addr)) dw_log(ERROR, DISASSEMBLY,"Both base and index registers are protected\n"); } else if(dw_is_protected_index((void *)index_addr)) { nb_protected++; entry->arg_m[arg_m].index_taint = index_addr; } arg_m++; break; case X86_OP_IMM: dw_log(INFO, DISASSEMBLY, "Immediate operand %lu, value %lu\n", i, x86->operands[i].imm); break; default: dw_log(INFO, DISASSEMBLY, "Invalid operand %lu\n", i); break; } } entry->nb_arg_m = arg_m; entry->nb_arg_r = arg_r; for(i = 0; i < arg_m; i++) { // We need to retaint the register unless it is overwritten by the instruction entry->arg_m[i].base_access = entry->arg_m[i].index_access = 0; // CS_AC_INVALID // Check if the base or index register, that may become tainted, is also a register argument for(j = 0; j < arg_r; j++) { if(dw_get_reg_entry(entry->arg_r[j].reg)->ucontext_index == dw_get_reg_entry(entry->arg_m[i].base)->ucontext_index) { entry->arg_m[i].base_access = entry->arg_r[j].access; if(entry->arg_m[arg_m].access == (CS_AC_READ | CS_AC_WRITE)) dw_log(WARNING, DISASSEMBLY, "Memory argument is unexpectedly read and written\n"); if(arg_r != 1) dw_log(WARNING, DISASSEMBLY, "More than one register argument, may be ambiguous\n"); } if(dw_get_reg_entry(entry->arg_r[j].reg)->ucontext_index == dw_get_reg_entry(entry->arg_m[i].index)->ucontext_index) { entry->arg_m[i].index_access = entry->arg_r[j].access; if(entry->arg_m[arg_m].access == (CS_AC_READ | CS_AC_WRITE)) dw_log(WARNING, DISASSEMBLY, "Memory argument is unexpectedly read and written\n"); if(arg_r != 1) dw_log(WARNING, DISASSEMBLY, "More than one register argument, may be ambiguous\n"); } } // Registers rsi and rdi are auto-incremented for some instructions with the rep prefix. // This is accounted for by reapplying the taint, not restoring the saved register value. // Here we check if there exists other cases apart from rsi and rdi with rep instructions. if(dw_reg_written(entry, entry->arg_m[i].base) && (entry->arg_m[i].base_access & CS_AC_WRITE) == 0 && !dw_reg_si_di(entry, entry->arg_m[i].base)) dw_log(WARNING, DISASSEMBLY, "Instruction 0x%llx, base register %s implicitly modified\n", entry->insn, dw_get_reg_entry(entry->arg_m[i].base)->name); if(dw_reg_written(entry, entry->arg_m[i].index) && (entry->arg_m[i].index_access & CS_AC_WRITE) == 0 && !dw_reg_si_di(entry, entry->arg_m[i].index)) dw_log(WARNING, DISASSEMBLY, "Instruction 0x%llx, index register %s implicitly modified\n", entry->insn, dw_get_reg_entry(entry->arg_m[i].index)->name); } if(nb_protected == 0) dw_log(ERROR, DISASSEMBLY,"No protected memory argument but generates a fault\n"); return entry; } static void check_patch(patch_status s, char *msg) { if(s == PATCH_OK) return; struct patch_error e; patch_last_error(&e); dw_log(WARNING, DISASSEMBLY, "Patch lib return value not OK, %d, for %s, origin %s, irritant %s, message %s\n", s, msg, e.origin, e.irritant, e.message); } void dw_patch_init() { const struct patch_option options[] = {{.type = PATCH_OPT_ENABLE_WXE, .enable_wxe = 0}}; (void)patch_init(options, sizeof(options) / sizeof(struct patch_option)); } // Patch the instruction accessing a protected object and attach a pre and post handler // to unprotect and reprotect the tainted registers bool dw_instruction_entry_patch(struct insn_entry *entry, enum dw_strategies strategy, dw_patch_probe patch_handler) { struct patch_location location = { .type = PATCH_LOCATION_ADDRESS, .direction = PATCH_LOCATION_FORWARD, .algorithm = PATCH_LOCATION_FIRST, .address = entry->insn, }; struct patch_exec_model exec_model = { .type = PATCH_EXEC_MODEL_PROBE_AROUND_STEP, .probe.read_registers = 0, .probe.write_registers = 0, .probe.clobber_registers = PATCH_REGS_ALL, .probe.user_data = entry, .probe.procedure = patch_handler, }; patch_t patch; patch_attr attr; patch_status s; // On control transfer instructions, post handlers are not available. if(!(entry->post_handler)) exec_model.type = PATCH_EXEC_MODEL_PROBE; s = patch_attr_init(&attr); check_patch(s, "attr init"); if(strategy == DW_PATCH_TRAP) { s= patch_attr_set_trap_policy(&attr, PATCH_TRAP_POLICY_FORCE); check_patch(s, "set policy FORCE"); } else if(strategy == DW_PATCH_JUMP) { s= patch_attr_set_trap_policy(&attr, PATCH_TRAP_POLICY_FORBID); check_patch(s, "set policy FORBID"); } else dw_log(ERROR, DISASSEMBLY, "Unknown patching strategy\n"); s = patch_attr_set_initial_state(&attr, PATCH_ENABLED); check_patch(s, "set enabled"); s = patch_make(&location, &exec_model, &attr, &patch, NULL); check_patch(s, "make"); if(s != PATCH_OK) return false; s = patch_commit(); check_patch(s, "commit"); if(s != PATCH_OK) return false; return true; } // With PATCH_EXEC_MODEL_AROUND_STEP_TRAP or PATCH_EXEC_MODEL_AROUND_STEP, the SIGSEGV handler will not // get called, the patch handler (pre and post) will be called instead. It will be called either through // the target instruction patched by a trap (int3), intercepted by libpatch with their own handler, // or through the target instruction patched by a jump. // // Eventually, to avoid the cost of saving a lot of registers and making a call, we may use // PATCH_EXEC_MODEL_DIVERT to jump to an OLX buffer that untaints, executes the relocated // instruction, and retaints in assembly directly. // // We save all the relevant registers in a static structure to check if some // registers are unexpectedly modified by the stepped instruction. This // structure should be Thread Local Storage for multi-threaded programs. // Once the algorithm is well tested and debugged, this saving and comparison // step will be removed. void dw_print_regs(struct patch_exec_context *ctx) { for(int i = 0; i < dw_nb_saved_registers; i++) dw_log(INFO, DISASSEMBLY, "%s, %llx\n", dw_get_reg_entry(dw_saved_registers[i])->name, ctx->general_purpose_registers[i]); } static bool dw_check_handling = false; void dw_set_check_handling(bool f) { dw_check_handling = f; } // A potentially tainted pointer is accessed, unprotect it before the access // (Check use of unprotect / retaint versus flavors other than OID, single unprotect / reprotect) void dw_unprotect_context(struct patch_exec_context *ctx) { struct insn_entry *entry = ctx->user_data; struct reg_entry *re, *reb, *rei; struct memory_arg *arg; unsigned i, reg, regb, regi; uintptr_t valueb, valuei, addr; if(dw_check_handling) { dw_log(INFO, DISASSEMBLY, "Unprotect instruction 0x%llx: %s\n", entry->insn, entry->disasm_insn); dw_print_regs(ctx); } // Untaint all possibly tainted memory arguments for(i = 0; i < entry->nb_arg_m; i++) { arg = &(entry->arg_m[i]); regb = arg->base; reb = dw_get_reg_entry(regb); if(regb == X86_REG_INVALID) valueb = 0; // no base register else valueb = dw_get_register(ctx, reb->libpatch_index); regi = arg->index; rei = dw_get_reg_entry(regi); if(regi == X86_REG_INVALID) valuei = 0; else valuei = dw_get_register(ctx, rei->libpatch_index); addr = valueb + valuei * arg->scale + arg->displacement; if(dw_is_protected((void *)valueb)) { if(arg->base_taint == 0) dw_log(INFO, DISASSEMBLY, "Newly tainted base for mem arg %d\n", i); if(dw_is_protected_index((void *)valuei)) dw_log(WARNING, DISASSEMBLY, "Both index and base tainted for mem arg %d\n", i); arg->base_taint = valueb; valueb = (uintptr_t)dw_unprotect((void *)valueb); dw_set_register(ctx, reb->libpatch_index, valueb); arg->index_taint = 0; // The base register is a pointer, if less than 8 bytes are read, this is suspicious. if((arg->base_access & CS_AC_READ) && (arg->length < 8)) dw_log(WARNING, DISASSEMBLY, "Instruction 0x%llx, base register %s only partially copied to/from memory\n", entry->insn, dw_get_reg_entry(regb)->name); } else if(dw_is_protected_index((void *)valuei)) { if(arg->index_taint == 0) dw_log(INFO, DISASSEMBLY, "Newly tainted index for mem arg %d\n", i); arg->index_taint = valuei; valuei = (uintptr_t)dw_unprotect((void *)valuei); dw_set_register(ctx, rei->libpatch_index, valuei); arg->base_taint = 0; // The index register is a pointer, if less than 8 bytes are read, this is suspicious. if((arg->index_access & CS_AC_READ) && (arg->length < 8)) dw_log(WARNING, DISASSEMBLY, "Instruction 0x%llx, index register %s only partially copied to/from memory\n", entry->insn, dw_get_reg_entry(regi)->name); } else { arg->base_taint = 0; arg->index_taint = 0; continue; } // With the computed tainted address, check if the access is valid. // We take the argument length, and use the repeat count if present. if(entry->repeat) { size_t count = dw_get_register(ctx, dw_get_reg_entry(X86_REG_RCX)->libpatch_index); dw_check_access((void *)addr, arg->length * count); } else dw_check_access((void *)addr, arg->length); // We have a special case, the same register is used to access the memory and as argument. // - For a memory read, register write, do not retaint the ovewritten register in post handler. // // - For a memory read, register read, it is presumably a comparison. We must untaint the register and // the memory for a proper comparison, and retaint both the register and memory in the post handler. // We save the memory address and value, and untaint the value. In the post handler // we retaint the register, and restore the saved value at the saved address // // - For a memory write, register read, the untainted register is stored in memory, we should retaint // both the register and memory in the post handler. // // - For a memory write, register write, not sure what to do. Not retaint the register but retaint memory? if((arg->base_access && arg->base_taint != 0) || (arg->index_access && arg->index_taint != 0)) { arg->saved_address = valueb + valuei * arg->scale + arg->displacement; addr = (uintptr_t)dw_unprotect((void *)addr); if(addr != arg->saved_address) dw_log(WARNING, DISASSEMBLY, "Both ways to unprotect address differ 0x%llx 0x%llx\n", addr, arg->saved_address); if((arg->access & CS_AC_READ) && (((arg->base_access & CS_AC_READ) && arg->base_taint) || ((arg->index_access & CS_AC_READ) && arg->index_taint))) { arg->saved_value = (uintptr_t)(*((void **)arg->saved_address)); *((void **)arg->saved_address) = dw_unprotect((void *)(arg->saved_value)); } } } if(dw_check_handling) { for(i = 0; i < dw_nb_saved_registers; i++) { reg = dw_saved_registers[i]; re = dw_get_reg_entry(reg); dw_save_regs[i] = dw_get_register(ctx, re->libpatch_index); dw_log(INFO, DISASSEMBLY, "%s, %llx\n", re->name, ctx->general_purpose_registers[i]); } } entry->hit_count++; } // In the post handler, we normally retaint all registers which were untainted in the pre handler. // There are a few special cases when the same register is used as a base or index to access the memory // and as argument. void dw_reprotect_context(struct patch_exec_context *ctx) { struct insn_entry *entry = ctx->user_data; struct memory_arg *arg; struct reg_entry *re; unsigned i, reg; uintptr_t value; if(dw_check_handling) { dw_log(INFO, DISASSEMBLY, "Reprotect instruction 0x%llx: %s\n", entry->insn, entry->disasm_insn); dw_print_regs(ctx); for(i = 0; i < dw_nb_saved_registers; i++) { // dw_log(INFO, MAIN, "%s = 0x%llx\n", dw_get_patch_reg_name(i), ctx->gregs[i]); reg = dw_saved_registers[i]; re = dw_get_reg_entry(reg); value = dw_get_register(ctx, re->libpatch_index); if((dw_save_regs[i] != value) && dw_reg_written(entry, reg) == false) dw_log(WARNING, MAIN, "Instruction 0x%llx, register %s modified but should not, now 0x%llx vs 0x%llx\n", entry->insn, re->name, value, dw_save_regs[i]); } } for(int i = 0; i < entry->nb_arg_m; i++) { arg = &(entry->arg_m[i]); // The tainted register, base or index, is retainted unless the same register // was also an overwritten register argument. if(arg->base_taint && ((arg->base_access & CS_AC_WRITE) == 0)) { reg = arg->base; re = dw_get_reg_entry(reg); value = dw_get_register(ctx, re->libpatch_index); dw_set_register(ctx, re->libpatch_index, (uint64_t)dw_retaint((void *)value, (void *)arg->base_taint)); } if(arg->index_taint && ((arg->index_access & CS_AC_WRITE) == 0)) { reg = arg->index; re = dw_get_reg_entry(reg); value = dw_get_register(ctx, re->libpatch_index); dw_set_register(ctx, re->libpatch_index, (uint64_t)dw_retaint((void *)value, (void *)arg->index_taint)); } // If the tainted register, base or index, was also a register argument, // we have special cases to consider. if((arg->base_access && arg->base_taint) || (arg->index_access && arg->index_taint)) { // If the memory was read and the tainted register, base or index, was a read register argument, // we presumably have a comparison. The memory value was untainted in the pre handler and should // be restored here. if(arg->access & CS_AC_READ) { if(((arg->base_access & CS_AC_READ) && arg->base_taint) || ((arg->index_access & CS_AC_READ) && arg->index_taint)) *((void **)arg->saved_address) = (void *)(arg->saved_value); } // If the memory was written and the tainted register, base or index, was also a register argument // we suppose that the untainted register was stored in memory and we need to retaint the memory // with the saved register taint else if(arg->access & CS_AC_WRITE) { uintptr_t saved_taint; if(arg->base_taint) saved_taint = arg->base_taint; else saved_taint = arg->index_taint; *((void **)arg->saved_address) = dw_retaint(*((void **)arg->saved_address), (void *)saved_taint); } } } } // Dump the content of the instruction table, for knowing the // number of instructions accessing protected objects, // and the number of hits for each instruction. // Print statistics about each instruction patched void dw_print_instruction_entries(instruction_table *table, int fd) { struct insn_entry *entry; unsigned count = 0; for(int i = 0; i < table->size; i++) { entry = &(table->entries[i]); if((void *)entry->insn != NULL) { dw_fprintf(fd, "%4d 0x%lx: %9u: %2u: %1u %s;\n", count, entry->insn, entry->hit_count, entry->insn_length, entry->strategy, entry->disasm_insn); count++; } } }