Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LoadBrowserProcessSpecificV8Snapshot fuse yields a crashing package #8797

Closed
t3chguy opened this issue Jan 22, 2025 · 10 comments
Closed

LoadBrowserProcessSpecificV8Snapshot fuse yields a crashing package #8797

t3chguy opened this issue Jan 22, 2025 · 10 comments

Comments

@t3chguy
Copy link
Contributor

t3chguy commented Jan 22, 2025

[0122/170408.987:FATAL:v8_initializer.cc(687)] Error loading V8 startup snapshot file

Digging deeper, LoadBrowserProcessSpecificV8Snapshot wants a browser_v8_context_snapshot.bin but this does not exist in the build

Image

Copying v8_context_snapshot.bin to browser_v8_context_snapshot.bin seems to resolve the issue

@mmaietta
Copy link
Collaborator

Hi there!
Missing some key details here, can you please list the following?
electron-builder version, electron version, and whether you're using elecron/fuses directly or via electronFuses config property.

electron-builder just unpacks an electron release artifact, so my initial guess here is that the browser_v8_context_snapshot file isn't in the release artifact. electron-builder is not supposed to create it AFAIK as electron-builder is a packager, not a bundler.

@charliez0
Copy link

I meet this problem too, all latest version, changing fuses with @elecron/fuses

@mmaietta
Copy link
Collaborator

Pretty sure you're supposed to compile/build/provide your OWN browser snapshot in order to use that fuse as it's specific to your own installed (or forked) electron version.
https://www.electronjs.org/docs/latest/tutorial/fuses#loadbrowserprocessspecificv8snapshot

Copying v8_context_snapshot.bin to browser_v8_context_snapshot.bin seems to resolve the issue

Doing this does nothing different than having LoadBrowserProcessSpecificV8Snapshot: false as all you're doing under-the-hood is forcing electron to load two identical snapshots, versus its default of loading one snapshot for both main and browser.

Overall, this isn't a bug, it's an end-user misconfiguration. Happy to be proven otherwise, but I'm not finding any resources on how to compile a xxxx_snapshot.bin

@t3chguy
Copy link
Contributor Author

t3chguy commented Jan 23, 2025

Doing this does nothing different than having LoadBrowserProcessSpecificV8Snapshot: false as all you're doing under-the-hood is forcing electron to load two identical snapshots, versus its default of loading one snapshot for both main and browser.

The fuses page says separate snapshots can also improve security

Using separate snapshots for renderer processes and the main process can improve security

Which could be construed as a form of scope isolation but agreed the docs are lacking

@t3chguy
Copy link
Contributor Author

t3chguy commented Jan 23, 2025

I think ultimately this issue is about https://www.electron.build/tutorials/adding-electron-fuses.html#loadbrowserprocessspecificv8snapshot not being clear, it looks like something you can just enable

@mmaietta
Copy link
Collaborator

Let me ask the electron HQ folks and get back to you on this. I see a way electron-builder could auto-copy the default snapshot (for the security hardening between main and renderer) while also allowing a user-supplied one to be copied instead. I need to see first if they're okay with that.

@RaisinTen
Copy link

Hi! I did some experiments to speed up Electron app startup time using V8 snapshots in https://github.com/RaisinTen/electron-snapshot-experiment, so y'all might find that useful. If you browse the repo at https://github.com/RaisinTen/electron-snapshot-experiment/tree/a3bf2c070d2aa72bf17720c1e7fdc95970c1a3ca, you can find out more about how to use the LoadBrowserProcessSpecificV8Snapshot fuse.

@mmaietta
Copy link
Collaborator

Here's a snippet of the response from Electron dev chat

So it sounds like, to utilize the fuse, the developer should be providing the browser v8 snapshot themselves? From the electron-builder config side, instead of a boolean property, have it as a filepath string and only if that is provided, copy it and activate the fuse.

Yes, it’s provided by the developer.
And in particular, the developer might provide two snapshots: one for the browser and one for the main process.

What this means is that I'll need to change the property LoadBrowserProcessSpecificV8Snapshot from boolean to be an object of:

loadBrowserProcessSpecificV8Snapshot?: {
	mainProcessSnapshotPath?: string
	browserProcessSnapshotPath: string
}

