readelf: nds32 specific decoding
[deliverable/binutils-gdb.git] / binutils / readelf.c
index 4f80bd94b9301753cb729cfe1055d46082d03930..25c2cce21ae8ecbc84a3519d5e01d6310a664ffa 100644 (file)
@@ -199,7 +199,8 @@ struct dump_list_entry
 
 /* A dynamic array of flags indicating for which sections a dump
    has been requested via command line switches.  */
-struct dump_data {
+struct dump_data
+{
   dump_type *          dump_sects;
   unsigned int         num_dump_sects;
 };
@@ -230,6 +231,8 @@ static bfd_boolean do_ctf = FALSE;
 static bfd_boolean do_arch = FALSE;
 static bfd_boolean do_notes = FALSE;
 static bfd_boolean do_archive_index = FALSE;
+static bfd_boolean do_checks = FALSE;
+static bfd_boolean check_all = FALSE;
 static bfd_boolean is_32bit_elf = FALSE;
 static bfd_boolean decompress_dumps = FALSE;
 
@@ -384,6 +387,25 @@ bfd_vmatoa (char *fmtch, bfd_vma value)
   return ret;
 }
 
+/* A version of the warn() function that is disabled if do_checks is not active.  */
+
+void
+warn (const char *message, ...)
+{
+  va_list args;
+
+  if (!do_checks)
+    return;
+
+  /* Try to keep warning messages in sync with the program's normal output.  */
+  fflush (stdout);
+
+  va_start (args, message);
+  fprintf (stderr, _("%s: Warning: "), program_name);
+  vfprintf (stderr, message, args);
+  va_end (args);
+}
+
 /* Retrieve NMEMB structures, each SIZE bytes long from FILEDATA starting at
    OFFSET + the offset of the current archive member, if we are examining an
    archive.  Put the retrieved data into VAR, if it is not NULL.  Otherwise
@@ -4478,6 +4500,8 @@ static struct option options[] =
   {"relocs",          no_argument, 0, 'r'},
   {"notes",           no_argument, 0, 'n'},
   {"dynamic",         no_argument, 0, 'd'},
+  {"lint",             no_argument, 0, 'L'},
+  {"enable-checks",    no_argument, 0, 'L'},
   {"arch-specific",    no_argument, 0, 'A'},
   {"version-info",     no_argument, 0, 'V'},
   {"use-dynamic",      no_argument, 0, 'D'},
@@ -4525,7 +4549,7 @@ usage (FILE * stream)
   -e --headers           Equivalent to: -h -l -S\n\
   -s --syms              Display the symbol table\n\
      --symbols           An alias for --syms\n\
-  --dyn-syms             Display the dynamic symbol table\n\
+     --dyn-syms          Display the dynamic symbol table\n\
   -n --notes             Display the core notes (if present)\n\
   -r --relocs            Display the relocations (if present)\n\
   -u --unwind            Display the unwind info (if present)\n\
@@ -4534,6 +4558,7 @@ usage (FILE * stream)
   -A --arch-specific     Display architecture specific information (if any)\n\
   -c --archive-index     Display the symbol/file index in an archive\n\
   -D --use-dynamic       Use the dynamic section info when displaying symbols\n\
+  -L --lint|--enable-checks  Display warning messages for possible problems\n\
   -x --hex-dump=<number|name>\n\
                          Dump the contents of section <number|name> as bytes\n\
   -p --string-dump=<number|name>\n\
@@ -4662,7 +4687,7 @@ parse_args (struct dump_data *dumpdata, int argc, char ** argv)
     usage (stderr);
 
   while ((c = getopt_long
-         (argc, argv, "ADHINR:SVWacdeghi:lnp:rstuvw::x:z", options, NULL)) != EOF)
+         (argc, argv, "ADHILNR:SVWacdeghi:lnp:rstuvw::x:z", options, NULL)) != EOF)
     {
       switch (c)
        {
@@ -4736,6 +4761,9 @@ parse_args (struct dump_data *dumpdata, int argc, char ** argv)
        case 'c':
          do_archive_index = TRUE;
          break;
+       case 'L':
+         do_checks = TRUE;
+         break;
        case 'x':
          request_dump (dumpdata, HEX_DUMP);
          break;
@@ -4832,7 +4860,18 @@ parse_args (struct dump_data *dumpdata, int argc, char ** argv)
       && !do_histogram && !do_debugging && !do_arch && !do_notes
       && !do_section_groups && !do_archive_index
       && !do_dyn_syms)
-    usage (stderr);
+    {
+      if (do_checks)
+       {
+         check_all = TRUE;
+         do_dynamic = do_syms = do_reloc = do_unwind = do_sections = TRUE;
+         do_segments = do_header = do_dump = do_version = TRUE;
+         do_histogram = do_debugging = do_arch = do_notes = TRUE;
+         do_section_groups = do_archive_index = do_dyn_syms = TRUE;
+       }
+      else
+       usage (stderr);
+    }
 }
 
 static const char *
@@ -6277,7 +6316,7 @@ process_section_headers (Filedata * filedata)
   while (0)
 
 #define CHECK_ENTSIZE(section, i, type)                                        \
-  CHECK_ENTSIZE_VALUES (section, i, sizeof (Elf32_External_##type),        \
+  CHECK_ENTSIZE_VALUES (section, i, sizeof (Elf32_External_##type),    \
                        sizeof (Elf64_External_##type))
 
   for (i = 0, section = filedata->section_headers;
@@ -6286,8 +6325,11 @@ process_section_headers (Filedata * filedata)
     {
       char * name = SECTION_NAME (section);
 
-      if (section->sh_type == SHT_DYNSYM)
+      /* Run some sanity checks on the headers and
+        possibly fill in some file data as well.  */
+      switch (section->sh_type)
        {
+       case SHT_DYNSYM:
          if (filedata->dynamic_symbols != NULL)
            {
              error (_("File contains multiple dynamic symbol tables\n"));
@@ -6297,45 +6339,74 @@ process_section_headers (Filedata * filedata)
          CHECK_ENTSIZE (section, i, Sym);
          filedata->dynamic_symbols
            = GET_ELF_SYMBOLS (filedata, section, &filedata->num_dynamic_syms);
-       }
-      else if (section->sh_type == SHT_STRTAB
-              && streq (name, ".dynstr"))
-       {
-         if (filedata->dynamic_strings != NULL)
+         break;
+
+       case SHT_STRTAB:
+         if (streq (name, ".dynstr"))
            {
-             error (_("File contains multiple dynamic string tables\n"));
-             continue;
+             if (filedata->dynamic_strings != NULL)
+               {
+                 error (_("File contains multiple dynamic string tables\n"));
+                 continue;
+               }
+
+             filedata->dynamic_strings
+               = (char *) get_data (NULL, filedata, section->sh_offset,
+                                    1, section->sh_size, _("dynamic strings"));
+             filedata->dynamic_strings_length
+               = filedata->dynamic_strings == NULL ? 0 : section->sh_size;
            }
+         break;
+
+       case SHT_SYMTAB_SHNDX:
+         {
+           elf_section_list * entry = xmalloc (sizeof * entry);
+
+           entry->hdr = section;
+           entry->next = filedata->symtab_shndx_list;
+           filedata->symtab_shndx_list = entry;
+         }
+         break;
+
+       case SHT_SYMTAB:
+         CHECK_ENTSIZE (section, i, Sym);
+         break;
+
+       case SHT_GROUP:
+         CHECK_ENTSIZE_VALUES (section, i, GRP_ENTRY_SIZE, GRP_ENTRY_SIZE);
+         break;
 
-         filedata->dynamic_strings
-           = (char *) get_data (NULL, filedata, section->sh_offset,
-                                1, section->sh_size, _("dynamic strings"));
-         filedata->dynamic_strings_length
-           = filedata->dynamic_strings == NULL ? 0 : section->sh_size;
-       }
-      else if (section->sh_type == SHT_SYMTAB_SHNDX)
-       {
-         elf_section_list * entry = xmalloc (sizeof * entry);
-
-         entry->hdr = section;
-         entry->next = filedata->symtab_shndx_list;
-         filedata->symtab_shndx_list = entry;
-       }
-      else if (section->sh_type == SHT_SYMTAB)
-       CHECK_ENTSIZE (section, i, Sym);
-      else if (section->sh_type == SHT_GROUP)
-       CHECK_ENTSIZE_VALUES (section, i, GRP_ENTRY_SIZE, GRP_ENTRY_SIZE);
-      else if (section->sh_type == SHT_REL)
-       CHECK_ENTSIZE (section, i, Rel);
-      else if (section->sh_type == SHT_RELA)
-       CHECK_ENTSIZE (section, i, Rela);
-      else if ((do_debugging || do_debug_info || do_debug_abbrevs
-               || do_debug_lines || do_debug_pubnames || do_debug_pubtypes
-               || do_debug_aranges || do_debug_frames || do_debug_macinfo
-               || do_debug_str || do_debug_loc || do_debug_ranges
-               || do_debug_addr || do_debug_cu_index || do_debug_links)
-              && (const_strneq (name, ".debug_")
-                   || const_strneq (name, ".zdebug_")))
+       case SHT_REL:
+         CHECK_ENTSIZE (section, i, Rel);
+         if (section->sh_size == 0)
+           warn (_("Section '%s': zero-sized relocation section\n"), name);
+         break;
+
+       case SHT_RELA:
+         CHECK_ENTSIZE (section, i, Rela);
+         if (section->sh_size == 0)
+           warn (_("Section '%s': zero-sized relocation section\n"), name);
+         break;
+
+       case SHT_NOTE:
+       case SHT_PROGBITS:
+         if (section->sh_size == 0)
+           /* This is not illegal according to the ELF standard, but
+              it might be an indication that something is wrong.  */
+           warn (_("Section '%s': has a size of zero - is this intended ?\n"), name);
+         break;
+
+       default:
+         break;
+       }
+
+      if ((do_debugging || do_debug_info || do_debug_abbrevs
+          || do_debug_lines || do_debug_pubnames || do_debug_pubtypes
+          || do_debug_aranges || do_debug_frames || do_debug_macinfo
+          || do_debug_str || do_debug_loc || do_debug_ranges
+          || do_debug_addr || do_debug_cu_index || do_debug_links)
+         && (const_strneq (name, ".debug_")
+             || const_strneq (name, ".zdebug_")))
        {
           if (name[1] == 'z')
             name += sizeof (".zdebug_") - 1;
@@ -9993,14 +10064,16 @@ get_num_dynamic_syms (Filedata * filedata)
       filedata->nbuckets = byte_get (nb, hash_ent_size);
       filedata->nchains = byte_get (nc, hash_ent_size);
 
-      filedata->buckets = get_dynamic_data (filedata, filedata->nbuckets,
-                                           hash_ent_size);
-      filedata->chains  = get_dynamic_data (filedata, filedata->nchains,
-                                           hash_ent_size);
-
-      if (filedata->buckets != NULL && filedata->chains != NULL)
-       num_of_syms = filedata->nchains;
+      if (filedata->nbuckets != 0 && filedata->nchains != 0)
+       {
+         filedata->buckets = get_dynamic_data (filedata, filedata->nbuckets,
+                                               hash_ent_size);
+         filedata->chains  = get_dynamic_data (filedata, filedata->nchains,
+                                               hash_ent_size);
 
+         if (filedata->buckets != NULL && filedata->chains != NULL)
+           num_of_syms = filedata->nchains;
+       }
     no_hash:
       if (num_of_syms == 0)
        {
@@ -10024,7 +10097,6 @@ get_num_dynamic_syms (Filedata * filedata)
       bfd_vma i, maxchain = 0xffffffff, bitmaskwords;
       bfd_vma buckets_vma;
       unsigned long hn;
-      bfd_boolean gnu_hash_error = FALSE;
 
       if (fseek (filedata->handle,
                 (filedata->archive_file_offset
@@ -10034,14 +10106,12 @@ get_num_dynamic_syms (Filedata * filedata)
                 SEEK_SET))
        {
          error (_("Unable to seek to start of dynamic information\n"));
-         gnu_hash_error = TRUE;
          goto no_gnu_hash;
        }
 
       if (fread (nb, 16, 1, filedata->handle) != 1)
        {
          error (_("Failed to read in number of buckets\n"));
-         gnu_hash_error = TRUE;
          goto no_gnu_hash;
        }
 
@@ -10060,7 +10130,6 @@ get_num_dynamic_syms (Filedata * filedata)
                 SEEK_SET))
        {
          error (_("Unable to seek to start of dynamic information\n"));
-         gnu_hash_error = TRUE;
          goto no_gnu_hash;
        }
 
@@ -10068,29 +10137,20 @@ get_num_dynamic_syms (Filedata * filedata)
        = get_dynamic_data (filedata, filedata->ngnubuckets, 4);
 
       if (filedata->gnubuckets == NULL)
-       {
-         gnu_hash_error = TRUE;
-         goto no_gnu_hash;
-       }
+       goto no_gnu_hash;
 
       for (i = 0; i < filedata->ngnubuckets; i++)
        if (filedata->gnubuckets[i] != 0)
          {
            if (filedata->gnubuckets[i] < filedata->gnusymidx)
-             {
-               gnu_hash_error = TRUE;
-               goto no_gnu_hash;
-             }
+             goto no_gnu_hash;
 
            if (maxchain == 0xffffffff || filedata->gnubuckets[i] > maxchain)
              maxchain = filedata->gnubuckets[i];
          }
 
       if (maxchain == 0xffffffff)
-       {
-         gnu_hash_error = TRUE;
-         goto no_gnu_hash;
-       }
+       goto no_gnu_hash;
 
       maxchain -= filedata->gnusymidx;
 
@@ -10103,7 +10163,6 @@ get_num_dynamic_syms (Filedata * filedata)
                 SEEK_SET))
        {
          error (_("Unable to seek to start of dynamic information\n"));
-         gnu_hash_error = TRUE;
          goto no_gnu_hash;
        }
 
