Skip to content
Draft
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
03e5061
Expose memory mapping; Add optional memfile dump
ValentaTomas Nov 15, 2025
eff49ed
Fix compile errors
ValentaTomas Nov 15, 2025
5cfd4e3
Remove required field from spec
ValentaTomas Dec 3, 2025
523dc12
Fix parameter
ValentaTomas Dec 5, 2025
8769de7
Fix type
ValentaTomas Dec 5, 2025
55b6479
Add upload script
ValentaTomas Dec 6, 2025
1133bd6
Parse without python
ValentaTomas Dec 9, 2025
0bb99c5
Refactor
ValentaTomas Dec 10, 2025
01f3491
Add tests
ValentaTomas Dec 18, 2025
178223a
Fix version
ValentaTomas Dec 18, 2025
dbc5552
fix(test_reboot): do not assert thread count
Manciukic Apr 30, 2025
f1510d5
Limit test suite
ValentaTomas Dec 19, 2025
4129956
Merge branch 'firecracker-v1.12' into firecracker-v1.12-direct-mem
ValentaTomas Dec 19, 2025
8a5491a
Tweak GHA
ValentaTomas Dec 19, 2025
63ed770
Merge branch 'firecracker-v1.12-direct-mem' of https://github.com/e2b…
ValentaTomas Dec 19, 2025
0111337
Change runner
ValentaTomas Dec 19, 2025
6726338
Remove flaky test invocation
ValentaTomas Dec 21, 2025
671febe
Fix build script
ValentaTomas Dec 21, 2025
6ffeddf
Switch to 7 hash chars everywhere
ValentaTomas Dec 21, 2025
1dfe77c
Remove g
ValentaTomas Dec 21, 2025
717921c
Cleanup
ValentaTomas Dec 22, 2025
54a1c1a
api: implement API for dirty memory
bchalios Feb 18, 2026
8fc760f
feat: enable write-protection on guest memory
bchalios Feb 18, 2026
a41d3fb
ci: remove dependency changes test
bchalios Feb 14, 2026
827b783
doc(swagger): add /memory/dirty endpoint definition
bchalios Feb 18, 2026
59b0ad1
doc(swagger): make mem_file_path optional in SnapshotCreateParams
bchalios Feb 18, 2026
210cbac
fix(kvmclock): don't pass KVM_CLOCK_REALTIME to KVM_SET_CLOCK
bchalios Mar 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ test_results/*
/resources/linux
/resources/x86_64
/resources/aarch64
.env
2 changes: 2 additions & 0 deletions .tool-versions
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
gcloud 534.0.0
rust 1.85.0
12 changes: 12 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
-include .env

.PHONY: build
build:
./scripts/build.sh

.PHONY: upload
upload:
./scripts/upload.sh $(GCP_PROJECT_ID)

.PHONY: build-and-upload
make build-and-upload: build upload
Comment thread
jakubno marked this conversation as resolved.
Outdated
Comment thread
jakubno marked this conversation as resolved.
Outdated
4 changes: 4 additions & 0 deletions resources/seccomp/aarch64-unknown-linux-musl.json
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,10 @@
"syscall": "madvise",
"comment": "Used by the VirtIO balloon device and by musl for some customer workloads. It is also used by aws-lc during random number generation. They setup a memory page that mark with MADV_WIPEONFORK to be able to detect forks. They also call it with -1 to see if madvise is supported in certain platforms."
},
{
"syscall": "mincore",
"comment": "Used by get_memory_dirty_bitmap to check if memory pages are resident"
},
{
"syscall": "mmap",
"comment": "Used by the VirtIO balloon device",
Expand Down
10 changes: 7 additions & 3 deletions resources/seccomp/x86_64-unknown-linux-musl.json
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,10 @@
"syscall": "madvise",
"comment": "Used by the VirtIO balloon device and by musl for some customer workloads. It is also used by aws-lc during random number generation. They setup a memory page that mark with MADV_WIPEONFORK to be able to detect forks. They also call it with -1 to see if madvise is supported in certain platforms."
},
{
"syscall": "mincore",
"comment": "Used by get_memory_dirty_bitmap to check if memory pages are resident"
},
{
"syscall": "mmap",
"comment": "Used by the VirtIO balloon device",
Expand Down Expand Up @@ -524,8 +528,8 @@
"comment": "sigaltstack is used by Rust stdlib to remove alternative signal stack during thread teardown."
},
{
"syscall": "getrandom",
"comment": "getrandom is used by `HttpServer` to reinialize `HashMap` after moving to the API thread"
"syscall": "getrandom",
"comment": "getrandom is used by `HttpServer` to reinialize `HashMap` after moving to the API thread"
},
{
"syscall": "accept4",
Expand Down Expand Up @@ -1152,4 +1156,4 @@
}
]
}
}
}
17 changes: 17 additions & 0 deletions scripts/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/bin/bash

set -euo pipefail

# The format will be: v<major>.<minor>.<patch>_g<commit_hash> — e.g. v1.7.2_g8bb88311
Comment thread
jakubno marked this conversation as resolved.
Outdated
# Extract full version from src/firecracker/swagger/firecracker.yaml
FC_VERSION=$(awk '/^info:/{flag=1} flag && /^ version:/{print $2; exit}' src/firecracker/swagger/firecracker.yaml)
commit_hash=$(git rev-parse --short=7 HEAD)
version_name="v${FC_VERSION}_${commit_hash}"
echo "Version name: $version_name"

echo "Starting to build Firecracker version: $version_name"
tools/devtool -y build --release

mkdir -p "./build/fc/${version_name}"
cp ./build/cargo_target/x86_64-unknown-linux-musl/release/firecracker "./build/fc/${version_name}/firecracker"
echo "Finished building Firecracker version: $version_name and copied to ./build/fc/${version_name}/firecracker"
13 changes: 13 additions & 0 deletions scripts/upload.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/bin/bash

set -euo pipefail

GCP_PROJECT_ID=$1

gsutil -h "Cache-Control:no-cache, max-age=0" cp -r "build/fc/*" "gs://${GCP_PROJECT_ID}-fc-versions"
if [ "$GCP_PROJECT_ID" == "e2b-prod" ]; then
# Upload kernel to GCP public builds bucket
gsutil -h "Cache-Control:no-cache, max-age=0" cp -r "build/fc/*" "gs://${GCP_PROJECT_ID}-public-builds/firecrackers/"
fi

rm -rf build/fc/*
1 change: 1 addition & 0 deletions src/cpu-template-helper/src/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ pub fn build_microvm_from_config(
state: VmState::NotStarted,
vmm_version: CPU_TEMPLATE_HELPER_VERSION.to_string(),
app_name: "cpu-template-helper".to_string(),
memory_regions: None,
};
let mut vm_resources =
VmResources::from_json(&config, &instance_info, HTTP_MAX_PAYLOAD_SIZE, None)
Expand Down
4 changes: 2 additions & 2 deletions src/firecracker/src/api_server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ mod tests {
Box::new(VmmAction::CreateSnapshot(CreateSnapshotParams {
snapshot_type: SnapshotType::Diff,
snapshot_path: PathBuf::new(),
mem_file_path: PathBuf::new(),
mem_file_path: Some(PathBuf::new()),
})),
start_time_us,
);
Expand All @@ -288,7 +288,7 @@ mod tests {
Box::new(VmmAction::CreateSnapshot(CreateSnapshotParams {
snapshot_type: SnapshotType::Diff,
snapshot_path: PathBuf::new(),
mem_file_path: PathBuf::new(),
mem_file_path: Some(PathBuf::new()),
})),
start_time_us,
);
Expand Down
50 changes: 50 additions & 0 deletions src/firecracker/src/api_server/parsed_request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use super::request::logger::parse_put_logger;
use super::request::machine_configuration::{
parse_get_machine_config, parse_patch_machine_config, parse_put_machine_config,
};
use super::request::memory::{parse_get_memory, parse_get_memory_mappings};
use super::request::metrics::parse_put_metrics;
use super::request::mmds::{parse_get_mmds, parse_patch_mmds, parse_put_mmds};
use super::request::net::{parse_patch_net, parse_put_net};
Expand Down Expand Up @@ -82,6 +83,14 @@ impl TryFrom<&Request> for ParsedRequest {
Ok(ParsedRequest::new_sync(VmmAction::GetFullVmConfig))
}
(Method::Get, "machine-config", None) => parse_get_machine_config(),
(Method::Get, "memory", None) => match path_tokens.next() {
Some("mappings") => parse_get_memory_mappings(),
None => parse_get_memory(),
_ => Err(RequestError::InvalidPathMethod(
request_uri.to_string(),
Method::Get,
)),
},
(Method::Get, "mmds", None) => parse_get_mmds(),
(Method::Get, _, Some(_)) => method_to_error(Method::Get),
(Method::Put, "actions", Some(body)) => parse_put_actions(body),
Expand Down Expand Up @@ -172,6 +181,8 @@ impl ParsedRequest {
}
VmmData::BalloonStats(stats) => Self::success_response_with_data(stats),
VmmData::InstanceInformation(info) => Self::success_response_with_data(info),
VmmData::MemoryMappings(mappings) => Self::success_response_with_data(mappings),
VmmData::Memory(memory) => Self::success_response_with_data(memory),
VmmData::VmmVersion(version) => Self::success_response_with_data(
&serde_json::json!({ "firecracker_version": version.as_str() }),
),
Expand Down Expand Up @@ -568,6 +579,12 @@ pub mod tests {
VmmData::InstanceInformation(info) => {
http_response(&serde_json::to_string(info).unwrap(), 200)
}
VmmData::MemoryMappings(mappings) => {
http_response(&serde_json::to_string(mappings).unwrap(), 200)
}
VmmData::Memory(memory) => {
http_response(&serde_json::to_string(memory).unwrap(), 200)
}
VmmData::VmmVersion(version) => http_response(
&serde_json::json!({ "firecracker_version": version.as_str() }).to_string(),
200,
Expand All @@ -589,6 +606,15 @@ pub mod tests {
verify_ok_response_with(VmmData::MachineConfiguration(MachineConfig::default()));
verify_ok_response_with(VmmData::MmdsValue(serde_json::from_str("{}").unwrap()));
verify_ok_response_with(VmmData::InstanceInformation(InstanceInfo::default()));
verify_ok_response_with(VmmData::MemoryMappings(
vmm::vmm_config::instance_info::MemoryMappingsResponse { mappings: vec![] },
));
verify_ok_response_with(VmmData::Memory(
vmm::vmm_config::instance_info::MemoryResponse {
resident: vec![],
empty: vec![],
},
));
verify_ok_response_with(VmmData::VmmVersion(String::default()));

// Error.
Expand Down Expand Up @@ -662,6 +688,30 @@ pub mod tests {
ParsedRequest::try_from(&req).unwrap();
}

#[test]
fn test_try_from_get_memory_mappings() {
let (mut sender, receiver) = UnixStream::pair().unwrap();
let mut connection = HttpConnection::new(receiver);
sender
.write_all(http_request("GET", "/memory/mappings", None).as_bytes())
.unwrap();
connection.try_read().unwrap();
let req = connection.pop_parsed_request().unwrap();
ParsedRequest::try_from(&req).unwrap();
}

#[test]
fn test_try_from_get_memory() {
let (mut sender, receiver) = UnixStream::pair().unwrap();
let mut connection = HttpConnection::new(receiver);
sender
.write_all(http_request("GET", "/memory", None).as_bytes())
.unwrap();
connection.try_read().unwrap();
let req = connection.pop_parsed_request().unwrap();
ParsedRequest::try_from(&req).unwrap();
}

#[test]
fn test_try_from_get_version() {
let (mut sender, receiver) = UnixStream::pair().unwrap();
Expand Down
39 changes: 39 additions & 0 deletions src/firecracker/src/api_server/request/memory.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

use vmm::logger::{IncMetric, METRICS};
use vmm::rpc_interface::VmmAction;

use super::super::parsed_request::{ParsedRequest, RequestError};

pub(crate) fn parse_get_memory_mappings() -> Result<ParsedRequest, RequestError> {
METRICS.get_api_requests.instance_info_count.inc();
Ok(ParsedRequest::new_sync(VmmAction::GetMemoryMappings))
}

pub(crate) fn parse_get_memory() -> Result<ParsedRequest, RequestError> {
METRICS.get_api_requests.instance_info_count.inc();
Comment thread
jakubno marked this conversation as resolved.
Ok(ParsedRequest::new_sync(VmmAction::GetMemory))
}

#[cfg(test)]
mod tests {
use super::*;
use crate::api_server::parsed_request::RequestAction;

#[test]
fn test_parse_get_memory_mappings_request() {
match parse_get_memory_mappings().unwrap().into_parts() {
(RequestAction::Sync(action), _) if *action == VmmAction::GetMemoryMappings => {}
_ => panic!("Test failed."),
}
}

#[test]
fn test_parse_get_memory_request() {
match parse_get_memory().unwrap().into_parts() {
(RequestAction::Sync(action), _) if *action == VmmAction::GetMemory => {}
_ => panic!("Test failed."),
}
}
}
1 change: 1 addition & 0 deletions src/firecracker/src/api_server/request/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ pub mod entropy;
pub mod instance_info;
pub mod logger;
pub mod machine_configuration;
pub mod memory;
pub mod metrics;
pub mod mmds;
pub mod net;
Expand Down
4 changes: 2 additions & 2 deletions src/firecracker/src/api_server/request/snapshot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ mod tests {
let expected_config = CreateSnapshotParams {
snapshot_type: SnapshotType::Diff,
snapshot_path: PathBuf::from("foo"),
mem_file_path: PathBuf::from("bar"),
mem_file_path: Some(PathBuf::from("bar")),
};
assert_eq!(
vmm_action_from_request(parse_put_snapshot(&Body::new(body), Some("create")).unwrap()),
Expand All @@ -154,7 +154,7 @@ mod tests {
let expected_config = CreateSnapshotParams {
snapshot_type: SnapshotType::Full,
snapshot_path: PathBuf::from("foo"),
mem_file_path: PathBuf::from("bar"),
mem_file_path: Some(PathBuf::from("bar")),
};
assert_eq!(
vmm_action_from_request(parse_put_snapshot(&Body::new(body), Some("create")).unwrap()),
Expand Down
1 change: 1 addition & 0 deletions src/firecracker/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,7 @@ fn main_exec() -> Result<(), MainError> {
state: VmState::NotStarted,
vmm_version: FIRECRACKER_VERSION.to_string(),
app_name: "Firecracker".to_string(),
memory_regions: None,
};

if let Some(metrics_path) = arguments.single_value("metrics-path") {
Expand Down
83 changes: 82 additions & 1 deletion src/firecracker/swagger/firecracker.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,35 @@ paths:
schema:
$ref: "#/definitions/Error"

/memory/mappings:
get:
summary: Gets the memory mappings with skippable pages bitmap.
operationId: getMemoryMappings
responses:
200:
description: OK
schema:
$ref: "#/definitions/MemoryMappingsResponse"
default:
description: Internal server error
schema:
$ref: "#/definitions/Error"

/memory:
get:
summary: Gets the memory info (resident and empty pages).
description: Returns an object with resident and empty bitmaps. The resident bitmap marks all pages that are resident. The empty bitmap marks zero pages (subset of resident pages). This is checked at the pageSize of each region. All regions must have the same page size.
operationId: getMemory
responses:
200:
description: OK
schema:
$ref: "#/definitions/MemoryResponse"
default:
description: Internal server error
schema:
$ref: "#/definitions/Error"

/version:
get:
summary: Gets the Firecracker version.
Expand Down Expand Up @@ -997,6 +1026,59 @@ definitions:
description: MicroVM hypervisor build version.
type: string

GuestMemoryRegionMapping:
type: object
description: Describes the region of guest memory that can be used for creating the memfile.
required:
- base_host_virt_addr
- size
- offset
- page_size
properties:
base_host_virt_addr:
type: integer
size:
description: The size of the region in bytes.
type: integer
offset:
description: The offset of the region in bytes.
type: integer
page_size:
description: The page size in bytes.
type: integer

MemoryMappingsResponse:
type: object
description: Response containing memory region mappings.
required:
- mappings
properties:
mappings:
type: array
description: The memory region mappings.
items:
$ref: "#/definitions/GuestMemoryRegionMapping"

MemoryResponse:
type: object
description: Response containing the memory info (resident and empty pages).
required:
- resident
- empty
properties:
resident:
type: array
description: The resident bitmap as a vector of u64 values. Each bit represents if the page is resident.
items:
type: integer
format: uint64
empty:
type: array
description: The empty bitmap as a vector of u64 values. Each bit represents if the page is zero (empty). This is a subset of the resident pages.
items:
type: integer
format: uint64

Logger:
type: object
description:
Expand Down Expand Up @@ -1198,7 +1280,6 @@ definitions:
SnapshotCreateParams:
type: object
required:
- mem_file_path
- snapshot_path
properties:
mem_file_path:
Expand Down
Loading