mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-02 05:12:02 +00:00
[common] debug: switch to libunwind instead of bfd
This commit is contained in:
8
.github/workflows/build.yml
vendored
8
.github/workflows/build.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
|||||||
- name: Install client dependencies
|
- name: Install client dependencies
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get install \
|
sudo apt-get install \
|
||||||
binutils-dev \
|
libdw-dev libunwind-dev \
|
||||||
libspice-protocol-dev nettle-dev \
|
libspice-protocol-dev nettle-dev \
|
||||||
libgl-dev libgles-dev \
|
libgl-dev libgles-dev \
|
||||||
libx11-dev libxss-dev libxi-dev libxinerama-dev libxcursor-dev libxpresent-dev \
|
libx11-dev libxss-dev libxi-dev libxinerama-dev libxcursor-dev libxpresent-dev \
|
||||||
@@ -77,8 +77,8 @@ jobs:
|
|||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
- name: Install Linux host dependencies
|
- name: Install Linux host dependencies
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get install binutils-dev libglib2.0-dev libxcb-xfixes0-dev \
|
sudo apt-get install libdw-dev libunwind-dev libglib2.0-dev \
|
||||||
libpipewire-0.3-dev libxcb-shm0-dev
|
libxcb-xfixes0-dev libpipewire-0.3-dev libxcb-shm0-dev
|
||||||
- name: Configure Linux host
|
- name: Configure Linux host
|
||||||
run: |
|
run: |
|
||||||
mkdir host/build
|
mkdir host/build
|
||||||
@@ -206,7 +206,7 @@ jobs:
|
|||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
- name: Install obs plugin dependencies
|
- name: Install obs plugin dependencies
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get install binutils-dev libobs-dev libgl1-mesa-dev
|
sudo apt-get install libdw-dev libunwind-dev libobs-dev libgl1-mesa-dev
|
||||||
- name: Configure obs plugin
|
- name: Configure obs plugin
|
||||||
run: |
|
run: |
|
||||||
mkdir obs/build
|
mkdir obs/build
|
||||||
|
|||||||
@@ -57,12 +57,20 @@ add_compile_options(
|
|||||||
"$<$<C_COMPILER_ID:GNU>:-Wimplicit-fallthrough=2>"
|
"$<$<C_COMPILER_ID:GNU>:-Wimplicit-fallthrough=2>"
|
||||||
"-Werror"
|
"-Werror"
|
||||||
"-Wfatal-errors"
|
"-Wfatal-errors"
|
||||||
|
"-g"
|
||||||
"-ffast-math"
|
"-ffast-math"
|
||||||
"-fdata-sections"
|
"-fdata-sections"
|
||||||
"-ffunction-sections"
|
"-ffunction-sections"
|
||||||
"$<$<CONFIG:DEBUG>:-O0;-g3;-ggdb>"
|
"$<$<CONFIG:DEBUG>:-O0;-g3;-ggdb>"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if(ENABLE_BACKTRACE)
|
||||||
|
add_compile_options(
|
||||||
|
"-fno-omit-frame-pointer"
|
||||||
|
"-fno-optimize-sibling-calls"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
set(EXE_FLAGS "-Wl,--gc-sections -z noexecstack")
|
set(EXE_FLAGS "-Wl,--gc-sections -z noexecstack")
|
||||||
set(CMAKE_C_STANDARD 11)
|
set(CMAKE_C_STANDARD 11)
|
||||||
|
|
||||||
@@ -156,6 +164,27 @@ add_executable(looking-glass-client ${SOURCES})
|
|||||||
|
|
||||||
target_compile_definitions(looking-glass-client PRIVATE CIMGUI_DEFINE_ENUMS_AND_STRUCTS=1)
|
target_compile_definitions(looking-glass-client PRIVATE CIMGUI_DEFINE_ENUMS_AND_STRUCTS=1)
|
||||||
|
|
||||||
|
if(NOT CMAKE_OBJCOPY)
|
||||||
|
message(FATAL_ERROR "objcopy is required to generate the client debug file")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT CMAKE_STRIP)
|
||||||
|
message(FATAL_ERROR "strip is required to generate the client debug file")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(CLIENT_DEBUG_FILE
|
||||||
|
"${CMAKE_CURRENT_BINARY_DIR}/looking-glass-client.debug")
|
||||||
|
|
||||||
|
add_custom_command(TARGET looking-glass-client POST_BUILD
|
||||||
|
COMMAND "${CMAKE_OBJCOPY}" --only-keep-debug
|
||||||
|
"$<TARGET_FILE:looking-glass-client>" "${CLIENT_DEBUG_FILE}"
|
||||||
|
COMMAND "${CMAKE_STRIP}" --strip-debug "$<TARGET_FILE:looking-glass-client>"
|
||||||
|
COMMAND "${CMAKE_OBJCOPY}" "--add-gnu-debuglink=${CLIENT_DEBUG_FILE}"
|
||||||
|
"$<TARGET_FILE:looking-glass-client>"
|
||||||
|
COMMENT "Extracting Looking Glass client debug information"
|
||||||
|
VERBATIM
|
||||||
|
)
|
||||||
|
|
||||||
target_link_libraries(looking-glass-client
|
target_link_libraries(looking-glass-client
|
||||||
${EXE_FLAGS}
|
${EXE_FLAGS}
|
||||||
PkgConfig::FONTCONFIG
|
PkgConfig::FONTCONFIG
|
||||||
@@ -182,6 +211,10 @@ install(TARGETS looking-glass-client
|
|||||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||||
COMPONENT binary)
|
COMPONENT binary)
|
||||||
|
|
||||||
|
install(FILES "${CLIENT_DEBUG_FILE}"
|
||||||
|
DESTINATION "${CMAKE_INSTALL_BINDIR}/.debug"
|
||||||
|
COMPONENT binary)
|
||||||
|
|
||||||
install(FILES "${CMAKE_BINARY_DIR}/resources/looking-glass-client.desktop"
|
install(FILES "${CMAKE_BINARY_DIR}/resources/looking-glass-client.desktop"
|
||||||
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications")
|
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications")
|
||||||
install(FILES "${PROJECT_TOP}/resources/lg-logo.svg"
|
install(FILES "${PROJECT_TOP}/resources/lg-logo.svg"
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
# Try to find the BFD librairies
|
|
||||||
# BFD_FOUND - system has BFD lib
|
|
||||||
# BFD_INCLUDE_DIR - the BFD include directory
|
|
||||||
# BFD_LIBRARIES - Libraries needed to use BFD
|
|
||||||
|
|
||||||
if (BFD_INCLUDE_DIR AND BFD_LIBRARIES)
|
|
||||||
# Already in cache, be silent
|
|
||||||
set(BFD_FIND_QUIETLY TRUE)
|
|
||||||
endif (BFD_INCLUDE_DIR AND BFD_LIBRARIES)
|
|
||||||
|
|
||||||
find_path(BFD_INCLUDE_DIR NAMES bfd.h)
|
|
||||||
find_library(BFD_LIBRARIES NAMES bfd)
|
|
||||||
|
|
||||||
include(FindPackageHandleStandardArgs)
|
|
||||||
|
|
||||||
if (";${BFD_LIBRARIES};" MATCHES "bfd.a;")
|
|
||||||
MESSAGE(STATUS "Linking against static bfd")
|
|
||||||
|
|
||||||
find_library(BFD_LIBIBERTY_LIBRARIES NAMES libiberty.a)
|
|
||||||
find_package_handle_standard_args(BFD_LIBIBERTY DEFAULT_MSG BFD_LIBIBERTY_LIBRARIES)
|
|
||||||
|
|
||||||
find_library(BFD_LIBZ_LIBRARIES NAMES libz.a)
|
|
||||||
find_package_handle_standard_args(BFD_LIBZ DEFAULT_MSG BFD_LIBZ_LIBRARIES)
|
|
||||||
|
|
||||||
if (NOT ${BFD_LIBIBERTY_FOUND})
|
|
||||||
message(FATAL_ERROR "Using static libbfd.a, but libiberty.a not available")
|
|
||||||
elseif (NOT ${BFD_LIBZ_FOUND})
|
|
||||||
message(FATAL_ERROR "Using static libbfd.a, but libz.a not available")
|
|
||||||
else()
|
|
||||||
list(APPEND BFD_LIBRARIES ${BFD_LIBIBERTY_LIBRARIES} ${BFD_LIBZ_LIBRARIES})
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
MESSAGE(STATUS "BFD libs: " "${BFD_LIBRARIES}")
|
|
||||||
|
|
||||||
find_package_handle_standard_args(BFD DEFAULT_MSG BFD_LIBRARIES BFD_INCLUDE_DIR)
|
|
||||||
|
|
||||||
MESSAGE(STATUS "BFD libs: " "${BFD_LIBRARIES}")
|
|
||||||
|
|
||||||
mark_as_advanced(BFD_INCLUDE_DIR BFD_LIBRARIES)
|
|
||||||
@@ -20,8 +20,13 @@ add_library(lg_common_platform_code STATIC
|
|||||||
)
|
)
|
||||||
|
|
||||||
if(ENABLE_BACKTRACE)
|
if(ENABLE_BACKTRACE)
|
||||||
find_package(BFD)
|
find_package(PkgConfig REQUIRED)
|
||||||
target_link_libraries(lg_common_platform_code ${BFD_LIBRARIES})
|
pkg_check_modules(LIBUNWIND REQUIRED IMPORTED_TARGET libunwind)
|
||||||
|
pkg_check_modules(LIBDW REQUIRED IMPORTED_TARGET libdw)
|
||||||
|
target_link_libraries(lg_common_platform_code
|
||||||
|
PkgConfig::LIBUNWIND
|
||||||
|
PkgConfig::LIBDW
|
||||||
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
target_link_libraries(lg_common_platform_code
|
target_link_libraries(lg_common_platform_code
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
/**
|
|
||||||
* Looking Glass
|
|
||||||
* Copyright © 2017-2026 The Looking Glass Authors
|
|
||||||
* https://looking-glass.io
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms of the GNU General Public License as published by the Free
|
|
||||||
* Software Foundation; either version 2 of the License, or (at your option)
|
|
||||||
* any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License along
|
|
||||||
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
|
||||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef PACKAGE
|
|
||||||
#define PACKAGE
|
|
||||||
#ifndef PACKAGE_VERSION
|
|
||||||
#define PACKAGE_VERSION
|
|
||||||
#include <bfd.h>
|
|
||||||
#undef PACKAGE_VERSION
|
|
||||||
#else
|
|
||||||
#include <bfd.h>
|
|
||||||
#endif
|
|
||||||
#undef PACKAGE
|
|
||||||
#else
|
|
||||||
#ifndef PACKAGE_VERSION
|
|
||||||
#define PACKAGE_VERSION
|
|
||||||
#include <bfd.h>
|
|
||||||
#undef PACKAGE_VERSION
|
|
||||||
#else
|
|
||||||
#include <bfd.h>
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
@@ -24,226 +24,374 @@
|
|||||||
|
|
||||||
#if defined(ENABLE_BACKTRACE)
|
#if defined(ENABLE_BACKTRACE)
|
||||||
|
|
||||||
#include <execinfo.h>
|
#define UNW_LOCAL_ONLY
|
||||||
|
#include <libunwind.h>
|
||||||
|
#include <elfutils/libdwfl.h>
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
#include <stdio.h>
|
#include <stdint.h>
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <sys/prctl.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
#include <ucontext.h>
|
#include <ucontext.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include <limits.h>
|
#define CRASH_MAX_FRAMES 64
|
||||||
|
|
||||||
#include <link.h>
|
struct CrashRequest
|
||||||
#include "bfd.inc.h"
|
|
||||||
|
|
||||||
struct range
|
|
||||||
{
|
{
|
||||||
intptr_t start, end;
|
int signal;
|
||||||
|
uintptr_t faultAddress;
|
||||||
|
unsigned frameCount;
|
||||||
|
uintptr_t frames[CRASH_MAX_FRAMES];
|
||||||
};
|
};
|
||||||
|
|
||||||
struct crash
|
struct CrashState
|
||||||
{
|
{
|
||||||
char * exe;
|
pid_t reporter;
|
||||||
struct range * ranges;
|
int requestFd;
|
||||||
int rangeCount;
|
int responseFd;
|
||||||
bfd * fd;
|
|
||||||
asection * section;
|
|
||||||
asymbol ** syms;
|
|
||||||
long symCount;
|
|
||||||
bool loaded;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static struct crash crash = {0};
|
static struct CrashState crash =
|
||||||
|
{
|
||||||
|
.reporter = -1,
|
||||||
|
.requestFd = -1,
|
||||||
|
.responseFd = -1
|
||||||
|
};
|
||||||
|
|
||||||
static bool load_symbols(void)
|
static bool writeAll(int fd, const void * buffer, size_t size)
|
||||||
{
|
{
|
||||||
bfd_init();
|
const uint8_t * pos = buffer;
|
||||||
crash.fd = bfd_openr(crash.exe, NULL);
|
while (size)
|
||||||
if (!crash.fd)
|
|
||||||
{
|
{
|
||||||
DEBUG_ERROR("failed to open '%s'", crash.exe);
|
ssize_t written = write(fd, pos, size);
|
||||||
|
if (written < 0)
|
||||||
|
{
|
||||||
|
if (errno == EINTR)
|
||||||
|
continue;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
crash.fd->flags |= BFD_DECOMPRESS;
|
pos += written;
|
||||||
|
size -= written;
|
||||||
char **matching;
|
|
||||||
if (!bfd_check_format_matches(crash.fd, bfd_object, &matching))
|
|
||||||
{
|
|
||||||
DEBUG_ERROR("executable is not a bfd_object");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
crash.section = bfd_get_section_by_name(crash.fd, ".text");
|
|
||||||
if (!crash.section)
|
|
||||||
{
|
|
||||||
DEBUG_ERROR("failed to find .text section");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((bfd_get_file_flags(crash.fd) & HAS_SYMS) == 0)
|
|
||||||
{
|
|
||||||
DEBUG_ERROR("executable '%s' has no symbols", crash.exe);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
long storage = bfd_get_symtab_upper_bound(crash.fd);
|
|
||||||
crash.syms = malloc(storage);
|
|
||||||
crash.symCount = bfd_canonicalize_symtab(crash.fd, crash.syms);
|
|
||||||
if (crash.symCount < 0)
|
|
||||||
{
|
|
||||||
DEBUG_ERROR("failed to get the symbol count");
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool lookup_address(bfd_vma pc, const char ** filename, const char ** function, unsigned int * line, unsigned int * discriminator)
|
static bool readAll(int fd, void * buffer, size_t size)
|
||||||
{
|
{
|
||||||
#ifdef bfd_get_section_flags
|
uint8_t * pos = buffer;
|
||||||
if ((bfd_get_section_flags(crash.fd, crash.section) & SEC_ALLOC) == 0)
|
while (size)
|
||||||
#else
|
{
|
||||||
if ((bfd_section_flags(crash.section) & SEC_ALLOC) == 0)
|
ssize_t received = read(fd, pos, size);
|
||||||
#endif
|
if (received == 0)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
#ifdef bfd_get_section_size
|
if (received < 0)
|
||||||
bfd_size_type size = bfd_get_section_size(crash.section);
|
{
|
||||||
#else
|
if (errno == EINTR)
|
||||||
bfd_size_type size = bfd_section_size(crash.section);
|
continue;
|
||||||
#endif
|
|
||||||
if (pc >= size)
|
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!bfd_find_nearest_line_discriminator(
|
pos += received;
|
||||||
crash.fd,
|
size -= received;
|
||||||
crash.section,
|
}
|
||||||
crash.syms,
|
|
||||||
pc,
|
|
||||||
filename,
|
|
||||||
function,
|
|
||||||
line,
|
|
||||||
discriminator
|
|
||||||
))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!*filename)
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void reportFrame(Dwfl * dwfl, unsigned index, uintptr_t pc)
|
||||||
|
{
|
||||||
|
// Except for the faulting instruction, an IP is a return address. Looking
|
||||||
|
// up the preceding byte attributes it to the call instruction.
|
||||||
|
Dwarf_Addr address = pc - (index != 0 && pc != 0);
|
||||||
|
Dwfl_Module * module = dwfl_addrmodule(dwfl, address);
|
||||||
|
if (!module)
|
||||||
|
{
|
||||||
|
DEBUG_ERROR("[trace]: (%u) 0x%" PRIxPTR, index, pc);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GElf_Off functionOffset = 0;
|
||||||
|
GElf_Sym symbol;
|
||||||
|
const char * function = dwfl_module_addrinfo(module, address,
|
||||||
|
&functionOffset, &symbol, NULL, NULL, NULL);
|
||||||
|
Dwfl_Line * source = dwfl_module_getsrc(module, address);
|
||||||
|
if (source)
|
||||||
|
{
|
||||||
|
int line = 0;
|
||||||
|
int column = 0;
|
||||||
|
const char * filename =
|
||||||
|
dwfl_lineinfo(source, NULL, &line, &column, NULL, NULL);
|
||||||
|
if (filename)
|
||||||
|
{
|
||||||
|
DEBUG_ERROR("[trace]: (%u) %s:%d:%d (%s)", index, filename, line,
|
||||||
|
column, function ? function : "unknown");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Dwarf_Addr moduleStart = 0;
|
||||||
|
const char * moduleName = dwfl_module_info(module, NULL, &moduleStart,
|
||||||
|
NULL, NULL, NULL, NULL, NULL);
|
||||||
|
if (!moduleName)
|
||||||
|
moduleName = "unknown";
|
||||||
|
|
||||||
|
if (function)
|
||||||
|
DEBUG_ERROR("[trace]: (%u) %s+0x%" PRIx64 " (%s)", index, function,
|
||||||
|
(uint64_t)functionOffset, moduleName);
|
||||||
|
else
|
||||||
|
DEBUG_ERROR("[trace]: (%u) %s+0x%" PRIx64 " [0x%" PRIxPTR "]", index,
|
||||||
|
moduleName, (uint64_t)(address - moduleStart), pc);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void reportCrash(pid_t parent, const struct CrashRequest * request)
|
||||||
|
{
|
||||||
|
if (request->signal)
|
||||||
|
{
|
||||||
|
DEBUG_ERROR("==== FATAL CRASH (%s) ====", BUILD_VERSION);
|
||||||
|
DEBUG_ERROR("signal %d (%s), address is 0x%" PRIxPTR, request->signal,
|
||||||
|
strsignal(request->signal), request->faultAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const Dwfl_Callbacks callbacks =
|
||||||
|
{
|
||||||
|
.find_elf = dwfl_linux_proc_find_elf,
|
||||||
|
.find_debuginfo = dwfl_standard_find_debuginfo
|
||||||
|
};
|
||||||
|
|
||||||
|
Dwfl * dwfl = dwfl_begin(&callbacks);
|
||||||
|
if (!dwfl)
|
||||||
|
{
|
||||||
|
DEBUG_ERROR("Failed to initialize stack trace symbolizer: %s",
|
||||||
|
dwfl_errmsg(-1));
|
||||||
|
goto raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dwfl_linux_proc_report(dwfl, parent) != 0 ||
|
||||||
|
dwfl_report_end(dwfl, NULL, NULL) != 0)
|
||||||
|
{
|
||||||
|
DEBUG_ERROR("Failed to inspect the crashed process: %s",
|
||||||
|
dwfl_errmsg(-1));
|
||||||
|
dwfl_end(dwfl);
|
||||||
|
goto raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (unsigned i = 0; i < request->frameCount; ++i)
|
||||||
|
reportFrame(dwfl, i, request->frames[i]);
|
||||||
|
|
||||||
|
dwfl_end(dwfl);
|
||||||
|
return;
|
||||||
|
|
||||||
|
raw:
|
||||||
|
for (unsigned i = 0; i < request->frameCount; ++i)
|
||||||
|
DEBUG_ERROR("[trace]: (%u) 0x%" PRIxPTR, i, request->frames[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void reporterLoop(pid_t parent, int requestFd, int responseFd)
|
||||||
|
{
|
||||||
|
// Do not leave an orphaned reporter if the client exits unexpectedly before
|
||||||
|
// it closes the request pipe.
|
||||||
|
prctl(PR_SET_PDEATHSIG, SIGKILL);
|
||||||
|
if (getppid() != parent)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const uint8_t ready = 1;
|
||||||
|
if (!writeAll(responseFd, &ready, sizeof(ready)))
|
||||||
|
return;
|
||||||
|
|
||||||
|
struct CrashRequest request;
|
||||||
|
while (readAll(requestFd, &request, sizeof(request)))
|
||||||
|
{
|
||||||
|
reportCrash(parent, &request);
|
||||||
|
|
||||||
|
const uint8_t complete = 1;
|
||||||
|
if (!writeAll(responseFd, &complete, sizeof(complete)))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool startReporter(void)
|
||||||
|
{
|
||||||
|
int requestPipe[2];
|
||||||
|
int responsePipe[2];
|
||||||
|
if (pipe(requestPipe) != 0)
|
||||||
|
{
|
||||||
|
DEBUG_ERROR("Failed to create crash reporter request pipe");
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pipe(responsePipe) != 0)
|
||||||
|
{
|
||||||
|
DEBUG_ERROR("Failed to create crash reporter response pipe");
|
||||||
|
close(requestPipe[0]);
|
||||||
|
close(requestPipe[1]);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (unsigned i = 0; i < 2; ++i)
|
||||||
|
{
|
||||||
|
fcntl(requestPipe[i], F_SETFD, FD_CLOEXEC);
|
||||||
|
fcntl(responsePipe[i], F_SETFD, FD_CLOEXEC);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pid_t parent = getpid();
|
||||||
|
const pid_t child = fork();
|
||||||
|
if (child < 0)
|
||||||
|
{
|
||||||
|
DEBUG_ERROR("Failed to start the crash reporter");
|
||||||
|
close(requestPipe [0]);
|
||||||
|
close(requestPipe [1]);
|
||||||
|
close(responsePipe[0]);
|
||||||
|
close(responsePipe[1]);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (child == 0)
|
||||||
|
{
|
||||||
|
close(requestPipe [1]);
|
||||||
|
close(responsePipe[0]);
|
||||||
|
reporterLoop(parent, requestPipe[0], responsePipe[1]);
|
||||||
|
close(requestPipe [0]);
|
||||||
|
close(responsePipe[1]);
|
||||||
|
_exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
close(requestPipe [0]);
|
||||||
|
close(responsePipe[1]);
|
||||||
|
|
||||||
|
crash.reporter = child;
|
||||||
|
crash.requestFd = requestPipe [1];
|
||||||
|
crash.responseFd = responsePipe[0];
|
||||||
|
|
||||||
|
uint8_t ready;
|
||||||
|
if (!readAll(crash.responseFd, &ready, sizeof(ready)))
|
||||||
|
{
|
||||||
|
DEBUG_ERROR("Crash reporter failed to initialize");
|
||||||
|
cleanupCrashHandler();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void cleanupCrashHandler(void)
|
void cleanupCrashHandler(void)
|
||||||
{
|
{
|
||||||
if (crash.syms)
|
if (crash.requestFd >= 0)
|
||||||
free(crash.syms);
|
{
|
||||||
|
close(crash.requestFd);
|
||||||
if (crash.fd)
|
crash.requestFd = -1;
|
||||||
bfd_close(crash.fd);
|
|
||||||
|
|
||||||
if (crash.ranges)
|
|
||||||
free(crash.ranges);
|
|
||||||
|
|
||||||
if (crash.exe)
|
|
||||||
free(crash.exe);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int dl_iterate_phdr_callback(struct dl_phdr_info * info, size_t size, void * data)
|
if (crash.responseFd >= 0)
|
||||||
{
|
{
|
||||||
// we are not a module, and as such we don't have a name
|
close(crash.responseFd);
|
||||||
if (strlen(info->dlpi_name) != 0)
|
crash.responseFd = -1;
|
||||||
return 0;
|
|
||||||
|
|
||||||
size_t ttl = 0;
|
|
||||||
for(int i = 0; i < info->dlpi_phnum; ++i)
|
|
||||||
{
|
|
||||||
const ElfW(Phdr) hdr = info->dlpi_phdr[i];
|
|
||||||
if (hdr.p_type == PT_LOAD && (hdr.p_flags & PF_X) == PF_X)
|
|
||||||
ttl += hdr.p_memsz;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void * tmp = realloc(crash.ranges,
|
if (crash.reporter > 0)
|
||||||
sizeof(*crash.ranges) * (crash.rangeCount + 1));
|
|
||||||
if (!tmp)
|
|
||||||
{
|
{
|
||||||
DEBUG_ERROR("out of memory");
|
while (waitpid(crash.reporter, NULL, 0) < 0 && errno == EINTR)
|
||||||
return 1;
|
continue;
|
||||||
|
crash.reporter = -1;
|
||||||
}
|
}
|
||||||
crash.ranges = tmp;
|
|
||||||
crash.ranges[crash.rangeCount].start = info->dlpi_addr;
|
|
||||||
crash.ranges[crash.rangeCount].end = info->dlpi_addr + ttl;
|
|
||||||
++crash.rangeCount;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void printBacktrace(void)
|
void printBacktrace(void)
|
||||||
{
|
{
|
||||||
void * array[50];
|
struct CrashRequest request =
|
||||||
char ** messages;
|
{
|
||||||
int size, i;
|
.signal = 0
|
||||||
|
};
|
||||||
|
|
||||||
size = backtrace(array, 50);
|
unw_context_t context;
|
||||||
messages = backtrace_symbols(array, size);
|
unw_cursor_t cursor;
|
||||||
|
if (unw_getcontext(&context) < 0 ||
|
||||||
|
unw_init_local(&cursor, &context) < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
for (i = 2; i < size && messages != NULL; ++i)
|
// Omit unw_getcontext and printBacktrace itself.
|
||||||
|
if (unw_step(&cursor) <= 0 || unw_step(&cursor) <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
do
|
||||||
{
|
{
|
||||||
intptr_t base = -1;
|
unw_word_t pc;
|
||||||
for(int c = 0; c < crash.rangeCount; ++c)
|
if (unw_get_reg(&cursor, UNW_REG_IP, &pc) < 0 || !pc)
|
||||||
{
|
|
||||||
if ((intptr_t)array[i] >= crash.ranges[c].start && (intptr_t)array[i] < crash.ranges[c].end)
|
|
||||||
{
|
|
||||||
base = crash.ranges[c].start + crash.section->vma;
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (base != -1)
|
request.frames[request.frameCount++] = (uintptr_t)pc;
|
||||||
|
}
|
||||||
|
while (request.frameCount < CRASH_MAX_FRAMES && unw_step(&cursor) > 0);
|
||||||
|
|
||||||
|
if (crash.requestFd >= 0 && crash.responseFd >= 0 &&
|
||||||
|
writeAll(crash.requestFd, &request, sizeof(request)))
|
||||||
{
|
{
|
||||||
const char * filename, * function;
|
uint8_t complete;
|
||||||
unsigned int line, discriminator;
|
readAll(crash.responseFd, &complete, sizeof(complete));
|
||||||
if (lookup_address((intptr_t)array[i] - base, &filename, &function, &line, &discriminator))
|
|
||||||
{
|
|
||||||
DEBUG_ERROR("[trace]: (%d) %s:%u (%s)", i - 2, filename, line, function);
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DEBUG_ERROR("[trace]: (%d) %s", i - 2, messages[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
free(messages);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void crit_err_hdlr(int sig_num, siginfo_t * info, void * ucontext)
|
static void crit_err_hdlr(int sig_num, siginfo_t * info, void * ucontext)
|
||||||
{
|
{
|
||||||
DEBUG_ERROR("==== FATAL CRASH (%s) ====", BUILD_VERSION);
|
struct CrashRequest request;
|
||||||
DEBUG_ERROR("signal %d (%s), address is %p", sig_num, strsignal(sig_num), info->si_addr);
|
request.signal = sig_num;
|
||||||
printBacktrace();
|
request.faultAddress = (uintptr_t)info->si_addr;
|
||||||
cleanupCrashHandler();
|
request.frameCount = 0;
|
||||||
abort();
|
|
||||||
|
unw_cursor_t cursor;
|
||||||
|
if (unw_init_local2(&cursor, (unw_context_t *)ucontext,
|
||||||
|
UNW_INIT_SIGNAL_FRAME) >= 0)
|
||||||
|
{
|
||||||
|
do
|
||||||
|
{
|
||||||
|
unw_word_t pc;
|
||||||
|
if (unw_get_reg(&cursor, UNW_REG_IP, &pc) < 0 || !pc)
|
||||||
|
break;
|
||||||
|
|
||||||
|
request.frames[request.frameCount++] = (uintptr_t)pc;
|
||||||
|
}
|
||||||
|
while (request.frameCount < CRASH_MAX_FRAMES &&
|
||||||
|
unw_step(&cursor) > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (crash.requestFd >= 0 && crash.responseFd >= 0 &&
|
||||||
|
writeAll(crash.requestFd, &request, sizeof(request)))
|
||||||
|
{
|
||||||
|
uint8_t complete;
|
||||||
|
readAll(crash.responseFd, &complete, sizeof(complete));
|
||||||
|
}
|
||||||
|
|
||||||
|
// SA_RESETHAND has restored the default disposition and SA_NODEFER leaves
|
||||||
|
// this signal unblocked, so this preserves the original fatal signal and
|
||||||
|
// still permits the operating system to produce a core dump.
|
||||||
|
kill(getpid(), sig_num);
|
||||||
|
_exit(128 + sig_num);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool installCrashHandler(const char * exe)
|
bool installCrashHandler(const char * exe)
|
||||||
{
|
{
|
||||||
|
(void)exe;
|
||||||
|
|
||||||
|
if (!startReporter())
|
||||||
|
return false;
|
||||||
|
|
||||||
struct sigaction sigact = { 0 };
|
struct sigaction sigact = { 0 };
|
||||||
|
|
||||||
crash.exe = realpath(exe, NULL);
|
|
||||||
if (!load_symbols())
|
|
||||||
{
|
|
||||||
DEBUG_WARN("Unable to load the binary symbols, not installing crash handler");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
dl_iterate_phdr(dl_iterate_phdr_callback, NULL);
|
|
||||||
|
|
||||||
sigact.sa_sigaction = crit_err_hdlr;
|
sigact.sa_sigaction = crit_err_hdlr;
|
||||||
sigact.sa_flags = SA_RESTART | SA_SIGINFO;
|
sigact.sa_flags =
|
||||||
|
SA_RESTART | SA_SIGINFO | SA_NODEFER | SA_RESETHAND;
|
||||||
|
sigemptyset(&sigact.sa_mask);
|
||||||
|
|
||||||
if (sigaction(SIGSEGV, &sigact, (struct sigaction *)NULL) != 0)
|
if (sigaction(SIGSEGV, &sigact, NULL) != 0)
|
||||||
{
|
{
|
||||||
DEBUG_ERROR("Error setting signal handler for %d (%s)", SIGSEGV, strsignal(SIGSEGV));
|
DEBUG_ERROR("Error setting SIGSEGV handler");
|
||||||
|
cleanupCrashHandler();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ Required dependencies
|
|||||||
All required packages must be listed.
|
All required packages must be listed.
|
||||||
|
|
||||||
- ``cmake``
|
- ``cmake``
|
||||||
|
- ``binutils``
|
||||||
- ``gcc``, ``g++`` \| ``clang``
|
- ``gcc``, ``g++`` \| ``clang``
|
||||||
- ``libegl-dev``
|
- ``libegl-dev``
|
||||||
- ``libgl-dev``
|
- ``libgl-dev``
|
||||||
@@ -88,7 +89,8 @@ feature is disabled when running :ref:`cmake <client_building>`.
|
|||||||
|
|
||||||
- Disable with ``cmake -DENABLE_BACKTRACE=no ..``
|
- Disable with ``cmake -DENABLE_BACKTRACE=no ..``
|
||||||
|
|
||||||
- ``binutils-dev``
|
- ``libdw-dev``
|
||||||
|
- ``libunwind-dev``
|
||||||
|
|
||||||
- Disable with ``cmake -DENABLE_X11=no ..``
|
- Disable with ``cmake -DENABLE_X11=no ..``
|
||||||
|
|
||||||
@@ -142,9 +144,9 @@ You can fetch these dependencies with the following command:
|
|||||||
|
|
||||||
.. code:: bash
|
.. code:: bash
|
||||||
|
|
||||||
apt-get install binutils-dev cmake fonts-dejavu-core libfontconfig-dev \
|
apt-get install binutils cmake fonts-dejavu-core libdw-dev libfontconfig-dev \
|
||||||
gcc g++ pkg-config libegl-dev libgl-dev libgles-dev libspice-protocol-dev \
|
libunwind-dev gcc g++ pkg-config libegl-dev libgl-dev libgles-dev \
|
||||||
nettle-dev libx11-dev libxcursor-dev libxi-dev libxinerama-dev \
|
libspice-protocol-dev nettle-dev libx11-dev libxcursor-dev libxi-dev libxinerama-dev \
|
||||||
libxpresent-dev libxss-dev libxkbcommon-dev libwayland-dev \
|
libxpresent-dev libxss-dev libxkbcommon-dev libwayland-dev \
|
||||||
libpipewire-0.3-dev libpulse-dev libsamplerate0-dev
|
libpipewire-0.3-dev libpulse-dev libsamplerate0-dev
|
||||||
|
|
||||||
@@ -169,7 +171,10 @@ into the *LookingGlass* directory.
|
|||||||
make
|
make
|
||||||
|
|
||||||
This will build the ``looking-glass-client`` binary, which is used to display
|
This will build the ``looking-glass-client`` binary, which is used to display
|
||||||
frames from the guest.
|
frames from the guest. It also produces ``looking-glass-client.debug``. Keep
|
||||||
|
this file with the matching binary to obtain source locations in crash stack
|
||||||
|
traces. The install target places it in the standard ``bin/.debug`` location
|
||||||
|
automatically.
|
||||||
|
|
||||||
You can then :ref:`continue installing Looking Glass <client_install>`, or run
|
You can then :ref:`continue installing Looking Glass <client_install>`, or run
|
||||||
it directly from the build directory:
|
it directly from the build directory:
|
||||||
|
|||||||
Reference in New Issue
Block a user