hexhacking
xDL
C

:fire: xDL is an enhanced implementation of the Android DL series functions.

Last updated Jul 3, 2026
634
Stars
125
Forks
5
Issues
+1
Stars/day
Attention Score
83
Language breakdown
C 93.9%
CMake 2.6%
Java 1.8%
Python 1.3%
Makefile 0.4%
โ–ธ Files click to expand
README

xDL

xDL is an enhanced implementation of the Android DL series functions.

็ฎ€ไฝ“ไธญๆ–‡

Features

  • Enhanced dlopen() + dlsym() + dladdr().
* Bypass the restrictions of Android 7.0+ linker namespace. * Lookup dynamic link symbols in .dynsym. * Lookup debuging symbols in .symtab and ".symtab in .gnu_debugdata".
  • Enhanced dliteratephdr().
* Compatible with Android 4.x on ARM32. * Including linker / linker64 (for Android <= 8.x). * Return full pathname instead of basename (for Android 5.x). * Return app\process32 / app\process64 instead of package name.
  • Support Android 4.1 - 17 (API level 16 - 37).
  • Support armeabi-v7a, arm64-v8a, x86 and x86_64.
  • MIT licensed.

Artifacts Size

If xDL is compiled into an independent dynamic library:

| ABI | Compressed (KB) | Uncompressed (KB) | | :---------- |----------------:|------------------:| | armeabi-v7a | 8.3 | 15 | | arm64-v8a | 9.1 | 20 | | x86 | 9.4 | 18 | | x86_64 | 9.3 | 20 |

Usage

1. Add dependency in build.gradle

xDL is published on Maven Central, and uses Prefab package format for native dependencies, which is supported by Android Gradle Plugin 4.0+.

android {
    buildFeatures {
        prefab true
    }
}

dependencies { implementation 'io.github.hexhacking:xdl:2.4.0' }

NOTE:

  • Starting from version 2.0.0 of xDL, group ID changed from io.hexhacking to io.github.hexhacking.
