enum lwp_stop_reason -> enum target_stop_reason
[deliverable/binutils-gdb.git] / gdb / gdbserver / linux-low.c
index 4d19c870da2f447d019b3e899f8a53f283dbcb98..48e4fa3698bb357048892380d6f6aab8e732a4aa 100644 (file)
@@ -27,6 +27,7 @@
 #include <sys/ptrace.h>
 #include "nat/linux-ptrace.h"
 #include "nat/linux-procfs.h"
+#include "nat/linux-personality.h"
 #include <signal.h>
 #include <sys/ioctl.h>
 #include <fcntl.h>
 
 #ifdef HAVE_LINUX_BTRACE
 # include "nat/linux-btrace.h"
+# include "btrace-common.h"
 #endif
 
 #ifndef HAVE_ELF32_AUXV_T
@@ -355,13 +357,13 @@ linux_add_process (int pid, int attached)
   struct process_info *proc;
 
   proc = add_process (pid, attached);
-  proc->private = xcalloc (1, sizeof (*proc->private));
+  proc->priv = xcalloc (1, sizeof (*proc->priv));
 
   /* Set the arch when the first LWP stops.  */
-  proc->private->new_inferior = 1;
+  proc->priv->new_inferior = 1;
 
   if (the_low_target.new_process != NULL)
-    proc->private->arch_private = the_low_target.new_process ();
+    proc->priv->arch_private = the_low_target.new_process ();
 
   return proc;
 }
@@ -426,30 +428,12 @@ handle_extended_wait (struct lwp_info *event_child, int wstat)
       /* Normally we will get the pending SIGSTOP.  But in some cases
         we might get another signal delivered to the group first.
         If we do get another signal, be sure not to lose it.  */
-      if (WSTOPSIG (status) == SIGSTOP)
-       {
-         if (stopping_threads == NOT_STOPPING_THREADS)
-           linux_resume_one_lwp (new_lwp, 0, 0, NULL);
-       }
-      else
+      if (WSTOPSIG (status) != SIGSTOP)
        {
          new_lwp->stop_expected = 1;
-
-         if (stopping_threads != NOT_STOPPING_THREADS)
-           {
-             new_lwp->status_pending_p = 1;
-             new_lwp->status_pending = status;
-           }
-         else
-           /* Pass the signal on.  This is what GDB does - except
-              shouldn't we really report it instead?  */
-           linux_resume_one_lwp (new_lwp, 0, WSTOPSIG (status), NULL);
+         new_lwp->status_pending_p = 1;
+         new_lwp->status_pending = status;
        }
-
-      /* Always resume the current thread.  If we are stopping
-        threads, it will have a pending SIGSTOP; we may as well
-        collect it now.  */
-      linux_resume_one_lwp (event_child, event_child->stepping, 0, NULL);
     }
 }
 
@@ -523,14 +507,8 @@ check_stopped_by_breakpoint (struct lwp_info *lwp)
   /* We may have just stepped a breakpoint instruction.  E.g., in
      non-stop mode, GDB first tells the thread A to step a range, and
      then the user inserts a breakpoint inside the range.  In that
-     case, we need to report the breakpoint PC.  But, when we're
-     trying to step past one of our own breakpoints, that happens to
-     have been placed on top of a permanent breakpoint instruction, we
-     shouldn't adjust the PC, otherwise the program would keep
-     trapping the permanent breakpoint forever.  */
-  if ((!lwp->stepping
-       || (!ptid_equal (ptid_of (current_thread), step_over_bkpt)
-          && lwp->stop_pc == sw_breakpoint_pc))
+     case we need to report the breakpoint PC.  */
+  if ((!lwp->stepping || lwp->stop_pc == sw_breakpoint_pc)
       && (*the_low_target.breakpoint_at) (sw_breakpoint_pc))
     {
       if (debug_threads)
@@ -550,7 +528,7 @@ check_stopped_by_breakpoint (struct lwp_info *lwp)
        }
 
       lwp->stop_pc = sw_breakpoint_pc;
-      lwp->stop_reason = LWP_STOPPED_BY_SW_BREAKPOINT;
+      lwp->stop_reason = TARGET_STOPPED_BY_SW_BREAKPOINT;
       current_thread = saved_thread;
       return 1;
     }
