| /* Copyright (C) 2021-2024 Free Software Foundation, Inc. | |
| Contributed by Oracle. | |
| This file is part of GNU Binutils. | |
| 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, 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, write to the Free Software | |
| Foundation, 51 Franklin Street - Fifth Floor, Boston, | |
| MA 02110-1301, USA. */ | |
| #include "config.h" | |
| #include <stdlib.h> | |
| #include <stdarg.h> | |
| #include "util.h" | |
| #include "Application.h" | |
| #include "CallStack.h" | |
| #include "Experiment.h" | |
| #include "Exp_Layout.h" | |
| #include "DataObject.h" | |
| #include "DbeSession.h" | |
| #include "MetricList.h" | |
| #include "Function.h" | |
| #include "Module.h" | |
| #include "MemObject.h" | |
| #include "DbeView.h" | |
| #include "Metric.h" | |
| #include "DataSpace.h" | |
| #include "LoadObject.h" | |
| #include "debug.h" | |
| #include "ABS.h" | |
| //char *DOBJ_UNSPECIFIED = STXT("(Not identified by the compiler as a memory-referencing instruction)"); | |
| char *DOBJ_UNSPECIFIED = STXT("(No type information)"); | |
| char *DOBJ_UNIDENTIFIED = STXT("(No identifying descriptor provided by the compiler)"); | |
| char *DOBJ_UNDETERMINED = STXT("(Not determined from the symbolic information provided by the compiler)"); | |
| char *DOBJ_ANON = STXT("(Padding in structure)"); | |
| // run-time codes | |
| // ABS_UNSUPPORTED = 0x01, /* inappropriate HWC event type */ | |
| // ABS_BLOCKED = 0x02, /* runtime backtrack blocker reached */ | |
| // ABS_INCOMPLETE = 0x03, /* runtime backtrack limit reached */ | |
| // ABS_REG_LOSS = 0x04, /* address register contaminated */ | |
| // ABS_INVALID_EA = 0x05, /* invalid effective address value */ | |
| const char *ABS_RT_CODES[NUM_ABS_RT_CODES] = { | |
| "(OK)", | |
| "(Dataspace data not requested during data collection)", | |
| "(Backtracking was prevented by a jump or call instruction)", | |
| "(Backtracking did not find trigger PC)", | |
| "(Could not determine VA because registers changed after trigger instruction)", | |
| "(Memory-referencing instruction did not specify a valid VA)", | |
| "(UNKNOWN)" | |
| }; | |
| // post-processing codes | |
| // ABS_NO_CTI_INFO = 0x10, /* no AnalyzerInfo for validation */ | |
| // ABS_INFO_FAILED = 0x20, /* info failed to validate backtrack */ | |
| // ABS_CTI_TARGET = 0x30, /* CTI target invalidated backtrack */ | |
| char *DOBJ_UNASCERTAINABLE = STXT("(Module with trigger PC not compiled with -xhwcprof)"); | |
| char *DOBJ_UNVERIFIABLE = STXT("(Backtracking failed to find a valid branch target)"); | |
| char *DOBJ_UNRESOLVABLE = STXT("(Backtracking traversed a branch target)"); | |
| char *ABS_PP_CODES[NUM_ABS_PP_CODES] = { | |
| STXT ("(OK)"), | |
| DOBJ_UNASCERTAINABLE, | |
| DOBJ_UNVERIFIABLE, | |
| DOBJ_UNRESOLVABLE, | |
| STXT ("(<INTERNAL ERROR DURING POST-PROCESSING>)") | |
| }; | |
| DataSpace::DataSpace (DbeView *_dbev, int /* _picked */) | |
| { | |
| dbev = _dbev; | |
| } | |
| DataSpace::~DataSpace () { } | |
| void | |
| DataSpace::reset () { } | |
| char * | |
| DataSpace::status_str () | |
| { | |
| return NULL; | |
| } | |
| Histable * | |
| DataSpace::get_hist_obj (Histable::Type type, DataView *dview, long i) | |
| { | |
| DataObject *dobj = NULL; | |
| char *errcode = NTXT ("<internal error>"); | |
| switch (type) | |
| { | |
| case Histable::DOBJECT: | |
| dobj = (DataObject*) dview->getObjValue (PROP_HWCDOBJ, i); | |
| if (dobj == NULL) | |
| { | |
| Vaddr leafVA = (Vaddr) dview->getLongValue (PROP_VADDR, i); | |
| unsigned rt_code = (unsigned) ABS_GET_RT_CODE (leafVA); | |
| unsigned pp_code = (unsigned) ABS_GET_PP_CODE (leafVA); | |
| if (leafVA < ABS_CODE_RANGE | |
| && (pp_code || (rt_code && rt_code != ABS_REG_LOSS))) | |
| { | |
| if (rt_code >= NUM_ABS_RT_CODES) | |
| rt_code = NUM_ABS_RT_CODES - 1; | |
| if (pp_code >= NUM_ABS_PP_CODES) | |
| pp_code = NUM_ABS_PP_CODES - 1; | |
| if (rt_code) | |
| errcode = PTXT (ABS_RT_CODES[rt_code]); | |
| else | |
| errcode = PTXT (ABS_PP_CODES[pp_code]); | |
| } | |
| else | |
| { | |
| // associate dataobject with event | |
| int index; | |
| // search for memop in Module infoList | |
| void *cstack = dview->getObjValue (PROP_MSTACK, i); | |
| Histable *leafPCObj = CallStack::getStackPC (cstack, 0); | |
| DbeInstr *leafPC = NULL; | |
| if (leafPCObj->get_type () == Histable::INSTR) | |
| leafPC = (DbeInstr*) leafPCObj; | |
| else // DBELINE | |
| leafPC = (DbeInstr*) leafPCObj->convertto (Histable::INSTR); | |
| Function *func = leafPC->func; | |
| uint64_t leafPC_offset = func->img_offset + leafPC->addr; | |
| Module *mod = func->module; | |
| uint32_t dtype_id = 0; | |
| inst_info_t *info = NULL; | |
| Vec_loop (inst_info_t*, mod->infoList, index, info) | |
| { | |
| if (info->offset == leafPC_offset) | |
| { | |
| dtype_id = info->memop->datatype_id; | |
| break; | |
| } | |
| } | |
| dobj = mod->get_dobj (dtype_id); | |
| if (dobj == NULL) | |
| { | |
| // ensure dobj is determined | |
| if (dtype_id == DataObject::UNSPECIFIED_ID) | |
| errcode = PTXT (DOBJ_UNSPECIFIED); | |
| else | |
| errcode = PTXT (DOBJ_UNIDENTIFIED); | |
| } | |
| else | |
| { | |
| // determine associated master dataobject | |
| if (!dobj->master && dobj->scope) | |
| dobj->master = dbeSession->createMasterDataObject (dobj); | |
| if (dobj->scope) | |
| dobj = dobj->master; // use associated master | |
| } | |
| } | |
| if (!dobj) | |
| { | |
| // if dobj is not set yet, supply a dobj for errcode | |
| // search for a dobj with the same name | |
| dobj = dbeSession->find_dobj_by_name (errcode); | |
| if (dobj == NULL) | |
| { | |
| // create new DataObject for unknown code | |
| dobj = (DataObject*) dbeSession->createHistObject (Histable::DOBJECT); | |
| dobj->size = 0; | |
| dobj->offset = -1; | |
| dobj->parent = dbeSession->get_Unknown_DataObject (); | |
| dobj->set_dobjname (errcode, NULL); // dobj->parent must already be set | |
| } | |
| } | |
| dview->setObjValue (PROP_HWCDOBJ, i, dobj); | |
| } | |
| break; | |
| default: | |
| break; | |
| } | |
| return dobj; | |
| } | |
| Hist_data * | |
| DataSpace::compute_metrics (MetricList *mlist, Histable::Type type, | |
| Hist_data::Mode mode, Histable *sel_obj) | |
| { | |
| int nmetrics = mlist->get_items ()->size (); | |
| int sort_ind = -1; | |
| Hist_data::HistItem *hi; | |
| int index; | |
| // reset event_data count for all datatypes | |
| Vector<LoadObject*> *lobjs = dbeSession->get_text_segments (); | |
| for (int i = 0, sz = lobjs ? lobjs->size () : -1; i < sz; i++) | |
| { | |
| LoadObject *lo = lobjs->fetch (i); | |
| Vector<Module*> *modules = lo->seg_modules; | |
| for (int j = 0, msize = modules ? modules->size () : -1; j < msize; j++) | |
| { | |
| Module *mod = modules->fetch (j); | |
| mod->reset_datatypes (); | |
| } | |
| } | |
| Hist_data *hist_data = new Hist_data (mlist, type, mode); | |
| // add each experiment, skipping disabled and broken experiments | |
| for (index = 0; index < dbeSession->nexps (); index++) | |
| { | |
| Experiment *exp = dbeSession->get_exp (index); | |
| if (exp->broken) | |
| continue; | |
| Collection_params *params = exp->get_params (); | |
| if (!params->xhw_mode) | |
| continue; | |
| char *expt_name = exp->get_expt_name (); | |
| char *base_name = strrchr (expt_name, '/'); | |
| base_name = base_name ? base_name + 1 : expt_name; | |
| // Determine mapping of experiment HWC metrics to hist_data metric list | |
| int *xlate = new int[MAX_HWCOUNT]; | |
| for (unsigned i = 0; i < MAX_HWCOUNT; i++) | |
| { | |
| xlate[i] = -1; | |
| if (params->hw_interval[i] > 0) | |
| { | |
| const char *ctr_name = params->hw_aux_name[i]; | |
| int mindex; | |
| Metric *met; | |
| Vec_loop (Metric*, mlist->get_items (), mindex, met) | |
| { | |
| if (dbe_strcmp (met->get_cmd (), ctr_name) == 0) | |
| xlate[i] = mindex; | |
| } | |
| } | |
| } | |
| // | |
| // Process hardware profiling data | |
| // | |
| DataView *dview = dbev->get_filtered_events (index, DATA_HWC); | |
| if (dview) | |
| { | |
| DataDescriptor *ddscr = dview ->getDataDescriptor (); | |
| if (ddscr->getProp (PROP_HWCDOBJ) == NULL) | |
| { | |
| PropDescr *prop = new PropDescr (PROP_HWCDOBJ, NTXT ("HWCDOBJ")); | |
| prop->uname = NULL; | |
| prop->vtype = TYPE_OBJ; | |
| ddscr->addProperty (prop); | |
| } | |
| } | |
| if (dview && dview->getSize () != 0) | |
| { | |
| char *msg = NULL; | |
| for (long i = 0; i < dview->getSize (); i++) | |
| { | |
| if (i % 5000 == 0) | |
| { | |
| int percent = (int) (100.0 * i / dview->getSize ()); | |
| if (percent == 0 && msg == NULL) | |
| msg = dbe_sprintf (GTXT ("Filtering HW Profile Address Data: %s"), base_name); | |
| theApplication->set_progress (percent, (percent != 0) ? NULL : msg); | |
| } | |
| uint32_t tag = dview->getIntValue (PROP_HWCTAG, i); | |
| if (tag < 0 || tag >= MAX_HWCOUNT) | |
| continue; // invalid HWC tag in the record; ignore it | |
| int mHwcntr_idx = xlate[tag]; | |
| if (mHwcntr_idx < 0) | |
| continue; | |
| Vaddr leafVA = (Vaddr) dview->getLongValue (PROP_VADDR, i); | |
| if (leafVA == ABS_UNSUPPORTED) | |
| continue; // skip this record | |
| Histable *obj = get_hist_obj (type, dview, i); | |
| if (obj == NULL) | |
| continue; | |
| uint64_t interval = dview->getLongValue (PROP_HWCINT, i); | |
| if (HWCVAL_HAS_ERR (interval)) | |
| continue; | |
| if (mode == Hist_data::ALL) | |
| { // data_objects | |
| hi = hist_data->append_hist_item (obj); | |
| hi->value[mHwcntr_idx].ll += interval; | |
| for (DataObject *dobj = ((DataObject *) obj)->parent; dobj; dobj = dobj->parent) | |
| { | |
| hi = hist_data->append_hist_item (dobj); | |
| hi->value[mHwcntr_idx].ll += interval; | |
| } | |
| } | |
| else if (mode == Hist_data::LAYOUT || mode == Hist_data::DETAIL) | |
| { // data_single | |
| { | |
| // for data layout, insert elements that have no metrics yet | |
| DataObject *tmpParent = ((DataObject *) obj)->parent; | |
| if (tmpParent && tmpParent->get_typename ()) | |
| { | |
| // dobj is an aggregate element | |
| if (!hist_data->find_hist_item (tmpParent)) | |
| { | |
| // parent not yet a member of hist_data | |
| // supply parent's children with 0 values for layout | |
| Vector<DataObject*> *elements = dbeSession->get_dobj_elements (tmpParent); | |
| for (long eli = 0, sz = elements->size (); eli < sz; eli++) | |
| { | |
| DataObject* element = elements->fetch (eli); | |
| assert (!hist_data->find_hist_item (element)); | |
| hi = hist_data->append_hist_item (element); | |
| } | |
| } | |
| } | |
| } | |
| // Same as for mode == Hist_data::ALL: | |
| hi = hist_data->append_hist_item (obj); | |
| hi->value[mHwcntr_idx].ll += interval; | |
| for (DataObject *dobj = ((DataObject *) obj)->parent; dobj; dobj = dobj->parent) | |
| { | |
| hi = hist_data->append_hist_item (dobj); | |
| hi->value[mHwcntr_idx].ll += interval; | |
| } | |
| } | |
| else if (mode == Hist_data::SELF) | |
| { // used by dbeGetSummary() | |
| if (obj == sel_obj) | |
| { | |
| hi = hist_data->append_hist_item (obj); | |
| hi->value[mHwcntr_idx].ll += interval; | |
| } | |
| else | |
| { | |
| for (DataObject *dobj = ((DataObject *) obj)->parent; dobj; dobj = dobj->parent) | |
| { | |
| if ((Histable*) dobj == sel_obj) | |
| { | |
| hi = hist_data->append_hist_item (dobj); | |
| hi->value[mHwcntr_idx].ll += interval; | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| // Update total | |
| hist_data->total->value[mHwcntr_idx].ll += interval; | |
| } | |
| free (msg); | |
| theApplication->set_progress (0, NTXT ("")); | |
| } | |
| delete[] xlate; | |
| } | |
| // include a regular HistItem for <Total> -- for all DataObjects, and MemObjects | |
| DataObject *dtot = dbeSession->get_Total_DataObject (); | |
| if (mode == Hist_data::ALL || mode == Hist_data::DETAIL | |
| || mode == Hist_data::LAYOUT || | |
| sel_obj == dtot) | |
| { | |
| hi = hist_data->append_hist_item (dtot); | |
| for (int mind = 0; mind < nmetrics; mind++) | |
| hi->value[mind] = hist_data->total->value[mind]; | |
| } | |
| if (hist_data->get_status () != Hist_data::SUCCESS) | |
| return hist_data; | |
| theApplication->set_progress (0, GTXT ("Constructing Metrics")); | |
| // Determine by which metric to sort if any | |
| bool rev_sort = mlist->get_sort_rev (); | |
| // Compute static metrics: SIZES, ADDRESS. | |
| for (int mind = 0; mind < nmetrics; mind++) | |
| { | |
| Metric *mtr = mlist->get_items ()->fetch (mind); | |
| if (mlist->get_sort_ref_index () == mind) | |
| sort_ind = mind; | |
| else if (!mtr->is_visible () && !mtr->is_tvisible () | |
| && !mtr->is_pvisible ()) | |
| continue; | |
| Metric::Type mtype = mtr->get_type (); | |
| if (mtype == Metric::SIZES) | |
| { | |
| Vec_loop (Hist_data::HistItem *, hist_data->hist_items, index, hi) | |
| { | |
| Histable *h = mtr->get_comparable_obj (hi->obj); | |
| hi->value[mind].tag = VT_LLONG; | |
| hi->value[mind].ll = h ? h->get_size () : 0; | |
| } | |
| } | |
| else if (mtype == Metric::ONAME | |
| && (mode == Hist_data::SELF | |
| || ((DataObject*) sel_obj == dbeSession->get_Total_DataObject ()))) | |
| { | |
| Vec_loop (Hist_data::HistItem *, hist_data->hist_items, index, hi) | |
| { | |
| hi->value[mind].tag = VT_OFFSET; // offset labels | |
| } | |
| } | |
| else if (mtype == Metric::ADDRESS) | |
| { // pseudo-address | |
| Vec_loop (Hist_data::HistItem *, hist_data->hist_items, index, hi) | |
| { | |
| hi->value[mind].tag = VT_ADDRESS; | |
| Histable *h = mtr->get_comparable_obj (hi->obj); | |
| hi->value[mind].ll = h ? h->get_addr () : 0; | |
| } | |
| // force sort by offset // XXXX should visibility also be set? | |
| if (mode == Hist_data::SELF) | |
| { // used by dbeGetSummary() | |
| sort_ind = mind; | |
| //hist_data->metrics->fetch(mind)->set_visible(T); | |
| } | |
| } | |
| else | |
| { | |
| ValueTag vtype = mtr->get_vtype (); | |
| switch (vtype) | |
| { | |
| case VT_ULLONG: // most Data-derived HWC metrics are VT_ULLONG | |
| hist_data->total->value[mind].tag = vtype; | |
| Vec_loop (Hist_data::HistItem *, hist_data->hist_items, index, hi) | |
| { | |
| hi->value[mind].tag = vtype; | |
| } | |
| break; | |
| case VT_DOUBLE: | |
| { | |
| double prec = mtr->get_precision (); | |
| hist_data->total->value[mind].tag = vtype; | |
| hist_data->total->value[mind].d = hist_data->total->value[mind].ll / prec; | |
| Vec_loop (Hist_data::HistItem *, hist_data->hist_items, index, hi) | |
| { | |
| hi->value[mind].tag = vtype; | |
| hi->value[mind].d = hi->value[mind].ll / prec; | |
| } | |
| break; | |
| } | |
| default: | |
| if (mtr->get_subtype () != Metric::STATIC) | |
| abort (); | |
| break; | |
| } | |
| } | |
| } | |
| hist_data->sort (sort_ind, rev_sort); | |
| hist_data->compute_minmax (); | |
| theApplication->set_progress (0, NTXT ("")); | |
| return hist_data; | |
| } | |
| // generate annotated structure info for data_layout | |
| // note: similar data traversal found in er_print_histogram::dump_detail() | |
| Hist_data * | |
| DataSpace::get_layout_data (Hist_data *sorted_data, | |
| Vector<int> *marks, int /* _threshold */) | |
| { | |
| Hist_data *data_items = NULL; | |
| Hist_data::HistItem *new_item; | |
| MetricList *mlist = new MetricList (sorted_data->get_metric_list ()); | |
| int no_metrics = mlist->get_items ()->size (); | |
| int index, addr_index = -1, name_index = -1; | |
| Dprintf (DEBUG_DATAOBJ, NTXT ("DataSpace::get_layout_data(ALL)\n")); | |
| // Allocate a new Hist_data for the list, to be copied from the DataObect list | |
| data_items = new Hist_data (mlist, Histable::DOBJECT, Hist_data::MODL); | |
| data_items->set_status (sorted_data->get_status ()); | |
| // suppress threshold setting | |
| // XXX this threshold should probably not be used | |
| sorted_data->set_threshold ((double) 75. / 100.0); | |
| TValue* all_empty = new TValue[no_metrics]; | |
| memset (all_empty, 0, sizeof (TValue) * no_metrics); | |
| Metric *mitem; | |
| Vec_loop (Metric*, mlist->get_items (), index, mitem) | |
| { | |
| // new data items have same total as original items | |
| data_items->total->value[index] = sorted_data->total->value[index]; | |
| // empty metric items need matching types | |
| all_empty[index].tag = mitem->get_vtype (); | |
| if (mitem->get_type () == Metric::ONAME) name_index = index; | |
| if (mitem->get_type () == Metric::ADDRESS) addr_index = index; | |
| } | |
| int64_t next_elem_offset = 0; | |
| for (long i = 0; i < sorted_data->size (); i++) | |
| { | |
| Hist_data::HistItem* ditem = sorted_data->fetch (i); | |
| DataObject* dobj = (DataObject*) (ditem->obj); | |
| if (!dobj->get_parent ()) | |
| { // doesn't have a parent; top level item | |
| next_elem_offset = 0; | |
| if (i > 0) | |
| { // add a blank line as separator | |
| // fixme xxxxx, is it really ok to create a DataObject just for this? | |
| DataObject* empty = new DataObject (); | |
| empty->size = 0; | |
| empty->offset = 0; | |
| empty->set_name (NTXT ("")); | |
| new_item = sorted_data->new_hist_item (empty, Module::AT_EMPTY, all_empty); | |
| new_item->value[name_index].tag = VT_LABEL; | |
| new_item->value[name_index].l = NULL; | |
| data_items->append_hist_item (new_item); | |
| } | |
| // then add the aggregate | |
| new_item = sorted_data->new_hist_item (dobj, Module::AT_SRC, ditem->value); | |
| new_item->value[name_index].tag = VT_OFFSET; | |
| new_item->value[name_index].l = dbe_strdup (dobj->get_name ()); | |
| data_items->append_hist_item (new_item); | |
| } | |
| else | |
| { // is a child | |
| if (dobj->get_parent ()->get_typename ()) | |
| { // typed sub-element that has offset | |
| if (dobj->offset > next_elem_offset) | |
| { // filler entry | |
| // hole in offsets | |
| // fixme xxxxx, is it really ok to create a DataObject just for this? | |
| DataObject* filler = new DataObject (); | |
| filler->set_name (PTXT (DOBJ_ANON)); | |
| filler->size = (dobj->offset - next_elem_offset); | |
| filler->offset = next_elem_offset; | |
| new_item = sorted_data->new_hist_item (filler, Module::AT_EMPTY, all_empty); | |
| new_item->value[name_index].tag = VT_OFFSET; | |
| new_item->value[name_index].l = dbe_strdup (filler->get_offset_name ()); | |
| if (addr_index >= 0) | |
| { | |
| new_item->value[addr_index].tag = VT_ADDRESS; | |
| new_item->value[addr_index].ll = (dobj->get_addr () - filler->size); | |
| } | |
| data_items->append_hist_item (new_item); | |
| } | |
| next_elem_offset = dobj->offset + dobj->size; | |
| } | |
| // then add the aggregate's subelement | |
| if (marks) | |
| if (sorted_data->above_threshold (ditem)) | |
| marks->append (data_items->size ()); | |
| new_item = sorted_data->new_hist_item (dobj, Module::AT_DIS, ditem->value); | |
| new_item->value[name_index].tag = VT_OFFSET; | |
| new_item->value[name_index].l = dbe_strdup (dobj->get_offset_name ()); | |
| data_items->append_hist_item (new_item); | |
| } | |
| } | |
| delete[] all_empty; | |
| return data_items; | |
| } |