Skip to content
 
 

Repository files navigation

-*-org-*-

Introduction

(See http://pmachata.github.io/dwgrep/ for a full HTML documentation.)

Dwgrep is a tool and an associated language for querying Dwarf (debuginfo) graphs. If you want to find out more about Dwarf, you can check out the following links. But you can also pretend that Dwarf is like XML, except nodes are called DIE’s. That, and perusing the output of eu-readelf -winfo, should be enough to get you started.

http://www.dwarfstd.org/doc/Debugging%20using%20DWARF.pdf http://dwarfstd.org/Download.php

You can think of dwgrep expressions as instructions describing a path through a graph, with assertions about the type of nodes along the way: that a node is of given type, that it has a given attribute, etc. There are also means of expressing sub-conditions, i.e. assertions that a given node is acceptable if a separate expression matches (or doesn’t match) a different path through the graph.

Apart from Dwarf objects (DIE’s and Attributes), dwgrep expressions can work with integers, strings, and sequences of other objects.

In particular, a simple expression in dwgrep might look like this:

entry ?DW_TAG_subprogram child ?DW_TAG_formal_parameter @DW_AT_name

On a command line, you would issue it like this:

dwgrep /some/file/somewhere -e 'entry ?DW_TAG_subprogram ...etc....'

The query itself (ignoring the initial colon that’s part of meta-syntax) says: show me values of attribute DW_AT_name of DW_TAG_formal_parameter nodes that are children of DW_TAG_subprogram entries (which here means debug information entries, or DIE’s). Reading forward, you get list of instructions to a matcher: take DIE’s, accept all DW_TAG_subprogram’s, look at their children, accept those that are DW_TAG_formal_parameter, take value of attribute DW_AT_name.

Another example comes from dwarflint:

entry ?DW_AT_decl_column !DW_AT_decl_line

… which looks for DIE’s that have DW_AT_decl_column, but don’t have DW_AT_decl_line–a semantic violation that is worth reporting.

Representation of Dwarf graph

Vocabulary

  • elem, relem, length – container-like access
  • child, parent – vertical tree access
  • next, prev – horizontal tree access
  • root – access to root tree element
  • high, low – boundaries
  • label – node denotation (attribute code, DIE tag, …)
  • abbrev – access to abbreviation(s)
  • attribute – access to attribute(s)
  • form – attribute form (a DW_FORM_* constant)
  • name – string associated with entity (file name, symbol name)
  • offset – constant describing where in the file the object is defined
  • address – address(es) of where in user space the object is defined
  • merge – re-establish logical view of the whole
  • entry – access to entries of a unit
  • unit – access to unit

·Entry points

·dwopen :: ?T_STR -> ?T_DWARF

  • Pops a file name, opens an ELF with that name, and pushes a value representing that file to TOS.
  • When files are passed on command line, those are pre-opened and pre-pushed by the query driver, and appear as sole value on runtime stack:
    $ dwgrep ./a.out -e 'type'
    T_DWARF
        

·T_DWARF

•entry :: ?T_DWARF ->* ?T_DIE

  • Yields all DIE’s in a .debug_info section.

•unit :: ?T_DWARF ->* ?T_CU

entry ?root unit

•abbrev :: ?T_DWARF ->* ?T_ABBREV_UNIT

  • Yields all abbreviation units in a .debug_abbrev section.

symbol :: ?T_DWARF ->* ?T_ELFSYM

  • Yields all symbols in .symtab, or minisymtab, or .dynsym.

•name :: ?T_DWARF -> ?T_STR

·T_CU

•offset :: ?T_CU -> ?T_CONST

next :: ?T_CU ->? T_CU

prev :: ?T_CU ->? T_CU

•abbrev :: ?T_CU -> ?T_ABBREV_UNIT

  • Produce an abbreviation unit associated with this CU.

•version :: ?T_CU -> ?T_CONST

•entry :: ?T_CU ->* ?T_DIE

root child*	# except in the right order

•root :: ?T_CU -> ?T_DIE

  • Produce a CU DIE of a CU.

·T_DIE

•label :: ?T_DIE -> ?T_CONST

Yields DIE tag.

•offset :: ?T_DIE -> ?T_CONST

Like dwarf_dieoffset.

•unit :: ?T_DIE -> ?T_CU

  • Selects unit that this DIE comes from.

•high :: ?T_DIE ->? ?T_CONST

attribute ?AT_highpc address

•low :: ?T_DIE ->? ?T_CONST

@AT_lowpc

•address :: ?T_DIE ->? ?T_ASET

Like dwarf_ranges.

•child :: ?T_DIE ->* ?T_DIE

Yields children of the DIE.

•attribute :: ?T_DIE ->* ?T_ATTR

Yields attributes of the DIE.

•parent :: ?T_DIE ->? ?T_DIE

Yields the parent of the DIE, if there’s any.

XXX DW_TAG_partial_unit

•root :: ?T_DIE -> ?T_DIE

root := {parent* !(parent)};

•?root :: ?T_DIE

?root holds if the DIE is a root node. Equivalent to !(parent).

parent* ?root        # finds a root node of node on TOS

next :: ?T_DIE ->? ?T_DIE

Like dwarf_siblingof, returns next sibling of the DIE.

def next {|D| D parent child (pos == D parent child (== D) pos 1 add)}

The first line is necessary to make sure that pos later refers to order in children array, not to e.g. a result of ([XYZ] each).

prev :: ?T_DIE ->? ?T_DIE

The opposite of next.

merge :: ?T_DIE ->* ?T_DIE

let merge := { ?DW_TAG_imported_unit @AT_import child+ };

A typical use would then be:

entry merge*		# if you want to see deref'd DIE's
entry merge* !(merge)	# if you care about the actual tree

XXX But that is fairly impractical, as one would have to use such operation after each DIE traversal (in particular after every (child) traversal). You actually rarely need the (entry merge*) thing above, because it already is the case that (entry) visits everyone. If you need anything at all, it is more likely to be:

entry !(merge)

… so that you filter out partial unit headers.

A better approach could be to have two types for DIE-related elements–raw and cooked DIE’s, raw and cooked CU’s, etc. Raw values don’t do attribute integration, don’t inline imported units and present attribute values as offsets instead of the referenced thing. Words (cooked) and (raw) switch between the two types. The default types would be “cooked”.

It’s not clear yet whether attributes that even raw DIE’s yield shouldn’t be cooked implicitly, as things like getting string or DIE offset instead of the thing itself seem rarely useful.Dwarf’s.

Cooked DIE’s will also have to keep track of the original DIE where the partial unit import took place, so that when you ask for (parent), you travel back to where you came from without trouble, as if the whole thing were one monolithic unit.

(unit) on cooked Dwarf would skip partial units. (entry) would descend through CU’s and include partial units this way, so that the logical tree is presented in its whole, and parent/child paths are preserved.

•@AT_decl_file

Value of this attribute is represented as actual string including path.

(XXX we ignore mtime and size. Those aren’t stored anyway, but maybe it would be useful to have them so that one can do this sort of querying in the first place–do we have any files where this is stored? Or after it gets to be stored in general, where this is not stored?)

•abbrev :: ?T_DIE -> ?T_ABBREV

Yield abbreviation associated with this DIE.

•name :: ?T_DIE -> ?T_STR

@AT_name

•@AT_* :: ?T_DIE ->* ?()

Syntactic sugar for (attribute ?(label == AT_*) value).

•?AT_* :: ?T_DIE

Holds if DIE has this attribute.

•?TAG_* :: ?T_DIE

Holds if DIE has this tag.

?LANG_* :: ?T_DIE

Holds if (@AT_language == DW_LANG_*).

?ATE_* :: ?T_DIE

Holds if @AT_encoding == DW_ATE_*.

?haschildren :: ?T_DIE

abbrev ?haschildren

Note that to find out whether a DIE actually does have children, one asks simply ?(child).

·T_ATTR

•label :: ?T_ATTR -> ?T_CONST

Yield an attribute name.

•value :: ?T_ATTR ->* ?()

Yields value(s) of attribute on TOS.

Some attributes refer to a location expression. These are represented as a number of nodes of type T_LOCLIST_ELEM. Children of these nodes are T_LOCLIST_OP, individual operations of location expression.

•address :: (?T_ATTR (?AT_high_pc||?AT_low_pc||?FORM_addr)) -> ?T_CONST

  • For DW_AT_high_pc, DW_AT_entry_pc with constant forms, this converts the value to address.
  • For attributes with address form, this is like calling “value”.
  • Otherwise it is an error to use this.

•form :: ?T_ATTR -> ?T_CONST

Yield a form of an attribute.

•die :: ?T_ATTR -> ?T_DIE

  • Yield a DIE that this attribute is associated with.

•unit :: ?T_ATTR -> ?T_CU

die unit

•?AT_* :: ?T_ATTR

Holds if it is this attribute.

•?FORM_* :: ?T_ATTR

Holds if the attribute has this form.

?LANG_* :: ?T_ATTR

Holds if (?AT_language value == DW_LANG_*).

?ATE_* :: ?T_ATTR

Holds if (?AT_encoding value == DW_ATE_*)

·T_ABBREV_UNIT

•offset :: ?T_ABBREV_UNIT -> ?T_CONST

  • Yield offset of this abbreviation unit.

•entry :: ?T_ABBREV_UNIT ->* ?T_ABBREV

  • Yield abbreviations defined in this unit. XXX this is currently done by elem.

•T_ABBREV

•label :: ?T_ABBREV -> ?T_CONST

•offset :: ?T_ABBREV -> ?T_CONST

•attribute :: ?T_ABBREV ->* ?T_ABBREV_ATTR

  • Yields all abbreviation attributes.

•code :: ?T_ABBREV -> ?T_CONST

  • Yields abbreviation code.

unit :: ?T_ABBREV -> ?T_ABBREV_UNIT

  • Yield an abbreviation unit that this abbreviation comes from.

•?TAG_* :: ?T_ABBREV

Holds if abbreviation has this tag.

•?AT_* :: ?T_ABBREV

Holds if abbreviation has this attribute.

•?haschildren :: ?T_ABBREV

  • Holds for abbreviations that form child-ful DIE’s.

•T_ABBREV_ATTR

•label :: ?T_ABBREV_ATTR -> ?T_CONST

Yields attribute name.

•offset :: ?T_ABBREV_ATTR -> ?T_CONST

Yields offset of attribute within abbreviation.

•form :: ?T_ABBREV_ATTR -> ?T_CONST

Yields attribute form.

•?AT_* :: ?T_ABBREV_ATTR

Holds if it is this attribute.

•?FORM_* :: ?T_ABBREV_ATTR

Holds if the attribute has this form.

T_ELFSYM

label :: ?T_ELFSYM -> ?T_CONST

Yields symbol type, such as STT_FUNC.

address :: ?T_ELFSYM -> ?T_ASET

Yield symbol value.

name :: ?T_ELFSYM -> ?T_STR

size :: ?T_ELFSYM -> ?T_CONST

bind :: ?T_ELFSYM -> ?T_CONST

vis :: ?T_ELFSYM -> ?T_CONST

index :: ?T_ELFSYM ->? ?T_CONST

XXX or maybe @section?

symbol :: ?T_CONST ->? T_ELFSYM

This find a symbol associated with an address on TOS. The match doesn’t have to be exact, offset would then be:

let A := some addr; A symbol dup address A sub
 # now TOS has offset and below TOS is symbol

XXX some of this is fairly easy to get by cross-matching like this:

symtab (address == some addr)

fuzzy matching (getting symbol/offset) would be more involved.

•T_LOCLIST_ELEM

•address :: ?T_LOCLIST_ELEM -> ?T_ASET

Yields where given location expression applies.

•elem :: ?T_LOCLIST_ELEM ->* ?T_LOCLIST_OP

Yields individual location expression operators.

•relem :: ?T_LOCLIST_ELEM ->* ?T_LOCLIST_OP

Like elem, but yields in opposite direction.

•?OP_* :: ?T_LOCLIST_ELEM

Holds if this location expression contains an operation with this opcode.

•T_LOCLIST_OP

•label :: ?T_LOCLIST_OP -> ?T_CONST

Yields operation opcode (a DW_OP_* constant).

•offset :: ?T_LOCLIST_OP -> ?T_CONST

Yields an offset of this op within the location expression.

•value :: ?T_LOCLIST_OP ->* ?()

Yields operands associated with this location expression operation. Operands have anywhere between zero and two operands of various types (some are e.g. T_DIE references).

For example:

[4e] variable
     [...]
     location (exprloc) [0x0..0xffffffffffffffff, [0:fbreg<-18>]]

Here we have only one T_LOCLIST_OP, and that has an offset of 0, a label of DW_OP_fbreg, and yields one value, -18.

•?OP_* :: ?T_LOCLIST_OP

Holds if this is an operation with this opcode.

•T_ASET

  • For holding a set of addresses.

•add :: ?T_ASET ?T_CONST -> ?T_ASET

•add :: ?T_ASET ?T_ASET -> ?T_ASET

•sub :: ?T_ASET ?T_CONST -> ?T_ASET

•sub :: ?T_ASET ?T_ASET -> ?T_ASET

•aset :: ?T_CONST ?T_CONST -> ?T_ASET

•range :: ?T_ASET ->* ?T_ASET

  • Extract continuous subranges of this aset and present them as individual asets.

•high :: ?T_ASET ->? ?T_CONST

Highest address set in this aset. Doesn’t yield at all if an aset is empty.

•low :: ?T_ASET ->? ?T_CONST

Lowest address set in this aset. Doesn’t yield at all if an aset is empty.

•length :: ?T_ASET -> ?T_CONST

[|A| address] length

•?empty :: ?T_ASET

!(elem)

•?overlaps :: ?T_ASET ?T_ASET

  • Holds if there is at least one value common to both osets.

•?contains :: ?T_ASET ?T_CONST

?(|A C| A elem (|E| (C >= E low) (C <= E high)))

•?contains :: ?T_ASET ?T_ASET

  • Holds if the lower aset contains all addresses of the TOS aset.
!(address !contains)

•overlap :: ?T_ASET ?T_ASET -> ?T_ASET

Compute aset that contains addresses common to both asets. Produces an empty aset if there’s no overlap.

•elem :: ?T_ASET ->* ?T_CONST

  • Actually enumerates all addresses in a range. Potentially a very bad idea for ranges that cover whole address space.

•relem :: ?T_ASET ->* ?T_CONST

[|A| A elem] relem

T_MACRO_UNIT ???

  • A value representing .debug_macro and .debug_macinfo units. Might be useful for DW_MACRO_GNU_transparent_include opcode, and for DW_AT_macro_info and DW_AT_GNU_macros attributes, which would hold this as a value.

entry :: ?T_MACRO_UNIT ->* ?T_MACRO_ENTRY

offset :: ?T_MACRO_UNIT ->* ?T_CONST

XXX :: something to get the prototype table???

T_MACRO_ENTRY

  • A value used for representing both .debug_macro and .debug_macinfo entries. Domain of entry label disambiguates which is which.

label :: ?T_MACRO_ENTRY -> ?T_CONST

  • Opcode of this macro entry.

attribute :: ?T_MACRO_ENTRY ->* ?T_MACRO_ATTRIBUTE

  • Yields value(s) associated with this opcode.
  • XXX could we somehow query a form?

merge :: ?T_MACRO_ENTRY ->* ?T_MACRO_ENTRY

let merge := { ?DW_MACRO_GNU_transparent_include value };

Should be used as with DIE’s, depending on what exactly is needed either (merge*) or (merge* !(merge)).

@0, @1, … or something???

  • Should this resemble DIE’s or T_LOCLIST_OP’s? Shouldn’t T_LOCLIST_OP’s actually resemble DIE’s as well? @X as a shorthand for (value (pos == X)) seems fairly natural.

    If X is a name instead of a number, it means:

    (attribute (label == X))
        

T_MACRO_ATTRIBUTE ???

  • Note that label is explicitly not applicable

value

form

Examples

Josh Stone’s nontrivial-parameters script

let A := entry ?TAG_subprogram !AT_declaration
         ?(@AT_decl_file (=~ "^/usr/") (!~ "^/usr/src/debug"));

:

let B := A child ?TAG_formal_parameter
         ?(@AT_type ((?TAG_const_type, ?TAG_volatile_type, ?TAG_typedef)
           @AT_type)* (?TAG_structure_type, ?TAG_class_type));

:

"%( A @AT_decl_file %): %( A @AT_decl_line %): note: in function "\
"`%( A @AT_name %)', parameter `%( B @AT_name %)' type is not trivial"

check_die_decl_call:

(entry ?AT_decl_column !AT_decl_line "%s has decl_column, but NOT decl_line"
, etc.)

check_die_line_info:

let A := entry (?TAG_subprogram, ?TAG_inlined_subroutine, ?TAG_entry_point,
               ?TAG_lexical_block, ?TAG_label, ?TAG_with_stmt,
               ?TAG_try_block, ?TAG_catch_block);
let B := A (@AT_entry_pc, address);
let C := A root address;
?(B C !overlaps || B C overlap != B)
"Address range %( B %) referenced from %( A %)"\
" not fully covered by line table."

contains DW_OP_fbreg && ! @frame_base

entry !AT_frame_base (@AT_location elem label == DW_OP_fbreg)

uses register based operators

entry ?(@AT_location elem label
        (== DW_OP_bregx || (>= DW_OP_reg0) (<= DW_OP_regx)))
entry ?(@AT_location elem label "%s" "_b?reg" ?find)

whether it uses a deref operator

entry ?(@AT_location elem label (?OP_deref, ?OP_xderef, ?OP_deref_size,
                                 ?OP_xderef_size, ?OP_GNU_deref_type))
entry ?(@AT_location elem label "%s" "deref" ?find)

contains DW_OP_call_frame_cfa && $@low_pc == 0

entry (@AT_low_pc == 0) ?(@AT_frame_base ?OP_call_frame_cfa)

is an empty location expression

entry ?(@AT_location !(elem))

location expression ends with implicit_value or stack_value

entry ?([@AT_location] relem (pos == 0) label
        (?DW_OP_implicit_value, ?DW_OP_stack_value))

types inconsistent between instance and specification

duplicat DW_TAG_const_type

  • http://gcc.gnu.org/bugzilla/show_bug.cgi?id=56740
    # Leaves on stack two DIE's that describe the same type.
    let A := entry (?TAG_const_type||?TAG_volatile_type||?TAG_restrict_type);
    A root child* (> A) (label == A label) (@AT_type == A @AT_type) A
        

find any attribute with value “blah”

attribute (value == "blah")

check_duplicate_DW_tag_variable.cc

Check for two full DW_TAG_variable DIEs with the same DW_AT_name value.

entry ?TAG_variable (@AT_name == next+ ?TAG_variable @AT_name)

check_linkage_external_die.cc

(|Dw|
 let GetSym := {|X| Dw symtab (?STT_OBJECT, ?STT_FUNC)
                    (@name == X @AT_linkage_name)};
 Dw entry ?AT_linkage_name
 if !(GetSym) then (
     !AT_declaration !AT_const_value
     (!structure_type !enumeration_type !union_type, ?AT_name)
     "%s has linkage_name attribute that's not in symtab, "\
     "but is not marked as declaration"
 ) else if (GetSym ?STB_LOCAL) then (
     !AT_declaration ?AT_external
     "%s has linkage_name attribute, "\
     "but the corresponding symbol is local"
 ) else (
     !AT_external
     "%s has linkage_name attribute, but no external attribute"
 ))

This is still 1:10 vs. dwarflint C++ (i.e., say 1:20 if we had to explore the DIE tree by hand), but fairly involved.

The interpreter would need to notice the ?symbol nodes are used as a sort of global variable for cross-referencing, otherwise this would lead to an ugly combinatorial explosion of states. Noticing that we look at the bottom slot and cross-reference @AT_name with @AT_linkage_name should be possible.

An alternative start would be something like:

let Dw := $1 dwopen;

CU A imports two PU’s B and C, and both import the same third PU

let imports := {root child ?TAG_imported_unit @AT_import};

:

let U := entry ?root ;
let A := U child ?TAG_imported_unit @AT_import ;
let B := U child ?TAG_imported_unit @AT_import (> A) ;
A imports B imports (== swap)
"PU %(offset%) is imported by PU's %(A offset%) and %(B offset%), "\
"which are both imported by %(U offset%)."

typedef resolution

  • dsmith asked for a way to get typedef “resolution” from DWARF (for use with the syscall types in the kernel). Which was timely since I was just thinking about having some “roundtripping” tests for GCC/elfutils DWARF types. So hacked up a dwfltypedef that prints all (C) typedefs found:

    $ ./dwfltypedef -e ./dwfltypedef [2d] typedef size_t long unsigned int (unsigned, 8 bytes); [70] typedef __off_t long int (signed, 8 bytes); [7b] typedef __off64_t long int (signed, 8 bytes); […]

    let T := entry ?TAG_typedef ;
    let U := T @AT_type (?TAG_typedef @AT_type)* !TAG_typedef ;
    "[%(T offset%)] typedef %(T @AT_name%) %(U @AT_name%) "\
    "(%( U @AT_encoding || "???" %), %( U @AT_byte_size || "???" %) bytes)"
        

    [0x57] typedef __int32_t int (DW_ATE_signed, 4 bytes) [0x70] typedef __off_t long int (DW_ATE_signed, 8 bytes) [0x7b] typedef __off64_t long int (DW_ATE_signed, 8 bytes) [0x9c] typedef __intptr_t long int (DW_ATE_signed, 8 bytes) [0xa7] typedef size_t long unsigned int (DW_ATE_unsigned, 8 bytes) […]

nested identifier names

(@AT_name == "a") child (@AT_name == "b") child (@AT_name == "c")

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages