emscripten-core / emscripten

Emscripten: An LLVM-to-WebAssembly Compiler
Other
25.74k stars 3.3k forks source link

dlopen returns incorrectly on subsequent load #21287

Open Mintyboi opened 8 months ago

Mintyboi commented 8 months ago

Please include the following in your bug report:

Version of emscripten/emsdk: emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 3.1.53 (ce5114bdd2175c7297583d3c25a53ca95d22f4ce)

Failing command line in full: emcc main.cpp -s MAIN_MODULE=2 --preload-file %output_path%/side.wasm@side.wasm -o %output_path%\index.html -s ASSERTIONS=0

Full link command and output with -v appended: Even for runtime issues it helps a lot if you can include the full link command. Adding -v to the link command will show all of the sub-commands run which can help us diagnose your issue.

Issue: After loading a corrupted side module the first time, if we happen to load that side module a second time again, it will succeed this time. This is not expected.

main.cpp

int main()
{
    printf(" ********* Test load side module ********* \n");
    auto handle = dlopen("side.wasm", RTLD_LAZY | RTLD_GLOBAL);
    if (handle == nullptr) {
        printf("1st load fail: %s\n", dlerror());
    }

    handle = dlopen("side.wasm", RTLD_LAZY | RTLD_GLOBAL);
    if (handle == nullptr) {
        printf("2nd load fail: %s\n", dlerror());
        return -1;
    }

    printf("2nd load SUCCESS??\n");
    return 0;
}

Result: image

Mintyboi commented 8 months ago

The reason is when we load the corrupted side module the first time, we create a dso object and stores it in the global LDSO object.

var newDSO = (name, handle, syms) => {
      var dso = {
        refcount: Infinity,
        name,
        exports: syms,
        global: true,
      };
      LDSO.loadedLibsByName[name] = dso;
      if (handle != undefined) {
        LDSO.loadedLibsByHandle[handle] = dso;
      }
      return dso;
    };

Although it errors out during the first load, we do not clean up the dso object in the array and on the subsequent load, it directly returns the dso object. Should we clean up the array upon hitting an exception during dlopenInternal? e.g.

try {
    return loadDynamicLibrary(filename, combinedFlags, localScope, handle)
} catch (e) {
    LDSO.loadedLibsByName[filename] = null;
    LDSO.loadedLibsByHandle[handle] = null;
    dlSetError(`Could not load dynamic lib: ${filename}\n${e}`);
    return 0;
}

Although I don't quite know how to deal with the async scenario yet