/*
* Copyright 2012, The Android Open Source Project
*
* 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.
*/
// This file contains portions derived from LLVM, with the original copyright
// header below:
//==----- GDBJITRegistrar.cpp - Notify GDB about in-memory object files ---==//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This file defines the GDBJITRegistrar object which is used by JIT engines to
// register in-memory object files with GDB for debugging.
//
//===----------------------------------------------------------------------===//
#include "bcc/ExecutionEngine/GDBJITRegistrar.h"
#include <llvm/ADT/DenseMap.h>
#include <llvm/Support/ErrorHandling.h>
#include <llvm/Support/Memory.h>
#include <llvm/Support/Mutex.h>
#include <llvm/Support/MutexGuard.h>
#include "bcc/ExecutionEngine/GDBJIT.h"
#include <fstream>
#ifdef ANDROID_ENGINEERING_BUILD
// Path to write dump output.
// It is expected that a debugger (plugin) sets this
// string to a writeable directory where files (such as JITted object files,
// IR dumps) are to be written. If this variable is 0, no debug dumps
// are generated.
char* gDebugDumpDirectory = 0;
#endif // ANDROID_ENGINEERING_BUILD
//************************************************************************
// COPIED/ADAPTED FROM llvm/lib/ExecutionEngine/JIT/JITDebugRegisterer.cpp
//************************************************************************
// This must be kept in sync with gdb/gdb/jit.h .
extern "C" {
// We put information about the JITed function in this global, which the
// debugger reads. Make sure to specify the version statically, because the
// debugger checks the version before we can set it during runtime.
static struct jit_descriptor __jit_debug_descriptor = { 1, 0, 0, 0 };
}
//****************************************************************************
// END COPIED/ADAPTED FROM llvm/lib/ExecutionEngine/JIT/JITDebugRegisterer.cpp
//****************************************************************************
namespace {
// Buffer for an in-memory object file in executable memory
typedef llvm::DenseMap< const ObjectBuffer*, std::pair<std::size_t, jit_code_entry*> >
RegisteredObjectBufferMap;
/// Global access point for the GDB JIT interface designed for use with a
/// singleton toolbox. Handles thread-safe registration and deregistration of
/// object files that are in executable memory managed by the client of this
/// class.
class GDBJITRegistrar {
/// A map of in-memory object files that have been registered with the GDB JIT interface.
RegisteredObjectBufferMap ObjectBufferMap;
public:
/// Instantiates the GDB JIT service.
GDBJITRegistrar() : ObjectBufferMap() {}
/// Unregisters each object that was previously registered with GDB, and
/// releases all internal resources.
~GDBJITRegistrar();
/// Creates an entry in the GDB JIT registry for the buffer @p Object,
/// which must contain an object file in executable memory with any
/// debug information for GDB.
void registerObject(const ObjectBuffer* Object, std::size_t Size);
/// Removes the internal registration of @p Object, and
/// frees associated resources.
/// Returns true if @p Object was found in ObjectBufferMap.
bool deregisterObject(const ObjectBuffer* Object);
private:
/// Deregister the debug info for the given object file from the debugger
/// and delete any temporary copies. This private method does not remove
/// the function from Map so that it can be called while iterating over Map.
void deregisterObjectInternal(RegisteredObjectBufferMap::iterator I);
};
/// Lock used to serialize all gdb-jit registration events, since they
/// modify global variables.
llvm::sys::Mutex JITDebugLock;
/// Acquire the lock and do the registration.
void NotifyGDB(jit_code_entry* JITCodeEntry) {
llvm::MutexGuard locked(JITDebugLock);
__jit_debug_descriptor.action_flag = JIT_REGISTER_FN;
// Insert this entry at the head of the list.
JITCodeEntry->prev_entry = NULL;
jit_code_entry* NextEntry = __jit_debug_descriptor.first_entry;
JITCodeEntry->next_entry = NextEntry;
if (NextEntry != NULL) {
NextEntry->prev_entry = JITCodeEntry;
}
__jit_debug_descriptor.first_entry = JITCodeEntry;
__jit_debug_descriptor.relevant_entry = JITCodeEntry;
__jit_debug_register_code();
}
GDBJITRegistrar* RegistrarSingleton() {
static GDBJITRegistrar* sRegistrar = NULL;
if (sRegistrar == NULL) {
// The mutex is here so that it won't slow down access once the registrar
// is instantiated
llvm::MutexGuard locked(JITDebugLock);
// Check again to be sure another thread didn't create this while we waited
if (sRegistrar == NULL) {
sRegistrar = new GDBJITRegistrar;
}
}
return sRegistrar;
}
GDBJITRegistrar::~GDBJITRegistrar() {
// Free all registered object files.
for (RegisteredObjectBufferMap::iterator I = ObjectBufferMap.begin(), E = ObjectBufferMap.end();
I != E; ++I) {
// Call the private method that doesn't update the map so our iterator
// doesn't break.
deregisterObjectInternal(I);
}
ObjectBufferMap.clear();
}
void GDBJITRegistrar::registerObject(const ObjectBuffer* Object, std::size_t Size) {
assert(Object && "Attempt to register a null object with a debugger.");
assert(ObjectBufferMap.find(Object) == ObjectBufferMap.end()
&& "Second attempt to perform debug registration.");
jit_code_entry* JITCodeEntry = new jit_code_entry();
if (JITCodeEntry == 0) {
llvm::report_fatal_error("Allocation failed when registering a GDB-JIT entry!\n");
}
else {
JITCodeEntry->symfile_addr = Object;
JITCodeEntry->symfile_size = Size;
ObjectBufferMap[Object] = std::make_pair(Size, JITCodeEntry);
NotifyGDB(JITCodeEntry);
#ifdef ANDROID_ENGINEERING_BUILD
if (0 != gDebugDumpDirectory) {
std::string Filename(gDebugDumpDirectory);
Filename += "/jit_registered.o";
std::ofstream outfile(Filename.c_str(), std::ofstream::binary);
outfile.write((char*)JITCodeEntry->symfile_addr, JITCodeEntry->symfile_size);
outfile.close();
}
#endif
}
}
bool GDBJITRegistrar::deregisterObject(const ObjectBuffer *Object) {
RegisteredObjectBufferMap::iterator I = ObjectBufferMap.find(Object);
if (I != ObjectBufferMap.end()) {
deregisterObjectInternal(I);
ObjectBufferMap.erase(I);
return true;
}
return false;
}
void GDBJITRegistrar::deregisterObjectInternal(
RegisteredObjectBufferMap::iterator I) {
jit_code_entry*& JITCodeEntry = I->second.second;
// Acquire the lock and do the unregistration.
{
llvm::MutexGuard locked(JITDebugLock);
__jit_debug_descriptor.action_flag = JIT_UNREGISTER_FN;
// Remove the jit_code_entry from the linked list.
jit_code_entry* PrevEntry = JITCodeEntry->prev_entry;
jit_code_entry* NextEntry = JITCodeEntry->next_entry;
if (NextEntry) {
NextEntry->prev_entry = PrevEntry;
}
if (PrevEntry) {
PrevEntry->next_entry = NextEntry;
}
else {
assert(__jit_debug_descriptor.first_entry == JITCodeEntry);
__jit_debug_descriptor.first_entry = NextEntry;
}
// Tell GDB which entry we removed, and unregister the code.
__jit_debug_descriptor.relevant_entry = JITCodeEntry;
__jit_debug_register_code();
}
delete JITCodeEntry;
JITCodeEntry = NULL;
}
} // end namespace
void registerObjectWithGDB(const ObjectBuffer* Object, std::size_t Size) {
GDBJITRegistrar* Registrar = RegistrarSingleton();
if (Registrar) {
Registrar->registerObject(Object, Size);
}
}
void deregisterObjectWithGDB(const ObjectBuffer* Object) {
GDBJITRegistrar* Registrar = RegistrarSingleton();
if (Registrar) {
Registrar->deregisterObject(Object);
}
}