1 use super::address_transform::AddressTransform;
2 use super::{Reader, TransformError};
3 use anyhow::{Error, bail};
4 use gimli::{DebugLineOffset, LineEncoding, Unit, write};
5 use wasmtime_environ::DefinedFuncIndex;
6 
7 #[derive(Debug)]
8 enum SavedLineProgramRow {
9     Normal {
10         address: u64,
11         op_index: u64,
12         file_index: u64,
13         line: u64,
14         column: u64,
15         discriminator: u64,
16         is_stmt: bool,
17         basic_block: bool,
18         prologue_end: bool,
19         epilogue_begin: bool,
20         isa: u64,
21     },
22     EndOfSequence,
23 }
24 
25 #[derive(Debug)]
26 struct FuncRows {
27     index: DefinedFuncIndex,
28     sorted_rows: Vec<(u64, SavedLineProgramRow)>,
29 }
30 
31 #[derive(Debug, Eq, PartialEq)]
32 enum ReadLineProgramState {
33     SequenceEnded,
34     ReadSequence(DefinedFuncIndex),
35     IgnoreSequence,
36 }
37 
38 pub(crate) fn clone_line_program<R>(
39     dwarf: &gimli::Dwarf<R>,
40     unit: &Unit<R, R::Offset>,
41     comp_name: Option<R>,
42     addr_tr: &AddressTransform,
43     out_encoding: gimli::Encoding,
44     out_strings: &mut write::StringTable,
45 ) -> Result<(write::LineProgram, DebugLineOffset, Vec<write::FileId>, u64), Error>
46 where
47     R: Reader,
48 {
49     if let Some(program) = unit.line_program.clone() {
50         let header = program.header();
51         let offset = header.offset();
52         let file_index_base = if header.version() < 5 { 1 } else { 0 };
53         assert!(header.version() <= 5, "not supported 6");
54         let line_encoding = LineEncoding {
55             minimum_instruction_length: header.minimum_instruction_length(),
56             maximum_operations_per_instruction: header.maximum_operations_per_instruction(),
57             default_is_stmt: header.default_is_stmt(),
58             line_base: header.line_base(),
59             line_range: header.line_range(),
60         };
61         let out_comp_dir = match header.directory(0) {
62             Some(comp_dir) => clone_line_string(
63                 dwarf.attr_string(unit, comp_dir)?,
64                 gimli::DW_FORM_string,
65                 out_strings,
66             )?,
67             None => write::LineString::String(Vec::new()),
68         };
69         let out_comp_name = match comp_name {
70             Some(comp_name) => clone_line_string(comp_name, gimli::DW_FORM_strp, out_strings)?,
71             None => write::LineString::String(Vec::new()),
72         };
73 
74         let mut out_program = write::LineProgram::new(
75             out_encoding,
76             line_encoding,
77             out_comp_dir,
78             None,
79             out_comp_name,
80             None,
81         );
82         let mut dirs = Vec::new();
83         dirs.push(out_program.default_directory());
84         for dir_attr in header.include_directories() {
85             let dir_id = out_program.add_directory(clone_line_string(
86                 dwarf.attr_string(unit, dir_attr.clone())?,
87                 gimli::DW_FORM_string,
88                 out_strings,
89             )?);
90             dirs.push(dir_id);
91         }
92         let mut files = Vec::new();
93         // Since we are outputting DWARF-4, perform base change.
94         let directory_index_correction = if header.version() >= 5 { 1 } else { 0 };
95         for file_entry in header.file_names() {
96             let dir_index = file_entry.directory_index() + directory_index_correction;
97             let dir_id = dirs[dir_index as usize];
98             let file_id = out_program.add_file(
99                 clone_line_string(
100                     dwarf.attr_string(unit, file_entry.path_name())?,
101                     gimli::DW_FORM_string,
102                     out_strings,
103                 )?,
104                 dir_id,
105                 None,
106             );
107             files.push(file_id);
108         }
109 
110         let mut rows = program.rows();
111         let mut func_rows = Vec::new();
112         let mut saved_rows: Vec<(u64, SavedLineProgramRow)> = Vec::new();
113         let mut state = ReadLineProgramState::SequenceEnded;
114         while let Some((_header, row)) = rows.next_row()? {
115             if state == ReadLineProgramState::IgnoreSequence {
116                 if row.end_sequence() {
117                     state = ReadLineProgramState::SequenceEnded;
118                 }
119                 continue;
120             }
121             let saved_row = if row.end_sequence() {
122                 let index = match state {
123                     ReadLineProgramState::ReadSequence(index) => index,
124                     _ => panic!(),
125                 };
126                 saved_rows.sort_by_key(|r| r.0);
127                 func_rows.push(FuncRows {
128                     index,
129                     sorted_rows: saved_rows,
130                 });
131 
132                 saved_rows = Vec::new();
133                 state = ReadLineProgramState::SequenceEnded;
134                 SavedLineProgramRow::EndOfSequence
135             } else {
136                 if state == ReadLineProgramState::SequenceEnded {
137                     // Discard sequences for non-existent code.
138                     if row.address() == 0 {
139                         state = ReadLineProgramState::IgnoreSequence;
140                         continue;
141                     }
142                     match addr_tr.find_func_index(row.address()) {
143                         Some(index) => {
144                             state = ReadLineProgramState::ReadSequence(index);
145                         }
146                         None => {
147                             // Some non-existent address found.
148                             state = ReadLineProgramState::IgnoreSequence;
149                             continue;
150                         }
151                     }
152                 }
153                 SavedLineProgramRow::Normal {
154                     address: row.address(),
155                     op_index: row.op_index(),
156                     file_index: row.file_index(),
157                     line: row.line().map(|nonzero| nonzero.get()).unwrap_or(0),
158                     column: match row.column() {
159                         gimli::ColumnType::LeftEdge => 0,
160                         gimli::ColumnType::Column(val) => val.get(),
161                     },
162                     discriminator: row.discriminator(),
163                     is_stmt: row.is_stmt(),
164                     basic_block: row.basic_block(),
165                     prologue_end: row.prologue_end(),
166                     epilogue_begin: row.epilogue_begin(),
167                     isa: row.isa(),
168                 }
169             };
170             saved_rows.push((row.address(), saved_row));
171         }
172 
173         for FuncRows {
174             index,
175             sorted_rows: saved_rows,
176         } in func_rows
177         {
178             let map = match addr_tr.map().get(index) {
179                 Some(map) if map.len > 0 => map,
180                 _ => {
181                     continue; // no code generated
182                 }
183             };
184             let symbol = map.symbol;
185             let base_addr = map.offset;
186             out_program.begin_sequence(Some(write::Address::Symbol { symbol, addend: 0 }));
187             // TODO track and place function declaration line here
188             let mut last_address = None;
189             for addr_map in map.addresses.iter() {
190                 let saved_row = match saved_rows.binary_search_by_key(&addr_map.wasm, |i| i.0) {
191                     Ok(i) => Some(&saved_rows[i].1),
192                     Err(i) => {
193                         if i > 0 {
194                             Some(&saved_rows[i - 1].1)
195                         } else {
196                             None
197                         }
198                     }
199                 };
200                 if let Some(SavedLineProgramRow::Normal {
201                     address,
202                     op_index,
203                     file_index,
204                     line,
205                     column,
206                     discriminator,
207                     is_stmt,
208                     basic_block,
209                     prologue_end,
210                     epilogue_begin,
211                     isa,
212                 }) = saved_row
213                 {
214                     // Ignore duplicates
215                     if Some(*address) != last_address {
216                         let address_offset = if last_address.is_none() {
217                             // Extend first entry to the function declaration
218                             // TODO use the function declaration line instead
219                             0
220                         } else {
221                             (addr_map.generated - base_addr) as u64
222                         };
223                         out_program.row().address_offset = address_offset;
224                         out_program.row().op_index = *op_index;
225                         out_program.row().file = files[(file_index - file_index_base) as usize];
226                         out_program.row().line = *line;
227                         out_program.row().column = *column;
228                         out_program.row().discriminator = *discriminator;
229                         out_program.row().is_statement = *is_stmt;
230                         out_program.row().basic_block = *basic_block;
231                         out_program.row().prologue_end = *prologue_end;
232                         out_program.row().epilogue_begin = *epilogue_begin;
233                         out_program.row().isa = *isa;
234                         out_program.generate_row();
235                         last_address = Some(*address);
236                     }
237                 }
238             }
239             let end_addr = (map.offset + map.len) as u64;
240             out_program.end_sequence(end_addr);
241         }
242         Ok((out_program, offset, files, file_index_base))
243     } else {
244         Err(TransformError("Valid line program not found").into())
245     }
246 }
247 
248 fn clone_line_string<R>(
249     value: R,
250     form: gimli::DwForm,
251     out_strings: &mut write::StringTable,
252 ) -> Result<write::LineString, Error>
253 where
254     R: Reader,
255 {
256     let content = value.to_string_lossy()?.into_owned();
257     Ok(match form {
258         gimli::DW_FORM_strp => {
259             let id = out_strings.add(content);
260             write::LineString::StringRef(id)
261         }
262         gimli::DW_FORM_string => write::LineString::String(content.into()),
263         _ => bail!("DW_FORM_line_strp or other not supported"),
264     })
265 }
266