Repository: DynamoRIO/dynamorio (2026-06-16T01:51:46Z)

Issue:
drmemtrace view tool gets SVE disp wrong b/c doesn't set vector length
While testing https://github.com/DynamoRIO/dynamorio/pull/7915 I noticed that DR prints this instruction with a 16-byte displacement:

```
  0x000000000040018c  a401a03c   ld1b   +0x10(%x1)[1byte] %p0/z -> %z28.b
```

objdump says:
```
000000000040018c <test_scalar_plus_immediate>:
  40018c:       a401a03c        ld1b    {z28.b}, p0/z, [x1, #1, mul vl]
```

Yet the drmemtrace view tool says:

```
          25          17:  W0.T814952 ifetch       4 byte(s) @ 0x000000000040018c a401a03c   ld1b   (%x1)[1byte] %p0/z -> %z28.b
```

So the displacement from the ISA is 1*(vector length in bytes).

The view tool hasn't set the vector length, and the default is 0.

For how to do it: see how raw2trace.cpp or invariant_checker.cpp does it on the vector length marker.

Best to set in decode_cache.cpp: but it doesn't see all the trace records.


Discussion:
Xref #6646 which argues tools should not have to do this: the default should be something reasonable and not 0.



Patch:
diff --git a/clients/drcachesim/tools/view.cpp b/clients/drcachesim/tools/view.cpp
index bd9f4defd5..8125a9e056 100644
--- a/clients/drcachesim/tools/view.cpp
+++ b/clients/drcachesim/tools/view.cpp
@@ -447,10 +447,33 @@ view_t::parallel_shard_memref(void *shard_data, const memref_t &memref)
             std::cerr << "<marker: wait for another core>\n";
             break;
         case TRACE_MARKER_TYPE_CORE_IDLE: std::cerr << "<marker: core is idle>\n"; break;
-        case TRACE_MARKER_TYPE_VECTOR_LENGTH:
+        case TRACE_MARKER_TYPE_VECTOR_LENGTH: {
             std::cerr << "<marker: vector length " << memref.marker.marker_value
                       << " bytes>\n";
+#ifdef AARCH64
+            const int new_vl_bits = memref.marker.marker_value * 8;
+            if (dr_get_vector_length() != new_vl_bits) {
+                // XXX i#6646: Ideally all uses wouldn't have to set this (we have to do
+                // this in raw2trace and invariant_checker as well) and there would be a
+                // non-0 accurate default set in drdecode's initializer, or for
+                // drmemtrace possibly the reader or decode cache: though there are
+                // problems with those (reader doesn't use libdynamorio, and decode
+                // cache doesn't see all records).  For dynamic changes to the vector
+                // length (xref i#6625), users would have to update it: but that is
+                // likely very rare? Or maybe that argues for decode cache being given
+                // access to every record?
+                dr_set_vector_length(new_vl_bits);
+                if (decode_cache_ != nullptr) {
+                    // Ensure we print the proper offset for SVE load/store instructions
+                    // whose offset depends on the vector length. The vector length
+                    // can change dynamically so this could happen mid-trace (at least
+                    // once i#6625 is finished so drmemtrace outputs a new marker).
+                    decode_cache_->clear_cache();
+                }
+            }
+#endif
             break;
+        }
         case TRACE_MARKER_TYPE_UNCOMPLETED_INSTRUCTION:
             // The value stores the encoding of the uncompleted instruction up
             // to the length of a pointer. The encoding may not be complete so


Test patch:
diff --git a/clients/drcachesim/tests/view_test.cpp b/clients/drcachesim/tests/view_test.cpp
index 8163d99ef9..1cfca492aa 100644
--- a/clients/drcachesim/tests/view_test.cpp
+++ b/clients/drcachesim/tests/view_test.cpp
@@ -912,6 +912,92 @@ run_regdeps_test(void *drcontext)
     return true;
 }
 
