#include "base/loader/aout_object.hh"
-#include "mem/translating_port.hh"
#include "base/loader/symtab.hh"
#include "base/trace.hh" // for DPRINTF
text.baseAddr = N_TXTADDR(*execHdr);
text.size = execHdr->tsize;
+ text.fileImage = fileData + N_TXTOFF(*execHdr);
data.baseAddr = N_DATADDR(*execHdr);
data.size = execHdr->dsize;
+ data.fileImage = fileData + N_DATOFF(*execHdr);
bss.baseAddr = N_BSSADDR(*execHdr);
bss.size = execHdr->bsize;
+ bss.fileImage = NULL;
DPRINTFR(Loader, "text: 0x%x %d\ndata: 0x%x %d\nbss: 0x%x %d\n",
text.baseAddr, text.size, data.baseAddr, data.size,
}
-bool
-AoutObject::loadSections(TranslatingPort *memPort, bool loadPhys)
-{
- Addr textAddr = text.baseAddr;
- Addr dataAddr = data.baseAddr;
-
- if (loadPhys) {
- textAddr &= (ULL(1) << 40) - 1;
- dataAddr &= (ULL(1) << 40) - 1;
- }
-
- // Since we don't really have an MMU and all memory is
- // zero-filled, there's no need to set up the BSS segment.
- if (text.size != 0)
- memPort->writeBlobFunctional(textAddr, fileData + N_TXTOFF(*execHdr),
- text.size, true);
- if (data.size != 0)
- memPort->writeBlobFunctional(dataAddr, fileData + N_DATOFF(*execHdr),
- data.size, true);
-
- return true;
-}
-
-
bool
AoutObject::loadGlobalSymbols(SymbolTable *symtab)
{
public:
virtual ~AoutObject() {}
- virtual bool loadSections(TranslatingPort *memPort, bool loadPhys = false);
virtual bool loadGlobalSymbols(SymbolTable *symtab);
virtual bool loadLocalSymbols(SymbolTable *symtab);
#include <string>
#include "base/loader/ecoff_object.hh"
-
-#include "mem/translating_port.hh"
#include "base/loader/symtab.hh"
#include "base/trace.hh" // for DPRINTF
text.baseAddr = aoutHdr->text_start;
text.size = aoutHdr->tsize;
+ text.fileImage = fileData + ECOFF_TXTOFF(execHdr);
data.baseAddr = aoutHdr->data_start;
data.size = aoutHdr->dsize;
+ data.fileImage = fileData + ECOFF_DATOFF(execHdr);
bss.baseAddr = aoutHdr->bss_start;
bss.size = aoutHdr->bsize;
+ bss.fileImage = NULL;
DPRINTFR(Loader, "text: 0x%x %d\ndata: 0x%x %d\nbss: 0x%x %d\n",
text.baseAddr, text.size, data.baseAddr, data.size,
}
-bool
-EcoffObject::loadSections(TranslatingPort *memPort, bool loadPhys)
-{
- Addr textAddr = text.baseAddr;
- Addr dataAddr = data.baseAddr;
-
- if (loadPhys) {
- textAddr &= (ULL(1) << 40) - 1;
- dataAddr &= (ULL(1) << 40) - 1;
- }
-
- // Since we don't really have an MMU and all memory is
- // zero-filled, there's no need to set up the BSS segment.
- memPort->writeBlobFunctional(textAddr, fileData + ECOFF_TXTOFF(execHdr),
- text.size, true);
- memPort->writeBlobFunctional(dataAddr, fileData + ECOFF_DATOFF(execHdr),
- data.size, true);
-
- return true;
-}
-
-
bool
EcoffObject::loadGlobalSymbols(SymbolTable *symtab)
{
public:
virtual ~EcoffObject() {}
- virtual bool loadSections(TranslatingPort *memPort, bool loadPhys = false);
virtual bool loadGlobalSymbols(SymbolTable *symtab);
virtual bool loadLocalSymbols(SymbolTable *symtab);
#include "base/loader/elf_object.hh"
-#include "mem/translating_port.hh"
#include "base/loader/symtab.hh"
#include "base/trace.hh" // for DPRINTF
if (text.size == 0) { // haven't seen text segment yet
text.baseAddr = phdr.p_vaddr;
text.size = phdr.p_filesz;
- // remember where the data is for loadSections()
- fileTextBits = fileData + phdr.p_offset;
+ text.fileImage = fileData + phdr.p_offset;
// if there's any padding at the end that's not in the
// file, call it the bss. This happens in the "text"
// segment if there's only one loadable segment (as for
// kernel images).
bss.size = phdr.p_memsz - phdr.p_filesz;
bss.baseAddr = phdr.p_vaddr + phdr.p_filesz;
+ bss.fileImage = NULL;
}
else if (data.size == 0) { // have text, this must be data
data.baseAddr = phdr.p_vaddr;
data.size = phdr.p_filesz;
- // remember where the data is for loadSections()
- fileDataBits = fileData + phdr.p_offset;
+ data.fileImage = fileData + phdr.p_offset;
// if there's any padding at the end that's not in the
// file, call it the bss. Warn if this happens for both
// the text & data segments (should only have one bss).
}
bss.size = phdr.p_memsz - phdr.p_filesz;
bss.baseAddr = phdr.p_vaddr + phdr.p_filesz;
+ bss.fileImage = NULL;
}
}
}
-bool
-ElfObject::loadSections(TranslatingPort *memPort, bool loadPhys)
-{
- Addr textAddr = text.baseAddr;
- Addr dataAddr = data.baseAddr;
-
- if (loadPhys) {
- textAddr &= (ULL(1) << 40) - 1;
- dataAddr &= (ULL(1) << 40) - 1;
- }
-
- // Since we don't really have an MMU and all memory is
- // zero-filled, there's no need to set up the BSS segment.
- if (text.size != 0)
- memPort->writeBlobFunctional(textAddr, fileTextBits, text.size, true);
- if (data.size != 0)
- memPort->writeBlobFunctional(dataAddr, fileDataBits, data.size, true);
-
- return true;
-}
-
-
bool
ElfObject::loadSomeSymbols(SymbolTable *symtab, int binding)
{
{
protected:
- uint8_t *fileTextBits; //!< Pointer to file's text segment image
- uint8_t *fileDataBits; //!< Pointer to file's data segment image
-
/// Helper functions for loadGlobalSymbols() and loadLocalSymbols().
bool loadSomeSymbols(SymbolTable *symtab, int binding);
public:
virtual ~ElfObject() {}
- virtual bool loadSections(TranslatingPort *memPort, bool loadPhys = false);
virtual bool loadGlobalSymbols(SymbolTable *symtab);
virtual bool loadLocalSymbols(SymbolTable *symtab);
#include "base/loader/aout_object.hh"
#include "base/loader/elf_object.hh"
+#include "mem/translating_port.hh"
+
using namespace std;
ObjectFile::ObjectFile(const string &_filename, int _fd,
}
+bool
+ObjectFile::loadSection(Section *sec, TranslatingPort *memPort, bool loadPhys)
+{
+ if (sec->size != 0) {
+ Addr addr = sec->baseAddr;
+ if (loadPhys) {
+ // this is Alpha-specific... going to have to fix this
+ // for other architectures
+ addr &= (ULL(1) << 40) - 1;
+ }
+
+ if (sec->fileImage) {
+ memPort->writeBlobFunctional(addr, sec->fileImage, sec->size, true);
+ }
+ else {
+ // no image: must be bss
+ memPort->memsetBlobFunctional(addr, 0, sec->size, true);
+ }
+ }
+ return true;
+}
+
+
+bool
+ObjectFile::loadSections(TranslatingPort *memPort, bool loadPhys)
+{
+ return (loadSection(&text, memPort, loadPhys)
+ && loadSection(&data, memPort, loadPhys)
+ && loadSection(&bss, memPort, loadPhys));
+}
+
+
void
ObjectFile::close()
{
void close();
- virtual bool loadSections(TranslatingPort *memPort, bool loadPhys = false) = 0;
+ virtual bool loadSections(TranslatingPort *memPort, bool loadPhys = false);
virtual bool loadGlobalSymbols(SymbolTable *symtab) = 0;
virtual bool loadLocalSymbols(SymbolTable *symtab) = 0;
protected:
struct Section {
- Addr baseAddr;
- size_t size;
+ Addr baseAddr;
+ uint8_t *fileImage;
+ size_t size;
};
Addr entry;
Section data;
Section bss;
+ bool loadSection(Section *sec, TranslatingPort *memPort, bool loadPhys);
+
public:
Addr entryPoint() const { return entry; }
Addr globalPointer() const { return globalPtr; }
*/
int number_of_threads;
- /**
- * A pointer to the port into the memory system to be used by syscall
- * emulation. This way the data being accessed via syscalls looks in
- * the memory heirachy for any changes that haven't been written back
- * to main memory yet.
- */
- Port* memPort;
-
/**
* Vector of per-thread instruction-based event queues. Used for
* scheduling events based on number of instructions committed by
#include "base/output.hh"
#include "cpu/profile.hh"
#include "kern/kernel_stats.hh"
-#include "mem/translating_port.hh"
#include "sim/serialize.hh"
#include "sim/sim_exit.hh"
#include "sim/system.hh"
#include "targetarch/stacktrace.hh"
#else
#include "sim/process.hh"
+#include "mem/translating_port.hh"
#endif
using namespace std;
}
#else
ExecContext::ExecContext(BaseCPU *_cpu, int _thread_num,
- Process *_process, int _asid)
+ Process *_process, int _asid, Port *mem_port)
: _status(ExecContext::Unallocated),
cpu(_cpu), thread_num(_thread_num), cpu_id(-1),
process(_process),
asid(_asid),
func_exe_inst(0), storeCondFailures(0)
{
- port = new TranslatingPort(cpu->memPort, process->pTable);
+ port = new TranslatingPort(mem_port, process->pTable);
memset(®s, 0, sizeof(RegFile));
}
#endif
#include "sim/host.hh"
#include "sim/serialize.hh"
#include "targetarch/byte_swap.hh"
-#include "mem/translating_port.hh"
class BaseCPU;
#else // !FULL_SYSTEM
#include "sim/process.hh"
+class TranslatingPort;
#endif // FULL_SYSTEM
AlphaITB *_itb, AlphaDTB *_dtb, FunctionalMemory *_dem);
#else
ExecContext(BaseCPU *_cpu, int _thread_num,
- Process *_process, int _asid);
+ Process *_process, int _asid, Port *mem_port);
#endif
virtual ~ExecContext();
dcachePort(this), tickEvent(this, p->width), xc(NULL)
{
_status = Idle;
-#if FULL_SYSTEM
- xc = new ExecContext(this, 0, p->system, p->itb, p->dtb, p->mem);
-
- // initialize CPU, including PC
- TheISA::initCPU(&xc->regs);
-#else
- xc = new ExecContext(this, /* thread_num */ 0, p->process, /* asid */ 0);
-#endif // !FULL_SYSTEM
-
- memPort = &dcachePort;
//Create Memory Ports (conect them up)
p->mem->addPort("DCACHE");
icachePort.setPeer(p->mem->getPort("ICACHE"));
(p->mem->getPort("ICACHE"))->setPeer(&icachePort);
+#if FULL_SYSTEM
+ xc = new ExecContext(this, 0, p->system, p->itb, p->dtb, p->mem);
+ // initialize CPU, including PC
+ TheISA::initCPU(&xc->regs);
+#else
+ xc = new ExecContext(this, /* thread_num */ 0, p->process, /* asid */ 0,
+ &dcachePort);
+#endif // !FULL_SYSTEM
- req = new CpuRequest;
-
- req->asid = 0;
+#if SIMPLE_CPU_MEM_ATOMIC || SIMPLE_CPU_MEM_IMMEDIATE
+ ifetch_req = new CpuRequest;
+ ifetch_req->asid = 0;
+ ifetch_req->size = sizeof(MachInst);
+ ifetch_pkt = new Packet;
+ ifetch_pkt->cmd = Read;
+ ifetch_pkt->data = (uint8_t *)&inst;
+ ifetch_pkt->req = ifetch_req;
+ ifetch_pkt->size = sizeof(MachInst);
+
+ data_read_req = new CpuRequest;
+ data_read_req->asid = 0;
+ data_read_pkt = new Packet;
+ data_read_pkt->cmd = Read;
+ data_read_pkt->data = new uint8_t[8];
+ data_read_pkt->req = data_read_req;
+
+ data_write_req = new CpuRequest;
+ data_write_req->asid = 0;
+ data_write_pkt = new Packet;
+ data_write_pkt->cmd = Write;
+ data_write_pkt->req = data_write_req;
+#endif
numInst = 0;
startNumInst = 0;
}
return fault;
#else
+ panic("copy not implemented");
return No_Fault;
#endif
}
if (status() == DcacheWaitResponse || status() == DcacheWaitSwitch) {
// Fault fault = xc->read(memReq,data);
// Not sure what to check for no fault...
- if (pkt->result == Success) {
- memcpy(&data, pkt->data, sizeof(T));
+ if (data_read_pkt->result == Success) {
+ memcpy(&data, data_read_pkt->data, sizeof(T));
}
if (traceData) {
// memReq->reset(addr, sizeof(T), flags);
+#if SIMPLE_CPU_MEM_TIMING
+ CpuRequest *data_read_req = new CpuRequest;
+#endif
+
+ data_read_req->vaddr = addr;
+ data_read_req->size = sizeof(T);
+ data_read_req->flags = flags;
+ data_read_req->time = curTick;
+
// translate to physical address
- // NEED NEW TRANSLATION HERE
- Fault fault = xc->translateDataReadReq(req);
+ Fault fault = xc->translateDataReadReq(data_read_req);
// Now do the access.
if (fault == No_Fault) {
- pkt = new Packet;
- pkt->cmd = Read;
- req->paddr = addr;
- pkt->size = sizeof(T);
- pkt->req = req;
+#if SIMPLE_CPU_MEM_TIMING
+ data_read_pkt = new Packet;
+ data_read_pkt->cmd = Read;
+ data_read_pkt->req = data_read_req;
+ data_read_pkt->data = new uint8_t[8];
+#endif
+ data_read_pkt->addr = data_read_req->paddr;
+ data_read_pkt->size = sizeof(T);
+
+ sendDcacheRequest(data_read_pkt);
+
+#if SIMPLE_CPU_MEM_IMMEDIATE
+ // Need to find a way to not duplicate code above.
- sendDcacheRequest();
+ if (data_read_pkt->result == Success) {
+ memcpy(&data, data_read_pkt->data, sizeof(T));
+ }
+
+ if (traceData) {
+ traceData->setAddr(addr);
+ }
+
+ // @todo: Figure out a way to create a Fault from the packet result.
+ return No_Fault;
+#endif
}
/*
memReq->cmd = Read;
}
*/
// This will need a new way to tell if it has a dcache attached.
- if (/*!dcacheInterface && */(req->flags & UNCACHEABLE))
+ if (data_read_req->flags & UNCACHEABLE)
recordEvent("Uncached Read");
return fault;
Fault
SimpleCPU::write(T data, Addr addr, unsigned flags, uint64_t *res)
{
-// memReq->reset(addr, sizeof(T), flags);
- req->vaddr = addr;
- req->time = curTick;
- req->size = sizeof(T);
+ data_write_req->vaddr = addr;
+ data_write_req->time = curTick;
+ data_write_req->size = sizeof(T);
+ data_write_req->flags = flags;
// translate to physical address
- // NEED NEW TRANSLATION HERE
- Fault fault = xc->translateDataWriteReq(req);
+ Fault fault = xc->translateDataWriteReq(data_write_req);
// Now do the access.
if (fault == No_Fault) {
- pkt = new Packet;
- pkt->cmd = Write;
- pkt->size = sizeof(T);
- pkt->req = req;
-
- // Copy data into the packet.
- pkt->data = new uint8_t[64];
- memcpy(pkt->data, &data, sizeof(T));
+#if SIMPLE_CPU_MEM_TIMING
+ data_write_pkt = new Packet;
+ data_write_pkt->cmd = Write;
+ data_write_pkt->req = data_write_req;
+ data_write_pkt->data = new uint8_t[64];
+ memcpy(data_write_pkt->data, &data, sizeof(T));
+#else
+ data_write_pkt->data = (uint8_t *)&data;
+#endif
+ data_write_pkt->addr = data_write_req->paddr;
+ data_write_pkt->size = sizeof(T);
- sendDcacheRequest();
+ sendDcacheRequest(data_write_pkt);
}
/*
}
*/
if (res && (fault == No_Fault))
- *res = pkt->result;
+ *res = data_write_pkt->result;
// This will need a new way to tell if it's hooked up to a cache or not.
- if (/*!dcacheInterface && */(req->flags & UNCACHEABLE))
+ if (data_write_req->flags & UNCACHEABLE)
recordEvent("Uncached Write");
// If the write needs to have a fault on the access, consider calling
#endif // FULL_SYSTEM
void
-SimpleCPU::sendIcacheRequest()
+SimpleCPU::sendIcacheRequest(Packet *pkt)
{
-#if 0
+ assert(!tickEvent.scheduled());
+#if SIMPLE_CPU_MEM_TIMING
+ retry_pkt = pkt;
bool success = icachePort.sendTiming(*pkt);
unscheduleTickEvent();
// Need to wait for cache to respond
_status = IcacheWaitResponse;
}
-#else
+#elif SIMPLE_CPU_MEM_ATOMIC
Tick latency = icachePort.sendAtomic(*pkt);
unscheduleTickEvent();
icacheStallCycles += latency;
_status = IcacheAccessComplete;
-
- delete pkt;
+#elif SIMPLE_CPU_MEM_IMMEDIATE
+ icachePort.sendAtomic(*pkt);
+#else
+#error "SimpleCPU has no mem model set"
#endif
}
void
-SimpleCPU::sendDcacheRequest()
+SimpleCPU::sendDcacheRequest(Packet *pkt)
{
+ assert(!tickEvent.scheduled());
+#if SIMPLE_CPU_MEM_TIMING
unscheduleTickEvent();
-#if 0
+ retry_pkt = pkt;
bool success = dcachePort.sendTiming(*pkt);
lastDcacheStall = curTick;
} else {
_status = DcacheWaitResponse;
}
-#else
+#elif SIMPLE_CPU_MEM_ATOMIC
+ unscheduleTickEvent();
+
Tick latency = dcachePort.sendAtomic(*pkt);
scheduleTickEvent(latency);
// we check the status of the packet sent (is this valid?),
// we won't know if the latency is a hit or a miss.
dcacheStallCycles += latency;
-
- // Delete the packet right here?
- delete pkt;
+#elif SIMPLE_CPU_MEM_IMMEDIATE
+ dcachePort.sendAtomic(*pkt);
+#else
+#error "SimpleCPU has no mem model set"
#endif
}
void
SimpleCPU::processResponse(Packet &response)
{
+ assert(SIMPLE_CPU_MEM_TIMING);
+
// For what things is the CPU the consumer of the packet it sent
// out? This may create a memory leak if that's the case and it's
// expected of the SimpleCPU to delete its own packet.
- pkt = &response;
+ Packet *pkt = &response;
switch (status()) {
case IcacheWaitResponse:
Packet *
SimpleCPU::processRetry()
{
+#if SIMPLE_CPU_MEM_TIMING
switch(status()) {
case IcacheRetry:
icacheRetryCycles += curTick - lastIcacheStall;
- return pkt;
+ return retry_pkt;
break;
case DcacheRetry:
dcacheRetryCycles += curTick - lastDcacheStall;
- return pkt;
+ return retry_pkt;
break;
default:
panic("SimpleCPU::processRetry: bad state");
break;
}
+#else
+ panic("shouldn't be here");
+#endif
}
#if FULL_SYSTEM
#define IFETCH_FLAGS(pc) 0
#endif
- req->vaddr = xc->regs.pc & ~3;
- req->time = curTick;
- req->size = sizeof(MachInst);
+#if SIMPLE_CPU_MEM_TIMING
+ CpuRequest *ifetch_req = new CpuRequest();
+ ifetch_req->size = sizeof(MachInst);
+#endif
+
+ ifetch_req->vaddr = xc->regs.pc & ~3;
+ ifetch_req->time = curTick;
/* memReq->reset(xc->regs.pc & ~3, sizeof(uint32_t),
IFETCH_FLAGS(xc->regs.pc));
*/
- fault = xc->translateInstReq(req);
+ fault = xc->translateInstReq(ifetch_req);
if (fault == No_Fault) {
- pkt = new Packet;
- pkt->cmd = Read;
- pkt->addr = req->paddr;
- pkt->size = sizeof(MachInst);
- pkt->req = req;
- pkt->data = (uint8_t *)&inst;
-
- sendIcacheRequest();
- return;
-/* fault = xc->mem->read(memReq, inst);
+#if SIMPLE_CPU_MEM_TIMING
+ Packet *ifetch_pkt = new Packet;
+ ifetch_pkt->cmd = Read;
+ ifetch_pkt->data = (uint8_t *)&inst;
+ ifetch_pkt->req = ifetch_req;
+ ifetch_pkt->size = sizeof(MachInst);
+#endif
+ ifetch_pkt->addr = ifetch_req->paddr;
+ sendIcacheRequest(ifetch_pkt);
+#if SIMPLE_CPU_MEM_TIMING || SIMPLE_CPU_MEM_ATOMIC
+ return;
+#endif
+/*
if (icacheInterface && fault == No_Fault) {
memReq->completionEvent = NULL;
// If we have a dcache miss, then we can't finialize the instruction
// trace yet because we want to populate it with the data later
- if (traceData &&
- !(status() == DcacheWaitResponse && pkt->cmd == Read)) {
+ if (traceData && (status() != DcacheWaitResponse)) {
traceData->finalize();
}
class InstRecord;
}
+
+// Set exactly one of these symbols to 1 to set the memory access
+// model. Probably should make these template parameters, or even
+// just fork the CPU models.
+//
+#define SIMPLE_CPU_MEM_TIMING 0
+#define SIMPLE_CPU_MEM_ATOMIC 0
+#define SIMPLE_CPU_MEM_IMMEDIATE 1
+
+
class SimpleCPU : public BaseCPU
{
class CpuPort : public Port
// current instruction
MachInst inst;
- CpuRequest *req;
- Packet *pkt;
+#if SIMPLE_CPU_MEM_TIMING
+ Packet *retry_pkt;
+#elif SIMPLE_CPU_MEM_ATOMIC || SIMPLE_CPU_MEM_IMMEDIATE
+ CpuRequest *ifetch_req;
+ Packet *ifetch_pkt;
+ CpuRequest *data_read_req;
+ Packet *data_read_pkt;
+ CpuRequest *data_write_req;
+ Packet *data_write_pkt;
+#endif
// Pointer to the sampler that is telling us to switchover.
// Used to signal the completion of the pipe drain and schedule
Stats::Scalar<> dcacheRetryCycles;
Counter lastDcacheRetry;
- void sendIcacheRequest();
- void sendDcacheRequest();
+ void sendIcacheRequest(Packet *pkt);
+ void sendDcacheRequest(Packet *pkt);
void processResponse(Packet &response);
Packet * processRetry();
default:
panic("unimplemented");
}
+
+ pkt.result = Success;
}
Port *
blobHelper(addr, p, size, Read);
}
+void
+Port::memsetBlobFunctional(Addr addr, uint8_t val, int size)
+{
+ // quick and dirty...
+ uint8_t *buf = new uint8_t[size];
+
+ memset(buf, val, size);
+ blobHelper(addr, buf, size, Write);
+}
return No_Fault;
}
-/*
+
Fault
-TranslatingPort::memsetBlobFunctional(Addr addr, uint8_t val, int size)
+TranslatingPort::memsetBlobFunctional(Addr addr, uint8_t val, int size,
+ bool alloc)
{
Addr paddr;
for (ChunkGenerator gen(addr, size, VMPageSize); !gen.done(); gen.next()) {
- if (!pTable->translate(gen.addr(),paddr))
- return Machine_Check_Fault;
+ if (!pTable->translate(gen.addr(), paddr)) {
+ if (alloc) {
+ pTable->allocate(roundDown(gen.addr(), VMPageSize),
+ VMPageSize);
+ pTable->translate(gen.addr(), paddr);
+ } else {
+ return Machine_Check_Fault;
+ }
+ }
- port->memsetBlobFunctional(paddr, val, gen.size());
+ port->memsetBlobFunctional(paddr, val, gen.size());
}
return No_Fault;
}
-*/
+
Fault
TranslatingPort::writeStringFunctional(Addr addr, const char *str)
Fault readBlobFunctional(Addr addr, uint8_t *p, int size);
Fault writeBlobFunctional(Addr addr, uint8_t *p, int size,
bool alloc = false);
- // Fault memsetBlobFunctional(Addr addr, uint8_t val, int size);
+ Fault memsetBlobFunctional(Addr addr, uint8_t val, int size,
+ bool alloc = false);
Fault writeStringFunctional(Addr addr, const char *str);
Fault readStringFunctional(std::string &str, Addr addr);