[compiler-rt] r369048 - [GWP-ASan] Implement stack frame compression.

Mitch Phillips via llvm-commits llvm-commits at lists.llvm.org
Thu Aug 15 14:09:09 PDT 2019


Author: hctim
Date: Thu Aug 15 14:09:09 2019
New Revision: 369048

URL: http://llvm.org/viewvc/llvm-project?rev=369048&view=rev
Log:
[GWP-ASan] Implement stack frame compression.

Summary:
This patch introduces stack frame compression to GWP-ASan. Each stack frame is
variable-length integer encoded as the difference between frame[i] and
frame[i - 1]. Furthermore, we use zig-zag encoding on the difference to ensure
that negative differences are also encoded into a relatively small number of
bytes.

Examples of what the compression looks like can be seen in
`gwp_asan/tests/compression.cpp`.

This compression can reduce the memory consumption cost of stack traces by
~50%.

Reviewers: vlad.tsyrklevich

Reviewed By: vlad.tsyrklevich

Subscribers: mgorny, #sanitizers, llvm-commits, eugenis, morehouse

Tags: #sanitizers, #llvm

Differential Revision: https://reviews.llvm.org/D66189

Added:
    compiler-rt/trunk/lib/gwp_asan/stack_trace_compressor.cpp
    compiler-rt/trunk/lib/gwp_asan/stack_trace_compressor.h
    compiler-rt/trunk/lib/gwp_asan/stack_trace_compressor_fuzzer.cpp
    compiler-rt/trunk/lib/gwp_asan/tests/compression.cpp
Modified:
    compiler-rt/trunk/lib/gwp_asan/CMakeLists.txt
    compiler-rt/trunk/lib/gwp_asan/guarded_pool_allocator.cpp
    compiler-rt/trunk/lib/gwp_asan/guarded_pool_allocator.h

Modified: compiler-rt/trunk/lib/gwp_asan/CMakeLists.txt
URL: http://llvm.org/viewvc/llvm-project/compiler-rt/trunk/lib/gwp_asan/CMakeLists.txt?rev=369048&r1=369047&r2=369048&view=diff
==============================================================================
--- compiler-rt/trunk/lib/gwp_asan/CMakeLists.txt (original)
+++ compiler-rt/trunk/lib/gwp_asan/CMakeLists.txt Thu Aug 15 14:09:09 2019
@@ -7,6 +7,7 @@ set(GWP_ASAN_SOURCES
   platform_specific/mutex_posix.cpp
   guarded_pool_allocator.cpp
   random.cpp
+  stack_trace_compressor.cpp
 )
 
 set(GWP_ASAN_HEADERS
@@ -16,6 +17,7 @@ set(GWP_ASAN_HEADERS
   options.h
   options.inc
   random.h
+  stack_trace_compressor.h
 )
 
 # Ensure that GWP-ASan meets the delegated requirements of some supporting
@@ -96,6 +98,18 @@ if (COMPILER_RT_HAS_GWP_ASAN)
       SOURCES optional/backtrace_sanitizer_common.cpp
       ADDITIONAL_HEADERS ${GWP_ASAN_BACKTRACE_HEADERS}
       CFLAGS ${GWP_ASAN_CFLAGS} ${SANITIZER_COMMON_CFLAGS})
+
+  # Build the stack trace compressor fuzzer.
+  add_llvm_executable(stack_trace_compressor_fuzzer
+    stack_trace_compressor_fuzzer.cpp
+    ${GWP_ASAN_SOURCES}
+    ${GWP_ASAN_HEADERS})
+  set_target_properties(stack_trace_compressor_fuzzer
+    PROPERTIES FOLDER "Fuzzers")
+  target_compile_options(stack_trace_compressor_fuzzer
+    PRIVATE -fsanitize=fuzzer-no-link)
+  target_link_options(stack_trace_compressor_fuzzer PRIVATE -fsanitize=fuzzer)
+  add_dependencies(gwp_asan stack_trace_compressor_fuzzer)
 endif()
 
 if(COMPILER_RT_INCLUDE_TESTS)

