public inbox for binutils@sourceware.org
 help / color / mirror / Atom feed
* [PATCH 1/2] ld: Add --pdb option
@ 2022-10-03  1:43 Mark Harmstone
  2022-10-03  1:43 ` [PATCH 2/2] ld: Add minimal pdb generation Mark Harmstone
                   ` (2 more replies)
  0 siblings, 3 replies; 11+ messages in thread
From: Mark Harmstone @ 2022-10-03  1:43 UTC (permalink / raw)
  To: binutils; +Cc: Mark Harmstone

This patch adds the --pdb option to ld when linking PE files, which
augments the existing CodeView record used for build IDs by adding a PDB
filename. If no filename is provided, this defaults to the image name
with the extension replaced by "pdb".

---
 bfd/libpei.h               |  6 +++--
 bfd/peXXigen.c             | 30 ++++++++++++++++-----
 bfd/peicode.h              |  2 +-
 ld/emultempl/pe.em         | 49 +++++++++++++++++++++++++++++++----
 ld/emultempl/pep.em        | 47 ++++++++++++++++++++++++++++++---
 ld/testsuite/ld-pe/pdb.exp | 53 ++++++++++++++++++++++++++++++++++++++
 ld/testsuite/ld-pe/pdb1.s  |  5 ++++
 7 files changed, 173 insertions(+), 19 deletions(-)
 create mode 100644 ld/testsuite/ld-pe/pdb.exp
 create mode 100644 ld/testsuite/ld-pe/pdb1.s

diff --git a/bfd/libpei.h b/bfd/libpei.h
index 4aca024192c..8b53bd90e84 100644
--- a/bfd/libpei.h
+++ b/bfd/libpei.h
@@ -388,9 +388,11 @@ void _bfd_XX_get_symbol_info (bfd *, asymbol *, symbol_info *);
 bool _bfd_XXi_final_link_postscript (bfd *, struct coff_final_link_info *);
 void _bfd_XXi_swap_debugdir_in (bfd *, void *, void *);
 unsigned _bfd_XXi_swap_debugdir_out (bfd *, void *, void *);
-unsigned _bfd_XXi_write_codeview_record (bfd *, file_ptr, CODEVIEW_INFO *);
+unsigned _bfd_XXi_write_codeview_record
+  (bfd *, file_ptr, CODEVIEW_INFO *, const char *);
 CODEVIEW_INFO *_bfd_XXi_slurp_codeview_record
-  (bfd * abfd, file_ptr where, unsigned long length, CODEVIEW_INFO *cvinfo);
+  (bfd * abfd, file_ptr where, unsigned long length, CODEVIEW_INFO *cvinfo,
+   char **pdb);
 
 /* The following are needed only for ONE of pe or pei, but don't
    otherwise vary; peicode.h fixes up ifdefs but we provide the
diff --git a/bfd/peXXigen.c b/bfd/peXXigen.c
index 5ab09387e72..8db188ce036 100644
--- a/bfd/peXXigen.c
+++ b/bfd/peXXigen.c
@@ -1134,7 +1134,8 @@ _bfd_XXi_swap_debugdir_out (bfd * abfd, void * inp, void * extp)
 }
 
 CODEVIEW_INFO *
-_bfd_XXi_slurp_codeview_record (bfd * abfd, file_ptr where, unsigned long length, CODEVIEW_INFO *cvinfo)
+_bfd_XXi_slurp_codeview_record (bfd * abfd, file_ptr where, unsigned long length, CODEVIEW_INFO *cvinfo,
+				char **pdb)
 {
   char buffer[256+1];
   bfd_size_type nread;
@@ -1174,6 +1175,9 @@ _bfd_XXi_slurp_codeview_record (bfd * abfd, file_ptr where, unsigned long length
       cvinfo->SignatureLength = CV_INFO_SIGNATURE_LENGTH;
       /* cvinfo->PdbFileName = cvinfo70->PdbFileName;  */
 
