Skip to content

Conversation

igorkudrin
Copy link
Collaborator

The EmulateInstructionARM64::EvaluateInstruction() method used uint32_t to store the PC value, causing addresses greater than 2^32 to be truncated.

As for now, the issue does not affect the mainline because the method is never called with both eEmulateInstructionOptionAutoAdvancePC and eEmulateInstructionOptionIgnoreConditions options set. However, it can trigger on a downstream that uses software stepping.

The `EmulateInstructionARM64::EvaluateInstruction()` method used
`uint32_t` to store the PC value, causing addresses greater than
2^32 to be truncated.

As for now, the issue does not affect the mainline because the method is
never called with both `eEmulateInstructionOptionAutoAdvancePC` and
`eEmulateInstructionOptionIgnoreConditions` options set. However, it can
trigger on a downstream that uses software stepping.
@llvmbot
Copy link
Member

llvmbot commented Jul 31, 2025

@llvm/pr-subscribers-lldb

Author: Igor Kudrin (igorkudrin)

Changes

The EmulateInstructionARM64::EvaluateInstruction() method used uint32_t to store the PC value, causing addresses greater than 2^32 to be truncated.

As for now, the issue does not affect the mainline because the method is never called with both eEmulateInstructionOptionAutoAdvancePC and eEmulateInstructionOptionIgnoreConditions options set. However, it can trigger on a downstream that uses software stepping.


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

2 Files Affected:

  • (modified) lldb/source/Plugins/Instruction/ARM64/EmulateInstructionARM64.cpp (+2-2)
  • (modified) lldb/unittests/Instruction/ARM64/TestAArch64Emulator.cpp (+125-1)
