mirror of
https://github.com/FEX-Emu/linux.git
synced 2024-12-27 03:47:43 +00:00
d2aa1acad2
It may be useful to debug writes to the readonly sections of memory, so provide a cmdline "rodata=off" to allow for this. This can be expanded in the future to support "log" and "write" modes, but that will need to be architecture-specific. This also makes KDB software breakpoints more usable, as read-only mappings can now be disabled on any kernel. Suggested-by: H. Peter Anvin <hpa@zytor.com> Signed-off-by: Kees Cook <keescook@chromium.org> Cc: Andy Lutomirski <luto@amacapital.net> Cc: Arnd Bergmann <arnd@arndb.de> Cc: Borislav Petkov <bp@alien8.de> Cc: Brian Gerst <brgerst@gmail.com> Cc: David Brown <david.brown@linaro.org> Cc: Denys Vlasenko <dvlasenk@redhat.com> Cc: Emese Revfy <re.emese@gmail.com> Cc: Linus Torvalds <torvalds@linux-foundation.org> Cc: Mathias Krause <minipli@googlemail.com> Cc: Michael Ellerman <mpe@ellerman.id.au> Cc: PaX Team <pageexec@freemail.hu> Cc: Peter Zijlstra <peterz@infradead.org> Cc: Thomas Gleixner <tglx@linutronix.de> Cc: kernel-hardening@lists.openwall.com Cc: linux-arch <linux-arch@vger.kernel.org> Link: http://lkml.kernel.org/r/1455748879-21872-3-git-send-email-keescook@chromium.org Signed-off-by: Ingo Molnar <mingo@kernel.org>
559 lines
11 KiB
C
559 lines
11 KiB
C
/*
|
|
* Kernel Debugger Architecture Independent Breakpoint Handler
|
|
*
|
|
* This file is subject to the terms and conditions of the GNU General Public
|
|
* License. See the file "COPYING" in the main directory of this archive
|
|
* for more details.
|
|
*
|
|
* Copyright (c) 1999-2004 Silicon Graphics, Inc. All Rights Reserved.
|
|
* Copyright (c) 2009 Wind River Systems, Inc. All Rights Reserved.
|
|
*/
|
|
|
|
#include <linux/string.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/init.h>
|
|
#include <linux/kdb.h>
|
|
#include <linux/kgdb.h>
|
|
#include <linux/smp.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/interrupt.h>
|
|
#include "kdb_private.h"
|
|
|
|
/*
|
|
* Table of kdb_breakpoints
|
|
*/
|
|
kdb_bp_t kdb_breakpoints[KDB_MAXBPT];
|
|
|
|
static void kdb_setsinglestep(struct pt_regs *regs)
|
|
{
|
|
KDB_STATE_SET(DOING_SS);
|
|
}
|
|
|
|
static char *kdb_rwtypes[] = {
|
|
"Instruction(i)",
|
|
"Instruction(Register)",
|
|
"Data Write",
|
|
"I/O",
|
|
"Data Access"
|
|
};
|
|
|
|
static char *kdb_bptype(kdb_bp_t *bp)
|
|
{
|
|
if (bp->bp_type < 0 || bp->bp_type > 4)
|
|
return "";
|
|
|
|
return kdb_rwtypes[bp->bp_type];
|
|
}
|
|
|
|
static int kdb_parsebp(int argc, const char **argv, int *nextargp, kdb_bp_t *bp)
|
|
{
|
|
int nextarg = *nextargp;
|
|
int diag;
|
|
|
|
bp->bph_length = 1;
|
|
if ((argc + 1) != nextarg) {
|
|
if (strncasecmp(argv[nextarg], "datar", sizeof("datar")) == 0)
|
|
bp->bp_type = BP_ACCESS_WATCHPOINT;
|
|
else if (strncasecmp(argv[nextarg], "dataw", sizeof("dataw")) == 0)
|
|
bp->bp_type = BP_WRITE_WATCHPOINT;
|
|
else if (strncasecmp(argv[nextarg], "inst", sizeof("inst")) == 0)
|
|
bp->bp_type = BP_HARDWARE_BREAKPOINT;
|
|
else
|
|
return KDB_ARGCOUNT;
|
|
|
|
bp->bph_length = 1;
|
|
|
|
nextarg++;
|
|
|
|
if ((argc + 1) != nextarg) {
|
|
unsigned long len;
|
|
|
|
diag = kdbgetularg((char *)argv[nextarg],
|
|
&len);
|
|
if (diag)
|
|
return diag;
|
|
|
|
|
|
if (len > 8)
|
|
return KDB_BADLENGTH;
|
|
|
|
bp->bph_length = len;
|
|
nextarg++;
|
|
}
|
|
|
|
if ((argc + 1) != nextarg)
|
|
return KDB_ARGCOUNT;
|
|
}
|
|
|
|
*nextargp = nextarg;
|
|
return 0;
|
|
}
|
|
|
|
static int _kdb_bp_remove(kdb_bp_t *bp)
|
|
{
|
|
int ret = 1;
|
|
if (!bp->bp_installed)
|
|
return ret;
|
|
if (!bp->bp_type)
|
|
ret = dbg_remove_sw_break(bp->bp_addr);
|
|
else
|
|
ret = arch_kgdb_ops.remove_hw_breakpoint(bp->bp_addr,
|
|
bp->bph_length,
|
|
bp->bp_type);
|
|
if (ret == 0)
|
|
bp->bp_installed = 0;
|
|
return ret;
|
|
}
|
|
|
|
static void kdb_handle_bp(struct pt_regs *regs, kdb_bp_t *bp)
|
|
{
|
|
if (KDB_DEBUG(BP))
|
|
kdb_printf("regs->ip = 0x%lx\n", instruction_pointer(regs));
|
|
|
|
/*
|
|
* Setup single step
|
|
*/
|
|
kdb_setsinglestep(regs);
|
|
|
|
/*
|
|
* Reset delay attribute
|
|
*/
|
|
bp->bp_delay = 0;
|
|
bp->bp_delayed = 1;
|
|
}
|
|
|
|
static int _kdb_bp_install(struct pt_regs *regs, kdb_bp_t *bp)
|
|
{
|
|
int ret;
|
|
/*
|
|
* Install the breakpoint, if it is not already installed.
|
|
*/
|
|
|
|
if (KDB_DEBUG(BP))
|
|
kdb_printf("%s: bp_installed %d\n",
|
|
__func__, bp->bp_installed);
|
|
if (!KDB_STATE(SSBPT))
|
|
bp->bp_delay = 0;
|
|
if (bp->bp_installed)
|
|
return 1;
|
|
if (bp->bp_delay || (bp->bp_delayed && KDB_STATE(DOING_SS))) {
|
|
if (KDB_DEBUG(BP))
|
|
kdb_printf("%s: delayed bp\n", __func__);
|
|
kdb_handle_bp(regs, bp);
|
|
return 0;
|
|
}
|
|
if (!bp->bp_type)
|
|
ret = dbg_set_sw_break(bp->bp_addr);
|
|
else
|
|
ret = arch_kgdb_ops.set_hw_breakpoint(bp->bp_addr,
|
|
bp->bph_length,
|
|
bp->bp_type);
|
|
if (ret == 0) {
|
|
bp->bp_installed = 1;
|
|
} else {
|
|
kdb_printf("%s: failed to set breakpoint at 0x%lx\n",
|
|
__func__, bp->bp_addr);
|
|
if (!bp->bp_type) {
|
|
kdb_printf("Software breakpoints are unavailable.\n"
|
|
" Boot the kernel with rodata=off\n"
|
|
" OR use hw breaks: help bph\n");
|
|
}
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* kdb_bp_install
|
|
*
|
|
* Install kdb_breakpoints prior to returning from the
|
|
* kernel debugger. This allows the kdb_breakpoints to be set
|
|
* upon functions that are used internally by kdb, such as
|
|
* printk(). This function is only called once per kdb session.
|
|
*/
|
|
void kdb_bp_install(struct pt_regs *regs)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < KDB_MAXBPT; i++) {
|
|
kdb_bp_t *bp = &kdb_breakpoints[i];
|
|
|
|
if (KDB_DEBUG(BP)) {
|
|
kdb_printf("%s: bp %d bp_enabled %d\n",
|
|
__func__, i, bp->bp_enabled);
|
|
}
|
|
if (bp->bp_enabled)
|
|
_kdb_bp_install(regs, bp);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* kdb_bp_remove
|
|
*
|
|
* Remove kdb_breakpoints upon entry to the kernel debugger.
|
|
*
|
|
* Parameters:
|
|
* None.
|
|
* Outputs:
|
|
* None.
|
|
* Returns:
|
|
* None.
|
|
* Locking:
|
|
* None.
|
|
* Remarks:
|
|
*/
|
|
void kdb_bp_remove(void)
|
|
{
|
|
int i;
|
|
|
|
for (i = KDB_MAXBPT - 1; i >= 0; i--) {
|
|
kdb_bp_t *bp = &kdb_breakpoints[i];
|
|
|
|
if (KDB_DEBUG(BP)) {
|
|
kdb_printf("%s: bp %d bp_enabled %d\n",
|
|
__func__, i, bp->bp_enabled);
|
|
}
|
|
if (bp->bp_enabled)
|
|
_kdb_bp_remove(bp);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* kdb_printbp
|
|
*
|
|
* Internal function to format and print a breakpoint entry.
|
|
*
|
|
* Parameters:
|
|
* None.
|
|
* Outputs:
|
|
* None.
|
|
* Returns:
|
|
* None.
|
|
* Locking:
|
|
* None.
|
|
* Remarks:
|
|
*/
|
|
|
|
static void kdb_printbp(kdb_bp_t *bp, int i)
|
|
{
|
|
kdb_printf("%s ", kdb_bptype(bp));
|
|
kdb_printf("BP #%d at ", i);
|
|
kdb_symbol_print(bp->bp_addr, NULL, KDB_SP_DEFAULT);
|
|
|
|
if (bp->bp_enabled)
|
|
kdb_printf("\n is enabled");
|
|
else
|
|
kdb_printf("\n is disabled");
|
|
|
|
kdb_printf("\taddr at %016lx, hardtype=%d installed=%d\n",
|
|
bp->bp_addr, bp->bp_type, bp->bp_installed);
|
|
|
|
kdb_printf("\n");
|
|
}
|
|
|
|
/*
|
|
* kdb_bp
|
|
*
|
|
* Handle the bp commands.
|
|
*
|
|
* [bp|bph] <addr-expression> [DATAR|DATAW]
|
|
*
|
|
* Parameters:
|
|
* argc Count of arguments in argv
|
|
* argv Space delimited command line arguments
|
|
* Outputs:
|
|
* None.
|
|
* Returns:
|
|
* Zero for success, a kdb diagnostic if failure.
|
|
* Locking:
|
|
* None.
|
|
* Remarks:
|
|
*
|
|
* bp Set breakpoint on all cpus. Only use hardware assist if need.
|
|
* bph Set breakpoint on all cpus. Force hardware register
|
|
*/
|
|
|
|
static int kdb_bp(int argc, const char **argv)
|
|
{
|
|
int i, bpno;
|
|
kdb_bp_t *bp, *bp_check;
|
|
int diag;
|
|
char *symname = NULL;
|
|
long offset = 0ul;
|
|
int nextarg;
|
|
kdb_bp_t template = {0};
|
|
|
|
if (argc == 0) {
|
|
/*
|
|
* Display breakpoint table
|
|
*/
|
|
for (bpno = 0, bp = kdb_breakpoints; bpno < KDB_MAXBPT;
|
|
bpno++, bp++) {
|
|
if (bp->bp_free)
|
|
continue;
|
|
kdb_printbp(bp, bpno);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
nextarg = 1;
|
|
diag = kdbgetaddrarg(argc, argv, &nextarg, &template.bp_addr,
|
|
&offset, &symname);
|
|
if (diag)
|
|
return diag;
|
|
if (!template.bp_addr)
|
|
return KDB_BADINT;
|
|
|
|
/*
|
|
* Find an empty bp structure to allocate
|
|
*/
|
|
for (bpno = 0, bp = kdb_breakpoints; bpno < KDB_MAXBPT; bpno++, bp++) {
|
|
if (bp->bp_free)
|
|
break;
|
|
}
|
|
|
|
if (bpno == KDB_MAXBPT)
|
|
return KDB_TOOMANYBPT;
|
|
|
|
if (strcmp(argv[0], "bph") == 0) {
|
|
template.bp_type = BP_HARDWARE_BREAKPOINT;
|
|
diag = kdb_parsebp(argc, argv, &nextarg, &template);
|
|
if (diag)
|
|
return diag;
|
|
} else {
|
|
template.bp_type = BP_BREAKPOINT;
|
|
}
|
|
|
|
/*
|
|
* Check for clashing breakpoints.
|
|
*
|
|
* Note, in this design we can't have hardware breakpoints
|
|
* enabled for both read and write on the same address.
|
|
*/
|
|
for (i = 0, bp_check = kdb_breakpoints; i < KDB_MAXBPT;
|
|
i++, bp_check++) {
|
|
if (!bp_check->bp_free &&
|
|
bp_check->bp_addr == template.bp_addr) {
|
|
kdb_printf("You already have a breakpoint at "
|
|
kdb_bfd_vma_fmt0 "\n", template.bp_addr);
|
|
return KDB_DUPBPT;
|
|
}
|
|
}
|
|
|
|
template.bp_enabled = 1;
|
|
|
|
/*
|
|
* Actually allocate the breakpoint found earlier
|
|
*/
|
|
*bp = template;
|
|
bp->bp_free = 0;
|
|
|
|
kdb_printbp(bp, bpno);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* kdb_bc
|
|
*
|
|
* Handles the 'bc', 'be', and 'bd' commands
|
|
*
|
|
* [bd|bc|be] <breakpoint-number>
|
|
* [bd|bc|be] *
|
|
*
|
|
* Parameters:
|
|
* argc Count of arguments in argv
|
|
* argv Space delimited command line arguments
|
|
* Outputs:
|
|
* None.
|
|
* Returns:
|
|
* Zero for success, a kdb diagnostic for failure
|
|
* Locking:
|
|
* None.
|
|
* Remarks:
|
|
*/
|
|
static int kdb_bc(int argc, const char **argv)
|
|
{
|
|
unsigned long addr;
|
|
kdb_bp_t *bp = NULL;
|
|
int lowbp = KDB_MAXBPT;
|
|
int highbp = 0;
|
|
int done = 0;
|
|
int i;
|
|
int diag = 0;
|
|
|
|
int cmd; /* KDBCMD_B? */
|
|
#define KDBCMD_BC 0
|
|
#define KDBCMD_BE 1
|
|
#define KDBCMD_BD 2
|
|
|
|
if (strcmp(argv[0], "be") == 0)
|
|
cmd = KDBCMD_BE;
|
|
else if (strcmp(argv[0], "bd") == 0)
|
|
cmd = KDBCMD_BD;
|
|
else
|
|
cmd = KDBCMD_BC;
|
|
|
|
if (argc != 1)
|
|
return KDB_ARGCOUNT;
|
|
|
|
if (strcmp(argv[1], "*") == 0) {
|
|
lowbp = 0;
|
|
highbp = KDB_MAXBPT;
|
|
} else {
|
|
diag = kdbgetularg(argv[1], &addr);
|
|
if (diag)
|
|
return diag;
|
|
|
|
/*
|
|
* For addresses less than the maximum breakpoint number,
|
|
* assume that the breakpoint number is desired.
|
|
*/
|
|
if (addr < KDB_MAXBPT) {
|
|
bp = &kdb_breakpoints[addr];
|
|
lowbp = highbp = addr;
|
|
highbp++;
|
|
} else {
|
|
for (i = 0, bp = kdb_breakpoints; i < KDB_MAXBPT;
|
|
i++, bp++) {
|
|
if (bp->bp_addr == addr) {
|
|
lowbp = highbp = i;
|
|
highbp++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Now operate on the set of breakpoints matching the input
|
|
* criteria (either '*' for all, or an individual breakpoint).
|
|
*/
|
|
for (bp = &kdb_breakpoints[lowbp], i = lowbp;
|
|
i < highbp;
|
|
i++, bp++) {
|
|
if (bp->bp_free)
|
|
continue;
|
|
|
|
done++;
|
|
|
|
switch (cmd) {
|
|
case KDBCMD_BC:
|
|
bp->bp_enabled = 0;
|
|
|
|
kdb_printf("Breakpoint %d at "
|
|
kdb_bfd_vma_fmt " cleared\n",
|
|
i, bp->bp_addr);
|
|
|
|
bp->bp_addr = 0;
|
|
bp->bp_free = 1;
|
|
|
|
break;
|
|
case KDBCMD_BE:
|
|
bp->bp_enabled = 1;
|
|
|
|
kdb_printf("Breakpoint %d at "
|
|
kdb_bfd_vma_fmt " enabled",
|
|
i, bp->bp_addr);
|
|
|
|
kdb_printf("\n");
|
|
break;
|
|
case KDBCMD_BD:
|
|
if (!bp->bp_enabled)
|
|
break;
|
|
|
|
bp->bp_enabled = 0;
|
|
|
|
kdb_printf("Breakpoint %d at "
|
|
kdb_bfd_vma_fmt " disabled\n",
|
|
i, bp->bp_addr);
|
|
|
|
break;
|
|
}
|
|
if (bp->bp_delay && (cmd == KDBCMD_BC || cmd == KDBCMD_BD)) {
|
|
bp->bp_delay = 0;
|
|
KDB_STATE_CLEAR(SSBPT);
|
|
}
|
|
}
|
|
|
|
return (!done) ? KDB_BPTNOTFOUND : 0;
|
|
}
|
|
|
|
/*
|
|
* kdb_ss
|
|
*
|
|
* Process the 'ss' (Single Step) command.
|
|
*
|
|
* ss
|
|
*
|
|
* Parameters:
|
|
* argc Argument count
|
|
* argv Argument vector
|
|
* Outputs:
|
|
* None.
|
|
* Returns:
|
|
* KDB_CMD_SS for success, a kdb error if failure.
|
|
* Locking:
|
|
* None.
|
|
* Remarks:
|
|
*
|
|
* Set the arch specific option to trigger a debug trap after the next
|
|
* instruction.
|
|
*/
|
|
|
|
static int kdb_ss(int argc, const char **argv)
|
|
{
|
|
if (argc != 0)
|
|
return KDB_ARGCOUNT;
|
|
/*
|
|
* Set trace flag and go.
|
|
*/
|
|
KDB_STATE_SET(DOING_SS);
|
|
return KDB_CMD_SS;
|
|
}
|
|
|
|
/* Initialize the breakpoint table and register breakpoint commands. */
|
|
|
|
void __init kdb_initbptab(void)
|
|
{
|
|
int i;
|
|
kdb_bp_t *bp;
|
|
|
|
/*
|
|
* First time initialization.
|
|
*/
|
|
memset(&kdb_breakpoints, '\0', sizeof(kdb_breakpoints));
|
|
|
|
for (i = 0, bp = kdb_breakpoints; i < KDB_MAXBPT; i++, bp++)
|
|
bp->bp_free = 1;
|
|
|
|
kdb_register_flags("bp", kdb_bp, "[<vaddr>]",
|
|
"Set/Display breakpoints", 0,
|
|
KDB_ENABLE_FLOW_CTRL | KDB_REPEAT_NO_ARGS);
|
|
kdb_register_flags("bl", kdb_bp, "[<vaddr>]",
|
|
"Display breakpoints", 0,
|
|
KDB_ENABLE_FLOW_CTRL | KDB_REPEAT_NO_ARGS);
|
|
if (arch_kgdb_ops.flags & KGDB_HW_BREAKPOINT)
|
|
kdb_register_flags("bph", kdb_bp, "[<vaddr>]",
|
|
"[datar [length]|dataw [length]] Set hw brk", 0,
|
|
KDB_ENABLE_FLOW_CTRL | KDB_REPEAT_NO_ARGS);
|
|
kdb_register_flags("bc", kdb_bc, "<bpnum>",
|
|
"Clear Breakpoint", 0,
|
|
KDB_ENABLE_FLOW_CTRL);
|
|
kdb_register_flags("be", kdb_bc, "<bpnum>",
|
|
"Enable Breakpoint", 0,
|
|
KDB_ENABLE_FLOW_CTRL);
|
|
kdb_register_flags("bd", kdb_bc, "<bpnum>",
|
|
"Disable Breakpoint", 0,
|
|
KDB_ENABLE_FLOW_CTRL);
|
|
|
|
kdb_register_flags("ss", kdb_ss, "",
|
|
"Single Step", 1,
|
|
KDB_ENABLE_FLOW_CTRL | KDB_REPEAT_NO_ARGS);
|
|
/*
|
|
* Architecture dependent initialization.
|
|
*/
|
|
}
|