| //===-- DNBBreakpoint.cpp ---------------------------------------*- C++ -*-===// | |
| // | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| //===----------------------------------------------------------------------===// | |
| // | |
| // Created by Greg Clayton on 6/29/07. | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "DNBBreakpoint.h" | |
| #include "DNBLog.h" | |
| #include "MachProcess.h" | |
| #include <algorithm> | |
| #include <cassert> | |
| #include <cinttypes> | |
| #pragma mark-- DNBBreakpoint | |
| DNBBreakpoint::DNBBreakpoint(nub_addr_t addr, nub_size_t byte_size, | |
| bool hardware) | |
| : m_retain_count(1), m_byte_size(static_cast<uint32_t>(byte_size)), | |
| m_opcode(), m_addr(addr), m_enabled(0), m_hw_preferred(hardware), | |
| m_is_watchpoint(0), m_watch_read(0), m_watch_write(0), | |
| m_hw_index(INVALID_NUB_HW_INDEX) {} | |
| DNBBreakpoint::~DNBBreakpoint() = default; | |
| void DNBBreakpoint::Dump() const { | |
| if (IsBreakpoint()) { | |
| DNBLog("DNBBreakpoint addr = 0x%llx state = %s type = %s breakpoint " | |
| "hw_index = %i", | |
| (uint64_t)m_addr, m_enabled ? "enabled " : "disabled", | |
| IsHardware() ? "hardware" : "software", GetHardwareIndex()); | |
| } else { | |
| DNBLog("DNBBreakpoint addr = 0x%llx size = %llu state = %s type = %s " | |
| "watchpoint (%s%s) hw_index = %i", | |
| (uint64_t)m_addr, (uint64_t)m_byte_size, | |
| m_enabled ? "enabled " : "disabled", | |
| IsHardware() ? "hardware" : "software", m_watch_read ? "r" : "", | |
| m_watch_write ? "w" : "", GetHardwareIndex()); | |
| } | |
| } | |
| #pragma mark-- DNBBreakpointList | |
| DNBBreakpointList::DNBBreakpointList() = default; | |
| DNBBreakpointList::~DNBBreakpointList() = default; | |
| DNBBreakpoint *DNBBreakpointList::Add(nub_addr_t addr, nub_size_t length, | |
| bool hardware) { | |
| m_breakpoints.insert( | |
| std::make_pair(addr, DNBBreakpoint(addr, length, hardware))); | |
| iterator pos = m_breakpoints.find(addr); | |
| return &pos->second; | |
| } | |
| bool DNBBreakpointList::Remove(nub_addr_t addr) { | |
| iterator pos = m_breakpoints.find(addr); | |
| if (pos != m_breakpoints.end()) { | |
| m_breakpoints.erase(pos); | |
| return true; | |
| } | |
| return false; | |
| } | |
| DNBBreakpoint *DNBBreakpointList::FindByAddress(nub_addr_t addr) { | |
| iterator pos = m_breakpoints.find(addr); | |
| if (pos != m_breakpoints.end()) | |
| return &pos->second; | |
| return NULL; | |
| } | |
| const DNBBreakpoint *DNBBreakpointList::FindByAddress(nub_addr_t addr) const { | |
| const_iterator pos = m_breakpoints.find(addr); | |
| if (pos != m_breakpoints.end()) | |
| return &pos->second; | |
| return NULL; | |
| } | |
| const DNBBreakpoint * | |
| DNBBreakpointList::FindByHardwareIndex(uint32_t idx) const { | |
| for (const auto &pos : m_breakpoints) | |
| if (pos.second.GetHardwareIndex() == idx) | |
| return &pos.second; | |
| return nullptr; | |
| } | |
| const DNBBreakpoint * | |
| DNBBreakpointList::FindNearestWatchpoint(nub_addr_t addr) const { | |
| // Exact match | |
| for (const auto &pos : m_breakpoints) { | |
| if (pos.second.IsEnabled()) { | |
| nub_addr_t start_addr = pos.second.Address(); | |
| nub_addr_t end_addr = start_addr + pos.second.ByteSize(); | |
| if (addr >= start_addr && addr < end_addr) | |
| return &pos.second; | |
| } | |
| } | |
| // Find watchpoint nearest to this address | |
| // before or after the watched region of memory | |
| const DNBBreakpoint *closest = nullptr; | |
| uint32_t best_match = UINT32_MAX; | |
| for (const auto &pos : m_breakpoints) { | |
| if (pos.second.IsEnabled()) { | |
| nub_addr_t start_addr = pos.second.Address(); | |
| nub_addr_t end_addr = start_addr + pos.second.ByteSize(); | |
| uint32_t delta = addr < start_addr ? start_addr - addr : addr - end_addr; | |
| if (delta < best_match) { | |
| closest = &pos.second; | |
| best_match = delta; | |
| } | |
| } | |
| } | |
| return closest; | |
| } | |
| // Finds the next breakpoint at an address greater than or equal to "addr" | |
| size_t DNBBreakpointList::FindBreakpointsThatOverlapRange( | |
| nub_addr_t addr, nub_addr_t size, std::vector<DNBBreakpoint *> &bps) { | |
| bps.clear(); | |
| if (m_breakpoints.empty()) | |
| return bps.size(); | |
| iterator end = m_breakpoints.end(); | |
| // Find the first breakpoint with an address >= to "addr" | |
| iterator pos = m_breakpoints.lower_bound(addr); | |
| if (pos != m_breakpoints.begin()) { | |
| // Watch out for a breakpoint at an address less than "addr" that might | |
| // still overlap | |
| iterator prev_pos = pos; | |
| --prev_pos; | |
| if (prev_pos->second.IntersectsRange(addr, size, NULL, NULL, NULL)) | |
| bps.push_back(&prev_pos->second); | |
| } | |
| while (pos != end) { | |
| // When we hit a breakpoint whose start address is greater than "addr + | |
| // size" we are done. | |
| // Do the math in a way that doesn't risk unsigned overflow with bad | |
| // input. | |
| if ((pos->second.Address() - addr) >= size) | |
| break; | |
| // Check if this breakpoint overlaps, and if it does, add it to the list | |
| if (pos->second.IntersectsRange(addr, size, NULL, NULL, NULL)) | |
| bps.push_back(&pos->second); | |
| ++pos; | |
| } | |
| return bps.size(); | |
| } | |
| void DNBBreakpointList::Dump() const { | |
| const_iterator pos; | |
| const_iterator end = m_breakpoints.end(); | |
| for (pos = m_breakpoints.begin(); pos != end; ++pos) | |
| pos->second.Dump(); | |
| } | |
| void DNBBreakpointList::DisableAll() { | |
| iterator pos, end = m_breakpoints.end(); | |
| for (pos = m_breakpoints.begin(); pos != end; ++pos) | |
| pos->second.SetEnabled(false); | |
| } | |
| void DNBBreakpointList::RemoveTrapsFromBuffer(nub_addr_t addr, nub_size_t size, | |
| void *p) const { | |
| if (m_breakpoints.empty()) | |
| return; | |
| uint8_t *buf = (uint8_t *)p; | |
| const_iterator end = m_breakpoints.end(); | |
| const_iterator pos = m_breakpoints.lower_bound(addr); | |
| // lower_bound finds a breakpoint starting >= addr. The breakpoint prior to | |
| // that may start before addr but extend beyond it, so it must be checked too. | |
| if (pos != m_breakpoints.begin()) | |
| --pos; | |
| while (pos != end && (pos->first < (addr + size))) { | |
| nub_addr_t intersect_addr; | |
| nub_size_t intersect_size; | |
| nub_size_t opcode_offset; | |
| const DNBBreakpoint &bp = pos->second; | |
| if (bp.IntersectsRange(addr, size, &intersect_addr, &intersect_size, | |
| &opcode_offset)) { | |
| assert(addr <= intersect_addr && intersect_addr < addr + size); | |
| assert(addr < intersect_addr + intersect_size && | |
| intersect_addr + intersect_size <= addr + size); | |
| assert(opcode_offset + intersect_size <= bp.ByteSize()); | |
| nub_size_t buf_offset = intersect_addr - addr; | |
| ::memcpy(buf + buf_offset, bp.SavedOpcodeBytes() + opcode_offset, | |
| intersect_size); | |
| } | |
| ++pos; | |
| } | |
| } | |
| void DNBBreakpointList::DisableAllBreakpoints(MachProcess *process) { | |
| iterator pos, end = m_breakpoints.end(); | |
| for (pos = m_breakpoints.begin(); pos != end; ++pos) | |
| process->DisableBreakpoint(pos->second.Address(), false); | |
| } | |
| void DNBBreakpointList::DisableAllWatchpoints(MachProcess *process) { | |
| iterator pos, end = m_breakpoints.end(); | |
| for (pos = m_breakpoints.begin(); pos != end; ++pos) | |
| process->DisableWatchpoint(pos->second.Address(), false); | |
| } | |
| void DNBBreakpointList::RemoveDisabled() { | |
| iterator pos = m_breakpoints.begin(); | |
| while (pos != m_breakpoints.end()) { | |
| if (!pos->second.IsEnabled()) | |
| pos = m_breakpoints.erase(pos); | |
| else | |
| ++pos; | |
| } | |
| } |