In the world of mobile development under control Android there are many abstractions that are hidden from the eyes of the average user, but are the foundation for the stability and performance of the entire operating system. One of these key components is Android Shared Library, or, in simple terms, a common shared library. This is a mechanism that allows different applications and system services to use the same code without duplicating it in each individual package. Without this technology, the size of the system would increase significantly, and RAM consumption would become critical.
For developers, understanding how shared libraryworks is a prerequisite for creating optimized code. Unlike static libraries, which are copied inside each executable when compiled, shared libraries are loaded into memory only once. This allows you to save precious device resources, especially considering the limited amount of RAM in budget smartphones. The operating principle is based on a dynamic library loader, which maps code symbols to addresses in memory during app execution.
Let's take a closer look at why this concept is so important for the ecosystem. Google. When you run an application that uses standard graphics or networking features, the system does not reload those features. It accesses the one already loaded into memory shared library. This speeds up application launch and reduces CPU load. However, like any complex mechanism, the use of shared libraries has its own nuances, which you need to be aware of in order to avoid version conflicts and runtime errors.
Principles of dynamic linking in Android
Dynamic linking mechanism in Android is based on the ELF (Executable and Linkable Format) file format, which is a standard for most Unix-like systems. When an application starts, the system boot loader linker scans the application's dependencies. If a function from an external library is required, the loader looks for the corresponding .so file (Shared Object) in predefined system directories. This process occurs transparently to the user, but is critical for the stability of the OS.
The main advantage of this approach is the ability to update libraries independently of applications. For example, if a security vulnerability is discovered in a system library, engineers can release an update specifically for that library through the Project Treble mechanism or system patches. Applications using this libc security vulnerability discovered, engineers Google may release an update specifically for this library through the Project Treble mechanism or system patches. Applications that use this shared librarywill automatically start working with the corrected version after the device is restarted, without the need to reinstall each application individually.
It is important to note that the search for libraries occurs in a strict order. First, the path specified in the environment variable is checked, then the system directories. This allows developers to test their versions of libraries without affecting system files. However, in a production environment, relying on random loading order is absolutely not allowed, as this can lead to unpredictable application behavior.
When debugging native code, always check the LD_LIBRARY_PATH environment variable to make sure that the application is loading the correct version of the library, and not the system equivalent.
It is also worth mentioning o_lazy binding_ (lazy binding). By default, symbols in shared libraries are not resolved immediately upon loading, but only at the time the function is first called. This speeds up the application startup, but may cause a delay (lag) when a heavy function is accessed for the first time. Developers can control this behavior through compiler flags, sacrificing startup speed for uniform performance.
Differences between static and shared libraries
The choice between a static and a shared library is one of the first architectural decisions an engineer makes when creating a native module for Android. The static library (.a) is included directly in the application binary file. This means that if ten applications use the same static library, there will be ten copies of that code in RAM. For small utilities this is acceptable, but for heavy computing modules this approach is wasteful.
In contrast, Android Shared Library (.so) exists as a separate file. When the first application that needs it is loaded, the library is mapped to the virtual address space. Subsequent applications simply access the same physical memory pages. This phenomenon is called sharing pages and is the cornerstone of effective memory management in mobile OS.
โ ๏ธ Warning: Using outdated versions of shared libraries can lead to symbol collision when two applications try to use different implementations of the same function with the same name.
From an update perspective, static libraries require rebuilding the entire application when the library code changes. This increases the size of the APK file with each update. Dynamic libraries allow you to update logic on the fly if they are placed in separate system partitions or loaded through the Android App Bundlemechanism. However, this introduces complexity into dependency management: API backwards compatibility must be guaranteed.
Below is a comparison table illustrating the key differences between the two approaches in a platform context. Android:
| Characteristic | Static library (.a) | Shared library (.so) |
|---|---|---|
| APK size | Increases (code is copied) | Remains unchanged (link to file) |
| RAM consumption | High (duplication code) | Low (shared memory access) |
| Code update | Requires application recompilation | Possible without rebuilding the application |
| Launch speed | More dynamic | Just below (character resolution required) |
Structure and location of .so files in the system
Shared library files in Android have the extension .so and are located in specific directories of the file system. Understanding this structure is necessary to debug module loading problems and to manually install system components (which requires root access). The main storage of system libraries is the directory for 32-bit architectures and for 64-bit architectures. Applications can also have their own native libraries, which are packaged inside the APK. When installing the application, these files are extracted to the private application data directory: /system/lib for 32-bit architectures and /system/lib64 for 64-bit.
Applications can also have their own native libraries, which are packaged inside the APK. When installing an application, these files are extracted to the application's private data directory: /data/data/<package_name>/lib. This ensures isolation: one application's libraries cannot be accidentally used by another unless the system explicitly allows it. This approach increases security by preventing the injection of malicious code through substitution of system libraries.
Starting with version Android 10, the rules for accessing library paths have been tightened. Now applications cannot arbitrarily load shared libraries from anywhere in the file system. The loader linker verifies signatures and paths, allowing downloads only from trusted directories. This change was implemented to protect against exploits that use the LD_PRELOAD mechanism to intercept system calls.
How to find all .so files on a device?
To find all shared libraries on a device, you can use the ADB command: "adb shell find / -name "*.so" 2>/dev/null". However, please note that without root access, searching system partitions may be limited.
It is important to distinguish between system libraries provided by the device vendor (for example, GPU drivers from Qualcomm or MediaTek), and runtime libraries such as libart.so or libc.so. The former are often proprietary and closed, the latter are part of the open source code AOSP (Android Open Source Project). A bug in a proprietary library can lead to a hardware hang, while a bug in a standard library usually causes the application to crash (Force Close).
Dependency management and versioning
One โโof the most difficult tasks when working with Android Shared Library is versioning. In the desktop Linux world, there is a symbol versioning mechanism that allows you to have multiple versions of the same function in one library. B Android this approach is used to a limited extent, and developers have to monitor ABI (Application Binary Interface) compatibility. Changing the function signature or data structure size in the library's public API breaks all applications compiled against the old version.
To solve this problem, the practice of hiding internal symbols is used. Only those functions that are intended for external use are exported via the symbol table. The rest are marked as static or hidden using the linker script. This reduces the attack surface and reduces the likelihood of third-party developers accidentally using an unstable API. In the configuration files Android.bp or Android.mk you can explicitly specify which symbols should be visible.
โ ๏ธ Warning: Never rely on undocumented functions of system libraries. They may be removed or changed in the next Android update without warning, which will lead to your application not working.
The Project Treblemechanism implemented in Android 8separated the framework implementation and the vendor implementation through a stable interface (VNDK - Vendor Native Development Kit). The VNDK is a set of shared libraries that vendors can use without fear of compatibility issues when updating a major version of Android. This has significantly accelerated the release of security updates for millions of devices.
When developing your own libraries, it is recommended to follow semantic versioning. If you change the behavior of a function but keep the signature, it's a minor update. If you remove a function or change the argument structure, this is a major update that requires changing the library name or using namespaces to avoid conflicts with already installed applications.
Diagnosing problems with loading libraries
Despite the well-functioning mechanisms, loading errors shared libraries happen regularly. The most common mistake is java.lang.UnsatisfiedLinkError. It occurs when the JVM cannot find the specified file .so or cannot resolve the characters within it. The reasons can be very different: from the lack of a library for a specific processor architecture (for example, the application is compiled only for armeabi-v7a, and the device runs on arm64-v8a) to errors in the search paths.
To diagnose such problems, use the utility readelf or objdump, which allows you to view the table of dynamic dependencies of the file. The command readelf -d libname.so | grep NEEDED will show what other libraries are required for this one to work. If one of them is missing from the system, the download will fail. It is also useful to check file permissions: if the process does not have rights to read the library file, the bootloader will refuse to work with it.
โ๏ธ Diagnosis UnsatisfiedLinkError
Bootloader logging can be enabled through system properties. Executing the command setprop debug.linker.verbose 1 will cause linker to display detailed information about the search and loading process for each library in the log logcat. This is an invaluable tool for understanding why the system is ignoring a certain file or choosing the wrong version. The logs will show the full paths that the loader checks and the reasons for the failure (for example, โwrong ELF classโ when trying to load a 64-bit library into a 32-bit process).
Another common problem is a mismatch between STL (Standard Template Library) versions. If the application is compiled using libc++_shared.so, this library must be correctly packaged in the APK or present on the system. Mixing different STL implementations (for example, gnustl and libc++) in one process leads to memory instability and random crashes, since they have different memory allocators.
Security and isolation of native code
In context security Android Shared Library represents a potential attack vector. Because native code runs with the same privileges as the host application, a vulnerability in the library (such as a buffer overflow) could allow an attacker to execute arbitrary code. The SELinux (Security-Enhanced Linux) mechanism in Android strictly controls process access to library files, preventing code from being loaded from untrusted sources.
Starting from Android 11, additional isolation was introduced for native libraries. Applications can no longer use relative paths to load libraries, which prevents DLL hijacking attacks where a malicious library replaces a legitimate one in the working directory. The bootloader now requires absolute paths or loading through system APIs that verify file integrity.
โ ๏ธ Note: Security implementation details may vary between versions of Android. Always check the official security documentation for the specific API version you're working on to avoid relying on outdated assumptions.
Integrity Verification is also worth mentioning. The system can check the digital signature of shared libraries before loading them. This is especially true for banking applications and services with high data protection requirements. If the hash of the file does not match the expected one, the download is blocked and the application receives an error notification, preventing the execution of the compromised code.
The security of native code depends not only on the quality of writing C++ code, but also on the correct configuration of access rights and the use of current versions of the loader.
Is it possible to use one shared library in several applications simultaneously?
Yes, this is the main purpose of shared libraries. The library code is loaded into physical memory once and then mapped into the virtual address spaces of all processes that need it. This saves the device's RAM.
What is the difference between lib and lib64 folders?
The folder lib contains 32-bit versions of libraries, and lib64 - 64-bit ones. Modern devices and applications tend to use 64-bit versions for better performance and support for more than 4 GB of RAM, but 32-bit libraries are still needed for compatibility with older software.
What should you do if an application crashes with a dlopen failed error?
Error dlopen failed usually means that the bootloader is not working was able to find the library file or one of its dependencies. Check the logs logcat, make sure that all required .so files are present in the correct directories and have a compatible processor architecture.
How to update the system shared library on a rooted device?
On a rooted device you can replace the file in /system/lib, but it is dangerous. It is recommended to use Magisk modules or mount partitions via overlayfs in order not to violate the integrity of the system partition and to be able to easily roll back changes in case of failure.