+      if (pdb)
+	*pdb = xstrdup (cvinfo70->PdbFileName);
+
       return cvinfo;
     }
   else if ((cvinfo->CVSignature == CVINFO_PDB20_CVSIGNATURE)
@@ -1185,6 +1189,9 @@ _bfd_XXi_slurp_codeview_record (bfd * abfd, file_ptr where, unsigned long length
       cvinfo->SignatureLength = 4;
       /* cvinfo->PdbFileName = cvinfo20->PdbFileName;  */
 
+      if (pdb)
+	*pdb = xstrdup (cvinfo20->PdbFileName);
+
       return cvinfo;
     }
 
@@ -1192,9 +1199,11 @@ _bfd_XXi_slurp_codeview_record (bfd * abfd, file_ptr where, unsigned long length
 }
 
 unsigned int
-_bfd_XXi_write_codeview_record (bfd * abfd, file_ptr where, CODEVIEW_INFO *cvinfo)
+_bfd_XXi_write_codeview_record (bfd * abfd, file_ptr where, CODEVIEW_INFO *cvinfo,
+				const char *pdb)
 {
-  const bfd_size_type size = sizeof (CV_INFO_PDB70) + 1;
+  size_t pdb_len = pdb ? strlen (pdb) : 0;
+  const bfd_size_type size = sizeof (CV_INFO_PDB70) + pdb_len + 1;
   bfd_size_type written;
   CV_INFO_PDB70 *cvinfo70;
   char * buffer;
@@ -1217,7 +1226,11 @@ _bfd_XXi_write_codeview_record (bfd * abfd, file_ptr where, CODEVIEW_INFO *cvinf
   memcpy (&(cvinfo70->Signature[8]), &(cvinfo->Signature[8]), 8);
 
   H_PUT_32 (abfd, cvinfo->Age, cvinfo70->Age);
-  cvinfo70->PdbFileName[0] = '\0';
+
+  if (pdb == NULL)
+    cvinfo70->PdbFileName[0] = '\0';
+  else
+    memcpy (cvinfo70->PdbFileName, pdb, pdb_len + 1);
 
   written = bfd_bwrite (buffer, size, abfd);
 
@@ -2615,22 +2628,25 @@ pe_print_debugdata (bfd * abfd, void * vfile)
 	     We need to use a 32-bit aligned buffer
 	     to safely read in a codeview record.  */
 	  char buffer[256 + 1] ATTRIBUTE_ALIGNED_ALIGNOF (CODEVIEW_INFO);
+	  char *pdb;
 
 	  CODEVIEW_INFO *cvinfo = (CODEVIEW_INFO *) buffer;
 
 	  /* The debug entry doesn't have to have to be in a section,
 	     in which case AddressOfRawData is 0, so always use PointerToRawData.  */
 	  if (!_bfd_XXi_slurp_codeview_record (abfd, (file_ptr) idd.PointerToRawData,
-					       idd.SizeOfData, cvinfo))
+					       idd.SizeOfData, cvinfo, &pdb))
 	    continue;
 
 	  for (j = 0; j < cvinfo->SignatureLength; j++)
 	    sprintf (&signature[j*2], "%02x", cvinfo->Signature[j] & 0xff);
 
 	  /* xgettext:c-format */
-	  fprintf (file, _("(format %c%c%c%c signature %s age %ld)\n"),
+	  fprintf (file, _("(format %c%c%c%c signature %s age %ld pdb %s)\n"),
 		   buffer[0], buffer[1], buffer[2], buffer[3],
-		   signature, cvinfo->Age);
+		   signature, cvinfo->Age, pdb[0] ? pdb : "(none)");
+
+	  free (pdb);
 	}
     }
 
