Skip to content

Memory Leak Detection by LSAN in Single-threaded Context with OpenMP Involvement #671

@MetalOxideSemi

Description

@MetalOxideSemi

Hi,
I am currently using Codon-compiled shared library (.so) in a single-threaded context, LSAN (Leak Sanitizer) reports memory leaks originating from libomp.so, despite explicitly setting GC_set_markers_count to 1 before loading the shared library.

I wanna use Codon-compiled so's with dlopen/dlsym, seems I have to call main in the so to get something initialized, but it seems to be a mistake. Calling main introduced the leak.

Codon Version: v0.18.2

Reduced reproduce sample:

Host

#include <iostream>
#include <fstream>
#include <memory>
#include <sstream>
#include <vector>
#include <dlfcn.h>
#include <unistd.h>

#ifndef LD_BIN
#define LD_BIN "ld.lld-19"
#endif
#ifndef CODON_PATH
#define CODON_PATH "/home/metaloxide/doc/codon"
#endif
#define CODON_BIN            CODON_PATH "/bin/codon"
#define CODON_LIB_PATH       CODON_PATH "/lib"
#define CODON_LIB_INNER_PATH CODON_LIB_PATH "/codon"
#define CODON_RUNTIME_PATH   CODON_LIB_INNER_PATH "/libcodonrt.so"

std::pair<std::string, int> exec(char const* cmd) {
  // 1 GB
  std::unique_ptr<char[]> buffer =
    std::make_unique<char[]>(1 * 1024 * 1024 * 1024);
  std::string result;
  std::unique_ptr<FILE, decltype(&pclose)> pipe(popen(cmd, "r"), &pclose);

  if (!pipe) {
    throw std::runtime_error("popen() failed");
  }

  while (fgets(buffer.get(), 1 * 1024 * 1024 * 1024, pipe.get()) != nullptr) {
    result += buffer.get();
  }

  // The pclose will be automatically called when pipe goes out of scope
  int status = WEXITSTATUS(pipe.get() ? pclose(pipe.release()) : -1);
  return {result, status};
}

struct BoehmGCHandlers {
  std::unique_ptr<void, std::integral_constant<decltype(&dlclose), &dlclose>>
    rt;
  void (*GC_set_markers_count)(unsigned) = {};
  void (*GC_gcollect_and_unmap)() = {};
  void (*GC_clear_roots)() = {};
  void (*GC_deinit)() = {};
  size_t (*GC_get_memory_use)() = {};
};

static BoehmGCHandlers* bdwgc_handlers = nullptr;

BoehmGCHandlers config_boehm_gc() {
  std::cout << CODON_RUNTIME_PATH << std::endl;
  void* codonrt = dlopen(CODON_RUNTIME_PATH, RTLD_LAZY);
  if (!codonrt) {
    throw std::runtime_error("dlopen() failed");
  }
  void (*GC_set_markers_count)(unsigned) =
    (void (*)(unsigned)) dlsym(codonrt, "GC_set_markers_count");
  GC_set_markers_count(1);
  BoehmGCHandlers handlers;
  handlers.rt.reset(codonrt);
  handlers.GC_set_markers_count = GC_set_markers_count;
  handlers.GC_gcollect_and_unmap =
    (void (*)()) dlsym(codonrt, "GC_gcollect_and_unmap");
  handlers.GC_clear_roots = (void (*)()) dlsym(codonrt, "GC_clear_roots");
  handlers.GC_deinit = (void (*)()) dlsym(codonrt, "GC_deinit");
  handlers.GC_get_memory_use =
    (size_t (*)()) dlsym(codonrt, "GC_get_memory_use");
  return handlers;
}

