#include <stdio.h>
#include <stdlib.h>
#include <new>
#include <OS.h>
#include <runtime_loader.h>
#include <util/OpenHashTable.h>
#include <syscalls.h>
#include "arch/ltrace_stub.h"
#ifdef TRACE_STUB
# define TRACE(x...) ktrace_printf(x)
#else
# define TRACE(x...) ;
#endif
static void* function_call_callback(const void* stub, const void* args);
static const size_t kPatchMemoryChunkSize = B_PAGE_SIZE * 16;
static const size_t kPatchMemoryReserveSize = 1 * 1024 * 1024;
static uint8* sPatchMemoryChunk = NULL;
static size_t sPatchMemoryChunkRemaining = 0;
static area_id sPatchMemoryChunkArea = 0;
static size_t sPatchMemoryChunkAreaSize = 0;
static uint8*
patch_malloc(size_t size)
{
if (sPatchMemoryChunkRemaining < size) {
status_t status = B_NO_MEMORY;
if (sPatchMemoryChunkArea != 0) {
status = _kern_resize_area(sPatchMemoryChunkArea,
sPatchMemoryChunkAreaSize + kPatchMemoryChunkSize);
if (status == B_OK) {
sPatchMemoryChunkAreaSize += kPatchMemoryChunkSize;
sPatchMemoryChunkRemaining += kPatchMemoryChunkSize;
}
}
if (status != B_OK) {
void* reservedBase;
status = _kern_reserve_address_range((addr_t*)&reservedBase,
B_RANDOMIZED_ANY_ADDRESS, kPatchMemoryReserveSize);
if (status != B_OK)
return NULL;
void* base = reservedBase;
area_id area = _kern_create_area("ltrace patches", &base,
B_EXACT_ADDRESS, size, B_NO_LOCK,
B_READ_AREA | B_WRITE_AREA | B_EXECUTE_AREA);
if (area < 0) {
_kern_unreserve_address_range((addr_t)reservedBase,
kPatchMemoryReserveSize);
return NULL;
}
sPatchMemoryChunk = (uint8*)base;
sPatchMemoryChunkArea = area;
sPatchMemoryChunkRemaining = sPatchMemoryChunkAreaSize = size;
}
}
uint8* allocation = sPatchMemoryChunk;
sPatchMemoryChunk += size;
sPatchMemoryChunkRemaining -= size;
return allocation;
}
struct PatchEntry {
PatchEntry* original_table_link;
void* original_function;
const char* function_name;
bool trace;
static PatchEntry* Create(const char* name, void* function)
{
void* memory = patch_malloc(_ALIGN(sizeof(PatchEntry))
+ arch_call_stub_size());
if (memory == NULL)
return NULL;
PatchEntry* entry = new(memory) PatchEntry;
void* stub = (uint8*)memory + OffsetToStub();
arch_init_call_stub(stub, &function_call_callback, function);
entry->original_function = function;
entry->function_name = name;
entry->trace = true;
return entry;
}
static size_t OffsetToStub()
{
return _ALIGN(sizeof(PatchEntry));
}
void* Stub()
{
return (uint8*)this + OffsetToStub();
}
};
struct OriginalTableDefinition {
typedef const void* KeyType;
typedef PatchEntry ValueType;
size_t HashKey(const void* key) const
{
return (addr_t)key >> 2;
}
size_t Hash(PatchEntry* value) const
{
return HashKey(value->original_function);
}
bool Compare(const void* key, PatchEntry* value) const
{
return value->original_function == key;
}
PatchEntry*& GetLink(PatchEntry* value) const
{
return value->original_table_link;
}
};
static rld_export* sRuntimeLoaderInterface;
static runtime_loader_add_on_export* sRuntimeLoaderAddOnInterface;
static BOpenHashTable<OriginalTableDefinition> sOriginalTable;
static void*
function_call_callback(const void* stub, const void* _args)
{
PatchEntry* entry = (PatchEntry*)((uint8*)stub - PatchEntry::OffsetToStub());
if (!entry->trace)
return entry->original_function;
char buffer[1024];
size_t bufferSize = sizeof(buffer);
size_t written = 0;
const ulong* args = (const ulong*)_args;
written += snprintf(buffer, bufferSize, "ltrace: %s(",
entry->function_name);
for (int32 i = 0; i < 5; i++) {
written += snprintf(buffer + written, bufferSize - written, "%s%#lx",
i == 0 ? "" : ", ", args[i]);
}
written += snprintf(buffer + written, bufferSize - written, ")\n");
write(0, buffer, written);
return entry->original_function;
}
static void
symbol_patcher(void* cookie, image_t* rootImage, image_t* image,
const char* name, image_t** foundInImage, void** symbol, int32* type)
{
TRACE("symbol_patcher(%p, %p, %p, \"%s\", %p, %p, %" B_PRId32 ")\n",
cookie, rootImage, image, name, *foundInImage, *symbol, *type);
if (*type != B_SYMBOL_TYPE_TEXT)
return;
PatchEntry* entry = sOriginalTable.Lookup(*symbol);
if (entry != NULL) {
*foundInImage = NULL;
*symbol = entry->Stub();
return;
}
entry = PatchEntry::Create(name, *symbol);
if (entry == NULL)
return;
sOriginalTable.Insert(entry);
TRACE(" -> patching to %p\n", entry->Stub());
*foundInImage = NULL;
*symbol = entry->Stub();
}
static void
ltrace_stub_init(rld_export* standardInterface,
runtime_loader_add_on_export* addOnInterface)
{
TRACE("ltrace_stub_init(%p, %p)\n", standardInterface, addOnInterface);
sRuntimeLoaderInterface = standardInterface;
sRuntimeLoaderAddOnInterface = addOnInterface;
sOriginalTable.Init();
}
static void
ltrace_stub_image_loaded(image_t* image)
{
TRACE("ltrace_stub_image_loaded(%p): \"%s\" (%" B_PRId32 ")\n",
image, image->path, image->id);
if (sRuntimeLoaderAddOnInterface->register_undefined_symbol_patcher(image,
symbol_patcher, (void*)(addr_t)0xc0011eaf) != B_OK) {
TRACE(" failed to install symbol patcher\n");
}
}
static void
ltrace_stub_image_relocated(image_t* image)
{
TRACE("ltrace_stub_image_relocated(%p): \"%s\" (%" B_PRId32 ")\n",
image, image->path, image->id);
}
static void
ltrace_stub_image_initialized(image_t* image)
{
TRACE("ltrace_stub_image_initialized(%p): \"%s\" (%" B_PRId32 ")\n",
image, image->path, image->id);
}
static void
ltrace_stub_image_uninitializing(image_t* image)
{
TRACE("ltrace_stub_image_uninitializing(%p): \"%s\" (%" B_PRId32
")\n",image, image->path, image->id);
}
static void
ltrace_stub_image_unloading(image_t* image)
{
TRACE("ltrace_stub_image_unloading(%p): \"%s\" (%" B_PRId32 ")\n",
image, image->path, image->id);
}
runtime_loader_add_on __gRuntimeLoaderAddOn = {
RUNTIME_LOADER_ADD_ON_VERSION,
0,
ltrace_stub_init,
ltrace_stub_image_loaded,
ltrace_stub_image_relocated,
ltrace_stub_image_initialized,
ltrace_stub_image_uninitializing,
ltrace_stub_image_unloading
};