electron-builder will handle the copying and throw an error if the file can't be found. If the snapshot files are successfully copied, then the fuse is activated. I'll probably also log a warning that mainProcessSnapshotPath should be provided to most efficiently leverage the fuse

@mmaietta
Copy link
Collaborator

mmaietta commented Jan 25, 2025

TLDR;
I've come to realize that I don't think electron-builder should automate this. Suppose you're already providing your own custom V8 snapshot for your build (which appears to be a very complex/advanced process). In that case, it's a small hop to leveraging the afterPack hook for manually copying in the correct snapshot file.


Long version:
There's a complication as I've learned that the snapshots are both OS+arch specific, so that means the config property in electron-builder for each platform would need to have its own LoadBrowserProcessSpecificV8Snapshot object. This is significantly complicated further by the fact that MacOS snapshot filenames include their arch in order to support universal builds.

I'm not sure there's an easy path forward without dramatically increasing the complexity of electron-builder's already-complex config. There are too many permutations to handle:

  • For mac, MacOptions would require browserV8SnapshotX64 and browserV8SnapshotArm64, and while only one needs to be provided for a single-arch build, both properties need to be provided for universal builds
  • For other platforms, their config object requires browserV8Snapshot<ARCH> for each arch supported by electron-builder.
  • If those config properties were provided, but electron fuses config is not provided, then don't copy the custom snapshots...or maybe we do and just activate that fuse? This flow is ambiguous.
  • For MacOS universal builds, don't flip the fuse for each arm64/x64 build, but still copy the snapshots during each arch's doPack

Then, for each entry of browserV8Snapshot<ARCH>, there needs to be a corresponding mainV8Snapshot<ARCH> to properly leverage the fuse

All this needs to happen before the afterPack hook, while flipping the fuses needs to happen after afterPack and before signing


The mac V8 snapshots seem to always be here within Electron Framework.framework (unless you using using custom framework branding).
arm64:

electron-quick-start-typescript.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/v8_context_snapshot.arm64.bin

Following this up with another post for the psuedo-ish code for implementing this on your side.

At minimum, I'll update the docs to make this more clear of a requirement to leverage the fuse.

@mmaietta
Copy link
Collaborator

mmaietta commented Jan 25, 2025

Here's a code snippet to insert into an afterPack hook for the next developer that comes across this GH issue. Written on the fly with typescript to make it easier to understand intent, untested, please use at your discretion after converting it to javascript.

export default (packContext: AfterPackContext) => {
   const appContentsDir = this.platform === Platform.MAC ? path.join(packContext.appOutDir, YOUR_APP_NAME.app, "Contents") : packContext.appOutDir
  const snapshotDestination = (filename: string) => {
    if (["mas", "darwin"].includes(packContext.electronPlatformName)) {
      return path.resolve(appContentsDir, `/Frameworks/Electron Framework.framework/Versions/A/Resources/${filename}`)
    }
    return path.resolve(appContentsDir, filename)
  }

  const copySnapshotFile = async (filePath: string) => {
    const snapshot = await packContext.packager.getResource(filePath)
    await fsExtra.copyFile(snapshot!, snapshotDestination(path.basename(filePath)))
  }

  const snapshotToPlatformToArchMap = {
    darwin: {
      x64: {
        browser: "relative relative path to browser_v8_context_snapshot.x86_64.bin",
        main: "relative relative path to v8_context_snapshot.x86_64.bin"
      },
      arm64: {
        browser: "relative path to browser_v8_context_snapshot.arm64.bin",
        main: "relative path to v8_context_snapshot.arm64.bin"
      },
    },
    win: {
      ia32: {
        browser: "relative path to browser_v8_context_snapshot.bin",
        main: "relative path to v8_context_snapshot.bin"
      },
      x64: {
        browser: "relative path to browser_v8_context_snapshot.bin",
        main: "relative path to v8_context_snapshot.bin"
      },
      arm64: {
        browser: "relative path to browser_v8_context_snapshot.bin",
        main: "relative path to v8_context_snapshot.bin"
      },
    },
    linux: {
      // more stuff like above
    }
  }

  const paths = snapshotToPlatformToArchMap[packContext.electronPlatformName][packContext.arch]
  await copySnapshotFile(paths.browser)
  await copySnapshotFile(paths.main)
}

Closing this GH issue for now, but happy to keep discussing

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants