/* This testcase is part of GDB, the GNU debugger.
Copyright 2015-2020 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_true (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 ();
}
/* Make sure we can relocate a B.cond instruction and condition is false. */
static void
can_relocate_bcond_false (void)
{
int ok = 0;
asm (" mov x0, #8\n"
" tst x0, #8\n" /* Clear the Z flag. */
"set_point10:\n" /* Set tracepoint here. */
" b.eq 0b\n" /* Condition is false. */
" mov %[ok], #1\n"
" b 1f\n"
"0:\n"
" mov %[ok], #0\n"
"1:\n"
: [ok] "=r" (ok)
:
: "0", "cc");
if (ok == 1)
pass ();
else
fail ();
}
static void
foo (void)
{
}
/* Make sure we can relocate a BL instruction. */
static void
can_relocate_bl (void)
{
asm ("set_point11:\n"
" bl foo\n"
" bl pass\n"
: : : "x30"); /* Test that LR is updated correctly. */
}
#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_true,
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,
can_relocate_bcond_false,
can_relocate_bl,
#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;
}