<html>
    <head>
      <base href="https://bugs.llvm.org/">
    </head>
    <body><table border="1" cellspacing="0" cellpadding="8">
        <tr>
          <th>Bug ID</th>
          <td><a class="bz_bug_link 
          bz_status_NEW "
   title="NEW - libunwind does not support rax as CFA register"
   href="https://bugs.llvm.org/show_bug.cgi?id=48186">48186</a>
          </td>
        </tr>

        <tr>
          <th>Summary</th>
          <td>libunwind does not support rax as CFA register
          </td>
        </tr>

        <tr>
          <th>Product</th>
          <td>libc++abi
          </td>
        </tr>

        <tr>
          <th>Version</th>
          <td>unspecified
          </td>
        </tr>

        <tr>
          <th>Hardware</th>
          <td>PC
          </td>
        </tr>

        <tr>
          <th>OS</th>
          <td>Linux
          </td>
        </tr>

        <tr>
          <th>Status</th>
          <td>NEW
          </td>
        </tr>

        <tr>
          <th>Severity</th>
          <td>normal
          </td>
        </tr>

        <tr>
          <th>Priority</th>
          <td>P
          </td>
        </tr>

        <tr>
          <th>Component</th>
          <td>All Bugs
          </td>
        </tr>

        <tr>
          <th>Assignee</th>
          <td>unassignedbugs@nondot.org
          </td>
        </tr>

        <tr>
          <th>Reporter</th>
          <td>man2gm@gmail.com
          </td>
        </tr>

        <tr>
          <th>CC</th>
          <td>llvm-bugs@lists.llvm.org, mclow.lists@gmail.com
          </td>
        </tr></table>
      <p>
        <div>
        <pre>libunwind does not understand the

.cfi_def_cfa_register %rax

instruction.

This is because register rax has number zero.
The code in DwarfInstructions.hpp in getCFA function is the following:

<a href="https://github.com/llvm/llvm-project/blob/master/libunwind/src/DwarfInstructions.hpp#L66">https://github.com/llvm/llvm-project/blob/master/libunwind/src/DwarfInstructions.hpp#L66</a>

The value of prolog.cfaRegister comes from:

<a href="https://github.com/llvm/llvm-project/blob/master/libunwind/src/DwarfParser.hpp#L581">https://github.com/llvm/llvm-project/blob/master/libunwind/src/DwarfParser.hpp#L581</a>

If libunwind was build with assertions, assertion will be triggered.
If libunwind was build without assertions, undefined behaviour will happen due
to __builtin_unreachable, and it goes down to the branch where cfaExpression is
evaluated, then segfaults in getULEB128 (by nullptr dereference).

AFAIK, the usage of rax for CFA register is legitimate.
Surprisingly it is not used by the code generated by clang or gcc.
But it happens to be present in some manually written assembly code.

Note that both:
- gcc's builtin unwinder from libgcc;
- HP's "nongnu" libunwind;
work correctly in this case.

Example 1 is OpenSSL and the same in BoringSSL:
<a href="https://github.com/openssl/openssl/pull/9624#issuecomment-566309194">https://github.com/openssl/openssl/pull/9624#issuecomment-566309194</a>
It generates asm code during build. The code of crypto/sha/asm/sha256-x86_64.s,
function sha256_block_data_order_ssse3 starts with:

sha256_block_data_order_ssse3:
.cfi_startproc
.Lssse3_shortcut:
        movq    %rsp,%rax
.cfi_def_cfa_register   %rax
        pushq   %rbx
.cfi_offset     %rbx,-16
        pushq   %rbp
.cfi_offset     %rbp,-24
        pushq   %r12
.cfi_offset     %r12,-32
        pushq   %r13
.cfi_offset     %r13,-40
        pushq   %r14
.cfi_offset     %r14,-48
        pushq   %r15
.cfi_offset     %r15,-56
        shlq    $4,%rdx
        subq    $96,%rsp
        leaq    (%rsi,%rdx,4),%rdx
        andq    $-64,%rsp
        movq    %rdi,64+0(%rsp)
        movq    %rsi,64+8(%rsp)
        movq    %rdx,64+16(%rsp)
        movq    %rax,88(%rsp)
.cfi_escape     0x0f,0x06,0x77,0xd8,0x00,0x06,0x23,0x08


If we do asynchronous unwinding from somewhere between lines
.cfi_def_cfa_register and .cfi_escape, segfault or assertion will happen.

To trigger the error you need to create a program that will do asynchronous
unwinding quite frequently. This is possible by using timer signals. Note that
libunwind functions are not signal-safe (and neither gcc's or HP's libunwind
are). But it is unrelated from this issue.


Minimal reproducing example:

unwind_test.cpp:

#include <sys/time.h>
#include <signal.h>
#include <unistd.h>
#include <time.h>
#include <cstdint>
#include <iostream>


