Skip to content

Conversation

@JDevlieghere
Copy link
Member

Recognize an MTE tag fault Mach exception. A tag fault is an error reported by Arm's Memory Tagging Extension (MTE) when a memory access attempts to use a pointer with a tag that doesn't match the tag stored with the memory. LLDB will print the tag and address to make the issue easier to diagnose.

This was hand tested by debugging an MTE enabled binary on an iPhone 17 running iOS 26.

rdar://113575216

Recognize an MTE tag fault Mach exception. A tag fault is an error
reported by Arm's Memory Tagging Extension (MTE) when a memory access
attempts to use a pointer with a tag that doesn't match the tag stored
with the memory. LLDB will print the tag and address to make the issue
easier to diagnose.

This was tested by debugging an MTE enabled binary running on an iPhone
17 running iOS 26.
@llvmbot
Copy link
Member

llvmbot commented Sep 16, 2025

@llvm/pr-subscribers-lldb

Author: Jonas Devlieghere (JDevlieghere)

Changes

Recognize an MTE tag fault Mach exception. A tag fault is an error reported by Arm's Memory Tagging Extension (MTE) when a memory access attempts to use a pointer with a tag that doesn't match the tag stored with the memory. LLDB will print the tag and address to make the issue easier to diagnose.

This was hand tested by debugging an MTE enabled binary on an iPhone 17 running iOS 26.

rdar://113575216


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

2 Files Affected:

  • (modified) lldb/source/Plugins/Process/Utility/StopInfoMachException.cpp (+31)
  • (modified) lldb/source/Plugins/Process/Utility/StopInfoMachException.h (+2)
diff --git a/lldb/source/Plugins/Process/Utility/StopInfoMachException.cpp b/lldb/source/Plugins/Process/Utility/StopInfoMachException.cpp
index 29a64a2a03bf0..6853121f3e01c 100644
--- a/lldb/source/Plugins/Process/Utility/StopInfoMachException.cpp
+++ b/lldb/source/Plugins/Process/Utility/StopInfoMachException.cpp
@@ -77,6 +77,35 @@ static void DescribeAddressBriefly(Stream &strm, const Address &addr,
   strm.Printf(".\n");
 }
 