@@ -10112,15 +10171,11 @@ get_num_dynamic_syms (Filedata * filedata)
          if (fread (nb, 4, 1, filedata->handle) != 1)
            {
              error (_("Failed to determine last chain length\n"));
-             gnu_hash_error = TRUE;
              goto no_gnu_hash;
            }
 
          if (maxchain + 1 == 0)
-           {
-             gnu_hash_error = TRUE;
-             goto no_gnu_hash;
-           }
+           goto no_gnu_hash;
 
          ++maxchain;
        }
@@ -10134,7 +10189,6 @@ get_num_dynamic_syms (Filedata * filedata)
                 SEEK_SET))
        {
          error (_("Unable to seek to start of dynamic information\n"));
-         gnu_hash_error = TRUE;
          goto no_gnu_hash;
        }
 
@@ -10142,10 +10196,7 @@ get_num_dynamic_syms (Filedata * filedata)
       filedata->ngnuchains = maxchain;
 
       if (filedata->gnuchains == NULL)
-       {
-         gnu_hash_error = TRUE;
-         goto no_gnu_hash;
-       }
+       goto no_gnu_hash;
 
       if (filedata->dynamic_info_DT_MIPS_XHASH)
        {
@@ -10157,11 +10208,12 @@ get_num_dynamic_syms (Filedata * filedata)
                     SEEK_SET))
            {
              error (_("Unable to seek to start of dynamic information\n"));
-             gnu_hash_error = TRUE;
              goto no_gnu_hash;
            }
 
          filedata->mipsxlat = get_dynamic_data (filedata, maxchain, 4);
