English | 中文
A comprehensive Android native memory analysis project for learning low-level Android development, demonstrating ByteDance's ByteHook PLT hook library for memory allocation tracking, combined with Linux kernel page idle monitoring for complete memory access pattern analysis.
- Android Native Memory Tracking & Analysis - Low-Level Learning Project
This project demonstrates advanced Android native development techniques for memory profiling and analysis:
- Memory Allocation Hooking: Intercept
malloc,free,mmap, and other memory operations using PLT hook - Lock-Free Logging: High-performance multi-level buffering system with minimal overhead
- Page Access Tracking: Monitor physical memory access patterns via
/sys/kernel/mm/page_idle/bitmap - Game Memory Simulation: Realistic memory allocation patterns mimicking game engine behavior
| Goal | Solution | Output |
|---|---|---|
| Trace memory allocation/free stack | App Hook memory functions + stack trace | mem_reg.log |
| Memory usage status | Device Root + Kernel with IdlePage enabled | mem_visit.log |
| ELF file parsing | Modular ELF Reader compatible with readelf | CLI tool elf_reader |
┌─────────────────────────────────────────────────────────────────────────┐
│ Android Application │
├─────────────────────────────────────────────────────────────────────────┤
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐ │
│ │ Game Sim │ │ Hook Lib │ │ Idle Page Monitor │ │
│ │ (libdemo_so) │◄────►│ (libso2) │◄────►│ (Kernel Interface) │ │
│ └──────────────┘ └──────┬───────┘ └──────────────────────┘ │
│ │ │
│ ┌────────┴────────┐ │
│ ▼ ▼ │
│ ┌─────────────────┐ ┌───────────────┐ │
│ │ ByteHook (bhook)│ │ Lock-Free Log │ │
│ │ PLT Hooking │ │ Buffer │ │
│ └─────────────────┘ └───────┬───────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ mem_reg.log │ │
│ │ mem_visit.log │ │
│ └──────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
Hooks 9 memory allocation functions with full call stack capture:
| Function | Description |
|---|---|
malloc / free |
Standard heap allocation |
calloc |
Zero-initialized allocation |
realloc |
Resize existing allocation |
mmap / munmap / mmap64 |
Memory-mapped files |
posix_memalign |
Aligned allocation (POSIX) |
aligned_alloc |
Aligned allocation (C11) |
Log Format (CSV):
timestamp_us,type,address,req_size,actual_size,tid,0,0,stack_addrs...
Simulates four memory "hotness" levels across separate threads:
| Level | Name | Pattern | Size | Lifecycle |
|---|---|---|---|---|
| L1 | HOT | Render buffers | 64KB (16 pages) | Never freed, 60FPS updates |
| L2 | WARM | Object pool | 4KB-8KB | 1-10ms lifecycle |
| L3 | COOL | Config cache | 16KB-64KB | Cleared every 5s |
| L4 | COLD | Resource packs | 4MB mmap | 30s lifecycle |
Three-tier buffering architecture for zero-contention logging:
Thread-Local Buffer (512B)
│
▼
Lock-Free Ring Buffer (16MB) ◄── Atomic operations only
│
▼
Background Writer Thread
│
▼
Disk (fdatasync)
Key Optimizations:
thread_localbuffers eliminate thread contention- Lock-free ring buffer using C++11 atomics
- Batch processing: 16 records per TLS flush, 64 per disk write
fdatasync()every 16KB for durability
Tracks physical memory page access patterns using Linux kernel interfaces.
Requirements:
- Root access for
/sys/kernel/mm/page_idle/bitmap - Kernel with
CONFIG_IDLE_PAGE_TRACKINGenabled - PFN Helper utility is required to properly observe page hot/cold status
Dual Monitoring Modes:
| Mode | Target | Source |
|---|---|---|
| SO_CODE_SECTIONS | SO code/data segments | /proc/self/maps |
| HEAP_ALLOCATIONS | Dynamic allocations | malloc/mmap hooks |
Sampling Workflow:
SAMPLE_START: Clear PTE Accessed bit viapage_idle_set()- Wait for sampling interval (adaptive: 10ms-1s)
SAMPLE_END: Check access status viapage_idle_get()- Log results with timestamp, vaddr, PFN, and access status
Educational modular ELF parsing library compatible with GNU readelf:
| Module | Functionality |
|---|---|
| Core | ELF header parsing (32/64-bit) |
| Sections | Section header table parsing |
| Symbols | .dynsym and .symtab parsing |
| Relocations | .rela.plt and .rela.dyn handling |
| PLT | PLT entry disassembly and GOT verification |
| Dynamic | .dynamic segment parsing |
| Segments | Program header (load segments) |
| DWARF | Debug line information |
A Streamlit-based web tool for joint analysis of memory allocation and access logs:
# Install dependencies
pip install streamlit pandas numpy plotly pyelftools
# Launch visualization interface
streamlit run lazy_visit.pyFeatures:
| Feature | Description |
|---|---|
| Memory Heatmap | Visualize page-level access patterns across sampling cycles |
| Hot/Cold Classification | Automatic categorization: Never Accessed, Cold, Warm, Hot, Burning Hot |
| Call Stack Resolution | ELF symbol resolution to identify allocation sources |
| Statistics Dashboard | Runtime duration, scan rounds, block count, total memory usage |
Usage: Upload mem_reg.log, mem_visit.log, and libdemo_so.so to analyze memory behavior interactively.
- Android Studio Hedgehog (2023.1.1) or later
- Android NDK r29 or later
- CMake 3.22.1+
- Android device/emulator with API 24+ (Android 7.0)
- Root access (required for page access tracking)
- Kernel with
CONFIG_IDLE_PAGE_TRACKINGenabled
# Build debug APK
./gradlew :app:assembleDebug
# Build release APK
./gradlew :app:assembleRelease
# Build native libraries only
./gradlew :app:externalNativeBuildDebugNote: The following steps only need to be executed once after the device boots. They must be re-executed after a device reboot:
# 1. Connect to Android device (if using network debugging)
adb connect <device_ip>:<port>
# 2. Get root access
adb root
# 3. Push PFN Helper to device
adb push app/build/intermediates/cxx/Debug/*/obj/*/pfn_helper /data/local/tmp/
# 4. Grant execute permission
adb shell chmod +x /data/local/tmp/pfn_helper
# 5. Change page_idle bitmap permissions (allow non-privileged access)
adb shell chmod 666 /sys/kernel/mm/page_idle/bitmap
# 6. Disable SELinux (required for debugging)
adb shell setenforce 0# Install to connected device
./gradlew :app:installDebugBefore each time you click the "Start Simulation" button, execute the following complete process:
-
Launch the app
adb shell am start -n com.example.demo_so/.MainActivity
-
Start PFN Helper (Must be executed before clicking "Start Simulation")
# Get app process ID and start PFN Helper adb shell /data/local/tmp/pfn_helper $(adb shell pidof com.example.demo_so)
Important: If this step is not executed,
mem_visit.logwill not show correct page hot/cold information. -
Tap "Start Simulation" button to begin memory simulation
-
Run for your desired duration
-
Tap "Stop Simulation" button to stop simulation
-
Pull logs for analysis
# Pull memory allocation logs
adb pull /storage/emulated/0/Android/data/com.example.demo_so/files/mem_reg.log
# Pull page access logs
adb pull /storage/emulated/0/Android/data/com.example.demo_so/files/mem_visit.logUse lazy_visit.py tool for joint visualization analysis:
pip install streamlit pandas numpy plotly pyelftools
streamlit run lazy_visit.pyThis project includes a complete ELF parser (located in app/src/main/cpp/elf_reader/), compatible with GNU readelf's main features, designed for learning ELF file structure and dynamic linking mechanisms.
# Build ELF reader binary
./gradlew :app:externalNativeBuildDebug
# Push to device
adb push app/build/intermediates/cxx/Debug/*/obj/*/elf_reader /data/local/tmp/
adb shell chmod +x /data/local/tmp/elf_reader| Option | Function | readelf Equivalent |
|---|---|---|
-h |
ELF header information | -h |
-S |
Section Header table | -S |
-s |
Dynamic symbol table (.dynsym) | --dyn-syms |
-r |
Relocation tables (.rela.plt + .rela.dyn) | -r |
-d |
Dynamic linking info (.dynamic) | -d |
-l |
PT_LOAD segment info | -l |
-D |
PLT disassembly | N/A |
-f |
.eh_frame exception handling frames | N/A |
-g |
DWARF debug line info (.debug_line) | N/A |
-R |
Read-only data section (.rodata) | N/A |
-a |
Show all information | -a |
1. View ELF Header
adb shell /data/local/tmp/elf_reader -h /system/lib64/libc.soOutput example:
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: DYN (Shared object file)
Machine: AArch64
Entry point address: 0x0
Start of program headers: 64 (bytes into file)
Start of section headers: 1408168 (bytes into file)
2. View Section Header Table
adb shell /data/local/tmp/elf_reader -S /system/lib64/libc.soOutput example:
Section Headers:
[Nr] Name Type Address Off Size ES Flg Lk Inf Al
[ 0] NULL 0000000000000000 000000 000000 00 0 0 0
[ 1] .note.android.ide NOTE 0000000000000238 000238 000098 00 A 0 0 4
[ 2] .note.gnu.build-i NOTE 00000000000002d0 0002d0 000024 00 A 0 0 4
[ 3] .dynsym DYNSYM 00000000000002f8 0002f8 008b38 18 A 9 1 8
[ 4] .gnu.version VERSYM 0000000000088e30 088e30 000b9e 02 A 3 0 2
[ 5] .gnu.version_r VERNEED 00000000000899d0 0899d0 000100 00 A 9 3 8
[ 6] .gnu.hash GNU_HASH 0000000000089ad0 089ad0 004ccc 00 A 3 0 8
[ 7] .hash HASH 00000000000de79c 0de79c 003710 04 A 3 0 4
[ 8] .rela.dyn RELA 00000000000e1eb0 0e1eb0 012258 18 A 3 0 8
[ 9] .dynstr STRTAB 00000000000f4108 0f4108 0074df 00 A 0 0 1
3. View Dynamic Symbol Table
adb shell /data/local/tmp/elf_reader -s /system/lib64/libc.soOutput example (partial):
Dynamic symbol table (.dynsym):
Num Value Size Type Bind Vis Ndx Name
0 0000000000000000 00000000 NOTYPE LOCAL DEFAULT UND
1 0000000000000000 00000000 SECTION LOCAL DEFAULT 1
...
1129 00000000000550b0 00000504 FUNC GLOBAL DEFAULT 13 malloc
1130 00000000000555b4 000002a0 FUNC GLOBAL DEFAULT 13 free
1131 0000000000055854 000007d0 FUNC GLOBAL DEFAULT 13 realloc
4. View Relocation Tables
adb shell /data/local/tmp/elf_reader -r /system/lib64/libc.soOutput example (partial):
PLT relocations (function jumps):
Entry Offset GOT Index Symbol
[ 0] 00000000000f6e78 [ 3] __libc_init
[ 1] 00000000000f6e80 [ 4] __stack_chk_fail
...
[ 21] 00000000000f6f10 [ 24] malloc
[ 22] 00000000000f6f18 [ 25] free
Note: .rela.plt[n] corresponds to GOT[n+3]
rela[21] → GOT[24] (malloc)
5. View Dynamic Linking Information
adb shell /data/local/tmp/elf_reader -d /system/lib64/libc.soOutput example:
Dynamic Section (.dynamic):
Dependency libraries (DT_NEEDED):
[None]
SONAME: libc.so
Symbol table address (DT_SYMTAB): 0x2f8
String table address (DT_STRTAB): 0xf4108
Hash table address (DT_HASH): 0xde79c
PLT relocation address (DT_JMPREL): 0xf6f20
GNU hash table address (DT_GNU_HASH): 0x89ad0
6. View PT_LOAD Segment Information
adb shell /data/local/tmp/elf_reader -l /system/lib64/libc.soOutput example:
PT_LOAD segments (runtime memory mapping):
No. | Virtual Address | File Off | File Sz | Mem Sz | Perm | Usage
----|--------------------|----------|----------|----------|------|----------
[ 0] | 0x0000000000000000 | 0x000000 | 0x0b3ca0 | 0x0b3ca0 | R E | Code (.text)
[ 1] | 0x00000000000b4000 | 0x0b4000 | 0x002298 | 0x0022a0 | RW- | Data (.data/.bss)
[ 2] | 0x00000000000d7000 | 0x0d62a0 | 0x00f5b8 | 0x00f5b8 | R-- | Read-only (.rodata)
7. PLT Disassembly
adb shell /data/local/tmp/elf_reader -D /system/lib64/libc.soOutput example (partial):
PLT Entries (disassembled):
[ 0] 0x00000000000ef890: adrp x16, #0xf6000
ldr x17, [x16, #0x8e0]
add x16, x16, #0x8e0
br x17
→ GOT[3] @ 0xf6e78 (rela[0] __libc_init)
[ 21] 0x00000000000ef8e4: adrp x16, #0xf6000
ldr x17, [x16, #0x910]
add x16, x16, #0x910
br x17
→ GOT[24] @ 0xf6f10 (rela[21] malloc)
8. View Exception Handling Frames (.eh_frame)
adb shell /data/local/tmp/elf_reader -f /system/lib64/libc.soOutput example:
.eh_frame exception handling frames:
CIE count: 1
FDE count: 1523
CIE [0] @ 0x0
Version: 1
Augmentation: zPR
Code alignment: 4
Data alignment: -8
Return register: 30 (x30/LR)
Has personality function
Has LSDA pointer
FDE [0] @ 0x14
Function address: 0x54000
Function size: 0x100
Associated CIE: 0
9. Show All Information
adb shell /data/local/tmp/elf_reader -a /system/lib64/libc.soEdit so2-hook.cpp to select monitoring mode at compile time:
// so2-hook.cpp (around line 142)
// Mode selection:
// 0 = SO_CODE_SECTIONS (monitor SO code sections, log shows permissions+filename)
// 1 = HEAP_ALLOCATIONS (monitor heap memory, log shows "(heap)")
auto mode = idle_page::IdlePageMonitor::MonitorMode::HEAP_ALLOCATIONS; // Change to SO_CODE_SECTIONS to switch modeAdjust sampling interval in idle_page_timer.h:
constexpr int FAST_MS = 100; // >10% pages accessed
constexpr int MEDIUM_MS = 500; // 1-10% pages accessed
constexpr int SLOW_MS = 2000; // <1% pages accessedConfigure buffer sizes in log_buffer.h:
constexpr size_t RING_BUFFER_SIZE = 16 * 1024 * 1024; // 16MB
constexpr size_t BATCH_SIZE = 512; // TLS buffer
constexpr uint32_t SAMPLE_RATE = 1; // 1/1 = fullByteHook is integrated via Maven with Prefab support:
dependencies {
implementation("com.bytedance:bytehook:1.1.1")
}
android {
buildFeatures {
prefab = true
}
packaging {
jniLibs.pickFirsts.add("**/libbytehook.so")
}
}Hook Implementation Pattern:
void* my_malloc(size_t size) {
BYTEHOOK_STACK_SCOPE(); // Required first line
void* result = BYTEHOOK_CALL_PREV(my_malloc, size);
// ... logging logic ...
return result;
}Uses C++11 atomics with acquire-release semantics:
std::atomic<size_t> write_pos_{0};
std::atomic<size_t> read_pos_{0};
// Producer (acquire-release for visibility)
size_t pos = write_pos_.fetch_add(len, std::memory_order_acq_rel);
// Consumer (acquire for synchronization)
size_t read_pos = read_pos_.load(std::memory_order_acquire);| Interface | Purpose | Access |
|---|---|---|
/proc/self/pagemap |
Virtual to PFN translation | Require root |
/sys/kernel/mm/page_idle/bitmap |
Page access tracking | Requires root + CAP_SYS_ADMIN |
/proc/self/maps |
Memory region enumeration | Unrestricted |
x86/x86_64 compilation fails:
// In app/build.gradle.kts
apply(from = rootProject.file("gradle/prefab_bypass.gradle"))Duplicate libbytehook.so:
packaging {
jniLibs.pickFirsts.add("**/libbytehook.so")
}No page access data (all -1 in mem_visit.log):
- Ensure PFN Helper has been started
- Root access required for
/sys/kernel/mm/page_idle/bitmap - Check
CONFIG_IDLE_PAGE_TRACKINGin kernel config - Verify SELinux is disabled (
setenforce 0)
High memory overhead:
- Reduce
RING_BUFFER_SIZEinlog_buffer.h - Increase
SAMPLE_RATEfor lower sampling
Missing stack traces:
- Ensure
-funwind-tablescompiler flag - Check
BYTEHOOK_STACK_SCOPE()is first in hook function
"Start Idle Monitor" button not working:
- Confirm PFN Helper is properly started
- Check logcat for permission errors
| Dependency | Version |
|---|---|
| ByteHook | 1.1.1 |
| Android NDK | r29 |
| CMake | 3.22.1 |
- ByteDance ByteHook - High-performance PLT hook library
- Linux Kernel - Page idle tracking infrastructure
- Android NDK Team - Native development tools
Disclaimer: This is a low-level learning project for educational purposes. Production use requires additional hardening and testing.