From 4a66b5f7f8f9fbaec814d956ac53362a574a8620 Mon Sep 17 00:00:00 2001 From: Giacomo Travaglini Date: Wed, 14 Mar 2018 17:26:06 +0000 Subject: [PATCH] arch-arm: Add support for Tarmac trace generation This patch introduces the TarmacTracer: an instruction tracer which allows to dump a gem5 execution trace in Tarmac format [1]. The new tracer is supporting either Tarmac and TarmacV8 format specifications. Not every traceable information has been implemented: Implemented Trace Type: Instruction Trace Register Trace Processor Memory Access Trace Unimplemented Trace Type: Program Flow Trace Event Trace Memory Bus Trace [1]: https://developer.arm.com/docs/dui0845/f/tarmac-trace-file-format Change-Id: I8799d8e5852e868673f728971db3fe8c63961f5e Signed-off-by: Giacomo Travaglini Reviewed-on: https://gem5-review.googlesource.com/9382 Reviewed-by: Andreas Sandberg Maintainer: Andreas Sandberg --- src/arch/arm/tracers/SConscript | 3 + src/arch/arm/tracers/TarmacTrace.py | 11 + src/arch/arm/tracers/tarmac_record.cc | 457 +++++++++++++++++++++++ src/arch/arm/tracers/tarmac_record.hh | 265 +++++++++++++ src/arch/arm/tracers/tarmac_record_v8.cc | 248 ++++++++++++ src/arch/arm/tracers/tarmac_record_v8.hh | 153 ++++++++ src/arch/arm/tracers/tarmac_tracer.cc | 103 +++++ src/arch/arm/tracers/tarmac_tracer.hh | 132 +++++++ 8 files changed, 1372 insertions(+) create mode 100644 src/arch/arm/tracers/tarmac_record.cc create mode 100644 src/arch/arm/tracers/tarmac_record.hh create mode 100644 src/arch/arm/tracers/tarmac_record_v8.cc create mode 100644 src/arch/arm/tracers/tarmac_record_v8.hh create mode 100644 src/arch/arm/tracers/tarmac_tracer.cc create mode 100644 src/arch/arm/tracers/tarmac_tracer.hh diff --git a/src/arch/arm/tracers/SConscript b/src/arch/arm/tracers/SConscript index 1491e1a62..ad11b0a67 100644 --- a/src/arch/arm/tracers/SConscript +++ b/src/arch/arm/tracers/SConscript @@ -42,3 +42,6 @@ if env['TARGET_ISA'] == 'arm': SimObject('TarmacTrace.py') Source('tarmac_base.cc') Source('tarmac_parser.cc') + Source('tarmac_tracer.cc') + Source('tarmac_record.cc') + Source('tarmac_record_v8.cc') diff --git a/src/arch/arm/tracers/TarmacTrace.py b/src/arch/arm/tracers/TarmacTrace.py index c2bcbb3c2..8955fadd6 100644 --- a/src/arch/arm/tracers/TarmacTrace.py +++ b/src/arch/arm/tracers/TarmacTrace.py @@ -64,3 +64,14 @@ class TarmacParser(InstTracer): ignore_mem_addr = Param.AddrRange(AddrRange(0, size=0), "Range of unverifiable memory addresses") + +class TarmacTracer(InstTracer): + type = 'TarmacTracer' + cxx_class = 'Trace::TarmacTracer' + cxx_header = "arch/arm/tracers/tarmac_tracer.hh" + + start_tick = Param.Tick(0, + "tracing starts when the tick time gets this value") + + end_tick = Param.Tick(MaxTick, + "tracing ends when the tick time gets this value") diff --git a/src/arch/arm/tracers/tarmac_record.cc b/src/arch/arm/tracers/tarmac_record.cc new file mode 100644 index 000000000..7034d2586 --- /dev/null +++ b/src/arch/arm/tracers/tarmac_record.cc @@ -0,0 +1,457 @@ +/* + * Copyright (c) 2017-2018 ARM Limited + * All rights reserved + * + * The license below extends only to copyright in the software and shall + * not be construed as granting a license to any other intellectual + * property including but not limited to intellectual property relating + * to a hardware implementation of the functionality of the software + * licensed hereunder. You may use the software subject to the license + * terms below provided that you ensure that this notice is replicated + * unmodified and in its entirety in all distributions of the software, + * modified or unmodified, in source code or in binary form. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer; + * redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution; + * neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Authors: Giacomo Travaglini + */ + +#include "arch/arm/tracers/tarmac_record.hh" + +#include "arch/arm/insts/static_inst.hh" +#include "tarmac_tracer.hh" + +namespace Trace { + +// TARMAC Instruction Record static variables +uint64_t TarmacTracerRecord::TraceInstEntry::instCount = 0; + +std::string +iSetStateToStr(TarmacBaseRecord::ISetState isetstate) +{ + switch (isetstate) { + case TarmacBaseRecord::ISET_ARM: + return "A"; + case TarmacBaseRecord::ISET_THUMB: + return "T"; + case TarmacBaseRecord::ISET_A64: + return "O"; + default: + return "Unsupported"; + } +} + +std::string +opModeToStr(OperatingMode opMode) +{ + switch (opMode) { + case MODE_EL0T: + return "EL0t"; + case MODE_EL1T: + return "EL1t"; + case MODE_EL1H: + return "EL1h"; + case MODE_EL2T: + return "EL2t"; + case MODE_EL2H: + return "EL2h"; + case MODE_EL3T: + return "EL3t"; + case MODE_EL3H: + return "EL3h"; + case MODE_USER: + return "usr"; + case MODE_FIQ: + return "fiq"; + case MODE_IRQ: + return "irq"; + case MODE_SVC: + return "svc"; + case MODE_MON: + return "mon"; + case MODE_ABORT: + return "abt"; + case MODE_HYP: + return "hyp"; + case MODE_UNDEFINED: + return "und"; + case MODE_SYSTEM: + return "sys"; + default: + return "Unsupported"; + } +} + +// TarmacTracerRecord ctor +TarmacTracerRecord::TarmacTracerRecord(Tick _when, ThreadContext *_thread, + const StaticInstPtr _staticInst, + PCState _pc, + TarmacTracer& _tracer, + const StaticInstPtr _macroStaticInst) + : TarmacBaseRecord(_when, _thread, _staticInst, + _pc, _macroStaticInst), + tracer(_tracer) +{ +} + +TarmacTracerRecord::TraceInstEntry::TraceInstEntry( + const TarmacContext& tarmCtx, + bool predicate) + : InstEntry(tarmCtx.thread, tarmCtx.pc, tarmCtx.staticInst, predicate) +{ + secureMode = inSecureState(tarmCtx.thread); + + auto arm_inst = static_cast( + tarmCtx.staticInst.get() + ); + + // Get the instruction size as a number of bits: + // (multiply byte size by 8) + instSize = (arm_inst->instSize() << 3); + + // Mask the opcode using the instruction size: the + // opcode field will otherwise be 32 bit wide even + // for 16bit (Thumb) instruction. + opcode = arm_inst->encoding(); + + // Update the instruction count: number of executed + // instructions. + instCount++; +} + +TarmacTracerRecord::TraceMemEntry::TraceMemEntry( + const TarmacContext& tarmCtx, + uint8_t _size, Addr _addr, uint64_t _data) + : MemEntry(_size, _addr, _data), + loadAccess(tarmCtx.staticInst->isLoad()) +{ +} + +TarmacTracerRecord::TraceRegEntry::TraceRegEntry( + const TarmacContext& tarmCtx, + const RegId& reg) + : RegEntry(tarmCtx.pc), + regValid(false), + regClass(reg.classValue()), + regRel(reg.index()) +{ +} + +void +TarmacTracerRecord::TraceRegEntry::update( + const TarmacContext& tarmCtx +) +{ + // Fill the register entry data, according to register + // class. + switch (regClass) { + case CCRegClass: + updateCC(tarmCtx, regRel); + break; + case FloatRegClass: + updateFloat(tarmCtx, regRel); + break; + case IntRegClass: + updateInt(tarmCtx, regRel); + break; + case MiscRegClass: + updateMisc(tarmCtx, regRel); + break; + default: + // If unsupported format, do nothing: non updating + // the register will prevent it to be printed. + break; + } +} + +void +TarmacTracerRecord::TraceRegEntry::updateMisc( + const TarmacContext& tarmCtx, + RegIndex regRelIdx +) +{ + auto thread = tarmCtx.thread; + + regValid = true; + regName = miscRegName[regRelIdx]; + valueLo = thread->readMiscRegNoEffect(regRelIdx); + + // If it is the CPSR: + // update the value of the CPSR register and add + // the CC flags on top of the value + if (regRelIdx == MISCREG_CPSR) { + CPSR cpsr = thread->readMiscRegNoEffect(MISCREG_CPSR); + cpsr.nz = thread->readCCReg(CCREG_NZ); + cpsr.c = thread->readCCReg(CCREG_C); + cpsr.v = thread->readCCReg(CCREG_V); + cpsr.ge = thread->readCCReg(CCREG_GE); + + // update the entry value + valueLo = cpsr; + } +} + +void +TarmacTracerRecord::TraceRegEntry::updateCC( + const TarmacContext& tarmCtx, + RegIndex regRelIdx +) +{ + auto thread = tarmCtx.thread; + + regValid = true; + regName = ccRegName[regRelIdx]; + valueLo = thread->readCCReg(regRelIdx); +} + +void +TarmacTracerRecord::TraceRegEntry::updateFloat( + const TarmacContext& tarmCtx, + RegIndex regRelIdx +) +{ + auto thread = tarmCtx.thread; + + regValid = true; + regName = "f" + std::to_string(regRelIdx); + valueLo = thread->readFloatReg(regRelIdx); +} + +void +TarmacTracerRecord::TraceRegEntry::updateInt( + const TarmacContext& tarmCtx, + RegIndex regRelIdx +) +{ + auto thread = tarmCtx.thread; + + // Reading operating mode from CPSR. + // This is needed when printing the register name in case + // of banked register (e.g. lr_svc) + CPSR cpsr = thread->readMiscRegNoEffect(MISCREG_CPSR); + OperatingMode mode = (OperatingMode)(uint8_t)cpsr.mode; + + std::string reg_suffix; + if (mode != MODE_USER) { + reg_suffix = "_" + opModeToStr(mode); + } + + regValid = true; + switch (regRelIdx) { + case PCReg: + regName = "pc"; + break; + case StackPointerReg: + regName = "sp" + reg_suffix ; + break; + case FramePointerReg: + regName = "fp" + reg_suffix; + break; + case ReturnAddressReg: + regName = "lr" + reg_suffix; + break; + default: + regName = "r" + std::to_string(regRelIdx); + break; + } + valueLo = thread->readIntReg(regRelIdx); +} + +void +TarmacTracerRecord::addInstEntry(std::vector& queue, + const TarmacContext& tarmCtx) +{ + // Generate an instruction entry in the record and + // add it to the Instruction Queue + queue.push_back( + m5::make_unique(tarmCtx, predicate) + ); +} + +void +TarmacTracerRecord::addMemEntry(std::vector& queue, + const TarmacContext& tarmCtx) +{ + // Generate a memory entry in the record if the record + // implies a valid memory access, and add it to the + // Memory Queue + if (getMemValid()) { + queue.push_back( + m5::make_unique(tarmCtx, + static_cast(getSize()), + getAddr(), getIntData()) + ); + } +} + +void +TarmacTracerRecord::addRegEntry(std::vector& queue, + const TarmacContext& tarmCtx) +{ + // Generate an entry for every ARM register being + // written by the current instruction + for (auto reg = 0; reg < staticInst->numDestRegs(); ++reg) { + + RegId reg_id = staticInst->destRegIdx(reg); + + // Creating a single register change entry + auto single_reg = genRegister(tarmCtx, reg_id); + + // Copying the entry and adding it to the "list" + // of entries to be dumped to trace. + queue.push_back( + m5::make_unique(single_reg) + ); + } + + // Gem5 is treating CPSR flags as separate registers (CC registers), + // in contrast with Tarmac specification: we need to merge the gem5 CC + // entries altogether with the CPSR register and produce a single entry. + mergeCCEntry(queue, tarmCtx); +} + +void +TarmacTracerRecord::dump() +{ + // Generate and print all the record's entries. + auto &instQueue = tracer.instQueue; + auto &memQueue = tracer.memQueue; + auto ®Queue = tracer.regQueue; + + const TarmacContext tarmCtx( + thread, + staticInst->isMicroop()? macroStaticInst : staticInst, + pc + ); + + if (!staticInst->isMicroop()) { + // Current instruction is NOT a micro-instruction: + // Generate Tarmac entries and dump them immediately + + // Generate Tarmac entries and add them to the respective + // queues. + addInstEntry(instQueue, tarmCtx); + addMemEntry(memQueue, tarmCtx); + addRegEntry(regQueue, tarmCtx); + + // Flush (print) any queued entry. + flushQueues(instQueue, memQueue, regQueue); + + } else { + // Current instruction is a micro-instruction: + // save micro entries into dedicated queues and flush them + // into the tracefile only when the MACRO-instruction + // has completed. + + if (staticInst->isFirstMicroop()) { + addInstEntry(instQueue, tarmCtx); + } + + addRegEntry(regQueue, tarmCtx); + addMemEntry(memQueue, tarmCtx); + + if (staticInst->isLastMicroop()) { + // Flush (print) any queued entry. + flushQueues(instQueue, memQueue, regQueue); + } + } +} + +template +void +TarmacTracerRecord::flushQueues(Queue& queue) +{ + std::ostream &outs = Trace::output(); + + for (const auto &single_entry : queue) { + single_entry->print(outs); + } + + queue.clear(); +} + +template +void +TarmacTracerRecord::flushQueues(Queue& queue, Args & ... args) +{ + flushQueues(queue); + flushQueues(args...); +} + +void +TarmacTracerRecord::TraceInstEntry::print( + std::ostream& outs, + int verbosity, + const std::string &prefix) const +{ + // Pad the opcode + std::string opcode_str = csprintf("%0*x", instSize >> 2, opcode); + + // Print the instruction record formatted according + // to the Tarmac specification + ccprintf(outs, "%s clk %s (%u) %08x %s %s %s_%s : %s\n", + curTick(), /* Tick time */ + taken? "IT" : "IS", /* Instruction taken/skipped */ + instCount, /* Instruction count */ + addr, /* Instruction address */ + opcode_str, /* Instruction opcode */ + iSetStateToStr(isetstate), /* Instruction Set */ + opModeToStr(mode), /* Exception level */ + secureMode? "s" : "ns", /* Security */ + disassemble); /* Instruction disass */ +} + +void +TarmacTracerRecord::TraceMemEntry::print( + std::ostream& outs, + int verbosity, + const std::string &prefix) const +{ + // Print the memory record formatted according + // to the Tarmac specification + ccprintf(outs, "%s clk M%s%d %08x %0*x\n", + curTick(), /* Tick time */ + loadAccess? "R" : "W", /* Access type */ + size, /* Access size */ + addr, /* Memory address */ + size*2, /* Padding with access size */ + data); /* Memory data */ +} + +void +TarmacTracerRecord::TraceRegEntry::print( + std::ostream& outs, + int verbosity, + const std::string &prefix) const +{ + // Print the register record formatted according + // to the Tarmac specification + if (regValid) + ccprintf(outs, "%s clk R %s %08x\n", + curTick(), /* Tick time */ + regName, /* Register name */ + valueLo); /* Register value */ +} + +} // namespace Trace diff --git a/src/arch/arm/tracers/tarmac_record.hh b/src/arch/arm/tracers/tarmac_record.hh new file mode 100644 index 000000000..f54abf56a --- /dev/null +++ b/src/arch/arm/tracers/tarmac_record.hh @@ -0,0 +1,265 @@ +/* + * Copyright (c) 2017-2018 ARM Limited + * All rights reserved + * + * The license below extends only to copyright in the software and shall + * not be construed as granting a license to any other intellectual + * property including but not limited to intellectual property relating + * to a hardware implementation of the functionality of the software + * licensed hereunder. You may use the software subject to the license + * terms below provided that you ensure that this notice is replicated + * unmodified and in its entirety in all distributions of the software, + * modified or unmodified, in source code or in binary form. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer; + * redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution; + * neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Authors: Giacomo Travaglini + */ + +/** + * @file: The file contains the informations used to generate records + * for the pre-ARMv8 cores. + */ + +#ifndef __ARCH_ARM_TRACERS_TARMAC_RECORD_HH__ +#define __ARCH_ARM_TRACERS_TARMAC_RECORD_HH__ + +#include "arch/arm/tracers/tarmac_base.hh" +#include "base/printable.hh" +#include "config/the_isa.hh" +#include "cpu/reg_class.hh" +#include "cpu/static_inst.hh" + +namespace Trace { + +class TarmacContext; + +class TarmacTracer; + +/** + * Returns the string representation of the instruction set being + * currently run according to the Tarmac format. + * + * @param isetstate: enum variable (ISetState) specifying an ARM + * instruction set. + * @return instruction set in string format. + */ +std::string +iSetStateToStr(TarmacBaseRecord::ISetState isetstate); + +/** + * Returns the string representation of the ARM Operating Mode + * (CPSR.M[3:0] field) according to the Tarmac format. + * + * @param opMode: ARM operating mode. + * @return operating mode in string format. + */ +std::string +opModeToStr(ArmISA::OperatingMode opMode); + +/** + * TarmacTracer Record: + * Record generated by the TarmacTracer for every executed instruction. + * The record is composed by a set of entries, matching the tracing + * capabilities provided by the Tarmac specifications: + * + * - Instruction Entry + * - Register Entry + * - Memory Entry + */ +class TarmacTracerRecord : public TarmacBaseRecord +{ + public: + /** Instruction Entry */ + struct TraceInstEntry: public InstEntry, Printable + { + TraceInstEntry(const TarmacContext& tarmCtx, bool predicate); + + virtual void print(std::ostream& outs, + int verbosity = 0, + const std::string &prefix = "") const override; + + protected: + /** Number of instructions being traced */ + static uint64_t instCount; + + /** True if instruction is executed in secure mode */ + bool secureMode; + /** + * Instruction size: + * 16 for 16-bit Thumb Instruction + * 32 otherwise (ARM and BigThumb) + */ + uint8_t instSize; + }; + + /** Register Entry */ + struct TraceRegEntry: public RegEntry, Printable + { + public: + TraceRegEntry(const TarmacContext& tarmCtx, const RegId& reg); + + /** + * This updates the register entry using the update table. It is + * a required step after the register entry generation. + * If unupdated, the entry will be marked as invalid. + * The entry update cannot be done automatically at TraceRegEntry + * construction: the entries are extended by consequent Tarmac + * Tracer versions (like V8), and virtual functions should + * be avoided during construction. + */ + void update(const TarmacContext& tarmCtx); + + virtual void print(std::ostream& outs, + int verbosity = 0, + const std::string &prefix = "") const override; + + protected: + /** Register update functions. */ + virtual void + updateMisc(const TarmacContext& tarmCtx, RegIndex regRelIdx); + + virtual void + updateCC(const TarmacContext& tarmCtx, RegIndex regRelIdx); + + virtual void + updateFloat(const TarmacContext& tarmCtx, RegIndex regRelIdx); + + virtual void + updateInt(const TarmacContext& tarmCtx, RegIndex regRelIdx); + + public: + /** True if register entry is valid */ + bool regValid; + /** Register class */ + RegClass regClass; + /** Register arch number */ + RegIndex regRel; + /** Register name to be printed */ + std::string regName; + }; + + /** Memory Entry */ + struct TraceMemEntry: public MemEntry, Printable + { + public: + TraceMemEntry(const TarmacContext& tarmCtx, + uint8_t _size, Addr _addr, uint64_t _data); + + virtual void print(std::ostream& outs, + int verbosity = 0, + const std::string &prefix = "") const override; + + protected: + /** True if memory access is a load */ + bool loadAccess; + }; + + public: + TarmacTracerRecord(Tick _when, ThreadContext *_thread, + const StaticInstPtr _staticInst, TheISA::PCState _pc, + TarmacTracer& _tracer, + const StaticInstPtr _macroStaticInst = NULL); + + virtual void dump() override; + + using InstPtr = std::unique_ptr; + using MemPtr = std::unique_ptr; + using RegPtr = std::unique_ptr; + + protected: + /** Generates an Entry for the executed instruction. */ + virtual void addInstEntry(std::vector& queue, + const TarmacContext& ptr); + + /** Generates an Entry for every triggered memory access */ + virtual void addMemEntry(std::vector& queue, + const TarmacContext& ptr); + + /** Generate an Entry for every register being written. */ + virtual void addRegEntry(std::vector& queue, + const TarmacContext& ptr); + + protected: + /** Generate and update a register entry. */ + template + RegEntry + genRegister(const TarmacContext& tarmCtx, const RegId& reg) + { + RegEntry single_reg(tarmCtx, reg); + single_reg.update(tarmCtx); + + return single_reg; + } + + template + void + mergeCCEntry(std::vector& queue, const TarmacContext& tarmCtx) + { + // Find all CC Entries and move them at the end of the queue + auto it = std::remove_if( + queue.begin(), queue.end(), + [] (RegPtr& reg) ->bool { return (reg->regClass == CCRegClass); } + ); + + if (it != queue.end()) { + // Remove all CC Entries. + queue.erase(it, queue.end()); + + auto is_cpsr = [] (RegPtr& reg) ->bool + { + return (reg->regClass == MiscRegClass) && + (reg->regRel == ArmISA::MISCREG_CPSR); + }; + + // Looking for the presence of a CPSR register entry. + auto cpsr_it = std::find_if( + queue.begin(), queue.end(), is_cpsr + ); + + // If CPSR entry not present, generate one + if (cpsr_it == queue.end()) { + RegId reg(MiscRegClass, ArmISA::MISCREG_CPSR); + queue.push_back( + m5::make_unique( + genRegister(tarmCtx, reg)) + ); + } + } + } + + /** Flush queues to the trace output */ + template + void flushQueues(Queue& queue); + template + void flushQueues(Queue& queue, Args & ... args); + + protected: + /** Reference to tracer */ + TarmacTracer& tracer; +}; + +} // namespace Trace + +#endif // __ARCH_ARM_TRACERS_TARMAC_RECORD_HH__ diff --git a/src/arch/arm/tracers/tarmac_record_v8.cc b/src/arch/arm/tracers/tarmac_record_v8.cc new file mode 100644 index 000000000..dea040992 --- /dev/null +++ b/src/arch/arm/tracers/tarmac_record_v8.cc @@ -0,0 +1,248 @@ +/* + * Copyright (c) 2017-2018 ARM Limited + * All rights reserved + * + * The license below extends only to copyright in the software and shall + * not be construed as granting a license to any other intellectual + * property including but not limited to intellectual property relating + * to a hardware implementation of the functionality of the software + * licensed hereunder. You may use the software subject to the license + * terms below provided that you ensure that this notice is replicated + * unmodified and in its entirety in all distributions of the software, + * modified or unmodified, in source code or in binary form. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer; + * redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution; + * neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Authors: Giacomo Travaglini + */ + +#include "arch/arm/tracers/tarmac_record_v8.hh" + +#include "arch/arm/insts/static_inst.hh" +#include "arch/arm/tlb.hh" +#include "arch/arm/tracers/tarmac_tracer.hh" + +namespace Trace { + +TarmacTracerRecordV8::TraceInstEntryV8::TraceInstEntryV8( + const TarmacContext& tarmCtx, + bool predicate) + : TraceInstEntry(tarmCtx, predicate), + TraceEntryV8(tarmCtx.tarmacCpuName()), + paddr(0), + paddrValid(false) +{ + const auto thread = tarmCtx.thread; + + // Evaluate physical address + TheISA::TLB* dtb = static_cast(thread->getDTBPtr()); + paddrValid = dtb->translateFunctional(thread, addr, paddr); +} + +TarmacTracerRecordV8::TraceMemEntryV8::TraceMemEntryV8( + const TarmacContext& tarmCtx, + uint8_t _size, Addr _addr, uint64_t _data) + : TraceMemEntry(tarmCtx, _size, _addr, _data), + TraceEntryV8(tarmCtx.tarmacCpuName()), + paddr(_addr) +{ + const auto thread = tarmCtx.thread; + + // Evaluate physical address + TheISA::TLB* dtb = static_cast(thread->getDTBPtr()); + dtb->translateFunctional(thread, addr, paddr); +} + +TarmacTracerRecordV8::TraceRegEntryV8::TraceRegEntryV8( + const TarmacContext& tarmCtx, + const RegId& reg) + : TraceRegEntry(tarmCtx, reg), + TraceEntryV8(tarmCtx.tarmacCpuName()), + regWidth(64) +{ +} + +void +TarmacTracerRecordV8::TraceRegEntryV8::updateInt( + const TarmacContext& tarmCtx, + RegIndex regRelIdx +) +{ + // Do not trace pseudo register accesses: invalid + // register entry. + if (regRelIdx > NUM_ARCH_INTREGS) { + regValid = false; + return; + } + + TraceRegEntry::updateInt(tarmCtx, regRelIdx); + + if ((regRelIdx != PCReg) || (regRelIdx != StackPointerReg) || + (regRelIdx != FramePointerReg) || (regRelIdx != ReturnAddressReg)) { + + const auto* arm_inst = static_cast( + tarmCtx.staticInst.get() + ); + + regWidth = (arm_inst->getIntWidth()); + if (regWidth == 32) { + regName = "W" + std::to_string(regRelIdx); + } else { + regName = "X" + std::to_string(regRelIdx); + } + } +} + +void +TarmacTracerRecordV8::TraceRegEntryV8::updateMisc( + const TarmacContext& tarmCtx, + RegIndex regRelIdx +) +{ + TraceRegEntry::updateMisc(tarmCtx, regRelIdx); + // System registers are 32bit wide + regWidth = 32; +} + +void +TarmacTracerRecordV8::addInstEntry(std::vector& queue, + const TarmacContext& tarmCtx) +{ + // Generate an instruction entry in the record and + // add it to the Instruction Queue + queue.push_back( + m5::make_unique(tarmCtx, predicate) + ); +} + +void +TarmacTracerRecordV8::addMemEntry(std::vector& queue, + const TarmacContext& tarmCtx) +{ + // Generate a memory entry in the record if the record + // implies a valid memory access, and add it to the + // Memory Queue + if (getMemValid()) { + queue.push_back( + m5::make_unique(tarmCtx, + static_cast(getSize()), + getAddr(), getIntData()) + ); + } +} + +void +TarmacTracerRecordV8::addRegEntry(std::vector& queue, + const TarmacContext& tarmCtx) +{ + // Generate an entry for every ARM register being + // written by the current instruction + for (auto reg = 0; reg < staticInst->numDestRegs(); ++reg) { + + RegId reg_id = staticInst->destRegIdx(reg); + + // Creating a single register change entry + auto single_reg = genRegister(tarmCtx, reg_id); + + // Copying the entry and adding it to the "list" + // of entries to be dumped to trace. + queue.push_back( + m5::make_unique(single_reg) + ); + } + + // Gem5 is treating CPSR flags as separate registers (CC registers), + // in contrast with Tarmac specification: we need to merge the gem5 CC + // entries altogether with the CPSR register and produce a single entry. + mergeCCEntry(queue, tarmCtx); +} + +void +TarmacTracerRecordV8::TraceInstEntryV8::print( + std::ostream& outs, + int verbosity, + const std::string &prefix) const +{ + // If there is a valid vaddr->paddr translation, print the + // physical address, otherwise print the virtual address only. + std::string paddr_str = paddrValid? csprintf(":%012x",paddr) : + std::string(); + + // Pad the opcode. + std::string opcode_str = csprintf("%0*x", instSize >> 2, opcode); + + // Print the instruction record formatted according + // to the Tarmac specification + ccprintf(outs, "%s clk %s %s (%u) %08x%s %s %s %s_%s : %s\n", + curTick(), /* Tick time */ + cpuName, /* Cpu name */ + taken? "IT" : "IS", /* Instruction taken/skipped */ + instCount, /* Instruction count */ + addr, /* Instruction virt address */ + paddr_str, /* Instruction phys address */ + opcode_str, /* Instruction opcode */ + iSetStateToStr(isetstate), /* Instruction Set */ + opModeToStr(mode), /* Exception level */ + secureMode? "s" : "ns", /* Security */ + disassemble); /* Instruction disass */ +} + +void +TarmacTracerRecordV8::TraceMemEntryV8::print( + std::ostream& outs, + int verbosity, + const std::string &prefix) const +{ + // Print the memory record formatted according + // to the Tarmac specification + ccprintf(outs, "%s clk %s M%s%d %08x:%012x %0*x\n", + curTick(), /* Tick time */ + cpuName, /* Cpu name */ + loadAccess? "R" : "W", /* Access type */ + size, /* Access size */ + addr, /* Virt Memory address */ + paddr, /* Phys Memory address */ + size*2, /* Padding with access size */ + data); /* Memory data */ +} + +void +TarmacTracerRecordV8::TraceRegEntryV8::print( + std::ostream& outs, + int verbosity, + const std::string &prefix) const +{ + // Print the register record formatted according + // to the Tarmac specification + if (regValid) { + ccprintf(outs, "%s clk %s R %s %0*x\n", + curTick(), /* Tick time */ + cpuName, /* Cpu name */ + regName, /* Register name */ + regWidth >> 2, /* Register value padding */ + valueLo); /* Register value */ + } +} + +} // namespace Trace diff --git a/src/arch/arm/tracers/tarmac_record_v8.hh b/src/arch/arm/tracers/tarmac_record_v8.hh new file mode 100644 index 000000000..adf638f99 --- /dev/null +++ b/src/arch/arm/tracers/tarmac_record_v8.hh @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2017-2018 ARM Limited + * All rights reserved + * + * The license below extends only to copyright in the software and shall + * not be construed as granting a license to any other intellectual + * property including but not limited to intellectual property relating + * to a hardware implementation of the functionality of the software + * licensed hereunder. You may use the software subject to the license + * terms below provided that you ensure that this notice is replicated + * unmodified and in its entirety in all distributions of the software, + * modified or unmodified, in source code or in binary form. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer; + * redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution; + * neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Authors: Giacomo Travaglini + */ + +/** + * @file: The file contains the informations used to generate records + * for ARMv8 cores. + */ + +#ifndef __ARCH_ARM_TRACERS_TARMAC_RECORD_V8_HH__ +#define __ARCH_ARM_TRACERS_TARMAC_RECORD_V8_HH__ + +#include "tarmac_record.hh" + +namespace Trace { + +/** + * TarmacTracer record for ARMv8 CPUs: + * The record is adding some data to the base TarmacTracer + * record. + */ +class TarmacTracerRecordV8 : public TarmacTracerRecord +{ + public: + + /** + * General data shared by all v8 entries + */ + struct TraceEntryV8 + { + public: + TraceEntryV8(std::string _cpuName) + : cpuName(_cpuName) + {} + + protected: + std::string cpuName; + }; + + /** + * Instruction entry for v8 records + */ + struct TraceInstEntryV8: public TraceInstEntry, TraceEntryV8 + { + public: + TraceInstEntryV8(const TarmacContext& tarmCtx, bool predicate); + + virtual void print(std::ostream& outs, + int verbosity = 0, + const std::string &prefix = "") const override; + + protected: + Addr paddr; + bool paddrValid; + }; + + /** + * Register entry for v8 records + */ + struct TraceRegEntryV8: public TraceRegEntry, TraceEntryV8 + { + public: + TraceRegEntryV8(const TarmacContext& tarmCtx, const RegId& reg); + + virtual void print(std::ostream& outs, + int verbosity = 0, + const std::string &prefix = "") const override; + + protected: + void updateInt(const TarmacContext& tarmCtx, + RegIndex regRelIdx) override; + + void updateMisc(const TarmacContext& tarmCtx, + RegIndex regRelIdx) override; + + uint8_t regWidth; + }; + + /** + * Memory Entry for V8 + */ + struct TraceMemEntryV8: public TraceMemEntry, TraceEntryV8 + { + public: + TraceMemEntryV8(const TarmacContext& tarmCtx, + uint8_t _size, Addr _addr, uint64_t _data); + + virtual void print(std::ostream& outs, + int verbosity = 0, + const std::string &prefix = "") const override; + + protected: + Addr paddr; + }; + + public: + TarmacTracerRecordV8(Tick _when, ThreadContext *_thread, + const StaticInstPtr _staticInst, TheISA::PCState _pc, + TarmacTracer& _parent, + const StaticInstPtr _macroStaticInst = NULL) + : TarmacTracerRecord(_when, _thread, _staticInst, _pc, + _parent, _macroStaticInst) + {} + + protected: + /** Generates an Entry for the executed instruction. */ + void addInstEntry(std::vector& queue, const TarmacContext& ptr); + + /** Generates an Entry for every memory access triggered */ + void addMemEntry(std::vector& queue, const TarmacContext& ptr); + + /** Generate a Record for every register being written */ + void addRegEntry(std::vector& queue, const TarmacContext& ptr); +}; + +} // namespace Trace + +#endif // __ARCH_ARM_TRACERS_TARMAC_RECORD_V8_HH__ diff --git a/src/arch/arm/tracers/tarmac_tracer.cc b/src/arch/arm/tracers/tarmac_tracer.cc new file mode 100644 index 000000000..b6f876de5 --- /dev/null +++ b/src/arch/arm/tracers/tarmac_tracer.cc @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2017-2018 ARM Limited + * All rights reserved + * + * The license below extends only to copyright in the software and shall + * not be construed as granting a license to any other intellectual + * property including but not limited to intellectual property relating + * to a hardware implementation of the functionality of the software + * licensed hereunder. You may use the software subject to the license + * terms below provided that you ensure that this notice is replicated + * unmodified and in its entirety in all distributions of the software, + * modified or unmodified, in source code or in binary form. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer; + * redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution; + * neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Authors: Giacomo Travaglini + */ + +#include "tarmac_tracer.hh" + +#include + +#include "arch/arm/system.hh" +#include "cpu/base.hh" + +namespace Trace { + +std::string +TarmacContext::tarmacCpuName() const +{ + auto id = thread->getCpuPtr()->cpuId(); + return "cpu" + std::to_string(id); +} + +TarmacTracer::TarmacTracer(const Params *p) + : InstTracer(p), + startTick(p->start_tick), + endTick(p->end_tick) +{ + // Wrong parameter setting: The trace end happens before the + // trace start. + panic_if(startTick > endTick, + "Tarmac start point: %lu is bigger than " + "Tarmac end point: %lu\n", startTick, endTick); + + // By default cpu tracers in gem5 are not tracing faults + // (exceptions). + // This is not in compliance with the Tarmac specification: + // instructions like SVC, SMC, HVC have to be traced. + // Tarmac Tracer is then automatically enabling this behaviour. + setDebugFlag("ExecFaulting"); +} + +InstRecord * +TarmacTracer::getInstRecord(Tick when, ThreadContext *tc, + const StaticInstPtr staticInst, + TheISA::PCState pc, + const StaticInstPtr macroStaticInst) +{ + // Check if we need to start tracing since we have passed the + // tick start point. + if (when < startTick || when > endTick) + return nullptr; + + if (ArmSystem::highestELIs64(tc)) { + // TarmacTracerV8 + return new TarmacTracerRecordV8(when, tc, staticInst, pc, *this, + macroStaticInst); + } else { + // TarmacTracer + return new TarmacTracerRecord(when, tc, staticInst, pc, *this, + macroStaticInst); + } +} + +} // namespace Trace + +Trace::TarmacTracer * +TarmacTracerParams::create() +{ + return new Trace::TarmacTracer(this); +} diff --git a/src/arch/arm/tracers/tarmac_tracer.hh b/src/arch/arm/tracers/tarmac_tracer.hh new file mode 100644 index 000000000..78739999d --- /dev/null +++ b/src/arch/arm/tracers/tarmac_tracer.hh @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2017-2018 ARM Limited + * All rights reserved + * + * The license below extends only to copyright in the software and shall + * not be construed as granting a license to any other intellectual + * property including but not limited to intellectual property relating + * to a hardware implementation of the functionality of the software + * licensed hereunder. You may use the software subject to the license + * terms below provided that you ensure that this notice is replicated + * unmodified and in its entirety in all distributions of the software, + * modified or unmodified, in source code or in binary form. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer; + * redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution; + * neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Authors: Giacomo Travaglini + */ + +/** + * @file: This file declares the interface of the Tarmac Tracer: + * the tracer based on the Tarmac specification. + */ + +#ifndef __ARCH_ARM_TRACERS_TARMAC_TRACER_HH__ +#define __ARCH_ARM_TRACERS_TARMAC_TRACER_HH__ + +#include "arch/arm/tracers/tarmac_record.hh" +#include "arch/arm/tracers/tarmac_record_v8.hh" +#include "params/TarmacTracer.hh" +#include "sim/insttracer.hh" + +class ThreadContext; + +namespace Trace { + +/** + * This object type is encapsulating the informations needed by + * a Tarmac record to generate it's own entries. + */ +class TarmacContext +{ + public: + TarmacContext(ThreadContext* _thread, + const StaticInstPtr _staticInst, + TheISA::PCState _pc) + : thread(_thread), staticInst(_staticInst), pc(_pc) + {} + + std::string tarmacCpuName() const; + + public: + ThreadContext* thread; + const StaticInstPtr staticInst; + TheISA::PCState pc; +}; + +/** + * Tarmac Tracer: this tracer generates a new Tarmac Record for + * every instruction being executed in gem5. + * The record is made by a collection of entries which are stored + * in the tracer queues. + */ +class TarmacTracer : public InstTracer +{ + friend class TarmacTracerRecord; + friend class TarmacTracerRecordV8; + + public: + typedef TarmacTracerParams Params; + + TarmacTracer(const Params *p); + + /** + * Generates a TarmacTracerRecord, depending on the Tarmac version. + * At the moment supported formats are: + * - Tarmac + * - TarmacV8 + */ + InstRecord* getInstRecord(Tick when, ThreadContext *tc, + const StaticInstPtr staticInst, + TheISA::PCState pc, + const StaticInstPtr macroStaticInst = NULL); + + protected: + typedef std::unique_ptr PEntryPtr; + typedef TarmacTracerRecord::InstPtr InstPtr; + typedef TarmacTracerRecord::MemPtr MemPtr; + typedef TarmacTracerRecord::RegPtr RegPtr; + + /** + * startTick and endTick allow to trace a specific window of ticks + * rather than the entire CPU execution. + */ + Tick startTick; + Tick endTick; + + /** + * Collection of heterogeneous printable entries: could be + * representing either instructions, register or memory entries. + * When dealing with MacroInstructions the following separate queues + * will be used. micro-instruction entries will be buffered and + * dumped to the tracefile only at the end of the Macro. + */ + std::vector instQueue; + std::vector memQueue; + std::vector regQueue; +}; + +} // namespace Trace + +#endif // __ARCH_ARM_TRACERS_TARMAC_TRACER_HH__ -- 2.30.2