+         if (filedata->mipsxlat == NULL)
+           goto no_gnu_hash;
        }
 
       for (hn = 0; hn < filedata->ngnubuckets; ++hn)
@@ -10188,9 +10240,9 @@ get_num_dynamic_syms (Filedata * filedata)
                   && (filedata->gnuchains[off++] & 1) == 0);
          }
 
-    no_gnu_hash:
-      if (gnu_hash_error)
+      if (num_of_syms == 0)
        {
+       no_gnu_hash:
          if (filedata->mipsxlat)
            {
              free (filedata->mipsxlat);
@@ -10243,6 +10295,8 @@ process_dynamic_section (Filedata * filedata)
   /* Find the appropriate symbol table.  */
   if (filedata->dynamic_symbols == NULL || do_histogram)
     {
+      unsigned long num_of_syms;
+
       for (entry = filedata->dynamic_section;
           entry < filedata->dynamic_section + filedata->dynamic_nent;
           ++entry)
@@ -10262,64 +10316,65 @@ process_dynamic_section (Filedata * filedata)
            filedata->dynamic_info_DT_GNU_HASH = entry->d_un.d_val;
          }
 
-      if (filedata->dynamic_info[DT_SYMTAB]
+      num_of_syms = get_num_dynamic_syms (filedata);
+
+      if (num_of_syms != 0
+         && filedata->dynamic_symbols == NULL
+         && filedata->dynamic_info[DT_SYMTAB]
          && filedata->dynamic_info[DT_SYMENT])
        {
          Elf_Internal_Phdr *seg;
-           bfd_vma vma = filedata->dynamic_info[DT_SYMTAB];
-
-           if (! get_program_headers (filedata))
-             {
-               error (_("Cannot interpret virtual addresses without program headers.\n"));
-               return FALSE;
-             }
+         bfd_vma vma = filedata->dynamic_info[DT_SYMTAB];
 
-           for (seg = filedata->program_headers;
-                seg < filedata->program_headers + filedata->file_header.e_phnum;
-                ++seg)
-             {
-               unsigned long num_of_syms;
+         if (! get_program_headers (filedata))
+           {
+             error (_("Cannot interpret virtual addresses "
+                      "without program headers.\n"));
+             return FALSE;
+           }
 
-               if (seg->p_type != PT_LOAD)
-                 continue;
+         for (seg = filedata->program_headers;
+              seg < filedata->program_headers + filedata->file_header.e_phnum;
+              ++seg)
+           {
+             if (seg->p_type != PT_LOAD)
+               continue;
 
-               if ((seg->p_offset + seg->p_filesz)
-                   > filedata->file_size)
-                 {
-                   /* See PR 21379 for a reproducer.  */
-                   error (_("Invalid PT_LOAD entry\n"));
-                   return FALSE;
-                 }
+             if (seg->p_offset + seg->p_filesz > filedata->file_size)
+               {
+                 /* See PR 21379 for a reproducer.  */
+                 error (_("Invalid PT_LOAD entry\n"));
+                 return FALSE;
+               }
 
-               if (vma >= (seg->p_vaddr & -seg->p_align)
-                   && vma <= seg->p_vaddr + seg->p_filesz
-                   && (num_of_syms = get_num_dynamic_syms (filedata)) != 0
-                   && filedata->dynamic_symbols == NULL)
-                 {
-                   /* Since we do not know how big the symbol table is,
-                      we default to reading in up to the end of PT_LOAD
-                      segment and processing that.  This is overkill, I
-                      know, but it should work.  */
-                   Elf_Internal_Shdr section;
-                   section.sh_offset = (vma - seg->p_vaddr
-                                        + seg->p_offset);
-                   section.sh_size = (num_of_syms
-                                      * filedata->dynamic_info[DT_SYMENT]);
-                   section.sh_entsize = filedata->dynamic_info[DT_SYMENT];
-                   section.sh_name = filedata->string_table_length;
-                   filedata->dynamic_symbols
-                     = GET_ELF_SYMBOLS (filedata, &section,
-                                        &filedata->num_dynamic_syms);
-                   if (filedata->dynamic_symbols == NULL
-                       || filedata->num_dynamic_syms != num_of_syms)
-                     {
-                       error (_("Corrupt DT_SYMTAB dynamic entry\n"));
-                       return FALSE;
-                     }
-                 }
-             }
-         }
-      }
+             if (vma >= (seg->p_vaddr & -seg->p_align)
+                 && vma < seg->p_vaddr + seg->p_filesz)
+               {
+                 /* Since we do not know how big the symbol table is,
+                    we default to reading in up to the end of PT_LOAD
+                    segment and processing that.  This is overkill, I
+                    know, but it should work.  */
+                 Elf_Internal_Shdr section;
+                 section.sh_offset = (vma - seg->p_vaddr
+                                      + seg->p_offset);
+                 section.sh_size = (num_of_syms
+                                    * filedata->dynamic_info[DT_SYMENT]);
+                 section.sh_entsize = filedata->dynamic_info[DT_SYMENT];
+                 section.sh_name = filedata->string_table_length;
+                 filedata->dynamic_symbols
+                   = GET_ELF_SYMBOLS (filedata, &section,
+                                      &filedata->num_dynamic_syms);
+                 if (filedata->dynamic_symbols == NULL
+                     || filedata->num_dynamic_syms != num_of_syms)
+                   {
+                     error (_("Corrupt DT_SYMTAB dynamic entry\n"));
+                     return FALSE;
+                   }
+                 break;
+               }
+           }
+       }
+    }
 
   /* Similarly find a string table.  */
   if (filedata->dynamic_strings == NULL)
