public inbox for systemtap@sourceware.org
 help / color / mirror / Atom feed
* [PATCH] Add new primitive @vma(address, module)
@ 2018-08-24 22:41 Yichun Zhang (agentzh)
  2018-09-05 20:56 ` [PATCH v2] Add new operator " Yichun Zhang (agentzh)
  0 siblings, 1 reply; 2+ messages in thread
From: Yichun Zhang (agentzh) @ 2018-08-24 22:41 UTC (permalink / raw)
  To: systemtap; +Cc: Yichun Zhang (agentzh)

Implemented new primitive @vma(address, module) and @vma(address) to
return absolute virtual memory (VM) addresses from relative addresses
in the specified (ELF) module (or the current module in the probe
handler context). For non-PIE program modules, the relative address is
the absolute address and @vma() simply returns the value of the first
argument as-is.

It just exposes the existing VMA tracker facility to the stap language
level.

This is useful for accessing static variables in target processes with
DWARF-less probing (where @var() is not usable). It is also useful to
probe the addresses of literal C strings residing in the .rodata section
of the ELF files which cannot be addressed via the DWARF info or the
symbol table.

The dyninst runtime does not support the VMA tracker yet, but we still
cover its error messages in our tests cases. So once it does support it,
we can know it immediately through new failures in these test cases.

The newly added tests rely on the `nm` utility to find the (relative)
addresses of symbols in the ELF files of our trivial example C prgrams,
just like some of the existing tests in the official test suite.
---
 dwflpp.cxx                             |   58 ++
 dwflpp.h                               |    2 +
 elaborate.cxx                          |   34 +
 elaborate.h                            |    1 +
 loc2stap.cxx                           |   54 +-
 parse.cxx                              |   42 ++
 staptree.cxx                           |   51 ++
 staptree.h                             |   16 +
 tapsets.cxx                            |  102 +++
 testsuite/systemtap.base/at_vma.exp    | 1146 ++++++++++++++++++++++++++++++++
 testsuite/systemtap.base/at_vma_1.c    |    5 +
 testsuite/systemtap.base/at_vma_1.stp  |    9 +
 testsuite/systemtap.base/at_vma_10.stp |    8 +
 testsuite/systemtap.base/at_vma_11.stp |    4 +
 testsuite/systemtap.base/at_vma_12.stp |   12 +
 testsuite/systemtap.base/at_vma_13.stp |   12 +
 testsuite/systemtap.base/at_vma_14.c   |    5 +
 testsuite/systemtap.base/at_vma_14.stp |    3 +
 testsuite/systemtap.base/at_vma_2.stp  |    8 +
 testsuite/systemtap.base/at_vma_3.stp  |    5 +
 testsuite/systemtap.base/at_vma_4.stp  |    5 +
 testsuite/systemtap.base/at_vma_5.stp  |    9 +
 testsuite/systemtap.base/at_vma_6.stp  |    5 +
 testsuite/systemtap.base/at_vma_7.stp  |    5 +
 testsuite/systemtap.base/at_vma_8.stp  |    4 +
 testsuite/systemtap.base/at_vma_9.stp  |    8 +
 translate.cxx                          |    8 +
 27 files changed, 1568 insertions(+), 53 deletions(-)
 create mode 100644 testsuite/systemtap.base/at_vma.exp
 create mode 100644 testsuite/systemtap.base/at_vma_1.c
 create mode 100644 testsuite/systemtap.base/at_vma_1.stp
 create mode 100644 testsuite/systemtap.base/at_vma_10.stp
 create mode 100644 testsuite/systemtap.base/at_vma_11.stp
 create mode 100644 testsuite/systemtap.base/at_vma_12.stp
 create mode 100644 testsuite/systemtap.base/at_vma_13.stp
 create mode 100644 testsuite/systemtap.base/at_vma_14.c
 create mode 100644 testsuite/systemtap.base/at_vma_14.stp
 create mode 100644 testsuite/systemtap.base/at_vma_2.stp
 create mode 100644 testsuite/systemtap.base/at_vma_3.stp
 create mode 100644 testsuite/systemtap.base/at_vma_4.stp
 create mode 100644 testsuite/systemtap.base/at_vma_5.stp
 create mode 100644 testsuite/systemtap.base/at_vma_6.stp
 create mode 100644 testsuite/systemtap.base/at_vma_7.stp
 create mode 100644 testsuite/systemtap.base/at_vma_8.stp
 create mode 100644 testsuite/systemtap.base/at_vma_9.stp

diff --git a/dwflpp.cxx b/dwflpp.cxx
index 2172e705a..a66fb99e7 100644
--- a/dwflpp.cxx
+++ b/dwflpp.cxx
@@ -3088,6 +3088,64 @@ dwflpp::pc_location_as_function_string(Dwarf_Addr pc)
   return locstr;
 }
 
+expression *
+dwflpp::translate_address(Dwarf_Addr addr, const token *tok)
+{
+  int n = dwfl_module_relocations (module);
+  Dwarf_Addr reloc_addr = addr;
+  const char *secname = "";
+
+  if (n > 1)
+    {
+      int i = dwfl_module_relocate_address (module, &reloc_addr);
+      secname = dwfl_module_relocation_info (module, i, NULL);
+    }
+
+  if (n > 0 && !(n == 1 && secname == NULL))
+    {
+      std::string c;
+
+      if (n > 1 || secname[0] != 0)
+	{
+	  // This gives us the module name and section name within the
+	  // module, for a kernel module.
+          c = "({ unsigned long addr = 0; "
+              "addr = _stp_kmodule_relocate (\""
+              + module_name + "\", \"" + secname + "\", "
+              + lex_cast_hex (reloc_addr)
+	      + "); addr; })";
+	}
+      else if (n == 1 && module_name == "kernel" && secname[0] == 0)
+	{
+	  // elfutils way of telling us that this is a relocatable kernel
+	  // address, which we need to treat the same way here as
+	  // dwarf_query::add_probe_point does.
+          c = "({ unsigned long addr = 0; "
+              "addr = _stp_kmodule_relocate (\"kernel\", \"_stext\", "
+              + lex_cast_hex (addr - sess.sym_stext)
+	      + "); addr; })";
+	}
+      else
+	{
+          c = "/* pragma:vma */ "
+              "({ unsigned long addr = 0; "
+              "addr = _stp_umodule_relocate (\""
+              + path_remove_sysroot(sess,
+				    resolve_path(module_name.c_str()))
+	      + "\", "
+              + lex_cast_hex (addr)
+	      + ", current); addr; })";
+	}
+
+      embedded_expr *r = new embedded_expr;
+      r->tok = tok;
+      r->code = c;
+      return r;
+    }
+  else
+    return new literal_number(addr);
+}
+
 struct location *
 dwflpp::translate_location(location_context *ctx,
 			   Dwarf_Attribute *attr, Dwarf_Die *die,
diff --git a/dwflpp.h b/dwflpp.h
index 998b96e7d..cde1d0d10 100644
--- a/dwflpp.h
+++ b/dwflpp.h
@@ -493,6 +493,8 @@ struct dwflpp
 
   Dwarf_Addr relocate_address(Dwarf_Addr addr, interned_string& reloc_section);
 
+  expression *translate_address(Dwarf_Addr addr, const token *tok);
+
   void resolve_unqualified_inner_typedie (Dwarf_Die *typedie,
                                           Dwarf_Die *innerdie,
                                           const target_symbol *e);
diff --git a/elaborate.cxx b/elaborate.cxx
index c55818f02..b72cda173 100644
--- a/elaborate.cxx
+++ b/elaborate.cxx
@@ -3267,6 +3267,11 @@ struct assignment_symbol_fetcher
     sym = NULL;
   }
 
+  void visit_atvma_op (atvma_op*)
+  {
+    sym = NULL;
+  }
+
   void visit_cast_op (cast_op*)
   {
     sym = NULL;
@@ -5589,6 +5594,7 @@ struct initial_typeresolution_info : public typeresolution_info
   // and not all substitutions are done, replace the functions that throw errors.
   void visit_target_symbol (target_symbol*) {}
   void visit_atvar_op (atvar_op*) {}
+  void visit_atvma_op (atvma_op*) {}
   void visit_defined_op (defined_op*) {}
   void visit_entry_op (entry_op*) {}
   void visit_cast_op (cast_op*) {}
@@ -6390,6 +6396,34 @@ typeresolution_info::visit_atvar_op (atvar_op* e)
 
 
 void
+typeresolution_info::visit_atvma_op (atvma_op* e)
+{
+  if (session.verbose > 2)
+    {
+      clog << _("Resolution problem with ");
+      if (current_function)
+        {
+          clog << "function " << current_function->name << endl;
+          current_function->body->print (clog);
+          clog << endl;
+        }
+      else if (current_probe)
+        {
+          clog << "probe " << *current_probe->sole_location() << endl;
+          current_probe->body->print (clog);
+          clog << endl;
+        }
+      else
+        //TRANSLATORS: simply saying not an issue with a probe or function
+        clog << _("other") << endl;
+    }
+
+  //clog << "expr: " << *e << endl;
+  throw SEMANTIC_ERROR(_("unresolved @vma() expression"), e->tok);
+}
+
+
+void
 typeresolution_info::visit_defined_op (defined_op* e)
 {
   // PR18079: if a @defined is still around, it may have a parameter that
diff --git a/elaborate.h b/elaborate.h
index 98754ece5..90ad94a6a 100644
--- a/elaborate.h
+++ b/elaborate.h
@@ -163,6 +163,7 @@ struct typeresolution_info: public visitor
   void visit_cast_op (cast_op* e);
   void visit_autocast_op (autocast_op* e);
   void visit_atvar_op (atvar_op* e);
+  void visit_atvma_op (atvma_op* e);
   void visit_defined_op (defined_op* e);
   void visit_entry_op (entry_op* e);
   void visit_perf_op (perf_op* e);
diff --git a/loc2stap.cxx b/loc2stap.cxx
index b73036c2f..5958e7ee0 100644
--- a/loc2stap.cxx
+++ b/loc2stap.cxx
@@ -68,59 +68,7 @@ location_context::translate_address(Dwarf_Addr addr)
   if (dw == NULL)
     return new literal_number(addr);
 
-  int n = dwfl_module_relocations (dw->module);
-  Dwarf_Addr reloc_addr = addr;
-  const char *secname = "";
-
-  if (n > 1)
-    {
-      int i = dwfl_module_relocate_address (dw->module, &reloc_addr);
-      secname = dwfl_module_relocation_info (dw->module, i, NULL);
-    }
-
-  if (n > 0 && !(n == 1 && secname == NULL))
-    {
-      std::string c;
-
-      if (n > 1 || secname[0] != 0)
-	{
-	  // This gives us the module name and section name within the
-	  // module, for a kernel module.
-          c = "({ unsigned long addr = 0; "
-              "addr = _stp_kmodule_relocate (\""
-              + dw->module_name + "\", \"" + secname + "\", "
-              + lex_cast_hex (reloc_addr)
-	      + "); addr; })";
-	}
-      else if (n == 1 && dw->module_name == "kernel" && secname[0] == 0)
-	{
-	  // elfutils way of telling us that this is a relocatable kernel
-	  // address, which we need to treat the same way here as
-	  // dwarf_query::add_probe_point does.
-          c = "({ unsigned long addr = 0; "
-              "addr = _stp_kmodule_relocate (\"kernel\", \"_stext\", "
-              + lex_cast_hex (addr - dw->sess.sym_stext)
-	      + "); addr; })";
-	}
-      else
-	{
-          c = "/* pragma:vma */ "
-              "({ unsigned long addr = 0; "
-              "addr = _stp_umodule_relocate (\""
-              + path_remove_sysroot(dw->sess,
-				    resolve_path(dw->module_name.c_str()))
-	      + "\", "
-              + lex_cast_hex (addr)
-	      + ", current); addr; })";
-	}
-
-      embedded_expr *r = new embedded_expr;
-      r->tok = e->tok;
-      r->code = c;
-      return r;
-    }
-  else
-    return new literal_number(addr);
+  return dw->translate_address(addr, e->tok);
 }
 
 location *
diff --git a/parse.cxx b/parse.cxx
index 751f56add..2fbcb623f 100644
--- a/parse.cxx
+++ b/parse.cxx
@@ -204,6 +204,7 @@ private: // nonterminals
   target_symbol *parse_target_symbol ();
   cast_op *parse_cast_op ();
   atvar_op *parse_atvar_op ();
+  atvma_op *parse_atvma_op ();
   expression* parse_entry_op (const token* t);
   expression* parse_defined_op (const token* t);
   expression* parse_const_op (const token* t);
@@ -3690,6 +3691,14 @@ parser::parse_dwarf_value ()
     expr = tsym = parse_cast_op ();
   else if (tok_is (t, tok_operator, "@var"))
     expr = tsym = parse_atvar_op ();
+  else if (tok_is (t, tok_operator, "@vma"))
+    {
+      expr = parse_atvma_op ();
+      if (addressof)
+	{
+	  throw PARSE_ERROR (_("cannot take address of @vma"), addrtok);
+	}
+    }
   else if (addressof && !input.has_version("2.6"))
     // '&' on old version only allowed specific target_symbol types
     throw PARSE_ERROR (_("expected @cast, @var or $var"));
@@ -4148,6 +4157,39 @@ atvar_op* parser::parse_atvar_op ()
 }
 
 
+// Parse a @vma.
+atvma_op* parser::parse_atvma_op ()
+{
+  const token* t = next ();
+  if (t->type == tok_operator && t->content == "@vma")
+    {
+      atvma_op *vop = new atvma_op;
+      int64_t address;
+      vop->tok = t;
+      expect_op ("(");
+      expect_number (address);
+      vop->address = (uint64_t) address;
+      if (peek_op (","))
+        {
+          swallow ();
+	  t = next ();
+	  if (!t || t->type != tok_string)
+	    throw PARSE_ERROR (_("expected string"));
+          vop->module = t->content;
+	  if (vop->module.find(":") != string::npos)
+	    throw PARSE_ERROR (_("@vma() cannot take multiple module values"),
+			       t);
+	  swallow ();
+        }
+      else
+        vop->module = "";
+      expect_op (")");
+      return vop;
+    }
+
+  throw PARSE_ERROR (_("expected @vma"));
+}
+
 // Parse a @defined().  Given head token has already been consumed.
 expression* parser::parse_defined_op (const token* t)
 {
diff --git a/staptree.cxx b/staptree.cxx
index d1193e336..275b89912 100644
--- a/staptree.cxx
+++ b/staptree.cxx
@@ -529,6 +529,15 @@ void atvar_op::print (ostream& o) const
 }
 
 
+void atvma_op::print (ostream& o) const
+{
+  o << "@vma(" << lex_cast_hex(address);
+  if (module.length() > 0)
+    o << ", " << lex_cast_qstring (module);
+  o << ')';
+}
+
+
 void cast_op::print (ostream& o) const
 {
   if (addressof)
@@ -1761,6 +1770,13 @@ atvar_op::visit (visitor* u)
 
 
 void
+atvma_op::visit (visitor* u)
+{
+  u->visit_atvma_op(this);
+}
+
+
+void
 defined_op::visit (visitor* u)
 {
   u->visit_defined_op(this);
@@ -2141,6 +2157,11 @@ traversing_visitor::visit_atvar_op (atvar_op* e)
 }
 
 void
+traversing_visitor::visit_atvma_op (atvma_op*)
+{
+}
+
+void
 traversing_visitor::visit_defined_op (defined_op* e)
 {
   e->operand->visit (this);
@@ -2403,6 +2424,13 @@ expression_visitor::visit_atvar_op (atvar_op* e)
 }
 
 void
+expression_visitor::visit_atvma_op (atvma_op* e)
+{
+  traversing_visitor::visit_atvma_op (e);
+  visit_expression (e);
+}
+
+void
 expression_visitor::visit_defined_op (defined_op* e)
 {
   traversing_visitor::visit_defined_op (e);
@@ -2682,6 +2710,11 @@ varuse_collecting_visitor::visit_atvar_op (atvar_op *e)
   functioncall_traversing_visitor::visit_atvar_op (e);
 }
 
+void
+varuse_collecting_visitor::visit_atvma_op (atvma_op *)
+{
+  // No need to call the base op, we've done everything here.
+}
 
 void
 varuse_collecting_visitor::visit_cast_op (cast_op *e)
@@ -3210,6 +3243,12 @@ throwing_visitor::visit_atvar_op (atvar_op* e)
 }
 
 void
+throwing_visitor::visit_atvma_op (atvma_op* e)
+{
+  throwone (e->tok);
+}
+
+void
 throwing_visitor::visit_cast_op (cast_op* e)
 {
   throwone (e->tok);
@@ -3551,6 +3590,12 @@ update_visitor::visit_atvar_op (atvar_op* e)
 }
 
 void
+update_visitor::visit_atvma_op (atvma_op* e)
+{
+  provide (e);
+}
+
+void
 update_visitor::visit_defined_op (defined_op* e)
 {
   replace (e->operand);
@@ -3845,6 +3890,12 @@ deep_copy_visitor::visit_atvar_op (atvar_op* e)
 }
 
 void
+deep_copy_visitor::visit_atvma_op (atvma_op* e)
+{
+  update_visitor::visit_atvma_op(new atvma_op(*e));
+}
+
+void
 deep_copy_visitor::visit_defined_op (defined_op* e)
 {
   update_visitor::visit_defined_op(new defined_op(*e));
diff --git a/staptree.h b/staptree.h
index 00c22f9f1..90c6bab52 100644
--- a/staptree.h
+++ b/staptree.h
@@ -420,6 +420,14 @@ struct atvar_op: public target_symbol
   void visit (visitor* u);
 };
 
+struct atvma_op: public expression
+{
+  interned_string module;
+  uint64_t address;
+  void print (std::ostream& o) const;
+  void visit (visitor* u);
+};
+
 struct defined_op: public expression
 {
   expression *operand;
@@ -967,6 +975,7 @@ struct visitor
   virtual void visit_cast_op (cast_op* e) = 0;
   virtual void visit_autocast_op (autocast_op* e) = 0;
   virtual void visit_atvar_op (atvar_op* e) = 0;
+  virtual void visit_atvma_op (atvma_op* e) = 0;
   virtual void visit_defined_op (defined_op* e) = 0;
   virtual void visit_entry_op (entry_op* e) = 0;
   virtual void visit_perf_op (perf_op* e) = 0;
@@ -1020,6 +1029,7 @@ struct nop_visitor: public visitor
   virtual void visit_cast_op (cast_op*) {};
   virtual void visit_autocast_op (autocast_op*) {};
   virtual void visit_atvar_op (atvar_op*) {};
+  virtual void visit_atvma_op (atvma_op*) {};
   virtual void visit_defined_op (defined_op*) {};
   virtual void visit_entry_op (entry_op*) {};
   virtual void visit_perf_op (perf_op*) {};
@@ -1073,6 +1083,7 @@ struct traversing_visitor: public visitor
   void visit_cast_op (cast_op* e);
   void visit_autocast_op (autocast_op* e);
   void visit_atvar_op (atvar_op* e);
+  void visit_atvma_op (atvma_op* e);
   void visit_defined_op (defined_op* e);
   void visit_entry_op (entry_op* e);
   void visit_perf_op (perf_op* e);
@@ -1113,6 +1124,7 @@ struct expression_visitor: public traversing_visitor
   void visit_cast_op (cast_op* e);
   void visit_autocast_op (autocast_op* e);
   void visit_atvar_op (atvar_op* e);
+  void visit_atvma_op (atvma_op* e);
   void visit_defined_op (defined_op* e);
   void visit_entry_op (entry_op* e);
   void visit_perf_op (perf_op* e);
@@ -1175,6 +1187,7 @@ struct varuse_collecting_visitor: public functioncall_traversing_visitor
   void visit_cast_op (cast_op* e);
   void visit_autocast_op (autocast_op* e);
   void visit_atvar_op (atvar_op *e);
+  void visit_atvma_op (atvma_op* e);
   void visit_defined_op (defined_op* e);
   void visit_entry_op (entry_op* e);
   void visit_perf_op (perf_op* e);
@@ -1236,6 +1249,7 @@ struct throwing_visitor: public visitor
   void visit_cast_op (cast_op* e);
   void visit_autocast_op (autocast_op* e);
   void visit_atvar_op (atvar_op* e);
+  void visit_atvma_op (atvma_op* e);
   void visit_defined_op (defined_op* e);
   void visit_entry_op (entry_op* e);
   void visit_perf_op (perf_op* e);
@@ -1350,6 +1364,7 @@ struct update_visitor: public visitor
   virtual void visit_cast_op (cast_op* e);
   virtual void visit_autocast_op (autocast_op* e);
   virtual void visit_atvar_op (atvar_op* e);
+  virtual void visit_atvma_op (atvma_op* e);
   virtual void visit_defined_op (defined_op* e);
   virtual void visit_entry_op (entry_op* e);
   virtual void visit_perf_op (perf_op* e);
@@ -1418,6 +1433,7 @@ struct deep_copy_visitor: public update_visitor
   virtual void visit_cast_op (cast_op* e);
   virtual void visit_autocast_op (autocast_op* e);
   virtual void visit_atvar_op (atvar_op* e);
+  virtual void visit_atvma_op (atvma_op* e);
   virtual void visit_defined_op (defined_op* e);
   virtual void visit_entry_op (entry_op* e);
   virtual void visit_perf_op (perf_op* e);
diff --git a/tapsets.cxx b/tapsets.cxx
index ae6cfd83f..12b4e3a85 100644
--- a/tapsets.cxx
+++ b/tapsets.cxx
@@ -2857,6 +2857,7 @@ struct dwarf_var_expanding_visitor: public var_expanding_visitor
   void visit_target_symbol_context (target_symbol* e);
   void visit_target_symbol (target_symbol* e);
   void visit_atvar_op (atvar_op* e);
+  void visit_atvma_op (atvma_op* e);
   void visit_cast_op (cast_op* e);
   void visit_entry_op (entry_op* e);
   void visit_perf_op (perf_op* e);
@@ -4437,6 +4438,17 @@ dwarf_var_expanding_visitor::visit_atvar_op (atvar_op *e)
 
 
 void
+dwarf_var_expanding_visitor::visit_atvma_op (atvma_op *e)
+{
+  // Fill in our current module context if needed
+  if (e->module.empty())
+    e->module = q.dw.module_name;
+
+  var_expanding_visitor::visit_atvma_op(e);
+}
+
+
+void
 dwarf_var_expanding_visitor::visit_target_symbol (target_symbol *e)
 {
   assert(e->name.size() > 0 && (e->name[0] == '$' || e->name == "@var"));
@@ -5137,6 +5149,93 @@ dwarf_atvar_expanding_visitor::visit_atvar_op (atvar_op* e)
 }
 
 
+struct dwarf_atvma_expanding_visitor: public var_expanding_visitor
+{
+  dwarf_builder& db;
+
+  dwarf_atvma_expanding_visitor(systemtap_session& s, dwarf_builder& db):
+    var_expanding_visitor(s), db(db) {}
+  void visit_atvma_op (atvma_op* e);
+};
+
+
+struct dwarf_atvma_query: public base_query
+{
+  atvma_op& e;
+  const bool userspace_p;
+  bool &found;
+
+  dwarf_atvma_query(dwflpp& dw, const string& module, atvma_op& e,
+                    const bool userspace_p, bool &found):
+    base_query(dw, module), e(e),
+    userspace_p(userspace_p), found(found) {}
+
+  void handle_query_module ();
+  void query_library (const char *) {}
+  void query_plt (const char *, size_t) {}
+};
+
+
+void
+dwarf_atvma_query::handle_query_module ()
+{
+  found = true;
+}
+
+
+void
+dwarf_atvma_expanding_visitor::visit_atvma_op (atvma_op* e)
+{
+  const bool lvalue = is_active_lvalue(e);
+  if (lvalue)
+    throw SEMANTIC_ERROR(_("writing to @vma variable not permitted"), e->tok);
+
+  if (e->module.empty())
+    e->module = "kernel";
+
+  bool found_module = false;
+
+  bool userspace_p = false;
+
+  // @vma() does not allow multiple modules separated by ':'.
+  // we check this in the parser.
+  string module = e->module;
+
+  dwflpp* dw;
+  userspace_p = is_user_module(module);
+  if (!userspace_p)
+    {
+      // kernel or kernel module target
+      dw = db.get_kern_dw(sess, module);
+    }
+  else
+    {
+      module = find_executable(module, "", sess.sysenv);
+      dw = db.get_user_dw(sess, module);
+    }
+
+  dwarf_atvma_query q (*dw, module, *e, userspace_p, found_module);
+  dw->iterate_over_modules<base_query>(&query_module, &q);
+
+  if (found_module)
+    {
+      sess.unwindsym_modules.insert(module);
+
+      expression* result = dw->translate_address(e->address, e->tok);
+
+      if (result)
+	{
+	  result->visit(this);
+	  return;
+	}
+    }
+
+  throw SEMANTIC_ERROR (_F("unable to find module '%s'",  module.c_str()),
+			e->tok);
+  provide(e);
+}
+
+
 void
 dwarf_derived_probe::printsig (ostream& o) const
 {
@@ -5835,6 +5934,9 @@ dwarf_derived_probe::register_patterns(systemtap_session& s)
   filter = new dwarf_atvar_expanding_visitor(s, *dw);
   s.code_filters.push_back(filter);
 
+  filter = new dwarf_atvma_expanding_visitor(s, *dw);
+  s.code_filters.push_back(filter);
+
   register_function_and_statement_variants(s, root->bind(TOK_KERNEL), dw, pr_privileged);
   register_function_and_statement_variants(s, root->bind_str(TOK_MODULE), dw, pr_privileged);
   root->bind(TOK_KERNEL)->bind_num(TOK_STATEMENT)->bind(TOK_ABSOLUTE)
diff --git a/testsuite/systemtap.base/at_vma.exp b/testsuite/systemtap.base/at_vma.exp
new file mode 100644
index 000000000..89ecd09aa
--- /dev/null
+++ b/testsuite/systemtap.base/at_vma.exp
@@ -0,0 +1,1146 @@
+set test "at_vma"
+set testpath "$srcdir/$subdir"
+
+if {! [installtest_p]} { untested "$test"; return }
+if {! [uretprobes_p]} { untested "$test"; return }
+
+# --- TEST 1 ---
+
+set subtest1 "TEST 1: @vma - not PIE - in func - explicit module"
+
+set res [target_compile ${testpath}/${test}_1.c ./a.out executable \
+    "additional_flags=-O additional_flags=-g"]
+if {$res ne ""} {
+    verbose "target_compile failed: $res" 2
+    fail "$test: $subtest1: unable to compile ${test}_1.c"
+} else {
+    set stp_fh [open "$srcdir/$subdir/${test}_1.stp" r]
+    set stp_src [read $stp_fh]
+    close $stp_fh
+
+    set cwd [pwd]
+    regsub -all -- {\$\^PWD\y} $stp_src $cwd stp_src
+
+    set nm_fh [open "| nm --defined-only ./a.out" r]
+    set found 0
+    while {[gets $nm_fh line] >= 0} {
+        if {[regexp -- {^\s*([0-9a-f]+)\s+[DT]\s+v\s*$} $line match addr]} {
+            send_log "found symbol 'v' at addr $addr in the nm output\n"
+            regsub -all -- {\$\^ADDR_v\y} $stp_src $addr stp_src
+            set found 1
+            break
+        }
+    }
+
+    if {[catch {close $nm_fh} nm_err] != 0} {
+        fail "$test: $subtest1: failed to run nm: $nm_err"
+        return
+    }
+
+    if {! $found} {
+        fail "$test: $subtest1: symbol 'v' not found in executable ./a.out"
+        return
+    }
+    set stp_fh [open "./${test}_1.stp" w+]
+    puts -nonewline $stp_fh $stp_src
+    close $stp_fh
+
+    foreach runtime [get_runtime_list] {
+        if {$runtime eq ""} {
+            set runtime "kernel"
+        }
+        set test_name "$test: $subtest1 ($runtime)"
+
+        set cmd "stap --runtime=$runtime -c ./a.out './${test}_1.stp'"
+        send_log "executing: $cmd\n"
+        set pipe [open "| sh -c {$cmd}" r]
+        set out [read $pipe]
+        set failed 0
+        if {[catch {close $pipe} stderr] != 0} {
+            set failed 1
+        }
+
+        set exp_out "0xbeefdead\n1\n"
+        regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+        if {$out eq $exp_out} {
+            pass "${test_name}: stdout matches \"$escaped_exp_out\""
+        } else {
+            fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+        }
+
+        if {$failed} {
+            fail "${test_name}: exit code not zero"
+        } else {
+            pass "${test_name}: exit code is zero"
+        }
+        if {$stderr ne ""} {
+            send_log "stderr:\n$stderr"
+        }
+    }
+}
+
+# --- TEST 2 ---
+
+set subtest2 "TEST 2: @vma - not PIE - in func - no module"
+
+set res [target_compile ${testpath}/${test}_1.c ./a.out executable \
+    "additional_flags=-O additional_flags=-g"]
+if {$res ne ""} {
+    verbose "target_compile failed: $res" 2
+    fail "$test: $subtest2: unable to compile ${test}_1.c"
+} else {
+    set stp_fh [open "$srcdir/$subdir/${test}_2.stp" r]
+    set stp_src [read $stp_fh]
+    close $stp_fh
+
+    set nm_fh [open "| nm --defined-only ./a.out" r]
+    set found 0
+    while {[gets $nm_fh line] >= 0} {
+        if {[regexp -- {^\s*([0-9a-f]+)\s+[DT]\s+v\s*$} $line match addr]} {
+            send_log "found symbol 'v' at addr $addr in the nm output\n"
+            regsub -all -- {\$\^ADDR_v\y} $stp_src $addr stp_src
+            set found 1
+            break
+        }
+    }
+
+    if {[catch {close $nm_fh} nm_err] != 0} {
+        fail "$test: $subtest2: failed to run nm: $nm_err"
+        return
+    }
+
+    if {! $found} {
+        fail "$test: $subtest2: symbol 'v' not found in executable ./a.out"
+        return
+    }
+    set stp_fh [open "./${test}_2.stp" w+]
+    puts -nonewline $stp_fh $stp_src
+    close $stp_fh
+
+    set test_name "$test: $subtest2"
+
+    set cmd "stap -c ./a.out './${test}_2.stp'"
+    send_log "executing: $cmd\n"
+    set pipe [open "| sh -c {$cmd}" r]
+    set out [read $pipe]
+    set failed 0
+    if {[catch {close $pipe} stderr] != 0} {
+        set failed 1
+    }
+
+    set exp_out "0x0\n"
+    regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+    if {$out eq $exp_out} {
+        pass "${test_name}: stdout matches \"$escaped_exp_out\""
+    } else {
+        fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+    }
+
+    if {$failed} {
+        fail "${test_name}: exit code not zero"
+    } else {
+        pass "${test_name}: exit code is zero"
+    }
+    if {$stderr ne ""} {
+        send_log "stderr:\n$stderr"
+    }
+}
+
+# --- TEST 3 ---
+
+set subtest3 "TEST 3: @vma - not PIE - in probe - explicit module"
+
+set res [target_compile ${testpath}/${test}_1.c ./a.out executable \
+    "additional_flags=-O additional_flags=-g"]
+if {$res ne ""} {
+    verbose "target_compile failed: $res" 2
+    fail "$test: $subtest3: unable to compile ${test}_1.c"
+} else {
+    set stp_fh [open "$srcdir/$subdir/${test}_3.stp" r]
+    set stp_src [read $stp_fh]
+    close $stp_fh
+
+    set cwd [pwd]
+    regsub -all -- {\$\^PWD\y} $stp_src $cwd stp_src
+
+    set nm_fh [open "| nm --defined-only ./a.out" r]
+    set found 0
+    while {[gets $nm_fh line] >= 0} {
+        if {[regexp -- {^\s*([0-9a-f]+)\s+[DT]\s+v\s*$} $line match addr]} {
+            send_log "found symbol 'v' at addr $addr in the nm output\n"
+            regsub -all -- {\$\^ADDR_v\y} $stp_src $addr stp_src
+            set found 1
+            break
+        }
+    }
+
+    if {[catch {close $nm_fh} nm_err] != 0} {
+        fail "$test: $subtest3: failed to run nm: $nm_err"
+        return
+    }
+
+    if {! $found} {
+        fail "$test: $subtest3: symbol 'v' not found in executable ./a.out"
+        return
+    }
+    set stp_fh [open "./${test}_3.stp" w+]
+    puts -nonewline $stp_fh $stp_src
+    close $stp_fh
+
+    foreach runtime [get_runtime_list] {
+        if {$runtime eq ""} {
+            set runtime "kernel"
+        }
+        set test_name "$test: $subtest3 ($runtime)"
+
+        set cmd "stap --runtime=$runtime -c ./a.out './${test}_3.stp'"
+        send_log "executing: $cmd\n"
+        set pipe [open "| sh -c {$cmd}" r]
+        set out [read $pipe]
+        set failed 0
+        if {[catch {close $pipe} stderr] != 0} {
+            set failed 1
+        }
+
+        set exp_out "0xbeefdead\n1\n"
+        regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+        if {$out eq $exp_out} {
+            pass "${test_name}: stdout matches \"$escaped_exp_out\""
+        } else {
+            fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+        }
+
+        if {$failed} {
+            fail "${test_name}: exit code not zero"
+        } else {
+            pass "${test_name}: exit code is zero"
+        }
+        if {$stderr ne ""} {
+            send_log "stderr:\n$stderr"
+        }
+    }
+}
+
+# --- TEST 4 ---
+
+set subtest4 "TEST 4: @vma - not PIE - in probe - no module arg"
+
+set res [target_compile ${testpath}/${test}_1.c ./a.out executable \
+    "additional_flags=-O additional_flags=-g"]
+if {$res ne ""} {
+    verbose "target_compile failed: $res" 2
+    fail "$test: $subtest4: unable to compile ${test}_1.c"
+} else {
+    set stp_fh [open "$srcdir/$subdir/${test}_4.stp" r]
+    set stp_src [read $stp_fh]
+    close $stp_fh
+
+    set cwd [pwd]
+    regsub -all -- {\$\^PWD\y} $stp_src $cwd stp_src
+
+    set nm_fh [open "| nm --defined-only ./a.out" r]
+    set found 0
+    while {[gets $nm_fh line] >= 0} {
+        if {[regexp -- {^\s*([0-9a-f]+)\s+[DT]\s+v\s*$} $line match addr]} {
+            send_log "found symbol 'v' at addr $addr in the nm output\n"
+            regsub -all -- {\$\^ADDR_v\y} $stp_src $addr stp_src
+            set found 1
+            break
+        }
+    }
+
+    if {[catch {close $nm_fh} nm_err] != 0} {
+        fail "$test: $subtest4: failed to run nm: $nm_err"
+        return
+    }
+
+    if {! $found} {
+        fail "$test: $subtest4: symbol 'v' not found in executable ./a.out"
+        return
+    }
+    set stp_fh [open "./${test}_4.stp" w+]
+    puts -nonewline $stp_fh $stp_src
+    close $stp_fh
+
+    foreach runtime [get_runtime_list] {
+        if {$runtime eq ""} {
+            set runtime "kernel"
+        }
+        set test_name "$test: $subtest4 ($runtime)"
+
+        set cmd "stap --runtime=$runtime -c ./a.out './${test}_4.stp'"
+        send_log "executing: $cmd\n"
+        set pipe [open "| sh -c {$cmd}" r]
+        set out [read $pipe]
+        set failed 0
+        if {[catch {close $pipe} stderr] != 0} {
+            set failed 1
+        }
+
+        set exp_out "0xbeefdead\n1\n"
+        regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+        if {$out eq $exp_out} {
+            pass "${test_name}: stdout matches \"$escaped_exp_out\""
+        } else {
+            fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+        }
+
+        if {$failed} {
+            fail "${test_name}: exit code not zero"
+        } else {
+            pass "${test_name}: exit code is zero"
+        }
+        if {$stderr ne ""} {
+            send_log "stderr:\n$stderr"
+        }
+    }
+}
+
+# --- TEST 5 ---
+
+set subtest5 "TEST 5: @vma - PIE - in func - explicit module"
+
+set res [target_compile ${testpath}/${test}_1.c ./a.out executable \
+    "additional_flags=-O additional_flags=-g additional_flags=-fpic additional_flags=-pie"]
+if {$res ne ""} {
+    verbose "target_compile failed: $res" 2
+    fail "$test: $subtest5: unable to compile ${test}_1.c"
+} else {
+    set stp_fh [open "$srcdir/$subdir/${test}_5.stp" r]
+    set stp_src [read $stp_fh]
+    close $stp_fh
+
+    set cwd [pwd]
+    regsub -all -- {\$\^PWD\y} $stp_src $cwd stp_src
+
+    set nm_fh [open "| nm --defined-only ./a.out" r]
+    set found 0
+    while {[gets $nm_fh line] >= 0} {
+        if {[regexp -- {^\s*([0-9a-f]+)\s+[DT]\s+v\s*$} $line match addr]} {
+            send_log "found symbol 'v' at addr $addr in the nm output\n"
+            regsub -all -- {\$\^ADDR_v\y} $stp_src $addr stp_src
+            set found 1
+            break
+        }
+    }
+
+    if {[catch {close $nm_fh} nm_err] != 0} {
+        fail "$test: $subtest5: failed to run nm: $nm_err"
+        return
+    }
+
+    if {! $found} {
+        fail "$test: $subtest5: symbol 'v' not found in executable ./a.out"
+        return
+    }
+    set stp_fh [open "./${test}_5.stp" w+]
+    puts -nonewline $stp_fh $stp_src
+    close $stp_fh
+
+    foreach runtime [get_runtime_list] {
+        if {$runtime eq ""} {
+            set runtime "kernel"
+        }
+        set test_name "$test: $subtest5 ($runtime)"
+
+        set cmd "stap --runtime=$runtime -c ./a.out './${test}_5.stp'"
+        send_log "executing: $cmd\n"
+        set pipe [open "| sh -c {$cmd}" r]
+        set out [read $pipe]
+        set failed 0
+        if {[catch {close $pipe} stderr] != 0} {
+            set failed 1
+        }
+
+        if {$runtime eq "dyninst"} {
+            set exp_out ""
+            regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+            if {$out eq $exp_out} {
+                pass "${test_name}: stdout matches \"$escaped_exp_out\""
+            } else {
+                fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+            }
+        } else {
+            set exp_out "0\n0xbeefdead\n"
+            regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+            if {$out eq $exp_out} {
+                pass "${test_name}: stdout matches \"$escaped_exp_out\""
+            } else {
+                fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+            }
+        }
+        if {$runtime eq "dyninst"} {
+
+            if {$failed} {
+                pass "${test_name}: exit code should be non-zero"
+            } else {
+                fail "${test_name}: exit code should be non-zero but is zero"
+            }
+
+            set stderr_pat "semantic error: VMA-tracking is only supported by the kernel runtime \\(PR15052\\): operator '\\@vma' at .*?\\.stp:2:20"
+            regsub -all -- {\n} $stderr_pat {\n} escaped_stderr_pat
+            if {[regexp -linestop -- $stderr_pat $stderr]} {
+                pass "${test_name}: stderr matches \"$escaped_stderr_pat\""
+            } else {
+                fail "${test_name}: stderr fails to match \"$escaped_stderr_pat\": got \"$stderr\""
+            }
+
+        } else {
+            if {$failed} {
+                fail "${test_name}: exit code not zero"
+            } else {
+                pass "${test_name}: exit code is zero"
+            }
+        }
+    }
+}
+
+# --- TEST 6 ---
+
+set subtest6 "TEST 6: @vma - PIE - in probe - explicit module"
+
+set res [target_compile ${testpath}/${test}_1.c ./a.out executable \
+    "additional_flags=-O additional_flags=-g additional_flags=-fpic additional_flags=-pie"]
+if {$res ne ""} {
+    verbose "target_compile failed: $res" 2
+    fail "$test: $subtest6: unable to compile ${test}_1.c"
+} else {
+    set stp_fh [open "$srcdir/$subdir/${test}_6.stp" r]
+    set stp_src [read $stp_fh]
+    close $stp_fh
+
+    set cwd [pwd]
+    regsub -all -- {\$\^PWD\y} $stp_src $cwd stp_src
+
+    set nm_fh [open "| nm --defined-only ./a.out" r]
+    set found 0
+    while {[gets $nm_fh line] >= 0} {
+        if {[regexp -- {^\s*([0-9a-f]+)\s+[DT]\s+v\s*$} $line match addr]} {
+            send_log "found symbol 'v' at addr $addr in the nm output\n"
+            regsub -all -- {\$\^ADDR_v\y} $stp_src $addr stp_src
+            set found 1
+            break
+        }
+    }
+
+    if {[catch {close $nm_fh} nm_err] != 0} {
+        fail "$test: $subtest6: failed to run nm: $nm_err"
+        return
+    }
+
+    if {! $found} {
+        fail "$test: $subtest6: symbol 'v' not found in executable ./a.out"
+        return
+    }
+    set stp_fh [open "./${test}_6.stp" w+]
+    puts -nonewline $stp_fh $stp_src
+    close $stp_fh
+
+    foreach runtime [get_runtime_list] {
+        if {$runtime eq ""} {
+            set runtime "kernel"
+        }
+        set test_name "$test: $subtest6 ($runtime)"
+
+        set cmd "stap --runtime=$runtime -c ./a.out './${test}_6.stp'"
+        send_log "executing: $cmd\n"
+        set pipe [open "| sh -c {$cmd}" r]
+        set out [read $pipe]
+        set failed 0
+        if {[catch {close $pipe} stderr] != 0} {
+            set failed 1
+        }
+
+        if {$runtime eq "dyninst"} {
+            set exp_out ""
+            regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+            if {$out eq $exp_out} {
+                pass "${test_name}: stdout matches \"$escaped_exp_out\""
+            } else {
+                fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+            }
+        } else {
+            set exp_out "0xbeefdead\n0\n"
+            regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+            if {$out eq $exp_out} {
+                pass "${test_name}: stdout matches \"$escaped_exp_out\""
+            } else {
+                fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+            }
+        }
+        if {$runtime eq "dyninst"} {
+
+            if {$failed} {
+                pass "${test_name}: exit code should be non-zero"
+            } else {
+                fail "${test_name}: exit code should be non-zero but is zero"
+            }
+
+            set stderr_pat "semantic error: VMA-tracking is only supported by the kernel runtime \\(PR15052\\): operator '\\@vma' at .*?\\.stp:2:31"
+            regsub -all -- {\n} $stderr_pat {\n} escaped_stderr_pat
+            if {[regexp -linestop -- $stderr_pat $stderr]} {
+                pass "${test_name}: stderr matches \"$escaped_stderr_pat\""
+            } else {
+                fail "${test_name}: stderr fails to match \"$escaped_stderr_pat\": got \"$stderr\""
+            }
+
+        } else {
+            if {$failed} {
+                fail "${test_name}: exit code not zero"
+            } else {
+                pass "${test_name}: exit code is zero"
+            }
+        }
+    }
+}
+
+# --- TEST 7 ---
+
+set subtest7 "TEST 7: @vma - PIE - in probe - no module arg"
+
+set res [target_compile ${testpath}/${test}_1.c ./a.out executable \
+    "additional_flags=-O additional_flags=-g additional_flags=-fpic additional_flags=-pie"]
+if {$res ne ""} {
+    verbose "target_compile failed: $res" 2
+    fail "$test: $subtest7: unable to compile ${test}_1.c"
+} else {
+    set stp_fh [open "$srcdir/$subdir/${test}_7.stp" r]
+    set stp_src [read $stp_fh]
+    close $stp_fh
+
+    set nm_fh [open "| nm --defined-only ./a.out" r]
+    set found 0
+    while {[gets $nm_fh line] >= 0} {
+        if {[regexp -- {^\s*([0-9a-f]+)\s+[DT]\s+v\s*$} $line match addr]} {
+            send_log "found symbol 'v' at addr $addr in the nm output\n"
+            regsub -all -- {\$\^ADDR_v\y} $stp_src $addr stp_src
+            set found 1
+            break
+        }
+    }
+
+    if {[catch {close $nm_fh} nm_err] != 0} {
+        fail "$test: $subtest7: failed to run nm: $nm_err"
+        return
+    }
+
+    if {! $found} {
+        fail "$test: $subtest7: symbol 'v' not found in executable ./a.out"
+        return
+    }
+    set stp_fh [open "./${test}_7.stp" w+]
+    puts -nonewline $stp_fh $stp_src
+    close $stp_fh
+
+    foreach runtime [get_runtime_list] {
+        if {$runtime eq ""} {
+            set runtime "kernel"
+        }
+        set test_name "$test: $subtest7 ($runtime)"
+
+        set cmd "stap --runtime=$runtime -c ./a.out './${test}_7.stp'"
+        send_log "executing: $cmd\n"
+        set pipe [open "| sh -c {$cmd}" r]
+        set out [read $pipe]
+        set failed 0
+        if {[catch {close $pipe} stderr] != 0} {
+            set failed 1
+        }
+
+        if {$runtime eq "dyninst"} {
+            set exp_out ""
+            regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+            if {$out eq $exp_out} {
+                pass "${test_name}: stdout matches \"$escaped_exp_out\""
+            } else {
+                fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+            }
+        } else {
+            set exp_out "0xbeefdead\n0\n"
+            regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+            if {$out eq $exp_out} {
+                pass "${test_name}: stdout matches \"$escaped_exp_out\""
+            } else {
+                fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+            }
+        }
+        if {$runtime eq "dyninst"} {
+
+            if {$failed} {
+                pass "${test_name}: exit code should be non-zero"
+            } else {
+                fail "${test_name}: exit code should be non-zero but is zero"
+            }
+
+            set stderr_pat "semantic error: VMA-tracking is only supported by the kernel runtime \\(PR15052\\): operator '\\@vma' at .*?\\.stp:2:31"
+            regsub -all -- {\n} $stderr_pat {\n} escaped_stderr_pat
+            if {[regexp -linestop -- $stderr_pat $stderr]} {
+                pass "${test_name}: stderr matches \"$escaped_stderr_pat\""
+            } else {
+                fail "${test_name}: stderr fails to match \"$escaped_stderr_pat\": got \"$stderr\""
+            }
+
+        } else {
+            if {$failed} {
+                fail "${test_name}: exit code not zero"
+            } else {
+                pass "${test_name}: exit code is zero"
+            }
+        }
+    }
+}
+
+# --- TEST 8 ---
+
+set subtest8 "TEST 8: take address of @vma"
+
+set res [target_compile ${testpath}/${test}_1.c ./a.out executable \
+    "additional_flags=-O additional_flags=-g additional_flags=-fpic additional_flags=-pie"]
+if {$res ne ""} {
+    verbose "target_compile failed: $res" 2
+    fail "$test: $subtest8: unable to compile ${test}_1.c"
+} else {
+    set stp_fh [open "$srcdir/$subdir/${test}_8.stp" r]
+    set stp_src [read $stp_fh]
+    close $stp_fh
+
+    set nm_fh [open "| nm --defined-only ./a.out" r]
+    set found 0
+    while {[gets $nm_fh line] >= 0} {
+        if {[regexp -- {^\s*([0-9a-f]+)\s+[DT]\s+v\s*$} $line match addr]} {
+            send_log "found symbol 'v' at addr $addr in the nm output\n"
+            regsub -all -- {\$\^ADDR_v\y} $stp_src $addr stp_src
+            set found 1
+            break
+        }
+    }
+
+    if {[catch {close $nm_fh} nm_err] != 0} {
+        fail "$test: $subtest8: failed to run nm: $nm_err"
+        return
+    }
+
+    if {! $found} {
+        fail "$test: $subtest8: symbol 'v' not found in executable ./a.out"
+        return
+    }
+    set stp_fh [open "./${test}_8.stp" w+]
+    puts -nonewline $stp_fh $stp_src
+    close $stp_fh
+
+    foreach runtime [get_runtime_list] {
+        if {$runtime eq ""} {
+            set runtime "kernel"
+        }
+        set test_name "$test: $subtest8 ($runtime)"
+
+        set cmd "stap --runtime=$runtime -c ./a.out './${test}_8.stp'"
+        send_log "executing: $cmd\n"
+        set pipe [open "| sh -c {$cmd}" r]
+        set out [read $pipe]
+        set failed 0
+        if {[catch {close $pipe} stderr] != 0} {
+            set failed 1
+        }
+
+        set exp_out ""
+        regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+        if {$out eq $exp_out} {
+            pass "${test_name}: stdout matches \"$escaped_exp_out\""
+        } else {
+            fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+        }
+
+        if {$failed} {
+            pass "${test_name}: exit code should be non-zero"
+        } else {
+            fail "${test_name}: exit code should be non-zero but is zero"
+        }
+
+        set stderr_pat "parse error: cannot take address of \\@vma\n\\s+at: operator '\\&' at .*?\\.stp:2:20\$"
+        regsub -all -- {\n} $stderr_pat {\n} escaped_stderr_pat
+        if {[regexp -lineanchor -- $stderr_pat $stderr]} {
+            pass "${test_name}: stderr matches \"$escaped_stderr_pat\""
+        } else {
+            fail "${test_name}: stderr fails to match \"$escaped_stderr_pat\": got \"$stderr\""
+        }
+    }
+}
+
+# --- TEST 9 ---
+
+set subtest9 "TEST 9: @vma - multiple module values separated by :"
+
+set res [target_compile ${testpath}/${test}_1.c ./a.out executable \
+    "additional_flags=-O additional_flags=-g"]
+if {$res ne ""} {
+    verbose "target_compile failed: $res" 2
+    fail "$test: $subtest9: unable to compile ${test}_1.c"
+} else {
+    set stp_fh [open "$srcdir/$subdir/${test}_9.stp" r]
+    set stp_src [read $stp_fh]
+    close $stp_fh
+
+    set cwd [pwd]
+    regsub -all -- {\$\^PWD\y} $stp_src $cwd stp_src
+
+    set nm_fh [open "| nm --defined-only ./a.out" r]
+    set found 0
+    while {[gets $nm_fh line] >= 0} {
+        if {[regexp -- {^\s*([0-9a-f]+)\s+[DT]\s+v\s*$} $line match addr]} {
+            send_log "found symbol 'v' at addr $addr in the nm output\n"
+            regsub -all -- {\$\^ADDR_v\y} $stp_src $addr stp_src
+            set found 1
+            break
+        }
+    }
+
+    if {[catch {close $nm_fh} nm_err] != 0} {
+        fail "$test: $subtest9: failed to run nm: $nm_err"
+        return
+    }
+
+    if {! $found} {
+        fail "$test: $subtest9: symbol 'v' not found in executable ./a.out"
+        return
+    }
+    set stp_fh [open "./${test}_9.stp" w+]
+    puts -nonewline $stp_fh $stp_src
+    close $stp_fh
+
+    foreach runtime [get_runtime_list] {
+        if {$runtime eq ""} {
+            set runtime "kernel"
+        }
+        set test_name "$test: $subtest9 ($runtime)"
+
+        set cmd "stap --runtime=$runtime -c ./a.out './${test}_9.stp'"
+        send_log "executing: $cmd\n"
+        set pipe [open "| sh -c {$cmd}" r]
+        set out [read $pipe]
+        set failed 0
+        if {[catch {close $pipe} stderr] != 0} {
+            set failed 1
+        }
+
+        set exp_out ""
+        regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+        if {$out eq $exp_out} {
+            pass "${test_name}: stdout matches \"$escaped_exp_out\""
+        } else {
+            fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+        }
+
+        if {$failed} {
+            pass "${test_name}: exit code should be non-zero"
+        } else {
+            fail "${test_name}: exit code should be non-zero but is zero"
+        }
+
+        set stderr_pat "\\yparse error: \\@vma\\(\\) cannot take multiple module values\n\\s+at: string '\[^'\\n\]*?/a\\.out:\[^'\\n\]*?/a\\.out' at .*?\\.stp:2:47\n"
+        regsub -all -- {\n} $stderr_pat {\n} escaped_stderr_pat
+        if {[regexp -lineanchor -- $stderr_pat $stderr]} {
+            pass "${test_name}: stderr matches \"$escaped_stderr_pat\""
+        } else {
+            fail "${test_name}: stderr fails to match \"$escaped_stderr_pat\": got \"$stderr\""
+        }
+    }
+}
+
+# --- TEST 10 ---
+
+set subtest10 "TEST 10: @vma - print - with module"
+
+set res [target_compile ${testpath}/${test}_1.c ./a.out executable \
+    "additional_flags=-O additional_flags=-g"]
+if {$res ne ""} {
+    verbose "target_compile failed: $res" 2
+    fail "$test: $subtest10: unable to compile ${test}_1.c"
+} else {
+    set stp_fh [open "$srcdir/$subdir/${test}_10.stp" r]
+    set stp_src [read $stp_fh]
+    close $stp_fh
+
+    set cwd [pwd]
+    regsub -all -- {\$\^PWD\y} $stp_src $cwd stp_src
+
+    set nm_fh [open "| nm --defined-only ./a.out" r]
+    set found 0
+    while {[gets $nm_fh line] >= 0} {
+        if {[regexp -- {^\s*([0-9a-f]+)\s+[DT]\s+v\s*$} $line match addr]} {
+            send_log "found symbol 'v' at addr $addr in the nm output\n"
+            regsub -all -- {\$\^ADDR_v\y} $stp_src $addr stp_src
+            set found 1
+            break
+        }
+    }
+
+    if {[catch {close $nm_fh} nm_err] != 0} {
+        fail "$test: $subtest10: failed to run nm: $nm_err"
+        return
+    }
+
+    if {! $found} {
+        fail "$test: $subtest10: symbol 'v' not found in executable ./a.out"
+        return
+    }
+    set stp_fh [open "./${test}_10.stp" w+]
+    puts -nonewline $stp_fh $stp_src
+    close $stp_fh
+
+    foreach runtime [get_runtime_list] {
+        if {$runtime eq ""} {
+            set runtime "kernel"
+        }
+        set test_name "$test: $subtest10 ($runtime)"
+
+        set cmd "stap -p1 --runtime=$runtime -c ./a.out './${test}_10.stp'"
+        send_log "executing: $cmd\n"
+        set pipe [open "| sh -c {$cmd}" r]
+        set out [read $pipe]
+        set failed 0
+        if {[catch {close $pipe} stderr] != 0} {
+            set failed 1
+        }
+
+        set out_pat "\\yreturn user_long\\(\\@vma\\(0x\[0-9a-f\]+, \"\[^\\n\"\]*?/a\\.out\"\\)\\);\$"
+        regsub -all -- {\n} $out_pat {\n} escaped_out_pat
+        if {[regexp -lineanchor -- $out_pat $out]} {
+            pass "${test_name}: stdout matches \"$escaped_out_pat\""
+        } else {
+            fail "${test_name}: stdout fails to match \"$escaped_out_pat\": got \"$out\""
+        }
+
+        if {$failed} {
+            fail "${test_name}: exit code not zero"
+        } else {
+            pass "${test_name}: exit code is zero"
+        }
+        if {$stderr ne ""} {
+            send_log "stderr:\n$stderr"
+        }
+    }
+}
+
+# --- TEST 11 ---
+
+set subtest11 "TEST 11: @vma - print - without module"
+
+set res [target_compile ${testpath}/${test}_1.c ./a.out executable \
+    "additional_flags=-O additional_flags=-g"]
+if {$res ne ""} {
+    verbose "target_compile failed: $res" 2
+    fail "$test: $subtest11: unable to compile ${test}_1.c"
+} else {
+    set stp_fh [open "$srcdir/$subdir/${test}_11.stp" r]
+    set stp_src [read $stp_fh]
+    close $stp_fh
+
+    set nm_fh [open "| nm --defined-only ./a.out" r]
+    set found 0
+    while {[gets $nm_fh line] >= 0} {
+        if {[regexp -- {^\s*([0-9a-f]+)\s+[DT]\s+v\s*$} $line match addr]} {
+            send_log "found symbol 'v' at addr $addr in the nm output\n"
+            regsub -all -- {\$\^ADDR_v\y} $stp_src $addr stp_src
+            set found 1
+            break
+        }
+    }
+
+    if {[catch {close $nm_fh} nm_err] != 0} {
+        fail "$test: $subtest11: failed to run nm: $nm_err"
+        return
+    }
+
+    if {! $found} {
+        fail "$test: $subtest11: symbol 'v' not found in executable ./a.out"
+        return
+    }
+    set stp_fh [open "./${test}_11.stp" w+]
+    puts -nonewline $stp_fh $stp_src
+    close $stp_fh
+
+    foreach runtime [get_runtime_list] {
+        if {$runtime eq ""} {
+            set runtime "kernel"
+        }
+        set test_name "$test: $subtest11 ($runtime)"
+
+        set cmd "stap -p1 --runtime=$runtime -c ./a.out './${test}_11.stp'"
+        send_log "executing: $cmd\n"
+        set pipe [open "| sh -c {$cmd}" r]
+        set out [read $pipe]
+        set failed 0
+        if {[catch {close $pipe} stderr] != 0} {
+            set failed 1
+        }
+
+        set out_pat ", user_long\\(\\@vma\\(0x\[0-9a-f\]+\\)\\)\\);\$"
+        regsub -all -- {\n} $out_pat {\n} escaped_out_pat
+        if {[regexp -lineanchor -- $out_pat $out]} {
+            pass "${test_name}: stdout matches \"$escaped_out_pat\""
+        } else {
+            fail "${test_name}: stdout fails to match \"$escaped_out_pat\": got \"$out\""
+        }
+
+        if {$failed} {
+            fail "${test_name}: exit code not zero"
+        } else {
+            pass "${test_name}: exit code is zero"
+        }
+        if {$stderr ne ""} {
+            send_log "stderr:\n$stderr"
+        }
+    }
+}
+
+# --- TEST 12 ---
+
+set subtest12 "TEST 12: @vma - not PIE - in func - explicit module - no DWARF"
+
+set res [target_compile ${testpath}/${test}_1.c ./a.out executable \
+    "additional_flags=-O"]
+if {$res ne ""} {
+    verbose "target_compile failed: $res" 2
+    fail "$test: $subtest12: unable to compile ${test}_1.c"
+} else {
+    set stp_fh [open "$srcdir/$subdir/${test}_12.stp" r]
+    set stp_src [read $stp_fh]
+    close $stp_fh
+
+    set cwd [pwd]
+    regsub -all -- {\$\^PWD\y} $stp_src $cwd stp_src
+
+    set nm_fh [open "| nm --defined-only ./a.out" r]
+    set found 0
+    while {[gets $nm_fh line] >= 0} {
+        if {[regexp -- {^\s*([0-9a-f]+)\s+[DT]\s+v\s*$} $line match addr]} {
+            send_log "found symbol 'v' at addr $addr in the nm output\n"
+            regsub -all -- {\$\^ADDR_v\y} $stp_src $addr stp_src
+            set found 1
+            break
+        }
+    }
+
+    if {[catch {close $nm_fh} nm_err] != 0} {
+        fail "$test: $subtest12: failed to run nm: $nm_err"
+        return
+    }
+
+    if {! $found} {
+        fail "$test: $subtest12: symbol 'v' not found in executable ./a.out"
+        return
+    }
+    set stp_fh [open "./${test}_12.stp" w+]
+    puts -nonewline $stp_fh $stp_src
+    close $stp_fh
+
+    foreach runtime [get_runtime_list] {
+        if {$runtime eq ""} {
+            set runtime "kernel"
+        }
+        set test_name "$test: $subtest12 ($runtime)"
+
+        set cmd "stap --runtime=$runtime -c ./a.out './${test}_12.stp'"
+        send_log "executing: $cmd\n"
+        set pipe [open "| sh -c {$cmd}" r]
+        set out [read $pipe]
+        set failed 0
+        if {[catch {close $pipe} stderr] != 0} {
+            set failed 1
+        }
+
+        set exp_out "0xbeefdead\nno var v found\n1\n"
+        regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+        if {$out eq $exp_out} {
+            pass "${test_name}: stdout matches \"$escaped_exp_out\""
+        } else {
+            fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+        }
+
+        if {$failed} {
+            fail "${test_name}: exit code not zero"
+        } else {
+            pass "${test_name}: exit code is zero"
+        }
+        if {$stderr ne ""} {
+            send_log "stderr:\n$stderr"
+        }
+    }
+}
+
+# --- TEST 13 ---
+
+set subtest13 "TEST 13: @vma - PIE - in func - explicit module - no DWARF"
+
+set res [target_compile ${testpath}/${test}_1.c ./a.out executable \
+    "additional_flags=-O additional_flags=-fpic additional_flags=-pie"]
+if {$res ne ""} {
+    verbose "target_compile failed: $res" 2
+    fail "$test: $subtest13: unable to compile ${test}_1.c"
+} else {
+    set stp_fh [open "$srcdir/$subdir/${test}_13.stp" r]
+    set stp_src [read $stp_fh]
+    close $stp_fh
+
+    set cwd [pwd]
+    regsub -all -- {\$\^PWD\y} $stp_src $cwd stp_src
+
+    set nm_fh [open "| nm --defined-only ./a.out" r]
+    set found 0
+    while {[gets $nm_fh line] >= 0} {
+        if {[regexp -- {^\s*([0-9a-f]+)\s+[DT]\s+v\s*$} $line match addr]} {
+            send_log "found symbol 'v' at addr $addr in the nm output\n"
+            regsub -all -- {\$\^ADDR_v\y} $stp_src $addr stp_src
+            set found 1
+            break
+        }
+    }
+
+    if {[catch {close $nm_fh} nm_err] != 0} {
+        fail "$test: $subtest13: failed to run nm: $nm_err"
+        return
+    }
+
+    if {! $found} {
+        fail "$test: $subtest13: symbol 'v' not found in executable ./a.out"
+        return
+    }
+    set stp_fh [open "./${test}_13.stp" w+]
+    puts -nonewline $stp_fh $stp_src
+    close $stp_fh
+
+    foreach runtime [get_runtime_list] {
+        if {$runtime eq ""} {
+            set runtime "kernel"
+        }
+        set test_name "$test: $subtest13 ($runtime)"
+
+        set cmd "stap --runtime=$runtime -c ./a.out './${test}_13.stp'"
+        send_log "executing: $cmd\n"
+        set pipe [open "| sh -c {$cmd}" r]
+        set out [read $pipe]
+        set failed 0
+        if {[catch {close $pipe} stderr] != 0} {
+            set failed 1
+        }
+
+        if {$runtime eq "dyninst"} {
+            set exp_out ""
+            regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+            if {$out eq $exp_out} {
+                pass "${test_name}: stdout matches \"$escaped_exp_out\""
+            } else {
+                fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+            }
+        } else {
+            set exp_out "no var v found\n0\n0xbeefdead\n"
+            regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+            if {$out eq $exp_out} {
+                pass "${test_name}: stdout matches \"$escaped_exp_out\""
+            } else {
+                fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+            }
+        }
+        if {$runtime eq "dyninst"} {
+
+            if {$failed} {
+                pass "${test_name}: exit code should be non-zero"
+            } else {
+                fail "${test_name}: exit code should be non-zero but is zero"
+            }
+
+            set stderr_pat "semantic error: VMA-tracking is only supported by the kernel runtime \\(PR15052\\): operator '\\@vma' at .*?\\.stp:2:20"
+            regsub -all -- {\n} $stderr_pat {\n} escaped_stderr_pat
+            if {[regexp -linestop -- $stderr_pat $stderr]} {
+                pass "${test_name}: stderr matches \"$escaped_stderr_pat\""
+            } else {
+                fail "${test_name}: stderr fails to match \"$escaped_stderr_pat\": got \"$stderr\""
+            }
+
+        } else {
+            if {$failed} {
+                fail "${test_name}: exit code not zero"
+            } else {
+                pass "${test_name}: exit code is zero"
+            }
+        }
+    }
+}
+
+# --- TEST 14 ---
+
+set subtest14 "TEST 14: @vma cannot be used as an lvalue"
+
+set res [target_compile ${testpath}/${test}_14.c ./a.out executable \
+    "additional_flags=-O additional_flags=-g"]
+if {$res ne ""} {
+    verbose "target_compile failed: $res" 2
+    fail "$test: $subtest14: unable to compile ${test}_14.c"
+} else {
+    set stp_fh [open "$srcdir/$subdir/${test}_14.stp" r]
+    set stp_src [read $stp_fh]
+    close $stp_fh
+
+    set nm_fh [open "| nm --defined-only ./a.out" r]
+    set found 0
+    while {[gets $nm_fh line] >= 0} {
+        if {[regexp -- {^\s*([0-9a-f]+)\s+[DT]\s+v\s*$} $line match addr]} {
+            send_log "found symbol 'v' at addr $addr in the nm output\n"
+            regsub -all -- {\$\^ADDR_v\y} $stp_src $addr stp_src
+            set found 1
+            break
+        }
+    }
+
+    if {[catch {close $nm_fh} nm_err] != 0} {
+        fail "$test: $subtest14: failed to run nm: $nm_err"
+        return
+    }
+
+    if {! $found} {
+        fail "$test: $subtest14: symbol 'v' not found in executable ./a.out"
+        return
+    }
+    set stp_fh [open "./${test}_14.stp" w+]
+    puts -nonewline $stp_fh $stp_src
+    close $stp_fh
+
+    foreach runtime [get_runtime_list] {
+        if {$runtime eq ""} {
+            set runtime "kernel"
+        }
+        set test_name "$test: $subtest14 ($runtime)"
+
+        set cmd "stap --runtime=$runtime -c ./a.out './${test}_14.stp'"
+        send_log "executing: $cmd\n"
+        set pipe [open "| sh -c {$cmd}" r]
+        set out [read $pipe]
+        set failed 0
+        if {[catch {close $pipe} stderr] != 0} {
+            set failed 1
+        }
+
+        set exp_out ""
+        regsub -all -- {\n} $exp_out {\n} escaped_exp_out
+        if {$out eq $exp_out} {
+            pass "${test_name}: stdout matches \"$escaped_exp_out\""
+        } else {
+            fail "${test_name}: stdout fails to match \"$escaped_exp_out\": got \"$out\""
+        }
+
+        if {$failed} {
+            pass "${test_name}: exit code should be non-zero"
+        } else {
+            fail "${test_name}: exit code should be non-zero but is zero"
+        }
+
+        set stderr_pat "^semantic error: writing to \\@vma variable not permitted: operator '\\@vma' at \[^\\n\]*?\\.stp:2:5\\n        source:     \\@vma\\(0x0000000000601020\\) = 32;\\n                    \\^\\n"
+        regsub -all -- {\n} $stderr_pat {\n} escaped_stderr_pat
+        if {[regexp -linestop -- $stderr_pat $stderr]} {
+            pass "${test_name}: stderr matches \"$escaped_stderr_pat\""
+        } else {
+            fail "${test_name}: stderr fails to match \"$escaped_stderr_pat\": got \"$stderr\""
+        }
+    }
+}
diff --git a/testsuite/systemtap.base/at_vma_1.c b/testsuite/systemtap.base/at_vma_1.c
new file mode 100644
index 000000000..8c3b7bc46
--- /dev/null
+++ b/testsuite/systemtap.base/at_vma_1.c
@@ -0,0 +1,5 @@
+long v = 0xbeefdead;
+
+int main(void) {
+    return 0;
+}
diff --git a/testsuite/systemtap.base/at_vma_1.stp b/testsuite/systemtap.base/at_vma_1.stp
new file mode 100644
index 000000000..857257ad1
--- /dev/null
+++ b/testsuite/systemtap.base/at_vma_1.stp
@@ -0,0 +1,9 @@
+function f() {
+    return user_long(@vma(0x$^ADDR_v, "$^PWD/a.out"));
+}
+
+probe process.function("main") {
+    printf("%#x\n", f());
+    printf("%d\n", @vma(0x$^ADDR_v, "$^PWD/a.out") == 0x$^ADDR_v);
+    exit();
+}
diff --git a/testsuite/systemtap.base/at_vma_10.stp b/testsuite/systemtap.base/at_vma_10.stp
new file mode 100644
index 000000000..67a45e7b3
--- /dev/null
+++ b/testsuite/systemtap.base/at_vma_10.stp
@@ -0,0 +1,8 @@
+function f() {
+    return user_long(@vma(0x$^ADDR_v, "$^PWD/a.out"));
+}
+
+probe process.function("main") {
+    printf("%#x\n", f());
+    exit();
+}
diff --git a/testsuite/systemtap.base/at_vma_11.stp b/testsuite/systemtap.base/at_vma_11.stp
new file mode 100644
index 000000000..f9e35c5ea
--- /dev/null
+++ b/testsuite/systemtap.base/at_vma_11.stp
@@ -0,0 +1,4 @@
+probe process.function("main") {
+    printf("%#x\n", user_long(@vma(0x$^ADDR_v)));
+    exit();
+}
diff --git a/testsuite/systemtap.base/at_vma_12.stp b/testsuite/systemtap.base/at_vma_12.stp
new file mode 100644
index 000000000..eaae7299f
--- /dev/null
+++ b/testsuite/systemtap.base/at_vma_12.stp
@@ -0,0 +1,12 @@
+function f() {
+    return user_long(@vma(0x$^ADDR_v, "$^PWD/a.out"));
+}
+
+probe process.function("main") {
+    printf("%#x\n", f());
+    if (!@defined(@var("v"))) {
+        println("no var v found");
+    }
+    printf("%d\n", @vma(0x$^ADDR_v, "$^PWD/a.out") == 0x$^ADDR_v);
+    exit();
+}
diff --git a/testsuite/systemtap.base/at_vma_13.stp b/testsuite/systemtap.base/at_vma_13.stp
new file mode 100644
index 000000000..d49090382
--- /dev/null
+++ b/testsuite/systemtap.base/at_vma_13.stp
@@ -0,0 +1,12 @@
+function f() {
+    printf("%d\n", @vma(0x$^ADDR_v, "$^PWD/a.out") == 0x$^ADDR_v);
+    return user_long(@vma(0x$^ADDR_v, "$^PWD/a.out"));
+}
+
+probe process.function("main") {
+    if (!@defined(@var("v"))) {
+        println("no var v found");
+    }
+    printf("%#x\n", f());
+    exit();
+}
diff --git a/testsuite/systemtap.base/at_vma_14.c b/testsuite/systemtap.base/at_vma_14.c
new file mode 100644
index 000000000..eeca645b9
--- /dev/null
+++ b/testsuite/systemtap.base/at_vma_14.c
@@ -0,0 +1,5 @@
+long v = 0xdeadbeef;
+
+int main(void) {
+    return 0;
+}
diff --git a/testsuite/systemtap.base/at_vma_14.stp b/testsuite/systemtap.base/at_vma_14.stp
new file mode 100644
index 000000000..c0bbd70eb
--- /dev/null
+++ b/testsuite/systemtap.base/at_vma_14.stp
@@ -0,0 +1,3 @@
+probe process.function("main") {
+    @vma(0x$^ADDR_v) = 32;
+}
diff --git a/testsuite/systemtap.base/at_vma_2.stp b/testsuite/systemtap.base/at_vma_2.stp
new file mode 100644
index 000000000..d579700d8
--- /dev/null
+++ b/testsuite/systemtap.base/at_vma_2.stp
@@ -0,0 +1,8 @@
+function f() {
+    return user_long(@vma(0x$^ADDR_v));
+}
+
+probe process.function("main") {
+    printf("%#x\n", f());
+    exit();
+}
diff --git a/testsuite/systemtap.base/at_vma_3.stp b/testsuite/systemtap.base/at_vma_3.stp
new file mode 100644
index 000000000..1ae731ad2
--- /dev/null
+++ b/testsuite/systemtap.base/at_vma_3.stp
@@ -0,0 +1,5 @@
+probe process.function("main") {
+    printf("%#x\n", user_long(@vma(0x$^ADDR_v, "$^PWD/a.out")));
+    printf("%d\n", @vma(0x$^ADDR_v, "$^PWD/a.out") == 0x$^ADDR_v);
+    exit();
+}
diff --git a/testsuite/systemtap.base/at_vma_4.stp b/testsuite/systemtap.base/at_vma_4.stp
new file mode 100644
index 000000000..01365f11a
--- /dev/null
+++ b/testsuite/systemtap.base/at_vma_4.stp
@@ -0,0 +1,5 @@
+probe process.function("main") {
+    printf("%#x\n", user_long(@vma(0x$^ADDR_v)));
+    printf("%d\n", @vma(0x$^ADDR_v, "$^PWD/a.out") == 0x$^ADDR_v);
+    exit();
+}
diff --git a/testsuite/systemtap.base/at_vma_5.stp b/testsuite/systemtap.base/at_vma_5.stp
new file mode 100644
index 000000000..888733eee
--- /dev/null
+++ b/testsuite/systemtap.base/at_vma_5.stp
@@ -0,0 +1,9 @@
+function f() {
+    printf("%d\n", @vma(0x$^ADDR_v, "$^PWD/a.out") == 0x$^ADDR_v);
+    return user_long(@vma(0x$^ADDR_v, "$^PWD/a.out"));
+}
+
+probe process.function("main") {
+    printf("%#x\n", f());
+    exit();
+}
diff --git a/testsuite/systemtap.base/at_vma_6.stp b/testsuite/systemtap.base/at_vma_6.stp
new file mode 100644
index 000000000..4a90930a1
--- /dev/null
+++ b/testsuite/systemtap.base/at_vma_6.stp
@@ -0,0 +1,5 @@
+probe process.function("main") {
+    printf("%#x\n", user_long(@vma(0x$^ADDR_v, "$^PWD/a.out")));
+    printf("%d\n", @vma(0x$^ADDR_v) == 0x$^ADDR_v);
+    exit();
+}
diff --git a/testsuite/systemtap.base/at_vma_7.stp b/testsuite/systemtap.base/at_vma_7.stp
new file mode 100644
index 000000000..ec8a86142
--- /dev/null
+++ b/testsuite/systemtap.base/at_vma_7.stp
@@ -0,0 +1,5 @@
+probe process.function("main") {
+    printf("%#x\n", user_long(@vma(0x$^ADDR_v)));
+    printf("%d\n", @vma(0x$^ADDR_v) == 0x$^ADDR_v);
+    exit();
+}
diff --git a/testsuite/systemtap.base/at_vma_8.stp b/testsuite/systemtap.base/at_vma_8.stp
new file mode 100644
index 000000000..9a39588a6
--- /dev/null
+++ b/testsuite/systemtap.base/at_vma_8.stp
@@ -0,0 +1,4 @@
+probe process.function("main") {
+    printf("%p\n", &@vma(0x$^ADDR_v));
+    exit();
+}
diff --git a/testsuite/systemtap.base/at_vma_9.stp b/testsuite/systemtap.base/at_vma_9.stp
new file mode 100644
index 000000000..b9ed14775
--- /dev/null
+++ b/testsuite/systemtap.base/at_vma_9.stp
@@ -0,0 +1,8 @@
+function f() {
+    return user_long(@vma(0x$^ADDR_v, "$^PWD/a.out:$^PWD/a.out"));
+}
+
+probe process.function("main") {
+    printf("%#x\n", f());
+    exit();
+}
diff --git a/translate.cxx b/translate.cxx
index c7e7aa53f..4249a2454 100644
--- a/translate.cxx
+++ b/translate.cxx
@@ -237,6 +237,7 @@ struct c_unparser: public unparser, public visitor
   void visit_cast_op (cast_op* e);
   void visit_autocast_op (autocast_op* e);
   void visit_atvar_op (atvar_op* e);
+  void visit_atvma_op (atvma_op* e);
   void visit_defined_op (defined_op* e);
   void visit_entry_op (entry_op* e);
   void visit_perf_op (perf_op* e);
@@ -5315,6 +5316,13 @@ c_unparser::visit_atvar_op (atvar_op* e)
 
 
 void
+c_unparser::visit_atvma_op (atvma_op* e)
+{
+  throw SEMANTIC_ERROR(_("cannot translate general @vma expression"), e->tok);
+}
+
+
+void
 c_unparser::visit_cast_op (cast_op* e)
 {
   throw SEMANTIC_ERROR(_("cannot translate general @cast expression"), e->tok);
-- 
2.11.0.295.gd7dffce

^ permalink raw reply	[flat|nested] 2+ messages in thread

* [PATCH v2] Add new operator @vma(address, module)
  2018-08-24 22:41 [PATCH] Add new primitive @vma(address, module) Yichun Zhang (agentzh)
@ 2018-09-05 20:56 ` Yichun Zhang (agentzh)
  0 siblings, 0 replies; 2+ messages in thread
