Skip to content

Commit

Permalink
Support mini debug info files.
Browse files Browse the repository at this point in the history
https://sourceware.org/gdb/onlinedocs/gdb/MiniDebugInfo.html
Some libraries have ".gnu_debugdata" section holding lzma
compressed elf file providing additional symbol information
for backtraces.

This commit adds an optional dependency on liblzma. If it is
available bcc will use it to decompress .gnu_debugdata and
return memory buffer based elf file from find_debug_file.
  • Loading branch information
michalgr committed Nov 7, 2022
1 parent 75fe69e commit 7eef39c
Show file tree
Hide file tree
Showing 6 changed files with 204 additions and 2 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ if(NOT PYTHON_ONLY)
find_package(FLEX)
find_package(LibElf REQUIRED)
find_package(LibDebuginfod)
find_package(LibLzma)
if(CLANG_DIR)
set(CMAKE_FIND_ROOT_PATH "${CLANG_DIR}")
include_directories("${CLANG_DIR}/include")
Expand Down
45 changes: 45 additions & 0 deletions cmake/FindLibLzma.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# - Try to find liblzma
# Once done this will define
#
# LIBLZMA_FOUND - system has liblzma
# LIBLZMA_INCLUDE_DIRS - the liblzma include directory
# LIBLZMA_LIBRARIES - Link these to use liblzma

if (LIBLZMA_LIBRARIES AND LIBLZMA_INCLUDE_DIRS)
set (LibLzma_FIND_QUIETLY TRUE)
endif (LIBLZMA_LIBRARIES AND LIBLZMA_INCLUDE_DIRS)

find_path (LIBLZMA_INCLUDE_DIRS
NAMES
lzma.h
PATHS
/usr/include
/usr/local/include
/opt/local/include
/sw/include
ENV CPATH)

find_library (LIBLZMA_LIBRARIES
NAMES
lzma
PATHS
/usr/lib
/usr/local/lib
/opt/local/lib
/sw/lib
ENV LIBRARY_PATH
ENV LD_LIBRARY_PATH)

include (FindPackageHandleStandardArgs)


# handle the QUIETLY and REQUIRED arguments and set LIBLZMA_FOUND to TRUE if all listed variables are TRUE
FIND_PACKAGE_HANDLE_STANDARD_ARGS(LibLzma DEFAULT_MSG
LIBLZMA_LIBRARIES
LIBLZMA_INCLUDE_DIRS)

if (LIBLZMA_FOUND)
add_definitions(-DHAVE_LIBLZMA)
endif (LIBLZMA_FOUND)

