LLDB blocks progress of program that handles SIGSEGV

Hello,

I am running Linux Ubuntu 14.04 and I tried both LLDB-3.6 (installed binaries using apt-get) and LLDB-3.7 (built from sources sync’ed to trunk), the result is the same.

I have a simple program (the source code is at the bottom of the message) that maps a page of memory as not accessible, installs a SIGSEGV handler that remaps this page as read-write, and then tries to read from it. So, the expected result is that program initially receives SIGSEGV but happily continues after the handler fixes the problem.

The program runs as expected, both standalone and under GDB:

$ ./mm
signal 11 received
success 777

$ gdb --quiet ./mm
Reading symbols from ./mm…done.
(gdb) r
Starting program: /home/eugene/tmp/mm
Program received signal SIGSEGV, Segmentation fault.
0x0000000000400acc in main () at mm.cpp:27
27 int x = (int)address;
(gdb) c
Continuing.
signal 11 received
success 777
[Inferior 1 (process 14155) exited normally]
(gdb) q

But under LLDB it spins forever on failing instruction without invoking the signal handler. Also, setting “process handle” does not have any effect at all:

$ ~/llvm/bin/lldb ./mm
(lldb) target create “./mm”
Current executable set to ‘./mm’ (x86_64).
(lldb) br se -b main
Breakpoint 1: where = mm`main + 30 at mm.cpp:24, address = 0x0000000000400a7e
(lldb) pr lau
Process 14194 launched: ‘./mm’ (x86_64)
Process 14194 stopped

  • thread #1: tid = 14194, 0x0000000000400a7e mmmain + 30 at mm.cpp:24, name = 'mm', stop reason = breakpoint 1.1 frame #0: 0x0000000000400a7e mmmain + 30 at mm.cpp:24
    21
    22 int main()
    23 {
    → 24 sigset(SIGSEGV, handler);
    25
    26 address = mmap(NULL, size, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
    27 int x = (int)address;
    (lldb) pr ha SIGSEGV -s false -p true -n false
    NAME PASS STOP NOTIFY
    ========== ===== ===== ======
    SIGSEGV true false false
    (lldb) c
    Process 14194 resuming
    Process 14194 stopped
  • thread #1: tid = 14194, 0x0000000000400acc mmmain + 108 at mm.cpp:27, name = 'mm', stop reason = address access protected (fault address: 0x7ffff7ff7000) frame #0: 0x0000000000400acc mmmain + 108 at mm.cpp:27
    24 sigset(SIGSEGV, handler);
    25
    26 address = mmap(NULL, size, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
    → 27 int x = (int)address;
    28 std::cout << (signaled ? "success " : "failure ") << x << “\n”;
    29 }
    (lldb) c
    Process 14194 resuming
    Process 14194 stopped
  • thread #1: tid = 14194, 0x0000000000400acc mmmain + 108 at mm.cpp:27, name = 'mm', stop reason = address access protected (fault address: 0x7ffff7ff7000) frame #0: 0x0000000000400acc mmmain + 108 at mm.cpp:27
    24 sigset(SIGSEGV, handler);
    25
    26 address = mmap(NULL, size, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
    → 27 int x = (int)address;
    28 std::cout << (signaled ? "success " : "failure ") << x << “\n”;
    29 }
    (lldb) c
    Process 14194 resuming
    Process 14194 stopped
  • thread #1: tid = 14194, 0x0000000000400acc mmmain + 108 at mm.cpp:27, name = 'mm', stop reason = address access protected (fault address: 0x7ffff7ff7000) frame #0: 0x0000000000400acc mmmain + 108 at mm.cpp:27
    24 sigset(SIGSEGV, handler);
    25
    26 address = mmap(NULL, size, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
    → 27 int x = (int)address;
    28 std::cout << (signaled ? "success " : "failure ") << x << “\n”;
    29 }
    (lldb)

So, do I miss some LLDB setting?
Here is the program code:

$ cat mm.cpp
#include
#include <sys/mman.h>
#include <signal.h>
#include <assert.h>
#include <sys/types.h>
#include <unistd.h>

void* address;
size_t size = 0x1000;
bool signaled = false;

void handler(int sig)
{
std::cout << “signal " << sig << " received\n”;
signaled = true;
munmap(address, size);
void* newaddr = mmap(address, size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_FIXED | MAP_PRIVATE, -1, 0);
assert(newaddr == address);
(int)newaddr = 777;
}

int main()
{
sigset(SIGSEGV, handler);
address = mmap(NULL, size, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
int x = (int)address;
std::cout << (signaled ? "success " : "failure ") << x << “\n”;
}
$

Thanks,
Eugene

Hello Eugene,

thanks for the report. What you are describing sounds like a genuine
LLDB problem. I have created a bug
<https://llvm.org/bugs/show_bug.cgi?id=23571&gt; so we don't forget about
this. It looks like LLGS (server component of lldb) is reporting this
segfault to LLDB as an "exception" rather than a signal
(reason:expection in the stop-reply packet). This causes LLDB to not
reinject the signal when continuing the inferior. Now there are two
places we can fix this:
- have LLGS report this event as a signal. Then LLDB should be able to
reinject the signal correctly.
- teach LLDB to reinject the signal even in when the stop was reported
as an exception.

I'm inclined to do the first thing but I am not sure. Could somebody
with more LLDB knowledge shed some light on this? What is the intended
usage/behavior in case LLGS reports reason:exception?

cheers,
pl

Thanks for the report Eugene.

I think because it’s an exception on Darwin.

I think that it should be reported as a signal because that’s what it is. Interested to hear from the apple guys though.

Vince

Pavel,

Thanks for reply. You are right, it is reported as exception, here is the portion of MonitorSignal routine on Linux:

case SIGSEGV:
case SIGILL:
case SIGFPE:
case SIGBUS:
if (thread_sp)
std::static_pointer_cast (thread_sp)->SetCrashedWithException (*info);
break;
default:
// This is just a pre-signal-delivery notification of the incoming signal.
if (thread_sp)
std::static_pointer_cast (thread_sp)->SetStoppedBySignal (signo);

I have no knowledge about LLGS (yet :)) but I’d vote for the second way: current LLDB behavior makes total sense for a “normal” application which is not in the business of managing its address space. The same applies to illegal instruction - the program could be self-modifying (say, JIT compiling). Exceptions are signals on Unix from the day one and we’d better not meddle with this long-established abstraction.

I think we also need to give the choice to the developer during interactive debugging: i.e. “continue” command better have a flag that tells if to pass signal to the application or not.

Thanks,
Eugene