Index: kernel/generic/include/debug/constants.h
===================================================================
--- kernel/generic/include/debug/constants.h	(revision 2fbb42fc7cb049341ed2bb0ef644df1429f5fa15)
+++ kernel/generic/include/debug/constants.h	(revision 2fbb42fc7cb049341ed2bb0ef644df1429f5fa15)
@@ -0,0 +1,173 @@
+/*
+ * Copyright (c) 2023 Jiří Zárevúcky
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef DWARFS_CONSTANTS_H_
+#define DWARFS_CONSTANTS_H_
+
+enum {
+	DW_SECT_INFO        = 1,  // .debug_info.dwo
+	DW_SECT_ABBREV      = 3,  // .debug_abbrev.dwo
+	DW_SECT_LINE        = 4,  // .debug_line.dwo
+	DW_SECT_LOCLISTS    = 5,  // .debug_loclists.dwo
+	DW_SECT_STR_OFFSETS = 6,  // .debug_str_offsets.dwo
+	DW_SECT_MACRO       = 7,  // .debug_macro.dwo
+	DW_SECT_RNGLISTS    = 8,  // .debug_rnglists.dwo
+};
+
+// ^(DW_[^ ]*) [‡† ]*([^ ]*).*
+
+#define CVAL(name, value) name = value,
+
+enum {
+#include "../debug/constants/dw_ut.h"
+
+	DW_UT_lo_user = 0x80,
+	DW_UT_hi_user = 0xff,
+};
+
+enum {
+#include "../debug/constants/dw_tag.h"
+
+	DW_TAG_lo_user = 0x4080,
+	DW_TAG_hi_user = 0xffff,
+};
+
+enum {
+#include "../debug/constants/dw_at.h"
+
+	DW_AT_lo_user = 0x2000,
+	DW_AT_hi_user = 0x3fff,
+};
+
+enum {
+#include "../debug/constants/dw_form.h"
+};
+
+enum {
+#include "../debug/constants/dw_op.h"
+
+	DW_OP_lo_user = 0xe0,
+	DW_OP_hi_user = 0xff,
+};
+
+enum {
+#include "../debug/constants/dw_lle.h"
+};
+
+enum {
+#include "../debug/constants/dw_ate.h"
+
+	DW_ATE_lo_user = 0x80,
+	DW_ATE_hi_user = 0xff,
+};
+
+enum {
+#include "../debug/constants/dw_ds.h"
+};
+
+enum {
+#include "../debug/constants/dw_end.h"
+
+	DW_END_lo_user = 0x40,
+	DW_END_hi_user = 0xff,
+};
+
+enum {
+#include "../debug/constants/dw_access.h"
+};
+
+enum {
+#include "../debug/constants/dw_vis.h"
+};
+
+enum {
+#include "../debug/constants/dw_virtuality.h"
+};
+
+enum {
+#include "../debug/constants/dw_lang.h"
+
+	DW_LANG_lo_user = 0x8000,
+	DW_LANG_hi_user = 0xffff,
+};
+
+enum {
+	DW_ADDR_none = 0,
+};
+
+enum {
+#include "../debug/constants/dw_id.h"
+};
+
+enum {
+#include "../debug/constants/dw_cc.h"
+
+	DW_CC_lo_user = 0x40,
+	DW_CC_hi_user = 0xff,
+};
+
+enum {
+#include "../debug/constants/dw_lns.h"
+};
+
+enum {
+#include "../debug/constants/dw_lne.h"
+
+	DW_LNE_lo_user = 0x80,
+	DW_LNE_hi_user = 0xff,
+};
+
+enum {
+#include "../debug/constants/dw_lnct.h"
+
+	DW_LNCT_lo_user = 0x2000,
+	DW_LNCT_hi_user = 0x3fff,
+};
+
+#undef CVAL
+
+typedef unsigned dw_ut_t;
+typedef unsigned dw_tag_t;
+typedef unsigned dw_at_t;
+typedef unsigned dw_form_t;
+typedef unsigned dw_op_t;
+typedef unsigned dw_lle_t;
+typedef unsigned dw_ate_t;
+typedef unsigned dw_ds_t;
+typedef unsigned dw_end_t;
+typedef unsigned dw_access_t;
+typedef unsigned dw_vis_t;
+typedef unsigned dw_virtuality_t;
+typedef unsigned dw_lang_t;
+typedef unsigned dw_id_t;
+typedef unsigned dw_cc_t;
+typedef unsigned dw_lns_t;
+typedef unsigned dw_lne_t;
+typedef unsigned dw_lnct_t;
+
+#endif /* DWARFS_CONSTANTS_H_ */
Index: kernel/generic/include/debug/constants/dw_access.h
===================================================================
--- kernel/generic/include/debug/constants/dw_access.h	(revision 2fbb42fc7cb049341ed2bb0ef644df1429f5fa15)
+++ kernel/generic/include/debug/constants/dw_access.h	(revision 2fbb42fc7cb049341ed2bb0ef644df1429f5fa15)
@@ -0,0 +1,3 @@
+CVAL(DW_ACCESS_public, 0x01)
+CVAL(DW_ACCESS_protected, 0x02)
+CVAL(DW_ACCESS_private, 0x03)
Index: kernel/generic/include/debug/constants/dw_at.h
===================================================================
--- kernel/generic/include/debug/constants/dw_at.h	(revision 2fbb42fc7cb049341ed2bb0ef644df1429f5fa15)
+++ kernel/generic/include/debug/constants/dw_at.h	(revision 2fbb42fc7cb049341ed2bb0ef644df1429f5fa15)
@@ -0,0 +1,121 @@
+CVAL(DW_AT_sibling, 0x01)
+CVAL(DW_AT_location, 0x02)
+CVAL(DW_AT_name, 0x03)
+CVAL(DW_AT_ordering, 0x09)
+CVAL(DW_AT_byte_size, 0x0b)
+CVAL(DW_AT_bit_offset, 0x0c)
+CVAL(DW_AT_bit_size, 0x0d)
+CVAL(DW_AT_stmt_list, 0x10)
+CVAL(DW_AT_low_pc, 0x11)
+CVAL(DW_AT_high_pc, 0x12)
+CVAL(DW_AT_language, 0x13)
+CVAL(DW_AT_discr, 0x15)
+CVAL(DW_AT_discr_value, 0x16)
+CVAL(DW_AT_visibility, 0x17)
+CVAL(DW_AT_import, 0x18)
+CVAL(DW_AT_string_length, 0x19)
+CVAL(DW_AT_common_reference, 0x1a)
+CVAL(DW_AT_comp_dir, 0x1b)
+CVAL(DW_AT_const_value, 0x1c)
+CVAL(DW_AT_containing_type, 0x1d)
+CVAL(DW_AT_default_value, 0x1e)
+CVAL(DW_AT_inline, 0x20)
+CVAL(DW_AT_is_optional, 0x21)
+CVAL(DW_AT_lower_bound, 0x22)
+CVAL(DW_AT_producer, 0x25)
+CVAL(DW_AT_prototyped, 0x27)
+CVAL(DW_AT_return_addr, 0x2a)
+CVAL(DW_AT_start_scope, 0x2c)
+CVAL(DW_AT_bit_stride, 0x2e)
+CVAL(DW_AT_upper_bound, 0x2f)
+CVAL(DW_AT_abstract_origin, 0x31)
+CVAL(DW_AT_accessibility, 0x32)
+CVAL(DW_AT_address_class, 0x33)
+CVAL(DW_AT_artificial, 0x34)
+CVAL(DW_AT_base_types, 0x35)
+CVAL(DW_AT_calling_convention, 0x36)
+CVAL(DW_AT_count, 0x37)
+CVAL(DW_AT_data_member_location, 0x38)
+CVAL(DW_AT_decl_column, 0x39)
+CVAL(DW_AT_decl_file, 0x3a)
+CVAL(DW_AT_decl_line, 0x3b)
+CVAL(DW_AT_declaration, 0x3c)
+CVAL(DW_AT_discr_list, 0x3d)
+CVAL(DW_AT_encoding, 0x3e)
+CVAL(DW_AT_external, 0x3f)
+CVAL(DW_AT_frame_base, 0x40)
+CVAL(DW_AT_friend, 0x41)
+CVAL(DW_AT_identifier_case, 0x42)
+CVAL(DW_AT_macro_info, 0x43)
+CVAL(DW_AT_namelist_item, 0x44)
+CVAL(DW_AT_priority, 0x45)
+CVAL(DW_AT_segment, 0x46)
+CVAL(DW_AT_specification, 0x47)
+CVAL(DW_AT_static_link, 0x48)
+CVAL(DW_AT_type, 0x49)
+CVAL(DW_AT_use_location, 0x4a)
+CVAL(DW_AT_variable_parameter, 0x4b)
+CVAL(DW_AT_virtuality, 0x4c)
+CVAL(DW_AT_vtable_elem_location, 0x4d)
+CVAL(DW_AT_allocated, 0x4e)
+CVAL(DW_AT_associated, 0x4f)
+CVAL(DW_AT_data_location, 0x50)
+CVAL(DW_AT_byte_stride, 0x51)
+CVAL(DW_AT_entry_pc, 0x52)
+CVAL(DW_AT_use_UTF8, 0x53)
+CVAL(DW_AT_extension, 0x54)
+CVAL(DW_AT_ranges, 0x55)
+CVAL(DW_AT_trampoline, 0x56)
+CVAL(DW_AT_call_column, 0x57)
+CVAL(DW_AT_call_file, 0x58)
+CVAL(DW_AT_call_line, 0x59)
+CVAL(DW_AT_description, 0x5a)
+CVAL(DW_AT_binary_scale, 0x5b)
+CVAL(DW_AT_decimal_scale, 0x5c)
+CVAL(DW_AT_small, 0x5d)
+CVAL(DW_AT_decimal_sign, 0x5e)
+CVAL(DW_AT_digit_count, 0x5f)
+CVAL(DW_AT_picture_string, 0x60)
+CVAL(DW_AT_mutable, 0x61)
+CVAL(DW_AT_threads_scaled, 0x62)
+CVAL(DW_AT_explicit, 0x63)
+CVAL(DW_AT_object_pointer, 0x64)
+CVAL(DW_AT_endianity, 0x65)
+CVAL(DW_AT_elemental, 0x66)
+CVAL(DW_AT_pure, 0x67)
+CVAL(DW_AT_recursive, 0x68)
+CVAL(DW_AT_signature, 0x69)
+CVAL(DW_AT_main_subprogram, 0x6a)
+CVAL(DW_AT_data_bit_offset, 0x6b)
+CVAL(DW_AT_const_expr, 0x6c)
+CVAL(DW_AT_enum_class, 0x6d)
+CVAL(DW_AT_linkage_name, 0x6e)
+CVAL(DW_AT_string_length_bit_size, 0x6f)
+CVAL(DW_AT_string_length_byte_size, 0x70)
+CVAL(DW_AT_rank, 0x71)
+CVAL(DW_AT_str_offsets_base, 0x72)
+CVAL(DW_AT_addr_base, 0x73)
+CVAL(DW_AT_rnglists_base, 0x74)
+CVAL(DW_AT_dwo_name, 0x76)
+CVAL(DW_AT_reference, 0x77)
+CVAL(DW_AT_rvalue_reference, 0x78)
+CVAL(DW_AT_macros, 0x79)
+CVAL(DW_AT_call_all_calls, 0x7a)
+CVAL(DW_AT_call_all_source_calls, 0x7b)
+CVAL(DW_AT_call_all_tail_calls, 0x7c)
+CVAL(DW_AT_call_return_pc, 0x7d)
+CVAL(DW_AT_call_value, 0x7e)
+CVAL(DW_AT_call_origin, 0x7f)
+CVAL(DW_AT_call_parameter, 0x80)
+CVAL(DW_AT_call_pc, 0x81)
+CVAL(DW_AT_call_tail_call, 0x82)
+CVAL(DW_AT_call_target, 0x83)
+CVAL(DW_AT_call_target_clobbered, 0x84)
+CVAL(DW_AT_call_data_location, 0x85)
+CVAL(DW_AT_call_data_value, 0x86)
+CVAL(DW_AT_noreturn, 0x87)
+CVAL(DW_AT_alignment, 0x88)
+CVAL(DW_AT_export_symbols, 0x89)
+CVAL(DW_AT_deleted, 0x8a)
+CVAL(DW_AT_defaulted, 0x8b)
+CVAL(DW_AT_loclists_base, 0x8c)
Index: kernel/generic/include/debug/constants/dw_ate.h
===================================================================
--- kernel/generic/include/debug/constants/dw_ate.h	(revision 2fbb42fc7cb049341ed2bb0ef644df1429f5fa15)
+++ kernel/generic/include/debug/constants/dw_ate.h	(revision 2fbb42fc7cb049341ed2bb0ef644df1429f5fa15)
@@ -0,0 +1,18 @@
+CVAL(DW_ATE_address, 0x01)
+CVAL(DW_ATE_boolean, 0x02)
+CVAL(DW_ATE_complex_float, 0x03)
+CVAL(DW_ATE_float, 0x04)
+CVAL(DW_ATE_signed, 0x05)
+CVAL(DW_ATE_signed_char, 0x06)
+CVAL(DW_ATE_unsigned, 0x07)
+CVAL(DW_ATE_unsigned_char, 0x08)
+CVAL(DW_ATE_imaginary_float, 0x09)
+CVAL(DW_ATE_packed_decimal, 0x0a)
+CVAL(DW_ATE_numeric_string, 0x0b)
+CVAL(DW_ATE_edited, 0x0c)
+CVAL(DW_ATE_signed_fixed, 0x0d)
+CVAL(DW_ATE_unsigned_fixed, 0x0e)
+CVAL(DW_ATE_decimal_float, 0x0f)
+CVAL(DW_ATE_UTF, 0x10)
+CVAL(DW_ATE_UCS, 0x11)
+CVAL(DW_ATE_ASCII, 0x12)
Index: kernel/generic/include/debug/constants/dw_cc.h
===================================================================
--- kernel/generic/include/debug/constants/dw_cc.h	(revision 2fbb42fc7cb049341ed2bb0ef644df1429f5fa15)
+++ kernel/generic/include/debug/constants/dw_cc.h	(revision 2fbb42fc7cb049341ed2bb0ef644df1429f5fa15)
@@ -0,0 +1,5 @@
+CVAL(DW_CC_normal, 0x01)
+CVAL(DW_CC_program, 0x02)
+CVAL(DW_CC_nocall, 0x03)
+CVAL(DW_CC_pass_by_reference, 0x04)
+CVAL(DW_CC_pass_by_value, 0x05)
Index: kernel/generic/include/debug/constants/dw_ds.h
===================================================================
--- kernel/generic/include/debug/constants/dw_ds.h	(revision 2fbb42fc7cb049341ed2bb0ef644df1429f5fa15)
+++ kernel/generic/include/debug/constants/dw_ds.h	(revision 2fbb42fc7cb049341ed2bb0ef644df1429f5fa15)
@@ -0,0 +1,5 @@
+CVAL(DW_DS_unsigned, 0x01)
+CVAL(DW_DS_leading_overpunch, 0x02)
+CVAL(DW_DS_trailing_overpunch, 0x03)
+CVAL(DW_DS_leading_separate, 0x04)
+CVAL(DW_DS_trailing_separate, 0x05)
Index: kernel/generic/include/debug/constants/dw_end.h
===================================================================
--- kernel/generic/include/debug/constants/dw_end.h	(revision 2fbb42fc7cb049341ed2bb0ef644df1429f5fa15)
+++ kernel/generic/include/debug/constants/dw_end.h	(revision 2fbb42fc7cb049341ed2bb0ef644df1429f5fa15)
@@ -0,0 +1,3 @@
+CVAL(DW_END_default, 0x00)
+CVAL(DW_END_big, 0x01)
+CVAL(DW_END_little, 0x02)
Index: kernel/generic/include/debug/constants/dw_form.h
===================================================================
--- kernel/generic/include/debug/constants/dw_form.h	(revision 2fbb42fc7cb049341ed2bb0ef644df1429f5fa15)
+++ kernel/generic/include/debug/constants/dw_form.h	(revision 2fbb42fc7cb049341ed2bb0ef644df1429f5fa15)
@@ -0,0 +1,43 @@
+CVAL(DW_FORM_addr, 0x01)
+CVAL(DW_FORM_block2, 0x03)
+CVAL(DW_FORM_block4, 0x04)
+CVAL(DW_FORM_data2, 0x05)
+CVAL(DW_FORM_data4, 0x06)
+CVAL(DW_FORM_data8, 0x07)
+CVAL(DW_FORM_string, 0x08)
+CVAL(DW_FORM_block, 0x09)
+CVAL(DW_FORM_block1, 0x0a)
+CVAL(DW_FORM_data1, 0x0b)
+CVAL(DW_FORM_flag, 0x0c)
+CVAL(DW_FORM_sdata, 0x0d)
+CVAL(DW_FORM_strp, 0x0e)
+CVAL(DW_FORM_udata, 0x0f)
+CVAL(DW_FORM_ref_addr, 0x10)
+CVAL(DW_FORM_ref1, 0x11)
+CVAL(DW_FORM_ref2, 0x12)
+CVAL(DW_FORM_ref4, 0x13)
+CVAL(DW_FORM_ref8, 0x14)
+CVAL(DW_FORM_ref_udata, 0x15)
+CVAL(DW_FORM_indirect, 0x16)
+CVAL(DW_FORM_sec_offset, 0x17)
+CVAL(DW_FORM_exprloc, 0x18)
+CVAL(DW_FORM_flag_present, 0x19)
+CVAL(DW_FORM_strx, 0x1a)
+CVAL(DW_FORM_addrx, 0x1b)
+CVAL(DW_FORM_ref_sup4, 0x1c)
+CVAL(DW_FORM_strp_sup, 0x1d)
+CVAL(DW_FORM_data16, 0x1e)
+CVAL(DW_FORM_line_strp, 0x1f)
+CVAL(DW_FORM_ref_sig8, 0x20)
+CVAL(DW_FORM_implicit_const, 0x21)
+CVAL(DW_FORM_loclistx, 0x22)
+CVAL(DW_FORM_rnglistx, 0x23)
+CVAL(DW_FORM_ref_sup8, 0x24)
+CVAL(DW_FORM_strx1, 0x25)
+CVAL(DW_FORM_strx2, 0x26)
+CVAL(DW_FORM_strx3, 0x27)
+CVAL(DW_FORM_strx4, 0x28)
+CVAL(DW_FORM_addrx1, 0x29)
+CVAL(DW_FORM_addrx2, 0x2a)
+CVAL(DW_FORM_addrx3, 0x2b)
+CVAL(DW_FORM_addrx4, 0x2c)
Index: kernel/generic/include/debug/constants/dw_id.h
===================================================================
--- kernel/generic/include/debug/constants/dw_id.h	(revision 2fbb42fc7cb049341ed2bb0ef644df1429f5fa15)
+++ kernel/generic/include/debug/constants/dw_id.h	(revision 2fbb42fc7cb049341ed2bb0ef644df1429f5fa15)
@@ -0,0 +1,4 @@
+CVAL(DW_ID_case_sensitive, 0x00)
+CVAL(DW_ID_up_case, 0x01)
+CVAL(DW_ID_down_case, 0x02)
+CVAL(DW_ID_case_insensitive, 0x03)
Index: kernel/generic/include/debug/constants/dw_lang.h
===================================================================
--- kernel/generic/include/debug/constants/dw_lang.h	(revision 2fbb42fc7cb049341ed2bb0ef644df1429f5fa15)
+++ kernel/generic/include/debug/constants/dw_lang.h	(revision 2fbb42fc7cb049341ed2bb0ef644df1429f5fa15)
@@ -0,0 +1,37 @@
+CVAL(DW_LANG_C89, 0x0001)
+CVAL(DW_LANG_C, 0x0002)
+CVAL(DW_LANG_Ada83, 0x0003)
+CVAL(DW_LANG_C_plus_plus, 0x0004)
+CVAL(DW_LANG_Cobol74, 0x0005)
+CVAL(DW_LANG_Cobol85, 0x0006)
+CVAL(DW_LANG_Fortran77, 0x0007)
+CVAL(DW_LANG_Fortran90, 0x0008)
+CVAL(DW_LANG_Pascal83, 0x0009)
+CVAL(DW_LANG_Modula2, 0x000a)
+CVAL(DW_LANG_Java, 0x000b)
+CVAL(DW_LANG_C99, 0x000c)
+CVAL(DW_LANG_Ada95, 0x000d)
+CVAL(DW_LANG_Fortran95, 0x000e)
+CVAL(DW_LANG_PLI, 0x000f)
+CVAL(DW_LANG_ObjC, 0x0010)
+CVAL(DW_LANG_ObjC_plus_plus, 0x0011)
+CVAL(DW_LANG_UPC, 0x0012)
+CVAL(DW_LANG_D, 0x0013)
+CVAL(DW_LANG_Python, 0x0014)
+CVAL(DW_LANG_OpenCL, 0x0015)
+CVAL(DW_LANG_Go, 0x0016)
+CVAL(DW_LANG_Modula3, 0x0017)
+CVAL(DW_LANG_Haskell, 0x0018)
+CVAL(DW_LANG_C_plus_plus_03, 0x0019)
+CVAL(DW_LANG_C_plus_plus_11, 0x001a)
+CVAL(DW_LANG_OCaml, 0x001b)
+CVAL(DW_LANG_Rust, 0x001c)
+CVAL(DW_LANG_C11, 0x001d)
+CVAL(DW_LANG_Swift, 0x001e)
+CVAL(DW_LANG_Julia, 0x001f)
+CVAL(DW_LANG_Dylan, 0x0020)
+CVAL(DW_LANG_C_plus_plus_14, 0x0021)
+CVAL(DW_LANG_Fortran03, 0x0022)
+CVAL(DW_LANG_Fortran08, 0x0023)
+CVAL(DW_LANG_RenderScript, 0x0024)
+CVAL(DW_LANG_BLISS, 0x0025)
Index: kernel/generic/include/debug/constants/dw_lle.h
===================================================================
--- kernel/generic/include/debug/constants/dw_lle.h	(revision 2fbb42fc7cb049341ed2bb0ef644df1429f5fa15)
+++ kernel/generic/include/debug/constants/dw_lle.h	(revision 2fbb42fc7cb049341ed2bb0ef644df1429f5fa15)
@@ -0,0 +1,9 @@
+CVAL(DW_LLE_end_of_list, 0x00)
+CVAL(DW_LLE_base_addressx, 0x01)
+CVAL(DW_LLE_startx_endx, 0x02)
+CVAL(DW_LLE_startx_length, 0x03)
+CVAL(DW_LLE_offset_pair, 0x04)
+CVAL(DW_LLE_default_location, 0x05)
+CVAL(DW_LLE_base_address, 0x06)
+CVAL(DW_LLE_start_end, 0x07)
+CVAL(DW_LLE_start_length, 0x08)
Index: kernel/generic/include/debug/constants/dw_lnct.h
===================================================================
--- kernel/generic/include/debug/constants/dw_lnct.h	(revision 2fbb42fc7cb049341ed2bb0ef644df1429f5fa15)
+++ kernel/generic/include/debug/constants/dw_lnct.h	(revision 2fbb42fc7cb049341ed2bb0ef644df1429f5fa15)
@@ -0,0 +1,5 @@
+CVAL(DW_LNCT_path, 0x1)
+CVAL(DW_LNCT_directory_index, 0x2)
+CVAL(DW_LNCT_timestamp, 0x3)
+CVAL(DW_LNCT_size, 0x4)
+CVAL(DW_LNCT_MD5, 0x5)
Index: kernel/generic/include/debug/constants/dw_lne.h
===================================================================
--- kernel/generic/include/debug/constants/dw_lne.h	(revision 2fbb42fc7cb049341ed2bb0ef644df1429f5fa15)
+++ kernel/generic/include/debug/constants/dw_lne.h	(revision 2fbb42fc7cb049341ed2bb0ef644df1429f5fa15)
@@ -0,0 +1,3 @@
+CVAL(DW_LNE_end_sequence, 0x01)
+CVAL(DW_LNE_set_address, 0x02)
+CVAL(DW_LNE_set_discriminator, 0x04)
Index: kernel/generic/include/debug/constants/dw_lns.h
===================================================================
--- kernel/generic/include/debug/constants/dw_lns.h	(revision 2fbb42fc7cb049341ed2bb0ef644df1429f5fa15)
+++ kernel/generic/include/debug/constants/dw_lns.h	(revision 2fbb42fc7cb049341ed2bb0ef644df1429f5fa15)
@@ -0,0 +1,12 @@
+CVAL(DW_LNS_copy, 0x01)
+CVAL(DW_LNS_advance_pc, 0x02)
+CVAL(DW_LNS_advance_line, 0x03)
+CVAL(DW_LNS_set_file, 0x04)
+CVAL(DW_LNS_set_column, 0x05)
+CVAL(DW_LNS_negate_stmt, 0x06)
+CVAL(DW_LNS_set_basic_block, 0x07)
+CVAL(DW_LNS_const_add_pc, 0x08)
+CVAL(DW_LNS_fixed_advance_pc, 0x09)
+CVAL(DW_LNS_set_prologue_end, 0x0a)
+CVAL(DW_LNS_set_epilogue_begin, 0x0b)
+CVAL(DW_LNS_set_isa, 0x0c)
Index: kernel/generic/include/debug/constants/dw_op.h
===================================================================
--- kernel/generic/include/debug/constants/dw_op.h	(revision 2fbb42fc7cb049341ed2bb0ef644df1429f5fa15)
+++ kernel/generic/include/debug/constants/dw_op.h	(revision 2fbb42fc7cb049341ed2bb0ef644df1429f5fa15)
@@ -0,0 +1,164 @@
+CVAL(DW_OP_addr, 0x03)
+CVAL(DW_OP_deref, 0x06)
+CVAL(DW_OP_const1u, 0x08)
+CVAL(DW_OP_const1s, 0x09)
+CVAL(DW_OP_const2u, 0x0a)
+CVAL(DW_OP_const2s, 0x0b)
+CVAL(DW_OP_const4u, 0x0c)
+CVAL(DW_OP_const4s, 0x0d)
+CVAL(DW_OP_const8u, 0x0e)
+CVAL(DW_OP_const8s, 0x0f)
+CVAL(DW_OP_constu, 0x10)
+CVAL(DW_OP_consts, 0x11)
+CVAL(DW_OP_dup, 0x12)
+CVAL(DW_OP_drop, 0x13)
+CVAL(DW_OP_over, 0x14)
+CVAL(DW_OP_pick, 0x15)
+CVAL(DW_OP_swap, 0x16)
+CVAL(DW_OP_rot, 0x17)
+CVAL(DW_OP_xderef, 0x18)
+CVAL(DW_OP_abs, 0x19)
+CVAL(DW_OP_and, 0x1a)
+CVAL(DW_OP_div, 0x1b)
+CVAL(DW_OP_minus, 0x1c)
+CVAL(DW_OP_mod, 0x1d)
+CVAL(DW_OP_mul, 0x1e)
+CVAL(DW_OP_neg, 0x1f)
+CVAL(DW_OP_not, 0x20)
+CVAL(DW_OP_or, 0x21)
+CVAL(DW_OP_plus, 0x22)
+CVAL(DW_OP_plus_uconst, 0x23)
+CVAL(DW_OP_shl, 0x24)
+CVAL(DW_OP_shr, 0x25)
+CVAL(DW_OP_shra, 0x26)
+CVAL(DW_OP_xor, 0x27)
+CVAL(DW_OP_bra, 0x28)
+CVAL(DW_OP_eq, 0x29)
+CVAL(DW_OP_ge, 0x2a)
+CVAL(DW_OP_gt, 0x2b)
+CVAL(DW_OP_le, 0x2c)
+CVAL(DW_OP_lt, 0x2d)
+CVAL(DW_OP_ne, 0x2e)
+CVAL(DW_OP_skip, 0x2f)
+CVAL(DW_OP_lit0, 0x30)
+CVAL(DW_OP_lit1, 0x31)
+CVAL(DW_OP_lit2, 0x32)
+CVAL(DW_OP_lit3, 0x33)
+CVAL(DW_OP_lit4, 0x34)
+CVAL(DW_OP_lit5, 0x35)
+CVAL(DW_OP_lit6, 0x36)
+CVAL(DW_OP_lit7, 0x37)
+CVAL(DW_OP_lit8, 0x38)
+CVAL(DW_OP_lit9, 0x39)
+CVAL(DW_OP_lit10, 0x3a)
+CVAL(DW_OP_lit11, 0x3b)
+CVAL(DW_OP_lit12, 0x3c)
+CVAL(DW_OP_lit13, 0x3d)
+CVAL(DW_OP_lit14, 0x3e)
+CVAL(DW_OP_lit15, 0x3f)
+CVAL(DW_OP_lit16, 0x40)
+CVAL(DW_OP_lit17, 0x41)
+CVAL(DW_OP_lit18, 0x42)
+CVAL(DW_OP_lit19, 0x43)
+CVAL(DW_OP_lit20, 0x44)
+CVAL(DW_OP_lit21, 0x45)
+CVAL(DW_OP_lit22, 0x46)
+CVAL(DW_OP_lit23, 0x47)
+CVAL(DW_OP_lit24, 0x48)
+CVAL(DW_OP_lit25, 0x49)
+CVAL(DW_OP_lit26, 0x4a)
+CVAL(DW_OP_lit27, 0x4b)
+CVAL(DW_OP_lit28, 0x4c)
+CVAL(DW_OP_lit29, 0x4d)
+CVAL(DW_OP_lit30, 0x4e)
+CVAL(DW_OP_lit31, 0x4f)
+CVAL(DW_OP_reg0, 0x50)
+CVAL(DW_OP_reg1, 0x51)
+CVAL(DW_OP_reg2, 0x52)
+CVAL(DW_OP_reg3, 0x53)
+CVAL(DW_OP_reg4, 0x54)
+CVAL(DW_OP_reg5, 0x55)
+CVAL(DW_OP_reg6, 0x56)
+CVAL(DW_OP_reg7, 0x57)
+CVAL(DW_OP_reg8, 0x58)
+CVAL(DW_OP_reg9, 0x59)
+CVAL(DW_OP_reg10, 0x5a)
+CVAL(DW_OP_reg11, 0x5b)
+CVAL(DW_OP_reg12, 0x5c)
+CVAL(DW_OP_reg13, 0x5d)
+CVAL(DW_OP_reg14, 0x5e)
+CVAL(DW_OP_reg15, 0x5f)
+CVAL(DW_OP_reg16, 0x60)
+CVAL(DW_OP_reg17, 0x61)
+CVAL(DW_OP_reg18, 0x62)
+CVAL(DW_OP_reg19, 0x63)
+CVAL(DW_OP_reg20, 0x64)
+CVAL(DW_OP_reg21, 0x65)
+CVAL(DW_OP_reg22, 0x66)
+CVAL(DW_OP_reg23, 0x67)
+CVAL(DW_OP_reg24, 0x68)
+CVAL(DW_OP_reg25, 0x69)
+CVAL(DW_OP_reg26, 0x6a)
+CVAL(DW_OP_reg27, 0x6b)
+CVAL(DW_OP_reg28, 0x6c)
+CVAL(DW_OP_reg29, 0x6d)
+CVAL(DW_OP_reg30, 0x6e)
+CVAL(DW_OP_reg31, 0x6f)
+CVAL(DW_OP_breg0, 0x70)
+CVAL(DW_OP_breg1, 0x71)
+CVAL(DW_OP_breg2, 0x72)
+CVAL(DW_OP_breg3, 0x73)
+CVAL(DW_OP_breg4, 0x74)
+CVAL(DW_OP_breg5, 0x75)
+CVAL(DW_OP_breg6, 0x76)
+CVAL(DW_OP_breg7, 0x77)
+CVAL(DW_OP_breg8, 0x78)
+CVAL(DW_OP_breg9, 0x79)
+CVAL(DW_OP_breg10, 0x7a)
+CVAL(DW_OP_breg11, 0x7b)
+CVAL(DW_OP_breg12, 0x7c)
+CVAL(DW_OP_breg13, 0x7d)
+CVAL(DW_OP_breg14, 0x7e)
+CVAL(DW_OP_breg15, 0x7f)
+CVAL(DW_OP_breg16, 0x80)
+CVAL(DW_OP_breg17, 0x81)
+CVAL(DW_OP_breg18, 0x82)
+CVAL(DW_OP_breg19, 0x83)
+CVAL(DW_OP_breg20, 0x84)
+CVAL(DW_OP_breg21, 0x85)
+CVAL(DW_OP_breg22, 0x86)
+CVAL(DW_OP_breg23, 0x87)
+CVAL(DW_OP_breg24, 0x88)
+CVAL(DW_OP_breg25, 0x89)
+CVAL(DW_OP_breg26, 0x8a)
+CVAL(DW_OP_breg27, 0x8b)
+CVAL(DW_OP_breg28, 0x8c)
+CVAL(DW_OP_breg29, 0x8d)
+CVAL(DW_OP_breg30, 0x8e)
+CVAL(DW_OP_breg31, 0x8f)
+CVAL(DW_OP_regx, 0x90)
+CVAL(DW_OP_fbreg, 0x91)
+CVAL(DW_OP_bregx, 0x92)
+CVAL(DW_OP_piece, 0x93)
+CVAL(DW_OP_deref_size, 0x94)
+CVAL(DW_OP_xderef_size, 0x95)
+CVAL(DW_OP_nop, 0x96)
+CVAL(DW_OP_push_object_address, 0x97)
+CVAL(DW_OP_call2, 0x98)
+CVAL(DW_OP_call4, 0x99)
+CVAL(DW_OP_call_ref, 0x9a)
+CVAL(DW_OP_form_tls_address, 0x9b)
+CVAL(DW_OP_call_frame_cfa, 0x9c)
+CVAL(DW_OP_bit_piece, 0x9d)
+CVAL(DW_OP_implicit_value, 0x9e)
+CVAL(DW_OP_stack_value, 0x9f)
+CVAL(DW_OP_implicit_pointer, 0xa0)
+CVAL(DW_OP_addrx, 0xa1)
+CVAL(DW_OP_constx, 0xa2)
+CVAL(DW_OP_entry_value, 0xa3)
+CVAL(DW_OP_const_type, 0xa4)
+CVAL(DW_OP_regval_type, 0xa5)
+CVAL(DW_OP_deref_type, 0xa6)
+CVAL(DW_OP_xderef_type, 0xa7)
+CVAL(DW_OP_convert, 0xa8)
+CVAL(DW_OP_reinterpret, 0xa9)
Index: kernel/generic/include/debug/constants/dw_tag.h
===================================================================
--- kernel/generic/include/debug/constants/dw_tag.h	(revision 2fbb42fc7cb049341ed2bb0ef644df1429f5fa15)
+++ kernel/generic/include/debug/constants/dw_tag.h	(revision 2fbb42fc7cb049341ed2bb0ef644df1429f5fa15)
@@ -0,0 +1,75 @@
+CVAL(DW_TAG_array_type, 0x01)
+CVAL(DW_TAG_class_type, 0x02)
+CVAL(DW_TAG_entry_point, 0x03)
+CVAL(DW_TAG_enumeration_type, 0x04)
+CVAL(DW_TAG_formal_parameter, 0x05)
+CVAL(DW_TAG_global_subroutine, 0x06)
+CVAL(DW_TAG_global_variable, 0x07)
+CVAL(DW_TAG_imported_declaration, 0x08)
+// Reserved 0x09
+CVAL(DW_TAG_label, 0x0a)
+CVAL(DW_TAG_lexical_block, 0x0b)
+CVAL(DW_TAG_local_variable, 0x0c)
+CVAL(DW_TAG_member, 0x0d)
+// Reserved 0x0e
+CVAL(DW_TAG_pointer_type, 0x0f)
+CVAL(DW_TAG_reference_type, 0x10)
+CVAL(DW_TAG_compile_unit, 0x11)
+CVAL(DW_TAG_string_type, 0x12)
+CVAL(DW_TAG_structure_type, 0x13)
+CVAL(DW_TAG_subroutine, 0x14)
+CVAL(DW_TAG_subroutine_type, 0x15)
+CVAL(DW_TAG_typedef, 0x16)
+CVAL(DW_TAG_union_type, 0x17)
+CVAL(DW_TAG_unspecified_parameters, 0x18)
+CVAL(DW_TAG_variant, 0x19)
+CVAL(DW_TAG_common_block, 0x1a)
+CVAL(DW_TAG_common_inclusion, 0x1b)
+CVAL(DW_TAG_inheritance, 0x1c)
+CVAL(DW_TAG_inlined_subroutine, 0x1d)
+CVAL(DW_TAG_module, 0x1e)
+CVAL(DW_TAG_ptr_to_member_type, 0x1f)
+CVAL(DW_TAG_set_type, 0x20)
+CVAL(DW_TAG_subrange_type, 0x21)
+CVAL(DW_TAG_with_stmt, 0x22)
+CVAL(DW_TAG_access_declaration, 0x23)
+CVAL(DW_TAG_base_type, 0x24)
+CVAL(DW_TAG_catch_block, 0x25)
+CVAL(DW_TAG_const_type, 0x26)
+CVAL(DW_TAG_constant, 0x27)
+CVAL(DW_TAG_enumerator, 0x28)
+CVAL(DW_TAG_file_type, 0x29)
+CVAL(DW_TAG_friend, 0x2a)
+CVAL(DW_TAG_namelist, 0x2b)
+CVAL(DW_TAG_namelist_item, 0x2c)
+CVAL(DW_TAG_packed_type, 0x2d)
+CVAL(DW_TAG_subprogram, 0x2e)
+CVAL(DW_TAG_template_type_parameter, 0x2f)
+CVAL(DW_TAG_template_value_parameter, 0x30)
+CVAL(DW_TAG_thrown_type, 0x31)
+CVAL(DW_TAG_try_block, 0x32)
+CVAL(DW_TAG_variant_part, 0x33)
+CVAL(DW_TAG_variable, 0x34)
+CVAL(DW_TAG_volatile_type, 0x35)
+CVAL(DW_TAG_dwarf_procedure, 0x36)
+CVAL(DW_TAG_restrict_type, 0x37)
+CVAL(DW_TAG_interface_type, 0x38)
+CVAL(DW_TAG_namespace, 0x39)
+CVAL(DW_TAG_imported_module, 0x3a)
+CVAL(DW_TAG_unspecified_type, 0x3b)
+CVAL(DW_TAG_partial_unit, 0x3c)
+CVAL(DW_TAG_imported_unit, 0x3d)
+CVAL(DW_TAG_mutable_type, 0x3e)
+CVAL(DW_TAG_condition, 0x3f)
+CVAL(DW_TAG_shared_type, 0x40)
+CVAL(DW_TAG_type_unit, 0x41)
+CVAL(DW_TAG_rvalue_reference_type, 0x42)
+CVAL(DW_TAG_template_alias, 0x43)
+CVAL(DW_TAG_coarray_type, 0x44)
+CVAL(DW_TAG_generic_subrange, 0x45)
+CVAL(DW_TAG_dynamic_type, 0x46)
+CVAL(DW_TAG_atomic_type, 0x47)
+CVAL(DW_TAG_call_site, 0x48)
+CVAL(DW_TAG_call_site_parameter, 0x49)
+CVAL(DW_TAG_skeleton_unit, 0x4a)
+CVAL(DW_TAG_immutable_type, 0x4b)
Index: kernel/generic/include/debug/constants/dw_ut.h
===================================================================
--- kernel/generic/include/debug/constants/dw_ut.h	(revision 2fbb42fc7cb049341ed2bb0ef644df1429f5fa15)
+++ kernel/generic/include/debug/constants/dw_ut.h	(revision 2fbb42fc7cb049341ed2bb0ef644df1429f5fa15)
@@ -0,0 +1,6 @@
+CVAL(DW_UT_compile, 1)
+CVAL(DW_UT_type, 2)
+CVAL(DW_UT_partial, 3)
+CVAL(DW_UT_skeleton, 4)
+CVAL(DW_UT_split_compile, 5)
+CVAL(DW_UT_split_type, 6)
Index: kernel/generic/include/debug/constants/dw_virtuality.h
===================================================================
--- kernel/generic/include/debug/constants/dw_virtuality.h	(revision 2fbb42fc7cb049341ed2bb0ef644df1429f5fa15)
+++ kernel/generic/include/debug/constants/dw_virtuality.h	(revision 2fbb42fc7cb049341ed2bb0ef644df1429f5fa15)
@@ -0,0 +1,3 @@
+CVAL(DW_VIRTUALITY_none, 0x00)
+CVAL(DW_VIRTUALITY_virtual, 0x01)
+CVAL(DW_VIRTUALITY_pure_virtual, 0x02)
Index: kernel/generic/include/debug/constants/dw_vis.h
===================================================================
--- kernel/generic/include/debug/constants/dw_vis.h	(revision 2fbb42fc7cb049341ed2bb0ef644df1429f5fa15)
+++ kernel/generic/include/debug/constants/dw_vis.h	(revision 2fbb42fc7cb049341ed2bb0ef644df1429f5fa15)
@@ -0,0 +1,3 @@
+CVAL(DW_VIS_local, 0x01)
+CVAL(DW_VIS_exported, 0x02)
+CVAL(DW_VIS_qualified, 0x03)
Index: kernel/generic/include/debug/line.h
===================================================================
--- kernel/generic/include/debug/line.h	(revision 2fbb42fc7cb049341ed2bb0ef644df1429f5fa15)
+++ kernel/generic/include/debug/line.h	(revision 2fbb42fc7cb049341ed2bb0ef644df1429f5fa15)
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2023 Jiří Zárevúcky
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef DWARFS_LINE_H_
+#define DWARFS_LINE_H_
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+struct debug_line_program_header {
+	uint64_t unit_length;
+	uint64_t header_length;
+	const uint8_t *unit_end;
+	const uint8_t *header_end;
+	const uint8_t *standard_opcode_lengths;
+	size_t standard_opcode_lengths_size;
+	unsigned width;
+	uint16_t version;
+	uint8_t minimum_instruction_length;
+	bool default_is_stmt;
+	int8_t line_base;
+	uint8_t line_range;
+	uint8_t opcode_base;
+
+	union {
+		struct {
+			const uint8_t *include_directories;
+			const uint8_t *include_directories_end;
+			const uint8_t *file_names;
+		} v3;
+		struct {
+			uint64_t directories_count;
+			uint64_t file_names_count;
+			const uint8_t *directory_entry_format;
+			const uint8_t *directory_entry_format_end;
+			const uint8_t *directories;
+			const uint8_t *directories_end;
+			const uint8_t *file_name_entry_format;
+			const uint8_t *file_name_entry_format_end;
+			const uint8_t *file_names;
+			const uint8_t *file_names_end;
+			uint8_t address_size;
+			uint8_t segment_selector_size;
+			uint8_t directory_entry_format_count;
+			uint8_t file_name_entry_format_count;
+			uint8_t maximum_operations_per_instruction;
+		} v5;
+	};
+};
+
+struct debug_line_program {
+	const struct debug_line_program_header *hdr;
+	const uint8_t *program;
+	const uint8_t *program_end;
+
+	uintptr_t address;
+	int op_advance;
+	int file;
+	int line;
+	int column;
+
+	bool end_sequence;
+	bool truncated;
+};
+
+static inline struct debug_line_program debug_line_program_create(const uint8_t *program,
+    const uint8_t *const program_end,
+	const struct debug_line_program_header *hdr)
+{
+	return (struct debug_line_program) {
+		.hdr = hdr,
+		.program = program,
+		.program_end = program_end,
+		.end_sequence = true,
+		.truncated = false,
+	};
+}
+
+extern bool debug_line_get_address_info(uintptr_t addr, int op_index, const char **file, const char **dir, int *line, int *col);
+
+#endif /* DWARFS_LINE_H_ */
Index: kernel/generic/include/debug/names.h
===================================================================
--- kernel/generic/include/debug/names.h	(revision 2fbb42fc7cb049341ed2bb0ef644df1429f5fa15)
+++ kernel/generic/include/debug/names.h	(revision 2fbb42fc7cb049341ed2bb0ef644df1429f5fa15)
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2023 Jiří Zárevúcky
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef DWARFS_NAMES_H_
+#define DWARFS_NAMES_H_
+
+#define D_(infix) \
+	typedef unsigned dw_##infix##_t; \
+	extern const char *dw_##infix##_name(dw_##infix##_t)
+
+D_(ut);
+D_(tag);
+D_(at);
+D_(form);
+D_(op);
+D_(lle);
+D_(ate);
+D_(ds);
+D_(end);
+D_(access);
+D_(vis);
+D_(virtuality);
+D_(lang);
+D_(id);
+D_(cc);
+D_(lns);
+D_(lne);
+D_(lnct);
+
+#undef D_
+
+#endif /* DWARFS_NAMES_H_ */
Index: kernel/generic/include/stacktrace.h
===================================================================
--- kernel/generic/include/stacktrace.h	(revision da139823a6e0523217c1ab89cf8936eb8fd62ea2)
+++ kernel/generic/include/stacktrace.h	(revision 2fbb42fc7cb049341ed2bb0ef644df1429f5fa15)
@@ -52,5 +52,5 @@
 	bool (*frame_pointer_prev)(stack_trace_context_t *, uintptr_t *);
 	bool (*return_address_get)(stack_trace_context_t *, uintptr_t *);
