| /* |
| * 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/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(); |
| } |