Load CONFIG_ARM64_VA_BITS from vmlinux and set to VA_BITS_ACTUAL.

arm64_calc_VA_BITS() can't find correct VA_BITS for a qemu arm64
ramdump file:

 1. The 'vabits_actual' symbol doesn't exist
 2. There is no VA_BITS in vmcoreinfo
 3. No TCR info (from vmcoreinfo)
 4. The VA_BITS_ACTUAL not specified from command line arguements
 5. The last code try to cacl it from the symboys address such as
    swapper_pg_dir which got a wrong VA_BITS which mismatch with
    the CONFIG_ARM64_VA_BITS

Thus we try to parse CONFIG_ARM64_VA_BITS from vmlinux before
symtab_init() which would mess with the _stext_vmlinux = UNINITIALIZED
condition.

Using kernel_config_data_end symbol to load config data when available
instead of seaching the magic end string, also restructure related code.

Even further, we can always read kernel config from vmlinux, and we
can verify if the config in vmlinux match with the config in the
core file, which also indicate if the vmlinux match with the core file.

Test:
 1. Build an ARM64 kernel with CONFIG_ARM64_VA_BITS set to 39
 2. Run the kernel in qemu and capture a ramdump
    (enter qmeu console by ctrl+a c, enter dump-guest-memory -z /home/abc/ramdump.elf)
 3. Load the ramdump with crash

before:
  crash can't load the dump due to VA_BITS set to 38 by arm64_calc_VA_BITS()

after:
  crash set VA_BITS_ACTUAL to CONFIG_ARM64_VA_BITS (39) thus it works
fine

Signed-off-by: Hong YANG <hong.yang3@nio.com>
---
 arm64.c   |   7 +
 defs.h    |   5 +
 kernel.c  | 434 ++++++++++++++++++++++++++++++++++--------------------
 main.c    |   1 +
 symbols.c |   2 +-
 5 files changed, 291 insertions(+), 158 deletions(-)

diff --git a/arm64.c b/arm64.c
index ef4a2b8..4682d08 100644
--- a/arm64.c
+++ b/arm64.c
@@ -322,6 +322,13 @@ arm64_init(int when)
 
            ms = machdep->machspec;
 
+           /* st->CONFIG_ARM64_VA_BITS loaded from read_in_kernel_config_from_vmlinux() */
+           ms->CONFIG_ARM64_VA_BITS = st->CONFIG_ARM64_VA_BITS;
+           if (ms->CONFIG_ARM64_VA_BITS && !ms->VA_BITS_ACTUAL) {
+                 error(WARNING, "Set VA_BITS_ACTUAL to CONFIG_ARM64_VA_BITS from vmlinux\n");
+                 ms->VA_BITS_ACTUAL = ms->CONFIG_ARM64_VA_BITS;
+           }
+
            /*
             * The st->_stext_vmlinux is needed in arm64_init(PRE_GDB) when a
             * dumpfile does not have vmcoreinfo and we use -m vabits_actual
diff --git a/defs.h b/defs.h
index 4cf169c..412c52e 100644
--- a/defs.h
+++ b/defs.h
@@ -2932,6 +2932,9 @@ struct symbol_table_data {
      int kernel_symbol_type;
      ulong linux_banner_vmlinux;
      struct syment *mod_symname_hash[SYMNAME_HASH];
+
+     /* Loaded from vmlinux */
+     ulong CONFIG_ARM64_VA_BITS;
 };
 
 /* flags for st */