| version range | group ID | artifact ID | Repository URL | |:---------------|:-------------------------|:------------| :--------------| | [1.0.3, 1.2.1] | io.hexhacking | xdl | repo | | 2.0.0, ) | io.github.hexhacking | xdl | [repo |
android.prefabVersion=2.0.0

2. Add dependency in CMakeLists.txt or Android.mk

CMakeLists.txt
find_package(xdl REQUIRED CONFIG)

add_library(mylib SHARED mylib.c) targetlinklibraries(mylib xdl::xdl)

Android.mk
include $(CLEAR_VARS)
LOCAL_MODULE           := mylib
LOCALSRCFILES        := mylib.c
LOCALSHAREDLIBRARIES += xdl
include $(BUILDSHAREDLIBRARY)

$(call import-module,prefab/xdl)

3. Specify one or more ABI(s) you need

android {
    defaultConfig {
        ndk {
            abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
        }
    }
}

4. Add packaging options

If you are using xDL in an SDK project, you may need to avoid packaging libxdl.so into your AAR, so as not to encounter duplicate libxdl.so file when packaging the app project.

android {
    packagingOptions {
        exclude '**/libxdl.so'
    }
}

On the other hand, if you are using xDL in an APP project, you may need to add some options to deal with conflicts caused by duplicate libxdl.so file.

android {
    packagingOptions {
        pickFirst '**/libxdl.so'
    }
}

There is a sample app in the xdl-sample folder you can refer to.

API

#include "xdl.h"

1. xdlopen() and xdlclose()

#define XDL_DEFAULT           0x00
#define XDLTRYFORCE_LOAD    0x01
#define XDLALWAYSFORCE_LOAD 0x02

void xdl_open(const char filename, int flags); void xdlopen2(struct dlphdr_info info); void xdl_close(void handle);

They are very similar to dlopen() and dlclose(). But xdl_open() can bypass the restrictions of Android 7.0+ linker namespace.

Depending on the value of the flags parameter, the behavior of xdl_open() will have some differences:

  • XDLDEFAULT: If the library has been loaded into memory, xdlopen() will not dlopen() it again. (But it will still return a valid handle)
  • XDLTRYFORCELOAD: If the library has not been loaded into memory, xdlopen() will try to dlopen() it.
  • XDLALWAYSFORCELOAD: xdlopen() will always dlopen() the library.
If xdlopen() really uses dlopen() to load the library, xdlclose() will return the handle from linker (the return value of dlopen()), and then you can decide whether and when to close it with standard dlclose(). Otherwise, NULL will be returned.

filename can be basename or full pathname. However, Android linker has used the namespace mechanism since 7.0. If you pass basename, you need to make sure that no duplicate ELF is loaded into the current process. xdl_open() will only return the first matching ELF. Please consider this fragment of /proc/self/maps on Android 10:

756fc2c000-756fc7c000 r--p 00000000 fd:03 2985  /system/lib64/vndk-sp-29/libc++.so
756fc7c000-756fcee000 --xp 00050000 fd:03 2985  /system/lib64/vndk-sp-29/libc++.so
756fcee000-756fcef000 rw-p 000c2000 fd:03 2985  /system/lib64/vndk-sp-29/libc++.so
756fcef000-756fcf7000 r--p 000c3000 fd:03 2985  /system/lib64/vndk-sp-29/libc++.so
7571fdd000-757202d000 r--p 00000000 07:38 20    /apex/com.android.conscrypt/lib64/libc++.so
757202d000-757209f000 --xp 00050000 07:38 20    /apex/com.android.conscrypt/lib64/libc++.so
757209f000-75720a0000 rw-p 000c2000 07:38 20    /apex/com.android.conscrypt/lib64/libc++.so
75720a0000-75720a8000 r--p 000c3000 07:38 20    /apex/com.android.conscrypt/lib64/libc++.so
760b9df000-760ba2f000 r--p 00000000 fd:03 2441  /system/lib64/libc++.so
760ba2f000-760baa1000 --xp 00050000 fd:03 2441  /system/lib64/libc++.so
760baa1000-760baa2000 rw-p 000c2000 fd:03 2441  /system/lib64/libc++.so
760baa2000-760baaa000 r--p 000c3000 fd:03 2441  /system/lib64/libc++.so

xdlopen2() creates a handle from struct dlphdrinfo. xdlopen2() is always XDL_DEFAULT semantics, i.e. it will not try to load ELF with dlopen().

2. xdlsym() and xdldsym()

void xdlsym(void handle, const char symbol, sizet symbol_size);
void xdldsym(void handle, const char symbol, sizet symbol_size);

They are very similar to dlsym(). They all takes a "handle" of an ELF returned by xdlopen() and the null-terminated symbol name, returning the address where that symbol is loaded into memory.

If the symbol_size parameter is not NULL, it will be assigned as "the bytes occupied by the content corresponding to the symbol in the ELF". If you don't need this information, just pass NULL.

xdl_sym() lookup "dynamic link symbols" in .dynsym as dlsym() does.

xdldsym() lookup "debuging symbols" in .symtab and ".symtab in .gnudebugdata".

Notice:

  • The symbol sets in .dynsym and .symtab do not contain each other. Some symbols only exist in .dynsym, and some only exist in .symtab. You may need to use tools such as readelf to determine which ELF section the symbol you are looking for is in.
  • xdldsym() needs to load debuging symbols from disk file, and xdlsym() only lookup dynamic link symbols from memory. So xdldsym() runs slower than xdlsym().
  • The dynamic linker only uses symbols in .dynsym. The debugger actually uses the symbols in both .dynsym and .symtab.

3. xdl_addr()

typedef struct
{
    const char       *dli_fname;
    void             *dli_fbase;
    const char       *dli_sname;
    void             *dli_saddr;
    sizet            dlissize;
    const ElfW(Phdr) *dlpi_phdr;
    sizet            dlpiphnum;
} xdlinfot;

#define XDL_DEFAULT 0x00 #define XDLNONSYM 0x01

int xdladdr(void addr, xdlinfo_t info, void **cache); int xdladdr4(void addr, xdlinfo_t info, void **cache, int flags); void xdladdrclean(void **cache);

xdladdr() is similar to dladdr(). But there are a few differences:

  • xdl_addr() can lookup not only dynamic link symbols, but also debugging symbols.
  • xdladdr() uses the xdlinfot structure instead of the Dlinfo structure, which contains more extended information: dlissize is the number of bytes occupied by the current symbol; dlpiphdr points to the program headers array of the ELF where the current symbol is located; dlpiphnum is the number of elements in the dlpiphdr array.
  • xdladdr() needs to pass an additional parameter (cache), which will cache the ELF handle opened during the execution of xdladdr(). The purpose of caching is to make subsequent executions of xdladdr() of the same ELF faster. When you do not need to execute xdladdr(), please use xdladdrclean() to clear the cache. For example:
void *cache = NULL;
xdlinfot info;
xdladdr(addr1, &info, &cache);
xdladdr(addr2, &info, &cache);
xdladdr(addr3, &info, &cache);
xdladdrclean(&cache);
  • xdladdr4() is similar to xdladdr(), except that it adds the flags parameter. When the flags value is XDLDEFAULT, the behavior of xdladdr4() is the same as xdladdr(). When the flags value is XDLNONSYM, xdladdr4() will not obtain symbol-related information (the values of dlisname, dlisaddr, and dlissize in xdlinfo_t are all 0).

4. xdliteratephdr()

#define XDL_DEFAULT       0x00
#define XDLFULLPATHNAME 0x01

int xdliteratephdr(int (callback)(struct dlphdrinfo , size_t, void ), void data, int flags);

xdliteratephdr() is similar to dliteratephdr(). But xdliterate_phdr() is compatible with android 4.x on ARM32, and always including linker / linker64.

xdliteratephdr() has an additional "flags" parameter, one or more flags can be bitwise-or'd in it:

  • XDL_DEFAULT: Default behavior.
  • XDLFULLPATHNAME: Always return full pathname instead of basename.
These flags are needed because these capabilities require additional execution time, and you don't always need them.

5. xdl_info()

#define XDLDIDLINFO 1  // type of info: xdlinfot

int xdl_info(void handle, int request, void info);

xdlinfo() is similar to dlinfo(). xdlinfo() obtains information about the dynamically loaded object referred to by handle (obtained by an earlier call to xdlopen).

The only request parameter currently supported is XDLDIDLINFO, which means to return data of type xdlinfot through the info parameter (note that the values of dlisname, dlisaddr, dlissize in the returned xdlinfo_t at this time both are 0).

On success, xdl_info() returns 0. On failure, it returns -1.

Support

Contributing

License

xDL is MIT licensed, as found in the LICENSE file.

ยฉ 2026 GitRepoTrend ยท hexhacking/xDL ยท Updated daily from GitHub