Skip to main content

miri/shims/
foreign_items.rs

1use std::collections::hash_map::Entry;
2use std::io::Write;
3use std::mem;
4use std::path::Path;
5
6use rustc_abi::{Align, ExternAbi, Size};
7use rustc_ast::expand::allocator::NO_ALLOC_SHIM_IS_UNSTABLE;
8use rustc_attr_ir::Linkage;
9use rustc_data_structures::either::Either;
10use rustc_hir::def::DefKind;
11use rustc_hir::def_id::CrateNum;
12use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
13use rustc_middle::mir::interpret::AllocInit;
14use rustc_middle::ty::{Instance, Ty};
15use rustc_middle::{mir, ty};
16use rustc_span::Symbol;
17use rustc_target::callconv::FnAbi;
18use rustc_target::spec::Os;
19
20use super::alloc::EvalContextExt as _;
21use super::backtrace::EvalContextExt as _;
22use crate::concurrency::GenmcEvalContextExt as _;
23use crate::eval::{ExitType, ProgramExitHandler};
24use crate::helpers::EvalContextExt as _;
25use crate::*;
26
27/// Type of dynamic symbols (for `dlsym` et al)
28#[derive(Debug, Copy, Clone)]
29pub struct DynSym(Symbol);
30
31#[expect(clippy::should_implement_trait)]
32impl DynSym {
33    pub fn from_str(name: &str) -> Self {
34        DynSym(Symbol::intern(name))
35    }
36}
37
38impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
39pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
40    /// Emulates calling a foreign item, failing if the item is not supported.
41    /// This function will handle `goto_block` if needed.
42    /// Returns Ok(None) if the foreign item was completely handled
43    /// by this function.
44    /// Returns Ok(Some(body)) if processing the foreign item
45    /// is delegated to another function.
46    fn emulate_foreign_item(
47        &mut self,
48        link_name: Symbol,
49        abi: &FnAbi<'tcx, Ty<'tcx>>,
50        args: &[OpTy<'tcx>],
51        dest: &PlaceTy<'tcx>,
52        ret: Option<mir::BasicBlock>,
53        unwind: mir::UnwindAction,
54    ) -> InterpResult<'tcx, Option<(&'tcx mir::Body<'tcx>, ty::Instance<'tcx>)>> {
55        let ecx = self.eval_context_mut();
56
57        // Handle allocator shim.
58        if let Some(shim) = ecx.machine.allocator_shim_symbols.get(&link_name) {
59            match *shim {
60                Either::Left(other_fn) => {
61                    let handler = ecx
62                        .lookup_exported_fn(other_fn)?
63                        .expect("missing alloc error handler symbol");
64                    return interp_ok(Some(handler));
65                }
66                Either::Right(special) => {
67                    ecx.rust_special_allocator_method(special, link_name, abi, args, dest)?;
68                    ecx.return_to_block(ret)?;
69                    return interp_ok(None);
70                }
71            }
72        }
73
74        // FIXME: avoid allocating memory
75        let dest = ecx.force_allocation(dest)?;
76
77        // The rest either implements the logic, or falls back to `lookup_exported_symbol`.
78        let res = ecx.emulate_foreign_item_inner(link_name, abi, args, &dest)?;
79        res.jump_to_next_block(ecx, &dest.clone().into(), ret, Some(unwind), |ecx| {
80            if let Some(body) = ecx.lookup_exported_fn(link_name)? {
81                return interp_ok(Some(body));
82            }
83
84            throw_machine_stop!(TerminationInfo::UnsupportedForeignItem(format!(
85                "can't call foreign function `{link_name}` on OS `{os}`",
86                os = ecx.tcx.sess.target.os,
87            )));
88        })
89    }
90
91    fn is_dyn_sym(&self, name: &str) -> bool {
92        let ecx = self.eval_context_ref();
93        match &ecx.tcx.sess.target.os {
94            _ if ecx.target_os_is_unix() =>
95                shims::unix::foreign_items::is_dyn_sym(name, &ecx.tcx.sess.target),
96            Os::Windows => shims::windows::foreign_items::is_dyn_sym(name),
97            _ => false,
98        }
99    }
100
101    /// Emulates a call to a `DynSym`.
102    fn emulate_dyn_sym(
103        &mut self,
104        sym: DynSym,
105        abi: &FnAbi<'tcx, Ty<'tcx>>,
106        args: &[OpTy<'tcx>],
107        dest: &PlaceTy<'tcx>,
108        ret: Option<mir::BasicBlock>,
109        unwind: mir::UnwindAction,
110    ) -> InterpResult<'tcx> {
111        let res = self.emulate_foreign_item(sym.0, abi, args, dest, ret, unwind)?;
112        assert!(res.is_none(), "DynSyms that delegate are not supported");
113        interp_ok(())
114    }
115
116    /// Lookup the instance that has `link_name` as the symbol name.
117    fn lookup_exported_symbol(
118        &self,
119        link_name: Symbol,
120    ) -> InterpResult<'tcx, Option<ty::Instance<'tcx>>> {
121        let ecx = self.eval_context_ref();
122        let tcx = ecx.tcx.tcx;
123
124        // If the result was cached, just return it.
125        // (Cannot use `or_insert` since the code below might have to throw an error.)
126        let mut cache = ecx.machine.exported_symbols_cache.borrow_mut();
127        let entry = cache.entry(link_name);
128        let instance = *match entry {
129            Entry::Occupied(e) => e.into_mut(),
130            Entry::Vacant(e) => {
131                // Find it if it was not cached.
132
133                struct SymbolTarget<'tcx> {
134                    instance: ty::Instance<'tcx>,
135                    cnum: CrateNum,
136                    is_weak: bool,
137                }
138                let mut symbol_target: Option<SymbolTarget<'tcx>> = None;
139                helpers::iter_exported_symbols(tcx, |cnum, def_id, _used| {
140                    let attrs = tcx.codegen_fn_attrs(def_id);
141                    // Skip over items without an explicitly defined symbol name.
142                    if !(attrs.symbol_name.is_some()
143                        || attrs.flags.contains(CodegenFnAttrFlags::RUSTC_STD_INTERNAL_SYMBOL))
144                    {
145                        return interp_ok(());
146                    }
147
148                    let instance = Instance::mono(tcx, def_id);
149                    let symbol_name = tcx.symbol_name(instance).name;
150                    let is_weak = attrs.linkage == Some(Linkage::WeakAny);
151                    if symbol_name == link_name.as_str() {
152                        if let Some(original) = &symbol_target {
153                            // There is more than one definition with this name. What we do now
154                            // depends on whether one or both definitions are weak.
155                            match (is_weak, original.is_weak) {
156                                (false, true) => {
157                                    // Original definition is a weak definition. Override it.
158
159                                    symbol_target = Some(SymbolTarget {
160                                        instance: ty::Instance::mono(tcx, def_id),
161                                        cnum,
162                                        is_weak,
163                                    });
164                                }
165                                (true, false) => {
166                                    // Current definition is a weak definition. Keep the original one.
167                                }
168                                (true, true) | (false, false) => {
169                                    // Either both definitions are non-weak or both are weak. In
170                                    // either case return an error. For weak definitions we error
171                                    // because it is unspecified which definition would have been
172                                    // picked by the linker.
173
174                                    // Make sure we are consistent wrt what is 'first' and 'second'.
175                                    let original_span = tcx.def_span(original.instance.def_id());
176                                    let span = tcx.def_span(def_id);
177                                    if original_span.lo_hi() < span.lo_hi() {
178                                        throw_machine_stop!(
179                                            TerminationInfo::MultipleSymbolDefinitions {
180                                                link_name,
181                                                first: original_span,
182                                                first_crate: tcx.crate_name(original.cnum),
183                                                second: span,
184                                                second_crate: tcx.crate_name(cnum),
185                                            }
186                                        );
187                                    } else {
188                                        throw_machine_stop!(
189                                            TerminationInfo::MultipleSymbolDefinitions {
190                                                link_name,
191                                                first: span,
192                                                first_crate: tcx.crate_name(cnum),
193                                                second: original_span,
194                                                second_crate: tcx.crate_name(original.cnum),
195                                            }
196                                        );
197                                    }
198                                }
199                            }
200                        } else {
201                            symbol_target = Some(SymbolTarget {
202                                instance: ty::Instance::mono(tcx, def_id),
203                                cnum,
204                                is_weak,
205                            });
206                        }
207                    }
208                    interp_ok(())
209                })?;
210
211                e.insert(symbol_target.map(|SymbolTarget { instance, .. }| instance))
212            }
213        };
214        drop(cache);
215        interp_ok(instance)
216    }
217
218    /// Lookup the body of a function that has `link_name` as the symbol name.
219    fn lookup_exported_fn(
220        &self,
221        link_name: Symbol,
222    ) -> InterpResult<'tcx, Option<(&'tcx mir::Body<'tcx>, ty::Instance<'tcx>)>> {
223        let ecx = self.eval_context_ref();
224        let instance = ecx.lookup_exported_symbol(link_name)?;
225        if let Some(instance) = &instance {
226            if !matches!(ecx.tcx.def_kind(instance.def_id()), DefKind::Fn | DefKind::AssocFn) {
227                throw_ub_format!(
228                    "attempt to call an exported symbol that is not defined as a function"
229                );
230            }
231        }
232        match instance {
233            None => interp_ok(None),
234            Some(instance) => interp_ok(Some((ecx.load_mir(instance.def, None)?, instance))),
235        }
236    }
237
238    /// Lookup the instance of a static that has `link_name` as the symbol name.
239    fn lookup_exported_static(
240        &self,
241        link_name: Symbol,
242    ) -> InterpResult<'tcx, Option<ty::Instance<'tcx>>> {
243        let ecx = self.eval_context_ref();
244        let instance = ecx.lookup_exported_symbol(link_name)?;
245        if let Some(instance) = &instance {
246            if !matches!(ecx.tcx.def_kind(instance.def_id()), DefKind::Static { .. }) {
247                throw_ub_format!(
248                    "attempt to access an exported symbol `{link_name}` that is not defined as a static"
249                );
250            }
251        }
252        interp_ok(instance)
253    }
254}
255
256impl<'tcx> EvalContextExtPriv<'tcx> for crate::MiriInterpCx<'tcx> {}
257trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
258    fn emulate_foreign_item_inner(
259        &mut self,
260        link_name: Symbol,
261        abi: &FnAbi<'tcx, Ty<'tcx>>,
262        args: &[OpTy<'tcx>],
263        dest: &MPlaceTy<'tcx>,
264    ) -> InterpResult<'tcx, EmulateItemResult> {
265        let ecx = self.eval_context_mut();
266
267        // First deal with any external C functions in linked .so file.
268        #[cfg(all(feature = "native-lib", unix))]
269        if !ecx.machine.native_lib.is_empty() {
270            use crate::shims::native_lib::EvalContextExt as _;
271            // An Ok(false) here means that the function being called was not exported
272            // by the specified `.so` file; we should continue and check if it corresponds to
273            // a provided shim.
274            if ecx.call_native_fn(link_name, dest, args)? {
275                return interp_ok(EmulateItemResult::NeedsReturn);
276            }
277        }
278        // When adding a new shim, you should follow the following pattern:
279        // ```
280        // "shim_name" => {
281        //     let [arg1, arg2, arg3] = &this.check_shim_sig(
282        //         shim_sig!(extern "C" fn(usize, *libc::some_type, *i32) -> u32),
283        //         (link_name, abi, args),
284        //     )?;
285        //     this.shim_name(arg1, arg2, arg3, dest)?;
286        // }
287        // ```
288        // and then define `shim_name` as a helper function in an extension trait in a suitable file
289        // (see e.g. `unix/fs.rs`):
290        // ```
291        // fn shim_name(
292        //     &mut self,
293        //     arg1: &OpTy<'tcx>,
294        //     arg2: &OpTy<'tcx>,
295        //     arg3: &OpTy<'tcx>,
296        //     dest: &MPlaceTy<'tcx>)
297        // -> InterpResult<'tcx, Scalar> {
298        //     let ecx = self.eval_context_mut();
299        //
300        //     // First thing: load all the arguments. Details depend on the shim.
301        //     let arg1 = ecx.read_scalar(arg1)?.to_u32()?;
302        //     let arg2 = ecx.read_pointer(arg2)?; // when you need to work with the pointer directly
303        //     let arg3 = ecx.deref_pointer(arg3)?; // when you want to load/store through the pointer
304        //
305        //     // ...
306        //
307        //     ecx.write_int(42, dest)?;
308        //     interp_ok(())
309        // }
310        // ```
311        // You might find existing shims not following this pattern, most
312        // likely because they predate it or because for some reason they cannot be made to fit.
313
314        // Here we dispatch all the shims for foreign functions. If you have a platform specific
315        // shim, add it to the corresponding submodule.
316        match link_name.as_str() {
317            // Magic function Rust emits (and not as part of the allocator shim).
318            name if name == ecx.mangle_internal_symbol(NO_ALLOC_SHIM_IS_UNSTABLE) => {
319                // This is a no-op shim that only exists to prevent making the allocator shims
320                // instantly stable.
321                let [] = &ecx
322                    .check_shim_sig(shim_sig!(extern "Rust" fn() -> ()), (link_name, abi, args))?;
323            }
324
325            // Miri-specific extern functions
326            "miri_alloc" => {
327                let [size, align] = &ecx.check_shim_sig(
328                    shim_sig!(extern "Rust" fn(usize, usize) -> *()),
329                    (link_name, abi, args),
330                )?;
331                let size = ecx.read_target_usize(size)?;
332                let align = ecx.read_target_usize(align)?;
333
334                ecx.check_rust_alloc_request(size, align)?;
335
336                let ptr = ecx.allocate_ptr(
337                    Size::from_bytes(size),
338                    Align::from_bytes(align).unwrap(),
339                    MiriMemoryKind::Miri.into(),
340                    AllocInit::Uninit,
341                )?;
342
343                ecx.write_pointer(ptr, dest)?;
344            }
345            "miri_dealloc" => {
346                let [ptr, old_size, align] = &ecx.check_shim_sig(
347                    shim_sig!(extern "Rust" fn(*(), usize, usize) -> ()),
348                    (link_name, abi, args),
349                )?;
350                let ptr = ecx.read_pointer(ptr)?;
351                let old_size = ecx.read_target_usize(old_size)?;
352                let align = ecx.read_target_usize(align)?;
353
354                // No need to check old_size/align; we anyway check that they match the allocation.
355                ecx.deallocate_ptr(
356                    ptr,
357                    Some((Size::from_bytes(old_size), Align::from_bytes(align).unwrap())),
358                    MiriMemoryKind::Miri.into(),
359                )?;
360            }
361            "miri_track_alloc" => {
362                let [ptr] = &ecx.check_shim_sig(
363                    shim_sig!(extern "Rust" fn(*()) -> ()),
364                    (link_name, abi, args),
365                )?;
366                let ptr = ecx.read_pointer(ptr)?;
367                let (alloc_id, _, _) = ecx.ptr_get_alloc_id(ptr, 0).map_err_kind(|_e| {
368                    err_machine_stop!(TerminationInfo::Abort(format!(
369                        "pointer passed to `miri_get_alloc_id` must not be dangling, got {ptr:?}"
370                    )))
371                })?;
372                if ecx.machine.tracked_alloc_ids.insert(alloc_id) {
373                    let info = ecx.get_alloc_info(alloc_id);
374                    ecx.emit_diagnostic(NonHaltingDiagnostic::TrackingAlloc(
375                        alloc_id, info.size, info.align,
376                    ));
377                }
378            }
379            "miri_start_unwind" => {
380                let [payload] = &ecx.check_shim_sig(
381                    shim_sig!(extern "Rust" fn(*()) -> !),
382                    (link_name, abi, args),
383                )?;
384                ecx.handle_miri_start_unwind(payload)?;
385                return interp_ok(EmulateItemResult::NeedsUnwind);
386            }
387            "miri_run_provenance_gc" => {
388                let [] = &ecx
389                    .check_shim_sig(shim_sig!(extern "Rust" fn() -> ()), (link_name, abi, args))?;
390                ecx.run_provenance_gc();
391            }
392            "miri_get_alloc_id" => {
393                let [ptr] = &ecx.check_shim_sig(
394                    shim_sig!(extern "Rust" fn(*()) -> u64),
395                    (link_name, abi, args),
396                )?;
397                let ptr = ecx.read_pointer(ptr)?;
398                let (alloc_id, _, _) = ecx.ptr_get_alloc_id(ptr, 0).map_err_kind(|_e| {
399                    err_machine_stop!(TerminationInfo::Abort(format!(
400                        "pointer passed to `miri_get_alloc_id` must not be dangling, got {ptr:?}"
401                    )))
402                })?;
403                ecx.write_scalar(Scalar::from_u64(alloc_id.0.get()), dest)?;
404            }
405            "miri_print_borrow_state" => {
406                let [id, show_unnamed] = &ecx.check_shim_sig(
407                    shim_sig!(extern "Rust" fn(u64, bool) -> ()),
408                    (link_name, abi, args),
409                )?;
410                let id = ecx.read_scalar(id)?.to_u64()?;
411                let show_unnamed = ecx.read_scalar(show_unnamed)?.to_bool()?;
412                if let Some(id) = std::num::NonZero::new(id).map(AllocId)
413                    && ecx.get_alloc_info(id).kind == AllocKind::LiveData
414                {
415                    ecx.print_borrow_state(id, show_unnamed)?;
416                } else {
417                    eprintln!("{id} is not the ID of a live data allocation");
418                }
419            }
420            "miri_pointer_name" => {
421                // This associates a name to a tag. Very useful for debugging, and also makes
422                // tests more strict.
423                let [ptr, nth_parent, name] = &ecx.check_shim_sig(
424                    shim_sig!(extern "Rust" fn(*(), u8, &[u8]) -> ()),
425                    (link_name, abi, args),
426                )?;
427                let ptr = ecx.read_pointer(ptr)?;
428                let nth_parent = ecx.read_scalar(nth_parent)?.to_u8()?;
429                let name = ecx.read_immediate(name)?;
430
431                let name = ecx.read_byte_slice(&name)?;
432                // We must make `name` owned because we need to
433                // end the shared borrow from `read_byte_slice` before we can
434                // start the mutable borrow for `give_pointer_debug_name`.
435                let name = String::from_utf8_lossy(name).into_owned();
436                ecx.give_pointer_debug_name(ptr, nth_parent, &name)?;
437            }
438            "miri_static_root" => {
439                let [ptr] = &ecx.check_shim_sig(
440                    shim_sig!(extern "Rust" fn(*()) -> ()),
441                    (link_name, abi, args),
442                )?;
443                let ptr = ecx.read_pointer(ptr)?;
444                let (alloc_id, offset, _) = ecx.ptr_get_alloc_id(ptr, 0)?;
445                if offset != Size::ZERO {
446                    throw_unsup_format!(
447                        "pointer passed to `miri_static_root` must point to beginning of an allocated block"
448                    );
449                }
450                ecx.machine.static_roots.push(alloc_id);
451            }
452            "miri_host_to_target_path" => {
453                let [ptr, out, out_size] = &ecx.check_shim_sig(
454                    shim_sig!(extern "Rust" fn(*(), *(), usize) -> usize),
455                    (link_name, abi, args),
456                )?;
457                let ptr = ecx.read_pointer(ptr)?;
458                let out = ecx.read_pointer(out)?;
459                let out_size = ecx.read_scalar(out_size)?.to_target_usize(ecx)?;
460
461                // The host affects program behavior here, so this requires isolation to be disabled.
462                ecx.check_no_isolation("`miri_host_to_target_path`")?;
463
464                // We read this as a plain OsStr and write it as a path, which will convert it to the target.
465                let path = ecx.read_os_str_from_c_str(ptr)?.to_owned();
466                let (success, needed_size) =
467                    ecx.write_path_to_c_str(Path::new(&path), out, out_size)?;
468                // Return value: 0 on success, otherwise the size it would have needed.
469                ecx.write_int(if success { 0 } else { needed_size }, dest)?;
470            }
471            "miri_thread_spawn" => {
472                let [start_routine, func_arg] = &ecx.check_shim_sig(
473                    // FIXME: The first argument is actually a function pointer.
474                    shim_sig!(extern "Rust" fn(fn(..) -> _, *()) -> usize),
475                    (link_name, abi, args),
476                )?;
477                let start_routine = ecx.read_pointer(start_routine)?;
478                let func_arg = ecx.read_immediate(func_arg)?;
479
480                ecx.start_regular_thread(
481                    Some(dest.clone()),
482                    start_routine,
483                    ExternAbi::Rust,
484                    func_arg,
485                    ecx.machine.layouts.unit,
486                )?;
487            }
488            "miri_thread_join" => {
489                let [thread_id] = &ecx.check_shim_sig(
490                    shim_sig!(extern "Rust" fn(usize) -> bool),
491                    (link_name, abi, args),
492                )?;
493
494                let thread = ecx.read_target_usize(thread_id)?;
495                // Joining a terminated thread is valid.
496                use crate::concurrency::thread::ThreadLookupError;
497                let thread = match ecx.thread_id_try_from(thread) {
498                    Ok(id) | Err(ThreadLookupError::Terminated(id)) => Some(id),
499                    Err(ThreadLookupError::InvalidId) => None,
500                };
501                if let Some(thread) = thread {
502                    ecx.join_thread_exclusive(
503                        thread,
504                        /* success_retval */ Scalar::from_bool(true),
505                        dest,
506                    )?;
507                } else {
508                    ecx.write_scalar(Scalar::from_bool(false), dest)?;
509                }
510            }
511            // Hint that a loop is spinning indefinitely.
512            "miri_spin_loop" => {
513                let [] = &ecx
514                    .check_shim_sig(shim_sig!(extern "Rust" fn() -> ()), (link_name, abi, args))?;
515
516                // Try to run another thread to maximize the chance of finding actual bugs.
517                ecx.yield_active_thread();
518            }
519            // Obtains the size of a Miri backtrace. See the README for details.
520            "miri_backtrace_size" => {
521                ecx.handle_miri_backtrace_size(abi, link_name, args, dest)?;
522            }
523            // Obtains a Miri backtrace. See the README for details.
524            "miri_get_backtrace" => {
525                // `check_shim` happens inside `handle_miri_get_backtrace`.
526                ecx.handle_miri_get_backtrace(abi, link_name, args)?;
527            }
528            // Resolves a Miri backtrace frame. See the README for details.
529            "miri_resolve_frame" => {
530                // `check_shim` happens inside `handle_miri_resolve_frame`.
531                ecx.handle_miri_resolve_frame(abi, link_name, args, dest)?;
532            }
533            // Writes the function and file names of a Miri backtrace frame into a user provided buffer. See the README for details.
534            "miri_resolve_frame_names" => {
535                ecx.handle_miri_resolve_frame_names(abi, link_name, args)?;
536            }
537            // Writes some bytes to the interpreter's stdout/stderr. See the README for details.
538            "miri_write_to_stdout" | "miri_write_to_stderr" => {
539                let [msg] = &ecx.check_shim_sig(
540                    shim_sig!(extern "Rust" fn(&[u8]) -> ()),
541                    (link_name, abi, args),
542                )?;
543                let msg = ecx.read_immediate(msg)?;
544                let msg = ecx.read_byte_slice(&msg)?;
545                // Note: we're ignoring errors writing to host stdout/stderr.
546                let _ignore = match link_name.as_str() {
547                    "miri_write_to_stdout" => std::io::stdout().write_all(msg),
548                    "miri_write_to_stderr" => std::io::stderr().write_all(msg),
549                    _ => unreachable!(),
550                };
551            }
552            // Promises that a pointer has a given symbolic alignment.
553            "miri_promise_symbolic_alignment" => {
554                use rustc_abi::AlignFromBytesError;
555
556                let [ptr, align] = &ecx.check_shim_sig(
557                    shim_sig!(extern "Rust" fn(*(), usize) -> ()),
558                    (link_name, abi, args),
559                )?;
560
561                let ptr = ecx.read_pointer(ptr)?;
562                let align = ecx.read_target_usize(align)?;
563                if !align.is_power_of_two() {
564                    throw_unsup_format!(
565                        "`miri_promise_symbolic_alignment`: alignment must be a power of 2, got {align}"
566                    );
567                }
568                let align = Align::from_bytes(align).unwrap_or_else(|err| {
569                    match err {
570                        AlignFromBytesError::NotPowerOfTwo(_) => unreachable!(),
571                        // When the alignment is a power of 2 but too big, clamp it to MAX.
572                        AlignFromBytesError::TooLarge(_) => Align::MAX,
573                    }
574                });
575                let addr = ptr.addr();
576                // Cannot panic since `align` is a power of 2 and hence non-zero.
577                if addr.bytes().strict_rem(align.bytes()) != 0 {
578                    throw_unsup_format!(
579                        "`miri_promise_symbolic_alignment`: pointer is not actually aligned"
580                    );
581                }
582                if let Ok((alloc_id, offset, ..)) = ecx.ptr_try_get_alloc_id(ptr, 0) {
583                    let alloc_align = ecx.get_alloc_info(alloc_id).align;
584                    // If the newly promised alignment is bigger than the native alignment of this
585                    // allocation, and bigger than the previously promised alignment, then set it.
586                    if align > alloc_align
587                        && ecx
588                            .machine
589                            .symbolic_alignment
590                            .get_mut()
591                            .get(&alloc_id)
592                            .is_none_or(|&(_, old_align)| align > old_align)
593                    {
594                        ecx.machine.symbolic_alignment.get_mut().insert(alloc_id, (offset, align));
595                    }
596                }
597            }
598            // GenMC mode: Assume statements block the current thread when their condition is false.
599            "miri_genmc_assume" => {
600                let [condition] = &ecx.check_shim_sig(
601                    shim_sig!(extern "Rust" fn(bool) -> ()),
602                    (link_name, abi, args),
603                )?;
604
605                if ecx.machine.data_race.as_genmc_ref().is_some() {
606                    ecx.handle_genmc_verifier_assume(condition)?;
607                } else {
608                    throw_unsup_format!("miri_genmc_assume is only supported in GenMC mode")
609                }
610            }
611
612            // Aborting the process.
613            "exit" => {
614                let [exit_code] = &ecx
615                    .check_shim_sig(shim_sig!(extern "C" fn(i32) -> ()), (link_name, abi, args))?;
616                let exit_code = ecx.read_scalar(exit_code)?.to_i32()?;
617                // Ensure `exit` gets called at most once.
618                let old_exit = mem::replace(&mut ecx.machine.exit_called, true);
619                if old_exit {
620                    throw_ub_format!("`exit` called more than once");
621                }
622                // Initialize the at-exit state machine.
623                ecx.replace_active_thread_stack({
624                    let mut state_machine =
625                        ProgramExitHandler::new(exit_code, ExitType::ExitCalled);
626                    Box::new(move |m| state_machine.on_stack_empty(m))
627                });
628                // We "jumped" to the exit handling.
629                return interp_ok(EmulateItemResult::AlreadyJumped);
630            }
631            "abort" => {
632                // FIXME: This does not have a direct test (#3179).
633                let [] =
634                    &ecx.check_shim_sig(shim_sig!(extern "C" fn() -> ()), (link_name, abi, args))?;
635                throw_machine_stop!(TerminationInfo::Abort(
636                    "the program aborted execution".to_owned()
637                ));
638            }
639
640            // Standard C allocation
641            "malloc" => {
642                let [size] = &ecx.check_shim_sig(
643                    shim_sig!(extern "C" fn(usize) -> *_),
644                    (link_name, abi, args),
645                )?;
646                let size = ecx.read_target_usize(size)?;
647                if size <= ecx.max_size_of_val().bytes() {
648                    let res = ecx.malloc(size, AllocInit::Uninit)?;
649                    ecx.write_pointer(res, dest)?;
650                } else {
651                    // If this does not fit in an isize, return null and, on Unix, set errno.
652                    if ecx.target_os_is_unix() {
653                        ecx.set_last_error(LibcError("ENOMEM"))?;
654                    }
655                    ecx.write_int(0, dest)?;
656                }
657            }
658            "calloc" => {
659                let [items, elem_size] = &ecx.check_shim_sig(
660                    shim_sig!(extern "C" fn(usize, usize) -> *_),
661                    (link_name, abi, args),
662                )?;
663                let items = ecx.read_target_usize(items)?;
664                let elem_size = ecx.read_target_usize(elem_size)?;
665                if let Some(size) = ecx.compute_size_in_bytes(Size::from_bytes(elem_size), items) {
666                    let res = ecx.malloc(size.bytes(), AllocInit::Zero)?;
667                    ecx.write_pointer(res, dest)?;
668                } else {
669                    // On size overflow, return null and, on Unix, set errno.
670                    if ecx.target_os_is_unix() {
671                        ecx.set_last_error(LibcError("ENOMEM"))?;
672                    }
673                    ecx.write_int(0, dest)?;
674                }
675            }
676            "free" => {
677                let [ptr] = &ecx
678                    .check_shim_sig(shim_sig!(extern "C" fn(*_) -> ()), (link_name, abi, args))?;
679                let ptr = ecx.read_pointer(ptr)?;
680                ecx.free(ptr)?;
681            }
682            "realloc" => {
683                let [old_ptr, new_size] = &ecx.check_shim_sig(
684                    shim_sig!(extern "C" fn(*_, usize) -> *_),
685                    (link_name, abi, args),
686                )?;
687                let old_ptr = ecx.read_pointer(old_ptr)?;
688                let new_size = ecx.read_target_usize(new_size)?;
689                if new_size <= ecx.max_size_of_val().bytes() {
690                    let res = ecx.realloc(old_ptr, new_size)?;
691                    ecx.write_pointer(res, dest)?;
692                } else {
693                    // If this does not fit in an isize, return null and, on Unix, set errno.
694                    if ecx.target_os_is_unix() {
695                        ecx.set_last_error(LibcError("ENOMEM"))?;
696                    }
697                    ecx.write_int(0, dest)?;
698                }
699            }
700            "malloc_usable_size" => {
701                ecx.check_target_os(&[Os::Linux, Os::FreeBsd, Os::Android], link_name)?;
702
703                let [ptr] = &ecx.check_shim_sig(
704                    shim_sig!(extern "C" fn(*_) -> usize),
705                    (link_name, abi, args),
706                )?;
707                let ptr = ecx.read_pointer(ptr)?;
708                let size = if ecx.ptr_is_null(ptr)? {
709                    0
710                } else {
711                    let (alloc_id, offset, _) = ecx.ptr_get_alloc_id(ptr, 0)?;
712                    if offset.bytes() != 0 {
713                        throw_ub_format!(
714                            "`malloc_usable_size` was called on a pointer that does not point to the beginning of its allocation"
715                        );
716                    }
717                    let Some((alloc_kind, _)) = ecx.memory.alloc_map().get(alloc_id) else {
718                        throw_ub_format!(
719                            "`malloc_usable_size` was called on a pointer to memory not managed by the C allocator"
720                        );
721                    };
722                    if *alloc_kind != MiriMemoryKind::C.into() {
723                        throw_ub_format!(
724                            "`malloc_usable_size` was called on a pointer to {alloc_kind} memory, which is not managed by the C allocator"
725                        );
726                    }
727                    ecx.get_alloc_info(alloc_id).size.bytes()
728                };
729                ecx.write_scalar(Scalar::from_target_usize(size, ecx), dest)?;
730            }
731
732            // C memory handling functions
733            "memcmp" => {
734                let [left, right, n] = &ecx.check_shim_sig(
735                    shim_sig!(extern "C" fn(*_, *_, usize) -> i32),
736                    (link_name, abi, args),
737                )?;
738                let left = ecx.read_pointer(left)?;
739                let right = ecx.read_pointer(right)?;
740                let n = Size::from_bytes(ecx.read_target_usize(n)?);
741
742                // C requires that this must always be a valid pointer (C18 §7.1.4).
743                ecx.ptr_get_alloc_id(left, 0)?;
744                ecx.ptr_get_alloc_id(right, 0)?;
745
746                // memcmp does *not* have any wording like `memchr` that says anything about
747                // stopping as soon as a difference is found. So we requires both buffers
748                // to be fully inbounds and initialized.
749
750                let result = {
751                    let left_bytes = ecx.read_bytes_ptr_strip_provenance(left, n)?;
752                    let right_bytes = ecx.read_bytes_ptr_strip_provenance(right, n)?;
753
754                    use std::cmp::Ordering::*;
755                    match left_bytes.cmp(right_bytes) {
756                        Less => -1i32,
757                        Equal => 0,
758                        Greater => 1,
759                    }
760                };
761
762                ecx.write_scalar(Scalar::from_i32(result), dest)?;
763            }
764            "memchr" => {
765                let [ptr, val, num] = &ecx.check_shim_sig(
766                    shim_sig!(extern "C" fn(*_, i32, usize) -> *_),
767                    (link_name, abi, args),
768                )?;
769                let ptr = ecx.read_pointer(ptr)?;
770                let val = ecx.read_scalar(val)?.to_i32()?;
771                let num = ecx.read_target_usize(num)?;
772                // The docs say val is "interpreted as unsigned char".
773                #[expect(clippy::as_conversions)]
774                let val = val as u8;
775
776                // C requires that this must always be a valid pointer (C18 §7.1.4).
777                ecx.ptr_get_alloc_id(ptr, 0)?;
778
779                // "The implementation shall behave as if it reads the characters sequentially and
780                // stops as soon as a matching character is found."
781                let needle_ptr = ecx.memchr(ptr, 0..num, val)?.map(|(_idx, ptr)| ptr);
782
783                if let Some(needle_ptr) = needle_ptr {
784                    ecx.write_pointer(needle_ptr, dest)?;
785                } else {
786                    ecx.write_int(0, dest)?;
787                }
788            }
789            "memrchr" => {
790                ecx.check_target_os(&[Os::Linux, Os::Android, Os::FreeBsd], link_name)?;
791
792                let [ptr, val, num] = &ecx.check_shim_sig(
793                    shim_sig!(extern "C" fn(*_, i32, usize) -> *_),
794                    (link_name, abi, args),
795                )?;
796                let ptr = ecx.read_pointer(ptr)?;
797                let val = ecx.read_scalar(val)?.to_i32()?;
798                let num = ecx.read_target_usize(num)?;
799                // The docs say val is "interpreted as unsigned char".
800                #[expect(clippy::as_conversions)]
801                let val = val as u8;
802
803                // C requires that this must always be a valid pointer (C18 §7.1.4).
804                ecx.ptr_get_alloc_id(ptr, 0)?;
805
806                // We use the same early-abort search strategy as `memchr` (see above).
807                let needle_ptr = ecx.memchr(ptr, (0..num).rev(), val)?.map(|(_idx, ptr)| ptr);
808
809                if let Some(needle_ptr) = needle_ptr {
810                    ecx.write_pointer(needle_ptr, dest)?;
811                } else {
812                    ecx.write_int(0, dest)?;
813                }
814            }
815            "strlen" => {
816                let [ptr] = &ecx.check_shim_sig(
817                    shim_sig!(extern "C" fn(*_) -> usize),
818                    (link_name, abi, args),
819                )?;
820                let ptr = ecx.read_pointer(ptr)?;
821                // This reads at least 1 byte, so we are already enforcing that this is a valid pointer.
822                let n = ecx.read_c_str(ptr)?.len();
823                ecx.write_scalar(Scalar::from_target_usize(u64::try_from(n).unwrap(), ecx), dest)?;
824            }
825            "strnlen" => {
826                let [ptr, num] = &ecx.check_shim_sig(
827                    shim_sig!(extern "C" fn(*_, usize) -> usize),
828                    (link_name, abi, args),
829                )?;
830                let ptr = ecx.read_pointer(ptr)?;
831                let num = ecx.read_target_usize(num)?;
832
833                // C requires that this must always be a valid pointer (C18 §7.1.4).
834                ecx.ptr_get_alloc_id(ptr, 0)?;
835
836                // The docs say this behaves like memchr, which only deref's the memory it actually
837                // needs to compare.
838                let idx = ecx.memchr(ptr, 0..num, 0)?.map(|(idx, _ptr)| idx).unwrap_or(num);
839                ecx.write_scalar(Scalar::from_target_usize(idx, ecx), dest)?;
840            }
841            "wcslen" => {
842                let [ptr] = &ecx.check_shim_sig(
843                    shim_sig!(extern "C" fn(*_) -> usize),
844                    (link_name, abi, args),
845                )?;
846                let ptr = ecx.read_pointer(ptr)?;
847                // This reads at least 1 byte, so we are already enforcing that this is a valid pointer.
848                let n = ecx.read_wchar_t_str(ptr)?.len();
849                ecx.write_scalar(Scalar::from_target_usize(u64::try_from(n).unwrap(), ecx), dest)?;
850            }
851            "memcpy" => {
852                let [ptr_dest, ptr_src, n] = &ecx.check_shim_sig(
853                    shim_sig!(extern "C" fn(*_, *_, usize) -> *_),
854                    (link_name, abi, args),
855                )?;
856                let ptr_dest = ecx.read_pointer(ptr_dest)?;
857                let ptr_src = ecx.read_pointer(ptr_src)?;
858                let n = ecx.read_target_usize(n)?;
859
860                // C requires that this must always be a valid pointer, even if `n` is zero, so we better check that.
861                // (This is more than Rust requires, so `mem_copy` is not sufficient.)
862                ecx.ptr_get_alloc_id(ptr_dest, 0)?;
863                ecx.ptr_get_alloc_id(ptr_src, 0)?;
864
865                ecx.mem_copy(ptr_src, ptr_dest, Size::from_bytes(n), true)?;
866                ecx.write_pointer(ptr_dest, dest)?;
867            }
868            "strcpy" => {
869                let [ptr_dest, ptr_src] = &ecx.check_shim_sig(
870                    shim_sig!(extern "C" fn(*_, *_) -> *_),
871                    (link_name, abi, args),
872                )?;
873                let ptr_dest = ecx.read_pointer(ptr_dest)?;
874                let ptr_src = ecx.read_pointer(ptr_src)?;
875
876                // We use `read_c_str` to determine the amount of data to copy,
877                // and then use `mem_copy` for the actual copy. This means
878                // pointer provenance is preserved by this implementation of `strcpy`.
879                // That is probably overly cautious, but there also is no fundamental
880                // reason to have `strcpy` destroy pointer provenance.
881                // This reads at least 1 byte, so we are already enforcing that this is a valid pointer.
882                let n = ecx.read_c_str(ptr_src)?.len().strict_add(1);
883                ecx.mem_copy(ptr_src, ptr_dest, Size::from_bytes(n), true)?;
884                ecx.write_pointer(ptr_dest, dest)?;
885            }
886            "memset" => {
887                let [ptr_dest, val, n] = &ecx.check_shim_sig(
888                    shim_sig!(extern "C" fn(*_, i32, usize) -> *_),
889                    (link_name, abi, args),
890                )?;
891                let ptr_dest = ecx.read_pointer(ptr_dest)?;
892                let val = ecx.read_scalar(val)?.to_i32()?;
893                let n = ecx.read_target_usize(n)?;
894                // The docs say val is "interpreted as unsigned char".
895                #[expect(clippy::as_conversions)]
896                let val = val as u8;
897
898                // C requires that this must always be a valid pointer, even if `n` is zero, so we better check that.
899                ecx.ptr_get_alloc_id(ptr_dest, 0)?;
900
901                let bytes = std::iter::repeat_n(val, n.try_into().unwrap());
902                ecx.write_bytes_ptr(ptr_dest, bytes)?;
903                ecx.write_pointer(ptr_dest, dest)?;
904            }
905
906            // Fallback to shims in submodules.
907            _ => {
908                // Math shims
909                if let res = shims::math::EvalContextExt::emulate_foreign_item_inner(
910                    ecx, link_name, abi, args, dest,
911                )? && !matches!(res, EmulateItemResult::NotSupported)
912                {
913                    return interp_ok(res);
914                }
915
916                // Platform-specific shims
917                return match &ecx.tcx.sess.target.os {
918                    _ if ecx.target_os_is_unix() =>
919                        shims::unix::foreign_items::EvalContextExt::emulate_foreign_item_inner(
920                            ecx, link_name, abi, args, dest,
921                        ),
922                    Os::Windows =>
923                        shims::windows::foreign_items::EvalContextExt::emulate_foreign_item_inner(
924                            ecx, link_name, abi, args, dest,
925                        ),
926                    _ => interp_ok(EmulateItemResult::NotSupported),
927                };
928            }
929        };
930        // We only fall through to here if we did *not* hit the `_` arm above,
931        // i.e., if we actually emulated the function with one of the shims.
932        interp_ok(EmulateItemResult::NeedsReturn)
933    }
934
935    /// For each `idx` yielded by `idxs`, check if `ptr + idx` equals `needle` and return that
936    /// index and a pointer to that element if so. Return `None` if none of the indices match.
937    fn memchr(
938        &self,
939        ptr: Pointer,
940        idxs: impl Iterator<Item = u64>,
941        needle: u8,
942    ) -> InterpResult<'tcx, Option<(u64, Pointer)>> {
943        let ecx = self.eval_context_ref();
944        for idx in idxs {
945            let ptr = ptr.wrapping_offset(Size::from_bytes(idx), ecx);
946            let place = ecx.ptr_to_mplace(ptr, ecx.machine.layouts.u8);
947            let val = ecx.read_scalar(&place)?.to_u8()?;
948            if val == needle {
949                return interp_ok(Some((idx, ptr)));
950            }
951        }
952        interp_ok(None)
953    }
954}