@@ -6336,6 +6339,7 @@ struct remote_file {
 ulonglong xen_m2p(ulonglong);
 
 void read_in_kernel_config(int);
+void read_in_kernel_config_from_vmlinux(int);
 
 #define IKCFG_INIT   (0)
 #define IKCFG_READ   (1)
@@ -6353,6 +6357,7 @@ enum {
 #define MAGIC_START  "IKCFG_ST"
 #define MAGIC_END    "IKCFG_ED"
 #define MAGIC_SIZE   (sizeof(MAGIC_START) - 1)
+#define GZIP_HEADER_SIZE     10
 
 /*
  *  dev.c
diff --git a/kernel.c b/kernel.c
index b8d3b79..ba5faf8 100644
--- a/kernel.c
+++ b/kernel.c
@@ -78,6 +78,8 @@ static int read_xc_p2m(ulonglong, void *, long);
 static void read_p2m(ulong, int, void *);
 static int search_mapping_page(ulong, ulong *, ulong *, ulong *);
 static void read_in_kernel_config_err(int, char *);
+static void kernel_config_scan(int, char *);
+static int kernel_config_deflat(char *, int, char *, int);
 static void BUG_bytes_init(void);
 static int BUG_x86(void);
 static int BUG_x86_64(void);
@@ -10627,13 +10629,14 @@ static char *ikconfig[] = {
         "CONFIG_HZ",
      "CONFIG_DEBUG_BUGVERBOSE",
      "CONFIG_DEBUG_INFO_REDUCED",
+     "CONFIG_ARM64_VA_BITS",
         NULL,
 };
 
 void
 read_in_kernel_config(int command)
 {
-     struct syment *sp;
+     struct syment *sp, *sp_end;
      int ii, jj, ret, end, found=0;
      unsigned long size, bufsz;
      uint64_t magic;
@@ -10654,108 +10657,94 @@ read_in_kernel_config(int command)
            return;
      }
      
-     /* We don't know how large IKCONFIG is, so we start with 
-      * 32k, if we can't find MAGIC_END assume we didn't read 
-      * enough, double it and try again.
-      */
-     ii = 32;
+     if ((sp_end = symbol_search("kernel_config_data_end")) != NULL) {
+           size = sp_end->value - sp->value;
 
+           if ((buf = (char *)malloc(size)) == NULL)
+           {
+                 error(WARNING, "cannot malloc IKCONFIG input buffer\n");
+                 return;
+           }
+           if (!readmem(sp->value, KVADDR, buf, size,
+                 "kernel_config_data", RETURN_ON_ERROR)) {
+                 error(WARNING, "cannot read kernel_config_data\n");
+                 goto out2;
+           }
+           bufsz = size;
+           head = buf + GZIP_HEADER_SIZE;
+     } else {
+           /* We don't know how large IKCONFIG is, so we start with
+            * 32k, if we can't find MAGIC_END assume we didn't read
+            * enough, double it and try again.
+            */
+           ii = 32;
 again:
-     size = ii * 1024;
+           size = ii * 1024;
 
-     if ((buf = (char *)malloc(size)) == NULL) {
-           error(WARNING, "cannot malloc IKCONFIG input buffer\n");
-           return;
-     }
-     
-        if (!readmem(sp->value, KVADDR, buf, size,
-            "kernel_config_data", RETURN_ON_ERROR)) {
-           error(WARNING, "cannot read kernel_config_data\n");
-           goto out2;
-     }
-           
-     /* Find the start */
-     if (strstr(buf, MAGIC_START))
-           head = buf + MAGIC_SIZE + 10; /* skip past MAGIC_START and gzip header */
-     else {
-           /*
-            *  Later versions put the magic number before the compressed data.
-            */
-           if (readmem(sp->value - 8, KVADDR, &magic, 8,
-                     "kernel_config_data MAGIC_START", RETURN_ON_ERROR) &&
-               STRNEQ(&magic, MAGIC_START)) {
-                 head = buf + 10;
-           } else {
-                 error(WARNING, "could not find MAGIC_START!\n");
+           if ((buf = (char *)malloc(size)) == NULL) {
+                 error(WARNING, "cannot malloc IKCONFIG input buffer\n");
+                 return;
+           }
+
+           if (!readmem(sp->value, KVADDR, buf, size,
+                 "kernel_config_data", RETURN_ON_ERROR)) {
+                 error(WARNING, "cannot read kernel_config_data\n");
                  goto out2;
            }
-     }
 
-     tail = head;
+           /* Find the start */
+           if (strstr(buf, MAGIC_START))
+                 head = buf + MAGIC_SIZE + GZIP_HEADER_SIZE; /* skip past MAGIC_START and gzip header */
+           else {
+                 /*
+                  *  Later versions put the magic number before the compressed data.
+                  */
+                 if (readmem(sp->value - MAGIC_SIZE, KVADDR, &magic, MAGIC_SIZE,
+                       "kernel_config_data MAGIC_START", RETURN_ON_ERROR) &&
+                       STRNEQ(&magic, MAGIC_START)) {
+                       head = buf + GZIP_HEADER_SIZE;
+                 } else {
+                       error(WARNING, "could not find MAGIC_START!\n");
+                       goto out2;
+                 }
+           }
 
-     end = strlen(MAGIC_END);
+           tail = head;
+           end = strlen(MAGIC_END);
 
-     /* Find the end*/
-     while (tail < (buf + (size - 1))) {
-           
-           if (strncmp(tail, MAGIC_END, end)==0) {
-                 found = 1;
-                 break;
+           /* Find the end*/
+           while (tail < (buf + (size - 1))) {
+                 if (strncmp(tail, MAGIC_END, end)==0) {
+                       found = 1;
+                       break;
+                 }
+                 tail++;
            }
-           tail++;
-     }
 
-     if (found) {
-           bufsz = tail - head;
-           size = 10 * bufsz;
-           if ((uncomp = (char *)malloc(size)) == NULL) {
-                 error(WARNING, "cannot malloc IKCONFIG output buffer\n");
-                 goto out2;
-           }
-     } else {
-           if (ii > 512) {
-                 error(WARNING, "could not find MAGIC_END!\n");
-                 goto out2;
+           if (found) {
+                 bufsz = tail - head;
            } else {
-                 free(buf);
-                 ii *= 2;
-                 goto again;
+                 if (ii > 512) {
+                       error(WARNING, "could not find MAGIC_END!\n");
+                       goto out2;
+                 } else {
+                       free(buf);
+                       ii *= 2;
+                       goto again;
+                 }
            }
      }
 
-
-     /* initialize zlib */
-     stream.next_in = (Bytef *)head;
-     stream.avail_in = (uInt)bufsz;
-
-     stream.next_out = (Bytef *)uncomp;
-     stream.avail_out = (uInt)size;
-
-     stream.zalloc = NULL;
-     stream.zfree = NULL;
-     stream.opaque = NULL;
-
-     ret = inflateInit2(&stream, -MAX_WBITS);
-     if (ret != Z_OK) {
-           read_in_kernel_config_err(ret, "initialize");
-           goto out1;
+     size = 10 * bufsz;
+     if ((uncomp = (char *)malloc(size)) == NULL) {
+           error(WARNING, "cannot malloc IKCONFIG output buffer\n");
+           goto out2;
      }
 
-     ret = inflate(&stream, Z_FINISH);
-
-     if (ret != Z_STREAM_END) {
-           inflateEnd(&stream);
-           if (ret == Z_NEED_DICT || 
-              (ret == Z_BUF_ERROR && stream.avail_in == 0)) {
-                 read_in_kernel_config_err(Z_DATA_ERROR, "uncompress");
-                 goto out1;
-           }
-           read_in_kernel_config_err(ret, "uncompress");
+     if (kernel_config_deflat(buf, bufsz, uncomp, size) != 0) {
+           error(WARNING, "kernel_config_deflat failed\n");
            goto out1;
      }
-     size = stream.total_out;
-
-     ret = inflateEnd(&stream);
 
      pos = uncomp;
 
@@ -10785,88 +10774,92 @@ again:
            goto out1;
      }
 
-     do {
-           ret = sscanf(pos, "%511[^\n]\n%n", line, &ii);
-           if (ret > 0) {
-                 if ((command == IKCFG_READ) || CRASHDEBUG(8))
-                       fprintf(fp, "%s\n", line);
+     kernel_config_scan(command, pos);
+out1:
+     free(uncomp);
+out2:
+     free(buf);
 
-                 pos += ii;
+     return;
+}
 
-                 ln = line;
-                       
-                 /* skip leading whitespace */
-                 while (whitespace(*ln))
-                       ln++;
-
-                 /* skip comments -- except when looking for "not set" */
-                 if (*ln == '#') {
-                       if (strstr(ln, "CONFIG_DEBUG_BUGVERBOSE") &&
-                           strstr(ln, "not set"))
-                             kt->flags |= BUGVERBOSE_OFF;
-                       if (strstr(ln, "CONFIG_DEBUG_INFO_REDUCED"))
-                             if (CRASHDEBUG(1))
-                                   error(INFO, "%s\n", ln);
-                       continue;
-                 }
+void
+read_in_kernel_config_from_vmlinux(int command)
+{
+     int ret;
+     bfd_vma config_data_addr = 0;
+     bfd_vma config_data_end_addr = 0;
 
-                 /* Find '=' */
-                 if ((head = strchr(ln, '=')) != NULL) {
-                       *head = '\0';
-                       val = head + 1;
+     if (!st->bfd && (st->bfd = bfd_openr(pc->namelist, NULL)) == NULL)
+           error(FATAL, "cannot open object file: %s\n", pc->namelist);
 
-                       head--;
+     if (!bfd_check_format(st->bfd, bfd_object)) {
+           error(WARNING, "File '%s' is not a supported object file\n", pc->namelist);
+           bfd_close(st->bfd);
+           st->bfd = NULL;
+           return;
+     }
 
-                       /* skip trailing whitespace */
-                       while (whitespace(*head)) {
-                             *head = '\0';
-                             head--;
-                       }
+     asymbol *config_sym = NULL;
+     void *minisyms;
+     unsigned int symsize;
+     long symcount = bfd_read_minisymbols(st->bfd, FALSE, &minisyms, &symsize);
+
+     if (symcount > 0) {
+           for (long i = 0; i < symcount; i++) {
+                 asymbol *sym;
+                 sym = bfd_minisymbol_to_symbol(st->bfd, FALSE, minisyms + i * symsize, NULL);
+                 const char *name = bfd_asymbol_name(sym);
+                 if (strcmp(name, "kernel_config_data") == 0) {
+                       config_data_addr = bfd_asymbol_value(sym);
+                       config_sym = sym;
+                 } else if (strcmp(name, "kernel_config_data_end") == 0) {
+                       config_data_end_addr = bfd_asymbol_value(sym);
+                       config_sym = sym;
+                       break;
+                 }
+           }
+           free(minisyms);
+     }
 
-                       /* skip whitespace */
-                       while (whitespace(*val))
-                             val++;
+     if (!config_data_addr || !config_data_end_addr) {
+           error(FATAL, "kernel_config_data/kernel_config_data_end symbol not found in %s\n", pc->namelist);
+     } else {
+           if (CRASHDEBUG(1))
+                 error(INFO, "Found config data: (0x%lx - 0x%lx)\n",
+                       config_data_addr, config_data_end_addr);
+     }
 
-                 } else /* Bad line, skip it */
-                       continue;
+     char *buf = NULL;
+     size_t size = config_data_end_addr - config_data_addr;
+     size_t bufsz = size;
+     if ((buf = (char *)malloc(size)) == NULL) {
+           error(WARNING, "cannot malloc IKCONFIG input buffer\n");
+           return;
+     }
 
-                 if (command != IKCFG_INIT)
-                       continue;
+     asection *section = config_sym->section;
+     bfd_vma offset = config_data_addr - section->vma;
+     if (CRASHDEBUG(1))
+           error(INFO, "Reading section %s vma 0x%lx, offset x%lx)\n",
+                 section->name, section->vma, offset);
+     if (!bfd_get_section_contents(st->bfd, section, buf, offset, size)) {
+           error(FATAL, "Failed to read section contents: %s\n", bfd_errmsg(bfd_get_error()));
+     }
 
-                 for (jj = 0; ikconfig[jj]; jj++) {
-                        if (STREQ(ln, ikconfig[jj])) {
-
-                             if (STREQ(ln, "CONFIG_NR_CPUS")) {
-                                   kt->kernel_NR_CPUS = atoi(val);
-                                   if (CRASHDEBUG(1)) 
-                                         error(INFO, 
-                                             "CONFIG_NR_CPUS: %d\n",
-                                               kt->kernel_NR_CPUS);
-
-                             } else if (STREQ(ln, "CONFIG_PGTABLE_4")) {
-                                   machdep->flags |= VM_4_LEVEL;
-                                   if (CRASHDEBUG(1))
-                                         error(INFO, "CONFIG_PGTABLE_4\n");
-
-                             } else if (STREQ(ln, "CONFIG_HZ")) {
-                                   machdep->hz = atoi(val);
-                                   if (CRASHDEBUG(1))
-                                         error(INFO, 
-                                             "CONFIG_HZ: %d\n",
-                                               machdep->hz);
-
-                             } else if (STREQ(ln, "CONFIG_DEBUG_INFO_REDUCED")) {
-                                   if (STREQ(val, "y")) {
-                                         error(WARNING, 
-                                             "CONFIG_DEBUG_INFO_REDUCED=y\n");
-                                         no_debugging_data(INFO);
-                                   }
-                             }
-                       }
-                 }
-           }
-     } while (ret > 0);
+     char *uncomp = NULL;
+     size = 10 * size;
+     if ((uncomp = (char *)malloc(size)) == NULL) {
+           error(WARNING, "cannot malloc IKCONFIG output buffer\n");
+           goto out2;
+     }
+
+     if (kernel_config_deflat(buf, bufsz, uncomp, size) != 0) {
+           error(WARNING, "kernel_config_deflat failed\n");
+           goto out1;
+     }
 
+     kernel_config_scan(command, uncomp);
 out1:
      free(uncomp);
 out2:
@@ -10875,6 +10868,133 @@ out2:
      return;
 }
 
+static int
+kernel_config_deflat(char *buf, int bufsz, char *uncomp, int size)
+{
+     int ret = -1;
+
+     /* initialize zlib */
+     z_stream stream;
+     stream.next_in = (Bytef *)buf + GZIP_HEADER_SIZE;
+     stream.avail_in = (uInt)bufsz;
+     stream.next_out = (Bytef *)uncomp;
+     stream.avail_out = (uInt)size;
+     stream.zalloc = NULL;
+     stream.zfree = NULL;
+     stream.opaque = NULL;
+
+     ret = inflateInit2(&stream, -MAX_WBITS);
+     if (ret != Z_OK) {
+           read_in_kernel_config_err(ret, "initialize");
+           return -1;
+     }
+
+     ret = inflate(&stream, Z_FINISH);
+
+     if (ret != Z_STREAM_END) {
+           inflateEnd(&stream);
+           if (ret == Z_NEED_DICT ||
+              (ret == Z_BUF_ERROR && stream.avail_in == 0)) {
+                 read_in_kernel_config_err(Z_DATA_ERROR, "uncompress");
+                 return -1;
+           }
+           read_in_kernel_config_err(ret, "uncompress");
+           return -1;
+     }
+     size = stream.total_out;
+     ret = inflateEnd(&stream);
+
+     return ret;
+}
+
+static void
+kernel_config_scan(int command, char *pos)
+{
+     int ii, jj, ret;
+     char *ln, *head, *val;
+     char line[512];
+
+     while ((ret = sscanf(pos, "%511[^\n]\n%n", line, &ii)) > 0) {
+           if ((command == IKCFG_READ) || CRASHDEBUG(8))
+                 fprintf(fp, "%s\n", line);
+
+           pos += ii;
+           ln = line;
+
+           /* skip leading whitespace */
+           while (whitespace(*ln))
+                 ln++;
+
+           /* skip comments -- except when looking for "not set" */
+           if (*ln == '#') {
+                 if (strstr(ln, "CONFIG_DEBUG_BUGVERBOSE") &&
+                     strstr(ln, "not set"))
+                       kt->flags |= BUGVERBOSE_OFF;
+                 if (strstr(ln, "CONFIG_DEBUG_INFO_REDUCED"))
+                       if (CRASHDEBUG(1))
+                             error(INFO, "%s\n", ln);
+                 continue;
+           }
+
+           /* Find '=' */
+           if ((head = strchr(ln, '=')) != NULL) {
+                 *head = '\0';
+                 val = head + 1;
+                 head--;
+
+                 /* skip trailing whitespace */
+                 while (whitespace(*head)) {
+                       *head = '\0';
+                       head--;
+                 }
+
+                 /* skip whitespace */
+                 while (whitespace(*val))
+                       val++;
+           } else /* Bad line, skip it */
+                 continue;
+
+           if (command != IKCFG_INIT)
+                 continue;
+
+           for (jj = 0; ikconfig[jj]; jj++) {
+                  if (STREQ(ln, ikconfig[jj])) {
+
+                       if (STREQ(ln, "CONFIG_NR_CPUS")) {
+                             kt->kernel_NR_CPUS = atoi(val);
+                             if (CRASHDEBUG(1))
+                                   error(INFO,
+                                       "CONFIG_NR_CPUS: %d\n",
+                                         kt->kernel_NR_CPUS);
+                       } else if (STREQ(ln, "CONFIG_PGTABLE_4")) {
+                             machdep->flags |= VM_4_LEVEL;
+                             if (CRASHDEBUG(1))
+                                   error(INFO, "CONFIG_PGTABLE_4\n");
+
+                       } else if (STREQ(ln, "CONFIG_HZ")) {
+                             machdep->hz = atoi(val);
+                             if (CRASHDEBUG(1))
+                                   error(INFO,
+                                       "CONFIG_HZ: %d\n",
+                                         machdep->hz);
+                       } else if (STREQ(ln, "CONFIG_DEBUG_INFO_REDUCED")) {
+                             if (STREQ(val, "y")) {
+                                   error(WARNING,
+                                       "CONFIG_DEBUG_INFO_REDUCED=y\n");
+                                   no_debugging_data(INFO);
+                             }
+                       } else if (STREQ(ln, "CONFIG_ARM64_VA_BITS")) {
+                             st->CONFIG_ARM64_VA_BITS = atol(val);
+                             if (CRASHDEBUG(1))
+                                   error(INFO,
+                                       "CONFIG_ARM64_VA_BITS: %lu\n",
+                                         st->CONFIG_ARM64_VA_BITS);
+                       }
+                 }
+           }
+     }
+}
+
 static void
 read_in_kernel_config_err(int e, char *msg)
 {
diff --git a/main.c b/main.c
index 71bcc15..a93d4e5 100644
--- a/main.c
+++ b/main.c
@@ -707,6 +707,7 @@ main(int argc, char **argv)
         cmdline_init();
         mem_init();
            hq_init();
+     read_in_kernel_config_from_vmlinux(IKCFG_INIT);
      machdep_init(PRE_SYMTAB);
         symtab_init();
      paravirt_init();
diff --git a/symbols.c b/symbols.c
index e30fafe..358df13 100644
--- a/symbols.c
+++ b/symbols.c
@@ -206,7 +206,7 @@ symtab_init(void)
      asymbol *sort_x;
      asymbol *sort_y;
 
-     if ((st->bfd = bfd_openr(pc->namelist, NULL)) == NULL) 
+     if (!st->bfd && (st->bfd = bfd_openr(pc->namelist, NULL)) == NULL)
            error(FATAL, "cannot open object file: %s\n", pc->namelist);
 
      if (!bfd_check_format_matches(st->bfd, bfd_object, &matching))
-- 
2.43.0