Trace Cpu

Trace Creation

Trace creation example: Create trace after the paddr is ready in the packet.

in src/cpu/o3/probe/elastic_trace.cc: ElasticTrace::fetchReqTrace.

// src/cpu/o3/probe/elastic_trace.cc

// Create a protobuf message including the request fields necessary to
// recreate the request in the TraceCPU.
ProtoMessage::Packet inst_fetch_pkt;
inst_fetch_pkt.set_tick(curTick());
inst_fetch_pkt.set_cmd(MemCmd::ReadReq);
inst_fetch_pkt.set_pc(req->getPC());
inst_fetch_pkt.set_flags(req->getFlags());
inst_fetch_pkt.set_addr(req->getPaddr());
inst_fetch_pkt.set_size(req->getSize());
// Write the message to the stream.
instTraceStream->write(inst_fetch_pkt);

Trace reading

Trace read example: read one record from trace file and create packet for replay.

FixedRetryGen class is used to read instruction fetching memory traces, and ElasticDataGen class is used to read data access memory traces.

TraceCPU::schedIcacheNext() is called as an event EventFunctionWrapper icacheNextEvent. This event is called/scheduled during the initialization of the instruction fetch trace file in TraceCPU::init().

TraceCPU::schedIcacheNext() will call icacheGen.tryNext(), which is defined as TraceCPU::FixedRetryGen::tryNext(). This method will read a new record from the trace file (via nextExecute()) and compose a packet from the trace and send it down to the icache port (by calling send().

Related code shown below:

// src/cpu/trace/trace_cpu.cc

bool
TraceCPU::FixedRetryGen::tryNext()
{
    // If there is a retry packet, try to send it
    if (retryPkt) {

        DPRINTF(TraceCPUInst, "Trying to send retry packet.\n");

        if (!port.sendTimingReq(retryPkt)) {
            // Still blocked! This should never occur.
            DPRINTF(TraceCPUInst, "Retry packet sending failed.\n");
            return false;
        }
        ++numRetrySucceeded;
    } else {

        DPRINTF(TraceCPUInst, "Trying to send packet for currElement.\n");

        // try sending current element
        assert(currElement.isValid());

        ++numSendAttempted;

        if (!send(currElement.addr, currElement.blocksize,
                    currElement.cmd, currElement.flags, currElement.pc)) {
            DPRINTF(TraceCPUInst, "currElement sending failed.\n");
            ++numSendFailed;
            // return false to indicate not to schedule next event
            return false;
        } else {
            ++numSendSucceeded;
        }
    }
    // If packet was sent successfully, either retryPkt or currElement, return
    // true to indicate to schedule event at current Tick plus delta. If packet
    // was sent successfully and there is no next packet to send, return false.
    DPRINTF(TraceCPUInst, "Packet sent successfully, trying to read next "
        "element.\n");
    retryPkt = nullptr;
    // Read next element into currElement, currElement gets cleared so save the
    // tick to calculate delta
    Tick last_tick = currElement.tick;
    if (nextExecute()) {
        assert(currElement.tick >= last_tick);
        delta = currElement.tick - last_tick;
    }
    return !traceComplete;
}

bool
TraceCPU::FixedRetryGen::nextExecute()
{
    if (traceComplete)
        // We are at the end of the file, thus we have no more messages.
        // Return false.
        return false;


    //Reset the currElement to the default values
    currElement.clear();

    // Read the next line to get the next message. If that fails then end of
    // trace has been reached and traceComplete needs to be set in addition
    // to returning false. If successful then next message is in currElement.
    if (!trace.read(&currElement)) {
        traceComplete = true;
        instLastTick = curTick();
        return false;
    }

    DPRINTF(TraceCPUInst, "inst fetch: %c addr %d pc %#x size %d tick %d\n",
            currElement.cmd.isRead() ? 'r' : 'w',
            currElement.addr,
            currElement.pc,
            currElement.blocksize,
            currElement.tick);

    return true;
}

The send() methods will create a packet in Gem5 and send it to icache port. Necessary elements include:

  • addr (this is a physical address; we can know it from how the trace is generated above)
  • size
  • flags
  • masterID
  • pc
  • ContextID
  • MemCmd cmd
  • newly allocated space for packet data
    • if a write request, set the data as 0xA chunks –> dummy content

Definition of this methods TraceCPU::FixedRetryGen::send() is shown below

// src/cpu/trace/trace_cpu.cc

bool
TraceCPU::FixedRetryGen::send(Addr addr, unsigned size, const MemCmd& cmd,
              Request::FlagsType flags, Addr pc)
{

    // Create new request
    auto req = std::make_shared<Request>(addr, size, flags, masterID);
    req->setPC(pc);

    // If this is not done it triggers assert in L1 cache for invalid contextId
    req->setContext(ContextID(0));

    // Embed it in a packet
    PacketPtr pkt = new Packet(req, cmd);

    uint8_t* pkt_data = new uint8_t[req->getSize()];
    pkt->dataDynamic(pkt_data);

    if (cmd.isWrite()) {
        memset(pkt_data, 0xA, req->getSize());
    }

    // Call MasterPort method to send a timing request for this packet
    bool success = port.sendTimingReq(pkt);
    if (!success) {
        // If it fails, save the packet to retry when a retry is signalled by
        // the cache
        retryPkt = pkt;
    }
    return success;
}

Reference 1


  1. src/cpu/trace/trace_cpu.hh/cc ↩
Created Jul 3, 2020 // Last Updated Jul 3, 2020

If you could revise
the fundmental principles of
computer system design
to improve security...

... what would you change?