From: Yichun Zhang (agentzh) @ 2018-09-05 20:56 UTC (permalink / raw)
  To: systemtap; +Cc: Yichun Zhang

From: Yichun Zhang <yichun@openresty.com>

Implemented new operator @vma(address, module) and @vma(address) to
return absolute virtual memory (VM) addresses from relative addresses
in the specified (ELF) module (or the current module in the probe
handler context). For non-PIE program modules, the relative address is
the absolute address and @vma() simply returns the value of the first
argument as-is.

Under the hood, it just exposes the existing VMA tracker facility to
the script language level.

This is useful for accessing static variables in target processes with
DWARF-less probing (where @var() is not usable). It is also useful to
probe the addresses of literal C strings residing in the .rodata section
of the ELF files which cannot be addressed via the DWARF info or the
symbol table.

The dyninst runtime does not support the VMA tracker yet, but we still
cover its error messages in our tests cases. So once it does support it,
we can know it immediately through new failures in these test cases.

The newly added tests rely on the `nm` utility to find the (relative)
addresses of symbols in the ELF files of our trivial example C prgrams,
just like some of the existing tests in the official test suite.

This new operator can be disabled by passing the --compatible 3.3
option to stap.
---
 NEWS                                   |  17 +
 dwflpp.cxx                             |  58 ++++
 dwflpp.h                               |   2 +
 elaborate.cxx                          |  33 ++
 elaborate.h                            |   1 +
 loc2stap.cxx                           |  54 +---
 parse.cxx                              |  42 +++
 staptree.cxx                           |  51 +++
 staptree.h                             |  16 +
 tapsets.cxx                            | 102 ++++++
 testsuite/lib/test_simple.exp          |  83 +++++
 testsuite/systemtap.base/at_vma.exp    | 548 +++++++++++++++++++++++++++++++++
 testsuite/systemtap.base/at_vma_1.c    |   5 +
 testsuite/systemtap.base/at_vma_1.stp  |   9 +
 testsuite/systemtap.base/at_vma_10.stp |   8 +
 testsuite/systemtap.base/at_vma_11.stp |   4 +
 testsuite/systemtap.base/at_vma_12.stp |  12 +
 testsuite/systemtap.base/at_vma_13.stp |  12 +
 testsuite/systemtap.base/at_vma_14.c   |   5 +
 testsuite/systemtap.base/at_vma_14.stp |   3 +
 testsuite/systemtap.base/at_vma_15.c   |   2 +
 testsuite/systemtap.base/at_vma_15.stp |   4 +
 testsuite/systemtap.base/at_vma_16.stp |   3 +
 testsuite/systemtap.base/at_vma_2.stp  |   8 +
 testsuite/systemtap.base/at_vma_3.stp  |   5 +
 testsuite/systemtap.base/at_vma_4.stp  |   5 +
 testsuite/systemtap.base/at_vma_5.stp  |   9 +
 testsuite/systemtap.base/at_vma_6.stp  |   5 +
 testsuite/systemtap.base/at_vma_7.stp  |   5 +
 testsuite/systemtap.base/at_vma_8.stp  |   4 +
 testsuite/systemtap.base/at_vma_9.stp  |   8 +
 translate.cxx                          |   8 +
 32 files changed, 1078 insertions(+), 53 deletions(-)
 create mode 100644 testsuite/systemtap.base/at_vma.exp
 create mode 100644 testsuite/systemtap.base/at_vma_1.c
 create mode 100644 testsuite/systemtap.base/at_vma_1.stp
 create mode 100644 testsuite/systemtap.base/at_vma_10.stp
 create mode 100644 testsuite/systemtap.base/at_vma_11.stp
 create mode 100644 testsuite/systemtap.base/at_vma_12.stp
 create mode 100644 testsuite/systemtap.base/at_vma_13.stp
 create mode 100644 testsuite/systemtap.base/at_vma_14.c
 create mode 100644 testsuite/systemtap.base/at_vma_14.stp
 create mode 100644 testsuite/systemtap.base/at_vma_15.c
 create mode 100644 testsuite/systemtap.base/at_vma_15.stp
 create mode 100644 testsuite/systemtap.base/at_vma_16.stp
 create mode 100644 testsuite/systemtap.base/at_vma_2.stp
 create mode 100644 testsuite/systemtap.base/at_vma_3.stp
 create mode 100644 testsuite/systemtap.base/at_vma_4.stp
 create mode 100644 testsuite/systemtap.base/at_vma_5.stp
 create mode 100644 testsuite/systemtap.base/at_vma_6.stp
 create mode 100644 testsuite/systemtap.base/at_vma_7.stp
 create mode 100644 testsuite/systemtap.base/at_vma_8.stp
 create mode 100644 testsuite/systemtap.base/at_vma_9.stp

