accellera-official / systemc

SystemC Reference Implementation
https://systemc.org/overview/systemc/
Apache License 2.0
451 stars 145 forks source link

Add support for ASAN to SystemC #17

Closed janweinstock closed 2 years ago

janweinstock commented 2 years ago

Hi all,

please consider this change for addition into SystemC 2.3.4, since currently libsystemc does not work with AddressSanitizer (ASAN), due to the way SC_THREADS are implemented. See below for a more elaborate explanation:

Let's say I have a simple SystemC program and I want to perform a leak check using ASAN:

#include <iostream>
#include <systemc>

class module : public sc_core::sc_module
{
public:
    int* m_ptr;

    module(const sc_core::sc_module_name& nm):
        sc_core::sc_module(nm), m_ptr(new int (0x42)) {
        SC_HAS_PROCESS(module);
        SC_THREAD(thread0);
    }

    virtual ~module() {
        // oops: we leak m_ptr
    }

    void thread0() {
        while (true) {
            std::cout << sc_core::sc_time_stamp() << ": running" << std::endl;
            wait(1, sc_core::SC_SEC);
        }
    }
};

int sc_main(int argc, char** argv) {
    module m("test");
    sc_start(10, sc_core::SC_SEC);
    return 0;
}

Compiling this with -fsanitize=address should tell us that we are leaking memory, but with our current SystemC implementation, ASAN just crashes while checking the memory allocated for the stack of thread0. This happens, because we mprotect the last page of the stack to guard against stack overflows. However, even at the end of the program (when ASAN performs a final check for leaks) mprotect is still in effect, since thread0 never returned and therefore no cleanup was performed. ASAN now assumes it always has read/write permissions on malloc'ed memory areas and walks into the red-zone and... dies. To avoid this, I propose to allocate the stacks for SC_THREADs using mmap instead of malloc, which makes ASAN not walk blindly into our protected memory and actually do its job:

=2988248==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 4 byte(s) in 1 object(s) allocated from:
    #0 0x7fd3f6efa947 in operator new(unsigned long) (/lib/x86_64-linux-gnu/libasan.so.5+0x10f947)
    #1 0x55def03db13e in module::module(sc_core::sc_module_name const&) /home/jan/systemc/systemc.cpp:10
    #2 0x55def03da714 in sc_main /home/jan/systemc/systemc.cpp:30
    #3 0x7fd3f6d0a746 in sc_elab_and_sim /home/jan/systemc/systemc/src/sysc/kernel/sc_main_main.cpp:89
    #4 0x7fd3f6d0a53a in main /home/jan/systemc/systemc/src/sysc/kernel/sc_main.cpp:36
    #5 0x7fd3f677d0b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)

Now, to make ASAN detect leaks from within SC_THREADs (e.g. if we were leaking something inside thread0), we need to do a little more work. Essentially what we are required to do is tell ASAN whenever we are switching stacks, more details on that can be found in its API documentation. This functionality is also present in the PR, and it is written in a way so that it always "just works", no matter if you compiled libsystemc with -fsanitize=address or not.

janweinstock commented 2 years ago

Scheduled for 2.3.4 - closing