509 lines
9 KiB
C
509 lines
9 KiB
C
|
/* This testcase is part of GDB, the GNU debugger.
|
||
|
|
||
|
Copyright 2015 Free Software Foundation, Inc.
|
||
|
|
||
|
This program is free software; you can redistribute it and/or modify
|
||
|
it under the terms of the GNU General Public License as published by
|
||
|
the Free Software Foundation; either version 3 of the License, or
|
||
|
(at your option) any later version.
|
||
|
|
||
|
This program is distributed in the hope that it will be useful,
|
||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
GNU General Public License for more details.
|
||
|
|
||
|
You should have received a copy of the GNU General Public License
|
||
|
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||
|
|
||
|
#include <stddef.h>
|
||
|
#include <stdint.h>
|
||
|
|
||
|
typedef void (*testcase_ftype)(void);
|
||
|
|
||
|
/* Each function checks the correctness of the instruction being
|
||
|
relocated due to a fast tracepoint. Call function pass if it is
|
||
|
correct, otherwise call function fail. GDB sets a breakpoints on
|
||
|
pass and fail in order to check the correctness. */
|
||
|
|
||
|
static void
|
||
|
pass (void)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
fail (void)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
#if (defined __x86_64__ || defined __i386__)
|
||
|
|
||
|
#ifdef SYMBOL_PREFIX
|
||
|
#define SYMBOL(str) SYMBOL_PREFIX #str
|
||
|
#else
|
||
|
#define SYMBOL(str) #str
|
||
|
#endif
|
||
|
|
||
|
/* Make sure we can relocate a CALL instruction. CALL instructions are
|
||
|
5 bytes long so we can always set a fast tracepoints on them.
|
||
|
|
||
|
JMP set_point0
|
||
|
f:
|
||
|
MOV $1, %[ok]
|
||
|
JMP end
|
||
|
set_point0:
|
||
|
CALL f ; tracepoint here.
|
||
|
end:
|
||
|
|
||
|
*/
|
||
|
|
||
|
static void
|
||
|
can_relocate_call (void)
|
||
|
{
|
||
|
int ok = 0;
|
||
|
|
||
|
asm (" .global " SYMBOL (set_point0) "\n"
|
||
|
" jmp " SYMBOL (set_point0) "\n"
|
||
|
"0:\n"
|
||
|
" mov $1, %[ok]\n"
|
||
|
" jmp 1f\n"
|
||
|
SYMBOL (set_point0) ":\n"
|
||
|
" call 0b\n"
|
||
|
"1:\n"
|
||
|
: [ok] "=r" (ok));
|
||
|
|
||
|
if (ok == 1)
|
||
|
pass ();
|
||
|
else
|
||
|
fail ();
|
||
|
}
|
||
|
|
||
|
/* Make sure we can relocate a JMP instruction. We need the JMP
|
||
|
instruction to be 5 bytes long in order to set a fast tracepoint on
|
||
|
it. To do this, we emit the opcode directly.
|
||
|
|
||
|
JMP next ; tracepoint here.
|
||
|
next:
|
||
|
MOV $1, %[ok]
|
||
|
|
||
|
*/
|
||
|
|
||
|
static void
|
||
|
can_relocate_jump (void)
|
||
|
{
|
||
|
int ok = 0;
|
||
|
|
||
|
asm (" .global " SYMBOL (set_point1) "\n"
|
||
|
SYMBOL (set_point1) ":\n"
|
||
|
".byte 0xe9\n" /* jmp */
|
||
|
".byte 0x00\n"
|
||
|
".byte 0x00\n"
|
||
|
".byte 0x00\n"
|
||
|
".byte 0x00\n"
|
||
|
" mov $1, %[ok]\n"
|
||
|
: [ok] "=r" (ok));
|
||
|
|
||
|
if (ok == 1)
|
||
|
pass ();
|
||
|
else
|
||
|
fail ();
|
||
|
}
|
||
|
#elif (defined __aarch64__)
|
||
|
|
||
|
/* Make sure we can relocate a B instruction.
|
||
|
|
||
|
B set_point0
|
||
|
set_ok:
|
||
|
MOV %[ok], #1
|
||
|
B end
|
||
|
set_point0:
|
||
|
B set_ok ; tracepoint here.
|
||
|
MOV %[ok], #0
|
||
|
end
|
||
|
|
||
|
*/
|
||
|
|
||
|
static void
|
||
|
can_relocate_b (void)
|
||
|
{
|
||
|
int ok = 0;
|
||
|
|
||
|
asm (" b set_point0\n"
|
||
|
"0:\n"
|
||
|
" mov %[ok], #1\n"
|
||
|
" b 1f\n"
|
||
|
"set_point0:\n"
|
||
|
" b 0b\n"
|
||
|
" mov %[ok], #0\n"
|
||
|
"1:\n"
|
||
|
: [ok] "=r" (ok));
|
||
|
|
||
|
if (ok == 1)
|
||
|
pass ();
|
||
|
else
|
||
|
fail ();
|
||
|
}
|
||
|
|
||
|
/* Make sure we can relocate a B.cond instruction.
|
||
|
|
||
|
MOV x0, #8
|
||
|
TST x0, #8 ; Clear the Z flag.
|
||
|
B set_point1
|
||
|
set_ok:
|
||
|
MOV %[ok], #1
|
||
|
B end
|
||
|
set_point1:
|
||
|
B.NE set_ok ; tracepoint here.
|
||
|
MOV %[ok], #0
|
||
|
end
|
||
|
|
||
|
*/
|
||
|
|
||
|
static void
|
||
|
can_relocate_bcond (void)
|
||
|
{
|
||
|
int ok = 0;
|
||
|
|
||
|
asm (" mov x0, #8\n"
|
||
|
" tst x0, #8\n"
|
||
|
" b set_point1\n"
|
||
|
"0:\n"
|
||
|
" mov %[ok], #1\n"
|
||
|
" b 1f\n"
|
||
|
"set_point1:\n"
|
||
|
" b.ne 0b\n"
|
||
|
" mov %[ok], #0\n"
|
||
|
"1:\n"
|
||
|
: [ok] "=r" (ok)
|
||
|
:
|
||
|
: "0", "cc");
|
||
|
|
||
|
if (ok == 1)
|
||
|
pass ();
|
||
|
else
|
||
|
fail ();
|
||
|
}
|
||
|
|
||
|
/* Make sure we can relocate a CBZ instruction.
|
||
|
|
||
|
MOV x0, #0
|
||
|
B set_point2
|
||
|
set_ok:
|
||
|
MOV %[ok], #1
|
||
|
B end
|
||
|
set_point2:
|
||
|
CBZ x0, set_ok ; tracepoint here.
|
||
|
MOV %[ok], #0
|
||
|
end
|
||
|
|
||
|
*/
|
||
|
|
||
|
static void
|
||
|
can_relocate_cbz (void)
|
||
|
{
|
||
|
int ok = 0;
|
||
|
|
||
|
asm (" mov x0, #0\n"
|
||
|
" b set_point2\n"
|
||
|
"0:\n"
|
||
|
" mov %[ok], #1\n"
|
||
|
" b 1f\n"
|
||
|
"set_point2:\n"
|
||
|
" cbz x0, 0b\n"
|
||
|
" mov %[ok], #0\n"
|
||
|
"1:\n"
|
||
|
: [ok] "=r" (ok)
|
||
|
:
|
||
|
: "0");
|
||
|
|
||
|
if (ok == 1)
|
||
|
pass ();
|
||
|
else
|
||
|
fail ();
|
||
|
}
|
||
|
|
||
|
/* Make sure we can relocate a CBNZ instruction.
|
||
|
|
||
|
MOV x0, #8
|
||
|
B set_point3
|
||
|
set_ok:
|
||
|
MOV %[ok], #1
|
||
|
B end
|
||
|
set_point3:
|
||
|
CBNZ x0, set_ok ; tracepoint here.
|
||
|
MOV %[ok], #0
|
||
|
end
|
||
|
|
||
|
*/
|
||
|
|
||
|
static void
|
||
|
can_relocate_cbnz (void)
|
||
|
{
|
||
|
int ok = 0;
|
||
|
|
||
|
asm (" mov x0, #8\n"
|
||
|
" b set_point3\n"
|
||
|
"0:\n"
|
||
|
" mov %[ok], #1\n"
|
||
|
" b 1f\n"
|
||
|
"set_point3:\n"
|
||
|
" cbnz x0, 0b\n"
|
||
|
" mov %[ok], #0\n"
|
||
|
"1:\n"
|
||
|
: [ok] "=r" (ok)
|
||
|
:
|
||
|
: "0");
|
||
|
|
||
|
if (ok == 1)
|
||
|
pass ();
|
||
|
else
|
||
|
fail ();
|
||
|
}
|
||
|
|
||
|
/* Make sure we can relocate a TBZ instruction.
|
||
|
|
||
|
MOV x0, #8
|
||
|
MVN x0, x0 ; Clear bit 3.
|
||
|
B set_point4
|
||
|
set_ok:
|
||
|
MOV %[ok], #1
|
||
|
B end
|
||
|
set_point4:
|
||
|
TBZ x0, #3, set_ok ; tracepoint here.
|
||
|
MOV %[ok], #0
|
||
|
end
|
||
|
|
||
|
*/
|
||
|
|
||
|
static void
|
||
|
can_relocate_tbz (void)
|
||
|
{
|
||
|
int ok = 0;
|
||
|
|
||
|
asm (" mov x0, #8\n"
|
||
|
" mvn x0, x0\n"
|
||
|
" b set_point4\n"
|
||
|
"0:\n"
|
||
|
" mov %[ok], #1\n"
|
||
|
" b 1f\n"
|
||
|
"set_point4:\n"
|
||
|
" tbz x0, #3, 0b\n"
|
||
|
" mov %[ok], #0\n"
|
||
|
"1:\n"
|
||
|
: [ok] "=r" (ok)
|
||
|
:
|
||
|
: "0");
|
||
|
|
||
|
if (ok == 1)
|
||
|
pass ();
|
||
|
else
|
||
|
fail ();
|
||
|
}
|
||
|
|
||
|
/* Make sure we can relocate a TBNZ instruction.
|
||
|
|
||
|
MOV x0, #8 ; Set bit 3.
|
||
|
B set_point5
|
||
|
set_ok:
|
||
|
MOV %[ok], #1
|
||
|
B end
|
||
|
set_point5:
|
||
|
TBNZ x0, #3, set_ok ; tracepoint here.
|
||
|
MOV %[ok], #0
|
||
|
end
|
||
|
|
||
|
*/
|
||
|
|
||
|
static void
|
||
|
can_relocate_tbnz (void)
|
||
|
{
|
||
|
int ok = 0;
|
||
|
|
||
|
asm (" mov x0, #8\n"
|
||
|
" b set_point5\n"
|
||
|
"0:\n"
|
||
|
" mov %[ok], #1\n"
|
||
|
" b 1f\n"
|
||
|
"set_point5:\n"
|
||
|
" tbnz x0, #3, 0b\n"
|
||
|
" mov %[ok], #0\n"
|
||
|
"1:\n"
|
||
|
: [ok] "=r" (ok)
|
||
|
:
|
||
|
: "0");
|
||
|
|
||
|
if (ok == 1)
|
||
|
pass ();
|
||
|
else
|
||
|
fail ();
|
||
|
}
|
||
|
|
||
|
/* Make sure we can relocate an ADR instruction with a positive offset.
|
||
|
|
||
|
set_point6:
|
||
|
ADR x0, target ; tracepoint here.
|
||
|
BR x0 ; jump to target
|
||
|
MOV %[ok], #0
|
||
|
B end
|
||
|
target:
|
||
|
MOV %[ok], #1
|
||
|
end
|
||
|
|
||
|
*/
|
||
|
|
||
|
static void
|
||
|
can_relocate_adr_forward (void)
|
||
|
{
|
||
|
int ok = 0;
|
||
|
|
||
|
asm ("set_point6:\n"
|
||
|
" adr x0, 0f\n"
|
||
|
" br x0\n"
|
||
|
" mov %[ok], #0\n"
|
||
|
" b 1f\n"
|
||
|
"0:\n"
|
||
|
" mov %[ok], #1\n"
|
||
|
"1:\n"
|
||
|
: [ok] "=r" (ok)
|
||
|
:
|
||
|
: "0");
|
||
|
|
||
|
if (ok == 1)
|
||
|
pass ();
|
||
|
else
|
||
|
fail ();
|
||
|
}
|
||
|
|
||
|
/* Make sure we can relocate an ADR instruction with a negative offset.
|
||
|
|
||
|
B set_point7
|
||
|
target:
|
||
|
MOV %[ok], #1
|
||
|
B end
|
||
|
set_point7:
|
||
|
ADR x0, target ; tracepoint here.
|
||
|
BR x0 ; jump to target
|
||
|
MOV %[ok], #0
|
||
|
end
|
||
|
|
||
|
*/
|
||
|
|
||
|
static void
|
||
|
can_relocate_adr_backward (void)
|
||
|
{
|
||
|
int ok = 0;
|
||
|
|
||
|
asm ("b set_point7\n"
|
||
|
"0:\n"
|
||
|
" mov %[ok], #1\n"
|
||
|
" b 1f\n"
|
||
|
"set_point7:\n"
|
||
|
" adr x0, 0b\n"
|
||
|
" br x0\n"
|
||
|
" mov %[ok], #0\n"
|
||
|
"1:\n"
|
||
|
: [ok] "=r" (ok)
|
||
|
:
|
||
|
: "0");
|
||
|
|
||
|
if (ok == 1)
|
||
|
pass ();
|
||
|
else
|
||
|
fail ();
|
||
|
}
|
||
|
|
||
|
/* Make sure we can relocate an ADRP instruction.
|
||
|
|
||
|
set_point8:
|
||
|
ADRP %[addr], set_point8 ; tracepoint here.
|
||
|
ADR %[pc], set_point8
|
||
|
|
||
|
ADR computes the address of the given label. While ADRP gives us its
|
||
|
page, on a 4K boundary. We can check ADRP executed normally by
|
||
|
making sure the result of ADR and ADRP are equivalent, except for the
|
||
|
12 lowest bits which should be cleared.
|
||
|
|
||
|
*/
|
||
|
|
||
|
static void
|
||
|
can_relocate_adrp (void)
|
||
|
{
|
||
|
uintptr_t page;
|
||
|
uintptr_t pc;
|
||
|
|
||
|
asm ("set_point8:\n"
|
||
|
" adrp %[page], set_point8\n"
|
||
|
" adr %[pc], set_point8\n"
|
||
|
: [page] "=r" (page), [pc] "=r" (pc));
|
||
|
|
||
|
if (page == (pc & ~0xfff))
|
||
|
pass ();
|
||
|
else
|
||
|
fail ();
|
||
|
}
|
||
|
|
||
|
/* Make sure we can relocate an LDR instruction, where the memory to
|
||
|
read is an offset from the current PC.
|
||
|
|
||
|
B set_point9
|
||
|
data:
|
||
|
.word 0x0cabba9e
|
||
|
set_point9:
|
||
|
LDR %[result], data ; tracepoint here.
|
||
|
|
||
|
*/
|
||
|
|
||
|
static void
|
||
|
can_relocate_ldr (void)
|
||
|
{
|
||
|
uint32_t result = 0;
|
||
|
|
||
|
asm ("b set_point9\n"
|
||
|
"0:\n"
|
||
|
" .word 0x0cabba9e\n"
|
||
|
"set_point9:\n"
|
||
|
" ldr %w[result], 0b\n"
|
||
|
: [result] "=r" (result));
|
||
|
|
||
|
if (result == 0x0cabba9e)
|
||
|
pass ();
|
||
|
else
|
||
|
fail ();
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
/* Functions testing relocations need to be placed here. GDB will read
|
||
|
n_testcases to know how many fast tracepoints to place. It will look
|
||
|
for symbols in the form of 'set_point\[0-9\]+' so each functions
|
||
|
needs one, starting at 0. */
|
||
|
|
||
|
static testcase_ftype testcases[] = {
|
||
|
#if (defined __x86_64__ || defined __i386__)
|
||
|
can_relocate_call,
|
||
|
can_relocate_jump
|
||
|
#elif (defined __aarch64__)
|
||
|
can_relocate_b,
|
||
|
can_relocate_bcond,
|
||
|
can_relocate_cbz,
|
||
|
can_relocate_cbnz,
|
||
|
can_relocate_tbz,
|
||
|
can_relocate_tbnz,
|
||
|
can_relocate_adr_forward,
|
||
|
can_relocate_adr_backward,
|
||
|
can_relocate_adrp,
|
||
|
can_relocate_ldr
|
||
|
#endif
|
||
|
};
|
||
|
|
||
|
static size_t n_testcases = (sizeof (testcases) / sizeof (testcase_ftype));
|
||
|
|
||
|
int
|
||
|
main ()
|
||
|
{
|
||
|
int i = 0;
|
||
|
|
||
|
for (i = 0; i < n_testcases; i++)
|
||
|
testcases[i] ();
|
||
|
|
||
|
return 0;
|
||
|
}
|