diff --git a/bfd/peicode.h b/bfd/peicode.h
index 02573c84694..326e9f9a8ca 100644
--- a/bfd/peicode.h
+++ b/bfd/peicode.h
@@ -1383,7 +1383,7 @@ pe_bfd_read_buildid (bfd *abfd)
 	  */
 	  if (_bfd_XXi_slurp_codeview_record (abfd,
 					      (file_ptr) idd.PointerToRawData,
-					      idd.SizeOfData, cvinfo))
+					      idd.SizeOfData, cvinfo, NULL))
 	    {
 	      struct bfd_build_id* build_id = bfd_alloc (abfd,
 			 sizeof (struct bfd_build_id) + cvinfo->SignatureLength);
diff --git a/ld/emultempl/pe.em b/ld/emultempl/pe.em
index 892bf70b7a6..ef15d9ee4ca 100644
--- a/ld/emultempl/pe.em
+++ b/ld/emultempl/pe.em
@@ -146,6 +146,8 @@ static lang_assignment_statement_type *image_base_statement = 0;
 static unsigned short pe_dll_characteristics = DEFAULT_DLL_CHARACTERISTICS;
 static bool insert_timestamp = true;
 static const char *emit_build_id;
+static int pdb;
+static char *pdb_name;
 
 #ifdef DLL_SUPPORT
 static int pe_enable_stdcall_fixup = -1; /* 0=disable 1=enable.  */
@@ -284,7 +286,8 @@ fragment <<EOF
 #define OPTION_INSERT_TIMESTAMP		(OPTION_TERMINAL_SERVER_AWARE + 1)
 #define OPTION_NO_INSERT_TIMESTAMP	(OPTION_INSERT_TIMESTAMP + 1)
 #define OPTION_BUILD_ID			(OPTION_NO_INSERT_TIMESTAMP + 1)
-#define OPTION_ENABLE_RELOC_SECTION	(OPTION_BUILD_ID + 1)
+#define OPTION_PDB			(OPTION_BUILD_ID + 1)
+#define OPTION_ENABLE_RELOC_SECTION	(OPTION_PDB + 1)
 #define OPTION_DISABLE_RELOC_SECTION	(OPTION_ENABLE_RELOC_SECTION + 1)
 /* DLL Characteristics flags.  */
 #define OPTION_DISABLE_DYNAMIC_BASE	(OPTION_DISABLE_RELOC_SECTION + 1)
@@ -383,6 +386,7 @@ gld${EMULATION_NAME}_add_options
     {"tsaware", no_argument, NULL, OPTION_TERMINAL_SERVER_AWARE},
     {"disable-tsaware", no_argument, NULL, OPTION_DISABLE_TERMINAL_SERVER_AWARE},
     {"build-id", optional_argument, NULL, OPTION_BUILD_ID},
+    {"pdb", optional_argument, NULL, OPTION_PDB},
     {"enable-reloc-section", no_argument, NULL, OPTION_ENABLE_RELOC_SECTION},
     {"disable-reloc-section", no_argument, NULL, OPTION_DISABLE_RELOC_SECTION},
     {NULL, no_argument, NULL, 0}
@@ -532,6 +536,7 @@ gld${EMULATION_NAME}_list_options (FILE *file)
   fprintf (file, _("  --[disable-]wdmdriver              Driver uses the WDM model\n"));
   fprintf (file, _("  --[disable-]tsaware                Image is Terminal Server aware\n"));
   fprintf (file, _("  --build-id[=STYLE]                 Generate build ID\n"));
+  fprintf (file, _("  --pdb[=FILENAME]                   Generate PDB file\n"));
 }
 
 
@@ -955,6 +960,13 @@ gld${EMULATION_NAME}_handle_option (int optc)
       if (strcmp (optarg, "none"))
 	emit_build_id = xstrdup (optarg);
       break;
+    case OPTION_PDB:
+      if (emit_build_id == NULL)
+	emit_build_id = xstrdup (DEFAULT_BUILD_ID_STYLE);
+      pdb = 1;
+      if (optarg)
+	pdb_name = xstrdup (optarg);
+      break;
     }
 
   /*  Set DLLCharacteristics bits  */
@@ -1256,6 +1268,7 @@ write_build_id (bfd *abfd)
   bfd_size_type size;
   bfd_size_type build_id_size;
   unsigned char *build_id;
+  const char *pdb_base_name = NULL;
 
   /* Find the section the .buildid output section has been merged info.  */
   for (asec = abfd->sections; asec != NULL; asec = asec->next)
@@ -1295,6 +1308,9 @@ write_build_id (bfd *abfd)
 
   bfd_vma ib = pe_data (link_info.output_bfd)->pe_opthdr.ImageBase;
 