+#ifdef AARCH64
+/* Test view tool setting the vector length. */
+bool
+run_vector_length_test(void *drcontext)
+{
+    instrlist_t *ilist = instrlist_create(drcontext);
+    instr_t *nop = XINST_CREATE_nop(drcontext);
+    constexpr int VECTOR_LENGTH_BYTES = 16; // In bytes.
+    dr_set_vector_length(VECTOR_LENGTH_BYTES * 8);
+    constexpr int DISP = VECTOR_LENGTH_BYTES;
+    // This instruction's memory operand displacement is defined by the
+    // ISA to be a multiple of the vector length.
+    instr_t *ld1b_imm = INSTR_CREATE_ld1b_sve_pred(
+        drcontext, opnd_create_reg_element_vector(DR_REG_Z12, OPSZ_1),
+        opnd_create_predicate_reg(DR_REG_P4, /*is_merge=*/false),
+        opnd_create_base_disp(REG2, DR_REG_NULL, 0, DISP, OPSZ_1));
+    instr_t *ld1b_noimm = INSTR_CREATE_ld1b_sve_pred(
+        drcontext, opnd_create_reg_element_vector(DR_REG_Z12, OPSZ_1),
+        opnd_create_predicate_reg(DR_REG_P4, /*is_merge=*/false),
+        opnd_create_base_disp(REG2, DR_REG_NULL, 0, 0, OPSZ_1));
+    instrlist_append(ilist, nop);
+    instrlist_append(ilist, ld1b_imm);
+    instrlist_append(ilist, ld1b_noimm);
+
+    constexpr int INSTR_SIZE = 4;
+    constexpr int MAX_INSTRS = 4;
+    int encodings[MAX_INSTRS];
+    byte *pc = instrlist_encode_to_copy(
+        drcontext, ilist, reinterpret_cast<byte *>(encodings),
+        reinterpret_cast<byte *>(static_cast<ptr_uint_t>(/*arbitrary*/ 4)), nullptr,
+        /*has_instr_jmp_targets=*/false);
+    assert(pc != nullptr);
+    assert(reinterpret_cast<int *>(pc) - encodings < MAX_INSTRS);
+
+    constexpr int OFFS_LD1B_IMM = 1;
+    constexpr int OFFS_LD1B_NOIMM = 2;
+    constexpr addr_t PC_LD1B_IMM = 0x1234;
+    constexpr addr_t PC_LD1B_NOIMM = PC_LD1B_IMM + INSTR_SIZE;
+
+    const memref_tid_t tid = 3;
+    std::vector<memref_tid_t> tids = { tid };
+    std::vector<trace_entry_t> records = {
+        test_util::make_thread(tid),
+        test_util::make_pid(/*pid=*/tid),
+        test_util::make_marker(TRACE_MARKER_TYPE_VERSION, 3),
+        test_util::make_marker(TRACE_MARKER_TYPE_FILETYPE,
+                               OFFLINE_FILE_TYPE_ARCH_AARCH64 |
+                                   OFFLINE_FILE_TYPE_ENCODINGS),
+        test_util::make_marker(TRACE_MARKER_TYPE_CACHE_LINE_SIZE, 64),
+        test_util::make_marker(TRACE_MARKER_TYPE_VECTOR_LENGTH, VECTOR_LENGTH_BYTES),
+        test_util::make_marker(TRACE_MARKER_TYPE_TIMESTAMP, 1001),
+        test_util::make_marker(TRACE_MARKER_TYPE_CPU_ID, 2),
+        test_util::make_encoding(INSTR_SIZE, encodings[OFFS_LD1B_IMM]),
+        test_util::make_instr(PC_LD1B_IMM, TRACE_TYPE_INSTR, INSTR_SIZE),
+        test_util::make_memref(0x42, TRACE_TYPE_READ, 4),
+        test_util::make_encoding(INSTR_SIZE, encodings[OFFS_LD1B_NOIMM]),
+        test_util::make_instr(PC_LD1B_NOIMM, TRACE_TYPE_INSTR, INSTR_SIZE),
+        test_util::make_memref(0x42, TRACE_TYPE_READ, 4),
+    };
+
+    // We are looking for the first ld1b's displacement to be 0x10.
+    // If the vector length marker isn't processed, it will be 0.
+    /* clang-format off */
+    std::string expect =
+        std::string(R"DELIM(           1           0:       W0.T3 <marker: version 3>
+           2           0:       W0.T3 <marker: filetype 0x208>
+           3           0:       W0.T3 <marker: cache line size 64>
+           4           0:       W0.T3 <marker: vector length 16 bytes>
+           5           0:       W0.T3 <marker: timestamp 1001>
+           6           0:       W0.T3 <marker: W0.T3 on core 2>
+           7           1:       W0.T3 ifetch       4 byte(s) @ 0x0000000000001234 a401b02c   ld1b   +0x10(%x1)[1byte] %p4/z -> %z12.b
+           8           1:       W0.T3 read         4 byte(s) @ 0x0000000000000042 by PC 0x0000000000001234
+           9           2:       W0.T3 ifetch       4 byte(s) @ 0x0000000000001238 a400b02c   ld1b   (%x1)[1byte] %p4/z -> %z12.b
+          10           2:       W0.T3 read         4 byte(s) @ 0x0000000000000042 by PC 0x0000000000001238
+)DELIM");
+    /* clang-format on */
+
+    std::string res = run_with_analyzer(drcontext, *ilist, records);
+    if (res != expect) {
+        std::cerr << "Output mismatch: got |" << res << "| expected |" << expect << "|\n";
+        return false;
+    }
+    return true;
+}
+#endif
+
 int
 test_main(int argc, const char *argv[])
 {
@@ -919,6 +1005,9 @@ test_main(int argc, const char *argv[])
     if (run_limit_tests(drcontext) && run_chunk_tests(drcontext) &&
 #ifdef X86
         run_unfetched_rep_string_test(drcontext) &&
+#endif
+#ifdef AARCH64
+        run_vector_length_test(drcontext) &&
 #endif
         run_regdeps_test(drcontext)) {
         std::cerr << "view_test passed\n";