int main() {
  BoehmGCHandlers codonrt = config_boehm_gc();
  bdwgc_handlers = &codonrt;
  std::string compile_cmd;
  std::string name = "sized_rang_tagged_pointer_8_32";
  std::string link_cmd;
  {
    std::ostringstream cmdos;
    std::unique_ptr<char, std::integral_constant<decltype(&free), &free>>
      cwd_buf {getcwd(nullptr, 0)};
    char* cwd = cwd_buf.get();
    cmdos << CODON_BIN << " ";
    cmdos << "build --relocation-model=pic ";
    cmdos << "-o ";
    cmdos << cwd << "/" << name << ".codon.o ";
    cmdos << cwd << "/" << name << ".codon.py";
    std::string cmd1 = cmdos.str();
    cmdos = std::ostringstream{};
    cmdos << LD_BIN << " ";
    cmdos << cwd << "/" << name << ".codon.o ";
    cmdos << "-shared ";
    cmdos << "-L" << CODON_LIB_PATH << " ";
    cmdos << "-rpath " << CODON_LIB_PATH << " ";
    cmdos << "-L" << CODON_LIB_INNER_PATH << " ";
    cmdos << "-rpath " << CODON_LIB_INNER_PATH << " ";
    cmdos << "-lcodonrt ";
    cmdos << "-o ";
    cmdos << cwd << "/" << name << ".codon.so";
    std::string cmd2 = cmdos.str();
    std::cout << "cmd1: " << cmd1 << std::endl;
    std::cout << "cmd2: " << cmd2 << std::endl;
    compile_cmd = std::move(cmd1);
    link_cmd = std::move(cmd2);
  }
  auto compile_pair = exec(compile_cmd.c_str());
  if (compile_pair.second != 0) {
    std::cerr << "compile failed: " << compile_pair.first << std::endl;
    exit(1);
  }
  std::cout << "compile output: " << compile_pair.first << std::endl;
  auto link_pair = exec(link_cmd.c_str());
  if (link_pair.second != 0) {
    std::cerr << "link failed: " << link_pair.first << std::endl;
    exit(1);
  }
  std::cout << "link output: " << link_pair.first << std::endl;

  std::unique_ptr<void, std::integral_constant<decltype(&dlclose), &dlclose>>
    dl_handler_u {dlopen(
      ("./" + name + ".codon.so").c_str(),
      RTLD_NOW | RTLD_LOCAL /*| RTLD_DEEPBIND*/
    )};
  void* dl_handler = dl_handler_u.get();
  if (dl_handler == nullptr) {
    std::cerr << "dlopen failed: " << dlerror() << std::endl;
    exit(1);
  }
  std::cout << "dlopen output: " << dl_handler << std::endl;

  void (*test_main)(int, char**) =
    (void (*)(int, char**)) dlsym(dl_handler, "main");
  if (test_main == nullptr) {
    std::cerr << "dlsym failed: " << dlerror() << std::endl;
    exit(1);
  }
  std::cout << "dlsym output (main): " << (void*) (test_main) << std::endl;

  std::vector<char> mock_argv = {'t', 'e', 's', 't', '\0'};
  char* mock_argvp[1] = {mock_argv.data()};
  // Called main manually, please note here
  test_main(1, mock_argvp);

  bdwgc_handlers->GC_clear_roots();
  bdwgc_handlers->GC_gcollect_and_unmap();
}
# sized_rang_tagged_pointer_8_32.py (Actually could be anything)
@export
def do_test_0(dump: Ptr[i8]) -> int:
  print("111")
  return 0

@export
def do_test_1(dump: Ptr[i8]) -> int:
  print("222")
  return 0

@export
def do_test_2(dump: Ptr[i8]) -> int:
  print("333")
  return 0

After

clang++-19 /home/metaloxide/doc/temp/reduced.cpp -std=c++14 -fsanitize=address -o reduced && ./reduced

I got

=================================================================
==964927==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 2237 byte(s) in 2 object(s) allocated from:
    #0 0x55c9d5877d6f in malloc (/cloudide/workspace/temp/reduced+0xcfd6f) (BuildId: 86fee8bc2cfa74ffb5de092eda4d1ce9b53ddc41)
    #1 0x7fa25bd56767  (<unknown module>)

Direct leak of 48 byte(s) in 1 object(s) allocated from:
    #0 0x55c9d5877f39 in calloc (/cloudide/workspace/temp/reduced+0xcff39) (BuildId: 86fee8bc2cfa74ffb5de092eda4d1ce9b53ddc41)
    #1 0x7fa25bd6b49a  (<unknown module>)

Direct leak of 24 byte(s) in 1 object(s) allocated from:
    #0 0x55c9d5877d6f in malloc (/cloudide/workspace/temp/reduced+0xcfd6f) (BuildId: 86fee8bc2cfa74ffb5de092eda4d1ce9b53ddc41)
    #1 0x7fa25bdeb627  (<unknown module>)

Indirect leak of 65 byte(s) in 1 object(s) allocated from:
    #0 0x55c9d5877d6f in malloc (/cloudide/workspace/temp/reduced+0xcfd6f) (BuildId: 86fee8bc2cfa74ffb5de092eda4d1ce9b53ddc41)
    #1 0x7fa25bdeb5b2  (<unknown module>)

where the unknown modules seem to be in libomp.so, is there any workround to get rid of it?
Thanks a lot

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions