Skip to content

[llvm] Win x64 Unwind V2 2/n: Support dumping UOP_Epilog #110338

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 14, 2025

Conversation

dpaoliello
Copy link
Contributor

Adds support to objdump and readobj for reading the UOP_Epilog entries of Windows x64 unwind v2.

UOP_Epilog has a weird format:

The first UOP_Epilog in the unwind data is the "header":

  • The least-significant bit of OpInfo is the "At End" flag, which signifies that there is an epilog at the very end of the associated function.
  • CodeOffset is the length each epilog described by the current unwind information (all epilogs have the same length).

Any subsequent UOP_Epilog represents another epilog for the current function, where OpInfo and CodeOffset are combined to a 12-bit value which is the offset of the beginning of the epilog from the end of the current function. If the offset is 0, then this entry is actually padding and can be ignored.

@llvmbot
Copy link
Member

llvmbot commented Sep 27, 2024

@llvm/pr-subscribers-llvm-support

@llvm/pr-subscribers-llvm-binary-utilities

Author: Daniel Paoliello (dpaoliello)

Changes

Adds support to objdump and readobj for reading the UOP_Epilog entries of Windows x64 unwind v2.

UOP_Epilog has a weird format:

The first UOP_Epilog in the unwind data is the "header":

  • The least-significant bit of OpInfo is the "At End" flag, which signifies that there is an epilog at the very end of the associated function.
  • CodeOffset is the length each epilog described by the current unwind information (all epilogs have the same length).

Any subsequent UOP_Epilog represents another epilog for the current function, where OpInfo and CodeOffset are combined to a 12-bit value which is the offset of the beginning of the epilog from the end of the current function. If the offset is 0, then this entry is actually padding and can be ignored.


Full diff: https://github.com/llvm/llvm-project/pull/110338.diff

3 Files Affected:

  • (modified) llvm/tools/llvm-objdump/COFFDump.cpp (+22-3)
  • (modified) llvm/tools/llvm-readobj/Win64EHDumper.cpp (+25-2)
  • (modified) llvm/tools/llvm-readobj/Win64EHDumper.h (+2-1)
