Why libonload.so without interp section is executable?

48 Views Asked by At

I know libc.so is executeable because:

  1. libc.so has a entry point function.
  2. There is .interp section in libc.so.

And I refered many documents, .interp section is considered to be necessary for an executable so. When I use call libonload.so, I can execute libonload.so, but readelf shows no .interp section. I don't know how it works. I run libonload.so, the result is:

[root@localhost onload-7.1.1.75]# /usr/lib64/libonload.so 
Onload 7.1.1.75
Copyright 2019-2021 Xilinx, 2006-2019 Solarflare Communications, 2002-2005 Level 5 Networks
Built: Mar  4 2024 15:49:30 (release)
Build profile header: <ci/internal/transport_config_opt_extra.h>

readelf -l /usr/lib64/libonload.so:

[root@localhost onload-7.1.1.75]# readelf -l /usr/lib64/libonload.so 

Elf file type is DYN (Shared object file)
Entry point 0xa2e0
There are 8 program headers, starting at offset 64

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  LOAD           0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x00000000000a5ff0 0x00000000000a5ff0  R E    200000
  LOAD           0x00000000000a6818 0x00000000002a6818 0x00000000002a6818
                 0x0000000000002770 0x0000000000003060  RW     200000
  DYNAMIC        0x00000000000a6d90 0x00000000002a6d90 0x00000000002a6d90
                 0x00000000000001f0 0x00000000000001f0  RW     8
  NOTE           0x0000000000000200 0x0000000000000200 0x0000000000000200
                 0x0000000000000024 0x0000000000000024  R      4
  TLS            0x00000000000a6818 0x00000000002a6818 0x00000000002a6818
                 0x0000000000000000 0x0000000000000bec  R      8
  GNU_EH_FRAME   0x0000000000094684 0x0000000000094684 0x0000000000094684
                 0x000000000000245c 0x000000000000245c  R      4
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000000 0x0000000000000000  RW     10
  GNU_RELRO      0x00000000000a6818 0x00000000002a6818 0x00000000002a6818
                 0x00000000000007e8 0x00000000000007e8  R      1

 Section to Segment mapping:
  Segment Sections...
   00     .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .plt .text .rodata .eh_frame_hdr .eh_frame 
   01     .init_array .data.rel.ro .dynamic .got .got.plt .data .bss 
   02     .dynamic 
   03     .note.gnu.build-id 
   04     .tbss 
   05     .eh_frame_hdr 
   06     
   07     .init_array .data.rel.ro .dynamic .got 

Btw, libonload.so is a driver for SolarFlare Nic.Source code can be got at [https://github.com/Xilinx-CNS/onload][1] [1]: https://github.com/Xilinx-CNS/onload

I write a demo according to onload source:

#include <sys/uio.h>                                                                                                        
#include <linux/unistd.h>
#include <unistd.h>
int my_do_syscall3(int num, long a1, long a2, long a3)
{
  int rc;
  __asm__ __volatile__(

   "syscall"
   : "=a" (rc)
   : "0"((long)num), "D"(a1), "S"(a2), "d"(a3)
   : "r11","rcx","memory"

  );

  return rc;
}

#define my_syscall3(call, a1, a2, a3) \
        my_do_syscall3(__NR_##call, (a1), (a2), (a3))
extern "C" int func()
{
        struct iovec v[1];
        static const char msg0[] = "Hello so\n";

        v[0].iov_base = (void*) msg0;
        v[0].iov_len  = sizeof(msg0)-1;

        my_syscall3(writev, STDOUT_FILENO, (long) v, 1);
        my_syscall3(exit, 0, 0, 0);

        return 0;
}

My build command is :

gcc -fPIC -shared -e func test.cpp -o libtest.so     

I execute ./libtest.so, resulting in segmention faults. Then I add .interp section in test.cpp:

extern const char elf_interpreter[] __attribute__((section(".interp"))) = "/lib64/ld-linux-x86-64.so.2";

And compile and execute again.It runs well as expected.

At last, I do it successfully.Adding static to my_do_syscall3 and it can work without an interpreter.

1

There are 1 best solutions below

1
KamilCuk On BEST ANSWER

Interp is (only) needed if you want an interpreter to run, like glibc or musl. You can hardcode syscalls and do all the stuff yourself without an ld.so interpreter, see man ld.so. It has nothing to do with shared library vs executable. Actually, any ELF executable is a shared library, any shared library can be an executable, it's all ELF format.

Why libonload.so without interp section is executable?

Because it was built that way. See https://github.com/majek/openonload/blob/aff60a36ed3543446d9ff179905f47c47d1e300b/src/lib/transport/unix/mmake.mk#L85 and man ld. See https://unix.stackexchange.com/questions/588240/what-mandates-the-start-entrypoint-kernel-ld-linux-so-etc and the linked articles https://lwn.net/Articles/630727/ and https://lwn.net/Articles/631631/ . See man elf documentation on e_entry.


There are a lot of executables and libraries without interp on my system:

$ f() { if a=$(readelf -l "$1" 2>/dev/null) && ! <<<"$a" grep -q interp; then echo $1; fi; }; export -f f; printf "%s\n" /bin/* /usr/lib/*.so | xargs -P20 -n1 bash -c 'f "$1"' --
/bin/busybox
/bin/catatonit
/bin/containerd-shim
/bin/containerd-shim-runc-v1
/bin/containerd-shim-runc-v2
/bin/docker-init
/bin/qemu-*-static
...
$ f() { if a=$(readelf -l "$1" 2>/dev/null) && ! <<<"$a" grep -q interp; then echo $1; fi; }; export -f f; printf "%s\n" /bin/* /usr/lib/*.so | xargs -P20 -n1 bash -c 'f "$1"' -- | wc -l
3099