11#![ feature( rustc_private) ]
22
33extern crate miri;
4+ extern crate rustc_abi;
45extern crate rustc_codegen_ssa;
56extern crate rustc_data_structures;
67extern crate rustc_driver;
@@ -16,13 +17,17 @@ extern crate rustc_type_ir;
1617
1718use std:: collections:: { HashMap , HashSet } ;
1819use std:: io:: { self , Write } ;
20+ use std:: num:: NonZeroU64 ;
21+ use std:: ops:: Range ;
1922use std:: path:: PathBuf ;
2023
21- use miri:: Immediate :: { Scalar , ScalarPair , Uninit } ;
22- use miri:: * ;
24+ use miri:: Immediate :: Uninit ;
25+ use miri:: { interpret, * } ;
26+ use rustc_abi:: Size ;
2327use rustc_driver:: Compilation ;
2428use rustc_hir:: attrs:: CrateType ;
2529use rustc_interface:: interface;
30+ use rustc_middle:: mir:: interpret:: AllocId ;
2631use rustc_middle:: mir:: { self , Local , ProjectionElem , VarDebugInfoContents , VarDebugInfoFragment } ;
2732use rustc_middle:: ty:: { TyCtxt , TyKind } ;
2833use rustc_session:: EarlyDiagCtxt ;
@@ -377,10 +382,25 @@ impl<'tcx> PrirodaContext<'tcx> {
377382 DebuggerCommand :: ListLocals => interp_ok ( CommandResult :: Locals ( self . list_locals ( ) ) ) ,
378383 DebuggerCommand :: Print ( local) =>
379384 interp_ok ( CommandResult :: SingleLocal ( self . get_local ( local) ) ) ,
385+ DebuggerCommand :: Follow ( alloc_id, offset) =>
386+ self . follow_alloc ( alloc_id, offset) . map ( CommandResult :: Memory ) ,
380387 DebuggerCommand :: TerminateSession => interp_ok ( CommandResult :: TerminateSession ) ,
381388 }
382389 }
383390
391+ fn follow_alloc ( & self , alloc_id : AllocId , offset : usize ) -> InterpResult < ' tcx , String > {
392+ let alloc = self . ecx . get_alloc_raw ( alloc_id) ?;
393+ if offset > alloc. len ( ) {
394+ return Err ( miri:: err_unsup_format!(
395+ "allocation offset {offset} is outside {alloc_id}"
396+ ) )
397+ . into ( ) ;
398+ }
399+
400+ let memory = self . render_alloc_bytes ( alloc_id, offset..alloc. len ( ) ) ?;
401+ interp_ok ( format ! ( "Allocation {alloc_id}+{offset}: {memory}" ) )
402+ }
403+
384404 fn get_local ( & self , local : usize ) -> Option < LocalDesc > {
385405 let frame = self . ecx . active_thread_stack ( ) . last ( ) ?;
386406
@@ -399,6 +419,106 @@ impl<'tcx> PrirodaContext<'tcx> {
399419 self . build_local_descs ( frame)
400420 }
401421
422+ /// Renders the current byte range of an indirect MIR value.
423+ ///
424+ /// Initialized bytes are shown in hexadecimal, uninitialized bytes as `??`,
425+ /// and complete pointer-sized provenance as pointer markers.
426+ fn render_mplace_bytes ( & self , mplace : & MPlaceTy < ' tcx > ) -> InterpResult < ' tcx , String > {
427+ let size = match self . ecx . size_and_align_of_val ( mplace) ? {
428+ Some ( ( size, _) ) => size,
429+ None => {
430+ // Extern types cannot currently be executed as by-value locals,
431+ // so this path cannot yet be covered by a Priroda UI fixture.
432+ // FIXME: Add coverage once Priroda supports printing dereferenced places.
433+ return interp_ok ( "<unsupported-unsized>" . to_string ( ) ) ;
434+ }
435+ } ;
436+
437+ let size = size. bytes_usize ( ) ;
438+ if size == 0 {
439+ return interp_ok ( "[]" . to_string ( ) ) ;
440+ }
441+
442+ let ( alloc_id, offset, _) =
443+ self . ecx . ptr_get_alloc_id ( mplace. ptr ( ) , size. try_into ( ) . unwrap ( ) ) ?;
444+ let offset = offset. bytes_usize ( ) ;
445+ let range = offset..offset. strict_add ( size) ;
446+
447+ self . render_alloc_bytes ( alloc_id, range)
448+ }
449+
450+ /// Render a raw allocation range without requiring a typed memory place.
451+ ///
452+ /// This is also used by the future-facing `follow` command, where we have a
453+ /// pointer target but do not yet know the target's type or size.
454+ fn render_alloc_bytes (
455+ & self ,
456+ alloc_id : AllocId ,
457+ range : Range < usize > ,
458+ ) -> InterpResult < ' tcx , String > {
459+ let alloc = self . ecx . get_alloc_raw ( alloc_id) ?;
460+
461+ let mut rendered = Vec :: with_capacity ( range. len ( ) ) ;
462+
463+ let ptr_size = self . ecx . tcx . data_layout . pointer_size ( ) ;
464+
465+ for chunk in alloc. init_mask ( ) . range_as_init_chunks ( range. into ( ) ) {
466+ let chunk_range = chunk. range ( ) ;
467+ let chunk_range = chunk_range. start . bytes_usize ( ) ..chunk_range. end . bytes_usize ( ) ;
468+
469+ if chunk. is_init ( ) {
470+ let ptr_size = ptr_size. bytes_usize ( ) ;
471+ let mut cursor = chunk_range. start ;
472+
473+ while cursor < chunk_range. end {
474+ // Full pointer provenance is rendered as a pointer marker. Bytewise
475+ // provenance fragments are intentionally left as raw bytes here: they do
476+ // not represent a complete pointer-sized value.
477+ if let Some ( prov) = alloc. provenance ( ) . get_ptr ( Size :: from_bytes ( cursor) )
478+ && cursor + ptr_size <= chunk_range. end
479+ {
480+ let bytes = alloc. inspect_with_uninit_and_ptr_outside_interpreter (
481+ cursor..cursor + ptr_size,
482+ ) ;
483+ let offset = read_target_uint ( self . ecx . tcx . data_layout . endian , bytes)
484+ . map_err ( |err| {
485+ miri:: err_unsup_format!( "invalid pointer representation: {err}" )
486+ } ) ?;
487+
488+ let offset = Size :: from_bytes ( offset) ;
489+ rendered. push ( format ! ( "{:?}" , Pointer :: new( Some ( prov) , offset) ) ) ;
490+
491+ cursor += ptr_size;
492+ } else {
493+ let byte = alloc
494+ . inspect_with_uninit_and_ptr_outside_interpreter ( cursor..cursor + 1 ) [ 0 ] ;
495+
496+ rendered. push ( format ! ( "{byte:02x}" ) ) ;
497+ cursor += 1 ;
498+ }
499+ }
500+ } else {
501+ rendered. extend ( std:: iter:: repeat_n ( "__" . to_string ( ) , chunk_range. len ( ) ) ) ;
502+ }
503+ }
504+
505+ interp_ok ( format ! ( "[{}]" , rendered. join( " " ) ) )
506+ }
507+
508+ /// Render an evaluated operand using the same raw representation for
509+ /// whole locals and projected MIR places.
510+ fn render_op ( & self , op : OpTy < ' tcx > ) -> String {
511+ match op. as_mplace_or_imm ( ) {
512+ Either :: Right ( imm) => format ! ( "{imm}" ) ,
513+
514+ Either :: Left ( mplace) =>
515+ match self . render_mplace_bytes ( & mplace) . report_err ( ) {
516+ Ok ( bytes) => bytes,
517+ Err ( err) => format ! ( "<error: {}>" , interpret:: format_interp_error( err) ) ,
518+ } ,
519+ }
520+ }
521+
402522 /// Render the source-side path from composite debug info, such as `.field`.
403523 fn render_source_projection (
404524 fragment : Option < & VarDebugInfoFragment < ' tcx > > ,
@@ -486,22 +606,14 @@ impl<'tcx> PrirodaContext<'tcx> {
486606 None => {
487607 local_desc. value = "<dead>" . to_string ( ) ;
488608 }
489- Some ( Either :: Left ( _) ) => {
490- local_desc. value = "<indirect>" . to_string ( ) ;
491- }
492- Some ( Either :: Right ( imm) ) => {
493- match imm {
494- Scalar ( _) => {
495- local_desc. value = "<immediate>" . to_string ( ) ;
496- }
497- ScalarPair ( _, _) => {
498- local_desc. value = "<immediate-pair>" . to_string ( ) ;
499- }
500-
501- Uninit => {
502- local_desc. value = "<uninit>" . to_string ( ) ;
503- }
504- } ;
609+ Some ( Either :: Right ( Uninit ) ) => local_desc. value = "<uninit>" . to_string ( ) ,
610+
611+ Some ( Either :: Left ( _) | Either :: Right ( _) ) => {
612+ let op = self
613+ . ecx
614+ . local_to_op ( local, None )
615+ . expect ( "this error can only occur in CTFE on generic code" ) ;
616+ local_desc. value = self . render_op ( op) ;
505617 }
506618 } ;
507619
@@ -552,7 +664,8 @@ impl<'tcx> PrirodaContext<'tcx> {
552664 // and `_slice._extra`, not as two separate locals both named `_slice`.
553665
554666 // Whole-place debug entries enrich the direct storage-local description.
555- // Projected places and constants are handled separately/deferred.
667+ // Projected places are evaluated from their original MIR Place and use
668+ // the same raw renderer as ordinary locals.
556669 for var_debug_info in & frame. body ( ) . var_debug_info {
557670 if let VarDebugInfoContents :: Place ( place) = & var_debug_info. value {
558671 if let Some ( local_idx) = place. as_local ( )
@@ -566,15 +679,21 @@ impl<'tcx> PrirodaContext<'tcx> {
566679 let storage_projection = Self :: render_storage_projection ( place. projection ) ;
567680 let source_projection =
568681 Self :: render_source_projection ( var_debug_info. composite . as_deref ( ) ) ;
682+ let value = self
683+ . ecx
684+ . eval_place_to_op ( * place, None )
685+ . map ( |op| self . render_op ( op) )
686+ . unwrap_or_else ( |err| {
687+ format ! ( "<error: {}>" , interpret:: format_interp_error( err) )
688+ } ) ;
569689
570690 local_descs. push ( LocalDesc {
571691 source_name : Some ( var_debug_info. name ) ,
572692 source_projection,
573693 local : Some ( place. local ) ,
574694 storage_projection,
575695 ty : place. ty ( local_decls, self . ecx . tcx . tcx ) . ty . to_string ( ) ,
576- // FIXME: projection not handled yet.
577- value : "<unsupported-projection>" . to_string ( ) ,
696+ value,
578697 } ) ;
579698 }
580699 }
@@ -592,6 +711,7 @@ enum DebuggerCommand {
592711 Breakpoint ( PathBuf , usize ) ,
593712 ListLocals ,
594713 Print ( usize ) ,
714+ Follow ( AllocId , usize ) ,
595715}
596716
597717enum BreakpointSetResult {
@@ -605,6 +725,7 @@ enum CommandResult {
605725 BreakpointResult ( BreakpointSetResult ) ,
606726 Locals ( Vec < LocalDesc > ) ,
607727 SingleLocal ( Option < LocalDesc > ) ,
728+ Memory ( String ) ,
608729 // FIXME: distinguish terminating the debugger session from disconnecting a
609730 // frontend and terminating the interpreted program once multiple frontends exist.
610731 TerminateSession ,
@@ -693,6 +814,7 @@ impl Cli {
693814 }
694815 None => println ! ( "no local for this id" ) ,
695816 } ,
817+ CommandResult :: Memory ( memory) => println ! ( "{memory}" ) ,
696818 CommandResult :: TerminateSession => {
697819 println ! ( "quitting" ) ;
698820 return interp_ok ( ( ) ) ;
@@ -725,6 +847,7 @@ impl Cli {
725847 "b" | "break" => self . parse_breakpoint ( args) ,
726848 "l" | "locals" => Some ( DebuggerCommand :: ListLocals ) ,
727849 "p" | "print" => self . parse_print_local ( args) ,
850+ "f" | "follow" => self . parse_follow ( args) ,
728851 _ => None ,
729852 }
730853 }
@@ -757,4 +880,18 @@ impl Cli {
757880 let local = input. parse ( ) . ok ( ) ?;
758881 Some ( DebuggerCommand :: Print ( local) )
759882 }
883+
884+ fn parse_follow ( & self , input : & str ) -> Option < DebuggerCommand > {
885+ let mut parts = input. split_whitespace ( ) ;
886+ let alloc_id = parts. next ( ) ?;
887+ let offset = parts. next ( ) ?;
888+ if parts. next ( ) . is_some ( ) {
889+ return None ;
890+ }
891+
892+ let alloc_id = alloc_id. strip_prefix ( "alloc" ) . unwrap_or ( alloc_id) . parse ( ) . ok ( ) ?;
893+ let alloc_id = AllocId ( NonZeroU64 :: new ( alloc_id) ?) ;
894+ let offset = offset. parse ( ) . ok ( ) ?;
895+ Some ( DebuggerCommand :: Follow ( alloc_id, offset) )
896+ }
760897}
0 commit comments