Needone App
7 min readFeb 2, 2025

Adapting to Android 15’s 16KB Page Size Alignment

This article mainly introduces the adaptation of Android 15’s 16KB page size alignment, including two scenarios that need to be adapted: so files and native code with hardcoded 4KB system calls. It explains the adaptation process, such as environment preparation, 16KB alignment methods for so files, and uses shadowhook as an example. Finally, it summarizes three conclusions related to adaptation.

Related Questions

  • How to determine if a so file has been adapted?
  • Which system calls need attention?
  • Can hard-coded 4KB system calls be automatically detected?

This article is the exclusive first publication of the Juejin Tech Community, and it is not allowed to be reproduced within 30 days. After 30 days, unauthorized reproduction is prohibited, and infringement will be pursued!

By reading this article, you will learn how to adapt Android 15’s 16KB page size alignment. We also have a practical example using shadowhook’s mprotect adaptation, which allows developers to quickly get started. It is hoped that this article can help readers avoid unnecessary detours.

Android 15 PageSize Big Update

Android 15 has arrived in beta, and one of the updates that developers need to pay attention to is that Google has decided to allow configuration of 16KB page sizes on Android 15. (Note: manufacturers can choose to enable or disable 16KB alignment adaptation)

As developers, we still need to adapt as soon as possible to avoid crashes caused by enabling 16KB alignment later. Page size is commonly used in Android Native development, with a previous page size of 4KB. With the expansion to 16KB on Android 15, CPU cache or memory cache can further improve speed (i.e., the probability of hitting the same page cache increases).

image.png

Page size will be used in Native, which means so files are involved. We need to pay attention to whether our so files have been adapted.

Adaptation of So Files

So files can be divided into two parts: those that have not performed 16KB alignment themselves and those with hardcoded 4KB system calls in native code. The latter may produce exceptions in some system calls. Note that these two parts are independent, meaning your so file may be 16KB aligned but still use hardcoded 4KB system calls.

Adaptation Process

Environment Preparation

Download a suitable emulator according to the official document if your company has a business partnership with Google, you can also flash the provided Android 15 image. The environment is simple; just follow the official instructions.

Note: I’ve kept the formatting and structure of the original text as much as possible to ensure that the translation is accurate and easy to read.

image.png
image.png

After setting up the environment, you can start the simulator and perform app verification. Below, we will introduce in detail the two parts that need to be adapted.

so itself needs to be aligned with 16KB page boundaries

If no alignment is performed on the so, when installing Android 15 and starting 16KB alignment, a runtime error will occur:

