]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - arch/s390/kernel/ftrace.c
Merge branch 'for-linus-v3.20' of git://git.infradead.org/linux-ubifs
[karo-tx-linux.git] / arch / s390 / kernel / ftrace.c
index b86bb8823f15192030f227d2c9cc189d66804d50..82c19899574f83070158c09c5eed5b438bc092c1 100644 (file)
  *     lg      %r14,8(%r15)            # offset 18
  * The jg instruction branches to offset 24 to skip as many instructions
  * as possible.
+ * In case we use gcc's hotpatch feature the original and also the disabled
+ * function prologue contains only a single six byte instruction and looks
+ * like this:
+ * >   brcl    0,0                     # offset 0
+ * To enable ftrace the code gets patched like above and afterwards looks
+ * like this:
+ * >   brasl   %r0,ftrace_caller       # offset 0
  */
 
 unsigned long ftrace_plt;
@@ -59,62 +66,71 @@ int ftrace_modify_call(struct dyn_ftrace *rec, unsigned long old_addr,
 int ftrace_make_nop(struct module *mod, struct dyn_ftrace *rec,
                    unsigned long addr)
 {
-       struct ftrace_insn insn;
-       unsigned short op;
-       void *from, *to;
-       size_t size;
-
-       ftrace_generate_nop_insn(&insn);
-       size = sizeof(insn);
-       from = &insn;
-       to = (void *) rec->ip;
-       if (probe_kernel_read(&op, (void *) rec->ip, sizeof(op)))
+       struct ftrace_insn orig, new, old;
+
+       if (probe_kernel_read(&old, (void *) rec->ip, sizeof(old)))
                return -EFAULT;
-       /*
-        * If we find a breakpoint instruction, a kprobe has been placed
-        * at the beginning of the function. We write the constant
-        * KPROBE_ON_FTRACE_NOP into the remaining four bytes of the original
-        * instruction so that the kprobes handler can execute a nop, if it
-        * reaches this breakpoint.
-        */
-       if (op == BREAKPOINT_INSTRUCTION) {
-               size -= 2;
-               from += 2;
-               to += 2;
-               insn.disp = KPROBE_ON_FTRACE_NOP;
+       if (addr == MCOUNT_ADDR) {
+               /* Initial code replacement */
+#ifdef CC_USING_HOTPATCH
+               /* We expect to see brcl 0,0 */
+               ftrace_generate_nop_insn(&orig);
+#else
+               /* We expect to see stg r14,8(r15) */
+               orig.opc = 0xe3e0;
+               orig.disp = 0xf0080024;
+#endif
+               ftrace_generate_nop_insn(&new);
+       } else if (old.opc == BREAKPOINT_INSTRUCTION) {
+               /*
+                * If we find a breakpoint instruction, a kprobe has been
+                * placed at the beginning of the function. We write the
+                * constant KPROBE_ON_FTRACE_NOP into the remaining four
+                * bytes of the original instruction so that the kprobes
+                * handler can execute a nop, if it reaches this breakpoint.
+                */
+               new.opc = orig.opc = BREAKPOINT_INSTRUCTION;
+               orig.disp = KPROBE_ON_FTRACE_CALL;
+               new.disp = KPROBE_ON_FTRACE_NOP;
+       } else {
+               /* Replace ftrace call with a nop. */
+               ftrace_generate_call_insn(&orig, rec->ip);
+               ftrace_generate_nop_insn(&new);
        }
-       if (probe_kernel_write(to, from, size))
+       /* Verify that the to be replaced code matches what we expect. */
+       if (memcmp(&orig, &old, sizeof(old)))
+               return -EINVAL;
+       if (probe_kernel_write((void *) rec->ip, &new, sizeof(new)))
                return -EPERM;
        return 0;
 }
 
 int ftrace_make_call(struct dyn_ftrace *rec, unsigned long addr)
 {
-       struct ftrace_insn insn;
-       unsigned short op;
-       void *from, *to;
-       size_t size;
-
-       ftrace_generate_call_insn(&insn, rec->ip);
-       size = sizeof(insn);
-       from = &insn;
-       to = (void *) rec->ip;
-       if (probe_kernel_read(&op, (void *) rec->ip, sizeof(op)))
+       struct ftrace_insn orig, new, old;
+
+       if (probe_kernel_read(&old, (void *) rec->ip, sizeof(old)))
                return -EFAULT;
-       /*
-        * If we find a breakpoint instruction, a kprobe has been placed
-        * at the beginning of the function. We write the constant
-        * KPROBE_ON_FTRACE_CALL into the remaining four bytes of the original
-        * instruction so that the kprobes handler can execute a brasl if it
-        * reaches this breakpoint.
-        */
-       if (op == BREAKPOINT_INSTRUCTION) {
-               size -= 2;
-               from += 2;
-               to += 2;
-               insn.disp = KPROBE_ON_FTRACE_CALL;
+       if (old.opc == BREAKPOINT_INSTRUCTION) {
+               /*
+                * If we find a breakpoint instruction, a kprobe has been
+                * placed at the beginning of the function. We write the
+                * constant KPROBE_ON_FTRACE_CALL into the remaining four
+                * bytes of the original instruction so that the kprobes
+                * handler can execute a brasl if it reaches this breakpoint.
+                */
+               new.opc = orig.opc = BREAKPOINT_INSTRUCTION;
+               orig.disp = KPROBE_ON_FTRACE_NOP;
+               new.disp = KPROBE_ON_FTRACE_CALL;
+       } else {
+               /* Replace nop with an ftrace call. */
+               ftrace_generate_nop_insn(&orig);
+               ftrace_generate_call_insn(&new, rec->ip);
        }
-       if (probe_kernel_write(to, from, size))
+       /* Verify that the to be replaced code matches what we expect. */
+       if (memcmp(&orig, &old, sizeof(old)))
+               return -EINVAL;
+       if (probe_kernel_write((void *) rec->ip, &new, sizeof(new)))
                return -EPERM;
        return 0;
 }