Um wrapper minimalista que resolve um problema específico do LLD no Android: o linker do LLVM detecta em qual modo deve operar pelo argv[0], mas quando ele é empacotado num APK, o arquivo precisa ser renomeado para lib*.so — e isso quebra a detecção.
Se você está compilando C/C++ para Android dentro do próprio app (sem PC),
precisa rodar o clang e o lld a partir de nativeLibraryDir/. Mas o
Android só extrai arquivos com nome lib*.so — e o LLD depende do nome do
arquivo para saber em qual modo operar.
Este wrapper resolve esse conflito.
O LLD é o linker do projeto LLVM. Ele é um driver genérico que se comporta de forma diferente dependendo do nome pelo qual é invocado:
argv[0] |
Modo |
|---|---|
ld.lld |
Unix/Linux ✅ |
ld64.lld |
macOS |
lld-link |
Windows |
wasm-ld |
WebAssembly |
No Android, para um binário ser extraído do APK para nativeLibraryDir/, ele precisa se chamar lib*.so. Ou seja:
ld.lld→ precisa virarlibldreal.so- Mas aí o LLD reclama:
lld is a generic driver. Invoke ld.lld (Unix)...
Este wrapper:
- É executado como
libldwrapper.so(nome válido pro Android extrair) - Localiza o
libldreal.sona mesma pasta - Executa ele passando
argv[0] = "ld.lld"
Resultado: o LLD entra em modo Unix normalmente, sem reclamar.
┌────────────────────────────────────────┐
│ 1. ProcessBuilder invoca libldwrapper.so
│ (que está em nativeLibraryDir/)
└────────────────────────────────────────┘
↓
┌────────────────────────────────────────┐
│ 2. ldwrapper lê /proc/self/exe
│ → descobre que está em .../lib/arm64/
└────────────────────────────────────────┘
↓
┌────────────────────────────────────────┐
│ 3. Monta o caminho do alvo:
│ .../lib/arm64/libldreal.so
└────────────────────────────────────────┘
↓
┌────────────────────────────────────────┐
│ 4. execv(alvo, ["ld.lld", ...args])
│ ^^^^^^^^ argv[0] correto para o LLD
└────────────────────────────────────────┘
↓
┌────────────────────────────────────────┐
│ 5. LLD detecta "ld.lld" → modo Unix ✅
└────────────────────────────────────────┘
Requer o Android NDK:
aarch64-linux-android21-clang++ \
-fPIC -pie -O2 \
-Wl,-dynamic-linker,/system/bin/linker64 \
-o libldwrapper.so main.cppOu simplesmente:
./build.sh• ✅ Android 5.0+ (API 21)
• ✅ ARM64 (aarch64-linux-android)
• ✅ ARM32 (armv7a-linux-androideabi)
• ✅ x86, x86_64
java String nativeDir = ctx.getApplicationInfo().nativeLibraryDir;
List cmd = Arrays.asList( nativeDir + "/libclang.so", "-shared", "-fPIC", "-O2", "--target=aarch64-linux-android21", "-fuse-ld=" + nativeDir + "/libldwrapper.so", "-o", saida.getAbsolutePath(), fonte.getAbsolutePath() );
ProcessBuilder pb = new ProcessBuilder(cmd); pb.environment().put("LD_LIBRARY_PATH", nativeDir); Process p = pb.start(); int exit = p.waitFor();
Requisitos:
libldwrapper.soelibldreal.soemjniLibs/arm64-v8a/- Renomear
ld.llddo NDK paralibldreal.soantes de copiar - Definir
LD_LIBRARY_PATHparanativeLibraryDir
Copie os dois binários para jniLibs/arm64-v8a/ do seu projeto:
app/src/main/jniLibs/arm64-v8a/
├── libldwrapper.so ← este wrapper
└── libldreal.so ← o LLD real (renomeado de "ld.lld")
Importante: o arquivo
ld.lldoriginal do NDK deve ser renomeado paralibldreal.so— senão o Android não extrai paranativeLibraryDir/.
Para garantir que o Android extraia os .so na primeira execução:
static {
try {
System.loadLibrary("ldreal");
System.loadLibrary("ldwrapper");
} catch (Throwable t) {
Log.w("App", "Falha ao carregar libs nativas", t);
}
}import android.content.Context;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public void invocarLinker(Context ctx, File fonte, File saida) throws IOException {
// 1. Localiza os binários em nativeLibraryDir
String nativeDir = ctx.getApplicationInfo().nativeLibraryDir;
File clang = new File(nativeDir, "libclang.so");
File ldwrapper = new File(nativeDir, "libldwrapper.so");
// 2. Monta o comando
List<String> cmd = new ArrayList<>();
cmd.add(clang.getAbsolutePath());
cmd.add("-x"); cmd.add("c++");
cmd.add("-shared");
cmd.add("-fPIC");
cmd.add("-O2");
cmd.add("-std=c++17");
cmd.add("--target=aarch64-linux-android21");
// ★ Aponta pro wrapper (que passa argv[0]="ld.lld" pro LLD real)
cmd.add("-fuse-ld=" + ldwrapper.getAbsolutePath());
cmd.add("-o"); cmd.add(saida.getAbsolutePath());
cmd.add(fonte.getAbsolutePath());
// 3. Executa com LD_LIBRARY_PATH configurado
ProcessBuilder pb = new ProcessBuilder(cmd);
pb.redirectErrorStream(true);
pb.environment().put("LD_LIBRARY_PATH", nativeDir);
Process processo = pb.start();
// 4. Captura a saída
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(processo.getInputStream()))) {
String linha;
while ((linha = reader.readLine()) != null) {
Log.i("Linker", linha);
}
}
int exit = processo.waitFor();
Log.i("Linker", "Exit code: " + exit);
}import android.content.Context
import java.io.BufferedReader
import java.io.File
import java.io.InputStreamReader
fun invocarLinker(ctx: Context, fonte: File, saida: File) {
val nativeDir = ctx.applicationInfo.nativeLibraryDir
val clang = File(nativeDir, "libclang.so")
val ldwrapper = File(nativeDir, "libldwrapper.so")
val cmd = listOf(
clang.absolutePath,
"-x", "c++",
"-shared",
"-fPIC",
"-O2",
"-std=c++17",
"--target=aarch64-linux-android21",
"-fuse-ld=${ldwrapper.absolutePath}", // ★ wrapper
"-o", saida.absolutePath,
fonte.absolutePath
)
val pb = ProcessBuilder(cmd)
pb.redirectErrorStream(true)
pb.environment()["LD_LIBRARY_PATH"] = nativeDir
val processo = pb.start()
BufferedReader(InputStreamReader(processo.inputStream)).use { reader ->
reader.forEachLine { Log.i("Linker", it) }
}
val exit = processo.waitFor()
Log.i("Linker", "Exit code: $exit")
}- Sucesso:
Exit code: 0e o arquivo.sogerado no caminho especificado - Falha:
Exit code: 1com mensagens de erro do LLD no logcat
Se você ver o erro:
lld is a generic driver.
Invoke ld.lld (Unix), ld64.lld (macOS), lld-link (Windows) instead
Significa que o wrapper não está sendo usado — o LLD real foi chamado direto sem corrigir o argv[0]. Verifique se o caminho passado em -fuse-ld= aponta para o libldwrapper.so (e não para o libldreal.so).
Se você usa CMake em vez de ProcessBuilder:
set(CMAKE_LINKER "${CMAKE_ANDROID_NDK}/../nativeLibraryDir/libldwrapper.so")Ou se preferir linha de comando direta (via shell):
LD_LIBRARY_PATH=/path/to/nativeLibraryDir \
/path/to/nativeLibraryDir/libldwrapper.so \
--version⚠️ O wrapper assume quelibldreal.soestá na mesma pasta quelibldwrapper.so. Se você mover um sem o outro, ele falha.⚠️ DefinirLD_LIBRARY_PATHé obrigatório para o LLD achar as libs dele (libc++_shared.so, etc).⚠️ Não funciona como symlink — precisa ser uma cópia real do arquivo.