skip to Main Content

I am trying to simulate a memory leak problem with the following code and then investigate the system calls resulting in memory leak.

include <iostream>
#include <thread>
#include <chrono>
#include <mutex>
#include <ctime>

class SharedObject {
public:
    std::string currentTime;

    SharedObject() {
        // Capture current time as string
        auto now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
        currentTime = std::ctime(&now); // Convert time to string
    }

    void displayTime() {
        std::cout << "Current Time: " << currentTime;
    }
};

void threadFunction(const std::string& threadName, int totalIterations) {
    for (int iteration = 1; iteration <= totalIterations; ++iteration) {
        SharedObject* obj = new SharedObject(); // Create object
        std::cout << threadName << " created an object at iteration " << iteration << std::endl;
        obj->displayTime();

        // Every 100 iterations, forget to delete the object (simulating memory leak)
        if (iteration % 2 == 1) {
            std::cout << threadName << " forgot to delete the object at iteration " << iteration << std::endl;
        } else {
            delete obj; // Delete object
            std::cout << threadName << " deleted the object at iteration " << iteration << std::endl;
        }

        // Sleep for 512 milliseconds
        std::this_thread::sleep_for(std::chrono::milliseconds(512));
    }
    std::cout << threadName << " completed all iterations.n";
}int main() {
    const int totalIterations = 100000;

    // Launch two threads
    std::thread thread1(threadFunction, "Thread 1", totalIterations);
    std::thread thread2(threadFunction, "Thread 2", totalIterations);

    // Wait for threads to finish
    thread1.join();
    thread2.join();

    std::cout << "Both threads completed execution.n";
    return 0;
}

I have compiled and running the program in Linux soumajit-HP-Pavilion-Desktop-590-p0xxx 5.4.0-150-generic #167~18.04.1-Ubuntu SMP Wed May 24 00:51:42 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux system

Post that I am trying to trace malloc and free system call using strace.
Following is the output

