f6faf3
From ce6e8556a8f93327d6de0446f21ac5e549861d82 Mon Sep 17 00:00:00 2001
f6faf3
Message-Id: <ce6e8556a8f93327d6de0446f21ac5e549861d82.1573552234.git.pmatilai@redhat.com>
f6faf3
From: Mark Wielaard <mark@klomp.org>
f6faf3
Date: Mon, 17 Jun 2019 11:23:24 +0200
f6faf3
Subject: [PATCH 1/3] debugedit: Refactor reading/writing of relocated values.
f6faf3
f6faf3
This refactors the reading and writing of relocated values into seperate
f6faf3
helper functions (setup_relbuf and update_rela_data). It will be easier
f6faf3
to reuse this code in case we want to read/write relocated values in other
f6faf3
sections than DEBUG_INFO. The only functional change is that we explicitly
f6faf3
track whether the relocation data is updated, and only explicitly update
f6faf3
and write out the relocation data if so. In the case there were no strp
f6faf3
or stmt updates, there will also not be any relocation updates, even if
f6faf3
there is relocation data available.
f6faf3
f6faf3
All new debugedit testcases pass before and after this refactoring.
f6faf3
---
f6faf3
 tools/debugedit.c | 395 +++++++++++++++++++++++++---------------------
f6faf3
 1 file changed, 216 insertions(+), 179 deletions(-)
f6faf3
f6faf3
diff --git a/tools/debugedit.c b/tools/debugedit.c
f6faf3
index 4be85b979..cf9cc3ca9 100644
f6faf3
--- a/tools/debugedit.c
f6faf3
+++ b/tools/debugedit.c
f6faf3
@@ -401,13 +401,18 @@ dwarf2_write_be32 (unsigned char *p, uint32_t v)
f6faf3
    relend). Might just update the addend. So relocations need to be
f6faf3
    updated at the end.  */
f6faf3
 
f6faf3
+static bool rel_updated;
f6faf3
+
f6faf3
 #define do_write_32_relocated(ptr,val) ({ \
f6faf3
   if (relptr && relptr < relend && relptr->ptr == ptr)	\
f6faf3
     {							\
f6faf3
       if (reltype == SHT_REL)				\
f6faf3
 	do_write_32 (ptr, val - relptr->addend);	\
f6faf3
       else						\
f6faf3
-	relptr->addend = val;				\
f6faf3
+	{						\
f6faf3
+	  relptr->addend = val;				\
f6faf3
+	  rel_updated = true;				\
f6faf3
+	}						\
f6faf3
     }							\
f6faf3
   else							\
f6faf3
     do_write_32 (ptr,val);				\
f6faf3
@@ -418,14 +423,18 @@ dwarf2_write_be32 (unsigned char *p, uint32_t v)
f6faf3
   ptr += 4;			       \
f6faf3
 })
f6faf3
 
f6faf3
-static struct
f6faf3
+typedef struct debug_section
f6faf3
   {
f6faf3
     const char *name;
f6faf3
     unsigned char *data;
f6faf3
     Elf_Data *elf_data;
f6faf3
     size_t size;
f6faf3
     int sec, relsec;
f6faf3
-  } debug_sections[] =
f6faf3
+    REL *relbuf;
f6faf3
+    REL *relend;
f6faf3
+  } debug_section;
f6faf3
+
f6faf3
+static debug_section debug_sections[] =
f6faf3
   {
f6faf3
 #define DEBUG_INFO	0
f6faf3
 #define DEBUG_ABBREV	1
f6faf3
@@ -458,6 +467,201 @@ static struct
f6faf3
     { NULL, NULL, NULL, 0, 0, 0 }
f6faf3
   };
f6faf3
 