mark_as_advanced(LIBLZMA_INCLUDE_DIRS LIBLZMA_LIBRARIES)
9 changes: 9 additions & 0 deletions src/cc/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR}/frontends/b)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/frontends/clang)
include_directories(${LLVM_INCLUDE_DIRS})
include_directories(${LIBELF_INCLUDE_DIRS})
if (LIBLZMA_FOUND)
include_directories(${LIBLZMA_INCLUDE_DIRS})
endif(LIBLZMA_FOUND)
if (LIBDEBUGINFOD_FOUND AND ENABLE_LIBDEBUGINFOD)
include_directories(${LIBDEBUGINFOD_INCLUDE_DIRS})
endif (LIBDEBUGINFOD_FOUND AND ENABLE_LIBDEBUGINFOD)
Expand Down Expand Up @@ -116,6 +119,9 @@ add_library(bpf-shared SHARED ${bpf_sources})
set_target_properties(bpf-shared PROPERTIES VERSION ${REVISION_LAST} SOVERSION 0)
set_target_properties(bpf-shared PROPERTIES OUTPUT_NAME bcc_bpf)
target_link_libraries(bpf-shared elf z)
if(LIBLZMA_FOUND)
target_link_libraries(bpf-shared ${LIBLZMA_LIBRARIES})
endif(LIBLZMA_FOUND)
if(LIBDEBUGINFOD_FOUND)
target_link_libraries(bpf-shared ${LIBDEBUGINFOD_LIBRARIES})
endif(LIBDEBUGINFOD_FOUND)
Expand All @@ -132,6 +138,9 @@ set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${llvm_lib_exclude_f
set(bcc_common_libs clang_frontend
-Wl,--whole-archive ${clang_libs} ${llvm_libs} -Wl,--no-whole-archive
${LIBELF_LIBRARIES})
if (LIBLZMA_FOUND)
list(APPEND bcc_common_libs ${LIBLZMA_LIBRARIES})
endif (LIBLZMA_FOUND)
if (LIBDEBUGINFOD_FOUND)
list(APPEND bcc_common_libs ${LIBDEBUGINFOD_LIBRARIES})
endif (LIBDEBUGINFOD_FOUND)
Expand Down
110 changes: 110 additions & 0 deletions src/cc/bcc_elf.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#ifdef HAVE_LIBLZMA
#include <lzma.h>
#endif
#ifdef HAVE_LIBDEBUGINFOD
#include <elfutils/debuginfod.h>
#endif
Expand All @@ -47,6 +50,19 @@ static int openelf_fd(int fd, Elf **elf_out) {
return 0;
}

#ifdef HAVE_LIBLZMA
static int openelf_mem(void *buf, size_t buf_len, Elf **elf_out) {
if (elf_version(EV_CURRENT) == EV_NONE)
return -1;

*elf_out = elf_memory(buf, buf_len);
if (*elf_out == NULL)
return -1;

return 0;
}
#endif

// Provides access to an Elf structure in an uniform way,
// independently from its source (file or memory buffer).
struct bcc_elf_file {
Expand All @@ -55,6 +71,10 @@ struct bcc_elf_file {
// Set only when the elf file is parsed from an opened file descriptor that
// needs to be closed. Otherwise set to -1.
int fd;

// Set only when the elf file is parsed from a memory buffer that needs to be
// freed.
void *buf;
};

// Initializes bcc_elf_file as not pointing to any elf file and not
Expand All @@ -63,8 +83,24 @@ struct bcc_elf_file {
static void bcc_elf_file_init(struct bcc_elf_file *elf_file) {
elf_file->elf = NULL;
elf_file->fd = -1;
elf_file->buf = NULL;
}

#ifdef HAVE_LIBLZMA
static int bcc_elf_file_open_buf(void *buf, size_t buf_len,
struct bcc_elf_file *out) {
Elf *elf = NULL;

if (openelf_mem(buf, buf_len, &elf)) {
return -1;
}

out->elf = elf;
out->buf = buf;
return 0;
}
#endif

static int bcc_elf_file_open_fd(int fd, struct bcc_elf_file *out) {
Elf *elf = NULL;

Expand Down Expand Up @@ -101,6 +137,10 @@ static void bcc_elf_file_close(struct bcc_elf_file *elf_file) {
close(elf_file->fd);
}

if (elf_file->buf) {
free(elf_file->buf);
}

bcc_elf_file_init(elf_file);
}

Expand Down Expand Up @@ -671,6 +711,71 @@ static int find_debug_via_symfs(Elf *e, const char *path,
return -1;
}

#ifdef HAVE_LIBLZMA

#define LZMA_MIN_BUFFER_SIZE 4096
#define LZMA_MEMLIMIT (128 * 1024 * 1024)
static int open_mini_debug_info_file(void *gnu_debugdata,
size_t gnu_debugdata_size,
struct bcc_elf_file *out) {
void *decompressed = NULL;
void *new_decompressed = NULL;
size_t decompressed_data_size = 0;
size_t decompressed_buffer_size = 0;
lzma_stream stream = LZMA_STREAM_INIT;
lzma_ret ret;

ret = lzma_stream_decoder(&stream, LZMA_MEMLIMIT, 0);
if (ret != LZMA_OK)
return -1;

stream.next_in = gnu_debugdata;
stream.avail_in = gnu_debugdata_size;
stream.avail_out = 0;

while (ret == LZMA_OK && stream.avail_in > 0) {
if (stream.avail_out < LZMA_MIN_BUFFER_SIZE) {
decompressed_buffer_size += LZMA_MIN_BUFFER_SIZE;
new_decompressed = realloc(decompressed, decompressed_buffer_size);
if (new_decompressed == NULL) {
ret = LZMA_MEM_ERROR;
break;
}

decompressed = new_decompressed;
stream.avail_out += LZMA_MIN_BUFFER_SIZE;
stream.next_out = decompressed + decompressed_data_size;
}
ret = lzma_code(&stream, LZMA_FINISH);
decompressed_data_size = decompressed_buffer_size - stream.avail_out;
}
lzma_end(&stream);

if (ret != LZMA_STREAM_END ||
bcc_elf_file_open_buf(decompressed, decompressed_data_size, out)) {
free(decompressed);
return -1;
}

return 0;
}

// Returns 0 on success, otherwise nonzero.
// If successfull, 'out' param is a valid bcc_elf_file.
// Caller is responsible for calling bcc_elf_file_close when done using it.
// See https://sourceware.org/gdb/onlinedocs/gdb/MiniDebugInfo.html
static int find_debug_via_mini_debug_info(Elf *elf, struct bcc_elf_file *out) {
Elf_Data *gnu_debugdata;

gnu_debugdata = get_section_elf_data(elf, ".gnu_debugdata");
if (gnu_debugdata == NULL)
return -1;

return open_mini_debug_info_file(gnu_debugdata->d_buf, gnu_debugdata->d_size,
out);
}
#endif

#ifdef HAVE_LIBDEBUGINFOD

// Returns 0 on success, otherwise nonzero.
Expand Down Expand Up @@ -717,6 +822,11 @@ static int find_debug_file(Elf *e, const char *path, int check_crc,
if (find_debug_via_debuglink(e, path, check_crc, out) == 0)
return 0;

#ifdef HAVE_LIBLZMA
if (find_debug_via_mini_debug_info(e, out) == 0)
return 0;
#endif

#ifdef HAVE_LIBDEBUGINFOD
if (find_debug_via_debuginfod(e, out) == 0)
return 0;
Expand Down
30 changes: 28 additions & 2 deletions tests/cc/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -47,24 +47,50 @@ set(TEST_LIBBCC_SOURCES
file(COPY dummy_proc_map.txt DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
add_library(usdt_test_lib SHARED usdt_test_lib.cc)

if(LIBLZMA_FOUND)
add_custom_command(OUTPUT libusdt_test_lib.so.xz
COMMAND xz
ARGS -k ${CMAKE_CURRENT_BINARY_DIR}/libusdt_test_lib.so
DEPENDS usdt_test_lib)
add_custom_target(libusdt_test_lib_so_xz DEPENDS libusdt_test_lib.so.xz)
add_custom_command(OUTPUT with_gnu_debugdata.so
COMMAND ${CMAKE_OBJCOPY}
ARGS --add-section .gnu_debugdata=${CMAKE_CURRENT_BINARY_DIR}/libusdt_test_lib.so.xz
--remove-section '.dynsym'
--strip-symbol 'lib_probed_function'
${CMAKE_CURRENT_BINARY_DIR}/libusdt_test_lib.so
${CMAKE_CURRENT_BINARY_DIR}/with_gnu_debugdata.so
DEPENDS libusdt_test_lib_so_xz)
add_custom_target(with_gnu_debugdata_so DEPENDS with_gnu_debugdata.so)
endif(LIBLZMA_FOUND)

if(NOT CMAKE_USE_LIBBPF_PACKAGE)
add_executable(test_libbcc ${TEST_LIBBCC_SOURCES})
add_dependencies(test_libbcc bcc-shared)
if(LIBLZMA_FOUND)
add_dependencies(test_libbcc with_gnu_debugdata_so)
endif(LIBLZMA_FOUND)

target_link_libraries(test_libbcc ${PROJECT_BINARY_DIR}/src/cc/libbcc.so dl usdt_test_lib)
set_target_properties(test_libbcc PROPERTIES INSTALL_RPATH ${PROJECT_BINARY_DIR}/src/cc)
target_compile_definitions(test_libbcc PRIVATE -DLIBBCC_NAME=\"libbcc.so\")
target_compile_definitions(test_libbcc PRIVATE -DLIBBCC_NAME=\"libbcc.so\"
-DLIB_WITH_MINI_DEBUG_INFO=\"${CMAKE_CURRENT_BINARY_DIR}/with_gnu_debugdata.so\")

add_test(NAME test_libbcc COMMAND ${TEST_WRAPPER} c_test_all sudo ${CMAKE_CURRENT_BINARY_DIR}/test_libbcc)
endif()

if(LIBBPF_FOUND)
add_executable(test_libbcc_no_libbpf ${TEST_LIBBCC_SOURCES})
add_dependencies(test_libbcc_no_libbpf bcc-shared)
if(LIBLZMA_FOUND)
add_dependencies(test_libbcc_no_libbpf with_gnu_debugdata_so)
endif(LIBLZMA_FOUND)


target_link_libraries(test_libbcc_no_libbpf ${PROJECT_BINARY_DIR}/src/cc/libbcc.so dl usdt_test_lib ${LIBBPF_LIBRARIES})
set_target_properties(test_libbcc_no_libbpf PROPERTIES INSTALL_RPATH ${PROJECT_BINARY_DIR}/src/cc)
target_compile_definitions(test_libbcc_no_libbpf PRIVATE -DLIBBCC_NAME=\"libbcc.so\")
target_compile_definitions(test_libbcc_no_libbpf PRIVATE -DLIBBCC_NAME=\"libbcc.so\"
-DLIB_WITH_MINI_DEBUG_INFO=\"${CMAKE_CURRENT_BINARY_DIR}/with_gnu_debugdata.so\")

add_test(NAME test_libbcc_no_libbpf COMMAND ${TEST_WRAPPER} c_test_all_no_libbpf sudo ${CMAKE_CURRENT_BINARY_DIR}/test_libbcc_no_libbpf)
endif()
Expand Down
11 changes: 11 additions & 0 deletions tests/cc/test_c_api.cc
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,17 @@ TEST_CASE("resolve symbol name in external library using loaded libraries", "[c_
bcc_procutils_free(sym.module);
}

#ifdef HAVE_LIBLZMA
TEST_CASE("resolve symbol name via mini debug info", "[c_api]") {
struct bcc_symbol sym;
REQUIRE(bcc_resolve_symname(LIB_WITH_MINI_DEBUG_INFO, "lib_probed_function",
0x0, 0, nullptr, &sym) == 0);
REQUIRE(sym.module[0] == '/');
REQUIRE(sym.offset != 0);
bcc_procutils_free(sym.module);
}
#endif

extern "C" int _a_test_function(const char *a_string) {
int i;
for (i = 0; a_string[i]; ++i)
Expand Down

0 comments on commit 7eef39c

Please sign in to comment.