From 6d772433b466fe44145d0b24fb9bc287676aba54 Mon Sep 17 00:00:00 2001 From: Laura Abbott Date: Mon, 8 Jun 2026 15:35:03 -0400 Subject: [PATCH 1/2] Refactor `dump_via_agent` This function has grown complicated and hard to follow. Extract the following to separate functions - `subargs.initialize_dump_agent` - `subargs.simulate_dumper`/`subargs.emulate_dumper`/ `subargs.simulate_task_dump` - `subargs.extract`/`subargs.force_read` There's a little bit of redundancy but it makes the logic much easier to follow. --- cmd/dump/src/lib.rs | 343 +++++++++++++++++++++++++++----------------- 1 file changed, 215 insertions(+), 128 deletions(-) diff --git a/cmd/dump/src/lib.rs b/cmd/dump/src/lib.rs index fcf793c3e..ec36bb525 100644 --- a/cmd/dump/src/lib.rs +++ b/cmd/dump/src/lib.rs @@ -408,7 +408,8 @@ fn print_dump_breakdown(breakdown: &DumpBreakdown, log: &Logger) { print_val("data expansion", breakdown.inverted); } -fn dump_via_agent( +/// Simulation and testing features +fn simulate_dump_via_agent( hubris: &HubrisArchive, core: &mut dyn Core, subargs: &DumpArgs, @@ -552,135 +553,55 @@ fn dump_via_agent( core.run()?; info!(log, "core resumed"); - } else { + } else if subargs.emulate_dumper { let segments = hubris.dump_segments(core, None, false)?; let mut agent = get_dump_agent(hubris, core, subargs, log)?; let header = agent.read_dump_header()?; - if !subargs.force_read && subargs.extract.is_none() { - if header.dumper != humpty::DUMPER_NONE - && !subargs.initialize_dump_agent - && !subargs.force_overwrite - && task.is_none() - { - bail!( - "there appears to already be one or more dumps in situ; \ + if header.dumper != humpty::DUMPER_NONE + && !subargs.force_overwrite + && task.is_none() + { + bail!( + "there appears to already be one or more dumps in situ; \ list them with --list and extract them with --extract_all" - ) - } - - if task.is_none() || subargs.initialize_dump_agent { - info!(log, "initializing dump agent state"); - agent.initialize_dump()?; - } - - if subargs.initialize_dump_agent { - return Ok(()); - } - - if task.is_none() { - info!(log, "initializing segments"); - agent.initialize_segments(&segments)?; - } + ) } - if subargs.emulate_dumper { - agent.core().halt()?; - info!(log, "core halted"); + if task.is_none() { + info!(log, "initializing segments"); + agent.initialize_segments(&segments)?; + } + agent.core().halt()?; + info!(log, "core halted"); - if let Some(ref stock) = subargs.stock_dumpfile { - hubris.dump(agent.core(), task, Some(stock), None, log)?; - } + if let Some(ref stock) = subargs.stock_dumpfile { + hubris.dump(agent.core(), task, Some(stock), None, log)?; + } - let base = header.address; - let total = segments.iter().fold(0, |ttl, (_, size)| ttl + size); + let base = header.address; + let total = segments.iter().fold(0, |ttl, (_, size)| ttl + size); - let address = if task.is_some() { - match emulate_task_dump_prep(agent.core(), &segments, base) { - Err(e) => { - agent.core().run()?; - info!(log, "core resumed after failure"); - return Err(e); - } - Ok(address) => { - assert!(area.is_none()); - area = Some(DumpArea::ByAddress(address)); - address - } + let address = if task.is_some() { + match emulate_task_dump_prep(agent.core(), &segments, base) { + Err(e) => { + agent.core().run()?; + info!(log, "core resumed after failure"); + return Err(e); } - } else { - base - }; - - emulate_dump(agent.core(), task, address, total, log)?; - agent.core().run()?; - info!(log, "core resumed"); - } else if !subargs.force_read && subargs.extract.is_none() { - if subargs.force_manual_initiation { - agent.core().halt()?; - info!(log, "leaving core halted"); - let base = header.address; - info!( - log, - "unplug probe and manually \ - initiate dump from address {:#x}", - base - ); - info!( - log, - "e.g., \"humility hiffy --call \ - Dumper.dump -a address={:#x}\"", - base - ); - return Ok(()); - } - - // - // We are about to disappear for -- as the kids say -- a minute. - // Set our timeout to be a literal minute so we don't prematurely - // give up. - // - agent.core().set_timeout(std::time::Duration::new(60, 0))?; - - // - // Tell the thing to take a dump. - // - if let Err(err) = agent.take_dump() { - // - // If that fails, it may be because we ran out of space. Check - // our dump headers; if all of them are consumed, assume - // that we ran out of space -- and if any of them are consumed, - // process whatever we find (some dump is better than none!) and - // warn accordingly. - // - if let Ok(all) = agent.read_dump_headers(true) { - let c = all - .iter() - .filter(|&&(h, _)| h.dumper != humpty::DUMPER_NONE) - .count(); - - if c == all.len() { - warn!( - log, - "dump has indicated failure ({err:?}), but this is \ - likely due to space exhaustion; \ - dump will be extracted but may be incomplete!" - ); - } else if c != 0 { - warn!( - log, - "dump has indicated failure ({err:?}), but some dump \ - contents appear to have been written; \ - dump will be extracted but may be incomplete!" - ); - } else { - return Err(err); - } - } else { - return Err(err); + Ok(address) => { + assert!(area.is_none()); + area = Some(DumpArea::ByAddress(address)); + address } } - } + } else { + base + }; + + emulate_dump(agent.core(), task, address, total, log)?; + agent.core().run()?; + info!(log, "core resumed"); // // If we're here, we have a dump in situ -- time to pull it. @@ -700,6 +621,173 @@ fn dump_via_agent( info!(log, "retaining dump agent state"); } } + + hubris.dump( + &mut out, + task, + subargs.dumpfile.as_deref(), + started, + log, + )?; + } + + Ok(()) +} + +fn extract_or_read_via_agent( + hubris: &HubrisArchive, + core: &mut dyn Core, + subargs: &DumpArgs, + log: &Logger, +) -> Result<()> { + let mut out = DumpAgentCore::new(HubrisFlashMap::new(hubris)?); + let started = Some(Instant::now()); + + let mut agent = get_dump_agent(hubris, core, subargs, log)?; + let area = subargs.extract.map(DumpArea::ByIndex); + + let task = read_dump(&mut agent, area, &mut out, subargs, log)?; + + // + // If this was a whole-system dump, we will leave our state initialized + // to assure that it will be ready to take subsequent task dumps (unless + // explicitly asked not to). + // + if task.is_none() { + if !subargs.retain_state { + info!(log, "resetting dump agent state"); + agent.initialize_dump()?; + } else { + info!(log, "retaining dump agent state"); + } + } + + hubris.dump(&mut out, task, subargs.dumpfile.as_deref(), started, log)?; + + Ok(()) +} + +fn init_dump_agent( + hubris: &HubrisArchive, + core: &mut dyn Core, + subargs: &DumpArgs, + log: &Logger, +) -> Result<()> { + let mut agent = get_dump_agent(hubris, core, subargs, log)?; + + info!(log, "initializing dump agent state"); + agent.initialize_dump()?; + Ok(()) +} + +fn dump_via_agent( + hubris: &HubrisArchive, + core: &mut dyn Core, + subargs: &DumpArgs, + log: &Logger, +) -> Result<()> { + let mut out = DumpAgentCore::new(HubrisFlashMap::new(hubris)?); + let started = Some(Instant::now()); + + let segments = hubris.dump_segments(core, None, false)?; + let mut agent = get_dump_agent(hubris, core, subargs, log)?; + let header = agent.read_dump_header()?; + let area = subargs.extract.map(DumpArea::ByIndex); + + if header.dumper != humpty::DUMPER_NONE && !subargs.force_overwrite { + bail!( + "there appears to already be one or more dumps in situ; \ + list them with --list and extract them with --extract_all" + ) + } + + info!(log, "initializing dump agent state"); + agent.initialize_dump()?; + + info!(log, "initializing segments"); + agent.initialize_segments(&segments)?; + if subargs.force_manual_initiation { + agent.core().halt()?; + info!(log, "leaving core halted"); + let base = header.address; + info!( + log, + "unplug probe and manually \ + initiate dump from address {:#x}", + base + ); + info!( + log, + "e.g., \"humility hiffy --call \ + Dumper.dump -a address={:#x}\"", + base + ); + return Ok(()); + } + + // + // We are about to disappear for -- as the kids say -- a minute. + // Set our timeout to be a literal minute so we don't prematurely + // give up. + // + agent.core().set_timeout(std::time::Duration::new(60, 0))?; + + // + // Tell the thing to take a dump. + // + if let Err(err) = agent.take_dump() { + // + // If that fails, it may be because we ran out of space. Check + // our dump headers; if all of them are consumed, assume + // that we ran out of space -- and if any of them are consumed, + // process whatever we find (some dump is better than none!) and + // warn accordingly. + // + if let Ok(all) = agent.read_dump_headers(true) { + let c = all + .iter() + .filter(|&&(h, _)| h.dumper != humpty::DUMPER_NONE) + .count(); + + if c == all.len() { + warn!( + log, + "dump has indicated failure ({err:?}), but this is \ + likely due to space exhaustion; \ + dump will be extracted but may be incomplete!" + ); + } else if c != 0 { + warn!( + log, + "dump has indicated failure ({err:?}), but some dump \ + contents appear to have been written; \ + dump will be extracted but may be incomplete!" + ); + } else { + return Err(err); + } + } else { + return Err(err); + } + } + + // + // If we're here, we have a dump in situ -- time to pull it. + // + let task = read_dump(&mut agent, area, &mut out, subargs, log)?; + + // + // If this was a whole-system dump, we will leave our state initialized + // to assure that it will be ready to take subsequent task dumps (unless + // explicitly asked not to). + // + if task.is_none() { + if !subargs.retain_state { + info!(log, "resetting dump agent state"); + agent.initialize_dump()?; + } else { + info!(log, "retaining dump agent state"); + } } hubris.dump(&mut out, task, subargs.dumpfile.as_deref(), started, log)?; @@ -806,9 +894,7 @@ fn dump_all( if headers[0].1.is_none() { // Extract the full-system dump via the usual method drop(agent); - let mut subargs = subargs.clone(); - subargs.force_read = true; - dump_via_agent(hubris, core, &subargs, log) + extract_or_read_via_agent(hubris, core, subargs, log) } else { let areas = task_areas(&headers); for (area, (task, headers)) in &areas { @@ -888,17 +974,18 @@ fn dumpcmd(subargs: DumpArgs, context: &mut ExecutionContext) -> Result<()> { info!(log, "--force-dump-agent is implied by --task"); } dump_task_via_agent(hubris, core, &subargs, log) - } else if core.is_net() - || subargs.force_dump_agent - || subargs.force_read - || subargs.extract.is_some() + } else if subargs.initialize_dump_agent { + init_dump_agent(hubris, core, &subargs, log) + } else if subargs.force_read || subargs.extract.is_some() { + extract_or_read_via_agent(hubris, core, &subargs, log) + } else if subargs.simulate_dumper + || subargs.emulate_dumper + || subargs.simulate_task_dump.is_some() { + simulate_dump_via_agent(hubris, core, &subargs, log) + } else if core.is_net() || subargs.force_dump_agent { dump_via_agent(hubris, core, &subargs, log) } else { - if subargs.initialize_dump_agent { - bail!("must also use --force-dump-agent to initialize dump agent"); - } - core.halt()?; info!(log, "core halted"); From 863bb7d12af386a6c21a1513d8c7a0efb23e36f8 Mon Sep 17 00:00:00 2001 From: Laura Abbott Date: Wed, 10 Jun 2026 10:50:23 -0400 Subject: [PATCH 2/2] Introduce `humility-dump-lib` One of the most common uses for humility is to take an SP dump on a system so it can be analyzed later. Having this as a library function makes it easier to do it more places. --- Cargo.lock | 15 + Cargo.toml | 2 + cmd/dump/Cargo.toml | 1 + cmd/dump/src/lib.rs | 172 ++------- humility-dump-lib/Cargo.toml | 20 + humility-dump-lib/examples/system_net_dump.rs | 33 ++ humility-dump-lib/src/lib.rs | 341 ++++++++++++++++++ 7 files changed, 443 insertions(+), 141 deletions(-) create mode 100644 humility-dump-lib/Cargo.toml create mode 100644 humility-dump-lib/examples/system_net_dump.rs create mode 100644 humility-dump-lib/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index fdaa8c61a..275d7ce4d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1849,6 +1849,7 @@ dependencies = [ "humility-cli", "humility-core", "humility-dump-agent", + "humility-dump-lib", "humpty", "indexmap 2.14.0", "indicatif", @@ -2635,6 +2636,20 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "humility-dump-lib" +version = "0.1.0" +dependencies = [ + "anyhow", + "humility-core", + "humility-dump-agent", + "humility-log", + "humility-net-core", + "humpty", + "slog", + "thiserror 2.0.18", +] + [[package]] name = "humility-hexdump" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 582a9a39e..d009202ad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ members = [ "humility-core", "humility-doppel", "humility-dump-agent", + "humility-dump-lib", "humility-hexdump", "humility-hiffy", "humility-i2c", @@ -119,6 +120,7 @@ humility-cortex = { path = "./humility-arch-cortex" } humility-cli = { path = "./humility-cli", default-features = false } humility-doppel = { path = "./humility-doppel" } humility-dump-agent = { path = "./humility-dump-agent" } +humility-dump-lib = { path = "./humility-dump-lib" } humility-hexdump = { path = "./humility-hexdump" } humility-hiffy = { path = "./humility-hiffy" } humility-i2c = { path = "./humility-i2c" } diff --git a/cmd/dump/Cargo.toml b/cmd/dump/Cargo.toml index ecdd193c6..29375bee5 100644 --- a/cmd/dump/Cargo.toml +++ b/cmd/dump/Cargo.toml @@ -22,3 +22,4 @@ humility.workspace = true humility-cli.workspace = true humility-dump-agent.workspace = true humility-arch-arm.workspace = true +humility-dump-lib.workspace = true diff --git a/cmd/dump/src/lib.rs b/cmd/dump/src/lib.rs index ec36bb525..45531cddd 100644 --- a/cmd/dump/src/lib.rs +++ b/cmd/dump/src/lib.rs @@ -69,7 +69,7 @@ use anyhow::{Result, bail}; use clap::{ArgGroup, Parser}; use humility::core::Core; use humility::hubris::*; -use humility::log::{Logger, info, warn}; +use humility::log::{Logger, info}; use humility_arch_arm::ARMRegister; use humility_cli::{ExecutionContext, humility_cmd}; use humility_dump_agent::{ @@ -686,111 +686,14 @@ fn dump_via_agent( subargs: &DumpArgs, log: &Logger, ) -> Result<()> { - let mut out = DumpAgentCore::new(HubrisFlashMap::new(hubris)?); - let started = Some(Instant::now()); - - let segments = hubris.dump_segments(core, None, false)?; - let mut agent = get_dump_agent(hubris, core, subargs, log)?; - let header = agent.read_dump_header()?; - let area = subargs.extract.map(DumpArea::ByIndex); - - if header.dumper != humpty::DUMPER_NONE && !subargs.force_overwrite { - bail!( - "there appears to already be one or more dumps in situ; \ - list them with --list and extract them with --extract_all" - ) - } - - info!(log, "initializing dump agent state"); - agent.initialize_dump()?; - - info!(log, "initializing segments"); - agent.initialize_segments(&segments)?; - if subargs.force_manual_initiation { - agent.core().halt()?; - info!(log, "leaving core halted"); - let base = header.address; - info!( - log, - "unplug probe and manually \ - initiate dump from address {:#x}", - base - ); - info!( - log, - "e.g., \"humility hiffy --call \ - Dumper.dump -a address={:#x}\"", - base - ); - return Ok(()); - } - - // - // We are about to disappear for -- as the kids say -- a minute. - // Set our timeout to be a literal minute so we don't prematurely - // give up. - // - agent.core().set_timeout(std::time::Duration::new(60, 0))?; - - // - // Tell the thing to take a dump. - // - if let Err(err) = agent.take_dump() { - // - // If that fails, it may be because we ran out of space. Check - // our dump headers; if all of them are consumed, assume - // that we ran out of space -- and if any of them are consumed, - // process whatever we find (some dump is better than none!) and - // warn accordingly. - // - if let Ok(all) = agent.read_dump_headers(true) { - let c = all - .iter() - .filter(|&&(h, _)| h.dumper != humpty::DUMPER_NONE) - .count(); - - if c == all.len() { - warn!( - log, - "dump has indicated failure ({err:?}), but this is \ - likely due to space exhaustion; \ - dump will be extracted but may be incomplete!" - ); - } else if c != 0 { - warn!( - log, - "dump has indicated failure ({err:?}), but some dump \ - contents appear to have been written; \ - dump will be extracted but may be incomplete!" - ); - } else { - return Err(err); - } - } else { - return Err(err); - } - } - - // - // If we're here, we have a dump in situ -- time to pull it. - // - let task = read_dump(&mut agent, area, &mut out, subargs, log)?; - - // - // If this was a whole-system dump, we will leave our state initialized - // to assure that it will be ready to take subsequent task dumps (unless - // explicitly asked not to). - // - if task.is_none() { - if !subargs.retain_state { - info!(log, "resetting dump agent state"); - agent.initialize_dump()?; - } else { - info!(log, "retaining dump agent state"); - } - } - - hubris.dump(&mut out, task, subargs.dumpfile.as_deref(), started, log)?; + humility_dump_lib::take_system_dump_via_agent( + hubris, + core, + subargs.dumpfile.as_ref(), + subargs.force_hiffy_agent, + subargs.force_overwrite, + log, + )?; Ok(()) } @@ -836,43 +739,30 @@ fn dump_list( subargs: &DumpArgs, log: &Logger, ) -> Result<()> { - let mut agent = get_dump_agent(hubris, core, subargs, log)?; - - println!("{:4} {:21} {:10} SIZE", "AREA", "TASK", "TIME"); - let headers = agent.read_dump_headers(false)?; - - if headers.is_empty() || headers[0].0.dumper == humpty::DUMPER_NONE { - return Ok(()); - } + use humility_dump_lib::DumpType; - if headers[0].1.is_none() { - let size = headers - .iter() - .filter(|&(h, _)| h.dumper != humpty::DUMPER_NONE) - .fold(0, |ttl, (h, _)| ttl + h.written); - - println!("{:>4} {:21} {:<10} {size}", 0, "", "-"); - return Ok(()); - } - - let areas = task_areas(&headers); - - for (area, (task, headers)) in &areas { - let size = headers.iter().fold(0, |ttl, h| ttl + h.written); + let dumps = humility_dump_lib::list_dumps_via_agent( + hubris, + subargs.force_hiffy_agent, + core, + log, + )?; - println!( - "{area:>4} {:21} {:<10} {size}", - match hubris.lookup_module(HubrisTask::Task(task.id.into())) { - Ok(module) => match headers[0].contents { - humpty::DUMP_CONTENTS_SINGLETASK => module.name.to_owned(), - humpty::DUMP_CONTENTS_TASKREGION => - format!("{} [region]", module.name.to_owned()), - c => bail!("unknown contents type: {c}"), - }, - _ => "".to_owned(), - }, - task.time, - ); + println!("{:4} {:21} {:10} SIZE", "AREA", "TASK", "TIME"); + match dumps { + DumpType::None => return Ok(()), + DumpType::System(size) => { + println!("{:>4} {:21} {:<10} {size}", 0, "", "-"); + return Ok(()); + } + DumpType::Tasks(tasks) => { + for t in tasks { + println!( + "{:>4} {:21} {:<10} {}", + t.area, t.task_name, t.time, t.size + ); + } + } } Ok(()) diff --git a/humility-dump-lib/Cargo.toml b/humility-dump-lib/Cargo.toml new file mode 100644 index 000000000..aca8b6f96 --- /dev/null +++ b/humility-dump-lib/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "humility-dump-lib" +version = "0.1.0" +edition.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +# anyhow is only for compatibiltiy +anyhow.workspace = true +humility.workspace = true +thiserror.workspace = true +humility-dump-agent.workspace = true +humpty.workspace = true +slog.workspace = true + +[dev-dependencies] +humility.workspace = true +humility-net-core.workspace = true +humility-log.workspace = true diff --git a/humility-dump-lib/examples/system_net_dump.rs b/humility-dump-lib/examples/system_net_dump.rs new file mode 100644 index 000000000..7481e9485 --- /dev/null +++ b/humility-dump-lib/examples/system_net_dump.rs @@ -0,0 +1,33 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! "Hey can you grab a hubris dump" + +use humility::hubris::HubrisArchive; +use humility::net::ScopedV6Addr; +use humility_net_core::attach_net; + +fn main() { + let log = humility_log::init(false); + + let hubris = std::env::var("HUMILITY_ARCHIVE").unwrap(); + let hubris = HubrisArchive::load_from_path(&hubris, &log).unwrap(); + + let addr = std::env::var("HUMILITY_IP").unwrap(); + let addr: ScopedV6Addr = addr.parse().unwrap(); + + let mut core = + attach_net(addr, &hubris, std::time::Duration::from_secs(5), &log) + .unwrap(); + + humility_dump_lib::take_system_dump_via_agent( + &hubris, + &mut core, + Some(&std::path::PathBuf::from("my_dump.0")), + false, // just use UDPRPC + false, // don't force an overwrite + &log, + ) + .unwrap(); +} diff --git a/humility-dump-lib/src/lib.rs b/humility-dump-lib/src/lib.rs new file mode 100644 index 000000000..7e9b9ee56 --- /dev/null +++ b/humility-dump-lib/src/lib.rs @@ -0,0 +1,341 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Library to support hubris dumps +//! +//! These functions can be used to collect a system dump or extract +//! individual task crash dumps + +#![warn(missing_docs)] +use humility::core::Core; +use humility::hubris::{HubrisArchive, HubrisFlashMap, HubrisTask}; +use humility_dump_agent::{ + DumpAgent, DumpAgentCore, DumpAgentExt, DumpArea, HiffyDumpAgent, + UdpDumpAgent, task_areas, +}; +use slog::Logger; +use std::path::PathBuf; +use std::time::Instant; + +/// Error returned from dumping +#[derive(Debug, thiserror::Error)] +pub enum DumpError { + /// Tried to take a dump when there were pending dumps. The + /// dumps should be extracted first before trying to take a dump. + #[error("Cannot take a dump while one is already in progress")] + DumpAlreadyInProgress, + /// Error creating UDP agent + #[error("Error creating UDP agent")] + UdpAgent(#[source] anyhow::Error), + /// Error creating hiffy agent + #[error("Error creating hiffy agent")] + HiffyAgent(#[source] anyhow::Error), + /// Error reading dump headers + #[error("Error reading dump headers")] + DumpHeaders(#[source] anyhow::Error), + /// Tried to extract a system dump when a task dump was expected + #[error("Found system dump, expected task dump(s)")] + SystemDump, + /// Error creating hubris flash map + #[error("Error creating hubris flash map")] + FlashMap(#[source] anyhow::Error), + /// Error reading dump via agent + #[error("Error reading dump")] + ReadDump(#[source] anyhow::Error), + /// Error writing dump + #[error("Error writing dump")] + HubrisDump(#[source] anyhow::Error), + /// Error taking a dump + #[error("Error taking a dump")] + TakeDump(#[source] anyhow::Error), + /// Dump has indicated failure but this is likely due to space + /// exhaustion. The dump can be extracted but may be incomplete + #[error("Dump space exhausted")] + DumpSpaceExhaustion(#[source] anyhow::Error), + /// Dump has indicated failure but some dump contents appear to + /// have been written. This can be extractec but may be incomplete. + #[error("Dump incomplete")] + IncompleteDump(#[source] anyhow::Error), + /// Expected to get a task + #[error("Expected task")] + ExpectedTask, + /// Error getting dump segments + #[error("Error getting dump segments")] + DumpSegments(#[source] anyhow::Error), + /// Error initializing dump + #[error("Error initializing dump")] + InitializeDump(#[source] anyhow::Error), + /// Error initializing segments + #[error("Error initialziing segments")] + InitializeSegments(#[source] anyhow::Error), + /// Error setting timeout + #[error("Error setting timeout")] + SetTimeout(#[source] anyhow::Error), +} + +fn get_dump_agent<'a>( + hubris: &'a HubrisArchive, + core: &'a mut dyn Core, + force_hiffy_agent: bool, + log: &Logger, +) -> Result, DumpError> { + // Find the dump agent task name. This is usually `dump_agent`, but that's + // not guaranteed; what *is* guaranteed is that it implements the DumpAgent + // interface. + let dump_agent_task = + hubris.lookup_module_by_iface("DumpAgent").map(|t| t.task); + + if core.is_net() + && !force_hiffy_agent + && dump_agent_task + .map(|t| hubris.does_task_have_feature(t, "net").unwrap()) + .unwrap_or(false) + { + let imageid = hubris.image_id(); + + Ok(Box::new( + UdpDumpAgent::new(core, imageid, log) + .map_err(DumpError::UdpAgent)?, + )) + } else { + // XXX + let timeout = std::time::Duration::from_millis(5000); + Ok(Box::new( + HiffyDumpAgent::new(hubris, core, timeout, log) + .map_err(DumpError::HiffyAgent)?, + )) + } +} + +/// Extract all task dumps +pub fn extract_all_task_dumps( + hubris: &HubrisArchive, + core: &mut dyn Core, + use_hiffy: bool, + log: &Logger, +) -> Result<(), DumpError> { + let mut agent = get_dump_agent(hubris, core, use_hiffy, log)?; + let headers = + agent.read_dump_headers(false).map_err(DumpError::DumpHeaders)?; + if headers.is_empty() || headers[0].0.dumper == humpty::DUMPER_NONE { + return Ok(()); + } + + if headers[0].1.is_none() { + return Err(DumpError::SystemDump); + } + + let areas = task_areas(&headers); + for (area, (task, headers)) in &areas { + let task_name = + match hubris.lookup_module(HubrisTask::Task(task.id.into())) { + Ok(module) => match headers[0].contents { + humpty::DUMP_CONTENTS_SINGLETASK => module.name.to_owned(), + humpty::DUMP_CONTENTS_TASKREGION => { + format!("{}.region", module.name.to_owned()) + } + c => panic!("unknown contents type: {c}"), + }, + _ => "".to_owned(), + }; + + let dumpfile = (0..) + .map(|i| PathBuf::from(format!("hubris.core.{task_name}.{i}"))) + .find(|f| !f.exists()) + .unwrap(); + + let mut out = DumpAgentCore::new( + HubrisFlashMap::new(hubris).map_err(DumpError::FlashMap)?, + ); + let started = Some(Instant::now()); + + let task = agent + .read_dump(Some(DumpArea::ByIndex(*area)), &mut out, false, log) + .map_err(DumpError::ReadDump)?; + + if task.0.is_none() { + return Err(DumpError::ExpectedTask); + } + hubris + .dump(&mut out, task.0, Some(&dumpfile), started, log) + .map_err(DumpError::HubrisDump)?; + } + + Ok(()) +} + +/// Extract an existing system dump via the agent +pub fn extract_system_dump_via_agent( + hubris: &HubrisArchive, + core: &mut dyn Core, + dumpfile: PathBuf, + use_hiffy: bool, + log: &Logger, +) -> Result<(), DumpError> { + let mut out = DumpAgentCore::new( + HubrisFlashMap::new(hubris).map_err(DumpError::FlashMap)?, + ); + let started = Some(Instant::now()); + + let mut agent = get_dump_agent(hubris, core, use_hiffy, log)?; + + let _ = agent + .read_dump(None, &mut out, false, log) + .map_err(DumpError::ReadDump)?; + + hubris + .dump(&mut out, None, Some(&dumpfile), started, log) + .map_err(DumpError::HubrisDump)?; + + Ok(()) +} + +/// Take and collect a dump via the agent. If a file is not +/// specified the file will be saved accoridng to the default +/// humility logic (hubris.core.0, hubris.core.1 etc.) +pub fn take_system_dump_via_agent( + hubris: &HubrisArchive, + core: &mut dyn Core, + dumpfile: Option<&PathBuf>, + use_hiffy: bool, + force_overwrite: bool, + log: &Logger, +) -> Result<(), DumpError> { + let mut out = DumpAgentCore::new( + HubrisFlashMap::new(hubris).map_err(DumpError::FlashMap)?, + ); + let started = Some(Instant::now()); + + let segments = hubris + .dump_segments(core, None, false) + .map_err(DumpError::DumpSegments)?; + let mut agent = get_dump_agent(hubris, core, use_hiffy, log)?; + let header = agent.read_dump_header().map_err(DumpError::DumpHeaders)?; + + if header.dumper != humpty::DUMPER_NONE && !force_overwrite { + return Err(DumpError::DumpAlreadyInProgress); + } + + agent.initialize_dump().map_err(DumpError::InitializeDump)?; + + agent + .initialize_segments(&segments) + .map_err(DumpError::InitializeSegments)?; + + // + // We are about to disappear for -- as the kids say -- a minute. + // Set our timeout to be a literal minute so we don't prematurely + // give up. + // + + agent + .core() + .set_timeout(std::time::Duration::new(60, 0)) + .map_err(DumpError::SetTimeout)?; + + if let Err(err) = agent.take_dump() { + if let Ok(all) = agent.read_dump_headers(true) { + let c = all + .iter() + .filter(|&&(h, _)| h.dumper != humpty::DUMPER_NONE) + .count(); + if c == all.len() { + return Err(DumpError::DumpSpaceExhaustion(err)); + } else if c != 0 { + return Err(DumpError::IncompleteDump(err)); + } else { + return Err(DumpError::TakeDump(err)); + } + } else { + return Err(DumpError::TakeDump(err)); + } + } + + let _ = agent + .read_dump(None, &mut out, false, log) + .map_err(DumpError::ReadDump)?; + + hubris + .dump(&mut out, None, dumpfile.map(|p| p.as_path()), started, log) + .map_err(DumpError::HubrisDump)?; + + Ok(()) +} + +/// Meta information about an available task dump +pub struct TaskDumpData { + /// Area number + pub area: usize, + /// Total size of dump + pub size: u32, + /// Task name or region + pub task_name: String, + /// Time the task was dumped + pub time: u64, +} + +/// Available dump types +pub enum DumpType { + /// No dumps + None, + /// A whole system dump and its size + System(u32), + /// Individual hubris tasks + Tasks(Vec), +} + +/// List all available dumps +pub fn list_dumps_via_agent( + hubris: &HubrisArchive, + use_hiffy: bool, + core: &mut dyn Core, + log: &Logger, +) -> Result { + let mut agent = get_dump_agent(hubris, core, use_hiffy, log)?; + + let headers = + agent.read_dump_headers(false).map_err(DumpError::DumpHeaders)?; + + if headers.is_empty() || headers[0].0.dumper == humpty::DUMPER_NONE { + return Ok(DumpType::None); + } + + if headers[0].1.is_none() { + let size = headers + .iter() + .filter(|&(h, _)| h.dumper != humpty::DUMPER_NONE) + .fold(0, |ttl, (h, _)| ttl + h.written); + + return Ok(DumpType::System(size)); + } + + let areas = task_areas(&headers); + + let mut tasks = vec![]; + + for (area, (task, headers)) in &areas { + let size = headers.iter().fold(0, |ttl, h| ttl + h.written); + + let task_name = + match hubris.lookup_module(HubrisTask::Task(task.id.into())) { + Ok(module) => match headers[0].contents { + humpty::DUMP_CONTENTS_SINGLETASK => module.name.to_owned(), + humpty::DUMP_CONTENTS_TASKREGION => { + format!("{} [region]", module.name.to_owned()) + } + c => panic!("unknown contents type: {c}"), + }, + _ => "".to_owned(), + }; + + tasks.push(TaskDumpData { + area: *area, + size, + task_name, + time: task.time, + }); + } + + Ok(DumpType::Tasks(tasks)) +}