llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-lldb
Author: Yao Qi (qiyao)
<details>
<summary>Changes</summary>
`target create` never returns for a little-endian 32-bit MIPS ELF file,
on any host whose platform advertises `armv7m`/`armv7em` compatibility.
On Darwin that is every platform, since `PlatformDarwin::GetCompatibleArchs()`
lists both for every arm64/arm64e host and falls through to that same list
for any other host core. It is found by `lldb-target-fuzzer`.
Reproduce with a 52-byte ELF32 header (`ELFCLASS32`, `ELFDATA2LSB`,
`e_machine = EM_MIPS`, `e_flags = EF_MIPS_ARCH_32`, everything else zero),
saved as `mips32el.elf`:
```
(lldb) target create mips32el.elf
```
This never returns. Attaching a debugger to the hung process shows the
same three frames looping forever, with the call stack never growing:
```
frame #<!-- -->1: cores_match(core1=eCore_arm_armv7m, core2=eCore_mips32el,
try_inverse=false, ...) at ArchSpec.cpp:1575 [inlined]
frame #<!-- -->2: ArchSpec::IsMatch(rhs=..., match=CompatibleMatch) at
ArchSpec.cpp:1449
frame #<!-- -->3: Platform::IsCompatibleArchitecture(...) at Platform.cpp:1193
```
`cores_match()` checks `core1` against `core2`. When nothing matches and
the caller did not ask for an exact match, it sets `try_inverse` and
retries once with the two cores swapped. It retries because the rule
tables are not symmetric: a core lists only what it accepts, not everything
that accepts it.
Every case that reaches this fallback must leave `try_inverse` false on the
way out, so the swap happens at most once. Three cases,
`eCore_arm_armv7em`, `eCore_arm_armv7m`, and `eCore_mips32el`, instead set
it back to true unconditionally. When both sides of a comparison are one
of these three, the swapped call re-arms `try_inverse`, and the tail call
back into `cores_match()` recurses forever.
This is a tail call, so an optimizing compiler turns the recursion into a
loop instead of a growing stack. That is why the process spins instead of
crashing with a stack overflow.
The three cases cannot simply leave `try_inverse` false like their
siblings. `ArchSpec("mipsel").IsCompatibleMatch(ArchSpec("mips64el"))` is
true today only because of this same swap: `eCore_mips32el`'s own rule says
nothing about 64-bit MIPS, but `eCore_mips64el`'s rule accepts any 32-bit
MIPS core with the same endianness, and only the inverted comparison
reaches that rule.
Instead, split the switch into a one-directional helper that never
recurses, and try both directions from a plain, non-recursive wrapper:
```
cores_match(A, B) := cores_match_one_direction(A, B) ||
cores_match_one_direction(B, A)
```
This keeps every existing one-directional rule exactly as it was, including
the mips32el/mips64el rule above, and makes a second retry impossible no
matter what any case does.
Adds two regression tests to ArchSpecTest.cpp. One asserts that the
previously-hanging pairs now return false right away. The other locks in
the two relationships that depend on checking both directions, so a future
change cannot bring the recursion back by "fixing" the three cases the
naive way.
Assisted-by: claude
---
Full diff: https://github.com/llvm/llvm-project/pull/221899.diff
2 Files Affected:
- (modified) lldb/source/Utility/ArchSpec.cpp (+17-25)
- (modified) lldb/unittests/Utility/ArchSpecTest.cpp (+40)
``````````diff
diff --git a/lldb/source/Utility/ArchSpec.cpp b/lldb/source/Utility/ArchSpec.cpp
index 0c9202e3b5e43..af2299e3c7480 100644
--- a/lldb/source/Utility/ArchSpec.cpp
+++ b/lldb/source/Utility/ArchSpec.cpp
@@ -25,7 +25,7 @@ using namespace lldb;
using namespace lldb_private;
static bool cores_match(const ArchSpec::Core core1, const ArchSpec::Core core2,
- bool try_inverse, bool enforce_exact_match);
+ bool enforce_exact_match);
namespace lldb_private {
@@ -1446,7 +1446,7 @@ static bool
IsCompatibleEnvironment(llvm::Triple::EnvironmentType lhs,
bool ArchSpec::IsMatch(const ArchSpec &rhs, MatchType match) const {
if (GetByteOrder() != rhs.GetByteOrder() ||
- !cores_match(GetCore(), rhs.GetCore(), true, match == ExactMatch))
+ !cores_match(GetCore(), rhs.GetCore(), match == ExactMatch))
return false;
const llvm::Triple &lhs_triple = GetTriple();
@@ -1570,11 +1570,12 @@ void ArchSpec::CoreUpdated(bool update_triple) {
//===----------------------------------------------------------------------===//
// Operators.
-static bool cores_match(const ArchSpec::Core core1, const ArchSpec::Core core2,
- bool try_inverse, bool enforce_exact_match) {
- if (core1 == core2)
- return true;
-
+// Checks core1's rules only, because they are not symmetric. Must not call
+// cores_match() here, or an unmatched pair would loop between the two
+// directions forever.
+static bool cores_match_one_direction(const ArchSpec::Core core1,
+ const ArchSpec::Core core2,
+ bool enforce_exact_match) {
switch (core1) {
case ArchSpec::kCore_any:
return true;
@@ -1642,7 +1643,6 @@ static bool cores_match(const ArchSpec::Core core1, const
ArchSpec::Core core2,
return true;
if (core2 == ArchSpec::eCore_arm_armv7)
return true;
- try_inverse = true;
}
break;
@@ -1660,7 +1660,6 @@ static bool cores_match(const ArchSpec::Core core1, const
ArchSpec::Core core2,
return true;
if (core2 == ArchSpec::eCore_arm_armv7em)
return true;
- try_inverse = true;
}
break;
@@ -1678,7 +1677,6 @@ static bool cores_match(const ArchSpec::Core core1, const
ArchSpec::Core core2,
return true;
if (core2 == ArchSpec::eCore_arm_armv6m)
return true;
- try_inverse = false;
}
break;
@@ -1692,14 +1690,12 @@ static bool cores_match(const ArchSpec::Core core1,
const ArchSpec::Core core2,
return true;
if (core2 == ArchSpec::eCore_arm_armv7)
return true;
- try_inverse = false;
}
break;
case ArchSpec::eCore_x86_64_x86_64h:
case ArchSpec::eCore_x86_64_amd64:
if (!enforce_exact_match) {
- try_inverse = false;
if (core2 == ArchSpec::eCore_x86_64_x86_64)
return true;
}
@@ -1713,7 +1709,6 @@ static bool cores_match(const ArchSpec::Core core1, const
ArchSpec::Core core2,
return true;
if (core2 == ArchSpec::eCore_arm_arm64e)
return true;
- try_inverse = false;
}
break;
@@ -1725,7 +1720,6 @@ static bool cores_match(const ArchSpec::Core core1, const
ArchSpec::Core core2,
return true;
if (core2 == ArchSpec::eCore_arm_armv8)
return true;
- try_inverse = false;
}
break;
case ArchSpec::eCore_arm_aarch64:
@@ -1736,7 +1730,6 @@ static bool cores_match(const ArchSpec::Core core1, const
ArchSpec::Core core2,
return true;
if (core2 == ArchSpec::eCore_arm_arm64e)
return true;
- try_inverse = false;
}
break;
@@ -1748,7 +1741,6 @@ static bool cores_match(const ArchSpec::Core core1, const
ArchSpec::Core core2,
return true;
if (core2 == ArchSpec::eCore_arm_arm64e)
return true;
- try_inverse = false;
}
break;
@@ -1756,7 +1748,6 @@ static bool cores_match(const ArchSpec::Core core1, const
ArchSpec::Core core2,
if (!enforce_exact_match) {
if (core2 == ArchSpec::eCore_arm_generic)
return true;
- try_inverse = false;
}
break;
@@ -1765,7 +1756,6 @@ static bool cores_match(const ArchSpec::Core core1, const
ArchSpec::Core core2,
if (core2 >= ArchSpec::kCore_mips32_first &&
core2 <= ArchSpec::kCore_mips32_last)
return true;
- try_inverse = false;
}
break;
@@ -1774,7 +1764,6 @@ static bool cores_match(const ArchSpec::Core core1, const
ArchSpec::Core core2,
if (core2 >= ArchSpec::kCore_mips32el_first &&
core2 <= ArchSpec::kCore_mips32el_last)
return true;
- try_inverse = true;
}
break;
@@ -1786,7 +1775,6 @@ static bool cores_match(const ArchSpec::Core core1, const
ArchSpec::Core core2,
if (core2 >= ArchSpec::kCore_mips64_first &&
core2 <= ArchSpec::kCore_mips64_last)
return true;
- try_inverse = false;
}
break;
@@ -1798,7 +1786,6 @@ static bool cores_match(const ArchSpec::Core core1, const
ArchSpec::Core core2,
if (core2 >= ArchSpec::kCore_mips64el_first &&
core2 <= ArchSpec::kCore_mips64el_last)
return true;
- try_inverse = false;
}
break;
@@ -1810,7 +1797,6 @@ static bool cores_match(const ArchSpec::Core core1, const
ArchSpec::Core core2,
return true;
if (core2 >= ArchSpec::kCore_mips64_first && core2 <= (core1 - 1))
return true;
- try_inverse = false;
}
break;
@@ -1822,7 +1808,6 @@ static bool cores_match(const ArchSpec::Core core1, const
ArchSpec::Core core2,
return true;
if (core2 >= ArchSpec::kCore_mips64el_first && core2 <= (core1 - 1))
return true;
- try_inverse = false;
}
break;
@@ -1882,11 +1867,18 @@ static bool cores_match(const ArchSpec::Core core1,
const ArchSpec::Core core2,
default:
break;
}
- if (try_inverse)
- return cores_match(core2, core1, false, enforce_exact_match);
return false;
}
+// The match rules only work in one direction, so this checks both ways.
+static bool cores_match(const ArchSpec::Core core1, const ArchSpec::Core core2,
+ bool enforce_exact_match) {
+ if (core1 == core2)
+ return true;
+ return cores_match_one_direction(core1, core2, enforce_exact_match) ||
+ cores_match_one_direction(core2, core1, enforce_exact_match);
+}
+
bool lldb_private::operator<(const ArchSpec &lhs, const ArchSpec &rhs) {
const ArchSpec::Core lhs_core = lhs.GetCore();
const ArchSpec::Core rhs_core = rhs.GetCore();
diff --git a/lldb/unittests/Utility/ArchSpecTest.cpp
b/lldb/unittests/Utility/ArchSpecTest.cpp
index 7027c7648a273..25ce85dd7f9f7 100644
--- a/lldb/unittests/Utility/ArchSpecTest.cpp
+++ b/lldb/unittests/Utility/ArchSpecTest.cpp
@@ -482,6 +482,46 @@ TEST(ArchSpecTest, Compatibility) {
}
}
+TEST(ArchSpecTest, UnrelatedCoresCompatibleMatchTerminates) {
+ {
+ ArchSpec A("mipsel-unknown-linux");
+ ArchSpec B("armv7em-apple-none");
+ ASSERT_FALSE(A.IsCompatibleMatch(B));
+ ASSERT_FALSE(B.IsCompatibleMatch(A));
+ }
+ {
+ ArchSpec A("mipsel-unknown-linux");
+ ArchSpec B("armv7m-apple-none");
+ ASSERT_FALSE(A.IsCompatibleMatch(B));
+ ASSERT_FALSE(B.IsCompatibleMatch(A));
+ }
+}
+
+TEST(ArchSpecTest, AsymmetricCoreRulesAreCheckedBothWays) {
+ // Some cores' compatibility rules only work one way: core A accepts B,
+ // but B's own rule says nothing about A. A compatible match must check
+ // both directions to find these cases.
+ {
+ // A 32-bit MIPS core has no rule about 64-bit MIPS, but 64-bit MIPS
+ // accepts the 32-bit family with the same endianness.
+ ArchSpec A("mipsel-unknown-linux");
+ ArchSpec B("mips64el-unknown-linux");
+ ASSERT_TRUE(A.IsCompatibleMatch(B));
+ ASSERT_TRUE(B.IsCompatibleMatch(A));
+ ASSERT_FALSE(A.IsExactMatch(B));
+ ASSERT_FALSE(B.IsExactMatch(A));
+ }
+ {
+ // The two Cortex-M cores explicitly accept each other.
+ ArchSpec A("armv7em-apple-none");
+ ArchSpec B("armv7m-apple-none");
+ ASSERT_TRUE(A.IsCompatibleMatch(B));
+ ASSERT_TRUE(B.IsCompatibleMatch(A));
+ ASSERT_FALSE(A.IsExactMatch(B));
+ ASSERT_FALSE(B.IsExactMatch(A));
+ }
+}
+
TEST(ArchSpecTest, WasmCompatibility) {
// A Wasm module encodes no vendor or OS: those are properties of the runtime
// executing it. A bare wasm32 or wasm64 architecture therefore has to stay
``````````
</details>
https://github.com/llvm/llvm-project/pull/221899
_______________________________________________
lldb-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits