[AIX][PGO] Teach profile runtime to read build-id
authorWael Yehia <wyehia@ca.ibm.com>
Mon, 20 Mar 2023 19:51:17 +0000 (15:51 -0400)
committerWael Yehia <wyehia@ca.ibm.com>
Wed, 29 Mar 2023 15:15:07 +0000 (15:15 +0000)
On AIX, the build-id can be embedded in a binary using the -mxcoff-build-id
compiler option. When present, the build id is stored as an ascii string at the
beginning of the string table in the loader section of the XCOFF file.

Reviewed By: stephenpeckham, daltenty

Differential Revision: https://reviews.llvm.org/D146976

compiler-rt/lib/profile/InstrProfilingInternal.h
compiler-rt/lib/profile/InstrProfilingPlatformAIX.c
compiler-rt/lib/profile/InstrProfilingPlatformLinux.c
compiler-rt/lib/profile/InstrProfilingWriter.c
compiler-rt/test/profile/AIX/binary-id-shared.c [new file with mode: 0644]
compiler-rt/test/profile/AIX/binary-id.c [new file with mode: 0644]

index b2ce110..360165e 100644 (file)
@@ -198,4 +198,12 @@ extern void (*VPMergeHook)(struct ValueProfData *, __llvm_profile_data *);
  */
 int __llvm_write_binary_ids(ProfDataWriter *Writer);
 
+/*
+ * Write binary id length and then its data, because binary id does not
+ * have a fixed length.
+ */
+int lprofWriteOneBinaryId(ProfDataWriter *Writer, uint64_t BinaryIdLen,
+                          const uint8_t *BinaryIdData,
+                          uint64_t BinaryIdPadding);
+
 #endif
index 1035894..ce03766 100644 (file)
@@ -8,7 +8,162 @@
 
 #if defined(_AIX)
 
+#ifdef __64BIT__
+#define __XCOFF64__
+#endif
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ldr.h>
+#include <xcoff.h>
+
 #include "InstrProfiling.h"
