Search code examples
node.jsvisual-studio-codeelectronvscode-extensionsnode-serialport

How can I determine the ABI version (and other details) of a compiled native module?


I am collaborating on a VSCode extension for which a native module (serialport) is used. in order to make the solution run stable on all platforms and over time as VScode changes electron versions, i would like to include the prebuild native modules.

it now appears that some (not all) of these precompiled modules are not the version they claim to be.

for testing / validation i would like to programmatically determine the ABI version, and if possible the runtime , platform ( darwin , linux , win32 ) and architecture for each of the native modules downloaded by install-prebuild

ie the following error is thrown when I try to load the module in electron 5.0.10 (ABI-70):

Uncaught Error: The module '\\?\C:\develop\NodeJS\electron-serialport\node_modules\@serialport\bindings\lib\binding\node-v70-win32-x64\bindings.node'
was compiled against a different Node.js version using
NODE_MODULE_VERSION 47. This version of Node.js requires
NODE_MODULE_VERSION 70. Please try re-compiling or re-installing
the module (for instance, using `npm rebuild` or `npm install`).

so its reporting ABI 47 while it was downloaded download by prebuild-install as ABI 70. Note: storing electron-bindings in node-bindings locations to allow 'bindings' to detect ABI , platform and architecture

## npx prebuild-install 
Download prebuild native binding for runtime electron : 5.0.5, abi: 70, win32, x64
prebuild-install info begin Prebuild-install version 5.3.0
prebuild-install info looking for cached prebuild @ C:\Users\josverl\AppData\Roaming\npm-cache\_prebuilds\bde028-bindings-v2.0.8-electron-v70-win32-x64.tar.gz
prebuild-install info found cached prebuild
prebuild-install info unpacking @ C:\Users\josverl\AppData\Roaming\npm-cache\_prebuilds\bde028-bindings-v2.0.8-electron-v70-win32-x64.tar.gz
prebuild-install info unpack resolved to C:\develop\NodeJS\electron-serialport\node_modules\@serialport\bindings\build\Release\bindings.node
prebuild-install info install Successfully installed prebuilt binary!

Copy to : 
 -> C:\develop\NodeJS\electron-serialport\noded_modules\@serialport\bindings\lib\binding\node-v70-win32-x64\bindings.node


Solution

  • landed on a decent but partial solution, as I have not been able to find an API or true cross platform solution.

    The closest I could get is by directly using process.dlopen but that still only gives a success or throws an error. the underlying loader does have the information mp->nm_version, but it is only reported though the error.

    So the only way found thus far is to parse that error message. limitation is that this can only work on same platform and CPU architecture as the current runtime, but it is better than nothing at all.

    The below will extract a native module's ABI version by catching the error message and extracting the ABI from that.

    // try to determine the ABI version for a native module
    const getNativeABI = (filename) => {
        var moduleVersion = 0
        try {
            var test = new Module(filename, null);
            process.dlopen(module, filename) //,os.constants.dlopen.RTLD_NOW);
            // if this works the node version is the same 
            moduleVersion = process.versions['modules']
            // but now we need to unload it :-( 
            return moduleVersion
        } catch (error) {
            var match
            var versionRegexp = /NODE_MODULE_VERSION (\d*)./gm
            var platformRegexp = /(is not a valid Win32 application|invalid ELF header|wrong ELF class)/g
            // check for ABI version mismatch 
                // Uncaught Error: The module '..\bindings.node'
                // was compiled against a different Node.js version using
                // NODE_MODULE_VERSION 47. This version of Node.js requires
                // NODE_MODULE_VERSION 70. Please try re-compiling or re-installing
            match = versionRegexp.exec(error.message)
            if (match != null){
                return match[1] // first version is that of the module 
            } 
            // not for valid on this win32 / linux 
            match = platformRegexp.exec(error.message)
            if (match != null){
                // todo: @linux : use error for elfclass to determine architecture :: wrong ELF class: ELFCLASS32
                return 0 // can't validate cross platform
            } 
            // other error 
            console.debug( error.message)
        }
        return moduleVersion // just in case
    }
    

    you'll need to pass in an dummy Module structure.

    /// dummy copy of  internal function
    function Module(id, parent) {
        this.id = id;
        this.exports = {};
        this.parent = parent;
        // updateChildren(parent, this, false);
        this.filename = null;
        this.loaded = false;
        this.children = [];
     }