hare

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

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