diff --git a/NEWS b/NEWS
index 26b03f62b..a5eaa1e58 100644
--- a/NEWS
+++ b/NEWS
@@ -51,6 +51,23 @@
 - Parentheses after unary '&' with a target-symbol expression is
   now accepted in the script language.
 
+- The script language now supports new operator @vma(address, module)
+  and @vma(address) to return absolute virtual memory (VM) addresses from
+  relative addresses in the specified (ELF) module (or the current module
+  in the probe handler context). For non-PIE program modules, the
+  relative address is the absolute address and @vma() simply returns
+  the value of the first argument as-is. Under the hood, it just exposes
+  the existing VMA tracker facility to the script language level.
+
+  This is useful for accessing static variables in target processes with
+  DWARF-less probing (where @var() is not usable). It is also useful to
+  probe the addresses of literal C strings residing in the .rodata section
+  of the ELF files which cannot be addressed via the DWARF info or the
+  symbol table.
+
+  This new operator can be disabled by passing the --compatible 3.3
+  option to stap.
+
 * What's new in version 3.3, 2018-06-08
 
 - A new "stap --example FOO.stp" mode searches the example scripts
diff --git a/dwflpp.cxx b/dwflpp.cxx
index 2172e705a..a66fb99e7 100644
--- a/dwflpp.cxx
+++ b/dwflpp.cxx
@@ -3088,6 +3088,64 @@ dwflpp::pc_location_as_function_string(Dwarf_Addr pc)
   return locstr;
 }
 
