https://bugs.llvm.org/show_bug.cgi?id=48186

            Bug ID: 48186
           Summary: libunwind does not support rax as CFA register
           Product: libc++abi
           Version: unspecified
          Hardware: PC
                OS: Linux
            Status: NEW
          Severity: normal
          Priority: P
         Component: All Bugs
          Assignee: unassignedb...@nondot.org
          Reporter: man...@gmail.com
                CC: llvm-bugs@lists.llvm.org, mclow.li...@gmail.com

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:

https://github.com/llvm/llvm-project/blob/master/libunwind/src/DwarfInstructions.hpp#L66

The value of prolog.cfaRegister comes from:

https://github.com/llvm/llvm-project/blob/master/libunwind/src/DwarfParser.hpp#L581

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:
https://github.com/openssl/openssl/pull/9624#issuecomment-566309194
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.

-- 
You are receiving this mail because:
You are on the CC list for the bug.
_______________________________________________
llvm-bugs mailing list
llvm-bugs@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-bugs

Reply via email to