Use visitor in aarch64_relocate_instruction

Nowadays, the instruction decodings and handling are mixed together
inside aarch64_relocate_instruction.  The patch decouples instruction
decoding and instruction handling by using visitor pattern.  That is,
aarch64_relocate_instruction decode instructions and visit each
instruction by different visitor methods.  Each visitor defines the
concrete things to different instructions.  Fast tracepoint instruction
relocation and displaced stepping can define their own visitors,
sub-class of struct aarch64_insn_data.

gdb/gdbserver:

2015-10-12  Yao Qi  <yao.qi@linaro.org>

	* linux-aarch64-low.c (struct aarch64_insn_data): New.
	(struct aarch64_insn_visitor): New.
	(struct aarch64_insn_relocation_data): New.
	(aarch64_ftrace_insn_reloc_b): New function.
	(aarch64_ftrace_insn_reloc_b_cond): Likewise.
	(aarch64_ftrace_insn_reloc_cb): Likewise.
	(aarch64_ftrace_insn_reloc_tb): Likewise.
	(aarch64_ftrace_insn_reloc_adr): Likewise.
	(aarch64_ftrace_insn_reloc_ldr_literal): Likewise.
	(aarch64_ftrace_insn_reloc_others): Likewise.
	(visitor): New.
	(aarch64_relocate_instruction): Use visitor.
This commit is contained in:
Yao Qi 2015-10-12 11:28:38 +01:00
parent dfaffe9d93
commit 0badd99faf
2 changed files with 318 additions and 147 deletions

View file

@ -1,3 +1,18 @@
2015-10-12 Yao Qi <yao.qi@linaro.org>
* linux-aarch64-low.c (struct aarch64_insn_data): New.
(struct aarch64_insn_visitor): New.
(struct aarch64_insn_relocation_data): New.
(aarch64_ftrace_insn_reloc_b): New function.
(aarch64_ftrace_insn_reloc_b_cond): Likewise.
(aarch64_ftrace_insn_reloc_cb): Likewise.
(aarch64_ftrace_insn_reloc_tb): Likewise.
(aarch64_ftrace_insn_reloc_adr): Likewise.
(aarch64_ftrace_insn_reloc_ldr_literal): Likewise.
(aarch64_ftrace_insn_reloc_others): Likewise.
(visitor): New.
(aarch64_relocate_instruction): Use visitor.
2015-10-12 Yao Qi <yao.qi@linaro.org>
* linux-aarch64-low.c (aarch64_relocate_instruction): Return

View file

