* Since disassembled code is actually function instance specific,
FunctionInstance does now also have a (DisassembledCode) source code
attribute. Function keeps its attribute, but it explicitly is a
FileSourceCode now.
* SourceCode:
- Removed GetStatementAtLocation(). Instead DisassembledCode has a
StatementAtLocation() now. As well as a StatementAtAddress() and
StatementAddressRange(). Rather cast to the subclass (in two instances)
instead of having those methods in the base class. In most cases we already
have the subclasses now, anyway.
- Added Lock()/Unlock(), which are implemented in FileSourceCode. The
statement ranges are no longer immutable, so we have to lock.
* TeamDebugModel:
- Revived GetBreakpointsInAddressRange().
- GetBreakpointsForSourceCode(): Optimized for DisassembledCode and fixed
in the FileSourceCode case. We need to compare with the functions' source
file instead of their source code, since they might not have the source
code set yet. Fixed two instances of the same problem in SourceView. Setting
breakpoints in functions that have no associated source code yet, works now.
* Team:
- GetStatementAtAddress(): Optimized by using the DisassembledCode, if
available.
- GetStatementAtSourceLocation(): If the supplied source code is
DisassembledCode, we have to get the statement from it directly, since
we can't get that information from the image debug info.
* TeamDebugInfo: Added LoadSourceCode() and DisassembleFunction(), the new way
to get FileSourceCode respectively DisassembledCode. SpecificTeamDebugInfo
has lost LoadSourceCode() and gained service methods AddSourceCodeInfo() and
ReadCode(). This avoids unnecessary code duplication in the subclasses.
Moreover it allows for joining source location info source files from
different images (and compilation units) -- interesting for inline functions
in headers.
* Adjusted LoadSourceCodeJob and TeamDebugger::FunctionSourceCodeRequested()
accordingly.
git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@31514 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
+39
-25
@@ -12,6 +12,8 @@
|
||||
#include "Architecture.h"
|
||||
#include "CpuState.h"
|
||||
#include "DebuggerInterface.h"
|
||||
#include "DisassembledCode.h"
|
||||
#include "FileSourceCode.h"
|
||||
#include "Function.h"
|
||||
#include "Image.h"
|
||||
#include "ImageDebugInfo.h"
|
||||
@@ -319,61 +321,73 @@ LoadImageDebugInfoJob::ScheduleIfNecessary(Worker* worker, Image* image,
|
||||
|
||||
LoadSourceCodeJob::LoadSourceCodeJob(
|
||||
DebuggerInterface* debuggerInterface, Architecture* architecture,
|
||||
Team* team, Function* function)
|
||||
Team* team, FunctionInstance* functionInstance, bool loadForFunction)
|
||||
:
|
||||
fDebuggerInterface(debuggerInterface),
|
||||
fArchitecture(architecture),
|
||||
fTeam(team),
|
||||
fFunction(function)
|
||||
fFunctionInstance(functionInstance),
|
||||
fLoadForFunction(loadForFunction)
|
||||
{
|
||||
fFunction->AddReference();
|
||||
fFunctionInstance->AddReference();
|
||||
}
|
||||
|
||||
|
||||
LoadSourceCodeJob::~LoadSourceCodeJob()
|
||||
{
|
||||
fFunction->RemoveReference();
|
||||
fFunctionInstance->RemoveReference();
|
||||
}
|
||||
|
||||
|
||||
JobKey
|
||||
LoadSourceCodeJob::Key() const
|
||||
{
|
||||
return JobKey(fFunction, JOB_TYPE_LOAD_SOURCE_CODE);
|
||||
return JobKey(fFunctionInstance, JOB_TYPE_LOAD_SOURCE_CODE);
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
LoadSourceCodeJob::Do()
|
||||
{
|
||||
// Get the function debug info for an instance which we can use to load the
|
||||
// source code.
|
||||
// if requested, try loading the source code for the function
|
||||
Function* function = fFunctionInstance->GetFunction();
|
||||
if (fLoadForFunction) {
|
||||
FileSourceCode* sourceCode;
|
||||
status_t error = fTeam->DebugInfo()->LoadSourceCode(
|
||||
function->SourceFile(), sourceCode);
|
||||
|
||||
AutoLocker<Team> locker(fTeam);
|
||||
|
||||
if (error == B_OK) {
|
||||
function->SetSourceCode(sourceCode, FUNCTION_SOURCE_LOADED);
|
||||
sourceCode->RemoveReference();
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
function->SetSourceCode(NULL, FUNCTION_SOURCE_UNAVAILABLE);
|
||||
}
|
||||
|
||||
// Only try to load the function instance code, if it's not overridden yet.
|
||||
AutoLocker<Team> locker(fTeam);
|
||||
|
||||
status_t error = B_OK;
|
||||
FunctionDebugInfo* functionDebugInfo = NULL;
|
||||
if (FunctionInstance* instance = fFunction->FirstInstance()) {
|
||||
functionDebugInfo = instance->GetFunctionDebugInfo();
|
||||
} else
|
||||
error = B_ENTRY_NOT_FOUND;
|
||||
Reference<FunctionDebugInfo> functionDebugInfoReference(functionDebugInfo);
|
||||
|
||||
if (fFunctionInstance->SourceCodeState() != FUNCTION_SOURCE_LOADING)
|
||||
return B_OK;
|
||||
locker.Unlock();
|
||||
|
||||
// load the source code, if we can
|
||||
SourceCode* sourceCode = NULL;
|
||||
if (error == B_OK) {
|
||||
error = functionDebugInfo->GetSpecificImageDebugInfo()->LoadSourceCode(
|
||||
functionDebugInfo, sourceCode);
|
||||
}
|
||||
// disassemble the function
|
||||
DisassembledCode* sourceCode = NULL;
|
||||
status_t error = fTeam->DebugInfo()->DisassembleFunction(fFunctionInstance,
|
||||
sourceCode);
|
||||
|
||||
// set the result
|
||||
locker.Lock();
|
||||
if (error == B_OK) {
|
||||
fFunction->SetSourceCode(sourceCode, FUNCTION_SOURCE_LOADED);
|
||||
sourceCode->RemoveReference();
|
||||
if (fFunctionInstance->SourceCodeState() == FUNCTION_SOURCE_LOADING) {
|
||||
fFunctionInstance->SetSourceCode(sourceCode,
|
||||
FUNCTION_SOURCE_LOADED);
|
||||
sourceCode->RemoveReference();
|
||||
}
|
||||
} else
|
||||
fFunction->SetSourceCode(NULL, FUNCTION_SOURCE_UNAVAILABLE);
|
||||
fFunctionInstance->SetSourceCode(NULL, FUNCTION_SOURCE_UNAVAILABLE);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ class Architecture;
|
||||
class CpuState;
|
||||
class DebuggerInterface;
|
||||
class Function;
|
||||
class FunctionInstance;
|
||||
class Image;
|
||||
class StackFrame;
|
||||
class Team;
|
||||
@@ -113,7 +114,8 @@ public:
|
||||
LoadSourceCodeJob(
|
||||
DebuggerInterface* debuggerInterface,
|
||||
Architecture* architecture, Team* team,
|
||||
Function* function);
|
||||
FunctionInstance* functionInstance,
|
||||
bool loadForFunction);
|
||||
virtual ~LoadSourceCodeJob();
|
||||
|
||||
virtual JobKey Key() const;
|
||||
@@ -123,7 +125,8 @@ private:
|
||||
DebuggerInterface* fDebuggerInterface;
|
||||
Architecture* fArchitecture;
|
||||
Team* fTeam;
|
||||
Function* fFunction;
|
||||
FunctionInstance* fFunctionInstance;
|
||||
bool fLoadForFunction;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -489,15 +489,27 @@ TeamDebugger::FunctionSourceCodeRequested(TeamWindow* window,
|
||||
|
||||
// mark loading
|
||||
AutoLocker< ::Team> locker(fTeam);
|
||||
if (function->SourceCodeState() != FUNCTION_SOURCE_NOT_LOADED)
|
||||
|
||||
if (functionInstance->SourceCodeState() != FUNCTION_SOURCE_NOT_LOADED)
|
||||
return;
|
||||
function->SetSourceCode(NULL, FUNCTION_SOURCE_LOADING);
|
||||
if (function->SourceCodeState() == FUNCTION_SOURCE_LOADED)
|
||||
return;
|
||||
|
||||
functionInstance->SetSourceCode(NULL, FUNCTION_SOURCE_LOADING);
|
||||
|
||||
bool loadForFunction = false;
|
||||
if (function->SourceCodeState() == FUNCTION_SOURCE_NOT_LOADED) {
|
||||
loadForFunction = true;
|
||||
function->SetSourceCode(NULL, FUNCTION_SOURCE_LOADING);
|
||||
}
|
||||
|
||||
locker.Unlock();
|
||||
|
||||
// schedule the job
|
||||
if (fWorker->ScheduleJob(
|
||||
new(std::nothrow) LoadSourceCodeJob(fDebuggerInterface,
|
||||
fDebuggerInterface->GetArchitecture(), fTeam, function),
|
||||
fDebuggerInterface->GetArchitecture(), fTeam, functionInstance,
|
||||
loadForFunction),
|
||||
this) != B_OK) {
|
||||
// scheduling failed -- mark unavailable
|
||||
locker.Lock();
|
||||
|
||||
@@ -13,12 +13,12 @@
|
||||
|
||||
|
||||
class CpuState;
|
||||
class DisassembledCode;
|
||||
class FunctionDebugInfo;
|
||||
class Image;
|
||||
class ImageDebugInfoProvider;
|
||||
class InstructionInfo;
|
||||
class Register;
|
||||
class SourceCode;
|
||||
class StackFrame;
|
||||
class StackTrace;
|
||||
class Statement;
|
||||
@@ -57,7 +57,7 @@ public:
|
||||
|
||||
virtual status_t DisassembleCode(FunctionDebugInfo* function,
|
||||
const void* buffer, size_t bufferSize,
|
||||
SourceCode*& _sourceCode) = 0;
|
||||
DisassembledCode*& _sourceCode) = 0;
|
||||
virtual status_t GetStatement(FunctionDebugInfo* function,
|
||||
target_addr_t address,
|
||||
Statement*& _statement) = 0;
|
||||
|
||||
@@ -271,7 +271,7 @@ ArchitectureX86::UpdateStackFrameCpuState(const StackFrame* frame,
|
||||
|
||||
status_t
|
||||
ArchitectureX86::DisassembleCode(FunctionDebugInfo* function,
|
||||
const void* buffer, size_t bufferSize, SourceCode*& _sourceCode)
|
||||
const void* buffer, size_t bufferSize, DisassembledCode*& _sourceCode)
|
||||
{
|
||||
DisassembledCode* source = new(std::nothrow) DisassembledCode;
|
||||
if (source == NULL)
|
||||
|
||||
@@ -35,7 +35,7 @@ public:
|
||||
|
||||
virtual status_t DisassembleCode(FunctionDebugInfo* function,
|
||||
const void* buffer, size_t bufferSize,
|
||||
SourceCode*& _sourceCode);
|
||||
DisassembledCode*& _sourceCode);
|
||||
virtual status_t GetStatement(FunctionDebugInfo* function,
|
||||
target_addr_t address,
|
||||
Statement*& _statement);
|
||||
|
||||
@@ -87,29 +87,6 @@ DebuggerImageDebugInfo::CreateFrame(Image* image, FunctionDebugInfo* function,
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
DebuggerImageDebugInfo::LoadSourceCode(FunctionDebugInfo* function,
|
||||
SourceCode*& _sourceCode)
|
||||
{
|
||||
// allocate a buffer for the function code
|
||||
static const target_size_t kMaxBufferSize = 64 * 1024;
|
||||
target_size_t bufferSize = std::min(function->Size(), kMaxBufferSize);
|
||||
void* buffer = malloc(bufferSize);
|
||||
if (buffer == NULL)
|
||||
return B_NO_MEMORY;
|
||||
MemoryDeleter bufferDeleter(buffer);
|
||||
|
||||
// read the function code
|
||||
ssize_t bytesRead = fDebuggerInterface->ReadMemory(function->Address(),
|
||||
buffer, bufferSize);
|
||||
if (bytesRead < 0)
|
||||
return bytesRead;
|
||||
|
||||
return fArchitecture->DisassembleCode(function, buffer, bytesRead,
|
||||
_sourceCode);
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
DebuggerImageDebugInfo::GetStatement(FunctionDebugInfo* function,
|
||||
target_addr_t address, Statement*& _statement)
|
||||
@@ -127,6 +104,22 @@ DebuggerImageDebugInfo::GetStatementAtSourceLocation(
|
||||
}
|
||||
|
||||
|
||||
ssize_t
|
||||
DebuggerImageDebugInfo::ReadCode(target_addr_t address, void* buffer,
|
||||
size_t size)
|
||||
{
|
||||
return fDebuggerInterface->ReadMemory(address, buffer, size);
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
DebuggerImageDebugInfo::AddSourceCodeInfo(LocatableFile* file,
|
||||
FileSourceCode* sourceCode)
|
||||
{
|
||||
return B_UNSUPPORTED;
|
||||
}
|
||||
|
||||
|
||||
/*static*/ int
|
||||
DebuggerImageDebugInfo::_CompareSymbols(const SymbolInfo* a,
|
||||
const SymbolInfo* b)
|
||||
|
||||
@@ -31,8 +31,6 @@ public:
|
||||
CpuState* cpuState,
|
||||
StackFrame*& _previousFrame,
|
||||
CpuState*& _previousCpuState);
|
||||
virtual status_t LoadSourceCode(FunctionDebugInfo* function,
|
||||
SourceCode*& _sourceCode);
|
||||
virtual status_t GetStatement(FunctionDebugInfo* function,
|
||||
target_addr_t address,
|
||||
Statement*& _statement);
|
||||
@@ -41,6 +39,12 @@ public:
|
||||
const SourceLocation& sourceLocation,
|
||||
Statement*& _statement);
|
||||
|
||||
virtual ssize_t ReadCode(target_addr_t address, void* buffer,
|
||||
size_t size);
|
||||
|
||||
virtual status_t AddSourceCodeInfo(LocatableFile* file,
|
||||
FileSourceCode* sourceCode);
|
||||
|
||||
private:
|
||||
static int _CompareSymbols(const SymbolInfo* a,
|
||||
const SymbolInfo* b);
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
* Distributed under the terms of the MIT License.
|
||||
*/
|
||||
|
||||
|
||||
#include "DwarfImageDebugInfo.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
@@ -30,84 +32,6 @@
|
||||
#include "StringUtils.h"
|
||||
|
||||
|
||||
// #pragma mark - SourceCodeEntry
|
||||
|
||||
|
||||
struct DwarfImageDebugInfo::SourceCodeKey {
|
||||
CompilationUnit* unit;
|
||||
LocatableFile* file;
|
||||
|
||||
SourceCodeKey(CompilationUnit* unit, LocatableFile* file)
|
||||
:
|
||||
unit(unit),
|
||||
file(file)
|
||||
{
|
||||
file->AcquireReference();
|
||||
}
|
||||
|
||||
~SourceCodeKey()
|
||||
{
|
||||
file->ReleaseReference();
|
||||
}
|
||||
|
||||
uint32 HashValue() const
|
||||
{
|
||||
return (uint32)((addr_t)unit ^ (addr_t)file);
|
||||
}
|
||||
|
||||
bool operator==(const SourceCodeKey& other) const
|
||||
{
|
||||
return unit == other.unit && file == other.file;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
struct DwarfImageDebugInfo::SourceCodeEntry : SourceCodeKey,
|
||||
HashTableLink<SourceCodeEntry> {
|
||||
FileSourceCode* sourceCode;
|
||||
|
||||
SourceCodeEntry(CompilationUnit* unit, LocatableFile* file,
|
||||
FileSourceCode* sourceCode)
|
||||
:
|
||||
SourceCodeKey(unit, file),
|
||||
sourceCode(sourceCode)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// #pragma mark - SourceCodeHashDefinition
|
||||
|
||||
|
||||
struct DwarfImageDebugInfo::SourceCodeHashDefinition {
|
||||
typedef SourceCodeKey KeyType;
|
||||
typedef SourceCodeEntry ValueType;
|
||||
|
||||
size_t HashKey(const SourceCodeKey& key) const
|
||||
{
|
||||
return key.HashValue();
|
||||
}
|
||||
|
||||
size_t Hash(const SourceCodeEntry* value) const
|
||||
{
|
||||
return value->HashValue();
|
||||
}
|
||||
|
||||
bool Compare(const SourceCodeKey& key, const SourceCodeEntry* value) const
|
||||
{
|
||||
return key == *value;
|
||||
}
|
||||
|
||||
HashTableLink<SourceCodeEntry>* GetLink(SourceCodeEntry* value) const
|
||||
{
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// #pragma mark - DwarfImageDebugInfo
|
||||
|
||||
|
||||
DwarfImageDebugInfo::DwarfImageDebugInfo(const ImageInfo& imageInfo,
|
||||
Architecture* architecture, FileManager* fileManager, DwarfFile* file)
|
||||
:
|
||||
@@ -117,22 +41,13 @@ DwarfImageDebugInfo::DwarfImageDebugInfo(const ImageInfo& imageInfo,
|
||||
fFileManager(fileManager),
|
||||
fFile(file),
|
||||
fTextSegment(NULL),
|
||||
fRelocationDelta(0),
|
||||
fSourceCodes(NULL)
|
||||
fRelocationDelta(0)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
DwarfImageDebugInfo::~DwarfImageDebugInfo()
|
||||
{
|
||||
SourceCodeEntry* entry = fSourceCodes->Clear(true);
|
||||
while (entry != NULL) {
|
||||
SourceCodeEntry* next = entry->fNext;
|
||||
entry->sourceCode->ReleaseReference();
|
||||
entry = next;
|
||||
}
|
||||
|
||||
delete fSourceCodes;
|
||||
}
|
||||
|
||||
|
||||
@@ -143,14 +58,6 @@ DwarfImageDebugInfo::Init()
|
||||
if (error != B_OK)
|
||||
return error;
|
||||
|
||||
fSourceCodes = new (std::nothrow) SourceCodeTable;
|
||||
if (fSourceCodes == NULL)
|
||||
return B_NO_MEMORY;
|
||||
|
||||
error = fSourceCodes->Init();
|
||||
if (error != B_OK)
|
||||
return error;
|
||||
|
||||
fTextSegment = fFile->GetElfFile()->TextSegment();
|
||||
if (fTextSegment == NULL)
|
||||
return B_ENTRY_NOT_FOUND;
|
||||
@@ -286,38 +193,6 @@ DwarfImageDebugInfo::CreateFrame(Image* image, FunctionDebugInfo* function,
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
DwarfImageDebugInfo::LoadSourceCode(FunctionDebugInfo* function,
|
||||
SourceCode*& _sourceCode)
|
||||
{
|
||||
AutoLocker<BLocker> locker(fLock);
|
||||
|
||||
status_t error = _LoadSourceCode(function, _sourceCode);
|
||||
if (error == B_OK)
|
||||
return B_OK;
|
||||
|
||||
// fall back to disassembling
|
||||
|
||||
static const target_size_t kMaxBufferSize = 64 * 1024;
|
||||
target_size_t bufferSize = std::min(function->Size(), kMaxBufferSize);
|
||||
void* buffer = malloc(bufferSize);
|
||||
if (buffer == NULL)
|
||||
return B_NO_MEMORY;
|
||||
MemoryDeleter bufferDeleter(buffer);
|
||||
|
||||
// read the function code
|
||||
target_addr_t functionOffset = function->Address() - fRelocationDelta
|
||||
- fTextSegment->LoadAddress() + fTextSegment->FileOffset();
|
||||
ssize_t bytesRead = pread(fFile->GetElfFile()->FD(), buffer, bufferSize,
|
||||
functionOffset);
|
||||
if (bytesRead < 0)
|
||||
return bytesRead;
|
||||
|
||||
return fArchitecture->DisassembleCode(function, buffer, bytesRead,
|
||||
_sourceCode);
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
DwarfImageDebugInfo::GetStatement(FunctionDebugInfo* _function,
|
||||
target_addr_t address, Statement*& _statement)
|
||||
@@ -489,55 +364,41 @@ printf(" -> found statement!\n");
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
DwarfImageDebugInfo::_LoadSourceCode(FunctionDebugInfo* _function,
|
||||
SourceCode*& _sourceCode)
|
||||
ssize_t
|
||||
DwarfImageDebugInfo::ReadCode(target_addr_t address, void* buffer, size_t size)
|
||||
{
|
||||
DwarfFunctionDebugInfo* function
|
||||
= dynamic_cast<DwarfFunctionDebugInfo*>(_function);
|
||||
if (function == NULL)
|
||||
return B_BAD_VALUE;
|
||||
|
||||
// get the source file
|
||||
LocatableFile* file = function->SourceFile();
|
||||
if (file == NULL)
|
||||
return B_ENTRY_NOT_FOUND;
|
||||
|
||||
// maybe it's already loaded
|
||||
CompilationUnit* unit = function->GetCompilationUnit();
|
||||
FileSourceCode* sourceCode = _LookupSourceCode(unit, file);
|
||||
if (sourceCode) {
|
||||
sourceCode->AcquireReference();
|
||||
_sourceCode = sourceCode;
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
// get the index of the source file in the compilation unit for cheaper
|
||||
// comparison below
|
||||
int32 fileIndex = _GetSourceFileIndex(unit, function->SourceFile());
|
||||
printf("DwarfImageDebugInfo::_LoadSourceCode(), file: %ld, function at: %#llx\n", fileIndex, function->Address());
|
||||
|
||||
for (int32 i = 0; const char* fileName = unit->FileAt(i, NULL); i++) {
|
||||
printf(" file %ld: %s\n", i, fileName);
|
||||
target_addr_t offset = address - fRelocationDelta
|
||||
- fTextSegment->LoadAddress() + fTextSegment->FileOffset();
|
||||
ssize_t bytesRead = pread(fFile->GetElfFile()->FD(), buffer, size, offset);
|
||||
return bytesRead >= 0 ? bytesRead : errno;
|
||||
}
|
||||
|
||||
// not loaded yet -- get the source file
|
||||
SourceFile* sourceFile;
|
||||
status_t error = fFileManager->LoadSourceFile(file, sourceFile);
|
||||
if (error != B_OK)
|
||||
return error;
|
||||
|
||||
// create the source code
|
||||
sourceCode = new(std::nothrow) FileSourceCode(file, sourceFile);
|
||||
sourceFile->ReleaseReference();
|
||||
if (sourceCode == NULL)
|
||||
return B_NO_MEMORY;
|
||||
status_t
|
||||
DwarfImageDebugInfo::AddSourceCodeInfo(LocatableFile* file,
|
||||
FileSourceCode* sourceCode)
|
||||
{
|
||||
bool addedAny = false;
|
||||
for (int32 i = 0; CompilationUnit* unit = fFile->CompilationUnitAt(i);
|
||||
i++) {
|
||||
int32 fileIndex = _GetSourceFileIndex(unit, file);
|
||||
if (fileIndex < 0)
|
||||
continue;
|
||||
|
||||
error = sourceCode->Init();
|
||||
if (error != B_OK)
|
||||
return error;
|
||||
ObjectDeleter<FileSourceCode> sourceCodeDeleter(sourceCode);
|
||||
status_t error = _AddSourceCodeInfo(unit, sourceCode, fileIndex);
|
||||
if (error == B_NO_MEMORY)
|
||||
return error;
|
||||
addedAny |= error == B_OK;
|
||||
}
|
||||
|
||||
return addedAny ? B_OK : B_ENTRY_NOT_FOUND;
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
DwarfImageDebugInfo::_AddSourceCodeInfo(CompilationUnit* unit,
|
||||
FileSourceCode* sourceCode, int32 fileIndex)
|
||||
{
|
||||
// Get the statements by executing the line number program for the
|
||||
// compilation unit and filtering the rows for our source file.
|
||||
LineNumberProgram& program = unit->GetLineNumberProgram();
|
||||
@@ -559,7 +420,7 @@ printf(" %#lx (%ld, %ld, %ld) %d\n", state.address, state.file, state.line, s
|
||||
target_addr_t endAddress = state.address;
|
||||
if (endAddress > statementAddress) {
|
||||
// add the statement
|
||||
error = sourceCode->AddSourceLocation(
|
||||
status_t error = sourceCode->AddSourceLocation(
|
||||
SourceLocation(statementLine, statementColumn));
|
||||
if (error != B_OK)
|
||||
return error;
|
||||
@@ -580,28 +441,10 @@ printf(" -> statement: %#llx - %#llx, source location: (%ld, %ld)\n", statement
|
||||
}
|
||||
}
|
||||
|
||||
SourceCodeEntry* entry = new(std::nothrow) SourceCodeEntry(unit, file,
|
||||
sourceCode);
|
||||
if (entry == NULL)
|
||||
return B_NO_MEMORY;
|
||||
|
||||
fSourceCodes->Insert(entry);
|
||||
|
||||
_sourceCode = sourceCodeDeleter.Detach();
|
||||
_sourceCode->AcquireReference();
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
FileSourceCode*
|
||||
DwarfImageDebugInfo::_LookupSourceCode(CompilationUnit* unit,
|
||||
LocatableFile* file)
|
||||
{
|
||||
SourceCodeEntry* entry = fSourceCodes->Lookup(SourceCodeKey(unit, file));
|
||||
return entry != NULL ? entry->sourceCode : NULL;
|
||||
}
|
||||
|
||||
|
||||
int32
|
||||
DwarfImageDebugInfo::_GetSourceFileIndex(CompilationUnit* unit,
|
||||
LocatableFile* sourceFile) const
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#ifndef DWARF_IMAGE_DEBUG_INFO_H
|
||||
#define DWARF_IMAGE_DEBUG_INFO_H
|
||||
|
||||
|
||||
#include <Locker.h>
|
||||
|
||||
#include <util/OpenHashTable.h>
|
||||
@@ -42,8 +43,6 @@ public:
|
||||
CpuState* cpuState,
|
||||
StackFrame*& _previousFrame,
|
||||
CpuState*& _previousCpuState);
|
||||
virtual status_t LoadSourceCode(FunctionDebugInfo* function,
|
||||
SourceCode*& _sourceCode);
|
||||
virtual status_t GetStatement(FunctionDebugInfo* function,
|
||||
target_addr_t address,
|
||||
Statement*& _statement);
|
||||
@@ -52,18 +51,16 @@ public:
|
||||
const SourceLocation& sourceLocation,
|
||||
Statement*& _statement);
|
||||
|
||||
private:
|
||||
struct SourceCodeKey;
|
||||
struct SourceCodeEntry;
|
||||
struct SourceCodeHashDefinition;
|
||||
virtual ssize_t ReadCode(target_addr_t address, void* buffer,
|
||||
size_t size);
|
||||
|
||||
typedef OpenHashTable<SourceCodeHashDefinition> SourceCodeTable;
|
||||
virtual status_t AddSourceCodeInfo(LocatableFile* file,
|
||||
FileSourceCode* sourceCode);
|
||||
|
||||
private:
|
||||
status_t _LoadSourceCode(FunctionDebugInfo* function,
|
||||
SourceCode*& _sourceCode);
|
||||
FileSourceCode* _LookupSourceCode(CompilationUnit* unit,
|
||||
LocatableFile* file);
|
||||
status_t _AddSourceCodeInfo(CompilationUnit* unit,
|
||||
FileSourceCode* sourceCode,
|
||||
int32 fileIndex);
|
||||
int32 _GetSourceFileIndex(CompilationUnit* unit,
|
||||
LocatableFile* sourceFile) const;
|
||||
|
||||
@@ -75,7 +72,6 @@ private:
|
||||
DwarfFile* fFile;
|
||||
ElfSegment* fTextSegment;
|
||||
target_addr_t fRelocationDelta;
|
||||
SourceCodeTable* fSourceCodes;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -5,13 +5,14 @@
|
||||
|
||||
#include "Function.h"
|
||||
|
||||
#include "SourceCode.h"
|
||||
#include "FileSourceCode.h"
|
||||
|
||||
|
||||
Function::Function()
|
||||
:
|
||||
fSourceCode(NULL),
|
||||
fSourceCodeState(FUNCTION_SOURCE_NOT_LOADED)
|
||||
fSourceCodeState(FUNCTION_SOURCE_NOT_LOADED),
|
||||
fNotificationsDisabled(0)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -23,7 +24,7 @@ Function::~Function()
|
||||
|
||||
|
||||
void
|
||||
Function::SetSourceCode(SourceCode* source, function_source_state state)
|
||||
Function::SetSourceCode(FileSourceCode* source, function_source_state state)
|
||||
{
|
||||
if (source == fSourceCode && state == fSourceCodeState)
|
||||
return;
|
||||
@@ -34,14 +35,20 @@ Function::SetSourceCode(SourceCode* source, function_source_state state)
|
||||
fSourceCode = source;
|
||||
fSourceCodeState = state;
|
||||
|
||||
if (fSourceCode != NULL)
|
||||
if (fSourceCode != NULL) {
|
||||
fSourceCode->AddReference();
|
||||
|
||||
// notify listeners
|
||||
for (ListenerList::Iterator it = fListeners.GetIterator();
|
||||
Listener* listener = it.Next();) {
|
||||
listener->FunctionSourceCodeChanged(this);
|
||||
// unset all instances' source codes
|
||||
fNotificationsDisabled++;
|
||||
for (FunctionInstanceList::Iterator it = fInstances.GetIterator();
|
||||
FunctionInstance* instance = it.Next();) {
|
||||
instance->SetSourceCode(NULL, FUNCTION_SOURCE_NOT_LOADED);
|
||||
}
|
||||
fNotificationsDisabled--;
|
||||
}
|
||||
|
||||
// notify listeners
|
||||
NotifySourceCodeChanged();
|
||||
}
|
||||
|
||||
|
||||
@@ -59,11 +66,9 @@ Function::RemoveListener(Listener* listener)
|
||||
}
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
void
|
||||
Function::AddInstance(FunctionInstance* instance)
|
||||
{
|
||||
printf(" %p: added %p\n", this, instance);
|
||||
fInstances.Add(instance);
|
||||
}
|
||||
|
||||
@@ -71,11 +76,23 @@ printf(" %p: added %p\n", this, instance);
|
||||
void
|
||||
Function::RemoveInstance(FunctionInstance* instance)
|
||||
{
|
||||
printf(" %p: removed %p\n", this, instance);
|
||||
fInstances.Remove(instance);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
Function::NotifySourceCodeChanged()
|
||||
{
|
||||
if (fNotificationsDisabled > 0)
|
||||
return;
|
||||
|
||||
for (ListenerList::Iterator it = fListeners.GetIterator();
|
||||
Listener* listener = it.Next();) {
|
||||
listener->FunctionSourceCodeChanged(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// #pragma mark - Listener
|
||||
|
||||
|
||||
|
||||
@@ -11,15 +11,7 @@
|
||||
#include "FunctionInstance.h"
|
||||
|
||||
|
||||
enum function_source_state {
|
||||
FUNCTION_SOURCE_NOT_LOADED,
|
||||
FUNCTION_SOURCE_LOADING,
|
||||
FUNCTION_SOURCE_LOADED,
|
||||
FUNCTION_SOURCE_UNAVAILABLE
|
||||
};
|
||||
|
||||
|
||||
class SourceCode;
|
||||
class FileSourceCode;
|
||||
|
||||
|
||||
class Function : public Referenceable, public HashTableLink<Function> {
|
||||
@@ -49,10 +41,10 @@ public:
|
||||
->GetSourceLocation(); }
|
||||
|
||||
// mutable attributes follow (locking required)
|
||||
SourceCode* GetSourceCode() const { return fSourceCode; }
|
||||
FileSourceCode* GetSourceCode() const { return fSourceCode; }
|
||||
function_source_state SourceCodeState() const
|
||||
{ return fSourceCodeState; }
|
||||
void SetSourceCode(SourceCode* source,
|
||||
void SetSourceCode(FileSourceCode* source,
|
||||
function_source_state state);
|
||||
|
||||
void AddListener(Listener* listener);
|
||||
@@ -62,14 +54,17 @@ public:
|
||||
void AddInstance(FunctionInstance* instance);
|
||||
void RemoveInstance(FunctionInstance* instance);
|
||||
|
||||
void NotifySourceCodeChanged();
|
||||
|
||||
private:
|
||||
typedef DoublyLinkedList<Listener> ListenerList;
|
||||
|
||||
private:
|
||||
FunctionInstanceList fInstances;
|
||||
SourceCode* fSourceCode;
|
||||
FileSourceCode* fSourceCode;
|
||||
function_source_state fSourceCodeState;
|
||||
ListenerList fListeners;
|
||||
int32 fNotificationsDisabled;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include "FunctionInstance.h"
|
||||
|
||||
#include "DisassembledCode.h"
|
||||
#include "Function.h"
|
||||
|
||||
|
||||
@@ -13,7 +14,9 @@ FunctionInstance::FunctionInstance(ImageDebugInfo* imageDebugInfo,
|
||||
:
|
||||
fImageDebugInfo(imageDebugInfo),
|
||||
fFunction(NULL),
|
||||
fFunctionDebugInfo(functionDebugInfo)
|
||||
fFunctionDebugInfo(functionDebugInfo),
|
||||
fSourceCode(NULL),
|
||||
fSourceCodeState(FUNCTION_SOURCE_NOT_LOADED)
|
||||
{
|
||||
fFunctionDebugInfo->AcquireReference();
|
||||
// TODO: What about fImageDebugInfo? We must be careful regarding cyclic
|
||||
@@ -39,3 +42,24 @@ FunctionInstance::SetFunction(Function* function)
|
||||
if (fFunction != NULL)
|
||||
fFunction->AcquireReference();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
FunctionInstance::SetSourceCode(DisassembledCode* source,
|
||||
function_source_state state)
|
||||
{
|
||||
if (source == fSourceCode && state == fSourceCodeState)
|
||||
return;
|
||||
|
||||
if (fSourceCode != NULL)
|
||||
fSourceCode->RemoveReference();
|
||||
|
||||
fSourceCode = source;
|
||||
fSourceCodeState = state;
|
||||
|
||||
if (fSourceCode != NULL)
|
||||
fSourceCode->AddReference();
|
||||
|
||||
if (fFunction != NULL)
|
||||
fFunction->NotifySourceCodeChanged();
|
||||
}
|
||||
|
||||
@@ -10,6 +10,15 @@
|
||||
#include "FunctionDebugInfo.h"
|
||||
|
||||
|
||||
enum function_source_state {
|
||||
FUNCTION_SOURCE_NOT_LOADED,
|
||||
FUNCTION_SOURCE_LOADING,
|
||||
FUNCTION_SOURCE_LOADED,
|
||||
FUNCTION_SOURCE_UNAVAILABLE
|
||||
};
|
||||
|
||||
|
||||
class DisassembledCode;
|
||||
class Function;
|
||||
class FunctionDebugInfo;
|
||||
class ImageDebugInfo;
|
||||
@@ -45,10 +54,21 @@ public:
|
||||
|
||||
void SetFunction(Function* function);
|
||||
// package private
|
||||
|
||||
// mutable attributes follow (locking required)
|
||||
DisassembledCode* GetSourceCode() const
|
||||
{ return fSourceCode; }
|
||||
function_source_state SourceCodeState() const
|
||||
{ return fSourceCodeState; }
|
||||
void SetSourceCode(DisassembledCode* source,
|
||||
function_source_state state);
|
||||
|
||||
private:
|
||||
ImageDebugInfo* fImageDebugInfo;
|
||||
Function* fFunction;
|
||||
FunctionDebugInfo* fFunctionDebugInfo;
|
||||
DisassembledCode* fSourceCode;
|
||||
function_source_state fSourceCodeState;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -96,6 +96,23 @@ ImageDebugInfo::FunctionAtAddress(target_addr_t address) const
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
ImageDebugInfo::AddSourceCodeInfo(LocatableFile* file,
|
||||
FileSourceCode* sourceCode) const
|
||||
{
|
||||
bool addedAny = false;
|
||||
for (int32 i = 0; SpecificImageDebugInfo* specificInfo
|
||||
= fSpecificInfos.ItemAt(i); i++) {
|
||||
status_t error = specificInfo->AddSourceCodeInfo(file, sourceCode);
|
||||
if (error == B_NO_MEMORY)
|
||||
return error;
|
||||
addedAny |= error == B_OK;
|
||||
}
|
||||
|
||||
return addedAny ? B_OK : B_ENTRY_NOT_FOUND;
|
||||
}
|
||||
|
||||
|
||||
/*static*/ int
|
||||
ImageDebugInfo::_CompareFunctions(const FunctionInstance* a,
|
||||
const FunctionInstance* b)
|
||||
|
||||
@@ -16,8 +16,10 @@
|
||||
|
||||
class Architecture;
|
||||
class DebuggerInterface;
|
||||
class FileSourceCode;
|
||||
class FunctionDebugInfo;
|
||||
class FunctionInstance;
|
||||
class LocatableFile;
|
||||
class SpecificImageDebugInfo;
|
||||
|
||||
|
||||
@@ -33,6 +35,9 @@ public:
|
||||
FunctionInstance* FunctionAt(int32 index) const;
|
||||
FunctionInstance* FunctionAtAddress(target_addr_t address) const;
|
||||
|
||||
status_t AddSourceCodeInfo(LocatableFile* file,
|
||||
FileSourceCode* sourceCode) const;
|
||||
|
||||
private:
|
||||
typedef BObjectList<SpecificImageDebugInfo> SpecificInfoList;
|
||||
typedef BObjectList<FunctionInstance> FunctionList;
|
||||
|
||||
@@ -14,9 +14,10 @@
|
||||
class Architecture;
|
||||
class CpuState;
|
||||
class DebuggerInterface;
|
||||
class FileSourceCode;
|
||||
class FunctionDebugInfo;
|
||||
class Image;
|
||||
class SourceCode;
|
||||
class LocatableFile;
|
||||
class SourceLocation;
|
||||
class StackFrame;
|
||||
class Statement;
|
||||
@@ -39,9 +40,6 @@ public:
|
||||
// returns reference to previous frame
|
||||
// and CPU state; returned CPU state
|
||||
// can be NULL; can return B_UNSUPPORTED
|
||||
virtual status_t LoadSourceCode(FunctionDebugInfo* function,
|
||||
SourceCode*& _sourceCode) = 0;
|
||||
// returns reference
|
||||
virtual status_t GetStatement(FunctionDebugInfo* function,
|
||||
target_addr_t address,
|
||||
Statement*& _statement) = 0;
|
||||
@@ -51,6 +49,12 @@ public:
|
||||
const SourceLocation& sourceLocation,
|
||||
Statement*& _statement) = 0;
|
||||
// returns reference
|
||||
|
||||
virtual ssize_t ReadCode(target_addr_t address, void* buffer,
|
||||
size_t size) = 0;
|
||||
|
||||
virtual status_t AddSourceCodeInfo(LocatableFile* file,
|
||||
FileSourceCode* sourceCode) = 0;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -11,12 +11,18 @@
|
||||
#include <new>
|
||||
|
||||
#include <AutoDeleter.h>
|
||||
#include <AutoLocker.h>
|
||||
|
||||
#include "Architecture.h"
|
||||
#include "DebuggerTeamDebugInfo.h"
|
||||
#include "DisassembledCode.h"
|
||||
#include "DwarfTeamDebugInfo.h"
|
||||
#include "FileManager.h"
|
||||
#include "FileSourceCode.h"
|
||||
#include "Function.h"
|
||||
#include "ImageDebugInfo.h"
|
||||
#include "LocatableFile.h"
|
||||
#include "SourceFile.h"
|
||||
#include "SpecificImageDebugInfo.h"
|
||||
#include "StringUtils.h"
|
||||
|
||||
@@ -76,13 +82,15 @@ struct TeamDebugInfo::FunctionHashDefinition {
|
||||
struct TeamDebugInfo::SourceFileEntry : public HashTableLink<SourceFileEntry> {
|
||||
SourceFileEntry(LocatableFile* sourceFile)
|
||||
:
|
||||
fSourceFile(sourceFile)
|
||||
fSourceFile(sourceFile),
|
||||
fSourceCode(NULL)
|
||||
{
|
||||
fSourceFile->AcquireReference();
|
||||
}
|
||||
|
||||
~SourceFileEntry()
|
||||
{
|
||||
SetSourceCode(NULL);
|
||||
fSourceFile->ReleaseReference();
|
||||
}
|
||||
|
||||
@@ -96,6 +104,26 @@ struct TeamDebugInfo::SourceFileEntry : public HashTableLink<SourceFileEntry> {
|
||||
return fSourceFile;
|
||||
}
|
||||
|
||||
FileSourceCode* GetSourceCode() const
|
||||
{
|
||||
return fSourceCode;
|
||||
}
|
||||
|
||||
void SetSourceCode(FileSourceCode* sourceCode)
|
||||
{
|
||||
if (sourceCode == fSourceCode)
|
||||
return;
|
||||
|
||||
if (fSourceCode != NULL)
|
||||
fSourceCode->ReleaseReference();
|
||||
|
||||
fSourceCode = sourceCode;
|
||||
|
||||
if (fSourceCode != NULL)
|
||||
fSourceCode->AcquireReference();
|
||||
}
|
||||
|
||||
|
||||
bool IsUnused() const
|
||||
{
|
||||
return fFunctions.IsEmpty();
|
||||
@@ -162,6 +190,7 @@ private:
|
||||
|
||||
private:
|
||||
LocatableFile* fSourceFile;
|
||||
FileSourceCode* fSourceCode;
|
||||
FunctionList fFunctions;
|
||||
};
|
||||
|
||||
@@ -201,6 +230,7 @@ struct TeamDebugInfo::SourceFileHashDefinition {
|
||||
TeamDebugInfo::TeamDebugInfo(DebuggerInterface* debuggerInterface,
|
||||
Architecture* architecture, FileManager* fileManager)
|
||||
:
|
||||
fLock("team debug info"),
|
||||
fDebuggerInterface(debuggerInterface),
|
||||
fArchitecture(architecture),
|
||||
fFileManager(fileManager),
|
||||
@@ -240,12 +270,17 @@ TeamDebugInfo::~TeamDebugInfo()
|
||||
status_t
|
||||
TeamDebugInfo::Init()
|
||||
{
|
||||
// check the lock
|
||||
status_t error = fLock.InitCheck();
|
||||
if (error != B_OK)
|
||||
return error;
|
||||
|
||||
// create function hash table
|
||||
fFunctions = new(std::nothrow) FunctionTable;
|
||||
if (fFunctions == NULL)
|
||||
return B_NO_MEMORY;
|
||||
|
||||
status_t error = fFunctions->Init();
|
||||
error = fFunctions->Init();
|
||||
if (error != B_OK)
|
||||
return error;
|
||||
|
||||
@@ -324,12 +359,103 @@ TeamDebugInfo::LoadImageDebugInfo(const ImageInfo& imageInfo,
|
||||
}
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
status_t
|
||||
TeamDebugInfo::LoadSourceCode(LocatableFile* file, FileSourceCode*& _sourceCode)
|
||||
{
|
||||
AutoLocker<BLocker> locker(fLock);
|
||||
|
||||
// If we don't know the source file, there's nothing we can do.
|
||||
SourceFileEntry* entry = fSourceFiles->Lookup(file);
|
||||
if (entry == NULL)
|
||||
return B_ENTRY_NOT_FOUND;
|
||||
|
||||
// the source might already be loaded
|
||||
FileSourceCode* sourceCode = entry->GetSourceCode();
|
||||
if (sourceCode != NULL) {
|
||||
sourceCode->AcquireReference();
|
||||
_sourceCode = sourceCode;
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
// no source code yet
|
||||
// locker.Unlock();
|
||||
// TODO: It would be nice to unlock here, but we need to iterate through
|
||||
// the images below. We could clone the list, acquire references, and
|
||||
// unlock. Then we have to compare the list with the then current list when
|
||||
// we're done loading.
|
||||
|
||||
// load the source file
|
||||
SourceFile* sourceFile;
|
||||
status_t error = fFileManager->LoadSourceFile(file, sourceFile);
|
||||
if (error != B_OK)
|
||||
return error;
|
||||
|
||||
// create the source code
|
||||
sourceCode = new(std::nothrow) FileSourceCode(file, sourceFile);
|
||||
sourceFile->ReleaseReference();
|
||||
if (sourceCode == NULL)
|
||||
return B_NO_MEMORY;
|
||||
Reference<FileSourceCode> sourceCodeReference(sourceCode, true);
|
||||
|
||||
error = sourceCode->Init();
|
||||
if (error != B_OK)
|
||||
return error;
|
||||
|
||||
// Iterate through all images that know the source file and ask them to add
|
||||
// information.
|
||||
bool anyInfo = false;
|
||||
for (int32 i = 0; ImageDebugInfo* imageDebugInfo = fImages.ItemAt(i); i++)
|
||||
anyInfo |= imageDebugInfo->AddSourceCodeInfo(file, sourceCode) == B_OK;
|
||||
|
||||
if (!anyInfo)
|
||||
return B_ENTRY_NOT_FOUND;
|
||||
|
||||
entry->SetSourceCode(sourceCode);
|
||||
|
||||
_sourceCode = sourceCodeReference.Detach();
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
TeamDebugInfo::DisassembleFunction(FunctionInstance* functionInstance,
|
||||
DisassembledCode*& _sourceCode)
|
||||
{
|
||||
// allocate a buffer for the function code
|
||||
static const target_size_t kMaxBufferSize = 64 * 1024;
|
||||
target_size_t bufferSize = std::min(functionInstance->Size(),
|
||||
kMaxBufferSize);
|
||||
void* buffer = malloc(bufferSize);
|
||||
if (buffer == NULL)
|
||||
return B_NO_MEMORY;
|
||||
MemoryDeleter bufferDeleter(buffer);
|
||||
|
||||
// read the function code
|
||||
FunctionDebugInfo* functionDebugInfo
|
||||
= functionInstance->GetFunctionDebugInfo();
|
||||
ssize_t bytesRead = functionDebugInfo->GetSpecificImageDebugInfo()
|
||||
->ReadCode(functionInstance->Address(), buffer, bufferSize);
|
||||
if (bytesRead < 0)
|
||||
return bytesRead;
|
||||
|
||||
return fArchitecture->DisassembleCode(functionDebugInfo, buffer, bytesRead,
|
||||
_sourceCode);
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
TeamDebugInfo::AddImageDebugInfo(ImageDebugInfo* imageDebugInfo)
|
||||
{
|
||||
printf("TeamDebugInfo::AddImageDebugInfo(%p)\n", imageDebugInfo);
|
||||
AutoLocker<BLocker> locker(fLock);
|
||||
// We have both locks now, so that for read-only access either lock
|
||||
// suffices.
|
||||
|
||||
if (!fImages.AddItem(imageDebugInfo))
|
||||
return B_NO_MEMORY;
|
||||
|
||||
// Match all of the image debug info's functions instances with functions.
|
||||
BObjectList<SourceFileEntry> sourceFileEntries;
|
||||
for (int32 i = 0;
|
||||
FunctionInstance* instance = imageDebugInfo->FunctionAt(i); i++) {
|
||||
// lookup the function or create it, if it doesn't exist yet
|
||||
@@ -339,6 +465,15 @@ printf("TeamDebugInfo::AddImageDebugInfo(%p)\n", imageDebugInfo);
|
||||
printf(" adding instance %p to existing function %p\n", instance, function);
|
||||
function->AddInstance(instance);
|
||||
instance->SetFunction(function);
|
||||
|
||||
// The new image debug info might have additional information about
|
||||
// the source file of the function, so remember the source file
|
||||
// entry.
|
||||
if (LocatableFile* sourceFile = function->SourceFile()) {
|
||||
SourceFileEntry* entry = fSourceFiles->Lookup(sourceFile);
|
||||
if (entry != NULL && entry->GetSourceCode() != NULL)
|
||||
sourceFileEntries.AddItem(entry);
|
||||
}
|
||||
} else {
|
||||
function = new(std::nothrow) Function;
|
||||
if (function == NULL) {
|
||||
@@ -361,6 +496,19 @@ printf(" adding instance %p to new function %p\n", instance, function);
|
||||
}
|
||||
}
|
||||
|
||||
// update the source files the image debug info knows about
|
||||
for (int32 i = 0; SourceFileEntry* entry = sourceFileEntries.ItemAt(i);
|
||||
i++) {
|
||||
FileSourceCode* sourceCode = entry->GetSourceCode();
|
||||
sourceCode->Lock();
|
||||
if (imageDebugInfo->AddSourceCodeInfo(entry->SourceFile(),
|
||||
sourceCode) == B_OK) {
|
||||
// TODO: Notify interesting parties! Iterate through all functions
|
||||
// for this source file?
|
||||
}
|
||||
sourceCode->Unlock();
|
||||
}
|
||||
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
@@ -368,6 +516,10 @@ printf(" adding instance %p to new function %p\n", instance, function);
|
||||
void
|
||||
TeamDebugInfo::RemoveImageDebugInfo(ImageDebugInfo* imageDebugInfo)
|
||||
{
|
||||
AutoLocker<BLocker> locker(fLock);
|
||||
// We have both locks now, so that for read-only access either lock
|
||||
// suffices.
|
||||
|
||||
// Remove the functions from all of the image debug info's functions
|
||||
// instances.
|
||||
for (int32 i = 0;
|
||||
@@ -390,6 +542,8 @@ TeamDebugInfo::RemoveImageDebugInfo(ImageDebugInfo* imageDebugInfo)
|
||||
// reference to the function.
|
||||
}
|
||||
}
|
||||
|
||||
fImages.RemoveItem(imageDebugInfo);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
#define TEAM_DEBUG_INFO_H
|
||||
|
||||
|
||||
#include <Locker.h>
|
||||
|
||||
#include <ObjectList.h>
|
||||
#include <Referenceable.h>
|
||||
#include <util/OpenHashTable.h>
|
||||
@@ -15,12 +17,15 @@
|
||||
|
||||
class Architecture;
|
||||
class DebuggerInterface;
|
||||
class DisassembledCode;
|
||||
class FileManager;
|
||||
class FileSourceCode;
|
||||
class Function;
|
||||
class FunctionInstance;
|
||||
class ImageDebugInfo;
|
||||
class ImageInfo;
|
||||
class LocatableFile;
|
||||
class SourceCode;
|
||||
class SourceLocation;
|
||||
class SpecificTeamDebugInfo;
|
||||
|
||||
@@ -39,6 +44,15 @@ public:
|
||||
LocatableFile* imageFile,
|
||||
ImageDebugInfo*& _imageDebugInfo);
|
||||
|
||||
status_t LoadSourceCode(LocatableFile* file,
|
||||
FileSourceCode*& _sourceCode);
|
||||
// returns reference
|
||||
status_t DisassembleFunction(
|
||||
FunctionInstance* functionInstance,
|
||||
DisassembledCode*& _sourceCode);
|
||||
// returns reference
|
||||
|
||||
|
||||
// team is locked
|
||||
status_t AddImageDebugInfo(
|
||||
ImageDebugInfo* imageDebugInfo);
|
||||
@@ -54,6 +68,7 @@ private:
|
||||
struct SourceFileHashDefinition;
|
||||
|
||||
typedef BObjectList<SpecificTeamDebugInfo> SpecificInfoList;
|
||||
typedef BObjectList<ImageDebugInfo> ImageList;
|
||||
typedef OpenHashTable<FunctionHashDefinition> FunctionTable;
|
||||
typedef OpenHashTable<SourceFileHashDefinition> SourceFileTable;
|
||||
|
||||
@@ -62,10 +77,12 @@ private:
|
||||
void _RemoveFunction(Function* function);
|
||||
|
||||
private:
|
||||
BLocker fLock;
|
||||
DebuggerInterface* fDebuggerInterface;
|
||||
Architecture* fArchitecture;
|
||||
FileManager* fFileManager;
|
||||
SpecificInfoList fSpecificInfos;
|
||||
ImageList fImages;
|
||||
FunctionTable* fFunctions;
|
||||
SourceFileTable* fSourceFiles;
|
||||
};
|
||||
|
||||
@@ -20,8 +20,9 @@
|
||||
#include <ObjectList.h>
|
||||
|
||||
#include "Breakpoint.h"
|
||||
#include "DisassembledCode.h"
|
||||
#include "Function.h"
|
||||
#include "SourceCode.h"
|
||||
#include "FileSourceCode.h"
|
||||
#include "StackTrace.h"
|
||||
#include "Statement.h"
|
||||
#include "TeamDebugModel.h"
|
||||
@@ -503,6 +504,8 @@ SourceView::MarkerView::Draw(BRect updateRect)
|
||||
|
||||
float width = Bounds().Width();
|
||||
|
||||
AutoLocker<SourceCode> sourceLocker(fSourceCode);
|
||||
|
||||
int32 markerIndex = 0;
|
||||
for (int32 line = minLine; line <= maxLine; line++) {
|
||||
bool drawBreakpointOptionMarker = true;
|
||||
@@ -518,7 +521,6 @@ SourceView::MarkerView::Draw(BRect updateRect)
|
||||
if (!drawBreakpointOptionMarker)
|
||||
continue;
|
||||
|
||||
|
||||
SourceLocation statementStart, statementEnd;
|
||||
if (!fSourceCode->GetStatementLocationRange(SourceLocation(line),
|
||||
statementStart, statementEnd)
|
||||
@@ -600,6 +602,8 @@ SourceView::MarkerView::_UpdateIPMarkers()
|
||||
fIPMarkers.MakeEmpty();
|
||||
|
||||
if (fSourceCode != NULL && fStackTrace != NULL) {
|
||||
LocatableFile* sourceFile = fSourceCode->GetSourceFile();
|
||||
|
||||
AutoLocker<TeamDebugModel> locker(fDebugModel);
|
||||
|
||||
for (int32 i = 0; StackFrame* frame = fStackTrace->FrameAt(i);
|
||||
@@ -614,7 +618,9 @@ SourceView::MarkerView::_UpdateIPMarkers()
|
||||
Reference<Statement> statementReference(statement, true);
|
||||
|
||||
uint32 line = statement->StartSourceLocation().Line();
|
||||
if (functionInstance->GetFunction()->GetSourceCode() != fSourceCode
|
||||
if ((functionInstance->GetSourceCode() != fSourceCode
|
||||
&& functionInstance->GetFunction()->SourceFile()
|
||||
!= sourceFile)
|
||||
|| line < 0 || line >= (uint32)LineCount()) {
|
||||
continue;
|
||||
}
|
||||
@@ -649,6 +655,8 @@ SourceView::MarkerView::_UpdateBreakpointMarkers()
|
||||
fBreakpointMarkers.MakeEmpty();
|
||||
|
||||
if (fSourceCode != NULL) {
|
||||
LocatableFile* sourceFile = fSourceCode->GetSourceFile();
|
||||
|
||||
AutoLocker<TeamDebugModel> locker(fDebugModel);
|
||||
|
||||
// get the breakpoints in our source code range
|
||||
@@ -669,7 +677,9 @@ SourceView::MarkerView::_UpdateBreakpointMarkers()
|
||||
Reference<Statement> statementReference(statement, true);
|
||||
|
||||
uint32 line = statement->StartSourceLocation().Line();
|
||||
if (functionInstance->GetFunction()->GetSourceCode() != fSourceCode
|
||||
if ((functionInstance->GetSourceCode() != fSourceCode
|
||||
&& functionInstance->GetFunction()->SourceFile()
|
||||
!= sourceFile)
|
||||
|| line < 0 || line >= (uint32)LineCount()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -18,11 +18,12 @@
|
||||
#include <AutoLocker.h>
|
||||
|
||||
#include "CpuState.h"
|
||||
#include "DisassembledCode.h"
|
||||
#include "FileSourceCode.h"
|
||||
#include "Image.h"
|
||||
#include "ImageDebugInfo.h"
|
||||
#include "MessageCodes.h"
|
||||
#include "RegisterView.h"
|
||||
#include "SourceCode.h"
|
||||
#include "StackTrace.h"
|
||||
#include "StackTraceView.h"
|
||||
|
||||
@@ -505,6 +506,8 @@ TeamWindow::_SetActiveFunction(FunctionInstance* functionInstance)
|
||||
|
||||
Function* function = fActiveFunction->GetFunction();
|
||||
sourceCode = function->GetSourceCode();
|
||||
if (sourceCode == NULL)
|
||||
sourceCode = fActiveFunction->GetSourceCode();
|
||||
sourceCodeReference.SetTo(sourceCode);
|
||||
|
||||
// If the source code is not loaded yet, request it.
|
||||
@@ -703,6 +706,8 @@ TeamWindow::_HandleSourceCodeChanged()
|
||||
AutoLocker<TeamDebugModel> locker(fDebugModel);
|
||||
|
||||
SourceCode* sourceCode = fActiveFunction->GetFunction()->GetSourceCode();
|
||||
if (sourceCode == NULL)
|
||||
sourceCode = fActiveFunction->GetSourceCode();
|
||||
Reference<SourceCode> sourceCodeReference(sourceCode);
|
||||
|
||||
locker.Unlock();
|
||||
|
||||
@@ -36,6 +36,8 @@ public:
|
||||
{ return fUserBreakpoints.Head(); }
|
||||
UserBreakpointInstance* LastUserBreakpoint() const
|
||||
{ return fUserBreakpoints.Tail(); }
|
||||
const UserBreakpointInstanceList& UserBreakpoints() const
|
||||
{ return fUserBreakpoints; }
|
||||
|
||||
void AddUserBreakpoint(
|
||||
UserBreakpointInstance* instance);
|
||||
|
||||
@@ -43,6 +43,20 @@ DisassembledCode::~DisassembledCode()
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
DisassembledCode::Lock()
|
||||
{
|
||||
// We're immutable, so no locking required.
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
DisassembledCode::Unlock()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
int32
|
||||
DisassembledCode::CountLines() const
|
||||
{
|
||||
@@ -80,17 +94,32 @@ DisassembledCode::GetSourceFile() const
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
DisassembledCode::GetStatementAtLocation(const SourceLocation& location,
|
||||
Statement*& _statement)
|
||||
Statement*
|
||||
DisassembledCode::StatementAtLocation(const SourceLocation& location) const
|
||||
{
|
||||
Line* line = fLines.ItemAt(location.Line());
|
||||
if (line == NULL || line->statement == NULL)
|
||||
return B_ENTRY_NOT_FOUND;
|
||||
return line != NULL ? line->statement : NULL;
|
||||
}
|
||||
|
||||
_statement = line->statement;
|
||||
_statement->AcquireReference();
|
||||
return B_OK;
|
||||
|
||||
Statement*
|
||||
DisassembledCode::StatementAtAddress(target_addr_t address) const
|
||||
{
|
||||
return fStatements.BinarySearchByKey(address, &_CompareAddressStatement);
|
||||
}
|
||||
|
||||
|
||||
TargetAddressRange
|
||||
DisassembledCode::StatementAddressRange() const
|
||||
{
|
||||
if (fStatements.IsEmpty())
|
||||
return TargetAddressRange();
|
||||
|
||||
ContiguousStatement* first = fStatements.ItemAt(0);
|
||||
ContiguousStatement* last
|
||||
= fStatements.ItemAt(fStatements.CountItems() - 1);
|
||||
return TargetAddressRange(first->AddressRange().Start(),
|
||||
last->AddressRange().End());
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -20,6 +20,9 @@ public:
|
||||
DisassembledCode();
|
||||
~DisassembledCode();
|
||||
|
||||
virtual bool Lock();
|
||||
virtual void Unlock();
|
||||
|
||||
virtual int32 CountLines() const;
|
||||
virtual const char* LineAt(int32 index) const;
|
||||
|
||||
@@ -30,9 +33,10 @@ public:
|
||||
|
||||
virtual LocatableFile* GetSourceFile() const;
|
||||
|
||||
virtual status_t GetStatementAtLocation(
|
||||
const SourceLocation& location,
|
||||
Statement*& _statement);
|
||||
Statement* StatementAtAddress(target_addr_t address) const;
|
||||
Statement* StatementAtLocation(
|
||||
const SourceLocation& location) const;
|
||||
TargetAddressRange StatementAddressRange() const;
|
||||
|
||||
public:
|
||||
bool AddCommentLine(const BString& line);
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
|
||||
FileSourceCode::FileSourceCode(LocatableFile* file, SourceFile* sourceFile)
|
||||
:
|
||||
fLock("source code"),
|
||||
fFile(file),
|
||||
fSourceFile(sourceFile)
|
||||
{
|
||||
@@ -33,7 +34,7 @@ FileSourceCode::~FileSourceCode()
|
||||
status_t
|
||||
FileSourceCode::Init()
|
||||
{
|
||||
return B_OK;
|
||||
return fLock.InitCheck();
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +51,20 @@ FileSourceCode::AddSourceLocation(const SourceLocation& location)
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
FileSourceCode::Lock()
|
||||
{
|
||||
return fLock.Lock();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
FileSourceCode::Unlock()
|
||||
{
|
||||
fLock.Unlock();
|
||||
}
|
||||
|
||||
|
||||
int32
|
||||
FileSourceCode::CountLines() const
|
||||
{
|
||||
@@ -95,14 +110,6 @@ FileSourceCode::GetSourceFile() const
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
FileSourceCode::GetStatementAtLocation(const SourceLocation& location,
|
||||
Statement*& _statement)
|
||||
{
|
||||
return B_UNSUPPORTED;
|
||||
}
|
||||
|
||||
|
||||
int32
|
||||
FileSourceCode::_FindSourceLocationIndex(const SourceLocation& location,
|
||||
bool& _foundMatch) const
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
#define FILE_SOURCE_CODE_H
|
||||
|
||||
|
||||
#include <Locker.h>
|
||||
|
||||
|
||||
#include "Array.h"
|
||||
#include "SourceCode.h"
|
||||
|
||||
@@ -24,6 +27,9 @@ public:
|
||||
status_t AddSourceLocation(
|
||||
const SourceLocation& location);
|
||||
|
||||
virtual bool Lock();
|
||||
virtual void Unlock();
|
||||
|
||||
virtual int32 CountLines() const;
|
||||
virtual const char* LineAt(int32 index) const;
|
||||
|
||||
@@ -34,16 +40,13 @@ public:
|
||||
|
||||
virtual LocatableFile* GetSourceFile() const;
|
||||
|
||||
virtual status_t GetStatementAtLocation(
|
||||
const SourceLocation& location,
|
||||
Statement*& _statement);
|
||||
|
||||
private:
|
||||
int32 _FindSourceLocationIndex(
|
||||
const SourceLocation& location,
|
||||
bool& _foundMatch) const;
|
||||
|
||||
private:
|
||||
BLocker fLock;
|
||||
LocatableFile* fFile;
|
||||
SourceFile* fSourceFile;
|
||||
Array<SourceLocation> fSourceLocations;
|
||||
|
||||
@@ -20,6 +20,11 @@ class SourceCode : public Referenceable {
|
||||
public:
|
||||
virtual ~SourceCode();
|
||||
|
||||
// Locking needed for GetStatementLocationRange(), since that might use
|
||||
// mutable data.
|
||||
virtual bool Lock() = 0;
|
||||
virtual void Unlock() = 0;
|
||||
|
||||
virtual int32 CountLines() const = 0;
|
||||
virtual const char* LineAt(int32 index) const = 0;
|
||||
|
||||
@@ -29,13 +34,6 @@ public:
|
||||
SourceLocation& _end) const = 0;
|
||||
|
||||
virtual LocatableFile* GetSourceFile() const = 0;
|
||||
|
||||
virtual status_t GetStatementAtLocation(
|
||||
const SourceLocation& location,
|
||||
Statement*& _statement) = 0;
|
||||
// returns a reference,
|
||||
// may return B_UNSUPPORTED, when
|
||||
// SourceFile() returns non-NULL
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -11,10 +11,12 @@
|
||||
|
||||
#include <AutoLocker.h>
|
||||
|
||||
#include "DisassembledCode.h"
|
||||
#include "Function.h"
|
||||
#include "ImageDebugInfo.h"
|
||||
#include "SourceCode.h"
|
||||
#include "SpecificImageDebugInfo.h"
|
||||
#include "Statement.h"
|
||||
#include "TeamDebugInfo.h"
|
||||
|
||||
|
||||
@@ -236,12 +238,23 @@ printf(" -> no function instance\n");
|
||||
return B_ENTRY_NOT_FOUND;
|
||||
}
|
||||
|
||||
// If the function instance has disassembled code attached, we can get the
|
||||
// statement directly.
|
||||
if (DisassembledCode* code = functionInstance->GetSourceCode()) {
|
||||
Statement* statement = code->StatementAtAddress(address);
|
||||
if (statement != NULL) {
|
||||
statement->AcquireReference();
|
||||
_statement = statement;
|
||||
_function = functionInstance;
|
||||
return B_OK;
|
||||
}
|
||||
}
|
||||
|
||||
// get the statement from the image debug info
|
||||
FunctionDebugInfo* functionDebugInfo
|
||||
= functionInstance->GetFunctionDebugInfo();
|
||||
status_t error = functionDebugInfo->GetSpecificImageDebugInfo()
|
||||
->GetStatement(functionDebugInfo, address, _statement);
|
||||
// TODO: Provide the corresponding SourceCode, if available!
|
||||
if (error != B_OK)
|
||||
{
|
||||
printf(" -> no statement from the specific image debug info\n");
|
||||
@@ -259,9 +272,15 @@ Team::GetStatementAtSourceLocation(SourceCode* sourceCode,
|
||||
{
|
||||
printf("Team::GetStatementAtSourceLocation(%p, (%ld, %ld))\n", sourceCode, location.Line(), location.Column());
|
||||
// If we're lucky the source code can provide us with a statement.
|
||||
status_t error = sourceCode->GetStatementAtLocation(location, _statement);
|
||||
if (error == B_OK)
|
||||
return error;
|
||||
if (DisassembledCode* code = dynamic_cast<DisassembledCode*>(sourceCode)) {
|
||||
Statement* statement = code->StatementAtLocation(location);
|
||||
if (statement == NULL)
|
||||
return B_ENTRY_NOT_FOUND;
|
||||
|
||||
statement->AcquireReference();
|
||||
_statement = statement;
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
// Go the long and stony way over the source file and the team debug info.
|
||||
// get the source file for the source code
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
#include <AutoLocker.h>
|
||||
|
||||
#include "Breakpoint.h"
|
||||
#include "DisassembledCode.h"
|
||||
#include "FileSourceCode.h"
|
||||
#include "Function.h"
|
||||
#include "UserBreakpoint.h"
|
||||
|
||||
@@ -109,24 +111,42 @@ TeamDebugModel::BreakpointAtAddress(target_addr_t address) const
|
||||
}
|
||||
|
||||
|
||||
//void
|
||||
//TeamDebugModel::GetBreakpointsInAddressRange(TargetAddressRange range,
|
||||
// BObjectList<Breakpoint>& breakpoints) const
|
||||
//{
|
||||
// int32 index = fBreakpoints.FindBinaryInsertionIndex(
|
||||
// BreakpointByAddressPredicate(range.Start()));
|
||||
// for (; Breakpoint* breakpoint = fBreakpoints.ItemAt(index); index++) {
|
||||
// if (breakpoint->Address() > range.End())
|
||||
// break;
|
||||
// breakpoints.AddItem(breakpoint);
|
||||
// }
|
||||
//}
|
||||
void
|
||||
TeamDebugModel::GetBreakpointsInAddressRange(TargetAddressRange range,
|
||||
BObjectList<UserBreakpoint>& breakpoints) const
|
||||
{
|
||||
int32 index = fBreakpoints.FindBinaryInsertionIndex(
|
||||
BreakpointByAddressPredicate(range.Start()));
|
||||
for (; Breakpoint* breakpoint = fBreakpoints.ItemAt(index); index++) {
|
||||
if (breakpoint->Address() > range.End())
|
||||
break;
|
||||
|
||||
for (UserBreakpointInstanceList::ConstIterator it
|
||||
= breakpoint->UserBreakpoints().GetIterator();
|
||||
UserBreakpointInstance* instance = it.Next();) {
|
||||
breakpoints.AddItem(instance->GetUserBreakpoint());
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Avoid duplicates!
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
TeamDebugModel::GetBreakpointsForSourceCode(SourceCode* sourceCode,
|
||||
BObjectList<UserBreakpoint>& breakpoints) const
|
||||
{
|
||||
if (DisassembledCode* disassembledCode
|
||||
= dynamic_cast<DisassembledCode*>(sourceCode)) {
|
||||
GetBreakpointsInAddressRange(disassembledCode->StatementAddressRange(),
|
||||
breakpoints);
|
||||
return;
|
||||
}
|
||||
|
||||
LocatableFile* sourceFile = sourceCode->GetSourceFile();
|
||||
if (sourceFile == NULL)
|
||||
return;
|
||||
|
||||
// TODO: This can probably be optimized. Maybe by registering the user
|
||||
// breakpoints with the team debug model and sorting them by source code.
|
||||
for (int32 i = 0; Breakpoint* breakpoint = fBreakpoints.ItemAt(i); i++) {
|
||||
@@ -137,7 +157,7 @@ TeamDebugModel::GetBreakpointsForSourceCode(SourceCode* sourceCode,
|
||||
|
||||
UserBreakpoint* userBreakpoint
|
||||
= userBreakpointInstance->GetUserBreakpoint();
|
||||
if (userBreakpoint->GetFunction()->GetSourceCode() == sourceCode)
|
||||
if (userBreakpoint->GetFunction()->SourceFile() == sourceFile)
|
||||
breakpoints.AddItem(userBreakpoint);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,9 +58,10 @@ public:
|
||||
Breakpoint* BreakpointAt(int32 index) const;
|
||||
Breakpoint* BreakpointAtAddress(
|
||||
target_addr_t address) const;
|
||||
// void GetBreakpointsInAddressRange(
|
||||
// TargetAddressRange range,
|
||||
// BObjectList<Breakpoint>& breakpoints) const;
|
||||
void GetBreakpointsInAddressRange(
|
||||
TargetAddressRange range,
|
||||
BObjectList<UserBreakpoint>& breakpoints)
|
||||
const;
|
||||
void GetBreakpointsForSourceCode(
|
||||
SourceCode* sourceCode,
|
||||
BObjectList<UserBreakpoint>& breakpoints)
|
||||
|
||||
Reference in New Issue
Block a user