+expression *
+dwflpp::translate_address(Dwarf_Addr addr, const token *tok)
+{
+  int n = dwfl_module_relocations (module);
+  Dwarf_Addr reloc_addr = addr;
+  const char *secname = "";
+
+  if (n > 1)
+    {
+      int i = dwfl_module_relocate_address (module, &reloc_addr);
+      secname = dwfl_module_relocation_info (module, i, NULL);
+    }
+
+  if (n > 0 && !(n == 1 && secname == NULL))
+    {
+      std::string c;
+
+      if (n > 1 || secname[0] != 0)
+	{
+	  // This gives us the module name and section name within the
+	  // module, for a kernel module.
+          c = "({ unsigned long addr = 0; "
+              "addr = _stp_kmodule_relocate (\""
+              + module_name + "\", \"" + secname + "\", "
+              + lex_cast_hex (reloc_addr)
+	      + "); addr; })";
+	}
+      else if (n == 1 && module_name == "kernel" && secname[0] == 0)
+	{
+	  // elfutils way of telling us that this is a relocatable kernel
+	  // address, which we need to treat the same way here as
+	  // dwarf_query::add_probe_point does.
+          c = "({ unsigned long addr = 0; "
+              "addr = _stp_kmodule_relocate (\"kernel\", \"_stext\", "
+              + lex_cast_hex (addr - sess.sym_stext)
+	      + "); addr; })";
+	}
+      else
+	{
+          c = "/* pragma:vma */ "
+              "({ unsigned long addr = 0; "
+              "addr = _stp_umodule_relocate (\""
+              + path_remove_sysroot(sess,
+				    resolve_path(module_name.c_str()))
+	      + "\", "
+              + lex_cast_hex (addr)
+	      + ", current); addr; })";
+	}
+
+      embedded_expr *r = new embedded_expr;
+      r->tok = tok;
+      r->code = c;
+      return r;
+    }
+  else
+    return new literal_number(addr);
+}
+
 struct location *
 dwflpp::translate_location(location_context *ctx,
 			   Dwarf_Attribute *attr, Dwarf_Die *die,
diff --git a/dwflpp.h b/dwflpp.h
index 998b96e7d..cde1d0d10 100644
--- a/dwflpp.h
+++ b/dwflpp.h
@@ -493,6 +493,8 @@ struct dwflpp
 
   Dwarf_Addr relocate_address(Dwarf_Addr addr, interned_string& reloc_section);
 
+  expression *translate_address(Dwarf_Addr addr, const token *tok);
+
   void resolve_unqualified_inner_typedie (Dwarf_Die *typedie,
                                           Dwarf_Die *innerdie,
                                           const target_symbol *e);
diff --git a/elaborate.cxx b/elaborate.cxx
index cd03df408..ed60d4238 100644
--- a/elaborate.cxx
+++ b/elaborate.cxx
@@ -3267,6 +3267,11 @@ struct assignment_symbol_fetcher
     sym = NULL;
   }
 
