/* * 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 #include "arch/generic/mmu.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->pTable->pageSize()), _brkPoint(brk_point), _stackBase(stack_base), _stackSize(max_stack_size), _maxStackSize(max_stack_size), _stackMin(stack_base - 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->threads) { tc->getMMUPtr()->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->threads) { tc->getMMUPtr()->flushAll(); } do { if (!_ownerProcess->pTable->isUnmapped(start_addr, _pageBytes)) _ownerProcess->pTable->remap(start_addr, _pageBytes, new_start_addr); start_addr += _pageBytes; new_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) { auto *tc = _ownerProcess->system->threads[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; } std::string MemState::printVmaList() { std::stringstream file_content; for (auto vma : _vmaList) { std::stringstream line; line << std::hex << vma.start() << "-"; line << std::hex << vma.end() << " "; line << "r-xp 00000000 00:00 0 "; line << "[" << vma.getName() << "]" << std::endl; file_content << line.str(); } return file_content.str(); }