Fix ARM machine state testcase failures
Commit Message
Hi,
When running GDB's reverse debugging testsuite against a few ARM
multilibs, i noticed failures in the machinestate* testcases.
Further investigation showed that push and pop instruction encodings A1
and A2 were not being handled properly, thus we missed saving important
contents from registers and memory. When going backwards, such contents
were not restored and thus we ended up with a corrupted state that did
not correspond to the real values we had at a particular point in time.
Attached is a patch that fixes around 36 failures for both
gdb.reverse/machinestate.exp and gdb.reverse/machinestate-precsave.exp
testcases, making them fully pass. This is for both armv7 and armv4. I
still see failures for armv4 thumb though, so it needs a bit more
investigation.
I see no regressions due to this patch for armv7, armv7 thumb, armv4 and
armv4 thumb.
Ok for trunk?
Regards,
Luis
Comments
Luis Machado <lgustavo@codesourcery.com> writes:
> Further investigation showed that push and pop instruction encodings
> A1 and A2 were not being handled properly, thus we missed saving
> important contents from registers and memory. When going backwards,
> such contents were not restored and thus we ended up with a corrupted
> state that did not correspond to the real values we had at a
> particular point in time.
Luis,
I agree on your analysis, but I have a question to the code. Do we
really need to handle PUSH and POP as a special case in LDR/STR
instructions? AFAICS, PUSH/POP is just one kind of LDR/STR instruction
with post-indexed addressing. Probably, we just need to tweak
arm_record_ld_st_imm_offset and arm_record_ld_st_multiple for
post-indexed addressing, at least LDR with post-indexed are not
handled in arm_record_ld_st_imm_offset. What do you think?
Hi Yao,
On 10/17/2014 05:35 AM, Yao Qi wrote:
> Luis Machado <lgustavo@codesourcery.com> writes:
>
>> Further investigation showed that push and pop instruction encodings
>> A1 and A2 were not being handled properly, thus we missed saving
>> important contents from registers and memory. When going backwards,
>> such contents were not restored and thus we ended up with a corrupted
>> state that did not correspond to the real values we had at a
>> particular point in time.
>
> Luis,
> I agree on your analysis, but I have a question to the code. Do we
> really need to handle PUSH and POP as a special case in LDR/STR
> instructions? AFAICS, PUSH/POP is just one kind of LDR/STR instruction
> with post-indexed addressing. Probably, we just need to tweak
> arm_record_ld_st_imm_offset and arm_record_ld_st_multiple for
> post-indexed addressing, at least LDR with post-indexed are not
> handled in arm_record_ld_st_imm_offset. What do you think?
>
Can PUSH/POP always be described as a LD/ST instruction though? The
documentation seems to indicate this is not always the case.
For POP, we have two encodings: A1 and A2. For A1, the documentation [1]
states that we should use LDM/LDMIA/LDFD if BitCount(register_list) < 2.
I interpreted this as "Unless we are popping only a single register, we
should use the POP instruction".
For PUSH, it seems to be the same case, but with STMDB/STMFD.
Disassembler output sometimes shows push/pop alongside the equivalent
ld/st syntax, but other times only push/pop is shown.
Base on the above, there is indeed a little overlap between the handling
of st/ld and push/pop instructions. For the sake of better code
organization and readability, i went for special-casing push/pop and
making it immediately obvious what we were handling, given the amount of
ARM encodings we need to handle.
Does it make sense?
[1] ARM Architecture Reference Manual - ARMv7-A and ARMv7-R edition
Luis Machado <lgustavo@codesourcery.com> writes:
> Can PUSH/POP always be described as a LD/ST instruction though? The
Yes, I think so. PUSH/POP is the special case of LD/ST instruction, in
terms of instruction encoding, see details blow.
> documentation seems to indicate this is not always the case.
>
> For POP, we have two encodings: A1 and A2. For A1, the documentation
> [1] states that we should use LDM/LDMIA/LDFD if
> BitCount(register_list) < 2. I interpreted this as "Unless we are
> popping only a single register, we should use the POP instruction".
The doc suggests what instructions to use here, nothing to do with
instruction encoding.
- POP A2 (POP instruction encoding A2) is a special case of LDR
(immediate, ARM) A1.
LDR (immediate, ARM), Encoding A1:
cond 0 1 0 P U 0 W 1 Rn Rt imm12
POP Encoding A2:
cond 0 1 0 0 1 0 0 1 1 1 0 1 Rt 0 0 0 0 0 0 0 0 0 1 0 0
we decode POP A2 as a LDR (immediate) A1, that means, P = 0, U = 1,
W = 0, Rn = 13, imm12 = 4. Then, index = false, add = true, wback =
true,
This is the post-indexed addressing. IOW, "pop {r1}" is the same
encoding as "ldr r1, [sp], #4".
- POP A1 is a special case of LDMIA A1.
LDMIA Encoding A1:
cond 1 0 0 0 1 0 W 1 Rn register_list
POP Encoding A1:
cond 1 0 0 0 1 0 1 1 (1 1 0 1) register_list
we code POP A1 as a LDIMA, that means W = 1, Rn = 13. IOW,
"pop {r1, r2}" encoding is the same as "ldmia sp!, {r1, r2}".
- PUSH A1 is a special case of STMDB A1.
STMDB A1:
cond 1 0 0 1 0 0 W 0 Rn register_list
PUSH A1:
cond 1 0 0 1 0 0 1 0 1 1 0 1 register_list
we decode PUSH A1 as STMDB A1, that means W = 1, Rn = 13.
"push {r1, r2}" is the same encoding as "stmdb sp!, {r1, r2}".
- Push A2 is a special case of STR (immediate ARM) A1.
STR (immediate) A1:
cond 0 1 0 P U 0 W 0 Rn Rt imm12
PUSH A2:
cond 0 1 0 1 0 0 1 0 1 1 0 1 Rt 0 0 0 0 0 0 0 0 0 1 0 0
we code PUSH A2 as STR (immediate) A1, then P = 1, U = 0, W = 1, Rn =
13, imm12 = 4. This is the STR with pre-indexed addressing mode. Instruction
"push {r1}" encoding is the same as "str r1, [sp, #-4]!".
IMO, we should fix gdb handling ld/st instructions in reverse debugging,
instead of handling only push/pop instructions.
2014-10-16 Luis Machado <lgustavo@codesourcery.com>
* arm-tdep.c (POP_A2, PUSH_A2): New #defines.
(arm_record_ld_st_imm_offset): Handle single-register ARM
mode push and pop instructions.
(POP_A1, PUSH_A1, PUSH_POP_SP_REG_MASK): New #defines.
(arm_record_ld_st_multiple): Handle multi-register ARM
mode push and pop instructions.
@@ -11473,6 +11473,10 @@ arm_record_data_proc_imm (insn_decode_record *arm_insn_r)
return 0;
}
+/* Masks for PUSH and POP instructions that take a single register. */
+#define POP_A2 0x049d0004 /* POP<c> <registers> */
+#define PUSH_A2 0x052d0004 /* PUSH<c> <registers> */
+
/* Handling opcode 010 insns. */
static int
@@ -11489,7 +11493,39 @@ arm_record_ld_st_imm_offset (insn_decode_record *arm_insn_r)
arm_insn_r->opcode = bits (arm_insn_r->arm_insn, 21, 24);
arm_insn_r->decode = bits (arm_insn_r->arm_insn, 4, 7);
- if (bit (arm_insn_r->arm_insn, INSN_S_L_BIT_NUM))
+ if ((arm_insn_r->arm_insn & POP_A2) == POP_A2)
+ {
+ /* Handle single-register pop. */
+ int regno = bits (arm_insn_r->arm_insn, 12, 15);
+
+ record_buf[0] = regno;
+ arm_insn_r->reg_rec_count = 1;
+
+ /* The stack pointer register is always modified. Check if we have
+ already saved it though. */
+ if (regno != ARM_SP_REGNUM)
+ {
+ record_buf[1] = ARM_SP_REGNUM;
+ arm_insn_r->reg_rec_count = 2;
+ }
+ }
+ else if ((arm_insn_r->arm_insn & PUSH_A2) == PUSH_A2)
+ {
+ /* Handle single-register push. */
+
+ /* The stack pointer register is always updated. */
+ record_buf[0] = ARM_SP_REGNUM;
+ arm_insn_r->reg_rec_count = 1;
+
+ /* The pushed register goes into the stack, so we need to record that
+ chunk of memory as well. */
+ regcache_raw_read_unsigned (reg_cache, ARM_SP_REGNUM, &u_regval);
+ tgt_mem_addr = (uint32_t) u_regval;
+ record_buf_mem[0] = INT_REGISTER_SIZE;
+ record_buf_mem[1] = tgt_mem_addr - INT_REGISTER_SIZE;
+ arm_insn_r->mem_rec_count = 1;
+ }
+ else if (bit (arm_insn_r->arm_insn, INSN_S_L_BIT_NUM))
{
reg_dest = bits (arm_insn_r->arm_insn, 12, 15);
/* LDR insn has a capability to do branching, if
@@ -11847,6 +11883,11 @@ arm_record_ld_st_reg_offset (insn_decode_record *arm_insn_r)
return 0;
}
+/* Masks for PUSH and POP instructions that take multiple registers. */
+#define POP_A1 0x08bd0000 /* POP<c> <registers> */
+#define PUSH_A1 0x092d0000 /* PUSH<c> <registers> */
+#define PUSH_POP_SP_REG_MASK 0x00002000
+
/* Handling opcode 100 insns. */
static int
@@ -11861,12 +11902,66 @@ arm_record_ld_st_multiple (insn_decode_record *arm_insn_r)
ULONGEST u_regval[2] = {0};
+ if ((arm_insn_r->arm_insn & POP_A1) == POP_A1)
+ {
+ /* Handle multi-register pop. */
+ int regno = 0;
+
+ register_bits = bits (arm_insn_r->arm_insn, 0, 15);
+
+ /* Record which registers are going to be modified. */
+ while (register_bits)
+ {
+ if (register_bits & 0x00000001)
+ record_buf[arm_insn_r->reg_rec_count++] = regno;
+ register_bits = register_bits >> 1;
+ regno++;
+ }
+
+ /* The stack pointer register is always modified. Check if we have
+ already saved it though. */
+ if ((bits (arm_insn_r->arm_insn, 0, 15) & PUSH_POP_SP_REG_MASK) == 0)
+ {
+ record_buf[arm_insn_r->reg_rec_count] = ARM_SP_REGNUM;
+ arm_insn_r->reg_rec_count++;
+ }
+ }
+ else if ((arm_insn_r->arm_insn & PUSH_A1) == PUSH_A1)
+ {
+ /* Handle multi-register push. */
+ int mem_chunks = 0;
+ uint32_t sp_addr;
+ ULONGEST u_regval;
+
+ register_bits = bits (arm_insn_r->arm_insn, 0, 15);
+ regcache_raw_read_unsigned (reg_cache, ARM_SP_REGNUM, &u_regval);
+ sp_addr = (uint32_t) u_regval;
+
+ /* The stack pointer register is always modified. */
+ record_buf[0] = ARM_SP_REGNUM;
+ arm_insn_r->reg_rec_count = 1;
+
+ /* The pushed registers go into the stack, so we need to record those
+ chunks of memory as well. Count how many registers are going to be
+ saved. */
+ while (register_bits)
+ {
+ if (register_bits & 0x00000001)
+ mem_chunks++;
+
+ register_bits = register_bits >> 1;
+ }
+
+ /* Record the chunks in one continuous block. */
+ record_buf_mem[0] = mem_chunks * INT_REGISTER_SIZE;
+ record_buf_mem[1] = sp_addr - mem_chunks * INT_REGISTER_SIZE;
+ arm_insn_r->mem_rec_count = 1;
+ }
/* This mode is exclusively for load and store multiple. */
/* Handle incremenrt after/before and decrment after.before mode;
Rn is changing depending on W bit, but as of now we store Rn too
without optimization. */
-
- if (bit (arm_insn_r->arm_insn, INSN_S_L_BIT_NUM))
+ else if (bit (arm_insn_r->arm_insn, INSN_S_L_BIT_NUM))
{
/* LDM (1,2,3) where LDM (3) changes CPSR too. */