hare

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

backtrace.ha (3913B)


      1 // SPDX-License-Identifier: MPL-2.0
      2 // (c) Hare authors <https://harelang.org>
      3 
      4 use bufio;
      5 use debug::dwarf;
      6 use debug::image;
      7 use fmt;
      8 use format::elf;
      9 use fs;
     10 use io;
     11 use os;
     12 use time;
     13 
     14 def MAX_FRAMES_TOP: size = 16z;
     15 def MAX_FRAMES_BOTTOM: size = 16z;
     16 def MAX_FRAMES: size = MAX_FRAMES_TOP + MAX_FRAMES_BOTTOM;
     17 
     18 // Prints a backtrace to the standard error.
     19 export fn backtrace(self: *image::image, frame: stackframe) void = {
     20 	let orig = frame;
     21 	let nframe = 1z;
     22 	for (true; nframe += 1) {
     23 		match (next(frame)) {
     24 		case let next: stackframe =>
     25 			frame = next;
     26 		case done => break;
     27 		};
     28 	};
     29 	frame = orig;
     30 
     31 	const st = match (os::fstat(self.fd)) {
     32 	case let st: fs::filestat =>
     33 		yield st;
     34 	case fs::error =>
     35 		yield fs::filestat { mask = 0, ... };
     36 	};
     37 
     38 	static let seen: [MAX_FRAMES]uintptr = [0: uintptr...];
     39 	let seen = seen[..0];
     40 
     41 	for (let i = 0z; i < nframe; i += 1) {
     42 		if (i < MAX_FRAMES_TOP || i > nframe - MAX_FRAMES_BOTTOM) {
     43 			printframe(self, &seen, &st, frame);
     44 		};
     45 		if (i == MAX_FRAMES_TOP && nframe > MAX_FRAMES) {
     46 			fmt::errorfln("\t({} additional frames omitted)",
     47 				nframe - MAX_FRAMES): void;
     48 		};
     49 
     50 		match (next(frame)) {
     51 		case let next: stackframe =>
     52 			frame = next;
     53 		case done =>
     54 			break;
     55 		};
     56 	};
     57 };
     58 
     59 fn printframe(
     60 	self: *image::image,
     61 	seen: *[]uintptr,
     62 	imgstat: *fs::filestat,
     63 	frame: stackframe,
     64 ) void = {
     65 	const pc = frame_pc(frame);
     66 
     67 	// Try to translate the address
     68 	match (translate(pc: uintptr)) {
     69 	case let ptr: uintptr =>
     70 		pc = ptr;
     71 	case =>
     72 		void;
     73 	};
     74 
     75 	const sym = match (symbol_byaddr(self, pc)) {
     76 	case let sym: elf::sym64 =>
     77 		yield sym;
     78 	case =>
     79 		fmt::errorfln("(unknown) [0x{:x}]", pc): void;
     80 		return;
     81 	};
     82 	const name = match (symbol_name(self, &sym)) {
     83 	case let name: const str =>
     84 		yield name;
     85 	case =>
     86 		fmt::errorfln("(unknown) [0x{:x}]", pc): void;
     87 		return;
     88 	};
     89 
     90 	// Look for DWARF line numbers, if possible
     91 	const (path, line, col) = match (dwarf::addr_to_line(self, pc)) {
     92 	case (void | io::error) =>
     93 		// No line number available, print what we've got
     94 		fmt::errorfln("{}+0x{:x} [0x{:x}]", symname_to_ident(name),
     95 			pc - sym.st_value: uintptr, pc): void;
     96 		return;
     97 	case let tuple: (const str, uint, uint) =>
     98 		yield tuple;
     99 	};
    100 
    101 	const file = match (os::open(path)) {
    102 	case fs::error =>
    103 		printframe_with_symbol(&sym, name, path, (line, col), pc);
    104 		return;
    105 	case let file: io::file =>
    106 		yield file;
    107 	};
    108 	defer io::close(file): void;
    109 
    110 	static let linebuf: [1024]u8 = [0...];
    111 	const scan = bufio::newscanner_static(file, linebuf);
    112 
    113 	let context = "";
    114 	for (let i = 0u; i < line; i += 1) {
    115 		match (bufio::scan_line(&scan)) {
    116 		case let s: const str =>
    117 			context = s;
    118 		case =>
    119 			printframe_with_symbol(&sym, name, path, (line, col), pc);
    120 			return;
    121 		};
    122 	};
    123 
    124 	fmt::errorf("{}:{}:{} {}+0x{:x} [0x{:x}]",
    125 		path, line, col, symname_to_ident(name),
    126 		pc - sym.st_value: uintptr, pc): void;
    127 
    128 	// Skip context on frames we've already printed
    129 	for (let i = 0z; i < len(seen); i += 1) {
    130 		if (seen[i] == pc) {
    131 			fmt::errorfln(" (already shown)"): void;
    132 			return;
    133 		};
    134 	};
    135 	static append(seen, pc)!;
    136 	fmt::errorln(): void;
    137 
    138 	if (imgstat.mask & fs::stat_mask::MTIME != 0) {
    139 		match (os::fstat(file)) {
    140 		case let st: fs::filestat =>
    141 			if (st.mask & fs::stat_mask::MTIME == 0) yield;
    142 			if (time::compare(st.mtime, imgstat.mtime) == 1) {
    143 				fmt::errorln("* Warning: file was modified after executable was built"): void;
    144 			};
    145 		case => void;
    146 		};
    147 	};
    148 
    149 	fmt::errorfln("| {}", context): void;
    150 
    151 	if (col != 0) {
    152 		fmt::errorf("  "): void;
    153 		for (let i = 1u; i < col - 1; i += 1) {
    154 			fmt::errorf(" "): void;
    155 		};
    156 		fmt::errorf("^"): void;
    157 	};
    158 
    159 	fmt::errorln(): void;
    160 
    161 };
    162 
    163 fn printframe_with_symbol(
    164 	sym: *elf::sym64,
    165 	name: str,
    166 	path: str,
    167 	loc: (uint, uint),
    168 	pc: uintptr,
    169 ) void = {
    170 	fmt::errorfln("{}:{}:{} {}+0x{:x} [0x{:x}]",
    171 		path, loc.0, loc.1, symname_to_ident(name),
    172 		pc - sym.st_value: uintptr, pc): void;
    173 	fmt::errorln(): void;
    174 };