Skip to content

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

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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