@@ -566,7 +544,7 @@ check_stopped_by_breakpoint (struct lwp_info *lwp)
        }
 
       lwp->stop_pc = pc;
-      lwp->stop_reason = LWP_STOPPED_BY_HW_BREAKPOINT;
+      lwp->stop_reason = TARGET_STOPPED_BY_HW_BREAKPOINT;
       current_thread = saved_thread;
       return 1;
     }
@@ -597,29 +575,11 @@ add_lwp (ptid_t ptid)
 static int
 linux_create_inferior (char *program, char **allargs)
 {
-#ifdef HAVE_PERSONALITY
-  int personality_orig = 0, personality_set = 0;
-#endif
   struct lwp_info *new_lwp;
   int pid;
   ptid_t ptid;
-
-#ifdef HAVE_PERSONALITY
-  if (disable_randomization)
-    {
-      errno = 0;
-      personality_orig = personality (0xffffffff);
-      if (errno == 0 && !(personality_orig & ADDR_NO_RANDOMIZE))
-       {
-         personality_set = 1;
-         personality (personality_orig | ADDR_NO_RANDOMIZE);
-       }
-      if (errno != 0 || (personality_set
-                        && !(personality (0xffffffff) & ADDR_NO_RANDOMIZE)))
-       warning ("Error disabling address space randomization: %s",
-                strerror (errno));
-    }
-#endif
+  struct cleanup *restore_personality
+    = maybe_disable_address_space_randomization (disable_randomization);
 
 #if defined(__UCLIBC__) && defined(HAS_NOMMU)
   pid = vfork ();
@@ -665,16 +625,7 @@ linux_create_inferior (char *program, char **allargs)
       _exit (0177);
     }
 
-#ifdef HAVE_PERSONALITY
-  if (personality_set)
-    {
-      errno = 0;
-      personality (personality_orig);
-      if (errno != 0)
-       warning ("Error restoring address space randomization: %s",
-                strerror (errno));
-    }
-#endif
+  do_cleanups (restore_personality);
 
   linux_add_process (pid, 0);
 
@@ -1216,10 +1167,10 @@ linux_mourn (struct process_info *process)
   find_inferior (&all_threads, delete_lwp_callback, process);
 
   /* Freeing all private data.  */
-  priv = process->private;
+  priv = process->priv;
   free (priv->arch_private);
   free (priv);
-  process->private = NULL;
+  process->priv = NULL;
 
   remove_process (process);
 }
@@ -1270,8 +1221,8 @@ thread_still_has_status_pending_p (struct thread_info *thread)
     return 0;
 
   if (thread->last_resume_kind != resume_stop
-      && (lp->stop_reason == LWP_STOPPED_BY_SW_BREAKPOINT
-         || lp->stop_reason == LWP_STOPPED_BY_HW_BREAKPOINT))
+      && (lp->stop_reason == TARGET_STOPPED_BY_SW_BREAKPOINT
+         || lp->stop_reason == TARGET_STOPPED_BY_HW_BREAKPOINT))
     {
       struct thread_info *saved_thread;
       CORE_ADDR pc;
@@ -1291,7 +1242,7 @@ thread_still_has_status_pending_p (struct thread_info *thread)
                          lwpid_of (thread));
          discard = 1;
        }
-      else if (lp->stop_reason == LWP_STOPPED_BY_SW_BREAKPOINT
+      else if (lp->stop_reason == TARGET_STOPPED_BY_SW_BREAKPOINT
               && !(*the_low_target.breakpoint_at) (pc))
        {
          if (debug_threads)
@@ -1299,7 +1250,7 @@ thread_still_has_status_pending_p (struct thread_info *thread)
                          lwpid_of (thread));
          discard = 1;
        }
