Skip to content

Commit

Permalink
tiny "progress" on location type model
Browse files Browse the repository at this point in the history
Signed-off-by: vsoch <vsoch@users.noreply.github.com>
  • Loading branch information
vsoch committed Apr 26, 2021
1 parent 58fc69a commit f5f52d3
Show file tree
Hide file tree
Showing 5 changed files with 231 additions and 76 deletions.
2 changes: 1 addition & 1 deletion etc/spack/defaults/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ config:
#
# 'clingo' currently requires the clingo ASP solver to be installed and
# built with python bindings. 'original' is built in.
concretizer: standard
concretizer: clingo


# How long to wait to lock the Spack installation database. This lock is used
Expand Down
164 changes: 89 additions & 75 deletions lib/spack/spack/analyzers/elf/asp.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@
"subprogram", "formal_parameter", "function"}


# skip these tags (and all children)
skip_tags = {"dw_tag_subroutine_type", "subroutine_type"}

# These are what we want to call interfaces
interfaces = {"function", "subprogram"}


class ABIFactGenerator(object):
"""Class to set up and generate corpus ABI facts."""

Expand Down Expand Up @@ -80,7 +87,7 @@ def generate_elf_symbols(self, corpora, details=False):
# Additional elf metadata / details to generate
if details:
self.gen.fact(fn.symbol_type(corpus.basename, symbol, meta['type']))
self.gen.fact(fn.symbol_version(corpus.basename, symbol, vinfo))
# self.gen.fact(fn.symbol_version(corpus.basename, symbol, vinfo))
self.gen.fact(fn.symbol_binding(corpus.basename, symbol, bind))
self.gen.fact(fn.symbol_visibility(corpus.basename, symbol, vis))

Expand Down Expand Up @@ -208,12 +215,20 @@ def _parse_die_children(self, corpus, die, parent=None):
# Keep track of unique id (hash of attributes, parent, and corpus)
die.unique_id = self._die_hash(die, corpus, parent)

# Skip these tags (and children) all together
if tag in skip_tags:
return

# Create a top level entry for the die based on it's tag type
if tag in parse_tags:
self.gen.fact(AspFunction(tag, args=[corpus.basename, die.unique_id]))

# Don't parse formal parameters for skipped subroutine_type
if tag == "formal_parameter" and "subroutine_type" in die.get_parent().tag:
return

# Children are represented as facts
self._add_children(corpus, die)
# Disable this for now
# self._add_children(corpus, die)

# Add to the lookup
self.die_lookup[corpus.path][die.abbrev_code] = die
Expand All @@ -234,7 +249,7 @@ def _parse_die_children(self, corpus, die, parent=None):
for child in die.iter_children():
self._parse_die_children(corpus, child, parent)

def generate_location(self, corpus, die, tag):
def get_location(self, die):
"""Given a DW_AT_location parameter, parse it to get a location.
"""
location_lists = die.dwarfinfo.location_lists()
Expand All @@ -247,13 +262,9 @@ def generate_location(self, corpus, die, tag):

# Attribute itself contains location information
if isinstance(loc, et.locationlists.LocationExpr):
locat = et.dwarf.describe_DWARF_expr(loc.loc_expr,
die.dwarfinfo.structs,
die.cu.cu_offset)
args = [corpus.basename, die.unique_id, locat]
self.gen.fact(
AspFunction(tag + "_location", args=args)
)
return et.dwarf.describe_DWARF_expr(loc.loc_expr,
die.dwarfinfo.structs,
die.cu.cu_offset)

# Attribute is reference to .debug_loc section
elif isinstance(loc, list):
Expand All @@ -272,43 +283,62 @@ def _parse_common_attributes(self, corpus, die, tag):
if tag not in parse_tags:
return

if tag in interfaces:
self.gen.fact(fn.interface(corpus.basename, die.unique_id))
else:
self.gen.fact(AspFunction(tag, args=[corpus.basename, die.unique_id]))

# We must have a name or linkage name
name = None
loc = None
die_type = None
if "DW_AT_linkage_name" in die.attributes:
name = self.bytes2str(die.attributes["DW_AT_linkage_name"].value)

if "DW_AT_name" in die.attributes:
name = self.bytes2str(die.attributes["DW_AT_name"].value)
self.gen.fact(
AspFunction(tag + "_name", args=[corpus.basename, die.unique_id, name])
)

if not name:
print('UNKNOWN INTERFACE?')
import IPython
IPython.embed()