@@ -10403,14 +10458,17 @@ process_dynamic_section (Filedata * filedata)
          filedata->dynamic_syminfo = (Elf_Internal_Syminfo *) malloc (syminsz);
          if (filedata->dynamic_syminfo == NULL)
            {
-             error (_("Out of memory allocating %lu byte for dynamic symbol info\n"),
+             error (_("Out of memory allocating %lu bytes "
+                      "for dynamic symbol info\n"),
                     (unsigned long) syminsz);
              return FALSE;
            }
 
-         filedata->dynamic_syminfo_nent = syminsz / sizeof (Elf_External_Syminfo);
+         filedata->dynamic_syminfo_nent
+           = syminsz / sizeof (Elf_External_Syminfo);
          for (syminfo = filedata->dynamic_syminfo, extsym = extsyminfo;
-              syminfo < filedata->dynamic_syminfo + filedata->dynamic_syminfo_nent;
+              syminfo < (filedata->dynamic_syminfo
+                         + filedata->dynamic_syminfo_nent);
               ++syminfo, ++extsym)
            {
              syminfo->si_boundto = BYTE_GET (extsym->si_boundto);
@@ -17423,18 +17481,21 @@ process_nds32_specific (Filedata * filedata)
   Elf_Internal_Shdr *sect = NULL;
 
   sect = find_section (filedata, ".nds32_e_flags");
-  if (sect != NULL)
+  if (sect != NULL && sect->sh_size >= 4)
     {
-      unsigned int *flag;
+      unsigned char *buf;
+      unsigned int flag;
 
       printf ("\nNDS32 elf flags section:\n");
-      flag = get_data (NULL, filedata, sect->sh_offset, 1,
-                      sect->sh_size, _("NDS32 elf flags section"));
+      buf = get_data (NULL, filedata, sect->sh_offset, 1, 4,
+                     _("NDS32 elf flags section"));
 
-      if (! flag)
+      if (buf == NULL)
        return FALSE;
 
-      switch ((*flag) & 0x3)
+      flag = byte_get (buf, 4);
+      free (buf);
+      switch (flag & 0x3)
        {
        case 0:
          printf ("(VEC_SIZE):\tNo entry.\n");
@@ -17642,6 +17703,8 @@ get_note_type (Filedata * filedata, unsigned e_type)
        return _("NT_ARM_HW_BREAK (AArch hardware breakpoint registers)");
       case NT_ARM_HW_WATCH:
        return _("NT_ARM_HW_WATCH (AArch hardware watchpoint registers)");
+      case NT_ARC_V2:
+       return _("NT_ARC_V2 (ARC HS accumulator/extra registers)");
       case NT_PSTATUS:
        return _("NT_PSTATUS (pstatus structure)");
       case NT_FPREGS:
@@ -20118,7 +20181,7 @@ process_object (Filedata * filedata)
 {
   bfd_boolean  have_separate_files;
   unsigned int i;
-  bfd_boolean res = TRUE;
+  bfd_boolean res;
 
   if (! get_file_header (filedata))
     {
@@ -20174,10 +20237,9 @@ process_object (Filedata * filedata)
     /* Without loaded section groups we cannot process unwind.  */
     do_unwind = FALSE;
 
-  if (process_program_headers (filedata))
-    process_dynamic_section (filedata);
-  else
-    res = FALSE;
+  res = process_program_headers (filedata);
+  if (res)
+    res = process_dynamic_section (filedata);
 
   if (! process_relocs (filedata))
     res = FALSE;
@@ -20658,7 +20720,7 @@ process_file (char * file_name)
     }
   else
     {
-      if (do_archive_index)
+      if (do_archive_index && !check_all)
        error (_("File %s is not an archive so its index cannot be displayed.\n"),
               file_name);
 
@@ -20724,9 +20786,14 @@ main (int argc, char ** argv)
   parse_args (& cmdline, argc, argv);
 
   if (optind < (argc - 1))
+    /* When displaying information for more than one file,
+       prefix the information with the file name.  */
     show_name = TRUE;
   else if (optind >= argc)
     {
+      /* Ensure that the warning is always displayed.  */
+      do_checks = TRUE;
+
       warn (_("Nothing to do.\n"));
       usage (stderr);
     }
This page took 0.033413 seconds and 4 git commands to generate.