+  if (pdb_name)
+    pdb_base_name = lbasename (pdb_name);
+
   /* Construct a debug directory entry which points to an immediately following CodeView record.  */
   struct internal_IMAGE_DEBUG_DIRECTORY idd;
   idd.Characteristics = 0;
@@ -1302,7 +1318,7 @@ write_build_id (bfd *abfd)
   idd.MajorVersion = 0;
   idd.MinorVersion = 0;
   idd.Type = PE_IMAGE_DEBUG_TYPE_CODEVIEW;
-  idd.SizeOfData = sizeof (CV_INFO_PDB70) + 1;
+  idd.SizeOfData = sizeof (CV_INFO_PDB70) + (pdb_base_name ? strlen (pdb_base_name) : 0) + 1;
   idd.AddressOfRawData = asec->vma - ib + link_order->offset
     + sizeof (struct external_IMAGE_DEBUG_DIRECTORY);
   idd.PointerToRawData = asec->filepos + link_order->offset
@@ -1331,7 +1347,8 @@ write_build_id (bfd *abfd)
   free (build_id);
 
   /* Write the codeview record.  */
-  if (_bfd_XXi_write_codeview_record (abfd, idd.PointerToRawData, &cvinfo) == 0)
+  if (_bfd_XXi_write_codeview_record (abfd, idd.PointerToRawData, &cvinfo,
+				      pdb_base_name) == 0)
     return 0;
 
   /* Record the location of the debug directory in the data directory.  */
@@ -1368,11 +1385,14 @@ setup_build_id (bfd *ibfd)
 
       /* Section is a fixed size:
 	 One IMAGE_DEBUG_DIRECTORY entry, of type IMAGE_DEBUG_TYPE_CODEVIEW,
-	 pointing at a CV_INFO_PDB70 record containing the build-id, with a
-	 null byte for PdbFileName.  */
+	 pointing at a CV_INFO_PDB70 record containing the build-id, followed by
+	 PdbFileName if relevant.  */
       s->size = sizeof (struct external_IMAGE_DEBUG_DIRECTORY)
 	+ sizeof (CV_INFO_PDB70) + 1;
 
+      if (pdb_name)
+	s->size += strlen (pdb_name);
+
       return true;
     }
 
@@ -1403,6 +1423,25 @@ gld${EMULATION_NAME}_after_open (void)
     }
 #endif
 
+  if (pdb && !pdb_name)
+    {
+      const char *base = lbasename (bfd_get_filename (link_info.output_bfd));
+      size_t len = strlen (base);
+      static const char suffix[] = ".pdb";
+
+      while (len > 0 && base[len] != '.')
+	{
+	  len--;
+	}
+
+      if (len == 0)
+	len = strlen (base);
+
+      pdb_name = xmalloc (len + sizeof (suffix));
+      memcpy (pdb_name, base, len);
+      memcpy (pdb_name + len, suffix, sizeof (suffix));
+    }
+
   if (emit_build_id != NULL)
     {
       bfd *abfd;
diff --git a/ld/emultempl/pep.em b/ld/emultempl/pep.em
index e68d1e69f17..78b36de49e7 100644
--- a/ld/emultempl/pep.em
+++ b/ld/emultempl/pep.em
@@ -157,6 +157,8 @@ static lang_assignment_statement_type *image_base_statement = 0;
 static unsigned short pe_dll_characteristics = DEFAULT_DLL_CHARACTERISTICS;
 static bool insert_timestamp = true;
 static const char *emit_build_id;
+static int pdb;
+static char *pdb_name;
 
 #ifdef DLL_SUPPORT
 static int    pep_enable_stdcall_fixup = 1; /* 0=disable 1=enable (default).  */
@@ -255,6 +257,7 @@ enum options
   OPTION_NO_INSERT_TIMESTAMP,
   OPTION_TERMINAL_SERVER_AWARE,
   OPTION_BUILD_ID,
+  OPTION_PDB,
   OPTION_ENABLE_RELOC_SECTION,
   OPTION_DISABLE_RELOC_SECTION,
   OPTION_DISABLE_HIGH_ENTROPY_VA,
@@ -343,6 +346,7 @@ gld${EMULATION_NAME}_add_options
     {"insert-timestamp", no_argument, NULL, OPTION_INSERT_TIMESTAMP},
     {"no-insert-timestamp", no_argument, NULL, OPTION_NO_INSERT_TIMESTAMP},
     {"build-id", optional_argument, NULL, OPTION_BUILD_ID},
+    {"pdb", optional_argument, NULL, OPTION_PDB},
     {"enable-reloc-section", no_argument, NULL, OPTION_ENABLE_RELOC_SECTION},
     {"disable-reloc-section", no_argument, NULL, OPTION_DISABLE_RELOC_SECTION},
     {"disable-high-entropy-va", no_argument, NULL, OPTION_DISABLE_HIGH_ENTROPY_VA},
@@ -490,6 +494,7 @@ gld${EMULATION_NAME}_list_options (FILE *file)
   fprintf (file, _("  --[disable-]wdmdriver              Driver uses the WDM model\n"));
   fprintf (file, _("  --[disable-]tsaware                Image is Terminal Server aware\n"));
   fprintf (file, _("  --build-id[=STYLE]                 Generate build ID\n"));
+  fprintf (file, _("  --pdb[=FILENAME]                   Generate PDB file\n"));
 #endif
 }
 