typedef enum {
  _URC_NO_REASON = 0,
  _URC_OK = 0,
  _URC_FOREIGN_EXCEPTION_CAUGHT = 1,
  _URC_FATAL_PHASE2_ERROR = 2,
  _URC_FATAL_PHASE1_ERROR = 3,
  _URC_NORMAL_STOP = 4,
  _URC_END_OF_STACK = 5,
  _URC_HANDLER_FOUND = 6,
  _URC_INSTALL_CONTEXT = 7,
  _URC_CONTINUE_UNWIND = 8,
} _Unwind_Reason_Code;

typedef struct _Unwind_Context _Unwind_Context;
typedef _Unwind_Reason_Code (*_Unwind_Trace_Fn)(struct _Unwind_Context *, void
*);
extern "C" _Unwind_Reason_Code _Unwind_Backtrace(_Unwind_Trace_Fn, void *);


extern "C" void test();

[[noreturn]] void __attribute__((__noinline__)) loop()
{
    while (true)
        test();
}


size_t i = 0;
void handler(int, siginfo_t * info, void *)
{
    if (info && info->si_overrun)
        return;

    ++i;
    if (i % 1000 == 0)
        (void)write(2, ".", 1);

    size_t num_frames = 0;
    _Unwind_Backtrace([](struct _Unwind_Context *, void * ptr) {
++*static_cast<size_t*>(ptr); return _URC_NO_REASON; }, &num_frames);

    if (i % 10000 == 0)
        std::cerr << num_frames << "\n";
}


[[noreturn]] void error(const char * message)
{
    std::cerr << message << "\n";
    exit(1);
}


void setupTimer()
{
    struct sigaction sa{};
    sa.sa_sigaction = handler;
    sa.sa_flags = SA_SIGINFO | SA_RESTART;

    if (sigemptyset(&sa.sa_mask))
        error("Failed to clean signal mask for query profiler");

    if (sigaddset(&sa.sa_mask, SIGPROF))
        error("Failed to add signal to mask for query profiler");

    if (sigaction(SIGPROF, &sa, nullptr))
        error("Failed to setup signal handler for query profiler");

    struct sigevent sev {};
    sev.sigev_notify = SIGEV_SIGNAL;
    sev.sigev_signo = SIGPROF;

    timer_t timer_id = nullptr;
    if (timer_create(CLOCK_MONOTONIC, &sev, &timer_id))
        error("Failed to create thread timer");

    struct timespec interval{.tv_sec = 0, .tv_nsec = 50000};
    struct timespec offset{.tv_sec = 0, .tv_nsec = 1};

    struct itimerspec timer_spec = {.it_interval = interval, .it_value =
offset};
    if (timer_settime(timer_id, 0, &timer_spec, nullptr))
        error("Failed to set thread timer period");
}


int main(int, char **)
{
    setupTimer();
    loop();
}


test.s:

.text

.globl  test
.type   test,@function
.align  64
test:
.cfi_startproc
movq %rsp,%rax
.cfi_def_cfa_register %rax

movq $1000000, %rbx
.LOOP:
subq $1, %rbx
cmpq %rbx, %rbx
jne .LOOP

movq %rax,%rsp
.cfi_def_cfa_register %rsp
retq
.cfi_endproc
.size   test,.-test


1. Compile with LLVM's libunwind:
Prepare the llvm-project repository, create the llvm-build directory, build
libunwind simply by
cmake ../llvm-project/libunwind && make

$ clang++ -g -O3 -pthread -ldl -lrt -fno-pic -fno-pie unwind_test.cpp test.s
-I~/work/llvm-project/libunwind/include/ -Wl,-Bstatic -L ~/work/llvm-build/lib/
-lunwind -Wl,-Bdynamic && ./a.out

a.out:
/home/milovidov/work/llvm-project/libunwind/src/DwarfInstructions.hpp:72:
static libunwind::DwarfInstructions<A, R>::pint_t
libunwind::DwarfInstructions<A, R>::getCFA(A&, const PrologInfo&, const R&)
[with A = libunwind::LocalAddressSpace; R = libunwind::Registers_x86_64;
libunwind::DwarfInstructions<A, R>::pint_t = long unsigned int;
libunwind::DwarfInstructions<A, R>::PrologInfo =
libunwind::CFI_Parser<libunwind::LocalAddressSpace>::PrologInfo]: Assertion `0
&& "getCFA(): unknown location"' failed.
Aborted (core dumped)

2. Compile with HP's "nongnu" libunwind:
sudo apt-get install libunwind-dev

$ clang++ -g -O3 -pthread -ldl -fno-pic -fno-pie unwind_test.cpp test.s -lrt
-Wl,-Bstatic -lunwind -llzma -Wl,-Bdynamic && ./a.out

Works perfectly.

3. Compile with libgcc's unwinder:

$ clang++ -g -O3 -pthread -ldl -fno-pic -fno-pie unwind_test.cpp test.s -lrt
-Wl,-Bstatic -lgcc -Wl,-Bdynamic && ./a.out

Works perfectly.</pre>
        </div>
      </p>


      <hr>
      <span>You are receiving this mail because:</span>

      <ul>
          <li>You are on the CC list for the bug.</li>
      </ul>
    </body>
</html>