runtime_loader: add support for ELF based TLS

This patch introduces support of ELF based TLS handling with lazy allocation
and initalization of TLS block for each DSO and thread. The implementation
generally follows the official ABI except that generation counter in dtv
is in fact a pointer to Generation object that contains both generation
counter and size of the dtv. That simplified the implementation a bit, but
could be changed later. The ABI requirements regariding in memory position
of TLS block is not honoured what results in static TLS model being
unsupported. However, that should not be a problem as long as
"executables" in Haiku are in fact shared objects and optimizations which
require specific TLS block in memory layout are not possible anyway.
This commit is contained in:
Pawel Dziepak
2014-05-04 04:00:17 +02:00
parent 828ea2c47d
commit 44c0c4d3d0
20 changed files with 497 additions and 10 deletions
@@ -45,6 +45,8 @@ struct rld_export {
const char** _architecture); const char** _architecture);
status_t (*get_next_image_dependency)(image_id id, uint32 *cookie, status_t (*get_next_image_dependency)(image_id id, uint32 *cookie,
const char **_name); const char **_name);
void* (*get_tls_address)(unsigned dso, addr_t offset);
void (*destroy_thread_tls)();
status_t (*reinit_after_fork)(); status_t (*reinit_after_fork)();
@@ -114,6 +116,8 @@ typedef struct image_t {
elf_rel *pltrel; elf_rel *pltrel;
int pltrel_len; int pltrel_len;
unsigned dso_tls_id;
uint32 num_needed; uint32 num_needed;
struct image_t **needed; struct image_t **needed;
+1
View File
@@ -158,6 +158,7 @@
#define STT_FUNC 2 #define STT_FUNC 2
#define STT_SECTION 3 #define STT_SECTION 3
#define STT_FILE 4 #define STT_FILE 4
#define STT_TLS 6
#define STT_LOPROC 13 #define STT_LOPROC 13
#define STT_HIPROC 15 #define STT_HIPROC 15
+1
View File
@@ -19,6 +19,7 @@ enum {
TLS_ERRNO_SLOT, TLS_ERRNO_SLOT,
TLS_ON_EXIT_THREAD_SLOT, TLS_ON_EXIT_THREAD_SLOT,
TLS_USER_THREAD_SLOT, TLS_USER_THREAD_SLOT,
TLS_DYNAMIC_THREAD_VECTOR,
// Note: these entries can safely be changed between // Note: these entries can safely be changed between
// releases; 3rd party code always calls tls_allocate() // releases; 3rd party code always calls tls_allocate()
+1 -1
View File
@@ -190,7 +190,7 @@ arch_team_init_team_struct(Team* p, bool kernel)
status_t status_t
arch_thread_init_tls(Thread* thread) arch_thread_init_tls(Thread* thread)
{ {
size_t tls[TLS_USER_THREAD_SLOT + 1]; addr_t tls[TLS_FIRST_FREE_SLOT];
thread->user_local_storage = thread->user_stack_base thread->user_local_storage = thread->user_stack_base
+ thread->user_stack_size; + thread->user_stack_size;
+29
View File
@@ -1,4 +1,5 @@
/* /*
* Copyright 2014, Paweł Dziepak, [email protected].
* Copyright 2003, Axel Dörfler, [email protected]. All rights reserved. * Copyright 2003, Axel Dörfler, [email protected]. All rights reserved.
* Distributed under the terms of the MIT License. * Distributed under the terms of the MIT License.
*/ */
@@ -9,10 +10,25 @@
# define _NO_INLINE_ASM 1 # define _NO_INLINE_ASM 1
#endif #endif
#include <runtime_loader/runtime_loader.h>
#include "support/TLS.h" #include "support/TLS.h"
#include "tls.h" #include "tls.h"
#if !defined(__GNUC__) || (__GNUC__ > 2)
struct tls_index {
unsigned long int module;
unsigned long int offset;
};
void* ___tls_get_addr(struct tls_index* ti) __attribute__((__regparm__(1)));
#endif // GCC2
static int32 gNextSlot = TLS_FIRST_FREE_SLOT; static int32 gNextSlot = TLS_FIRST_FREE_SLOT;
@@ -58,3 +74,16 @@ tls_set(int32 index, void *value)
: : "r" (index), "r" (value)); : : "r" (index), "r" (value));
} }
#if !defined(__GNUC__) || (__GNUC__ > 2)
void* __attribute__((__regparm__(1)))
___tls_get_addr(struct tls_index* ti)
{
return __gRuntimeLoader->get_tls_address(ti->module, ti->offset);
}
#endif // GCC2
+16
View File
@@ -8,12 +8,20 @@
# define _NO_INLINE_ASM 1 # define _NO_INLINE_ASM 1
#endif #endif
#include <runtime_loader/runtime_loader.h>
#include <support/TLS.h> #include <support/TLS.h>
#include <tls.h> #include <tls.h>
#include <assert.h> #include <assert.h>
struct tls_index {
unsigned long int module;
unsigned long int offset;
};
static int32 gNextSlot = TLS_FIRST_FREE_SLOT; static int32 gNextSlot = TLS_FIRST_FREE_SLOT;
@@ -63,3 +71,11 @@ tls_set(int32 _index, void* value)
"movq %1, %%fs:(, %0, 8)" "movq %1, %%fs:(, %0, 8)"
: : "r" (index), "r" (value)); : : "r" (index), "r" (value));
} }
extern "C" void*
__tls_get_addr(tls_index* ti)
{
return __gRuntimeLoader->get_tls_address(ti->module, ti->offset);
}
+3
View File
@@ -11,6 +11,7 @@
#include <libroot_private.h> #include <libroot_private.h>
#include <pthread_private.h> #include <pthread_private.h>
#include <runtime_loader.h>
#include <thread_defs.h> #include <thread_defs.h>
#include <tls.h> #include <tls.h>
#include <syscalls.h> #include <syscalls.h>
@@ -70,6 +71,8 @@ _thread_do_exit_work(void)
tls_set(TLS_ON_EXIT_THREAD_SLOT, NULL); tls_set(TLS_ON_EXIT_THREAD_SLOT, NULL);
__gRuntimeLoader->destroy_thread_tls();
__pthread_destroy_thread(); __pthread_destroy_thread();
} }
View File
+4
View File
@@ -0,0 +1,4 @@
/*
* Copyright 2014, Paweł Dziepak, [email protected].
* Distributed under the terms of the MIT License.
*/
+2
View File
@@ -30,6 +30,7 @@ StaticLibrary libruntime_loader.a :
<src!system!libroot!os!$(architecture)>recursive_lock.o <src!system!libroot!os!$(architecture)>recursive_lock.o
<src!system!libroot!os!$(architecture)>syscalls.o <src!system!libroot!os!$(architecture)>syscalls.o
<src!system!libroot!os!$(architecture)>sem.o <src!system!libroot!os!$(architecture)>sem.o
<src!system!libroot!os!arch!$(TARGET_ARCH)!$(architecture)>tls.o
<src!system!libroot!posix!$(architecture)>errno.o <src!system!libroot!posix!$(architecture)>errno.o
<src!system!libroot!posix!$(architecture)>fcntl.o <src!system!libroot!posix!$(architecture)>fcntl.o
@@ -75,6 +76,7 @@ local sources =
elf_haiku_version.cpp elf_haiku_version.cpp
elf_load_image.cpp elf_load_image.cpp
elf_symbol_lookup.cpp elf_symbol_lookup.cpp
elf_tls.cpp
elf_versioning.cpp elf_versioning.cpp
pe.cpp pe.cpp
errors.cpp errors.cpp
@@ -38,6 +38,7 @@ relocate_rel(image_t *rootImage, image_t *image, struct Elf32_Rel *rel,
case R_386_GLOB_DAT: case R_386_GLOB_DAT:
case R_386_JMP_SLOT: case R_386_JMP_SLOT:
case R_386_GOTOFF: case R_386_GOTOFF:
case R_386_TLS_DTPOFF32:
{ {
struct Elf32_Sym *sym; struct Elf32_Sym *sym;
status_t status; status_t status;
@@ -90,6 +91,12 @@ relocate_rel(image_t *rootImage, image_t *image, struct Elf32_Rel *rel,
final_val = GOT + A - P; final_val = GOT + A - P;
break; break;
#endif #endif
case R_386_TLS_DTPMOD32:
final_val = image->dso_tls_id;
break;
case R_386_TLS_DTPOFF32:
final_val = S;
break;
default: default:
TRACE(("unhandled relocation type %d\n", ELF32_R_TYPE(rel[i].r_info))); TRACE(("unhandled relocation type %d\n", ELF32_R_TYPE(rel[i].r_info)));
return B_NOT_ALLOWED; return B_NOT_ALLOWED;
@@ -56,6 +56,13 @@ relocate_rela(image_t* rootImage, image_t* image, Elf64_Rela* rel,
case R_X86_64_RELATIVE: case R_X86_64_RELATIVE:
relocValue = image->regions[0].delta + rel[i].r_addend; relocValue = image->regions[0].delta + rel[i].r_addend;
break; break;
case R_X86_64_DTPMOD64:
relocValue = image->dso_tls_id;
break;
case R_X86_64_DTPOFF32:
case R_X86_64_DTPOFF64:
relocValue = symAddr;
break;
default: default:
TRACE(("unhandled relocation type %d\n", type)); TRACE(("unhandled relocation type %d\n", type));
return B_BAD_DATA; return B_BAD_DATA;
+3
View File
@@ -26,6 +26,7 @@
#include "add_ons.h" #include "add_ons.h"
#include "elf_load_image.h" #include "elf_load_image.h"
#include "elf_symbol_lookup.h" #include "elf_symbol_lookup.h"
#include "elf_tls.h"
#include "elf_versioning.h" #include "elf_versioning.h"
#include "errors.h" #include "errors.h"
#include "images.h" #include "images.h"
@@ -635,6 +636,8 @@ unload_library(void* handle, image_id imageID, bool addOn)
if (image->term_routine) if (image->term_routine)
((init_term_function)image->term_routine)(image->id); ((init_term_function)image->term_routine)(image->id);
TLSBlockTemplates::Get().Unregister(image->dso_tls_id);
dequeue_disposable_image(image); dequeue_disposable_image(image);
unmap_image(image); unmap_image(image);
@@ -18,6 +18,7 @@
#include "add_ons.h" #include "add_ons.h"
#include "elf_haiku_version.h" #include "elf_haiku_version.h"
#include "elf_symbol_lookup.h" #include "elf_symbol_lookup.h"
#include "elf_tls.h"
#include "elf_versioning.h" #include "elf_versioning.h"
#include "images.h" #include "images.h"
#include "runtime_loader_private.h" #include "runtime_loader_private.h"
@@ -77,6 +78,9 @@ count_regions(const char* imagePath, char const* buff, int phnum, int phentsize)
case PT_STACK: case PT_STACK:
// we don't use it // we don't use it
break; break;
case PT_TLS:
// will be handled at some other place
break;
default: default:
FATAL("%s: Unhandled pheader type in count 0x%" B_PRIx32 "\n", FATAL("%s: Unhandled pheader type in count 0x%" B_PRIx32 "\n",
imagePath, pheaders->p_type); imagePath, pheaders->p_type);
@@ -95,6 +99,8 @@ parse_program_headers(image_t* image, char* buff, int phnum, int phentsize)
int regcount; int regcount;
int i; int i;
image->dso_tls_id = unsigned(-1);
regcount = 0; regcount = 0;
for (i = 0; i < phnum; i++) { for (i = 0; i < phnum; i++) {
pheader = (elf_phdr*)(buff + i * phentsize); pheader = (elf_phdr*)(buff + i * phentsize);
@@ -193,6 +199,12 @@ parse_program_headers(image_t* image, char* buff, int phnum, int phentsize)
case PT_STACK: case PT_STACK:
// we don't use it // we don't use it
break; break;
case PT_TLS:
image->dso_tls_id
= TLSBlockTemplates::Get().Register(
TLSBlockTemplate((void*)pheader->p_vaddr,
pheader->p_filesz, pheader->p_memsz));
break;
default: default:
FATAL("%s: Unhandled pheader type in parse 0x%" B_PRIx32 "\n", FATAL("%s: Unhandled pheader type in parse 0x%" B_PRIx32 "\n",
image->path, pheader->p_type); image->path, pheader->p_type);
@@ -315,6 +327,10 @@ parse_dynamic_segment(image_t* image)
uint32 flags = d[i].d_un.d_val; uint32 flags = d[i].d_un.d_val;
if ((flags & DF_SYMBOLIC) != 0) if ((flags & DF_SYMBOLIC) != 0)
image->flags |= RFLAG_SYMBOLIC; image->flags |= RFLAG_SYMBOLIC;
if ((flags & DF_STATIC_TLS) != 0) {
FATAL("Static TLS model is not supported.\n");
return false;
}
break; break;
} }
default: default:
@@ -514,6 +514,7 @@ resolve_symbol(image_t* rootImage, image_t* image, elf_sym* sym,
} }
enum { enum {
SUCCESS,
ERROR_NO_SYMBOL, ERROR_NO_SYMBOL,
ERROR_WRONG_TYPE, ERROR_WRONG_TYPE,
ERROR_NOT_EXPORTED, ERROR_NOT_EXPORTED,
@@ -521,6 +522,7 @@ resolve_symbol(image_t* rootImage, image_t* image, elf_sym* sym,
}; };
uint32 lookupError = ERROR_UNPATCHED; uint32 lookupError = ERROR_UNPATCHED;
bool tlsSymbol = sym->Type() == STT_TLS;
void* location = NULL; void* location = NULL;
if (sharedSym == NULL) { if (sharedSym == NULL) {
// symbol not found at all // symbol not found at all
@@ -538,14 +540,20 @@ resolve_symbol(image_t* rootImage, image_t* image, elf_sym* sym,
sharedImage = NULL; sharedImage = NULL;
} else { } else {
// symbol is fine, get its location // symbol is fine, get its location
location = (void*)(sharedSym->st_value location = (void*)sharedSym->st_value;
+ sharedImage->regions[0].delta); if (!tlsSymbol) {
location
= (void*)((addr_t)location + sharedImage->regions[0].delta);
} else
lookupError = SUCCESS;
} }
patch_undefined_symbol(rootImage, image, symName, &sharedImage, &location, if (!tlsSymbol) {
&type); patch_undefined_symbol(rootImage, image, symName, &sharedImage,
&location, &type);
}
if (location == NULL) { if (location == NULL && lookupError != SUCCESS) {
switch (lookupError) { switch (lookupError) {
case ERROR_NO_SYMBOL: case ERROR_NO_SYMBOL:
FATAL("%s: Could not resolve symbol '%s'\n", FATAL("%s: Could not resolve symbol '%s'\n",
+304
View File
@@ -0,0 +1,304 @@
/*
* Copyright 2014, Paweł Dziepak, pdziepak@quarnos.org.
* Distributed under the terms of the MIT License.
*/
#include "elf_tls.h"
#include <stdlib.h>
#include <string.h>
#include <support/TLS.h>
#include <tls.h>
class TLSBlock {
public:
inline TLSBlock();
inline TLSBlock(void* pointer);
inline status_t Initialize(unsigned dso);
void Destroy();
bool IsInvalid() const { return fPointer == NULL; }
void* operator+(addr_t offset) const
{ return (void*)((addr_t)fPointer + offset); }
private:
void* fPointer;
};
class Generation {
public:
inline Generation();
unsigned Counter() const { return fCounter; }
unsigned Size() const { return fSize; }
void SetCounter(unsigned counter) { fCounter = counter; }
void SetSize(unsigned size) { fSize = size; }
private:
unsigned fCounter;
unsigned fSize;
};
class DynamicThreadVector {
public:
inline DynamicThreadVector();
void DestroyAll();
inline TLSBlock& operator[](unsigned dso);
private:
bool _DoesExist() const { return *fVector != NULL; }
unsigned _Size() const
{ return _DoesExist()
? fGeneration->Size() : 0; }
unsigned _Generation() const;
status_t _ResizeVector(unsigned minimumSize);
TLSBlock** fVector;
Generation* fGeneration;
TLSBlock fNullBlock;
};
TLSBlockTemplates* TLSBlockTemplates::fInstance;
void
TLSBlockTemplate::SetBaseAddress(addr_t baseAddress)
{
fAddress = (void*)((addr_t)fAddress + baseAddress);
}
TLSBlock
TLSBlockTemplate::CreateBlock()
{
void* pointer = malloc(fMemorySize);
if (pointer == NULL)
return TLSBlock();
memcpy(pointer, fAddress, fFileSize);
if (fMemorySize > fFileSize)
memset((char*)pointer + fFileSize, 0, fMemorySize - fFileSize);
return TLSBlock(pointer);
}
TLSBlockTemplates&
TLSBlockTemplates::Get()
{
if (fInstance == NULL)
fInstance = new TLSBlockTemplates;
return *fInstance;
}
unsigned
TLSBlockTemplates::Register(const TLSBlockTemplate& block)
{
unsigned dso;
if (!fFreeDSOs.empty()) {
dso = fFreeDSOs.back();
fFreeDSOs.pop_back();
fTemplates[dso] = block;
} else {
dso = fTemplates.size();
fTemplates.push_back(block);
}
fTemplates[dso].SetGeneration(fGeneration++);
return dso;
}
void
TLSBlockTemplates::Unregister(unsigned dso)
{
if (dso == unsigned(-1))
return;
fFreeDSOs.push_back(dso);
}
void
TLSBlockTemplates::SetBaseAddress(unsigned dso, addr_t baseAddress)
{
if (dso != unsigned(-1))
fTemplates[dso].SetBaseAddress(baseAddress);
}
unsigned
TLSBlockTemplates::GetGeneration(unsigned dso) const
{
if (dso == unsigned(-1))
return fGeneration;
return fTemplates[dso].Generation();
}
TLSBlock
TLSBlockTemplates::CreateBlock(unsigned dso)
{
return fTemplates[dso].CreateBlock();
}
TLSBlock::TLSBlock()
:
fPointer(NULL)
{
}
TLSBlock::TLSBlock(void* pointer)
:
fPointer(pointer)
{
}
status_t
TLSBlock::Initialize(unsigned dso)
{
fPointer = TLSBlockTemplates::Get().CreateBlock(dso).fPointer;
return fPointer != NULL ? B_OK : B_NO_MEMORY;
}
void
TLSBlock::Destroy()
{
free(fPointer);
fPointer = NULL;
}
Generation::Generation()
:
fCounter(0),
fSize(0)
{
}
DynamicThreadVector::DynamicThreadVector()
:
fVector((TLSBlock**)tls_address(TLS_DYNAMIC_THREAD_VECTOR)),
fGeneration(NULL)
{
if (*fVector != NULL)
fGeneration = (Generation*)*(void**)*fVector;
}
void
DynamicThreadVector::DestroyAll()
{
for (unsigned i = 0; i < _Size(); i++) {
TLSBlock& block = (*fVector)[i + 1];
if (!block.IsInvalid())
block.Destroy();
}
free(*fVector);
*fVector = NULL;
delete fGeneration;
}
TLSBlock&
DynamicThreadVector::operator[](unsigned dso)
{
unsigned generation = TLSBlockTemplates::Get().GetGeneration(-1);
if (_Generation() < generation) {
for (unsigned i = 0; i < _Size(); i++) {
TLSBlock& block = (*fVector)[i + 1];
unsigned dsoGeneration
= TLSBlockTemplates::Get().GetGeneration(dso);
if (_Generation() < dsoGeneration && dsoGeneration <= generation)
block.Destroy();
}
fGeneration->SetCounter(generation);
}
if (_Size() <= dso) {
status_t result = _ResizeVector(dso + 1);
if (result != B_OK)
return fNullBlock;
}
TLSBlock& block = (*fVector)[dso + 1];
if (block.IsInvalid()) {
status_t result = block.Initialize(dso);
if (result != B_OK)
return fNullBlock;
};
return block;
}
unsigned
DynamicThreadVector::_Generation() const
{
if (fGeneration != NULL)
return fGeneration->Counter();
return unsigned(-1);
}
status_t
DynamicThreadVector::_ResizeVector(unsigned minimumSize)
{
static const unsigned kInitialSize = 4;
unsigned size = std::max(minimumSize, kInitialSize);
unsigned oldSize = _Size();
if (size <= oldSize)
return B_OK;
void* newVector = realloc(*fVector, (size + 1) * sizeof(TLSBlock));
if (newVector == NULL)
return B_NO_MEMORY;
*fVector = (TLSBlock*)newVector;
memset(*fVector + oldSize + 1, 0, (size - oldSize) * sizeof(TLSBlock));
if (fGeneration == NULL)
fGeneration = new Generation;
*(Generation**)*fVector = fGeneration;
fGeneration->SetSize(size);
return B_OK;
}
void*
get_tls_address(unsigned dso, addr_t offset)
{
DynamicThreadVector dynamicThreadVector;
TLSBlock& block = dynamicThreadVector[dso];
if (block.IsInvalid())
return NULL;
return block + offset;
}
void
destroy_thread_tls()
{
DynamicThreadVector().DestroyAll();
}
+76
View File
@@ -0,0 +1,76 @@
/*
* Copyright 2014, Paweł Dziepak, pdziepak@quarnos.org.
* Distributed under the terms of the MIT License.
*/
#ifndef ELF_TLS_H
#define ELF_TLS_H
#include "runtime_loader_private.h"
#include "utility.h"
class TLSBlock;
class TLSBlockTemplate {
public:
TLSBlockTemplate() { }
inline TLSBlockTemplate(void* address, size_t fileSize,
size_t memorySize);
void SetGeneration(unsigned generation)
{ fGeneration = generation; }
unsigned Generation() const { return fGeneration; }
void SetBaseAddress(addr_t baseAddress);
TLSBlock CreateBlock();
private:
unsigned fGeneration;
void* fAddress;
size_t fFileSize;
size_t fMemorySize;
};
class TLSBlockTemplates {
public:
static TLSBlockTemplates& Get();
unsigned Register(const TLSBlockTemplate& block);
void Unregister(unsigned dso);
void SetBaseAddress(unsigned dso, addr_t baseAddress);
unsigned GetGeneration(unsigned dso) const;
TLSBlock CreateBlock(unsigned dso);
private:
TLSBlockTemplates() { }
static TLSBlockTemplates* fInstance;
unsigned fGeneration;
utility::vector<TLSBlockTemplate> fTemplates;
utility::vector<unsigned> fFreeDSOs;
};
TLSBlockTemplate::TLSBlockTemplate(void* address, size_t fileSize,
size_t memorySize)
:
fAddress(address),
fFileSize(fileSize),
fMemorySize(memorySize)
{
}
void* get_tls_address(unsigned dso, addr_t offset);
void destroy_thread_tls();
#endif // ELF_TLS_H
+5
View File
@@ -8,6 +8,7 @@
#include "runtime_loader_private.h" #include "runtime_loader_private.h"
#include "elf_tls.h"
// exported via the rld_export structure in user space program arguments // exported via the rld_export structure in user space program arguments
@@ -55,6 +56,8 @@ struct rld_export gRuntimeLoader = {
test_executable, test_executable,
get_executable_architecture, get_executable_architecture,
get_next_image_dependency, get_next_image_dependency,
get_tls_address,
destroy_thread_tls,
elf_reinit_after_fork, elf_reinit_after_fork,
NULL, // call_atexit_hooks_for_range NULL, // call_atexit_hooks_for_range
@@ -66,6 +69,8 @@ struct rld_export gRuntimeLoader = {
0 // ABI version 0 // ABI version
}; };
rld_export* __gRuntimeLoader = &gRuntimeLoader;
void void
rldexport_init(void) rldexport_init(void)
+5
View File
@@ -20,6 +20,7 @@
#include <vm_defs.h> #include <vm_defs.h>
#include "add_ons.h" #include "add_ons.h"
#include "elf_tls.h"
#include "runtime_loader_private.h" #include "runtime_loader_private.h"
@@ -396,6 +397,10 @@ map_image(int fd, char const* path, image_t* image)
image->regions[i].delta = loadAddress - image->regions[i].vmstart; image->regions[i].delta = loadAddress - image->regions[i].vmstart;
image->regions[i].vmstart = loadAddress; image->regions[i].vmstart = loadAddress;
if (i == 0) {
TLSBlockTemplates::Get().SetBaseAddress(image->dso_tls_id,
loadAddress);
}
} }
if (image->dynamic_ptr != 0) if (image->dynamic_ptr != 0)
@@ -50,9 +50,7 @@ extern bool gProgramLoaded;
extern image_t* gProgramImage; extern image_t* gProgramImage;
#ifdef __cplusplus
extern "C" { extern "C" {
#endif
int runtime_loader(void* arg, void* commpage); int runtime_loader(void* arg, void* commpage);
int open_executable(char* name, image_type type, const char* rpath, int open_executable(char* name, image_type type, const char* rpath,
@@ -93,8 +91,6 @@ status_t heap_init(void);
status_t arch_relocate_image(image_t* rootImage, image_t* image, status_t arch_relocate_image(image_t* rootImage, image_t* image,
SymbolLookupCache* cache); SymbolLookupCache* cache);
#ifdef __cplusplus
} }
#endif
#endif /* RUNTIME_LOADER_PRIVATE_H */ #endif /* RUNTIME_LOADER_PRIVATE_H */