@@ -898,6 +903,13 @@ gld${EMULATION_NAME}_handle_option (int optc)
       if (strcmp (optarg, "none"))
 	emit_build_id = xstrdup (optarg);
       break;
+    case OPTION_PDB:
+      if (emit_build_id == NULL)
+	emit_build_id = xstrdup (DEFAULT_BUILD_ID_STYLE);
+      pdb = 1;
+      if (optarg)
+	pdb_name = xstrdup (optarg);
+      break;
     }
 
   /*  Set DLLCharacteristics bits  */
@@ -1240,6 +1252,7 @@ write_build_id (bfd *abfd)
   bfd_size_type size;
   bfd_size_type build_id_size;
   unsigned char *build_id;
+  const char *pdb_base_name = NULL;
 
   /* Find the section the .buildid output section has been merged info.  */
   for (asec = abfd->sections; asec != NULL; asec = asec->next)
@@ -1279,6 +1292,9 @@ write_build_id (bfd *abfd)
 
   bfd_vma ib = pe_data (link_info.output_bfd)->pe_opthdr.ImageBase;
 
+  if (pdb_name)
+    pdb_base_name = lbasename (pdb_name);
+
   /* Construct a debug directory entry which points to an immediately following CodeView record.  */
   struct internal_IMAGE_DEBUG_DIRECTORY idd;
   idd.Characteristics = 0;
@@ -1286,7 +1302,7 @@ write_build_id (bfd *abfd)
   idd.MajorVersion = 0;
   idd.MinorVersion = 0;
   idd.Type = PE_IMAGE_DEBUG_TYPE_CODEVIEW;
-  idd.SizeOfData = sizeof (CV_INFO_PDB70) + 1;
+  idd.SizeOfData = sizeof (CV_INFO_PDB70) + (pdb_base_name ? strlen (pdb_base_name) : 0) + 1;
   idd.AddressOfRawData = asec->vma - ib + link_order->offset
     + sizeof (struct external_IMAGE_DEBUG_DIRECTORY);
   idd.PointerToRawData = asec->filepos + link_order->offset
@@ -1315,7 +1331,8 @@ write_build_id (bfd *abfd)
   free (build_id);
 
   /* Write the codeview record.  */
-  if (_bfd_XXi_write_codeview_record (abfd, idd.PointerToRawData, &cvinfo) == 0)
+  if (_bfd_XXi_write_codeview_record (abfd, idd.PointerToRawData, &cvinfo,
+				      pdb_base_name) == 0)
     return 0;
 
   /* Record the location of the debug directory in the data directory.  */
@@ -1352,11 +1369,14 @@ setup_build_id (bfd *ibfd)
 
       /* Section is a fixed size:
 	 One IMAGE_DEBUG_DIRECTORY entry, of type IMAGE_DEBUG_TYPE_CODEVIEW,
-	 pointing at a CV_INFO_PDB70 record containing the build-id, with a
-	 null byte for PdbFileName.  */
+	 pointing at a CV_INFO_PDB70 record containing the build-id, followed by
+	 PdbFileName if relevant.  */
       s->size = sizeof (struct external_IMAGE_DEBUG_DIRECTORY)
 	+ sizeof (CV_INFO_PDB70) + 1;
 