f6faf3
+static int
f6faf3
+rel_cmp (const void *a, const void *b)
f6faf3
+{
f6faf3
+  REL *rela = (REL *) a, *relb = (REL *) b;
f6faf3
+
f6faf3
+  if (rela->ptr < relb->ptr)
f6faf3
+    return -1;
f6faf3
+
f6faf3
+  if (rela->ptr > relb->ptr)
f6faf3
+    return 1;
f6faf3
+
f6faf3
+  return 0;
f6faf3
+}
f6faf3
+
f6faf3
+/* Returns a malloced REL array, or NULL when there are no relocations
f6faf3
+   for this section.  When there are relocations, will setup relend,
f6faf3
+   as the last REL, and reltype, as SHT_REL or SHT_RELA.  */
f6faf3
+static void
f6faf3
+setup_relbuf (DSO *dso, debug_section *sec, int *reltype)
f6faf3
+{
f6faf3
+  int ndx, maxndx;
f6faf3
+  GElf_Rel rel;
f6faf3
+  GElf_Rela rela;
f6faf3
+  GElf_Sym sym;
f6faf3
+  GElf_Addr base = dso->shdr[sec->sec].sh_addr;
f6faf3
+  Elf_Data *symdata = NULL;
f6faf3
+  int rtype;
f6faf3
+  REL *relbuf;
f6faf3
+  Elf_Scn *scn;
f6faf3
+  Elf_Data *data;
f6faf3
+  int i = sec->relsec;
f6faf3
+
f6faf3
+  /* No relocations, or did we do this already? */
f6faf3
+  if (i == 0 || sec->relbuf != NULL)
f6faf3
+    {
f6faf3
+      relptr = sec->relbuf;
f6faf3
+      relend = sec->relend;
f6faf3
+      return;
f6faf3
+    }
f6faf3
+
f6faf3
+  scn = dso->scn[i];
f6faf3
+  data = elf_getdata (scn, NULL);
f6faf3
+  assert (data != NULL && data->d_buf != NULL);
f6faf3
+  assert (elf_getdata (scn, data) == NULL);
f6faf3
+  assert (data->d_off == 0);
f6faf3
+  assert (data->d_size == dso->shdr[i].sh_size);
f6faf3
+  maxndx = dso->shdr[i].sh_size / dso->shdr[i].sh_entsize;
f6faf3
+  relbuf = malloc (maxndx * sizeof (REL));
f6faf3
+  *reltype = dso->shdr[i].sh_type;
f6faf3
+  if (relbuf == NULL)
f6faf3
+    error (1, errno, "%s: Could not allocate memory", dso->filename);
f6faf3
+
f6faf3
+  symdata = elf_getdata (dso->scn[dso->shdr[i].sh_link], NULL);
f6faf3
+  assert (symdata != NULL && symdata->d_buf != NULL);
f6faf3
+  assert (elf_getdata (dso->scn[dso->shdr[i].sh_link], symdata) == NULL);
f6faf3
+  assert (symdata->d_off == 0);
f6faf3
+  assert (symdata->d_size == dso->shdr[dso->shdr[i].sh_link].sh_size);
f6faf3
+
f6faf3
+  for (ndx = 0, relend = relbuf; ndx < maxndx; ++ndx)
f6faf3
+    {
f6faf3
+      if (dso->shdr[i].sh_type == SHT_REL)
f6faf3
+	{
f6faf3
+	  gelf_getrel (data, ndx, &rel;;
f6faf3
+	  rela.r_offset = rel.r_offset;
f6faf3
+	  rela.r_info = rel.r_info;
f6faf3
+	  rela.r_addend = 0;
f6faf3
+	}
f6faf3
+      else
f6faf3
+	gelf_getrela (data, ndx, &rela);
f6faf3
+      gelf_getsym (symdata, ELF64_R_SYM (rela.r_info), &sym);
f6faf3
+      /* Relocations against section symbols are uninteresting in REL.  */
f6faf3
+      if (dso->shdr[i].sh_type == SHT_REL && sym.st_value == 0)
f6faf3
+	continue;
f6faf3
+      /* Only consider relocations against .debug_str, .debug_line
f6faf3
+	 and .debug_abbrev.  */
f6faf3
+      if (sym.st_shndx != debug_sections[DEBUG_STR].sec
f6faf3
+	  && sym.st_shndx != debug_sections[DEBUG_LINE].sec
f6faf3
+	  && sym.st_shndx != debug_sections[DEBUG_ABBREV].sec)
f6faf3
+	continue;
f6faf3
+      rela.r_addend += sym.st_value;
f6faf3
+      rtype = ELF64_R_TYPE (rela.r_info);
f6faf3
+      switch (dso->ehdr.e_machine)
f6faf3
+	{
f6faf3
+	case EM_SPARC:
f6faf3
+	case EM_SPARC32PLUS:
f6faf3
+	case EM_SPARCV9:
f6faf3
+	  if (rtype != R_SPARC_32 && rtype != R_SPARC_UA32)
f6faf3
+	    goto fail;
f6faf3
+	  break;
f6faf3
+	case EM_386:
f6faf3
+	  if (rtype != R_386_32)
f6faf3
+	    goto fail;
f6faf3
+	  break;
f6faf3
+	case EM_PPC:
f6faf3
+	case EM_PPC64:
f6faf3
+	  if (rtype != R_PPC_ADDR32 && rtype != R_PPC_UADDR32)
f6faf3
+	    goto fail;
f6faf3
+	  break;
f6faf3
+	case EM_S390:
f6faf3
+	  if (rtype != R_390_32)
f6faf3
+	    goto fail;
f6faf3
+	  break;
f6faf3
+	case EM_IA_64:
f6faf3
+	  if (rtype != R_IA64_SECREL32LSB)
f6faf3
+	    goto fail;
f6faf3
+	  break;
f6faf3
+	case EM_X86_64:
f6faf3
+	  if (rtype != R_X86_64_32)
f6faf3
+	    goto fail;
f6faf3
+	  break;
f6faf3
+	case EM_ALPHA:
f6faf3
+	  if (rtype != R_ALPHA_REFLONG)
f6faf3
+	    goto fail;
f6faf3
+	  break;
f6faf3
+#if defined(EM_AARCH64) && defined(R_AARCH64_ABS32)
f6faf3
+	case EM_AARCH64:
f6faf3
+	  if (rtype != R_AARCH64_ABS32)
f6faf3
+	    goto fail;
f6faf3
+	  break;
f6faf3
+#endif
f6faf3
+	case EM_68K:
f6faf3
+	  if (rtype != R_68K_32)
f6faf3
+	    goto fail;
f6faf3
+	  break;
f6faf3
+#if defined(EM_RISCV) && defined(R_RISCV_32)
f6faf3
+	case EM_RISCV:
f6faf3
+	  if (rtype != R_RISCV_32)
f6faf3
+	    goto fail;
f6faf3
+	  break;
f6faf3
+#endif
f6faf3
+	default:
f6faf3
+	fail:
f6faf3
+	  error (1, 0, "%s: Unhandled relocation %d in %s section",
f6faf3
+		 dso->filename, rtype, sec->name);
f6faf3
+	}
f6faf3
+      relend->ptr = sec->data
f6faf3
+	+ (rela.r_offset - base);
f6faf3
+      relend->addend = rela.r_addend;
f6faf3
+      relend->ndx = ndx;
f6faf3
+      ++(relend);
f6faf3
+    }
f6faf3
+  if (relbuf == relend)
f6faf3
+    {
f6faf3
+      free (relbuf);
f6faf3
+      relbuf = NULL;
f6faf3
+      relend = NULL;
f6faf3
+    }
f6faf3
+  else
f6faf3
+    qsort (relbuf, relend - relbuf, sizeof (REL), rel_cmp);
f6faf3
+
f6faf3
+  sec->relbuf = relbuf;
f6faf3
+  sec->relend = relend;
f6faf3
+  relptr = relbuf;
f6faf3
+}
f6faf3
+
f6faf3
+/* Updates SHT_RELA section associated with the given section based on
f6faf3
+   the relbuf data. The relbuf data is freed at the end.  */
f6faf3
+static void
f6faf3
+update_rela_data (DSO *dso, struct debug_section *sec)
f6faf3
+{
f6faf3
+  Elf_Data *symdata;
f6faf3
+  int relsec_ndx = sec->relsec;
f6faf3
+  Elf_Data *data = elf_getdata (dso->scn[relsec_ndx], NULL);
f6faf3
+  symdata = elf_getdata (dso->scn[dso->shdr[relsec_ndx].sh_link],
f6faf3
+			 NULL);
f6faf3
+
f6faf3
+  relptr = sec->relbuf;
f6faf3
+  relend = sec->relend;
f6faf3
+  while (relptr < relend)
f6faf3
+    {
f6faf3
+      GElf_Sym sym;
f6faf3
+      GElf_Rela rela;
f6faf3
+      int ndx = relptr->ndx;
f6faf3
+
f6faf3
+      if (gelf_getrela (data, ndx, &rela) == NULL)
f6faf3
+	error (1, 0, "Couldn't get relocation: %s",
f6faf3
+	       elf_errmsg (-1));
f6faf3
+
f6faf3
+      if (gelf_getsym (symdata, GELF_R_SYM (rela.r_info),
f6faf3
+		       &sym) == NULL)
f6faf3
+	error (1, 0, "Couldn't get symbol: %s", elf_errmsg (-1));
f6faf3
+
f6faf3
+      rela.r_addend = relptr->addend - sym.st_value;
f6faf3
+
f6faf3
+      if (gelf_update_rela (data, ndx, &rela) == 0)
f6faf3
+	error (1, 0, "Couldn't update relocations: %s",
f6faf3
+	       elf_errmsg (-1));
f6faf3
+
f6faf3
+      ++relptr;
f6faf3
+    }
f6faf3
+  elf_flagdata (data, ELF_C_SET, ELF_F_DIRTY);
f6faf3
+
f6faf3
+  free (sec->relbuf);
f6faf3
+}
f6faf3
+
f6faf3
 struct abbrev_attr
f6faf3
   {
f6faf3
     unsigned int attr;
f6faf3
@@ -1743,20 +1947,6 @@ edit_attributes (DSO *dso, unsigned char *ptr, struct abbrev_tag *t, int phase)
f6faf3
   return ptr;
f6faf3
 }
f6faf3
 
f6faf3
-static int
f6faf3
-rel_cmp (const void *a, const void *b)
f6faf3
-{
f6faf3
-  REL *rela = (REL *) a, *relb = (REL *) b;
f6faf3
-
f6faf3
-  if (rela->ptr < relb->ptr)
f6faf3
-    return -1;
f6faf3
-
f6faf3
-  if (rela->ptr > relb->ptr)
f6faf3
-    return 1;
f6faf3
-
f6faf3
-  return 0;
f6faf3
-}
f6faf3
-
f6faf3
 static int
f6faf3
 line_rel_cmp (const void *a, const void *b)
f6faf3
 {
f6faf3
@@ -1871,132 +2061,7 @@ edit_dwarf2 (DSO *dso)
f6faf3
       htab_t abbrev;
f6faf3
       struct abbrev_tag tag, *t;
f6faf3
       int phase;
f6faf3
-      REL *relbuf = NULL;
f6faf3
-
f6faf3
-      if (debug_sections[DEBUG_INFO].relsec)
f6faf3
-	{
f6faf3
-	  int ndx, maxndx;
f6faf3
-	  GElf_Rel rel;
f6faf3
-	  GElf_Rela rela;
f6faf3
-	  GElf_Sym sym;
f6faf3
-	  GElf_Addr base = dso->shdr[debug_sections[DEBUG_INFO].sec].sh_addr;
f6faf3
-	  Elf_Data *symdata = NULL;
f6faf3
-	  int rtype;
f6faf3
-
f6faf3
-	  i = debug_sections[DEBUG_INFO].relsec;
f6faf3
-	  scn = dso->scn[i];
f6faf3
-	  data = elf_getdata (scn, NULL);
f6faf3
-	  assert (data != NULL && data->d_buf != NULL);
f6faf3
-	  assert (elf_getdata (scn, data) == NULL);
f6faf3
-	  assert (data->d_off == 0);
f6faf3
-	  assert (data->d_size == dso->shdr[i].sh_size);
f6faf3
-	  maxndx = dso->shdr[i].sh_size / dso->shdr[i].sh_entsize;
f6faf3
-	  relbuf = malloc (maxndx * sizeof (REL));
f6faf3
-	  reltype = dso->shdr[i].sh_type;
f6faf3
-	  if (relbuf == NULL)
f6faf3
-	    error (1, errno, "%s: Could not allocate memory", dso->filename);
f6faf3
-
f6faf3
-	  symdata = elf_getdata (dso->scn[dso->shdr[i].sh_link], NULL);
f6faf3
-	  assert (symdata != NULL && symdata->d_buf != NULL);
f6faf3
-	  assert (elf_getdata (dso->scn[dso->shdr[i].sh_link], symdata)
f6faf3
-		  == NULL);
f6faf3
-	  assert (symdata->d_off == 0);
f6faf3
-	  assert (symdata->d_size
f6faf3
-		  == dso->shdr[dso->shdr[i].sh_link].sh_size);
f6faf3
-
f6faf3
-	  for (ndx = 0, relend = relbuf; ndx < maxndx; ++ndx)
f6faf3
-	    {
f6faf3
-	      if (dso->shdr[i].sh_type == SHT_REL)
f6faf3
-		{
f6faf3
-		  gelf_getrel (data, ndx, &rel;;
f6faf3
-		  rela.r_offset = rel.r_offset;
f6faf3
-		  rela.r_info = rel.r_info;
f6faf3
-		  rela.r_addend = 0;
f6faf3
-		}
f6faf3
-	      else
f6faf3
-		gelf_getrela (data, ndx, &rela);
f6faf3
-	      gelf_getsym (symdata, ELF64_R_SYM (rela.r_info), &sym);
f6faf3
-	      /* Relocations against section symbols are uninteresting
f6faf3
-		 in REL.  */
f6faf3
-	      if (dso->shdr[i].sh_type == SHT_REL && sym.st_value == 0)
f6faf3
-		continue;
f6faf3
-	      /* Only consider relocations against .debug_str, .debug_line
f6faf3
-		 and .debug_abbrev.  */
f6faf3
-	      if (sym.st_shndx != debug_sections[DEBUG_STR].sec
f6faf3
-		  && sym.st_shndx != debug_sections[DEBUG_LINE].sec
f6faf3
-		  && sym.st_shndx != debug_sections[DEBUG_ABBREV].sec)
f6faf3
-		continue;
f6faf3
-	      rela.r_addend += sym.st_value;
f6faf3
-	      rtype = ELF64_R_TYPE (rela.r_info);
f6faf3
-	      switch (dso->ehdr.e_machine)
f6faf3
-		{
f6faf3
-		case EM_SPARC:
f6faf3
-		case EM_SPARC32PLUS:
f6faf3
-		case EM_SPARCV9:
f6faf3
-		  if (rtype != R_SPARC_32 && rtype != R_SPARC_UA32)
f6faf3
-		    goto fail;
f6faf3
-		  break;
f6faf3
-		case EM_386:
f6faf3
-		  if (rtype != R_386_32)
f6faf3
-		    goto fail;
f6faf3
-		  break;
f6faf3
-		case EM_PPC:
f6faf3
-		case EM_PPC64:
f6faf3
-		  if (rtype != R_PPC_ADDR32 && rtype != R_PPC_UADDR32)
f6faf3
-		    goto fail;
f6faf3
-		  break;
f6faf3
-		case EM_S390:
f6faf3
-		  if (rtype != R_390_32)
f6faf3
-		    goto fail;
f6faf3
-		  break;
f6faf3
-		case EM_IA_64:
f6faf3
-		  if (rtype != R_IA64_SECREL32LSB)
f6faf3
-		    goto fail;
f6faf3
-		  break;
f6faf3
-		case EM_X86_64:
f6faf3
-		  if (rtype != R_X86_64_32)
f6faf3
-		    goto fail;
f6faf3
-		  break;
f6faf3
-		case EM_ALPHA:
f6faf3
-		  if (rtype != R_ALPHA_REFLONG)
f6faf3
-		    goto fail;
f6faf3
-		  break;
f6faf3
-#if defined(EM_AARCH64) && defined(R_AARCH64_ABS32)
f6faf3
-		case EM_AARCH64:
f6faf3
-		  if (rtype != R_AARCH64_ABS32)
f6faf3
-		    goto fail;
f6faf3
-		  break;
f6faf3
-#endif
f6faf3
-		case EM_68K:
f6faf3
-		  if (rtype != R_68K_32)
f6faf3
-		    goto fail;
f6faf3
-		  break;
f6faf3
-#if defined(EM_RISCV) && defined(R_RISCV_32)
f6faf3
-		case EM_RISCV:
f6faf3
-		  if (rtype != R_RISCV_32)
f6faf3
-		    goto fail;
f6faf3
-		  break;
f6faf3
-#endif
f6faf3
-		default:
f6faf3
-		fail:
f6faf3
-		  error (1, 0, "%s: Unhandled relocation %d in .debug_info section",
f6faf3
-			 dso->filename, rtype);
f6faf3
-		}
f6faf3
-	      relend->ptr = debug_sections[DEBUG_INFO].data
f6faf3
-			    + (rela.r_offset - base);
f6faf3
-	      relend->addend = rela.r_addend;
f6faf3
-	      relend->ndx = ndx;
f6faf3
-	      ++relend;
f6faf3
-	    }
f6faf3
-	  if (relbuf == relend)
f6faf3
-	    {
f6faf3
-	      free (relbuf);
f6faf3
-	      relbuf = NULL;
f6faf3
-	      relend = NULL;
f6faf3
-	    }
f6faf3
-	  else
f6faf3
-	    qsort (relbuf, relend - relbuf, sizeof (REL), rel_cmp);
f6faf3
-	}
f6faf3
+      bool info_rel_updated = false;
f6faf3
 
f6faf3
       for (phase = 0; phase < 2; phase++)
f6faf3
 	{
f6faf3
@@ -2008,7 +2073,8 @@ edit_dwarf2 (DSO *dso)
f6faf3
 	    break;
f6faf3
 
f6faf3
 	  ptr = debug_sections[DEBUG_INFO].data;
f6faf3
-	  relptr = relbuf;
f6faf3
+	  setup_relbuf(dso, &debug_sections[DEBUG_INFO], &reltype);
f6faf3
+	  rel_updated = false;
f6faf3
 	  endsec = ptr + debug_sections[DEBUG_INFO].size;
f6faf3
 	  while (ptr < endsec)
f6faf3
 	    {
f6faf3
@@ -2096,6 +2162,10 @@ edit_dwarf2 (DSO *dso)
f6faf3
 	      htab_delete (abbrev);
f6faf3
 	    }
f6faf3
 
f6faf3
+	  /* Remember whether any .debug_info relocations might need
f6faf3
+	     to be updated. */
f6faf3
+	  info_rel_updated = rel_updated;
f6faf3
+
f6faf3
 	  /* We might have to recalculate/rewrite the debug_line
f6faf3
 	     section.  We need to do that before going into phase one
f6faf3
 	     so we have all new offsets.  We do this separately from
f6faf3
@@ -2240,41 +2310,8 @@ edit_dwarf2 (DSO *dso)
f6faf3
 	dirty_section (DEBUG_INFO);
f6faf3
 
f6faf3
       /* Update any debug_info relocations addends we might have touched. */
f6faf3
-      if (relbuf != NULL && reltype == SHT_RELA)
f6faf3
-	{
f6faf3
-	  Elf_Data *symdata;
f6faf3
-          int relsec_ndx = debug_sections[DEBUG_INFO].relsec;
f6faf3
-          data = elf_getdata (dso->scn[relsec_ndx], NULL);
f6faf3
-	  symdata = elf_getdata (dso->scn[dso->shdr[relsec_ndx].sh_link],
f6faf3
-				 NULL);
f6faf3
-
f6faf3
-	  relptr = relbuf;
f6faf3
-	  while (relptr < relend)
f6faf3
-	    {
f6faf3
-	      GElf_Sym sym;
f6faf3
-	      GElf_Rela rela;
f6faf3
-	      int ndx = relptr->ndx;
f6faf3
-
f6faf3
-	      if (gelf_getrela (data, ndx, &rela) == NULL)
f6faf3
-		error (1, 0, "Couldn't get relocation: %s",
f6faf3
-		       elf_errmsg (-1));
f6faf3
-
f6faf3
-	      if (gelf_getsym (symdata, GELF_R_SYM (rela.r_info),
f6faf3
-			       &sym) == NULL)
f6faf3
-		error (1, 0, "Couldn't get symbol: %s", elf_errmsg (-1));
f6faf3
-
f6faf3
-	      rela.r_addend = relptr->addend - sym.st_value;
f6faf3
-
f6faf3
-	      if (gelf_update_rela (data, ndx, &rela) == 0)
f6faf3
-		error (1, 0, "Couldn't update relocations: %s",
f6faf3
-		       elf_errmsg (-1));
f6faf3
-
f6faf3
-	      ++relptr;
f6faf3
-	    }
f6faf3
-	  elf_flagdata (data, ELF_C_SET, ELF_F_DIRTY);
f6faf3
-	}
f6faf3
-
f6faf3
-      free (relbuf);
f6faf3
+      if (info_rel_updated)
f6faf3
+	update_rela_data (dso, &debug_sections[DEBUG_INFO]);
f6faf3
     }
f6faf3
 
f6faf3
   return 0;
f6faf3
-- 
f6faf3
2.23.0
f6faf3