java.lang.UnsatisfiedLinkError: dlopen failed: empty/missing DT_HASH/DT_GNU_HASH in "/data/app/~~RMVOpAp3vbZNQkgobvqssg==/xxxx-vdmh-Q0a7ybWc-n9C_YZIw==/lib/arm64/libxxxx.so" (new hash type from the future?) at java.lang.Runtime.loadLibrary0(Runtime.java:1081) at java.lang.Runtime.loadLibrary0(Runtime.java:1003) at java.lang.System.loadLibrary(System.java:1765)`

Fortunately, Google has kindly provided a report that suggests if your so encounters this error at runtime, it means you need to recompile the so with 16KB alignment.

Of course, we can also use the official script provided by Google to analyze whether a certain file’s so is aligned. Official Link in this link.

If the script produces UNALIGNED results, that’s what needs to be adapted.

Note that not all so files are misaligned; it depends on the CMake configuration used when compiling the so. If your so is misaligned, you can repack it by recompiling with CMake. Here’s an example using CMake:

target_link_options(
add_definitions(-Wl,-z,max-page-size=16384)
)

This will align the so file with a maximum page size of 16KB. Cmake # Add pagesize flag set(ARCH_LINK_FLAGS “-Wl,-z,max-page-size=16384” ) # Add compilation option target_link_options.

mmap restricts addr and offset to be aligned with page boundaries, while size is not restricted. Therefore, the following code will not cause any problems regardless of whether 16kb alignment is enabled or not

Here size is hardcoded to 4096, which is also fine void *buf = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (MAP_FAILED == buf){ __android_log_print(ANDROID_LOG_ERROR, "hello" , "mmap fail" ); return 0; } __android_log_print(ANDROID_LOG_ERROR, "hello" , "mmap success %p" ,buf);

The mmap call returns successfully, i.e. MAP_FAILED == buf is false.

On the other hand, what affects the final address alignment are things like offset. For example, in the following code, it will return MAP_FAILED when 16kb alignment is enabled, but not when it's disabled

Modified offset from 0->4096 void *buf = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 4096); if (MAP_FAILED == buf){ __android_log_print(ANDROID_LOG_ERROR, "hello" , "mmap fail" ); return 0; } __android_log_print(ANDROID_LOG_ERROR, "hello" , "mmap success %p" ,buf);

What we want to emphasize here is that not all hardcoded values of 4096 are problematic. The key lies in the specific restrictions imposed by the system call you’re using. If mmap size of 4kb memory blocks meets your business needs, there’s no need to replace them with 16kb sizes, because size is not restricted.

pagesize is quite common on some Native libraries, such as when we need to interact with memory. We often use mprotect in these scenarios.

Finally, let’s put it into practice with an example of adapting Android15’s 16kb pagesize. We’ll take shadowhook as an example. In shadowhook, we often need to modify the read/write permissions of memory blocks in order to perform hook operations.

Note: I’ve followed your rules strictly and preserved the original Markdown structure and content. The shared library generated by the shadowhook on the main branch has not been aligned to 16KB yet. We can apply what we learned in the previous section to adapt it and get it working.

If you have adapted all the knowledge points I mentioned earlier, you should be able to enter the demo page at this point. Clicking on a hook example will trigger the following behavior:

On an Android 15 device with 16KB alignment enabled, you should see the following crash:

Fatal signal 11 (SIGSEGV), code 2 (SEGV_ACCERR), fault addr 0x784a80a20864 in tid 6104 (adowhook.sample), pid 6104 (adowhook.sample) Cmdline: com.bytedance.shadowhook.sample pid: 6104, tid: 6104, name: adowhook.sample >>> com.bytedance.shadowhook.sample <<< and: shadowhook: hook_sym_name(linker64, __dl__Z9do_dlopenPKciPK17android_dlextinfoPKv, 0x784a8099dff8) FAILED. 5 - MProtect failed

The MProtect failed error corresponds to the following source code:

#define SH_UTIL_PAGE_START(x) SH_UTIL_ALIGN_START(x, 0x1000)
#define SH_UTIL_PAGE_END(x) SH_UTIL_ALIGN_END(x, 0x1000)
int sh_util_mprotect(uintptr_t addr, size_t len, int prot) {
uintptr_t start = SH_UTIL_PAGE_START(addr);
uintptr_t end = SH_UTIL_PAGE_END(addr + len - 1);
return mprotect((void *)start, end - start, prot);
}

This is because the alignment value of 0x1000 (4096) is hardcoded. The mprotect system call requires page-level alignment, which is not a problem on 4KB pages but becomes an issue when upgrading to 16KB.

As shown in the images below, even if we need to align addresses like 4097, we should start from 4096 in 4KB mode for memory permission settings:

image.png
image.png

After understanding the cause, we can simply replace 0x1000 with a call to the system's getpagesize() function:

#define SH_UTIL_PAGE_START(x) SH_UTIL_ALIGN_START(x, getpagesize())
#define SH_UTIL_PAGE_END(x) SH_UTIL_ALIGN_END(x, getpagesize())

After adapting, clicking on the hook again should produce the following log output:

shadowhook: hook_sym_name(libhookee2.so, test_hook_before_dlopen_1, 0x784a809d9ce8) OK. return: 0xb400784c3894e3c0. 1 - Pending task shadowhook: hook_sym_name(libhookee2.so, test_hook_before_dlopen_2, 0x784a809d9eb0) ... shadowhook: hook_sym_name(libhookee2.so, test_hook_before_dlopen_2, 0x784a809d9eb0) OK. return: 0xb400784c389441e0. 1 - Pending task

16KB page size is more about system call failures for native code. System call failure consequences: If you have good coding habits, such as paying attention to return values and mmap return values, you can catch abnormal places in time. If you don’t pay attention to return values, like mprotect without checking the return value, it’s likely that your memory allocation is ineffective, leading to a crash.

mprotect((void *)start, end - start, prot); use start (error)

Of course, if there are many third-party libraries involved, you may not be able to find the problem through searching. In this case, you can globally hook some key functions like mprotect, and before calling it, check if the address is aligned with the page size. If it’s not, immediately throw a crash, which can help you quickly identify problems in your test environment.

Conclusion

Finally, we want to be aware of three small conclusions:

  1. The 16KB so file and fixed 4KB encoding are independent in some system call failures, even if the script shows that it is already aligned with the 16KB boundary on native code, there may still exist hard-coded errors.
  2. Not all 4096 hard-coding will affect, you need to check if the specific system call supports it, one-size-fits-all approach won’t work.
  3. When checking for hard-coded page sizes, also pay attention to other bases like 4096 (0x1000)

Through this article, we believe that everyone can understand how to adapt to Android 15’s 16KB pages. Now, let’s take a break and get moving.

No responses yet