+#include "InstrProfilingInternal.h"
+
+#define BIN_ID_PREFIX "xcoff_binary_id:"
+
+// If found, write the build-id into the Result buffer.
+static size_t FindBinaryId(char *Result, size_t Size) {
+  unsigned long EntryAddr = (unsigned long)__builtin_return_address(0);
+
+  // Use loadquery to get information about loaded modules; loadquery writes
+  // its result into a buffer of unknown size.
+  char Buf[1024];
+  size_t BufSize = sizeof(Buf);
+  char *BufPtr = Buf;
+  int RC = -1;
+
+  errno = 0;
+  RC = loadquery(L_GETXINFO | L_IGNOREUNLOAD, BufPtr, (unsigned int)BufSize);
+  if (RC == -1 && errno == ENOMEM) {
+    BufSize = 64000; // should be plenty for any program.
+    BufPtr = malloc(BufSize);
+    RC = loadquery(L_GETXINFO | L_IGNOREUNLOAD, BufPtr, (unsigned int)BufSize);
+  }
+
+  if (RC == -1)
+    return RC;
+
+  // Locate the ld_xinfo corresponding to this module.
+  struct ld_xinfo *CurInfo = (struct ld_xinfo *)BufPtr;
+  while (1) {
+    unsigned long CurTextStart = (uint64_t)CurInfo->ldinfo_textorg;
+    unsigned long CurTextEnd = CurTextStart + CurInfo->ldinfo_textsize;
+    if (CurTextStart <= EntryAddr && EntryAddr < CurTextEnd) {
+      // Found my slot. Now search for the build-id.
+      char *p = (char *)CurInfo->ldinfo_textorg;
+
+      FILHDR *f = (FILHDR *)p;
+      AOUTHDR *a = (AOUTHDR *)(p + FILHSZ);
+      SCNHDR *s =
+          (SCNHDR *)(p + FILHSZ + f->f_opthdr + SCNHSZ * (a->o_snloader - 1));
+      LDHDR *ldhdr = (LDHDR *)(p + s->s_scnptr);
+      // This is the loader string table
+      char *lstr = (char *)ldhdr + ldhdr->l_stoff;
+
+      // If the build-id exists, it's the first entry.
+      // Each entry is comprised of a 2-byte size component, followed by the
+      // data.
+      size_t len = *(short *)lstr;
+      char *str = (char *)(lstr + 2);
+      size_t PrefixLen = sizeof(BIN_ID_PREFIX) - 1;
+      if (len > PrefixLen && (len - PrefixLen) <= Size &&
+          strncmp(str, BIN_ID_PREFIX, PrefixLen) == 0) {
+        memcpy(Result, str + PrefixLen, len - PrefixLen);
+        RC = len - PrefixLen;
+        goto done;
+      }
+      break;
+    }
+    if (CurInfo->ldinfo_next == 0u)
+      break;
+    CurInfo = (struct ld_xinfo *)((char *)CurInfo + CurInfo->ldinfo_next);
+  }
+done:
+  if (BufSize != sizeof(Buf) && BufPtr != 0)
+    free(BufPtr);
+  return RC;
+}
+
+static int StrToHexError = 0;
+static uint8_t StrToHex(char c) {
+  if (c >= '0' && c <= '9')
+    return c - '0';
+  if (c >= 'a' && c <= 'f')
+    return c - 'a' + 0xa;
+  if (c >= 'A' && c <= 'F')
+    return c - 'A' + 0xa;
+  StrToHexError = 1;
+  return 0;
+}
+
+COMPILER_RT_VISIBILITY int __llvm_write_binary_ids(ProfDataWriter *Writer) {
+  // 200 bytes should be enough for the build-id hex string.
+  static char Buf[200];
+  // Profile reading tools expect this to be 8-bytes long.
+  static int64_t BinaryIdLen = 0;
+  static uint8_t *BinaryIdData = 0;
+
+  // -1 means we already checked for a BinaryId and didn't find one.
+  if (BinaryIdLen == -1)
+    return 0;
+
+  // Are we being called for the first time?
+  if (BinaryIdLen == 0) {
+    if (getenv("LLVM_PROFILE_NO_BUILD_ID"))
+      goto fail;
+
+    int BuildIdLen = FindBinaryId(Buf, sizeof(Buf));
+    if (BuildIdLen <= 0)
+      goto fail;
+
+    if (Buf[BuildIdLen - 1] == '\0')
+      BuildIdLen--;
+
+    // assume even number of digits/chars, so 0xabc must be 0x0abc
+    if ((BuildIdLen % 2) != 0 || BuildIdLen == 0)
+      goto fail;
+
+    // The numeric ID is represented as an ascii string in the loader section,
+    // so convert it to raw binary.
+    BinaryIdLen = BuildIdLen / 2;
+    BinaryIdData = (uint8_t *)Buf;
+
+    // Skip "0x" prefix if it exists.
+    if (Buf[0] == '0' && Buf[1] == 'x') {
+      BinaryIdLen -= 1;
+      BinaryIdData += 2;
+    }
+
+    StrToHexError = 0;
+    for (int i = 0; i < BinaryIdLen; i++)
+      BinaryIdData[i] = (StrToHex(BinaryIdData[2 * i]) << 4) +
+                        StrToHex(BinaryIdData[2 * i + 1]);
+
+    if (StrToHexError)
+      goto fail;
+
+    if (getenv("LLVM_PROFILE_VERBOSE")) {
+      char *StrBuf = (char *)COMPILER_RT_ALLOCA(2 * BinaryIdLen + 1);
+      for (int i = 0; i < (int)BinaryIdLen; i++)
+        sprintf(&StrBuf[2 * i], "%02x", BinaryIdData[i]);
+      PROF_NOTE("Writing binary id: %s\n", StrBuf);
+    }
+  }
+
+  uint8_t BinaryIdPadding = __llvm_profile_get_num_padding_bytes(BinaryIdLen);
+  if (Writer && lprofWriteOneBinaryId(Writer, BinaryIdLen, BinaryIdData,
+                                      BinaryIdPadding) == -1)
+    return -1; // Return -1 rather goto fail to match the NT_GNU_BUILD_ID path.
+
+  return sizeof(BinaryIdLen) + BinaryIdLen + BinaryIdPadding;
+
+fail:
+  if (getenv("LLVM_PROFILE_VERBOSE"))
+    fprintf(stderr, "no or invalid binary id: %.*s\n", (int)sizeof(Buf), Buf);
+  BinaryIdLen = -1;
+  return 0;
+}
 
 // Empty stubs to allow linking object files using the registration-based scheme
 COMPILER_RT_VISIBILITY
