ARM Architecture Debate: SL vs. THR Registers in Dart AOT true/false

Posted on Aug 31, 2024

The Debate:

A lively discussion took place in a Telegram group called TDOHex Discussion in April 2024, where two individuals debated which register is used for handling true/false in ARM architecture within Dart Ahead-of-Time (AOT) compilation. While it was an insightful exchange, it’s unfortunate that in a group of over 1,000 members, only these two showed interest in the topic. Now, almost half a year later in September 2024, the discussion remains unresolved, with no further contributions from others. Here’s a breakdown of what went down:

NOTE: This article covers only arm architecture i.e. arm32 as solution for the debate and also you all are already familiar with the 64-bit architecture example:

  • Assembly for return true in ARM64:
add x0, x22, 0x20 // x22 is the NULL register in dart
  • Assembly for return false in ARM64:
add x0, x22, 0x30 // x22 is the NULL register in dart

Side 1:
They claimed that the THR (Thread) register is used to handle true/false. Their analysis, done back in 2021, mentioned this code snippet:

ldr r2, [thr, 0x70]

Side 2:
The other person argued that the SL (Stack Limit) register is responsible for handling true/false. Their analysis from 2024 provided this code:

ldr r0, [sl, 0x38]

Surprisingly, both were right! 😲 But technically, one of them was more right.


The Surprising Truth: Both Are Correct! 🎉

Yes, both sides were correct, but with a twist. Technically, Side 1’s answer is more accurate. However, Side 2 would’ve been right if not for an issue with Radare2’s disassembly tool, which led to some confusion.

Breaking Down the Registers:

  1. THR Register: Side 1’s analysis refers to the THR (Thread) register, which is used for holding a pointer to the current running thread. According to the ARM Developer’s website, the r10 register (also called v7 or sl) serves as the Stack Limit pointer in stack-checked variants:
    Register Synonym Special Role in the procedure call standard
    r10 v7 sl ARM-state variable register 7. Stack limit pointer in stack-checked variants.
  2. SL Register: Side 2’s analysis mentioned the SL register. However, the confusion arises because Dart’s SDK uses the r10 register (which can be referred to as sl) for THR.

Diving into the Dart SDK 📂

Let’s look at the Dart SDK to clarify things further:

Dart SDK Register Constants

In the constants_arm.h file of the Dart SDK, you’ll see that r10 is defined as THR:

R10 = 10,  // THR

Offsets for Objects

In the runtime_offsets_extracted.h, you’ll find offsets used to determine object values for thread:

~ For Stack Limit:

  static constexpr dart::compiler::target::word AOT_Thread_stack_limit_offset = 0x38;

So, for Stack Limit, it would be called by [r10 + 0x38].

~ For NULL:

  static constexpr dart::compiler::target::word AOT_Thread_object_null_offset = 0x68;

Note: Dart recently updated this offset to 0x70 in its latest commit, but not all developers are using this yet.

**True/False in Dart **

In some Dart SDK versions like 3.4.0, the true/false values are handled with these offsets:

static constexpr dart::compiler::target::word Thread_bool_false_offset = 0x3c;
static constexpr dart::compiler::target::word Thread_bool_true_offset = 0x38;

So, the assembly code would look like this:

  • For false: ldr r0, [THR, 0x3c]
  • For true: ldr r0, [THR, 0x38]
  • Here too, recently Dart made changes to these registers which you’ll encounter in future

The Radare2 Confusion 🤔

Radare2, a popular disassembly tool, mistakenly showed the code using the sl register, which led Side 2 to conclude that SL was responsible for handling true/false. But in reality, it’s THR (which uses r10 as well).

Check out the screenshots that clarify this misunderstanding:

  1. Radare2 Output (Mistaken):

Radare2 SL Register

  1. To Confirm:

armconverter output

The Final Verdict 🏆

~ Side 1’s Analysis (2021) was based on the older offset, which was based on that time dart version.

~ Side 2’s Analysis (2024) was confused by Radare2’s incorrect disassembly, leading them to the wrong conclusion.

But with the Dart SDK’s updates and some careful analysis, it’s clear that the THR register (r10) is indeed the correct register to look at for true/false values in Dart AOT, not SL.

Hope this clears up the confusion! 😁 Happy Reversing! 🔧💻