libipt, block: cache the current section

The block decoder needs to find and map the section containing the block's start
IP in the current address-space on every call to pt_blk_next().  The image
maintains an LRU cache so finding the desired section is typically fast.

The operation still requires one additional pt_section_get() and one additional
pt_section_map() to ensure that the section is not unmapped or destroyed while
the block decoder uses it.  Plus the corresponding pt_section_unmap() and
pt_section_put() at the end of pt_blk_next().

Cache the current section across pt_blk_next() calls to avoid this overhead.

We validate that a pt_image_find() would return the cached section before using
it.  This check is much cheaper, though, as it does not require any additional
get/put or map/unmap.

Change-Id: I50ee5e549db0b2ad816c4b5825ae6802503e8bf8
Signed-off-by: Markus Metzger <markus.t.metzger@intel.com>
diff --git a/libipt/internal/include/pt_block_decoder.h b/libipt/internal/include/pt_block_decoder.h
index 9fc3939..cd667a3 100644
--- a/libipt/internal/include/pt_block_decoder.h
+++ b/libipt/internal/include/pt_block_decoder.h
@@ -33,6 +33,35 @@
 #include "pt_image.h"
 #include "pt_retstack.h"
 
+struct pt_section;
+
+
+/* A cached mapped section.
+ *
+ * This caches a single mapped section across pt_blk_next() calls to avoid
+ * repeated get/map and unmap/put of the current section.
+ *
+ * Since we can't guarantee that the image doesn't change between pt_blk_next()
+ * calls, we still need to validate that the cached section is accurate.  This
+ * can be done without additional get/put or map/unmap of the cached section,
+ * though, and is significantly cheaper.
+ */
+struct pt_cached_section {
+	/* The cached section.
+	 *
+	 * The cache is valid if and only if @section is non-NULL.
+	 *
+	 * It needs to be unmapped and put.  Use pt_blk_scache_invalidate() to
+	 * release the cached section and to invalidate the cache.
+	 */
+	struct pt_section *section;
+
+	/* The virtual address at which @section was loaded. */
+	uint64_t laddr;
+
+	/* The section identifier. */
+	int isid;
+};
 
 /* A block decoder.
  *
@@ -50,6 +79,9 @@
 	/* The image. */
 	struct pt_image *image;
 
+	/* The current cached section. */
+	struct pt_cached_section scache;
+
 	/* The current address space. */
 	struct pt_asid asid;
 
diff --git a/libipt/src/pt_block_decoder.c b/libipt/src/pt_block_decoder.c
index ed35040..ef15523 100644
--- a/libipt/src/pt_block_decoder.c
+++ b/libipt/src/pt_block_decoder.c
@@ -47,6 +47,99 @@
 					  struct pt_section *, uint64_t);
 
 
+/* Release a cached section.
+ *
+ * If @scache does not contain a section, this does noting.
+ *
+ * Returns zero on success, a negative error code otherwise.
+ * Returns -pte_internal, if @scache is NULL.
+ */
+static int pt_blk_scache_invalidate(struct pt_cached_section *scache)
+{
+	struct pt_section *section;
+	int errcode;
+
+	if (!scache)
+		return -pte_internal;
+
+	section = scache->section;
+	if (!section)
+		return 0;
+
+	errcode = pt_section_unmap(section);
+	if (errcode < 0)
+		return errcode;
+
+	scache->section = NULL;
+
+	return pt_section_put(section);
+}
+
+/* Cache @section loaded at @laddr identified by @isid in @scache.
+ *
+ * The caller transfers its use- and map-count to @scache.
+ *
+ * Returns zero on success, a negative error code otherwise.
+ * Returns -pte_internal if @scache or @section is NULL.
+ * Returns -pte_internal if another section is already cached.
+ */
+static int pt_blk_cache_section(struct pt_cached_section *scache,
+				struct pt_section *section, uint64_t laddr,
+				int isid)
+{
+	if (!scache || !section)
+		return -pte_internal;
+
+	if (scache->section)
+		return -pte_internal;
+
+	scache->section = section;
+	scache->laddr = laddr;
+	scache->isid = isid;
+
+	return 0;
+}
+
+/* Get @scache's cached section.
+ *
+ * Check whether @scache contains a section that an image lookup of @ip in @asid
+ * would return.  On success, provides the cached section in @psection and its
+ * load address in @pladdr.
+ *
+ * Returns the section's identifier on success, a negative error code otherwise.
+ * Returns -pte_internal if @scache, @psection, or @pladdr is NULL.
+ * Returns -pte_nomap if @scache does not have a section cached.
+ * Returns -pte_nomap if @scache's cached section does not contain @ip.
+ */
+static int pt_blk_cached_section(struct pt_cached_section *scache,
+				 struct pt_section **psection, uint64_t *pladdr,
+				 struct pt_image *image, struct pt_asid *asid,
+				 uint64_t ip)
+{
+	struct pt_section *section;
+	uint64_t laddr;
+	int isid, errcode;
+
+	if (!scache || !psection || !pladdr)
+		return -pte_internal;
+
+
+	section = scache->section;
+	laddr = scache->laddr;
+	isid = scache->isid;
+	if (!section)
+		return -pte_nomap;
+
+	errcode = pt_image_validate(image, asid, ip, section, laddr, isid);
+	if (errcode < 0)
+		return errcode;
+
+	*psection = section;
+	*pladdr = laddr;
+
+	return isid;
+}
+
 static void pt_blk_reset(struct pt_block_decoder *decoder)
 {
 	if (!decoder)
@@ -78,6 +171,8 @@
 	pt_image_init(&decoder->default_image, NULL);
 	decoder->image = &decoder->default_image;
 
+	memset(&decoder->scache, 0, sizeof(decoder->scache));
+
 	pt_blk_reset(decoder);
 
 	return 0;
@@ -88,6 +183,9 @@
 	if (!decoder)
 		return;
 
+	/* Release the cached section so we don't leak it. */
+	(void) pt_blk_scache_invalidate(&decoder->scache);
+
 	pt_image_fini(&decoder->default_image);
 	pt_qry_decoder_fini(&decoder->query);
 }