index f17e543..2cce0a4 100644 (file)
@@ -94,26 +94,6 @@ static size_t RoundUp(size_t size, size_t align) {
 }
 
 /*
- * Write binary id length and then its data, because binary id does not
- * have a fixed length.
- */
-static int WriteOneBinaryId(ProfDataWriter *Writer, uint64_t BinaryIdLen,
-                            const uint8_t *BinaryIdData,
-                            uint64_t BinaryIdPadding) {
-  ProfDataIOVec BinaryIdIOVec[] = {
-      {&BinaryIdLen, sizeof(uint64_t), 1, 0},
-      {BinaryIdData, sizeof(uint8_t), BinaryIdLen, 0},
-      {NULL, sizeof(uint8_t), BinaryIdPadding, 1},
-  };
-  if (Writer->Write(Writer, BinaryIdIOVec,
-                    sizeof(BinaryIdIOVec) / sizeof(*BinaryIdIOVec)))
-    return -1;
-
-  /* Successfully wrote binary id, report success. */
-  return 0;
-}
-
-/*
  * Look for the note that has the name "GNU\0" and type NT_GNU_BUILD_ID
  * that contains build id. If build id exists, write binary id.
  *
@@ -135,8 +115,9 @@ static int WriteBinaryIdForNote(ProfDataWriter *Writer,
     const uint8_t *BinaryIdData =
         (const uint8_t *)(NoteName + RoundUp(Note->n_namesz, 4));
     uint8_t BinaryIdPadding = __llvm_profile_get_num_padding_bytes(BinaryIdLen);
-    if (Writer != NULL && WriteOneBinaryId(Writer, BinaryIdLen, BinaryIdData,
-                                           BinaryIdPadding) == -1)
+    if (Writer != NULL &&
+        lprofWriteOneBinaryId(Writer, BinaryIdLen, BinaryIdData,
+                              BinaryIdPadding) == -1)
       return -1;
 
     BinaryIdSize = sizeof(BinaryIdLen) + BinaryIdLen + BinaryIdPadding;
@@ -220,7 +201,7 @@ COMPILER_RT_VISIBILITY int __llvm_write_binary_ids(ProfDataWriter *Writer) {
 
   return TotalBinaryIdsSize;
 }
-#else /* !NT_GNU_BUILD_ID */
+#elif !defined(_AIX) /* !NT_GNU_BUILD_ID */
 /*
  * Fallback implementation for targets that don't support the GNU
  * extensions NT_GNU_BUILD_ID and __ehdr_start.
index 366451a..4a39298 100644 (file)
@@ -336,3 +336,24 @@ lprofWriteDataImpl(ProfDataWriter *Writer, const __llvm_profile_data *DataBegin,
 
   return writeValueProfData(Writer, VPDataReader, DataBegin, DataEnd);
 }
+
+/*
+ * Write binary id length and then its data, because binary id does not
+ * have a fixed length.
+ */
+COMPILER_RT_VISIBILITY
+int lprofWriteOneBinaryId(ProfDataWriter *Writer, uint64_t BinaryIdLen,
+                          const uint8_t *BinaryIdData,
+                          uint64_t BinaryIdPadding) {
+  ProfDataIOVec BinaryIdIOVec[] = {
+      {&BinaryIdLen, sizeof(uint64_t), 1, 0},
+      {BinaryIdData, sizeof(uint8_t), BinaryIdLen, 0},
+      {NULL, sizeof(uint8_t), BinaryIdPadding, 1},
+  };
+  if (Writer->Write(Writer, BinaryIdIOVec,
+                    sizeof(BinaryIdIOVec) / sizeof(*BinaryIdIOVec)))
+    return -1;
+
+  /* Successfully wrote binary id, report success. */
+  return 0;
+}
diff --git a/compiler-rt/test/profile/AIX/binary-id-shared.c b/compiler-rt/test/profile/AIX/binary-id-shared.c
new file mode 100644 (file)
index 0000000..55ba9d8
--- /dev/null
@@ -0,0 +1,35 @@
+// RUN: split-file %s %t
+// RUN: cd %t
+// RUN: %clang_pgogen -c shr1.c -o shr1.o -Xclang -fprofile-instrument-path=default_1.profraw
+// RUN: %clang_pgogen -shared shr1.o -o shr1.so -mxcoff-build-id=0x01
+//
+// RUN: %clangxx_pgogen -c shr2.cpp -o shr2.o -Xclang -fprofile-instrument-path=default_2.profraw
+// RUN: %clangxx_pgogen -shared shr2.o -o shr2.so -mxcoff-build-id=0x02
+//
+// RUN: %clang_pgogen -c main.c -o main.o -Xclang -fprofile-instrument-path=default_main.profraw
+// RUN: %clang_pgogen main.o -L%t shr1.so shr2.so -o a.out -mxcoff-build-id=0xFFFFFFFFFFFFFFFF
+//
+// RUN: %run ./a.out
+//
+// RUN: llvm-profdata show --binary-ids default_1.profraw | FileCheck %s --check-prefix=SHARED1
+// RUN: llvm-profdata show --binary-ids default_2.profraw | FileCheck %s --check-prefix=SHARED2
+// RUN: llvm-profdata show --binary-ids default_main.profraw | FileCheck %s --check-prefix=MAIN
+
+// SHARED1: Binary IDs:
+// SHARED1-NEXT:  {{^}}01{{$}}
+// SHARED2: Binary IDs:
+// SHARED2-NEXT:  {{^}}02{{$}}
+// MAIN: Binary IDs:
+// MAIN-NEXT:  {{^}}ffffffffffffffff{{$}}
+
+//--- shr1.c
+int shr1() { return 1; }
+
+//--- shr2.cpp
+int helper() { return 3; }
+extern "C" int shr2() { return helper(); }
+
+//--- main.c
+int shr1();
+int shr2();
+int main() { return 4 - shr1() - shr2(); }
diff --git a/compiler-rt/test/profile/AIX/binary-id.c b/compiler-rt/test/profile/AIX/binary-id.c
new file mode 100644 (file)
index 0000000..b60bfd8
--- /dev/null
@@ -0,0 +1,47 @@
+// RUN: %clang_pgogen -c -o %t.o %s
+//
+// Test valid IDs:
+// (20-byte, ends with 2 zeroes, upper case)
+// RUN: %clang_pgogen -mxcoff-build-id=0x8d7AEC8b900dce6c14afe557dc8889230518be00 -o %t %t.o
+// RUN: env LLVM_PROFILE_FILE=%t.profraw %run %t
+// RUN: llvm-profdata show --binary-ids %t.profraw | FileCheck %s --check-prefix=LONG
+
+// (all zeroes)
+// RUN: %clang_pgogen -mxcoff-build-id=0x00 -o %t %t.o
+// RUN: env LLVM_PROFILE_FILE=%t.profraw %run %t
+// RUN: llvm-profdata show --binary-ids %t.profraw | FileCheck %s --check-prefix=00
+
+// (starts with one zero)
+// RUN: %clang_pgogen -mxcoff-build-id=0x0120 -o %t %t.o
+// RUN: env LLVM_PROFILE_FILE=%t.profraw %run %t
+// RUN: llvm-profdata show --binary-ids %t.profraw | FileCheck %s --check-prefix=0120
+
+// (starts with 8 zeroes == 4 bytes)
+// RUN: %clang_pgogen -mxcoff-build-id=0x0000000012 -o %t %t.o
+// RUN: env LLVM_PROFILE_FILE=%t.profraw %run %t
+// RUN: llvm-profdata show --binary-ids %t.profraw | FileCheck %s --check-prefix=0000000012
+
+// (starts with 16 zeroes == 8 bytes)
+// RUN: %clang_pgogen -mxcoff-build-id=0x0000000000000000ff -o %t %t.o
+// RUN: env LLVM_PROFILE_FILE=%t.profraw %run %t
+// RUN: llvm-profdata show --binary-ids %t.profraw | FileCheck %s --check-prefix=0000000000000000ff
+
+// (starts with 17 zeroes)
+// RUN: %clang_pgogen -mxcoff-build-id=0x00000000000000000f -o %t %t.o
+// RUN: env LLVM_PROFILE_FILE=%t.profraw %run %t
+// RUN: llvm-profdata show --binary-ids %t.profraw | FileCheck %s --check-prefix=00000000000000000f
+
+// LONG: Binary IDs:
+// LONG-NEXT:  8d7aec8b900dce6c14afe557dc8889230518be00
+// 00: Binary IDs:
+// 00-NEXT:  {{^}}00{{$}}
+// 0120: Binary IDs:
+// 0120-NEXT:  {{^}}0120{{$}}
+// 0000000012: Binary IDs:
+// 0000000012-NEXT:  {{^}}0000000012{{$}}
+// 0000000000000000ff: Binary IDs:
+// 0000000000000000ff-NEXT:  {{^}}0000000000000000ff{{$}}
+// 00000000000000000f: Binary IDs:
+// 00000000000000000f-NEXT:  {{^}}00000000000000000f{{$}}
+
+int main() { return 0; }