+  void visit_atvma_op (atvma_op*)
+  {
+    sym = NULL;
+  }
+
   void visit_cast_op (cast_op*)
   {
     sym = NULL;
@@ -5589,6 +5594,7 @@ struct initial_typeresolution_info : public typeresolution_info
   // and not all substitutions are done, replace the functions that throw errors.
   void visit_target_symbol (target_symbol*) {}
   void visit_atvar_op (atvar_op*) {}
+  void visit_atvma_op (atvma_op*) {}
   void visit_defined_op (defined_op*) {}
   void visit_entry_op (entry_op*) {}
   void visit_cast_op (cast_op*) {}
@@ -6390,6 +6396,33 @@ typeresolution_info::visit_atvar_op (atvar_op* e)
 
 
 void
+typeresolution_info::visit_atvma_op (atvma_op* e)
+{
+  if (session.verbose > 2)
+    {
+      clog << _("Resolution problem with ");
+      if (current_function)
+        {
+          clog << "function " << current_function->name << endl;
+          current_function->body->print (clog);
+          clog << endl;
+        }
+      else if (current_probe)
+        {
+          clog << "probe " << *current_probe->sole_location() << endl;
+          current_probe->body->print (clog);
+          clog << endl;
+        }
+      else
+        //TRANSLATORS: simply saying not an issue with a probe or function
+        clog << _("other") << endl;
+    }
+
+  throw SEMANTIC_ERROR(_("unresolved @vma() expression"), e->tok);
+}
+
+
+void
 typeresolution_info::visit_defined_op (defined_op* e)
 {
   // PR18079: if a @defined is still around, it may have a parameter that
diff --git a/elaborate.h b/elaborate.h
index 98754ece5..90ad94a6a 100644
--- a/elaborate.h
+++ b/elaborate.h
@@ -163,6 +163,7 @@ struct typeresolution_info: public visitor
   void visit_cast_op (cast_op* e);
   void visit_autocast_op (autocast_op* e);
   void visit_atvar_op (atvar_op* e);
+  void visit_atvma_op (atvma_op* e);
   void visit_defined_op (defined_op* e);
   void visit_entry_op (entry_op* e);
   void visit_perf_op (perf_op* e);
diff --git a/loc2stap.cxx b/loc2stap.cxx
index b73036c2f..5958e7ee0 100644
--- a/loc2stap.cxx
+++ b/loc2stap.cxx
@@ -68,59 +68,7 @@ location_context::translate_address(Dwarf_Addr addr)
   if (dw == NULL)
     return new literal_number(addr);
 
-  int n = dwfl_module_relocations (dw->module);
-  Dwarf_Addr reloc_addr = addr;
-  const char *secname = "";
-
-  if (n > 1)
-    {
-      int i = dwfl_module_relocate_address (dw->module, &reloc_addr);
-      secname = dwfl_module_relocation_info (dw->module, i, NULL);
-    }
-
-  if (n > 0 && !(n == 1 && secname == NULL))
-    {
-      std::string c;
-
-      if (n > 1 || secname[0] != 0)
-	{
-	  // This gives us the module name and section name within the
-	  // module, for a kernel module.
-          c = "({ unsigned long addr = 0; "
-              "addr = _stp_kmodule_relocate (\""
-              + dw->module_name + "\", \"" + secname + "\", "
-              + lex_cast_hex (reloc_addr)
-	      + "); addr; })";
-	}
-      else if (n == 1 && dw->module_name == "kernel" && secname[0] == 0)
-	{
-	  // elfutils way of telling us that this is a relocatable kernel
-	  // address, which we need to treat the same way here as
-	  // dwarf_query::add_probe_point does.
-          c = "({ unsigned long addr = 0; "
-              "addr = _stp_kmodule_relocate (\"kernel\", \"_stext\", "
-              + lex_cast_hex (addr - dw->sess.sym_stext)
-	      + "); addr; })";
-	}
-      else
-	{
-          c = "/* pragma:vma */ "
-              "({ unsigned long addr = 0; "
-              "addr = _stp_umodule_relocate (\""
-              + path_remove_sysroot(dw->sess,
-				    resolve_path(dw->module_name.c_str()))
-	      + "\", "
-              + lex_cast_hex (addr)
-	      + ", current); addr; })";
-	}
-
-      embedded_expr *r = new embedded_expr;
-      r->tok = e->tok;
-      r->code = c;
-      return r;
-    }
-  else
-    return new literal_number(addr);
+  return dw->translate_address(addr, e->tok);
 }
 
 location *
diff --git a/parse.cxx b/parse.cxx
index 0574f87c2..401945c20 100644
--- a/parse.cxx
+++ b/parse.cxx
@@ -204,6 +204,7 @@ private: // nonterminals
   target_symbol *parse_target_symbol ();
   cast_op *parse_cast_op ();
   atvar_op *parse_atvar_op ();
+  atvma_op *parse_atvma_op ();
   expression* parse_entry_op (const token* t);
   expression* parse_defined_op (const token* t);
   expression* parse_const_op (const token* t);
@@ -3698,6 +3699,14 @@ parser::parse_dwarf_value ()
     expr = tsym = parse_cast_op ();
   else if (tok_is (t, tok_operator, "@var"))
     expr = tsym = parse_atvar_op ();
+  else if (tok_is (t, tok_operator, "@vma") && input.has_version("4.0"))
+    {
+      expr = parse_atvma_op ();
+      if (addressof)
+	{
+	  throw PARSE_ERROR (_("cannot take address of @vma"), addrtok);
+	}
+    }
   else if (addressof && !input.has_version("2.6"))
     // '&' on old version only allowed specific target_symbol types
     throw PARSE_ERROR (_("expected @cast, @var or $var"));
@@ -4165,6 +4174,39 @@ atvar_op* parser::parse_atvar_op ()
 }
 
 