Modified: compiler-rt/trunk/lib/gwp_asan/guarded_pool_allocator.cpp
URL: http://llvm.org/viewvc/llvm-project/compiler-rt/trunk/lib/gwp_asan/guarded_pool_allocator.cpp?rev=369048&r1=369047&r2=369048&view=diff
==============================================================================
--- compiler-rt/trunk/lib/gwp_asan/guarded_pool_allocator.cpp (original)
+++ compiler-rt/trunk/lib/gwp_asan/guarded_pool_allocator.cpp Thu Aug 15 14:09:09 2019
@@ -69,12 +69,18 @@ void GuardedPoolAllocator::AllocationMet
   // TODO(hctim): Ask the caller to provide the thread ID, so we don't waste
   // other thread's time getting the thread ID under lock.
   AllocationTrace.ThreadID = getThreadID();
-  AllocationTrace.TraceLength = 0;
-  DeallocationTrace.TraceLength = 0;
+  AllocationTrace.TraceSize = 0;
+  DeallocationTrace.TraceSize = 0;
   DeallocationTrace.ThreadID = kInvalidThreadID;
-  if (Backtrace)
-    AllocationTrace.TraceLength =
-        Backtrace(AllocationTrace.Trace, kMaximumStackFrames);
+
+  if (Backtrace) {
+    uintptr_t UncompressedBuffer[kMaxTraceLengthToCollect];
+    size_t BacktraceLength =
+        Backtrace(UncompressedBuffer, kMaxTraceLengthToCollect);
+    AllocationTrace.TraceSize = compression::pack(
+        UncompressedBuffer, BacktraceLength, AllocationTrace.CompressedTrace,
+        kStackFrameStorageBytes);
+  }
 }
 
 void GuardedPoolAllocator::AllocationMetadata::RecordDeallocation(
@@ -82,11 +88,16 @@ void GuardedPoolAllocator::AllocationMet
   IsDeallocated = true;
   // Ensure that the unwinder is not called if the recursive flag is set,
   // otherwise non-reentrant unwinders may deadlock.
-  DeallocationTrace.TraceLength = 0;
+  DeallocationTrace.TraceSize = 0;
   if (Backtrace && !ThreadLocals.RecursiveGuard) {
     ScopedBoolean B(ThreadLocals.RecursiveGuard);
-    DeallocationTrace.TraceLength =
-        Backtrace(DeallocationTrace.Trace, kMaximumStackFrames);
+
+    uintptr_t UncompressedBuffer[kMaxTraceLengthToCollect];
+    size_t BacktraceLength =
+        Backtrace(UncompressedBuffer, kMaxTraceLengthToCollect);
+    DeallocationTrace.TraceSize = compression::pack(
+        UncompressedBuffer, BacktraceLength, DeallocationTrace.CompressedTrace,
+        kStackFrameStorageBytes);
   }
   DeallocationTrace.ThreadID = getThreadID();
 }
@@ -443,8 +454,13 @@ void printAllocDeallocTraces(uintptr_t A
       Printf("0x%zx was deallocated by thread %zu here:\n", AccessPtr,
              Meta->DeallocationTrace.ThreadID);
 
-    PrintBacktrace(Meta->DeallocationTrace.Trace,
-                   Meta->DeallocationTrace.TraceLength, Printf);
+    uintptr_t UncompressedTrace[AllocationMetadata::kMaxTraceLengthToCollect];
+    size_t UncompressedLength = compression::unpack(
+        Meta->DeallocationTrace.CompressedTrace,
+        Meta->DeallocationTrace.TraceSize, UncompressedTrace,
+        AllocationMetadata::kMaxTraceLengthToCollect);
+
+    PrintBacktrace(UncompressedTrace, UncompressedLength, Printf);
   }
 
   if (Meta->AllocationTrace.ThreadID == GuardedPoolAllocator::kInvalidThreadID)
