--- /dev/null
+/*
+ * Copyright (c) 2017-2020 Advanced Micro Devices, Inc.
+ * All rights reserved.
+ *
+ * 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.
+ */
+
+#include "sim/mem_state.hh"
+
+#include <cassert>
+
+#include "arch/generic/tlb.hh"
+#include "debug/Vma.hh"
+#include "mem/se_translating_port_proxy.hh"
+#include "sim/process.hh"
+#include "sim/syscall_debug_macros.hh"
+#include "sim/system.hh"
+#include "sim/vma.hh"
+
+MemState::MemState(Process *owner, Addr brk_point, Addr stack_base,
+ Addr max_stack_size, Addr next_thread_stack_base,
+ Addr mmap_end)
+ : _ownerProcess(owner),
+ _pageBytes(owner->system->getPageBytes()), _brkPoint(brk_point),
+ _stackBase(stack_base), _maxStackSize(max_stack_size),
+ _nextThreadStackBase(next_thread_stack_base),
+ _mmapEnd(mmap_end), _endBrkPoint(brk_point)
+{
+}
+
+MemState&
+MemState::operator=(const MemState &in)
+{
+ if (this == &in)
+ return *this;
+
+ _pageBytes = in._pageBytes;
+ _brkPoint = in._brkPoint;
+ _stackBase = in._stackBase;
+ _stackSize = in._stackSize;
+ _maxStackSize = in._maxStackSize;
+ _stackMin = in._stackMin;
+ _nextThreadStackBase = in._nextThreadStackBase;
+ _mmapEnd = in._mmapEnd;
+ _endBrkPoint = in._endBrkPoint;
+ _vmaList = in._vmaList; /* This assignment does a deep copy. */
+
+ return *this;
+}
+
+void
+MemState::resetOwner(Process *owner)
+{
+ _ownerProcess = owner;
+}
+
+bool
+MemState::isUnmapped(Addr start_addr, Addr length)
+{
+ Addr end_addr = start_addr + length;
+ const AddrRange range(start_addr, end_addr);
+ for (const auto &vma : _vmaList) {
+ if (vma.intersects(range))
+ return false;
+ }
+
+ /**
+ * In case someone skips the VMA interface and just directly maps memory
+ * also consult the page tables to make sure that this memory isnt mapped.
+ */
+ for (auto start = start_addr; start < end_addr;
+ start += _pageBytes) {
+ if (_ownerProcess->pTable->lookup(start) != nullptr) {
+ panic("Someone allocated physical memory at VA %p without "
+ "creating a VMA!\n", start);
+ return false;
+ }
+ }
+ return true;
+}
+
+void
+MemState::updateBrkRegion(Addr old_brk, Addr new_brk)
+{
+ /**
+ * To make this simple, avoid reducing the heap memory area if the
+ * new_brk point is less than the old_brk; this occurs when the heap is
+ * receding because the application has given back memory. The brk point
+ * is still tracked in the MemState class as an independent field so that
+ * it can be returned to the application; we just do not update the
+ * region unless we expand it out.
+ */
+ if (new_brk < old_brk) {
+ _brkPoint = new_brk;
+ return;
+ }
+
+ /**
+ * The regions must be page aligned but the break point can be set on
+ * byte boundaries. Ensure that the restriction is maintained here by
+ * extending the request out to the end of the page. (The roundUp
+ * function will not round up an already aligned page.)
+ */
+ auto page_aligned_brk = roundUp(new_brk, _pageBytes);
+
+ /**
+ * Create a new mapping for the heap region. We only create a mapping
+ * for the extra memory that is requested so we do not create a situation
+ * where there can be overlapping mappings in the regions.
+ *
+ * Since we do not track the type of the region and we also do not
+ * coalesce the regions together, we can create a fragmented set of
+ * heap regions. To resolve this, we keep the furthest point ever mapped
+ * by the _endBrkPoint field.
+ */
+ if (page_aligned_brk > _endBrkPoint) {
+ auto length = page_aligned_brk - _endBrkPoint;
+ /**
+ * Check if existing mappings impede the expansion of brk expansion.
+ * If brk cannot expand, it must return the original, unmodified brk
+ * address and should not modify the mappings here.
+ */
+ if (!isUnmapped(_endBrkPoint, length)) {
+ return;
+ }
+
+ /**
+ * Note that the heap regions are always contiguous but there is
+ * no mechanism right now to coalesce together memory that belongs
+ * to the same region with similar access permissions. This could be
+ * implemented if it actually becomes necessary; probably only
+ * necessary if the list becomes too long to walk.
+ */
+ mapRegion(_endBrkPoint, length, "heap");
+ _endBrkPoint = page_aligned_brk;
+ }
+
+ _brkPoint = new_brk;
+}
+
+void
+MemState::mapRegion(Addr start_addr, Addr length,
+ const std::string& region_name, int sim_fd, Addr offset)
+{
+ DPRINTF(Vma, "memstate: creating vma (%s) [0x%x - 0x%x]\n",
+ region_name.c_str(), start_addr, start_addr + length);
+
+ /**
+ * Avoid creating a region that has preexisting mappings. This should
+ * not happen under normal circumstances so consider this to be a bug.
+ */
+ assert(isUnmapped(start_addr, length));
+
+ /**
+ * Record the region in our list structure.
+ */
+ _vmaList.emplace_back(AddrRange(start_addr, start_addr + length),
+ _pageBytes, region_name, sim_fd, offset);
+}
+
+void
+MemState::unmapRegion(Addr start_addr, Addr length)
+{
+ Addr end_addr = start_addr + length;
+ const AddrRange range(start_addr, end_addr);
+
+ auto vma = std::begin(_vmaList);
+ while (vma != std::end(_vmaList)) {
+ if (vma->isStrictSuperset(range)) {
+ DPRINTF(Vma, "memstate: split vma [0x%x - 0x%x] into "
+ "[0x%x - 0x%x] and [0x%x - 0x%x]\n",
+ vma->start(), vma->end(),
+ vma->start(), start_addr,
+ end_addr, vma->end());
+ /**
+ * Need to split into two smaller regions.
+ * Create a clone of the old VMA and slice it to the right.
+ */
+ _vmaList.push_back(*vma);
+ _vmaList.back().sliceRegionRight(start_addr);
+
+ /**
+ * Slice old VMA to encapsulate the left region.
+ */
+ vma->sliceRegionLeft(end_addr);
+
+ /**
+ * Region cannot be in any more VMA, because it is completely
+ * contained in this one!
+ */
+ break;
+ } else if (vma->isSubset(range)) {
+ DPRINTF(Vma, "memstate: destroying vma [0x%x - 0x%x]\n",
+ vma->start(), vma->end());
+ /**
+ * Need to nuke the existing VMA.
+ */
+ vma = _vmaList.erase(vma);
+
+ continue;
+
+ } else if (vma->intersects(range)) {
+ /**
+ * Trim up the existing VMA.
+ */
+ if (vma->start() < start_addr) {
+ DPRINTF(Vma, "memstate: resizing vma [0x%x - 0x%x] "
+ "into [0x%x - 0x%x]\n",
+ vma->start(), vma->end(),
+ vma->start(), start_addr);
+ /**
+ * Overlaps from the right.
+ */
+ vma->sliceRegionRight(start_addr);
+ } else {
+ DPRINTF(Vma, "memstate: resizing vma [0x%x - 0x%x] "
+ "into [0x%x - 0x%x]\n",
+ vma->start(), vma->end(),
+ end_addr, vma->end());
+ /**
+ * Overlaps from the left.
+ */
+ vma->sliceRegionLeft(end_addr);
+ }
+ }
+
+ vma++;
+ }
+
+ /**
+ * TLBs need to be flushed to remove any stale mappings from regions
+ * which were unmapped. Currently the entire TLB is flushed. This results
+ * in functionally correct execution, but real systems do not flush all
+ * entries when a single mapping changes since it degrades performance.
+ * There is currently no general method across all TLB implementations
+ * that can flush just part of the address space.
+ */
+ for (auto tc : _ownerProcess->system->threadContexts) {
+ tc->getDTBPtr()->flushAll();
+ tc->getITBPtr()->flushAll();
+ }
+
+ do {
+ if (!_ownerProcess->pTable->isUnmapped(start_addr, _pageBytes))
+ _ownerProcess->pTable->unmap(start_addr, _pageBytes);
+
+ start_addr += _pageBytes;
+
+ /**
+ * The regions need to always be page-aligned otherwise the while
+ * condition will loop indefinitely. (The Addr type is currently
+ * defined to be uint64_t in src/base/types.hh; it can underflow
+ * since it is unsigned.)
+ */
+ length -= _pageBytes;
+ } while (length > 0);
+}
+
+void
+MemState::remapRegion(Addr start_addr, Addr new_start_addr, Addr length)
+{
+ Addr end_addr = start_addr + length;
+ const AddrRange range(start_addr, end_addr);
+
+ auto vma = std::begin(_vmaList);
+ while (vma != std::end(_vmaList)) {
+ if (vma->isStrictSuperset(range)) {
+ /**
+ * Create clone of the old VMA and slice right.
+ */
+ _vmaList.push_back(*vma);
+ _vmaList.back().sliceRegionRight(start_addr);
+
+ /**
+ * Create clone of the old VMA and slice it left.
+ */
+ _vmaList.push_back(*vma);
+ _vmaList.back().sliceRegionLeft(end_addr);
+
+ /**
+ * Slice the old VMA left and right to adjust the file backing,
+ * then overwrite the virtual addresses!
+ */
+ vma->sliceRegionLeft(start_addr);
+ vma->sliceRegionRight(end_addr);
+ vma->remap(new_start_addr);
+
+ /**
+ * The region cannot be in any more VMAs, because it is
+ * completely contained in this one!
+ */
+ break;
+ } else if (vma->isSubset(range)) {
+ /**
+ * Just go ahead and remap it!
+ */
+ vma->remap(vma->start() - start_addr + new_start_addr);
+ } else if (vma->intersects(range)) {
+ /**
+ * Create a clone of the old VMA.
+ */
+ _vmaList.push_back(*vma);
+
+ if (vma->start() < start_addr) {
+ /**
+ * Overlaps from the right.
+ */
+ _vmaList.back().sliceRegionRight(start_addr);
+
+ /**
+ * Remap the old region.
+ */
+ vma->sliceRegionLeft(start_addr);
+ vma->remap(new_start_addr);
+ } else {
+ /**
+ * Overlaps from the left.
+ */
+ _vmaList.back().sliceRegionLeft(end_addr);
+
+ /**
+ * Remap the old region.
+ */
+ vma->sliceRegionRight(end_addr);
+ vma->remap(new_start_addr + vma->start() - start_addr);
+ }
+ }
+
+ vma++;
+ }
+
+ /**
+ * TLBs need to be flushed to remove any stale mappings from regions
+ * which were remapped. Currently the entire TLB is flushed. This results
+ * in functionally correct execution, but real systems do not flush all
+ * entries when a single mapping changes since it degrades performance.
+ * There is currently no general method across all TLB implementations
+ * that can flush just part of the address space.
+ */
+ for (auto tc : _ownerProcess->system->threadContexts) {
+ tc->getDTBPtr()->flushAll();
+ tc->getITBPtr()->flushAll();
+ }
+
+ do {
+ if (!_ownerProcess->pTable->isUnmapped(start_addr, _pageBytes))
+ _ownerProcess->pTable->remap(start_addr, _pageBytes,
+ new_start_addr);
+
+ start_addr += _pageBytes;
+
+ /**
+ * The regions need to always be page-aligned otherwise the while
+ * condition will loop indefinitely. (The Addr type is currently
+ * defined to be uint64_t in src/base/types.hh; it can underflow
+ * since it is unsigned.)
+ */
+ length -= _pageBytes;
+ } while (length > 0);
+}
+
+bool
+MemState::fixupFault(Addr vaddr)
+{
+ /**
+ * Check if we are accessing a mapped virtual address. If so then we
+ * just haven't allocated it a physical page yet and can do so here.
+ */
+ for (const auto &vma : _vmaList) {
+ if (vma.contains(vaddr)) {
+ Addr vpage_start = roundDown(vaddr, _pageBytes);
+ _ownerProcess->allocateMem(vpage_start, _pageBytes);
+
+ /**
+ * We are assuming that fresh pages are zero-filled, so there is
+ * no need to zero them out when there is no backing file.
+ * This assumption will not hold true if/when physical pages
+ * are recycled.
+ */
+ if (vma.hasHostBuf()) {
+ /**
+ * Write the memory for the host buffer contents for all
+ * ThreadContexts associated with this process.
+ */
+ for (auto &cid : _ownerProcess->contextIds) {
+ ThreadContext *tc =
+ _ownerProcess->system->getThreadContext(cid);
+ SETranslatingPortProxy
+ virt_mem(tc, SETranslatingPortProxy::Always);
+ vma.fillMemPages(vpage_start, _pageBytes, virt_mem);
+ }
+ }
+ return true;
+ }
+ }
+
+ /**
+ * Check if the stack needs to be grown in the case where the ISAs
+ * process argsInit does not explicitly map the entire stack.
+ *
+ * Check if this is already on the stack and there's just no page there
+ * yet.
+ */
+ if (vaddr >= _stackMin && vaddr < _stackBase) {
+ _ownerProcess->allocateMem(roundDown(vaddr, _pageBytes), _pageBytes);
+ return true;
+ }
+
+ /**
+ * We've accessed the next page of the stack, so extend it to include
+ * this address.
+ */
+ if (vaddr < _stackMin && vaddr >= _stackBase - _maxStackSize) {
+ while (vaddr < _stackMin) {
+ _stackMin -= _pageBytes;
+ if (_stackBase - _stackMin > _maxStackSize) {
+ fatal("Maximum stack size exceeded\n");
+ }
+ _ownerProcess->allocateMem(_stackMin, _pageBytes);
+ inform("Increasing stack size by one page.");
+ }
+ return true;
+ }
+
+ return false;
+}
+
+Addr
+MemState::extendMmap(Addr length)
+{
+ Addr start = _mmapEnd;
+
+ if (_ownerProcess->mmapGrowsDown())
+ start = _mmapEnd - length;
+
+ // Look for a contiguous region of free virtual memory. We can't assume
+ // that the region beyond mmap_end is free because of fixed mappings from
+ // the user.
+ while (!isUnmapped(start, length)) {
+ DPRINTF(Vma, "memstate: cannot extend vma for mmap region at %p. "
+ "Virtual address range is already reserved! Skipping a page "
+ "and trying again!\n", start);
+ start = (_ownerProcess->mmapGrowsDown()) ? start - _pageBytes :
+ start + _pageBytes;
+ }
+
+ DPRINTF(Vma, "memstate: extending mmap region (old %p) (new %p)\n",
+ _mmapEnd,
+ _ownerProcess->mmapGrowsDown() ? start : start + length);
+
+ _mmapEnd = _ownerProcess->mmapGrowsDown() ? start : start + length;
+
+ return start;
+}
/*
- * Copyright (c) 2017 Advanced Micro Devices, Inc.
+ * Copyright (c) 2017-2020 Advanced Micro Devices, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* 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.
- *
- * Author: Brandon Potter
*/
#ifndef SRC_SIM_MEM_STATE_HH
#define SRC_SIM_MEM_STATE_HH
+#include <list>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "config/the_isa.hh"
+#include "debug/Vma.hh"
+#include "mem/page_table.hh"
+#include "mem/se_translating_port_proxy.hh"
#include "sim/serialize.hh"
+#include "sim/vma.hh"
+
+class Process;
+class ProcessParams;
+class System;
/**
* This class holds the memory state for the Process class and all of its
* derived, architecture-specific children.
*
- * The fields held in this class dynamically change as the process object
- * is run in the simulator. They are updated by system calls and faults;
- * each change represents a modification to the process address space.
- * The stack, heap, and mmap boundaries are held with this class along with
- * the base of the next thread stack.
+ * The class represents the Process' address space which may change
+ * dynamically while the simulation is running. They are updated by system
+ * calls and faults. Each change represents a modification to the process
+ * address space.
*
* The class is meant to be allocated dynamically and shared through a
- * pointer interface because two process can potentially share their virtual
- * address space if certain options are passed into the clone(2).
+ * pointer interface. Multiple process can potentially share portions of their
+ * virtual address space if specific options are passed into the clone(2)
+ * system call.
*/
class MemState : public Serializable
{
public:
- MemState(Addr brk_point, Addr stack_base, Addr max_stack_size,
- Addr next_thread_stack_base, Addr mmap_end)
- : _brkPoint(brk_point), _stackBase(stack_base), _stackSize(0),
- _maxStackSize(max_stack_size), _stackMin(0),
- _nextThreadStackBase(next_thread_stack_base), _mmapEnd(mmap_end)
- { }
-
- MemState&
- operator=(const MemState &in)
- {
- if (this == &in)
- return *this;
-
- _brkPoint = in._brkPoint;
- _stackBase = in._stackBase;
- _stackSize = in._stackSize;
- _maxStackSize = in._maxStackSize;
- _stackMin = in._stackMin;
- _nextThreadStackBase = in._nextThreadStackBase;
- _mmapEnd = in._mmapEnd;
- return *this;
- }
+ MemState(Process *owner, Addr brk_point, Addr stack_base,
+ Addr max_stack_size, Addr next_thread_stack_base,
+ Addr mmap_end);
+ MemState& operator=(const MemState &in);
+
+ /**
+ * Change the Process owner in case this MemState is copied.
+ */
+ void resetOwner(Process *owner);
+
+ /**
+ * Get/set base addresses and sizes for the stack and data segments of
+ * the process' memory.
+ */
Addr getBrkPoint() const { return _brkPoint; }
Addr getStackBase() const { return _stackBase; }
Addr getStackSize() const { return _stackSize; }
Addr getStackMin() const { return _stackMin; }
Addr getNextThreadStackBase() const { return _nextThreadStackBase; }
Addr getMmapEnd() const { return _mmapEnd; }
-
void setBrkPoint(Addr brk_point) { _brkPoint = brk_point; }
void setStackBase(Addr stack_base) { _stackBase = stack_base; }
void setStackSize(Addr stack_size) { _stackSize = stack_size; }
void setNextThreadStackBase(Addr ntsb) { _nextThreadStackBase = ntsb; }
void setMmapEnd(Addr mmap_end) { _mmapEnd = mmap_end; }
+ /*
+ * Extend the end of the mmap region by length bytes. Once a contiguous
+ * region of free virtual memory is found the start of that region is
+ * returned.
+ */
+ Addr extendMmap(Addr length);
+
+ /**
+ * Check if any page in the virtual address range from start_addr to
+ * start_addr + length is already mapped in the page table.
+ *
+ * @param start_addr Starting address of region to check.
+ * @param length Length of the range to check.
+ *
+ * @return true if all pages in the range are unmapped in page table
+ */
+ bool isUnmapped(Addr start_addr, Addr length);
+
+ /**
+ * Add a new memory region. The region represents a contiguous virtual
+ * address range which can map to physical memory or a host-backed file.
+ * Regions which are not file-backed should use -1 for sim_fd and 0 for
+ * offset.
+ *
+ * @param start_addr Starting address of the region.
+ * @param length Size of the region.
+ * @param name Name of region. Optional.
+ * @param sim_fd File descriptor for file-backed regions or -1.
+ * @param offset Offset in file in which region starts.
+ */
+ void mapRegion(Addr start_addr, Addr length,
+ const std::string& name="anon", int sim_fd=-1,
+ Addr offset=0);
+
+ /**
+ * Unmap a pre-existing region. Depending on the range being unmapped
+ * the resulting new regions will either be split, resized, or
+ * removed completely.
+ *
+ * @param start_addr Starting address of region to unmap.
+ * @param length Size of region to unmap.
+ */
+ void unmapRegion(Addr start_addr, Addr length);
+
+ /**
+ * Remap a pre-existing region. This changes the virtual address
+ * range of the region. This will result in regions being expanded
+ * if there is overlap with another region or simply moving the range
+ * otherwise.
+ *
+ * @param start_addr Start address of region being remapped.
+ * @param new_start_addr New start address of the region.
+ * @param length Length of the newly remapped region.
+ */
+ void remapRegion(Addr start_addr, Addr new_start_addr, Addr length);
+
+ /**
+ * Change the end of a process' program break. This represents the end
+ * of the heap segment of a process.
+ *
+ * @param old_brk Old program break address
+ * @param new_brk New program break address
+ */
+ void updateBrkRegion(Addr old_brk, Addr new_brk);
+
+ /**
+ * Attempt to fix up a fault at vaddr by allocating a page. The fault
+ * likely occurred because a virtual page which does not have physical
+ * page assignment is being accessed.
+ *
+ * @param vaddr The virtual address which is causing the fault.
+ * @return Whether the fault has been fixed.
+ */
+ bool fixupFault(Addr vaddr);
+
+ /**
+ * Given the vaddr and size, this method will chunk the allocation into
+ * page granularity and then request physical pages (frames) from the
+ * system object. After retrieving a frame, the method updates the page
+ * table mappings.
+ *
+ * @param vaddr The virtual address in need of a frame allocation.
+ * @param size The size in bytes of the requested mapping.
+ * @param clobber This flag specifies whether mappings in the page tables
+ * can be overwritten and replaced with the new mapping.
+ */
+ void allocateMem(Addr vaddr, int64_t size, bool clobber = false);
+
void
serialize(CheckpointOut &cp) const override
{
}
private:
+ /**
+ * Owner process of MemState. Used to manipulate page tables.
+ */
+ Process * _ownerProcess;
+
+ Addr _pageBytes;
Addr _brkPoint;
Addr _stackBase;
Addr _stackSize;
Addr _stackMin;
Addr _nextThreadStackBase;
Addr _mmapEnd;
+
+ /**
+ * Keeps record of the furthest mapped heap location.
+ */
+ Addr _endBrkPoint;
+
+ /**
+ * The _vmaList member is a list of virtual memory areas in the target
+ * application space that have been allocated by the target. In most
+ * operating systems, lazy allocation is used and these structures (or
+ * equivalent ones) are used to track the valid address ranges.
+ *
+ * This could use a more efficient data structure like an interval
+ * tree, but it is unclear whether the vmas will be modified often enough
+ * for the improvement in lookup time to matter. Unmapping VMAs currently
+ * modifies the list while iterating so the STL container must either
+ * support this or the unmapping method must be changed.
+ */
+ std::list<VMA> _vmaList;
};
#endif