+// Parse a @vma.
+atvma_op* parser::parse_atvma_op ()
+{
+  const token* t = next ();
+  if (t->type == tok_operator && t->content == "@vma")
+    {
+      atvma_op *vop = new atvma_op;
+      int64_t address;
+      vop->tok = t;
+      expect_op ("(");
+      expect_number (address);
+      vop->address = (uint64_t) address;
+      if (peek_op (","))
+        {
+          swallow ();
+	  t = next ();
+	  if (!t || t->type != tok_string)
+	    throw PARSE_ERROR (_("expected string"));
+          vop->module = t->content;
+	  if (vop->module.find(":") != string::npos)
+	    throw PARSE_ERROR (_("@vma() cannot take multiple module values"),
+			       t);
+	  swallow ();
+        }
+      else
+        vop->module = "";
+      expect_op (")");
+      return vop;
+    }
+
+  throw PARSE_ERROR (_("expected @vma"));
+}
+
 // Parse a @defined().  Given head token has already been consumed.
 expression* parser::parse_defined_op (const token* t)
 {
diff --git a/staptree.cxx b/staptree.cxx
index 25cc9fe7c..615fa1370 100644
--- a/staptree.cxx
+++ b/staptree.cxx
@@ -529,6 +529,15 @@ void atvar_op::print (ostream& o) const
 }
 
 
+void atvma_op::print (ostream& o) const
+{
+  o << "@vma(" << lex_cast_hex(address);
+  if (module.length() > 0)
+    o << ", " << lex_cast_qstring (module);
+  o << ')';
+}
+
+
 void cast_op::print (ostream& o) const
 {
   if (addressof)
@@ -1764,6 +1773,13 @@ atvar_op::visit (visitor* u)
 
 
 void
+atvma_op::visit (visitor* u)
+{
+  u->visit_atvma_op(this);
+}
+
+
+void
 defined_op::visit (visitor* u)
 {
   u->visit_defined_op(this);
@@ -2145,6 +2161,11 @@ traversing_visitor::visit_atvar_op (atvar_op* e)
 }
 
 void
+traversing_visitor::visit_atvma_op (atvma_op*)
+{
+}
+
+void
 traversing_visitor::visit_defined_op (defined_op* e)
 {
   e->operand->visit (this);
@@ -2407,6 +2428,13 @@ expression_visitor::visit_atvar_op (atvar_op* e)
 }
 
 void
+expression_visitor::visit_atvma_op (atvma_op* e)
+{
+  traversing_visitor::visit_atvma_op (e);
+  visit_expression (e);
+}
+
+void
 expression_visitor::visit_defined_op (defined_op* e)
 {
   traversing_visitor::visit_defined_op (e);
@@ -2686,6 +2714,11 @@ varuse_collecting_visitor::visit_atvar_op (atvar_op *e)
   functioncall_traversing_visitor::visit_atvar_op (e);
 }
 
+void
+varuse_collecting_visitor::visit_atvma_op (atvma_op *)
+{
+  // No need to call the base op, we've done everything here.
+}
 
 void
 varuse_collecting_visitor::visit_cast_op (cast_op *e)
@@ -3214,6 +3247,12 @@ throwing_visitor::visit_atvar_op (atvar_op* e)
 }
 
 void
+throwing_visitor::visit_atvma_op (atvma_op* e)
+{
+  throwone (e->tok);
+}
+
+void
 throwing_visitor::visit_cast_op (cast_op* e)
 {
   throwone (e->tok);
@@ -3555,6 +3594,12 @@ update_visitor::visit_atvar_op (atvar_op* e)
 }
 
 void
+update_visitor::visit_atvma_op (atvma_op* e)
+{
+  provide (e);
+}
+
+void
 update_visitor::visit_defined_op (defined_op* e)
 {
   replace (e->operand);
@@ -3849,6 +3894,12 @@ deep_copy_visitor::visit_atvar_op (atvar_op* e)
 }
 
 void
+deep_copy_visitor::visit_atvma_op (atvma_op* e)
+{
+  update_visitor::visit_atvma_op(new atvma_op(*e));
+}
+
+void
 deep_copy_visitor::visit_defined_op (defined_op* e)
 {
   update_visitor::visit_defined_op(new defined_op(*e));
diff --git a/staptree.h b/staptree.h
index 00c22f9f1..90c6bab52 100644
--- a/staptree.h
+++ b/staptree.h
@@ -420,6 +420,14 @@ struct atvar_op: public target_symbol
   void visit (visitor* u);
 };
 