# Generate the declarations for interfaces
if tag in interfaces and name:
self.gen.fact(fn.interface(corpus.basename, die.unique_id, name))

# TODO need to parse pointers
# abi_typelocation('exe', 'main', 'char**', 'fb-32')
# // Probably not how we'll eventually represent pointers.

# DW_AT_type is a reference to another die (the type)
if "DW_AT_type" in die.attributes:
self._parse_die_type(corpus, die, tag)

# Not to be confused with "bite size" :)
if "DW_AT_byte_size" in die.attributes:
size_in_bits = die.attributes["DW_AT_byte_size"].value * 8
self.gen.fact(
AspFunction(
tag + "_size_in_bits",
args=[corpus.basename, die.unique_id, size_in_bits],
)
)

# The size this DIE occupies in the section (not sure about units)
if hasattr(die, "size"):
self.gen.fact(
AspFunction(
tag + "_die_size", args=[corpus.basename, die.unique_id, die.size]
)
)
die_type = self._get_die_type(die)

if "DW_AT_location" in die.attributes:
self.generate_location(corpus, die, tag)
loc = self.get_location(die)

if "DW_AT_linkage_name" in die.attributes:
name = self.bytes2str(die.attributes["DW_AT_linkage_name"].value)
args = [corpus.basename, die.unique_id, name]
self.gen.fact(AspFunction(tag + "_mangled_name", args=args))
# If it's a subprogram and we have a type, it's a return type!
# abi_typelocation('exe', 'main', 'int', 'return')
if tag == "function" and die_type:
cname = corpus.basename
self.gen.fact(fn.abi_typelocation(cname, name, die_type, "return"))

# function without a return type
elif tag == "function" and not die_type:
return

def _parse_die_type(self, corpus, die, tag, lookup_die=None):
elif not (loc and die_type):
print("MISSING LOC OR DIE TYPE")
print(die)
import IPython
IPython.embed()
import sys
sys.exit(0)
else:
self.gen.fact(fn.abi_typelocation(corpus.basename, name, die_type, loc))

def _get_die_type(self, die, lookup_die=None):
"""
Parse the die type.
Expand All @@ -327,14 +357,15 @@ def _parse_die_type(self, corpus, die, tag, lookup_die=None):
query_die = lookup_die or die

# CU relative offset

if query_die.attributes["DW_AT_type"].form.startswith("DW_FORM_ref"):
try:
type_die = query_die.cu.get_DIE_from_refaddr(
query_die.attributes["DW_AT_type"].value
)
except Exception:
pass

# DWARFError: refaddr 48991 not in DIE range of CU 66699
except et.exceptions.DWARFError:
return "ref-address-not-in-range"

# Absolute offset
elif query_die.attributes["DW_AT_type"].startswith("DW_FORM_ref_addr"):
Expand All @@ -348,30 +379,27 @@ def _parse_die_type(self, corpus, die, tag, lookup_die=None):
# have it's parent here at the moment
if type_die:

# Not sure how to parse this, call it const for now
if type_die.tag == 'DW_TAG_const_type':
return "const"

# Just call structures a type for now, and pointers
if type_die.tag == 'DW_TAG_structure_type':
return "structure"

if type_die.tag == "DW_TAG_pointer_type":
return "pointer"

# If we have another type def, call function again until we find it
if "DW_AT_type" in type_die.attributes:
return self._parse_die_type(corpus, die, tag, type_die)

# If it's a pointer, we have the byte size (no name)
if re.search(type_die.tag, "pointer_type"):
if "DW_AT_byte_size" in type_die.attributes:
size_in_bits = type_die.attributes["DW_AT_byte_size"].value * 8
self.gen.fact(
AspFunction(
tag + "_size_in_bits",
args=[corpus.basename, die.unique_id, size_in_bits],
)
)
return self._get_die_type(die, type_die)

# Not sure how to parse non complete types
# https://stackoverflow.com/questions/38225269/dwarf-reading-not-complete-types
elif "DW_AT_declaration" in type_die.attributes:
self.gen.fact(
AspFunction(
tag + "_non_complete_type",
args=[corpus.basename, die.unique_id, "yes"],
)
)
print('DW_AT_declaration')
import IPython
IPython.embed()

# Here we are supposed to walk member types and calc size with offsets
# For now let's assume we can just compare all child sizes
Expand All @@ -380,28 +408,14 @@ def _parse_die_type(self, corpus, die, tag, lookup_die=None):
return

else:
type_size = type_die.attributes["DW_AT_byte_size"].value * 8
self.gen.fact(
AspFunction(
tag + "_size_in_bits",
args=[corpus.basename, die.unique_id, type_size],
)
)
type_name = None
if "DW_AT_linkage_name" in type_die.attributes:
type_name = self.bytes2str(
type_die.attributes["DW_AT_linkage_name"].value
)
elif "DW_AT_name" in type_die.attributes:
type_name = self.bytes2str(type_die.attributes["DW_AT_name"].value)

if type_name:
self.gen.fact(
AspFunction(
tag + "_type_name",
args=[corpus.basename, die.unique_id, type_name],
)
)
return type_name

def _parse_compile_unit(self, corpus, die, tag):
"""
Expand Down
2 changes: 2 additions & 0 deletions lib/spack/spack/analyzers/elf/corpus.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ def __init__(self):
from elftools.dwarf import descriptions as dwarf
from elftools.dwarf import locationlists as locationlists
from elftools.common import py3compat as py3compat
from elftools.common import exceptions as exceptions
self.dynamic = dynamic
self.descriptions = descriptions
self.sections = sections
Expand All @@ -41,6 +42,7 @@ def __init__(self):
self.dwarf = dwarf
self.locationlists = locationlists
self.py3compat = py3compat
self.exceptions = exceptions


