line.ha (7065B)
1 // SPDX-License-Identifier: MPL-2.0 2 // (c) Hare authors <https://harelang.org> 3 4 use debug::image; 5 use errors; 6 use format::elf; 7 use io; 8 use memio; 9 10 def MIN_LINE_VERSION: u16 = 2; 11 def MAX_LINE_VERSION: u16 = 3; 12 13 // Boolean flags for the line number state machine 14 export type line_flag = enum uint { 15 NONE = 0, 16 IS_STMT = 1 << 0, 17 BASIC_BLOCK = 1 << 1, 18 END_SEQUENCE = 1 << 2, 19 PROLOGUE_END = 1 << 3, 20 EPILOGUE_BEGIN = 1 << 4, 21 }; 22 23 // Line number program state 24 export type line_state = struct { 25 vm_loc: u64, 26 addr: uintptr, 27 op_index: uint, 28 file: uint, 29 line: uint, 30 column: uint, 31 flags: line_flag, 32 isa: uint, 33 discriminator: uint, 34 }; 35 36 // A file with associated line numbers. 37 export type line_file = struct { 38 name: str, 39 dir: u64, 40 mtime: u64, 41 length: u64, 42 }; 43 44 // Header information for a .debug_line program. 45 export type line_header = struct { 46 min_instr_length: u8, 47 max_ops_per_instr: u8, 48 default_isstmt: bool, 49 line_base: i8, 50 line_range: u8, 51 opcode_base: u8, 52 opcode_lengths: []u8, 53 dirs: []str, 54 files: []line_file, 55 }; 56 57 // Line number program 58 export type line_program = struct { 59 mem: *memio::stream, 60 rd: *table_reader, 61 state: line_state, 62 head: line_header, 63 }; 64 65 // Initializes a new line number state machine to run the line number program at 66 // the specified offset in .debug_line. 67 // 68 // Use [[line_step]] to step the state machine, and pass the result to 69 // [[line_program_finish]] to free resources associated with the state machine 70 // when done using it. 71 export fn exec_line_program( 72 image: *image::image, 73 offs: u64, 74 ) (line_program | void | io::error) = { 75 const sec = match (image::section_byname(image, ".debug_line")) { 76 case let sec: *elf::section64 => 77 yield sec; 78 case null => 79 return; 80 }; 81 const memrd = alloc(image::section_reader(image, sec)); 82 io::seek(memrd, offs: io::off, io::whence::SET)?; 83 const rd = alloc(new_table_reader(memrd, true)? as table_reader); 84 85 // Read program header 86 const ver = read_uhalf(rd)!; 87 assert(ver >= MIN_LINE_VERSION && ver <= MAX_LINE_VERSION, 88 "debug::dwarf: unsupported .debug_line version"); 89 90 let head = line_header { ... }; 91 const head_len = read_secword(rd)?; 92 head.min_instr_length = read_ubyte(rd)?; 93 head.max_ops_per_instr = 1; // Non-VLIW architectures only 94 head.default_isstmt = read_ubyte(rd)? != 0; 95 head.line_base = read_sbyte(rd)?; 96 head.line_range = read_ubyte(rd)?; 97 head.opcode_base = read_ubyte(rd)?; 98 99 // Opcode lengths 100 for (let i = 0u8; i < head.opcode_base - 1; i += 1) { 101 const op = read_ubyte(rd)?; 102 append(head.opcode_lengths, op); 103 }; 104 105 // Directories 106 for (true) { 107 const dir = read_string(rd)?; 108 if (len(dir) == 0) { 109 break; 110 }; 111 append(head.dirs, dir); 112 }; 113 114 // Files 115 for (true) { 116 const name = read_string(rd)?; 117 if (len(name) == 0) { 118 break; 119 }; 120 const dir = read_uleb128(rd)?; 121 const mtime = read_uleb128(rd)?; 122 const length = read_uleb128(rd)?; 123 append(head.files, line_file { 124 name = name, 125 dir = dir, 126 mtime = mtime, 127 length = length, 128 }); 129 }; 130 131 let prog = line_program { 132 mem = memrd, 133 rd = rd, 134 state = line_state { ... }, 135 head = head, 136 }; 137 line_prog_reset(&prog); 138 return prog; 139 }; 140 141 fn line_prog_reset(prog: *line_program) void = { 142 const head = &prog.head; 143 prog.state = line_state { 144 vm_loc = 0, 145 addr = 0, 146 op_index = 0, 147 file = 1, 148 line = 1, 149 column = 0, 150 flags = if (head.default_isstmt) line_flag::IS_STMT else 0, 151 isa = 0, 152 discriminator = 0, 153 }; 154 }; 155 156 // Frees resources associated with a [[line_program]]. 157 export fn line_program_finish(prog: *line_program) void = { 158 free(prog.mem); 159 free(prog.rd); 160 free(prog.head.opcode_lengths); 161 free(prog.head.dirs); 162 free(prog.head.files); 163 }; 164 165 // Runs the line number state machine until the next COPY instruction. 166 export fn line_next(prog: *line_program) (line_state | io::EOF | io::error) = { 167 for (true) { 168 match (line_step(prog)?) { 169 case let state: line_state => 170 return state; 171 case io::EOF => 172 return io::EOF; 173 case void => continue; 174 }; 175 }; 176 }; 177 178 // Step the line number state machine. Returns the current line_state on a copy 179 // or end-of-sequence instruction, [[io::EOF]] at the end of the file, or void 180 // otherwise. 181 export fn line_step( 182 prog: *line_program, 183 ) (line_state | void | io::EOF | io::error) = { 184 let state = &prog.state; 185 if (read_iseof(prog.rd)) { 186 return io::EOF; 187 }; 188 state.vm_loc = read_tell(prog.rd); 189 190 const opcode = read_ubyte(prog.rd)?; 191 if (opcode == 0) { 192 // Extended opcode 193 const length = read_uleb128(prog.rd)?; 194 const opcode = read_ubyte(prog.rd)?; 195 switch (opcode) { 196 case DW_LNE_end_sequence => 197 let copy = *state; 198 line_prog_reset(prog); 199 return copy; 200 case DW_LNE_set_address => 201 state.addr = read_ulong(prog.rd)?: uintptr; 202 case DW_LNE_define_file => 203 const name = read_string(prog.rd)?; 204 const dir = read_uleb128(prog.rd)?; 205 const mtime = read_uleb128(prog.rd)?; 206 const length = read_uleb128(prog.rd)?; 207 append(prog.head.files, line_file { 208 name = name, 209 dir = dir, 210 mtime = mtime, 211 length = length, 212 }); 213 state.file = len(prog.head.files): uint; 214 case DW_LNE_set_discriminator => 215 state.discriminator = read_uleb128(prog.rd)?: uint; 216 case => 217 // Unknown opcode, skip 218 read_slice(prog.rd, length - 1)?; 219 }; 220 } else if (opcode < prog.head.opcode_base) { 221 // Special opcode 222 switch (opcode) { 223 case DW_LNS_copy => 224 let copy = *state; 225 state.discriminator = 0; 226 state.flags &= ~( 227 line_flag::BASIC_BLOCK | 228 line_flag::PROLOGUE_END | 229 line_flag::EPILOGUE_BEGIN); 230 return copy; 231 case DW_LNS_advance_pc => 232 const op_adv = read_uleb128(prog.rd)?; 233 state.addr += (prog.head.min_instr_length * op_adv): uintptr; 234 case DW_LNS_advance_line => 235 const line = state.line: i64; 236 const offs = read_sleb128(prog.rd)?; 237 line += offs; 238 state.line = line: uint; 239 case DW_LNS_set_file => 240 state.file = read_uleb128(prog.rd)?: uint; 241 case DW_LNS_set_column => 242 state.column = read_uleb128(prog.rd)?: uint; 243 case DW_LNS_negate_stmt => 244 state.flags ^= line_flag::IS_STMT; 245 case DW_LNS_set_basic_block => 246 state.flags |= line_flag::BASIC_BLOCK; 247 case DW_LNS_const_add_pc => 248 const opcode = 255 - prog.head.opcode_base; 249 const op_adv = opcode / prog.head.line_range; 250 state.addr += (prog.head.min_instr_length * op_adv): uintptr; 251 case DW_LNS_fixed_advance_pc => 252 state.addr += read_uhalf(prog.rd)?: uintptr; 253 state.op_index = 0; 254 case DW_LNS_set_prologue_end => 255 state.flags |= line_flag::PROLOGUE_END; 256 case DW_LNS_set_epilogue_begin => 257 state.flags |= line_flag::EPILOGUE_BEGIN; 258 case DW_LNS_isa => 259 state.isa = read_uleb128(prog.rd)?: uint; 260 case => 261 // Unknown opcode, skip 262 const length = prog.head.opcode_lengths[opcode - 1]; 263 for (length != 0; length -= 1) { 264 read_uleb128(prog.rd)?; 265 }; 266 }; 267 } else { 268 const opcode = opcode - prog.head.opcode_base; 269 const op_adv = opcode / prog.head.line_range; 270 state.addr += (prog.head.min_instr_length * op_adv): uintptr; 271 let line = state.line: int; 272 line += prog.head.line_base: int + 273 opcode: int % prog.head.line_range: int; 274 state.line = line: uint; 275 }; 276 };