qemu-arm
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

ldrd implementation issue?


From: Stu Grossman
Subject: ldrd implementation issue?
Date: Mon, 10 Feb 2025 08:54:06 -0800
User-agent: Mozilla Thunderbird

I've been getting SIGBUS cores with a bunch of user apps running under linux 5.15 and qemu-system-aarch64. These happen to be 32 bit (T32?) programs.

All of the cores point at the following instruction:

        ldrd r2,r3,[r2]

where r2 points at four bytes prior to the end of a page.  Eg: 0x10ffc.

The bug appears to be that we get a page fault between the accesses for each word. Prior to the fault, the first register is updated. Later, after the fault is serviced, the instruction is restarted with the index register containing the word loaded prior to the fault, which is probably not the desired address, resulting in a coredump.

So the conditions for the problem are:

        - the index register must be one of the registers being loaded.
        - the index register must point at a uint64_t that spans a page
          boundary.

I understand that the uint64_t spanning a page boundary may not be allowed by the arm 32 bit API. However, the ldrd instruction definition allows for four byte alignment.

Note that ldm may have similar issues if the index is one of the registers being loaded and the words cross a page boundary.

The fix is to defer the register stores till after both words have been read from memory.

Here is my fix:

diff --git a/target/arm/tcg/translate.c b/target/arm/tcg/translate.c
index 9ee761fc64..78ad0ed186 100644
--- a/target/arm/tcg/translate.c
+++ b/target/arm/tcg/translate.c
@@ -5006,7 +5006,7 @@ static bool op_store_rr(DisasContext *s, arg_ldst_rr *a,
 static bool trans_LDRD_rr(DisasContext *s, arg_ldst_rr *a)
 {
     int mem_idx = get_mem_index(s);
-    TCGv_i32 addr, tmp;
+    TCGv_i32 addr, tmp1, tmp2;

     if (!ENABLE_ARCH_5TE) {
         return false;
@@ -5017,15 +5017,16 @@ static bool trans_LDRD_rr(DisasContext *s, arg_ldst_rr *a)
     }
     addr = op_addr_rr_pre(s, a);

-    tmp = tcg_temp_new_i32();
-    gen_aa32_ld_i32(s, tmp, addr, mem_idx, MO_UL | MO_ALIGN);
-    store_reg(s, a->rt, tmp);
+    tmp1 = tcg_temp_new_i32();
+    gen_aa32_ld_i32(s, tmp1, addr, mem_idx, MO_UL | MO_ALIGN);

     tcg_gen_addi_i32(addr, addr, 4);

-    tmp = tcg_temp_new_i32();
-    gen_aa32_ld_i32(s, tmp, addr, mem_idx, MO_UL | MO_ALIGN);
-    store_reg(s, a->rt + 1, tmp);
+    tmp2 = tcg_temp_new_i32();
+    gen_aa32_ld_i32(s, tmp2, addr, mem_idx, MO_UL | MO_ALIGN);
+
+    store_reg(s, a->rt, tmp1);
+    store_reg(s, a->rt + 1, tmp2);

     /* LDRD w/ base writeback is undefined if the registers overlap.  */
     op_addr_rr_post(s, a, addr, -4);
@@ -5153,19 +5154,20 @@ static bool op_store_ri(DisasContext *s, arg_ldst_ri *a,
 static bool op_ldrd_ri(DisasContext *s, arg_ldst_ri *a, int rt2)
 {
     int mem_idx = get_mem_index(s);
-    TCGv_i32 addr, tmp;
+    TCGv_i32 addr, tmp1, tmp2;

     addr = op_addr_ri_pre(s, a);

-    tmp = tcg_temp_new_i32();
-    gen_aa32_ld_i32(s, tmp, addr, mem_idx, MO_UL | MO_ALIGN);
-    store_reg(s, a->rt, tmp);
+    tmp1 = tcg_temp_new_i32();
+    gen_aa32_ld_i32(s, tmp1, addr, mem_idx, MO_UL | MO_ALIGN);

     tcg_gen_addi_i32(addr, addr, 4);

-    tmp = tcg_temp_new_i32();
-    gen_aa32_ld_i32(s, tmp, addr, mem_idx, MO_UL | MO_ALIGN);
-    store_reg(s, rt2, tmp);
+    tmp2 = tcg_temp_new_i32();
+    gen_aa32_ld_i32(s, tmp2, addr, mem_idx, MO_UL | MO_ALIGN);
+
+    store_reg(s, a->rt, tmp1);
+    store_reg(s, rt2, tmp2);

     /* LDRD w/ base writeback is undefined if the registers overlap.  */
     op_addr_ri_post(s, a, addr, -4);

                Stu




reply via email to

[Prev in Thread] Current Thread [Next in Thread]