forked from Imagelibrary/binutils-gdb
btrace: Add support for interrupt events.
Newer Intel CPUs support recording asynchronous events in the PT trace. Libipt also recently added support for decoding these. This patch adds support for interrupt events, based on the existing aux infrastructure. GDB can now display such events during the record instruction-history and function-call-history commands. Subsequent patches will add the rest of the events currently supported. Approved-By: Markus Metzger <markus.t.metzger@intel.com>
This commit is contained in:
138
gdb/btrace.c
138
gdb/btrace.c
@@ -547,31 +547,39 @@ ftrace_new_gap (struct btrace_thread_info *btinfo, int errcode,
|
||||
Return the chronologically latest function segment, never NULL. */
|
||||
|
||||
static struct btrace_function *
|
||||
ftrace_update_function (struct btrace_thread_info *btinfo, CORE_ADDR pc)
|
||||
ftrace_update_function (struct btrace_thread_info *btinfo,
|
||||
std::optional<CORE_ADDR> pc)
|
||||
{
|
||||
struct minimal_symbol *mfun;
|
||||
struct symbol *fun;
|
||||
struct btrace_function *bfun;
|
||||
struct minimal_symbol *mfun = nullptr;
|
||||
struct symbol *fun = nullptr;
|
||||
|
||||
/* 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 (pc.has_value ())
|
||||
{
|
||||
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 (fun == nullptr && mfun == nullptr)
|
||||
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 ();
|
||||
btrace_function *bfun = &btinfo->functions.back ();
|
||||
if (bfun->errcode != 0)
|
||||
return ftrace_new_function (btinfo, mfun, fun);
|
||||
|
||||
/* If there is no valid PC, which can happen for events with a
|
||||
suppressed IP, we can't do more than return the last bfun. */
|
||||
if (!pc.has_value ())
|
||||
return bfun;
|
||||
|
||||
/* 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. */
|
||||
@@ -605,7 +613,7 @@ ftrace_update_function (struct btrace_thread_info *btinfo, CORE_ADDR pc)
|
||||
|
||||
case BTRACE_INSN_CALL:
|
||||
/* Ignore calls to the next instruction. They are used for PIC. */
|
||||
if (last->pc + last->size == pc)
|
||||
if (last->pc + last->size == *pc)
|
||||
break;
|
||||
|
||||
return ftrace_new_call (btinfo, mfun, fun);
|
||||
@@ -614,10 +622,10 @@ ftrace_update_function (struct btrace_thread_info *btinfo, CORE_ADDR pc)
|
||||
{
|
||||
CORE_ADDR start;
|
||||
|
||||
start = get_pc_function_start (pc);
|
||||
start = get_pc_function_start (*pc);
|
||||
|
||||
/* A jump to the start of a function is (typically) a tail call. */
|
||||
if (start == pc)
|
||||
if (start == *pc)
|
||||
return ftrace_new_tailcall (btinfo, mfun, fun);
|
||||
|
||||
/* Some versions of _Unwind_RaiseException use an indirect
|
||||
@@ -642,6 +650,18 @@ ftrace_update_function (struct btrace_thread_info *btinfo, CORE_ADDR pc)
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case BTRACE_INSN_AUX:
|
||||
/* An aux insn couldn't have switched the function. But the
|
||||
segment might not have had a symbol name resolved yet, as events
|
||||
might not have an IP. Use the current IP in that case and update
|
||||
the name. */
|
||||
if (bfun->sym == nullptr && bfun->msym == nullptr)
|
||||
{
|
||||
bfun->sym = fun;
|
||||
bfun->msym = mfun;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -668,6 +688,8 @@ ftrace_update_insns (struct btrace_function *bfun, const btrace_insn &insn)
|
||||
|
||||
if (insn.iclass == BTRACE_INSN_AUX)
|
||||
bfun->flags |= BFUN_CONTAINS_AUX;
|
||||
else
|
||||
bfun->flags |= BFUN_CONTAINS_NON_AUX;
|
||||
|
||||
if (record_debug > 1)
|
||||
ftrace_debug (bfun, "update insn");
|
||||
@@ -1101,7 +1123,8 @@ btrace_compute_ftrace_bts (struct thread_info *tp,
|
||||
break;
|
||||
}
|
||||
|
||||
bfun = ftrace_update_function (btinfo, pc);
|
||||
bfun = ftrace_update_function (btinfo,
|
||||
std::make_optional<CORE_ADDR> (pc));
|
||||
|
||||
/* Maintain the function level offset.
|
||||
For all but the last block, we do it here. */
|
||||
@@ -1211,10 +1234,10 @@ pt_btrace_insn (const struct pt_insn &insn)
|
||||
|
||||
static void
|
||||
handle_pt_aux_insn (btrace_thread_info *btinfo, std::string &aux_str,
|
||||
CORE_ADDR ip)
|
||||
std::optional<CORE_ADDR> pc)
|
||||
{
|
||||
btinfo->aux_data.emplace_back (std::move (aux_str));
|
||||
struct btrace_function *bfun = ftrace_update_function (btinfo, ip);
|
||||
struct btrace_function *bfun = ftrace_update_function (btinfo, pc);
|
||||
|
||||
btrace_insn insn {{btinfo->aux_data.size () - 1}, 0,
|
||||
BTRACE_INSN_AUX, 0};
|
||||
@@ -1222,8 +1245,47 @@ handle_pt_aux_insn (btrace_thread_info *btinfo, std::string &aux_str,
|
||||
ftrace_update_insns (bfun, insn);
|
||||
}
|
||||
|
||||
/* Check if the recording contains real instructions and not only auxiliary
|
||||
instructions since the last gap (or since the beginning). */
|
||||
|
||||
static bool
|
||||
ftrace_contains_non_aux_since_last_gap (const btrace_thread_info *btinfo)
|
||||
{
|
||||
const std::vector<btrace_function> &functions = btinfo->functions;
|
||||
|
||||
std::vector<btrace_function>::const_reverse_iterator rit;
|
||||
for (rit = functions.crbegin (); rit != functions.crend (); ++rit)
|
||||
{
|
||||
if (rit->errcode != 0)
|
||||
return false;
|
||||
|
||||
if ((rit->flags & BFUN_CONTAINS_NON_AUX) != 0)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
#endif /* defined (HAVE_PT_INSN_EVENT) */
|
||||
|
||||
#if (LIBIPT_VERSION >= 0x201)
|
||||
/* Translate an interrupt vector to a mnemonic string as defined for x86.
|
||||
Returns nullptr if there is none. */
|
||||
|
||||
static const char *
|
||||
decode_interrupt_vector (const uint8_t vector)
|
||||
{
|
||||
static const char *mnemonic[]
|
||||
= { "#de", "#db", nullptr, "#bp", "#of", "#br", "#ud", "#nm",
|
||||
"#df", "#mf", "#ts", "#np", "#ss", "#gp", "#pf", nullptr,
|
||||
"#mf", "#ac", "#mc", "#xm", "#ve", "#cp" };
|
||||
|
||||
if (vector < (sizeof (mnemonic) / sizeof (mnemonic[0])))
|
||||
return mnemonic[vector];
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
#endif /* defined (LIBIPT_VERSION >= 0x201) */
|
||||
|
||||
/* Handle instruction decode events (libipt-v2). */
|
||||
|
||||
static int
|
||||
@@ -1236,6 +1298,7 @@ handle_pt_insn_events (struct btrace_thread_info *btinfo,
|
||||
{
|
||||
struct pt_event event;
|
||||
uint64_t offset;
|
||||
std::optional<CORE_ADDR> pc;
|
||||
|
||||
status = pt_insn_event (decoder, &event, sizeof (event));
|
||||
if (status < 0)
|
||||
@@ -1251,8 +1314,13 @@ handle_pt_insn_events (struct btrace_thread_info *btinfo,
|
||||
if (event.status_update != 0)
|
||||
break;
|
||||
|
||||
/* Only create a new gap if there are non-aux instructions in
|
||||
the trace since the last gap. We could be at the beginning
|
||||
of the recording and could already have handled one or more
|
||||
events, like ptev_iret, that created aux insns. In that
|
||||
case we don't want to create a gap or print a warning. */
|
||||
if (event.variant.enabled.resumed == 0
|
||||
&& !btinfo->functions.empty ())
|
||||
&& ftrace_contains_non_aux_since_last_gap (btinfo))
|
||||
{
|
||||
struct btrace_function *bfun
|
||||
= ftrace_new_gap (btinfo, BDE_PT_NON_CONTIGUOUS, gaps);
|
||||
@@ -1282,7 +1350,6 @@ handle_pt_insn_events (struct btrace_thread_info *btinfo,
|
||||
#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
|
||||
@@ -1314,9 +1381,10 @@ handle_pt_insn_events (struct btrace_thread_info *btinfo,
|
||||
}
|
||||
}
|
||||
|
||||
if (pc == 0)
|
||||
if (!pc.has_value ())
|
||||
warning (_("Failed to determine the PC for ptwrite."));
|
||||
|
||||
|
||||
if (btinfo->ptw_callback_fun != nullptr)
|
||||
ptw_string
|
||||
= btinfo->ptw_callback_fun (event.variant.ptwrite.payload,
|
||||
@@ -1333,6 +1401,34 @@ handle_pt_insn_events (struct btrace_thread_info *btinfo,
|
||||
break;
|
||||
}
|
||||
#endif /* defined (HAVE_STRUCT_PT_EVENT_VARIANT_PTWRITE) */
|
||||
|
||||
#if (LIBIPT_VERSION >= 0x201)
|
||||
case ptev_interrupt:
|
||||
{
|
||||
std::string aux_string = std::string (_("interrupt: vector = "))
|
||||
+ hex_string (event.variant.interrupt.vector);
|
||||
|
||||
const char *decoded
|
||||
= decode_interrupt_vector (event.variant.interrupt.vector);
|
||||
if (decoded != nullptr)
|
||||
aux_string += std::string (" (") + decoded + ")";
|
||||
|
||||
if (event.variant.interrupt.has_cr2 != 0)
|
||||
{
|
||||
aux_string += std::string (", cr2 = ")
|
||||
+ hex_string (event.variant.interrupt.cr2);
|
||||
}
|
||||
|
||||
if (event.ip_suppressed == 0)
|
||||
{
|
||||
pc = event.variant.interrupt.ip;
|
||||
aux_string += std::string (", ip = ") + hex_string (*pc);
|
||||
}
|
||||
|
||||
handle_pt_aux_insn (btinfo, aux_string, pc);
|
||||
break;
|
||||
}
|
||||
#endif /* defined (LIBIPT_VERSION >= 0x201) */
|
||||
}
|
||||
}
|
||||
#endif /* defined (HAVE_PT_INSN_EVENT) */
|
||||
@@ -1428,7 +1524,9 @@ ftrace_add_pt (struct btrace_thread_info *btinfo,
|
||||
/* Handle events indicated by flags in INSN. */
|
||||
handle_pt_insn_event_flags (btinfo, decoder, insn, gaps);
|
||||
|
||||
bfun = ftrace_update_function (btinfo, insn.ip);
|
||||
bfun
|
||||
= ftrace_update_function (btinfo,
|
||||
std::make_optional<CORE_ADDR> (insn.ip));
|
||||
|
||||
/* Maintain the function level offset. */
|
||||
*plevel = std::min (*plevel, bfun->level);
|
||||
|
||||
Reference in New Issue
Block a user