et = ElftoolsWrapper()
Expand Down
109 changes: 109 additions & 0 deletions lib/spack/spack/solver/logic/locationtype.lp
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
%==============================================================================
% The location type compatitiblity model looks at elf symbols, types, locations
%==============================================================================

%=============================================================================
% Matching function and variable symbols
% symbols are missing if they are needed (present in the working library),
% not undefined in the working library (indicating they come from elsewhere)
% and undefined in both the potential library and binary.
%=============================================================================

% These are symbols to skip checking (profiling and other)
skip_symbol("_ITM_deregisterTMCloneTable").
skip_symbol("__gmon_start__").
skip_symbol("_ITM_registerTMCloneTable").

%----------------------------------------------------------------------------
% Corpus symbols: libtcl8.6.so
%----------------------------------------------------------------------------

% model 1: base (spack solver)
% model 2: symbols
% model 3: libabigail
% model 4: type/location model "code centric model"
% model 5: inlined code "data centric model"

% something is a symbol if any binary has it
symbol :- has_symbol(_, Symbol).

% a package corpus needs a symbol if:
needs_symbol(Package, Version, Symbol, Corpus)

% it's not one flagged to skip
:- not skip_symbol(Symbol),

% the corpus has it
corpus(Package, Version, Corpus),

% the corpus has the symbol
has_symbol(Corpus, Symbol).


% A symbol is undefined for a package and corpus
% (a corpus is a binary/library provided by the package) if:
undefined_symbol(Package, Version, Symbol, Corpus)

% the package needs the symbol
:- needs_symbol(Package, Version, Symbol, Corpus),

% and it's definition is UND
symbol_definition(Corpus, Symbol, "UND").

% a symbol is undefined for a package version if
undefined_symbol(Package, Version, Symbol)

% it's undefined in any corpora
:- undefined_symbol(Package, Version, Symbol, _).


% A symbol is defined for a package version if:
defined_symbol(Package, Version, Symbol, Corpus, Definition)

% the package needs the symbol
:- needs_symbol(Package, Version, Symbol, Corpus),

% the corpus has a symbol definition
symbol_definition(Corpus, Symbol, Definition),

% and definition != UND
Definition != "UND".

% A symbol is defined for a package verison across all corpora if
defined_symbol(Package, Version, Symbol)

% it's defined for any corpora
:- defined_symbol(Package, Version, Symbol, _, _).


% a package and dependency are linkable if
linkable(Package, Dependency)

% The package and dependency are nodes in the graph
:- node(Package),
node(Dependency),

% And the dependency holds
dependency_holds(Package, Dependency, _).


% A package and dependency are incompatible if:
incompatible(Package, V1, Dependency, V2)

% they are linkable
:- linkable(Package, Dependency),

% The package and dependency each have their own version
version(Package, V1),
version(Dependency, V2),

% The package has an undefined symbol
undefined_symbol(Package, V1, Symbol),

% the package depends on the dependency
depends_on(Package, Dependency),

% and the symbol is not defined for the dependency
not defined_symbol(Dependency, V2, Symbol).

#show incompatible/4.

0 comments on commit f5f52d3

Please sign in to comment.