[pid 18031] <... stat resumed> {st_mode=S_IFREG|0644, st_size=312, ...}) = 0
[pid 18032] write(1, "Current Time: Tue Jan 14 23:08:3"..., 39) = 39
[pid 18031] futex(0x7fcf11c08c20, FUTEX_WAKE_PRIVATE, 1 <unfinished ...>
[pid 18032] write(1, "Thread 2 deleted the object at i"..., 46) = 46
[pid 18031] <... futex resumed> )       = 0
[pid 18032] nanosleep({tv_sec=0, tv_nsec=512000000},  <unfinished ...>
[pid 18031] write(1, "Thread 1 created an object at it"..., 45) = 45
[pid 18031] write(1, "Current Time: Tue Jan 14 23:08:3"..., 39) = 39
[pid 18031] write(1, "Thread 1 forgot to delete the ob"..., 55) = 55
[pid 18031] nanosleep({tv_sec=0, tv_nsec=512000000},  <unfinished ...>
[pid 18032] <... nanosleep resumed> 0x7fcf10c7ace0) = 0
[pid 18032] stat("/etc/localtime",  <unfinished ...>
[pid 18031] <... nanosleep resumed> 0x7fcf1147bce0) = 0
[pid 18032] <... stat resumed> {st_mode=S_IFREG|0644, st_size=312, ...}) = 0
[pid 18031] futex(0x7fcf11c08c20, FUTEX_WAIT_PRIVATE, 2, NULL <unfinished ...>
[pid 18032] futex(0x7fcf11c08c20, FUTEX_WAKE_PRIVATE, 1 <unfinished ...>
[pid 18031] <... futex resumed> )       = -1 EAGAIN (Resource temporarily unavailable)
[pid 18032] <... futex resumed> )       = 0
[pid 18031] stat("/etc/localtime",  <unfinished ...>
[pid 18032] write(1, "Thread 2 created an object at it"..., 45 <unfinished ...>
[pid 18031] <... stat resumed> {st_mode=S_IFREG|0644, st_size=312, ...}) = 0
[pid 18032] <... write resumed> )       = 45
[pid 18032] write(1, "Current Time: Tue Jan 14 23:08:3"..., 39 <unfinished ...>
[pid 18031] futex(0x7fcf11c08c20, FUTEX_WAKE_PRIVATE, 1 <unfinished ...>
[pid 18032] <... write resumed> )       = 39
[pid 18031] <... futex resumed> )       = 0
[pid 18032] write(1, "Thread 2 forgot to delete the ob"..., 55 <unfinished ...>
[pid 18031] futex(0x7fcf11c088c0, FUTEX_WAIT_PRIVATE, 2, NULL <unfinished ...>
[pid 18032] <... write resumed> )       = 55
[pid 18032] futex(0x7fcf11c088c0, FUTEX_WAKE_PRIVATE, 1) = 1
[pid 18031] <... futex resumed> )       = 0
[pid 18032] nanosleep({tv_sec=0, tv_nsec=512000000},  <unfinished ...>
[pid 18031] futex(0x7fcf11c088c0, FUTEX_WAKE_PRIVATE, 1) = 0
[pid 18031] write(1, "Thread 1 created an object at it"..., 45) = 45
[pid 18031] write(1, "Current Time: Tue Jan 14 23:08:3"..., 39) = 39
[pid 18031] write(1, "Thread 1 deleted the object at i"..., 46) = 46
[pid 18031] nanosleep({tv_sec=0, tv_nsec=512000000}, 

Why does the memory allocation and de-allocation calls not captured ?

[![ldd binary][1]][1] [![ldd binary and ltrace version][1]][1]

enter image description here

2

Answers


  1. strace is short for "system call/signal trace". There are some system calls related to memory management (sbrk/brk/mmap) but those are low-level and not useful to debug memory leaking issues.

    If you want to trace memory leaks you can use something like the AddressSanitizer available in g++/clang++:

    $ g++ -fsanitize=address -g -o code code.cc
    $ ./code
    ...
    Thread 2 completed all iterations.
    Thread 1 completed all iterations.
    Both threads completed execution.
    
    =================================================================
    ==1153114==ERROR: LeakSanitizer: detected memory leaks
    
    Direct leak of 320 byte(s) in 10 object(s) allocated from:
        #0 0x75f22d59e548 in operator new(unsigned long) ../../../../src/libsanitizer/asan/asan_new_delete.cpp:95
        #1 0x56ef3081f76d in threadFunction(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, int) /tmp/x.cc:24
        #2 0x56ef30822e7a in void std::__invoke_impl<void, void (*)(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, int), char const*, int>(std::__invoke_other, void (*&&)(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, int), char const*&&, int&&) /usr/include/c++/13/bits/invoke.h:61
        #3 0x56ef30822c4b in std::__invoke_result<void (*)(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, int), char const*, int>::type std::__invoke<void (*)(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, int), char const*, int>(void (*&&)(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, int), char const*&&, int&&) /usr/include/c++/13/bits/invoke.h:96
        #4 0x56ef30822b6a in void std::thread::_Invoker<std::tuple<void (*)(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, int), char const*, int> >::_M_invoke<0ul, 1ul, 2ul>(std::_Index_tuple<0ul, 1ul, 2ul>) /usr/include/c++/13/bits/std_thread.h:292
        #5 0x56ef30822b03 in std::thread::_Invoker<std::tuple<void (*)(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, int), char const*, int> >::operator()() /usr/include/c++/13/bits/std_thread.h:299
        #6 0x56ef30822ae3 in std::thread::_State_impl<std::thread::_Invoker<std::tuple<void (*)(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, int), char const*, int> > >::_M_run() /usr/include/c++/13/bits/std_thread.h:244
        #7 0x75f22d30edb3  (/lib/x86_64-linux-gnu/libstdc++.so.6+0xecdb3) (BuildId: ca77dae775ec87540acd7218fa990c40d1c94ab1)
        #8 0x75f22d4fea41 in asan_thread_start ../../../../src/libsanitizer/asan/asan_interceptors.cpp:234
        #9 0x75f22cf95a93 in start_thread nptl/pthread_create.c:447
    

    (I limited the number of iterations to 10)

    More info here.

    Login or Signup to reply.
  2. Why does the memory allocation and de-allocation calls not captured
    in strace output ?

    You don’t see malloc() and free() because they are not
    syscalls but wrappers around syscalls
    implemented by libc. See for example how it is implemented in musl
    libc
    (which is much easier to read than the infamous glibc):

    __syscall(SYS_brk, brk+req)
    

    If you want to analyze memory leaks in your program without
    recompiling it you could use valgrind although
    you’d get better results if you compiled the executable with -g.

    If you want to see calls to malloc() and free() you can use
    ltrace, for example with this:

    #include <stdlib.h>
    
    int main(void)
    {
      int *p = malloc(1024);
      free(p);
    }
    

    Build without optimizations:

    $ gcc main.c -o main
    

    Use ltrace:

    $ ltrace ./main
    malloc(1024)                                                                                                                       = 0x401662a0
    free(0x401662a0)                                                                                                                   = <void>
    +++ exited (status 0) +++
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search