-	bool (*symbol_resolve)(uintptr_t, const char **, uintptr_t *);
+	bool (*symbol_resolve)(uintptr_t, int, const char **, uintptr_t *, const char **, const char **, int *, int *);
 } stack_trace_ops_t;
 
Index: kernel/generic/meson.build
===================================================================
--- kernel/generic/meson.build	(revision da139823a6e0523217c1ab89cf8936eb8fd62ea2)
+++ kernel/generic/meson.build	(revision 2fbb42fc7cb049341ed2bb0ef644df1429f5fa15)
@@ -46,4 +46,6 @@
 	'src/cpu/cpu_mask.c',
 	'src/ddi/irq.c',
+	'src/debug/line.c',
+	'src/debug/names.c',
 	'src/debug/panic.c',
 	'src/debug/profile.c',
@@ -51,4 +53,5 @@
 	'src/debug/stacktrace.c',
 	'src/debug/symtab.c',
+	'src/debug/util.c',
 	'src/ipc/event.c',
 	'src/ipc/ipc.c',
Index: kernel/generic/src/debug/line.c
===================================================================
--- kernel/generic/src/debug/line.c	(revision 2fbb42fc7cb049341ed2bb0ef644df1429f5fa15)
+++ kernel/generic/src/debug/line.c	(revision 2fbb42fc7cb049341ed2bb0ef644df1429f5fa15)
@@ -0,0 +1,737 @@
+/*
+ * Copyright (c) 2023 Jiří Zárevúcky
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdatomic.h>
+
+#include <stdlib.h>
+
+#include <debug/constants.h>
+#include <debug/line.h>
+#include <debug/names.h>
+#include <debug/sections.h>
+
+#include "util.h"
+
+static inline void line_program_reset(struct debug_line_program *lp)
+{
+	lp->address = 0;
+	lp->op_advance = 0;
+	lp->file = 1;
+	lp->line = 1;
+	lp->column = 0;
+	lp->end_sequence = false;
+}
+
+/**
+ * Quickly advance program just past the next sequence end, without processing
+ * anything on the way. This is useful for when we just want to get start
+ * addresses of every range to build a table.
+ *
+ * @param lp
+ */
+static void debug_line_program_skip_to_sequence_end(struct debug_line_program *lp)
+{
+	const uint8_t opcode_base = lp->hdr->opcode_base;
+
+	const uint8_t *program = lp->program;
+	const uint8_t *const program_end = lp->program_end;
+
+	if (lp->end_sequence)
+		line_program_reset(lp);
+
+	while (program < program_end) {
+		uint8_t opcode = read_byte(&program, program_end);
+
+		if (opcode >= opcode_base) {
+			// Special opcode.
+			continue;
+		}
+
+		switch (opcode) {
+		case DW_LNS_fixed_advance_pc:
+			safe_increment(&program, program_end, 2);
+			break;
+
+		case DW_LNS_copy:
+		case DW_LNS_negate_stmt:
+		case DW_LNS_set_basic_block:
+		case DW_LNS_set_prologue_end:
+		case DW_LNS_set_epilogue_begin:
+		case DW_LNS_const_add_pc:
+			break;
+
+		case DW_LNS_advance_pc:
+		case DW_LNS_advance_line:
+		case DW_LNS_set_file:
+		case DW_LNS_set_column:
+		case DW_LNS_set_isa:
+			skip_leb128(&program, program_end);
+			break;
+
+		case 0:
+			// Extended opcodes.
+			size_t length = read_uleb128(&program, program_end);
+
+			if (read_byte(&program, program_end) == DW_LNE_end_sequence) {
+				lp->program = program;
+				lp->end_sequence = true;
+				return;
+			}
+
+			safe_increment(&program, program_end, length - 1);
+			break;
+
+		default:
+			// Unknown standard opcode.
+
+			// If the opcode length array is truncated, there is already
+			// something wrong with the parse, so we don't care if we misparse
+			// the remainder.
+			if (opcode < lp->hdr->standard_opcode_lengths_size) {
+				int len = lp->hdr->standard_opcode_lengths[opcode];
+				for (int i = 0; i < len; i++) {
+					// Drop arguments.
+					skip_leb128(&program, program_end);
+				}
+			}
+		}
+	}
+
+	lp->program = program;
+	lp->truncated = true;
+}
+
+static void transfer_op_advance(struct debug_line_program *lp)
+{
+	if (lp->hdr->version < 5) {
+		lp->address += lp->op_advance * lp->hdr->minimum_instruction_length;
+		lp->op_advance = 0;
+	} else {
+		int maxops = lp->hdr->v5.maximum_operations_per_instruction;
+
+		// For branch prediction, tell GCC maxops is likely to be 1, because
+		// I don't want to punish sensible architectures for VLIW madness.
+		if (__builtin_expect(maxops == 1, 1)) {
+			lp->address += lp->op_advance * lp->hdr->minimum_instruction_length;
+			lp->op_advance = 0;
+		} else {
+			int adv = lp->op_advance;
+			int d = adv / maxops;
+			int r = adv % maxops;
+
+			lp->address += d * lp->hdr->minimum_instruction_length;
+			lp->op_advance = r;
+		}
+	}
+}
+
+static void debug_line_program_advance(struct debug_line_program *lp)
+{
+	const uint8_t opcode_base = lp->hdr->opcode_base;
+	const int8_t line_base = lp->hdr->line_base;
+	const uint8_t line_range = lp->hdr->line_range;
+	const int const_advance = (255 - opcode_base) / lp->hdr->line_range;
+
+	const uint8_t *program = lp->program;
+	const uint8_t *const program_end = lp->program_end;
+
+	if (lp->end_sequence)
+		line_program_reset(lp);
+
+	while (program < program_end) {
+		uint8_t opcode = read_byte(&program, program_end);
+
+		if (opcode >= opcode_base) {
+			// Special opcode.
+			int adjusted = opcode - opcode_base;
+			DEBUGF("DW_LNS_special(%d)\n", adjusted);
+
+			lp->op_advance += adjusted / line_range;
+			lp->line += line_base + (adjusted % line_range);
+
+			transfer_op_advance(lp);
+			lp->program = program;
+			return;
+		}
+
+		const char *opname = dw_lns_name(opcode);
+
+		uint64_t arg;
+		uint16_t arg16;
+
+		switch (opcode) {
+		case DW_LNS_copy:
+			DEBUGF("%s()\n", opname);
+
+			transfer_op_advance(lp);
+			lp->program = program;
+			return;
+
+		case DW_LNS_advance_pc:
+			arg = read_uleb128(&program, program_end);
+			DEBUGF("%s(%"PRIu64")\n", opname, arg);
+			lp->op_advance += arg;
+			break;
+
+		case DW_LNS_advance_line:
+			lp->line += read_sleb128(&program, program_end);
+			DEBUGF("%s(line = %d)\n", opname, lp->line);
+			break;
+
+		case DW_LNS_set_file:
+			lp->file = read_uleb128(&program, program_end);
+			DEBUGF("%s(%d)\n", opname, lp->file);
+			break;
+
+		case DW_LNS_set_column:
+			lp->column = read_uleb128(&program, program_end);
+			DEBUGF("%s(%d)\n", opname, lp->column);
+			break;
+
+		case DW_LNS_negate_stmt:
+		case DW_LNS_set_basic_block:
+		case DW_LNS_set_prologue_end:
+		case DW_LNS_set_epilogue_begin:
+			DEBUGF("%s()\n", opname);
+			// ignore
+			break;
+
+		case DW_LNS_set_isa:
+			// discard argument and ignore
+			arg = read_uleb128(&program, program_end);
+			DEBUGF("%s(%" PRIu64 ")\n", opname, arg);
+			break;
+
+		case DW_LNS_const_add_pc:
+			DEBUGF("%s(%d)\n", opname, const_advance);
+			lp->op_advance += const_advance;
+			break;
+
+		case DW_LNS_fixed_advance_pc:
+			arg16 = read_uint16(&program, program_end);
+			DEBUGF("%s(%u)\n", opname, arg16);
+
+			transfer_op_advance(lp);
+			lp->address += arg16;
+			lp->op_advance = 0;
+			break;
+
+		case 0:
+			/* Extended opcodes. */
+			size_t length = read_uleb128(&program, program_end);
+
+			opcode = read_byte(&program, program_end);
+			const char *opname = dw_lne_name(opcode);
+
+			switch (opcode) {
+			case DW_LNE_end_sequence:
+				DEBUGF("%s:%zu()\n", opname, length);
+
+				transfer_op_advance(lp);
+				lp->program = program;
+				lp->end_sequence = true;
+				return;
+
+			case DW_LNE_set_address:
+				lp->address = read_uint(&program, program_end, sizeof(uintptr_t));
+				lp->op_advance = 0;
+
+				DEBUGF("%s:%zu(0x%zx)\n", opname, length, lp->address);
+				break;
+
+			case DW_LNE_set_discriminator:
+				uint64_t arg = read_uleb128(&program, program_end);
+				DEBUGF("%s:%zu(%"PRIu64")\n", opname, length, arg);
+				break;
+
+			default:
+				DEBUGF("unknown extended opcode %d:%zu\n", opcode, length);
+				safe_increment(&program, program_end, length - 1);
+			}
+			break;
+
+		default:
+			DEBUGF("unknown standard opcode %d\n", opcode);
+
+			// If the opcode length array is truncated, there is already
+			// something wrong with the parse, so we don't care if we misparse
+			// the remainder.
+			if (opcode < lp->hdr->standard_opcode_lengths_size) {
+				int len = lp->hdr->standard_opcode_lengths[opcode];
+				for (int i = 0; i < len; i++) {
+					// Drop arguments.
+					skip_leb128(&program, program_end);
+				}
+			}
+		}
+	}
+
+	transfer_op_advance(lp);
+	lp->program = program;
+	lp->truncated = true;
+}
+
+static void debug_line_program_header_parse(const uint8_t *data, const uint8_t *data_end, struct debug_line_program_header *hdr)
+{
+	const uint8_t *unit_start = data;
+
+#define FIELD(name, spec, expr) (hdr->name = expr, DEBUGF(#name ": %" spec "\n", hdr->name))
+
+	unsigned width;
+
+	FIELD(unit_length, PRId64, read_initial_length(&data, data_end, &width));
+	FIELD(width, "d", width);
+
+	hdr->unit_end = data;
+	safe_increment(&hdr->unit_end, data_end, hdr->unit_length);
+	DEBUGF("unit size: %zu\n", hdr->unit_end - unit_start);
+
+	data_end = hdr->unit_end;
+
+	FIELD(version, "u", read_uint16(&data, data_end));
+
+	if (hdr->version < 3 || hdr->version > 5) {
+		// Not supported.
+		hdr->header_end = hdr->unit_end;
+		return;
+	}
+
+	if (hdr->version >= 5) {
+		FIELD(v5.address_size, "u", read_byte(&data, data_end));
+		FIELD(v5.segment_selector_size, "u", read_byte(&data, data_end));
+	}
+
+	FIELD(header_length, PRIu64, read_uint(&data, data_end, width));
+
+	hdr->header_end = data;
+	safe_increment(&hdr->header_end, data_end, hdr->header_length);
+
+	data_end = hdr->header_end;
+
+	FIELD(minimum_instruction_length, "u", read_byte(&data, data_end));
+	if (hdr->version >= 5)
+		FIELD(v5.maximum_operations_per_instruction, "u", read_byte(&data, data_end));
+	FIELD(default_is_stmt, "u", read_byte(&data, data_end));
+	FIELD(line_base, "d", (int8_t) read_byte(&data, data_end));
+	FIELD(line_range, "u", read_byte(&data, data_end));
+	FIELD(opcode_base, "u", read_byte(&data, data_end));
+
+	hdr->standard_opcode_lengths = data;
+	safe_increment(&data, data_end, hdr->opcode_base - 1);
+	hdr->standard_opcode_lengths_size = data - hdr->standard_opcode_lengths;
+
+	if (hdr->version < 5) {
+		hdr->v3.include_directories = data;
+		while (data < data_end && *data != 0)
+			skip_string(&data, data_end);
+		hdr->v3.include_directories_end = data;
+
+		// Sanitize the list a little.
+		while (hdr->v3.include_directories < hdr->v3.include_directories_end && hdr->v3.include_directories_end[-1] != 0)
+			hdr->v3.include_directories_end--;
+
+		safe_increment(&data, data_end, 1);
+
+		hdr->v3.file_names = data;
+	}
+
+	if (hdr->version >= 5) {
+		FIELD(v5.directory_entry_format_count, "u", read_byte(&data, data_end));
+
+		hdr->v5.directory_entry_format = data;
+		skip_format(&data, data_end, hdr->v5.directory_entry_format_count);
+		hdr->v5.directory_entry_format_end = data;
+
+		print_format("directory_entry_format",
+			hdr->v5.directory_entry_format, hdr->v5.directory_entry_format_end);
+
+		FIELD(v5.directories_count, PRIu64, read_uleb128(&data, data_end));
+
+		hdr->v5.directories = data;
+		skip_formatted_list(&data, data_end, hdr->v5.directories_count,
+			hdr->v5.directory_entry_format, hdr->v5.directory_entry_format_end, width);
+		hdr->v5.directories_end = data;
+
+		print_formatted_list("directories", hdr->v5.directories, hdr->v5.directories_end,
+			hdr->v5.directory_entry_format, hdr->v5.directory_entry_format_end, width);
+
+		FIELD(v5.file_name_entry_format_count, "u", read_byte(&data, data_end));
+
+		hdr->v5.file_name_entry_format = data;
+		skip_format(&data, data_end, hdr->v5.file_name_entry_format_count);
+		hdr->v5.file_name_entry_format_end = data;
+
+		print_format("file_name_entry_format",
+			hdr->v5.file_name_entry_format, hdr->v5.file_name_entry_format_end);
+
+		FIELD(v5.file_names_count, PRIu64, read_uleb128(&data, data_end));
+
+		hdr->v5.file_names = data;
+		skip_formatted_list(&data, data_end, hdr->v5.file_names_count,
+			hdr->v5.file_name_entry_format, hdr->v5.file_name_entry_format_end, width);
+		hdr->v5.file_names_end = data;
+
+		print_formatted_list("file_names", hdr->v5.file_names, hdr->v5.file_names_end,
+			hdr->v5.file_name_entry_format, hdr->v5.file_name_entry_format_end, width);
+	}
+}
+
+static bool has_usable_name(const uint8_t *format, const uint8_t *format_end, unsigned width)
+{
+	// Check if there is an appropriate entry we can use.
+	bool has_usable_name = false;
+	const uint8_t *dummy = NULL;
+
+	while (format < format_end) {
+		uint64_t type = read_uleb128(&format, format_end);
+		uint64_t form = read_uleb128(&format, format_end);
+
+		if (type == DW_LNCT_path) {
+			if (form == DW_FORM_string || form == DW_FORM_line_strp) {
+				has_usable_name = true;
+			}
+		}
+
+		if (!skip_data(form, &dummy, NULL, width)) {
+			// Encountered DW_FORM that we don't understand,
+			// which means we can't skip it.
+			return false;
+		}
+	}
+
+	return has_usable_name;
+}
+
+static const char *get_file_name_v3(struct debug_line_program_header *hdr, int file, int *dir)
+{
+	if (file == 0) {
+		// We'd have to find and read the compilation unit header for this one,
+		// and we don't wanna.
+		return NULL;
+	}
+
+	file--;
+
+	const uint8_t *files = hdr->v3.file_names;
+	const uint8_t *files_end = hdr->header_end;
+
+	for (int i = 0; i < file; i++) {
+		if (files >= files_end || *files == 0) {
+			// End of list.
+			return NULL;
+		}
+
+		// Skip an entry.
+		skip_string(&files, files_end);
+		skip_leb128(&files, files_end);
+		skip_leb128(&files, files_end);
+		skip_leb128(&files, files_end);
+	}
+
+	if (files >= files_end || *files == 0)
+		return NULL;
+
+	const char *fn = (const char *) files;
+	skip_string(&files, files_end);
+	*dir = read_uleb128(&files, files_end);
+	return fn;
+}
+
+static const char *get_file_name_v5(struct debug_line_program_header *hdr, int file, int *dir)
+{
+	// DWARF5 has dynamic layout for file information, which is why
+	// this is so horrible to decode. Enjoy.
+
+	if (!has_usable_name(hdr->v5.file_name_entry_format, hdr->v5.file_name_entry_format_end, hdr->width))
+		return NULL;
+
+	const uint8_t *fns = hdr->v5.file_names;
+	const uint8_t *fns_end = hdr->v5.file_names_end;
+
+	const char *filename = NULL;
+
+	for (uint8_t i = 0; i < hdr->v5.file_names_count; i++) {
+
+		const uint8_t *fnef = hdr->v5.file_name_entry_format;
+		const uint8_t *fnef_end = hdr->v5.file_name_entry_format_end;
+
+		for (uint8_t j = 0; j < hdr->v5.file_name_entry_format_count; j++) {
+			uint64_t type = read_uleb128(&fnef, fnef_end);
+			uint64_t form = read_uleb128(&fnef, fnef_end);
+
+			if (i == file && type == DW_LNCT_path) {
+				if (form == DW_FORM_string) {
+					filename = read_string(&fns, fns_end);
+					continue;
+				}
+
+				if (form == DW_FORM_line_strp) {
+					uint64_t offset = read_uint(&fns, fns_end, hdr->width);
+					if (offset < debug_line_str_size) {
+						filename = debug_line_str + offset;
+					}
+					continue;
+				}
+			}
+
+			if (i == file && type == DW_LNCT_directory_index) {
+				if (form == DW_FORM_data1) {
+					*dir = read_byte(&fns, fns_end);
+					continue;
+				}
+
+				if (form == DW_FORM_data2) {
+					*dir = read_uint16(&fns, fns_end);
+					continue;
+				}
+
+				if (form == DW_FORM_udata) {
+					*dir = read_uleb128(&fns, fns_end);
+					continue;
+				}
+			}
+
+			skip_data(form, &fns, fns_end, hdr->width);
+		}
+
+		if (i == file)
+			break;
+	}
+
+	return filename;
+}
+
+static const char *get_file_name(struct debug_line_program_header *hdr, int file, int *dir)
+{
+	switch (hdr->version) {
+	case 3:
+	case 4:
+		return get_file_name_v3(hdr, file, dir);
+	case 5:
+		return get_file_name_v5(hdr, file, dir);
+	default:
+		return NULL;
+	}
+}
+
+static const char *get_dir_name_v3(struct debug_line_program_header *hdr, int dir)
+{
+	if (dir == 0)
+		return ".";
+
+	const uint8_t *dirs = hdr->v3.include_directories;
+	const uint8_t *dirs_end = hdr->v3.include_directories_end;
+
+	for (int i = 1; i < dir; i++) {
+		skip_string(&dirs, dirs_end);
+	}
+
+	if (dirs < dirs_end)
+		return (const char *) dirs;
+	else
+		return NULL;
+}
+
+static const char *get_dir_name_v5(struct debug_line_program_header *hdr, int dir)
+{
+	// TODO: basically a copypaste of get_file_name(). Try to deduplicate it.
+
+	if (!has_usable_name(hdr->v5.directory_entry_format, hdr->v5.directory_entry_format_end, hdr->width))
+		return NULL;
+
+	const uint8_t *fns = hdr->v5.directories;
+	const uint8_t *fns_end = hdr->v5.directories_end;
+
+	const char *filename = NULL;
+
+	for (uint8_t i = 0; i < hdr->v5.directories_count; i++) {
+
+		const uint8_t *fnef = hdr->v5.directory_entry_format;
+		const uint8_t *fnef_end = hdr->v5.directory_entry_format_end;
+
+		for (uint8_t j = 0; j < hdr->v5.directory_entry_format_count; j++) {
+			uint64_t type = read_uleb128(&fnef, fnef_end);
+			uint64_t form = read_uleb128(&fnef, fnef_end);
+
+			if (i == dir && type == DW_LNCT_path) {
+				if (form == DW_FORM_string) {
+					filename = read_string(&fns, fns_end);
+					continue;
+				}
+
+				if (form == DW_FORM_line_strp) {
+					uint64_t offset = read_uint(&fns, fns_end, hdr->width);
+					if (offset < debug_line_str_size) {
+						filename = debug_line_str + offset;
+					}
+					continue;
+				}
+			}
+
+			skip_data(form, &fns, fns_end, hdr->width);
+		}
+
+		if (i == dir)
+			break;
+	}
+
+	return filename;
+}
+
+static const char *get_dir_name(struct debug_line_program_header *hdr, int dir)
+{
+	switch (hdr->version) {
+	case 3:
+	case 4:
+		return get_dir_name_v3(hdr, dir);
+	case 5:
+		return get_dir_name_v5(hdr, dir);
+	default:
+		return NULL;
+	}
+}
+
+static const uint8_t *find_line_program(uintptr_t addr)
+{
+	// TODO: use .debug_aranges to find the data quickly
+	// This implementation just iterates through the entire .debug_line
+
+	uintptr_t closest_addr = 0;
+	const uint8_t *closest_prog = NULL;
+
+	const uint8_t *debug_line_ptr = debug_line;
+	const uint8_t *const debug_line_end = debug_line + debug_line_size;
+
+	while (debug_line_ptr < debug_line_end) {
+		const uint8_t *prog = debug_line_ptr;
+
+		// Parse header
+		struct debug_line_program_header hdr = { };
+		debug_line_program_header_parse(prog, debug_line_end, &hdr);
+		assert(hdr.unit_end > debug_line_ptr);
+		assert(hdr.unit_end <= debug_line_end);
+		debug_line_ptr = hdr.unit_end;
+
+		struct debug_line_program lp = debug_line_program_create(
+		    hdr.header_end, hdr.unit_end, &hdr);
+
+		while (lp.program < lp.program_end) {
+			// Find the start address of every sequence
+
+			debug_line_program_advance(&lp);
+			DEBUGF("<< address: %zx, line: %d, column: %d >>\n", lp.address, lp.line, lp.column);
+
+			if (!lp.truncated) {
+				if (lp.address <= addr && lp.address > closest_addr) {
+					closest_addr = lp.address;
+					closest_prog = prog;
+				}
+			}
+
+			/*
+			if (!lp.end_sequence) {
+				debug_line_program_skip_to_sequence_end(&lp);
+				assert(lp.truncated || lp.end_sequence);
+			}
+			*/
+		}
+	}
+
+	return closest_prog;
+}
+
+static bool get_info(const struct debug_line_program_header *hdr, uintptr_t addr, int op_index, int *file, int *line, int *column) {
+	struct debug_line_program lp = debug_line_program_create(
+		hdr->header_end, hdr->unit_end, hdr);
+
+	int last_file = 0;
+	int last_line = 0;
+	int last_column = 0;
+
+	while (lp.program < lp.program_end) {
+		bool first = lp.end_sequence;
+
+		debug_line_program_advance(&lp);
+
+		if (!lp.truncated) {
+			// We check for the last address before addr, because addr
+			// is a return address and we want the call instruction.
+			if (lp.address >= addr && lp.op_advance >= op_index) {
+				if (!first) {
+					*file = last_file;
+					*line = last_line;
+					*column = last_column;
+					return true;
+				}
+
+				// First address is already too large, skip to the next sequence.
+				debug_line_program_skip_to_sequence_end(&lp);
+			}
+
+			last_file = lp.file;
+			last_line = lp.line;
+			last_column = lp.column;
+		}
+	}
+
+	return false;
+}
+
+bool debug_line_get_address_info(uintptr_t addr, int op_index, const char **file_name, const char **dir_name, int *line, int *column)
+{
+	const uint8_t *data = find_line_program(addr);
+	if (data == NULL) {
+		return false;
+	}
+
+	const uint8_t *const debug_line_end = debug_line + debug_line_size;
+
+	struct debug_line_program_header hdr = { };
+	debug_line_program_header_parse(data, debug_line_end, &hdr);
+	assert(hdr.unit_end > data);
+	assert(hdr.unit_end <= debug_line_end);
+
+	int dir = -1;
+	int file = -1;
+
+	if (!get_info(&hdr, addr, op_index, &file, line, column)) {
+		printf("no info for 0x%zx: prog offset 0x%zx\n", addr, (void *) data - debug_line);
+		return false;
+	}
+
+	// printf("got info for 0x%zx: file %d, line %d, column %d\n", addr, file, *line, *column);
+
+	if (file >= 0)
+		*file_name = get_file_name(&hdr, file, &dir);
+
+	if (dir >= 0)
+		*dir_name = get_dir_name(&hdr, dir);
+
+	return true;
+}
+
Index: kernel/generic/src/debug/names.c
===================================================================
--- kernel/generic/src/debug/names.c	(revision 2fbb42fc7cb049341ed2bb0ef644df1429f5fa15)
+++ kernel/generic/src/debug/names.c	(revision 2fbb42fc7cb049341ed2bb0ef644df1429f5fa15)
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) 2023 Jiří Zárevúcky
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stddef.h>
+#include <stdint.h>
+#include <debug/constants.h>
+#include <debug/names.h>
+
+#define CVAL(name, value) [value] = #name,
+
+static const char *const dw_ut_names[] = {
+	#include <debug/constants/dw_ut.h>
+};
+
+static const char *const dw_tag_names[] = {
+	#include <debug/constants/dw_tag.h>
+};
+
+static const char *const dw_at_names[] = {
+	#include <debug/constants/dw_at.h>
+};
+
+static const char *const dw_form_names[] = {
+	#include <debug/constants/dw_form.h>
+};
+
+static const char *const dw_op_names[] = {
+	#include <debug/constants/dw_op.h>
+};
+
+static const char *const dw_lle_names[] = {
+	#include <debug/constants/dw_lle.h>
+};
+
+static const char *const dw_ate_names[] = {
+	#include <debug/constants/dw_ate.h>
+};
+
+static const char *const dw_ds_names[] = {
+	#include <debug/constants/dw_ds.h>
+};
+
+static const char *const dw_end_names[] = {
+	#include <debug/constants/dw_end.h>
+};
+
+static const char *const dw_access_names[] = {
+	#include <debug/constants/dw_access.h>
+};
+
+static const char *const dw_vis_names[] = {
+	#include <debug/constants/dw_vis.h>
+};
+
+static const char *const dw_virtuality_names[] = {
+	#include <debug/constants/dw_virtuality.h>
+};
+
+static const char *const dw_lang_names[] = {
+	#include <debug/constants/dw_lang.h>
+};
+
+static const char *const dw_id_names[] = {
+	#include <debug/constants/dw_id.h>
+};
+
+static const char *const dw_cc_names[] = {
+	#include <debug/constants/dw_cc.h>
+};
+
+static const char *const dw_lns_names[] = {
+	#include <debug/constants/dw_lns.h>
+};
+
+static const char *const dw_lne_names[] = {
+	#include <debug/constants/dw_lne.h>
+};
+
+static const char *const dw_lnct_names[] = {
+	#include <debug/constants/dw_lnct.h>
+};
+
+#undef CVAL
+
+#define D_(infix) \
+		const char *dw_##infix##_name(dw_##infix##_t val) { \
+			if (val >= sizeof(dw_##infix##_names) / sizeof(const char *)) \
+				return NULL; \
+			return dw_##infix##_names[val]; \
+		}
+
+D_(ut);
+D_(tag);
+D_(at);
+D_(form);
+D_(op);
+D_(lle);
+D_(ate);
+D_(ds);
+D_(end);
+D_(access);
+D_(vis);
+D_(virtuality);
+D_(lang);
+D_(id);
+D_(cc);
+D_(lns);
+D_(lne);
+D_(lnct);
+
+#undef D_
Index: kernel/generic/src/debug/stacktrace.c
===================================================================
--- kernel/generic/src/debug/stacktrace.c	(revision da139823a6e0523217c1ab89cf8936eb8fd62ea2)
+++ kernel/generic/src/debug/stacktrace.c	(revision 2fbb42fc7cb049341ed2bb0ef644df1429f5fa15)
@@ -39,4 +39,6 @@
 #include <stdio.h>
 
+#include <debug/line.h>
+
 #define STACK_FRAMES_MAX  20
 
@@ -44,6 +46,5 @@
 {
 	int cnt = 0;
-	const char *symbol;
-	uintptr_t offset;
+
 	uintptr_t fp;
 	uintptr_t pc;
@@ -51,11 +52,30 @@
 	while ((cnt++ < STACK_FRAMES_MAX) &&
 	    (ops->stack_trace_context_validate(ctx))) {
+
+		const char *symbol = NULL;
+		uintptr_t symbol_addr = 0;
+		const char *file_name = NULL;
+		const char *dir_name = NULL;
+		int line = 0;
+		int column = 0;
+
 		if (ops->symbol_resolve &&
-		    ops->symbol_resolve(ctx->pc, &symbol, &offset)) {
-			if (offset)
-				printf("%p: %s()+%p\n", (void *) ctx->fp,
-				    symbol, (void *) offset);
-			else
-				printf("%p: %s()\n", (void *) ctx->fp, symbol);
+		    ops->symbol_resolve(ctx->pc, 0, &symbol, &symbol_addr, &file_name, &dir_name, &line, &column)) {
+
+			if (symbol == NULL)
+				symbol = "<unknown>";
+
+			if (file_name == NULL && line == 0) {
+				printf("%p: %24s()+%zu\n", (void *) ctx->fp, symbol, ctx->pc - symbol_addr);
+			} else {
+				if (file_name == NULL)
+					file_name = "<unknown>";
+				if (dir_name == NULL)
+					dir_name = "<unknown>";
+
+				printf("%p: %20s()+%zu\t %s/%s:%d:%d\n",
+				    (void *) ctx->fp, symbol, ctx->pc - symbol_addr,
+				    dir_name, file_name, line, column);
+			}
 		} else
 			printf("%p: %p()\n", (void *) ctx->fp, (void *) ctx->pc);
@@ -104,10 +124,13 @@
 
 static bool
-kernel_symbol_resolve(uintptr_t addr, const char **sp, uintptr_t *op)
+resolve_kernel_address(uintptr_t addr, int op_index,
+		const char **symbol, uintptr_t *symbol_addr,
+		const char **filename, const char **dirname,
+		int *line, int *column)
 {
-	uintptr_t symbol_addr = 0;
-	*sp = symtab_name_lookup(addr, &symbol_addr);
-	*op = addr - symbol_addr;
-	return symbol_addr != 0;
+	*symbol_addr = 0;
+	*symbol = symtab_name_lookup(addr, symbol_addr);
+
+	return debug_line_get_address_info(addr, op_index, filename, dirname, line, column) || *symbol_addr != 0;
 }
 
@@ -116,5 +139,5 @@
 	.frame_pointer_prev = kernel_frame_pointer_prev,
 	.return_address_get = kernel_return_address_get,
-	.symbol_resolve = kernel_symbol_resolve
+	.symbol_resolve = resolve_kernel_address,
 };
 
Index: kernel/generic/src/debug/util.c
===================================================================
--- kernel/generic/src/debug/util.c	(revision 2fbb42fc7cb049341ed2bb0ef644df1429f5fa15)
+++ kernel/generic/src/debug/util.c	(revision 2fbb42fc7cb049341ed2bb0ef644df1429f5fa15)
@@ -0,0 +1,295 @@
+/*
+ * Copyright (c) 2023 Jiří Zárevúcky
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "util.h"
+
+#include <stdio.h>
+#include <debug/sections.h>
+#include <debug/names.h>
+
+/* These declarations cause global definitions for the functions to be emitted
+ * in this compilation unit, so if the compiler decides not to inline some of
+ * them, only one external copy exists. See C99 inline rules.
+ */
+extern inline uint8_t read_byte(const uint8_t **, const uint8_t *);
+extern inline uint16_t read_uint16(const uint8_t **, const uint8_t *);
+extern inline uint32_t read_uint24(const uint8_t **, const uint8_t *);
+extern inline uint32_t read_uint32(const uint8_t **, const uint8_t *);
+extern inline uint64_t read_uint64(const uint8_t **, const uint8_t *);
+extern inline uint64_t read_uint(const uint8_t **, const uint8_t *, unsigned);
+extern inline uint64_t read_uleb128(const uint8_t **, const uint8_t *);
+extern inline int64_t read_sleb128(const uint8_t **, const uint8_t *);
+extern inline void skip_leb128(const uint8_t **, const uint8_t *);
+extern inline uint64_t read_initial_length(const uint8_t **, const uint8_t *, unsigned *);
+extern inline const char *read_string(const uint8_t **, const uint8_t *);
+extern inline void skip_string(const uint8_t **, const uint8_t *);
+extern inline void safe_increment(const uint8_t **, const uint8_t *, ptrdiff_t);
+
+extern inline void skip_format(const uint8_t **, const uint8_t *, unsigned);
+extern inline void skip_formatted_entry(const uint8_t **, const uint8_t *,
+    const uint8_t *, const uint8_t *, unsigned);
+extern inline void skip_formatted_list(const uint8_t **, const uint8_t *,
+    unsigned, const uint8_t *, const uint8_t *, unsigned);
+
+bool skip_data(unsigned form, const uint8_t **const data,
+    const uint8_t *data_end, unsigned width)
+{
+	// Skip data we don't care about reading.
+
+	size_t len;
+
+	switch (form) {
+	case DW_FORM_string:
+		skip_string(data, data_end);
+		break;
+
+	case DW_FORM_strp:
+	case DW_FORM_line_strp:
+	case DW_FORM_strp_sup:
+	case DW_FORM_sec_offset:
+		safe_increment(data, data_end, width);
+		break;
+
+	case DW_FORM_strx:
+	case DW_FORM_sdata:
+	case DW_FORM_udata:
+		skip_leb128(data, data_end);
+		break;
+
+	case DW_FORM_strx1:
+	case DW_FORM_data1:
+	case DW_FORM_flag:
+		safe_increment(data, data_end, 1);
+		break;
+
+	case DW_FORM_strx2:
+	case DW_FORM_data2:
+		safe_increment(data, data_end, 2);
+		break;
+
+	case DW_FORM_strx3:
+		safe_increment(data, data_end, 3);
+		break;
+
+	case DW_FORM_strx4:
+	case DW_FORM_data4:
+		safe_increment(data, data_end, 4);
+		break;
+
+	case DW_FORM_data8:
+		safe_increment(data, data_end, 8);
+		break;
+
+	case DW_FORM_data16:
+		safe_increment(data, data_end, 16);
+		break;
+
+	case DW_FORM_block:
+		len = read_uleb128(data, data_end);
+		safe_increment(data, data_end, len);
+		break;
+
+	case DW_FORM_block1:
+		len = read_byte(data, data_end);
+		safe_increment(data, data_end, len);
+		break;
+
+	case DW_FORM_block2:
+		len = read_uint16(data, data_end);
+		safe_increment(data, data_end, len);
+		break;
+
+	case DW_FORM_block4:
+		len = read_uint32(data, data_end);
+		safe_increment(data, data_end, len);
+		break;
+
+	default:
+		// Unknown FORM.
+		*data = data_end;
+		return false;
+	}
+
+	return true;
+}
+
+void print_format(const char *name, const uint8_t *format, const uint8_t *format_end)
+{
+	DEBUGF("%s: ", name);
+
+	while (format < format_end) {
+		uint64_t lnct = read_uleb128(&format, format_end);
+		uint64_t form = read_uleb128(&format, format_end);
+
+		const char *fname = dw_form_name(form);
+
+		if (fname)
+			DEBUGF("%s:%s, ", dw_lnct_name(lnct), fname);
+		else
+			DEBUGF("%s:unknown DW_FORM_(%" PRIu64 "), ", dw_lnct_name(lnct), form);
+	}
+
+	DEBUGF("\n");
+}
+
+void print_formatted_list(const char *name,
+    const uint8_t *data, const uint8_t *const data_end,
+    const uint8_t *format, const uint8_t *format_end,
+    unsigned width)
+{
+	DEBUGF("%s: ", name);
+
+	while (data < data_end) {
+		const uint8_t *old_data = data;
+		const uint8_t *format_ptr = format;
+
+		while (format_ptr < format_end) {
+			uint64_t lnct = read_uleb128(&format_ptr, format_end);
+			uint64_t form = read_uleb128(&format_ptr, format_end);
+
+			DEBUGF("%s:%s:", dw_lnct_name(lnct), dw_form_name(form));
+			print_formed_data(form, &data, data_end, width);
+			DEBUGF("\n");
+		}
+
+		if (data <= old_data)
+			break;
+	}
+
+	DEBUGF("\n");
+}
+
+void print_block(const uint8_t **const data,
+	    const uint8_t *data_end, unsigned bytes)
+{
+	while (bytes > 0 && *data < data_end) {
+		DEBUGF("%02x ", **data);
+		(*data)++;
+		bytes--;
+	}
+}
+
+void print_formed_data(unsigned form, const uint8_t **const data,
+	    const uint8_t *data_end, unsigned width)
+{
+	size_t len;
+	uint64_t offset;
+
+	switch (form) {
+	case DW_FORM_string:
+		DEBUGF("\"%s\"", read_string(data, data_end));
+		break;
+
+	case DW_FORM_strp:
+	case DW_FORM_strp_sup:
+		offset = read_uint(data, data_end, width);
+		if (offset >= debug_str_size)
+			DEBUGF("<out of range>");
+		else
+			DEBUGF("\"%s\"", debug_str + offset);
+		break;
+
+	case DW_FORM_line_strp:
+		offset = read_uint(data, data_end, width);
+		if (offset >= debug_line_str_size)
+			DEBUGF("<out of range>");
+		else
+			DEBUGF("\"%s\"", debug_line_str + offset);
+		break;
+
+	case DW_FORM_sec_offset:
+		if (width == 4)
+			DEBUGF("0x%08" PRIx64, read_uint(data, data_end, width));
+		else
+			DEBUGF("0x%016" PRIx64, read_uint(data, data_end, width));
+		break;
+
+	case DW_FORM_strx:
+	case DW_FORM_udata:
+		DEBUGF("%" PRIu64, read_uleb128(data, data_end));
+		break;
+
+	case DW_FORM_sdata:
+		DEBUGF("%" PRId64, read_sleb128(data, data_end));
+		break;
+
+	case DW_FORM_strx1:
+	case DW_FORM_data1:
+	case DW_FORM_flag:
+		DEBUGF("%u", read_byte(data, data_end));
+		break;
+
+	case DW_FORM_strx2:
+	case DW_FORM_data2:
+		DEBUGF("%u", read_uint16(data, data_end));
+		break;
+
+	case DW_FORM_strx3:
+		DEBUGF("%u", read_uint24(data, data_end));
+		break;
+
+	case DW_FORM_strx4:
+	case DW_FORM_data4:
+		DEBUGF("%u", read_uint32(data, data_end));
+		break;
+
+	case DW_FORM_data8:
+		DEBUGF("%" PRIu64, read_uint64(data, data_end));
+		break;
+
+	case DW_FORM_data16:
+		uint64_t data1 = read_uint64(data, data_end);
+		uint64_t data2 = read_uint64(data, data_end);
+		DEBUGF("0x%016"PRIx64"%016"PRIx64, data2, data1);
+		break;
+
+	case DW_FORM_block:
+		len = read_uleb128(data, data_end);
+		print_block(data, data_end, len);
+		break;
+
+	case DW_FORM_block1:
+		len = read_byte(data, data_end);
+		print_block(data, data_end, len);
+		break;
+
+	case DW_FORM_block2:
+		len = read_uint16(data, data_end);
+		print_block(data, data_end, len);
+		break;
+
+	case DW_FORM_block4:
+		len = read_uint32(data, data_end);
+		print_block(data, data_end, len);
+		break;
+
+	default:
+		DEBUGF("unexpected form");
+		*data = data_end;
+	}
+}
Index: kernel/generic/src/debug/util.h
===================================================================
--- kernel/generic/src/debug/util.h	(revision 2fbb42fc7cb049341ed2bb0ef644df1429f5fa15)
+++ kernel/generic/src/debug/util.h	(revision 2fbb42fc7cb049341ed2bb0ef644df1429f5fa15)
@@ -0,0 +1,267 @@
+/*
+ * Copyright (c) 2023 Jiří Zárevúcky
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef DEBUG_UTIL_H_
+#define DEBUG_UTIL_H_
+
+#include <assert.h>
+#include <stdint.h>
+#include <debug/constants.h>
+#include <debug.h>
+
+#define DEBUGF dummy_printf
+
+extern bool skip_data(unsigned, const uint8_t **const, const uint8_t *, unsigned);
+extern void print_format(const char *, const uint8_t *, const uint8_t *);
+extern void print_formatted_list(const char *, const uint8_t *, const uint8_t *,
+    const uint8_t *, const uint8_t *, unsigned);
+
+extern void print_block(const uint8_t **, const uint8_t *, unsigned);
+extern void print_formed_data(unsigned, const uint8_t **const, const uint8_t *, unsigned);
+
+inline uint8_t read_byte(const uint8_t **data, const uint8_t *data_end)
+{
+	if (*data >= data_end) {
+		return 0;
+	} else {
+		return *((*data)++);
+	}
+}
+
+inline uint16_t read_uint16(const uint8_t **data, const uint8_t *data_end)
+{
+	/* Casting to these structures allows us to read unaligned integers safely. */
+	struct u16 {
+		uint16_t val;
+	} __attribute__((packed));
+
+	if (*data + 2 > data_end) {
+		/* Safe exit path for malformed input. */
+		*data = data_end;
+		return 0;
+	}
+
+	uint16_t v = ((struct u16 *) *data)->val;
+	*data += 2;
+	return v;
+}
+
+inline uint32_t read_uint24(const uint8_t **data, const uint8_t *data_end)
+{
+	if (*data + 3 > data_end) {
+		/* Safe exit path for malformed input. */
+		*data = data_end;
+		return 0;
+	}
+
+#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+	uint32_t v = (*data)[0] | (*data)[1] << 8 | (*data)[2] << 16;
+#else
+	uint32_t v = (*data)[2] | (*data)[1] << 8 | (*data)[0] << 16;
+#endif
+
+	*data += 3;
+	return v;
+}
+
+inline uint32_t read_uint32(const uint8_t **data, const uint8_t *data_end)
+{
+	struct u32 {
+		uint32_t val;
+	} __attribute__((packed));
+
+	if (*data + 4 > data_end) {
+		/* Safe exit path for malformed input. */
+		*data = data_end;
+		return 0;
+	}
+
+	uint32_t v = ((struct u32 *) *data)->val;
+	*data += 4;
+	return v;
+}
+
+inline uint64_t read_uint64(const uint8_t **data, const uint8_t *data_end)
+{
+	struct u64 {
+		uint64_t val;
+	} __attribute__((packed));
+
+	if (*data + 8 > data_end) {
+		/* Safe exit path for malformed input. */
+		*data = data_end;
+		return 0;
+	}
+
+	uint64_t v = ((struct u64 *) *data)->val;
+	*data += 8;
+	return v;
+}
+
+inline uint64_t read_uint(const uint8_t **data, const uint8_t *data_end, unsigned bytes)
+{
+	switch (bytes) {
+	case 1:
+		return read_byte(data, data_end);
+	case 2:
+		return read_uint16(data, data_end);
+	case 4:
+		return read_uint32(data, data_end);
+	case 8:
+		return read_uint64(data, data_end);
+	default:
+		panic("unimplemented");
+	}
+}
+
+inline uint64_t read_uleb128(const uint8_t **data, const uint8_t *data_end)
+{
+	uint64_t result = 0;
+	unsigned shift = 0;
+
+	while (*data < data_end) {
+		uint8_t byte = *((*data)++);
+		result |= (byte & 0x7f) << shift;
+		shift += 7;
+
+		if ((byte & 0x80) == 0)
+			break;
+	}
+
+	return result;
+}
+
+inline int64_t read_sleb128(const uint8_t **data, const uint8_t *data_end)
+{
+	uint64_t result = 0;
+	unsigned shift = 0;
+
+	while (*data < data_end) {
+		uint8_t byte = *((*data)++);
+		result |= (byte & 0x7f) << shift;
+		shift += 7;
+
+		if ((byte & 0x80) == 0) {
+			if (shift < 64 && (byte & 0x40) != 0) {
+				/* sign extend */
+				result |= -(1 << shift);
+			}
+			break;
+		}
+	}
+
+	return (int64_t) result;
+}
+
+inline void skip_leb128(const uint8_t **data, const uint8_t *data_end)
+{
+	while (*data < data_end) {
+		uint8_t byte = *((*data)++);
+		if ((byte & 0x80) == 0)
+			break;
+	}
+}
+
+inline uint64_t read_initial_length(const uint8_t **data, const uint8_t *data_end, unsigned *width)
+{
+	uint32_t initial = read_uint32(data, data_end);
+	if (initial == 0xffffffffu) {
+		*width = 8;
+		return read_uint64(data, data_end);
+	} else {
+		*width = 4;
+		return initial;
+	}
+}
+
+inline const char *read_string(const uint8_t **data, const uint8_t *data_end)
+{
+	const char *start = (const char *) *data;
+
+	// NUL-terminated string.
+	while (*data < data_end && **data != 0)
+		(*data)++;
+
+	if (*data < data_end) {
+		// Skip the terminating zero.
+		(*data)++;
+		return start;
+	} else {
+		// No terminating zero, we can't use this.
+		return NULL;
+	}
+}
+
+inline void skip_string(const uint8_t **data, const uint8_t *data_end)
+{
+	(void) read_string(data, data_end);
+}
+
+inline void safe_increment(const uint8_t **data,
+    const uint8_t *data_end, ptrdiff_t increment)
+{
+	assert(data_end >= *data);
+
+	if (increment >= data_end - *data) {
+		*data = data_end;
+	} else {
+		(*data) += increment;
+	}
+}
+
+inline void skip_format(const uint8_t **data, const uint8_t *const data_end,
+    unsigned count)
+{
+	for (unsigned i = 0; i < count; i++) {
+		(void) read_uleb128(data, data_end);
+		(void) read_uleb128(data, data_end);
+	}
+}
+
+inline void skip_formatted_entry(const uint8_t **data, const uint8_t *const data_end,
+    const uint8_t *format, const uint8_t *format_end, unsigned width)
+{
+	while (format < format_end) {
+		/* Ignore content type code */
+		(void) read_uleb128(&format, format_end);
+
+		uint64_t form = read_uleb128(&format, format_end);
+		skip_data(form, data, data_end, width);
+	}
+}
+
+inline void skip_formatted_list(const uint8_t **data, const uint8_t *const data_end,
+    unsigned count,	const uint8_t *format, const uint8_t *format_end,
+    unsigned width)
+{
+	for (unsigned i = 0; i < count; i++) {
+		skip_formatted_entry(data, data_end, format, format_end, width);
+	}
+}
+
+#endif /* DEBUG_UTIL_H_ */
Index: kernel/meson.build
===================================================================
--- kernel/meson.build	(revision da139823a6e0523217c1ab89cf8936eb8fd62ea2)
+++ kernel/meson.build	(revision 2fbb42fc7cb049341ed2bb0ef644df1429f5fa15)
@@ -95,4 +95,6 @@
 kernel_c_args = arch_kernel_c_args + kernel_defs + [
 	'-ffreestanding',
+	'-fdebug-prefix-map=../../kernel/=',
+	'-fdebug-prefix-map=../../../kernel/=',
 
 	cc.get_supported_arguments([
@@ -112,4 +114,9 @@
 ]
 
+if not CONFIG_LINE_DEBUG
+	# Keep the debug info needed to get file names for kernel stack traces.
+	kernel_c_args += [ '-gdwarf-5', '-g1', '-gno-statement-frontiers' ]
+endif
+
 if CONFIG_LTO
 	kernel_c_args += [ '-flto' ]