@ -1924,8 +1924,278 @@ can_encode_int32 (int32_t val, unsigned bits)
return rest == 0 || rest == -1;
}
/* Relocate an instruction INSN from OLDLOC to TO and save the relocated
instructions in BUF. The number of instructions in BUF is returned.
/* Data passed to each method of aarch64_insn_visitor. */
struct aarch64_insn_data
{
/* The instruction address. */
CORE_ADDR insn_addr;
};
/* Visit different instructions by different methods. */
struct aarch64_insn_visitor
{
/* Visit instruction B/BL OFFSET. */
void (*b) (const int is_bl, const int32_t offset,
struct aarch64_insn_data *data);
/* Visit instruction B.COND OFFSET. */
void (*b_cond) (const unsigned cond, const int32_t offset,
struct aarch64_insn_data *data);
/* Visit instruction CBZ/CBNZ Rn, OFFSET. */
void (*cb) (const int32_t offset, const int is_cbnz,
const unsigned rn, int is64,
struct aarch64_insn_data *data);
/* Visit instruction TBZ/TBNZ Rt, #BIT, OFFSET. */
void (*tb) (const int32_t offset, int is_tbnz,
const unsigned rt, unsigned bit,
struct aarch64_insn_data *data);
/* Visit instruction ADR/ADRP Rd, OFFSET. */
void (*adr) (const int32_t offset, const unsigned rd,
const int is_adrp, struct aarch64_insn_data *data);
/* Visit instruction LDR/LDRSW Rt, OFFSET. */
void (*ldr_literal) (const int32_t offset, const int is_sw,
const unsigned rt, const int is64,
struct aarch64_insn_data *data);
/* Visit instruction INSN of other kinds. */
void (*others) (const uint32_t insn, struct aarch64_insn_data *data);
};
/* Sub-class of struct aarch64_insn_data, store information of
instruction relocation for fast tracepoint. Visitor can
relocate an instruction from BASE.INSN_ADDR to NEW_ADDR and save
the relocated instructions in buffer pointed by INSN_PTR. */
struct aarch64_insn_relocation_data
{
struct aarch64_insn_data base;
/* The new address the instruction is relocated to. */
CORE_ADDR new_addr;
/* Pointer to the buffer of relocated instruction(s). */
uint32_t *insn_ptr;
};
/* Implementation of aarch64_insn_visitor method "b". */
static void
aarch64_ftrace_insn_reloc_b (const int is_bl, const int32_t offset,
struct aarch64_insn_data *data)
{
struct aarch64_insn_relocation_data *insn_reloc
= (struct aarch64_insn_relocation_data *) data;
int32_t new_offset
= insn_reloc->base.insn_addr - insn_reloc->new_addr + offset;
if (can_encode_int32 (new_offset, 28))
insn_reloc->insn_ptr += emit_b (insn_reloc->insn_ptr, is_bl, new_offset);
}
/* Implementation of aarch64_insn_visitor method "b_cond". */
static void
aarch64_ftrace_insn_reloc_b_cond (const unsigned cond, const int32_t offset,
struct aarch64_insn_data *data)
{
struct aarch64_insn_relocation_data *insn_reloc
= (struct aarch64_insn_relocation_data *) data;
int32_t new_offset
= insn_reloc->base.insn_addr - insn_reloc->new_addr + offset;
if (can_encode_int32 (new_offset, 21))
{
insn_reloc->insn_ptr += emit_bcond (insn_reloc->insn_ptr, cond,
new_offset);
}
else if (can_encode_int32 (new_offset, 28))
{
/* The offset is out of range for a conditional branch
instruction but not for a unconditional branch. We can use
the following instructions instead:
B.COND TAKEN ; If cond is true, then jump to TAKEN.
B NOT_TAKEN ; Else jump over TAKEN and continue.
TAKEN:
B #(offset - 8)
NOT_TAKEN:
*/
insn_reloc->insn_ptr += emit_bcond (insn_reloc->insn_ptr, cond, 8);
insn_reloc->insn_ptr += emit_b (insn_reloc->insn_ptr, 0, 8);
insn_reloc->insn_ptr += emit_b (insn_reloc->insn_ptr, 0, new_offset - 8);
}
}
/* Implementation of aarch64_insn_visitor method "cb". */
static void
aarch64_ftrace_insn_reloc_cb (const int32_t offset, const int is_cbnz,
const unsigned rn, int is64,
struct aarch64_insn_data *data)
{
struct aarch64_insn_relocation_data *insn_reloc
= (struct aarch64_insn_relocation_data *) data;
int32_t new_offset
= insn_reloc->base.insn_addr - insn_reloc->new_addr + offset;
if (can_encode_int32 (new_offset, 21))
{
insn_reloc->insn_ptr += emit_cb (insn_reloc->insn_ptr, is_cbnz,
aarch64_register (rn, is64), new_offset);
}
else if (can_encode_int32 (new_offset, 28))
{
/* The offset is out of range for a compare and branch
instruction but not for a unconditional branch. We can use
the following instructions instead:
CBZ xn, TAKEN ; xn == 0, then jump to TAKEN.
B NOT_TAKEN ; Else jump over TAKEN and continue.
TAKEN:
B #(offset - 8)
NOT_TAKEN:
*/
insn_reloc->insn_ptr += emit_cb (insn_reloc->insn_ptr, is_cbnz,
aarch64_register (rn, is64), 8);
insn_reloc->insn_ptr += emit_b (insn_reloc->insn_ptr, 0, 8);
insn_reloc->insn_ptr += emit_b (insn_reloc->insn_ptr, 0, new_offset - 8);
}
}
/* Implementation of aarch64_insn_visitor method "tb". */
static void
aarch64_ftrace_insn_reloc_tb (const int32_t offset, int is_tbnz,
const unsigned rt, unsigned bit,
struct aarch64_insn_data *data)
{
struct aarch64_insn_relocation_data *insn_reloc
= (struct aarch64_insn_relocation_data *) data;
int32_t new_offset
= insn_reloc->base.insn_addr - insn_reloc->new_addr + offset;
if (can_encode_int32 (new_offset, 16))
{
insn_reloc->insn_ptr += emit_tb (insn_reloc->insn_ptr, is_tbnz, bit,
aarch64_register (rt, 1), new_offset);
}
else if (can_encode_int32 (new_offset, 28))
{
/* The offset is out of range for a test bit and branch
instruction but not for a unconditional branch. We can use
the following instructions instead:
TBZ xn, #bit, TAKEN ; xn[bit] == 0, then jump to TAKEN.
B NOT_TAKEN ; Else jump over TAKEN and continue.
TAKEN:
B #(offset - 8)
NOT_TAKEN:
*/
insn_reloc->insn_ptr += emit_tb (insn_reloc->insn_ptr, is_tbnz, bit,
aarch64_register (rt, 1), 8);
insn_reloc->insn_ptr += emit_b (insn_reloc->insn_ptr, 0, 8);
insn_reloc->insn_ptr += emit_b (insn_reloc->insn_ptr, 0,
new_offset - 8);
}
}
/* Implementation of aarch64_insn_visitor method "adr". */
static void
aarch64_ftrace_insn_reloc_adr (const int32_t offset, const unsigned rd,
const int is_adrp,
struct aarch64_insn_data *data)
{
struct aarch64_insn_relocation_data *insn_reloc
= (struct aarch64_insn_relocation_data *) data;
/* We know exactly the address the ADR{P,} instruction will compute.
We can just write it to the destination register. */
CORE_ADDR address = data->insn_addr + offset;
if (is_adrp)
{
/* Clear the lower 12 bits of the offset to get the 4K page. */
insn_reloc->insn_ptr += emit_mov_addr (insn_reloc->insn_ptr,
aarch64_register (rd, 1),
address & ~0xfff);
}
else
insn_reloc->insn_ptr += emit_mov_addr (insn_reloc->insn_ptr,
aarch64_register (rd, 1), address);
}
/* Implementation of aarch64_insn_visitor method "ldr_literal". */
static void
aarch64_ftrace_insn_reloc_ldr_literal (const int32_t offset, const int is_sw,
const unsigned rt, const int is64,
struct aarch64_insn_data *data)
{
struct aarch64_insn_relocation_data *insn_reloc
= (struct aarch64_insn_relocation_data *) data;
CORE_ADDR address = data->insn_addr + offset;
insn_reloc->insn_ptr += emit_mov_addr (insn_reloc->insn_ptr,
aarch64_register (rt, 1), address);
/* We know exactly what address to load from, and what register we
can use:
MOV xd, #(oldloc + offset)
MOVK xd, #((oldloc + offset) >> 16), lsl #16
...
LDR xd, [xd] ; or LDRSW xd, [xd]
*/
if (is_sw)
insn_reloc->insn_ptr += emit_ldrsw (insn_reloc->insn_ptr,
aarch64_register (rt, 1),
aarch64_register (rt, 1),
offset_memory_operand (0));
else
insn_reloc->insn_ptr += emit_ldr (insn_reloc->insn_ptr,
aarch64_register (rt, is64),
aarch64_register (rt, 1),
offset_memory_operand (0));
}
/* Implementation of aarch64_insn_visitor method "others". */
static void
aarch64_ftrace_insn_reloc_others (const uint32_t insn,
struct aarch64_insn_data *data)
{
struct aarch64_insn_relocation_data *insn_reloc
= (struct aarch64_insn_relocation_data *) data;
/* The instruction is not PC relative. Just re-emit it at the new
location. */
insn_reloc->insn_ptr += emit_insn (insn_reloc->insn_ptr, insn);
}
static const struct aarch64_insn_visitor visitor =
{
aarch64_ftrace_insn_reloc_b,
aarch64_ftrace_insn_reloc_b_cond,
aarch64_ftrace_insn_reloc_cb,
aarch64_ftrace_insn_reloc_tb,
aarch64_ftrace_insn_reloc_adr,
aarch64_ftrace_insn_reloc_ldr_literal,
aarch64_ftrace_insn_reloc_others,
};
/* Visit an instruction INSN by VISITOR with all needed information in DATA.
PC relative instructions need to be handled specifically:
@ -1936,12 +2206,11 @@ can_encode_int32 (int32_t val, unsigned bits)
- ADR/ADRP
- LDR/LDRSW (literal) */
static int
aarch64_relocate_instruction (const CORE_ADDR to, const CORE_ADDR oldloc,
uint32_t insn, uint32_t *buf)
static void
aarch64_relocate_instruction (uint32_t insn,
const struct aarch64_insn_visitor *visitor,
struct aarch64_insn_data *data)
{
uint32_t *p = buf;
int is_bl;
int is64;
int is_sw;
@ -1955,144 +2224,23 @@ aarch64_relocate_instruction (const CORE_ADDR to, const CORE_ADDR oldloc,
unsigned bit;
int32_t offset;
if (aarch64_decode_b (oldloc, insn, &is_bl, &offset))
{
offset = (oldloc - to + offset);
if (can_encode_int32 (offset, 28))
p += emit_b (p, is_bl, offset);
else
return 0;
}
else if (aarch64_decode_bcond (oldloc, insn, &cond, &offset))
{
offset = (oldloc - to + offset);
if (can_encode_int32 (offset, 21))
p += emit_bcond (p, cond, offset);
else if (can_encode_int32 (offset, 28))
{
/* The offset is out of range for a conditional branch
instruction but not for a unconditional branch. We can use
the following instructions instead:
B.COND TAKEN ; If cond is true, then jump to TAKEN.
B NOT_TAKEN ; Else jump over TAKEN and continue.
TAKEN:
B #(offset - 8)
NOT_TAKEN:
*/
p += emit_bcond (p, cond, 8);
p += emit_b (p, 0, 8);
p += emit_b (p, 0, offset - 8);
}
else
return 0;
}
else if (aarch64_decode_cb (oldloc, insn, &is64, &is_cbnz, &rn, &offset))
{
offset = (oldloc - to + offset);
if (can_encode_int32 (offset, 21))
p += emit_cb (p, is_cbnz, aarch64_register (rn, is64), offset);
else if (can_encode_int32 (offset, 28))
{
/* The offset is out of range for a compare and branch
instruction but not for a unconditional branch. We can use
the following instructions instead:
CBZ xn, TAKEN ; xn == 0, then jump to TAKEN.
B NOT_TAKEN ; Else jump over TAKEN and continue.
TAKEN:
B #(offset - 8)
NOT_TAKEN:
*/
p += emit_cb (p, is_cbnz, aarch64_register (rn, is64), 8);
p += emit_b (p, 0, 8);
p += emit_b (p, 0, offset - 8);
}
else
return 0;
}
else if (aarch64_decode_tb (oldloc, insn, &is_tbnz, &bit, &rt, &offset))
{
offset = (oldloc - to + offset);
if (can_encode_int32 (offset, 16))
p += emit_tb (p, is_tbnz, bit, aarch64_register (rt, 1), offset);
else if (can_encode_int32 (offset, 28))
{
/* The offset is out of range for a test bit and branch
instruction but not for a unconditional branch. We can use
the following instructions instead:
TBZ xn, #bit, TAKEN ; xn[bit] == 0, then jump to TAKEN.
B NOT_TAKEN ; Else jump over TAKEN and continue.
TAKEN:
B #(offset - 8)
NOT_TAKEN:
*/
p += emit_tb (p, is_tbnz, bit, aarch64_register (rt, 1), 8);
p += emit_b (p, 0, 8);
p += emit_b (p, 0, offset - 8);
}
else
return 0;
}
else if (aarch64_decode_adr (oldloc, insn, &is_adrp, &rd, &offset))
{
/* We know exactly the address the ADR{P,} instruction will compute.
We can just write it to the destination register. */
CORE_ADDR address = oldloc + offset;
if (is_adrp)
{
/* Clear the lower 12 bits of the offset to get the 4K page. */
p += emit_mov_addr (p, aarch64_register (rd, 1),
address & ~0xfff);
}
else
p += emit_mov_addr (p, aarch64_register (rd, 1), address);
}
else if (aarch64_decode_ldr_literal (oldloc, insn, &is_sw, &is64, &rt,
&offset))
{
/* We know exactly what address to load from, and what register we
can use:
MOV xd, #(oldloc + offset)
MOVK xd, #((oldloc + offset) >> 16), lsl #16
...
LDR xd, [xd] ; or LDRSW xd, [xd]
*/
CORE_ADDR address = oldloc + offset;
p += emit_mov_addr (p, aarch64_register (rt, 1), address);
if (is_sw)
p += emit_ldrsw (p, aarch64_register (rt, 1),
aarch64_register (rt, 1),
offset_memory_operand (0));
else
p += emit_ldr (p, aarch64_register (rt, is64),
aarch64_register (rt, 1),
offset_memory_operand (0));
}
if (aarch64_decode_b (data->insn_addr, insn, &is_bl, &offset))
visitor->b (is_bl, offset, data);
else if (aarch64_decode_bcond (data->insn_addr, insn, &cond, &offset))
visitor->b_cond (cond, offset, data);
else if (aarch64_decode_cb (data->insn_addr, insn, &is64, &is_cbnz, &rn,
&offset))
visitor->cb (offset, is_cbnz, rn, is64, data);
else if (aarch64_decode_tb (data->insn_addr, insn, &is_tbnz, &bit, &rt,
&offset))
visitor->tb (offset, is_tbnz, rt, bit, data);
else if (aarch64_decode_adr (data->insn_addr, insn, &is_adrp, &rd, &offset))
visitor->adr (offset, rd, is_adrp, data);
else if (aarch64_decode_ldr_literal (data->insn_addr, insn, &is_sw, &is64,
&rt, &offset))
visitor->ldr_literal (offset, is_sw, rt, is64, data);
else
{
/* The instruction is not PC relative. Just re-emit it at the new
location. */
p += emit_insn (p, insn);
}
return (int) (p - buf);
visitor->others (insn, data);
}
/* Implementation of linux_target_ops method
@ -2119,6 +2267,7 @@ aarch64_install_fast_tracepoint_jump_pad (CORE_ADDR tpoint,
int i;
uint32_t insn;
CORE_ADDR buildaddr = *jump_entry;
struct aarch64_insn_relocation_data insn_data;
/* We need to save the current state on the stack both to restore it
later and to collect register values when the tracepoint is hit.
@ -2421,9 +2570,16 @@ aarch64_install_fast_tracepoint_jump_pad (CORE_ADDR tpoint,
/* Now emit the relocated instruction. */
*adjusted_insn_addr = buildaddr;
target_read_uint32 (tpaddr, &insn);
i = aarch64_relocate_instruction (buildaddr, tpaddr, insn, buf);
insn_data.base.insn_addr = tpaddr;
insn_data.new_addr = buildaddr;
insn_data.insn_ptr = buf;
aarch64_relocate_instruction (insn, &visitor,
(struct aarch64_insn_data *) &insn_data);
/* We may not have been able to relocate the instruction. */
if (i == 0)
if (insn_data.insn_ptr == buf)
{
sprintf (err,
"E.Could not relocate instruction from %s to %s.",
@ -2432,7 +2588,7 @@ aarch64_install_fast_tracepoint_jump_pad (CORE_ADDR tpoint,
return 1;
}
else
append_insns (&buildaddr, i, buf);
append_insns (&buildaddr, insn_data.insn_ptr - buf, buf);
*adjusted_insn_addr_end = buildaddr;
/* Go back to the start of the buffer. */