mirror of
https://sourceware.org/git/binutils-gdb.git
synced 2024-11-27 03:51:15 +08:00
387f97a1b2
Simon pointed out to me that there are some failures when building with clang,
that were caused by my commit
commit d894edfcc4
Author: Felix Willgerodt <felix.willgerodt@intel.com>
Date: Mon Feb 18 13:49:25 2019 +0100
btrace: Introduce auxiliary instructions.
The errors are:
CXX btrace.o
gdb/btrace.c:1203:11: error: suggest braces around initialization of subobject [-Werror,-Wmissing-braces]
1203 | return {(CORE_ADDR) insn.ip, (gdb_byte) insn.size,
| ^~~~~~~~~~~~~~~~~~~
| { }
gdb/btrace.c:1218:21: error: suggest braces around initialization of subobject [-Werror,-Wmissing-braces]
1218 | btrace_insn insn {btinfo->aux_data.size () - 1, 0,
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
| { }
gdb/btrace.c:1323:34: error: variable 'bfun' is uninitialized when used here [-Werror,-Wuninitialized]
1323 | handle_pt_aux_insn (btinfo, bfun, *ptw_string, pc);
| ^~~~
gdb/btrace.c:1236:35: note: initialize the variable 'bfun' to silence this warning
1236 | struct btrace_function *bfun;
| ^
| = nullptr
3 errors generated.
make[1]: *** [Makefile:1961: btrace.o] Error 1
This fixes those errors and switches two casts to C++ casts while we
are at it.
Approved-By: Simon Marchi <simon.marchi@efficios.com>
3284 lines
85 KiB
C
3284 lines
85 KiB
C
/* Branch trace support for GDB, the GNU debugger.
|
|
|
|
Copyright (C) 2013-2024 Free Software Foundation, Inc.
|
|
|
|
Contributed by Intel Corp. <markus.t.metzger@intel.com>
|
|
|
|
This file is part of GDB.
|
|
|
|
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 "btrace.h"
|
|
#include "gdbthread.h"
|
|
#include "inferior.h"
|
|
#include "target.h"
|
|
#include "record.h"
|
|
#include "symtab.h"
|
|
#include "disasm.h"
|
|
#include "source.h"
|
|
#include "filenames.h"
|
|
#include "regcache.h"
|
|
#include "gdbsupport/rsp-low.h"
|
|
#include "cli/cli-cmds.h"
|
|
#include "cli/cli-utils.h"
|
|
#include "extension.h"
|
|
#include "gdbarch.h"
|
|
|
|
/* For maintenance commands. */
|
|
#include "record-btrace.h"
|
|
|
|
#include <inttypes.h>
|
|
#include <ctype.h>
|
|
#include <algorithm>
|
|
#include <string>
|
|
|
|
/* Command lists for btrace maintenance commands. */
|
|
static struct cmd_list_element *maint_btrace_cmdlist;
|
|
static struct cmd_list_element *maint_btrace_set_cmdlist;
|
|
static struct cmd_list_element *maint_btrace_show_cmdlist;
|
|
static struct cmd_list_element *maint_btrace_pt_set_cmdlist;
|
|
static struct cmd_list_element *maint_btrace_pt_show_cmdlist;
|
|
|
|
/* Control whether to skip PAD packets when computing the packet history. */
|
|
static bool maint_btrace_pt_skip_pad = true;
|
|
|
|
static void btrace_add_pc (struct thread_info *tp);
|
|
|
|
/* Print a record debug message. Use do ... while (0) to avoid ambiguities
|
|
when used in if statements. */
|
|
|
|
#define DEBUG(msg, args...) \
|
|
do \
|
|
{ \
|
|
if (record_debug != 0) \
|
|
gdb_printf (gdb_stdlog, \
|
|
"[btrace] " msg "\n", ##args); \
|
|
} \
|
|
while (0)
|
|
|
|
#define DEBUG_FTRACE(msg, args...) DEBUG ("[ftrace] " msg, ##args)
|
|
|
|
/* Return the function name of a recorded function segment for printing.
|
|
This function never returns NULL. */
|
|
|
|
static const char *
|
|
ftrace_print_function_name (const struct btrace_function *bfun)
|
|
{
|
|
struct minimal_symbol *msym;
|
|
struct symbol *sym;
|
|
|
|
msym = bfun->msym;
|
|
sym = bfun->sym;
|
|
|
|
if (sym != NULL)
|
|
return sym->print_name ();
|
|
|
|
if (msym != NULL)
|
|
return msym->print_name ();
|
|
|
|
return "<unknown>";
|
|
}
|
|
|
|
/* Return the file name of a recorded function segment for printing.
|
|
This function never returns NULL. */
|
|
|
|
static const char *
|
|
ftrace_print_filename (const struct btrace_function *bfun)
|
|
{
|
|
struct symbol *sym;
|
|
const char *filename;
|
|
|
|
sym = bfun->sym;
|
|
|
|
if (sym != NULL)
|
|
filename = symtab_to_filename_for_display (sym->symtab ());
|
|
else
|
|
filename = "<unknown>";
|
|
|
|
return filename;
|
|
}
|
|
|
|
/* Return a string representation of the address of an instruction.
|
|
This function never returns NULL. */
|
|
|
|
static const char *
|
|
ftrace_print_insn_addr (const struct btrace_insn *insn)
|
|
{
|
|
if (insn == NULL)
|
|
return "<nil>";
|
|
|
|
return core_addr_to_string_nz (insn->pc);
|
|
}
|
|
|
|
/* Print an ftrace debug status message. */
|
|
|
|
static void
|
|
ftrace_debug (const struct btrace_function *bfun, const char *prefix)
|
|
{
|
|
const char *fun, *file;
|
|
unsigned int ibegin, iend;
|
|
int level;
|
|
|
|
fun = ftrace_print_function_name (bfun);
|
|
file = ftrace_print_filename (bfun);
|
|
level = bfun->level;
|
|
|
|
ibegin = bfun->insn_offset;
|
|
iend = ibegin + bfun->insn.size ();
|
|
|
|
DEBUG_FTRACE ("%s: fun = %s, file = %s, level = %d, insn = [%u; %u)",
|
|
prefix, fun, file, level, ibegin, iend);
|
|
}
|
|
|
|
/* Return the number of instructions in a given function call segment. */
|
|
|
|
static unsigned int
|
|
ftrace_call_num_insn (const struct btrace_function* bfun)
|
|
{
|
|
if (bfun == NULL)
|
|
return 0;
|
|
|
|
/* A gap is always counted as one instruction. */
|
|
if (bfun->errcode != 0)
|
|
return 1;
|
|
|
|
return bfun->insn.size ();
|
|
}
|
|
|
|
/* Return the function segment with the given NUMBER or NULL if no such segment
|
|
exists. BTINFO is the branch trace information for the current thread. */
|
|
|
|
static struct btrace_function *
|
|
ftrace_find_call_by_number (struct btrace_thread_info *btinfo,
|
|
unsigned int number)
|
|
{
|
|
if (number == 0 || number > btinfo->functions.size ())
|
|
return NULL;
|
|
|
|
return &btinfo->functions[number - 1];
|
|
}
|
|
|
|
/* A const version of the function above. */
|
|
|
|
static const struct btrace_function *
|
|
ftrace_find_call_by_number (const struct btrace_thread_info *btinfo,
|
|
unsigned int number)
|
|
{
|
|
if (number == 0 || number > btinfo->functions.size ())
|
|
return NULL;
|
|
|
|
return &btinfo->functions[number - 1];
|
|
}
|
|
|
|
/* Return non-zero if BFUN does not match MFUN and FUN,
|
|
return zero otherwise. */
|
|
|
|
static int
|
|
ftrace_function_switched (const struct btrace_function *bfun,
|
|
const struct minimal_symbol *mfun,
|
|
const struct symbol *fun)
|
|
{
|
|
struct minimal_symbol *msym;
|
|
struct symbol *sym;
|
|
|
|
msym = bfun->msym;
|
|
sym = bfun->sym;
|
|
|
|
/* If the minimal symbol changed, we certainly switched functions. */
|
|
if (mfun != NULL && msym != NULL
|
|
&& strcmp (mfun->linkage_name (), msym->linkage_name ()) != 0)
|
|
return 1;
|
|
|
|
/* If the symbol changed, we certainly switched functions. */
|
|
if (fun != NULL && sym != NULL)
|
|
{
|
|
const char *bfname, *fname;
|
|
|
|
/* Check the function name. */
|
|
if (strcmp (fun->linkage_name (), sym->linkage_name ()) != 0)
|
|
return 1;
|
|
|
|
/* Check the location of those functions, as well. */
|
|
bfname = symtab_to_fullname (sym->symtab ());
|
|
fname = symtab_to_fullname (fun->symtab ());
|
|
if (filename_cmp (fname, bfname) != 0)
|
|
return 1;
|
|
}
|
|
|
|
/* If we lost symbol information, we switched functions. */
|
|
if (!(msym == NULL && sym == NULL) && mfun == NULL && fun == NULL)
|
|
return 1;
|
|
|
|
/* If we gained symbol information, we switched functions. */
|
|
if (msym == NULL && sym == NULL && !(mfun == NULL && fun == NULL))
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Allocate and initialize a new branch trace function segment at the end of
|
|
the trace.
|
|
BTINFO is the branch trace information for the current thread.
|
|
MFUN and FUN are the symbol information we have for this function.
|
|
This invalidates all struct btrace_function pointer currently held. */
|
|
|
|
static struct btrace_function *
|
|
ftrace_new_function (struct btrace_thread_info *btinfo,
|
|
struct minimal_symbol *mfun,
|
|
struct symbol *fun)
|
|
{
|
|
int level;
|
|
unsigned int number, insn_offset;
|
|
|
|
if (btinfo->functions.empty ())
|
|
{
|
|
/* Start counting NUMBER and INSN_OFFSET at one. */
|
|
level = 0;
|
|
number = 1;
|
|
insn_offset = 1;
|
|
}
|
|
else
|
|
{
|
|
const struct btrace_function *prev = &btinfo->functions.back ();
|
|
level = prev->level;
|
|
number = prev->number + 1;
|
|
insn_offset = prev->insn_offset + ftrace_call_num_insn (prev);
|
|
}
|
|
|
|
return &btinfo->functions.emplace_back (mfun, fun, number, insn_offset,
|
|
level);
|
|
}
|
|
|
|
/* Update the UP field of a function segment. */
|
|
|
|
static void
|
|
ftrace_update_caller (struct btrace_function *bfun,
|
|
struct btrace_function *caller,
|
|
btrace_function_flags flags)
|
|
{
|
|
if (bfun->up != 0)
|
|
ftrace_debug (bfun, "updating caller");
|
|
|
|
bfun->up = caller->number;
|
|
bfun->flags = flags;
|
|
|
|
ftrace_debug (bfun, "set caller");
|
|
ftrace_debug (caller, "..to");
|
|
}
|
|
|
|
/* Fix up the caller for all segments of a function. */
|
|
|
|
static void
|
|
ftrace_fixup_caller (struct btrace_thread_info *btinfo,
|
|
struct btrace_function *bfun,
|
|
struct btrace_function *caller,
|
|
btrace_function_flags flags)
|
|
{
|
|
unsigned int prev, next;
|
|
|
|
prev = bfun->prev;
|
|
next = bfun->next;
|
|
ftrace_update_caller (bfun, caller, flags);
|
|
|
|
/* Update all function segments belonging to the same function. */
|
|
for (; prev != 0; prev = bfun->prev)
|
|
{
|
|
bfun = ftrace_find_call_by_number (btinfo, prev);
|
|
ftrace_update_caller (bfun, caller, flags);
|
|
}
|
|
|
|
for (; next != 0; next = bfun->next)
|
|
{
|
|
bfun = ftrace_find_call_by_number (btinfo, next);
|
|
ftrace_update_caller (bfun, caller, flags);
|
|
}
|
|
}
|
|
|
|
/* Add a new function segment for a call at the end of the trace.
|
|
BTINFO is the branch trace information for the current thread.
|
|
MFUN and FUN are the symbol information we have for this function. */
|
|
|
|
static struct btrace_function *
|
|
ftrace_new_call (struct btrace_thread_info *btinfo,
|
|
struct minimal_symbol *mfun,
|
|
struct symbol *fun)
|
|
{
|
|
const unsigned int length = btinfo->functions.size ();
|
|
struct btrace_function *bfun = ftrace_new_function (btinfo, mfun, fun);
|
|
|
|
bfun->up = length;
|
|
bfun->level += 1;
|
|
|
|
ftrace_debug (bfun, "new call");
|
|
|
|
return bfun;
|
|
}
|
|
|
|
/* Add a new function segment for a tail call at the end of the trace.
|
|
BTINFO is the branch trace information for the current thread.
|
|
MFUN and FUN are the symbol information we have for this function. */
|
|
|
|
static struct btrace_function *
|
|
ftrace_new_tailcall (struct btrace_thread_info *btinfo,
|
|
struct minimal_symbol *mfun,
|
|
struct symbol *fun)
|
|
{
|
|
const unsigned int length = btinfo->functions.size ();
|
|
struct btrace_function *bfun = ftrace_new_function (btinfo, mfun, fun);
|
|
|
|
bfun->up = length;
|
|
bfun->level += 1;
|
|
bfun->flags |= BFUN_UP_LINKS_TO_TAILCALL;
|
|
|
|
ftrace_debug (bfun, "new tail call");
|
|
|
|
return bfun;
|
|
}
|
|
|
|
/* Return the caller of BFUN or NULL if there is none. This function skips
|
|
tail calls in the call chain. BTINFO is the branch trace information for
|
|
the current thread. */
|
|
static struct btrace_function *
|
|
ftrace_get_caller (struct btrace_thread_info *btinfo,
|
|
struct btrace_function *bfun)
|
|
{
|
|
for (; bfun != NULL; bfun = ftrace_find_call_by_number (btinfo, bfun->up))
|
|
if ((bfun->flags & BFUN_UP_LINKS_TO_TAILCALL) == 0)
|
|
return ftrace_find_call_by_number (btinfo, bfun->up);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* Find the innermost caller in the back trace of BFUN with MFUN/FUN
|
|
symbol information. BTINFO is the branch trace information for the current
|
|
thread. */
|
|
|
|
static struct btrace_function *
|
|
ftrace_find_caller (struct btrace_thread_info *btinfo,
|
|
struct btrace_function *bfun,
|
|
struct minimal_symbol *mfun,
|
|
struct symbol *fun)
|
|
{
|
|
for (; bfun != NULL; bfun = ftrace_find_call_by_number (btinfo, bfun->up))
|
|
{
|
|
/* Skip functions with incompatible symbol information. */
|
|
if (ftrace_function_switched (bfun, mfun, fun))
|
|
continue;
|
|
|
|
/* This is the function segment we're looking for. */
|
|
break;
|
|
}
|
|
|
|
return bfun;
|
|
}
|
|
|
|
/* Find the innermost caller in the back trace of BFUN, skipping all
|
|
function segments that do not end with a call instruction (e.g.
|
|
tail calls ending with a jump). BTINFO is the branch trace information for
|
|
the current thread. */
|
|
|
|
static struct btrace_function *
|
|
ftrace_find_call (struct btrace_thread_info *btinfo,
|
|
struct btrace_function *bfun)
|
|
{
|
|
for (; bfun != NULL; bfun = ftrace_find_call_by_number (btinfo, bfun->up))
|
|
{
|
|
/* Skip gaps. */
|
|
if (bfun->errcode != 0)
|
|
continue;
|
|
|
|
btrace_insn &last = bfun->insn.back ();
|
|
|
|
if (last.iclass == BTRACE_INSN_CALL)
|
|
break;
|
|
}
|
|
|
|
return bfun;
|
|
}
|
|
|
|
/* Add a continuation segment for a function into which we return at the end of
|
|
the trace.
|
|
BTINFO is the branch trace information for the current thread.
|
|
MFUN and FUN are the symbol information we have for this function. */
|
|
|
|
static struct btrace_function *
|
|
ftrace_new_return (struct btrace_thread_info *btinfo,
|
|
struct minimal_symbol *mfun,
|
|
struct symbol *fun)
|
|
{
|
|
struct btrace_function *prev, *bfun, *caller;
|
|
|
|
bfun = ftrace_new_function (btinfo, mfun, fun);
|
|
prev = ftrace_find_call_by_number (btinfo, bfun->number - 1);
|
|
|
|
/* It is important to start at PREV's caller. Otherwise, we might find
|
|
PREV itself, if PREV is a recursive function. */
|
|
caller = ftrace_find_call_by_number (btinfo, prev->up);
|
|
caller = ftrace_find_caller (btinfo, caller, mfun, fun);
|
|
if (caller != NULL)
|
|
{
|
|
/* The caller of PREV is the preceding btrace function segment in this
|
|
function instance. */
|
|
gdb_assert (caller->next == 0);
|
|
|
|
caller->next = bfun->number;
|
|
bfun->prev = caller->number;
|
|
|
|
/* Maintain the function level. */
|
|
bfun->level = caller->level;
|
|
|
|
/* Maintain the call stack. */
|
|
bfun->up = caller->up;
|
|
bfun->flags = caller->flags;
|
|
|
|
ftrace_debug (bfun, "new return");
|
|
}
|
|
else
|
|
{
|
|
/* We did not find a caller. This could mean that something went
|
|
wrong or that the call is simply not included in the trace. */
|
|
|
|
/* Let's search for some actual call. */
|
|
caller = ftrace_find_call_by_number (btinfo, prev->up);
|
|
caller = ftrace_find_call (btinfo, caller);
|
|
if (caller == NULL)
|
|
{
|
|
/* There is no call in PREV's back trace. We assume that the
|
|
branch trace did not include it. */
|
|
|
|
/* Let's find the topmost function and add a new caller for it.
|
|
This should handle a series of initial tail calls. */
|
|
while (prev->up != 0)
|
|
prev = ftrace_find_call_by_number (btinfo, prev->up);
|
|
|
|
bfun->level = prev->level - 1;
|
|
|
|
/* Fix up the call stack for PREV. */
|
|
ftrace_fixup_caller (btinfo, prev, bfun, BFUN_UP_LINKS_TO_RET);
|
|
|
|
ftrace_debug (bfun, "new return - no caller");
|
|
}
|
|
else
|
|
{
|
|
/* There is a call in PREV's back trace to which we should have
|
|
returned but didn't. Let's start a new, separate back trace
|
|
from PREV's level. */
|
|
bfun->level = prev->level - 1;
|
|
|
|
/* We fix up the back trace for PREV but leave other function segments
|
|
on the same level as they are.
|
|
This should handle things like schedule () correctly where we're
|
|
switching contexts. */
|
|
prev->up = bfun->number;
|
|
prev->flags = BFUN_UP_LINKS_TO_RET;
|
|
|
|
ftrace_debug (bfun, "new return - unknown caller");
|
|
}
|
|
}
|
|
|
|
return bfun;
|
|
}
|
|
|
|
/* Add a new function segment for a function switch at the end of the trace.
|
|
BTINFO is the branch trace information for the current thread.
|
|
MFUN and FUN are the symbol information we have for this function. */
|
|
|
|
static struct btrace_function *
|
|
ftrace_new_switch (struct btrace_thread_info *btinfo,
|
|
struct minimal_symbol *mfun,
|
|
struct symbol *fun)
|
|
{
|
|
struct btrace_function *prev, *bfun;
|
|
|
|
/* This is an unexplained function switch. We can't really be sure about the
|
|
call stack, yet the best I can think of right now is to preserve it. */
|
|
bfun = ftrace_new_function (btinfo, mfun, fun);
|
|
prev = ftrace_find_call_by_number (btinfo, bfun->number - 1);
|
|
bfun->up = prev->up;
|
|
bfun->flags = prev->flags;
|
|
|
|
ftrace_debug (bfun, "new switch");
|
|
|
|
return bfun;
|
|
}
|
|
|
|
/* Add a new function segment for a gap in the trace due to a decode error at
|
|
the end of the trace.
|
|
BTINFO is the branch trace information for the current thread.
|
|
ERRCODE is the format-specific error code. */
|
|
|
|
static struct btrace_function *
|
|
ftrace_new_gap (struct btrace_thread_info *btinfo, int errcode,
|
|
std::vector<unsigned int> &gaps)
|
|
{
|
|
struct btrace_function *bfun;
|
|
|
|
if (btinfo->functions.empty ())
|
|
bfun = ftrace_new_function (btinfo, NULL, NULL);
|
|
else
|
|
{
|
|
/* We hijack the previous function segment if it was empty. */
|
|
bfun = &btinfo->functions.back ();
|
|
if (bfun->errcode != 0 || !bfun->insn.empty ())
|
|
bfun = ftrace_new_function (btinfo, NULL, NULL);
|
|
}
|
|
|
|
bfun->errcode = errcode;
|
|
gaps.push_back (bfun->number);
|
|
|
|
ftrace_debug (bfun, "new gap");
|
|
|
|
return bfun;
|
|
}
|
|
|
|
/* Update the current function segment at the end of the trace in BTINFO with
|
|
respect to the instruction at PC. This may create new function segments.
|
|
Return the chronologically latest function segment, never NULL. */
|
|
|
|
static struct btrace_function *
|
|
ftrace_update_function (struct btrace_thread_info *btinfo, CORE_ADDR pc)
|
|
{
|
|
struct minimal_symbol *mfun;
|
|
struct symbol *fun;
|
|
struct btrace_function *bfun;
|
|
|
|
/* Try to determine the function we're in. We use both types of symbols
|
|
to avoid surprises when we sometimes get a full symbol and sometimes
|
|
only a minimal symbol. */
|
|
fun = find_pc_function (pc);
|
|
bound_minimal_symbol bmfun = lookup_minimal_symbol_by_pc (pc);
|
|
mfun = bmfun.minsym;
|
|
|
|
if (fun == NULL && mfun == NULL)
|
|
DEBUG_FTRACE ("no symbol at %s", core_addr_to_string_nz (pc));
|
|
|
|
/* If we didn't have a function, we create one. */
|
|
if (btinfo->functions.empty ())
|
|
return ftrace_new_function (btinfo, mfun, fun);
|
|
|
|
/* If we had a gap before, we create a function. */
|
|
bfun = &btinfo->functions.back ();
|
|
if (bfun->errcode != 0)
|
|
return ftrace_new_function (btinfo, mfun, fun);
|
|
|
|
/* Check the last instruction, if we have one.
|
|
We do this check first, since it allows us to fill in the call stack
|
|
links in addition to the normal flow links. */
|
|
btrace_insn *last = NULL;
|
|
if (!bfun->insn.empty ())
|
|
last = &bfun->insn.back ();
|
|
|
|
if (last != NULL)
|
|
{
|
|
switch (last->iclass)
|
|
{
|
|
case BTRACE_INSN_RETURN:
|
|
{
|
|
const char *fname;
|
|
|
|
/* On some systems, _dl_runtime_resolve returns to the resolved
|
|
function instead of jumping to it. From our perspective,
|
|
however, this is a tailcall.
|
|
If we treated it as return, we wouldn't be able to find the
|
|
resolved function in our stack back trace. Hence, we would
|
|
lose the current stack back trace and start anew with an empty
|
|
back trace. When the resolved function returns, we would then
|
|
create a stack back trace with the same function names but
|
|
different frame id's. This will confuse stepping. */
|
|
fname = ftrace_print_function_name (bfun);
|
|
if (strcmp (fname, "_dl_runtime_resolve") == 0)
|
|
return ftrace_new_tailcall (btinfo, mfun, fun);
|
|
|
|
return ftrace_new_return (btinfo, mfun, fun);
|
|
}
|
|
|
|
case BTRACE_INSN_CALL:
|
|
/* Ignore calls to the next instruction. They are used for PIC. */
|
|
if (last->pc + last->size == pc)
|
|
break;
|
|
|
|
return ftrace_new_call (btinfo, mfun, fun);
|
|
|
|
case BTRACE_INSN_JUMP:
|
|
{
|
|
CORE_ADDR start;
|
|
|
|
start = get_pc_function_start (pc);
|
|
|
|
/* A jump to the start of a function is (typically) a tail call. */
|
|
if (start == pc)
|
|
return ftrace_new_tailcall (btinfo, mfun, fun);
|
|
|
|
/* Some versions of _Unwind_RaiseException use an indirect
|
|
jump to 'return' to the exception handler of the caller
|
|
handling the exception instead of a return. Let's restrict
|
|
this heuristic to that and related functions. */
|
|
const char *fname = ftrace_print_function_name (bfun);
|
|
if (strncmp (fname, "_Unwind_", strlen ("_Unwind_")) == 0)
|
|
{
|
|
struct btrace_function *caller
|
|
= ftrace_find_call_by_number (btinfo, bfun->up);
|
|
caller = ftrace_find_caller (btinfo, caller, mfun, fun);
|
|
if (caller != NULL)
|
|
return ftrace_new_return (btinfo, mfun, fun);
|
|
}
|
|
|
|
/* If we can't determine the function for PC, we treat a jump at
|
|
the end of the block as tail call if we're switching functions
|
|
and as an intra-function branch if we don't. */
|
|
if (start == 0 && ftrace_function_switched (bfun, mfun, fun))
|
|
return ftrace_new_tailcall (btinfo, mfun, fun);
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Check if we're switching functions for some other reason. */
|
|
if (ftrace_function_switched (bfun, mfun, fun))
|
|
{
|
|
DEBUG_FTRACE ("switching from %s in %s at %s",
|
|
ftrace_print_insn_addr (last),
|
|
ftrace_print_function_name (bfun),
|
|
ftrace_print_filename (bfun));
|
|
|
|
return ftrace_new_switch (btinfo, mfun, fun);
|
|
}
|
|
|
|
return bfun;
|
|
}
|
|
|
|
/* Add the instruction at PC to BFUN's instructions. */
|
|
|
|
static void
|
|
ftrace_update_insns (struct btrace_function *bfun, const btrace_insn &insn)
|
|
{
|
|
bfun->insn.push_back (insn);
|
|
|
|
if (insn.iclass == BTRACE_INSN_AUX)
|
|
bfun->flags |= BFUN_CONTAINS_AUX;
|
|
|
|
if (record_debug > 1)
|
|
ftrace_debug (bfun, "update insn");
|
|
}
|
|
|
|
/* Classify the instruction at PC. */
|
|
|
|
static enum btrace_insn_class
|
|
ftrace_classify_insn (struct gdbarch *gdbarch, CORE_ADDR pc)
|
|
{
|
|
enum btrace_insn_class iclass;
|
|
|
|
iclass = BTRACE_INSN_OTHER;
|
|
try
|
|
{
|
|
if (gdbarch_insn_is_call (gdbarch, pc))
|
|
iclass = BTRACE_INSN_CALL;
|
|
else if (gdbarch_insn_is_ret (gdbarch, pc))
|
|
iclass = BTRACE_INSN_RETURN;
|
|
else if (gdbarch_insn_is_jump (gdbarch, pc))
|
|
iclass = BTRACE_INSN_JUMP;
|
|
}
|
|
catch (const gdb_exception_error &error)
|
|
{
|
|
}
|
|
|
|
return iclass;
|
|
}
|
|
|
|
/* Try to match the back trace at LHS to the back trace at RHS. Returns the
|
|
number of matching function segments or zero if the back traces do not
|
|
match. BTINFO is the branch trace information for the current thread. */
|
|
|
|
static int
|
|
ftrace_match_backtrace (struct btrace_thread_info *btinfo,
|
|
struct btrace_function *lhs,
|
|
struct btrace_function *rhs)
|
|
{
|
|
int matches;
|
|
|
|
for (matches = 0; lhs != NULL && rhs != NULL; ++matches)
|
|
{
|
|
if (ftrace_function_switched (lhs, rhs->msym, rhs->sym))
|
|
return 0;
|
|
|
|
lhs = ftrace_get_caller (btinfo, lhs);
|
|
rhs = ftrace_get_caller (btinfo, rhs);
|
|
}
|
|
|
|
return matches;
|
|
}
|
|
|
|
/* Add ADJUSTMENT to the level of BFUN and succeeding function segments.
|
|
BTINFO is the branch trace information for the current thread. */
|
|
|
|
static void
|
|
ftrace_fixup_level (struct btrace_thread_info *btinfo,
|
|
struct btrace_function *bfun, int adjustment)
|
|
{
|
|
if (adjustment == 0)
|
|
return;
|
|
|
|
DEBUG_FTRACE ("fixup level (%+d)", adjustment);
|
|
ftrace_debug (bfun, "..bfun");
|
|
|
|
while (bfun != NULL)
|
|
{
|
|
bfun->level += adjustment;
|
|
bfun = ftrace_find_call_by_number (btinfo, bfun->number + 1);
|
|
}
|
|
}
|
|
|
|
/* Recompute the global level offset. Traverse the function trace and compute
|
|
the global level offset as the negative of the minimal function level. */
|
|
|
|
static void
|
|
ftrace_compute_global_level_offset (struct btrace_thread_info *btinfo)
|
|
{
|
|
int level = INT_MAX;
|
|
|
|
if (btinfo == NULL)
|
|
return;
|
|
|
|
if (btinfo->functions.empty ())
|
|
return;
|
|
|
|
unsigned int length = btinfo->functions.size() - 1;
|
|
for (unsigned int i = 0; i < length; ++i)
|
|
level = std::min (level, btinfo->functions[i].level);
|
|
|
|
/* The last function segment contains the current instruction, which is not
|
|
really part of the trace. If it contains just this one instruction, we
|
|
ignore the segment. */
|
|
struct btrace_function *last = &btinfo->functions.back();
|
|
if (last->insn.size () != 1)
|
|
level = std::min (level, last->level);
|
|
|
|
DEBUG_FTRACE ("setting global level offset: %d", -level);
|
|
btinfo->level = -level;
|
|
}
|
|
|
|
/* Connect the function segments PREV and NEXT in a bottom-to-top walk as in
|
|
ftrace_connect_backtrace. BTINFO is the branch trace information for the
|
|
current thread. */
|
|
|
|
static void
|
|
ftrace_connect_bfun (struct btrace_thread_info *btinfo,
|
|
struct btrace_function *prev,
|
|
struct btrace_function *next)
|
|
{
|
|
DEBUG_FTRACE ("connecting...");
|
|
ftrace_debug (prev, "..prev");
|
|
ftrace_debug (next, "..next");
|
|
|
|
/* The function segments are not yet connected. */
|
|
gdb_assert (prev->next == 0);
|
|
gdb_assert (next->prev == 0);
|
|
|
|
prev->next = next->number;
|
|
next->prev = prev->number;
|
|
|
|
/* We may have moved NEXT to a different function level. */
|
|
ftrace_fixup_level (btinfo, next, prev->level - next->level);
|
|
|
|
/* If we run out of back trace for one, let's use the other's. */
|
|
if (prev->up == 0)
|
|
{
|
|
const btrace_function_flags flags = next->flags;
|
|
|
|
next = ftrace_find_call_by_number (btinfo, next->up);
|
|
if (next != NULL)
|
|
{
|
|
DEBUG_FTRACE ("using next's callers");
|
|
ftrace_fixup_caller (btinfo, prev, next, flags);
|
|
}
|
|
}
|
|
else if (next->up == 0)
|
|
{
|
|
const btrace_function_flags flags = prev->flags;
|
|
|
|
prev = ftrace_find_call_by_number (btinfo, prev->up);
|
|
if (prev != NULL)
|
|
{
|
|
DEBUG_FTRACE ("using prev's callers");
|
|
ftrace_fixup_caller (btinfo, next, prev, flags);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* PREV may have a tailcall caller, NEXT can't. If it does, fixup the up
|
|
link to add the tail callers to NEXT's back trace.
|
|
|
|
This removes NEXT->UP from NEXT's back trace. It will be added back
|
|
when connecting NEXT and PREV's callers - provided they exist.
|
|
|
|
If PREV's back trace consists of a series of tail calls without an
|
|
actual call, there will be no further connection and NEXT's caller will
|
|
be removed for good. To catch this case, we handle it here and connect
|
|
the top of PREV's back trace to NEXT's caller. */
|
|
if ((prev->flags & BFUN_UP_LINKS_TO_TAILCALL) != 0)
|
|
{
|
|
struct btrace_function *caller;
|
|
btrace_function_flags next_flags, prev_flags;
|
|
|
|
/* We checked NEXT->UP above so CALLER can't be NULL. */
|
|
caller = ftrace_find_call_by_number (btinfo, next->up);
|
|
next_flags = next->flags;
|
|
prev_flags = prev->flags;
|
|
|
|
DEBUG_FTRACE ("adding prev's tail calls to next");
|
|
|
|
prev = ftrace_find_call_by_number (btinfo, prev->up);
|
|
ftrace_fixup_caller (btinfo, next, prev, prev_flags);
|
|
|
|
for (; prev != NULL; prev = ftrace_find_call_by_number (btinfo,
|
|
prev->up))
|
|
{
|
|
/* At the end of PREV's back trace, continue with CALLER. */
|
|
if (prev->up == 0)
|
|
{
|
|
DEBUG_FTRACE ("fixing up link for tailcall chain");
|
|
ftrace_debug (prev, "..top");
|
|
ftrace_debug (caller, "..up");
|
|
|
|
ftrace_fixup_caller (btinfo, prev, caller, next_flags);
|
|
|
|
/* If we skipped any tail calls, this may move CALLER to a
|
|
different function level.
|
|
|
|
Note that changing CALLER's level is only OK because we
|
|
know that this is the last iteration of the bottom-to-top
|
|
walk in ftrace_connect_backtrace.
|
|
|
|
Otherwise we will fix up CALLER's level when we connect it
|
|
to PREV's caller in the next iteration. */
|
|
ftrace_fixup_level (btinfo, caller,
|
|
prev->level - caller->level - 1);
|
|
break;
|
|
}
|
|
|
|
/* There's nothing to do if we find a real call. */
|
|
if ((prev->flags & BFUN_UP_LINKS_TO_TAILCALL) == 0)
|
|
{
|
|
DEBUG_FTRACE ("will fix up link in next iteration");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Connect function segments on the same level in the back trace at LHS and RHS.
|
|
The back traces at LHS and RHS are expected to match according to
|
|
ftrace_match_backtrace. BTINFO is the branch trace information for the
|
|
current thread. */
|
|
|
|
static void
|
|
ftrace_connect_backtrace (struct btrace_thread_info *btinfo,
|
|
struct btrace_function *lhs,
|
|
struct btrace_function *rhs)
|
|
{
|
|
while (lhs != NULL && rhs != NULL)
|
|
{
|
|
struct btrace_function *prev, *next;
|
|
|
|
gdb_assert (!ftrace_function_switched (lhs, rhs->msym, rhs->sym));
|
|
|
|
/* Connecting LHS and RHS may change the up link. */
|
|
prev = lhs;
|
|
next = rhs;
|
|
|
|
lhs = ftrace_get_caller (btinfo, lhs);
|
|
rhs = ftrace_get_caller (btinfo, rhs);
|
|
|
|
ftrace_connect_bfun (btinfo, prev, next);
|
|
}
|
|
}
|
|
|
|
/* Bridge the gap between two function segments left and right of a gap if their
|
|
respective back traces match in at least MIN_MATCHES functions. BTINFO is
|
|
the branch trace information for the current thread.
|
|
|
|
Returns non-zero if the gap could be bridged, zero otherwise. */
|
|
|
|
static int
|
|
ftrace_bridge_gap (struct btrace_thread_info *btinfo,
|
|
struct btrace_function *lhs, struct btrace_function *rhs,
|
|
int min_matches)
|
|
{
|
|
struct btrace_function *best_l, *best_r, *cand_l, *cand_r;
|
|
int best_matches;
|
|
|
|
DEBUG_FTRACE ("checking gap at insn %u (req matches: %d)",
|
|
rhs->insn_offset - 1, min_matches);
|
|
|
|
best_matches = 0;
|
|
best_l = NULL;
|
|
best_r = NULL;
|
|
|
|
/* We search the back traces of LHS and RHS for valid connections and connect
|
|
the two function segments that give the longest combined back trace. */
|
|
|
|
for (cand_l = lhs; cand_l != NULL;
|
|
cand_l = ftrace_get_caller (btinfo, cand_l))
|
|
for (cand_r = rhs; cand_r != NULL;
|
|
cand_r = ftrace_get_caller (btinfo, cand_r))
|
|
{
|
|
int matches;
|
|
|
|
matches = ftrace_match_backtrace (btinfo, cand_l, cand_r);
|
|
if (best_matches < matches)
|
|
{
|
|
best_matches = matches;
|
|
best_l = cand_l;
|
|
best_r = cand_r;
|
|
}
|
|
}
|
|
|
|
/* We need at least MIN_MATCHES matches. */
|
|
gdb_assert (min_matches > 0);
|
|
if (best_matches < min_matches)
|
|
return 0;
|
|
|
|
DEBUG_FTRACE ("..matches: %d", best_matches);
|
|
|
|
/* We will fix up the level of BEST_R and succeeding function segments such
|
|
that BEST_R's level matches BEST_L's when we connect BEST_L to BEST_R.
|
|
|
|
This will ignore the level of RHS and following if BEST_R != RHS. I.e. if
|
|
BEST_R is a successor of RHS in the back trace of RHS (phases 1 and 3).
|
|
|
|
To catch this, we already fix up the level here where we can start at RHS
|
|
instead of at BEST_R. We will ignore the level fixup when connecting
|
|
BEST_L to BEST_R as they will already be on the same level. */
|
|
ftrace_fixup_level (btinfo, rhs, best_l->level - best_r->level);
|
|
|
|
ftrace_connect_backtrace (btinfo, best_l, best_r);
|
|
|
|
return best_matches;
|
|
}
|
|
|
|
/* Try to bridge gaps due to overflow or decode errors by connecting the
|
|
function segments that are separated by the gap. */
|
|
|
|
static void
|
|
btrace_bridge_gaps (struct thread_info *tp, std::vector<unsigned int> &gaps)
|
|
{
|
|
struct btrace_thread_info *btinfo = &tp->btrace;
|
|
std::vector<unsigned int> remaining;
|
|
int min_matches;
|
|
|
|
DEBUG ("bridge gaps");
|
|
|
|
/* We require a minimum amount of matches for bridging a gap. The number of
|
|
required matches will be lowered with each iteration.
|
|
|
|
The more matches the higher our confidence that the bridging is correct.
|
|
For big gaps or small traces, however, it may not be feasible to require a
|
|
high number of matches. */
|
|
for (min_matches = 5; min_matches > 0; --min_matches)
|
|
{
|
|
/* Let's try to bridge as many gaps as we can. In some cases, we need to
|
|
skip a gap and revisit it again after we closed later gaps. */
|
|
while (!gaps.empty ())
|
|
{
|
|
for (const unsigned int number : gaps)
|
|
{
|
|
struct btrace_function *gap, *lhs, *rhs;
|
|
int bridged;
|
|
|
|
gap = ftrace_find_call_by_number (btinfo, number);
|
|
|
|
/* We may have a sequence of gaps if we run from one error into
|
|
the next as we try to re-sync onto the trace stream. Ignore
|
|
all but the leftmost gap in such a sequence.
|
|
|
|
Also ignore gaps at the beginning of the trace. */
|
|
lhs = ftrace_find_call_by_number (btinfo, gap->number - 1);
|
|
if (lhs == NULL || lhs->errcode != 0)
|
|
continue;
|
|
|
|
/* Skip gaps to the right. */
|
|
rhs = ftrace_find_call_by_number (btinfo, gap->number + 1);
|
|
while (rhs != NULL && rhs->errcode != 0)
|
|
rhs = ftrace_find_call_by_number (btinfo, rhs->number + 1);
|
|
|
|
/* Ignore gaps at the end of the trace. */
|
|
if (rhs == NULL)
|
|
continue;
|
|
|
|
bridged = ftrace_bridge_gap (btinfo, lhs, rhs, min_matches);
|
|
|
|
/* Keep track of gaps we were not able to bridge and try again.
|
|
If we just pushed them to the end of GAPS we would risk an
|
|
infinite loop in case we simply cannot bridge a gap. */
|
|
if (bridged == 0)
|
|
remaining.push_back (number);
|
|
}
|
|
|
|
/* Let's see if we made any progress. */
|
|
if (remaining.size () == gaps.size ())
|
|
break;
|
|
|
|
gaps.clear ();
|
|
gaps.swap (remaining);
|
|
}
|
|
|
|
/* We get here if either GAPS is empty or if GAPS equals REMAINING. */
|
|
if (gaps.empty ())
|
|
break;
|
|
|
|
remaining.clear ();
|
|
}
|
|
|
|
/* We may omit this in some cases. Not sure it is worth the extra
|
|
complication, though. */
|
|
ftrace_compute_global_level_offset (btinfo);
|
|
}
|
|
|
|
/* Compute the function branch trace from BTS trace. */
|
|
|
|
static void
|
|
btrace_compute_ftrace_bts (struct thread_info *tp,
|
|
const struct btrace_data_bts *btrace,
|
|
std::vector<unsigned int> &gaps)
|
|
{
|
|
/* We may end up doing target calls that require the current thread to be TP,
|
|
for example reading memory through gdb_insn_length. Make sure TP is the
|
|
current thread. */
|
|
scoped_restore_current_thread restore_thread;
|
|
switch_to_thread (tp);
|
|
|
|
struct btrace_thread_info *btinfo;
|
|
unsigned int blk;
|
|
int level;
|
|
|
|
gdbarch *gdbarch = current_inferior ()->arch ();
|
|
btinfo = &tp->btrace;
|
|
blk = btrace->blocks->size ();
|
|
|
|
if (btinfo->functions.empty ())
|
|
level = INT_MAX;
|
|
else
|
|
level = -btinfo->level;
|
|
|
|
while (blk != 0)
|
|
{
|
|
CORE_ADDR pc;
|
|
|
|
blk -= 1;
|
|
|
|
const btrace_block &block = btrace->blocks->at (blk);
|
|
pc = block.begin;
|
|
|
|
for (;;)
|
|
{
|
|
struct btrace_function *bfun;
|
|
struct btrace_insn insn;
|
|
int size;
|
|
|
|
/* We should hit the end of the block. Warn if we went too far. */
|
|
if (block.end < pc)
|
|
{
|
|
/* Indicate the gap in the trace. */
|
|
bfun = ftrace_new_gap (btinfo, BDE_BTS_OVERFLOW, gaps);
|
|
|
|
warning (_("Recorded trace may be corrupted at instruction "
|
|
"%u (pc = %s)."), bfun->insn_offset - 1,
|
|
core_addr_to_string_nz (pc));
|
|
|
|
break;
|
|
}
|
|
|
|
bfun = ftrace_update_function (btinfo, pc);
|
|
|
|
/* Maintain the function level offset.
|
|
For all but the last block, we do it here. */
|
|
if (blk != 0)
|
|
level = std::min (level, bfun->level);
|
|
|
|
size = 0;
|
|
try
|
|
{
|
|
size = gdb_insn_length (gdbarch, pc);
|
|
}
|
|
catch (const gdb_exception_error &error)
|
|
{
|
|
}
|
|
|
|
insn.pc = pc;
|
|
insn.size = size;
|
|
insn.iclass = ftrace_classify_insn (gdbarch, pc);
|
|
insn.flags = 0;
|
|
|
|
ftrace_update_insns (bfun, insn);
|
|
|
|
/* We're done once we pushed the instruction at the end. */
|
|
if (block.end == pc)
|
|
break;
|
|
|
|
/* We can't continue if we fail to compute the size. */
|
|
if (size <= 0)
|
|
{
|
|
/* Indicate the gap in the trace. We just added INSN so we're
|
|
not at the beginning. */
|
|
bfun = ftrace_new_gap (btinfo, BDE_BTS_INSN_SIZE, gaps);
|
|
|
|
warning (_("Recorded trace may be incomplete at instruction %u "
|
|
"(pc = %s)."), bfun->insn_offset - 1,
|
|
core_addr_to_string_nz (pc));
|
|
|
|
break;
|
|
}
|
|
|
|
pc += size;
|
|
|
|
/* Maintain the function level offset.
|
|
For the last block, we do it here to not consider the last
|
|
instruction.
|
|
Since the last instruction corresponds to the current instruction
|
|
and is not really part of the execution history, it shouldn't
|
|
affect the level. */
|
|
if (blk == 0)
|
|
level = std::min (level, bfun->level);
|
|
}
|
|
}
|
|
|
|
/* LEVEL is the minimal function level of all btrace function segments.
|
|
Define the global level offset to -LEVEL so all function levels are
|
|
normalized to start at zero. */
|
|
btinfo->level = -level;
|
|
}
|
|
|
|
#if defined (HAVE_LIBIPT)
|
|
|
|
static enum btrace_insn_class
|
|
pt_reclassify_insn (enum pt_insn_class iclass)
|
|
{
|
|
switch (iclass)
|
|
{
|
|
case ptic_call:
|
|
return BTRACE_INSN_CALL;
|
|
|
|
case ptic_return:
|
|
return BTRACE_INSN_RETURN;
|
|
|
|
case ptic_jump:
|
|
return BTRACE_INSN_JUMP;
|
|
|
|
default:
|
|
return BTRACE_INSN_OTHER;
|
|
}
|
|
}
|
|
|
|
/* Return the btrace instruction flags for INSN. */
|
|
|
|
static btrace_insn_flags
|
|
pt_btrace_insn_flags (const struct pt_insn &insn)
|
|
{
|
|
btrace_insn_flags flags = 0;
|
|
|
|
if (insn.speculative)
|
|
flags |= BTRACE_INSN_FLAG_SPECULATIVE;
|
|
|
|
return flags;
|
|
}
|
|
|
|
/* Return the btrace instruction for INSN. */
|
|
|
|
static btrace_insn
|
|
pt_btrace_insn (const struct pt_insn &insn)
|
|
{
|
|
return {{static_cast<CORE_ADDR> (insn.ip)},
|
|
static_cast<gdb_byte> (insn.size),
|
|
pt_reclassify_insn (insn.iclass),
|
|
pt_btrace_insn_flags (insn)};
|
|
}
|
|
|
|
#if defined (HAVE_PT_INSN_EVENT)
|
|
/* Helper for events that will result in an aux_insn. */
|
|
|
|
static void
|
|
handle_pt_aux_insn (btrace_thread_info *btinfo, std::string &aux_str,
|
|
CORE_ADDR ip)
|
|
{
|
|
btinfo->aux_data.emplace_back (std::move (aux_str));
|
|
struct btrace_function *bfun = ftrace_update_function (btinfo, ip);
|
|
|
|
btrace_insn insn {{btinfo->aux_data.size () - 1}, 0,
|
|
BTRACE_INSN_AUX, 0};
|
|
|
|
ftrace_update_insns (bfun, insn);
|
|
}
|
|
|
|
#endif /* defined (HAVE_PT_INSN_EVENT) */
|
|
|
|
/* Handle instruction decode events (libipt-v2). */
|
|
|
|
static int
|
|
handle_pt_insn_events (struct btrace_thread_info *btinfo,
|
|
struct pt_insn_decoder *decoder,
|
|
std::vector<unsigned int> &gaps, int status)
|
|
{
|
|
#if defined (HAVE_PT_INSN_EVENT)
|
|
while (status & pts_event_pending)
|
|
{
|
|
struct pt_event event;
|
|
uint64_t offset;
|
|
|
|
status = pt_insn_event (decoder, &event, sizeof (event));
|
|
if (status < 0)
|
|
break;
|
|
|
|
switch (event.type)
|
|
{
|
|
default:
|
|
break;
|
|
|
|
case ptev_enabled:
|
|
{
|
|
if (event.status_update != 0)
|
|
break;
|
|
|
|
if (event.variant.enabled.resumed == 0
|
|
&& !btinfo->functions.empty ())
|
|
{
|
|
struct btrace_function *bfun
|
|
= ftrace_new_gap (btinfo, BDE_PT_DISABLED, gaps);
|
|
|
|
pt_insn_get_offset (decoder, &offset);
|
|
|
|
warning
|
|
(_("Non-contiguous trace at instruction %u (offset = 0x%"
|
|
PRIx64 ")."), bfun->insn_offset - 1, offset);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case ptev_overflow:
|
|
{
|
|
struct btrace_function *bfun
|
|
= ftrace_new_gap (btinfo, BDE_PT_OVERFLOW, gaps);
|
|
|
|
pt_insn_get_offset (decoder, &offset);
|
|
|
|
warning (_("Overflow at instruction %u (offset = 0x%" PRIx64 ")."),
|
|
bfun->insn_offset - 1, offset);
|
|
|
|
break;
|
|
}
|
|
#if defined (HAVE_STRUCT_PT_EVENT_VARIANT_PTWRITE)
|
|
case ptev_ptwrite:
|
|
{
|
|
uint64_t pc = 0;
|
|
std::optional<std::string> ptw_string;
|
|
|
|
/* Lookup the PC if available. The event often doesn't provide
|
|
one, so we look into the last function segment as well.
|
|
Looking further back makes limited sense for ptwrite. */
|
|
if (event.ip_suppressed == 0)
|
|
pc = event.variant.ptwrite.ip;
|
|
else if (!btinfo->functions.empty ())
|
|
{
|
|
std::vector<btrace_insn> &insns
|
|
= btinfo->functions.back ().insn;
|
|
for (auto insn = insns.rbegin (); insn != insns.rend ();
|
|
++insn)
|
|
{
|
|
switch (insn->iclass)
|
|
{
|
|
case BTRACE_INSN_AUX:
|
|
continue;
|
|
|
|
case BTRACE_INSN_OTHER:
|
|
case BTRACE_INSN_CALL:
|
|
case BTRACE_INSN_RETURN:
|
|
case BTRACE_INSN_JUMP:
|
|
pc = insn->pc;
|
|
break;
|
|
/* No default to rely on compiler warnings. */
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (pc == 0)
|
|
warning (_("Failed to determine the PC for ptwrite."));
|
|
|
|
if (btinfo->ptw_callback_fun != nullptr)
|
|
ptw_string
|
|
= btinfo->ptw_callback_fun (event.variant.ptwrite.payload,
|
|
pc, btinfo->ptw_context);
|
|
|
|
if (ptw_string.has_value () && (*ptw_string).empty ())
|
|
continue;
|
|
|
|
if (!ptw_string.has_value ())
|
|
*ptw_string = hex_string (event.variant.ptwrite.payload);
|
|
|
|
handle_pt_aux_insn (btinfo, *ptw_string, pc);
|
|
|
|
break;
|
|
}
|
|
#endif /* defined (HAVE_STRUCT_PT_EVENT_VARIANT_PTWRITE) */
|
|
}
|
|
}
|
|
#endif /* defined (HAVE_PT_INSN_EVENT) */
|
|
|
|
return status;
|
|
}
|
|
|
|
/* Handle events indicated by flags in INSN (libipt-v1). */
|
|
|
|
static void
|
|
handle_pt_insn_event_flags (struct btrace_thread_info *btinfo,
|
|
struct pt_insn_decoder *decoder,
|
|
const struct pt_insn &insn,
|
|
std::vector<unsigned int> &gaps)
|
|
{
|
|
#if defined (HAVE_STRUCT_PT_INSN_ENABLED)
|
|
/* Tracing is disabled and re-enabled each time we enter the kernel. Most
|
|
times, we continue from the same instruction we stopped before. This is
|
|
indicated via the RESUMED instruction flag. The ENABLED instruction flag
|
|
means that we continued from some other instruction. Indicate this as a
|
|
trace gap except when tracing just started. */
|
|
if (insn.enabled && !btinfo->functions.empty ())
|
|
{
|
|
struct btrace_function *bfun;
|
|
uint64_t offset;
|
|
|
|
bfun = ftrace_new_gap (btinfo, BDE_PT_DISABLED, gaps);
|
|
|
|
pt_insn_get_offset (decoder, &offset);
|
|
|
|
warning (_("Non-contiguous trace at instruction %u (offset = 0x%" PRIx64
|
|
", pc = 0x%" PRIx64 ")."), bfun->insn_offset - 1, offset,
|
|
insn.ip);
|
|
}
|
|
#endif /* defined (HAVE_STRUCT_PT_INSN_ENABLED) */
|
|
|
|
#if defined (HAVE_STRUCT_PT_INSN_RESYNCED)
|
|
/* Indicate trace overflows. */
|
|
if (insn.resynced)
|
|
{
|
|
struct btrace_function *bfun;
|
|
uint64_t offset;
|
|
|
|
bfun = ftrace_new_gap (btinfo, BDE_PT_OVERFLOW, gaps);
|
|
|
|
pt_insn_get_offset (decoder, &offset);
|
|
|
|
warning (_("Overflow at instruction %u (offset = 0x%" PRIx64 ", pc = 0x%"
|
|
PRIx64 ")."), bfun->insn_offset - 1, offset, insn.ip);
|
|
}
|
|
#endif /* defined (HAVE_STRUCT_PT_INSN_RESYNCED) */
|
|
}
|
|
|
|
/* Add function branch trace to BTINFO using DECODER. */
|
|
|
|
static void
|
|
ftrace_add_pt (struct btrace_thread_info *btinfo,
|
|
struct pt_insn_decoder *decoder,
|
|
int *plevel,
|
|
std::vector<unsigned int> &gaps)
|
|
{
|
|
struct btrace_function *bfun;
|
|
uint64_t offset;
|
|
int status;
|
|
|
|
/* Register the ptwrite filter. */
|
|
apply_ext_lang_ptwrite_filter (btinfo);
|
|
|
|
for (;;)
|
|
{
|
|
struct pt_insn insn;
|
|
|
|
status = pt_insn_sync_forward (decoder);
|
|
if (status < 0)
|
|
{
|
|
if (status != -pte_eos)
|
|
warning (_("Failed to synchronize onto the Intel Processor "
|
|
"Trace stream: %s."), pt_errstr (pt_errcode (status)));
|
|
break;
|
|
}
|
|
|
|
for (;;)
|
|
{
|
|
/* Handle events from the previous iteration or synchronization. */
|
|
status = handle_pt_insn_events (btinfo, decoder, gaps, status);
|
|
if (status < 0)
|
|
break;
|
|
|
|
status = pt_insn_next (decoder, &insn, sizeof(insn));
|
|
if (status < 0)
|
|
break;
|
|
|
|
/* Handle events indicated by flags in INSN. */
|
|
handle_pt_insn_event_flags (btinfo, decoder, insn, gaps);
|
|
|
|
bfun = ftrace_update_function (btinfo, insn.ip);
|
|
|
|
/* Maintain the function level offset. */
|
|
*plevel = std::min (*plevel, bfun->level);
|
|
|
|
ftrace_update_insns (bfun, pt_btrace_insn (insn));
|
|
}
|
|
|
|
if (status == -pte_eos)
|
|
break;
|
|
|
|
/* Indicate the gap in the trace. */
|
|
bfun = ftrace_new_gap (btinfo, status, gaps);
|
|
|
|
pt_insn_get_offset (decoder, &offset);
|
|
|
|
warning (_("Decode error (%d) at instruction %u (offset = 0x%" PRIx64
|
|
", pc = 0x%" PRIx64 "): %s."), status, bfun->insn_offset - 1,
|
|
offset, insn.ip, pt_errstr (pt_errcode (status)));
|
|
}
|
|
}
|
|
|
|
/* A callback function to allow the trace decoder to read the inferior's
|
|
memory. */
|
|
|
|
static int
|
|
btrace_pt_readmem_callback (gdb_byte *buffer, size_t size,
|
|
const struct pt_asid *asid, uint64_t pc,
|
|
void *context)
|
|
{
|
|
int result, errcode;
|
|
|
|
result = (int) size;
|
|
try
|
|
{
|
|
errcode = target_read_code ((CORE_ADDR) pc, buffer, size);
|
|
if (errcode != 0)
|
|
result = -pte_nomap;
|
|
}
|
|
catch (const gdb_exception_error &error)
|
|
{
|
|
result = -pte_nomap;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/* Translate the vendor from one enum to another. */
|
|
|
|
static enum pt_cpu_vendor
|
|
pt_translate_cpu_vendor (enum btrace_cpu_vendor vendor)
|
|
{
|
|
switch (vendor)
|
|
{
|
|
default:
|
|
return pcv_unknown;
|
|
|
|
case CV_INTEL:
|
|
return pcv_intel;
|
|
}
|
|
}
|
|
|
|
/* Finalize the function branch trace after decode. */
|
|
|
|
static void btrace_finalize_ftrace_pt (struct pt_insn_decoder *decoder,
|
|
struct thread_info *tp, int level)
|
|
{
|
|
pt_insn_free_decoder (decoder);
|
|
|
|
/* LEVEL is the minimal function level of all btrace function segments.
|
|
Define the global level offset to -LEVEL so all function levels are
|
|
normalized to start at zero. */
|
|
tp->btrace.level = -level;
|
|
|
|
/* Add a single last instruction entry for the current PC.
|
|
This allows us to compute the backtrace at the current PC using both
|
|
standard unwind and btrace unwind.
|
|
This extra entry is ignored by all record commands. */
|
|
btrace_add_pc (tp);
|
|
}
|
|
|
|
/* Compute the function branch trace from Intel Processor Trace
|
|
format. */
|
|
|
|
static void
|
|
btrace_compute_ftrace_pt (struct thread_info *tp,
|
|
const struct btrace_data_pt *btrace,
|
|
std::vector<unsigned int> &gaps)
|
|
{
|
|
/* We may end up doing target calls that require the current thread to be TP,
|
|
for example reading memory through btrace_pt_readmem_callback. Make sure
|
|
TP is the current thread. */
|
|
scoped_restore_current_thread restore_thread;
|
|
switch_to_thread (tp);
|
|
|
|
struct btrace_thread_info *btinfo;
|
|
struct pt_insn_decoder *decoder;
|
|
struct pt_config config;
|
|
int level, errcode;
|
|
|
|
if (btrace->size == 0)
|
|
return;
|
|
|
|
btinfo = &tp->btrace;
|
|
if (btinfo->functions.empty ())
|
|
level = INT_MAX;
|
|
else
|
|
level = -btinfo->level;
|
|
|
|
pt_config_init(&config);
|
|
config.begin = btrace->data;
|
|
config.end = btrace->data + btrace->size;
|
|
|
|
/* We treat an unknown vendor as 'no errata'. */
|
|
if (btrace->config.cpu.vendor != CV_UNKNOWN)
|
|
{
|
|
config.cpu.vendor
|
|
= pt_translate_cpu_vendor (btrace->config.cpu.vendor);
|
|
config.cpu.family = btrace->config.cpu.family;
|
|
config.cpu.model = btrace->config.cpu.model;
|
|
config.cpu.stepping = btrace->config.cpu.stepping;
|
|
|
|
errcode = pt_cpu_errata (&config.errata, &config.cpu);
|
|
if (errcode < 0)
|
|
error (_("Failed to configure the Intel Processor Trace "
|
|
"decoder: %s."), pt_errstr (pt_errcode (errcode)));
|
|
}
|
|
|
|
decoder = pt_insn_alloc_decoder (&config);
|
|
if (decoder == NULL)
|
|
error (_("Failed to allocate the Intel Processor Trace decoder."));
|
|
|
|
try
|
|
{
|
|
struct pt_image *image;
|
|
|
|
image = pt_insn_get_image(decoder);
|
|
if (image == NULL)
|
|
error (_("Failed to configure the Intel Processor Trace decoder."));
|
|
|
|
errcode = pt_image_set_callback(image, btrace_pt_readmem_callback, NULL);
|
|
if (errcode < 0)
|
|
error (_("Failed to configure the Intel Processor Trace decoder: "
|
|
"%s."), pt_errstr (pt_errcode (errcode)));
|
|
|
|
ftrace_add_pt (btinfo, decoder, &level, gaps);
|
|
}
|
|
catch (const gdb_exception &error)
|
|
{
|
|
/* Indicate a gap in the trace if we quit trace processing. */
|
|
if (error.reason == RETURN_QUIT && !btinfo->functions.empty ())
|
|
ftrace_new_gap (btinfo, BDE_PT_USER_QUIT, gaps);
|
|
|
|
btrace_finalize_ftrace_pt (decoder, tp, level);
|
|
|
|
throw;
|
|
}
|
|
|
|
btrace_finalize_ftrace_pt (decoder, tp, level);
|
|
}
|
|
|
|
#else /* defined (HAVE_LIBIPT) */
|
|
|
|
static void
|
|
btrace_compute_ftrace_pt (struct thread_info *tp,
|
|
const struct btrace_data_pt *btrace,
|
|
std::vector<unsigned int> &gaps)
|
|
{
|
|
internal_error (_("Unexpected branch trace format."));
|
|
}
|
|
|
|
#endif /* defined (HAVE_LIBIPT) */
|
|
|
|
/* Compute the function branch trace from a block branch trace BTRACE for
|
|
a thread given by BTINFO. If CPU is not NULL, overwrite the cpu in the
|
|
branch trace configuration. This is currently only used for the PT
|
|
format. */
|
|
|
|
static void
|
|
btrace_compute_ftrace_1 (struct thread_info *tp,
|
|
struct btrace_data *btrace,
|
|
const struct btrace_cpu *cpu,
|
|
std::vector<unsigned int> &gaps)
|
|
{
|
|
DEBUG ("compute ftrace");
|
|
|
|
switch (btrace->format)
|
|
{
|
|
case BTRACE_FORMAT_NONE:
|
|
return;
|
|
|
|
case BTRACE_FORMAT_BTS:
|
|
btrace_compute_ftrace_bts (tp, &btrace->variant.bts, gaps);
|
|
return;
|
|
|
|
case BTRACE_FORMAT_PT:
|
|
/* Overwrite the cpu we use for enabling errata workarounds. */
|
|
if (cpu != nullptr)
|
|
btrace->variant.pt.config.cpu = *cpu;
|
|
|
|
btrace_compute_ftrace_pt (tp, &btrace->variant.pt, gaps);
|
|
return;
|
|
}
|
|
|
|
internal_error (_("Unknown branch trace format."));
|
|
}
|
|
|
|
static void
|
|
btrace_finalize_ftrace (struct thread_info *tp, std::vector<unsigned int> &gaps)
|
|
{
|
|
if (!gaps.empty ())
|
|
{
|
|
tp->btrace.ngaps += gaps.size ();
|
|
btrace_bridge_gaps (tp, gaps);
|
|
}
|
|
}
|
|
|
|
static void
|
|
btrace_compute_ftrace (struct thread_info *tp, struct btrace_data *btrace,
|
|
const struct btrace_cpu *cpu)
|
|
{
|
|
std::vector<unsigned int> gaps;
|
|
|
|
try
|
|
{
|
|
btrace_compute_ftrace_1 (tp, btrace, cpu, gaps);
|
|
}
|
|
catch (const gdb_exception &error)
|
|
{
|
|
btrace_finalize_ftrace (tp, gaps);
|
|
|
|
throw;
|
|
}
|
|
|
|
btrace_finalize_ftrace (tp, gaps);
|
|
}
|
|
|
|
/* Add an entry for the current PC. */
|
|
|
|
static void
|
|
btrace_add_pc (struct thread_info *tp)
|
|
{
|
|
struct btrace_data btrace;
|
|
struct regcache *regcache;
|
|
CORE_ADDR pc;
|
|
|
|
regcache = get_thread_regcache (tp);
|
|
pc = regcache_read_pc (regcache);
|
|
|
|
btrace.format = BTRACE_FORMAT_BTS;
|
|
btrace.variant.bts.blocks = new std::vector<btrace_block>;
|
|
|
|
btrace.variant.bts.blocks->emplace_back (pc, pc);
|
|
|
|
btrace_compute_ftrace (tp, &btrace, NULL);
|
|
}
|
|
|
|
/* See btrace.h. */
|
|
|
|
void
|
|
btrace_enable (struct thread_info *tp, const struct btrace_config *conf)
|
|
{
|
|
if (tp->btrace.target != NULL)
|
|
error (_("Recording already enabled on thread %s (%s)."),
|
|
print_thread_id (tp), target_pid_to_str (tp->ptid).c_str ());
|
|
|
|
#if !defined (HAVE_LIBIPT)
|
|
if (conf->format == BTRACE_FORMAT_PT)
|
|
error (_("Intel Processor Trace support was disabled at compile time."));
|
|
#endif /* !defined (HAVE_LIBIPT) */
|
|
|
|
DEBUG ("enable thread %s (%s)", print_thread_id (tp),
|
|
tp->ptid.to_string ().c_str ());
|
|
|
|
tp->btrace.target = target_enable_btrace (tp, conf);
|
|
|
|
if (tp->btrace.target == NULL)
|
|
error (_("Failed to enable recording on thread %s (%s)."),
|
|
print_thread_id (tp), target_pid_to_str (tp->ptid).c_str ());
|
|
|
|
/* We need to undo the enable in case of errors. */
|
|
try
|
|
{
|
|
/* Add an entry for the current PC so we start tracing from where we
|
|
enabled it.
|
|
|
|
If we can't access TP's registers, TP is most likely running. In this
|
|
case, we can't really say where tracing was enabled so it should be
|
|
safe to simply skip this step.
|
|
|
|
This is not relevant for BTRACE_FORMAT_PT since the trace will already
|
|
start at the PC at which tracing was enabled. */
|
|
if (conf->format != BTRACE_FORMAT_PT
|
|
&& can_access_registers_thread (tp))
|
|
btrace_add_pc (tp);
|
|
}
|
|
catch (const gdb_exception &exception)
|
|
{
|
|
btrace_disable (tp);
|
|
|
|
throw;
|
|
}
|
|
}
|
|
|
|
/* See btrace.h. */
|
|
|
|
const struct btrace_config *
|
|
btrace_conf (const struct btrace_thread_info *btinfo)
|
|
{
|
|
if (btinfo->target == NULL)
|
|
return NULL;
|
|
|
|
return target_btrace_conf (btinfo->target);
|
|
}
|
|
|
|
/* See btrace.h. */
|
|
|
|
void
|
|
btrace_disable (struct thread_info *tp)
|
|
{
|
|
struct btrace_thread_info *btp = &tp->btrace;
|
|
|
|
if (btp->target == NULL)
|
|
error (_("Recording not enabled on thread %s (%s)."),
|
|
print_thread_id (tp), target_pid_to_str (tp->ptid).c_str ());
|
|
|
|
DEBUG ("disable thread %s (%s)", print_thread_id (tp),
|
|
tp->ptid.to_string ().c_str ());
|
|
|
|
target_disable_btrace (btp->target);
|
|
btp->target = NULL;
|
|
|
|
btrace_clear (tp);
|
|
}
|
|
|
|
/* See btrace.h. */
|
|
|
|
void
|
|
btrace_teardown (struct thread_info *tp)
|
|
{
|
|
struct btrace_thread_info *btp = &tp->btrace;
|
|
|
|
if (btp->target == NULL)
|
|
return;
|
|
|
|
DEBUG ("teardown thread %s (%s)", print_thread_id (tp),
|
|
tp->ptid.to_string ().c_str ());
|
|
|
|
target_teardown_btrace (btp->target);
|
|
btp->target = NULL;
|
|
|
|
btrace_clear (tp);
|
|
}
|
|
|
|
/* Stitch branch trace in BTS format. */
|
|
|
|
static int
|
|
btrace_stitch_bts (struct btrace_data_bts *btrace, struct thread_info *tp)
|
|
{
|
|
struct btrace_thread_info *btinfo;
|
|
struct btrace_function *last_bfun;
|
|
btrace_block *first_new_block;
|
|
|
|
btinfo = &tp->btrace;
|
|
gdb_assert (!btinfo->functions.empty ());
|
|
gdb_assert (!btrace->blocks->empty ());
|
|
|
|
last_bfun = &btinfo->functions.back ();
|
|
|
|
/* If the existing trace ends with a gap, we just glue the traces
|
|
together. We need to drop the last (i.e. chronologically first) block
|
|
of the new trace, though, since we can't fill in the start address.*/
|
|
if (last_bfun->insn.empty ())
|
|
{
|
|
btrace->blocks->pop_back ();
|
|
return 0;
|
|
}
|
|
|
|
/* Beware that block trace starts with the most recent block, so the
|
|
chronologically first block in the new trace is the last block in
|
|
the new trace's block vector. */
|
|
first_new_block = &btrace->blocks->back ();
|
|
const btrace_insn &last_insn = last_bfun->insn.back ();
|
|
|
|
/* If the current PC at the end of the block is the same as in our current
|
|
trace, there are two explanations:
|
|
1. we executed the instruction and some branch brought us back.
|
|
2. we have not made any progress.
|
|
In the first case, the delta trace vector should contain at least two
|
|
entries.
|
|
In the second case, the delta trace vector should contain exactly one
|
|
entry for the partial block containing the current PC. Remove it. */
|
|
if (first_new_block->end == last_insn.pc && btrace->blocks->size () == 1)
|
|
{
|
|
btrace->blocks->pop_back ();
|
|
return 0;
|
|
}
|
|
|
|
DEBUG ("stitching %s to %s", ftrace_print_insn_addr (&last_insn),
|
|
core_addr_to_string_nz (first_new_block->end));
|
|
|
|
/* Do a simple sanity check to make sure we don't accidentally end up
|
|
with a bad block. This should not occur in practice. */
|
|
if (first_new_block->end < last_insn.pc)
|
|
{
|
|
warning (_("Error while trying to read delta trace. Falling back to "
|
|
"a full read."));
|
|
return -1;
|
|
}
|
|
|
|
/* We adjust the last block to start at the end of our current trace. */
|
|
gdb_assert (first_new_block->begin == 0);
|
|
first_new_block->begin = last_insn.pc;
|
|
|
|
/* We simply pop the last insn so we can insert it again as part of
|
|
the normal branch trace computation.
|
|
Since instruction iterators are based on indices in the instructions
|
|
vector, we don't leave any pointers dangling. */
|
|
DEBUG ("pruning insn at %s for stitching",
|
|
ftrace_print_insn_addr (&last_insn));
|
|
|
|
last_bfun->insn.pop_back ();
|
|
|
|
/* The instructions vector may become empty temporarily if this has
|
|
been the only instruction in this function segment.
|
|
This violates the invariant but will be remedied shortly by
|
|
btrace_compute_ftrace when we add the new trace. */
|
|
|
|
/* The only case where this would hurt is if the entire trace consisted
|
|
of just that one instruction. If we remove it, we might turn the now
|
|
empty btrace function segment into a gap. But we don't want gaps at
|
|
the beginning. To avoid this, we remove the entire old trace. */
|
|
if (last_bfun->number == 1 && last_bfun->insn.empty ())
|
|
btrace_clear (tp);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Adjust the block trace in order to stitch old and new trace together.
|
|
BTRACE is the new delta trace between the last and the current stop.
|
|
TP is the traced thread.
|
|
May modifx BTRACE as well as the existing trace in TP.
|
|
Return 0 on success, -1 otherwise. */
|
|
|
|
static int
|
|
btrace_stitch_trace (struct btrace_data *btrace, struct thread_info *tp)
|
|
{
|
|
/* If we don't have trace, there's nothing to do. */
|
|
if (btrace->empty ())
|
|
return 0;
|
|
|
|
switch (btrace->format)
|
|
{
|
|
case BTRACE_FORMAT_NONE:
|
|
return 0;
|
|
|
|
case BTRACE_FORMAT_BTS:
|
|
return btrace_stitch_bts (&btrace->variant.bts, tp);
|
|
|
|
case BTRACE_FORMAT_PT:
|
|
/* Delta reads are not supported. */
|
|
return -1;
|
|
}
|
|
|
|
internal_error (_("Unknown branch trace format."));
|
|
}
|
|
|
|
/* Clear the branch trace histories in BTINFO. */
|
|
|
|
static void
|
|
btrace_clear_history (struct btrace_thread_info *btinfo)
|
|
{
|
|
xfree (btinfo->insn_history);
|
|
xfree (btinfo->call_history);
|
|
xfree (btinfo->replay);
|
|
|
|
btinfo->insn_history = NULL;
|
|
btinfo->call_history = NULL;
|
|
btinfo->replay = NULL;
|
|
|
|
btinfo->aux_data.clear ();
|
|
}
|
|
|
|
/* Clear the branch trace maintenance histories in BTINFO. */
|
|
|
|
static void
|
|
btrace_maint_clear (struct btrace_thread_info *btinfo)
|
|
{
|
|
switch (btinfo->data.format)
|
|
{
|
|
default:
|
|
break;
|
|
|
|
case BTRACE_FORMAT_BTS:
|
|
btinfo->maint.variant.bts.packet_history.begin = 0;
|
|
btinfo->maint.variant.bts.packet_history.end = 0;
|
|
break;
|
|
|
|
#if defined (HAVE_LIBIPT)
|
|
case BTRACE_FORMAT_PT:
|
|
delete btinfo->maint.variant.pt.packets;
|
|
|
|
btinfo->maint.variant.pt.packets = NULL;
|
|
btinfo->maint.variant.pt.packet_history.begin = 0;
|
|
btinfo->maint.variant.pt.packet_history.end = 0;
|
|
break;
|
|
#endif /* defined (HAVE_LIBIPT) */
|
|
}
|
|
}
|
|
|
|
/* See btrace.h. */
|
|
|
|
const char *
|
|
btrace_decode_error (enum btrace_format format, int errcode)
|
|
{
|
|
switch (format)
|
|
{
|
|
case BTRACE_FORMAT_BTS:
|
|
switch (errcode)
|
|
{
|
|
case BDE_BTS_OVERFLOW:
|
|
return _("instruction overflow");
|
|
|
|
case BDE_BTS_INSN_SIZE:
|
|
return _("unknown instruction");
|
|
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
|
|
#if defined (HAVE_LIBIPT)
|
|
case BTRACE_FORMAT_PT:
|
|
switch (errcode)
|
|
{
|
|
case BDE_PT_USER_QUIT:
|
|
return _("trace decode cancelled");
|
|
|
|
case BDE_PT_DISABLED:
|
|
return _("disabled");
|
|
|
|
case BDE_PT_OVERFLOW:
|
|
return _("overflow");
|
|
|
|
default:
|
|
if (errcode < 0)
|
|
return pt_errstr (pt_errcode (errcode));
|
|
break;
|
|
}
|
|
break;
|
|
#endif /* defined (HAVE_LIBIPT) */
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return _("unknown");
|
|
}
|
|
|
|
/* See btrace.h. */
|
|
|
|
void
|
|
btrace_fetch (struct thread_info *tp, const struct btrace_cpu *cpu)
|
|
{
|
|
struct btrace_thread_info *btinfo;
|
|
struct btrace_target_info *tinfo;
|
|
struct btrace_data btrace;
|
|
int errcode;
|
|
|
|
DEBUG ("fetch thread %s (%s)", print_thread_id (tp),
|
|
tp->ptid.to_string ().c_str ());
|
|
|
|
btinfo = &tp->btrace;
|
|
tinfo = btinfo->target;
|
|
if (tinfo == NULL)
|
|
return;
|
|
|
|
/* There's no way we could get new trace while replaying.
|
|
On the other hand, delta trace would return a partial record with the
|
|
current PC, which is the replay PC, not the last PC, as expected. */
|
|
if (btinfo->replay != NULL)
|
|
return;
|
|
|
|
/* With CLI usage, TP is always the current thread when we get here.
|
|
However, since we can also store a gdb.Record object in Python
|
|
referring to a different thread than the current one, we need to
|
|
temporarily set the current thread. */
|
|
scoped_restore_current_thread restore_thread;
|
|
switch_to_thread (tp);
|
|
|
|
/* We should not be called on running or exited threads. */
|
|
gdb_assert (can_access_registers_thread (tp));
|
|
|
|
/* Let's first try to extend the trace we already have. */
|
|
if (!btinfo->functions.empty ())
|
|
{
|
|
errcode = target_read_btrace (&btrace, tinfo, BTRACE_READ_DELTA);
|
|
if (errcode == 0)
|
|
{
|
|
/* Success. Let's try to stitch the traces together. */
|
|
errcode = btrace_stitch_trace (&btrace, tp);
|
|
}
|
|
else
|
|
{
|
|
/* We failed to read delta trace. Let's try to read new trace. */
|
|
errcode = target_read_btrace (&btrace, tinfo, BTRACE_READ_NEW);
|
|
|
|
/* If we got any new trace, discard what we have. */
|
|
if (errcode == 0 && !btrace.empty ())
|
|
btrace_clear (tp);
|
|
}
|
|
|
|
/* If we were not able to read the trace, we start over. */
|
|
if (errcode != 0)
|
|
{
|
|
btrace_clear (tp);
|
|
errcode = target_read_btrace (&btrace, tinfo, BTRACE_READ_ALL);
|
|
}
|
|
}
|
|
else
|
|
errcode = target_read_btrace (&btrace, tinfo, BTRACE_READ_ALL);
|
|
|
|
/* If we were not able to read the branch trace, signal an error. */
|
|
if (errcode != 0)
|
|
error (_("Failed to read branch trace."));
|
|
|
|
/* Compute the trace, provided we have any. */
|
|
if (!btrace.empty ())
|
|
{
|
|
/* Store the raw trace data. The stored data will be cleared in
|
|
btrace_clear, so we always append the new trace. */
|
|
btrace_data_append (&btinfo->data, &btrace);
|
|
btrace_maint_clear (btinfo);
|
|
|
|
btrace_clear_history (btinfo);
|
|
btrace_compute_ftrace (tp, &btrace, cpu);
|
|
}
|
|
}
|
|
|
|
/* See btrace.h. */
|
|
|
|
void
|
|
btrace_clear (struct thread_info *tp)
|
|
{
|
|
struct btrace_thread_info *btinfo;
|
|
|
|
DEBUG ("clear thread %s (%s)", print_thread_id (tp),
|
|
tp->ptid.to_string ().c_str ());
|
|
|
|
/* Make sure btrace frames that may hold a pointer into the branch
|
|
trace data are destroyed. */
|
|
reinit_frame_cache ();
|
|
|
|
btinfo = &tp->btrace;
|
|
|
|
btinfo->functions.clear ();
|
|
btinfo->ngaps = 0;
|
|
|
|
/* Must clear the maint data before - it depends on BTINFO->DATA. */
|
|
btrace_maint_clear (btinfo);
|
|
btinfo->data.clear ();
|
|
btrace_clear_history (btinfo);
|
|
}
|
|
|
|
/* See btrace.h. */
|
|
|
|
void
|
|
btrace_free_objfile (struct objfile *objfile)
|
|
{
|
|
DEBUG ("free objfile");
|
|
|
|
for (thread_info *tp : all_non_exited_threads ())
|
|
btrace_clear (tp);
|
|
}
|
|
|
|
/* See btrace.h. */
|
|
|
|
const struct btrace_insn *
|
|
btrace_insn_get (const struct btrace_insn_iterator *it)
|
|
{
|
|
const struct btrace_function *bfun;
|
|
unsigned int index, end;
|
|
|
|
index = it->insn_index;
|
|
bfun = &it->btinfo->functions[it->call_index];
|
|
|
|
/* Check if the iterator points to a gap in the trace. */
|
|
if (bfun->errcode != 0)
|
|
return NULL;
|
|
|
|
/* The index is within the bounds of this function's instruction vector. */
|
|
end = bfun->insn.size ();
|
|
gdb_assert (0 < end);
|
|
gdb_assert (index < end);
|
|
|
|
return &bfun->insn[index];
|
|
}
|
|
|
|
/* See btrace.h. */
|
|
|
|
int
|
|
btrace_insn_get_error (const struct btrace_insn_iterator *it)
|
|
{
|
|
return it->btinfo->functions[it->call_index].errcode;
|
|
}
|
|
|
|
/* See btrace.h. */
|
|
|
|
unsigned int
|
|
btrace_insn_number (const struct btrace_insn_iterator *it)
|
|
{
|
|
return it->btinfo->functions[it->call_index].insn_offset + it->insn_index;
|
|
}
|
|
|
|
/* See btrace.h. */
|
|
|
|
void
|
|
btrace_insn_begin (struct btrace_insn_iterator *it,
|
|
const struct btrace_thread_info *btinfo)
|
|
{
|
|
if (btinfo->functions.empty ())
|
|
error (_("No trace."));
|
|
|
|
it->btinfo = btinfo;
|
|
it->call_index = 0;
|
|
it->insn_index = 0;
|
|
}
|
|
|
|
/* See btrace.h. */
|
|
|
|
void
|
|
btrace_insn_end (struct btrace_insn_iterator *it,
|
|
const struct btrace_thread_info *btinfo)
|
|
{
|
|
const struct btrace_function *bfun;
|
|
unsigned int length;
|
|
|
|
if (btinfo->functions.empty ())
|
|
error (_("No trace."));
|
|
|
|
bfun = &btinfo->functions.back ();
|
|
length = bfun->insn.size ();
|
|
|
|
/* The last function may either be a gap or it contains the current
|
|
instruction, which is one past the end of the execution trace; ignore
|
|
it. */
|
|
if (length > 0)
|
|
length -= 1;
|
|
|
|
it->btinfo = btinfo;
|
|
it->call_index = bfun->number - 1;
|
|
it->insn_index = length;
|
|
}
|
|
|
|
/* See btrace.h. */
|
|
|
|
unsigned int
|
|
btrace_insn_next (struct btrace_insn_iterator *it, unsigned int stride)
|
|
{
|
|
const struct btrace_function *bfun;
|
|
unsigned int index, steps;
|
|
|
|
bfun = &it->btinfo->functions[it->call_index];
|
|
steps = 0;
|
|
index = it->insn_index;
|
|
|
|
while (stride != 0)
|
|
{
|
|
unsigned int end, space, adv;
|
|
|
|
end = bfun->insn.size ();
|
|
|
|
/* An empty function segment represents a gap in the trace. We count
|
|
it as one instruction. */
|
|
if (end == 0)
|
|
{
|
|
const struct btrace_function *next;
|
|
|
|
next = ftrace_find_call_by_number (it->btinfo, bfun->number + 1);
|
|
if (next == NULL)
|
|
break;
|
|
|
|
stride -= 1;
|
|
steps += 1;
|
|
|
|
bfun = next;
|
|
index = 0;
|
|
|
|
continue;
|
|
}
|
|
|
|
gdb_assert (0 < end);
|
|
gdb_assert (index < end);
|
|
|
|
/* Compute the number of instructions remaining in this segment. */
|
|
space = end - index;
|
|
|
|
/* Advance the iterator as far as possible within this segment. */
|
|
adv = std::min (space, stride);
|
|
stride -= adv;
|
|
index += adv;
|
|
steps += adv;
|
|
|
|
/* Move to the next function if we're at the end of this one. */
|
|
if (index == end)
|
|
{
|
|
const struct btrace_function *next;
|
|
|
|
next = ftrace_find_call_by_number (it->btinfo, bfun->number + 1);
|
|
if (next == NULL)
|
|
{
|
|
/* We stepped past the last function.
|
|
|
|
Let's adjust the index to point to the last instruction in
|
|
the previous function. */
|
|
index -= 1;
|
|
steps -= 1;
|
|
break;
|
|
}
|
|
|
|
/* We now point to the first instruction in the new function. */
|
|
bfun = next;
|
|
index = 0;
|
|
}
|
|
|
|
/* We did make progress. */
|
|
gdb_assert (adv > 0);
|
|
}
|
|
|
|
/* Update the iterator. */
|
|
it->call_index = bfun->number - 1;
|
|
it->insn_index = index;
|
|
|
|
return steps;
|
|
}
|
|
|
|
/* See btrace.h. */
|
|
|
|
unsigned int
|
|
btrace_insn_prev (struct btrace_insn_iterator *it, unsigned int stride)
|
|
{
|
|
const struct btrace_function *bfun;
|
|
unsigned int index, steps;
|
|
|
|
bfun = &it->btinfo->functions[it->call_index];
|
|
steps = 0;
|
|
index = it->insn_index;
|
|
|
|
while (stride != 0)
|
|
{
|
|
unsigned int adv;
|
|
|
|
/* Move to the previous function if we're at the start of this one. */
|
|
if (index == 0)
|
|
{
|
|
const struct btrace_function *prev;
|
|
|
|
prev = ftrace_find_call_by_number (it->btinfo, bfun->number - 1);
|
|
if (prev == NULL)
|
|
break;
|
|
|
|
/* We point to one after the last instruction in the new function. */
|
|
bfun = prev;
|
|
index = bfun->insn.size ();
|
|
|
|
/* An empty function segment represents a gap in the trace. We count
|
|
it as one instruction. */
|
|
if (index == 0)
|
|
{
|
|
stride -= 1;
|
|
steps += 1;
|
|
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/* Advance the iterator as far as possible within this segment. */
|
|
adv = std::min (index, stride);
|
|
|
|
stride -= adv;
|
|
index -= adv;
|
|
steps += adv;
|
|
|
|
/* We did make progress. */
|
|
gdb_assert (adv > 0);
|
|
}
|
|
|
|
/* Update the iterator. */
|
|
it->call_index = bfun->number - 1;
|
|
it->insn_index = index;
|
|
|
|
return steps;
|
|
}
|
|
|
|
/* See btrace.h. */
|
|
|
|
int
|
|
btrace_insn_cmp (const struct btrace_insn_iterator *lhs,
|
|
const struct btrace_insn_iterator *rhs)
|
|
{
|
|
gdb_assert (lhs->btinfo == rhs->btinfo);
|
|
|
|
if (lhs->call_index != rhs->call_index)
|
|
return lhs->call_index - rhs->call_index;
|
|
|
|
return lhs->insn_index - rhs->insn_index;
|
|
}
|
|
|
|
/* See btrace.h. */
|
|
|
|
int
|
|
btrace_find_insn_by_number (struct btrace_insn_iterator *it,
|
|
const struct btrace_thread_info *btinfo,
|
|
unsigned int number)
|
|
{
|
|
const struct btrace_function *bfun;
|
|
unsigned int upper, lower;
|
|
|
|
if (btinfo->functions.empty ())
|
|
return 0;
|
|
|
|
lower = 0;
|
|
bfun = &btinfo->functions[lower];
|
|
if (number < bfun->insn_offset)
|
|
return 0;
|
|
|
|
upper = btinfo->functions.size () - 1;
|
|
bfun = &btinfo->functions[upper];
|
|
if (number >= bfun->insn_offset + ftrace_call_num_insn (bfun))
|
|
return 0;
|
|
|
|
/* We assume that there are no holes in the numbering. */
|
|
for (;;)
|
|
{
|
|
const unsigned int average = lower + (upper - lower) / 2;
|
|
|
|
bfun = &btinfo->functions[average];
|
|
|
|
if (number < bfun->insn_offset)
|
|
{
|
|
upper = average - 1;
|
|
continue;
|
|
}
|
|
|
|
if (number >= bfun->insn_offset + ftrace_call_num_insn (bfun))
|
|
{
|
|
lower = average + 1;
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
it->btinfo = btinfo;
|
|
it->call_index = bfun->number - 1;
|
|
it->insn_index = number - bfun->insn_offset;
|
|
return 1;
|
|
}
|
|
|
|
/* Returns true if the recording ends with a function segment that
|
|
contains only a single (i.e. the current) instruction. */
|
|
|
|
static bool
|
|
btrace_ends_with_single_insn (const struct btrace_thread_info *btinfo)
|
|
{
|
|
const btrace_function *bfun;
|
|
|
|
if (btinfo->functions.empty ())
|
|
return false;
|
|
|
|
bfun = &btinfo->functions.back ();
|
|
if (bfun->errcode != 0)
|
|
return false;
|
|
|
|
return ftrace_call_num_insn (bfun) == 1;
|
|
}
|
|
|
|
/* See btrace.h. */
|
|
|
|
const struct btrace_function *
|
|
btrace_call_get (const struct btrace_call_iterator *it)
|
|
{
|
|
if (it->index >= it->btinfo->functions.size ())
|
|
return NULL;
|
|
|
|
return &it->btinfo->functions[it->index];
|
|
}
|
|
|
|
/* See btrace.h. */
|
|
|
|
unsigned int
|
|
btrace_call_number (const struct btrace_call_iterator *it)
|
|
{
|
|
const unsigned int length = it->btinfo->functions.size ();
|
|
|
|
/* If the last function segment contains only a single instruction (i.e. the
|
|
current instruction), skip it. */
|
|
if ((it->index == length) && btrace_ends_with_single_insn (it->btinfo))
|
|
return length;
|
|
|
|
return it->index + 1;
|
|
}
|
|
|
|
/* See btrace.h. */
|
|
|
|
void
|
|
btrace_call_begin (struct btrace_call_iterator *it,
|
|
const struct btrace_thread_info *btinfo)
|
|
{
|
|
if (btinfo->functions.empty ())
|
|
error (_("No trace."));
|
|
|
|
it->btinfo = btinfo;
|
|
it->index = 0;
|
|
}
|
|
|
|
/* See btrace.h. */
|
|
|
|
void
|
|
btrace_call_end (struct btrace_call_iterator *it,
|
|
const struct btrace_thread_info *btinfo)
|
|
{
|
|
if (btinfo->functions.empty ())
|
|
error (_("No trace."));
|
|
|
|
it->btinfo = btinfo;
|
|
it->index = btinfo->functions.size ();
|
|
}
|
|
|
|
/* See btrace.h. */
|
|
|
|
unsigned int
|
|
btrace_call_next (struct btrace_call_iterator *it, unsigned int stride)
|
|
{
|
|
const unsigned int length = it->btinfo->functions.size ();
|
|
|
|
if (it->index + stride < length - 1)
|
|
/* Default case: Simply advance the iterator. */
|
|
it->index += stride;
|
|
else if (it->index + stride == length - 1)
|
|
{
|
|
/* We land exactly at the last function segment. If it contains only one
|
|
instruction (i.e. the current instruction) it is not actually part of
|
|
the trace. */
|
|
if (btrace_ends_with_single_insn (it->btinfo))
|
|
it->index = length;
|
|
else
|
|
it->index = length - 1;
|
|
}
|
|
else
|
|
{
|
|
/* We land past the last function segment and have to adjust the stride.
|
|
If the last function segment contains only one instruction (i.e. the
|
|
current instruction) it is not actually part of the trace. */
|
|
if (btrace_ends_with_single_insn (it->btinfo))
|
|
stride = length - it->index - 1;
|
|
else
|
|
stride = length - it->index;
|
|
|
|
it->index = length;
|
|
}
|
|
|
|
return stride;
|
|
}
|
|
|
|
/* See btrace.h. */
|
|
|
|
unsigned int
|
|
btrace_call_prev (struct btrace_call_iterator *it, unsigned int stride)
|
|
{
|
|
const unsigned int length = it->btinfo->functions.size ();
|
|
int steps = 0;
|
|
|
|
gdb_assert (it->index <= length);
|
|
|
|
if (stride == 0 || it->index == 0)
|
|
return 0;
|
|
|
|
/* If we are at the end, the first step is a special case. If the last
|
|
function segment contains only one instruction (i.e. the current
|
|
instruction) it is not actually part of the trace. To be able to step
|
|
over this instruction, we need at least one more function segment. */
|
|
if ((it->index == length) && (length > 1))
|
|
{
|
|
if (btrace_ends_with_single_insn (it->btinfo))
|
|
it->index = length - 2;
|
|
else
|
|
it->index = length - 1;
|
|
|
|
steps = 1;
|
|
stride -= 1;
|
|
}
|
|
|
|
stride = std::min (stride, it->index);
|
|
|
|
it->index -= stride;
|
|
return steps + stride;
|
|
}
|
|
|
|
/* See btrace.h. */
|
|
|
|
int
|
|
btrace_call_cmp (const struct btrace_call_iterator *lhs,
|
|
const struct btrace_call_iterator *rhs)
|
|
{
|
|
gdb_assert (lhs->btinfo == rhs->btinfo);
|
|
return (int) (lhs->index - rhs->index);
|
|
}
|
|
|
|
/* See btrace.h. */
|
|
|
|
int
|
|
btrace_find_call_by_number (struct btrace_call_iterator *it,
|
|
const struct btrace_thread_info *btinfo,
|
|
unsigned int number)
|
|
{
|
|
const unsigned int length = btinfo->functions.size ();
|
|
|
|
if ((number == 0) || (number > length))
|
|
return 0;
|
|
|
|
it->btinfo = btinfo;
|
|
it->index = number - 1;
|
|
return 1;
|
|
}
|
|
|
|
/* See btrace.h. */
|
|
|
|
void
|
|
btrace_set_insn_history (struct btrace_thread_info *btinfo,
|
|
const struct btrace_insn_iterator *begin,
|
|
const struct btrace_insn_iterator *end)
|
|
{
|
|
if (btinfo->insn_history == NULL)
|
|
btinfo->insn_history = XCNEW (struct btrace_insn_history);
|
|
|
|
btinfo->insn_history->begin = *begin;
|
|
btinfo->insn_history->end = *end;
|
|
}
|
|
|
|
/* See btrace.h. */
|
|
|
|
void
|
|
btrace_set_call_history (struct btrace_thread_info *btinfo,
|
|
const struct btrace_call_iterator *begin,
|
|
const struct btrace_call_iterator *end)
|
|
{
|
|
gdb_assert (begin->btinfo == end->btinfo);
|
|
|
|
if (btinfo->call_history == NULL)
|
|
btinfo->call_history = XCNEW (struct btrace_call_history);
|
|
|
|
btinfo->call_history->begin = *begin;
|
|
btinfo->call_history->end = *end;
|
|
}
|
|
|
|
/* See btrace.h. */
|
|
|
|
int
|
|
btrace_is_replaying (struct thread_info *tp)
|
|
{
|
|
return tp->btrace.replay != NULL;
|
|
}
|
|
|
|
/* See btrace.h. */
|
|
|
|
int
|
|
btrace_is_empty (struct thread_info *tp)
|
|
{
|
|
struct btrace_insn_iterator begin, end;
|
|
struct btrace_thread_info *btinfo;
|
|
|
|
btinfo = &tp->btrace;
|
|
|
|
if (btinfo->functions.empty ())
|
|
return 1;
|
|
|
|
btrace_insn_begin (&begin, btinfo);
|
|
btrace_insn_end (&end, btinfo);
|
|
|
|
return btrace_insn_cmp (&begin, &end) == 0;
|
|
}
|
|
|
|
#if defined (HAVE_LIBIPT)
|
|
|
|
/* Print a single packet. */
|
|
|
|
static void
|
|
pt_print_packet (const struct pt_packet *packet)
|
|
{
|
|
switch (packet->type)
|
|
{
|
|
default:
|
|
gdb_printf (("[??: %x]"), packet->type);
|
|
break;
|
|
|
|
case ppt_psb:
|
|
gdb_printf (("psb"));
|
|
break;
|
|
|
|
case ppt_psbend:
|
|
gdb_printf (("psbend"));
|
|
break;
|
|
|
|
case ppt_pad:
|
|
gdb_printf (("pad"));
|
|
break;
|
|
|
|
case ppt_tip:
|
|
gdb_printf (("tip %u: 0x%" PRIx64 ""),
|
|
packet->payload.ip.ipc,
|
|
packet->payload.ip.ip);
|
|
break;
|
|
|
|
case ppt_tip_pge:
|
|
gdb_printf (("tip.pge %u: 0x%" PRIx64 ""),
|
|
packet->payload.ip.ipc,
|
|
packet->payload.ip.ip);
|
|
break;
|
|
|
|
case ppt_tip_pgd:
|
|
gdb_printf (("tip.pgd %u: 0x%" PRIx64 ""),
|
|
packet->payload.ip.ipc,
|
|
packet->payload.ip.ip);
|
|
break;
|
|
|
|
case ppt_fup:
|
|
gdb_printf (("fup %u: 0x%" PRIx64 ""),
|
|
packet->payload.ip.ipc,
|
|
packet->payload.ip.ip);
|
|
break;
|
|
|
|
case ppt_tnt_8:
|
|
gdb_printf (("tnt-8 %u: 0x%" PRIx64 ""),
|
|
packet->payload.tnt.bit_size,
|
|
packet->payload.tnt.payload);
|
|
break;
|
|
|
|
case ppt_tnt_64:
|
|
gdb_printf (("tnt-64 %u: 0x%" PRIx64 ""),
|
|
packet->payload.tnt.bit_size,
|
|
packet->payload.tnt.payload);
|
|
break;
|
|
|
|
case ppt_pip:
|
|
gdb_printf (("pip %" PRIx64 "%s"), packet->payload.pip.cr3,
|
|
packet->payload.pip.nr ? (" nr") : (""));
|
|
break;
|
|
|
|
case ppt_tsc:
|
|
gdb_printf (("tsc %" PRIx64 ""), packet->payload.tsc.tsc);
|
|
break;
|
|
|
|
case ppt_cbr:
|
|
gdb_printf (("cbr %u"), packet->payload.cbr.ratio);
|
|
break;
|
|
|
|
case ppt_mode:
|
|
switch (packet->payload.mode.leaf)
|
|
{
|
|
default:
|
|
gdb_printf (("mode %u"), packet->payload.mode.leaf);
|
|
break;
|
|
|
|
case pt_mol_exec:
|
|
gdb_printf (("mode.exec%s%s"),
|
|
packet->payload.mode.bits.exec.csl
|
|
? (" cs.l") : (""),
|
|
packet->payload.mode.bits.exec.csd
|
|
? (" cs.d") : (""));
|
|
break;
|
|
|
|
case pt_mol_tsx:
|
|
gdb_printf (("mode.tsx%s%s"),
|
|
packet->payload.mode.bits.tsx.intx
|
|
? (" intx") : (""),
|
|
packet->payload.mode.bits.tsx.abrt
|
|
? (" abrt") : (""));
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case ppt_ovf:
|
|
gdb_printf (("ovf"));
|
|
break;
|
|
|
|
case ppt_stop:
|
|
gdb_printf (("stop"));
|
|
break;
|
|
|
|
case ppt_vmcs:
|
|
gdb_printf (("vmcs %" PRIx64 ""), packet->payload.vmcs.base);
|
|
break;
|
|
|
|
case ppt_tma:
|
|
gdb_printf (("tma %x %x"), packet->payload.tma.ctc,
|
|
packet->payload.tma.fc);
|
|
break;
|
|
|
|
case ppt_mtc:
|
|
gdb_printf (("mtc %x"), packet->payload.mtc.ctc);
|
|
break;
|
|
|
|
case ppt_cyc:
|
|
gdb_printf (("cyc %" PRIx64 ""), packet->payload.cyc.value);
|
|
break;
|
|
|
|
case ppt_mnt:
|
|
gdb_printf (("mnt %" PRIx64 ""), packet->payload.mnt.payload);
|
|
break;
|
|
|
|
#if (LIBIPT_VERSION >= 0x200)
|
|
case ppt_ptw:
|
|
gdb_printf (("ptw %u: 0x%" PRIx64 "%s"), packet->payload.ptw.plc,
|
|
packet->payload.ptw.payload,
|
|
packet->payload.ptw.ip ? (" ip") : (""));
|
|
break;
|
|
#endif /* defined (LIBIPT_VERSION >= 0x200) */
|
|
}
|
|
}
|
|
|
|
/* Decode packets into MAINT using DECODER. */
|
|
|
|
static void
|
|
btrace_maint_decode_pt (struct btrace_maint_info *maint,
|
|
struct pt_packet_decoder *decoder)
|
|
{
|
|
int errcode;
|
|
|
|
if (maint->variant.pt.packets == NULL)
|
|
maint->variant.pt.packets = new std::vector<btrace_pt_packet>;
|
|
|
|
for (;;)
|
|
{
|
|
struct btrace_pt_packet packet;
|
|
|
|
errcode = pt_pkt_sync_forward (decoder);
|
|
if (errcode < 0)
|
|
break;
|
|
|
|
for (;;)
|
|
{
|
|
pt_pkt_get_offset (decoder, &packet.offset);
|
|
|
|
errcode = pt_pkt_next (decoder, &packet.packet,
|
|
sizeof(packet.packet));
|
|
if (errcode < 0)
|
|
break;
|
|
|
|
if (maint_btrace_pt_skip_pad == 0 || packet.packet.type != ppt_pad)
|
|
{
|
|
packet.errcode = pt_errcode (errcode);
|
|
maint->variant.pt.packets->push_back (packet);
|
|
}
|
|
}
|
|
|
|
if (errcode == -pte_eos)
|
|
break;
|
|
|
|
packet.errcode = pt_errcode (errcode);
|
|
maint->variant.pt.packets->push_back (packet);
|
|
|
|
warning (_("Error at trace offset 0x%" PRIx64 ": %s."),
|
|
packet.offset, pt_errstr (packet.errcode));
|
|
}
|
|
|
|
if (errcode != -pte_eos)
|
|
warning (_("Failed to synchronize onto the Intel Processor Trace "
|
|
"stream: %s."), pt_errstr (pt_errcode (errcode)));
|
|
}
|
|
|
|
/* Update the packet history in BTINFO. */
|
|
|
|
static void
|
|
btrace_maint_update_pt_packets (struct btrace_thread_info *btinfo)
|
|
{
|
|
struct pt_packet_decoder *decoder;
|
|
const struct btrace_cpu *cpu;
|
|
struct btrace_data_pt *pt;
|
|
struct pt_config config;
|
|
int errcode;
|
|
|
|
pt = &btinfo->data.variant.pt;
|
|
|
|
/* Nothing to do if there is no trace. */
|
|
if (pt->size == 0)
|
|
return;
|
|
|
|
memset (&config, 0, sizeof(config));
|
|
|
|
config.size = sizeof (config);
|
|
config.begin = pt->data;
|
|
config.end = pt->data + pt->size;
|
|
|
|
cpu = record_btrace_get_cpu ();
|
|
if (cpu == nullptr)
|
|
cpu = &pt->config.cpu;
|
|
|
|
/* We treat an unknown vendor as 'no errata'. */
|
|
if (cpu->vendor != CV_UNKNOWN)
|
|
{
|
|
config.cpu.vendor = pt_translate_cpu_vendor (cpu->vendor);
|
|
config.cpu.family = cpu->family;
|
|
config.cpu.model = cpu->model;
|
|
config.cpu.stepping = cpu->stepping;
|
|
|
|
errcode = pt_cpu_errata (&config.errata, &config.cpu);
|
|
if (errcode < 0)
|
|
error (_("Failed to configure the Intel Processor Trace "
|
|
"decoder: %s."), pt_errstr (pt_errcode (errcode)));
|
|
}
|
|
|
|
decoder = pt_pkt_alloc_decoder (&config);
|
|
if (decoder == NULL)
|
|
error (_("Failed to allocate the Intel Processor Trace decoder."));
|
|
|
|
try
|
|
{
|
|
btrace_maint_decode_pt (&btinfo->maint, decoder);
|
|
}
|
|
catch (const gdb_exception &except)
|
|
{
|
|
pt_pkt_free_decoder (decoder);
|
|
|
|
if (except.reason < 0)
|
|
throw;
|
|
}
|
|
|
|
pt_pkt_free_decoder (decoder);
|
|
}
|
|
|
|
#endif /* !defined (HAVE_LIBIPT) */
|
|
|
|
/* Update the packet maintenance information for BTINFO and store the
|
|
low and high bounds into BEGIN and END, respectively.
|
|
Store the current iterator state into FROM and TO. */
|
|
|
|
static void
|
|
btrace_maint_update_packets (struct btrace_thread_info *btinfo,
|
|
unsigned int *begin, unsigned int *end,
|
|
unsigned int *from, unsigned int *to)
|
|
{
|
|
switch (btinfo->data.format)
|
|
{
|
|
default:
|
|
*begin = 0;
|
|
*end = 0;
|
|
*from = 0;
|
|
*to = 0;
|
|
break;
|
|
|
|
case BTRACE_FORMAT_BTS:
|
|
/* Nothing to do - we operate directly on BTINFO->DATA. */
|
|
*begin = 0;
|
|
*end = btinfo->data.variant.bts.blocks->size ();
|
|
*from = btinfo->maint.variant.bts.packet_history.begin;
|
|
*to = btinfo->maint.variant.bts.packet_history.end;
|
|
break;
|
|
|
|
#if defined (HAVE_LIBIPT)
|
|
case BTRACE_FORMAT_PT:
|
|
if (btinfo->maint.variant.pt.packets == nullptr)
|
|
btinfo->maint.variant.pt.packets = new std::vector<btrace_pt_packet>;
|
|
|
|
if (btinfo->maint.variant.pt.packets->empty ())
|
|
btrace_maint_update_pt_packets (btinfo);
|
|
|
|
*begin = 0;
|
|
*end = btinfo->maint.variant.pt.packets->size ();
|
|
*from = btinfo->maint.variant.pt.packet_history.begin;
|
|
*to = btinfo->maint.variant.pt.packet_history.end;
|
|
break;
|
|
#endif /* defined (HAVE_LIBIPT) */
|
|
}
|
|
}
|
|
|
|
/* Print packets in BTINFO from BEGIN (inclusive) until END (exclusive) and
|
|
update the current iterator position. */
|
|
|
|
static void
|
|
btrace_maint_print_packets (struct btrace_thread_info *btinfo,
|
|
unsigned int begin, unsigned int end)
|
|
{
|
|
switch (btinfo->data.format)
|
|
{
|
|
default:
|
|
break;
|
|
|
|
case BTRACE_FORMAT_BTS:
|
|
{
|
|
const std::vector<btrace_block> &blocks
|
|
= *btinfo->data.variant.bts.blocks;
|
|
unsigned int blk;
|
|
|
|
for (blk = begin; blk < end; ++blk)
|
|
{
|
|
const btrace_block &block = blocks.at (blk);
|
|
|
|
gdb_printf ("%u\tbegin: %s, end: %s\n", blk,
|
|
core_addr_to_string_nz (block.begin),
|
|
core_addr_to_string_nz (block.end));
|
|
}
|
|
|
|
btinfo->maint.variant.bts.packet_history.begin = begin;
|
|
btinfo->maint.variant.bts.packet_history.end = end;
|
|
}
|
|
break;
|
|
|
|
#if defined (HAVE_LIBIPT)
|
|
case BTRACE_FORMAT_PT:
|
|
{
|
|
const std::vector<btrace_pt_packet> &packets
|
|
= *btinfo->maint.variant.pt.packets;
|
|
unsigned int pkt;
|
|
|
|
for (pkt = begin; pkt < end; ++pkt)
|
|
{
|
|
const struct btrace_pt_packet &packet = packets.at (pkt);
|
|
|
|
gdb_printf ("%u\t", pkt);
|
|
gdb_printf ("0x%" PRIx64 "\t", packet.offset);
|
|
|
|
if (packet.errcode == pte_ok)
|
|
pt_print_packet (&packet.packet);
|
|
else
|
|
gdb_printf ("[error: %s]", pt_errstr (packet.errcode));
|
|
|
|
gdb_printf ("\n");
|
|
}
|
|
|
|
btinfo->maint.variant.pt.packet_history.begin = begin;
|
|
btinfo->maint.variant.pt.packet_history.end = end;
|
|
}
|
|
break;
|
|
#endif /* defined (HAVE_LIBIPT) */
|
|
}
|
|
}
|
|
|
|
/* Read a number from an argument string. */
|
|
|
|
static unsigned int
|
|
get_uint (const char **arg)
|
|
{
|
|
const char *begin, *pos;
|
|
char *end;
|
|
unsigned long number;
|
|
|
|
begin = *arg;
|
|
pos = skip_spaces (begin);
|
|
|
|
if (!isdigit (*pos))
|
|
error (_("Expected positive number, got: %s."), pos);
|
|
|
|
number = strtoul (pos, &end, 10);
|
|
if (number > UINT_MAX)
|
|
error (_("Number too big."));
|
|
|
|
*arg += (end - begin);
|
|
|
|
return (unsigned int) number;
|
|
}
|
|
|
|
/* Read a context size from an argument string. */
|
|
|
|
static int
|
|
get_context_size (const char **arg)
|
|
{
|
|
const char *pos = skip_spaces (*arg);
|
|
|
|
if (!isdigit (*pos))
|
|
error (_("Expected positive number, got: %s."), pos);
|
|
|
|
char *end;
|
|
long result = strtol (pos, &end, 10);
|
|
*arg = end;
|
|
return result;
|
|
}
|
|
|
|
/* Complain about junk at the end of an argument string. */
|
|
|
|
static void
|
|
no_chunk (const char *arg)
|
|
{
|
|
if (*arg != 0)
|
|
error (_("Junk after argument: %s."), arg);
|
|
}
|
|
|
|
/* The "maintenance btrace packet-history" command. */
|
|
|
|
static void
|
|
maint_btrace_packet_history_cmd (const char *arg, int from_tty)
|
|
{
|
|
struct btrace_thread_info *btinfo;
|
|
unsigned int size, begin, end, from, to;
|
|
|
|
thread_info *tp = current_inferior ()->find_thread (inferior_ptid);
|
|
if (tp == NULL)
|
|
error (_("No thread."));
|
|
|
|
size = 10;
|
|
btinfo = &tp->btrace;
|
|
|
|
btrace_maint_update_packets (btinfo, &begin, &end, &from, &to);
|
|
if (begin == end)
|
|
{
|
|
gdb_printf (_("No trace.\n"));
|
|
return;
|
|
}
|
|
|
|
if (arg == NULL || *arg == 0 || strcmp (arg, "+") == 0)
|
|
{
|
|
from = to;
|
|
|
|
if (end - from < size)
|
|
size = end - from;
|
|
to = from + size;
|
|
}
|
|
else if (strcmp (arg, "-") == 0)
|
|
{
|
|
to = from;
|
|
|
|
if (to - begin < size)
|
|
size = to - begin;
|
|
from = to - size;
|
|
}
|
|
else
|
|
{
|
|
from = get_uint (&arg);
|
|
if (end <= from)
|
|
error (_("'%u' is out of range."), from);
|
|
|
|
arg = skip_spaces (arg);
|
|
if (*arg == ',')
|
|
{
|
|
arg = skip_spaces (++arg);
|
|
|
|
if (*arg == '+')
|
|
{
|
|
arg += 1;
|
|
size = get_context_size (&arg);
|
|
|
|
no_chunk (arg);
|
|
|
|
if (end - from < size)
|
|
size = end - from;
|
|
to = from + size;
|
|
}
|
|
else if (*arg == '-')
|
|
{
|
|
arg += 1;
|
|
size = get_context_size (&arg);
|
|
|
|
no_chunk (arg);
|
|
|
|
/* Include the packet given as first argument. */
|
|
from += 1;
|
|
to = from;
|
|
|
|
if (to - begin < size)
|
|
size = to - begin;
|
|
from = to - size;
|
|
}
|
|
else
|
|
{
|
|
to = get_uint (&arg);
|
|
|
|
/* Include the packet at the second argument and silently
|
|
truncate the range. */
|
|
if (to < end)
|
|
to += 1;
|
|
else
|
|
to = end;
|
|
|
|
no_chunk (arg);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
no_chunk (arg);
|
|
|
|
if (end - from < size)
|
|
size = end - from;
|
|
to = from + size;
|
|
}
|
|
|
|
dont_repeat ();
|
|
}
|
|
|
|
btrace_maint_print_packets (btinfo, from, to);
|
|
}
|
|
|
|
/* The "maintenance btrace clear-packet-history" command. */
|
|
|
|
static void
|
|
maint_btrace_clear_packet_history_cmd (const char *args, int from_tty)
|
|
{
|
|
if (args != NULL && *args != 0)
|
|
error (_("Invalid argument."));
|
|
|
|
if (inferior_ptid == null_ptid)
|
|
error (_("No thread."));
|
|
|
|
thread_info *tp = inferior_thread ();
|
|
btrace_thread_info *btinfo = &tp->btrace;
|
|
|
|
/* Must clear the maint data before - it depends on BTINFO->DATA. */
|
|
btrace_maint_clear (btinfo);
|
|
btinfo->data.clear ();
|
|
}
|
|
|
|
/* The "maintenance btrace clear" command. */
|
|
|
|
static void
|
|
maint_btrace_clear_cmd (const char *args, int from_tty)
|
|
{
|
|
if (args != NULL && *args != 0)
|
|
error (_("Invalid argument."));
|
|
|
|
if (inferior_ptid == null_ptid)
|
|
error (_("No thread."));
|
|
|
|
thread_info *tp = inferior_thread ();
|
|
btrace_clear (tp);
|
|
}
|
|
|
|
/* The "maintenance info btrace" command. */
|
|
|
|
static void
|
|
maint_info_btrace_cmd (const char *args, int from_tty)
|
|
{
|
|
struct btrace_thread_info *btinfo;
|
|
const struct btrace_config *conf;
|
|
|
|
if (args != NULL && *args != 0)
|
|
error (_("Invalid argument."));
|
|
|
|
if (inferior_ptid == null_ptid)
|
|
error (_("No thread."));
|
|
|
|
thread_info *tp = inferior_thread ();
|
|
|
|
btinfo = &tp->btrace;
|
|
|
|
conf = btrace_conf (btinfo);
|
|
if (conf == NULL)
|
|
error (_("No btrace configuration."));
|
|
|
|
gdb_printf (_("Format: %s.\n"),
|
|
btrace_format_string (conf->format));
|
|
|
|
switch (conf->format)
|
|
{
|
|
default:
|
|
break;
|
|
|
|
case BTRACE_FORMAT_BTS:
|
|
gdb_printf (_("Number of packets: %zu.\n"),
|
|
btinfo->data.variant.bts.blocks->size ());
|
|
break;
|
|
|
|
#if defined (HAVE_LIBIPT)
|
|
case BTRACE_FORMAT_PT:
|
|
{
|
|
struct pt_version version;
|
|
|
|
version = pt_library_version ();
|
|
gdb_printf (_("Version: %u.%u.%u%s.\n"), version.major,
|
|
version.minor, version.build,
|
|
version.ext != NULL ? version.ext : "");
|
|
|
|
btrace_maint_update_pt_packets (btinfo);
|
|
gdb_printf (_("Number of packets: %zu.\n"),
|
|
((btinfo->maint.variant.pt.packets == nullptr)
|
|
? 0 : btinfo->maint.variant.pt.packets->size ()));
|
|
}
|
|
break;
|
|
#endif /* defined (HAVE_LIBIPT) */
|
|
}
|
|
}
|
|
|
|
/* The "maint show btrace pt skip-pad" show value function. */
|
|
|
|
static void
|
|
show_maint_btrace_pt_skip_pad (struct ui_file *file, int from_tty,
|
|
struct cmd_list_element *c,
|
|
const char *value)
|
|
{
|
|
gdb_printf (file, _("Skip PAD packets is %s.\n"), value);
|
|
}
|
|
|
|
|
|
/* Initialize btrace maintenance commands. */
|
|
|
|
void _initialize_btrace ();
|
|
void
|
|
_initialize_btrace ()
|
|
{
|
|
add_cmd ("btrace", class_maintenance, maint_info_btrace_cmd,
|
|
_("Info about branch tracing data."), &maintenanceinfolist);
|
|
|
|
add_basic_prefix_cmd ("btrace", class_maintenance,
|
|
_("Branch tracing maintenance commands."),
|
|
&maint_btrace_cmdlist, 0, &maintenancelist);
|
|
|
|
add_setshow_prefix_cmd ("btrace", class_maintenance,
|
|
_("Set branch tracing specific variables."),
|
|
_("Show branch tracing specific variables."),
|
|
&maint_btrace_set_cmdlist,
|
|
&maint_btrace_show_cmdlist,
|
|
&maintenance_set_cmdlist,
|
|
&maintenance_show_cmdlist);
|
|
|
|
add_setshow_prefix_cmd ("pt", class_maintenance,
|
|
_("Set Intel Processor Trace specific variables."),
|
|
_("Show Intel Processor Trace specific variables."),
|
|
&maint_btrace_pt_set_cmdlist,
|
|
&maint_btrace_pt_show_cmdlist,
|
|
&maint_btrace_set_cmdlist,
|
|
&maint_btrace_show_cmdlist);
|
|
|
|
add_setshow_boolean_cmd ("skip-pad", class_maintenance,
|
|
&maint_btrace_pt_skip_pad, _("\
|
|
Set whether PAD packets should be skipped in the btrace packet history."), _("\
|
|
Show whether PAD packets should be skipped in the btrace packet history."),_("\
|
|
When enabled, PAD packets are ignored in the btrace packet history."),
|
|
NULL, show_maint_btrace_pt_skip_pad,
|
|
&maint_btrace_pt_set_cmdlist,
|
|
&maint_btrace_pt_show_cmdlist);
|
|
|
|
add_cmd ("packet-history", class_maintenance, maint_btrace_packet_history_cmd,
|
|
_("Print the raw branch tracing data.\n\
|
|
With no argument, print ten more packets after the previous ten-line print.\n\
|
|
With '-' as argument print ten packets before a previous ten-line print.\n\
|
|
One argument specifies the starting packet of a ten-line print.\n\
|
|
Two arguments with comma between specify starting and ending packets to \
|
|
print.\n\
|
|
Preceded with '+'/'-' the second argument specifies the distance from the \
|
|
first."),
|
|
&maint_btrace_cmdlist);
|
|
|
|
add_cmd ("clear-packet-history", class_maintenance,
|
|
maint_btrace_clear_packet_history_cmd,
|
|
_("Clears the branch tracing packet history.\n\
|
|
Discards the raw branch tracing data but not the execution history data."),
|
|
&maint_btrace_cmdlist);
|
|
|
|
add_cmd ("clear", class_maintenance, maint_btrace_clear_cmd,
|
|
_("Clears the branch tracing data.\n\
|
|
Discards the raw branch tracing data and the execution history data.\n\
|
|
The next 'record' command will fetch the branch tracing data anew."),
|
|
&maint_btrace_cmdlist);
|
|
|
|
}
|