2

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.

5
  • 1
    Calling printf() from within constructors/destructors like that can cause problems as printf() 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. Commented yesterday
  • 2
    Replace printf() with puts() inside your init() and fin(). The error should go away. Commented yesterday
  • When in the destructor is called stdin might 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) dlclose isn'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 call dlclose) Commented yesterday
  • @WhiteOwl the printed/not printed error might go away, but the printf segfault happens in the main() part of the program Commented yesterday
  • Is aligning %rsp in _start enough to make libc functions to work That's what glibc CRT does. Commented yesterday

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.