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