Skip to content

Instantly share code, notes, and snippets.

@0xdevalias
Last active May 28, 2024 05:24
Show Gist options
  • Save 0xdevalias/428e56a146e3c09ec129ee58584583ba to your computer and use it in GitHub Desktop.
Save 0xdevalias/428e56a146e3c09ec129ee58584583ba to your computer and use it in GitHub Desktop.
Debugging Electron Apps (and related memory issues)

Debugging Electron Apps (and related memory issues)

Table of Contents

Unsorted


eg.

Open the electron app, and enable debugging for the main electron process:

open /Applications/Beeper.app --args --inspect=1337

Connect to this debug process from chrome://inspect/#devices

Run the following in the DevTools console:

const _electron = require('electron')

// Get "Array of ProcessMetric objects that correspond to memory and CPU usage statistics of all the processes associated with the app."
// https://www.electronjs.org/docs/latest/api/app#appgetappmetrics
// https://www.electronjs.org/docs/latest/api/structures/process-metric
// https://www.electronjs.org/docs/latest/api/structures/memory-info
_electron.app.getAppMetrics()
// Looking at the PID's of the object returned, and contrasting them against the Process Name in macOS' Activity Monitor:
//  [0] with `type: 'Browser'` seems to correspond to the main 'Beeper' process
//  [1] with `type: 'GPU', serviceName: 'GPU'` seems to correspond to the 'Beeper Helper (GPU)' process
//  [2] with `type: 'Utility', serviceName: 'network.mojom.NetworkService', name: 'Network Service'` seems to correspond to the 'Beeper Helper' process
//  [3] with `type: 'Tab'` seems to correspond to the 'Beeper Helper (Renderer)' process

// Open devtools for the first webContents (can do similar for others if there are more than one listed)
_electron.webContents.getAllWebContents()[0].openDevTools()

// Find the webContents for the main Beeper app
const beeperWebContents = _electron.webContents.getAllWebContents().find(wc => wc.mainFrame.origin === 'nova://nova-web')

// Access the .mainFrame
beeperWebContents.mainFrame

// View the preload paths
beepereWebContents._getPreloadPaths()
// [
//   '/Applications/Beeper.app/Contents/Resources/app.asar/node_modules/@sentry/electron/preload/index.js',
//   '/Applications/Beeper.app/Contents/Resources/app.asar/lib/preload.js'
// ]

Extract the *.asar bundle code:

⇒ npm install -g @electron/asar

⇒ nodenv rehash

⇒ asar -h
Usage: asar [options] [command]

Manipulate asar archive files

Options:
  -V, --version                         output the version number
  -h, --help                            display help for command

Commands:
  pack|p [options] <dir> <output>       create asar archive
  list|l [options] <archive>            list files of asar archive
  extract-file|ef <archive> <filename>  extract one file from archive
  extract|e <archive> <dest>            extract archive
  *
  help [command]                        display help for command
  
⇒ ls /Applications/Beeper.app/Contents/Resources/*.asar
/Applications/Beeper.app/Contents/Resources/app.asar
/Applications/Beeper.app/Contents/Resources/webapp.asar

⇒ asar extract /Applications/Beeper.app/Contents/Resources/app.asar ~/Desktop/Beeper-app.asar

⇒ asar extract /Applications/Beeper.app/Contents/Resources/webapp.asar ~/Desktop/Beeper-webapp.asar

See Also

My Other Related Deepdive Gist's and Projects

@0xdevalias
Copy link
Author

0xdevalias commented Jul 16, 2023

Using asar's CLI, we can 're-pack' a *.asar file such that all of it's included files are outside of the actual *.asar file in the *.asar.unpacked directory (eg. to make it easier to inspect/inject into/manipulate the script files while exploring the app) as follows:

⇒ npx asar extract app.asar app-unpacked

⇒ npx asar pack app-unpacked app-all-unpacked.asar --unpack-dir "{**/*,**/.*}"

⇒ npx asar list --is-pack app4.asar

The relevant help for these commands is as follows:

npx asar extract -h
Usage: asar extract|e [options] <archive> <dest>

extract archive

Options:
  -h, --help  display help for command
 ⇒ npx asar pack -h
Usage: asar pack|p [options] <dir> <output>

create asar archive

Options:
  --ordering <file path>     path to a text file for ordering contents
  --unpack <expression>      do not pack files matching glob <expression>
  --unpack-dir <expression>  do not pack dirs matching glob <expression> or starting with
                             literal <expression>
  --exclude-hidden           exclude hidden files
  -h, --help                 display help for command
⇒ npx asar list -h
Usage: asar list|l [options] <archive>

list files of asar archive

Options:
  -i, --is-pack  each file in the asar is pack or unpack
  -h, --help     display help for command

We could also use asar programmatically if we didn't want to use the CLI:


Here is more about the asar format:

  • https://github.com/electron/asar#format
    • Asar uses Pickle to safely serialize binary value to file.

    • The format of asar is very flat:

      • | UInt32: header_size | String: header | Bytes: file1 | ... | Bytes: file42 |
      • The header_size and header are serialized with Pickle class, and header_size's Pickle object is 8 bytes.
      • The header is a JSON string, and the header_size is the size of header's Pickle object.
      • offset and size records the information to read the file from archive, the offset starts from 0 so you have to manually add the size of header_size and header to the offset to get the real offset of the file.
      • offset is a UINT64 number represented in string, because there is no way to precisely represent UINT64 in JavaScript Number. size is a JavaScript Number that is no larger than Number.MAX_SAFE_INTEGER, which has a value of 9007199254740991 and is about 8PB in size. We didn't store size in UINT64 because file size in Node.js is represented as Number and it is not safe to convert Number to UINT64.
      • integrity is an object consisting of a few keys:
        • A hashing algorithm, currently only SHA256 is supported.
        • A hex encoded hash value representing the hash of the entire file.
        • An array of hex encoded hashes for the blocks of the file. i.e. for a blockSize of 4KB this array contains the hash of every block if you split the file into N 4KB blocks.
        • A integer value blockSize representing the size in bytes of each block in the blocks hashes above

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment