1use std::borrow::Borrow;
6use std::ffi::{CStr, OsStr};
7use std::fmt::Write;
8use std::os::unix::ffi::OsStrExt;
9use std::thread;
10
11use anyhow::Context;
12use clap::Parser;
13use nix::sys::{personality, resource, signal};
14use signal_hook::{consts, iterator::Signals};
15
16use crate::core::configuration::{CliOptions, ConfigFileOptions, ConfigOptions};
17use crate::core::controller::Controller;
18use crate::core::logger::shadow_logger;
19use crate::core::sim_config::SimConfig;
20use crate::core::worker;
21use crate::cshadow as c;
22use crate::utility::shm_cleanup;
23
24use shadow_build_info::{BUILD_TIMESTAMP, GIT_BRANCH, GIT_COMMIT_INFO, GIT_DATE};
25
26const HELP_INFO_STR: &str =
27 "For more information, visit https://shadow.github.io or https://github.com/shadow";
28
29pub fn run_shadow(args: Vec<&OsStr>) -> anyhow::Result<()> {
31 let _guard = unsafe { crate::shadow_shmem::allocator::SharedMemAllocatorDropGuard::new() };
34
35 verify_glib_version().context("Unsupported GLib version")?;
36
37 let mut signals_list = Signals::new([consts::signal::SIGINT, consts::signal::SIGTERM])?;
38 thread::spawn(move || {
39 if let Some(signal) = signals_list.forever().next() {
42 log::info!("Received signal {}. Flushing log and exiting", signal);
43 log::logger().flush();
44 std::process::exit(1);
45 }
46 log::debug!("Finished waiting for a signal");
47 });
48
49 signal::sigprocmask(
52 signal::SigmaskHow::SIG_SETMASK,
53 Some(&signal::SigSet::empty()),
54 None,
55 )?;
56
57 let options = match CliOptions::try_parse_from(args.clone()) {
59 Ok(x) => x,
60 Err(e) => {
61 e.print().unwrap();
63 if e.use_stderr() {
64 std::process::exit(1);
66 } else {
67 std::process::exit(0);
70 }
71 }
72 };
73
74 if options.show_build_info {
75 write_build_info(std::io::stderr()).unwrap();
76 std::process::exit(0);
77 }
78
79 if options.shm_cleanup {
80 shm_cleanup::shm_cleanup(shm_cleanup::SHM_DIR_PATH)
82 .context("Cleaning shared memory files")?;
83 std::process::exit(0);
84 }
85
86 let config_filename: String = match options.config.as_ref().unwrap().as_str() {
88 "-" => "/dev/stdin",
89 x => x,
90 }
91 .into();
92
93 let config_file = load_config_file(&config_filename, true)
95 .with_context(|| format!("Failed to load configuration file {}", config_filename))?;
96
97 let shadow_config = ConfigOptions::new(config_file, options.clone());
99
100 if options.show_config {
101 eprintln!("{:#?}", shadow_config);
102 return Ok(());
103 }
104
105 if shadow_config.experimental.use_object_counters.unwrap() {
107 worker::enable_object_counters();
108 }
109
110 let log_level = shadow_config.general.log_level.unwrap();
112 let log_level: log::Level = log_level.into();
113
114 shadow_logger::init(
116 log_level.to_level_filter(),
117 shadow_config.experimental.report_errors_to_stderr.unwrap(),
118 )
119 .unwrap();
120
121 shadow_logger::set_buffering_enabled(false);
123
124 if log_level > log::STATIC_MAX_LEVEL {
126 log::warn!(
127 "Log level set to {}, but messages higher than {} have been compiled out",
128 log_level,
129 log::STATIC_MAX_LEVEL,
130 );
131 }
132
133 if nix::unistd::getuid().is_root() {
135 log::warn!(
139 "Shadow is running as root. Shadow does not emulate Linux permissions, and some
140 applications may behave differently when running as root. It is recommended to run
141 Shadow as a non-root user."
142 );
143 } else if nix::unistd::geteuid().is_root() {
144 log::warn!(
145 "Shadow is running with root privileges. Shadow does not emulate Linux permissions,
146 and some applications may behave differently when running with root privileges. It
147 is recommended to run Shadow as a non-root user."
148 );
149 }
150
151 if let Err(e) = shm_cleanup::shm_cleanup(shm_cleanup::SHM_DIR_PATH) {
153 log::warn!("Unable to clean up shared memory files: {:?}", e);
154 }
155
156 if shadow_config.experimental.use_cpu_pinning.unwrap() {
158 #[allow(clippy::collapsible_if)]
159 if unsafe { c::affinity_initPlatformInfo() } != 0 {
160 return Err(anyhow::anyhow!("Unable to initialize platform info"));
161 }
162 }
163
164 raise_rlimit(resource::Resource::RLIMIT_NOFILE).context("Could not raise fd limit")?;
166
167 raise_rlimit(resource::Resource::RLIMIT_NPROC).context("Could not raise proc limit")?;
169
170 if shadow_config.experimental.use_sched_fifo.unwrap() {
171 set_sched_fifo().context("Could not set real-time scheduler mode to SCHED_FIFO")?;
172 log::debug!("Successfully set real-time scheduler mode to SCHED_FIFO");
173 }
174
175 match disable_aslr() {
179 Ok(()) => log::debug!("ASLR disabled for processes forked from this parent process"),
180 Err(e) => log::warn!(
181 "Could not disable address space layout randomization. This may affect determinism: {e:#}"
182 ),
183 };
184
185 if sidechannel_mitigations_enabled().context("Failed to get sidechannel mitigation status")? {
187 log::warn!(
188 "Speculative Store Bypass sidechannel mitigation is enabled (perhaps by seccomp?). \
189 This typically adds ~30% performance overhead."
190 );
191 }
192
193 eprintln!("** Starting Shadow {}", env!("CARGO_PKG_VERSION"));
195 let mut build_info = Vec::new();
196 write_build_info(&mut build_info).unwrap();
197 for line in std::str::from_utf8(&build_info).unwrap().trim().split('\n') {
198 log::info!("{line}");
199 }
200 log::info!("Logging current startup arguments and environment");
201 log_environment(args.clone());
202
203 if let Err(e) = verify_supported_system() {
204 log::warn!("Couldn't verify supported system: {e:?}")
205 }
206
207 log::debug!("Startup checks passed, we are ready to start the simulation");
208
209 if options.gdb {
211 pause_for_gdb_attach().context("Could not pause shadow to allow gdb to attach")?;
212 }
213
214 let sim_config = SimConfig::new(&shadow_config, &options.debug_hosts.unwrap_or_default())
215 .context("Failed to initialize the simulation")?;
216
217 let controller = Controller::new(sim_config, &shadow_config);
219
220 let buffer_log = !log::log_enabled!(log::Level::Trace);
222 shadow_logger::set_buffering_enabled(buffer_log);
223 if buffer_log {
224 log::info!("Log message buffering is enabled for efficiency");
225 }
226
227 controller.run().context("Failed to run the simulation")?;
229
230 shadow_logger::set_buffering_enabled(false);
232 if buffer_log {
233 log::info!("Log message buffering is disabled during cleanup");
235 }
236
237 Ok(())
238}
239
240pub fn version() -> String {
241 let mut s = env!("CARGO_PKG_VERSION").to_string();
242
243 if let (Some(commit), Some(date)) = (GIT_COMMIT_INFO, GIT_DATE) {
244 write!(s, " — {commit} {date}").unwrap();
245 }
246
247 s
248}
249
250fn write_build_info(mut w: impl std::io::Write) -> std::io::Result<()> {
251 writeln!(w, "Shadow {}", version())?;
252 writeln!(
253 w,
254 "GLib {}.{}.{}",
255 c::GLIB_MAJOR_VERSION,
256 c::GLIB_MINOR_VERSION,
257 c::GLIB_MICRO_VERSION,
258 )?;
259 writeln!(w, "Built on {}", BUILD_TIMESTAMP)?;
260 writeln!(
261 w,
262 "Built from git branch {}",
263 GIT_BRANCH.unwrap_or("<unknown>"),
264 )?;
265 writeln!(w, "{}", env!("SHADOW_BUILD_INFO"))?;
266 writeln!(w, "{HELP_INFO_STR}")?;
267
268 Ok(())
269}
270
271fn verify_supported_system() -> anyhow::Result<()> {
272 let uts_name = nix::sys::utsname::uname()?;
273 let sysname = uts_name
274 .sysname()
275 .to_str()
276 .with_context(|| "Decoding system name")?;
277 if sysname != "Linux" {
278 anyhow::bail!("Unsupported sysname: {sysname}");
279 }
280 let version = uts_name
281 .release()
282 .to_str()
283 .with_context(|| "Decoding system release")?;
284 let mut version_parts = version.split('.');
285 let Some(major) = version_parts.next() else {
286 anyhow::bail!("Couldn't find major version in : {version}");
287 };
288 let major: i32 = major
289 .parse()
290 .with_context(|| format!("Parsing major version number '{major}'"))?;
291 let Some(minor) = version_parts.next() else {
292 anyhow::bail!("Couldn't find minor version in : {version}");
293 };
294 let minor: i32 = minor
295 .parse()
296 .with_context(|| format!("Parsing minor version number '{minor}'"))?;
297
298 const MIN_KERNEL_VERSION: (i32, i32) = (5, 4);
300
301 if (major, minor) < MIN_KERNEL_VERSION {
302 anyhow::bail!(
303 "kernel version {major}.{minor} is older than minimum supported version {}.{}",
304 MIN_KERNEL_VERSION.0,
305 MIN_KERNEL_VERSION.1
306 );
307 }
308
309 Ok(())
310}
311
312fn verify_glib_version() -> anyhow::Result<()> {
313 if c::GLIB_MAJOR_VERSION == 2 && c::GLIB_MINOR_VERSION == 40 {
317 anyhow::bail!(
318 "You compiled against GLib version {}.{}.{}, which has bugs known to break \"
319 Shadow. Please update to a newer version of GLib.",
320 c::GLIB_MAJOR_VERSION,
321 c::GLIB_MINOR_VERSION,
322 c::GLIB_MICRO_VERSION,
323 );
324 }
325
326 let mismatch = unsafe {
328 c::glib_check_version(
329 c::GLIB_MAJOR_VERSION,
330 c::GLIB_MINOR_VERSION,
331 c::GLIB_MICRO_VERSION,
332 )
333 };
334
335 if !mismatch.is_null() {
336 let mismatch = unsafe { std::ffi::CStr::from_ptr(mismatch) };
337 anyhow::bail!(
338 "The version of the run-time GLib library ({}.{}.{}) is not compatible with \
339 the version against which Shadow was compiled ({}.{}.{}). GLib message: '{}'.",
340 unsafe { c::glib_major_version },
341 unsafe { c::glib_minor_version },
342 unsafe { c::glib_micro_version },
343 c::GLIB_MAJOR_VERSION,
344 c::GLIB_MINOR_VERSION,
345 c::GLIB_MICRO_VERSION,
346 mismatch.to_string_lossy(),
347 );
348 }
349
350 Ok(())
351}
352
353fn load_config_file(
354 filename: impl AsRef<std::path::Path>,
355 extended_yaml: bool,
356) -> anyhow::Result<ConfigFileOptions> {
357 let file = std::fs::File::open(filename).context("Could not open config file")?;
358
359 let mut config_file: serde_yaml::Value =
366 serde_yaml::from_reader(file).context("Could not parse configuration file as yaml")?;
367
368 if extended_yaml {
369 config_file
371 .apply_merge()
372 .context("Could not merge '<<' keys")?;
373
374 if let serde_yaml::Value::Mapping(mapping) = &mut config_file {
376 mapping.retain(|key, _value| {
379 if let serde_yaml::Value::String(key) = key {
380 if key.starts_with("x-") {
381 return false;
382 }
383 }
384 true
385 });
386 }
387 }
388
389 serde_yaml::from_value(config_file).context("Could not parse configuration file")
390}
391
392fn pause_for_gdb_attach() -> anyhow::Result<()> {
393 let pid = nix::unistd::getpid();
394 log::info!(
395 "Pausing with SIGTSTP to enable debugger attachment (pid {})",
396 pid
397 );
398 eprintln!(
399 "** Pausing with SIGTSTP to enable debugger attachment (pid {})",
400 pid
401 );
402
403 signal::raise(signal::Signal::SIGTSTP)?;
404
405 log::info!("Resuming now");
406 Ok(())
407}
408
409fn set_sched_fifo() -> anyhow::Result<()> {
410 let mut param: libc::sched_param = unsafe { std::mem::zeroed() };
411 param.sched_priority = 1;
412
413 let rv = nix::errno::Errno::result(unsafe {
414 libc::sched_setscheduler(0, libc::SCHED_FIFO, std::ptr::from_ref(¶m))
415 })
416 .context("Could not set kernel SCHED_FIFO")?;
417
418 assert_eq!(rv, 0);
419
420 Ok(())
421}
422
423fn raise_rlimit(resource: resource::Resource) -> anyhow::Result<()> {
424 let (_soft_limit, hard_limit) = resource::getrlimit(resource)?;
425 resource::setrlimit(resource, hard_limit, hard_limit)?;
426 Ok(())
427}
428
429fn disable_aslr() -> anyhow::Result<()> {
430 let pers = personality::get().context("Could not get personality")?;
431 personality::set(pers | personality::Persona::ADDR_NO_RANDOMIZE)
432 .context("Could not set personality")?;
433 Ok(())
434}
435
436fn sidechannel_mitigations_enabled() -> anyhow::Result<bool> {
437 let state = nix::errno::Errno::result(unsafe {
438 libc::prctl(
439 libc::PR_GET_SPECULATION_CTRL,
440 libc::PR_SPEC_STORE_BYPASS,
441 0,
442 0,
443 0,
444 )
445 })
446 .context("Failed prctl()")?;
447 let state = state as u32;
448 Ok((state & libc::PR_SPEC_DISABLE) != 0)
449}
450
451fn log_environment(args: Vec<&OsStr>) {
452 for arg in args {
453 log::info!("arg: {}", arg.to_string_lossy());
454 }
455
456 for (key, value) in std::env::vars_os() {
457 let level = match key.to_string_lossy().borrow() {
458 "LD_PRELOAD" | "LD_STATIC_TLS_EXTRA" | "G_DEBUG" | "G_SLICE" => log::Level::Info,
459 _ => log::Level::Trace,
460 };
461 log::log!(level, "env: {:?}={:?}", key, value);
462 }
463}
464
465mod export {
466 use super::*;
467
468 #[unsafe(no_mangle)]
469 pub extern "C-unwind" fn main_runShadow(
470 argc: libc::c_int,
471 argv: *const *const libc::c_char,
472 ) -> libc::c_int {
473 let args = (0..argc).map(|x| unsafe { CStr::from_ptr(*argv.add(x as usize)) });
474 let args = args.map(|x| OsStr::from_bytes(x.to_bytes()));
475
476 let result = run_shadow(args.collect());
477 log::logger().flush();
478
479 if let Err(e) = result {
480 if log::log_enabled!(log::Level::Error) {
482 for line in format!("{:?}", e).split('\n') {
483 log::error!("{}", line);
484 }
485 log::logger().flush();
486
487 eprintln!("** Shadow did not complete successfully: {}", e);
489 eprintln!("** {}", e.root_cause());
490 eprintln!("** See the log for details");
491 } else {
492 eprintln!("{:?}", e);
494 }
495
496 return 1;
497 }
498
499 eprintln!("** Shadow completed successfully");
500 0
501 }
502}