+      if (pdb_name)
+	s->size += strlen (pdb_name);
+
       return true;
     }
 
@@ -1388,6 +1408,25 @@ gld${EMULATION_NAME}_after_open (void)
     }
 #endif
 
+  if (pdb && !pdb_name)
+    {
+      const char *base = lbasename (bfd_get_filename (link_info.output_bfd));
+      size_t len = strlen (base);
+      static const char suffix[] = ".pdb";
+
+      while (len > 0 && base[len] != '.')
+	{
+	  len--;
+	}
+
+      if (len == 0)
+	len = strlen (base);
+
+      pdb_name = xmalloc (len + sizeof (suffix));
+      memcpy (pdb_name, base, len);
+      memcpy (pdb_name + len, suffix, sizeof (suffix));
+    }
+
   if (emit_build_id != NULL)
     {
       bfd *abfd;
diff --git a/ld/testsuite/ld-pe/pdb.exp b/ld/testsuite/ld-pe/pdb.exp
new file mode 100644
index 00000000000..1560241cdb8
--- /dev/null
+++ b/ld/testsuite/ld-pe/pdb.exp
@@ -0,0 +1,53 @@
+# Expect script for creating PDB files when linking.
+#   Copyright (C) 2022 Free Software Foundation, Inc.
+#
+# This file is part of the GNU Binutils.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston,
+# MA 02110-1301, USA.
+
+if {![istarget i*86-*-mingw*]
+  && ![istarget x86_64-*-mingw*]} {
+    return
+}
+
+proc get_pdb_name { pe } {
+    global OBJDUMP
+
+    set exec_output [run_host_cmd "$OBJDUMP" "-p $pe"]
+
+    if ![regexp -line "^\\(format RSDS signature (\[0-9a-fA-F\]{32}) age 1 pdb (.*)\\)$" $exec_output full sig pdb] {
+	return ""
+    }
+
+    return $pdb
+}
+
+if ![ld_assemble $as $srcdir/$subdir/pdb1.s tmpdir/pdb1.o] {
+    unsupported "Build pdb1.o"
+    return
+}
+
+if ![ld_link $ld "tmpdir/pdb1.exe" "--pdb=tmpdir/pdb1.pdb tmpdir/pdb1.o"] {
+    fail "Could not create a PE image with a PDB file"
+    return
+}
+
+if ![string equal [get_pdb_name "tmpdir/pdb1.exe"] "pdb1.pdb"] {
+    fail "PDB filename not found in CodeView debug info"
+    return
+}
+
+pass "PDB filename present in CodeView debug info"
diff --git a/ld/testsuite/ld-pe/pdb1.s b/ld/testsuite/ld-pe/pdb1.s
new file mode 100644
index 00000000000..30a8cfcca2c
--- /dev/null
+++ b/ld/testsuite/ld-pe/pdb1.s
@@ -0,0 +1,5 @@
+.text
+
+.global foo
+foo:
+	.long 0x12345678
-- 
2.35.1


^ permalink raw reply	[flat|nested] 11+ messages in thread

end of thread, other threads:[~2022-10-10 20:58 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-10-03  1:43 [PATCH 1/2] ld: Add --pdb option Mark Harmstone
2022-10-03  1:43 ` [PATCH 2/2] ld: Add minimal pdb generation Mark Harmstone
2022-10-03  5:12 ` [PATCH 1/2] ld: Add --pdb option Martin Storsjö
2022-10-03 16:57   ` Mark Harmstone
2022-10-03 18:58     ` Martin Storsjö
2022-10-07 12:16       ` Martin Storsjö
2022-10-09 23:46         ` Mark Harmstone
2022-10-10 10:27           ` Martin Storsjö
2022-10-10 16:55             ` Mark Harmstone
2022-10-10 20:58               ` Martin Storsjö
2022-10-05  4:20 ` Alan Modra

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).