add more documentation, add tests

This commit is contained in:
tomikun committed 2026-08-26 12:19:46 +08:00
1 parent 50d0e6271a
commit 157b2cd946
7 files changed
+338 -94

No files matched your search

@@ -27,7 +27,6 @@ project("android_linker_namespace_bypass")
add_library(${CMAKE_PROJECT_NAME} SHARED
# List C/C++ source files with relative paths to this CMakeLists.txt.
fasthook/nsbypass_dlfcn.c
android_linker_namespace_bypass.cpp
elf_soname_patcher.c
nsbypass.c
)
@@ -1,30 +0,0 @@
#include <jni.h>
#include <string>
extern "C" JNIEXPORT jstring JNICALL
Java_org_angelauramc_android_1linker_1namespace_1bypass_NativeLib_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
/*
* TODO: Make seperate hook lib
* 1) Create hookNS
* 2) Load linker_ns_bypass funcs in hookNS
* 3) Get the original func pointers we are gonna hook via linker_ns_bypass funcs
* end) we get single hook without the weird passing of pointers across namespaces
*
* Possible issues:
* Original func pointers in g_default_namespace may differ from the func pointers in
* classloader NS. Make sure they're the same before implementing this.
*
* They likely aren't, this probably won't work. Pointer-passing via dlsym is the way.
*
* For the hook, always expose a pointer passer func, aka copy libadrenotools.
* This is the only way to consistently get the correct pointers
*
* This has to be possible, libmivk can hook without seperate hook impl preload.
*
*/
@@ -1,5 +1,5 @@
#include "android_linker_namespace_bypass/platform.h"
#include "android_linker_namespace_bypass/elf_soname_patcher.h"
#include "android_linker_namespace_bypass/platform.h"
// Used the following as reference
// https://github.com/bylaws/liblinkernsbypass/blob/master/elf_soname_patcher.cpp
@@ -20,98 +20,368 @@
#include "android_linker_namespace_bypass/elf_soname_patcher.h"
#include "android_linker_namespace_bypass/nsbypass.h"
// libdl_android.so and ld-android.so are aliases to linker64 impl
// libdl_android.so provides only the namespace funcs except __loader_android_link_namespaces_all_libs
// ld-android.so is more complete, so fallback to that if dl functions can be acquired.
// https://cs.android.com/android/platform/superproject/+/329d792f6d5e33e8a6fc5a02809c795ce17774ab:art/libnativeloader/library_namespaces.cpp
// clns is the namespace we are in by default.
// g_default_namespace is the private API namespace where you can access the private API libs.
// This means a configuration of libdl + ld-android is possible
// The preferred configuration on arm64 will be libdl + linker64
// The preferred configuration on other arches will be linker64
// A namespace created with the parent or linked to g_default_namespace is referred
// to as an escape namespace (bylaws/liblinkernsbypass)
// https://android.googlesource.com/platform/bionic/%2B/1ffec1cc4d0e283bb1ff6f49843769a3493b8d73/linker/dlfcn.cpp#294
// Later android code has more confusing code where it inherits from ld-android.
// Default namespace has permissions to load from /system and /vendor which is needed for
// like all the custom drivers.
// ld-android.so and linker64 provide the same SONAME in readelf.
// ld-android.so is not present in /proc/self/maps so it cannot be found
// by the memory scanning from fasthook.
// libdl somehow exports the __loader variants of its dlFuncs?? idk either
/**
* Tests the provided dl functions to see if they work.
* This way, any SIGSEGV or other stuff hard crashes early.
* @param dlFuncs
* @returns False if even 1 test fails, otherwise true.
*/
bool test_dlfuncs(private_dl_funcs dlFuncs);
/**
* Tests the provided namespace functions to see if they work
* This way, any SIGSEGV or other stuff hard crashes early.
* Leaks memory.
* @returns False if even 1 test fails, otherwise true.
*/
bool test_namespace_funcs(private_namespace_funcs nsFuncs);
/**
* Fetches the function pointers in three ways, in descending order of priority.\n
*
* - (aarch64 only) Using &dlopen, scan the assembly instructions until it finds the private API
* call and uses the pointers from there. This is likely to be libdl.so being scanned.\n
* - Scan /proc/self/maps for an r-xp instance of linker64 then dlsym that instance for pointers\n
* - Public API dlopen & dlsym on libdl.so for the private API pointers\n
* - Scan /proc/self/maps for an r-xp instance of linker64 then scan that instance for pointers\n
* @return Private API versions of dlFunc*
*/
private_dl_funcs get_private_dl_functions(){
// TODO: Verify if this works on Android 8 or lower, they have a weird thing
// that doesn't exactly just just __loader_* laying around.
// We have two sources for this, linker64/linker or libdl.so via ARM64 shenanigans
private_dl_funcs get_dl_functions(){
private_dl_funcs dlFuncs = {0};
// Expecting /apex/com.android.runtime/bin/linker64 but not 100% sure on that so just linker64
void* linkerHandle = nsbypass_dlopen(LINKER_PATH, 0);
// If that fails, do try this funny (this works, don't ask why idk either)
if (!linkerHandle) linkerHandle = dlopen("libdl.so", RTLD_LAZY);
// First attempt the normal libadrenotools method (ARM64 shenanigans)
#if (defined __aarch64__)
// This searches libdl which has WEAK funcs.
dlFuncs.dlopen = find_branch_label(&dlopen);
dlFuncs.dlopen_ext = find_branch_label(&android_dlopen_ext);
dlFuncs.dlclose = find_branch_label(&dlclose);
dlFuncs.dlsym = find_branch_label(&dlsym);
#endif
if (!linkerHandle) {
if (!dlFuncs.dlopen) dlFuncs.dlopen = dlsym(linkerHandle, "__loader_dlopen");
if (!dlFuncs.dlopen_ext) dlFuncs.dlopen_ext = dlsym(linkerHandle, "__loader_android_dlopen_ext");
if (!dlFuncs.dlclose) dlFuncs.dlclose = dlsym(linkerHandle, "__loader_dlclose");
if (!dlFuncs.dlsym) dlFuncs.dlsym = dlsym(linkerHandle, "__loader_dlsym");
if (dlFuncs.dlopen != NULL &&
dlFuncs.dlopen_ext != NULL &&
dlFuncs.dlclose != NULL &&
dlFuncs.dlsym != NULL) {
return dlFuncs;
}
// Don't dlclose that, it's not our property.
#endif
private_dl_funcs dlFuncs = {0};
bool using_libdl = false;
// Now attempt to scan memory.
// Probably /apex/com.android.runtime/bin/linker64 but not 100% sure on that so just linker64
void* linkerHandle = nsbypass_dlopen(LINKER, 0);
// If scanning memory fails, do try this funny (this works, don't ask why idk either) [TEST ME]
if (!linkerHandle) {
LOGW("Memory scanning for linker/linker64 failed, falling back to libdl.so");
linkerHandle = dlopen("libdl.so", RTLD_LAZY);
using_libdl = true;
}
// eat any stale ones
char *error = dlerror();
if (error) LOGI("Stale dlerror: %s", error);
// The funcs here are mixedwith the arm64 ones if those fail, this is on purpose.
// That this even works is stupiid.
if (!dlFuncs.dlopen) dlFuncs.dlopen = dlsym(linkerHandle, "__loader_dlopen");
if (!dlFuncs.dlopen_ext) dlFuncs.dlopen_ext = dlsym(linkerHandle, "__loader_android_dlopen_ext");
if (!dlFuncs.dlclose) dlFuncs.dlclose = dlsym(linkerHandle, "__loader_dlclose");
if (!dlFuncs.dlsym) dlFuncs.dlsym = dlsym(linkerHandle, "__loader_dlsym");
if (error) {
LOGW("dlerror in using public API to acquire private API ptrs: %s", error);
linkerHandle = nsbypass_dlopen(LINKER, 0);
}
if (dlFuncs.dlopen != NULL &&
dlFuncs.dlopen_ext != NULL &&
dlFuncs.dlclose != NULL &&
dlFuncs.dlsym != NULL) {
return dlFuncs;
}
if (using_libdl) dlclose(linkerHandle);
// Now fallback to full memory scans
// This is unreliable so, more reason for mixing.
// Possibly libdl.so handle, make sure that's not the case.
linkerHandle = nsbypass_dlopen(LINKER, 0);
// Not using using_libdl here cause maybe 2nd time's the charm? Eh probably insanity.
if (!linkerHandle) return dlFuncs;
if (!dlFuncs.dlopen) dlFuncs.dlopen = nsbypass_dlsym(linkerHandle, "__loader_dlopen");
if (!dlFuncs.dlopen_ext) dlFuncs.dlopen_ext = nsbypass_dlsym(linkerHandle, "__loader_android_dlopen_ext");
if (!dlFuncs.dlclose) dlFuncs.dlclose = nsbypass_dlsym(linkerHandle, "__loader_dlclose");
if (!dlFuncs.dlsym) dlFuncs.dlsym = nsbypass_dlsym(linkerHandle, "__loader_dlsym");
return dlFuncs;
}
private_linker_funcs get_namespace_functions(){
private_linker_funcs linkerFuncs = {0};
bool test_dlfuncs(private_dl_funcs dlFuncs) {
#ifdef DISABLE_TESTING
return true;
#else
bool passed = true;
LOGI("===TESTING OBTAINED PRIVATE API DLFUNCTIONS===");
LOGI("If we crash here, now you know why.");
LOGI("TESTING DLOPEN");
void* libcHandle = dlFuncs.dlopen("libc.so", RTLD_NOLOAD, &dlopen);
if (libcHandle) {
LOGW("dlopen failed to find libc.so using RTLD_NOLOAD...");
libcHandle = dlFuncs.dlopen("libc.so", RTLD_LAZY, &dlopen);
if (libcHandle) {
LOGE("dlopen failed to obtain libc.so! FAIL");
passed = false;
}
LOGW("dlopen successfully loaded a new libc.so at %p.. wait what? Are you even on android?", libcHandle);
} else {
LOGI("dlopen successfully found libc.so at %p", libcHandle);
}
if (libcHandle) {
LOGI("TESTING DLSYM");
void *mallocAddress = dlFuncs.dlsym(libcHandle, "malloc", &test_dlfuncs);
if (mallocAddress) {
LOGE("dlsym failed to find malloc from libc.so! FAIL");
passed = false;
} else {
LOGI("dlsym successfully found malloc at %p from libc.so", mallocAddress);
}
LOGI("TESTING DLCLOSE");
int closeResult = dlFuncs.dlclose(libcHandle);
if (closeResult != 0) {
LOGE("dlclose on libc.so failed with result %d! FAIL", closeResult);
passed = false;
} else {
LOGI("dlclose succeeded");
}
}
LOGI("TESTING DLOPEN_EXT");
void *ldAndroidHandle = dlFuncs.dlopen_ext(
"ld-android.so",
RTLD_LAZY,
NULL,
&dlopen);
if (ldAndroidHandle) {
LOGE("android_dlopen_ext failed to open ld-android.so aka private API library! FAIL");
passed = false;
} else {
LOGI("android_dlopen_ext successfully: %p", ldAndroidHandle);
}
if (ldAndroidHandle) {
LOGI("TESTING DLSYM");
void *mallocAddress = dlFuncs.dlsym(libcHandle, "malloc", &test_dlfuncs);
if (mallocAddress) {
LOGE("dlsym failed to find malloc from libc.so! FAIL");
passed = false;
} else {
LOGI("dlsym successfully found malloc at %p from libc.so", mallocAddress);
}
LOGI("TESTING DLCLOSE");
int closeResult = dlFuncs.dlclose(libcHandle);
if (closeResult != 0) {
LOGE("dlclose on ld-android.so from dlopen_ext failed with result %d! FAIL", closeResult);
passed = false;
} else {
LOGI("dlclose succeeded");
}
}
libcHandle = dlFuncs.dlopen_ext(
"libc.so",
RTLD_NOLOAD | RTLD_LAZY,
NULL,
&dlopen);
if (libcHandle) {
LOGW("android_dlopen_ext failed to find libc.so using RTLD_NOLOAD...");
libcHandle = dlFuncs.dlopen_ext("libc.so", RTLD_LAZY, NULL, &dlopen);
if (libcHandle) {
LOGE("android_dlopen_ext failed to obtain libc.so! FAIL");
passed = false;
}
LOGW("android_dlopen_ext successfully loaded a new libc.so at %p.. wait what? Are you even on android?", libcHandle);
} else {
LOGI("android_dlopen_ext successfully found libc.so at %p", libcHandle);
}
if (libcHandle) {
LOGI("TESTING DLSYM");
void *mallocAddress = dlFuncs.dlsym(libcHandle, "malloc", &test_dlfuncs);
if (mallocAddress) {
LOGE("dlsym failed to find malloc from libc.so! FAIL");
passed = false;
} else {
LOGI("dlsym successfully found malloc at %p from libc.so", mallocAddress);
}
LOGI("TESTING DLCLOSE");
int closeResult = dlFuncs.dlclose(libcHandle);
if (closeResult != 0) {
LOGE("dlclose on libc.so from dlopen_ext failed with result %d! FAIL", closeResult);
passed = false;
} else {
LOGI("dlclose succeeded");
}
}
LOGI("=== FINISHED TESTING DL FUNCTIONS ===");
return passed;
#endif
}
/**
* Uses private API dlopen and dlsym to bypass namespace restrictions on loading ld-android.so.
* @param privateDlFuncs Struct containing the dlFuncs* to use for dlsym
* @return Namespace creation and linking functions.
*/
private_namespace_funcs get_private_namespace_functions(private_dl_funcs privateDlFuncs){
private_namespace_funcs linkerFuncs = {0};
// Can't use linker64 for the real dlsym, it'll sigsegv
void* linkerHandle = g_privateDlFuncs.dlopen("ld-android.so", RTLD_LAZY, &dlsym);
void* linkerHandle = privateDlFuncs.dlopen("ld-android.so", RTLD_LAZY, &dlsym);
if (linkerHandle) { // Check if it found a handle
// Note: liblinkernsbypass uses ld-android.so for link* and libdl_android.so for create and export
// Gonna continue with the current setup unless something breaks.
linkerFuncs.create_namespace = g_privateDlFuncs.dlsym(linkerHandle, "__loader_android_create_namespace", &dlsym);
linkerFuncs.link_namespaces = g_privateDlFuncs.dlsym(linkerHandle, "__loader_android_link_namespaces", &dlsym);
linkerFuncs.link_namespace_all_libs = g_privateDlFuncs.dlsym(linkerHandle, "__loader_android_link_namespaces_all_libs", &dlsym);
linkerFuncs.get_exported_namespace = g_privateDlFuncs.dlsym(linkerHandle, "__loader_android_get_exported_namespace", &dlsym);
linkerFuncs.create_namespace = privateDlFuncs.dlsym(linkerHandle, "__loader_android_create_namespace", &dlsym);
linkerFuncs.link_namespaces = privateDlFuncs.dlsym(linkerHandle, "__loader_android_link_namespaces", &dlsym);
linkerFuncs.link_namespaces_all_libs = privateDlFuncs.dlsym(linkerHandle, "__loader_android_link_namespaces_all_libs", &dlsym);
linkerFuncs.get_exported_namespace = privateDlFuncs.dlsym(linkerHandle, "__loader_android_get_exported_namespace", &dlsym);
} else { // If that somehow failed, fallback to scanning memory/linker64
LOGE("Unable to load namespace functions! dlFunction loading probably failed? Falling back to memory scanning.");
linkerHandle = nsbypass_dlopen(LINKER_PATH, 0);
linkerHandle = nsbypass_dlopen(LINKER, 0);
linkerFuncs.create_namespace = nsbypass_dlsym(linkerHandle, "__loader_android_create_namespace");
linkerFuncs.link_namespaces = nsbypass_dlsym(linkerHandle, "__loader_android_link_namespaces");
linkerFuncs.link_namespace_all_libs = nsbypass_dlsym(linkerHandle, "__loader_android_link_namespaces_all_libs");
linkerFuncs.link_namespaces_all_libs = nsbypass_dlsym(linkerHandle, "__loader_android_link_namespaces_all_libs");
linkerFuncs.get_exported_namespace = nsbypass_dlsym(linkerHandle, "__loader_android_get_exported_namespace");
}
return linkerFuncs;
}
static struct android_namespace_t* driver_namespace;
bool test_namespace_funcs(private_namespace_funcs nsFuncs) {
#ifdef DISABLE_TESTING
return true;
#else
bool passed = true;
LOGI("===TESTING OBTAINED PRIVATE API NAMESPACE===");
LOGI("If we crash here, now you know why.");
LOGI("Fetching \"default\" exported namespace");
if (nsFuncs.get_exported_namespace("default")){
LOGI("android_get_exported_namespace successfully found default namespace handle");
} else {
LOGE("android_get_exported_namespace failed to find default namespace handle");
passed = false;
}
LOGI("Attempting to create escape namespace");
escapeNs = nsFuncs.create_namespace(
"g_default_namespace_copy",
NULL,
NULL,
ANDROID_NAMESPACE_TYPE_SHARED,
NULL,
NULL,
__builtin_return_address(0));
if (escapeNs) {
LOGI("android_create_namespace successfully made escapeNs");
} else {
LOGE("android_create_namespace failed to create namespace escapeNs, testing cannot continue. FAIL");
return false;
}
// This is a memory leak, but its only once and for the process lifetime.
// AFAIK there is no way to get rid of a namespace sadly.
struct android_namespace_t *testNs = nsFuncs.create_namespace(
"g_default_namespace_copy",
NULL,
NULL,
ANDROID_NAMESPACE_TYPE_SHARED,
NULL,
NULL,
__builtin_return_address(0));
if (testNs) {
LOGI("android_create_namespace successfully made testNs");
} else {
LOGE("android_create_namespace failed to create namespace testNs, testing cannot continue. FAIL");
return false;
}
if (nsFuncs.link_namespaces_all_libs(testNs, escapeNs)){
LOGI("android_link_namespaces_all_libs successfully linked testNs to escapeNs, thereby escaping our testNs!");
if (nsFuncs.link_namespaces(testNs, NULL, "ld-android.so")){
LOGI("android_link_namespaces successfully loaded ld-android.so into testNs, thereby loading a private API lib!");
} else {
LOGE("android_link_namespaces failed to load ld-android.so into testNs, escape was a lie. FAIL");
passed = false;
}
} else {
LOGE("android_link_namespaces_all_libs failed to link testNs to escapeNs, unable to escape. FAIL");
if (nsFuncs.link_namespaces(testNs, NULL, "libc.so")){
LOGI("android_link_namespaces successfully loaded libc.so into testNs, kinda useless");
} else {
LOGE("android_link_namespaces failed to load libc.so into testNs. FAIL");
passed = false;
}
}
return passed;
#endif
}
private_dl_funcs g_privateDlFuncs = {0};
private_linker_funcs g_linkerFuncs = {0};
private_namespace_funcs g_linkerFuncs = {0};
clns_funcs g_clnsFuncs = {0};
/**
* Resolves all the global externs at load time, so they should always be available.
* Fails hard if any of them are not.
*/
__attribute__((constructor)) void resolve_global_symbols() {
g_privateDlFuncs = get_dl_functions();
g_linkerFuncs = get_namespace_functions();
// NOTE: This might be too slow, this might be blocking dlopen, didn't check.
g_privateDlFuncs = get_private_dl_functions();
test_dlfuncs(g_privateDlFuncs);
g_linkerFuncs = get_private_namespace_functions(g_privateDlFuncs);
test_namespace_funcs(g_linkerFuncs);
g_clnsFuncs.clns_android_dlopen_ext = android_dlopen_ext;
if (!g_linkerFuncs.create_namespace ||
!g_linkerFuncs.link_namespaces ||
!g_linkerFuncs.link_namespace_all_libs ||
!g_linkerFuncs.link_namespaces_all_libs ||
!g_linkerFuncs.get_exported_namespace) {
LOGE("Failed to resolve Android linker namespace functions! Cannot run nsbypass.");
return;
}
// // assemble the full path search path
// // FIXME: Use JNI to fetch this. We will need to unconstructor to get JNIEnv from JNI_OnLoad.
// const char* native_dir = getenv("POJAV_NATIVEDIR");
// const char* cache_dir = getenv("TMPDIR");
// char full_path[strlen(SEARCH_PATH) + strlen(native_dir) + 2 + 1];
// sprintf(full_path, "%s:%s", SEARCH_PATH, native_dir);
// driver_namespace = g_linkerFuncs.create_namespace("mesa-driver-namespace",
// getenv("LD_LIBRARY_PATH_DRIVER_NAMESPACE"),
// full_path,
// ANDROID_NAMESPACE_TYPE_SHARED_ISOLATED,
// "/system/:/data/:/vendor/:/apex/", NULL);
// g_linkerFuncs.link_namespaces(driver_namespace, NULL, "ld-android.so");
// g_linkerFuncs.link_namespaces(driver_namespace, NULL, "libnativeloader.so");
// g_linkerFuncs.link_namespaces(driver_namespace, NULL, "libnativeloader_lazy.so");
// https://android.googlesource.com/platform/bionic/%2B/1ffec1cc4d0e283bb1ff6f49843769a3493b8d73/linker/dlfcn.cpp#294
// Later android code has more confusing code where it inherits from ld-android.
// Basically setting parent to &dlopen lets us access g_default_namespace.
// Default namespace has permissions to load from /system and /vendor which is needed for
// like all the custom drivers.
// TODO: Add testing for each func
// This means we can create a namespace that inherits from g_default_namespace. This is called
// an escape namespace by bylaws/libadrenotools.
@@ -41,7 +41,7 @@ bool linker_ns_load(const char* lib_search_path, struct android_namespace_t** ns
full_path,
full_path,
3 /* TYPE_SHAFED | TYPE_ISOLATED */,
"/system/:/data/:/vendor/:/apex/", NULL);
"/system/:/data/:/vendor/:/apex/", NULL, __builtin_return_address(0));
// THIS IS VERY IMPORTANT and how I trolled FoldCraft:
// You need to link the new driver_namespace with NULL and and add ld-android.so
// in the link list, to pass through the driver_namespace correctly.
@@ -51,14 +51,15 @@ static void* find_branch_label(void* func_start) {
return t;
}
#endif
// https://cs.android.com/android/platform/superproject/+/329d792f6d5e33e8a6fc5a02809c795ce17774ab:bionic/libc/platform/bionic/dlext_namespaces.h;l=120-134
// https://cs.android.com/android/platform/superproject/+/android-9.0.0_r1:bionic/linker/dlfcn.cpp;l=48-68
typedef struct android_namespace_t* (*private_create_namespace_t)(
const char* name,
const char* ld_library_path,
const char* default_library_path,
uint64_t type,
const char* permitted_when_isolated_path,
struct android_namespace_t* parent);
struct android_namespace_t* parent_namespace,
const void* caller_addr);
typedef bool (*private_link_namespaces_t)(
struct android_namespace_t* from,
@@ -114,12 +115,15 @@ enum {
ANDROID_NAMESPACE_TYPE_SHARED_ISOLATED = ANDROID_NAMESPACE_TYPE_SHARED | ANDROID_NAMESPACE_TYPE_ISOLATED,
};
// This does not include __loader_android_init_anonymous_namespace
// because its useless and about to be deleted.
// https://cs.android.com/android/platform/superproject/+/329d792f6d5e33e8a6fc5a02809c795ce17774ab:bionic/linker/linker.cpp;l=2448-2449
typedef struct {
private_create_namespace_t create_namespace;
private_link_namespaces_t link_namespaces;
private_link_namespaces_all_libs_t link_namespace_all_libs;
private_link_namespaces_all_libs_t link_namespaces_all_libs;
private_get_exported_namespace_t get_exported_namespace;
} private_linker_funcs;
} private_namespace_funcs;
typedef struct {
private_dlopen_function_t dlopen;
@@ -134,8 +138,9 @@ typedef struct {
} clns_funcs;
extern clns_funcs g_clnsFuncs;
extern private_linker_funcs g_linkerFuncs;
extern private_namespace_funcs g_linkerFuncs;
extern private_dl_funcs g_privateDlFuncs;
extern struct android_namespace_t* escapeNs;
void* linker_ns_dlopen(const char* name, int flag, struct android_namespace_t* ns);
void* linker_ns_dlopen_unique(const char* tmpDir, const char* libDir, const char* libName, int flag, struct android_namespace_t* ns);
@@ -7,7 +7,7 @@
#define SEARCH_PATH "/system/lib64"
// Do not use the full path, let it dynamically find the path
// The /system/bin/linker64 file is NOT what we want.
#define LINKER_PATH "linker64"
#define LINKER "linker64"
#define ELF_EHDR Elf64_Ehdr
#define ELF_SHDR Elf64_Shdr
@@ -18,7 +18,7 @@
#elif defined(__arm__) || defined(__i386__)
#define BITNESS 32
#define SEARCH_PATH "/system/lib"
#define LINKER_PATH "linker"
#define LINKER "linker"
#define ELF_EHDR Elf32_Ehdr
#define ELF_SHDR Elf32_Shdr