wasmtime_internal_cranelift/debug/transform/
line_program.rs

1use super::address_transform::AddressTransform;
2use super::{Reader, TransformError};
3use anyhow::{Error, bail};
4use gimli::{DebugLineOffset, LineEncoding, Unit, write};
5use wasmtime_environ::DefinedFuncIndex;
6
7#[derive(Debug)]
8enum 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)]
26struct FuncRows {
27    index: DefinedFuncIndex,
28    sorted_rows: Vec<(u64, SavedLineProgramRow)>,
29}
30
31#[derive(Debug, Eq, PartialEq)]
32enum ReadLineProgramState {
33    SequenceEnded,
34    ReadSequence(DefinedFuncIndex),
35    IgnoreSequence,
36}
37
38pub(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>
46where
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
248fn clone_line_string<R>(
249    value: R,
250    form: gimli::DwForm,
251    out_strings: &mut write::StringTable,
252) -> Result<write::LineString, Error>
253where
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}