static int fbt_provide_module_function(linker_file_t lf, int symindx, linker_symval_t *symval, void *opaque) { char *modname = opaque; const char *name = symval->name; fbt_probe_t *fbt, *retfbt; int j; int size; u_int8_t *instr, *limit; if (strncmp(name, "dtrace_", 7) == 0 && strncmp(name, "dtrace_safe_", 12) != 0) { /* * Anything beginning with "dtrace_" may be called * from probe context unless it explicitly indicates * that it won't be called from probe context by * using the prefix "dtrace_safe_". */ return (0); } if (name[0] == '_' && name[1] == '_') return (0); size = symval->size; instr = (u_int8_t *) symval->value; limit = (u_int8_t *) symval->value + symval->size; #ifdef __amd64__ while (instr < limit) { if (*instr == FBT_PUSHL_EBP) break; if ((size = dtrace_instr_size(instr)) <= 0) break; instr += size; } if (instr >= limit || *instr != FBT_PUSHL_EBP) { /* * We either don't save the frame pointer in this * function, or we ran into some disassembly * screw-up. Either way, we bail. */ return (0); } #else if (instr[0] != FBT_PUSHL_EBP) return (0); if (!(instr[1] == FBT_MOVL_ESP_EBP0_V0 && instr[2] == FBT_MOVL_ESP_EBP1_V0) && !(instr[1] == FBT_MOVL_ESP_EBP0_V1 && instr[2] == FBT_MOVL_ESP_EBP1_V1)) return (0); #endif fbt = malloc(sizeof (fbt_probe_t), M_FBT, M_WAITOK | M_ZERO); fbt->fbtp_name = name; fbt->fbtp_id = dtrace_probe_create(fbt_id, modname, name, FBT_ENTRY, 3, fbt); fbt->fbtp_patchpoint = instr; fbt->fbtp_ctl = lf; fbt->fbtp_loadcnt = lf->loadcnt; fbt->fbtp_rval = DTRACE_INVOP_PUSHL_EBP; fbt->fbtp_savedval = *instr; fbt->fbtp_patchval = FBT_PATCHVAL; fbt->fbtp_symindx = symindx; fbt->fbtp_hashnext = fbt_probetab[FBT_ADDR2NDX(instr)]; fbt_probetab[FBT_ADDR2NDX(instr)] = fbt; lf->fbt_nentries++; retfbt = NULL; again: if (instr >= limit) return (0); /* * If this disassembly fails, then we've likely walked off into * a jump table or some other unsuitable area. Bail out of the * disassembly now. */ if ((size = dtrace_instr_size(instr)) <= 0) return (0); #ifdef __amd64__ /* * We only instrument "ret" on amd64 -- we don't yet instrument * ret imm16, largely because the compiler doesn't seem to * (yet) emit them in the kernel... */ if (*instr != FBT_RET) { instr += size; goto again; } #else if (!(size == 1 && (*instr == FBT_POPL_EBP || *instr == FBT_LEAVE) && (*(instr + 1) == FBT_RET || *(instr + 1) == FBT_RET_IMM16))) { instr += size; goto again; } #endif /* * We (desperately) want to avoid erroneously instrumenting a * jump table, especially given that our markers are pretty * short: two bytes on x86, and just one byte on amd64. To * determine if we're looking at a true instruction sequence * or an inline jump table that happens to contain the same * byte sequences, we resort to some heuristic sleeze: we * treat this instruction as being contained within a pointer, * and see if that pointer points to within the body of the * function. If it does, we refuse to instrument it. */ for (j = 0; j < sizeof (uintptr_t); j++) { caddr_t check = (caddr_t) instr - j; uint8_t *ptr; if (check < symval->value) break; if (check + sizeof (caddr_t) > (caddr_t)limit) continue; ptr = *(uint8_t **)check; if (ptr >= (uint8_t *) symval->value && ptr < limit) { instr += size; goto again; } } /* * We have a winner! */ fbt = malloc(sizeof (fbt_probe_t), M_FBT, M_WAITOK | M_ZERO); fbt->fbtp_name = name; if (retfbt == NULL) { fbt->fbtp_id = dtrace_probe_create(fbt_id, modname, name, FBT_RETURN, 3, fbt); } else { retfbt->fbtp_next = fbt; fbt->fbtp_id = retfbt->fbtp_id; } retfbt = fbt; fbt->fbtp_patchpoint = instr; fbt->fbtp_ctl = lf; fbt->fbtp_loadcnt = lf->loadcnt; fbt->fbtp_symindx = symindx; #ifndef __amd64__ if (*instr == FBT_POPL_EBP) { fbt->fbtp_rval = DTRACE_INVOP_POPL_EBP; } else { ASSERT(*instr == FBT_LEAVE); fbt->fbtp_rval = DTRACE_INVOP_LEAVE; } fbt->fbtp_roffset = (uintptr_t)(instr - (uint8_t *) symval->value) + 1; #else ASSERT(*instr == FBT_RET); fbt->fbtp_rval = DTRACE_INVOP_RET; fbt->fbtp_roffset = (uintptr_t)(instr - (uint8_t *) symval->value); #endif fbt->fbtp_savedval = *instr; fbt->fbtp_patchval = FBT_PATCHVAL; fbt->fbtp_hashnext = fbt_probetab[FBT_ADDR2NDX(instr)]; fbt_probetab[FBT_ADDR2NDX(instr)] = fbt; lf->fbt_nentries++; instr += size; goto again; }
/*ARGSUSED*/ static void fbt_provide_module(void *arg, struct modctl *ctl) { struct module *mp = ctl->mod_mp; char *str = mp->strings; int nsyms = mp->nsyms; Shdr *symhdr = mp->symhdr; char *modname = ctl->mod_modname; char *name; fbt_probe_t *fbt, *retfbt; size_t symsize; int i, size; /* * Employees of dtrace and their families are ineligible. Void * where prohibited. */ if (strcmp(modname, "dtrace") == 0) return; if (ctl->mod_requisites != NULL) { struct modctl_list *list; list = (struct modctl_list *)ctl->mod_requisites; for (; list != NULL; list = list->modl_next) { if (strcmp(list->modl_modp->mod_modname, "dtrace") == 0) return; } } /* * KMDB is ineligible for instrumentation -- it may execute in * any context, including probe context. */ if (strcmp(modname, "kmdbmod") == 0) return; if (str == NULL || symhdr == NULL || symhdr->sh_addr == NULL) { /* * If this module doesn't (yet) have its string or symbol * table allocated, clear out. */ return; } symsize = symhdr->sh_entsize; if (mp->fbt_nentries) { /* * This module has some FBT entries allocated; we're afraid * to screw with it. */ return; } for (i = 1; i < nsyms; i++) { uint8_t *instr, *limit; Sym *sym = (Sym *)(symhdr->sh_addr + i * symsize); int j; if (ELF_ST_TYPE(sym->st_info) != STT_FUNC) continue; /* * Weak symbols are not candidates. This could be made to * work (where weak functions and their underlying function * appear as two disjoint probes), but it's not simple. */ if (ELF_ST_BIND(sym->st_info) == STB_WEAK) continue; name = str + sym->st_name; if (strstr(name, "dtrace_") == name && strstr(name, "dtrace_safe_") != name) { /* * Anything beginning with "dtrace_" may be called * from probe context unless it explitly indicates * that it won't be called from probe context by * using the prefix "dtrace_safe_". */ continue; } if (strstr(name, "kdi_") == name || strstr(name, "_kdi_") != NULL) { /* * Any function name beginning with "kdi_" or * containing the string "_kdi_" is a part of the * kernel debugger interface and may be called in * arbitrary context -- including probe context. */ continue; } /* * Due to 4524008, _init and _fini may have a bloated st_size. * While this bug was fixed quite some time ago, old drivers * may be lurking. We need to develop a better solution to * this problem, such that correct _init and _fini functions * (the vast majority) may be correctly traced. One solution * may be to scan through the entire symbol table to see if * any symbol overlaps with _init. If none does, set a bit in * the module structure that this module has correct _init and * _fini sizes. This will cause some pain the first time a * module is scanned, but at least it would be O(N) instead of * O(N log N)... */ if (strcmp(name, "_init") == 0) continue; if (strcmp(name, "_fini") == 0) continue; /* * In order to be eligible, the function must begin with the * following sequence: * * pushl %esp * movl %esp, %ebp * * Note that there are two variants of encodings that generate * the movl; we must check for both. For 64-bit, we would * normally insist that a function begin with the following * sequence: * * pushq %rbp * movq %rsp, %rbp * * However, the compiler for 64-bit often splits these two * instructions -- and the first instruction in the function * is often not the pushq. As a result, on 64-bit we look * for any "pushq %rbp" in the function and we instrument * this with a breakpoint instruction. */ instr = (uint8_t *)sym->st_value; limit = (uint8_t *)(sym->st_value + sym->st_size); #ifdef __amd64 while (instr < limit) { if (*instr == FBT_PUSHL_EBP) break; if ((size = dtrace_instr_size(instr)) <= 0) break; instr += size; } if (instr >= limit || *instr != FBT_PUSHL_EBP) { /* * We either don't save the frame pointer in this * function, or we ran into some disassembly * screw-up. Either way, we bail. */ continue; } #else if (instr[0] != FBT_PUSHL_EBP) continue; if (!(instr[1] == FBT_MOVL_ESP_EBP0_V0 && instr[2] == FBT_MOVL_ESP_EBP1_V0) && !(instr[1] == FBT_MOVL_ESP_EBP0_V1 && instr[2] == FBT_MOVL_ESP_EBP1_V1)) continue; #endif fbt = kmem_zalloc(sizeof (fbt_probe_t), KM_SLEEP); fbt->fbtp_name = name; fbt->fbtp_id = dtrace_probe_create(fbt_id, modname, name, FBT_ENTRY, 3, fbt); fbt->fbtp_patchpoint = instr; fbt->fbtp_ctl = ctl; fbt->fbtp_loadcnt = ctl->mod_loadcnt; fbt->fbtp_rval = DTRACE_INVOP_PUSHL_EBP; fbt->fbtp_savedval = *instr; fbt->fbtp_patchval = FBT_PATCHVAL; fbt->fbtp_hashnext = fbt_probetab[FBT_ADDR2NDX(instr)]; fbt->fbtp_symndx = i; fbt_probetab[FBT_ADDR2NDX(instr)] = fbt; mp->fbt_nentries++; retfbt = NULL; again: if (instr >= limit) continue; /* * If this disassembly fails, then we've likely walked off into * a jump table or some other unsuitable area. Bail out of the * disassembly now. */ if ((size = dtrace_instr_size(instr)) <= 0) continue; #ifdef __amd64 /* * We only instrument "ret" on amd64 -- we don't yet instrument * ret imm16, largely because the compiler doesn't seem to * (yet) emit them in the kernel... */ if (*instr != FBT_RET) { instr += size; goto again; } #else if (!(size == 1 && (*instr == FBT_POPL_EBP || *instr == FBT_LEAVE) && (*(instr + 1) == FBT_RET || *(instr + 1) == FBT_RET_IMM16))) { instr += size; goto again; } #endif /* * We (desperately) want to avoid erroneously instrumenting a * jump table, especially given that our markers are pretty * short: two bytes on x86, and just one byte on amd64. To * determine if we're looking at a true instruction sequence * or an inline jump table that happens to contain the same * byte sequences, we resort to some heuristic sleeze: we * treat this instruction as being contained within a pointer, * and see if that pointer points to within the body of the * function. If it does, we refuse to instrument it. */ for (j = 0; j < sizeof (uintptr_t); j++) { uintptr_t check = (uintptr_t)instr - j; uint8_t *ptr; if (check < sym->st_value) break; if (check + sizeof (uintptr_t) > (uintptr_t)limit) continue; ptr = *(uint8_t **)check; if (ptr >= (uint8_t *)sym->st_value && ptr < limit) { instr += size; goto again; } } /* * We have a winner! */ fbt = kmem_zalloc(sizeof (fbt_probe_t), KM_SLEEP); fbt->fbtp_name = name; if (retfbt == NULL) { fbt->fbtp_id = dtrace_probe_create(fbt_id, modname, name, FBT_RETURN, 3, fbt); } else { retfbt->fbtp_next = fbt; fbt->fbtp_id = retfbt->fbtp_id; } retfbt = fbt; fbt->fbtp_patchpoint = instr; fbt->fbtp_ctl = ctl; fbt->fbtp_loadcnt = ctl->mod_loadcnt; #ifndef __amd64 if (*instr == FBT_POPL_EBP) { fbt->fbtp_rval = DTRACE_INVOP_POPL_EBP; } else { ASSERT(*instr == FBT_LEAVE); fbt->fbtp_rval = DTRACE_INVOP_LEAVE; } fbt->fbtp_roffset = (uintptr_t)(instr - (uint8_t *)sym->st_value) + 1; #else ASSERT(*instr == FBT_RET); fbt->fbtp_rval = DTRACE_INVOP_RET; fbt->fbtp_roffset = (uintptr_t)(instr - (uint8_t *)sym->st_value); #endif fbt->fbtp_savedval = *instr; fbt->fbtp_patchval = FBT_PATCHVAL; fbt->fbtp_hashnext = fbt_probetab[FBT_ADDR2NDX(instr)]; fbt->fbtp_symndx = i; fbt_probetab[FBT_ADDR2NDX(instr)] = fbt; mp->fbt_nentries++; instr += size; goto again; } }
int fasttrap_pid_probe(arm_saved_state_t *regs) { proc_t *p = current_proc(); user_addr_t new_pc = 0; fasttrap_bucket_t *bucket; lck_mtx_t *pid_mtx; fasttrap_tracepoint_t *tp, tp_local; pid_t pid; dtrace_icookie_t cookie; uint_t is_enabled = 0; int instr_size; int was_simulated = 1, retire_tp = 1; user_addr_t pc = regs->pc; uthread_t uthread = (uthread_t) get_bsdthread_info(current_thread()); /* * It's possible that a user (in a veritable orgy of bad planning) * could redirect this thread's flow of control before it reached the * return probe fasttrap. In this case we need to kill the process * since it's in a unrecoverable state. */ if (uthread->t_dtrace_step) { ASSERT(uthread->t_dtrace_on); fasttrap_sigtrap(p, uthread, pc); return (0); } /* * Clear all user tracing flags. */ uthread->t_dtrace_ft = 0; uthread->t_dtrace_pc = 0; uthread->t_dtrace_npc = 0; uthread->t_dtrace_scrpc = 0; uthread->t_dtrace_astpc = 0; /* * Treat a child created by a call to vfork(2) as if it were its * parent. We know that there's only one thread of control in such a * process: this one. */ if (p->p_lflag & P_LINVFORK) { proc_list_lock(); while (p->p_lflag & P_LINVFORK) p = p->p_pptr; proc_list_unlock(); } pid = p->p_pid; pid_mtx = &cpu_core[CPU->cpu_id].cpuc_pid_lock; lck_mtx_lock(pid_mtx); bucket = &fasttrap_tpoints.fth_table[FASTTRAP_TPOINTS_INDEX(pid,pc)]; /* * Lookup the tracepoint that the process just hit. */ for (tp = bucket->ftb_data; tp != NULL; tp = tp->ftt_next) { if (pid == tp->ftt_pid && pc == tp->ftt_pc && tp->ftt_proc->ftpc_acount != 0) break; } /* * If we couldn't find a matching tracepoint, either a tracepoint has * been inserted without using the pid<pid> ioctl interface (see * fasttrap_ioctl), or somehow we have mislaid this tracepoint. */ if (tp == NULL) { lck_mtx_unlock(pid_mtx); return (-1); } /* Default to always execute */ int condition_code = 0xE; if (tp->ftt_thumb) { uint32_t itstate = GETITSTATE(regs->cpsr); if (itstate != 0) { /* In IT block, make sure it's the last statement in the block */ if (ISLASTINIT(itstate)) { condition_code = itstate >> 4; } else { printf("dtrace: fasttrap: Tried to trace instruction %08x at %08x but not at end of IT block\n", (tp->ftt_thumb && dtrace_instr_size(tp->ftt_instr,tp->ftt_thumb) == 2) ? tp->ftt_instr1 : tp->ftt_instr, pc); fasttrap_tracepoint_remove(p, tp); lck_mtx_unlock(pid_mtx); return (-1); } }
int fasttrap_tracepoint_init(proc_t *p, fasttrap_tracepoint_t *tp, user_addr_t pc, fasttrap_probe_type_t type) { #pragma unused(type) uint32_t instr; /* * Read the instruction at the given address out of the process's * address space. We don't have to worry about a debugger * changing this instruction before we overwrite it with our trap * instruction since P_PR_LOCK is set. Since instructions can span * pages, we potentially read the instruction in two parts. If the * second part fails, we just zero out that part of the instruction. */ /* * APPLE NOTE: Of course, we do not have a P_PR_LOCK, so this is racey... */ if (uread(p, &instr, 4, pc) != 0) return (-1); /* We want &instr to always point to the saved instruction, so just copy the * whole thing When cast to a pointer to a uint16_t, that will give us a * pointer to the first two bytes, which is the thumb instruction. */ tp->ftt_instr = instr; if (tp->ftt_fntype != FASTTRAP_FN_DONE_INIT) { switch(tp->ftt_fntype) { case FASTTRAP_FN_UNKNOWN: /* Can't instrument without any information. We can add some heuristics later if necessary. */ return (-1); case FASTTRAP_FN_USDT: if (IS_ARM_NOP(instr) || IS_ARM_IS_ENABLED(instr)) { tp->ftt_thumb = 0; } else if (IS_THUMB_NOP(THUMB_INSTR(instr)) || IS_THUMB_IS_ENABLED(THUMB_INSTR(instr))) { tp->ftt_thumb = 1; } else { /* Shouldn't reach here - this means we don't recognize * the instruction at one of the USDT probe locations */ return (-1); } tp->ftt_fntype = FASTTRAP_FN_DONE_INIT; break; case FASTTRAP_FN_ARM: tp->ftt_thumb = 0; tp->ftt_fntype = FASTTRAP_FN_DONE_INIT; break; case FASTTRAP_FN_THUMB: tp->ftt_thumb = 1; tp->ftt_fntype = FASTTRAP_FN_DONE_INIT; break; default: return (-1); } } if (tp->ftt_thumb) { tp->ftt_type = dtrace_decode_thumb(instr); } else { tp->ftt_type = dtrace_decode_arm(instr); } if (tp->ftt_type == FASTTRAP_T_INV) { /* This is an instruction we either don't recognize or can't instrument */ printf("dtrace: fasttrap: Unrecognized instruction: %08x at %08x\n", (tp->ftt_thumb && dtrace_instr_size(tp->ftt_instr,tp->ftt_thumb) == 2) ? tp->ftt_instr1 : instr, pc); return (-1); } return (0); }
static int fbt_seq_show(struct seq_file *seq, void *v) { int i, s; int n = (int) (long) v; int target; fbt_probe_t *fbt = NULL; unsigned long size; unsigned long offset; char *modname = NULL; char name[KSYM_NAME_LEN]; char ibuf[64]; char *cp; //printk("%s v=%p\n", __func__, v); if (n == 1) { seq_printf(seq, "# count patchpoint opcode inslen modrm name\n"); return 0; } if (n > num_probes) return 0; /***********************************************/ /* Find first probe. This is incredibly */ /* slow and inefficient, but we dont want */ /* to waste memory keeping a list (an extra */ /* ptr for each probe). */ /***********************************************/ target = n; for (i = 0; target > 0 && i < fbt_probetab_size; i++) { fbt = fbt_probetab[i]; if (fbt == NULL) continue; target--; while (target > 0 && fbt) { if ((fbt = fbt->fbtp_hashnext) == NULL) break; target--; } } if (fbt == NULL) return 0; /***********************************************/ /* Dump the instruction so we can make sure */ /* we can single step them in the adjust */ /* cpu code. */ /* When a module is loaded and we parse the */ /* module, this may include syms which may */ /* get unmapped later on. So, trying to */ /* access a patchpoint instruction can GPF. */ /* We need to tread very carefully here not */ /* to GPF whilst catting /proc/dtrace/fbt. */ /* We actually need to disable these */ /* probes. */ /***********************************************/ //printk("fbtproc %p\n", fbt->fbtp_patchpoint); if (!validate_ptr(fbt->fbtp_patchpoint)) { s = 0xfff; strcpy(ibuf, "<unmapped>"); } else { s = dtrace_instr_size((uchar_t *) fbt->fbtp_patchpoint); ibuf[0] = '\0'; for (cp = ibuf, i = 0; i < s; i++) { snprintf(cp, sizeof ibuf - (cp - ibuf) - 3, "%02x ", fbt->fbtp_patchpoint[i] & 0xff); cp += 3; } } cp = (char *) my_kallsyms_lookup((unsigned long) fbt->fbtp_patchpoint, &size, &offset, &modname, name); # if defined(__arm__) seq_printf(seq, "%d %04u%c %p %08x %d %2d %s:%s:%s %s\n", n-1, # else seq_printf(seq, "%d %04u%c %p %02x %d %2d %s:%s:%s %s\n", n-1, # endif fbt->fbtp_fired, fbt->fbtp_overrun ? '*' : ' ', fbt->fbtp_patchpoint, fbt->fbtp_savedval, fbt->fbtp_inslen, fbt->fbtp_modrm, modname ? modname : "kernel", cp ? cp : "NULL", fbt->fbtp_type ? "return" : "entry", ibuf); return 0; }
static void fbt_provide_function(struct modctl *mp, par_module_t *pmp, char *modname, char *name, uint8_t *st_value, uint8_t *instr, uint8_t *limit, int symndx) { int do_print = FALSE; int invop = 0; fbt_probe_t *fbt, *retfbt; int size; int modrm; /***********************************************/ /* Dont let us register anything on the */ /* notifier list(s) we are on, else we will */ /* have recursion trouble. */ /***********************************************/ if (on_notifier_list(instr)) { printk("fbt_provide_function: Skip %s:%s - on notifier_chain\n", modname, name); return; } # define UNHANDLED_FBT() if (do_print || dtrace_unhandled) { \ printk("fbt:unhandled instr %s:%p %02x %02x %02x %02x\n", \ name, instr, instr[0], instr[1], instr[2], instr[3]); \ } /***********************************************/ /* Make sure we dont try and handle data or */ /* bad instructions. */ /***********************************************/ if ((size = dtrace_instr_size_modrm(instr, &modrm)) <= 0) return; /***********************************************/ /* Allow us to work on a single function */ /* for debugging/tracing else we generate */ /* too much printk() output and swamp the */ /* log daemon. */ /***********************************************/ // do_print = strncmp(name, "update_process", 9) == NULL; #ifdef __amd64 // am not happy with 0xe8 CALLR instructions, so disable them for now. if (*instr == 0xe8) return; invop = DTRACE_INVOP_ANY; switch (instr[0] & 0xf0) { case 0x00: case 0x10: case 0x20: case 0x30: break; case 0x40: if (instr[0] == 0x48 && instr[1] == 0xcf) return; break; case 0x50: case 0x60: case 0x70: case 0x80: case 0x90: case 0xa0: case 0xb0: break; case 0xc0: /***********************************************/ /* We cannot single step an IRET for now, */ /* so just ditch them as potential probes. */ /***********************************************/ if (instr[0] == 0xcf) return; break; case 0xd0: case 0xe0: break; case 0xf0: /***********************************************/ /* This doesnt work - if we single step a */ /* HLT, well, the kernel doesnt really like */ /* it. */ /***********************************************/ if (instr[0] == 0xf4) return; // printk("fbt:F instr %s:%p size=%d %02x %02x %02x %02x %02x %02x\n", name, instr, size, instr[0], instr[1], instr[2], instr[3], instr[4], instr[5]); break; } #else /***********************************************/ /* GCC generates lots of different */ /* assembler functions plus we have inline */ /* assembler to deal with. We break the */ /* opcodes down into groups, which helped */ /* during debugging, or if we need to */ /* single out specific opcodes, but for */ /* now, we can pretty much allow all */ /* opcodes. (Apart from HLT - which I may */ /* get around to fixing). */ /***********************************************/ invop = DTRACE_INVOP_ANY; switch (instr[0] & 0xf0) { case 0x00: case 0x10: case 0x20: case 0x30: case 0x40: case 0x50: case 0x60: case 0x70: case 0x80: case 0x90: case 0xa0: case 0xb0: break; case 0xc0: /***********************************************/ /* We cannot single step an IRET for now, */ /* so just ditch them as potential probes. */ /***********************************************/ if (instr[0] == 0xcf) return; break; case 0xd0: case 0xe0: break; case 0xf0: /***********************************************/ /* This doesnt work - if we single step a */ /* HLT, well, the kernel doesnt really like */ /* it. */ /***********************************************/ if (instr[0] == 0xf4) return; // printk("fbt:F instr %s:%p size=%d %02x %02x %02x %02x %02x %02x\n", name, instr, size, instr[0], instr[1], instr[2], instr[3], instr[4], instr[5]); break; } #endif /***********************************************/ /* Handle fact we may not be ready to cope */ /* with this instruction yet. */ /***********************************************/ if (invop == 0) { UNHANDLED_FBT(); return; } /***********************************************/ /* Make sure this doesnt overlap another */ /* sym. We are in trouble when this happens */ /* - eg we will mistaken what the emulation */ /* is for, but also, it means something */ /* strange happens, like kernel is reusing */ /* a page (eg for init/exit section of a */ /* module). */ /***********************************************/ if (fbt_is_patched(name, instr)) return; /*if (modrm >= 0 && (instr[modrm] & 0xc7) == 0x05) { static char buf[128]; sprintf(buf, "MODRM_%s", name); name = buf; } */ /***********************************************/ /* Special code here for debugging - we can */ /* make the name of a probe a function of */ /* the instruction, so we can validate and */ /* binary search to find faulty instruction */ /* stepping code in cpu_x86.c */ /***********************************************/ if (fbt_name_opcodes) { static char buf[128]; if (fbt_name_opcodes == 2) snprintf(buf, sizeof buf, "x%02x%02x_%s", *instr, instr[1], name); else snprintf(buf, sizeof buf, "x%02x_%s", *instr, name); name = buf; } fbt = kmem_zalloc(sizeof (fbt_probe_t), KM_SLEEP); fbt->fbtp_name = name; fbt->fbtp_id = dtrace_probe_create(fbt_id, modname, name, FBT_ENTRY, 3, fbt); num_probes++; fbt->fbtp_patchpoint = instr; fbt->fbtp_ctl = mp; // ctl; fbt->fbtp_loadcnt = get_refcount(mp); fbt->fbtp_rval = invop; /***********************************************/ /* Save potential overwrite of instruction */ /* and length, because we will need the */ /* entire instruction when we single step */ /* over it. */ /***********************************************/ fbt->fbtp_savedval = *instr; fbt->fbtp_inslen = size; //if (modrm >= 0 && (instr[modrm] & 0xc7) == 0x05) printk("modrm %s %p rm=%d\n", name, instr, modrm); fbt->fbtp_modrm = modrm; fbt->fbtp_patchval = FBT_PATCHVAL; fbt->fbtp_hashnext = fbt_probetab[FBT_ADDR2NDX(instr)]; fbt->fbtp_symndx = symndx; fbt_probetab[FBT_ADDR2NDX(instr)] = fbt; if (do_print) printk("%d:alloc entry-patchpoint: %s %p invop=%x sz=%d %02x %02x %02x\n", __LINE__, name, fbt->fbtp_patchpoint, fbt->fbtp_rval, fbt->fbtp_inslen, instr[0], instr[1], instr[2]); pmp->fbt_nentries++; retfbt = NULL; /***********************************************/ /* This point is part of a loop */ /* (implemented with goto) to find the end */ /* part of a function. Original Solaris */ /* code assumes a single exit, via RET, for */ /* amd64, but is more accepting for i386. */ /* However, with GCC as a compiler a lot */ /* more things can happen - very */ /* specifically, we can have multiple exit */ /* points in a function. So we need to find */ /* each of those. */ /***********************************************/ again: if (instr >= limit) { return; } /* * If this disassembly fails, then we've likely walked off into * a jump table or some other unsuitable area. Bail out of the * disassembly now. */ if ((size = dtrace_instr_size(instr)) <= 0) return; //HERE(); #ifdef __amd64 /* * We only instrument "ret" on amd64 -- we don't yet instrument * ret imm16, largely because the compiler doesn't seem to * (yet) emit them in the kernel... */ switch (*instr) { case FBT_RET: invop = DTRACE_INVOP_RET; break; default: instr += size; goto again; } #else switch (*instr) { /* case FBT_POPL_EBP: invop = DTRACE_INVOP_POPL_EBP; break; case FBT_LEAVE: invop = DTRACE_INVOP_LEAVE; break; */ case FBT_RET: invop = DTRACE_INVOP_RET; break; case FBT_RET_IMM16: invop = DTRACE_INVOP_RET_IMM16; break; /***********************************************/ /* Some functions can end in a jump, e.g. */ /* security_file_permission has an indirect */ /* jmp. Dont think we care too much about */ /* this, but we could handle direct and */ /* indirect jumps out of the function body. */ /***********************************************/ default: instr += size; goto again; } #endif /***********************************************/ /* Sanity check for bad things happening. */ /***********************************************/ if (fbt_is_patched(name, instr)) { instr += size; goto again; } /* * We have a winner! */ fbt = kmem_zalloc(sizeof (fbt_probe_t), KM_SLEEP); fbt->fbtp_name = name; if (retfbt == NULL) { fbt->fbtp_id = dtrace_probe_create(fbt_id, modname, name, FBT_RETURN, 3, fbt); num_probes++; } else { retfbt->fbtp_next = fbt; fbt->fbtp_id = retfbt->fbtp_id; } retfbt = fbt; fbt->fbtp_patchpoint = instr; fbt->fbtp_ctl = mp; //ctl; fbt->fbtp_loadcnt = get_refcount(mp); /***********************************************/ /* Swapped sense of the following ifdef */ /* around so we are consistent. */ /***********************************************/ fbt->fbtp_rval = invop; #ifdef __amd64 ASSERT(*instr == FBT_RET); fbt->fbtp_roffset = (uintptr_t)(instr - (uint8_t *)st_value); #else fbt->fbtp_roffset = (uintptr_t)(instr - (uint8_t *)st_value) + 1; #endif /***********************************************/ /* Save potential overwrite of instruction */ /* and length, because we will need the */ /* entire instruction when we single step */ /* over it. */ /***********************************************/ fbt->fbtp_savedval = *instr; fbt->fbtp_inslen = size; fbt->fbtp_modrm = -1; fbt->fbtp_patchval = FBT_PATCHVAL; fbt->fbtp_hashnext = fbt_probetab[FBT_ADDR2NDX(instr)]; fbt->fbtp_symndx = symndx; if (do_print) { printk("%d:alloc return-patchpoint: %s %p: %02x %02x invop=%d\n", __LINE__, name, instr, instr[0], instr[1], invop); } fbt_probetab[FBT_ADDR2NDX(instr)] = fbt; pmp->fbt_nentries++; instr += size; goto again; }