+struct atvma_op: public expression
+{
+  interned_string module;
+  uint64_t address;
+  void print (std::ostream& o) const;
+  void visit (visitor* u);
+};
+
 struct defined_op: public expression
 {
   expression *operand;
@@ -967,6 +975,7 @@ struct visitor
   virtual void visit_cast_op (cast_op* e) = 0;
   virtual void visit_autocast_op (autocast_op* e) = 0;
   virtual void visit_atvar_op (atvar_op* e) = 0;
+  virtual void visit_atvma_op (atvma_op* e) = 0;
   virtual void visit_defined_op (defined_op* e) = 0;
   virtual void visit_entry_op (entry_op* e) = 0;
   virtual void visit_perf_op (perf_op* e) = 0;
@@ -1020,6 +1029,7 @@ struct nop_visitor: public visitor
   virtual void visit_cast_op (cast_op*) {};
   virtual void visit_autocast_op (autocast_op*) {};
   virtual void visit_atvar_op (atvar_op*) {};
+  virtual void visit_atvma_op (atvma_op*) {};
   virtual void visit_defined_op (defined_op*) {};
   virtual void visit_entry_op (entry_op*) {};
   virtual void visit_perf_op (perf_op*) {};
@@ -1073,6 +1083,7 @@ struct traversing_visitor: public visitor
   void visit_cast_op (cast_op* e);
   void visit_autocast_op (autocast_op* e);
   void visit_atvar_op (atvar_op* e);
+  void visit_atvma_op (atvma_op* e);
   void visit_defined_op (defined_op* e);
   void visit_entry_op (entry_op* e);
   void visit_perf_op (perf_op* e);
@@ -1113,6 +1124,7 @@ struct expression_visitor: public traversing_visitor
   void visit_cast_op (cast_op* e);
   void visit_autocast_op (autocast_op* e);
   void visit_atvar_op (atvar_op* e);
+  void visit_atvma_op (atvma_op* e);
   void visit_defined_op (defined_op* e);
   void visit_entry_op (entry_op* e);
   void visit_perf_op (perf_op* e);
@@ -1175,6 +1187,7 @@ struct varuse_collecting_visitor: public functioncall_traversing_visitor
   void visit_cast_op (cast_op* e);
   void visit_autocast_op (autocast_op* e);
   void visit_atvar_op (atvar_op *e);
+  void visit_atvma_op (atvma_op* e);
   void visit_defined_op (defined_op* e);
   void visit_entry_op (entry_op* e);
   void visit_perf_op (perf_op* e);
@@ -1236,6 +1249,7 @@ struct throwing_visitor: public visitor
   void visit_cast_op (cast_op* e);
   void visit_autocast_op (autocast_op* e);
   void visit_atvar_op (atvar_op* e);
+  void visit_atvma_op (atvma_op* e);
   void visit_defined_op (defined_op* e);
   void visit_entry_op (entry_op* e);
   void visit_perf_op (perf_op* e);
@@ -1350,6 +1364,7 @@ struct update_visitor: public visitor
   virtual void visit_cast_op (cast_op* e);
   virtual void visit_autocast_op (autocast_op* e);
   virtual void visit_atvar_op (atvar_op* e);
+  virtual void visit_atvma_op (atvma_op* e);
   virtual void visit_defined_op (defined_op* e);
   virtual void visit_entry_op (entry_op* e);
   virtual void visit_perf_op (perf_op* e);
@@ -1418,6 +1433,7 @@ struct deep_copy_visitor: public update_visitor
   virtual void visit_cast_op (cast_op* e);
   virtual void visit_autocast_op (autocast_op* e);
   virtual void visit_atvar_op (atvar_op* e);
+  virtual void visit_atvma_op (atvma_op* e);
   virtual void visit_defined_op (defined_op* e);
   virtual void visit_entry_op (entry_op* e);
   virtual void visit_perf_op (perf_op* e);
diff --git a/tapsets.cxx b/tapsets.cxx
index 509e45748..70dd1f45c 100644
--- a/tapsets.cxx
+++ b/tapsets.cxx
@@ -2858,6 +2858,7 @@ struct dwarf_var_expanding_visitor: public var_expanding_visitor
   void visit_target_symbol_context (target_symbol* e);
   void visit_target_symbol (target_symbol* e);
   void visit_atvar_op (atvar_op* e);
+  void visit_atvma_op (atvma_op* e);
   void visit_cast_op (cast_op* e);
   void visit_entry_op (entry_op* e);
   void visit_perf_op (perf_op* e);
@@ -4438,6 +4439,17 @@ dwarf_var_expanding_visitor::visit_atvar_op (atvar_op *e)
 
 
 void
+dwarf_var_expanding_visitor::visit_atvma_op (atvma_op *e)
+{
+  // Fill in our current module context if needed
+  if (e->module.empty())
+    e->module = q.dw.module_name;
+
+  var_expanding_visitor::visit_atvma_op(e);
+}
+
+
+void
 dwarf_var_expanding_visitor::visit_target_symbol (target_symbol *e)
 {
   assert(e->name.size() > 0 && (e->name[0] == '$' || e->name == "@var"));
@@ -5138,6 +5150,93 @@ dwarf_atvar_expanding_visitor::visit_atvar_op (atvar_op* e)
 }
 
 
+struct dwarf_atvma_expanding_visitor: public var_expanding_visitor
+{
+  dwarf_builder& db;
+
+  dwarf_atvma_expanding_visitor(systemtap_session& s, dwarf_builder& db):
+    var_expanding_visitor(s), db(db) {}
+  void visit_atvma_op (atvma_op* e);
+};
+
+
+struct dwarf_atvma_query: public base_query
+{
+  atvma_op& e;
+  const bool userspace_p;
+  bool &found;
+
+  dwarf_atvma_query(dwflpp& dw, const string& module, atvma_op& e,
+                    const bool userspace_p, bool &found):
+    base_query(dw, module), e(e),
+    userspace_p(userspace_p), found(found) {}
+
+  void handle_query_module ();
+  void query_library (const char *) {}
+  void query_plt (const char *, size_t) {}
+};
+
+
+void
+dwarf_atvma_query::handle_query_module ()
+{
+  found = true;
+}
+
+
+void
+dwarf_atvma_expanding_visitor::visit_atvma_op (atvma_op* e)
+{
+  const bool lvalue = is_active_lvalue(e);
+  if (lvalue)
+    throw SEMANTIC_ERROR(_("writing to @vma variable not permitted"), e->tok);
+
+  if (e->module.empty())
+    e->module = "kernel";
+
+  bool found_module = false;
+
+  bool userspace_p = false;
+
+  // @vma() does not allow multiple modules separated by ':'.
+  // we check this in the parser.
+  string module = e->module;
+
+  dwflpp* dw;
+  userspace_p = is_user_module(module);
+  if (!userspace_p)
+    {
+      // kernel or kernel module target
+      dw = db.get_kern_dw(sess, module);
+    }
+  else
+    {
+      module = find_executable(module, "", sess.sysenv);
+      dw = db.get_user_dw(sess, module);
+    }
+
+  dwarf_atvma_query q (*dw, module, *e, userspace_p, found_module);
+  dw->iterate_over_modules<base_query>(&query_module, &q);
+
+  if (found_module)
+    {
+      sess.unwindsym_modules.insert(module);
+
+      expression* result = dw->translate_address(e->address, e->tok);
+
+      if (result)
+	{
+	  result->visit(this);
+	  return;
+	}
+    }
+
+  throw SEMANTIC_ERROR (_F("unable to find module '%s'",  module.c_str()),
+			e->tok);
+  provide(e);
+}
+
+
 void
 dwarf_derived_probe::printsig (ostream& o) const
 {
@@ -5836,6 +5935,9 @@ dwarf_derived_probe::register_patterns(systemtap_session& s)
   filter = new dwarf_atvar_expanding_visitor(s, *dw);
   s.code_filters.push_back(filter);
 
+  filter = new dwarf_atvma_expanding_visitor(s, *dw);
+  s.code_filters.push_back(filter);
+
   register_function_and_statement_variants(s, root->bind(TOK_KERNEL), dw, pr_privileged);
   register_function_and_statement_variants(s, root->bind_str(TOK_MODULE), dw, pr_privileged);
   root->bind(TOK_KERNEL)->bind_num(TOK_STATEMENT)->bind(TOK_ABSOLUTE)
diff --git a/testsuite/lib/test_simple.exp b/testsuite/lib/test_simple.exp
index ef3661278..589f92239 100644
--- a/testsuite/lib/test_simple.exp
+++ b/testsuite/lib/test_simple.exp
@@ -138,3 +138,86 @@ proc nok { test_name result } {
     pass "${test_name}: sould NOT be ok: $result"
     return 1
 }
+
+# process_template_file TEMPLATE_FILE OUT_FILE BIN_FILE ERR_VAR
+# Processes the template file specified by the TEMPLATE_FILE argument,
+# expands special macro variables in the template file, and genetates the final
+# .stp file specified by the OUT_FILE argument.
+# The following macro variables are supported:
+# * $^PWD for the current working directory
+# * $^ADDR_NAME for the hex address (without the 0x prefix) for the symbol
+#   named NAME by inspecting the binary target program file BIN_FILE via the
+#   nm utility.
+# Returns 1 when all macro variables are expanded successfully; 0 otherwise.
+# In case of error, the error message will be returned in the variable with
+# the name specified by the 4th argument.
+
+proc process_template_file { template_file out_file bin_file err_var } {
+    upvar 1 $err_var err
+
+    set in [open $template_file r]
+    set src [read $in]
+    close $in
+
+    set cwd [pwd]
+    regsub -all -- {\$\^PWD\y} $src $cwd src
+
+    set matches [regexp -all -inline -- {\$\^ADDR_([_a-zA-Z]\w*)} $src]
+
+    set nsyms 0
+    if {[llength $matches] > 0} {
+        array set names {}
+        foreach {match, name} $matches {
+            if {! [info exists names($name)]} {
+                incr nsyms
+                set names($name) 1
+            }
+        }
+
+        set nm_fh [open "| nm --defined-only ./a.out" r]
+        set hits 0
+        while {[gets $nm_fh line] >= 0} {
+            if {[regexp -- {^\s*([0-9a-f]+)\s+[DT]\s+([_a-zA-Z]\w*)\s*$} $line \
+                    match addr name]} {
+                if {! [info exists names($name)]} {
+                    continue
+                }
+                unset names($name)
+                send_log "found symbol '$name' at addr $addr in the nm output\n"
+                regsub -all -- "\\$\\^ADDR_$name\\y" $src $addr src
+                if {[incr hits] == $nsyms} {
+                    break
+                }
+            }
+        }
+
+        if {[catch {close $nm_fh} nm_err] != 0} {
+            global errorCode
+            if {[lindex $errorCode 0] eq "CHILDSTATUS"} {
+                if {[lindex $errorCode 2] != 0} {
+                    set err "failed to run nm: $nm_err"
+                    return 0
+                }
+            }
+        }
+
+        if {$hits < $nsyms} {
+            set unmatched_names [array names names]
+            if {[llength $unmatched_names] == 1} {
+                set sym [lindex $unmatched_names 0]
+                set err "symbol '$sym' not found in binary file $bin_file"
+                return 0
+            }
+
+            set sym_list [join [lmap e [array names names] { expr {"'$e'"} }] ", "]
+            set err "symbols $sym_list not found in binary file $bin_file"
+            return 0
+        }
+    }
+
+    set out [open $out_file w+]
+    puts -nonewline $out $src
+    close $out
+
+    return 1
+}
diff --git a/testsuite/systemtap.base/at_vma.exp b/testsuite/systemtap.base/at_vma.exp
new file mode 100644
index 000000000..9927026cf
--- /dev/null
+++ b/testsuite/systemtap.base/at_vma.exp
@@ -0,0 +1,548 @@
+set test "at_vma"
+set testpath "$srcdir/$subdir"
+
+if {! [installtest_p]} { untested "$test"; return }
+if {! [uretprobes_p]} { untested "$test"; return }
+
+# --- TEST 1 ---
+
+set subtest1 "TEST 1: @vma - not PIE - in func - explicit module"
+
+set res [target_compile ${testpath}/${test}_1.c ./a.out executable \
+    "additional_flags=-O additional_flags=-g"]
+if {$res ne ""} {
+    verbose "target_compile failed: $res" 2
+    fail "$test: $subtest1: unable to compile ${test}_1.c"
+} else {
+    if {! [process_template_file "$srcdir/$subdir/${test}_1.stp" \
+                                 "./${test}_1.stp" "./a.out" nm_err]} {
+        fail "$test: $subtest1: $nm_err"
+    } else {
+        foreach runtime [get_runtime_list] {
+            if {$runtime eq ""} {
+                set runtime "kernel"
+            }
+            set test_name "$test: $subtest1 ($runtime)"
+            set exit_code [run_cmd_2way "stap --runtime=$runtime -c ./a.out './${test}_1.stp'" \
+                                        out stderr]
+            set exp_out "0xbeefdead
+1
+"
+            is "${test_name}: stdout" $out $exp_out
+            is "${test_name}: exit code" $exit_code 0
+            if {$stderr ne ""} {
+                send_log "stderr:\n$stderr"
+            }
+        }
+    }
+}
+
+# --- TEST 2 ---
+
+set subtest2 "TEST 2: @vma - not PIE - in func - no module"
+
+set res [target_compile ${testpath}/${test}_1.c ./a.out executable \
+    "additional_flags=-O additional_flags=-g"]
+if {$res ne ""} {
+    verbose "target_compile failed: $res" 2
+    fail "$test: $subtest2: unable to compile ${test}_1.c"
+} else {
+    if {! [process_template_file "$srcdir/$subdir/${test}_2.stp" \
+                                 "./${test}_2.stp" "./a.out" nm_err]} {
+        fail "$test: $subtest2: $nm_err"
+    } else {
+        set test_name "$test: $subtest2"
+
+        set exit_code [run_cmd_2way "stap -c ./a.out './${test}_2.stp'" \
+                                    out stderr]
+        set exp_out "0x0\n"
+        is "${test_name}: stdout" $out $exp_out
+        is "${test_name}: exit code" $exit_code 0
+        if {$stderr ne ""} {
+            send_log "stderr:\n$stderr"
+        }
+    }
+}
+
+# --- TEST 3 ---
+
+set subtest3 "TEST 3: @vma - not PIE - in probe - explicit module"
+
+set res [target_compile ${testpath}/${test}_1.c ./a.out executable \
+    "additional_flags=-O additional_flags=-g"]
+if {$res ne ""} {
+    verbose "target_compile failed: $res" 2
+    fail "$test: $subtest3: unable to compile ${test}_1.c"
+} else {
+    if {! [process_template_file "$srcdir/$subdir/${test}_3.stp" \
+                                 "./${test}_3.stp" "./a.out" nm_err]} {
+        fail "$test: $subtest3: $nm_err"
+    } else {
+        foreach runtime [get_runtime_list] {
+            if {$runtime eq ""} {
+                set runtime "kernel"
+            }
+            set test_name "$test: $subtest3 ($runtime)"
+            set exit_code [run_cmd_2way "stap --runtime=$runtime -c ./a.out './${test}_3.stp'" \
+                                        out stderr]
+            set exp_out "0xbeefdead
+1
+"
+            is "${test_name}: stdout" $out $exp_out
+            is "${test_name}: exit code" $exit_code 0
+            if {$stderr ne ""} {
+                send_log "stderr:\n$stderr"
+            }
+        }
+    }
+}
+
+# --- TEST 4 ---
+
+set subtest4 "TEST 4: @vma - not PIE - in probe - no module arg"
+
+set res [target_compile ${testpath}/${test}_1.c ./a.out executable \
+    "additional_flags=-O additional_flags=-g"]
+if {$res ne ""} {
+    verbose "target_compile failed: $res" 2
+    fail "$test: $subtest4: unable to compile ${test}_1.c"
+} else {
+    if {! [process_template_file "$srcdir/$subdir/${test}_4.stp" \
+                                 "./${test}_4.stp" "./a.out" nm_err]} {
+        fail "$test: $subtest4: $nm_err"
+    } else {
+        foreach runtime [get_runtime_list] {
+            if {$runtime eq ""} {
+                set runtime "kernel"
+            }
+            set test_name "$test: $subtest4 ($runtime)"
+            set exit_code [run_cmd_2way "stap --runtime=$runtime -c ./a.out './${test}_4.stp'" \
+                                        out stderr]
+            set exp_out "0xbeefdead
+1
+"
+            is "${test_name}: stdout" $out $exp_out
+            is "${test_name}: exit code" $exit_code 0
+            if {$stderr ne ""} {
+                send_log "stderr:\n$stderr"
+            }
+        }
+    }
+}
+
+# --- TEST 5 ---
+
+set subtest5 "TEST 5: @vma - PIE - in func - explicit module"
+
+set res [target_compile ${testpath}/${test}_1.c ./a.out executable \
+    "additional_flags=-O additional_flags=-g additional_flags=-fpic additional_flags=-pie"]
+if {$res ne ""} {
+    verbose "target_compile failed: $res" 2
+    fail "$test: $subtest5: unable to compile ${test}_1.c"
+} else {
+    if {! [process_template_file "$srcdir/$subdir/${test}_5.stp" \
+                                 "./${test}_5.stp" "./a.out" nm_err]} {
+        fail "$test: $subtest5: $nm_err"
+    } else {
+        foreach runtime [get_runtime_list] {
+            if {$runtime eq ""} {
+                set runtime "kernel"
+            }
+            set test_name "$test: $subtest5 ($runtime)"
+            set exit_code [run_cmd_2way "stap --runtime=$runtime -c ./a.out './${test}_5.stp'" \
+                                        out stderr]
+            if {$runtime eq "dyninst"} {
+                set exp_out ""
+                is "${test_name}: stdout" $out $exp_out
+            } else {
+                set exp_out "0
+0xbeefdead
+"
+                is "${test_name}: stdout" $out $exp_out
+            }
+            if {$runtime eq "dyninst"} {
+                isnt "${test_name}: exit code" $exit_code 0
+                set stderr_pat "semantic error: VMA-tracking is only supported by the kernel runtime \\(PR15052\\): operator '\\@vma' at .*?\\.stp:2:20"
+                like "${test_name}: stderr" $stderr $stderr_pat "-linestop"
+            } else {
+                is "${test_name}: exit code" $exit_code 0
+            }
+        }
+    }
+}
+
+# --- TEST 6 ---
+
+set subtest6 "TEST 6: @vma - PIE - in probe - explicit module"
+
+set res [target_compile ${testpath}/${test}_1.c ./a.out executable \
+    "additional_flags=-O additional_flags=-g additional_flags=-fpic additional_flags=-pie"]
+if {$res ne ""} {
+    verbose "target_compile failed: $res" 2
+    fail "$test: $subtest6: unable to compile ${test}_1.c"
+} else {
+    if {! [process_template_file "$srcdir/$subdir/${test}_6.stp" \
+                                 "./${test}_6.stp" "./a.out" nm_err]} {
+        fail "$test: $subtest6: $nm_err"
+    } else {
+        foreach runtime [get_runtime_list] {
+            if {$runtime eq ""} {
+                set runtime "kernel"
+            }
+            set test_name "$test: $subtest6 ($runtime)"
+            set exit_code [run_cmd_2way "stap --runtime=$runtime -c ./a.out './${test}_6.stp'" \
+                                        out stderr]
+            if {$runtime eq "dyninst"} {
+                set exp_out ""
+                is "${test_name}: stdout" $out $exp_out
+            } else {
+                set exp_out "0xbeefdead
+0
+"
+                is "${test_name}: stdout" $out $exp_out
+            }
+            if {$runtime eq "dyninst"} {
+                isnt "${test_name}: exit code" $exit_code 0
+                set stderr_pat "semantic error: VMA-tracking is only supported by the kernel runtime \\(PR15052\\): operator '\\@vma' at .*?\\.stp:2:31"
+                like "${test_name}: stderr" $stderr $stderr_pat "-linestop"
+            } else {
+                is "${test_name}: exit code" $exit_code 0
+            }
+        }
+    }
+}
+
+# --- TEST 7 ---
+
+set subtest7 "TEST 7: @vma - PIE - in probe - no module arg"
+
+set res [target_compile ${testpath}/${test}_1.c ./a.out executable \
+    "additional_flags=-O additional_flags=-g additional_flags=-fpic additional_flags=-pie"]
+if {$res ne ""} {
+    verbose "target_compile failed: $res" 2
+    fail "$test: $subtest7: unable to compile ${test}_1.c"
+} else {
+    if {! [process_template_file "$srcdir/$subdir/${test}_7.stp" \
+                                 "./${test}_7.stp" "./a.out" nm_err]} {
+        fail "$test: $subtest7: $nm_err"
+    } else {
+        foreach runtime [get_runtime_list] {
+            if {$runtime eq ""} {
+                set runtime "kernel"
+            }
+            set test_name "$test: $subtest7 ($runtime)"
+            set exit_code [run_cmd_2way "stap --runtime=$runtime -c ./a.out './${test}_7.stp'" \
+                                        out stderr]
+            if {$runtime eq "dyninst"} {
+                set exp_out ""
+                is "${test_name}: stdout" $out $exp_out
+            } else {
+                set exp_out "0xbeefdead
+0
+"
+                is "${test_name}: stdout" $out $exp_out
+            }
+            if {$runtime eq "dyninst"} {
+                isnt "${test_name}: exit code" $exit_code 0
+                set stderr_pat "semantic error: VMA-tracking is only supported by the kernel runtime \\(PR15052\\): operator '\\@vma' at .*?\\.stp:2:31"
+                like "${test_name}: stderr" $stderr $stderr_pat "-linestop"
+            } else {
+                is "${test_name}: exit code" $exit_code 0
+            }
+        }
+    }
+}
+
+# --- TEST 8 ---
+
+set subtest8 "TEST 8: take address of @vma"
+
+set res [target_compile ${testpath}/${test}_1.c ./a.out executable \
+    "additional_flags=-O additional_flags=-g additional_flags=-fpic additional_flags=-pie"]
+if {$res ne ""} {
+    verbose "target_compile failed: $res" 2
+    fail "$test: $subtest8: unable to compile ${test}_1.c"
+} else {
+    if {! [process_template_file "$srcdir/$subdir/${test}_8.stp" \
+                                 "./${test}_8.stp" "./a.out" nm_err]} {
+        fail "$test: $subtest8: $nm_err"
+    } else {
+        foreach runtime [get_runtime_list] {
+            if {$runtime eq ""} {
+                set runtime "kernel"
+            }
+            set test_name "$test: $subtest8 ($runtime)"
+            set exit_code [run_cmd_2way "stap --runtime=$runtime -c ./a.out './${test}_8.stp'" \
+                                        out stderr]
+            set exp_out ""
+            is "${test_name}: stdout" $out $exp_out
+            isnt "${test_name}: exit code" $exit_code 0
+            set stderr_pat "parse error: cannot take address of \\@vma
+\\s+at: operator '\\&' at .*?\\.stp:2:20\$"
+            like "${test_name}: stderr" $stderr $stderr_pat "-lineanchor"
+        }
+    }
+}
+
+# --- TEST 9 ---
+
+set subtest9 "TEST 9: @vma - multiple module values separated by :"
+
+set res [target_compile ${testpath}/${test}_1.c ./a.out executable \
+    "additional_flags=-O additional_flags=-g"]
+if {$res ne ""} {
+    verbose "target_compile failed: $res" 2
+    fail "$test: $subtest9: unable to compile ${test}_1.c"
+} else {
+    if {! [process_template_file "$srcdir/$subdir/${test}_9.stp" \
+                                 "./${test}_9.stp" "./a.out" nm_err]} {
+        fail "$test: $subtest9: $nm_err"
+    } else {
+        foreach runtime [get_runtime_list] {
+            if {$runtime eq ""} {
+                set runtime "kernel"
+            }
+            set test_name "$test: $subtest9 ($runtime)"
+            set exit_code [run_cmd_2way "stap --runtime=$runtime -c ./a.out './${test}_9.stp'" \
+                                        out stderr]
+            set exp_out ""
+            is "${test_name}: stdout" $out $exp_out
+            isnt "${test_name}: exit code" $exit_code 0
+            set stderr_pat "\\yparse error: \\@vma\\(\\) cannot take multiple module values
+\\s+at: string '\[^'\\n\]*?/a\\.out:\[^'\\n\]*?/a\\.out' at .*?\\.stp:2:47
+"
+            like "${test_name}: stderr" $stderr $stderr_pat "-lineanchor"
+        }
+    }
+}
+
+# --- TEST 10 ---
+
+set subtest10 "TEST 10: @vma - print - with module"
+
+set res [target_compile ${testpath}/${test}_1.c ./a.out executable \
+    "additional_flags=-O additional_flags=-g"]
+if {$res ne ""} {
+    verbose "target_compile failed: $res" 2
+    fail "$test: $subtest10: unable to compile ${test}_1.c"
+} else {
+    if {! [process_template_file "$srcdir/$subdir/${test}_10.stp" \
+                                 "./${test}_10.stp" "./a.out" nm_err]} {
+        fail "$test: $subtest10: $nm_err"
+    } else {
+        foreach runtime [get_runtime_list] {
+            if {$runtime eq ""} {
+                set runtime "kernel"
+            }
+            set test_name "$test: $subtest10 ($runtime)"
+            set exit_code [run_cmd_2way "stap -p1 --runtime=$runtime -c ./a.out './${test}_10.stp'" \
+                                        out stderr]
+            set out_pat "\\yreturn user_long\\(\\@vma\\(0x\[0-9a-f\]+, \"\[^\\n\"\]*?/a\\.out\"\\)\\);\$"
+            like "${test_name}: stdout" $out $out_pat "-lineanchor"
+            is "${test_name}: exit code" $exit_code 0
+            if {$stderr ne ""} {
+                send_log "stderr:\n$stderr"
+            }
+        }
+    }
+}
+
+# --- TEST 11 ---
+
+set subtest11 "TEST 11: @vma - print - without module"
+
+set res [target_compile ${testpath}/${test}_1.c ./a.out executable \
+    "additional_flags=-O additional_flags=-g"]
+if {$res ne ""} {
+    verbose "target_compile failed: $res" 2
+    fail "$test: $subtest11: unable to compile ${test}_1.c"
+} else {
+    if {! [process_template_file "$srcdir/$subdir/${test}_11.stp" \
+                                 "./${test}_11.stp" "./a.out" nm_err]} {
+        fail "$test: $subtest11: $nm_err"
+    } else {
+        foreach runtime [get_runtime_list] {
+            if {$runtime eq ""} {
+                set runtime "kernel"
+            }
+            set test_name "$test: $subtest11 ($runtime)"
+            set exit_code [run_cmd_2way "stap -p1 --runtime=$runtime -c ./a.out './${test}_11.stp'" \
+                                        out stderr]
+            set out_pat ", user_long\\(\\@vma\\(0x\[0-9a-f\]+\\)\\)\\);\$"
+            like "${test_name}: stdout" $out $out_pat "-lineanchor"
+            is "${test_name}: exit code" $exit_code 0
+            if {$stderr ne ""} {
+                send_log "stderr:\n$stderr"
+            }
+        }
+    }
+}
+
+# --- TEST 12 ---
+
+set subtest12 "TEST 12: @vma - not PIE - in func - explicit module - no DWARF"
+
+set res [target_compile ${testpath}/${test}_1.c ./a.out executable \
+    "additional_flags=-O"]
+if {$res ne ""} {
+    verbose "target_compile failed: $res" 2
+    fail "$test: $subtest12: unable to compile ${test}_1.c"
+} else {
+    if {! [process_template_file "$srcdir/$subdir/${test}_12.stp" \
+                                 "./${test}_12.stp" "./a.out" nm_err]} {
+        fail "$test: $subtest12: $nm_err"
+    } else {
+        foreach runtime [get_runtime_list] {
+            if {$runtime eq ""} {
+                set runtime "kernel"
+            }
+            set test_name "$test: $subtest12 ($runtime)"
+            set exit_code [run_cmd_2way "stap --runtime=$runtime -c ./a.out './${test}_12.stp'" \
+                                        out stderr]
+            set exp_out "0xbeefdead
+no var v found
+1
+"
+            is "${test_name}: stdout" $out $exp_out
+            is "${test_name}: exit code" $exit_code 0
+            if {$stderr ne ""} {
+                send_log "stderr:\n$stderr"
+            }
+        }
+    }
+}
+
+# --- TEST 13 ---
+
+set subtest13 "TEST 13: @vma - PIE - in func - explicit module - no DWARF"
+
+set res [target_compile ${testpath}/${test}_1.c ./a.out executable \
+    "additional_flags=-O additional_flags=-fpic additional_flags=-pie"]
+if {$res ne ""} {
+    verbose "target_compile failed: $res" 2
+    fail "$test: $subtest13: unable to compile ${test}_1.c"
+} else {
+    if {! [process_template_file "$srcdir/$subdir/${test}_13.stp" \
+                                 "./${test}_13.stp" "./a.out" nm_err]} {
+        fail "$test: $subtest13: $nm_err"
+    } else {
+        foreach runtime [get_runtime_list] {
+            if {$runtime eq ""} {
+                set runtime "kernel"
+            }
+            set test_name "$test: $subtest13 ($runtime)"
+            set exit_code [run_cmd_2way "stap --runtime=$runtime -c ./a.out './${test}_13.stp'" \
+                                        out stderr]
+            if {$runtime eq "dyninst"} {
+                set exp_out ""
+                is "${test_name}: stdout" $out $exp_out
+            } else {
+                set exp_out "no var v found
+0
+0xbeefdead
+"
+                is "${test_name}: stdout" $out $exp_out
+            }
+            if {$runtime eq "dyninst"} {
+                isnt "${test_name}: exit code" $exit_code 0
+                set stderr_pat "semantic error: VMA-tracking is only supported by the kernel runtime \\(PR15052\\): operator '\\@vma' at .*?\\.stp:2:20"
+                like "${test_name}: stderr" $stderr $stderr_pat "-linestop"
+            } else {
+                is "${test_name}: exit code" $exit_code 0
+            }
+        }
+    }
+}
+
+# --- TEST 14 ---
+
+set subtest14 "TEST 14: @vma cannot be used as an lvalue"
+
+set res [target_compile ${testpath}/${test}_14.c ./a.out executable \
+    "additional_flags=-O additional_flags=-g"]
+if {$res ne ""} {
+    verbose "target_compile failed: $res" 2
+    fail "$test: $subtest14: unable to compile ${test}_14.c"
+} else {
+    if {! [process_template_file "$srcdir/$subdir/${test}_14.stp" \
+                                 "./${test}_14.stp" "./a.out" nm_err]} {
+        fail "$test: $subtest14: $nm_err"
+    } else {
+        foreach runtime [get_runtime_list] {
+            if {$runtime eq ""} {
+                set runtime "kernel"
+            }
+            set test_name "$test: $subtest14 ($runtime)"
+            set exit_code [run_cmd_2way "stap --runtime=$runtime -c ./a.out './${test}_14.stp'" \
+                                        out stderr]
+            set exp_out ""
+            is "${test_name}: stdout" $out $exp_out
+            isnt "${test_name}: exit code" $exit_code 0
+            set stderr_pat "^semantic error: writing to \\@vma variable not permitted: operator '\\@vma' at \[^\\n\]*?\\.stp:2:5
+        source:     \\@vma\\(0x0000000000601020\\) = 32;
+                    \\^
+"
+            like "${test_name}: stderr" $stderr $stderr_pat "-linestop"
+        }
+    }
+}
+
+# --- TEST 15 ---
+
+set subtest15 "TEST 15: @vma present in 4.0"
+
+set res [target_compile ${testpath}/${test}_15.c ./a.out executable \
+    "additional_flags=-O additional_flags=-g"]
+if {$res ne ""} {
+    verbose "target_compile failed: $res" 2
+    fail "$test: $subtest15: unable to compile ${test}_15.c"
+} else {
+    if {! [process_template_file "$srcdir/$subdir/${test}_15.stp" \
+                                 "./${test}_15.stp" "./a.out" nm_err]} {
+        fail "$test: $subtest15: $nm_err"
+    } else {
+        foreach runtime [get_runtime_list] {
+            if {$runtime eq ""} {
+                set runtime "kernel"
+            }
+            set test_name "$test: $subtest15 ($runtime)"
+            set exit_code [run_cmd_2way "stap --compatible 4.0 --runtime=$runtime -c ./a.out './${test}_15.stp'" \
+                                        out stderr]
+            set exp_out "3\n"
+            is "${test_name}: stdout" $out $exp_out
+            set exp_stderr ""
+            is "${test_name}: stderr" $stderr $exp_stderr
+            is "${test_name}: exit code" $exit_code 0
+        }
+    }
+}
+
+# --- TEST 16 ---
+
+set subtest16 "TEST 16: @vma not present in 3.3"
+
+set res [target_compile ${testpath}/${test}_15.c ./a.out executable \
+    "additional_flags=-O additional_flags=-g"]
+if {$res ne ""} {
+    verbose "target_compile failed: $res" 2
+    fail "$test: $subtest16: unable to compile ${test}_15.c"
+} else {
+    if {! [process_template_file "$srcdir/$subdir/${test}_16.stp" \
+                                 "./${test}_16.stp" "./a.out" nm_err]} {
+        fail "$test: $subtest16: $nm_err"
+    } else {
+        foreach runtime [get_runtime_list] {
+            if {$runtime eq ""} {
+                set runtime "kernel"
+            }
+            set test_name "$test: $subtest16 ($runtime)"
+            set exit_code [run_cmd_2way "stap --compatible 3.3 --runtime=$runtime -c ./a.out './${test}_16.stp'" \
+                                        out stderr]
+            set exp_out ""
+            is "${test_name}: stdout" $out $exp_out
+            isnt "${test_name}: exit code" $exit_code 0
+            set stderr_pat "^parse error: unknown operator \\@vma\$"
+            like "${test_name}: stderr" $stderr $stderr_pat "-linestop -lineanchor"
+        }
+    }
+}
diff --git a/testsuite/systemtap.base/at_vma_1.c b/testsuite/systemtap.base/at_vma_1.c
new file mode 100644
index 000000000..8c3b7bc46
--- /dev/null
+++ b/testsuite/systemtap.base/at_vma_1.c
@@ -0,0 +1,5 @@
+long v = 0xbeefdead;
+
+int main(void) {
+    return 0;
+}
diff --git a/testsuite/systemtap.base/at_vma_1.stp b/testsuite/systemtap.base/at_vma_1.stp
new file mode 100644
index 000000000..857257ad1
--- /dev/null
+++ b/testsuite/systemtap.base/at_vma_1.stp
@@ -0,0 +1,9 @@
+function f() {
+    return user_long(@vma(0x$^ADDR_v, "$^PWD/a.out"));
+}
+
+probe process.function("main") {
+    printf("%#x\n", f());
+    printf("%d\n", @vma(0x$^ADDR_v, "$^PWD/a.out") == 0x$^ADDR_v);
+    exit();
+}
diff --git a/testsuite/systemtap.base/at_vma_10.stp b/testsuite/systemtap.base/at_vma_10.stp
new file mode 100644
index 000000000..67a45e7b3
--- /dev/null
+++ b/testsuite/systemtap.base/at_vma_10.stp
@@ -0,0 +1,8 @@
+function f() {
+    return user_long(@vma(0x$^ADDR_v, "$^PWD/a.out"));
+}
+
+probe process.function("main") {
+    printf("%#x\n", f());
+    exit();
+}
diff --git a/testsuite/systemtap.base/at_vma_11.stp b/testsuite/systemtap.base/at_vma_11.stp
new file mode 100644
index 000000000..f9e35c5ea
--- /dev/null
+++ b/testsuite/systemtap.base/at_vma_11.stp
@@ -0,0 +1,4 @@
+probe process.function("main") {
+    printf("%#x\n", user_long(@vma(0x$^ADDR_v)));
+    exit();
+}
diff --git a/testsuite/systemtap.base/at_vma_12.stp b/testsuite/systemtap.base/at_vma_12.stp
new file mode 100644
index 000000000..eaae7299f
--- /dev/null
+++ b/testsuite/systemtap.base/at_vma_12.stp
@@ -0,0 +1,12 @@
+function f() {
+    return user_long(@vma(0x$^ADDR_v, "$^PWD/a.out"));
+}
+
+probe process.function("main") {
+    printf("%#x\n", f());
+    if (!@defined(@var("v"))) {
+        println("no var v found");
+    }
+    printf("%d\n", @vma(0x$^ADDR_v, "$^PWD/a.out") == 0x$^ADDR_v);
+    exit();
+}
diff --git a/testsuite/systemtap.base/at_vma_13.stp b/testsuite/systemtap.base/at_vma_13.stp
new file mode 100644
index 000000000..d49090382
--- /dev/null
+++ b/testsuite/systemtap.base/at_vma_13.stp
@@ -0,0 +1,12 @@
+function f() {
+    printf("%d\n", @vma(0x$^ADDR_v, "$^PWD/a.out") == 0x$^ADDR_v);
+    return user_long(@vma(0x$^ADDR_v, "$^PWD/a.out"));
+}
+
+probe process.function("main") {
+    if (!@defined(@var("v"))) {
+        println("no var v found");
+    }
+    printf("%#x\n", f());
+    exit();
+}
diff --git a/testsuite/systemtap.base/at_vma_14.c b/testsuite/systemtap.base/at_vma_14.c
new file mode 100644
index 000000000..eeca645b9
--- /dev/null
+++ b/testsuite/systemtap.base/at_vma_14.c
@@ -0,0 +1,5 @@
+long v = 0xdeadbeef;
+
+int main(void) {
+    return 0;
+}
diff --git a/testsuite/systemtap.base/at_vma_14.stp b/testsuite/systemtap.base/at_vma_14.stp
new file mode 100644
index 000000000..c0bbd70eb
--- /dev/null
+++ b/testsuite/systemtap.base/at_vma_14.stp
@@ -0,0 +1,3 @@
+probe process.function("main") {
+    @vma(0x$^ADDR_v) = 32;
+}
diff --git a/testsuite/systemtap.base/at_vma_15.c b/testsuite/systemtap.base/at_vma_15.c
new file mode 100644
index 000000000..7e8f661bb
--- /dev/null
+++ b/testsuite/systemtap.base/at_vma_15.c
@@ -0,0 +1,2 @@
+int a = 3;
+int main(void) { return 0; }
diff --git a/testsuite/systemtap.base/at_vma_15.stp b/testsuite/systemtap.base/at_vma_15.stp
new file mode 100644
index 000000000..07da4e8a7
--- /dev/null
+++ b/testsuite/systemtap.base/at_vma_15.stp
@@ -0,0 +1,4 @@
+probe process.function("main") {
+    println((user_int(@vma(0x$^ADDR_a, "$^PWD/a.out"))));
+    exit()
+}
diff --git a/testsuite/systemtap.base/at_vma_16.stp b/testsuite/systemtap.base/at_vma_16.stp
new file mode 100644
index 000000000..be03786cc
--- /dev/null
+++ b/testsuite/systemtap.base/at_vma_16.stp
@@ -0,0 +1,3 @@
+probe process.function("main") {
+    println((user_int(@vma(0x$^ADDR_a, "$^PWD/a.out"))));
+}
diff --git a/testsuite/systemtap.base/at_vma_2.stp b/testsuite/systemtap.base/at_vma_2.stp
new file mode 100644
index 000000000..d579700d8
--- /dev/null
+++ b/testsuite/systemtap.base/at_vma_2.stp
@@ -0,0 +1,8 @@
+function f() {
+    return user_long(@vma(0x$^ADDR_v));
+}
+
+probe process.function("main") {
+    printf("%#x\n", f());
+    exit();
+}
diff --git a/testsuite/systemtap.base/at_vma_3.stp b/testsuite/systemtap.base/at_vma_3.stp
new file mode 100644
index 000000000..1ae731ad2
--- /dev/null
+++ b/testsuite/systemtap.base/at_vma_3.stp
@@ -0,0 +1,5 @@
+probe process.function("main") {
+    printf("%#x\n", user_long(@vma(0x$^ADDR_v, "$^PWD/a.out")));
+    printf("%d\n", @vma(0x$^ADDR_v, "$^PWD/a.out") == 0x$^ADDR_v);
+    exit();
+}
diff --git a/testsuite/systemtap.base/at_vma_4.stp b/testsuite/systemtap.base/at_vma_4.stp
new file mode 100644
index 000000000..01365f11a
--- /dev/null
+++ b/testsuite/systemtap.base/at_vma_4.stp
@@ -0,0 +1,5 @@
+probe process.function("main") {
+    printf("%#x\n", user_long(@vma(0x$^ADDR_v)));
+    printf("%d\n", @vma(0x$^ADDR_v, "$^PWD/a.out") == 0x$^ADDR_v);
+    exit();
+}
diff --git a/testsuite/systemtap.base/at_vma_5.stp b/testsuite/systemtap.base/at_vma_5.stp
new file mode 100644
index 000000000..888733eee
--- /dev/null
+++ b/testsuite/systemtap.base/at_vma_5.stp
@@ -0,0 +1,9 @@
+function f() {
+    printf("%d\n", @vma(0x$^ADDR_v, "$^PWD/a.out") == 0x$^ADDR_v);
+    return user_long(@vma(0x$^ADDR_v, "$^PWD/a.out"));
+}
+
+probe process.function("main") {
+    printf("%#x\n", f());
+    exit();
+}
diff --git a/testsuite/systemtap.base/at_vma_6.stp b/testsuite/systemtap.base/at_vma_6.stp
new file mode 100644
index 000000000..4a90930a1
--- /dev/null
+++ b/testsuite/systemtap.base/at_vma_6.stp
@@ -0,0 +1,5 @@
+probe process.function("main") {
+    printf("%#x\n", user_long(@vma(0x$^ADDR_v, "$^PWD/a.out")));
+    printf("%d\n", @vma(0x$^ADDR_v) == 0x$^ADDR_v);
+    exit();
+}
diff --git a/testsuite/systemtap.base/at_vma_7.stp b/testsuite/systemtap.base/at_vma_7.stp
new file mode 100644
index 000000000..ec8a86142
--- /dev/null
+++ b/testsuite/systemtap.base/at_vma_7.stp
@@ -0,0 +1,5 @@
+probe process.function("main") {
+    printf("%#x\n", user_long(@vma(0x$^ADDR_v)));
+    printf("%d\n", @vma(0x$^ADDR_v) == 0x$^ADDR_v);
+    exit();
+}
diff --git a/testsuite/systemtap.base/at_vma_8.stp b/testsuite/systemtap.base/at_vma_8.stp
new file mode 100644
index 000000000..9a39588a6
--- /dev/null
+++ b/testsuite/systemtap.base/at_vma_8.stp
@@ -0,0 +1,4 @@
+probe process.function("main") {
+    printf("%p\n", &@vma(0x$^ADDR_v));
+    exit();
+}
diff --git a/testsuite/systemtap.base/at_vma_9.stp b/testsuite/systemtap.base/at_vma_9.stp
new file mode 100644
index 000000000..b9ed14775
--- /dev/null
+++ b/testsuite/systemtap.base/at_vma_9.stp
@@ -0,0 +1,8 @@
+function f() {
+    return user_long(@vma(0x$^ADDR_v, "$^PWD/a.out:$^PWD/a.out"));
+}
+
+probe process.function("main") {
+    printf("%#x\n", f());
+    exit();
+}
diff --git a/translate.cxx b/translate.cxx
index ad120db06..959ab48b3 100644
--- a/translate.cxx
+++ b/translate.cxx
@@ -237,6 +237,7 @@ struct c_unparser: public unparser, public visitor
   void visit_cast_op (cast_op* e);
   void visit_autocast_op (autocast_op* e);
   void visit_atvar_op (atvar_op* e);
+  void visit_atvma_op (atvma_op* e);
   void visit_defined_op (defined_op* e);
   void visit_entry_op (entry_op* e);
   void visit_perf_op (perf_op* e);
@@ -5322,6 +5323,13 @@ c_unparser::visit_atvar_op (atvar_op* e)
 
 
 void
+c_unparser::visit_atvma_op (atvma_op* e)
+{
+  throw SEMANTIC_ERROR(_("cannot translate general @vma expression"), e->tok);
+}
+
+
+void
 c_unparser::visit_cast_op (cast_op* e)
 {
   throw SEMANTIC_ERROR(_("cannot translate general @cast expression"), e->tok);
-- 
2.11.0.295.gd7dffce

^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2018-09-05 20:56 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-08-24 22:41 [PATCH] Add new primitive @vma(address, module) Yichun Zhang (agentzh)
2018-09-05 20:56 ` [PATCH v2] Add new operator " Yichun Zhang (agentzh)

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).