Skip to content

[Bug]: Non-deterministic .gnu.hash generation when building from Windows symlinked directory in WSL #2221

@gabiteodoru

Description

@gabiteodoru

Description

Non-deterministic .gnu.hash generation when building native libraries from symlinked directory

Environment:

  • NDK: r29 (29.0.14206865)
  • Platform: Linux (WSL2) - 6.6.87.2-microsoft-standard-WSL2
  • Gradle: 8.13
  • AGP: (check build.gradle.kts for com.android.application version)
  • CMake: (using SDK default)

Issue:
Native library builds produce non-deterministic output when the project is located in a symlinked directory. Specifically, the .gnu.hash section differs between builds despite identical source code and build configuration.

Runtime Impact:
The non-deterministic builds from the symlinked directory also crash at runtime, while the deterministic non-symlink builds work correctly. The crash occurs during library loading with SIGSEGV (signal 11) in the Android dynamic linker:

#00 pc 00000000000624c8 /apex/com.android.runtime/bin/linker64 (soinfo_do_lookup_impl+228)
#1 pc 0000000000061a5c /apex/com.android.runtime/bin/linker64 (plain_relocate_impl+328)
#2 pc 000000000005ffdc /apex/com.android.runtime/bin/linker64 (soinfo::relocate+608)

The crash happens during symbol resolution when the dynamic linker tries to relocate symbols in libdelaycam_tflite.so. This suggests the corrupted .gnu.hash table is causing the linker to fail during runtime symbol lookup, not just a build reproducibility issue.

Setup:

  • Symlink: ~/d/delaycam → /mnt/d/python/delaycam
  • Control directory (no symlink): ~/delaycam-test (copy of same code)

Steps to Reproduce:

  1. Build native library from symlinked directory (~/d/delaycam)
  2. Note APK/library checksum
  3. Clean and rebuild from same directory
  4. Compare checksums

Expected Behavior:
Identical source code + identical build config = identical binary output (deterministic builds)

Actual Behavior:

  • Non-symlink builds: Deterministic (same checksum every time)
  • Symlink builds: Non-deterministic (different checksum each build)

Evidence (NDK r29 builds):
Working (non-symlink):

  • Build 1: e987d815ebe3b75d8f5601c0a9e33326
  • Build 2: e987d815ebe3b75d8f5601c0a9e33326 ✓ Identical

Symlinked:

  • Build 1: 1ca199410fa990e9513343d5f249f65d
  • Build 2: 49034b0ac716b72e56ae0999333d9a99 ✗ Different

Technical Details:
Analysis of differing bytes shows:

  • 12,312 bytes differ between symlink builds (out of 9.3MB)
  • 8,188 bytes in .gnu.hash section (hash table for dynamic linker)
  • 4,099 bytes in .dynstr (dynamic string table)
  • Build ID differs (expected, computed from binary)
  • Code sections (.text, .rodata, .data) are identical (same size, same content)

The .gnu.hash section contains completely different random-looking data between builds, suggesting non-deterministic hash function or iteration over unordered data structure during linking.

Tried to attach, wouldn't let me, can provide on request: three libdelaycam_tflite.so files (28MB total):

  • working-libdelaycam_tflite.so - deterministic non-symlink build
  • symlinked1-libdelaycam_tflite.so - first symlink build
  • symlinked2-libdelaycam_tflite.so - second symlink build (differs from first)

Upstream bug

No response

Commit to cherry-pick

No response

I am using a supported NDK

  • I have checked and the NDK I'm using is currently supported

Affected versions

r29, r28

Host OS

Linux

Host OS version

Ubuntu 24.04.2 LTS (WSL2) - Kernel 6.6.87.2-microsoft-standard-WSL2

Affected ABIs

arm64-v8a

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions