/* * Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de. * Copyright 2012-2016, Rene Gollent, rene@gollent.com. * Distributed under the terms of the MIT License. */ #include "TeamDebugInfo.h" #include #include #include #include #include "Architecture.h" #include "DebuggerInterface.h" #include "DebuggerTeamDebugInfo.h" #include "DisassembledCode.h" #include "DwarfTeamDebugInfo.h" #include "FileManager.h" #include "FileSourceCode.h" #include "Function.h" #include "FunctionID.h" #include "ImageDebugInfo.h" #include "ImageDebugInfoLoadingState.h" #include "LocatableFile.h" #include "SourceFile.h" #include "SourceLanguage.h" #include "SpecificImageDebugInfo.h" #include "StringUtils.h" #include "Type.h" #include "TypeLookupConstraints.h" // #pragma mark - FunctionHashDefinition struct TeamDebugInfo::FunctionHashDefinition { typedef const FunctionInstance* KeyType; typedef Function ValueType; size_t HashKey(const FunctionInstance* key) const { // Instances without source file only equal themselves. if (key->SourceFile() == NULL) return (uint32)(addr_t)key; uint32 hash = StringUtils::HashValue(key->Name()); hash = hash * 17 + (uint32)(addr_t)key->SourceFile(); SourceLocation location = key->GetSourceLocation(); hash = hash * 17 + location.Line(); hash = hash * 17 + location.Column(); return hash; } size_t Hash(const Function* value) const { return HashKey(value->FirstInstance()); } bool Compare(const FunctionInstance* key, const Function* value) const { // source file must be the same if (key->SourceFile() != value->SourceFile()) return false; // Instances without source file only equal themselves. if (key->SourceFile() == NULL) return key == value->FirstInstance(); // Source location and function name must also match. return key->GetSourceLocation() == value->GetSourceLocation() && key->Name() == value->Name(); } Function*& GetLink(Function* value) const { return value->fNext; } }; // #pragma mark - SourceFileEntry struct TeamDebugInfo::SourceFileEntry { SourceFileEntry(LocatableFile* sourceFile) : fSourceFile(sourceFile), fSourceCode(NULL) { fSourceFile->AcquireReference(); } ~SourceFileEntry() { SetSourceCode(NULL); fSourceFile->ReleaseReference(); } status_t Init() { return B_OK; } LocatableFile* SourceFile() const { 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(); } status_t AddFunction(Function* function) { if (!fFunctions.BinaryInsert(function, &_CompareFunctions)) return B_NO_MEMORY; return B_OK; } void RemoveFunction(Function* function) { int32 index = fFunctions.BinarySearchIndex(*function, &_CompareFunctions); if (index >= 0) fFunctions.RemoveItemAt(index); } Function* FunctionAtLocation(const SourceLocation& location) const { int32 index = fFunctions.BinarySearchIndexByKey(location, &_CompareLocationFunction); if (index >= 0) return fFunctions.ItemAt(index); // No exact match, so we return the previous function which might still // contain the location. index = -index - 1; if (index == 0) return NULL; return fFunctions.ItemAt(index - 1); } Function* FunctionAt(int32 index) const { return fFunctions.ItemAt(index); } Function* FunctionByName(const BString& name) const { // TODO: That's not exactly optimal. for (int32 i = 0; Function* function = fFunctions.ItemAt(i); i++) { if (name == function->Name()) return function; } return NULL; } private: typedef BObjectList FunctionList; private: static int _CompareFunctions(const Function* a, const Function* b) { SourceLocation locationA = a->GetSourceLocation(); SourceLocation locationB = b->GetSourceLocation(); if (locationA < locationB) return -1; if (locationA != locationB ) return 1; // if the locations match we still need to compare by name to be // certain, since differently typed instantiations of template // functions will have the same source file and location return a->Name().Compare(b->Name()); } static int _CompareLocationFunction(const SourceLocation* location, const Function* function) { SourceLocation functionLocation = function->GetSourceLocation(); if (*location < functionLocation) return -1; return *location == functionLocation ? 0 : 1; } private: LocatableFile* fSourceFile; FileSourceCode* fSourceCode; FunctionList fFunctions; public: SourceFileEntry* fNext; }; // #pragma mark - SourceFileHashDefinition struct TeamDebugInfo::SourceFileHashDefinition { typedef const LocatableFile* KeyType; typedef SourceFileEntry ValueType; size_t HashKey(const LocatableFile* key) const { return (size_t)(addr_t)key; } size_t Hash(const SourceFileEntry* value) const { return HashKey(value->SourceFile()); } bool Compare(const LocatableFile* key, const SourceFileEntry* value) const { return key == value->SourceFile(); } SourceFileEntry*& GetLink(SourceFileEntry* value) const { return value->fNext; } }; // #pragma mark - TeamDebugInfo TeamDebugInfo::TeamDebugInfo(DebuggerInterface* debuggerInterface, Architecture* architecture, FileManager* fileManager) : fLock("team debug info"), fDebuggerInterface(debuggerInterface), fArchitecture(architecture), fFileManager(fileManager), fSpecificInfos(10, true), fFunctions(NULL), fSourceFiles(NULL), fTypeCache(NULL), fMainFunction(NULL) { fDebuggerInterface->AcquireReference(); } TeamDebugInfo::~TeamDebugInfo() { if (fTypeCache != NULL) fTypeCache->ReleaseReference(); if (fSourceFiles != NULL) { SourceFileEntry* entry = fSourceFiles->Clear(true); while (entry != NULL) { SourceFileEntry* next = entry->fNext; delete entry; entry = next; } delete fSourceFiles; } if (fFunctions != NULL) { Function* function = fFunctions->Clear(true); while (function != NULL) { Function* next = function->fNext; function->ReleaseReference(); function = next; } delete fFunctions; } fDebuggerInterface->ReleaseReference(); } 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; error = fFunctions->Init(); if (error != B_OK) return error; // create source file hash table fSourceFiles = new(std::nothrow) SourceFileTable; if (fSourceFiles == NULL) return B_NO_MEMORY; error = fSourceFiles->Init(); if (error != B_OK) return error; // create a type cache fTypeCache = new(std::nothrow) GlobalTypeCache; if (fTypeCache == NULL) return B_NO_MEMORY; error = fTypeCache->Init(); if (error != B_OK) return error; // Create specific infos for all types of debug info we support, in // descending order of expressiveness. // DWARF DwarfTeamDebugInfo* dwarfInfo = new(std::nothrow) DwarfTeamDebugInfo( fArchitecture, fDebuggerInterface, fFileManager, this, this, fTypeCache); if (dwarfInfo == NULL || !fSpecificInfos.AddItem(dwarfInfo)) { delete dwarfInfo; return B_NO_MEMORY; } error = dwarfInfo->Init(); if (error != B_OK) return error; // debugger based info DebuggerTeamDebugInfo* debuggerInfo = new(std::nothrow) DebuggerTeamDebugInfo(fDebuggerInterface, fArchitecture); if (debuggerInfo == NULL || !fSpecificInfos.AddItem(debuggerInfo)) { delete debuggerInfo; return B_NO_MEMORY; } error = debuggerInfo->Init(); if (error != B_OK) return error; return B_OK; } status_t TeamDebugInfo::LookupTypeByName(const BString& name, const TypeLookupConstraints& constraints, Type*& _type) { return GetType(fTypeCache, name, constraints, _type); } bool TeamDebugInfo::TypeExistsByName(const BString& name, const TypeLookupConstraints& constraints) { return HasType(fTypeCache, name, constraints); } status_t TeamDebugInfo::GetType(GlobalTypeCache* cache, const BString& name, const TypeLookupConstraints& constraints, Type*& _type) { // maybe the type is already cached AutoLocker cacheLocker(cache); Type* type = cache->GetType(name, constraints); if (type != NULL) { type->AcquireReference(); _type = type; return B_OK; } cacheLocker.Unlock(); // Clone the image list and get references to the images, so we can iterate // through them without locking. AutoLocker locker(fLock); ImageList images; for (int32 i = 0; ImageDebugInfo* imageDebugInfo = fImages.ItemAt(i); i++) { if (images.AddItem(imageDebugInfo)) imageDebugInfo->AcquireReference(); } locker.Unlock(); // get the type status_t error = B_ENTRY_NOT_FOUND; for (int32 i = 0; ImageDebugInfo* imageDebugInfo = images.ItemAt(i); i++) { error = imageDebugInfo->GetType(cache, name, constraints, type); if (error == B_OK) { _type = type; break; } } // release the references for (int32 i = 0; ImageDebugInfo* imageDebugInfo = images.ItemAt(i); i++) imageDebugInfo->ReleaseReference(); return error; } bool TeamDebugInfo::HasType(GlobalTypeCache* cache, const BString& name, const TypeLookupConstraints& constraints) { // maybe the type is already cached AutoLocker cacheLocker(cache); Type* type = cache->GetType(name, constraints); if (type != NULL) return true; cacheLocker.Unlock(); // Clone the image list and get references to the images, so we can iterate // through them without locking. AutoLocker locker(fLock); ImageList images; for (int32 i = 0; ImageDebugInfo* imageDebugInfo = fImages.ItemAt(i); i++) { if (images.AddItem(imageDebugInfo)) imageDebugInfo->AcquireReference(); } locker.Unlock(); bool found = false; for (int32 i = 0; ImageDebugInfo* imageDebugInfo = images.ItemAt(i); i++) { if (imageDebugInfo->HasType(name, constraints)) { found = true; break; } } // release the references for (int32 i = 0; ImageDebugInfo* imageDebugInfo = images.ItemAt(i); i++) imageDebugInfo->ReleaseReference(); return found; } status_t TeamDebugInfo::GetActiveSourceCode(FunctionDebugInfo* info, SourceCode*& _code) { AutoLocker locker(fLock); LocatableFile* file = info->SourceFile(); if (file != NULL) { Function* function = FunctionAtSourceLocation(file, info->SourceStartLocation()); if (function != NULL) { function_source_state state = function->SourceCodeState(); if (function->SourceCodeState() == FUNCTION_SOURCE_LOADED) { _code = function->GetSourceCode(); _code->AcquireReference(); return B_OK; } else if (state == FUNCTION_SOURCE_NOT_LOADED) { // if the function's source state is not loaded, check // if we already know the file anyways. Currently, when // a source code job runs, it does so on behalf of a specific // function, and consequently only sets the loaded source code // on that particular function at that point in time, rather // than all others sharing that same file. Consequently, // set it lazily here. SourceFileEntry* entry = fSourceFiles->Lookup(file); if (entry != NULL) { FileSourceCode* sourceCode = entry->GetSourceCode(); if (sourceCode != NULL) { function->SetSourceCode(sourceCode, FUNCTION_SOURCE_LOADED); _code = sourceCode; _code->AcquireReference(); return B_OK; } } } } } for (int32 i = 0; i < fImages.CountItems(); i++) { ImageDebugInfo* imageInfo = fImages.ItemAt(i); FunctionInstance* instance = imageInfo->FunctionAtAddress( info->Address()); if (instance != NULL && instance->SourceCodeState() == FUNCTION_SOURCE_LOADED) { _code = instance->GetSourceCode(); _code->AcquireReference(); return B_OK; } } return B_ENTRY_NOT_FOUND; } status_t TeamDebugInfo::LoadImageDebugInfo(const ImageInfo& imageInfo, LocatableFile* imageFile, ImageDebugInfoLoadingState& _state, ImageDebugInfo*& _imageDebugInfo) { ImageDebugInfo* imageDebugInfo = new(std::nothrow) ImageDebugInfo( imageInfo); if (imageDebugInfo == NULL) return B_NO_MEMORY; BReference imageDebugInfoReference(imageDebugInfo, true); for (int32 i = 0; SpecificTeamDebugInfo* specificTeamInfo = fSpecificInfos.ItemAt(i); i++) { SpecificImageDebugInfo* specificImageInfo; status_t error = specificTeamInfo->CreateImageDebugInfo(imageInfo, imageFile, _state, specificImageInfo); if (error == B_OK) { if (!imageDebugInfo->AddSpecificInfo(specificImageInfo)) { delete specificImageInfo; return B_NO_MEMORY; } } else if (_state.UserInputRequired()) { _state.SetSpecificInfoIndex(i); return error; } else if (error == B_NO_MEMORY) return error; // fail only when out of memory _state.ClearSpecificDebugInfoLoadingState(); // if we made it this far, then we're done with current specific // info, and its corresponding state object, if any, is no longer // needed } status_t error = imageDebugInfo->FinishInit(fDebuggerInterface); if (error != B_OK) return error; if (fMainFunction == NULL) { FunctionInstance* instance = imageDebugInfo->MainFunction(); if (instance != NULL) fMainFunction = instance; } _imageDebugInfo = imageDebugInfoReference.Detach(); return B_OK; } status_t TeamDebugInfo::LoadSourceCode(LocatableFile* file, FileSourceCode*& _sourceCode) { AutoLocker 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; } // get the source language from some function's image debug info Function* function = entry->FunctionAt(0); if (function == NULL) return B_ENTRY_NOT_FOUND; FunctionDebugInfo* functionDebugInfo = function->FirstInstance()->GetFunctionDebugInfo(); SourceLanguage* language; status_t error = functionDebugInfo->GetSpecificImageDebugInfo() ->GetSourceLanguage(functionDebugInfo, language); if (error != B_OK) return error; BReference languageReference(language, true); // 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; error = fFileManager->LoadSourceFile(file, sourceFile); if (error != B_OK) return error; // create the source code sourceCode = new(std::nothrow) FileSourceCode(file, sourceFile, language); sourceFile->ReleaseReference(); if (sourceCode == NULL) return B_NO_MEMORY; BReference 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; } void TeamDebugInfo::ClearSourceCode(LocatableFile* sourceFile) { AutoLocker locker(fLock); SourceFileEntry* entry = fSourceFiles->Lookup(sourceFile); if (entry != NULL) entry->SetSourceCode(NULL); } 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) { AutoLocker 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 sourceFileEntries; for (int32 i = 0; FunctionInstance* instance = imageDebugInfo->FunctionAt(i); i++) { // lookup the function or create it, if it doesn't exist yet Function* function = fFunctions->Lookup(instance); if (function != NULL) { // TODO: Also update possible user breakpoints in this 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) { RemoveImageDebugInfo(imageDebugInfo); return B_NO_MEMORY; } function->AddInstance(instance); instance->SetFunction(function); status_t error = _AddFunction(function); // Insert after adding the instance. Otherwise the function // wouldn't be hashable/comparable. if (error != B_OK) { function->RemoveInstance(instance); instance->SetFunction(NULL); RemoveImageDebugInfo(imageDebugInfo); return error; } } } // 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; } void TeamDebugInfo::RemoveImageDebugInfo(ImageDebugInfo* imageDebugInfo) { AutoLocker 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; FunctionInstance* instance = imageDebugInfo->FunctionAt(i); i++) { if (Function* function = instance->GetFunction()) { // TODO: Also update possible user breakpoints in this function! if (function->FirstInstance() == function->LastInstance()) { // function unused -- remove it // Note, that we have to remove it from the hash before removing // the instance, since otherwise the function cannot be compared // anymore. _RemoveFunction(function); function->ReleaseReference(); // The instance still has a reference. } function->RemoveInstance(instance); instance->SetFunction(NULL); // If this was the last instance, it will remove the last // reference to the function. } } // remove cached types from that image fTypeCache->RemoveTypes(imageDebugInfo->GetImageInfo().ImageID()); fImages.RemoveItem(imageDebugInfo); } ImageDebugInfo* TeamDebugInfo::ImageDebugInfoByName(const char* name) const { for (int32 i = 0; ImageDebugInfo* imageDebugInfo = fImages.ItemAt(i); i++) { if (imageDebugInfo->GetImageInfo().Name() == name) return imageDebugInfo; } return NULL; } Function* TeamDebugInfo::FunctionAtSourceLocation(LocatableFile* file, const SourceLocation& location) const { if (SourceFileEntry* entry = fSourceFiles->Lookup(file)) return entry->FunctionAtLocation(location); return NULL; } Function* TeamDebugInfo::FunctionByID(FunctionID* functionID) const { if (SourceFunctionID* sourceFunctionID = dynamic_cast(functionID)) { // get the source file LocatableFile* file = fFileManager->GetSourceFile( sourceFunctionID->SourceFilePath()); if (file == NULL) return NULL; BReference fileReference(file, true); if (SourceFileEntry* entry = fSourceFiles->Lookup(file)) return entry->FunctionByName(functionID->FunctionName()); return NULL; } ImageFunctionID* imageFunctionID = dynamic_cast(functionID); if (imageFunctionID == NULL) return NULL; ImageDebugInfo* imageDebugInfo = ImageDebugInfoByName(imageFunctionID->ImageName()); if (imageDebugInfo == NULL) return NULL; FunctionInstance* functionInstance = imageDebugInfo->FunctionByName( functionID->FunctionName()); return functionInstance != NULL ? functionInstance->GetFunction() : NULL; } status_t TeamDebugInfo::_AddFunction(Function* function) { // If the function refers to a source file, add it to the respective entry. if (LocatableFile* sourceFile = function->SourceFile()) { SourceFileEntry* entry = fSourceFiles->Lookup(sourceFile); if (entry == NULL) { // no entry for the source file yet -- create on entry = new(std::nothrow) SourceFileEntry(sourceFile); if (entry == NULL) return B_NO_MEMORY; status_t error = entry->Init(); if (error != B_OK) { delete entry; return error; } fSourceFiles->Insert(entry); } // add the function status_t error = entry->AddFunction(function); if (error != B_OK) { if (entry->IsUnused()) { fSourceFiles->Remove(entry); delete entry; } return error; } } fFunctions->Insert(function); return B_OK; } void TeamDebugInfo::_RemoveFunction(Function* function) { fFunctions->Remove(function); // If the function refers to a source file, remove it from the respective // entry. if (LocatableFile* sourceFile = function->SourceFile()) { if (SourceFileEntry* entry = fSourceFiles->Lookup(sourceFile)) entry->RemoveFunction(function); } }