+static constexpr uint8_t g_mte_tag_shift = 64 - 8;
+static constexpr uintptr_t g_mte_tag_mask = (uintptr_t)0x0f << g_mte_tag_shift;
+
+bool StopInfoMachException::DetermineTagMismatch(ExecutionContext &exe_ctx) {
+  const bool IsBadAccess = m_value == 1;            // EXC_BAD_ACCESS
+  const bool IsMTETagFault = (m_exc_code == 0x106); // EXC_ARM_MTE_TAG_FAULT
+  if (!IsBadAccess || !IsMTETagFault)
+    return false;
+
+  if (m_exc_data_count < 2)
+    return false;
+
+  const uint64_t bad_address = m_exc_subcode;
+
+  StreamString strm;
+  strm.Printf("EXC_ARM_MTE_TAG_FAULT (code=%" PRIu64 ", address=0x%" PRIx64
+              ")\n",
+              m_exc_code, bad_address);
+
+  const uint8_t tag = (bad_address & g_mte_tag_mask) >> g_mte_tag_shift;
+  const uint64_t canonical_addr = bad_address & ~g_mte_tag_mask;
+  strm.Printf(
+      "Note: MTE tag mismatch detected: pointer tag=%d, address=0x%" PRIx64,
+      tag, canonical_addr);
+  m_description = std::string(strm.GetString());
+
+  return true;
+}
+
 bool StopInfoMachException::DeterminePtrauthFailure(ExecutionContext &exe_ctx) {
   bool IsBreakpoint = m_value == 6; // EXC_BREAKPOINT
   bool IsBadAccess = m_value == 1;  // EXC_BAD_ACCESS
@@ -266,6 +295,8 @@ const char *StopInfoMachException::GetDescription() {
     case llvm::Triple::aarch64:
       if (DeterminePtrauthFailure(exe_ctx))
         return m_description.c_str();
+      if (DetermineTagMismatch(exe_ctx))
+        return m_description.c_str();
       break;
 
     default:
diff --git a/lldb/source/Plugins/Process/Utility/StopInfoMachException.h b/lldb/source/Plugins/Process/Utility/StopInfoMachException.h
index c612ac400b4c4..c02389e5b3642 100644
--- a/lldb/source/Plugins/Process/Utility/StopInfoMachException.h
+++ b/lldb/source/Plugins/Process/Utility/StopInfoMachException.h
@@ -27,6 +27,8 @@ class StopInfoMachException : public StopInfo {
   /// is auth-related failure, and returns false otherwise.
   bool DeterminePtrauthFailure(ExecutionContext &exe_ctx);
 
+  bool DetermineTagMismatch(ExecutionContext &exe_ctx);
+
 public:
   // Constructors and Destructors
   StopInfoMachException(Thread &thread, uint32_t exc_type,

@DavidSpickett
Copy link
Collaborator

The Linux version for reference - d510b5f

Which uses the tag manager to handle all the masking. Though it appears to be lldb-server side, I'm not sure if this Mach bit is. What's the plan for handling this generally, are you going to use the tag manager object where you can?

Luckily there's not much dynamism to MTE, so copy pasting the fixed mask a few times isn't the end of the world. The tag manager abstraction is more to handle when the entire tagging scheme changes, but would still be nice for it to work for MTE everywhere.

How will this be tested? We can run API tests on iOS, right? I get that only you at Apple are likely to be able to do that with any convenience, but still.

const uint8_t tag = (bad_address & g_mte_tag_mask) >> g_mte_tag_shift;
const uint64_t canonical_addr = bad_address & ~g_mte_tag_mask;
strm.Printf(
"Note: MTE tag mismatch detected: pointer tag=%d, address=0x%" PRIx64,
Copy link
Collaborator

Choose a reason for hiding this comment

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

The Linux version will go and fetch the allocation tag, but I am not sure whether you have access to process here to do that.

@DavidSpickett
Copy link
Collaborator

How will this be tested?

And I don't care about re-using the existing MTE tests so much. A lot of them do tricks like assuming mmap calls will produce memory in a certain sequence that are likely not portable or even stable to begin with :)

@DavidSpickett
Copy link
Collaborator

Also you'd probably save a bunch of time by doing one giant test that checks all the little MTE additions in one go, maybe that is in fact your plan.

@JDevlieghere
Copy link
Member Author

How will this be tested? We can run API tests on iOS, right? I get that only you at Apple are likely to be able to do that with any convenience, but still.

Also you'd probably save a bunch of time by doing one giant test that checks all the little MTE additions in one go, maybe that is in fact your plan.

Yeah, that was indeed my plan. Downstream this was implemented before we had hardware so at the time I wrote a test that uses an emulator (which isn't available publicly). Given that, as you said, only we can run the on-device test suite, I figured it wouldn't add much value to add a small test for this change at this time. We have some more changes in the pipeline (including from @yln) that have more comprehensive testing, so we can do one big MTE test once all the pieces land if that's okay with you.

@DavidSpickett
Copy link
Collaborator

Yes that makes sense.

I try to include some test skeleton with the first changes, but I'm just one author who doesn't have to coordinate with anyone so that's easier to do. Adding a test case later is fine.

I should also note that the Linux MTE tests are not being run regularly. I've thought about a "architecture next" sort of bot but never got the time for it. Point is, more reason to add your own tests.

Copy link
Collaborator

@yln yln left a comment

Choose a reason for hiding this comment

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

This looks good from my side. I will follow up with more patches as @JDevlieghere said.

The first one to prepare for bette testing is here:
#153914 (review)

I will address the feedback there shortly.

@JDevlieghere JDevlieghere merged commit def2020 into llvm:main Sep 17, 2025
11 checks passed
@JDevlieghere JDevlieghere deleted the lldb-mte-tag-fault branch September 17, 2025 18:20
DavidSpickett added a commit that referenced this pull request Sep 18, 2025
…9523)

uintptr_t is usually a good idea when handling pointers, but lldb has to
handle 64-bit addresses that might be from a remote system, on a 32-bit
system.

So I've changed a few instances here to use addr_t which is 64-bit
everywhere.

Before we got:
https://lab.llvm.org/buildbot/#/builders/18/builds/21247

```
../llvm-project/lldb/source/Plugins/Process/Utility/StopInfoMachException.cpp:81:28: error: constexpr variable 'g_mte_tag_mask' must be initialized by a constant expression
   81 | static constexpr uintptr_t g_mte_tag_mask = (uintptr_t)0x0f << g_mte_tag_shift;
      |                            ^                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
../llvm-project/lldb/source/Plugins/Process/Utility/StopInfoMachException.cpp:81:61: note: shift count 56 >= width of type 'uintptr_t' (aka 'unsigned int') (32 bits)
   81 | static constexpr uintptr_t g_mte_tag_mask = (uintptr_t)0x0f << g_mte_tag_shift;
      |                                                             ^
1 error generated.
```

Original code added by #159117.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants