1. 23

  2. 4

    Note that this approach is not setting the TLS base pointer (or allocating TLS) and so both threads will share TLS. If you’re not using libc, there’s a good chance that you’re not using C/C++ TLS and so this may not matter to you.

    1. 2

      I wish doing really basic things without libc on Linux didn’t require so much assembly.

      GCC/Clang provide the builtin __builtin_frame_address(), which I’ve used in the past to get argc/argv in pure C. I’m not entirely sure if it’s guaranteed to work, but it worked for me across GCC and Clang, with or without optimizations.

      Here’s the (old, untested for a couple years) code:

      __attribute__ ((force_align_arg_pointer))
      _Noreturn void _start() {
        // find arguments: per the SysV ABI, these are placed on the stack when
        // _start is invoked. Conveniently, gcc/clang give us __builtin_frame_address,
        // which gives us a stable way to access the memory above our stack frame
        // without worrying about things like the size of our stack frame or
        // whether the compiler might optimize out rbp.
        // This +1 offset is inexplicable: my reading of the ABI is that argc should
        // be exactly at %rsp when the entry point is called, but actually there are
        // 8 zero bytes first. Either I'm misunderstanding something (likely), or the
        // ABI doesn't match what Linux actually does.
        long* argc_ptr = ((long*) __builtin_frame_address(0)) + 1;
        long argc = *argc_ptr;
        char** argv = (char**) (argc_ptr + 1);
      1. 1

        Could the +1 be required because of a return address being pushed onto the stack before running start? (For platforms such as x86[-64] where return addresses are always on the stack.) I mean there’s not really anywhere to return to after start but I assume there’s some kind of sentinel (0x0?) for terminating stack-walks.