-      else if (lp->stop_reason == LWP_STOPPED_BY_HW_BREAKPOINT
+      else if (lp->stop_reason == TARGET_STOPPED_BY_HW_BREAKPOINT
               && !hardware_breakpoint_inserted_here (pc))
        {
          if (debug_threads)
@@ -1331,9 +1282,8 @@ status_pending_p_callback (struct inferior_list_entry *entry, void *arg)
   ptid_t ptid = * (ptid_t *) arg;
 
   /* Check if we're only interested in events from a specific process
-     or its lwps.  */
-  if (!ptid_equal (minus_one_ptid, ptid)
-      && ptid_get_pid (ptid) != ptid_get_pid (thread->entry.id))
+     or a specific LWP.  */
+  if (!ptid_match (ptid_of (thread), ptid))
     return 0;
 
   if (lp->status_pending_p
@@ -1781,19 +1731,6 @@ dequeue_one_deferred_signal (struct lwp_info *lwp, int *wstat)
   return 0;
 }
 
-/* Return true if the event in LP may be caused by breakpoint.  */
-
-static int
-wstatus_maybe_breakpoint (int wstatus)
-{
-  return (WIFSTOPPED (wstatus)
-         && (WSTOPSIG (wstatus) == SIGTRAP
-             /* SIGILL and SIGSEGV are also treated as traps in case a
-                breakpoint is inserted at the current PC.  */
-             || WSTOPSIG (wstatus) == SIGILL
-             || WSTOPSIG (wstatus) == SIGSEGV));
-}
-
 /* Fetch the possibly triggered data watchpoint info and store it in
    CHILD.
 
@@ -1821,7 +1758,7 @@ check_stopped_by_watchpoint (struct lwp_info *child)
 
       if (the_low_target.stopped_by_watchpoint ())
        {
-         child->stop_reason = LWP_STOPPED_BY_WATCHPOINT;
+         child->stop_reason = TARGET_STOPPED_BY_WATCHPOINT;
 
          if (the_low_target.stopped_data_address != NULL)
            child->stopped_data_address
@@ -1833,7 +1770,7 @@ check_stopped_by_watchpoint (struct lwp_info *child)
       current_thread = saved_thread;
     }
 
-  return child->stop_reason == LWP_STOPPED_BY_WATCHPOINT;
+  return child->stop_reason == TARGET_STOPPED_BY_WATCHPOINT;
 }
 
 /* Do low-level handling of the event, and check if we should go on
@@ -1905,7 +1842,7 @@ linux_low_filter_event (int lwpid, int wstat)
         is stopped for the first time, but before we access any
         inferior registers.  */
       proc = find_process_pid (pid_of (thread));
-      if (proc->private->new_inferior)
+      if (proc->priv->new_inferior)
        {
          struct thread_info *saved_thread;
 
@@ -1916,7 +1853,7 @@ linux_low_filter_event (int lwpid, int wstat)
 
          current_thread = saved_thread;
 
-         proc->private->new_inferior = 0;
+         proc->priv->new_inferior = 0;
        }
     }
 
@@ -1941,7 +1878,7 @@ linux_low_filter_event (int lwpid, int wstat)
   if (WIFSTOPPED (wstat) && WSTOPSIG (wstat) == SIGTRAP
       && check_stopped_by_watchpoint (child))
     ;
-  else if (WIFSTOPPED (wstat) && wstatus_maybe_breakpoint (wstat))
+  else if (WIFSTOPPED (wstat) && linux_wstatus_maybe_breakpoint (wstat))
     {
       if (check_stopped_by_breakpoint (child))
        have_stop_pc = 1;
@@ -1981,6 +1918,32 @@ linux_low_filter_event (int lwpid, int wstat)
   return child;
 }
 
+/* Resume LWPs that are currently stopped without any pending status
+   to report, but are resumed from the core's perspective.  */
+
+static void
+resume_stopped_resumed_lwps (struct inferior_list_entry *entry)
+{
+  struct thread_info *thread = (struct thread_info *) entry;
+  struct lwp_info *lp = get_thread_lwp (thread);
+
+  if (lp->stopped
+      && !lp->status_pending_p
+      && thread->last_resume_kind != resume_stop
+      && thread->last_status.kind == TARGET_WAITKIND_IGNORE)
+    {
+      int step = thread->last_resume_kind == resume_step;
+
+      if (debug_threads)
+       debug_printf ("RSRL: resuming stopped-resumed LWP %s at %s: step=%d\n",
+                     target_pid_to_str (ptid_of (thread)),
+                     paddress (lp->stop_pc),
+                     step);
+
+      linux_resume_one_lwp (lp, step, GDB_SIGNAL_0, NULL);
+    }
+}
+
 /* Wait for an event from child(ren) WAIT_PTID, and return any that
    match FILTER_PTID (leaving others pending).  The PTIDs can be:
    minus_one_ptid, to specify any child; a pid PTID, specifying all
@@ -2114,8 +2077,13 @@ linux_wait_for_event_filtered (ptid_t wait_ptid, ptid_t filter_ptid,
          continue;
        }
 
-      /* Now that we've pulled all events out of the kernel, check if
-        there's any LWP with a status to report to the core.  */
+      /* Now that we've pulled all events out of the kernel, resume
+        LWPs that don't have an interesting event to report.  */
+      if (stopping_threads == NOT_STOPPING_THREADS)
+       for_each_inferior (&all_threads, resume_stopped_resumed_lwps);
+
+      /* ... and find an LWP with a status to report to the core, if
+        any.  */
       event_thread = (struct thread_info *)
        find_inferior (&all_threads, status_pending_p_callback, &filter_ptid);
       if (event_thread != NULL)
@@ -2578,6 +2546,36 @@ linux_wait_1 (ptid_t ptid,
       return ptid_of (current_thread);
     }
 
+  /* If step-over executes a breakpoint instruction, it means a
+     gdb/gdbserver breakpoint had been planted on top of a permanent
+     breakpoint.  The PC has been adjusted by
+     check_stopped_by_breakpoint to point at the breakpoint address.
+     Advance the PC manually past the breakpoint, otherwise the
+     program would keep trapping the permanent breakpoint forever.  */
+  if (!ptid_equal (step_over_bkpt, null_ptid)
+      && event_child->stop_reason == TARGET_STOPPED_BY_SW_BREAKPOINT)
+    {
+      unsigned int increment_pc = the_low_target.breakpoint_len;
+
+      if (debug_threads)
+       {
+         debug_printf ("step-over for %s executed software breakpoint\n",
+                       target_pid_to_str (ptid_of (current_thread)));
+       }
+
+      if (increment_pc != 0)
+       {
+         struct regcache *regcache
+           = get_thread_regcache (current_thread, 1);
+
+         event_child->stop_pc += increment_pc;
+         (*the_low_target.set_pc) (regcache, event_child->stop_pc);
+
+         if (!(*the_low_target.breakpoint_at) (event_child->stop_pc))
+           event_child->stop_reason = TARGET_STOPPED_BY_NO_REASON;
+       }
+    }
+
   /* If this event was not handled before, and is not a SIGTRAP, we
      report it.  SIGILL and SIGSEGV are also treated as traps in case
      a breakpoint is inserted at the current PC.  If this target does
@@ -2751,21 +2749,23 @@ linux_wait_1 (ptid_t ptid,
      any that GDB specifically requested we ignore.  But never ignore
      SIGSTOP if we sent it ourselves, and do not ignore signals when
      stepping - they may require special handling to skip the signal
-     handler.  */
+     handler. Also never ignore signals that could be caused by a
+     breakpoint.  */
   /* FIXME drow/2002-06-09: Get signal numbers from the inferior's
      thread library?  */
   if (WIFSTOPPED (w)
       && current_thread->last_resume_kind != resume_step
       && (
 #if defined (USE_THREAD_DB) && !defined (__ANDROID__)
-         (current_process ()->private->thread_db != NULL
+         (current_process ()->priv->thread_db != NULL
           && (WSTOPSIG (w) == __SIGRTMIN
               || WSTOPSIG (w) == __SIGRTMIN + 1))
          ||
 #endif
          (pass_signals[gdb_signal_from_host (WSTOPSIG (w))]
           && !(WSTOPSIG (w) == SIGSTOP
-               && current_thread->last_resume_kind == resume_stop))))
+               && current_thread->last_resume_kind == resume_stop)
+          && !linux_wstatus_maybe_breakpoint (w))))
     {
       siginfo_t info, *info_p;
 
@@ -2799,7 +2799,7 @@ linux_wait_1 (ptid_t ptid,
   report_to_gdb = (!maybe_internal_trap
                   || (current_thread->last_resume_kind == resume_step
                       && !in_step_range)
-                  || event_child->stop_reason == LWP_STOPPED_BY_WATCHPOINT
+                  || event_child->stop_reason == TARGET_STOPPED_BY_WATCHPOINT
                   || (!step_over_finished && !in_step_range
                       && !bp_explains_trap && !trace_event)
                   || (gdb_breakpoint_here (event_child->stop_pc)
@@ -2863,7 +2863,7 @@ linux_wait_1 (ptid_t ptid,
          else if (!lwp_in_step_range (event_child))
            debug_printf ("Out of step range, reporting event.\n");
        }
-      if (event_child->stop_reason == LWP_STOPPED_BY_WATCHPOINT)
+      if (event_child->stop_reason == TARGET_STOPPED_BY_WATCHPOINT)
        debug_printf ("Stopped by watchpoint.\n");
       else if (gdb_breakpoint_here (event_child->stop_pc))
        debug_printf ("Stopped by GDB breakpoint.\n");
@@ -2939,7 +2939,7 @@ linux_wait_1 (ptid_t ptid,
 
   /* Now that we've selected our final event LWP, un-adjust its PC if
      it was a software breakpoint.  */
-  if (event_child->stop_reason == LWP_STOPPED_BY_SW_BREAKPOINT)
+  if (event_child->stop_reason == TARGET_STOPPED_BY_SW_BREAKPOINT)
     {
       int decr_pc = the_low_target.decr_pc_after_break;
 
@@ -3216,7 +3216,7 @@ stuck_in_jump_pad_callback (struct inferior_list_entry *entry, void *data)
   return (supports_fast_tracepoints ()
          && agent_loaded_p ()
          && (gdb_breakpoint_here (lwp->stop_pc)
-             || lwp->stop_reason == LWP_STOPPED_BY_WATCHPOINT
+             || lwp->stop_reason == TARGET_STOPPED_BY_WATCHPOINT
              || thread->last_resume_kind == resume_step)
          && linux_fast_tracepoint_collecting (lwp, NULL));
 }
@@ -3235,7 +3235,7 @@ move_out_of_jump_pad_callback (struct inferior_list_entry *entry)
 
   /* Allow debugging the jump pad, gdb_collect, etc.  */
   if (!gdb_breakpoint_here (lwp->stop_pc)
-      && lwp->stop_reason != LWP_STOPPED_BY_WATCHPOINT
+      && lwp->stop_reason != TARGET_STOPPED_BY_WATCHPOINT
       && thread->last_resume_kind != resume_step
       && maybe_move_out_of_jump_pad (lwp, wstat))
     {
@@ -3500,7 +3500,7 @@ linux_resume_one_lwp (struct lwp_info *lwp,
   regcache_invalidate_thread (thread);
   errno = 0;
   lwp->stopped = 0;
-  lwp->stop_reason = LWP_STOPPED_BY_NO_REASON;
+  lwp->stop_reason = TARGET_STOPPED_BY_NO_REASON;
   lwp->stepping = step;
   ptrace (step ? PTRACE_SINGLESTEP : PTRACE_CONT, lwpid_of (thread),
          (PTRACE_TYPE_ARG3) 0,
@@ -4821,7 +4821,7 @@ linux_look_up_symbols (void)
 #ifdef USE_THREAD_DB
   struct process_info *proc = current_process ();
 
-  if (proc->private->thread_db != NULL)
+  if (proc->priv->thread_db != NULL)
     return;
 
   /* If the kernel supports tracing clones, then we don't need to
@@ -4906,7 +4906,7 @@ linux_stopped_by_watchpoint (void)
 {
   struct lwp_info *lwp = get_thread_lwp (current_thread);
 
-  return lwp->stop_reason == LWP_STOPPED_BY_WATCHPOINT;
+  return lwp->stop_reason == TARGET_STOPPED_BY_WATCHPOINT;
 }
 
 static CORE_ADDR
@@ -5741,7 +5741,7 @@ linux_qxfer_libraries_svr4 (const char *annex, unsigned char *readbuf,
 {
   char *document;
   unsigned document_len;
-  struct process_info_private *const priv = current_process ()->private;
+  struct process_info_private *const priv = current_process ()->priv;
   char filename[PATH_MAX];
   int pid, is_elf64;
 
@@ -5950,13 +5950,13 @@ linux_qxfer_libraries_svr4 (const char *annex, unsigned char *readbuf,
 /* See to_enable_btrace target method.  */
 
 static struct btrace_target_info *
-linux_low_enable_btrace (ptid_t ptid)
+linux_low_enable_btrace (ptid_t ptid, const struct btrace_config *conf)
 {
   struct btrace_target_info *tinfo;
 
-  tinfo = linux_enable_btrace (ptid);
+  tinfo = linux_enable_btrace (ptid, conf);
 
-  if (tinfo != NULL)
+  if (tinfo != NULL && tinfo->ptr_bits == 0)
     {
       struct thread_info *thread = find_thread_ptid (ptid);
       struct regcache *regcache = get_thread_regcache (thread, 0);
@@ -5984,12 +5984,13 @@ static int
 linux_low_read_btrace (struct btrace_target_info *tinfo, struct buffer *buffer,
                       int type)
 {
-  VEC (btrace_block_s) *btrace;
+  struct btrace_data btrace;
   struct btrace_block *block;
   enum btrace_error err;
   int i;
 
-  btrace = NULL;
+  btrace_data_init (&btrace);
+
   err = linux_read_btrace (&btrace, tinfo, type);
   if (err != BTRACE_ERR_NONE)
     {
@@ -5998,20 +5999,68 @@ linux_low_read_btrace (struct btrace_target_info *tinfo, struct buffer *buffer,
       else
        buffer_grow_str0 (buffer, "E.Generic Error.");
 
+      btrace_data_fini (&btrace);
+      return -1;
+    }
+
+  switch (btrace.format)
+    {
+    case BTRACE_FORMAT_NONE:
+      buffer_grow_str0 (buffer, "E.No Trace.");
+      break;
+
+    case BTRACE_FORMAT_BTS:
+      buffer_grow_str (buffer, "<!DOCTYPE btrace SYSTEM \"btrace.dtd\">\n");
+      buffer_grow_str (buffer, "<btrace version=\"1.0\">\n");
+
+      for (i = 0;
+          VEC_iterate (btrace_block_s, btrace.variant.bts.blocks, i, block);
+          i++)
+       buffer_xml_printf (buffer, "<block begin=\"0x%s\" end=\"0x%s\"/>\n",
+                          paddress (block->begin), paddress (block->end));
+
+      buffer_grow_str0 (buffer, "</btrace>\n");
+      break;
+
+    default:
+      buffer_grow_str0 (buffer, "E.Unknown Trace Format.");
+
+      btrace_data_fini (&btrace);
       return -1;
     }
 
-  buffer_grow_str (buffer, "<!DOCTYPE btrace SYSTEM \"btrace.dtd\">\n");
-  buffer_grow_str (buffer, "<btrace version=\"1.0\">\n");
+  btrace_data_fini (&btrace);
+  return 0;
+}
 
-  for (i = 0; VEC_iterate (btrace_block_s, btrace, i, block); i++)
-    buffer_xml_printf (buffer, "<block begin=\"0x%s\" end=\"0x%s\"/>\n",
-                      paddress (block->begin), paddress (block->end));
+/* See to_btrace_conf target method.  */
 
-  buffer_grow_str0 (buffer, "</btrace>\n");
+static int
+linux_low_btrace_conf (const struct btrace_target_info *tinfo,
+                      struct buffer *buffer)
+{
+  const struct btrace_config *conf;
+
+  buffer_grow_str (buffer, "<!DOCTYPE btrace-conf SYSTEM \"btrace-conf.dtd\">\n");
+  buffer_grow_str (buffer, "<btrace-conf version=\"1.0\">\n");
+
+  conf = linux_btrace_conf (tinfo);
+  if (conf != NULL)
+    {
+      switch (conf->format)
+       {
+       case BTRACE_FORMAT_NONE:
+         break;
 
-  VEC_free (btrace_block_s, btrace);
+       case BTRACE_FORMAT_BTS:
+         buffer_xml_printf (buffer, "<bts");
+         buffer_xml_printf (buffer, " size=\"0x%x\"", conf->bts.size);
+         buffer_xml_printf (buffer, " />\n");
+         break;
+       }
+    }
 
+  buffer_grow_str0 (buffer, "</btrace-conf>\n");
   return 0;
 }
 #endif /* HAVE_LINUX_BTRACE */
@@ -6087,11 +6136,13 @@ static struct target_ops linux_target_ops = {
   linux_low_enable_btrace,
   linux_low_disable_btrace,
   linux_low_read_btrace,
+  linux_low_btrace_conf,
 #else
   NULL,
   NULL,
   NULL,
   NULL,
+  NULL,
 #endif
   linux_supports_range_stepping,
 };
This page took 0.032884 seconds and 4 git commands to generate.