///|
// DWARF line-table file/location directives (C emit.c emitdbgfile/emitdbgloc).
//
// `.file` and `.loc` are assembler directives: the assembler turns them into a
// DWARF .debug_line section, which is what a debugger uses for source-level
// breakpoints and stepping. The state mirrors emit.c's static nfile/curfile/
// file[] and is reset for every module compile.
///|
let dbg_nfile : Array[Int] = Array::make(1, 0)
///|
let dbg_curfile : Array[Int] = Array::make(1, 0)
///|
let dbg_paths : Array[String] = []
///|
// Whether the DWARF extensions (CFI, .debug_info) are enabled. Off by
// default so plain output stays byte-identical to the reference; the CLI's
// -g/--debug-info sets it.
let dbg_info_on : Array[Bool] = Array::make(1, false)
///|
// Enable or disable the DWARF debug-info extensions.
pub fn set_debug_info(on : Bool) -> Unit {
dbg_info_on[0] = on
}
///|
// True when DWARF debug-info extensions are enabled.
pub fn debug_info() -> Bool {
dbg_info_on[0]
}
///|
// Reset the file table (called once per module compile).
pub fn dbg_reset() -> Unit {
dbg_nfile[0] = 0
dbg_curfile[0] = 0
}
///|
// C emit.c emitdbgfile: for a path not seen before emit `.file \"\"`
// and set the current file number; existing paths only update curfile.
pub fn emit_dbgfile(path : String, sb : StringBuilder) -> Unit {
let n = dbg_nfile[0]
for i in 0.. n {
dbg_paths[n] = path
} else {
dbg_paths.push(path)
}
dbg_nfile[0] = n + 1
dbg_curfile[0] = n + 1
sb.write_string(".file ")
sb.write_string(dbg_curfile[0].to_string())
sb.write_string(" \"")
sb.write_string(path)
sb.write_string("\"\n")
}
///|
// C emit.c emitdbgloc: emit `\t.loc [ ]`.
pub fn emit_dbgloc(line : Int, col : Int, sb : StringBuilder) -> Unit {
sb.write_string("\t.loc ")
sb.write_string(dbg_curfile[0].to_string())
sb.write_char(' ')
sb.write_string(line.to_string())
if col != 0 {
sb.write_char(' ')
sb.write_string(col.to_string())
}
sb.write_char('\n')
}
///|
// Emit a DWARF4 compilation unit that ties the assembler-generated line table
// to the code, so a debugger can turn an address back into a file/line.
//
// DWARF4, not 5: Apple's lldb rejects version 5. The CU's low/high PC are
// plain DW_FORM_addr values in `.debug_info`, which the linker's debug map
// relocates. A single CU covers [first_fn, end-of-text].
pub fn emit_debug_info(
gasloc : String,
gassym : String,
first_fn : String,
cu_name : String,
comp_dir : String,
apple : Bool,
sb : StringBuilder,
) -> Unit {
let producer = "qbe.mbt"
// .debug_info content length, excluding the leading unit_length: header
// (version 2 + abbrev_offset 4 + address_size 1) + abbrev(1) + producer +
// language(2) + name + comp_dir + low_pc(8) + high_pc(8) + stmt_list(4).
let info_len = 7 +
1 +
(producer.length() + 1) +
2 +
(cu_name.length() + 1) +
(comp_dir.length() + 1) +
8 +
8 +
4
let text_label = gasloc + "dbgend"
// The end of the code range: the current end of .text.
sb.write_string(".text\n")
sb.write_string(text_label)
sb.write_string(":\n")
// .debug_abbrev
if apple {
sb.write_string(".section __DWARF,__debug_abbrev,regular,debug\n")
} else {
sb.write_string(".section .debug_abbrev,\"\",@progbits\n")
}
sb.write_string("\t.byte 0x01\n\t.byte 0x11\n\t.byte 0x00\n")
sb.write_string("\t.byte 0x25\n\t.byte 0x08\n") // producer, string
sb.write_string("\t.byte 0x13\n\t.byte 0x05\n") // language, data2
sb.write_string("\t.byte 0x03\n\t.byte 0x08\n") // name, string
sb.write_string("\t.byte 0x1b\n\t.byte 0x08\n") // comp_dir, string
sb.write_string("\t.byte 0x11\n\t.byte 0x01\n") // low_pc, addr
sb.write_string("\t.byte 0x12\n\t.byte 0x01\n") // high_pc, addr
sb.write_string("\t.byte 0x10\n\t.byte 0x17\n") // stmt_list, sec_offset
sb.write_string("\t.byte 0x00\n\t.byte 0x00\n\t.byte 0x00\n")
// .debug_info
if apple {
sb.write_string(".section __DWARF,__debug_info,regular,debug\n")
} else {
sb.write_string(".section .debug_info,\"\",@progbits\n")
}
sb.write_string("\t.long ")
sb.write_string(info_len.to_string())
sb.write_char('\n')
sb.write_string("\t.short 4\n\t.long 0\n\t.byte 8\n")
sb.write_string("\t.byte 0x01\n")
sb.write_string("\t.asciz \"")
sb.write_string(producer)
sb.write_string("\"\n")
sb.write_string("\t.short 12\n")
sb.write_string("\t.asciz \"")
sb.write_string(cu_name)
sb.write_string("\"\n")
sb.write_string("\t.asciz \"")
sb.write_string(comp_dir)
sb.write_string("\"\n")
sb.write_string("\t.quad ")
sb.write_string(gassym)
sb.write_string(first_fn)
sb.write_char('\n')
sb.write_string("\t.quad ")
sb.write_string(text_label)
sb.write_char('\n')
sb.write_string("\t.long 0\n")
}