@@ -453,8 +469,12 @@ void printAllocDeallocTraces(uintptr_t A
     Printf("0x%zx was allocated by thread %zu here:\n", Meta->Addr,
            Meta->AllocationTrace.ThreadID);
 
-  PrintBacktrace(Meta->AllocationTrace.Trace, Meta->AllocationTrace.TraceLength,
-                 Printf);
+  uintptr_t UncompressedTrace[AllocationMetadata::kMaxTraceLengthToCollect];
+  size_t UncompressedLength = compression::unpack(
+      Meta->AllocationTrace.CompressedTrace, Meta->AllocationTrace.TraceSize,
+      UncompressedTrace, AllocationMetadata::kMaxTraceLengthToCollect);
+
+  PrintBacktrace(UncompressedTrace, UncompressedLength, Printf);
 }
 
 struct ScopedEndOfReportDecorator {

Modified: compiler-rt/trunk/lib/gwp_asan/guarded_pool_allocator.h
URL: http://llvm.org/viewvc/llvm-project/compiler-rt/trunk/lib/gwp_asan/guarded_pool_allocator.h?rev=369048&r1=369047&r2=369048&view=diff
==============================================================================
--- compiler-rt/trunk/lib/gwp_asan/guarded_pool_allocator.h (original)
+++ compiler-rt/trunk/lib/gwp_asan/guarded_pool_allocator.h Thu Aug 15 14:09:09 2019
@@ -13,6 +13,7 @@
 #include "gwp_asan/mutex.h"
 #include "gwp_asan/options.h"
 #include "gwp_asan/random.h"
+#include "gwp_asan/stack_trace_compressor.h"
 
 #include <stddef.h>
 #include <stdint.h>
@@ -39,9 +40,15 @@ public:
   };
 
   struct AllocationMetadata {
-    // Maximum number of stack trace frames to collect for allocations + frees.
-    // TODO(hctim): Implement stack frame compression, a-la Chromium.
-    static constexpr size_t kMaximumStackFrames = 64;
+    // The number of bytes used to store a compressed stack frame. On 64-bit
+    // platforms, assuming a compression ratio of 50%, this should allow us to
+    // store ~64 frames per trace.
+    static constexpr size_t kStackFrameStorageBytes = 256;
+
+    // Maximum number of stack frames to collect on allocation/deallocation. The
+    // actual number of collected frames may be less than this as the stack
+    // frames are compressed into a fixed memory range.
+    static constexpr size_t kMaxTraceLengthToCollect = 128;
 
     // Records the given allocation metadata into this struct.
     void RecordAllocation(uintptr_t Addr, size_t Size,
@@ -51,12 +58,13 @@ public:
     void RecordDeallocation(options::Backtrace_t Backtrace);
 
     struct CallSiteInfo {
-      // The backtrace to the allocation/deallocation.
-      uintptr_t Trace[kMaximumStackFrames] = {};
+      // The compressed backtrace to the allocation/deallocation.
+      uint8_t CompressedTrace[kStackFrameStorageBytes];
       // The thread ID for this trace, or kInvalidThreadID if not available.
       uint64_t ThreadID = kInvalidThreadID;
-      // The length of the trace. Zero indicates that no trace was collected.
-      size_t TraceLength = 0;
+      // The size of the compressed trace (in bytes). Zero indicates that no
+      // trace was collected.
+      size_t TraceSize = 0;
     };
 
     // The address of this allocation.

Added: compiler-rt/trunk/lib/gwp_asan/stack_trace_compressor.cpp
URL: http://llvm.org/viewvc/llvm-project/compiler-rt/trunk/lib/gwp_asan/stack_trace_compressor.cpp?rev=369048&view=auto
==============================================================================
--- compiler-rt/trunk/lib/gwp_asan/stack_trace_compressor.cpp (added)
+++ compiler-rt/trunk/lib/gwp_asan/stack_trace_compressor.cpp Thu Aug 15 14:09:09 2019
@@ -0,0 +1,111 @@
+//===-- stack_trace_compressor.cpp ------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "gwp_asan/stack_trace_compressor.h"
+
+namespace gwp_asan {
+namespace compression {
+namespace {
+// Encodes `Value` as a variable-length integer to `Out`. Returns zero if there
+// was not enough space in the output buffer to write the complete varInt.
+// Otherwise returns the length of the encoded integer.
+size_t varIntEncode(uintptr_t Value, uint8_t *Out, size_t OutLen) {
+  for (size_t i = 0; i < OutLen; ++i) {
+    Out[i] = Value & 0x7f;
+    Value >>= 7;
+    if (!Value)
+      return i + 1;
+
+    Out[i] |= 0x80;
+  }
+
+  return 0;
+}
+
+// Decodes a variable-length integer to `Out`. Returns zero if the integer was
+// too large to be represented in a uintptr_t, or if the input buffer finished
+// before the integer was decoded (either case meaning that the `In` does not
+// point to a valid varInt buffer). Otherwise, returns the number of bytes that
+// were used to store the decoded integer.
+size_t varIntDecode(const uint8_t *In, size_t InLen, uintptr_t *Out) {
+  *Out = 0;
+  uint8_t Shift = 0;
+
+  for (size_t i = 0; i < InLen; ++i) {
+    *Out |= (static_cast<uintptr_t>(In[i]) & 0x7f) << Shift;
+
+    if (In[i] < 0x80)
+      return i + 1;
+
+    Shift += 7;
+
+    // Disallow overflowing the range of the output integer.
+    if (Shift >= sizeof(uintptr_t) * 8)
+      return 0;
+  }
+  return 0;
+}
+
+uintptr_t zigzagEncode(uintptr_t Value) {
+  uintptr_t Encoded = Value << 1;
+  if (static_cast<intptr_t>(Value) >= 0)
+    return Encoded;
+  return ~Encoded;
+}
+
+uintptr_t zigzagDecode(uintptr_t Value) {
+  uintptr_t Decoded = Value >> 1;
+  if (!(Value & 1))
+    return Decoded;
+  return ~Decoded;
+}
+} // anonymous namespace
+
+size_t pack(const uintptr_t *Unpacked, size_t UnpackedSize, uint8_t *Packed,
+            size_t PackedMaxSize) {
+  size_t Index = 0;
+  for (size_t CurrentDepth = 0; CurrentDepth < UnpackedSize; CurrentDepth++) {
+    uintptr_t Diff = Unpacked[CurrentDepth];
+    if (CurrentDepth > 0)
+      Diff -= Unpacked[CurrentDepth - 1];
+    size_t EncodedLength =
+        varIntEncode(zigzagEncode(Diff), Packed + Index, PackedMaxSize - Index);
+    if (!EncodedLength)
+      break;
+
+    Index += EncodedLength;
+  }
+
+  return Index;
+}
+
+size_t unpack(const uint8_t *Packed, size_t PackedSize, uintptr_t *Unpacked,
+              size_t UnpackedMaxSize) {
+  size_t CurrentDepth;
+  size_t Index = 0;
+  for (CurrentDepth = 0; CurrentDepth < UnpackedMaxSize; CurrentDepth++) {
+    uintptr_t EncodedDiff;
+    size_t DecodedLength =
+        varIntDecode(Packed + Index, PackedSize - Index, &EncodedDiff);
+    if (!DecodedLength)
+      break;
+    Index += DecodedLength;
+
+    Unpacked[CurrentDepth] = zigzagDecode(EncodedDiff);
+    if (CurrentDepth > 0)
+      Unpacked[CurrentDepth] += Unpacked[CurrentDepth - 1];
+  }
+
+  if (Index != PackedSize && CurrentDepth != UnpackedMaxSize)
+    return 0;
+
+  return CurrentDepth;
+}
+
+} // namespace compression
+} // namespace gwp_asan

Added: compiler-rt/trunk/lib/gwp_asan/stack_trace_compressor.h
URL: http://llvm.org/viewvc/llvm-project/compiler-rt/trunk/lib/gwp_asan/stack_trace_compressor.h?rev=369048&view=auto
==============================================================================
--- compiler-rt/trunk/lib/gwp_asan/stack_trace_compressor.h (added)
+++ compiler-rt/trunk/lib/gwp_asan/stack_trace_compressor.h Thu Aug 15 14:09:09 2019
@@ -0,0 +1,38 @@
+//===-- stack_trace_compressor.h --------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef GWP_ASAN_STACK_TRACE_COMPRESSOR_
+#define GWP_ASAN_STACK_TRACE_COMPRESSOR_
+
+#include <stddef.h>
+#include <stdint.h>
+
+// These functions implement stack frame compression and decompression. We store
+// the zig-zag encoded pointer difference between frame[i] and frame[i - 1] as
+// a variable-length integer. This can reduce the memory overhead of stack
+// traces by 50%.
+
+namespace gwp_asan {
+namespace compression {
+
+// For the stack trace in `Unpacked` with length `UnpackedSize`, pack it into
+// the buffer `Packed` maximum length `PackedMaxSize`. The return value is the
+// number of bytes that were written to the output buffer.
+size_t pack(const uintptr_t *Unpacked, size_t UnpackedSize, uint8_t *Packed,
+            size_t PackedMaxSize);
+
+// From the packed stack trace in `Packed` of length `PackedSize`, write the
+// unpacked stack trace of maximum length `UnpackedMaxSize` into `Unpacked`.
+// Returns the number of full entries unpacked, or zero on error.
+size_t unpack(const uint8_t *Packed, size_t PackedSize, uintptr_t *Unpacked,
+              size_t UnpackedMaxSize);
+
+} // namespace compression
+} // namespace gwp_asan
+
+#endif // GWP_ASAN_STACK_TRACE_COMPRESSOR_

Added: compiler-rt/trunk/lib/gwp_asan/stack_trace_compressor_fuzzer.cpp
URL: http://llvm.org/viewvc/llvm-project/compiler-rt/trunk/lib/gwp_asan/stack_trace_compressor_fuzzer.cpp?rev=369048&view=auto
==============================================================================
--- compiler-rt/trunk/lib/gwp_asan/stack_trace_compressor_fuzzer.cpp (added)
+++ compiler-rt/trunk/lib/gwp_asan/stack_trace_compressor_fuzzer.cpp Thu Aug 15 14:09:09 2019
@@ -0,0 +1,49 @@
+#include <cstddef>
+#include <cstdint>
+#include <cstdio>
+#include <cstdlib>
+#include <vector>
+
+#include "gwp_asan/stack_trace_compressor.h"
+
+constexpr size_t kBytesForLargestVarInt = (sizeof(uintptr_t) * 8) / 7 + 1;
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
+  size_t BufferSize = kBytesForLargestVarInt * Size / sizeof(uintptr_t);
+  std::vector<uint8_t> Buffer(BufferSize);
+  std::vector<uint8_t> Buffer2(BufferSize);
+
+  // Unpack the fuzz bytes.
+  gwp_asan::compression::unpack(Data, Size,
+                                reinterpret_cast<uintptr_t *>(Buffer2.data()),
+                                BufferSize / sizeof(uintptr_t));
+
+  // Pack the fuzz bytes.
+  size_t BytesWritten = gwp_asan::compression::pack(
+      reinterpret_cast<const uintptr_t *>(Data), Size / sizeof(uintptr_t),
+      Buffer.data(), BufferSize);
+
+  // Unpack the compressed buffer.
+  size_t DecodedElements = gwp_asan::compression::unpack(
+      Buffer.data(), BytesWritten,
+      reinterpret_cast<uintptr_t *>(Buffer2.data()),
+      BufferSize / sizeof(uintptr_t));
+
+  // Ensure that every element was encoded and decoded properly.
+  if (DecodedElements != Size / sizeof(uintptr_t))
+    abort();
+
+  // Ensure that the compression and uncompression resulted in the same trace.
+  const uintptr_t *FuzzPtrs = reinterpret_cast<const uintptr_t *>(Data);
+  const uintptr_t *DecodedPtrs =
+      reinterpret_cast<const uintptr_t *>(Buffer2.data());
+  for (size_t i = 0; i < Size / sizeof(uintptr_t); ++i) {
+    if (FuzzPtrs[i] != DecodedPtrs[i]) {
+      fprintf(stderr, "FuzzPtrs[%zu] != DecodedPtrs[%zu] (0x%zx vs. 0x%zx)", i,
+              i, FuzzPtrs[i], DecodedPtrs[i]);
+      abort();
+    }
+  }
+
+  return 0;
+}

Added: compiler-rt/trunk/lib/gwp_asan/tests/compression.cpp
URL: http://llvm.org/viewvc/llvm-project/compiler-rt/trunk/lib/gwp_asan/tests/compression.cpp?rev=369048&view=auto
==============================================================================
--- compiler-rt/trunk/lib/gwp_asan/tests/compression.cpp (added)
+++ compiler-rt/trunk/lib/gwp_asan/tests/compression.cpp Thu Aug 15 14:09:09 2019
@@ -0,0 +1,258 @@
+//===-- compression.cpp -----------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "gwp_asan/stack_trace_compressor.h"
+#include "gtest/gtest.h"
+
+namespace gwp_asan {
+namespace compression {
+
+TEST(GwpAsanCompressionTest, SingleByteVarInt) {
+  uint8_t Compressed[1];
+
+  uintptr_t Uncompressed = 0x00;
+  EXPECT_EQ(1u, pack(&Uncompressed, 1u, Compressed, sizeof(Compressed)));
+  EXPECT_EQ(Compressed[0], 0x00);
+
+  Uncompressed = 0x01;
+  EXPECT_EQ(1u, pack(&Uncompressed, 1u, Compressed, sizeof(Compressed)));
+  EXPECT_EQ(Compressed[0], 0x02); // +1 => 2 in zigzag.
+
+  Uncompressed = 0x3f;
+  EXPECT_EQ(1u, pack(&Uncompressed, 1u, Compressed, sizeof(Compressed)));
+  EXPECT_EQ(Compressed[0], 0x7e); // +63 => 127 in zigzag.
+}
+
+TEST(GwpAsanCompressionTest, MultiByteVarInt) {
+  uint8_t Compressed[sizeof(uintptr_t) + 1];
+
+  uintptr_t Uncompressed = 0x40;
+  EXPECT_EQ(2u, pack(&Uncompressed, 1u, Compressed, sizeof(Compressed)));
+  EXPECT_EQ(Compressed[0], 0x80); // +64 => 128 in zigzag.
+  EXPECT_EQ(Compressed[1], 0x01);
+
+  Uncompressed = 0x41;
+  EXPECT_EQ(2u, pack(&Uncompressed, 1u, Compressed, sizeof(Compressed)));
+  EXPECT_EQ(Compressed[0], 0x82); // +65 => 130 in zigzag
+  EXPECT_EQ(Compressed[1], 0x01);
+
+  Uncompressed = 0x1fff;
+  EXPECT_EQ(2u, pack(&Uncompressed, 1u, Compressed, sizeof(Compressed)));
+  EXPECT_EQ(Compressed[0], 0xfe); // +8191 => 16382 in zigzag
+  EXPECT_EQ(Compressed[1], 0x7f);
+
+  Uncompressed = 0x2000;
+  EXPECT_EQ(3u, pack(&Uncompressed, 1u, Compressed, sizeof(Compressed)));
+  EXPECT_EQ(Compressed[0], 0x80); // +8192 => 16384 in zigzag
+  EXPECT_EQ(Compressed[1], 0x80);
+  EXPECT_EQ(Compressed[2], 0x01);
+
+  Uncompressed = 0xff010ff0;
+  EXPECT_EQ(5u, pack(&Uncompressed, 1u, Compressed, sizeof(Compressed)));
+  EXPECT_EQ(Compressed[0], 0xe0); // +0xff010ff0 => 0x1FE021FE0 in zigzag
+  EXPECT_EQ(Compressed[1], 0xbf);
+  EXPECT_EQ(Compressed[2], 0x88);
+  EXPECT_EQ(Compressed[3], 0xf0);
+  EXPECT_EQ(Compressed[4], 0x1f);
+}
+
+TEST(GwpAsanCompressionTest, CorrectDifference) {
+  uint8_t Compressed[10];
+  uintptr_t Uncompressed[2] = {0x00, 0x00};
+
+  EXPECT_EQ(2u, pack(Uncompressed, sizeof(Uncompressed) / sizeof(uintptr_t),
+                     Compressed, sizeof(Compressed)));
+  EXPECT_EQ(Compressed[1], 0x00); // +0 difference => 0 in zigzag.
+
+  Uncompressed[1] = 0x01;
+  EXPECT_EQ(2u, pack(Uncompressed, sizeof(Uncompressed) / sizeof(uintptr_t),
+                     Compressed, sizeof(Compressed)));
+  EXPECT_EQ(Compressed[1], 0x02); // +1 difference => 2 in zigzag.
+
+  Uncompressed[1] = 0x02;
+  EXPECT_EQ(2u, pack(Uncompressed, sizeof(Uncompressed) / sizeof(uintptr_t),
+                     Compressed, sizeof(Compressed)));
+  EXPECT_EQ(Compressed[1], 0x04); // +2 difference => 4 in zigzag.
+
+  Uncompressed[1] = 0x80;
+  EXPECT_EQ(3u, pack(Uncompressed, sizeof(Uncompressed) / sizeof(uintptr_t),
+                     Compressed, sizeof(Compressed)));
+  EXPECT_EQ(Compressed[1], 0x80); // +128 difference => +256 in zigzag (note the
+  EXPECT_EQ(Compressed[2], 0x02); // varint encoding here).
+
+  Uncompressed[0] = 0x01;
+  Uncompressed[1] = 0x00;
+  EXPECT_EQ(2u, pack(Uncompressed, sizeof(Uncompressed) / sizeof(uintptr_t),
+                     Compressed, sizeof(Compressed)));
+  EXPECT_EQ(Compressed[1], 0x01); // -1 difference => +1 in zigzag.
+
+  Uncompressed[0] = 0x02;
+  EXPECT_EQ(2u, pack(Uncompressed, sizeof(Uncompressed) / sizeof(uintptr_t),
+                     Compressed, sizeof(Compressed)));
+  EXPECT_EQ(Compressed[1], 0x03); // -2 difference => +3 in zigzag.
+
+  Uncompressed[0] = 0x80;
+  EXPECT_EQ(4u, pack(Uncompressed, sizeof(Uncompressed) / sizeof(uintptr_t),
+                     Compressed, sizeof(Compressed)));
+  EXPECT_EQ(Compressed[2], 0xff); // -128 difference => +255 in zigzag (note the
+  EXPECT_EQ(Compressed[3], 0x01); // varint encoding here).
+}
+
+// Space needed to encode the biggest uintptr_t as a varint is ceil((8 / 7) *
+// sizeof(uintptr_t)), as each 7 bits requires 8 bits of space.
+constexpr size_t kBytesForLargestVarInt = (sizeof(uintptr_t) * 8) / 7 + 1;
+
+// Ensures that when the closest diff between two pointers is via. underflow,
+// we take the underflow option.
+TEST(GwpAsanCompressionTest, ClosestDiffIsUnderflow) {
+  uint8_t Compressed[2];
+  uintptr_t Uncompressed[2] = {0x00, UINTPTR_MAX};
+
+  EXPECT_EQ(2u, pack(Uncompressed, sizeof(Uncompressed) / sizeof(uintptr_t),
+                     Compressed, sizeof(Compressed)));
+  // -1 difference => +1 in zigzag.
+  EXPECT_EQ(Compressed[1], 0x01);
+}
+
+// Ensures that when the closest diff between two pointers is via. overflow,
+// that we take this option.
+TEST(GwpAsanCompressionTest, ClosestDiffIsOverflow) {
+  uint8_t Compressed[2];
+  uintptr_t Uncompressed[2] = {UINTPTR_MAX, 0x00};
+
+  // Note here that the first element is encoded as the difference from zero.
+  EXPECT_EQ(2u, pack(Uncompressed, sizeof(Uncompressed) / sizeof(uintptr_t),
+                     Compressed, sizeof(Compressed)));
+  // -1 difference => +1 in zigzag (the first pointer is encoded as -1).
+  EXPECT_EQ(Compressed[0], 0x01);
+  // +1 difference => +2 in zigzag.
+  EXPECT_EQ(Compressed[1], 0x02);
+}
+
+void runPackUnpack(uintptr_t *Test, size_t NumEntries) {
+  // Setup the input/output buffers based on the maximum possible size.
+  uintptr_t *Uncompressed =
+      static_cast<uintptr_t *>(alloca(NumEntries * sizeof(uintptr_t)));
+  size_t CompressedBufferSize = NumEntries * kBytesForLargestVarInt;
+  uint8_t *Compressed = static_cast<uint8_t *>(alloca(CompressedBufferSize));
+
+  // Pack the provided testcase, recoding the number of bytes it took for
+  // storage.
+  size_t BytesUsedForPacking =
+      pack(Test, NumEntries, Compressed, CompressedBufferSize);
+  EXPECT_NE(BytesUsedForPacking, 0u);
+
+  // Unpack the testcase and ensure that the correct number of entries was
+  // unpacked.
+  EXPECT_EQ(NumEntries,
+            unpack(Compressed, BytesUsedForPacking, Uncompressed, NumEntries));
+
+  // Ensure that the unpacked trace is the same as the original testcase.
+  for (size_t i = 0; i < NumEntries; ++i) {
+    EXPECT_EQ(Uncompressed[i], Test[i]);
+  }
+}
+
+TEST(GwpAsanCompressionTest, UncompressVarInt) {
+  uint8_t Compressed[] = {0x00, 0xaa, 0xaf, 0xd0, 0xda, 0x24};
+  uintptr_t Uncompressed[2];
+
+  EXPECT_EQ(2u, unpack(Compressed, sizeof(Compressed), Uncompressed, 2u));
+  EXPECT_EQ(Uncompressed[0], 0x00u);
+  EXPECT_EQ(Uncompressed[1], 0x125aa0bd5u);
+}
+
+TEST(GwpAsanCompressionTest, CompressUncompressAscending) {
+  uintptr_t Test[] = {1, 2, 3};
+  runPackUnpack(Test, sizeof(Test) / sizeof(uintptr_t));
+}
+
+TEST(GwpAsanCompressionTest, CompressUncompressDescending) {
+  uintptr_t Test[] = {3, 2, 1};
+  runPackUnpack(Test, sizeof(Test) / sizeof(uintptr_t));
+}
+
+TEST(GwpAsanCompressionTest, CompressUncompressRepeated) {
+  uintptr_t Test[] = {3, 3, 3};
+  runPackUnpack(Test, sizeof(Test) / sizeof(uintptr_t));
+}
+
+TEST(GwpAsanCompressionTest, CompressUncompressZigZag) {
+  uintptr_t Test[] = {1, 3, 2, 4, 1, 2};
+  runPackUnpack(Test, sizeof(Test) / sizeof(uintptr_t));
+}
+
+TEST(GwpAsanCompressionTest, CompressUncompressVarInt) {
+  uintptr_t Test[] = {0x1981561, 0x18560, 0x125ab9135, 0x1232562};
+  runPackUnpack(Test, sizeof(Test) / sizeof(uintptr_t));
+}
+
+TEST(GwpAsanCompressionTest, CompressUncompressLargestDifference) {
+  uintptr_t Test[] = {0x00, INTPTR_MAX, UINTPTR_MAX, INTPTR_MAX, 0x00};
+  runPackUnpack(Test, sizeof(Test) / sizeof(uintptr_t));
+}
+
+TEST(GwpAsanCompressionTest, CompressUncompressBigPointers) {
+  uintptr_t Test[] = {UINTPTR_MAX, UINTPTR_MAX - 10};
+  runPackUnpack(Test, sizeof(Test) / sizeof(uintptr_t));
+
+  uintptr_t Test2[] = {UINTPTR_MAX - 10, UINTPTR_MAX};
+  runPackUnpack(Test2, sizeof(Test2) / sizeof(uintptr_t));
+}
+
+TEST(GwpAsanCompressionTest, UncompressFailsWithOutOfBoundsVarInt) {
+  uint8_t Compressed[kBytesForLargestVarInt + 1];
+  for (size_t i = 0; i < kBytesForLargestVarInt; ++i) {
+    Compressed[i] = 0x80;
+  }
+  Compressed[kBytesForLargestVarInt] = 0x00;
+
+  uintptr_t Uncompressed;
+  EXPECT_EQ(unpack(Compressed, kBytesForLargestVarInt + 1, &Uncompressed, 1),
+            0u);
+}
+
+TEST(GwpAsanCompressionTest, UncompressFailsWithTooSmallBuffer) {
+  uint8_t Compressed[] = {0x80, 0x00};
+
+  uintptr_t Uncompressed;
+  EXPECT_EQ(unpack(Compressed, 1u, &Uncompressed, 1), 0u);
+}
+
+TEST(GwpAsanCompressionTest, CompressPartiallySucceedsWithTooSmallBuffer) {
+  uintptr_t Uncompressed[] = {
+      0x80,  // Requires 2 bytes for varint.
+      0x100, // Requires two bytes for varint difference of 0x80.
+      0xff,  // Requires single byte for varint difference of -0x01
+  };
+  uint8_t Compressed[3 * kBytesForLargestVarInt];
+
+  // Zero and one byte buffers shouldn't encode anything (see above for size
+  // requirements).
+  EXPECT_EQ(pack(Uncompressed, 3u, Compressed, 0u), 0u);
+  EXPECT_EQ(pack(Uncompressed, 3u, Compressed, 1u), 0u);
+
+  // Two byte buffer should hold a single varint-encoded value.
+  EXPECT_EQ(pack(Uncompressed, 3u, Compressed, 2u), 2u);
+
+  // Three bytes isn't enough to cover the first two pointers, as both take two
+  // bytes each to store. Expect a single value to be compressed.
+  EXPECT_EQ(pack(Uncompressed, 3u, Compressed, 3u), 2u);
+
+  // Four bytes is enough for the first two pointers to be stored.
+  EXPECT_EQ(pack(Uncompressed, 3u, Compressed, 4u), 4u);
+
+  // And five is enough for all three pointers to be stored.
+  EXPECT_EQ(pack(Uncompressed, 3u, Compressed, 5u), 5u);
+  // And a buffer that's bigger than five bytes should still only write five
+  // bytes.
+  EXPECT_EQ(pack(Uncompressed, 3u, Compressed, 6u), 5u);
+  EXPECT_EQ(pack(Uncompressed, 3u, Compressed, 3 * kBytesForLargestVarInt), 5u);
+}
+} // namespace compression
+} // namespace gwp_asan




More information about the llvm-commits mailing list