-
Couldn't load subscription status.
- Fork 15k
[lldb] Recognize MTE fault Mach exceptions #159117
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
Conversation
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.
|
@llvm/pr-subscribers-lldb Author: Jonas Devlieghere (JDevlieghere) ChangesRecognize 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:
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,
|
|
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, |
There was a problem hiding this comment.
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.
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 :) |
|
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. |
|
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. |
There was a problem hiding this 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.
…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.
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