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 {signal}. Flushing log and exiting");
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                    && key.starts_with("x-")
381                {
382                    return false;
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!("Pausing with SIGTSTP to enable debugger attachment (pid {pid})");
395    eprintln!("** Pausing with SIGTSTP to enable debugger attachment (pid {pid})");
396
397    signal::raise(signal::Signal::SIGTSTP)?;
398
399    log::info!("Resuming now");
400    Ok(())
401}
402
403fn set_sched_fifo() -> anyhow::Result<()> {
404    let mut param: libc::sched_param = unsafe { std::mem::zeroed() };
405    param.sched_priority = 1;
406
407    let rv = nix::errno::Errno::result(unsafe {
408        libc::sched_setscheduler(0, libc::SCHED_FIFO, std::ptr::from_ref(¶m))
409    })
410    .context("Could not set kernel SCHED_FIFO")?;
411
412    assert_eq!(rv, 0);
413
414    Ok(())
415}
416
417fn raise_rlimit(resource: resource::Resource) -> anyhow::Result<()> {
418    let (_soft_limit, hard_limit) = resource::getrlimit(resource)?;
419    resource::setrlimit(resource, hard_limit, hard_limit)?;
420    Ok(())
421}
422
423fn disable_aslr() -> anyhow::Result<()> {
424    let pers = personality::get().context("Could not get personality")?;
425    personality::set(pers | personality::Persona::ADDR_NO_RANDOMIZE)
426        .context("Could not set personality")?;
427    Ok(())
428}
429
430fn sidechannel_mitigations_enabled() -> anyhow::Result<bool> {
431    let state = nix::errno::Errno::result(unsafe {
432        libc::prctl(
433            libc::PR_GET_SPECULATION_CTRL,
434            libc::PR_SPEC_STORE_BYPASS,
435            0,
436            0,
437            0,
438        )
439    })
440    .context("Failed prctl()")?;
441    let state = state as u32;
442    Ok((state & libc::PR_SPEC_DISABLE) != 0)
443}
444
445fn log_environment(args: Vec<&OsStr>) {
446    for arg in args {
447        log::info!("arg: {}", arg.to_string_lossy());
448    }
449
450    for (key, value) in std::env::vars_os() {
451        let level = match key.to_string_lossy().borrow() {
452            "LD_PRELOAD" | "LD_STATIC_TLS_EXTRA" | "G_DEBUG" | "G_SLICE" => log::Level::Info,
453            _ => log::Level::Trace,
454        };
455        log::log!(level, "env: {key:?}={value:?}");
456    }
457}
458
459mod export {
460    use super::*;
461
462    #[unsafe(no_mangle)]
463    pub extern "C-unwind" fn main_runShadow(
464        argc: libc::c_int,
465        argv: *const *const libc::c_char,
466    ) -> libc::c_int {
467        let args = (0..argc).map(|x| unsafe { CStr::from_ptr(*argv.add(x as usize)) });
468        let args = args.map(|x| OsStr::from_bytes(x.to_bytes()));
469
470        let result = run_shadow(args.collect());
471        log::logger().flush();
472
473        if let Err(e) = result {
474            if log::log_enabled!(log::Level::Error) {
476                for line in format!("{e:?}").split('\n') {
477                    log::error!("{line}");
478                }
479                log::logger().flush();
480
481                eprintln!("** Shadow did not complete successfully: {e}");
483                eprintln!("**   {}", e.root_cause());
484                eprintln!("** See the log for details");
485            } else {
486                eprintln!("{e:?}");
488            }
489
490            return 1;
491        }
492
493        eprintln!("** Shadow completed successfully");
494        0
495    }
496}