5353//! [0; 4096]
5454//! ```
5555
56- use anyhow:: { bail, Context , Result } ;
56+ use anyhow:: { anyhow , bail, Context , Result } ;
5757use clap:: { CommandFactory , Parser } ;
5858use zerocopy:: FromBytes ;
5959
60- use humility:: { core:: Core , hubris:: HubrisArchive } ;
60+ use humility:: { core:: Core , hubris:: HubrisArchive , reflect , reflect :: Load } ;
6161use humility_cli:: { ExecutionContext , Subcommand } ;
6262use humility_cmd:: { Archive , Attach , Command , CommandKind , Validate } ;
63+ use humility_doppel as doppel;
6364use humility_log:: { msg, warn} ;
6465
6566#[ derive( Parser , Debug ) ]
@@ -77,35 +78,115 @@ struct HostArgs {
7778 cmd : HostCommand ,
7879}
7980
80- fn read_var (
81+ static SEPARATE_HOST_BOOT_FAIL_NAME : & str = "LAST_HOST_BOOT_FAIL" ;
82+
83+ static SEPARATE_LAST_HOST_PANIC_NAME : & str = "LAST_HOST_PANIC" ;
84+
85+ static HOST_STATE_BUF_NAME : & str =
86+ "task_host_sp_comms::ServerImpl::claim_static_resources::BUFS" ;
87+
88+ /// Mirror type of the internal buf struct in `host_sp_comms`. Must be kept in
89+ /// (partial) sync with that structure (fields that are present need to match,
90+ /// other fields can be ignored).
91+ #[ derive( Load ) ]
92+ struct HostStateBuf {
93+ last_boot_fail : Vec < u8 > ,
94+ last_panic : Vec < u8 > ,
95+ }
96+
97+ fn read_uqvar (
8198 hubris : & HubrisArchive ,
8299 core : & mut dyn Core ,
83100 name : & str ,
84- ) -> Result < Vec < u8 > > {
85- msg ! ( "reading {name}" ) ;
86- let var = hubris
87- . lookup_variable ( name)
88- . context ( format ! ( "could not find {name}; is this a Gimlet image?" ) ) ?;
101+ ) -> Result < Option < Vec < u8 > > > {
102+ let Some ( var) = hubris. lookup_variable ( name) . ok ( ) else {
103+ return Ok ( None ) ;
104+ } ;
89105
90106 let mut buf: Vec < u8 > = vec ! [ 0u8 ; var. size] ;
91107
92108 core. halt ( ) ?;
93109 core. read_8 ( var. addr , buf. as_mut_slice ( ) ) ?;
94110 core. run ( ) ?;
95111
96- Ok ( buf)
112+ Ok ( Some ( buf) )
97113}
98114
99- fn host_boot_fail ( hubris : & HubrisArchive , core : & mut dyn Core ) -> Result < ( ) > {
100- let d = read_var ( hubris, core, "LAST_HOST_BOOT_FAIL" ) ?;
101- if d. iter ( ) . all ( |& c| c == 0 ) {
102- println ! ( "[0; {}]" , d. len( ) ) ;
115+ fn read_qualified_state_buf (
116+ hubris : & HubrisArchive ,
117+ core : & mut dyn Core ,
118+ name : & str ,
119+ ) -> Result < Option < HostStateBuf > > {
120+ let Some ( var) = hubris. lookup_qualified_variable ( name) . ok ( ) else {
121+ return Ok ( None ) ;
122+ } ;
123+
124+ let var_ty = hubris. lookup_type ( var. goff ) ?;
125+
126+ let mut buf: Vec < u8 > = vec ! [ 0u8 ; var. size] ;
127+
128+ core. halt ( ) ?;
129+ core. read_8 ( var. addr , & mut buf) ?;
130+ core. run ( ) ?;
131+
132+ let v = reflect:: load_value ( hubris, & buf, var_ty, 0 ) ?;
133+ let as_static_cell = doppel:: ClaimOnceCell :: from_value ( & v) ?;
134+ Ok ( Some ( HostStateBuf :: from_value ( & as_static_cell. cell . value ) ?) )
135+ }
136+
137+ fn print_escaped_ascii ( mut bytes : & [ u8 ] ) {
138+ // Drop trailing NULs to avoid escaping them and cluttering up our output.
139+ while let Some ( ( & b'\0' , prefix) ) = bytes. split_last ( ) {
140+ bytes = prefix;
141+ }
142+
143+ if bytes. is_empty ( ) {
144+ println ! ( "Message contains no non-NUL bytes, not printing." ) ;
145+ return ;
103146 } else {
104- match std:: str:: from_utf8 ( & d) {
105- Ok ( s) => println ! ( "{}" , s. trim_matches( '\0' ) ) ,
106- Err ( e) => println ! ( "{:?}\n (could not decode: {e})" , d) ,
147+ println ! ( "Message is {} bytes long:" , bytes. len( ) ) ;
148+ }
149+
150+ let mut buf = String :: new ( ) ;
151+ for & b in bytes {
152+ match b {
153+ b'\\' => {
154+ // Escape any backslashes in the original, so that any escapes
155+ // _we_ emit are unambiguous.
156+ buf. push_str ( "\\ \\ " ) ;
157+ }
158+ b'\n' | b'\r' | b'\t' | 0x20 ..=0x7E => {
159+ // Pass through basic text characters that we expect we might
160+ // see in a message.
161+ buf. push ( b as char ) ;
162+ }
163+ _ => {
164+ // Escape any other non-printable characters.
165+ buf. push_str ( "\\ x" ) ;
166+ buf. push_str ( & format ! ( "{b:02X}" ) ) ;
167+ }
107168 }
108169 }
170+ println ! ( "{buf}" ) ;
171+ }
172+
173+ fn host_boot_fail ( hubris : & HubrisArchive , core : & mut dyn Core ) -> Result < ( ) > {
174+ // Try old name:
175+ let d = read_uqvar ( hubris, core, SEPARATE_HOST_BOOT_FAIL_NAME ) ?;
176+ if let Some ( d) = d {
177+ print_escaped_ascii ( & d) ;
178+ return Ok ( ( ) ) ;
179+ }
180+ // Try new name
181+ let buf = read_qualified_state_buf ( hubris, core, HOST_STATE_BUF_NAME ) ?
182+ . ok_or_else ( || {
183+ anyhow ! (
184+ "Could not find host boot variables under any known name; \
185+ is this a Gimlet image?"
186+ )
187+ } ) ?;
188+
189+ print_escaped_ascii ( & buf. last_boot_fail [ ..] ) ;
109190
110191 Ok ( ( ) )
111192}
@@ -149,8 +230,25 @@ struct IpccPanicStack {
149230}
150231
151232fn host_last_panic ( hubris : & HubrisArchive , core : & mut dyn Core ) -> Result < ( ) > {
152- let d = read_var ( hubris, core, "LAST_HOST_PANIC" ) ?;
233+ // Try original name:
234+ let d = read_uqvar ( hubris, core, SEPARATE_LAST_HOST_PANIC_NAME ) ?;
235+ if let Some ( d) = d {
236+ return print_panic ( d) ;
237+ }
238+
239+ // Try new name:
240+ let buf = read_qualified_state_buf ( hubris, core, HOST_STATE_BUF_NAME ) ?
241+ . ok_or_else ( || {
242+ anyhow ! (
243+ "Could not find host boot variables under any known name; \
244+ is this a Gimlet image?"
245+ )
246+ } ) ?;
247+
248+ print_panic ( buf. last_panic )
249+ }
153250
251+ fn print_panic ( d : Vec < u8 > ) -> Result < ( ) > {
154252 // Fix for https://github.com/oxidecomputer/hubris/issues/1554
155253 //
156254 // In some cases, `ipd_cause` is unambiguous based on the first byte;
0 commit comments