1+ //! # qemu-run
2+ //!
13//! An alternative to the [`probe-run`](https://github.com/knurling-rs/probe-run) printer,
24//! used by [`defmt`](https://github.com/knurling-rs/defmt).
5+ //!
36//! Parses data sent by QEMU over semihosting (ARM Cortex-M only).
4- //! *Printers* are *host* programs that receive log data, format it and display it.
57
68use std:: {
79 env, fs,
@@ -10,9 +12,35 @@ use std::{
1012} ;
1113
1214use anyhow:: { anyhow, bail} ;
15+ use clap:: Parser ;
1316use defmt_decoder:: { DecodeError , StreamDecoder , Table } ;
1417use process:: Child ;
1518
19+ /// Run qemu-system-arm, takes defmt logs from semihosting output and prints them to stdout
20+ #[ derive( clap:: Parser , Clone ) ]
21+ #[ command( name = "qemu-run" ) ]
22+ struct Opts {
23+ /// The firmware running on the device being logged
24+ #[ arg( required = false , conflicts_with( "version" ) ) ]
25+ elf : Option < std:: path:: PathBuf > ,
26+
27+ /// Specify the QEMU machine type
28+ #[ arg( long, required = false ) ]
29+ machine : Option < String > ,
30+
31+ /// Specify the QEMU CPU type
32+ #[ arg( long, required = false ) ]
33+ cpu : Option < String > ,
34+
35+ /// Print the version number, and quit
36+ #[ arg( short = 'V' , long) ]
37+ version : bool ,
38+
39+ /// Print the version number, and quit
40+ #[ arg( short = 'v' , long) ]
41+ verbose : bool ,
42+ }
43+
1644fn main ( ) -> Result < ( ) , anyhow:: Error > {
1745 notmain ( ) . map ( |opt_code| {
1846 if let Some ( code) = opt_code {
@@ -22,38 +50,69 @@ fn main() -> Result<(), anyhow::Error> {
2250}
2351
2452fn notmain ( ) -> Result < Option < i32 > , anyhow:: Error > {
25- let args = env:: args ( ) . skip ( 1 /* program name */ ) . collect :: < Vec < _ > > ( ) ;
53+ let opts = Opts :: parse ( ) ;
54+
55+ if opts. version {
56+ return print_version ( ) ;
57+ }
58+
59+ let Some ( elf_path) = opts. elf else {
60+ bail ! ( "ELF filename is required. Syntax: `qemu-run -machine <machine> <path-to-elf>`." ) ;
61+ } ;
2662
27- if args. len ( ) != 1 {
28- bail ! ( "expected exactly one argument. Syntax: `qemu-run <path-to-elf>`" ) ;
63+ let Some ( machine) = opts. machine else {
64+ bail ! ( "Machine type is required. Syntax: `qemu-run -machine <machine> <path-to-elf>`." ) ;
65+ } ;
66+
67+ if opts. verbose {
68+ eprintln ! ( "QEMU machine is {:?}" , machine) ;
69+ if let Some ( cpu) = & opts. cpu {
70+ eprintln ! ( "QEMU cpu is {:?}" , cpu) ;
71+ }
2972 }
3073
31- let path = & args[ 0 ] ;
32- let bytes = fs:: read ( path) ?;
74+ let bytes = fs:: read ( & elf_path) ?;
3375
3476 let table = if env:: var_os ( "QEMU_RUN_IGNORE_VERSION" ) . is_some ( ) {
3577 Table :: parse_ignore_version ( & bytes)
3678 } else {
3779 Table :: parse ( & bytes)
3880 } ;
39- let table = table?. ok_or_else ( || anyhow ! ( "`.defmt` section not found" ) ) ?;
81+ let table = match table {
82+ Ok ( Some ( table) ) => table,
83+ Ok ( None ) => {
84+ bail ! ( "Loaded ELF but did not find a .defmt section" ) ;
85+ }
86+ Err ( e) => {
87+ bail ! ( "Failed to load ELF: {:?}" , e) ;
88+ }
89+ } ;
90+
91+ let mut command = Command :: new ( "qemu-system-arm" ) ;
92+ command. args ( [
93+ "-machine" ,
94+ & machine,
95+ "-nographic" ,
96+ "-monitor" ,
97+ "none" ,
98+ "-semihosting-config" ,
99+ "enable=on,target=native" ,
100+ "-kernel" ,
101+ ] ) ;
102+ command. arg ( elf_path) ;
103+ command. stdout ( Stdio :: piped ( ) ) ;
104+
105+ if let Some ( cpu) = & opts. cpu {
106+ command. arg ( "-cpu" ) ;
107+ command. arg ( cpu) ;
108+ }
109+
110+ if opts. verbose {
111+ eprintln ! ( "Running: {:?}" , command) ;
112+ }
40113
41114 let mut child = KillOnDrop (
42- Command :: new ( "qemu-system-arm" )
43- . args ( [
44- "-cpu" ,
45- "cortex-m3" ,
46- "-machine" ,
47- "lm3s6965evb" ,
48- "-nographic" ,
49- "-monitor" ,
50- "none" ,
51- "-semihosting-config" ,
52- "enable=on,target=native" ,
53- "-kernel" ,
54- ] )
55- . arg ( path)
56- . stdout ( Stdio :: piped ( ) )
115+ command
57116 . spawn ( )
58117 . expect ( "Error running qemu-system-arm; perhaps you haven't installed it yet?" ) ,
59118 ) ;
@@ -71,24 +130,25 @@ fn notmain() -> Result<Option<i32>, anyhow::Error> {
71130 loop {
72131 let n = stdout. read ( & mut readbuf) ?;
73132 decoder. received ( & readbuf[ ..n] ) ;
74- decode ( & mut * decoder) ?;
133+ decode_and_print ( decoder. as_mut ( ) ) ?;
75134
76135 if let Some ( status) = child. 0 . try_wait ( ) ? {
136+ // process finished - grab all remaining bytes and quit
77137 exit_code = status. code ( ) ;
78-
79138 let mut data = Vec :: new ( ) ;
80139 stdout. read_to_end ( & mut data) ?;
81140 decoder. received ( & data) ;
82- decode ( & mut * decoder) ?;
83-
141+ decode_and_print ( decoder. as_mut ( ) ) ?;
84142 break ;
85143 }
86144 }
87145
146+ // pass back qemu exit code (if any)
88147 Ok ( exit_code)
89148}
90149
91- fn decode ( decoder : & mut dyn StreamDecoder ) -> Result < ( ) , DecodeError > {
150+ /// Pump the decoder and print any new frames
151+ fn decode_and_print ( decoder : & mut dyn StreamDecoder ) -> Result < ( ) , DecodeError > {
92152 loop {
93153 match decoder. decode ( ) {
94154 Ok ( frame) => {
@@ -110,3 +170,21 @@ impl Drop for KillOnDrop {
110170 self . 0 . kill ( ) . ok ( ) ;
111171 }
112172}
173+
174+ /// Report version from Cargo.toml _(e.g. "0.1.4")_ and supported `defmt`-versions.
175+ ///
176+ /// Used by `--version` flag.
177+ #[ allow( clippy:: unnecessary_wraps) ]
178+ fn print_version ( ) -> anyhow:: Result < Option < i32 > > {
179+ println ! ( "{} {}" , env!( "CARGO_PKG_NAME" ) , env!( "CARGO_PKG_VERSION" ) ) ;
180+ let s = if defmt_decoder:: DEFMT_VERSIONS . len ( ) > 1 {
181+ "s"
182+ } else {
183+ ""
184+ } ;
185+ println ! (
186+ "supported defmt version{s}: {}" ,
187+ defmt_decoder:: DEFMT_VERSIONS . join( ", " )
188+ ) ;
189+ Ok ( Some ( 0 ) )
190+ }
0 commit comments