Repository navigation
Expand file tree
/
Copy pathdebuggerstate.h
More file actions
380 lines (311 loc) · 14.5 KB
/
Copy pathdebuggerstate.h
File metadata and controls
380 lines (311 loc) · 14.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
/*
Copyright 2020-2026 Vector 35 Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#pragma once
#include <unordered_map>
#include "binaryninjaapi.h"
#include "ui/uitypes.h"
#include "debugadaptertype.h"
#include "debuggercommon.h"
#include "semaphore.h"
#include "ffi_global.h"
#include "refcountobject.h"
DECLARE_DEBUGGER_API_OBJECT(BNDebuggerState, DebuggerState);
namespace BinaryNinjaDebugger {
class DebuggerState;
typedef BNDebugAdapterConnectionStatus DebugAdapterConnectionStatus;
typedef BNDebugAdapterTargetStatus DebugAdapterTargetStatus;
class DebuggerRegisters
{
private:
DebuggerState* m_state;
std::unordered_map<std::string, DebugRegister> m_registerCache;
bool m_dirty;
std::recursive_mutex m_registersMutex;
std::unordered_map<std::string, DebugRegister> GetCachedRegisters();
public:
DebuggerRegisters(DebuggerState* state);
// DebugRegister operator[](std::string name);
intx::uint512 GetRegisterValue(const std::string& name);
bool SetRegisterValue(const std::string& name, intx::uint512 value);
void MarkDirty();
bool IsDirty() const { return m_dirty; }
void Update();
std::vector<DebugRegister> GetAllRegisters();
};
class DebuggerModules
{
private:
DebuggerState* m_state;
std::vector<DebugModule> m_modules;
bool m_dirty;
std::recursive_mutex m_modulesMutex;
public:
DebuggerModules(DebuggerState* state);
void MarkDirty();
void Update();
bool IsDirty() const { return m_dirty; }
std::vector<DebugModule> GetAllModules();
// TODO: These conversion functions are not very robust for lookup failures. They need to be improved for it.
DebugModule GetModuleByName(const std::string& module);
bool GetModuleBase(const std::string& name, uint64_t& address);
DebugModule GetModuleForAddress(uint64_t remoteAddress);
ModuleNameAndOffset AbsoluteAddressToRelative(uint64_t absoluteAddress);
uint64_t RelativeAddressToAbsolute(const ModuleNameAndOffset& relativeAddress);
};
class DebuggerMemoryMap
{
private:
DebuggerState* m_state;
std::vector<DebugMemoryRegion> m_regions;
bool m_dirty;
std::recursive_mutex m_regionsMutex;
public:
DebuggerMemoryMap(DebuggerState* state);
void MarkDirty();
void Update();
bool IsDirty() const { return m_dirty; }
std::vector<DebugMemoryRegion> GetAllRegions();
// Return the region that contains the given remote address, if any. `found` is set to false
// when no mapped region covers the address.
DebugMemoryRegion GetRegionForAddress(uint64_t remoteAddress, bool& found);
};
struct BreakpointEntry
{
ModuleNameAndOffset location;
bool enabled = true;
std::string condition;
uint64_t address = 0; // Absolute address (for absolute addressing or resolved relative)
DebugBreakpointType type = SoftwareBreakpoint; // Breakpoint type (Software, HardwareExecute, etc.)
size_t size = 1; // Size for hardware watchpoints
bool isRelative = true; // True if using module+offset, false if using absolute address
// Helper methods
bool IsSoftware() const { return type == SoftwareBreakpoint; }
bool IsHardware() const { return type != SoftwareBreakpoint; }
};
class DebuggerBreakpoints
{
private:
DebuggerState* m_state;
std::vector<BreakpointEntry> m_breakpoints;
public:
DebuggerBreakpoints(DebuggerState* state, std::vector<ModuleNameAndOffset> initial = {});
bool AddAbsolute(uint64_t remoteAddress);
bool AddOffset(const ModuleNameAndOffset& address);
bool RemoveAbsolute(uint64_t remoteAddress);
bool RemoveOffset(const ModuleNameAndOffset& address);
bool EnableAbsolute(uint64_t remoteAddress);
bool EnableOffset(const ModuleNameAndOffset& address);
bool DisableAbsolute(uint64_t remoteAddress);
bool DisableOffset(const ModuleNameAndOffset& address);
bool ContainsAbsolute(uint64_t address);
bool ContainsOffset(const ModuleNameAndOffset& address);
bool IsEnabledAbsolute(uint64_t address);
bool IsEnabledOffset(const ModuleNameAndOffset& address);
void Apply();
void SerializeMetadata();
void UnserializedMetadata();
std::vector<BreakpointEntry> GetBreakpointList() const { return m_breakpoints; }
bool SetConditionAbsolute(uint64_t remoteAddress, const std::string& condition);
bool SetConditionOffset(const ModuleNameAndOffset& address, const std::string& condition);
std::string GetConditionAbsolute(uint64_t address);
std::string GetConditionOffset(const ModuleNameAndOffset& address);
bool HasConditionAbsolute(uint64_t address);
bool HasConditionOffset(const ModuleNameAndOffset& address);
// Hardware breakpoint methods
bool AddHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size);
bool RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size);
bool EnableHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size);
bool DisableHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size);
bool ContainsHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size);
// Hardware breakpoint methods - module+offset (ASLR-safe)
bool AddHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size);
bool RemoveHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size);
bool EnableHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size);
bool DisableHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size);
bool ContainsHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size);
private:
// Find breakpoint by address, handling module name differences via absolute address comparison
std::vector<BreakpointEntry>::iterator FindBreakpoint(const ModuleNameAndOffset& address);
std::vector<BreakpointEntry>::const_iterator FindBreakpoint(const ModuleNameAndOffset& address) const;
};
class DebuggerThreads
{
private:
DebuggerState* m_state;
std::vector<DebugThread> m_threads;
std::map<uint32_t, std::vector<DebugFrame>> m_frames;
bool m_dirty;
std::recursive_mutex m_threadsMutex;
public:
DebuggerThreads(DebuggerState* state);
void MarkDirty();
void Update();
DebugThread GetActiveThread() const;
bool SetActiveThread(const DebugThread& thread);
bool IsDirty() const { return m_dirty; }
std::vector<DebugThread> GetAllThreads();
std::map<uint32_t, std::vector<DebugFrame>> GetAllFrames();
std::vector<DebugFrame> GetFramesOfThread(uint32_t tid);
bool SuspendThread(std::uint32_t tid);
bool ResumeThread(std::uint32_t tid);
void SymbolizeFrames(std::vector<DebugFrame>& frames);
};
enum MemoryByteCacheStatus
{
DefaultStatus,
UpToDateStatus,
OutOfDateStatus,
FailedToReadStatus
};
enum MemoryByteCacheSource
{
NoSource,
PausedTargetSource,
BackingBinaryViewSource
};
struct MemoryBytesCache
{
// The readable bytes of this cached run. Empty for a known-unreadable (hole) marker.
DataBuffer value;
// Number of bytes this entry covers, starting at its key address. For a readable run this equals
// value.GetLength(); for a hole marker it is the size of the unreadable region (a single byte).
uint64_t length;
MemoryByteCacheStatus status;
MemoryByteCacheSource source;
};
class DebuggerMemory
{
// The maximum number of bytes read from the backend and cached in a single block.
static constexpr uint64_t CacheBlockSize = 0x100;
DebuggerState* m_state;
std::map<uint64_t, MemoryBytesCache> m_valueCache;
std::map<uint64_t, std::pair<uint64_t, DataBuffer>> m_valueCachePrefilled;
std::recursive_mutex m_memoryMutex;
public:
DebuggerMemory(DebuggerState* state);
void MarkDirty();
// Reads the readable run starting exactly at `address` (up to CacheBlockSize bytes) and caches it, or returns
// an empty buffer if `address` itself is unreadable. The address is NOT rounded down to a cache-block boundary.
DataBuffer ReadAndCacheBlock(uint64_t address);
DataBuffer ReadMemory(uint64_t offset, size_t len);
bool WriteMemory(std::uintptr_t address, const DataBuffer& buffer);
void PrefillValueCache();
void OnRebased();
};
class DebuggerController;
// DebuggerState is the core of the debugger. Every operation is sent to this class, which then sends it the
// backend. After the backend responds, it first updates its internal state, and then update the UI (if the UI is
// enabled).
class DebuggerState
{
IMPLEMENT_DEBUGGER_API_OBJECT(BNDebuggerState);
private:
DebuggerController* m_controller;
DebugAdapterConnectionStatus m_connectionStatus;
DebugAdapterTargetStatus m_targetStatus;
DebugAdapter* m_adapter;
DebuggerModules* m_modules;
DebuggerMemoryMap* m_memoryMap;
DebuggerRegisters* m_registers;
DebuggerThreads* m_threads;
DebuggerBreakpoints* m_breakpoints;
DebuggerMemory* m_memory;
std::string m_adapterType;
std::vector<std::string> m_availableAdapters;
Ref<Architecture> m_remoteArch;
bool m_connectedToDebugServer = false;
// Serializes every live call into the adapter, regardless of which thread makes it
// (worker control ops, worker cache refresh, UI memory/register reads, UI writes).
// Held ONLY around an individual adapter call -- never across the run-wait -- so
// Pause/BreakInto can still acquire it while the target is running. Recursive to
// tolerate any synchronous re-entrant adapter call during a callback.
std::recursive_mutex m_adapterAccessMutex;
std::string GetBestAdapter(BinaryViewRef data);
public:
DebuggerState(Ref<BinaryView> data, DebuggerController* controller);
~DebuggerState();
DebugAdapter* GetAdapter() const { return m_adapter; }
std::recursive_mutex& AdapterAccessMutex() { return m_adapterAccessMutex; }
DebuggerController* GetController() const { return m_controller; }
DebuggerModules* GetModules() const { return m_modules; }
DebuggerMemoryMap* GetMemoryMap() const { return m_memoryMap; }
DebuggerBreakpoints* GetBreakpoints() const { return m_breakpoints; }
DebuggerRegisters* GetRegisters() const { return m_registers; }
DebuggerThreads* GetThreads() const { return m_threads; }
DebuggerMemory* GetMemory() const { return m_memory; }
// This is no longer a remote architecture, because we do not really read the remote arch
Ref<Architecture> GetRemoteArchitecture() const;
std::string GetAdapterType() const { return m_adapterType; }
std::string GetExecutablePath();
std::string GetInputFile();
std::string GetWorkingDirectory();
std::string GetCommandLineArguments();
std::string GetRemoteHost();
uint32_t GetRemotePort();
bool GetRequestTerminalEmulator();
int32_t GetPIDAttach();
void SetAdapterType(const std::string& adapter);
void SetExecutablePath(const std::string& path);
void SetInputFile(const std::string& path);
void SetWorkingDirectory(const std::string& directory);
void SetCommandLineArguments(const std::string& arguments);
void SetRemoteHost(const std::string& host);
void SetRemotePort(uint32_t port);
void SetRequestTerminalEmulator(bool requested);
void SetPIDAttach(int32_t pid);
// This is the center hub for adding and deleting breakpoints. It is called from DebugView, the CLI, the
// DebugBreakpointsWidget, and the planned C++/Python API.
// It will communicate with the adapter and add/delete the breakpoint. It will also update the UI if needed.
void AddBreakpoint(uint64_t address);
void AddBreakpoint(const ModuleNameAndOffset& address);
void DeleteBreakpoint(uint64_t address);
void DeleteBreakpoint(const ModuleNameAndOffset& address);
void EnableBreakpoint(uint64_t address);
void EnableBreakpoint(const ModuleNameAndOffset& address);
void DisableBreakpoint(uint64_t address);
void DisableBreakpoint(const ModuleNameAndOffset& address);
// Hardware breakpoint methods - absolute address
bool AddHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size);
bool RemoveHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size);
bool EnableHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size);
bool DisableHardwareBreakpoint(uint64_t address, DebugBreakpointType type, size_t size);
// Hardware breakpoint methods - module+offset (ASLR-safe)
bool AddHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size);
bool RemoveHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size);
bool EnableHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size);
bool DisableHardwareBreakpoint(const ModuleNameAndOffset& location, DebugBreakpointType type, size_t size);
uint64_t IP();
uint64_t StackPointer();
bool IsConnected() const { return m_connectionStatus == DebugAdapterConnectedStatus; }
bool IsConnecting() const { return m_connectionStatus == DebugAdapterConnectingStatus; }
bool IsRunning() const { return m_targetStatus == DebugAdapterRunningStatus; }
DebugAdapterConnectionStatus GetConnectionStatus() const { return m_connectionStatus; }
DebugAdapterTargetStatus GetTargetStatus() const { return m_targetStatus; }
bool IsConnectedToDebugServer() { return m_connectedToDebugServer; }
void SetConnectedToDebugServer(bool connected) { m_connectedToDebugServer = connected; }
// This is slightly different from the Python implementation. The caller does not need to first
// retrieve the DebuggerThreads object and then call SetActiveThread() on it. They call this function.
bool SetActiveThread(const DebugThread& thread);
void MarkDirty();
void UpdateCaches();
bool GetRemoteBase(uint64_t& address);
void ApplyBreakpoints();
void SetConnectionStatus(DebugAdapterConnectionStatus status) { m_connectionStatus = status; }
void SetExecutionStatus(DebugAdapterTargetStatus status) { m_targetStatus = status; }
std::vector<std::string> GetAvailableAdapters() { return m_availableAdapters; }
void SetAdapter(DebugAdapter* adapter) { m_adapter = adapter; }
// Check a debug adapter exists, or create one if necessary. Return true if an adapter exists or get created,
// return false if the adapter is still nullptr despite trying to create it
bool EnsureDebugAdapterExists();
};
}; // namespace BinaryNinjaDebugger