diff options
author | Felix Willgerodt <felix.willgerodt@intel.com> | 2023-06-26 16:54:25 +0200 |
---|---|---|
committer | Felix Willgerodt <felix.willgerodt@intel.com> | 2024-09-24 14:22:28 +0200 |
commit | cdd65168f30913731047b86b5a3ef0082d18a90e (patch) | |
tree | 097a1a35a10d78bd746d527f875c0850bbac1eca | |
parent | 13b3a89bc272ded10242e3359bc0871e99338e6c (diff) | |
download | binutils-cdd65168f30913731047b86b5a3ef0082d18a90e.zip binutils-cdd65168f30913731047b86b5a3ef0082d18a90e.tar.gz binutils-cdd65168f30913731047b86b5a3ef0082d18a90e.tar.bz2 |
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>
-rw-r--r-- | gdb/btrace.c | 138 | ||||
-rw-r--r-- | gdb/btrace.h | 8 | ||||
-rw-r--r-- | gdb/python/py-record-btrace.c | 6 | ||||
-rw-r--r-- | gdb/testsuite/gdb.btrace/event-tracing-gap.c | 32 | ||||
-rw-r--r-- | gdb/testsuite/gdb.btrace/event-tracing-gap.exp | 72 | ||||
-rw-r--r-- | gdb/testsuite/gdb.btrace/event-tracing.exp | 51 | ||||
-rw-r--r-- | gdb/testsuite/gdb.btrace/null-deref.c | 26 | ||||
-rw-r--r-- | gdb/testsuite/lib/gdb.exp | 56 |
8 files changed, 364 insertions, 25 deletions
diff --git a/gdb/btrace.c b/gdb/btrace.c index 649e0ad..e72bea4 100644 --- a/gdb/btrace.c +++ b/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); diff --git a/gdb/btrace.h b/gdb/btrace.h index 8a81426..a4960f3 100644 --- a/gdb/btrace.h +++ b/gdb/btrace.h @@ -110,7 +110,11 @@ enum btrace_function_flag /* Indicates that at least one auxiliary instruction is in the current function segment. */ - BFUN_CONTAINS_AUX = (1 << 2) + BFUN_CONTAINS_AUX = (1 << 2), + + /* Indicates that at least one instruction not of type BTRACE_INSN_AUX + is in the current function segment. */ + BFUN_CONTAINS_NON_AUX = (1 << 3) }; DEF_ENUM_FLAGS_TYPE (enum btrace_function_flag, btrace_function_flags); @@ -356,7 +360,7 @@ struct btrace_thread_info /* Function pointer to the ptwrite callback. Returns the string returned by the ptwrite filter function. */ std::optional<std::string> (*ptw_callback_fun) (const uint64_t payload, - const uint64_t ip, + std::optional<uint64_t> ip, const void *ptw_context) = nullptr; diff --git a/gdb/python/py-record-btrace.c b/gdb/python/py-record-btrace.c index e4f0fd1..bdfebf1 100644 --- a/gdb/python/py-record-btrace.c +++ b/gdb/python/py-record-btrace.c @@ -810,7 +810,7 @@ recpy_bt_function_call_history (PyObject *self, void *closure) /* Helper function that calls PTW_FILTER with PAYLOAD and IP as arguments. Returns the string that will be printed, if there is a filter to call. */ static std::optional<std::string> -recpy_call_filter (const uint64_t payload, const uint64_t ip, +recpy_call_filter (const uint64_t payload, std::optional<uint64_t> ip, const void *ptw_filter) { std::optional<std::string> result; @@ -824,10 +824,10 @@ recpy_call_filter (const uint64_t payload, const uint64_t ip, gdbpy_ref<> py_payload = gdb_py_object_from_ulongest (payload); gdbpy_ref<> py_ip; - if (ip == 0) + if (!ip.has_value ()) py_ip = gdbpy_ref<>::new_reference (Py_None); else - py_ip = gdb_py_object_from_ulongest (ip); + py_ip = gdb_py_object_from_ulongest (*ip); gdbpy_ref<> py_result (PyObject_CallFunctionObjArgs ((PyObject *) ptw_filter, py_payload.get (), diff --git a/gdb/testsuite/gdb.btrace/event-tracing-gap.c b/gdb/testsuite/gdb.btrace/event-tracing-gap.c new file mode 100644 index 0000000..b04f74d --- /dev/null +++ b/gdb/testsuite/gdb.btrace/event-tracing-gap.c @@ -0,0 +1,32 @@ +/* This testcase is part of GDB, the GNU debugger. + + Copyright 2024 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +static int +square (int num) +{ + return num * num; /* bp1. */ +} + +int +main (void) +{ + int a = 2; + int ans = 0; + + ans = square (a); + return 0; /* bp2. */ +} diff --git a/gdb/testsuite/gdb.btrace/event-tracing-gap.exp b/gdb/testsuite/gdb.btrace/event-tracing-gap.exp new file mode 100644 index 0000000..13bf45d --- /dev/null +++ b/gdb/testsuite/gdb.btrace/event-tracing-gap.exp @@ -0,0 +1,72 @@ +# This testcase is part of GDB, the GNU debugger. +# +# Copyright 2024 Free Software Foundation, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +# Test event tracing with gaps. + +require allow_btrace_pt_event_trace_tests + +standard_testfile + +if {[prepare_for_testing "failed to prepare" $testfile $srcfile]} { + return -1 +} + +if {![runto_main]} { + return -1 +} + +gdb_test_no_output "set record btrace pt event-tracing on" +gdb_test_no_output "record btrace pt" + +set bp_1 [gdb_get_line_number "bp1"] +set bp_2 [gdb_get_line_number "bp2"] +gdb_breakpoint $bp_1 +gdb_breakpoint $bp_2 + +gdb_test "next" + +# Inferior calls and return commands will create gaps in the recording. +gdb_test "call square (3)" [multi_line \ + "" \ + "Breakpoint $decimal, square \\(num=3\\) at \[^\r\n\]+:$bp_1" \ + "$decimal.*bp1.*" \ + "The program being debugged stopped while in a function called from GDB\\." \ + "Evaluation of the expression containing the function" \ + "\\(square\\) will be abandoned\\." \ + "When the function is done executing, GDB will silently stop\\."] + +gdb_test "return 9" "0.*main.*" \ + "return" \ + "Make.*return now\\? \\(y or n\\) " "y" + +gdb_continue_to_breakpoint "break at bp_1" ".*$srcfile:$bp_1.*" +gdb_continue_to_breakpoint "break at bp_2" ".*$srcfile:$bp_2.*" + +# We should have 2 gaps and events for each breakpoint we hit. +# Note that due to the asynchronous nature of certain events, we use +# gdb_test_sequence and check only for events that we can control. +gdb_test_sequence "record function-call-history" "function-call-history" { + "\[0-9\]+\tmain" + "\[0-9\]+\t\\\[non-contiguous\\\]" + "\[0-9\]+\tsquare" + "\\\[interrupt: vector = 0x3 \\\(#bp\\\)(, ip = 0x\[0-9a-fA-F\]+)?\\\]" + "\[0-9\]+\t\\\[non-contiguous\\\]" + "\[0-9\]+\tmain" + "\[0-9\]+\tsquare" + "\\\[interrupt: vector = 0x3 \\\(#bp\\\)(, ip = 0x\[0-9a-fA-F\]+)?\\\]" + "\[0-9\]+\tmain" +} diff --git a/gdb/testsuite/gdb.btrace/event-tracing.exp b/gdb/testsuite/gdb.btrace/event-tracing.exp new file mode 100644 index 0000000..4e6e785 --- /dev/null +++ b/gdb/testsuite/gdb.btrace/event-tracing.exp @@ -0,0 +1,51 @@ +# This testcase is part of GDB, the GNU debugger. +# +# Copyright 2024 Free Software Foundation, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +# Test basic Intel PT event tracing + +require allow_btrace_pt_event_trace_tests + +standard_testfile null-deref.c + +if {[prepare_for_testing "failed to prepare" $testfile $srcfile]} { + return -1 +} + +if {![runto_main]} { + return -1 +} + +gdb_test_no_output "set record btrace pt event-tracing on" +gdb_test_no_output "record btrace pt" + +gdb_test "continue" "Program received signal SIGSEGV, Segmentation fault.*" + +# Test printing of at least one INTERRUPT event. +# This uses test_sequence to avoid random events failing the tests. +gdb_test_sequence "record function-call-history" "function-call-history" { + "\[0-9\]+\tmain" + "\t \\\[interrupt: vector = 0xe \\\(#pf\\\)(, cr2 = 0x0)?(, ip = 0x\[0-9a-fA-F\]+)?\\\]" +} + +# Test the instruction-history. Assembly can differ between compilers. To +# avoid creating a .S file for this test we just check the event at the end. +gdb_test "record instruction-history" \ + "$decimal\t \\\[interrupt: vector = 0xe \\\(#pf\\\)(, cr2 = 0x0)?(, ip = $hex)?\\\]" + +# Test reverse stepping and replay stepping +gdb_test "reverse-stepi" "\\\[interrupt: vector = 0xe \\\(#pf\\\)(, cr2 = 0x0)?(, ip = $hex)?\\\].*" +gdb_test "stepi" "\\\[interrupt: vector = 0xe \\\(#pf\\\)(, cr2 = 0x0)?(, ip = $hex)?\\\].*" diff --git a/gdb/testsuite/gdb.btrace/null-deref.c b/gdb/testsuite/gdb.btrace/null-deref.c new file mode 100644 index 0000000..f0de31f --- /dev/null +++ b/gdb/testsuite/gdb.btrace/null-deref.c @@ -0,0 +1,26 @@ +/* Copyright 2024 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#include <stddef.h> + +int +main () +{ + int a = 34; + int *b = NULL; + + a = *b; /* BP1. */ + return 0; +} diff --git a/gdb/testsuite/lib/gdb.exp b/gdb/testsuite/lib/gdb.exp index 7d8d413..9ea9c3b 100644 --- a/gdb/testsuite/lib/gdb.exp +++ b/gdb/testsuite/lib/gdb.exp @@ -4438,6 +4438,62 @@ gdb_caching_proc allow_btrace_ptw_tests {} { } +# Run a test on the target to see if GDB supports event tracing on it. +# Return 1 if so, 0 if it does not. + +gdb_caching_proc allow_btrace_pt_event_trace_tests {} { + global srcdir subdir + set me "allow_btrace_pt_event_trace_tests" + require allow_btrace_pt_tests + + set src { + int + main () + { + return 0; + } + } + + if {![gdb_simple_compile $me $src executable]} { + return 0 + } + + gdb_exit + gdb_start + gdb_reinitialize_dir $srcdir/$subdir + gdb_load "$obj" + if ![runto_main] { + return 0 + } + + set allow_event_trace_tests 0 + gdb_test_multiple "set record btrace pt event-tracing on" "$me: first check" { + -re -wrap "Event-tracing is not supported by GDB." { + } + -re -wrap "" { + set allow_event_trace_tests 1 + } + } + + if { $allow_event_trace_tests == 1 } { + gdb_test_multiple "record btrace pt" "$me: check OS support" { + -re -wrap "^" { + } + -re -wrap "" { + verbose -log "$me: Target doesn't support event tracing." + set allow_event_trace_tests 0 + } + } + } + + gdb_exit + remote_file build delete $obj + + verbose "$me: returning $allow_event_trace_tests" 2 + return $allow_event_trace_tests +} + + # Run a test on the target to see if it supports Aarch64 SVE hardware. # Return 1 if so, 0 if it does not. Note this causes a restart of GDB. |