diff --git a/lldb/source/Plugins/Instruction/ARM64/EmulateInstructionARM64.cpp b/lldb/source/Plugins/Instruction/ARM64/EmulateInstructionARM64.cpp
index 29f03fee47b0d..a8901beda3970 100644
--- a/lldb/source/Plugins/Instruction/ARM64/EmulateInstructionARM64.cpp
+++ b/lldb/source/Plugins/Instruction/ARM64/EmulateInstructionARM64.cpp
@@ -404,7 +404,7 @@ bool EmulateInstructionARM64::EvaluateInstruction(uint32_t evaluate_options) {
   if (!success && !m_ignore_conditions)
     return false;
 
-  uint32_t orig_pc_value = 0;
+  uint64_t orig_pc_value = 0;
   if (auto_advance_pc) {
     orig_pc_value =
         ReadRegisterUnsigned(eRegisterKindLLDB, gpr_pc_arm64, 0, &success);
@@ -418,7 +418,7 @@ bool EmulateInstructionARM64::EvaluateInstruction(uint32_t evaluate_options) {
     return false;
 
   if (auto_advance_pc) {
-    uint32_t new_pc_value =
+    uint64_t new_pc_value =
         ReadRegisterUnsigned(eRegisterKindLLDB, gpr_pc_arm64, 0, &success);
     if (!success)
       return false;
diff --git a/lldb/unittests/Instruction/ARM64/TestAArch64Emulator.cpp b/lldb/unittests/Instruction/ARM64/TestAArch64Emulator.cpp
index 4506c200dee3b..c021d994bb062 100644
--- a/lldb/unittests/Instruction/ARM64/TestAArch64Emulator.cpp
+++ b/lldb/unittests/Instruction/ARM64/TestAArch64Emulator.cpp
@@ -13,15 +13,124 @@
 #include "lldb/Core/Disassembler.h"
 #include "lldb/Target/ExecutionContext.h"
 #include "lldb/Utility/ArchSpec.h"
+#include "lldb/Utility/RegisterValue.h"
 
 #include "Plugins/Instruction/ARM64/EmulateInstructionARM64.h"
+#include "Plugins/Process/Utility/RegisterInfoPOSIX_arm64.h"
+#include "Plugins/Process/Utility/lldb-arm64-register-enums.h"
 
 using namespace lldb;
 using namespace lldb_private;
 
 struct Arch64EmulatorTester : public EmulateInstructionARM64 {
+  RegisterInfoPOSIX_arm64::GPR gpr;
+  uint8_t memory[64] = {0};
+  uint64_t memory_offset = 0;
+
   Arch64EmulatorTester()
-      : EmulateInstructionARM64(ArchSpec("arm64-apple-ios")) {}
+      : EmulateInstructionARM64(ArchSpec("arm64-apple-ios")) {
+    memset(&gpr, 0, sizeof(gpr));
+    EmulateInstruction::SetCallbacks(ReadMemoryCallback, WriteMemoryCallback,
+                                     ReadRegisterCallback,
+                                     WriteRegisterCallback);
+  }
+
+  static bool ReadRegisterCallback(EmulateInstruction *instruction, void *baton,
+                                   const RegisterInfo *reg_info,
+                                   RegisterValue &reg_value) {
+    auto *tester = static_cast<Arch64EmulatorTester *>(instruction);
+    uint32_t reg = reg_info->kinds[eRegisterKindLLDB];
+    if (reg >= gpr_x1_arm64 && reg <= gpr_x28_arm64) {
+      reg_value.SetUInt64(tester->gpr.x[reg - gpr_x0_arm64]);
+      return true;
+    }
+    if (reg >= gpr_w1_arm64 && reg <= gpr_w28_arm64) {
+      reg_value.SetUInt32(tester->gpr.x[reg - gpr_w0_arm64]);
+      return true;
+    }
+    switch (reg) {
+    case gpr_x0_arm64:
+      reg_value.SetUInt64(0);
+      return true;
+    case gpr_w0_arm64:
+      reg_value.SetUInt32(0);
+      return true;
+    case gpr_fp_arm64:
+      reg_value.SetUInt64(tester->gpr.fp);
+      return true;
+    case gpr_lr_arm64:
+      reg_value.SetUInt64(tester->gpr.lr);
+      return true;
+    case gpr_sp_arm64:
+      reg_value.SetUInt64(tester->gpr.sp);
+      return true;
+    case gpr_pc_arm64:
+      reg_value.SetUInt64(tester->gpr.pc);
+      return true;
+    case gpr_cpsr_arm64:
+      reg_value.SetUInt64(tester->gpr.cpsr);
+      return true;
+    default:
+      return false;
+    }
+  }
+
+  static bool WriteRegisterCallback(EmulateInstruction *instruction,
+                                    void *baton, const Context &context,
+                                    const RegisterInfo *reg_info,
+                                    const RegisterValue &reg_value) {
+    auto *tester = static_cast<Arch64EmulatorTester *>(instruction);
+    uint32_t reg = reg_info->kinds[eRegisterKindLLDB];
+    if (reg >= gpr_x1_arm64 && reg <= gpr_x28_arm64) {
+      tester->gpr.x[reg - gpr_x0_arm64] = reg_value.GetAsUInt64();
+      return true;
+    }
+    if (reg >= gpr_w1_arm64 && reg <= gpr_w28_arm64) {
+      tester->gpr.x[reg - gpr_w0_arm64] = reg_value.GetAsUInt32();
+      return true;
+    }
+    switch (reg) {
+    case gpr_fp_arm64:
+      tester->gpr.fp = reg_value.GetAsUInt64();
+      return true;
+    case gpr_lr_arm64:
+      tester->gpr.lr = reg_value.GetAsUInt64();
+      return true;
+    case gpr_sp_arm64:
+      tester->gpr.sp = reg_value.GetAsUInt64();
+      return true;
+    case gpr_pc_arm64:
+      tester->gpr.pc = reg_value.GetAsUInt64();
+      return true;
+    case gpr_cpsr_arm64:
+      tester->gpr.cpsr = reg_value.GetAsUInt64();
+      return true;
+    default:
+      return false;
+    }
+  }
+
+  static size_t ReadMemoryCallback(EmulateInstruction *instruction, void *baton,
+                                   const Context &context, addr_t addr,
+                                   void *dst, size_t length) {
+    auto *tester = static_cast<Arch64EmulatorTester *>(instruction);
+    assert(addr >= tester->memory_offset);
+    assert(addr - tester->memory_offset + length <= sizeof(tester->memory));
+    if (addr >= tester->memory_offset &&
+        addr - tester->memory_offset + length <= sizeof(tester->memory)) {
+      memcpy(dst, tester->memory + addr - tester->memory_offset, length);
+      return length;
+    }
+    return 0;
+  };
+
+  static size_t WriteMemoryCallback(EmulateInstruction *instruction,
+                                    void *baton, const Context &context,
+                                    addr_t addr, const void *dst,
+                                    size_t length) {
+    // TODO: implement when required
+    return 0;
+  };
 
   static uint64_t AddWithCarry(uint32_t N, uint64_t x, uint64_t y, bool carry_in,
                                EmulateInstructionARM64::ProcState &proc_state) {
@@ -60,3 +169,18 @@ TEST_F(TestAArch64Emulator, TestOverflow) {
   ASSERT_EQ(pstate.V, 1ULL);
   ASSERT_EQ(pstate.C, 0ULL);
 }
+
+TEST_F(TestAArch64Emulator, TestAutoAdvancePC) {
+  Arch64EmulatorTester emu;
+  emu.memory_offset = 0x1234567800;
+  emu.gpr.pc = 0x1234567800;
+  emu.gpr.x[8] = 0x1234567820;
+  memcpy(emu.memory, "\x08\x01\x40\xb9", 4); // ldr w8, [x8]
+  memcpy(emu.memory + 0x20, "\x11\x22\x33\x44", 4); // 0x44332211
+  ASSERT_TRUE(emu.ReadInstruction());
+  ASSERT_TRUE(
+      emu.EvaluateInstruction(eEmulateInstructionOptionAutoAdvancePC |
+                              eEmulateInstructionOptionIgnoreConditions));
+  ASSERT_EQ(emu.gpr.pc, 0x1234567804);
+  ASSERT_EQ(emu.gpr.x[8], 0x44332211);
+}

Copy link

github-actions bot commented Jul 31, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

void *baton, const Context &context,
addr_t addr, const void *dst,
size_t length) {
// TODO: implement when required
Copy link
Collaborator

Choose a reason for hiding this comment

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

Does this ever get called? If not you could add an assert here too.

Copy link
Collaborator

Choose a reason for hiding this comment

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

or even llvm_unreachable

Comment on lines 52 to 57
case gpr_x0_arm64:
reg_value.SetUInt64(0);
return true;
case gpr_w0_arm64:
reg_value.SetUInt32(0);
return true;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why are these tied to 0? AArch64's zero register is xzr and it's not the 0th register as it is for MIPS and RISC-V.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thanks! Fixed.

TEST_F(TestAArch64Emulator, TestAutoAdvancePC) {
Arch64EmulatorTester emu;
emu.memory_offset = 0x1234567800;
emu.gpr.pc = 0x1234567800;
Copy link
Collaborator

Choose a reason for hiding this comment

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

I see that this would be truncated if not for this fix, but please make it a full 64-bit value so that it is as obvious as it can be.

void *baton, const Context &context,
addr_t addr, const void *dst,
size_t length) {
// TODO: implement when required
Copy link
Collaborator

Choose a reason for hiding this comment

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

or even llvm_unreachable

@igorkudrin igorkudrin merged commit 64eba6e into llvm:main Aug 5, 2025
9 checks passed
@igorkudrin igorkudrin deleted the lldb-arm64-autoadvancepc branch August 5, 2025 22:42
@llvm-ci
Copy link
Collaborator

llvm-ci commented Aug 5, 2025

LLVM Buildbot has detected a new failure on builder lldb-arm-ubuntu running on linaro-lldb-arm-ubuntu while building lldb at step 6 "test".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/18/builds/20127

Here is the relevant piece of the build log for the reference
Step 6 (test) failure: build (failure)
...
PASS: lldb-unit :: Instruction/./EmulatorTests/13/62 (2480 of 3379)
PASS: lldb-unit :: Instruction/./EmulatorTests/14/62 (2481 of 3379)
PASS: lldb-unit :: Instruction/./EmulatorTests/15/62 (2482 of 3379)
PASS: lldb-unit :: Instruction/./EmulatorTests/16/62 (2483 of 3379)
PASS: lldb-unit :: Instruction/./EmulatorTests/17/62 (2484 of 3379)
PASS: lldb-unit :: Instruction/./EmulatorTests/18/62 (2485 of 3379)
PASS: lldb-unit :: Instruction/./EmulatorTests/19/62 (2486 of 3379)
PASS: lldb-unit :: Instruction/./EmulatorTests/2/62 (2487 of 3379)
PASS: lldb-unit :: Instruction/./EmulatorTests/20/62 (2488 of 3379)
PASS: lldb-unit :: Instruction/./EmulatorTests/21/62 (2489 of 3379)
FAIL: lldb-unit :: Instruction/./EmulatorTests/1/62 (2490 of 3379)
******************** TEST 'lldb-unit :: Instruction/./EmulatorTests/1/62' FAILED ********************
Script(shard):
--
GTEST_OUTPUT=json:/home/tcwg-buildbot/worker/lldb-arm-ubuntu/build/tools/lldb/unittests/Instruction/./EmulatorTests-lldb-unit-3325209-1-62.json GTEST_SHUFFLE=0 GTEST_TOTAL_SHARDS=62 GTEST_SHARD_INDEX=1 /home/tcwg-buildbot/worker/lldb-arm-ubuntu/build/tools/lldb/unittests/Instruction/./EmulatorTests
--

Note: This is test shard 2 of 62.
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from TestAArch64Emulator
[ RUN      ] TestAArch64Emulator.TestAutoAdvancePC
 #0 0x00d3b164 llvm::sys::PrintStackTrace(llvm::raw_ostream&, int) (/home/tcwg-buildbot/worker/lldb-arm-ubuntu/build/tools/lldb/unittests/Instruction/./EmulatorTests+0x33b164)
 #1 0x00d38640 llvm::sys::RunSignalHandlers() (/home/tcwg-buildbot/worker/lldb-arm-ubuntu/build/tools/lldb/unittests/Instruction/./EmulatorTests+0x338640)
 #2 0x00d3c0ec SignalHandler(int, siginfo_t*, void*) Signals.cpp:0:0
 #3 0xf758d6f0 __default_rt_sa_restorer ./signal/../sysdeps/unix/sysv/linux/arm/sigrestorer.S:80:0
 #4 0x00ca6960 testing::AssertionResult testing::internal::CmpHelperEQ<unsigned long long, long long>(char const*, char const*, unsigned long long const&, long long const&) (/home/tcwg-buildbot/worker/lldb-arm-ubuntu/build/tools/lldb/unittests/Instruction/./EmulatorTests+0x2a6960)
 #5 0x00ca573c TestAArch64Emulator_TestAutoAdvancePC_Test::TestBody() (/home/tcwg-buildbot/worker/lldb-arm-ubuntu/build/tools/lldb/unittests/Instruction/./EmulatorTests+0x2a573c)
 #6 0x00d4f924 testing::Test::Run() (/home/tcwg-buildbot/worker/lldb-arm-ubuntu/build/tools/lldb/unittests/Instruction/./EmulatorTests+0x34f924)
 #7 0x00d50668 testing::TestInfo::Run() (/home/tcwg-buildbot/worker/lldb-arm-ubuntu/build/tools/lldb/unittests/Instruction/./EmulatorTests+0x350668)
 #8 0x00d5128c testing::TestSuite::Run() (/home/tcwg-buildbot/worker/lldb-arm-ubuntu/build/tools/lldb/unittests/Instruction/./EmulatorTests+0x35128c)
 #9 0x00d61f98 testing::internal::UnitTestImpl::RunAllTests() (/home/tcwg-buildbot/worker/lldb-arm-ubuntu/build/tools/lldb/unittests/Instruction/./EmulatorTests+0x361f98)
#10 0x00d61518 testing::UnitTest::Run() (/home/tcwg-buildbot/worker/lldb-arm-ubuntu/build/tools/lldb/unittests/Instruction/./EmulatorTests+0x361518)
#11 0x00d3d354 main (/home/tcwg-buildbot/worker/lldb-arm-ubuntu/build/tools/lldb/unittests/Instruction/./EmulatorTests+0x33d354)
#12 0xf757d7d6 __libc_start_call_main ./csu/../sysdeps/nptl/libc_start_call_main.h:74:3
#13 0xf757d886 call_init ./csu/libc-start.c:128:20
#14 0xf757d886 __libc_start_main ./csu/libc-start.c:379:5

--
exit: -7
--
shard JSON output does not exist: /home/tcwg-buildbot/worker/lldb-arm-ubuntu/build/tools/lldb/unittests/Instruction/./EmulatorTests-lldb-unit-3325209-1-62.json
********************
PASS: lldb-unit :: Instruction/./EmulatorTests/22/62 (2491 of 3379)
PASS: lldb-unit :: Instruction/./EmulatorTests/23/62 (2492 of 3379)
PASS: lldb-unit :: Instruction/./EmulatorTests/24/62 (2493 of 3379)
PASS: lldb-unit :: Instruction/./EmulatorTests/25/62 (2494 of 3379)
PASS: lldb-unit :: Instruction/./EmulatorTests/26/62 (2495 of 3379)
PASS: lldb-unit :: Instruction/./EmulatorTests/27/62 (2496 of 3379)

igorkudrin added a commit that referenced this pull request Aug 5, 2025
… >= 4G (#151460)"

This reverts commit 64eba6e.

It breaks on lldb-arm-ubuntu
@DavidSpickett
Copy link
Collaborator

DavidSpickett commented Aug 6, 2025

Feels like someone stored a uint64_t into a void* or size_t that's 32-bit on Arm 32-bit. Thanks for reverting, I'm looking into it now.

DavidSpickett added a commit that referenced this pull request Aug 6, 2025
… >= 4G (#151460)"

This reverts commit 600976f.

The test was crashing trying to access any element of the GPR struct.

(gdb) disas
Dump of assembler code for function _ZN7testing8internal11CmpHelperEQIyyEENS_15AssertionResultEPKcS4_RKT_RKT0_:
   0x00450afc <+0>:	push	{r4, r5, r6, r7, r8, r9, r10, r11, lr}
   0x00450b00 <+4>:	sub	sp, sp, #60	; 0x3c
   0x00450b04 <+8>:	ldr	r5, [sp, #96]	; 0x60
=> 0x00450b08 <+12>:	ldm	r3, {r4, r7}
   0x00450b0c <+16>:	ldm	r5, {r6, r9}
   0x00450b10 <+20>:	eor	r7, r7, r9
   0x00450b14 <+24>:	eor	r6, r4, r6
   0x00450b18 <+28>:	orrs	r7, r6, r7

(gdb) p/x r3
$3 = 0x3e300f6e

"However, load and store multiple instructions (LDM and STM) and load and store double-word (LDRD or STRD) must be aligned to at least a word boundary."

https://developer.arm.com/documentation/den0013/d/Porting/Alignment

>>> 0x3e300f6e % 4
2

Program received signal SIGBUS, Bus error.
0x00450b08 in testing::AssertionResult testing::internal::CmpHelperEQ<unsigned long long, unsigned long long>(char const*, char const*, unsigned long long const&, unsigned long long const&) ()

The struct is packed with 1 byte alignment, but it needs to start at an aligned address for us
to ldm from it. So I've done that with alignas.

Also fixed some compiler warnings in the test itself.
@DavidSpickett
Copy link
Collaborator

Actually was an alignment issue trying to load from a struct - 49d5dd3.

@igorkudrin
Copy link
Collaborator Author

Actually was an alignment issue trying to load from a struct - 49d5dd3.

Thank you for fixing this!

@JDevlieghere
Copy link
Member

Seems like this is tripping up UBSan:

[----------] 1 test from TestAArch64Emulator
[ RUN      ] TestAArch64Emulator.TestAutoAdvancePC
/Users/ec2-user/jenkins/workspace/llvm.org/lldb-cmake-sanitized/llvm-project/lldb/unittests/Instruction/ARM64/TestAArch64Emulator.cpp:115:34: runtime error: index 1311768467463790080 out of bounds for type 'uint8_t[64]' (aka 'unsigned char[64]')
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior /Users/ec2-user/jenkins/workspace/llvm.org/lldb-cmake-sanitized/llvm-project/lldb/unittests/Instruction/ARM64/TestAArch64Emulator.cpp:115:34 in 

igorkudrin added a commit that referenced this pull request Aug 7, 2025
@igorkudrin
Copy link
Collaborator Author

Seems like this is tripping up UBSan:

Thanks for reporting, fixed in 11e1d46

krishna2803 pushed a commit to krishna2803/llvm-project that referenced this pull request Aug 12, 2025
…lvm#151460)

The `EmulateInstructionARM64::EvaluateInstruction()` method used
`uint32_t` to store the PC value, causing addresses greater than 2^32 to
be truncated.

As for now, the issue does not affect the mainline because the method is
never called with both `eEmulateInstructionOptionAutoAdvancePC` and
`eEmulateInstructionOptionIgnoreConditions` options set. However, it can
trigger on a downstream that uses software stepping.
krishna2803 pushed a commit to krishna2803/llvm-project that referenced this pull request Aug 12, 2025
… >= 4G (llvm#151460)"

This reverts commit 64eba6e.

It breaks on lldb-arm-ubuntu
krishna2803 pushed a commit to krishna2803/llvm-project that referenced this pull request Aug 12, 2025
… >= 4G (llvm#151460)"

This reverts commit 600976f.

The test was crashing trying to access any element of the GPR struct.

(gdb) disas
Dump of assembler code for function _ZN7testing8internal11CmpHelperEQIyyEENS_15AssertionResultEPKcS4_RKT_RKT0_:
   0x00450afc <+0>:	push	{r4, r5, r6, r7, r8, r9, r10, r11, lr}
   0x00450b00 <+4>:	sub	sp, sp, llvm#60	; 0x3c
   0x00450b04 <+8>:	ldr	r5, [sp, llvm#96]	; 0x60
=> 0x00450b08 <+12>:	ldm	r3, {r4, r7}
   0x00450b0c <+16>:	ldm	r5, {r6, r9}
   0x00450b10 <+20>:	eor	r7, r7, r9
   0x00450b14 <+24>:	eor	r6, r4, r6
   0x00450b18 <+28>:	orrs	r7, r6, r7

(gdb) p/x r3
$3 = 0x3e300f6e

"However, load and store multiple instructions (LDM and STM) and load and store double-word (LDRD or STRD) must be aligned to at least a word boundary."

https://developer.arm.com/documentation/den0013/d/Porting/Alignment

>>> 0x3e300f6e % 4
2

Program received signal SIGBUS, Bus error.
0x00450b08 in testing::AssertionResult testing::internal::CmpHelperEQ<unsigned long long, unsigned long long>(char const*, char const*, unsigned long long const&, unsigned long long const&) ()

The struct is packed with 1 byte alignment, but it needs to start at an aligned address for us
to ldm from it. So I've done that with alignas.

Also fixed some compiler warnings in the test itself.
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.

6 participants