[llvm-bugs] [Bug 48186] New: libunwind does not support rax as CFA register

via llvm-bugs llvm-bugs at lists.llvm.org
Sun Nov 15 08:29:05 PST 2020


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: unassignedbugs at nondot.org
          Reporter: man2gm at gmail.com
                CC: llvm-bugs at lists.llvm.org, mclow.lists at 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, at 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.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/llvm-bugs/attachments/20201115/c729b065/attachment.html>


More information about the llvm-bugs mailing list