diff --git a/contrib/pg_visibility/pg_visibility--1.0.sql b/contrib/pg_visibility/pg_visibility--1.0.sql
index da511e5..d0bdeca 100644
--- a/contrib/pg_visibility/pg_visibility--1.0.sql
+++ b/contrib/pg_visibility/pg_visibility--1.0.sql
@@ -44,9 +44,16 @@ RETURNS record
 AS 'MODULE_PATHNAME', 'pg_visibility_map_summary'
 LANGUAGE C STRICT;
 
+-- Show tupleids of non-frozen tuples if any in frozen pages for a relation.
+CREATE FUNCTION pg_check_visibility(regclass, t_ctid OUT tid)
+RETURNS SETOF tid
+AS 'MODULE_PATHNAME', 'pg_check_visibility'
+LANGUAGE C STRICT;
+
 -- Don't want these to be available to public.
 REVOKE ALL ON FUNCTION pg_visibility_map(regclass, bigint) FROM PUBLIC;
 REVOKE ALL ON FUNCTION pg_visibility(regclass, bigint) FROM PUBLIC;
 REVOKE ALL ON FUNCTION pg_visibility_map(regclass) FROM PUBLIC;
 REVOKE ALL ON FUNCTION pg_visibility(regclass) FROM PUBLIC;
 REVOKE ALL ON FUNCTION pg_visibility_map_summary(regclass) FROM PUBLIC;
+REVOKE ALL ON FUNCTION pg_check_visibility(regclass) FROM PUBLIC;
diff --git a/contrib/pg_visibility/pg_visibility.c b/contrib/pg_visibility/pg_visibility.c
index 5e5c7cc..51663e6 100644
--- a/contrib/pg_visibility/pg_visibility.c
+++ b/contrib/pg_visibility/pg_visibility.c
@@ -25,14 +25,23 @@ typedef struct vbits
 	uint8		bits[FLEXIBLE_ARRAY_MEMBER];
 } vbits;
 
+typedef struct tupleids
+{
+	BlockNumber next;
+	BlockNumber count;
+	ItemPointer tids;
+} tupleids;
+
 PG_FUNCTION_INFO_V1(pg_visibility_map);
 PG_FUNCTION_INFO_V1(pg_visibility_map_rel);
 PG_FUNCTION_INFO_V1(pg_visibility);
 PG_FUNCTION_INFO_V1(pg_visibility_rel);
 PG_FUNCTION_INFO_V1(pg_visibility_map_summary);
+PG_FUNCTION_INFO_V1(pg_check_visibility);
 
 static TupleDesc pg_visibility_tupdesc(bool include_blkno, bool include_pd);
 static vbits *collect_visibility_data(Oid relid, bool include_pd);
+static tupleids *collect_nonfrozen_items(Oid relid);
 
 /*
  * Visibility map information for a single block of a relation.
@@ -259,6 +268,36 @@ pg_visibility_map_summary(PG_FUNCTION_ARGS)
 }
 
 /*
+ * Return the tids of non-frozen tuples present in frozen pages.  All such
+ * tids' indicates corrupt tuples.
+ */
+Datum
+pg_check_visibility(PG_FUNCTION_ARGS)
+{
+	FuncCallContext *funcctx;
+	tupleids   *info;
+
+	if (SRF_IS_FIRSTCALL())
+	{
+		Oid			relid = PG_GETARG_OID(0);
+		MemoryContext oldcontext;
+
+		funcctx = SRF_FIRSTCALL_INIT();
+		oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+		funcctx->user_fctx = collect_nonfrozen_items(relid);
+		MemoryContextSwitchTo(oldcontext);
+	}
+
+	funcctx = SRF_PERCALL_SETUP();
+	info = (tupleids *) funcctx->user_fctx;
+
+	if (info->next < info->count)
+		SRF_RETURN_NEXT(funcctx, PointerGetDatum(&info->tids[info->next++]));
+
+	SRF_RETURN_DONE(funcctx);
+}
+
+/*
  * Helper function to construct whichever TupleDesc we need for a particular
  * call.
  */
@@ -348,3 +387,95 @@ collect_visibility_data(Oid relid, bool include_pd)
 
 	return info;
 }
+
+/*
+ * Collect non frozen items in the frozen pages for a relation.
+ */
+static tupleids *
+collect_nonfrozen_items(Oid relid)
+{
+	Relation	rel;
+	BlockNumber nblocks;
+	tupleids   *info;
+	BlockNumber blkno;
+	uint64		nallocated;
+	uint64		count_non_frozen = 0;
+	Buffer		vmbuffer = InvalidBuffer;
+	BufferAccessStrategy bstrategy = GetAccessStrategy(BAS_BULKREAD);
+
+	rel = relation_open(relid, AccessShareLock);
+
+	nblocks = RelationGetNumberOfBlocks(rel);
+
+	/*
+	 * Guess an initial array size, we don't expect many corrupted tuples, so
+	 * start with smaller number.  Having initial size as MaxHeapTuplesPerPage
+	 * allows us to check whether tids array needs to be enlarged at page
+	 * level rather than at tuple level.
+	 */
+	nallocated = MaxHeapTuplesPerPage;
+	info = palloc0(sizeof(tupleids));
+	info->tids = palloc(nallocated * sizeof(ItemPointerData));
+	info->next = 0;
+
+	for (blkno = 0; blkno < nblocks; ++blkno)
+	{
+		/* Make sure we are interruptible. */
+		CHECK_FOR_INTERRUPTS();
+
+		/* enlarge output array if needed. */
+		if (count_non_frozen >= nallocated)
+		{
+			nallocated *= 2;
+			info->tids = repalloc(info->tids, nallocated * sizeof(ItemPointerData));
+		}
+
+		/* collect the non-frozen tuples on a frozen page. */
+		if (VM_ALL_FROZEN(rel, blkno, &vmbuffer))
+		{
+			Buffer		buffer;
+			Page		page;
+			OffsetNumber offnum,
+						maxoff;
+
+			buffer = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL,
+										bstrategy);
+			LockBuffer(buffer, BUFFER_LOCK_SHARE);
+
+			page = BufferGetPage(buffer);
+			maxoff = PageGetMaxOffsetNumber(page);
+
+			for (offnum = FirstOffsetNumber;
+				 offnum <= maxoff;
+				 offnum = OffsetNumberNext(offnum))
+			{
+				HeapTupleHeader tuphdr;
+				ItemId		itemid;
+
+				itemid = PageGetItemId(page, offnum);
+
+				/* Unused or redirect line pointers are of no interest. */
+				if (!ItemIdIsUsed(itemid) || ItemIdIsRedirected(itemid))
+					continue;
+
+				tuphdr = (HeapTupleHeader) PageGetItem(page, itemid);
+				if (heap_tuple_needs_eventual_freeze(tuphdr))
+				{
+					info->tids[count_non_frozen] = tuphdr->t_ctid;
+					++count_non_frozen;
+				}
+			}
+
+			UnlockReleaseBuffer(buffer);
+		}
+	}
+
+	/* Clean up. */
+	if (vmbuffer != InvalidBuffer)
+		ReleaseBuffer(vmbuffer);
+	relation_close(rel, AccessShareLock);
+
+	info->count = count_non_frozen;
+
+	return info;
+}