diff --git a/llvm/tools/llvm-objdump/COFFDump.cpp b/llvm/tools/llvm-objdump/COFFDump.cpp
index 71697fa01e627d..97901ebf9851d8 100644
--- a/llvm/tools/llvm-objdump/COFFDump.cpp
+++ b/llvm/tools/llvm-objdump/COFFDump.cpp
@@ -240,10 +240,10 @@ static unsigned getNumUsedSlots(const UnwindCode &UnwindCode) {
   case UOP_AllocSmall:
   case UOP_SetFPReg:
   case UOP_PushMachFrame:
+  case UOP_Epilog:
     return 1;
   case UOP_SaveNonVol:
   case UOP_SaveXMM128:
-  case UOP_Epilog:
     return 2;
   case UOP_SaveNonVolBig:
   case UOP_SaveXMM128Big:
@@ -257,7 +257,7 @@ static unsigned getNumUsedSlots(const UnwindCode &UnwindCode) {
 // Prints one unwind code. Because an unwind code can occupy up to 3 slots in
 // the unwind codes array, this function requires that the correct number of
 // slots is provided.
-static void printUnwindCode(ArrayRef<UnwindCode> UCs) {
+static void printUnwindCode(ArrayRef<UnwindCode> UCs, bool &SeenFirstEpilog) {
   assert(UCs.size() >= getNumUsedSlots(UCs[0]));
   outs() <<  format("      0x%02x: ", unsigned(UCs[0].u.CodeOffset))
          << getUnwindCodeTypeName(UCs[0].getUnwindOp());
@@ -301,11 +301,30 @@ static void printUnwindCode(ArrayRef<UnwindCode> UCs) {
     outs() << " " << (UCs[0].getOpInfo() ? "w/o" : "w")
            << " error code";
     break;
+
+  case UOP_Epilog:
+    if (SeenFirstEpilog) {
+      uint32_t Offset = (UCs[0].getOpInfo() << 8) |
+                        static_cast<uint32_t>(UCs[0].u.CodeOffset);
+      if (Offset == 0) {
+        outs() << " padding";
+      } else {
+        outs() << " offset=" << format("0x%X", Offset);
+      }
+    } else {
+      SeenFirstEpilog = true;
+      bool AtEnd = (UCs[0].getOpInfo() & 0x1) != 0;
+      uint32_t Length = UCs[0].u.CodeOffset;
+      outs() << " atend=" << (AtEnd ? "yes" : "no")
+             << ", length=" << format("0x%X", Length);
+    }
+    break;
   }
   outs() << "\n";
 }
 
 static void printAllUnwindCodes(ArrayRef<UnwindCode> UCs) {
+  bool SeenFirstEpilog = false;
   for (const UnwindCode *I = UCs.begin(), *E = UCs.end(); I < E; ) {
     unsigned UsedSlots = getNumUsedSlots(*I);
     if (UsedSlots > UCs.size()) {
@@ -316,7 +335,7 @@ static void printAllUnwindCodes(ArrayRef<UnwindCode> UCs) {
              << " remaining in buffer";
       return ;
     }
-    printUnwindCode(ArrayRef(I, E));
+    printUnwindCode(ArrayRef(I, E), SeenFirstEpilog);
     I += UsedSlots;
   }
 }
diff --git a/llvm/tools/llvm-readobj/Win64EHDumper.cpp b/llvm/tools/llvm-readobj/Win64EHDumper.cpp
index e4bd772191514a..ee043c9d0d10a1 100644
--- a/llvm/tools/llvm-readobj/Win64EHDumper.cpp
+++ b/llvm/tools/llvm-readobj/Win64EHDumper.cpp
@@ -65,6 +65,8 @@ static StringRef getUnwindCodeTypeName(uint8_t Code) {
   case UOP_SaveXMM128: return "SAVE_XMM128";
   case UOP_SaveXMM128Big: return "SAVE_XMM128_FAR";
   case UOP_PushMachFrame: return "PUSH_MACHFRAME";
+  case UOP_Epilog:
+    return "EPILOG";
   }
 }
 
@@ -99,6 +101,7 @@ static unsigned getNumUsedSlots(const UnwindCode &UnwindCode) {
   case UOP_AllocSmall:
   case UOP_SetFPReg:
   case UOP_PushMachFrame:
+  case UOP_Epilog:
     return 1;
   case UOP_SaveNonVol:
   case UOP_SaveXMM128:
@@ -254,7 +257,8 @@ void Dumper::printRuntimeFunctionEntry(const Context &Ctx,
 // Prints one unwind code. Because an unwind code can occupy up to 3 slots in
 // the unwind codes array, this function requires that the correct number of
 // slots is provided.
-void Dumper::printUnwindCode(const UnwindInfo& UI, ArrayRef<UnwindCode> UC) {
+void Dumper::printUnwindCode(const UnwindInfo &UI, ArrayRef<UnwindCode> UC,
+                             bool &SeenFirstEpilog) {
   assert(UC.size() >= getNumUsedSlots(UC[0]));
 
   SW.startLine() << format("0x%02X: ", unsigned(UC[0].u.CodeOffset))
@@ -306,6 +310,24 @@ void Dumper::printUnwindCode(const UnwindInfo& UI, ArrayRef<UnwindCode> UC) {
   case UOP_PushMachFrame:
     OS << " errcode=" << (UC[0].getOpInfo() == 0 ? "no" : "yes");
     break;
+
+  case UOP_Epilog:
+    if (SeenFirstEpilog) {
+      uint32_t Offset =
+          (UC[0].getOpInfo() << 8) | static_cast<uint32_t>(UC[0].u.CodeOffset);
+      if (Offset == 0) {
+        OS << " padding";
+      } else {
+        OS << " offset=" << format("0x%X", Offset);
+      }
+    } else {
+      SeenFirstEpilog = true;
+      bool AtEnd = (UC[0].getOpInfo() & 0x1) != 0;
+      uint32_t Length = UC[0].u.CodeOffset;
+      OS << " atend=" << (AtEnd ? "yes" : "no")
+         << ", length=" << format("0x%X", Length);
+    }
+    break;
   }
 
   OS << "\n";
@@ -330,6 +352,7 @@ void Dumper::printUnwindInfo(const Context &Ctx, const coff_section *Section,
   {
     ListScope UCS(SW, "UnwindCodes");
     ArrayRef<UnwindCode> UC(&UI.UnwindCodes[0], UI.NumCodes);
+    bool SeenFirstEpilog = false;
     for (const UnwindCode *UCI = UC.begin(), *UCE = UC.end(); UCI < UCE; ++UCI) {
       unsigned UsedSlots = getNumUsedSlots(*UCI);
       if (UsedSlots > UC.size()) {
@@ -337,7 +360,7 @@ void Dumper::printUnwindInfo(const Context &Ctx, const coff_section *Section,
         return;
       }
 
-      printUnwindCode(UI, ArrayRef(UCI, UCE));
+      printUnwindCode(UI, ArrayRef(UCI, UCE), SeenFirstEpilog);
       UCI = UCI + UsedSlots - 1;
     }
   }
diff --git a/llvm/tools/llvm-readobj/Win64EHDumper.h b/llvm/tools/llvm-readobj/Win64EHDumper.h
index 97458c916bec6f..a23d30be7a113d 100644
--- a/llvm/tools/llvm-readobj/Win64EHDumper.h
+++ b/llvm/tools/llvm-readobj/Win64EHDumper.h
@@ -44,7 +44,8 @@ class Dumper {
                                  const object::coff_section *Section,
                                  uint64_t SectionOffset,
                                  const RuntimeFunction &RF);
-  void printUnwindCode(const UnwindInfo& UI, ArrayRef<UnwindCode> UC);
+  void printUnwindCode(const UnwindInfo &UI, ArrayRef<UnwindCode> UC,
+                       bool &SeenFirstEpilog);
   void printUnwindInfo(const Context &Ctx, const object::coff_section *Section,
                        off_t Offset, const UnwindInfo &UI);
   void printRuntimeFunction(const Context &Ctx,

@efriedma-quic
Copy link
Collaborator

Can you use yaml2obj to write testcases?

12 bits seems a bit small, but I guess I'll see how you address that in followup patches.

@dpaoliello
Copy link
Contributor Author

Can you use yaml2obj to write testcases?

Done

12 bits seems a bit small, but I guess I'll see how you address that in followup patches.

Yep, it's one of the limitations of v2: epilogs must be within 4Kb of the function end.

Essentially, there will be two different modes that can be enabled:

  • Enable v2 unwind info: this will emit v2 where possible, but fallback to v1 where requirements are not met.
  • Require v2 unwind info: this will always emit v2, which means that we will need to change code generation to meet the requirements (move epilogs to within 4Kb of end, all prologs must be in "canonical" form, etc.).

@efriedma-quic
Copy link
Collaborator

AArch64 Windows has a similar limitation to function size (the function size limit is 20 bits, but still similar idea). The way we handle it there is that we emit multiple EH records for the function (the first record contains a normal prologue, the subsequent records are marked up to indicate they don't contain the prologue).

Is there some reason we can't do that for x64?

@dpaoliello
Copy link
Contributor Author

AArch64 Windows has a similar limitation to function size (the function size limit is 20 bits, but still similar idea). The way we handle it there is that we emit multiple EH records for the function (the first record contains a normal prologue, the subsequent records are marked up to indicate they don't contain the prologue).

Is there some reason we can't do that for x64?

@pmsjt thoughts on splitting EH records to workaround the 4Kb offset limit? I know that we can chain unwind records, but I'm not sure if we can set a different function end location on each of the chained records.

@dpaoliello
Copy link
Contributor Author

Ping...

Characteristics: [ IMAGE_SCN_CNT_CODE, IMAGE_SCN_MEM_EXECUTE, IMAGE_SCN_MEM_READ ]
VirtualAddress: 4096
VirtualSize: 113
SectionData: C3662E0F1F8400000000000F1F4400005048890C2458C3660F1F8400000000004883EC38E8D7FFFFFFE900000000488D4C2430E8D8FFFFFF904883C438C3488944242889542424488D4C2430E8BFFFFFFF488B4C2428E805000000CC0F1F4000C3662E0F1F8400000000000F1F440000C3
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the test doesn't actually parse the contents of the text section, please zero it out. (We don't want difficult-to-audit bits of binary data in the regression tests where we can avoid it.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure.

case UOP_Epilog:
if (SeenFirstEpilog) {
uint32_t Offset = (UCs[0].getOpInfo() << 8) |
static_cast<uint32_t>(UCs[0].u.CodeOffset);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to package up this math into a helper in Win64EH.h?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea.

Copy link
Collaborator

@efriedma-quic efriedma-quic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@dpaoliello dpaoliello merged commit 2201164 into llvm:main Jan 14, 2025
8 checks passed
@dpaoliello dpaoliello deleted the dumpepilog branch January 14, 2025 00:53
@Prabhuk
Copy link
Contributor

Prabhuk commented Jan 14, 2025

We are seeing failures in our Windows clang builders and this PR seems to be the only one related to Windows from the blamelist. I am trying to verify if this PR is the root cause. Find below the links to the failing builder and the logs. Thank you.

Builder: https://luci-milo.appspot.com/ui/p/fuchsia/builders/prod/clang-windows-x64/b8725790748165344833
Logs: https://logs.chromium.org/logs/fuchsia/buildbucket/cr-buildbucket/8725790748165344833/+/u/clang/test/stdout

Failure:

FAILED: tools/polly/unittests/Flatten/CMakeFiles/FlattenTests.dir/C_/b/s/w/ir/x/w/llvm-llvm-project/llvm/resources/windows_version_resource.rc.res 
C:/b/s/w/ir/x/w/CIPD_T~1/fuchsia/THIRD_~1/cmake/72C014~1/bin/cmcldeps.exe RC C:/b/s/w/ir/x/w/llvm-llvm-project/llvm/resources/windows_version_resource.rc tools\polly\unittests\Flatten\CMakeFiles\FlattenTests.dir\C_\b\s\w\ir\x\w\llvm-llvm-project\llvm\resources\windows_version_resource.rc.res.d tools/polly/unittests/Flatten/CMakeFiles/FlattenTests.dir/C_/b/s/w/ir/x/w/llvm-llvm-project/llvm/resources/windows_version_resource.rc.res "Note: including file: " "C:/b/s/w/ir/x/w/llvm_build/./bin/clang-cl.exe" C:\b\s\w\ir\cache\WINDOW~1\WINDOW~1\10\bin\100190~1.0\x64\rc.exe -DGTEST_HAS_RTTI=0 -DLLVM_BUILD_STATIC -DUNICODE -D_CRT_NONSTDC_NO_DEPRECATE -D_CRT_NONSTDC_NO_WARNINGS -D_CRT_SECURE_NO_DEPRECATE -D_CRT_SECURE_NO_WARNINGS -D_GNU_SOURCE -D_HAS_EXCEPTIONS=0 -D_SCL_SECURE_NO_DEPRECATE -D_SCL_SECURE_NO_WARNINGS -D_UNICODE -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS -DRC_FILE_VERSION=\"0\" -DRC_INTERNAL_NAME=\"FlattenTests\" -DRC_PRODUCT_NAME=\"LLVM\" -DRC_PRODUCT_VERSION=\"0\" -DRC_VERSION_FIELD_1=20 -DRC_VERSION_FIELD_2=0 -DRC_VERSION_FIELD_3=0 -DRC_VERSION_FIELD_4=0 -I C:/b/s/w/ir/x/w/llvm_build/tools/clang/stage2-bins/tools/polly/unittests/Flatten -I C:/b/s/w/ir/x/w/llvm-llvm-project/polly/unittests/Flatten -I C:/b/s/w/ir/x/w/llvm_build/tools/clang/stage2-bins/tools/polly/include -I C:/b/s/w/ir/x/w/llvm-llvm-project/polly/lib/External -I C:/b/s/w/ir/x/w/llvm-llvm-project/polly/lib/External/pet/include -I C:/b/s/w/ir/x/w/llvm-llvm-project/polly/lib/External/isl/include -I C:/b/s/w/ir/x/w/llvm_build/tools/clang/stage2-bins/tools/polly/lib/External/isl/include -I C:/b/s/w/ir/x/w/llvm-llvm-project/polly/include -I C:/b/s/w/ir/x/w/rc/tensorflow-venv/store/python_venv-hkmrlc4m3ec6leekpl82qkh2kk/contents/Lib/site-packages/tensorflow/include -I C:/b/s/w/ir/x/w/llvm_build/tools/clang/stage2-bins/include -I C:/b/s/w/ir/x/w/llvm-llvm-project/llvm/include -I C:/b/s/w/ir/x/w/llvm-llvm-project/third-party/unittest/googletest/include -I C:/b/s/w/ir/x/w/llvm-llvm-project/third-party/unittest/googlemock/include -I C:/b/s/w/ir/x/w/zlib_install_target/include -I C:/b/s/w/ir/x/w/zstd_install/include -DWIN32 /nologo /fo tools/polly/unittests/Flatten/CMakeFiles/FlattenTests.dir/C_/b/s/w/ir/x/w/llvm-llvm-project/llvm/resources/windows_version_resource.rc.res C:/b/s/w/ir/x/w/llvm-llvm-project/llvm/resources/windows_version_resource.rc
error: unable to open output file 'windows_version_resource.i': 'operation not permitted'

1 error generated.

@dpaoliello
Copy link
Contributor Author

We are seeing failures in our Windows clang builders and this PR seems to be the only one related to Windows from the blamelist. I am trying to verify if this PR is the root cause. Find below the links to the failing builder and the logs. Thank you.

I don't understand how this change is related to that failure: I didn't modify anything related to resources, CMake/build system, or polly.

Your builders also had the same failure on the 10th, which was before my change went in: https://luci-milo.appspot.com/ui/p/fuchsia/builders/prod/clang-windows-x64/b8726085201223378929/overview

@Prabhuk
Copy link
Contributor

Prabhuk commented Jan 16, 2025

We are seeing failures in our Windows clang builders and this PR seems to be the only one related to Windows from the blamelist. I am trying to verify if this PR is the root cause. Find below the links to the failing builder and the logs. Thank you.

I don't understand how this change is related to that failure: I didn't modify anything related to resources, CMake/build system, or polly.

Your builders also had the same failure on the 10th, which was before my change went in: https://luci-milo.appspot.com/ui/p/fuchsia/builders/prod/clang-windows-x64/b8726085201223378929/overview

Thanks for the response. This was indeed unrelated to the patch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants