hare

[hare] The Hare programming language
git clone https://git.torresjrjr.com/hare.git
Log | Files | Refs | README | LICENSE

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 };