I have 2 files:
crt.c:
const char service_interp[] __attribute__((section(".interp"))) = "${LD_SO}";
extern void _exit (int __status) __attribute__ ((__noreturn__));
int main();
void _start() {
_exit(main());
}
Main.c:
#include <jni.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
jint Java_Main_Main(JNIEnv*,jclass);
int main() {
const char * str = "Hello from executable\n";
write(1,str,strlen(str));
return Java_Main_Main(NULL,NULL);
}
__attribute__((constructor)) void init() {
printf("Hello from constructor\n");
}
__attribute__((destructor)) void fin() {
printf("Hello from destructor");
}
jint Java_Main_Main(JNIEnv * env, jclass Main) {
printf("Hello from shared object\n");
return 0;
}
that I compile with
clang version 21.1.6
Target: x86_64-unknown-linux-android24
Thread model: posix
InstalledDir: /data/data/com.termux/files/usr/bin
sed "s/\${LD_SO}/$(./getldso.sh | sed "s/\//\\\\\//g")/g" crt.c | clang -x c - -c -fPIC -o crt.o
clang Main.c -g -fPIC -o libMain.so -DLD_SO=$(./getldso.sh) crt.o -shared
getldso.sh finds and prints the path to the system linker (/system/bin/linker64 here)
If I load the file using Java's System.loadLibrary or C's dlopen it correctly prints
Hello from constructor
Hello from shared object
Hello from destructor // only in C if I use `dlclose`
But if I try to execute it (./libMain.so) it failes:
Hello from constructor
Hello from executable
Segmentation fault
Upon trying to debug it, I found that it throws the segfault in the second printf call
┌──────────────────────────────────────────────────────────────┐
│ 0x7ffff7a9f6e0 push %rbp │
│ 0x7ffff7a9f6e1 mov %rsp,%rbp │
│ 0x7ffff7a9f6e4 push %r14 │
│ 0x7ffff7a9f6e6 push %rbx │
│ 0x7ffff7a9f6e7 sub $0xd0,%rsp │
│ 0x7ffff7a9f6ee mov %rdi,%rbx │
│ 0x7ffff7a9f6f1 mov %rsi,-0xd8(%rbp) │
│ 0x7ffff7a9f6f8 mov %rdx,-0xd0(%rbp) │
│ 0x7ffff7a9f6ff mov %rcx,-0xc8(%rbp) │
│ 0x7ffff7a9f706 mov %r8,-0xc0(%rbp) │
│ 0x7ffff7a9f70d mov %r9,-0xb8(%rbp) │
│ 0x7ffff7a9f714 test %al,%al │
│ 0x7ffff7a9f716 je 0x7ffff7a9f741 │
│ 0x7ffff7a9f718 movaps %xmm0,-0xb0(%rbp) │
│ 0x7ffff7a9f71f movaps %xmm1,-0xa0(%rbp) │
│ 0x7ffff7a9f726 movaps %xmm2,-0x90(%rbp) │
│ 0x7ffff7a9f72d movaps %xmm3,-0x80(%rbp) │
│ 0x7ffff7a9f731 movaps %xmm4,-0x70(%rbp) │
│ 0x7ffff7a9f735 movaps %xmm5,-0x60(%rbp) │
│ 0x7ffff7a9f739 movaps %xmm6,-0x50(%rbp) │
│ 0x7ffff7a9f73d movaps %xmm7,-0x40(%rbp) │
│ 0x7ffff7a9f741 mov %fs:0x28,%rax │
│ 0x7ffff7a9f74a mov %rax,-0x18(%rbp) │
│ 0x7ffff7a9f74e xorps %xmm0,%xmm0 │
│ >0x7ffff7a9f751 movaps %xmm0,-0x30(%rbp) │
│ 0x7ffff7a9f755 lea -0xe0(%rbp),%rax │
│ 0x7ffff7a9f75c mov %rax,-0x20(%rbp) │
│ 0x7ffff7a9f760 movabs $0x3000000008,%rax │
│ 0x7ffff7a9f76a mov %rax,-0x30(%rbp) │
│ 0x7ffff7a9f76e lea 0x10(%rbp),%rax │
│ 0x7ffff7a9f772 mov %rax,-0x28(%rbp) │
│ 0x7ffff7a9f776 mov 0x655b3(%rip),%rax # 0x7ffff7b04d30 │
│ 0x7ffff7a9f77d mov (%rax),%r14 │
│ 0x7ffff7a9f780 mov 0x58(%r14),%rdi │
│ 0x7ffff7a9f784 cmpb $0x0,0x60(%rdi) │
│ 0x7ffff7a9f788 jne 0x7ffff7a9f793 │
│ 0x7ffff7a9f78a add $0x38,%rdi │
│ 0x7ffff7a9f78e call 0x7ffff7afd9d0 │
└──────────────────────────────────────────────────────────────┘
Program received signal SIGSEGV, Segmentation fault.
0x00007ffff7a9f751 in ?? ()
and the %rbp register has the value 0x7fffffffdad8, so %rbp-0x30 is not 16-bit aligned, which causes the segfault to be thrown.
When I compile the same code (with the exception of the ld.so's path and that I have to pass -I /usr/lib/jvm/java-25-openjdk-amd64/include -iquote /usr/lib/jvm/java-25-openjdk-amd64/include/linux when compiling) on Linux, the code executes correctly.
clang version:
$ clang -v
Ubuntu clang version 18.1.3 (1ubuntu1)
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin
Found candidate GCC installation: /usr/bin/../lib/gcc/i686-linux-gnu/14
Found candidate GCC installation: /usr/bin/../lib/gcc/x86_64-linux-gnu/13
Found candidate GCC installation: /usr/bin/../lib/gcc/x86_64-linux-gnu/14
Selected GCC installation: /usr/bin/../lib/gcc/x86_64-linux-gnu/14
Candidate multilib: .;@m64
Selected multilib: .;@m64
Is aligning %rsp in _start enough to make libc functions to work (it fixed printf on Android), or is it just a "hacky" fix, that does't solve the main problem and I need to do something else/more (maybe call some kind of a __libc_init function)?
I would also like to know if that printf fail is considered a "bug" (in that case I would only need to align %rsp when compiling for android), or is what I'm doing implemtation defined (so I need to do it always, 'cause it might break in another version of libc on Linux).
Also the Hello from destructor line is not printed on neither Android nor Linux (from ./libMain.so), but is printed on Linux when running from java (and not on android).
Hello from constructor is called from everywhere except from Linux from ./libMain.so.
printf()from within constructors/destructors like that can cause problems asprintf()can rely on heap allocations. The process heap can be locked or otherwise unavailable when constructors or destructors are run, and that will be very platform-dependent.printf()withputs()inside yourinit()andfin(). The error should go away.stdinmight be sometimes closed, so it printing anything might never work, but I'm more concerned about the fact, that on android in java the (I think)dlcloseisn't called, and that when the parent program terminates the destructiors might not be called (as in the C program that loaded the library and didn't calldlclose)printfsegfault happens in themain()part of the program