@@ -2277,21 +2375,42 @@
 	struct pt_block_cache *bcache;
 	struct pt_section *section;
 	uint64_t laddr;
-	int isid, errcode, status;
+	int isid, errcode;
 
 	if (!decoder || !block)
 		return -pte_internal;
 
-	isid = pt_image_find(decoder->image, &section, &laddr, &decoder->asid,
-			     decoder->ip);
+	isid = pt_blk_cached_section(&decoder->scache, &section, &laddr,
+				     decoder->image, &decoder->asid,
+				     decoder->ip);
 	if (isid < 0) {
 		if (isid != -pte_nomap)
 			return isid;
 
-		/* Even if there is no such section in the image, we may still
-		 * read the memory via the callback function.
-		 */
-		return pt_blk_proceed_no_event_uncached(decoder, block);
+		errcode = pt_blk_scache_invalidate(&decoder->scache);
+		if (errcode < 0)
+			return errcode;
+
+		isid = pt_image_find(decoder->image, &section, &laddr,
+				     &decoder->asid, decoder->ip);
+		if (isid < 0) {
+			if (isid != -pte_nomap)
+				return isid;
+
+			/* Even if there is no such section in the image, we may
+			 * still read the memory via the callback function.
+			 */
+			return pt_blk_proceed_no_event_uncached(decoder, block);
+		}
+
+		errcode = pt_section_map(section);
+		if (errcode < 0)
+			goto out_put;
+
+		errcode = pt_blk_cache_section(&decoder->scache, section, laddr,
+					       isid);
+		if (errcode < 0)
+			goto out_unmap;
 	}
 
 	/* We do not switch sections inside a block. */
@@ -2302,35 +2421,15 @@
 		block->isid = isid;
 	}
 
-	errcode = pt_section_map(section);
-	if (errcode < 0)
-		goto out_put;
-
 	bcache = pt_section_bcache(section);
-	if (!bcache) {
-		errcode = pt_section_unmap(section);
-		if (errcode < 0)
-			goto out_put;
-
-		errcode = pt_section_put(section);
-		if (errcode < 0)
-			return errcode;
-
+	if (!bcache)
 		return pt_blk_proceed_no_event_uncached(decoder, block);
-	}
 
-	status = pt_blk_proceed_no_event_cached(decoder, block, bcache,
-						section, laddr);
+	return pt_blk_proceed_no_event_cached(decoder, block, bcache, section,
+					      laddr);
 
-	errcode = pt_section_unmap(section);
-	if (errcode < 0)
-		goto out_put;
-
-	errcode = pt_section_put(section);
-	if (errcode < 0)
-		return errcode;
-
-	return status;
+out_unmap:
+	(void) pt_section_unmap(section);
 
 out_put:
 	(void) pt_section_put(section);