ANRdaemon: fix a bug in handling dump request when trace is running.
am: b403779c94

Change-Id: Icc5dfd9103191213921b4544f2bfa7e3d187652d
diff --git a/ANRdaemon/Android.mk b/ANRdaemon/Android.mk
index 449df1e..24072e2 100644
--- a/ANRdaemon/Android.mk
+++ b/ANRdaemon/Android.mk
@@ -9,6 +9,7 @@
 LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
 LOCAL_MODULE_TAGS := debug
 LOCAL_SHARED_LIBRARIES := \
+    liblog \
     libbinder \
     libcutils \
     libutils \
diff --git a/alloc-stress/alloc-stress.cpp b/alloc-stress/alloc-stress.cpp
index 726ea96..12eecc5 100644
--- a/alloc-stress/alloc-stress.cpp
+++ b/alloc-stress/alloc-stress.cpp
@@ -111,7 +111,7 @@
         char writeFdStr[16];
         snprintf(readFdStr, sizeof(readFdStr), "%d", pipe.getReadFd());
         snprintf(writeFdStr, sizeof(writeFdStr), "%d", pipe.getWriteFd());
-        execl(exName, exName, "--worker", arg, readFdStr, writeFdStr, 0);
+        execl(exName, exName, "--worker", arg, readFdStr, writeFdStr, nullptr);
         ASSERT_TRUE(0);
     }
     // parent process
diff --git a/avb/.clang-format b/avb/.clang-format
new file mode 120000
index 0000000..f9066d4
--- /dev/null
+++ b/avb/.clang-format
@@ -0,0 +1 @@
+../../../build/tools/brillo-clang-format
\ No newline at end of file
diff --git a/avb/Android.mk b/avb/Android.mk
new file mode 100644
index 0000000..47653c3
--- /dev/null
+++ b/avb/Android.mk
@@ -0,0 +1,138 @@
+#
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_PATH := $(my-dir)
+
+avb_common_cflags := \
+    -D_FILE_OFFSET_BITS=64 \
+    -D_POSIX_C_SOURCE=199309L \
+    -Wa,--noexecstack \
+    -Werror \
+    -Wall \
+    -Wextra \
+    -Wformat=2 \
+    -Wno-psabi \
+    -Wno-unused-parameter \
+    -ffunction-sections \
+    -fstack-protector-strong \
+    -fvisibility=hidden
+avb_common_cppflags := \
+    -Wnon-virtual-dtor \
+    -fno-strict-aliasing
+avb_common_ldflags := \
+    -Wl,--gc-sections
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := avbtool
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_IS_HOST_MODULE := true
+LOCAL_MODULE := avbtool
+include $(BUILD_PREBUILT)
+
+# Build for the target (for e.g. fs_mgr usage).
+include $(CLEAR_VARS)
+LOCAL_MODULE := libavb
+LOCAL_MODULE_HOST_OS := linux
+LOCAL_EXPORT_C_INDLUDE_DIRS := $(LOCAL_PATH)/libavb
+LOCAL_CLANG := true
+LOCAL_CFLAGS := $(avb_common_cflags) -fno-stack-protector -DAVB_ENABLE_DEBUG -DAVB_COMPILATION
+LOCAL_LDFLAGS := $(avb_common_ldflags)
+LOCAL_C_INCLUDES :=
+LOCAL_SRC_FILES := \
+    libavb/avb_chain_partition_descriptor.c \
+    libavb/avb_crypto.c \
+    libavb/avb_descriptor.c \
+    libavb/avb_footer.c \
+    libavb/avb_hash_descriptor.c \
+    libavb/avb_hashtree_descriptor.c \
+    libavb/avb_kernel_cmdline_descriptor.c \
+    libavb/avb_property_descriptor.c \
+    libavb/avb_rsa.c \
+    libavb/avb_sha256.c \
+    libavb/avb_sha512.c \
+    libavb/avb_slot_verify.c \
+    libavb/avb_sysdeps_posix.c \
+    libavb/avb_util.c \
+    libavb/avb_vbmeta_image.c
+include $(BUILD_SHARED_LIBRARY)
+
+# Build for the host (for unit tests).
+include $(CLEAR_VARS)
+LOCAL_MODULE := libavb_host
+LOCAL_MODULE_HOST_OS := linux
+LOCAL_MODULE_CLASS := STATIC_LIBRARIES
+LOCAL_CLANG := true
+LOCAL_CFLAGS := $(avb_common_cflags) -fno-stack-protector -DAVB_ENABLE_DEBUG -DAVB_COMPILATION
+LOCAL_LDFLAGS := $(avb_common_ldflags)
+LOCAL_C_INCLUDES :=
+LOCAL_SRC_FILES := \
+    libavb/avb_chain_partition_descriptor.c \
+    libavb/avb_crypto.c \
+    libavb/avb_descriptor.c \
+    libavb/avb_footer.c \
+    libavb/avb_hash_descriptor.c \
+    libavb/avb_hashtree_descriptor.c \
+    libavb/avb_kernel_cmdline_descriptor.c \
+    libavb/avb_property_descriptor.c \
+    libavb/avb_rsa.c \
+    libavb/avb_sha256.c \
+    libavb/avb_sha512.c \
+    libavb/avb_slot_verify.c \
+    libavb/avb_util.c \
+    libavb/avb_vbmeta_image.c
+include $(BUILD_HOST_STATIC_LIBRARY)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := libavb_host_sysdeps
+LOCAL_MODULE_HOST_OS := linux
+LOCAL_MODULE_CLASS := STATIC_LIBRARIES
+LOCAL_CLANG := true
+LOCAL_CFLAGS := $(avb_common_cflags) -DAVB_ENABLE_DEBUG -DAVB_COMPILATION
+LOCAL_LDFLAGS := $(avb_common_ldflags)
+LOCAL_C_INCLUDES :=
+LOCAL_SRC_FILES := \
+    libavb/avb_sysdeps_posix.c
+include $(BUILD_HOST_STATIC_LIBRARY)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := libavb_host_unittest
+LOCAL_REQUIRED_MODULES := simg2img img2simg
+LOCAL_MODULE_HOST_OS := linux
+LOCAL_CPP_EXTENSION := .cc
+LOCAL_CLANG := true
+LOCAL_CFLAGS := $(avb_common_cflags) -DAVB_ENABLE_DEBUG -DAVB_COMPILATION
+LOCAL_CPPFLAGS := $(avb_common_cppflags)
+LOCAL_LDFLAGS := $(avb_common_ldflags)
+LOCAL_C_INCLUDES := $(LOCAL_PATH)/libavb
+LOCAL_STATIC_LIBRARIES := \
+    libavb_host \
+    libavb_host_sysdeps \
+    libgmock_host
+LOCAL_SHARED_LIBRARIES := \
+    libchrome
+LOCAL_SRC_FILES := \
+    test/avb_slot_verify_unittest.cc \
+    test/avb_util_unittest.cc \
+    test/avb_vbmeta_image_unittest.cc \
+    test/avbtool_unittest.cc
+LOCAL_LDLIBS_linux := -lrt
+include $(BUILD_HOST_NATIVE_TEST)
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := libavb_host_symbols_test
+LOCAL_MODULE_TAGS := debug
+LOCAL_ADDITIONAL_DEPENDENCIES := libavb_host
+include $(BUILD_HOST_PREBUILT)
diff --git a/avb/README b/avb/README
new file mode 100644
index 0000000..8872757
--- /dev/null
+++ b/avb/README
@@ -0,0 +1,223 @@
+This directory contains avbtool and libavb.
+
+The main job of avbtool is to create vbmeta.img which is the
+top-level object for verified boot. This image is designed to go into
+the vbmeta partition (or, if using A/B, the slot in question
+e.g. vbmeta_a or vbmeta_b) and be of minimal size (for out-of-band
+updates). The vbmeta image is cryptographically signed and contains
+verification data (e.g. cryptographic digests) for verifying boot.img,
+system.img, and other partitions/images.
+
+The vbmeta image can also contain references to other partitions where
+verification data is stored as well as a public key indicating who
+should sign the verification data. This indirection provides
+delegation, that is, it allows a 3rd party to control content on a given
+partition by including the public key said 3rd party is using to sign
+the data with, in vbmeta.img. By design, this authority can be easily
+revoked by simply updating vbmeta.img with new descriptors for the
+partition in question.
+
+Storing signed verification data on other images - for example
+boot.img and system.img - is also done with avbtool.
+
+In addition to avbtool, a library - libavb - is provided. This library
+performs all verification on the device side e.g. it starts by loading
+the vbmeta partition, checks the signature, and then goes on to load
+the boot partition for verification.
+
+The libavb library is intended to be used in both boot loaders and
+inside Android. It has a simple abstraction for system dependencies
+(see libavb/avb_sysdeps.h) as well as operations that the boot loader
+or OS is expected to implement (see libavb/avb_ops.h).
+
+In addition to handling verified boot, libavb will in the future be
+extended to handle A/B selection in a way that can be used in the
+device's fastboot implementation, its boot loader, and its
+boot_control HAL implementation. This will be implemented in a future
+CL.
+
+-- FILES AND DIRECTORIES
+
+ libavb/
+
+   An implementation for image verification and A/B selection. This
+   code is designed to be highly portable so it can be used in as many
+   contexts as possible. This code requires a C99-compliant C
+   compiler.
+
+   Part of this code is considered internal to the implementation and
+   should not be used outside it. For example, this applies to the
+   avb_rsa.[ch] and avb_sha.[ch] files.
+
+   System dependencies expected to be provided by the platform is
+   defined in avb_sysdeps.h. If the platform provides the standard C
+   runtime avb_sysdeps_posix.c can be used.
+
+ Android.mk
+
+   Build instructions for building libavb_host (for unit tests),
+   libavb (for use on the device) and associated unit tests.
+
+ avbtool
+
+   A tool written in Python for working with images related to
+   verified boot.
+
+ test/
+
+   Contains unit tests for abvtool and libavb.
+
+-- AUDIENCE AND PORTABILITY NOTES
+
+This code is intended to be used in bootloaders in devices running
+Android. The suggested approach is to copy the appropriate header and
+C files mentioned in the previous section into the boot loader and
+integrate as appropriate.
+
+The libavb/ codebase will evolve over time so integration should be as
+non-invasive as possible. The intention is to keep the API of the
+library stable however it will be broken if necessary.
+
+As for portability, the library is intended to be highly portable,
+work on both little- and big-endian architectures and 32- and
+64-bit. It's also intended to work in non-standard environments
+without the standard C library and runtime.
+
+If the AVB_ENABLE_DEBUG preprocessor symbol is set, the code will
+include useful debug information and run-time checks. Production
+builds should not use this.
+
+The preprocessor symbol AVB_COMPILATION should be set only when
+compiling the library. The code must be compiled into a separate
+library.
+
+Applications using the compiled library must only include the
+libavb/libavb.h file (which will include all public interfaces) and
+must not have the AVB_COMPILATION preprocessor symbol set. This is to
+ensure that internal code that may be change in the future (for
+example avb_sha.[ch] and avb_rsa.[ch]) will not be visible to
+application code.
+
+-- COMPATIBILITY NOTES
+
+The VBMeta structure (as defined in libavb/avb_vbmeta_header.h)
+guarantees forwards- and backwards-compatibility provided the major
+version does not change.
+
+When backwards-compatible changes are made - for example, when a new
+field is added to AvbVBMetaImageHeader - the minor version will be
+bumped. At the same time, libavb will also be modified to test for the
+appropriate minor version before attempting to access the newly added
+field. This ensures that version 1.N of the library is able to read an
+old vbmeta image header produced with version 1.M where N > M.
+
+The usual scenario is that the code parsing the AvbVBMetaImageHeader
+rarely changes (it's usually in the firmware of a device and this
+firmware is rarely updated if ever), let's say it's fixed at version
+1.N. At the same time, the version of the avbtool used to produce the
+vbmeta image is rolling forward and is at version 1.M where M > N. The
+problem with this scenario is that version 1.M may easily and
+inadvertently introduce a seemingly compatible change that isn't. For
+example, consider if a new verification algorithm is added - in this
+case version 1.N of the reference implementation will fail at
+verification time when validating the |algorithm_field| of a 1.M image
+if it's set to the new algorithm.
+
+The best approach for dealing with this problem is to always used a
+pinned version of avbtool (say, use version 1.N to generate images
+targeted for devices running version 1.N) for generating and signing
+images but sometimes this is not always possible nor
+desirable. Therefore, to avoid this compatibility problem, avbtool is
+designed to always take such input as a command-line argument so it
+can be kept constant by the caller. In other words, as long as you
+keep your command-line options passed to the avb tool the same, images
+produced by newer versions of avb will continue to work on the same
+version of the reference implementation.
+
+-- USAGE
+
+The content for the vbmeta partition can be generated as follows:
+
+ $ avbtool make_vbmeta_image                                                \
+     --output OUTPUT                                                        \
+     [--algorithm ALGORITHM] [--key /path/to/key_used_for_signing.bin]      \
+     [--rollback_index NUMBER]                                              \
+     [--include_descriptors_from_footer /path/to/image.bin]                 \
+     [--generate_dm_verity_cmdline_from_footer /path/to/image.bin]          \
+     [--chain_partition part_name:rollback_index_slot:/path/to/key1.bin]
+
+An integrity footer containing the hash for an entire partition can be
+added to an existing image as follows:
+
+ $ avbtool add_hash_footer                                             \
+     --image IMAGE                                                     \
+     --partition_name PARTNAME --partition_size SIZE                   \
+     [--rollback_index NUMBER]                                         \
+     [--algorithm ALGORITHM] [--key /path/to/key_used_for_signing.bin] \
+     [--hash_algorithm HASH_ALG] [--salt HEX]                          \
+     [--include_descriptors_from_footer /path/to/image.bin]            \
+     [--generate_dm_verity_cmdline_from_footer /path/to/image.bin]
+
+An integrity footer containing the root digest and salt for a hashtree
+for a partition can be added to an existing image as follows. The
+hashtree is also appended to the image.
+
+  $ avbtool add_hashtree_footer                                        \
+     --image IMAGE                                                     \
+     --partition_name PARTNAME --partition_size SIZE                   \
+     [--rollback_index NUMBER]                                         \
+     [--algorithm ALGORITHM] [--key /path/to/key_used_for_signing.bin] \
+     [--hash_algorithm HASH_ALG] [--salt HEX] [--block_size SIZE]      \
+     [--include_descriptors_from_footer /path/to/image.bin]            \
+     [--generate_dm_verity_cmdline_from_footer /path/to/image.bin]
+
+The integrity footer on an image can be removed from an image. The
+hashtree can optionally be kept in place.
+
+  $ avbtool erase_footer --image IMAGE [--keep_hashtree]
+
+-- BUILD SYSTEM INTEGRATION NOTES
+
+Android Verified Boot is enabled by the BOARD_AVB_ENABLE variable
+
+ BOARD_AVB_ENABLE := true
+
+This will make the build system create vbmeta.img which will contain a
+hash descriptor for boot.img, a hashtree descriptor for system.img, a
+kernel-cmdline descriptor for setting up dm-verity for system.img and
+append a hash-tree to system.img.
+
+By default, the algorithm SHA256_RSA4096 is used with a test key from
+this directory. This can be overriden by the BOARD_AVB_ALGORITHM and
+BOARD_AVB_KEY_PATH variables to use e.g. a 4096-bit RSA key and
+SHA-512:
+
+ BOARD_AVB_ALGORITHM := SHA512_RSA4096
+ BOARD_AVB_KEY_PATH := /path/to/rsa_key_4096bits.pem
+
+Remember that the public part of this key needs to be embedded in the
+bootloader of the device expected to process resulting images. Use
+'avbtool extract_public_key' to do this.
+
+To prevent rollback attakcs, the rollback index should be increased on
+a regular basis. The rollback index can be set with the
+BOARD_AVB_ROLLBACK_INDEX variable:
+
+ BOARD_AVB_ROLLBACK_INDEX := 5
+
+If this is not set, the rollback index defaults to 0.
+
+The variable BOARD_AVB_MAKE_VBMETA_IMAGE_ARGS can be used to specify
+additional options passed to 'avbtool make_vbmeta_image'. Typical
+options to be used here include '--prop', '--prop_from_file', and
+'--chain_partition'.
+
+The variable BOARD_AVBTOOL_BOOT_ADD_HASH_FOOTER_ARGS can be used to
+specify additional options passed to 'avbtool add_hash_footer' for
+boot.img. Typical options to be used here include '--hash_algorithm'
+and '--salt'.
+
+The variable BOARD_AVBTOOL_SYSTEM_ADD_HASHTREE_FOOTER_ARGS can be used
+to specify additional options passed to 'avbtool add_hashtree_footer'
+for systems.img. Typical options to be used here include
+'--hash_algorithm', '--salt', and '--block_size'.
diff --git a/avb/avbtool b/avb/avbtool
new file mode 100755
index 0000000..4a0ad79
--- /dev/null
+++ b/avb/avbtool
@@ -0,0 +1,2525 @@
+#!/usr/bin/env python
+
+# Copyright 2016, The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+"""Command-line tool for working with Brillo Verified Boot images."""
+
+import argparse
+import bisect
+import hashlib
+import os
+import struct
+import subprocess
+import sys
+
+import Crypto.PublicKey.RSA
+
+# Keep in sync with avb_vbmeta_header.h.
+AVB_VERSION_MAJOR = 1
+AVB_VERSION_MINOR = 0
+
+
+class AvbError(Exception):
+  """Application-specific errors.
+
+  These errors represent issues for which a stack-trace should not be
+  presented.
+
+  Attributes:
+    message: Error message.
+  """
+
+  def __init__(self, message):
+    Exception.__init__(self, message)
+
+
+class Algorithm(object):
+  """Contains details about an algorithm.
+
+  See the avb_vbmeta_header.h file for more details about
+  algorithms.
+
+  The constant |ALGORITHMS| is a dictionary from human-readable
+  names (e.g 'SHA256_RSA2048') to instances of this class.
+
+  Attributes:
+    algorithm_type: Integer code corresponding to |AvbAlgorithmType|.
+    hash_num_bytes: Number of bytes used to store the hash.
+    signature_num_bytes: Number of bytes used to store the signature.
+    public_key_num_bytes: Number of bytes used to store the public key.
+    padding: Padding used for signature, if any.
+  """
+
+  def __init__(self, algorithm_type, hash_num_bytes, signature_num_bytes,
+               public_key_num_bytes, padding):
+    self.algorithm_type = algorithm_type
+    self.hash_num_bytes = hash_num_bytes
+    self.signature_num_bytes = signature_num_bytes
+    self.public_key_num_bytes = public_key_num_bytes
+    self.padding = padding
+
+# This must be kept in sync with the avb_crypto.h file.
+#
+# The PKC1-v1.5 padding is a blob of binary DER of ASN.1 and is
+# obtained from section 5.2.2 of RFC 4880.
+ALGORITHMS = {
+    'NONE': Algorithm(
+        algorithm_type=0,        # AVB_ALGORITHM_TYPE_NONE
+        hash_num_bytes=0,
+        signature_num_bytes=0,
+        public_key_num_bytes=0,
+        padding=[]),
+    'SHA256_RSA2048': Algorithm(
+        algorithm_type=1,        # AVB_ALGORITHM_TYPE_SHA256_RSA2048
+        hash_num_bytes=32,
+        signature_num_bytes=256,
+        public_key_num_bytes=8 + 2*2048/8,
+        padding=[
+            # PKCS1-v1_5 padding
+            0x00, 0x01] + [0xff]*202 + [0x00] + [
+                # ASN.1 header
+                0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
+                0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05,
+                0x00, 0x04, 0x20,
+            ]),
+    'SHA256_RSA4096': Algorithm(
+        algorithm_type=2,        # AVB_ALGORITHM_TYPE_SHA256_RSA4096
+        hash_num_bytes=32,
+        signature_num_bytes=512,
+        public_key_num_bytes=8 + 2*4096/8,
+        padding=[
+            # PKCS1-v1_5 padding
+            0x00, 0x01] + [0xff]*458 + [0x00] + [
+                # ASN.1 header
+                0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
+                0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05,
+                0x00, 0x04, 0x20,
+            ]),
+    'SHA256_RSA8192': Algorithm(
+        algorithm_type=3,        # AVB_ALGORITHM_TYPE_SHA256_RSA8192
+        hash_num_bytes=32,
+        signature_num_bytes=1024,
+        public_key_num_bytes=8 + 2*8192/8,
+        padding=[
+            # PKCS1-v1_5 padding
+            0x00, 0x01] + [0xff]*970 + [0x00] + [
+                # ASN.1 header
+                0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
+                0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05,
+                0x00, 0x04, 0x20,
+            ]),
+    'SHA512_RSA2048': Algorithm(
+        algorithm_type=4,        # AVB_ALGORITHM_TYPE_SHA512_RSA2048
+        hash_num_bytes=64,
+        signature_num_bytes=256,
+        public_key_num_bytes=8 + 2*2048/8,
+        padding=[
+            # PKCS1-v1_5 padding
+            0x00, 0x01] + [0xff]*170 + [0x00] + [
+                # ASN.1 header
+                0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
+                0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05,
+                0x00, 0x04, 0x40
+            ]),
+    'SHA512_RSA4096': Algorithm(
+        algorithm_type=5,        # AVB_ALGORITHM_TYPE_SHA512_RSA4096
+        hash_num_bytes=64,
+        signature_num_bytes=512,
+        public_key_num_bytes=8 + 2*4096/8,
+        padding=[
+            # PKCS1-v1_5 padding
+            0x00, 0x01] + [0xff]*426 + [0x00] + [
+                # ASN.1 header
+                0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
+                0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05,
+                0x00, 0x04, 0x40
+            ]),
+    'SHA512_RSA8192': Algorithm(
+        algorithm_type=6,        # AVB_ALGORITHM_TYPE_SHA512_RSA8192
+        hash_num_bytes=64,
+        signature_num_bytes=1024,
+        public_key_num_bytes=8 + 2*8192/8,
+        padding=[
+            # PKCS1-v1_5 padding
+            0x00, 0x01] + [0xff]*938 + [0x00] + [
+                # ASN.1 header
+                0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
+                0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05,
+                0x00, 0x04, 0x40
+            ]),
+}
+
+
+def round_to_multiple(number, size):
+  """Rounds a number up to nearest multiple of another number.
+
+  Args:
+    number: The number to round up.
+    size: The multiple to round up to.
+
+  Returns:
+    If |number| is a multiple of |size|, returns |number|, otherwise
+    returns |number| + |size|.
+  """
+  remainder = number % size
+  if remainder == 0:
+    return number
+  return number + size - remainder
+
+
+def round_to_pow2(number):
+  """Rounds a number up to the next power of 2.
+
+  Args:
+    number: The number to round up.
+
+  Returns:
+    If |number| is already a power of 2 then |number| is
+    returned. Otherwise the smallest power of 2 greater than |number|
+    is returned.
+  """
+  return 2**((number - 1).bit_length())
+
+
+def write_long(output, num_bits, value):
+  """Writes a long to an output stream using a given amount of bits.
+
+  This number is written big-endian, e.g. with the most significant
+  bit first.
+
+  Arguments:
+    output: The object to write the output to.
+    num_bits: The number of bits to write, e.g. 2048.
+    value: The value to write.
+  """
+  for bit_pos in range(num_bits, 0, -8):
+    octet = (value >> (bit_pos - 8)) & 0xff
+    output.write(struct.pack('!B', octet))
+
+
+def encode_long(num_bits, value):
+  """Encodes a long to a bytearray() using a given amount of bits.
+
+  This number is written big-endian, e.g. with the most significant
+  bit first.
+
+  Arguments:
+    num_bits: The number of bits to write, e.g. 2048.
+    value: The value to write.
+
+  Returns:
+    A bytearray() with the encoded long.
+  """
+  ret = bytearray()
+  for bit_pos in range(num_bits, 0, -8):
+    octet = (value >> (bit_pos - 8)) & 0xff
+    ret.extend(struct.pack('!B', octet))
+  return ret
+
+
+def egcd(a, b):
+  """Calculate greatest common divisor of two numbers.
+
+  This implementation uses a recursive version of the extended
+  Euclidian algorithm.
+
+  Arguments:
+    a: First number.
+    b: Second number.
+
+  Returns:
+    A tuple (gcd, x, y) that where |gcd| is the greatest common
+    divisor of |a| and |b| and |a|*|x| + |b|*|y| = |gcd|.
+  """
+  if a == 0:
+    return (b, 0, 1)
+  else:
+    g, y, x = egcd(b % a, a)
+    return (g, x - (b // a) * y, y)
+
+
+def modinv(a, m):
+  """Calculate modular multiplicative inverse of |a| modulo |m|.
+
+  This calculates the number |x| such that |a| * |x| == 1 (modulo
+  |m|). This number only exists if |a| and |m| are co-prime - |None|
+  is returned if this isn't true.
+
+  Arguments:
+    a: The number to calculate a modular inverse of.
+    m: The modulo to use.
+
+  Returns:
+    The modular multiplicative inverse of |a| and |m| or |None| if
+    these numbers are not co-prime.
+  """
+  gcd, x, _ = egcd(a, m)
+  if gcd != 1:
+    return None  # modular inverse does not exist
+  else:
+    return x % m
+
+
+def parse_number(string):
+  """Parse a string as a number.
+
+  This is just a short-hand for int(string, 0) suitable for use in the
+  |type| parameter of |ArgumentParser|'s add_argument() function. An
+  improvement to just using type=int is that this function supports
+  numbers in other bases, e.g. "0x1234".
+
+  Arguments:
+    string: The string to parse.
+
+  Returns:
+    The parsed integer.
+
+  Raises:
+    ValueError: If the number could not be parsed.
+  """
+  return int(string, 0)
+
+
+def write_rsa_key(output, key):
+  """Writes a public RSA key in |AvbRSAPublicKeyHeader| format.
+
+  This writes the |AvbRSAPublicKeyHeader| as well as the two large
+  numbers (|key_num_bits| bits long) following it.
+
+  Arguments:
+    output: The object to write the output to.
+    key: A Crypto.PublicKey.RSA object.
+  """
+  # key.e is exponent
+  # key.n is modulus
+  key_num_bits = key.size() + 1
+  # Calculate n0inv = -1/n[0] (mod 2^32)
+  b = 2L**32
+  n0inv = b - modinv(key.n, b)
+  # Calculate rr = r^2 (mod N), where r = 2^(# of key bits)
+  r = 2L**key.n.bit_length()
+  rrmodn = r * r % key.n
+  output.write(struct.pack('!II', key_num_bits, n0inv))
+  write_long(output, key_num_bits, key.n)
+  write_long(output, key_num_bits, rrmodn)
+
+
+def encode_rsa_key(key):
+  """Encodes a public RSA key in |AvbRSAPublicKeyHeader| format.
+
+  This creates a |AvbRSAPublicKeyHeader| as well as the two large
+  numbers (|key_num_bits| bits long) following it.
+
+  Arguments:
+    key: A Crypto.PublicKey.RSA object.
+
+  Returns:
+    A bytearray() with the |AvbRSAPublicKeyHeader|.
+  """
+  ret = bytearray()
+  # key.e is exponent
+  # key.n is modulus
+  key_num_bits = key.size() + 1
+  # Calculate n0inv = -1/n[0] (mod 2^32)
+  b = 2L**32
+  n0inv = b - modinv(key.n, b)
+  # Calculate rr = r^2 (mod N), where r = 2^(# of key bits)
+  r = 2L**key.n.bit_length()
+  rrmodn = r * r % key.n
+  ret.extend(struct.pack('!II', key_num_bits, n0inv))
+  ret.extend(encode_long(key_num_bits, key.n))
+  ret.extend(encode_long(key_num_bits, rrmodn))
+  return ret
+
+
+def lookup_algorithm_by_type(alg_type):
+  """Looks up algorithm by type.
+
+  Arguments:
+    alg_type: The integer representing the type.
+
+  Returns:
+    A tuple with the algorithm name and an |Algorithm| instance.
+
+  Raises:
+    Exception: If the algorithm cannot be found
+  """
+  for alg_name in ALGORITHMS:
+    alg_data = ALGORITHMS[alg_name]
+    if alg_data.algorithm_type == alg_type:
+      return (alg_name, alg_data)
+  raise AvbError('Unknown algorithm type {}'.format(alg_type))
+
+
+class ImageChunk(object):
+  """Data structure used for representing chunks in Android sparse files.
+
+  Attributes:
+    chunk_type: One of TYPE_RAW, TYPE_FILL, or TYPE_DONT_CARE.
+    chunk_offset: Offset in the sparse file where this chunk begins.
+    output_offset: Offset in de-sparsified file where output begins.
+    output_size: Number of bytes in output.
+    input_offset: Offset in sparse file for data if TYPE_RAW otherwise None.
+    fill_data: Blob with data to fill if TYPE_FILL otherwise None.
+  """
+
+  FORMAT = '<2H2I'
+  TYPE_RAW = 0xcac1
+  TYPE_FILL = 0xcac2
+  TYPE_DONT_CARE = 0xcac3
+  TYPE_CRC32 = 0xcac4
+
+  def __init__(self, chunk_type, chunk_offset, output_offset, output_size,
+               input_offset, fill_data):
+    """Initializes an ImageChunk object.
+
+    Arguments:
+      chunk_type: One of TYPE_RAW, TYPE_FILL, or TYPE_DONT_CARE.
+      chunk_offset: Offset in the sparse file where this chunk begins.
+      output_offset: Offset in de-sparsified file.
+      output_size: Number of bytes in output.
+      input_offset: Offset in sparse file if TYPE_RAW otherwise None.
+      fill_data: Blob with data to fill if TYPE_FILL otherwise None.
+
+    Raises:
+      ValueError: If data is not well-formed.
+    """
+    self.chunk_type = chunk_type
+    self.chunk_offset = chunk_offset
+    self.output_offset = output_offset
+    self.output_size = output_size
+    self.input_offset = input_offset
+    self.fill_data = fill_data
+    # Check invariants.
+    if self.chunk_type == self.TYPE_RAW:
+      if self.fill_data is not None:
+        raise ValueError('RAW chunk cannot have fill_data set.')
+      if not self.input_offset:
+        raise ValueError('RAW chunk must have input_offset set.')
+    elif self.chunk_type == self.TYPE_FILL:
+      if self.fill_data is None:
+        raise ValueError('FILL chunk must have fill_data set.')
+      if self.input_offset:
+        raise ValueError('FILL chunk cannot have input_offset set.')
+    elif self.chunk_type == self.TYPE_DONT_CARE:
+      if self.fill_data is not None:
+        raise ValueError('DONT_CARE chunk cannot have fill_data set.')
+      if self.input_offset:
+        raise ValueError('DONT_CARE chunk cannot have input_offset set.')
+    else:
+      raise ValueError('Invalid chunk type')
+
+
+class ImageHandler(object):
+  """Abstraction for image I/O with support for Android sparse images.
+
+  This class provides an interface for working with image files that
+  may be using the Android Sparse Image format. When an instance is
+  constructed, we test whether it's an Android sparse file. If so,
+  operations will be on the sparse file by interpreting the sparse
+  format, otherwise they will be directly on the file. Either way the
+  operations do the same.
+
+  For reading, this interface mimics a file object - it has seek(),
+  tell(), and read() methods. For writing, only truncation
+  (truncate()) and appending is supported (append_raw() and
+  append_dont_care()). Additionally, data can only be written in units
+  of the block size.
+
+  Attributes:
+    is_sparse: Whether the file being operated on is sparse.
+    block_size: The block size, typically 4096.
+    image_size: The size of the unsparsified file.
+    care_size: Position in the unsparsified file where only
+      DONT_CARE data follows.
+  """
+  # See system/core/libsparse/sparse_format.h for details.
+  MAGIC = 0xed26ff3a
+  HEADER_FORMAT = '<I4H4I'
+
+  # These are formats and offset of just the |total_chunks| and
+  # |total_blocks| fields.
+  NUM_CHUNKS_AND_BLOCKS_FORMAT = '<II'
+  NUM_CHUNKS_AND_BLOCKS_OFFSET = 16
+
+  def __init__(self, image_filename):
+    """Initializes an image handler.
+
+    Arguments:
+      image_filename: The name of the file to operate on.
+
+    Raises:
+      ValueError: If data in the file is invalid.
+    """
+    self._image_filename = image_filename
+    self._read_header()
+
+  def _read_header(self):
+    """Initializes internal data structures used for reading file.
+
+    This may be called multiple times and is typically called after
+    modifying the file (e.g. appending, truncation).
+
+    Raises:
+      ValueError: If data in the file is invalid.
+    """
+    self.is_sparse = False
+    self.block_size = 4096
+    self._file_pos = 0
+    self._image = open(self._image_filename, 'r+b')
+    self._image.seek(0, os.SEEK_END)
+    self.care_size = self._image.tell()
+    self.image_size = self._image.tell()
+
+    self._image.seek(0, os.SEEK_SET)
+    header_bin = self._image.read(struct.calcsize(self.HEADER_FORMAT))
+    (magic, major_version, minor_version, file_hdr_sz, chunk_hdr_sz,
+     block_size, self._num_total_blocks, self._num_total_chunks,
+     _) = struct.unpack(self.HEADER_FORMAT, header_bin)
+    if magic != self.MAGIC:
+      # Not a sparse image, our job here is done.
+      return
+    if not (major_version == 1 and minor_version == 0):
+      raise ValueError('Encountered sparse image format version {}.{} but '
+                       'only 1.0 is supported'.format(major_version,
+                                                      minor_version))
+    if file_hdr_sz != struct.calcsize(self.HEADER_FORMAT):
+      raise ValueError('Unexpected file_hdr_sz value {}.'.
+                       format(file_hdr_sz))
+    if chunk_hdr_sz != struct.calcsize(ImageChunk.FORMAT):
+      raise ValueError('Unexpected chunk_hdr_sz value {}.'.
+                       format(chunk_hdr_sz))
+
+    self.block_size = block_size
+
+    # Build an list of chunks by parsing the file.
+    self._chunks = []
+
+    # Find the smallest offset where only "Don't care" chunks
+    # follow. This will be the size of the content in the sparse
+    # image.
+    offset = 0
+    output_offset = 0
+    last_dont_care_section_output_offset = None
+    last_section_was_dont_care = False
+    for _ in xrange(1, self._num_total_chunks + 1):
+      chunk_offset = self._image.tell()
+
+      header_bin = self._image.read(struct.calcsize(ImageChunk.FORMAT))
+      (chunk_type, _, chunk_sz, total_sz) = struct.unpack(ImageChunk.FORMAT,
+                                                          header_bin)
+      data_sz = total_sz - struct.calcsize(ImageChunk.FORMAT)
+
+      last_section_was_dont_care = False
+
+      if chunk_type == ImageChunk.TYPE_RAW:
+        if data_sz != (chunk_sz * self.block_size):
+          raise ValueError('Raw chunk input size ({}) does not match output '
+                           'size ({})'.
+                           format(data_sz, chunk_sz*self.block_size))
+        self._chunks.append(ImageChunk(ImageChunk.TYPE_RAW,
+                                       chunk_offset,
+                                       output_offset,
+                                       chunk_sz*self.block_size,
+                                       self._image.tell(),
+                                       None))
+        self._image.read(data_sz)
+
+      elif chunk_type == ImageChunk.TYPE_FILL:
+        if data_sz != 4:
+          raise ValueError('Fill chunk should have 4 bytes of fill, but this '
+                           'has {}'.format(data_sz))
+        fill_data = self._image.read(4)
+        self._chunks.append(ImageChunk(ImageChunk.TYPE_FILL,
+                                       chunk_offset,
+                                       output_offset,
+                                       chunk_sz*self.block_size,
+                                       None,
+                                       fill_data))
+      elif chunk_type == ImageChunk.TYPE_DONT_CARE:
+        if data_sz != 0:
+          raise ValueError('Don\'t care chunk input size is non-zero ({})'.
+                           format(data_sz))
+        else:
+          if not last_section_was_dont_care:
+            last_dont_care_section_output_offset = output_offset
+            last_section_was_dont_care = True
+        self._chunks.append(ImageChunk(ImageChunk.TYPE_DONT_CARE,
+                                       chunk_offset,
+                                       output_offset,
+                                       chunk_sz*self.block_size,
+                                       None,
+                                       None))
+      elif chunk_type == ImageChunk.TYPE_CRC32:
+        if data_sz != 4:
+          raise ValueError('CRC32 chunk should have 4 bytes of CRC, but '
+                           'this has {}'.format(data_sz))
+        self._image.read(4)
+      else:
+        raise ValueError('Unknown chunk type {}'.format(chunk_type))
+
+      offset += chunk_sz
+      output_offset += chunk_sz*self.block_size
+
+    # Record where sparse data end.
+    self._sparse_end = self._image.tell()
+
+    # Now that we've traversed all chunks, sanity check.
+    if self._num_total_blocks != offset:
+      raise ValueError('The header said we should have {} output blocks, '
+                       'but we saw {}'.format(self._num_total_blocks, offset))
+    junk_len = len(self._image.read())
+    if junk_len > 0:
+      raise ValueError('There were {} bytes of extra data at the end of the '
+                       'file.'.format(junk_len))
+
+    # Assign |image_size| and |care_size| attributes.
+    self.image_size = output_offset
+    if last_section_was_dont_care:
+      self.care_size = last_dont_care_section_output_offset
+    else:
+      self.care_size = output_offset
+
+    # This is used when bisecting in read() to find the initial slice.
+    self._chunk_output_offsets = [i.output_offset for i in self._chunks]
+
+    self.is_sparse = True
+
+  def _update_chunks_and_blocks(self):
+    """Helper function to update the image header.
+
+    The the |total_chunks| and |total_blocks| fields in the header
+    will be set to value of the |_num_total_blocks| and
+    |_num_total_chunks| attributes.
+
+    """
+    self._image.seek(self.NUM_CHUNKS_AND_BLOCKS_OFFSET, os.SEEK_SET)
+    self._image.write(struct.pack(self.NUM_CHUNKS_AND_BLOCKS_FORMAT,
+                                  self._num_total_blocks,
+                                  self._num_total_chunks))
+
+  def append_dont_care(self, num_bytes):
+    """Appends a DONT_CARE chunk to the sparse file.
+
+    The given number of bytes must be a multiple of the block size.
+
+    Arguments:
+      num_bytes: Size in number of bytes of the DONT_CARE chunk.
+    """
+    assert num_bytes % self.block_size == 0
+
+    if not self.is_sparse:
+      self._image.seek(0, os.SEEK_END)
+      # This is more efficient that writing NUL bytes since it'll add
+      # a hole on file systems that support sparse files (native
+      # sparse, not Android sparse).
+      self._image.truncate(self._image.tell() + num_bytes)
+      self._read_header()
+      return
+
+    self._num_total_chunks += 1
+    self._num_total_blocks += num_bytes / self.block_size
+    self._update_chunks_and_blocks()
+
+    self._image.seek(self._sparse_end, os.SEEK_SET)
+    self._image.write(struct.pack(ImageChunk.FORMAT,
+                                  ImageChunk.TYPE_DONT_CARE,
+                                  0,  # Reserved
+                                  num_bytes / self.block_size,
+                                  struct.calcsize(ImageChunk.FORMAT)))
+    self._read_header()
+
+  def append_raw(self, data):
+    """Appends a RAW chunk to the sparse file.
+
+    The length of the given data must be a multiple of the block size.
+
+    Arguments:
+      data: Data to append.
+    """
+    assert len(data) % self.block_size == 0
+
+    if not self.is_sparse:
+      self._image.seek(0, os.SEEK_END)
+      self._image.write(data)
+      self._read_header()
+      return
+
+    self._num_total_chunks += 1
+    self._num_total_blocks += len(data) / self.block_size
+    self._update_chunks_and_blocks()
+
+    self._image.seek(self._sparse_end, os.SEEK_SET)
+    self._image.write(struct.pack(ImageChunk.FORMAT,
+                                  ImageChunk.TYPE_RAW,
+                                  0,  # Reserved
+                                  len(data) / self.block_size,
+                                  len(data) +
+                                  struct.calcsize(ImageChunk.FORMAT)))
+    self._image.write(data)
+    self._read_header()
+
+  def append_fill(self, fill_data, size):
+    """Appends a fill chunk to the sparse file.
+
+    The total length of the fill data must be a multiple of the block size.
+
+    Arguments:
+      fill_data: Fill data to append - must be four bytes.
+      size: Number of chunk - must be a multiple of four and the block size.
+    """
+    assert len(fill_data) == 4
+    assert size % 4 == 0
+    assert size % self.block_size == 0
+
+    if not self.is_sparse:
+      self._image.seek(0, os.SEEK_END)
+      self._image.write(fill_data * (size/4))
+      self._read_header()
+      return
+
+    self._num_total_chunks += 1
+    self._num_total_blocks += size / self.block_size
+    self._update_chunks_and_blocks()
+
+    self._image.seek(self._sparse_end, os.SEEK_SET)
+    self._image.write(struct.pack(ImageChunk.FORMAT,
+                                  ImageChunk.TYPE_FILL,
+                                  0,  # Reserved
+                                  size / self.block_size,
+                                  4 + struct.calcsize(ImageChunk.FORMAT)))
+    self._image.write(fill_data)
+    self._read_header()
+
+  def seek(self, offset):
+    """Sets the cursor position for reading from unsparsified file.
+
+    Arguments:
+      offset: Offset to seek to from the beginning of the file.
+    """
+    self._file_pos = offset
+
+  def read(self, size):
+    """Reads data from the unsparsified file.
+
+    This method may return fewer than |size| bytes of data if the end
+    of the file was encountered.
+
+    The file cursor for reading is advanced by the number of bytes
+    read.
+
+    Arguments:
+      size: Number of bytes to read.
+
+    Returns:
+      The data.
+
+    """
+    if not self.is_sparse:
+      self._image.seek(self._file_pos)
+      data = self._image.read(size)
+      self._file_pos += len(data)
+      return data
+
+    # Iterate over all chunks.
+    chunk_idx = bisect.bisect_right(self._chunk_output_offsets,
+                                    self._file_pos) - 1
+    data = bytearray()
+    to_go = size
+    while to_go > 0:
+      chunk = self._chunks[chunk_idx]
+      chunk_pos_offset = self._file_pos - chunk.output_offset
+      chunk_pos_to_go = min(chunk.output_size - chunk_pos_offset, to_go)
+
+      if chunk.chunk_type == ImageChunk.TYPE_RAW:
+        self._image.seek(chunk.input_offset + chunk_pos_offset)
+        data.extend(self._image.read(chunk_pos_to_go))
+      elif chunk.chunk_type == ImageChunk.TYPE_FILL:
+        all_data = chunk.fill_data*(chunk_pos_to_go/len(chunk.fill_data) + 2)
+        offset_mod = chunk_pos_offset % len(chunk.fill_data)
+        data.extend(all_data[offset_mod:(offset_mod + chunk_pos_to_go)])
+      else:
+        assert chunk.chunk_type == ImageChunk.TYPE_DONT_CARE
+        data.extend('\0' * chunk_pos_to_go)
+
+      to_go -= chunk_pos_to_go
+      self._file_pos += chunk_pos_to_go
+      chunk_idx += 1
+      # Generate partial read in case of EOF.
+      if chunk_idx >= len(self._chunks):
+        break
+
+    return data
+
+  def tell(self):
+    """Returns the file cursor position for reading from unsparsified file.
+
+    Returns:
+      The file cursor position for reading.
+    """
+    return self._file_pos
+
+  def truncate(self, size):
+    """Truncates the unsparsified file.
+
+    Arguments:
+      size: Desired size of unsparsified file.
+
+    Raises:
+      ValueError: If desired size isn't a multiple of the block size.
+    """
+    if not self.is_sparse:
+      self._image.truncate(size)
+      self._read_header()
+      return
+
+    if size % self.block_size != 0:
+      raise ValueError('Cannot truncate to a size which is not a multiple '
+                       'of the block size')
+
+    if size == self.image_size:
+      # Trivial where there's nothing to do.
+      return
+    elif size < self.image_size:
+      chunk_idx = bisect.bisect_right(self._chunk_output_offsets, size) - 1
+      chunk = self._chunks[chunk_idx]
+      if chunk.output_offset != size:
+        # Truncation in the middle of a trunk - need to keep the chunk
+        # and modify it.
+        chunk_idx_for_update = chunk_idx + 1
+        num_to_keep = size - chunk.output_offset
+        assert num_to_keep % self.block_size == 0
+        if chunk.chunk_type == ImageChunk.TYPE_RAW:
+          truncate_at = (chunk.chunk_offset +
+                         struct.calcsize(ImageChunk.FORMAT) + num_to_keep)
+          data_sz = num_to_keep
+        elif chunk.chunk_type == ImageChunk.TYPE_FILL:
+          truncate_at = (chunk.chunk_offset +
+                         struct.calcsize(ImageChunk.FORMAT) + 4)
+          data_sz = 4
+        else:
+          assert chunk.chunk_type == ImageChunk.TYPE_DONT_CARE
+          truncate_at = chunk.chunk_offset + struct.calcsize(ImageChunk.FORMAT)
+          data_sz = 0
+        chunk_sz = num_to_keep/self.block_size
+        total_sz = data_sz + struct.calcsize(ImageChunk.FORMAT)
+        self._image.seek(chunk.chunk_offset)
+        self._image.write(struct.pack(ImageChunk.FORMAT,
+                                      chunk.chunk_type,
+                                      0,  # Reserved
+                                      chunk_sz,
+                                      total_sz))
+        chunk.output_size = num_to_keep
+      else:
+        # Truncation at trunk boundary.
+        truncate_at = chunk.chunk_offset
+        chunk_idx_for_update = chunk_idx
+
+      self._num_total_chunks = chunk_idx_for_update
+      self._num_total_blocks = 0
+      for i in range(0, chunk_idx_for_update):
+        self._num_total_blocks += self._chunks[i].output_size / self.block_size
+      self._update_chunks_and_blocks()
+      self._image.truncate(truncate_at)
+
+      # We've modified the file so re-read all data.
+      self._read_header()
+    else:
+      # Truncating to grow - just add a DONT_CARE section.
+      self.append_dont_care(size - self.image_size)
+
+
+class AvbDescriptor(object):
+  """Class for AVB descriptor.
+
+  See the |AvbDescriptor| C struct for more information.
+
+  Attributes:
+    tag: The tag identifying what kind of descriptor this is.
+    data: The data in the descriptor.
+  """
+
+  SIZE = 16
+  FORMAT_STRING = ('!QQ')  # tag, num_bytes_following (descriptor header)
+
+  def __init__(self, data):
+    """Initializes a new property descriptor.
+
+    Arguments:
+      data: If not None, must be a bytearray().
+
+    Raises:
+      LookupError: If the given descriptor is malformed.
+    """
+    assert struct.calcsize(self.FORMAT_STRING) == self.SIZE
+
+    if data:
+      (self.tag, num_bytes_following) = (
+          struct.unpack(self.FORMAT_STRING, data[0:self.SIZE]))
+      self.data = data[self.SIZE:self.SIZE + num_bytes_following]
+    else:
+      self.tag = None
+      self.data = None
+
+  def print_desc(self, o):
+    """Print the descriptor.
+
+    Arguments:
+      o: The object to write the output to.
+    """
+    o.write('    Unknown descriptor:\n')
+    o.write('      Tag:  {}\n'.format(self.tag))
+    if len(self.data) < 256:
+      o.write('      Data: {} ({} bytes)\n'.format(
+          repr(str(self.data)), len(self.data)))
+    else:
+      o.write('      Data: {} bytes\n'.format(len(self.data)))
+
+  def encode(self):
+    """Serializes the descriptor.
+
+    Returns:
+      A bytearray() with the descriptor data.
+    """
+    num_bytes_following = len(self.data)
+    nbf_with_padding = round_to_multiple(num_bytes_following, 8)
+    padding_size = nbf_with_padding - num_bytes_following
+    desc = struct.pack(self.FORMAT_STRING, self.tag, nbf_with_padding)
+    padding = struct.pack(str(padding_size) + 'x')
+    ret = desc + self.data + padding
+    return bytearray(ret)
+
+
+class AvbPropertyDescriptor(AvbDescriptor):
+  """A class for property descriptors.
+
+  See the |AvbPropertyDescriptor| C struct for more information.
+
+  Attributes:
+    key: The key.
+    value: The key.
+  """
+
+  TAG = 0
+  SIZE = 32
+  FORMAT_STRING = ('!QQ'  # tag, num_bytes_following (descriptor header)
+                   'Q'  # key size (bytes)
+                   'Q')  # value size (bytes)
+
+  def __init__(self, data=None):
+    """Initializes a new property descriptor.
+
+    Arguments:
+      data: If not None, must be a bytearray of size |SIZE|.
+
+    Raises:
+      LookupError: If the given descriptor is malformed.
+    """
+    AvbDescriptor.__init__(self, None)
+    assert struct.calcsize(self.FORMAT_STRING) == self.SIZE
+
+    if data:
+      (tag, num_bytes_following, key_size,
+       value_size) = struct.unpack(self.FORMAT_STRING, data[0:self.SIZE])
+      expected_size = round_to_multiple(
+          self.SIZE - 16 + key_size + 1 + value_size + 1, 8)
+      if tag != self.TAG or num_bytes_following != expected_size:
+        raise LookupError('Given data does not look like a property '
+                          'descriptor.')
+      self.key = data[self.SIZE:(self.SIZE + key_size)]
+      self.value = data[(self.SIZE + key_size + 1):(self.SIZE + key_size + 1 +
+                                                    value_size)]
+    else:
+      self.key = ''
+      self.value = ''
+
+  def print_desc(self, o):
+    """Print the descriptor.
+
+    Arguments:
+      o: The object to write the output to.
+    """
+    if len(self.value) < 256:
+      o.write('    Prop: {} -> {}\n'.format(self.key, repr(str(self.value))))
+    else:
+      o.write('    Prop: {} -> ({} bytes)\n'.format(self.key, len(self.value)))
+
+  def encode(self):
+    """Serializes the descriptor.
+
+    Returns:
+      A bytearray() with the descriptor data.
+    """
+    num_bytes_following = self.SIZE + len(self.key) + len(self.value) + 2 - 16
+    nbf_with_padding = round_to_multiple(num_bytes_following, 8)
+    padding_size = nbf_with_padding - num_bytes_following
+    desc = struct.pack(self.FORMAT_STRING, self.TAG, nbf_with_padding,
+                       len(self.key), len(self.value))
+    padding = struct.pack(str(padding_size) + 'x')
+    ret = desc + self.key + '\0' + self.value + '\0' + padding
+    return bytearray(ret)
+
+
+class AvbHashtreeDescriptor(AvbDescriptor):
+  """A class for hashtree descriptors.
+
+  See the |AvbHashtreeDescriptor| C struct for more information.
+
+  Attributes:
+    dm_verity_version: dm-verity version used.
+    image_size: Size of the image, after rounding up to |block_size|.
+    tree_offset: Offset of the hash tree in the file.
+    tree_size: Size of the tree.
+    data_block_size: Data block size
+    hash_block_size: Hash block size
+    hash_algorithm: Hash algorithm used.
+    partition_name: Partition name.
+    salt: Salt used.
+    root_digest: Root digest.
+  """
+
+  TAG = 1
+  SIZE = 96
+  FORMAT_STRING = ('!QQ'  # tag, num_bytes_following (descriptor header)
+                   'L'  # dm-verity version used
+                   'Q'  # image size (bytes)
+                   'Q'  # tree offset (bytes)
+                   'Q'  # tree size (bytes)
+                   'L'  # data block size (bytes)
+                   'L'  # hash block size (bytes)
+                   '32s'  # hash algorithm used
+                   'L'  # partition name (bytes)
+                   'L'  # salt length (bytes)
+                   'L')  # root digest length (bytes)
+
+  def __init__(self, data=None):
+    """Initializes a new hashtree descriptor.
+
+    Arguments:
+      data: If not None, must be a bytearray of size |SIZE|.
+
+    Raises:
+      LookupError: If the given descriptor is malformed.
+    """
+    AvbDescriptor.__init__(self, None)
+    assert struct.calcsize(self.FORMAT_STRING) == self.SIZE
+
+    if data:
+      (tag, num_bytes_following, self.dm_verity_version, self.image_size,
+       self.tree_offset, self.tree_size, self.data_block_size,
+       self.hash_block_size, self.hash_algorithm, partition_name_len, salt_len,
+       root_digest_len) = struct.unpack(self.FORMAT_STRING, data[0:self.SIZE])
+      expected_size = round_to_multiple(
+          self.SIZE - 16 + partition_name_len + salt_len + root_digest_len, 8)
+      if tag != self.TAG or num_bytes_following != expected_size:
+        raise LookupError('Given data does not look like a hashtree '
+                          'descriptor.')
+      # Nuke NUL-bytes at the end.
+      self.hash_algorithm = self.hash_algorithm.split('\0', 1)[0]
+      o = 0
+      self.partition_name = str(data[(self.SIZE + o):(self.SIZE + o +
+                                                      partition_name_len)])
+      # Validate UTF-8 - decode() raises UnicodeDecodeError if not valid UTF-8.
+      self.partition_name.decode('utf-8')
+      o += partition_name_len
+      self.salt = data[(self.SIZE + o):(self.SIZE + o + salt_len)]
+      o += salt_len
+      self.root_digest = data[(self.SIZE + o):(self.SIZE + o + root_digest_len)]
+      if root_digest_len != len(hashlib.new(name=self.hash_algorithm).digest()):
+        raise LookupError('root_digest_len doesn\'t match hash algorithm')
+
+    else:
+      self.dm_verity_version = 0
+      self.image_size = 0
+      self.tree_offset = 0
+      self.tree_size = 0
+      self.data_block_size = 0
+      self.hash_block_size = 0
+      self.hash_algorithm = ''
+      self.partition_name = ''
+      self.salt = bytearray()
+      self.root_digest = bytearray()
+
+  def print_desc(self, o):
+    """Print the descriptor.
+
+    Arguments:
+      o: The object to write the output to.
+    """
+    o.write('    Hashtree descriptor:\n')
+    o.write('      Version of dm-verity:  {}\n'.format(self.dm_verity_version))
+    o.write('      Image Size:            {} bytes\n'.format(self.image_size))
+    o.write('      Tree Offset:           {}\n'.format(self.tree_offset))
+    o.write('      Tree Size:             {} bytes\n'.format(self.tree_size))
+    o.write('      Data Block Size:       {} bytes\n'.format(
+        self.data_block_size))
+    o.write('      Hash Block Size:       {} bytes\n'.format(
+        self.hash_block_size))
+    o.write('      Hash Algorithm:        {}\n'.format(self.hash_algorithm))
+    o.write('      Partition Name:        {}\n'.format(self.partition_name))
+    o.write('      Salt:                  {}\n'.format(str(self.salt).encode(
+        'hex')))
+    o.write('      Root Digest:           {}\n'.format(str(
+        self.root_digest).encode('hex')))
+
+  def encode(self):
+    """Serializes the descriptor.
+
+    Returns:
+      A bytearray() with the descriptor data.
+    """
+    encoded_name = self.partition_name.encode('utf-8')
+    num_bytes_following = (self.SIZE + len(encoded_name) + len(self.salt) +
+                           len(self.root_digest) - 16)
+    nbf_with_padding = round_to_multiple(num_bytes_following, 8)
+    padding_size = nbf_with_padding - num_bytes_following
+    desc = struct.pack(self.FORMAT_STRING, self.TAG, nbf_with_padding,
+                       self.dm_verity_version, self.image_size,
+                       self.tree_offset, self.tree_size, self.data_block_size,
+                       self.hash_block_size, self.hash_algorithm,
+                       len(encoded_name), len(self.salt), len(self.root_digest))
+    padding = struct.pack(str(padding_size) + 'x')
+    ret = desc + encoded_name + self.salt + self.root_digest + padding
+    return bytearray(ret)
+
+
+class AvbHashDescriptor(AvbDescriptor):
+  """A class for hash descriptors.
+
+  See the |AvbHashDescriptor| C struct for more information.
+
+  Attributes:
+    image_size: Image size, in bytes.
+    hash_algorithm: Hash algorithm used.
+    partition_name: Partition name.
+    salt: Salt used.
+    digest: The hash value of salt and data combined.
+  """
+
+  TAG = 2
+  SIZE = 68
+  FORMAT_STRING = ('!QQ'  # tag, num_bytes_following (descriptor header)
+                   'Q'  # image size (bytes)
+                   '32s'  # hash algorithm used
+                   'L'  # partition name (bytes)
+                   'L'  # salt length (bytes)
+                   'L')  # digest length (bytes)
+
+  def __init__(self, data=None):
+    """Initializes a new hash descriptor.
+
+    Arguments:
+      data: If not None, must be a bytearray of size |SIZE|.
+
+    Raises:
+      LookupError: If the given descriptor is malformed.
+    """
+    AvbDescriptor.__init__(self, None)
+    assert struct.calcsize(self.FORMAT_STRING) == self.SIZE
+
+    if data:
+      (tag, num_bytes_following, self.image_size, self.hash_algorithm,
+       partition_name_len, salt_len,
+       digest_len) = struct.unpack(self.FORMAT_STRING, data[0:self.SIZE])
+      expected_size = round_to_multiple(
+          self.SIZE - 16 + partition_name_len + salt_len + digest_len, 8)
+      if tag != self.TAG or num_bytes_following != expected_size:
+        raise LookupError('Given data does not look like a hash ' 'descriptor.')
+      # Nuke NUL-bytes at the end.
+      self.hash_algorithm = self.hash_algorithm.split('\0', 1)[0]
+      o = 0
+      self.partition_name = str(data[(self.SIZE + o):(self.SIZE + o +
+                                                      partition_name_len)])
+      # Validate UTF-8 - decode() raises UnicodeDecodeError if not valid UTF-8.
+      self.partition_name.decode('utf-8')
+      o += partition_name_len
+      self.salt = data[(self.SIZE + o):(self.SIZE + o + salt_len)]
+      o += salt_len
+      self.digest = data[(self.SIZE + o):(self.SIZE + o + digest_len)]
+      if digest_len != len(hashlib.new(name=self.hash_algorithm).digest()):
+        raise LookupError('digest_len doesn\'t match hash algorithm')
+
+    else:
+      self.image_size = 0
+      self.hash_algorithm = ''
+      self.partition_name = ''
+      self.salt = bytearray()
+      self.digest = bytearray()
+
+  def print_desc(self, o):
+    """Print the descriptor.
+
+    Arguments:
+      o: The object to write the output to.
+    """
+    o.write('    Hash descriptor:\n')
+    o.write('      Image Size:            {} bytes\n'.format(self.image_size))
+    o.write('      Hash Algorithm:        {}\n'.format(self.hash_algorithm))
+    o.write('      Partition Name:        {}\n'.format(self.partition_name))
+    o.write('      Salt:                  {}\n'.format(str(self.salt).encode(
+        'hex')))
+    o.write('      Digest:                {}\n'.format(str(self.digest).encode(
+        'hex')))
+
+  def encode(self):
+    """Serializes the descriptor.
+
+    Returns:
+      A bytearray() with the descriptor data.
+    """
+    encoded_name = self.partition_name.encode('utf-8')
+    num_bytes_following = (
+        self.SIZE + len(encoded_name) + len(self.salt) + len(self.digest) - 16)
+    nbf_with_padding = round_to_multiple(num_bytes_following, 8)
+    padding_size = nbf_with_padding - num_bytes_following
+    desc = struct.pack(self.FORMAT_STRING, self.TAG, nbf_with_padding,
+                       self.image_size, self.hash_algorithm, len(encoded_name),
+                       len(self.salt), len(self.digest))
+    padding = struct.pack(str(padding_size) + 'x')
+    ret = desc + encoded_name + self.salt + self.digest + padding
+    return bytearray(ret)
+
+
+class AvbKernelCmdlineDescriptor(AvbDescriptor):
+  """A class for kernel command-line descriptors.
+
+  See the |AvbKernelCmdlineDescriptor| C struct for more information.
+
+  Attributes:
+    kernel_cmdline: The kernel command-line.
+  """
+
+  TAG = 3
+  SIZE = 20
+  FORMAT_STRING = ('!QQ'  # tag, num_bytes_following (descriptor header)
+                   'L')  # cmdline length (bytes)
+
+  def __init__(self, data=None):
+    """Initializes a new kernel cmdline descriptor.
+
+    Arguments:
+      data: If not None, must be a bytearray of size |SIZE|.
+
+    Raises:
+      LookupError: If the given descriptor is malformed.
+    """
+    AvbDescriptor.__init__(self, None)
+    assert struct.calcsize(self.FORMAT_STRING) == self.SIZE
+
+    if data:
+      (tag, num_bytes_following, kernel_cmdline_length) = (
+          struct.unpack(self.FORMAT_STRING, data[0:self.SIZE]))
+      expected_size = round_to_multiple(self.SIZE - 16 + kernel_cmdline_length,
+                                        8)
+      if tag != self.TAG or num_bytes_following != expected_size:
+        raise LookupError('Given data does not look like a kernel cmdline '
+                          'descriptor.')
+      # Nuke NUL-bytes at the end.
+      self.kernel_cmdline = str(data[self.SIZE:(self.SIZE +
+                                                kernel_cmdline_length)])
+      # Validate UTF-8 - decode() raises UnicodeDecodeError if not valid UTF-8.
+      self.kernel_cmdline.decode('utf-8')
+    else:
+      self.kernel_cmdline = ''
+
+  def print_desc(self, o):
+    """Print the descriptor.
+
+    Arguments:
+      o: The object to write the output to.
+    """
+    o.write('    Kernel Cmdline descriptor:\n')
+    o.write('      Kernel Cmdline:        {}\n'.format(repr(
+        self.kernel_cmdline)))
+
+  def encode(self):
+    """Serializes the descriptor.
+
+    Returns:
+      A bytearray() with the descriptor data.
+    """
+    encoded_str = self.kernel_cmdline.encode('utf-8')
+    num_bytes_following = (self.SIZE + len(encoded_str) - 16)
+    nbf_with_padding = round_to_multiple(num_bytes_following, 8)
+    padding_size = nbf_with_padding - num_bytes_following
+    desc = struct.pack(self.FORMAT_STRING, self.TAG, nbf_with_padding,
+                       len(encoded_str))
+    padding = struct.pack(str(padding_size) + 'x')
+    ret = desc + encoded_str + padding
+    return bytearray(ret)
+
+
+class AvbChainPartitionDescriptor(AvbDescriptor):
+  """A class for chained partition descriptors.
+
+  See the |AvbChainPartitionDescriptor| C struct for more information.
+
+  Attributes:
+    rollback_index_slot: The rollback index slot to use.
+    partition_name: Partition name.
+    public_key: Bytes for the public key.
+  """
+
+  TAG = 4
+  SIZE = 28
+  FORMAT_STRING = ('!QQ'  # tag, num_bytes_following (descriptor header)
+                   'L'  # rollback_index_slot
+                   'L'  # partition_name_size (bytes)
+                   'L')  # public_key_size (bytes)
+
+  def __init__(self, data=None):
+    """Initializes a new chain partition descriptor.
+
+    Arguments:
+      data: If not None, must be a bytearray of size |SIZE|.
+
+    Raises:
+      LookupError: If the given descriptor is malformed.
+    """
+    AvbDescriptor.__init__(self, None)
+    assert struct.calcsize(self.FORMAT_STRING) == self.SIZE
+
+    if data:
+      (tag, num_bytes_following, self.rollback_index_slot, partition_name_len,
+       public_key_len) = struct.unpack(self.FORMAT_STRING, data[0:self.SIZE])
+      expected_size = round_to_multiple(
+          self.SIZE - 16 + partition_name_len + public_key_len, 8)
+      if tag != self.TAG or num_bytes_following != expected_size:
+        raise LookupError('Given data does not look like a chain partition '
+                          'descriptor.')
+      o = 0
+      self.partition_name = str(data[(self.SIZE + o):(self.SIZE + o +
+                                                      partition_name_len)])
+      # Validate UTF-8 - decode() raises UnicodeDecodeError if not valid UTF-8.
+      self.partition_name.decode('utf-8')
+      o += partition_name_len
+      self.public_key = data[(self.SIZE + o):(self.SIZE + o + public_key_len)]
+
+    else:
+      self.rollback_index_slot = 0
+      self.partition_name = ''
+      self.public_key = bytearray()
+
+  def print_desc(self, o):
+    """Print the descriptor.
+
+    Arguments:
+      o: The object to write the output to.
+    """
+    o.write('    Chain Partition descriptor:\n')
+    o.write('      Partition Name:        {}\n'.format(self.partition_name))
+    o.write('      Rollback Index Slot:   {}\n'.format(
+        self.rollback_index_slot))
+    # Just show the SHA1 of the key, for size reasons.
+    hexdig = hashlib.sha1(self.public_key).hexdigest()
+    o.write('      Public key (sha1):     {}\n'.format(hexdig))
+
+  def encode(self):
+    """Serializes the descriptor.
+
+    Returns:
+      A bytearray() with the descriptor data.
+    """
+    encoded_name = self.partition_name.encode('utf-8')
+    num_bytes_following = (
+        self.SIZE + len(encoded_name) + len(self.public_key) - 16)
+    nbf_with_padding = round_to_multiple(num_bytes_following, 8)
+    padding_size = nbf_with_padding - num_bytes_following
+    desc = struct.pack(self.FORMAT_STRING, self.TAG, nbf_with_padding,
+                       self.rollback_index_slot, len(encoded_name),
+                       len(self.public_key))
+    padding = struct.pack(str(padding_size) + 'x')
+    ret = desc + encoded_name + self.public_key + padding
+    return bytearray(ret)
+
+
+DESCRIPTOR_CLASSES = [
+    AvbPropertyDescriptor, AvbHashtreeDescriptor, AvbHashDescriptor,
+    AvbKernelCmdlineDescriptor, AvbChainPartitionDescriptor
+]
+
+
+def parse_descriptors(data):
+  """Parses a blob of data into descriptors.
+
+  Arguments:
+    data: A bytearray() with encoded descriptors.
+
+  Returns:
+    A list of instances of objects derived from AvbDescriptor. For
+    unknown descriptors, the class AvbDescriptor is used.
+  """
+  o = 0
+  ret = []
+  while o < len(data):
+    tag, nb_following = struct.unpack('!2Q', data[o:o + 16])
+    if tag < len(DESCRIPTOR_CLASSES):
+      c = DESCRIPTOR_CLASSES[tag]
+    else:
+      c = AvbDescriptor
+    ret.append(c(bytearray(data[o:o + 16 + nb_following])))
+    o += 16 + nb_following
+  return ret
+
+
+class AvbFooter(object):
+  """A class for parsing and writing footers.
+
+  Footers are stored at the end of partitions and point to where the
+  AvbVBMeta blob is located. They also contain the original size of
+  the image before AVB information was added.
+
+  Attributes:
+    magic: Magic for identifying the footer, see |MAGIC|.
+    version_major: The major version of avbtool that wrote the footer.
+    version_minor: The minor version of avbtool that wrote the footer.
+    original_image_size: Original image size.
+    vbmeta_offset: Offset of where the AvbVBMeta blob is stored.
+    vbmeta_size: Size of the AvbVBMeta blob.
+  """
+
+  MAGIC = 'AVBf'
+  SIZE = 64
+  RESERVED = 28
+  FORMAT_STRING = ('!4s2L'  # magic, 2 x version.
+                   'Q'  # Original image size.
+                   'Q'  # Offset of VBMeta blob.
+                   'Q' +  # Size of VBMeta blob.
+                   str(RESERVED) + 'x')  # padding for reserved bytes
+
+  def __init__(self, data=None):
+    """Initializes a new footer object.
+
+    Arguments:
+      data: If not None, must be a bytearray of size 4096.
+
+    Raises:
+      LookupError: If the given footer is malformed.
+      struct.error: If the given data has no footer.
+    """
+    assert struct.calcsize(self.FORMAT_STRING) == self.SIZE
+
+    if data:
+      (self.magic, self.version_major, self.version_minor,
+       self.original_image_size, self.vbmeta_offset,
+       self.vbmeta_size) = struct.unpack(self.FORMAT_STRING, data)
+      if self.magic != self.MAGIC:
+        raise LookupError('Given data does not look like a Brillo  footer.')
+    else:
+      self.magic = self.MAGIC
+      self.version_major = AVB_VERSION_MAJOR
+      self.version_minor = AVB_VERSION_MINOR
+      self.original_image_size = 0
+      self.vbmeta_offset = 0
+      self.vbmeta_size = 0
+
+  def encode(self):
+    """Gets a string representing the binary encoding of the footer.
+
+    Returns:
+      A bytearray() with a binary representation of the footer.
+    """
+    return struct.pack(self.FORMAT_STRING, self.magic, self.version_major,
+                       self.version_minor, self.original_image_size,
+                       self.vbmeta_offset, self.vbmeta_size)
+
+
+class AvbVBMetaHeader(object):
+  """A class for parsing and writing Brillo Verified Boot vbmeta images.
+
+  Attributes:
+    The attributes correspond to the |AvbVBMetaHeader| struct
+    defined in avb_vbmeta_header.h.
+  """
+
+  SIZE = 256
+
+  # Keep in sync with |reserved| field of |AvbVBMetaImageHeader|.
+  RESERVED = 152
+
+  # Keep in sync with |AvbVBMetaImageHeader|.
+  FORMAT_STRING = ('!4s2L'  # magic, 2 x version
+                   '2Q'  # 2 x block size
+                   'L'  # algorithm type
+                   '2Q'  # offset, size (hash)
+                   '2Q'  # offset, size (signature)
+                   '2Q'  # offset, size (public key)
+                   '2Q'  # offset, size (descriptors)
+                   'Q' +  # rollback_index
+                   str(RESERVED) + 'x')  # padding for reserved bytes
+
+  def __init__(self, data=None):
+    """Initializes a new header object.
+
+    Arguments:
+      data: If not None, must be a bytearray of size 8192.
+
+    Raises:
+      Exception: If the given data is malformed.
+    """
+    assert struct.calcsize(self.FORMAT_STRING) == self.SIZE
+
+    if data:
+      (self.magic, self.header_version_major, self.header_version_minor,
+       self.authentication_data_block_size, self.auxiliary_data_block_size,
+       self.algorithm_type, self.hash_offset, self.hash_size,
+       self.signature_offset, self.signature_size, self.public_key_offset,
+       self.public_key_size, self.descriptors_offset, self.descriptors_size,
+       self.rollback_index) = struct.unpack(self.FORMAT_STRING, data)
+      # Nuke NUL-bytes at the end of the string.
+      if self.magic != 'AVB0':
+        raise AvbError('Given image does not look like a Brillo boot image')
+    else:
+      self.magic = 'AVB0'
+      self.header_version_major = AVB_VERSION_MAJOR
+      self.header_version_minor = AVB_VERSION_MINOR
+      self.authentication_data_block_size = 0
+      self.auxiliary_data_block_size = 0
+      self.algorithm_type = 0
+      self.hash_offset = 0
+      self.hash_size = 0
+      self.signature_offset = 0
+      self.signature_size = 0
+      self.public_key_offset = 0
+      self.public_key_size = 0
+      self.descriptors_offset = 0
+      self.descriptors_size = 0
+      self.rollback_index = 0
+
+  def save(self, output):
+    """Serializes the header (256 bytes) to disk.
+
+    Arguments:
+      output: The object to write the output to.
+    """
+    output.write(struct.pack(
+        self.FORMAT_STRING, self.magic, self.header_version_major,
+        self.header_version_minor, self.authentication_data_block_size,
+        self.auxiliary_data_block_size, self.algorithm_type, self.hash_offset,
+        self.hash_size, self.signature_offset, self.signature_size,
+        self.public_key_offset, self.public_key_size, self.descriptors_offset,
+        self.descriptors_size, self.rollback_index))
+
+  def encode(self):
+    """Serializes the header (256) to a bytearray().
+
+    Returns:
+      A bytearray() with the encoded header.
+    """
+    return struct.pack(self.FORMAT_STRING, self.magic,
+                       self.header_version_major, self.header_version_minor,
+                       self.authentication_data_block_size,
+                       self.auxiliary_data_block_size, self.algorithm_type,
+                       self.hash_offset, self.hash_size, self.signature_offset,
+                       self.signature_size, self.public_key_offset,
+                       self.public_key_size, self.descriptors_offset,
+                       self.descriptors_size, self.rollback_index)
+
+
+class Avb(object):
+  """Business logic for avbtool command-line tool."""
+
+  def erase_footer(self, image_filename, keep_hashtree):
+    """Implements the 'erase_footer' command.
+
+    Arguments:
+      image_filename: File to erase a footer from.
+      keep_hashtree: If True, keep the hashtree around.
+
+    Raises:
+      AvbError: If there's no footer in the image.
+    """
+
+    image = ImageHandler(image_filename)
+
+    (footer, _, descriptors, _) = self._parse_image(image)
+
+    if not footer:
+      raise AvbError('Given image does not have a footer.')
+
+    new_image_size = None
+    if not keep_hashtree:
+      new_image_size = footer.original_image_size
+    else:
+      # If requested to keep the hashtree, search for a hashtree
+      # descriptor to figure out the location and size of the hashtree.
+      for desc in descriptors:
+        if isinstance(desc, AvbHashtreeDescriptor):
+          # The hashtree is always just following the main data so the
+          # new size is easily derived.
+          new_image_size = desc.tree_offset + desc.tree_size
+          break
+      if not new_image_size:
+        raise AvbError('Requested to keep hashtree but no hashtree '
+                       'descriptor was found.')
+
+    # And cut...
+    image.truncate(new_image_size)
+
+  def info_image(self, image_filename, output):
+    """Implements the 'info_image' command.
+
+    Arguments:
+      image_filename: Image file to get information from (file object).
+      output: Output file to write human-readable information to (file object).
+    """
+
+    image = ImageHandler(image_filename)
+
+    o = output
+
+    (footer, header, descriptors, image_size) = self._parse_image(image)
+
+    if footer:
+      o.write('Footer version:           {}.{}\n'.format(footer.version_major,
+                                                         footer.version_minor))
+      o.write('Image size:               {} bytes\n'.format(image_size))
+      o.write('Original image size:      {} bytes\n'.format(
+          footer.original_image_size))
+      o.write('VBMeta offset:            {}\n'.format(footer.vbmeta_offset))
+      o.write('VBMeta size:              {} bytes\n'.format(footer.vbmeta_size))
+      o.write('--\n')
+
+    (alg_name, _) = lookup_algorithm_by_type(header.algorithm_type)
+
+    o.write('VBMeta image version:     {}.{}{}\n'.format(
+        header.header_version_major, header.header_version_minor,
+        ' (Sparse)' if image.is_sparse else ''))
+    o.write('Header Block:             {} bytes\n'.format(AvbVBMetaHeader.SIZE))
+    o.write('Authentication Block:     {} bytes\n'.format(
+        header.authentication_data_block_size))
+    o.write('Auxiliary Block:          {} bytes\n'.format(
+        header.auxiliary_data_block_size))
+    o.write('Algorithm:                {}\n'.format(alg_name))
+    o.write('Rollback Index:           {}\n'.format(header.rollback_index))
+
+    # Print descriptors.
+    num_printed = 0
+    o.write('Descriptors:\n')
+    for desc in descriptors:
+      desc.print_desc(o)
+      num_printed += 1
+    if num_printed == 0:
+      o.write('    (none)\n')
+
+  def _parse_image(self, image):
+    """Gets information about an image.
+
+    The image can either be a vbmeta or an image with a footer.
+
+    Arguments:
+      image: An ImageHandler (vbmeta or footer) with a hashtree descriptor.
+
+    Returns:
+      A tuple where the first argument is a AvbFooter (None if there
+      is no footer on the image), the second argument is a
+      AvbVBMetaHeader, the third argument is a list of
+      AvbDescriptor-derived instances, and the fourth argument is the
+      size of |image|.
+    """
+    assert isinstance(image, ImageHandler)
+    footer = None
+    image_size = image.care_size
+    image.seek(image_size - AvbFooter.SIZE)
+    try:
+      footer = AvbFooter(image.read(AvbFooter.SIZE))
+    except (LookupError, struct.error):
+      # Nope, just seek back to the start.
+      image.seek(0)
+
+    vbmeta_offset = 0
+    if footer:
+      vbmeta_offset = footer.vbmeta_offset
+
+    image.seek(vbmeta_offset)
+    h = AvbVBMetaHeader(image.read(AvbVBMetaHeader.SIZE))
+
+    auth_block_offset = vbmeta_offset + AvbVBMetaHeader.SIZE
+    aux_block_offset = auth_block_offset + h.authentication_data_block_size
+    desc_start_offset = aux_block_offset + h.descriptors_offset
+    image.seek(desc_start_offset)
+    descriptors = parse_descriptors(image.read(h.descriptors_size))
+
+    return footer, h, descriptors, image_size
+
+  def _get_cmdline_descriptor_for_dm_verity(self, image):
+    """Generate kernel cmdline descriptor for dm-verity.
+
+    Arguments:
+      image: An ImageHandler (vbmeta or footer) with a hashtree descriptor.
+
+    Returns:
+      A AvbKernelCmdlineDescriptor with dm-verity kernel cmdline
+      instructions for the hashtree.
+
+    Raises:
+      AvbError: If  |image| doesn't have a hashtree descriptor.
+
+    """
+
+    (_, _, descriptors, _) = self._parse_image(image)
+
+    ht = None
+    for desc in descriptors:
+      if isinstance(desc, AvbHashtreeDescriptor):
+        ht = desc
+        break
+
+    if not ht:
+      raise AvbError('No hashtree descriptor in given image')
+
+    c = 'dm="1 vroot none ro 1,'
+    c += '0 '  # start
+    c += '{} '.format((ht.image_size / 512))  # size (# sectors)
+    c += 'verity {} '.format(ht.dm_verity_version)  # type and version
+    c += 'PARTUUID=$(ANDROID_SYSTEM_PARTUUID) '  # data_dev
+    c += 'PARTUUID=$(ANDROID_SYSTEM_PARTUUID) '  # hash_dev
+    c += '{} '.format(ht.data_block_size)  # data_block
+    c += '{} '.format(ht.hash_block_size)  # hash_block
+    c += '{} '.format(ht.image_size / ht.data_block_size)  # #blocks
+    c += '{} '.format(ht.image_size / ht.data_block_size)  # hash_offset
+    c += '{} '.format(ht.hash_algorithm)  # hash_alg
+    c += '{} '.format(str(ht.root_digest).encode('hex'))  # root_digest
+    c += '{}'.format(str(ht.salt).encode('hex'))  # salt
+    c += '"'
+
+    desc = AvbKernelCmdlineDescriptor()
+    desc.kernel_cmdline = c
+    return desc
+
+  def make_vbmeta_image(self, output, chain_partitions, algorithm_name,
+                        key_path, rollback_index, props, props_from_file,
+                        kernel_cmdlines,
+                        generate_dm_verity_cmdline_from_hashtree,
+                        include_descriptors_from_image):
+    """Implements the 'make_vbmeta_image' command.
+
+    Arguments:
+      output: File to write the image to.
+      chain_partitions: List of partitions to chain.
+      algorithm_name: Name of algorithm to use.
+      key_path: Path to key to use or None.
+      rollback_index: The rollback index to use.
+      props: Properties to insert (list of strings of the form 'key:value').
+      props_from_file: Properties to insert (list of strings 'key:<path>').
+      kernel_cmdlines: Kernel cmdlines to insert (list of strings).
+      generate_dm_verity_cmdline_from_hashtree: None or file to generate from.
+      include_descriptors_from_image: List of file objects with descriptors.
+
+    Raises:
+      AvbError: If a chained partition is malformed.
+    """
+
+    descriptors = []
+
+    # Insert chained partition descriptors.
+    if chain_partitions:
+      for cp in chain_partitions:
+        cp_tokens = cp.split(':')
+        if len(cp_tokens) != 3:
+          raise AvbError('Malformed chained partition "{}".'.format(cp))
+        desc = AvbChainPartitionDescriptor()
+        desc.partition_name = cp_tokens[0]
+        desc.rollback_index_slot = int(cp_tokens[1])
+        if desc.rollback_index_slot < 1:
+          raise AvbError('Rollback index slot must be 1 or larger.')
+        file_path = cp_tokens[2]
+        desc.public_key = open(file_path, 'rb').read()
+        descriptors.append(desc)
+
+    vbmeta_blob = self._generate_vbmeta_blob(
+        algorithm_name, key_path, descriptors, rollback_index, props,
+        props_from_file, kernel_cmdlines,
+        generate_dm_verity_cmdline_from_hashtree,
+        include_descriptors_from_image)
+
+    # Write entire vbmeta blob (header, authentication, auxiliary).
+    output.seek(0)
+    output.write(vbmeta_blob)
+
+  def _generate_vbmeta_blob(self, algorithm_name, key_path, descriptors,
+                            rollback_index, props, props_from_file,
+                            kernel_cmdlines,
+                            generate_dm_verity_cmdline_from_hashtree,
+                            include_descriptors_from_image):
+    """Generates a VBMeta blob.
+
+    This blob contains the header (struct AvbVBMetaHeader), the
+    authentication data block (which contains the hash and signature
+    for the header and auxiliary block), and the auxiliary block
+    (which contains descriptors, the public key used, and other data).
+
+    The |key| parameter can |None| only if the |algorithm_name| is
+    'NONE'.
+
+    Arguments:
+      algorithm_name: The algorithm name as per the ALGORITHMS dict.
+      key_path: The path to the .pem file used to sign the blob.
+      descriptors: A list of descriptors to insert or None.
+      rollback_index: The rollback index to use.
+      props: Properties to insert (List of strings of the form 'key:value').
+      props_from_file: Properties to insert (List of strings 'key:<path>').
+      kernel_cmdlines: Kernel cmdlines to insert (list of strings).
+      generate_dm_verity_cmdline_from_hashtree: None or file to generate
+        dm-verity kernel cmdline from.
+      include_descriptors_from_image: List of file objects for which
+        to insert descriptors from.
+
+    Returns:
+      A bytearray() with the VBMeta blob.
+
+    Raises:
+      Exception: If the |algorithm_name| is not found, if no key has
+        been given and the given algorithm requires one, or the key is
+        of the wrong size.
+
+    """
+    try:
+      alg = ALGORITHMS[algorithm_name]
+    except KeyError:
+      raise AvbError('Unknown algorithm with name {}'.format(algorithm_name))
+
+    # Descriptors.
+    encoded_descriptors = bytearray()
+    if descriptors:
+      for desc in descriptors:
+        encoded_descriptors.extend(desc.encode())
+
+    # Add properties.
+    if props:
+      for prop in props:
+        idx = prop.find(':')
+        if idx == -1:
+          raise AvbError('Malformed property "{}".'.format(prop))
+        desc = AvbPropertyDescriptor()
+        desc.key = prop[0:idx]
+        desc.value = prop[(idx + 1):]
+        encoded_descriptors.extend(desc.encode())
+    if props_from_file:
+      for prop in props_from_file:
+        idx = prop.find(':')
+        if idx == -1:
+          raise AvbError('Malformed property "{}".'.format(prop))
+        desc = AvbPropertyDescriptor()
+        desc.key = prop[0:idx]
+        desc.value = prop[(idx + 1):]
+        file_path = prop[(idx + 1):]
+        desc.value = open(file_path, 'rb').read()
+        encoded_descriptors.extend(desc.encode())
+
+    # Add AvbKernelCmdline descriptor for dm-verity, if requested.
+    if generate_dm_verity_cmdline_from_hashtree:
+      image_handler = ImageHandler(
+          generate_dm_verity_cmdline_from_hashtree.name)
+      encoded_descriptors.extend(self._get_cmdline_descriptor_for_dm_verity(
+          image_handler).encode())
+
+    # Add kernel command-lines.
+    if kernel_cmdlines:
+      for i in kernel_cmdlines:
+        desc = AvbKernelCmdlineDescriptor()
+        desc.kernel_cmdline = i
+        encoded_descriptors.extend(desc.encode())
+
+    # Add descriptors from other images.
+    if include_descriptors_from_image:
+      for image in include_descriptors_from_image:
+        image_handler = ImageHandler(image.name)
+        (_, _, image_descriptors, _) = self._parse_image(image_handler)
+        for desc in image_descriptors:
+          encoded_descriptors.extend(desc.encode())
+
+    key = None
+    encoded_key = bytearray()
+    if alg.public_key_num_bytes > 0:
+      if not key_path:
+        raise AvbError('Key is required for algorithm {}'.format(
+            algorithm_name))
+      key = Crypto.PublicKey.RSA.importKey(open(key_path).read())
+      encoded_key = encode_rsa_key(key)
+      if len(encoded_key) != alg.public_key_num_bytes:
+        raise AvbError('Key is wrong size for algorithm {}'.format(
+            algorithm_name))
+
+    h = AvbVBMetaHeader()
+
+    # For the Auxiliary data block, descriptors are stored at offset 0
+    # and the public key is immediately after that.
+    h.auxiliary_data_block_size = round_to_multiple(
+        len(encoded_descriptors) + len(encoded_key), 64)
+    h.descriptors_offset = 0
+    h.descriptors_size = len(encoded_descriptors)
+    h.public_key_offset = h.descriptors_size
+    h.public_key_size = len(encoded_key)
+
+    # For the Authentication data block, the hash is first and then
+    # the signature.
+    h.authentication_data_block_size = round_to_multiple(
+        alg.hash_num_bytes + alg.public_key_num_bytes, 64)
+    h.algorithm_type = alg.algorithm_type
+    h.hash_offset = 0
+    h.hash_size = alg.hash_num_bytes
+    # Signature offset and size - it's stored right after the hash
+    # (in Authentication data block).
+    h.signature_offset = alg.hash_num_bytes
+    h.signature_size = alg.signature_num_bytes
+
+    h.rollback_index = rollback_index
+
+    # Generate Header data block.
+    header_data_blob = h.encode()
+
+    # Generate Auxiliary data block.
+    aux_data_blob = bytearray()
+    aux_data_blob.extend(encoded_descriptors)
+    aux_data_blob.extend(encoded_key)
+    padding_bytes = h.auxiliary_data_block_size - len(aux_data_blob)
+    aux_data_blob.extend('\0' * padding_bytes)
+
+    # Calculate the hash.
+    binary_hash = bytearray()
+    binary_signature = bytearray()
+    if algorithm_name != 'NONE':
+      if algorithm_name[0:6] == 'SHA256':
+        ha = hashlib.sha256()
+      elif algorithm_name[0:6] == 'SHA512':
+        ha = hashlib.sha512()
+      else:
+        raise AvbError('Unsupported algorithm {}.'.format(algorithm_name))
+      ha.update(header_data_blob)
+      ha.update(aux_data_blob)
+      binary_hash.extend(ha.digest())
+
+      # Calculate the signature.
+      p = subprocess.Popen(
+          ['openssl', 'rsautl', '-sign', '-inkey', key_path, '-raw'],
+          stdin=subprocess.PIPE,
+          stdout=subprocess.PIPE,
+          stderr=subprocess.PIPE)
+      padding_and_hash = str(bytearray(alg.padding)) + binary_hash
+      (pout, perr) = p.communicate(padding_and_hash)
+      retcode = p.wait()
+      if retcode != 0:
+        raise AvbError('Error signing: {}'.format(perr))
+      binary_signature.extend(pout)
+
+    # Generate Authentication data block.
+    auth_data_blob = bytearray()
+    auth_data_blob.extend(binary_hash)
+    auth_data_blob.extend(binary_signature)
+    padding_bytes = h.authentication_data_block_size - len(auth_data_blob)
+    auth_data_blob.extend('\0' * padding_bytes)
+
+    return header_data_blob + auth_data_blob + aux_data_blob
+
+  def extract_public_key(self, key_path, output):
+    """Implements the 'extract_public_key' command.
+
+    Arguments:
+      key_path: The path to a RSA private key file.
+      output: The file to write to.
+    """
+    key = Crypto.PublicKey.RSA.importKey(open(key_path).read())
+    write_rsa_key(output, key)
+
+  def add_hash_footer(self, image_filename, partition_size, partition_name,
+                      hash_algorithm, salt, algorithm_name, key_path,
+                      rollback_index, props, props_from_file, kernel_cmdlines,
+                      generate_dm_verity_cmdline_from_hashtree,
+                      include_descriptors_from_image):
+    """Implementation of the add_hash_footer on unsparse images.
+
+    Arguments:
+      image_filename: File to add the footer to.
+      partition_size: Size of partition.
+      partition_name: Name of partition (without A/B suffix).
+      hash_algorithm: Hash algorithm to use.
+      salt: Salt to use as a hexadecimal string or None to use /dev/urandom.
+      algorithm_name: Name of algorithm to use.
+      key_path: Path to key to use or None.
+      rollback_index: Rollback index.
+      props: Properties to insert (List of strings of the form 'key:value').
+      props_from_file: Properties to insert (List of strings 'key:<path>').
+      kernel_cmdlines: Kernel cmdlines to insert (list of strings).
+      generate_dm_verity_cmdline_from_hashtree: None or file to generate
+        dm-verity kernel cmdline from.
+      include_descriptors_from_image: List of file objects for which
+        to insert descriptors from.
+
+    Raises:
+      AvbError: If an argument is incorrect.
+    """
+    image = ImageHandler(image_filename)
+
+    if partition_size % image.block_size != 0:
+      raise AvbError('Partition size of {} is not a multiple of the image '
+                     'block size {}.'.format(partition_size,
+                                             image.block_size))
+
+    # If there's already a footer, truncate the image to its original
+    # size. This way 'avbtool add_hash_footer' is idempotent (modulo
+    # salts).
+    image_size = image.care_size
+    image.seek(image_size - AvbFooter.SIZE)
+    try:
+      footer = AvbFooter(image.read(AvbFooter.SIZE))
+      # Existing footer found. Just truncate.
+      original_image_size = footer.original_image_size
+      image_size = footer.original_image_size
+      image.truncate(image_size)
+    except (LookupError, struct.error):
+      original_image_size = image_size
+
+    # If anything goes wrong from here-on, restore the image back to
+    # its original size.
+    try:
+      digest_size = len(hashlib.new(name=hash_algorithm).digest())
+      if salt:
+        salt = salt.decode('hex')
+      else:
+        if salt is None:
+          # If salt is not explicitly specified, choose a hash
+          # that's the same size as the hash size.
+          hash_size = digest_size
+          salt = open('/dev/urandom').read(hash_size)
+        else:
+          salt = ''
+
+      hasher = hashlib.new(name=hash_algorithm, string=salt)
+      # TODO(zeuthen): might want to read this in chunks to avoid
+      # memory pressure, then again, this is only supposed to be used
+      # on kernel/initramfs partitions. Possible optimization.
+      image.seek(0)
+      hasher.update(image.read(image_size))
+      digest = hasher.digest()
+
+      h_desc = AvbHashDescriptor()
+      h_desc.image_size = image_size
+      h_desc.hash_algorithm = hash_algorithm
+      h_desc.partition_name = partition_name
+      h_desc.salt = salt
+      h_desc.digest = digest
+
+      # Generate the VBMeta footer.
+      vbmeta_blob = self._generate_vbmeta_blob(
+          algorithm_name, key_path, [h_desc], rollback_index, props,
+          props_from_file, kernel_cmdlines,
+          generate_dm_verity_cmdline_from_hashtree,
+          include_descriptors_from_image)
+
+      # We might have a DONT_CARE hole at the end (in which case
+      # |image.care_size| < |image.image_size|) so truncate here.
+      image.truncate(image.care_size)
+
+      # If the image isn't sparse, its size might not be a multiple of
+      # the block size. This will screw up padding later so just grow it.
+      if image.care_size % image.block_size != 0:
+        assert not image.is_sparse
+        padding_needed = image.block_size - (image.care_size%image.block_size)
+        image.truncate(image.care_size + padding_needed)
+
+      # The append_raw() method requires content with size being a
+      # multiple of |block_size| so add padding as needed. Also record
+      # where this is written to since we'll need to put that in the
+      # footer.
+      vbmeta_offset = image.care_size
+      padding_needed = (round_to_multiple(len(vbmeta_blob), image.block_size) -
+                        len(vbmeta_blob))
+      vbmeta_blob_with_padding = vbmeta_blob + '\0'*padding_needed
+      image.append_raw(vbmeta_blob_with_padding)
+      vbmeta_end_offset = vbmeta_offset + len(vbmeta_blob_with_padding)
+
+      # Now insert a DONT_CARE chunk with enough bytes such that the
+      # final Footer block is at the end of partition_size..
+      image.append_dont_care(partition_size - vbmeta_end_offset -
+                             1*image.block_size)
+
+      # Generate the Footer that tells where the VBMeta footer
+      # is. Also put enough padding in the front of the footer since
+      # we'll write out an entire block.
+      footer = AvbFooter()
+      footer.original_image_size = original_image_size
+      footer.vbmeta_offset = vbmeta_offset
+      footer.vbmeta_size = len(vbmeta_blob)
+      footer_blob = footer.encode()
+      footer_blob_with_padding = ('\0'*(image.block_size - AvbFooter.SIZE) +
+                                  footer_blob)
+      image.append_raw(footer_blob_with_padding)
+
+    except:
+      # Truncate back to original size, then re-raise
+      image.truncate(original_image_size)
+      raise
+
+  def add_hashtree_footer(self, image_filename, partition_size, partition_name,
+                          hash_algorithm, block_size, salt, algorithm_name,
+                          key_path, rollback_index, props, props_from_file,
+                          kernel_cmdlines,
+                          generate_dm_verity_cmdline_from_hashtree,
+                          include_descriptors_from_image):
+    """Implements the 'add_hashtree_footer' command.
+
+    See https://gitlab.com/cryptsetup/cryptsetup/wikis/DMVerity for
+    more information about dm-verity and these hashes.
+
+    Arguments:
+      image_filename: File to add the footer to.
+      partition_size: Size of partition.
+      partition_name: Name of partition (without A/B suffix).
+      hash_algorithm: Hash algorithm to use.
+      block_size: Block size to use.
+      salt: Salt to use as a hexadecimal string or None to use /dev/urandom.
+      algorithm_name: Name of algorithm to use.
+      key_path: Path to key to use or None.
+      rollback_index: Rollback index.
+      props: Properties to insert (List of strings of the form 'key:value').
+      props_from_file: Properties to insert (List of strings 'key:<path>').
+      kernel_cmdlines: Kernel cmdlines to insert (list of strings).
+      generate_dm_verity_cmdline_from_hashtree: None or file to generate
+        dm-verity kernel cmdline from.
+      include_descriptors_from_image: List of file objects for which
+        to insert descriptors from.
+
+    Raises:
+      AvbError: If an argument is incorrect.
+    """
+    image = ImageHandler(image_filename)
+
+    if partition_size % image.block_size != 0:
+      raise AvbError('Partition size of {} is not a multiple of the image '
+                     'block size {}.'.format(partition_size,
+                                             image.block_size))
+
+    # If there's already a footer, truncate the image to its original
+    # size. This way 'avbtool add_hashtree_footer' is idempotent
+    # (modulo salts).
+    image_size = image.care_size
+    image.seek(image_size - AvbFooter.SIZE)
+    try:
+      footer = AvbFooter(image.read(AvbFooter.SIZE))
+      # Existing footer found. Just truncate.
+      original_image_size = footer.original_image_size
+      image_size = footer.original_image_size
+      image.truncate(image_size)
+    except (LookupError, struct.error):
+      original_image_size = image_size
+
+    # If anything goes wrong from here-on, restore the image back to
+    # its original size.
+    try:
+      # Ensure image is multiple of block_size.
+      rounded_image_size = round_to_multiple(image_size, block_size)
+      if rounded_image_size > image_size:
+        image.append_raw('\0' * (rounded_image_size - image_size))
+        image_size = rounded_image_size
+
+      digest_size = len(hashlib.new(name=hash_algorithm).digest())
+      digest_padding = round_to_pow2(digest_size) - digest_size
+
+      if salt:
+        salt = salt.decode('hex')
+      else:
+        if salt is None:
+          # If salt is not explicitly specified, choose a hash
+          # that's the same size as the hash size.
+          hash_size = digest_size
+          salt = open('/dev/urandom').read(hash_size)
+        else:
+          salt = ''
+
+      # Hashes are stored upside down so we need to calculate hash
+      # offsets in advance.
+      (hash_level_offsets, tree_size) = calc_hash_level_offsets(
+          image_size, block_size, digest_size + digest_padding)
+
+      # We might have a DONT_CARE hole at the end (in which case
+      # |image.care_size| < |image.image_size|) so truncate here.
+      image.truncate(image.care_size)
+
+      # If the image isn't sparse, its size might not be a multiple of
+      # the block size. This will screw up padding later so just grow it.
+      if image.care_size % image.block_size != 0:
+        assert not image.is_sparse
+        padding_needed = image.block_size - (image.care_size%image.block_size)
+        image.truncate(image.care_size + padding_needed)
+
+      # Generate the tree and add padding as needed.
+      tree_offset = image.care_size
+      root_digest, hash_tree = generate_hash_tree(image, image_size,
+                                                  block_size,
+                                                  hash_algorithm, salt,
+                                                  digest_padding,
+                                                  hash_level_offsets,
+                                                  tree_size)
+      padding_needed = (round_to_multiple(len(hash_tree), image.block_size) -
+                        len(hash_tree))
+      hash_tree_with_padding = hash_tree + '\0'*padding_needed
+      image.append_raw(hash_tree_with_padding)
+
+      # Generate HashtreeDescriptor with details about the tree we
+      # just generated.
+      ht_desc = AvbHashtreeDescriptor()
+      ht_desc.dm_verity_version = 1
+      ht_desc.image_size = image_size
+      ht_desc.tree_offset = tree_offset
+      ht_desc.tree_size = tree_size
+      ht_desc.data_block_size = block_size
+      ht_desc.hash_block_size = block_size
+      ht_desc.hash_algorithm = hash_algorithm
+      ht_desc.partition_name = partition_name
+      ht_desc.salt = salt
+      ht_desc.root_digest = root_digest
+
+      # Generate the VBMeta footer and add padding as needed.
+      vbmeta_offset = tree_offset + len(hash_tree_with_padding)
+      vbmeta_blob = self._generate_vbmeta_blob(
+          algorithm_name, key_path, [ht_desc], rollback_index, props,
+          props_from_file, kernel_cmdlines,
+          generate_dm_verity_cmdline_from_hashtree,
+          include_descriptors_from_image)
+      padding_needed = (round_to_multiple(len(vbmeta_blob), image.block_size) -
+                        len(vbmeta_blob))
+      vbmeta_blob_with_padding = vbmeta_blob + '\0'*padding_needed
+      image.append_raw(vbmeta_blob_with_padding)
+
+      # Now insert a DONT_CARE chunk with enough bytes such that the
+      # final Footer block is at the end of partition_size..
+      image.append_dont_care(partition_size - image.care_size -
+                             1*image.block_size)
+
+      # Generate the Footer that tells where the VBMeta footer
+      # is. Also put enough padding in the front of the footer since
+      # we'll write out an entire block.
+      footer = AvbFooter()
+      footer.original_image_size = original_image_size
+      footer.vbmeta_offset = vbmeta_offset
+      footer.vbmeta_size = len(vbmeta_blob)
+      footer_blob = footer.encode()
+      footer_blob_with_padding = ('\0'*(image.block_size - AvbFooter.SIZE) +
+                                  footer_blob)
+      image.append_raw(footer_blob_with_padding)
+
+    except:
+      # Truncate back to original size, then re-raise
+      image.truncate(original_image_size)
+      raise
+
+
+def calc_hash_level_offsets(image_size, block_size, digest_size):
+  """Calculate the offsets of all the hash-levels in a Merkle-tree.
+
+  Arguments:
+    image_size: The size of the image to calculate a Merkle-tree for.
+    block_size: The block size, e.g. 4096.
+    digest_size: The size of each hash, e.g. 32 for SHA-256.
+
+  Returns:
+    A tuple where the first argument is an array of offsets and the
+    second is size of the tree, in bytes.
+  """
+  level_offsets = []
+  level_sizes = []
+  tree_size = 0
+
+  num_levels = 0
+  size = image_size
+  while size > block_size:
+    num_blocks = (size + block_size - 1) / block_size
+    level_size = round_to_multiple(num_blocks * digest_size, block_size)
+
+    level_sizes.append(level_size)
+    tree_size += level_size
+    num_levels += 1
+
+    size = level_size
+
+  for n in range(0, num_levels):
+    offset = 0
+    for m in range(n + 1, num_levels):
+      offset += level_sizes[m]
+    level_offsets.append(offset)
+
+  return level_offsets, tree_size
+
+
+def generate_hash_tree(image, image_size, block_size, hash_alg_name, salt,
+                       digest_padding, hash_level_offsets, tree_size):
+  """Generates a Merkle-tree for a file.
+
+  Args:
+    image: The image, as a file.
+    image_size: The size of the image.
+    block_size: The block size, e.g. 4096.
+    hash_alg_name: The hash algorithm, e.g. 'sha256' or 'sha1'.
+    salt: The salt to use.
+    digest_padding: The padding for each digest.
+    hash_level_offsets: The offsets from calc_hash_level_offsets().
+    tree_size: The size of the tree, in number of bytes.
+
+  Returns:
+    A tuple where the first element is the top-level hash and the
+    second element is the hash-tree.
+  """
+  hash_ret = bytearray(tree_size)
+  hash_src_offset = 0
+  hash_src_size = image_size
+  level_num = 0
+  while hash_src_size > block_size:
+    level_output = ''
+    remaining = hash_src_size
+    while remaining > 0:
+      hasher = hashlib.new(name=hash_alg_name, string=salt)
+      # Only read from the file for the first level - for subsequent
+      # levels, access the array we're building.
+      if level_num == 0:
+        image.seek(hash_src_offset + hash_src_size - remaining)
+        data = image.read(min(remaining, block_size))
+      else:
+        offset = hash_level_offsets[level_num - 1] + hash_src_size - remaining
+        data = hash_ret[offset:offset + block_size]
+      hasher.update(data)
+
+      remaining -= len(data)
+      if len(data) < block_size:
+        hasher.update('\0' * (block_size - len(data)))
+      level_output += hasher.digest()
+      if digest_padding > 0:
+        level_output += '\0' * digest_padding
+
+    padding_needed = (round_to_multiple(
+        len(level_output), block_size) - len(level_output))
+    level_output += '\0' * padding_needed
+
+    # Copy level-output into resulting tree.
+    offset = hash_level_offsets[level_num]
+    hash_ret[offset:offset + len(level_output)] = level_output
+
+    # Continue on to the next level.
+    hash_src_size = len(level_output)
+    level_num += 1
+
+  hasher = hashlib.new(name=hash_alg_name, string=salt)
+  hasher.update(level_output)
+  return hasher.digest(), hash_ret
+
+
+class AvbTool(object):
+  """Object for avbtool command-line tool."""
+
+  def __init__(self):
+    """Initializer method."""
+    self.avb = Avb()
+
+  def _add_common_args(self, sub_parser):
+    """Adds arguments used by several sub-commands.
+
+    Arguments:
+      sub_parser: The parser to add arguments to.
+    """
+    sub_parser.add_argument('--algorithm',
+                            help='Algorithm to use (default: NONE)',
+                            metavar='ALGORITHM',
+                            default='NONE')
+    sub_parser.add_argument('--key',
+                            help='Path to RSA private key file',
+                            metavar='KEY',
+                            required=False)
+    sub_parser.add_argument('--rollback_index',
+                            help='Rollback Index',
+                            type=parse_number,
+                            default=0)
+    sub_parser.add_argument('--prop',
+                            help='Add property',
+                            metavar='KEY:VALUE',
+                            action='append')
+    sub_parser.add_argument('--prop_from_file',
+                            help='Add property from file',
+                            metavar='KEY:PATH',
+                            action='append')
+    sub_parser.add_argument('--kernel_cmdline',
+                            help='Add kernel cmdline',
+                            metavar='CMDLINE',
+                            action='append')
+    sub_parser.add_argument('--generate_dm_verity_cmdline_from_hashtree',
+                            metavar='IMAGE',
+                            help='Generate kernel cmdline for dm-verity',
+                            type=argparse.FileType('rb'))
+    sub_parser.add_argument('--include_descriptors_from_image',
+                            help='Include descriptors from image',
+                            metavar='IMAGE',
+                            action='append',
+                            type=argparse.FileType('rb'))
+
+  def run(self, argv):
+    """Command-line processor.
+
+    Arguments:
+      argv: Pass sys.argv from main.
+    """
+    parser = argparse.ArgumentParser()
+    subparsers = parser.add_subparsers(title='subcommands')
+
+    sub_parser = subparsers.add_parser('version',
+                                       help='Prints version of avbtool.')
+    sub_parser.set_defaults(func=self.version)
+
+    sub_parser = subparsers.add_parser('extract_public_key',
+                                       help='Extract public key.')
+    sub_parser.add_argument('--key',
+                            help='Path to RSA private key file',
+                            required=True)
+    sub_parser.add_argument('--output',
+                            help='Output file name',
+                            type=argparse.FileType('wb'),
+                            required=True)
+    sub_parser.set_defaults(func=self.extract_public_key)
+
+    sub_parser = subparsers.add_parser('make_vbmeta_image',
+                                       help='Makes a vbmeta image.')
+    sub_parser.add_argument('--output',
+                            help='Output file name',
+                            type=argparse.FileType('wb'),
+                            required=True)
+    self._add_common_args(sub_parser)
+    sub_parser.add_argument('--chain_partition',
+                            help='Allow signed integrity-data for partition',
+                            metavar='PART_NAME:ROLLBACK_SLOT:KEY_PATH',
+                            action='append')
+    sub_parser.set_defaults(func=self.make_vbmeta_image)
+
+    sub_parser = subparsers.add_parser('add_hash_footer',
+                                       help='Add hashes and footer to image.')
+    sub_parser.add_argument('--image',
+                            help='Brillo boot image to add hashes to',
+                            type=argparse.FileType('rab+'))
+    sub_parser.add_argument('--partition_size',
+                            help='Partition size',
+                            type=parse_number,
+                            required=True)
+    sub_parser.add_argument('--partition_name',
+                            help='Partition name',
+                            required=True)
+    sub_parser.add_argument('--hash_algorithm',
+                            help='Hash algorithm to use (default: sha256)',
+                            default='sha256')
+    sub_parser.add_argument('--salt',
+                            help='Salt in hex (default: /dev/urandom)')
+    self._add_common_args(sub_parser)
+    sub_parser.set_defaults(func=self.add_hash_footer)
+
+    sub_parser = subparsers.add_parser('add_hashtree_footer',
+                                       help='Add hashtree and footer to image.')
+    sub_parser.add_argument('--image',
+                            help='Brillo boot image to add hashes to',
+                            type=argparse.FileType('rab+'))
+    sub_parser.add_argument('--partition_size',
+                            help='Partition size',
+                            type=parse_number,
+                            required=True)
+    sub_parser.add_argument('--partition_name',
+                            help='Partition name',
+                            required=True)
+    sub_parser.add_argument('--hash_algorithm',
+                            help='Hash algorithm to use (default: sha1)',
+                            default='sha1')
+    sub_parser.add_argument('--salt',
+                            help='Salt in hex (default: /dev/urandom)')
+    sub_parser.add_argument('--block_size',
+                            help='Block size (default: 4096)',
+                            type=parse_number,
+                            default=4096)
+    self._add_common_args(sub_parser)
+    sub_parser.set_defaults(func=self.add_hashtree_footer)
+
+    sub_parser = subparsers.add_parser('erase_footer',
+                                       help='Erase footer from an image.')
+    sub_parser.add_argument('--image',
+                            help='Brillo image with a footer',
+                            type=argparse.FileType('rwb+'),
+                            required=True)
+    sub_parser.add_argument('--keep_hashtree',
+                            help='Keep the hashtree in the image',
+                            action='store_true')
+    sub_parser.set_defaults(func=self.erase_footer)
+
+    sub_parser = subparsers.add_parser(
+        'info_image',
+        help='Show information about vbmeta or footer.')
+    sub_parser.add_argument('--image',
+                            help='Brillo boot image to use',
+                            type=argparse.FileType('rb'),
+                            required=True)
+    sub_parser.add_argument('--output',
+                            help='Write info to file',
+                            type=argparse.FileType('wt'),
+                            default=sys.stdout)
+    sub_parser.set_defaults(func=self.info_image)
+
+    args = parser.parse_args(argv[1:])
+    try:
+      args.func(args)
+    except AvbError as e:
+      sys.stderr.write('{}: {}\n'.format(argv[0], e.message))
+      sys.exit(1)
+
+  def version(self, _):
+    """Implements the 'version' sub-command."""
+    print '{}.{}'.format(AVB_VERSION_MAJOR, AVB_VERSION_MINOR)
+
+  def extract_public_key(self, args):
+    """Implements the 'extract_public_key' sub-command."""
+    self.avb.extract_public_key(args.key, args.output)
+
+  def make_vbmeta_image(self, args):
+    """Implements the 'make_vbmeta_image' sub-command."""
+    self.avb.make_vbmeta_image(args.output, args.chain_partition,
+                               args.algorithm, args.key, args.rollback_index,
+                               args.prop, args.prop_from_file,
+                               args.kernel_cmdline,
+                               args.generate_dm_verity_cmdline_from_hashtree,
+                               args.include_descriptors_from_image)
+
+  def add_hash_footer(self, args):
+    """Implements the 'add_hash_footer' sub-command."""
+    self.avb.add_hash_footer(args.image.name, args.partition_size,
+                             args.partition_name, args.hash_algorithm,
+                             args.salt, args.algorithm, args.key,
+                             args.rollback_index, args.prop,
+                             args.prop_from_file, args.kernel_cmdline,
+                             args.generate_dm_verity_cmdline_from_hashtree,
+                             args.include_descriptors_from_image)
+
+  def add_hashtree_footer(self, args):
+    """Implements the 'add_hashtree_footer' sub-command."""
+    self.avb.add_hashtree_footer(args.image.name, args.partition_size,
+                                 args.partition_name, args.hash_algorithm,
+                                 args.block_size, args.salt, args.algorithm,
+                                 args.key, args.rollback_index, args.prop,
+                                 args.prop_from_file, args.kernel_cmdline,
+                                 args.generate_dm_verity_cmdline_from_hashtree,
+                                 args.include_descriptors_from_image)
+
+  def erase_footer(self, args):
+    """Implements the 'erase_footer' sub-command."""
+    self.avb.erase_footer(args.image.name, args.keep_hashtree)
+
+  def info_image(self, args):
+    """Implements the 'info_image' sub-command."""
+    self.avb.info_image(args.image.name, args.output)
+
+
+if __name__ == '__main__':
+  tool = AvbTool()
+  tool.run(sys.argv)
diff --git a/avb/libavb/avb_chain_partition_descriptor.c b/avb/libavb/avb_chain_partition_descriptor.c
new file mode 100644
index 0000000..5815832
--- /dev/null
+++ b/avb/libavb/avb_chain_partition_descriptor.c
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "avb_chain_partition_descriptor.h"
+#include "avb_util.h"
+
+bool avb_chain_partition_descriptor_validate_and_byteswap(
+    const AvbChainPartitionDescriptor* src, AvbChainPartitionDescriptor* dest) {
+  uint64_t expected_size;
+
+  avb_memcpy(dest, src, sizeof(AvbChainPartitionDescriptor));
+
+  if (!avb_descriptor_validate_and_byteswap((const AvbDescriptor*)src,
+                                            (AvbDescriptor*)dest))
+    return false;
+
+  if (dest->parent_descriptor.tag != AVB_DESCRIPTOR_TAG_CHAIN_PARTITION) {
+    avb_error("Invalid tag for chain partition descriptor.\n");
+    return false;
+  }
+
+  dest->rollback_index_slot = avb_be32toh(dest->rollback_index_slot);
+  dest->partition_name_len = avb_be32toh(dest->partition_name_len);
+  dest->public_key_len = avb_be32toh(dest->public_key_len);
+
+  if (dest->rollback_index_slot < 1) {
+    avb_error("Invalid rollback index slot value.\n");
+    return false;
+  }
+
+  /* Check that partition_name and public_key are fully contained. */
+  expected_size = sizeof(AvbChainPartitionDescriptor) - sizeof(AvbDescriptor);
+  if (!avb_safe_add_to(&expected_size, dest->partition_name_len) ||
+      !avb_safe_add_to(&expected_size, dest->public_key_len)) {
+    avb_error("Overflow while adding up sizes.\n");
+    return false;
+  }
+  if (expected_size > dest->parent_descriptor.num_bytes_following) {
+    avb_error("Descriptor payload size overflow.\n");
+    return false;
+  }
+  return true;
+}
diff --git a/avb/libavb/avb_chain_partition_descriptor.h b/avb/libavb/avb_chain_partition_descriptor.h
new file mode 100644
index 0000000..043e1fa
--- /dev/null
+++ b/avb/libavb/avb_chain_partition_descriptor.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#if !defined(AVB_INSIDE_LIBAVB_H) && !defined(AVB_COMPILATION)
+#error "Never include this file directly, include libavb.h instead."
+#endif
+
+#ifndef AVB_CHAIN_PARTITION_DESCRIPTOR_H_
+#define AVB_CHAIN_PARTITION_DESCRIPTOR_H_
+
+#include "avb_descriptor.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* A descriptor containing a pointer to signed integrity data stored
+ * on another partition. The descriptor contains the partition name in
+ * question (without the A/B suffix), the public key used to sign the
+ * integrity data, and rollback index slot to use for rollback
+ * protection.
+ *
+ * Following this struct are |partition_name_len| bytes of the
+ * partition name (UTF-8 encoded) and |public_key_len| bytes of the
+ * public key.
+ */
+typedef struct AvbChainPartitionDescriptor {
+  AvbDescriptor parent_descriptor;
+  uint32_t rollback_index_slot;
+  uint32_t partition_name_len;
+  uint32_t public_key_len;
+} AVB_ATTR_PACKED AvbChainPartitionDescriptor;
+
+/* Copies |src| to |dest| and validates, byte-swapping fields in the
+ * process if needed. Returns true if valid, false if invalid.
+ *
+ * Data following the struct is not validated nor copied.
+ */
+bool avb_chain_partition_descriptor_validate_and_byteswap(
+    const AvbChainPartitionDescriptor* src,
+    AvbChainPartitionDescriptor* dest) AVB_ATTR_WARN_UNUSED_RESULT;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* AVB_CHAIN_PARTITION_DESCRIPTOR_H_ */
diff --git a/avb/libavb/avb_crypto.c b/avb/libavb/avb_crypto.c
new file mode 100644
index 0000000..168cfc3
--- /dev/null
+++ b/avb/libavb/avb_crypto.c
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "avb_crypto.h"
+#include "avb_rsa.h"
+#include "avb_sha.h"
+#include "avb_util.h"
+
+bool avb_rsa_public_key_header_validate_and_byteswap(
+    const AvbRSAPublicKeyHeader* src, AvbRSAPublicKeyHeader* dest) {
+  avb_memcpy(dest, src, sizeof(AvbRSAPublicKeyHeader));
+
+  dest->key_num_bits = avb_be32toh(dest->key_num_bits);
+  dest->n0inv = avb_be32toh(dest->n0inv);
+
+  return true;
+}
diff --git a/avb/libavb/avb_crypto.h b/avb/libavb/avb_crypto.h
new file mode 100644
index 0000000..5260881
--- /dev/null
+++ b/avb/libavb/avb_crypto.h
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#if !defined(AVB_INSIDE_LIBAVB_H) && !defined(AVB_COMPILATION)
+#error "Never include this file directly, include libavb.h instead."
+#endif
+
+#ifndef AVB_CRYPTO_H_
+#define AVB_CRYPTO_H_
+
+#include "avb_sysdeps.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Algorithms that can be used in the vbmeta image for
+ * verification. An algorithm consists of a hash type and a signature
+ * type.
+ *
+ * The data used to calculate the hash is the three blocks mentioned
+ * in the documentation for |AvbVBMetaImageHeader| except for the data
+ * in the "Authentication data" block.
+ *
+ * For signatures with RSA keys, PKCS v1.5 padding is used. The public
+ * key data is stored in the auxiliary data block, see
+ * |AvbRSAPublicKeyHeader| for the serialization format.
+ *
+ * Each algorithm type is described below:
+ *
+ * AVB_ALGORITHM_TYPE_NONE: There is no hash, no signature of the
+ * data, and no public key. The data cannot be verified. The fields
+ * |hash_size|, |signature_size|, and |public_key_size| must be zero.
+ *
+ * AVB_ALGORITHM_TYPE_SHA256_RSA2048: The hash function used is
+ * SHA-256, resulting in 32 bytes of hash digest data. This hash is
+ * signed with a 2048-bit RSA key. The field |hash_size| must be 32,
+ * |signature_size| must be 256, and the public key data must have
+ * |key_num_bits| set to 2048.
+ *
+ * AVB_ALGORITHM_TYPE_SHA256_RSA4096: Like above, but only with
+ * a 4096-bit RSA key and |signature_size| set to 512.
+ *
+ * AVB_ALGORITHM_TYPE_SHA256_RSA8192: Like above, but only with
+ * a 8192-bit RSA key and |signature_size| set to 1024.
+ *
+ * AVB_ALGORITHM_TYPE_SHA512_RSA2048: The hash function used is
+ * SHA-512, resulting in 64 bytes of hash digest data. This hash is
+ * signed with a 2048-bit RSA key. The field |hash_size| must be 64,
+ * |signature_size| must be 256, and the public key data must have
+ * |key_num_bits| set to 2048.
+ *
+ * AVB_ALGORITHM_TYPE_SHA512_RSA4096: Like above, but only with
+ * a 4096-bit RSA key and |signature_size| set to 512.
+ *
+ * AVB_ALGORITHM_TYPE_SHA512_RSA8192: Like above, but only with
+ * a 8192-bit RSA key and |signature_size| set to 1024.
+ */
+typedef enum {
+  AVB_ALGORITHM_TYPE_NONE,
+  AVB_ALGORITHM_TYPE_SHA256_RSA2048,
+  AVB_ALGORITHM_TYPE_SHA256_RSA4096,
+  AVB_ALGORITHM_TYPE_SHA256_RSA8192,
+  AVB_ALGORITHM_TYPE_SHA512_RSA2048,
+  AVB_ALGORITHM_TYPE_SHA512_RSA4096,
+  AVB_ALGORITHM_TYPE_SHA512_RSA8192,
+  _AVB_ALGORITHM_NUM_TYPES
+} AvbAlgorithmType;
+
+/* The header for a serialized RSA public key.
+ *
+ * The size of the key is given by |key_num_bits|, for example 2048
+ * for a RSA-2048 key. By definition, a RSA public key is the pair (n,
+ * e) where |n| is the modulus (which can be represented in
+ * |key_num_bits| bits) and |e| is the public exponent. The exponent
+ * is not stored since it's assumed to always be 65537.
+ *
+ * To optimize verification, the key block includes two precomputed
+ * values, |n0inv| (fits in 32 bits) and |rr| and can always be
+ * represented in |key_num_bits|.
+
+ * The value |n0inv| is the value -1/n[0] (mod 2^32). The value |rr|
+ * is (2^key_num_bits)^2 (mod n).
+ *
+ * Following this header is |key_num_bits| bits of |n|, then
+ * |key_num_bits| bits of |rr|. Both values are stored with most
+ * significant bit first. Each serialized number takes up
+ * |key_num_bits|/8 bytes.
+ *
+ * All fields in this struct are stored in network byte order when
+ * serialized.  To generate a copy with fields swapped to native byte
+ * order, use the function avb_rsa_public_key_header_validate_and_byteswap().
+ *
+ * The avb_rsa_verify() function expects a key in this serialized
+ * format.
+ *
+ * The 'avbtool extract_public_key' command can be used to generate a
+ * serialized RSA public key.
+ */
+typedef struct AvbRSAPublicKeyHeader {
+  uint32_t key_num_bits;
+  uint32_t n0inv;
+} AVB_ATTR_PACKED AvbRSAPublicKeyHeader;
+
+/* Copies |src| to |dest| and validates, byte-swapping fields in the
+ * process if needed. Returns true if valid, false if invalid.
+ */
+bool avb_rsa_public_key_header_validate_and_byteswap(
+    const AvbRSAPublicKeyHeader* src,
+    AvbRSAPublicKeyHeader* dest) AVB_ATTR_WARN_UNUSED_RESULT;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* AVB_CRYPTO_H_ */
diff --git a/avb/libavb/avb_descriptor.c b/avb/libavb/avb_descriptor.c
new file mode 100644
index 0000000..f8bc3a2
--- /dev/null
+++ b/avb/libavb/avb_descriptor.c
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "avb_descriptor.h"
+#include "avb_util.h"
+#include "avb_vbmeta_image.h"
+
+bool avb_descriptor_validate_and_byteswap(const AvbDescriptor* src,
+                                          AvbDescriptor* dest) {
+  dest->tag = avb_be64toh(src->tag);
+  dest->num_bytes_following = avb_be64toh(src->num_bytes_following);
+
+  if ((dest->num_bytes_following & 0x07) != 0) {
+    avb_error("Descriptor size is not divisible by 8.\n");
+    return false;
+  }
+  return true;
+}
+
+bool avb_descriptor_foreach(const uint8_t* image_data, size_t image_size,
+                            AvbDescriptorForeachFunc foreach_func,
+                            void* user_data) {
+  const AvbVBMetaImageHeader* header = NULL;
+  bool ret = false;
+  const uint8_t* image_end;
+  const uint8_t* desc_start;
+  const uint8_t* desc_end;
+  const uint8_t* p;
+
+  if (image_data == NULL) {
+    avb_error("image_data is NULL\n.");
+    goto out;
+  }
+
+  if (foreach_func == NULL) {
+    avb_error("foreach_func is NULL\n.");
+    goto out;
+  }
+
+  if (image_size < sizeof(AvbVBMetaImageHeader)) {
+    avb_error("Length is smaller than header.\n");
+    goto out;
+  }
+
+  /* Ensure magic is correct. */
+  if (avb_memcmp(image_data, AVB_MAGIC, AVB_MAGIC_LEN) != 0) {
+    avb_error("Magic is incorrect.\n");
+    goto out;
+  }
+
+  /* Careful, not byteswapped - also ensure it's aligned properly. */
+  avb_assert_word_aligned(image_data);
+  header = (const AvbVBMetaImageHeader*)image_data;
+  image_end = image_data + image_size;
+
+  desc_start = image_data + sizeof(AvbVBMetaImageHeader) +
+               avb_be64toh(header->authentication_data_block_size) +
+               avb_be64toh(header->descriptors_offset);
+
+  desc_end = desc_start + avb_be64toh(header->descriptors_size);
+
+  if (desc_start < image_data || desc_start > image_end ||
+      desc_end < image_data || desc_end > image_end || desc_end < desc_start) {
+    avb_error("Descriptors not inside passed-in data.\n");
+    goto out;
+  }
+
+  for (p = desc_start; p < desc_end;) {
+    const AvbDescriptor* dh = (const AvbDescriptor*)p;
+    avb_assert_word_aligned(dh);
+    uint64_t nb_following = avb_be64toh(dh->num_bytes_following);
+    uint64_t nb_total = sizeof(AvbDescriptor) + nb_following;
+
+    if ((nb_total & 7) != 0) {
+      avb_error("Invalid descriptor length.\n");
+      goto out;
+    }
+
+    if (nb_total + p < desc_start || nb_total + p > desc_end) {
+      avb_error("Invalid data in descriptors array.\n");
+      goto out;
+    }
+
+    if (foreach_func(dh, user_data) == 0) {
+      goto out;
+    }
+
+    p += nb_total;
+  }
+
+  ret = true;
+
+out:
+  return ret;
+}
+
+static bool count_descriptors(const AvbDescriptor* descriptor,
+                              void* user_data) {
+  size_t* num_descriptors = user_data;
+  *num_descriptors += 1;
+  return true;
+}
+
+typedef struct {
+  size_t descriptor_number;
+  const AvbDescriptor** descriptors;
+} SetDescriptorData;
+
+static bool set_descriptors(const AvbDescriptor* descriptor, void* user_data) {
+  SetDescriptorData* data = user_data;
+  data->descriptors[data->descriptor_number++] = descriptor;
+  return true;
+}
+
+const AvbDescriptor** avb_descriptor_get_all(const uint8_t* image_data,
+                                             size_t image_size,
+                                             size_t* out_num_descriptors) {
+  size_t num_descriptors = 0;
+  SetDescriptorData data;
+
+  avb_descriptor_foreach(image_data, image_size, count_descriptors,
+                         &num_descriptors);
+
+  data.descriptor_number = 0;
+  data.descriptors =
+      avb_calloc(sizeof(const AvbDescriptor*) * (num_descriptors + 1));
+  if (data.descriptors == NULL) {
+    return NULL;
+  }
+  avb_descriptor_foreach(image_data, image_size, set_descriptors, &data);
+  avb_assert(data.descriptor_number == num_descriptors);
+
+  if (out_num_descriptors != NULL) {
+    *out_num_descriptors = num_descriptors;
+  }
+
+  return data.descriptors;
+}
diff --git a/avb/libavb/avb_descriptor.h b/avb/libavb/avb_descriptor.h
new file mode 100644
index 0000000..c22ed9d
--- /dev/null
+++ b/avb/libavb/avb_descriptor.h
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#if !defined(AVB_INSIDE_LIBAVB_H) && !defined(AVB_COMPILATION)
+#error "Never include this file directly, include libavb.h instead."
+#endif
+
+#ifndef AVB_DESCRIPTOR_H_
+#define AVB_DESCRIPTOR_H_
+
+#include "avb_sysdeps.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Well-known descriptor tags.
+ *
+ * AVB_DESCRIPTOR_TAG_PROPERTY: see |AvbPropertyDescriptor| struct.
+ * AVB_DESCRIPTOR_TAG_HASHTREE: see |AvbHashtreeDescriptor| struct.
+ * AVB_DESCRIPTOR_TAG_HASH: see |AvbHashDescriptor| struct.
+ * AVB_DESCRIPTOR_TAG_KERNEL_CMDLINE: see |AvbKernelCmdlineDescriptor| struct.
+ * AVB_DESCRIPTOR_TAG_CHAIN_PARTITION: see |AvbChainPartitionDescriptor| struct.
+ */
+typedef enum {
+  AVB_DESCRIPTOR_TAG_PROPERTY,
+  AVB_DESCRIPTOR_TAG_HASHTREE,
+  AVB_DESCRIPTOR_TAG_HASH,
+  AVB_DESCRIPTOR_TAG_KERNEL_CMDLINE,
+  AVB_DESCRIPTOR_TAG_CHAIN_PARTITION,
+} AvbDescriptorTag;
+
+/* The header for a serialized descriptor.
+ *
+ * A descriptor always have two fields, a |tag| (denoting its type,
+ * see the |AvbDescriptorTag| enumeration) and the size of the bytes
+ * following, |num_bytes_following|.
+ *
+ * For padding, |num_bytes_following| is always a multiple of 8.
+ */
+typedef struct AvbDescriptor {
+  uint64_t tag;
+  uint64_t num_bytes_following;
+} AVB_ATTR_PACKED AvbDescriptor;
+
+/* Copies |src| to |dest| and validates, byte-swapping fields in the
+ * process if needed. Returns true if valid, false if invalid.
+ *
+ * Data following the struct is not validated nor copied.
+ */
+bool avb_descriptor_validate_and_byteswap(
+    const AvbDescriptor* src, AvbDescriptor* dest) AVB_ATTR_WARN_UNUSED_RESULT;
+
+/* Signature for callback function used in avb_descriptor_foreach().
+ * The passed in descriptor is given by |descriptor| and the
+ * |user_data| passed to avb_descriptor_foreach() function is in
+ * |user_data|. Return true to continue iterating, false to stop
+ * iterating.
+ *
+ * Note that |descriptor| points into the image passed to
+ * avb_descriptor_foreach() - all fields need to be byteswapped!
+ */
+typedef bool AvbDescriptorForeachFunc(const AvbDescriptor* descriptor,
+                                      void* user_data);
+
+/* Convenience function to iterate over all descriptors in an vbmeta
+ * image.
+ *
+ * The function given by |foreach_func| will be called for each
+ * descriptor. The given function should return true to continue
+ * iterating, false to stop.
+ *
+ * The |user_data| parameter will be passed to |foreach_func|.
+ *
+ * Returns false if the iteration was short-circuited, that is if
+ * an invocation of |foreach_func| returned false.
+ *
+ * Before using this function, you MUST verify |image_data| with
+ * avb_vbmeta_image_verify() and reject it unless it's signed by a known
+ * good public key. Additionally, |image_data| must be word-aligned.
+ */
+bool avb_descriptor_foreach(const uint8_t* image_data, size_t image_size,
+                            AvbDescriptorForeachFunc foreach_func,
+                            void* user_data);
+
+/* Gets all descriptors in a vbmeta image.
+ *
+ * The return value is a NULL-pointer terminated array of
+ * AvbDescriptor pointers. Free with avb_free() when you are done with
+ * it. If |out_num_descriptors| is non-NULL, the number of descriptors
+ * will be returned there.
+ *
+ * Note that each AvbDescriptor pointer in the array points into
+ * |image_data| - all fields need to be byteswapped!
+ *
+ * Before using this function, you MUST verify |image_data| with
+ * avb_vbmeta_image_verify() and reject it unless it's signed by a known
+ * good public key. Additionally, |image_data| must be word-aligned.
+ */
+const AvbDescriptor** avb_descriptor_get_all(
+    const uint8_t* image_data, size_t image_size,
+    size_t* out_num_descriptors) AVB_ATTR_WARN_UNUSED_RESULT;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* AVB_DESCRIPTOR_H_ */
diff --git a/avb/libavb/avb_footer.c b/avb/libavb/avb_footer.c
new file mode 100644
index 0000000..4f8662a
--- /dev/null
+++ b/avb/libavb/avb_footer.c
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "avb_footer.h"
+#include "avb_util.h"
+
+bool avb_footer_validate_and_byteswap(const AvbFooter* src, AvbFooter* dest) {
+  avb_memcpy(dest, src, sizeof(AvbFooter));
+
+  dest->version_major = avb_be32toh(dest->version_major);
+  dest->version_minor = avb_be32toh(dest->version_minor);
+
+  dest->original_image_size = avb_be64toh(dest->original_image_size);
+  dest->vbmeta_offset = avb_be64toh(dest->vbmeta_offset);
+  dest->vbmeta_size = avb_be64toh(dest->vbmeta_size);
+
+  /* Check that magic is correct. */
+  if (avb_safe_memcmp(dest->magic, AVB_FOOTER_MAGIC, AVB_FOOTER_MAGIC_LEN) !=
+      0) {
+    avb_error("Footer magic is incorrect.\n");
+    return false;
+  }
+
+  /* Ensure we don't attempt to access any fields if the footer major
+   * version is not supported.
+   */
+  if (dest->version_major > AVB_FOOTER_MAJOR_VERSION) {
+    avb_error("No support for footer version.\n");
+    return false;
+  }
+
+  return true;
+}
diff --git a/avb/libavb/avb_footer.h b/avb/libavb/avb_footer.h
new file mode 100644
index 0000000..3958ea7
--- /dev/null
+++ b/avb/libavb/avb_footer.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#if !defined(AVB_INSIDE_LIBAVB_H) && !defined(AVB_COMPILATION)
+#error "Never include this file directly, include libavb.h instead."
+#endif
+
+#ifndef AVB_FOOTER_H_
+#define AVB_FOOTER_H_
+
+#include "avb_sysdeps.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Magic for the footer. */
+#define AVB_FOOTER_MAGIC "AVBf"
+#define AVB_FOOTER_MAGIC_LEN 4
+
+/* Size of the footer. */
+#define AVB_FOOTER_SIZE 64
+
+/* The current MAJOR and MINOR versions used - keep in sync with avbtool. */
+#define AVB_FOOTER_MAJOR_VERSION 1
+#define AVB_FOOTER_MINOR_VERSION 0
+
+/* The struct used as a footer used on partitions, used to find the
+ * AvbVBMetaImageHeader struct. This struct is always stored at the
+ * end of a partition.
+ */
+typedef struct AvbFooter {
+  /*   0: Four bytes equal to "AVBf" (AVB_FOOTER_MAGIC). */
+  uint8_t magic[AVB_FOOTER_MAGIC_LEN];
+  /*   4: The major version of the footer struct. */
+  uint32_t version_major;
+  /*   8: The minor version of the footer struct. */
+  uint32_t version_minor;
+
+  /*  12: The original size of the image on the partition. */
+  uint64_t original_image_size;
+
+  /*  20: The offset of the |AvbVBMetaImageHeader| struct. */
+  uint64_t vbmeta_offset;
+
+  /*  28: The size of the vbmeta block (header + auth + aux blocks). */
+  uint64_t vbmeta_size;
+
+  /*  36: Padding to ensure struct is size AVB_FOOTER_SIZE bytes. This
+   * must be set to zeroes.
+   */
+  uint8_t reserved[28];
+} AVB_ATTR_PACKED AvbFooter;
+
+/* Copies |src| to |dest| and validates, byte-swapping fields in the
+ * process if needed. Returns true if valid, false if invalid.
+ */
+bool avb_footer_validate_and_byteswap(const AvbFooter* src, AvbFooter* dest)
+    AVB_ATTR_WARN_UNUSED_RESULT;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* AVB_FOOTER_H_ */
diff --git a/avb/libavb/avb_hash_descriptor.c b/avb/libavb/avb_hash_descriptor.c
new file mode 100644
index 0000000..60e9b44
--- /dev/null
+++ b/avb/libavb/avb_hash_descriptor.c
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "avb_hash_descriptor.h"
+#include "avb_util.h"
+
+bool avb_hash_descriptor_validate_and_byteswap(const AvbHashDescriptor* src,
+                                               AvbHashDescriptor* dest) {
+  uint64_t expected_size;
+
+  avb_memcpy(dest, src, sizeof(AvbHashDescriptor));
+
+  if (!avb_descriptor_validate_and_byteswap((const AvbDescriptor*)src,
+                                            (AvbDescriptor*)dest))
+    return false;
+
+  if (dest->parent_descriptor.tag != AVB_DESCRIPTOR_TAG_HASH) {
+    avb_error("Invalid tag for hash descriptor.\n");
+    return false;
+  }
+
+  dest->image_size = avb_be64toh(dest->image_size);
+  dest->partition_name_len = avb_be32toh(dest->partition_name_len);
+  dest->salt_len = avb_be32toh(dest->salt_len);
+  dest->digest_len = avb_be32toh(dest->digest_len);
+
+  /* Check that partition_name, salt, and digest are fully contained. */
+  expected_size = sizeof(AvbHashDescriptor) - sizeof(AvbDescriptor);
+  if (!avb_safe_add_to(&expected_size, dest->partition_name_len) ||
+      !avb_safe_add_to(&expected_size, dest->salt_len) ||
+      !avb_safe_add_to(&expected_size, dest->digest_len)) {
+    avb_error("Overflow while adding up sizes.\n");
+    return false;
+  }
+  if (expected_size > dest->parent_descriptor.num_bytes_following) {
+    avb_error("Descriptor payload size overflow.\n");
+    return false;
+  }
+  return true;
+}
diff --git a/avb/libavb/avb_hash_descriptor.h b/avb/libavb/avb_hash_descriptor.h
new file mode 100644
index 0000000..c3d7125
--- /dev/null
+++ b/avb/libavb/avb_hash_descriptor.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#if !defined(AVB_INSIDE_LIBAVB_H) && !defined(AVB_COMPILATION)
+#error "Never include this file directly, include libavb.h instead."
+#endif
+
+#ifndef AVB_HASH_DESCRIPTOR_H_
+#define AVB_HASH_DESCRIPTOR_H_
+
+#include "avb_descriptor.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* A descriptor containing information about hash for an image.
+ *
+ * This descriptor is typically used for boot partitions to verify the
+ * entire kernel+initramfs image before executing it.
+ *
+ * Following this struct are |partition_name_len| bytes of the
+ * partition name (UTF-8 encoded), |salt_len| bytes of salt, and then
+ * |digest_len| bytes of the digest.
+ */
+typedef struct AvbHashDescriptor {
+  AvbDescriptor parent_descriptor;
+  uint64_t image_size;
+  uint8_t hash_algorithm[32];
+  uint32_t partition_name_len;
+  uint32_t salt_len;
+  uint32_t digest_len;
+} AVB_ATTR_PACKED AvbHashDescriptor;
+
+/* Copies |src| to |dest| and validates, byte-swapping fields in the
+ * process if needed. Returns true if valid, false if invalid.
+ *
+ * Data following the struct is not validated nor copied.
+ */
+bool avb_hash_descriptor_validate_and_byteswap(const AvbHashDescriptor* src,
+                                               AvbHashDescriptor* dest)
+    AVB_ATTR_WARN_UNUSED_RESULT;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* AVB_HASH_DESCRIPTOR_H_ */
diff --git a/avb/libavb/avb_hashtree_descriptor.c b/avb/libavb/avb_hashtree_descriptor.c
new file mode 100644
index 0000000..3a79eb7
--- /dev/null
+++ b/avb/libavb/avb_hashtree_descriptor.c
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "avb_hashtree_descriptor.h"
+#include "avb_util.h"
+
+bool avb_hashtree_descriptor_validate_and_byteswap(
+    const AvbHashtreeDescriptor* src, AvbHashtreeDescriptor* dest) {
+  uint64_t expected_size;
+
+  avb_memcpy(dest, src, sizeof(AvbHashtreeDescriptor));
+
+  if (!avb_descriptor_validate_and_byteswap((const AvbDescriptor*)src,
+                                            (AvbDescriptor*)dest))
+    return false;
+
+  if (dest->parent_descriptor.tag != AVB_DESCRIPTOR_TAG_HASHTREE) {
+    avb_error("Invalid tag for hashtree descriptor.\n");
+    return false;
+  }
+
+  dest->dm_verity_version = avb_be32toh(dest->dm_verity_version);
+  dest->image_size = avb_be64toh(dest->image_size);
+  dest->tree_offset = avb_be64toh(dest->tree_offset);
+  dest->tree_size = avb_be64toh(dest->tree_size);
+  dest->data_block_size = avb_be32toh(dest->data_block_size);
+  dest->hash_block_size = avb_be32toh(dest->hash_block_size);
+  dest->partition_name_len = avb_be32toh(dest->partition_name_len);
+  dest->salt_len = avb_be32toh(dest->salt_len);
+  dest->root_digest_len = avb_be32toh(dest->root_digest_len);
+
+  /* Check that partition_name, salt, and root_digest are fully contained. */
+  expected_size = sizeof(AvbHashtreeDescriptor) - sizeof(AvbDescriptor);
+  if (!avb_safe_add_to(&expected_size, dest->partition_name_len) ||
+      !avb_safe_add_to(&expected_size, dest->salt_len) ||
+      !avb_safe_add_to(&expected_size, dest->root_digest_len)) {
+    avb_error("Overflow while adding up sizes.\n");
+    return false;
+  }
+  if (expected_size > dest->parent_descriptor.num_bytes_following) {
+    avb_error("Descriptor payload size overflow.\n");
+    return false;
+  }
+  return true;
+}
diff --git a/avb/libavb/avb_hashtree_descriptor.h b/avb/libavb/avb_hashtree_descriptor.h
new file mode 100644
index 0000000..6d1d34f
--- /dev/null
+++ b/avb/libavb/avb_hashtree_descriptor.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#if !defined(AVB_INSIDE_LIBAVB_H) && !defined(AVB_COMPILATION)
+#error "Never include this file directly, include libavb.h instead."
+#endif
+
+#ifndef AVB_HASHTREE_DESCRIPTOR_H_
+#define AVB_HASHTREE_DESCRIPTOR_H_
+
+#include "avb_descriptor.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* A descriptor containing information about a dm-verity hashtree.
+ *
+ * Hash-trees are used to verify large partitions typically containing
+ * file systems. See
+ * https://gitlab.com/cryptsetup/cryptsetup/wikis/DMVerity for more
+ * information about dm-verity.
+ *
+ * Following this struct are |partition_name_len| bytes of the
+ * partition name (UTF-8 encoded), |salt_len| bytes of salt, and then
+ * |root_digest_len| bytes of the root digest.
+ */
+typedef struct AvbHashtreeDescriptor {
+  AvbDescriptor parent_descriptor;
+  uint32_t dm_verity_version;
+  uint64_t image_size;
+  uint64_t tree_offset;
+  uint64_t tree_size;
+  uint32_t data_block_size;
+  uint32_t hash_block_size;
+  uint8_t hash_algorithm[32];
+  uint32_t partition_name_len;
+  uint32_t salt_len;
+  uint32_t root_digest_len;
+} AVB_ATTR_PACKED AvbHashtreeDescriptor;
+
+/* Copies |src| to |dest| and validates, byte-swapping fields in the
+ * process if needed. Returns true if valid, false if invalid.
+ *
+ * Data following the struct is not validated nor copied.
+ */
+bool avb_hashtree_descriptor_validate_and_byteswap(
+    const AvbHashtreeDescriptor* src,
+    AvbHashtreeDescriptor* dest) AVB_ATTR_WARN_UNUSED_RESULT;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* AVB_HASHTREE_DESCRIPTOR_H_ */
diff --git a/avb/libavb/avb_kernel_cmdline_descriptor.c b/avb/libavb/avb_kernel_cmdline_descriptor.c
new file mode 100644
index 0000000..3783140
--- /dev/null
+++ b/avb/libavb/avb_kernel_cmdline_descriptor.c
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "avb_kernel_cmdline_descriptor.h"
+#include "avb_util.h"
+
+bool avb_kernel_cmdline_descriptor_validate_and_byteswap(
+    const AvbKernelCmdlineDescriptor* src, AvbKernelCmdlineDescriptor* dest) {
+  uint64_t expected_size;
+
+  avb_memcpy(dest, src, sizeof(AvbKernelCmdlineDescriptor));
+
+  if (!avb_descriptor_validate_and_byteswap((const AvbDescriptor*)src,
+                                            (AvbDescriptor*)dest))
+    return false;
+
+  if (dest->parent_descriptor.tag != AVB_DESCRIPTOR_TAG_KERNEL_CMDLINE) {
+    avb_error("Invalid tag for kernel cmdline descriptor.\n");
+    return false;
+  }
+
+  dest->kernel_cmdline_length = avb_be32toh(dest->kernel_cmdline_length);
+
+  /* Check that kernel_cmdline is fully contained. */
+  expected_size = sizeof(AvbKernelCmdlineDescriptor) - sizeof(AvbDescriptor);
+  if (!avb_safe_add_to(&expected_size, dest->kernel_cmdline_length)) {
+    avb_error("Overflow while adding up sizes.\n");
+    return false;
+  }
+  if (expected_size > dest->parent_descriptor.num_bytes_following) {
+    avb_error("Descriptor payload size overflow.\n");
+    return false;
+  }
+
+  return true;
+}
diff --git a/avb/libavb/avb_kernel_cmdline_descriptor.h b/avb/libavb/avb_kernel_cmdline_descriptor.h
new file mode 100644
index 0000000..bba8bc2
--- /dev/null
+++ b/avb/libavb/avb_kernel_cmdline_descriptor.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#if !defined(AVB_INSIDE_LIBAVB_H) && !defined(AVB_COMPILATION)
+#error "Never include this file directly, include libavb.h instead."
+#endif
+
+#ifndef AVB_KERNEL_CMDLINE_DESCRIPTOR_H_
+#define AVB_KERNEL_CMDLINE_DESCRIPTOR_H_
+
+#include "avb_descriptor.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* A descriptor containing information to be appended to the kernel
+ * command-line.
+ *
+ * Following this struct are |kernel_cmdline_len| bytes with the
+ * kernel command-line (UTF-8 encoded).
+ */
+typedef struct AvbKernelCmdlineDescriptor {
+  AvbDescriptor parent_descriptor;
+  uint32_t kernel_cmdline_length;
+} AVB_ATTR_PACKED AvbKernelCmdlineDescriptor;
+
+/* Copies |src| to |dest| and validates, byte-swapping fields in the
+ * process if needed. Returns true if valid, false if invalid.
+ *
+ * Data following the struct is not validated nor copied.
+ */
+bool avb_kernel_cmdline_descriptor_validate_and_byteswap(
+    const AvbKernelCmdlineDescriptor* src,
+    AvbKernelCmdlineDescriptor* dest) AVB_ATTR_WARN_UNUSED_RESULT;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* AVB_KERNEL_CMDLINE_DESCRIPTOR_H_ */
diff --git a/avb/libavb/avb_ops.h b/avb/libavb/avb_ops.h
new file mode 100644
index 0000000..e21e846
--- /dev/null
+++ b/avb/libavb/avb_ops.h
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#if !defined(AVB_INSIDE_LIBAVB_H) && !defined(AVB_COMPILATION)
+#error "Never include this file directly, include libavb.h instead."
+#endif
+
+#ifndef AVB_OPS_H_
+#define AVB_OPS_H_
+
+#include "avb_sysdeps.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Return codes used for I/O operations.
+ *
+ * AVB_IO_RESULT_OK is returned if the requested operation was
+ * successful.
+ *
+ * AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION is returned if the requested
+ * partition does not exist.
+ *
+ * AVB_IO_RESULT_ERROR_RANGE_OUTSIDE_PARTITION is returned if the
+ * range of bytes requested to be read or written is outside the range
+ * of the partition.
+ *
+ * AVB_IO_RESULT_ERROR_IO is returned if the underlying disk
+ * encountered an I/O error.
+ */
+typedef enum {
+  AVB_IO_RESULT_OK,
+  AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION,
+  AVB_IO_RESULT_ERROR_RANGE_OUTSIDE_PARTITION,
+  AVB_IO_RESULT_ERROR_IO,
+} AvbIOResult;
+
+struct AvbOps;
+typedef struct AvbOps AvbOps;
+
+/* High-level operations/functions/methods that are platform
+ * dependent.
+ */
+struct AvbOps {
+  /* Reads |num_bytes| from offset |offset| from partition with name
+   * |partition| (NUL-terminated UTF-8 string). If |offset| is
+   * negative, its absolute value should be interpreted as the number
+   * of bytes from the end of the partition.
+   *
+   * This function returns AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION if
+   * there is no partition with the given name,
+   * AVB_IO_RESULT_ERROR_RANGE_OUTSIDE_PARTITION if the requested
+   * |offset| is outside the partition, and AVB_IO_RESULT_ERROR_IO if
+   * there was an I/O error from the underlying I/O subsystem.  If the
+   * operation succeeds as requested AVB_IO_RESULT_OK is returned and
+   * the data is available in |buffer|.
+   *
+   * The only time partial I/O may occur is if reading beyond the end
+   * of the partition. In this case the value returned in
+   * |out_num_read| may be smaller than |num_bytes|.
+   */
+  AvbIOResult (*read_from_partition)(AvbOps* ops, const char* partition,
+                                     int64_t offset, size_t num_bytes,
+                                     void* buffer, size_t* out_num_read);
+
+  /* Writes |num_bytes| from |bffer| at offset |offset| to partition
+   * with name |partition| (NUL-terminated UTF-8 string). If |offset|
+   * is negative, its absolute value should be interpreted as the
+   * number of bytes from the end of the partition.
+   *
+   * This function returns AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION if
+   * there is no partition with the given name,
+   * AVB_IO_RESULT_ERROR_RANGE_OUTSIDE_PARTITION if the requested
+   * byterange goes outside the partition, and AVB_IO_RESULT_ERROR_IO
+   * if there was an I/O error from the underlying I/O subsystem.  If
+   * the operation succeeds as requested AVB_IO_RESULT_OK is
+   * returned.
+   *
+   * This function never does any partial I/O, it either transfers all
+   * of the requested bytes or returns an error.
+   */
+  AvbIOResult (*write_to_partition)(AvbOps* ops, const char* partition,
+                                    int64_t offset, size_t num_bytes,
+                                    const void* buffer);
+
+  /* Checks if the given public key used to sign the 'vbmeta'
+   * partition is trusted. Boot loaders typically compare this with
+   * embedded key material generated with 'avbtool
+   * extract_public_key'.
+   *
+   * Return true if trusted or false if untrusted.
+   */
+  bool (*validate_vbmeta_public_key)(AvbOps* ops,
+                                     const uint8_t* public_key_data,
+                                     size_t public_key_length);
+
+  /* Gets the rollback index corresponding to the slot given by
+   * |rollback_index_slot|. The value is returned in
+   * |out_rollback_index|. Returns true if the rollback index was
+   * retrieved, false on error.
+   *
+   * A device may have a limited amount of rollback index slots (say,
+   * one or four) so may error out if |rollback_index_slot| exceeds
+   * this number.
+   */
+  bool (*read_rollback_index)(AvbOps* ops, size_t rollback_index_slot,
+                              uint64_t* out_rollback_index);
+
+  /* Sets the rollback index corresponding to the slot given by
+   * |rollback_index_slot| to |rollback_index|. Returns true if
+   * the rollback index was set, false on error.
+   *
+   * A device may have a limited amount of rollback index slots (say,
+   * one or four) so may error out if |rollback_index_slot| exceeds
+   * this number.
+   */
+  bool (*write_rollback_index)(AvbOps* ops, size_t rollback_index_slot,
+                               uint64_t rollback_index);
+
+  /* Gets whether the device is unlocked. The value is returned in
+   * |out_is_unlocked| (true if unlocked, false otherwise). Returns
+   * true if the state was retrieved, false on error.
+   */
+  bool (*read_is_device_unlocked)(AvbOps* ops, bool* out_is_unlocked);
+
+  /* Gets the unique partition GUID for a partition with name in
+   * |partition| (NUL-terminated UTF-8 string). The GUID is copied as
+   * a string into |guid_buf| of size |guid_buf_size| and will be NUL
+   * terminated. The string must be lower-case and properly
+   * hyphenated. For example:
+   *
+   *  527c1c6d-6361-4593-8842-3c78fcd39219
+   *
+   * Returns false if the operation fails (no such partition or
+   * |buf_size| is too small), true if it succeeds.
+   */
+  bool (*get_unique_guid_for_partition)(AvbOps* ops, const char* partition,
+                                        char* guid_buf, size_t guid_buf_size);
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* AVB_OPS_H_ */
diff --git a/avb/libavb/avb_property_descriptor.c b/avb/libavb/avb_property_descriptor.c
new file mode 100644
index 0000000..4bd010f
--- /dev/null
+++ b/avb/libavb/avb_property_descriptor.c
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "avb_property_descriptor.h"
+#include "avb_util.h"
+
+bool avb_property_descriptor_validate_and_byteswap(
+    const AvbPropertyDescriptor* src, AvbPropertyDescriptor* dest) {
+  uint64_t expected_size;
+
+  avb_memcpy(dest, src, sizeof(AvbPropertyDescriptor));
+
+  if (!avb_descriptor_validate_and_byteswap((const AvbDescriptor*)src,
+                                            (AvbDescriptor*)dest))
+    return false;
+
+  if (dest->parent_descriptor.tag != AVB_DESCRIPTOR_TAG_PROPERTY) {
+    avb_error("Invalid tag for property descriptor.\n");
+    return false;
+  }
+
+  dest->key_num_bytes = avb_be64toh(dest->key_num_bytes);
+  dest->value_num_bytes = avb_be64toh(dest->value_num_bytes);
+
+  /* Check that key and value are fully contained. */
+  expected_size = sizeof(AvbPropertyDescriptor) - sizeof(AvbDescriptor) + 2;
+  if (!avb_safe_add_to(&expected_size, dest->key_num_bytes) ||
+      !avb_safe_add_to(&expected_size, dest->value_num_bytes)) {
+    avb_error("Overflow while adding up sizes.\n");
+    return false;
+  }
+  if (expected_size > dest->parent_descriptor.num_bytes_following) {
+    avb_error("Descriptor payload size overflow.\n");
+    return false;
+  }
+
+  return true;
+}
+
+typedef struct {
+  const char* key;
+  size_t key_size;
+  const char* ret_value;
+  size_t ret_value_size;
+} PropertyIteratorData;
+
+static bool property_lookup_desc_foreach(const AvbDescriptor* header,
+                                         void* user_data) {
+  PropertyIteratorData* data = (PropertyIteratorData*)user_data;
+  AvbPropertyDescriptor prop_desc;
+  const uint8_t* p;
+  bool ret = true;
+
+  if (header->tag != AVB_DESCRIPTOR_TAG_PROPERTY) goto out;
+
+  if (!avb_property_descriptor_validate_and_byteswap(
+          (const AvbPropertyDescriptor*)header, &prop_desc))
+    goto out;
+
+  p = (const uint8_t*)header;
+  if (p[sizeof(AvbPropertyDescriptor) + prop_desc.key_num_bytes] != 0) {
+    avb_error("No terminating NUL byte in key.\n");
+    goto out;
+  }
+
+  if (data->key_size == prop_desc.key_num_bytes) {
+    if (avb_memcmp(p + sizeof(AvbPropertyDescriptor), data->key,
+                   data->key_size) == 0) {
+      data->ret_value = (const char*)(p + sizeof(AvbPropertyDescriptor) +
+                                      prop_desc.key_num_bytes + 1);
+      data->ret_value_size = prop_desc.value_num_bytes;
+      /* Stop iterating. */
+      ret = false;
+      goto out;
+    }
+  }
+
+out:
+  return ret;
+}
+
+const char* avb_property_lookup(const uint8_t* image_data, size_t image_size,
+                                const char* key, size_t key_size,
+                                size_t* out_value_size) {
+  PropertyIteratorData data;
+
+  if (key_size == 0) key_size = avb_strlen(key);
+
+  data.key = key;
+  data.key_size = key_size;
+
+  if (avb_descriptor_foreach(image_data, image_size,
+                             property_lookup_desc_foreach, &data) == 0) {
+    if (out_value_size != NULL) *out_value_size = data.ret_value_size;
+    return data.ret_value;
+  }
+
+  if (out_value_size != NULL) *out_value_size = 0;
+  return NULL;
+}
+
+bool avb_property_lookup_uint64(const uint8_t* image_data, size_t image_size,
+                                const char* key, size_t key_size,
+                                uint64_t* out_value) {
+  const char* value;
+  bool ret = false;
+  uint64_t parsed_val;
+  int base;
+  int n;
+
+  value = avb_property_lookup(image_data, image_size, key, key_size, NULL);
+  if (value == NULL) goto out;
+
+  base = 10;
+  if (avb_memcmp(value, "0x", 2) == 0) {
+    base = 16;
+    value += 2;
+  }
+
+  parsed_val = 0;
+  for (n = 0; value[n] != '\0'; n++) {
+    int c = value[n];
+    int digit;
+
+    parsed_val *= base;
+
+    if (c >= '0' && c <= '9') {
+      digit = c - '0';
+    } else if (base == 16 && c >= 'a' && c <= 'f') {
+      digit = c - 'a' + 10;
+    } else if (base == 16 && c >= 'A' && c <= 'F') {
+      digit = c - 'A' + 10;
+    } else {
+      avb_error("Invalid digit.\n");
+      goto out;
+    }
+
+    parsed_val += digit;
+  }
+
+  ret = true;
+  if (out_value != NULL) *out_value = parsed_val;
+
+out:
+  return ret;
+}
diff --git a/avb/libavb/avb_property_descriptor.h b/avb/libavb/avb_property_descriptor.h
new file mode 100644
index 0000000..1d5a3cb
--- /dev/null
+++ b/avb/libavb/avb_property_descriptor.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#if !defined(AVB_INSIDE_LIBAVB_H) && !defined(AVB_COMPILATION)
+#error "Never include this file directly, include libavb.h instead."
+#endif
+
+#ifndef AVB_PROPERTY_DESCRIPTOR_H_
+#define AVB_PROPERTY_DESCRIPTOR_H_
+
+#include "avb_descriptor.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* A descriptor for properties (free-form key/value pairs).
+ *
+ * Following this struct are |key_num_bytes| bytes of key data,
+ * followed by a NUL byte, then |value_num_bytes| bytes of value data,
+ * followed by a NUL byte and then enough padding to make the combined
+ * size a multiple of 8.
+ */
+typedef struct AvbPropertyDescriptor {
+  AvbDescriptor parent_descriptor;
+  uint64_t key_num_bytes;
+  uint64_t value_num_bytes;
+} AVB_ATTR_PACKED AvbPropertyDescriptor;
+
+/* Copies |src| to |dest| and validates, byte-swapping fields in the
+ * process if needed. Returns true if valid, false if invalid.
+ *
+ * Data following the struct is not validated nor copied.
+ */
+bool avb_property_descriptor_validate_and_byteswap(
+    const AvbPropertyDescriptor* src,
+    AvbPropertyDescriptor* dest) AVB_ATTR_WARN_UNUSED_RESULT;
+
+/* Convenience function for looking up the value for a property with
+ * name |key| in a vbmeta image. If |key_size| is 0, |key| must be
+ * NUL-terminated.
+ *
+ * The |image_data| parameter must be a pointer to a vbmeta image of
+ * size |image_size|.
+ *
+ * This function returns a pointer to the value inside the passed-in
+ * image or NULL if not found. Note that the value is always
+ * guaranteed to be followed by a NUL byte.
+ *
+ * If the value was found and |out_value_size| is not NULL, the size
+ * of the value is returned there.
+ *
+ * This function is O(n) in number of descriptors so if you need to
+ * look up a lot of values, you may want to build a more efficient
+ * lookup-table by manually walking all descriptors using
+ * avb_descriptor_foreach().
+ *
+ * Before using this function, you MUST verify |image_data| with
+ * avb_vbmeta_image_verify() and reject it unless it's signed by a
+ * known good public key.
+ */
+const char* avb_property_lookup(
+    const uint8_t* image_data, size_t image_size, const char* key,
+    size_t key_size, size_t* out_value_size) AVB_ATTR_WARN_UNUSED_RESULT;
+
+/* Like avb_property_lookup() but parses the intial portions of the
+ * value as an unsigned 64-bit integer. Both decimal and hexadecimal
+ * representations (e.g. "0x2a") are supported. Returns false on
+ * failure and true on success. On success, the parsed value is
+ * returned in |out_value|.
+ */
+bool avb_property_lookup_uint64(
+    const uint8_t* image_data, size_t image_size, const char* key,
+    size_t key_size, uint64_t* out_value) AVB_ATTR_WARN_UNUSED_RESULT;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* AVB_PROPERTY_DESCRIPTOR_H_ */
diff --git a/avb/libavb/avb_rsa.c b/avb/libavb/avb_rsa.c
new file mode 100644
index 0000000..5878c19
--- /dev/null
+++ b/avb/libavb/avb_rsa.c
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/* Implementation of RSA signature verification which uses a pre-processed
+ * key for computation. The code extends libmincrypt RSA verification code to
+ * support multiple RSA key lengths and hash digest algorithms.
+ */
+
+#include "avb_rsa.h"
+#include "avb_sha.h"
+#include "avb_util.h"
+#include "avb_vbmeta_image.h"
+
+typedef struct Key {
+  unsigned int len; /* Length of n[] in number of uint32_t */
+  uint32_t n0inv;   /* -1 / n[0] mod 2^32 */
+  uint32_t* n;      /* modulus as array (host-byte order) */
+  uint32_t* rr;     /* R^2 as array (host-byte order) */
+} Key;
+
+Key* parse_key_data(const uint8_t* data, size_t length) {
+  AvbRSAPublicKeyHeader h;
+  Key* key = NULL;
+  size_t expected_length;
+  unsigned int i;
+  const uint8_t* n;
+  const uint8_t* rr;
+
+  if (!avb_rsa_public_key_header_validate_and_byteswap(
+          (const AvbRSAPublicKeyHeader*)data, &h)) {
+    avb_error("Invalid key.\n");
+    goto fail;
+  }
+
+  if (!(h.key_num_bits == 2048 || h.key_num_bits == 4096 ||
+        h.key_num_bits == 8192)) {
+    avb_error("Unexpected key length.\n");
+    goto fail;
+  }
+
+  expected_length = sizeof(AvbRSAPublicKeyHeader) + 2 * h.key_num_bits / 8;
+  if (length != expected_length) {
+    avb_error("Key does not match expected length.\n");
+    goto fail;
+  }
+
+  n = data + sizeof(AvbRSAPublicKeyHeader);
+  rr = data + sizeof(AvbRSAPublicKeyHeader) + h.key_num_bits / 8;
+
+  /* Store n and rr following the key header so we only have to do one
+   * allocation.
+   */
+  key = (Key*)(avb_malloc(sizeof(Key) + 2 * h.key_num_bits / 8));
+  if (key == NULL) goto fail;
+
+  key->len = h.key_num_bits / 32;
+  key->n0inv = h.n0inv;
+  key->n = (uint32_t*)(key + 1); /* Skip ahead sizeof(Key) bytes. */
+  key->rr = key->n + key->len;
+
+  /* Crypto-code below (modpowF4() and friends) expects the key in
+   * little-endian format (rather than the format we're storing the
+   * key in), so convert it.
+   */
+  for (i = 0; i < key->len; i++) {
+    key->n[i] = avb_be32toh(((uint32_t*)n)[key->len - i - 1]);
+    key->rr[i] = avb_be32toh(((uint32_t*)rr)[key->len - i - 1]);
+  }
+  return key;
+
+fail:
+  if (key != NULL) avb_free(key);
+  return NULL;
+}
+
+void free_parsed_key(Key* key) { avb_free(key); }
+
+/* a[] -= mod */
+static void subM(const Key* key, uint32_t* a) {
+  int64_t A = 0;
+  uint32_t i;
+  for (i = 0; i < key->len; ++i) {
+    A += (uint64_t)a[i] - key->n[i];
+    a[i] = (uint32_t)A;
+    A >>= 32;
+  }
+}
+
+/* return a[] >= mod */
+static int geM(const Key* key, uint32_t* a) {
+  uint32_t i;
+  for (i = key->len; i;) {
+    --i;
+    if (a[i] < key->n[i]) return 0;
+    if (a[i] > key->n[i]) return 1;
+  }
+  return 1; /* equal */
+}
+
+/* montgomery c[] += a * b[] / R % mod */
+static void montMulAdd(const Key* key, uint32_t* c, const uint32_t a,
+                       const uint32_t* b) {
+  uint64_t A = (uint64_t)a * b[0] + c[0];
+  uint32_t d0 = (uint32_t)A * key->n0inv;
+  uint64_t B = (uint64_t)d0 * key->n[0] + (uint32_t)A;
+  uint32_t i;
+
+  for (i = 1; i < key->len; ++i) {
+    A = (A >> 32) + (uint64_t)a * b[i] + c[i];
+    B = (B >> 32) + (uint64_t)d0 * key->n[i] + (uint32_t)A;
+    c[i - 1] = (uint32_t)B;
+  }
+
+  A = (A >> 32) + (B >> 32);
+
+  c[i - 1] = (uint32_t)A;
+
+  if (A >> 32) {
+    subM(key, c);
+  }
+}
+
+/* montgomery c[] = a[] * b[] / R % mod */
+static void montMul(const Key* key, uint32_t* c, uint32_t* a, uint32_t* b) {
+  uint32_t i;
+  for (i = 0; i < key->len; ++i) {
+    c[i] = 0;
+  }
+  for (i = 0; i < key->len; ++i) {
+    montMulAdd(key, c, a[i], b);
+  }
+}
+
+/* In-place public exponentiation. (65537}
+ * Input and output big-endian byte array in inout.
+ */
+static void modpowF4(const Key* key, uint8_t* inout) {
+  uint32_t* a = (uint32_t*)avb_malloc(key->len * sizeof(uint32_t));
+  uint32_t* aR = (uint32_t*)avb_malloc(key->len * sizeof(uint32_t));
+  uint32_t* aaR = (uint32_t*)avb_malloc(key->len * sizeof(uint32_t));
+  if (a == NULL || aR == NULL || aaR == NULL) goto out;
+
+  uint32_t* aaa = aaR; /* Re-use location. */
+  int i;
+
+  /* Convert from big endian byte array to little endian word array. */
+  for (i = 0; i < (int)key->len; ++i) {
+    uint32_t tmp = (inout[((key->len - 1 - i) * 4) + 0] << 24) |
+                   (inout[((key->len - 1 - i) * 4) + 1] << 16) |
+                   (inout[((key->len - 1 - i) * 4) + 2] << 8) |
+                   (inout[((key->len - 1 - i) * 4) + 3] << 0);
+    a[i] = tmp;
+  }
+
+  montMul(key, aR, a, key->rr); /* aR = a * RR / R mod M   */
+  for (i = 0; i < 16; i += 2) {
+    montMul(key, aaR, aR, aR);  /* aaR = aR * aR / R mod M */
+    montMul(key, aR, aaR, aaR); /* aR = aaR * aaR / R mod M */
+  }
+  montMul(key, aaa, aR, a); /* aaa = aR * a / R mod M */
+
+  /* Make sure aaa < mod; aaa is at most 1x mod too large. */
+  if (geM(key, aaa)) {
+    subM(key, aaa);
+  }
+
+  /* Convert to bigendian byte array */
+  for (i = (int)key->len - 1; i >= 0; --i) {
+    uint32_t tmp = aaa[i];
+    *inout++ = (uint8_t)(tmp >> 24);
+    *inout++ = (uint8_t)(tmp >> 16);
+    *inout++ = (uint8_t)(tmp >> 8);
+    *inout++ = (uint8_t)(tmp >> 0);
+  }
+
+out:
+  if (a != NULL) avb_free(a);
+  if (aR != NULL) avb_free(aR);
+  if (aaR != NULL) avb_free(aaR);
+}
+
+/* Verify a RSA PKCS1.5 signature against an expected hash.
+ * Returns false on failure, true on success.
+ */
+bool avb_rsa_verify(const uint8_t* key, size_t key_num_bytes,
+                    const uint8_t* sig, size_t sig_num_bytes,
+                    const uint8_t* hash, size_t hash_num_bytes,
+                    const uint8_t* padding, size_t padding_num_bytes) {
+  uint8_t* buf = NULL;
+  Key* parsed_key = NULL;
+  bool success = false;
+
+  if (key == NULL || sig == NULL || hash == NULL || padding == NULL) {
+    avb_error("Invalid input.\n");
+    goto out;
+  }
+
+  parsed_key = parse_key_data(key, key_num_bytes);
+  if (parsed_key == NULL) {
+    avb_error("Error parsing key.\n");
+    goto out;
+  }
+
+  if (sig_num_bytes != (parsed_key->len * sizeof(uint32_t))) {
+    avb_error("Signature length does not match key length.\n");
+    goto out;
+  }
+
+  if (padding_num_bytes != sig_num_bytes - hash_num_bytes) {
+    avb_error("Padding length does not match hash and signature lengths.\n");
+    goto out;
+  }
+
+  buf = (uint8_t*)avb_malloc(sig_num_bytes);
+  if (buf == NULL) {
+    avb_error("Error allocating memory.\n");
+    goto out;
+  }
+  avb_memcpy(buf, sig, sig_num_bytes);
+
+  modpowF4(parsed_key, buf);
+
+  /* Check padding bytes.
+   *
+   * Even though there are probably no timing issues here, we use
+   * avb_safe_memcmp() just to be on the safe side.
+   */
+  if (avb_safe_memcmp(buf, padding, padding_num_bytes)) {
+    avb_error("Padding check failed.\n");
+    goto out;
+  }
+
+  /* Check hash. */
+  if (avb_safe_memcmp(buf + padding_num_bytes, hash, hash_num_bytes)) {
+    avb_error("Hash check failed.\n");
+    goto out;
+  }
+
+  success = true;
+
+out:
+  if (parsed_key != NULL) free_parsed_key(parsed_key);
+  if (buf != NULL) avb_free(buf);
+  return success;
+}
diff --git a/avb/libavb/avb_rsa.h b/avb/libavb/avb_rsa.h
new file mode 100644
index 0000000..70917a0
--- /dev/null
+++ b/avb/libavb/avb_rsa.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifdef AVB_INSIDE_LIBAVB_H
+#error "You can't include avb_rsa.h in the public header libavb.h."
+#endif
+
+#ifndef AVB_COMPILATION
+#error "Never include this file, it may only be used from internal avb code."
+#endif
+
+#ifndef AVB_RSA_H_
+#define AVB_RSA_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "avb_sysdeps.h"
+
+/* Size of a RSA-2048 signature. */
+#define AVB_RSA2048_NUM_BYTES 256
+
+/* Size of a RSA-4096 signature. */
+#define AVB_RSA4096_NUM_BYTES 512
+
+/* Size of a RSA-8192 signature. */
+#define AVB_RSA8192_NUM_BYTES 1024
+
+/* Using the key given by |key|, verify a RSA signature |sig| of
+ * length |sig_num_bytes| against an expected |hash| of length
+ * |hash_num_bytes|. The padding to expect must be passed in using
+ * |padding| of length |padding_num_bytes|.
+ *
+ * The data in |key| must match the format defined in
+ * |AvbRSAPublicKeyHeader|, including the two large numbers
+ * following. The |key_num_bytes| must be the size of the entire
+ * serialized key.
+ *
+ * Returns false if verification fails, true otherwise.
+ */
+bool avb_rsa_verify(const uint8_t* key, size_t key_num_bytes,
+                    const uint8_t* sig, size_t sig_num_bytes,
+                    const uint8_t* hash, size_t hash_num_bytes,
+                    const uint8_t* padding,
+                    size_t padding_num_bytes) AVB_ATTR_WARN_UNUSED_RESULT;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* AVB_RSA_H_ */
diff --git a/avb/libavb/avb_sha.h b/avb/libavb/avb_sha.h
new file mode 100644
index 0000000..33e421b
--- /dev/null
+++ b/avb/libavb/avb_sha.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifdef AVB_INSIDE_LIBAVB_H
+#error "You can't include avb_sha.h in the public header libavb.h."
+#endif
+
+#ifndef AVB_COMPILATION
+#error "Never include this file, it may only be used from internal avb code."
+#endif
+
+#ifndef AVB_SHA_H_
+#define AVB_SHA_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "avb_sysdeps.h"
+
+/* Size in bytes of a SHA-256 digest. */
+#define AVB_SHA256_DIGEST_SIZE 32
+
+/* Block size in bytes of a SHA-256 digest. */
+#define AVB_SHA256_BLOCK_SIZE 64
+
+/* Size in bytes of a SHA-512 digest. */
+#define AVB_SHA512_DIGEST_SIZE 64
+
+/* Block size in bytes of a SHA-512 digest. */
+#define AVB_SHA512_BLOCK_SIZE 128
+
+/* Data structure used for SHA-256. */
+typedef struct {
+  uint32_t h[8];
+  uint32_t tot_len;
+  uint32_t len;
+  uint8_t block[2 * AVB_SHA256_BLOCK_SIZE];
+  uint8_t buf[AVB_SHA256_DIGEST_SIZE]; /* Used for storing the final digest. */
+} AvbSHA256Ctx;
+
+/* Data structure used for SHA-512. */
+typedef struct {
+  uint64_t h[8];
+  uint32_t tot_len;
+  uint32_t len;
+  uint8_t block[2 * AVB_SHA512_BLOCK_SIZE];
+  uint8_t buf[AVB_SHA512_DIGEST_SIZE]; /* Used for storing the final digest. */
+} AvbSHA512Ctx;
+
+/* Initializes the SHA-256 context. */
+void avb_sha256_init(AvbSHA256Ctx* ctx);
+
+/* Updates the SHA-256 context with |len| bytes from |data|. */
+void avb_sha256_update(AvbSHA256Ctx* ctx, const uint8_t* data, uint32_t len);
+
+/* Returns the SHA-256 digest. */
+uint8_t* avb_sha256_final(AvbSHA256Ctx* ctx) AVB_ATTR_WARN_UNUSED_RESULT;
+
+/* Initializes the SHA-512 context. */
+void avb_sha512_init(AvbSHA512Ctx* ctx);
+
+/* Updates the SHA-512 context with |len| bytes from |data|. */
+void avb_sha512_update(AvbSHA512Ctx* ctx, const uint8_t* data, uint32_t len);
+
+/* Returns the SHA-512 digest. */
+uint8_t* avb_sha512_final(AvbSHA512Ctx* ctx) AVB_ATTR_WARN_UNUSED_RESULT;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* AVB_SHA_H_ */
diff --git a/avb/libavb/avb_sha256.c b/avb/libavb/avb_sha256.c
new file mode 100644
index 0000000..b1748eb
--- /dev/null
+++ b/avb/libavb/avb_sha256.c
@@ -0,0 +1,384 @@
+/* SHA-256 and SHA-512 implementation based on code by Oliver Gay
+ * <olivier.gay@a3.epfl.ch> under a BSD-style license. See below.
+ */
+
+/*
+ * FIPS 180-2 SHA-224/256/384/512 implementation
+ * Last update: 02/02/2007
+ * Issue date:  04/30/2005
+ *
+ * Copyright (C) 2005, 2007 Olivier Gay <olivier.gay@a3.epfl.ch>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the project nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "avb_sha.h"
+
+#define SHFR(x, n) (x >> n)
+#define ROTR(x, n) ((x >> n) | (x << ((sizeof(x) << 3) - n)))
+#define ROTL(x, n) ((x << n) | (x >> ((sizeof(x) << 3) - n)))
+#define CH(x, y, z) ((x & y) ^ (~x & z))
+#define MAJ(x, y, z) ((x & y) ^ (x & z) ^ (y & z))
+
+#define SHA256_F1(x) (ROTR(x, 2) ^ ROTR(x, 13) ^ ROTR(x, 22))
+#define SHA256_F2(x) (ROTR(x, 6) ^ ROTR(x, 11) ^ ROTR(x, 25))
+#define SHA256_F3(x) (ROTR(x, 7) ^ ROTR(x, 18) ^ SHFR(x, 3))
+#define SHA256_F4(x) (ROTR(x, 17) ^ ROTR(x, 19) ^ SHFR(x, 10))
+
+#define UNPACK32(x, str)                 \
+  {                                      \
+    *((str) + 3) = (uint8_t)((x));       \
+    *((str) + 2) = (uint8_t)((x) >> 8);  \
+    *((str) + 1) = (uint8_t)((x) >> 16); \
+    *((str) + 0) = (uint8_t)((x) >> 24); \
+  }
+
+#define PACK32(str, x)                                                    \
+  {                                                                       \
+    *(x) = ((uint32_t) * ((str) + 3)) | ((uint32_t) * ((str) + 2) << 8) | \
+           ((uint32_t) * ((str) + 1) << 16) |                             \
+           ((uint32_t) * ((str) + 0) << 24);                              \
+  }
+
+/* Macros used for loops unrolling */
+
+#define SHA256_SCR(i) \
+  { w[i] = SHA256_F4(w[i - 2]) + w[i - 7] + SHA256_F3(w[i - 15]) + w[i - 16]; }
+
+#define SHA256_EXP(a, b, c, d, e, f, g, h, j)                               \
+  {                                                                         \
+    t1 = wv[h] + SHA256_F2(wv[e]) + CH(wv[e], wv[f], wv[g]) + sha256_k[j] + \
+         w[j];                                                              \
+    t2 = SHA256_F1(wv[a]) + MAJ(wv[a], wv[b], wv[c]);                       \
+    wv[d] += t1;                                                            \
+    wv[h] = t1 + t2;                                                        \
+  }
+
+static const uint32_t sha256_h0[8] = {0x6a09e667, 0xbb67ae85, 0x3c6ef372,
+                                      0xa54ff53a, 0x510e527f, 0x9b05688c,
+                                      0x1f83d9ab, 0x5be0cd19};
+
+static const uint32_t sha256_k[64] = {
+    0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1,
+    0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
+    0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
+    0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
+    0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,
+    0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
+    0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
+    0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
+    0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,
+    0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
+    0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2};
+
+/* SHA-256 implementation */
+void avb_sha256_init(AvbSHA256Ctx* ctx) {
+#ifndef UNROLL_LOOPS
+  int i;
+  for (i = 0; i < 8; i++) {
+    ctx->h[i] = sha256_h0[i];
+  }
+#else
+  ctx->h[0] = sha256_h0[0];
+  ctx->h[1] = sha256_h0[1];
+  ctx->h[2] = sha256_h0[2];
+  ctx->h[3] = sha256_h0[3];
+  ctx->h[4] = sha256_h0[4];
+  ctx->h[5] = sha256_h0[5];
+  ctx->h[6] = sha256_h0[6];
+  ctx->h[7] = sha256_h0[7];
+#endif /* !UNROLL_LOOPS */
+
+  ctx->len = 0;
+  ctx->tot_len = 0;
+}
+
+static void SHA256_transform(AvbSHA256Ctx* ctx, const uint8_t* message,
+                             unsigned int block_nb) {
+  uint32_t w[64];
+  uint32_t wv[8];
+  uint32_t t1, t2;
+  const unsigned char* sub_block;
+  int i;
+
+#ifndef UNROLL_LOOPS
+  int j;
+#endif
+
+  for (i = 0; i < (int)block_nb; i++) {
+    sub_block = message + (i << 6);
+
+#ifndef UNROLL_LOOPS
+    for (j = 0; j < 16; j++) {
+      PACK32(&sub_block[j << 2], &w[j]);
+    }
+
+    for (j = 16; j < 64; j++) {
+      SHA256_SCR(j);
+    }
+
+    for (j = 0; j < 8; j++) {
+      wv[j] = ctx->h[j];
+    }
+
+    for (j = 0; j < 64; j++) {
+      t1 = wv[7] + SHA256_F2(wv[4]) + CH(wv[4], wv[5], wv[6]) + sha256_k[j] +
+           w[j];
+      t2 = SHA256_F1(wv[0]) + MAJ(wv[0], wv[1], wv[2]);
+      wv[7] = wv[6];
+      wv[6] = wv[5];
+      wv[5] = wv[4];
+      wv[4] = wv[3] + t1;
+      wv[3] = wv[2];
+      wv[2] = wv[1];
+      wv[1] = wv[0];
+      wv[0] = t1 + t2;
+    }
+
+    for (j = 0; j < 8; j++) {
+      ctx->h[j] += wv[j];
+    }
+#else
+    PACK32(&sub_block[0], &w[0]);
+    PACK32(&sub_block[4], &w[1]);
+    PACK32(&sub_block[8], &w[2]);
+    PACK32(&sub_block[12], &w[3]);
+    PACK32(&sub_block[16], &w[4]);
+    PACK32(&sub_block[20], &w[5]);
+    PACK32(&sub_block[24], &w[6]);
+    PACK32(&sub_block[28], &w[7]);
+    PACK32(&sub_block[32], &w[8]);
+    PACK32(&sub_block[36], &w[9]);
+    PACK32(&sub_block[40], &w[10]);
+    PACK32(&sub_block[44], &w[11]);
+    PACK32(&sub_block[48], &w[12]);
+    PACK32(&sub_block[52], &w[13]);
+    PACK32(&sub_block[56], &w[14]);
+    PACK32(&sub_block[60], &w[15]);
+
+    SHA256_SCR(16);
+    SHA256_SCR(17);
+    SHA256_SCR(18);
+    SHA256_SCR(19);
+    SHA256_SCR(20);
+    SHA256_SCR(21);
+    SHA256_SCR(22);
+    SHA256_SCR(23);
+    SHA256_SCR(24);
+    SHA256_SCR(25);
+    SHA256_SCR(26);
+    SHA256_SCR(27);
+    SHA256_SCR(28);
+    SHA256_SCR(29);
+    SHA256_SCR(30);
+    SHA256_SCR(31);
+    SHA256_SCR(32);
+    SHA256_SCR(33);
+    SHA256_SCR(34);
+    SHA256_SCR(35);
+    SHA256_SCR(36);
+    SHA256_SCR(37);
+    SHA256_SCR(38);
+    SHA256_SCR(39);
+    SHA256_SCR(40);
+    SHA256_SCR(41);
+    SHA256_SCR(42);
+    SHA256_SCR(43);
+    SHA256_SCR(44);
+    SHA256_SCR(45);
+    SHA256_SCR(46);
+    SHA256_SCR(47);
+    SHA256_SCR(48);
+    SHA256_SCR(49);
+    SHA256_SCR(50);
+    SHA256_SCR(51);
+    SHA256_SCR(52);
+    SHA256_SCR(53);
+    SHA256_SCR(54);
+    SHA256_SCR(55);
+    SHA256_SCR(56);
+    SHA256_SCR(57);
+    SHA256_SCR(58);
+    SHA256_SCR(59);
+    SHA256_SCR(60);
+    SHA256_SCR(61);
+    SHA256_SCR(62);
+    SHA256_SCR(63);
+
+    wv[0] = ctx->h[0];
+    wv[1] = ctx->h[1];
+    wv[2] = ctx->h[2];
+    wv[3] = ctx->h[3];
+    wv[4] = ctx->h[4];
+    wv[5] = ctx->h[5];
+    wv[6] = ctx->h[6];
+    wv[7] = ctx->h[7];
+
+    SHA256_EXP(0, 1, 2, 3, 4, 5, 6, 7, 0);
+    SHA256_EXP(7, 0, 1, 2, 3, 4, 5, 6, 1);
+    SHA256_EXP(6, 7, 0, 1, 2, 3, 4, 5, 2);
+    SHA256_EXP(5, 6, 7, 0, 1, 2, 3, 4, 3);
+    SHA256_EXP(4, 5, 6, 7, 0, 1, 2, 3, 4);
+    SHA256_EXP(3, 4, 5, 6, 7, 0, 1, 2, 5);
+    SHA256_EXP(2, 3, 4, 5, 6, 7, 0, 1, 6);
+    SHA256_EXP(1, 2, 3, 4, 5, 6, 7, 0, 7);
+    SHA256_EXP(0, 1, 2, 3, 4, 5, 6, 7, 8);
+    SHA256_EXP(7, 0, 1, 2, 3, 4, 5, 6, 9);
+    SHA256_EXP(6, 7, 0, 1, 2, 3, 4, 5, 10);
+    SHA256_EXP(5, 6, 7, 0, 1, 2, 3, 4, 11);
+    SHA256_EXP(4, 5, 6, 7, 0, 1, 2, 3, 12);
+    SHA256_EXP(3, 4, 5, 6, 7, 0, 1, 2, 13);
+    SHA256_EXP(2, 3, 4, 5, 6, 7, 0, 1, 14);
+    SHA256_EXP(1, 2, 3, 4, 5, 6, 7, 0, 15);
+    SHA256_EXP(0, 1, 2, 3, 4, 5, 6, 7, 16);
+    SHA256_EXP(7, 0, 1, 2, 3, 4, 5, 6, 17);
+    SHA256_EXP(6, 7, 0, 1, 2, 3, 4, 5, 18);
+    SHA256_EXP(5, 6, 7, 0, 1, 2, 3, 4, 19);
+    SHA256_EXP(4, 5, 6, 7, 0, 1, 2, 3, 20);
+    SHA256_EXP(3, 4, 5, 6, 7, 0, 1, 2, 21);
+    SHA256_EXP(2, 3, 4, 5, 6, 7, 0, 1, 22);
+    SHA256_EXP(1, 2, 3, 4, 5, 6, 7, 0, 23);
+    SHA256_EXP(0, 1, 2, 3, 4, 5, 6, 7, 24);
+    SHA256_EXP(7, 0, 1, 2, 3, 4, 5, 6, 25);
+    SHA256_EXP(6, 7, 0, 1, 2, 3, 4, 5, 26);
+    SHA256_EXP(5, 6, 7, 0, 1, 2, 3, 4, 27);
+    SHA256_EXP(4, 5, 6, 7, 0, 1, 2, 3, 28);
+    SHA256_EXP(3, 4, 5, 6, 7, 0, 1, 2, 29);
+    SHA256_EXP(2, 3, 4, 5, 6, 7, 0, 1, 30);
+    SHA256_EXP(1, 2, 3, 4, 5, 6, 7, 0, 31);
+    SHA256_EXP(0, 1, 2, 3, 4, 5, 6, 7, 32);
+    SHA256_EXP(7, 0, 1, 2, 3, 4, 5, 6, 33);
+    SHA256_EXP(6, 7, 0, 1, 2, 3, 4, 5, 34);
+    SHA256_EXP(5, 6, 7, 0, 1, 2, 3, 4, 35);
+    SHA256_EXP(4, 5, 6, 7, 0, 1, 2, 3, 36);
+    SHA256_EXP(3, 4, 5, 6, 7, 0, 1, 2, 37);
+    SHA256_EXP(2, 3, 4, 5, 6, 7, 0, 1, 38);
+    SHA256_EXP(1, 2, 3, 4, 5, 6, 7, 0, 39);
+    SHA256_EXP(0, 1, 2, 3, 4, 5, 6, 7, 40);
+    SHA256_EXP(7, 0, 1, 2, 3, 4, 5, 6, 41);
+    SHA256_EXP(6, 7, 0, 1, 2, 3, 4, 5, 42);
+    SHA256_EXP(5, 6, 7, 0, 1, 2, 3, 4, 43);
+    SHA256_EXP(4, 5, 6, 7, 0, 1, 2, 3, 44);
+    SHA256_EXP(3, 4, 5, 6, 7, 0, 1, 2, 45);
+    SHA256_EXP(2, 3, 4, 5, 6, 7, 0, 1, 46);
+    SHA256_EXP(1, 2, 3, 4, 5, 6, 7, 0, 47);
+    SHA256_EXP(0, 1, 2, 3, 4, 5, 6, 7, 48);
+    SHA256_EXP(7, 0, 1, 2, 3, 4, 5, 6, 49);
+    SHA256_EXP(6, 7, 0, 1, 2, 3, 4, 5, 50);
+    SHA256_EXP(5, 6, 7, 0, 1, 2, 3, 4, 51);
+    SHA256_EXP(4, 5, 6, 7, 0, 1, 2, 3, 52);
+    SHA256_EXP(3, 4, 5, 6, 7, 0, 1, 2, 53);
+    SHA256_EXP(2, 3, 4, 5, 6, 7, 0, 1, 54);
+    SHA256_EXP(1, 2, 3, 4, 5, 6, 7, 0, 55);
+    SHA256_EXP(0, 1, 2, 3, 4, 5, 6, 7, 56);
+    SHA256_EXP(7, 0, 1, 2, 3, 4, 5, 6, 57);
+    SHA256_EXP(6, 7, 0, 1, 2, 3, 4, 5, 58);
+    SHA256_EXP(5, 6, 7, 0, 1, 2, 3, 4, 59);
+    SHA256_EXP(4, 5, 6, 7, 0, 1, 2, 3, 60);
+    SHA256_EXP(3, 4, 5, 6, 7, 0, 1, 2, 61);
+    SHA256_EXP(2, 3, 4, 5, 6, 7, 0, 1, 62);
+    SHA256_EXP(1, 2, 3, 4, 5, 6, 7, 0, 63);
+
+    ctx->h[0] += wv[0];
+    ctx->h[1] += wv[1];
+    ctx->h[2] += wv[2];
+    ctx->h[3] += wv[3];
+    ctx->h[4] += wv[4];
+    ctx->h[5] += wv[5];
+    ctx->h[6] += wv[6];
+    ctx->h[7] += wv[7];
+#endif /* !UNROLL_LOOPS */
+  }
+}
+
+void avb_sha256_update(AvbSHA256Ctx* ctx, const uint8_t* data, uint32_t len) {
+  unsigned int block_nb;
+  unsigned int new_len, rem_len, tmp_len;
+  const uint8_t* shifted_data;
+
+  tmp_len = AVB_SHA256_BLOCK_SIZE - ctx->len;
+  rem_len = len < tmp_len ? len : tmp_len;
+
+  avb_memcpy(&ctx->block[ctx->len], data, rem_len);
+
+  if (ctx->len + len < AVB_SHA256_BLOCK_SIZE) {
+    ctx->len += len;
+    return;
+  }
+
+  new_len = len - rem_len;
+  block_nb = new_len / AVB_SHA256_BLOCK_SIZE;
+
+  shifted_data = data + rem_len;
+
+  SHA256_transform(ctx, ctx->block, 1);
+  SHA256_transform(ctx, shifted_data, block_nb);
+
+  rem_len = new_len % AVB_SHA256_BLOCK_SIZE;
+
+  avb_memcpy(ctx->block, &shifted_data[block_nb << 6], rem_len);
+
+  ctx->len = rem_len;
+  ctx->tot_len += (block_nb + 1) << 6;
+}
+
+uint8_t* avb_sha256_final(AvbSHA256Ctx* ctx) {
+  unsigned int block_nb;
+  unsigned int pm_len;
+  unsigned int len_b;
+#ifndef UNROLL_LOOPS
+  int i;
+#endif
+
+  block_nb =
+      (1 + ((AVB_SHA256_BLOCK_SIZE - 9) < (ctx->len % AVB_SHA256_BLOCK_SIZE)));
+
+  len_b = (ctx->tot_len + ctx->len) << 3;
+  pm_len = block_nb << 6;
+
+  avb_memset(ctx->block + ctx->len, 0, pm_len - ctx->len);
+  ctx->block[ctx->len] = 0x80;
+  UNPACK32(len_b, ctx->block + pm_len - 4);
+
+  SHA256_transform(ctx, ctx->block, block_nb);
+
+#ifndef UNROLL_LOOPS
+  for (i = 0; i < 8; i++) {
+    UNPACK32(ctx->h[i], &ctx->buf[i << 2]);
+  }
+#else
+  UNPACK32(ctx->h[0], &ctx->buf[0]);
+  UNPACK32(ctx->h[1], &ctx->buf[4]);
+  UNPACK32(ctx->h[2], &ctx->buf[8]);
+  UNPACK32(ctx->h[3], &ctx->buf[12]);
+  UNPACK32(ctx->h[4], &ctx->buf[16]);
+  UNPACK32(ctx->h[5], &ctx->buf[20]);
+  UNPACK32(ctx->h[6], &ctx->buf[24]);
+  UNPACK32(ctx->h[7], &ctx->buf[28]);
+#endif /* !UNROLL_LOOPS */
+
+  return ctx->buf;
+}
diff --git a/avb/libavb/avb_sha512.c b/avb/libavb/avb_sha512.c
new file mode 100644
index 0000000..f2f0ec4
--- /dev/null
+++ b/avb/libavb/avb_sha512.c
@@ -0,0 +1,380 @@
+/* SHA-256 and SHA-512 implementation based on code by Oliver Gay
+ * <olivier.gay@a3.epfl.ch> under a BSD-style license. See below.
+ */
+
+/*
+ * FIPS 180-2 SHA-224/256/384/512 implementation
+ * Last update: 02/02/2007
+ * Issue date:  04/30/2005
+ *
+ * Copyright (C) 2005, 2007 Olivier Gay <olivier.gay@a3.epfl.ch>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the project nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "avb_sha.h"
+
+#define SHFR(x, n) (x >> n)
+#define ROTR(x, n) ((x >> n) | (x << ((sizeof(x) << 3) - n)))
+#define ROTL(x, n) ((x << n) | (x >> ((sizeof(x) << 3) - n)))
+#define CH(x, y, z) ((x & y) ^ (~x & z))
+#define MAJ(x, y, z) ((x & y) ^ (x & z) ^ (y & z))
+
+#define SHA512_F1(x) (ROTR(x, 28) ^ ROTR(x, 34) ^ ROTR(x, 39))
+#define SHA512_F2(x) (ROTR(x, 14) ^ ROTR(x, 18) ^ ROTR(x, 41))
+#define SHA512_F3(x) (ROTR(x, 1) ^ ROTR(x, 8) ^ SHFR(x, 7))
+#define SHA512_F4(x) (ROTR(x, 19) ^ ROTR(x, 61) ^ SHFR(x, 6))
+
+#define UNPACK32(x, str)                 \
+  {                                      \
+    *((str) + 3) = (uint8_t)((x));       \
+    *((str) + 2) = (uint8_t)((x) >> 8);  \
+    *((str) + 1) = (uint8_t)((x) >> 16); \
+    *((str) + 0) = (uint8_t)((x) >> 24); \
+  }
+
+#define UNPACK64(x, str)                         \
+  {                                              \
+    *((str) + 7) = (uint8_t)x;                   \
+    *((str) + 6) = (uint8_t)((uint64_t)x >> 8);  \
+    *((str) + 5) = (uint8_t)((uint64_t)x >> 16); \
+    *((str) + 4) = (uint8_t)((uint64_t)x >> 24); \
+    *((str) + 3) = (uint8_t)((uint64_t)x >> 32); \
+    *((str) + 2) = (uint8_t)((uint64_t)x >> 40); \
+    *((str) + 1) = (uint8_t)((uint64_t)x >> 48); \
+    *((str) + 0) = (uint8_t)((uint64_t)x >> 56); \
+  }
+
+#define PACK64(str, x)                                                        \
+  {                                                                           \
+    *(x) =                                                                    \
+        ((uint64_t) * ((str) + 7)) | ((uint64_t) * ((str) + 6) << 8) |        \
+        ((uint64_t) * ((str) + 5) << 16) | ((uint64_t) * ((str) + 4) << 24) | \
+        ((uint64_t) * ((str) + 3) << 32) | ((uint64_t) * ((str) + 2) << 40) | \
+        ((uint64_t) * ((str) + 1) << 48) | ((uint64_t) * ((str) + 0) << 56);  \
+  }
+
+/* Macros used for loops unrolling */
+
+#define SHA512_SCR(i) \
+  { w[i] = SHA512_F4(w[i - 2]) + w[i - 7] + SHA512_F3(w[i - 15]) + w[i - 16]; }
+
+#define SHA512_EXP(a, b, c, d, e, f, g, h, j)                               \
+  {                                                                         \
+    t1 = wv[h] + SHA512_F2(wv[e]) + CH(wv[e], wv[f], wv[g]) + sha512_k[j] + \
+         w[j];                                                              \
+    t2 = SHA512_F1(wv[a]) + MAJ(wv[a], wv[b], wv[c]);                       \
+    wv[d] += t1;                                                            \
+    wv[h] = t1 + t2;                                                        \
+  }
+
+static const uint64_t sha512_h0[8] = {
+    0x6a09e667f3bcc908ULL, 0xbb67ae8584caa73bULL, 0x3c6ef372fe94f82bULL,
+    0xa54ff53a5f1d36f1ULL, 0x510e527fade682d1ULL, 0x9b05688c2b3e6c1fULL,
+    0x1f83d9abfb41bd6bULL, 0x5be0cd19137e2179ULL};
+
+static const uint64_t sha512_k[80] = {
+    0x428a2f98d728ae22ULL, 0x7137449123ef65cdULL, 0xb5c0fbcfec4d3b2fULL,
+    0xe9b5dba58189dbbcULL, 0x3956c25bf348b538ULL, 0x59f111f1b605d019ULL,
+    0x923f82a4af194f9bULL, 0xab1c5ed5da6d8118ULL, 0xd807aa98a3030242ULL,
+    0x12835b0145706fbeULL, 0x243185be4ee4b28cULL, 0x550c7dc3d5ffb4e2ULL,
+    0x72be5d74f27b896fULL, 0x80deb1fe3b1696b1ULL, 0x9bdc06a725c71235ULL,
+    0xc19bf174cf692694ULL, 0xe49b69c19ef14ad2ULL, 0xefbe4786384f25e3ULL,
+    0x0fc19dc68b8cd5b5ULL, 0x240ca1cc77ac9c65ULL, 0x2de92c6f592b0275ULL,
+    0x4a7484aa6ea6e483ULL, 0x5cb0a9dcbd41fbd4ULL, 0x76f988da831153b5ULL,
+    0x983e5152ee66dfabULL, 0xa831c66d2db43210ULL, 0xb00327c898fb213fULL,
+    0xbf597fc7beef0ee4ULL, 0xc6e00bf33da88fc2ULL, 0xd5a79147930aa725ULL,
+    0x06ca6351e003826fULL, 0x142929670a0e6e70ULL, 0x27b70a8546d22ffcULL,
+    0x2e1b21385c26c926ULL, 0x4d2c6dfc5ac42aedULL, 0x53380d139d95b3dfULL,
+    0x650a73548baf63deULL, 0x766a0abb3c77b2a8ULL, 0x81c2c92e47edaee6ULL,
+    0x92722c851482353bULL, 0xa2bfe8a14cf10364ULL, 0xa81a664bbc423001ULL,
+    0xc24b8b70d0f89791ULL, 0xc76c51a30654be30ULL, 0xd192e819d6ef5218ULL,
+    0xd69906245565a910ULL, 0xf40e35855771202aULL, 0x106aa07032bbd1b8ULL,
+    0x19a4c116b8d2d0c8ULL, 0x1e376c085141ab53ULL, 0x2748774cdf8eeb99ULL,
+    0x34b0bcb5e19b48a8ULL, 0x391c0cb3c5c95a63ULL, 0x4ed8aa4ae3418acbULL,
+    0x5b9cca4f7763e373ULL, 0x682e6ff3d6b2b8a3ULL, 0x748f82ee5defb2fcULL,
+    0x78a5636f43172f60ULL, 0x84c87814a1f0ab72ULL, 0x8cc702081a6439ecULL,
+    0x90befffa23631e28ULL, 0xa4506cebde82bde9ULL, 0xbef9a3f7b2c67915ULL,
+    0xc67178f2e372532bULL, 0xca273eceea26619cULL, 0xd186b8c721c0c207ULL,
+    0xeada7dd6cde0eb1eULL, 0xf57d4f7fee6ed178ULL, 0x06f067aa72176fbaULL,
+    0x0a637dc5a2c898a6ULL, 0x113f9804bef90daeULL, 0x1b710b35131c471bULL,
+    0x28db77f523047d84ULL, 0x32caab7b40c72493ULL, 0x3c9ebe0a15c9bebcULL,
+    0x431d67c49c100d4cULL, 0x4cc5d4becb3e42b6ULL, 0x597f299cfc657e2aULL,
+    0x5fcb6fab3ad6faecULL, 0x6c44198c4a475817ULL};
+
+/* SHA-512 implementation */
+
+void avb_sha512_init(AvbSHA512Ctx* ctx) {
+#ifdef UNROLL_LOOPS_SHA512
+  ctx->h[0] = sha512_h0[0];
+  ctx->h[1] = sha512_h0[1];
+  ctx->h[2] = sha512_h0[2];
+  ctx->h[3] = sha512_h0[3];
+  ctx->h[4] = sha512_h0[4];
+  ctx->h[5] = sha512_h0[5];
+  ctx->h[6] = sha512_h0[6];
+  ctx->h[7] = sha512_h0[7];
+#else
+  int i;
+
+  for (i = 0; i < 8; i++) ctx->h[i] = sha512_h0[i];
+#endif /* UNROLL_LOOPS_SHA512 */
+
+  ctx->len = 0;
+  ctx->tot_len = 0;
+}
+
+static void SHA512_transform(AvbSHA512Ctx* ctx, const uint8_t* message,
+                             unsigned int block_nb) {
+  uint64_t w[80];
+  uint64_t wv[8];
+  uint64_t t1, t2;
+  const uint8_t* sub_block;
+  int i, j;
+
+  for (i = 0; i < (int)block_nb; i++) {
+    sub_block = message + (i << 7);
+
+#ifdef UNROLL_LOOPS_SHA512
+    PACK64(&sub_block[0], &w[0]);
+    PACK64(&sub_block[8], &w[1]);
+    PACK64(&sub_block[16], &w[2]);
+    PACK64(&sub_block[24], &w[3]);
+    PACK64(&sub_block[32], &w[4]);
+    PACK64(&sub_block[40], &w[5]);
+    PACK64(&sub_block[48], &w[6]);
+    PACK64(&sub_block[56], &w[7]);
+    PACK64(&sub_block[64], &w[8]);
+    PACK64(&sub_block[72], &w[9]);
+    PACK64(&sub_block[80], &w[10]);
+    PACK64(&sub_block[88], &w[11]);
+    PACK64(&sub_block[96], &w[12]);
+    PACK64(&sub_block[104], &w[13]);
+    PACK64(&sub_block[112], &w[14]);
+    PACK64(&sub_block[120], &w[15]);
+
+    SHA512_SCR(16);
+    SHA512_SCR(17);
+    SHA512_SCR(18);
+    SHA512_SCR(19);
+    SHA512_SCR(20);
+    SHA512_SCR(21);
+    SHA512_SCR(22);
+    SHA512_SCR(23);
+    SHA512_SCR(24);
+    SHA512_SCR(25);
+    SHA512_SCR(26);
+    SHA512_SCR(27);
+    SHA512_SCR(28);
+    SHA512_SCR(29);
+    SHA512_SCR(30);
+    SHA512_SCR(31);
+    SHA512_SCR(32);
+    SHA512_SCR(33);
+    SHA512_SCR(34);
+    SHA512_SCR(35);
+    SHA512_SCR(36);
+    SHA512_SCR(37);
+    SHA512_SCR(38);
+    SHA512_SCR(39);
+    SHA512_SCR(40);
+    SHA512_SCR(41);
+    SHA512_SCR(42);
+    SHA512_SCR(43);
+    SHA512_SCR(44);
+    SHA512_SCR(45);
+    SHA512_SCR(46);
+    SHA512_SCR(47);
+    SHA512_SCR(48);
+    SHA512_SCR(49);
+    SHA512_SCR(50);
+    SHA512_SCR(51);
+    SHA512_SCR(52);
+    SHA512_SCR(53);
+    SHA512_SCR(54);
+    SHA512_SCR(55);
+    SHA512_SCR(56);
+    SHA512_SCR(57);
+    SHA512_SCR(58);
+    SHA512_SCR(59);
+    SHA512_SCR(60);
+    SHA512_SCR(61);
+    SHA512_SCR(62);
+    SHA512_SCR(63);
+    SHA512_SCR(64);
+    SHA512_SCR(65);
+    SHA512_SCR(66);
+    SHA512_SCR(67);
+    SHA512_SCR(68);
+    SHA512_SCR(69);
+    SHA512_SCR(70);
+    SHA512_SCR(71);
+    SHA512_SCR(72);
+    SHA512_SCR(73);
+    SHA512_SCR(74);
+    SHA512_SCR(75);
+    SHA512_SCR(76);
+    SHA512_SCR(77);
+    SHA512_SCR(78);
+    SHA512_SCR(79);
+
+    wv[0] = ctx->h[0];
+    wv[1] = ctx->h[1];
+    wv[2] = ctx->h[2];
+    wv[3] = ctx->h[3];
+    wv[4] = ctx->h[4];
+    wv[5] = ctx->h[5];
+    wv[6] = ctx->h[6];
+    wv[7] = ctx->h[7];
+
+    j = 0;
+
+    do {
+      SHA512_EXP(0, 1, 2, 3, 4, 5, 6, 7, j);
+      j++;
+      SHA512_EXP(7, 0, 1, 2, 3, 4, 5, 6, j);
+      j++;
+      SHA512_EXP(6, 7, 0, 1, 2, 3, 4, 5, j);
+      j++;
+      SHA512_EXP(5, 6, 7, 0, 1, 2, 3, 4, j);
+      j++;
+      SHA512_EXP(4, 5, 6, 7, 0, 1, 2, 3, j);
+      j++;
+      SHA512_EXP(3, 4, 5, 6, 7, 0, 1, 2, j);
+      j++;
+      SHA512_EXP(2, 3, 4, 5, 6, 7, 0, 1, j);
+      j++;
+      SHA512_EXP(1, 2, 3, 4, 5, 6, 7, 0, j);
+      j++;
+    } while (j < 80);
+
+    ctx->h[0] += wv[0];
+    ctx->h[1] += wv[1];
+    ctx->h[2] += wv[2];
+    ctx->h[3] += wv[3];
+    ctx->h[4] += wv[4];
+    ctx->h[5] += wv[5];
+    ctx->h[6] += wv[6];
+    ctx->h[7] += wv[7];
+#else
+    for (j = 0; j < 16; j++) {
+      PACK64(&sub_block[j << 3], &w[j]);
+    }
+
+    for (j = 16; j < 80; j++) {
+      SHA512_SCR(j);
+    }
+
+    for (j = 0; j < 8; j++) {
+      wv[j] = ctx->h[j];
+    }
+
+    for (j = 0; j < 80; j++) {
+      t1 = wv[7] + SHA512_F2(wv[4]) + CH(wv[4], wv[5], wv[6]) + sha512_k[j] +
+           w[j];
+      t2 = SHA512_F1(wv[0]) + MAJ(wv[0], wv[1], wv[2]);
+      wv[7] = wv[6];
+      wv[6] = wv[5];
+      wv[5] = wv[4];
+      wv[4] = wv[3] + t1;
+      wv[3] = wv[2];
+      wv[2] = wv[1];
+      wv[1] = wv[0];
+      wv[0] = t1 + t2;
+    }
+
+    for (j = 0; j < 8; j++) ctx->h[j] += wv[j];
+#endif /* UNROLL_LOOPS_SHA512 */
+  }
+}
+
+void avb_sha512_update(AvbSHA512Ctx* ctx, const uint8_t* data, uint32_t len) {
+  unsigned int block_nb;
+  unsigned int new_len, rem_len, tmp_len;
+  const uint8_t* shifted_data;
+
+  tmp_len = AVB_SHA512_BLOCK_SIZE - ctx->len;
+  rem_len = len < tmp_len ? len : tmp_len;
+
+  avb_memcpy(&ctx->block[ctx->len], data, rem_len);
+
+  if (ctx->len + len < AVB_SHA512_BLOCK_SIZE) {
+    ctx->len += len;
+    return;
+  }
+
+  new_len = len - rem_len;
+  block_nb = new_len / AVB_SHA512_BLOCK_SIZE;
+
+  shifted_data = data + rem_len;
+
+  SHA512_transform(ctx, ctx->block, 1);
+  SHA512_transform(ctx, shifted_data, block_nb);
+
+  rem_len = new_len % AVB_SHA512_BLOCK_SIZE;
+
+  avb_memcpy(ctx->block, &shifted_data[block_nb << 7], rem_len);
+
+  ctx->len = rem_len;
+  ctx->tot_len += (block_nb + 1) << 7;
+}
+
+uint8_t* avb_sha512_final(AvbSHA512Ctx* ctx) {
+  unsigned int block_nb;
+  unsigned int pm_len;
+  unsigned int len_b;
+
+#ifndef UNROLL_LOOPS_SHA512
+  int i;
+#endif
+
+  block_nb =
+      1 + ((AVB_SHA512_BLOCK_SIZE - 17) < (ctx->len % AVB_SHA512_BLOCK_SIZE));
+
+  len_b = (ctx->tot_len + ctx->len) << 3;
+  pm_len = block_nb << 7;
+
+  avb_memset(ctx->block + ctx->len, 0, pm_len - ctx->len);
+  ctx->block[ctx->len] = 0x80;
+  UNPACK32(len_b, ctx->block + pm_len - 4);
+
+  SHA512_transform(ctx, ctx->block, block_nb);
+
+#ifdef UNROLL_LOOPS_SHA512
+  UNPACK64(ctx->h[0], &ctx->buf[0]);
+  UNPACK64(ctx->h[1], &ctx->buf[8]);
+  UNPACK64(ctx->h[2], &ctx->buf[16]);
+  UNPACK64(ctx->h[3], &ctx->buf[24]);
+  UNPACK64(ctx->h[4], &ctx->buf[32]);
+  UNPACK64(ctx->h[5], &ctx->buf[40]);
+  UNPACK64(ctx->h[6], &ctx->buf[48]);
+  UNPACK64(ctx->h[7], &ctx->buf[56]);
+#else
+  for (i = 0; i < 8; i++) UNPACK64(ctx->h[i], &ctx->buf[i << 3]);
+#endif /* UNROLL_LOOPS_SHA512 */
+
+  return ctx->buf;
+}
diff --git a/avb/libavb/avb_slot_verify.c b/avb/libavb/avb_slot_verify.c
new file mode 100644
index 0000000..5c4061d
--- /dev/null
+++ b/avb/libavb/avb_slot_verify.c
@@ -0,0 +1,731 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "avb_slot_verify.h"
+#include "avb_chain_partition_descriptor.h"
+#include "avb_footer.h"
+#include "avb_hash_descriptor.h"
+#include "avb_kernel_cmdline_descriptor.h"
+#include "avb_sha.h"
+#include "avb_util.h"
+#include "avb_vbmeta_image.h"
+
+/* Maximum allow length (in bytes) of a partition name, including
+ * ab_suffix.
+ */
+#define PART_NAME_MAX_SIZE 32
+
+/* Maximum size of a vbmeta image - 64 KiB. */
+#define VBMETA_MAX_SIZE (64 * 1024)
+
+static AvbSlotVerifyResult load_and_verify_hash_partition(
+    AvbOps* ops, const char* ab_suffix, const AvbDescriptor* descriptor,
+    AvbSlotVerifyData* slot_data) {
+  AvbHashDescriptor hash_desc;
+  const uint8_t* desc_partition_name;
+  const uint8_t* desc_salt;
+  const uint8_t* desc_digest;
+  char part_name[PART_NAME_MAX_SIZE];
+  AvbSlotVerifyResult ret;
+  AvbIOResult io_ret;
+  uint8_t* image_buf = NULL;
+  size_t part_num_read;
+  uint8_t* digest;
+  size_t digest_len;
+
+  if (!avb_hash_descriptor_validate_and_byteswap(
+          (const AvbHashDescriptor*)descriptor, &hash_desc)) {
+    ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;
+    goto out;
+  }
+
+  desc_partition_name =
+      ((const uint8_t*)descriptor) + sizeof(AvbHashDescriptor);
+  desc_salt = desc_partition_name + hash_desc.partition_name_len;
+  desc_digest = desc_salt + hash_desc.salt_len;
+
+  if (!avb_validate_utf8(desc_partition_name, hash_desc.partition_name_len)) {
+    avb_error("Partition name is not valid UTF-8.\n");
+    ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;
+    goto out;
+  }
+
+  if (!avb_str_concat(
+          part_name, sizeof part_name, (const char*)desc_partition_name,
+          hash_desc.partition_name_len, ab_suffix, avb_strlen(ab_suffix))) {
+    avb_error("Partition name and suffix does not fit.\n");
+    ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;
+    goto out;
+  }
+
+  image_buf = avb_malloc(hash_desc.image_size);
+  if (image_buf == NULL) {
+    ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
+    goto out;
+  }
+
+  io_ret =
+      ops->read_from_partition(ops, part_name, 0 /* offset */,
+                               hash_desc.image_size, image_buf, &part_num_read);
+  if (io_ret != AVB_IO_RESULT_OK) {
+    avb_errorv(part_name, ": Error loading data from partition.\n", NULL);
+    ret = AVB_SLOT_VERIFY_RESULT_ERROR_IO;
+    goto out;
+  }
+  if (part_num_read != hash_desc.image_size) {
+    avb_errorv(part_name, ": Read fewer than requested bytes.\n", NULL);
+    ret = AVB_SLOT_VERIFY_RESULT_ERROR_IO;
+    goto out;
+  }
+
+  if (avb_strcmp((const char*)hash_desc.hash_algorithm, "sha256") == 0) {
+    AvbSHA256Ctx sha256_ctx;
+    avb_sha256_init(&sha256_ctx);
+    avb_sha256_update(&sha256_ctx, desc_salt, hash_desc.salt_len);
+    avb_sha256_update(&sha256_ctx, image_buf, hash_desc.image_size);
+    digest = avb_sha256_final(&sha256_ctx);
+    digest_len = AVB_SHA256_DIGEST_SIZE;
+  } else if (avb_strcmp((const char*)hash_desc.hash_algorithm, "sha512") == 0) {
+    AvbSHA512Ctx sha512_ctx;
+    avb_sha512_init(&sha512_ctx);
+    avb_sha512_update(&sha512_ctx, desc_salt, hash_desc.salt_len);
+    avb_sha512_update(&sha512_ctx, image_buf, hash_desc.image_size);
+    digest = avb_sha512_final(&sha512_ctx);
+    digest_len = AVB_SHA512_DIGEST_SIZE;
+  } else {
+    avb_errorv(part_name, ": Unsupported hash algorithm.\n", NULL);
+    ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;
+    goto out;
+  }
+
+  if (digest_len != hash_desc.digest_len) {
+    avb_errorv(part_name, ": Digest in descriptor not of expected size.\n",
+               NULL);
+    ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;
+    goto out;
+  }
+
+  if (avb_safe_memcmp(digest, desc_digest, digest_len) != 0) {
+    avb_errorv(part_name,
+               ": Hash of data does not match digest in descriptor.\n", NULL);
+    ret = AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION;
+    goto out;
+  }
+
+  ret = AVB_SLOT_VERIFY_RESULT_OK;
+
+  /* If this is the boot partition, copy to slot_data. */
+  if (hash_desc.partition_name_len == 4 &&
+      avb_memcmp(desc_partition_name, "boot", 4) == 0) {
+    if (slot_data->boot_data != NULL) {
+      avb_free(slot_data->boot_data);
+    }
+    slot_data->boot_size = hash_desc.image_size;
+    slot_data->boot_data = image_buf;
+    image_buf = NULL;
+  }
+
+out:
+  if (image_buf != NULL) {
+    avb_free(image_buf);
+  }
+  return ret;
+}
+
+static AvbSlotVerifyResult load_and_verify_vbmeta(
+    AvbOps* ops, const char* ab_suffix, int rollback_index_slot,
+    const char* partition_name, size_t partition_name_len,
+    const uint8_t* expected_public_key, size_t expected_public_key_length,
+    AvbSlotVerifyData* slot_data, AvbAlgorithmType* out_algorithm_type) {
+  char full_partition_name[PART_NAME_MAX_SIZE];
+  AvbSlotVerifyResult ret;
+  AvbIOResult io_ret;
+  size_t vbmeta_offset;
+  size_t vbmeta_size;
+  uint8_t* vbmeta_buf = NULL;
+  size_t vbmeta_num_read;
+  AvbVBMetaVerifyResult vbmeta_ret;
+  const uint8_t* pk_data;
+  size_t pk_len;
+  AvbVBMetaImageHeader vbmeta_header;
+  uint64_t stored_rollback_index;
+  const AvbDescriptor** descriptors = NULL;
+  size_t num_descriptors;
+  size_t n;
+  int is_main_vbmeta;
+
+  avb_assert(slot_data != NULL);
+
+  is_main_vbmeta = (avb_strcmp(partition_name, "vbmeta") == 0);
+
+  if (!avb_validate_utf8((const uint8_t*)partition_name, partition_name_len)) {
+    avb_error("Partition name is not valid UTF-8.\n");
+    ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;
+    goto out;
+  }
+
+  /* Construct full partition name. */
+  if (!avb_str_concat(full_partition_name, sizeof full_partition_name,
+                      partition_name, partition_name_len, ab_suffix,
+                      avb_strlen(ab_suffix))) {
+    avb_error("Partition name and suffix does not fit.\n");
+    ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;
+    goto out;
+  }
+
+  avb_debugv("Loading vbmeta struct from partition '", full_partition_name,
+             "'.\n", NULL);
+
+  /* If we're loading from the main vbmeta partition, the vbmeta
+   * struct is in the beginning. Otherwise we have to locate it via a
+   * footer.
+   */
+  if (is_main_vbmeta) {
+    vbmeta_offset = 0;
+    vbmeta_size = VBMETA_MAX_SIZE;
+  } else {
+    uint8_t footer_buf[AVB_FOOTER_SIZE];
+    size_t footer_num_read;
+    AvbFooter footer;
+
+    io_ret =
+        ops->read_from_partition(ops, full_partition_name, -AVB_FOOTER_SIZE,
+                                 AVB_FOOTER_SIZE, footer_buf, &footer_num_read);
+    if (io_ret != AVB_IO_RESULT_OK) {
+      avb_errorv(full_partition_name, ": Error loading footer.\n", NULL);
+      ret = AVB_SLOT_VERIFY_RESULT_ERROR_IO;
+      goto out;
+    }
+    avb_assert(footer_num_read == AVB_FOOTER_SIZE);
+
+    if (!avb_footer_validate_and_byteswap((const AvbFooter*)footer_buf,
+                                          &footer)) {
+      avb_errorv(full_partition_name, ": Error validating footer.\n", NULL);
+      ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;
+      goto out;
+    }
+
+    /* Basic footer sanity check since the data is untrusted. */
+    if (footer.vbmeta_size > VBMETA_MAX_SIZE) {
+      avb_errorv(full_partition_name, ": Invalid vbmeta size in footer.\n",
+                 NULL);
+      ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;
+      goto out;
+    }
+
+    vbmeta_offset = footer.vbmeta_offset;
+    vbmeta_size = footer.vbmeta_size;
+  }
+
+  vbmeta_buf = avb_malloc(vbmeta_size);
+  if (vbmeta_buf == NULL) {
+    ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
+    goto out;
+  }
+
+  io_ret = ops->read_from_partition(ops, full_partition_name, vbmeta_offset,
+                                    vbmeta_size, vbmeta_buf, &vbmeta_num_read);
+  if (io_ret != AVB_IO_RESULT_OK) {
+    avb_errorv(full_partition_name, ": Error loading vbmeta data.\n", NULL);
+    ret = AVB_SLOT_VERIFY_RESULT_ERROR_IO;
+    goto out;
+  }
+  avb_assert(vbmeta_num_read <= vbmeta_size);
+
+  /* Check if the image is properly signed and get the public key used
+   * to sign the image.
+   */
+  vbmeta_ret =
+      avb_vbmeta_image_verify(vbmeta_buf, vbmeta_num_read, &pk_data, &pk_len);
+  if (vbmeta_ret != AVB_VBMETA_VERIFY_RESULT_OK) {
+    avb_errorv(full_partition_name, ": Error verifying vbmeta image.\n", NULL);
+    ret = AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION;
+    goto out;
+  }
+
+  /* Check if key used to make signature matches what is expected. */
+  if (expected_public_key != NULL) {
+    avb_assert(!is_main_vbmeta);
+    if (expected_public_key_length != pk_len ||
+        avb_safe_memcmp(expected_public_key, pk_data, pk_len) != 0) {
+      avb_errorv(full_partition_name,
+                 ": Public key used to sign data does not match key in chain "
+                 "partition descriptor.\n",
+                 NULL);
+      ret = AVB_SLOT_VERIFY_RESULT_ERROR_PUBLIC_KEY_REJECTED;
+      goto out;
+    }
+  } else {
+    avb_assert(is_main_vbmeta);
+    if (!ops->validate_vbmeta_public_key(ops, pk_data, pk_len)) {
+      avb_errorv(full_partition_name,
+                 ": Public key used to sign data rejected.\n", NULL);
+      ret = AVB_SLOT_VERIFY_RESULT_ERROR_PUBLIC_KEY_REJECTED;
+      goto out;
+    }
+  }
+
+  avb_vbmeta_image_header_to_host_byte_order((AvbVBMetaImageHeader*)vbmeta_buf,
+                                             &vbmeta_header);
+
+  /* Check rollback index. */
+  if (!ops->read_rollback_index(ops, rollback_index_slot,
+                                &stored_rollback_index)) {
+    avb_errorv(full_partition_name,
+               ": Error getting rollback index for slot.\n", NULL);
+    ret = AVB_SLOT_VERIFY_RESULT_ERROR_IO;
+    goto out;
+  }
+  if (vbmeta_header.rollback_index < stored_rollback_index) {
+    avb_errorv(
+        full_partition_name,
+        ": Image rollback index is less than the stored rollback index.\n",
+        NULL);
+    ret = AVB_SLOT_VERIFY_RESULT_ERROR_ROLLBACK_INDEX;
+    goto out;
+  }
+
+  /* Now go through all descriptors and take the appropriate action:
+   *
+   * - hash descriptor: Load data from partition, calculate hash, and
+   *   checks that it matches what's in the hash descriptor.
+   *
+   * - hashtree descriptor: Do nothing since verification happens
+   *   on-the-fly from within the OS.
+   *
+   * - chained partition descriptor: Load the footer, load the vbmeta
+   *   image, verify vbmeta image (includes rollback checks, hash
+   *   checks, bail on chained partitions).
+   */
+  descriptors =
+      avb_descriptor_get_all(vbmeta_buf, vbmeta_num_read, &num_descriptors);
+  for (n = 0; n < num_descriptors; n++) {
+    AvbDescriptor desc;
+
+    if (!avb_descriptor_validate_and_byteswap(descriptors[n], &desc)) {
+      avb_errorv(full_partition_name, ": Descriptor is invalid.\n", NULL);
+      ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;
+      goto out;
+    }
+
+    switch (desc.tag) {
+      case AVB_DESCRIPTOR_TAG_HASH: {
+        AvbSlotVerifyResult sub_ret;
+        sub_ret = load_and_verify_hash_partition(ops, ab_suffix, descriptors[n],
+                                                 slot_data);
+        if (sub_ret != AVB_SLOT_VERIFY_RESULT_OK) {
+          ret = sub_ret;
+          goto out;
+        }
+      } break;
+
+      case AVB_DESCRIPTOR_TAG_CHAIN_PARTITION: {
+        AvbSlotVerifyResult sub_ret;
+        AvbChainPartitionDescriptor chain_desc;
+        const uint8_t* chain_partition_name;
+        const uint8_t* chain_public_key;
+
+        /* Only allow CHAIN_PARTITION descriptors in the main vbmeta image. */
+        if (!is_main_vbmeta) {
+          avb_errorv(full_partition_name,
+                     ": Encountered chain descriptor not in main image.\n",
+                     NULL);
+          ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;
+          goto out;
+        }
+
+        if (!avb_chain_partition_descriptor_validate_and_byteswap(
+                (AvbChainPartitionDescriptor*)descriptors[n], &chain_desc)) {
+          avb_errorv(full_partition_name,
+                     ": Chain partition descriptor is invalid.\n", NULL);
+          ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;
+          goto out;
+        }
+
+        chain_partition_name = ((const uint8_t*)descriptors[n]) +
+                               sizeof(AvbChainPartitionDescriptor);
+        chain_public_key = chain_partition_name + chain_desc.partition_name_len;
+
+        sub_ret = load_and_verify_vbmeta(
+            ops, ab_suffix, chain_desc.rollback_index_slot,
+            (const char*)chain_partition_name, chain_desc.partition_name_len,
+            chain_public_key, chain_desc.public_key_len, slot_data,
+            NULL /* out_algorithm_type */);
+        if (sub_ret != AVB_SLOT_VERIFY_RESULT_OK) {
+          ret = sub_ret;
+          goto out;
+        }
+      } break;
+
+      case AVB_DESCRIPTOR_TAG_KERNEL_CMDLINE: {
+        const uint8_t* kernel_cmdline;
+        AvbKernelCmdlineDescriptor kernel_cmdline_desc;
+
+        if (!avb_kernel_cmdline_descriptor_validate_and_byteswap(
+                (AvbKernelCmdlineDescriptor*)descriptors[n],
+                &kernel_cmdline_desc)) {
+          avb_errorv(full_partition_name,
+                     ": Kernel cmdline descriptor is invalid.\n", NULL);
+          ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;
+          goto out;
+        }
+
+        kernel_cmdline = ((const uint8_t*)descriptors[n]) +
+                         sizeof(AvbKernelCmdlineDescriptor);
+
+        if (!avb_validate_utf8(kernel_cmdline,
+                               kernel_cmdline_desc.kernel_cmdline_length)) {
+          avb_errorv(full_partition_name,
+                     ": Kernel cmdline is not valid UTF-8.\n", NULL);
+          ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;
+          goto out;
+        }
+
+        if (slot_data->cmdline == NULL) {
+          slot_data->cmdline =
+              avb_calloc(kernel_cmdline_desc.kernel_cmdline_length + 1);
+          if (slot_data->cmdline == NULL) {
+            ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
+            goto out;
+          }
+          avb_memcpy(slot_data->cmdline, kernel_cmdline,
+                     kernel_cmdline_desc.kernel_cmdline_length);
+        } else {
+          /* new cmdline is: <existing_cmdline> + ' ' + <newcmdline> + '\0' */
+          size_t orig_size = avb_strlen(slot_data->cmdline);
+          size_t new_size =
+              orig_size + 1 + kernel_cmdline_desc.kernel_cmdline_length + 1;
+          char* new_cmdline = avb_calloc(new_size);
+          if (new_cmdline == NULL) {
+            ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
+            goto out;
+          }
+          avb_memcpy(new_cmdline, slot_data->cmdline, orig_size);
+          new_cmdline[orig_size] = ' ';
+          avb_memcpy(new_cmdline + orig_size + 1, kernel_cmdline,
+                     kernel_cmdline_desc.kernel_cmdline_length);
+          avb_free(slot_data->cmdline);
+          slot_data->cmdline = new_cmdline;
+        }
+      } break;
+
+      /* Explicit fall-through */
+      case AVB_DESCRIPTOR_TAG_PROPERTY:
+      case AVB_DESCRIPTOR_TAG_HASHTREE:
+        /* Do nothing. */
+        break;
+    }
+  }
+
+  ret = AVB_SLOT_VERIFY_RESULT_OK;
+
+  /* So far, so good. Copy needed data to user, if requested. */
+  if (is_main_vbmeta) {
+    if (slot_data->vbmeta_data != NULL) {
+      avb_free(slot_data->vbmeta_data);
+    }
+    /* Note that |vbmeta_buf| is actually |vbmeta_num_read| bytes long
+     * and this includes data past the end of the image. Pass the
+     * actual size of the vbmeta image. Also, no need to use
+     * avb_safe_add() since the header has already been verified.
+     */
+    slot_data->vbmeta_size = sizeof(AvbVBMetaImageHeader) +
+                             vbmeta_header.authentication_data_block_size +
+                             vbmeta_header.auxiliary_data_block_size;
+    slot_data->vbmeta_data = vbmeta_buf;
+    vbmeta_buf = NULL;
+  }
+
+  if (rollback_index_slot >= AVB_MAX_NUMBER_OF_ROLLBACK_INDEX_SLOTS) {
+    avb_errorv(full_partition_name, ": Invalid rollback_index_slot.\n", NULL);
+    ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;
+    goto out;
+  }
+
+  slot_data->rollback_indexes[rollback_index_slot] =
+      vbmeta_header.rollback_index;
+
+  if (out_algorithm_type != NULL) {
+    *out_algorithm_type = (AvbAlgorithmType)vbmeta_header.algorithm_type;
+  }
+
+out:
+  if (vbmeta_buf != NULL) {
+    avb_free(vbmeta_buf);
+  }
+  if (descriptors != NULL) {
+    avb_free(descriptors);
+  }
+  return ret;
+}
+
+/* Substitutes all variables (e.g. $(ANDROID_SYSTEM_PARTUUID)) with
+ * values. Returns NULL on OOM, otherwise the cmdline with values
+ * replaced.
+ */
+static char* sub_cmdline(AvbOps* ops, const char* cmdline,
+                         const char* ab_suffix) {
+  const int NUM_GUIDS = 2;
+  const char* part_name_str[NUM_GUIDS] = {"system", "boot"};
+  const char* replace_str[NUM_GUIDS] = {"$(ANDROID_SYSTEM_PARTUUID)",
+                                        "$(ANDROID_BOOT_PARTUUID)"};
+  char* ret = NULL;
+
+  /* Replace unique partition GUIDs */
+  for (size_t n = 0; n < NUM_GUIDS; n++) {
+    char part_name[PART_NAME_MAX_SIZE];
+    char guid_buf[37];
+    char* new_ret;
+
+    if (!avb_str_concat(part_name, sizeof part_name, part_name_str[n],
+                        avb_strlen(part_name_str[n]), ab_suffix,
+                        avb_strlen(ab_suffix))) {
+      avb_error("Partition name and suffix does not fit.\n");
+      goto fail;
+    }
+
+    if (!ops->get_unique_guid_for_partition(ops, part_name, guid_buf,
+                                            sizeof guid_buf)) {
+      avb_error("Error getting unique GUID for partition.\n");
+      goto fail;
+    }
+
+    if (ret == NULL) {
+      new_ret = avb_replace(cmdline, replace_str[n], guid_buf);
+    } else {
+      new_ret = avb_replace(ret, replace_str[n], guid_buf);
+    }
+    if (new_ret == NULL) {
+      goto fail;
+    }
+    ret = new_ret;
+  }
+
+  return ret;
+
+fail:
+  if (ret != NULL) {
+    avb_free(ret);
+  }
+  return NULL;
+}
+
+static int cmdline_append_option(AvbSlotVerifyData* slot_data, const char* key,
+                                 const char* value) {
+  size_t offset, key_len, value_len;
+  char* new_cmdline;
+
+  key_len = avb_strlen(key);
+  value_len = avb_strlen(value);
+
+  offset = 0;
+  if (slot_data->cmdline != NULL) {
+    offset = avb_strlen(slot_data->cmdline);
+    if (offset > 0) {
+      offset += 1;
+    }
+  }
+
+  new_cmdline = avb_calloc(offset + key_len + value_len + 2);
+  if (new_cmdline == NULL) {
+    return 0;
+  }
+  if (offset > 0) {
+    avb_memcpy(new_cmdline, slot_data->cmdline, offset - 1);
+    new_cmdline[offset - 1] = ' ';
+  }
+  avb_memcpy(new_cmdline + offset, key, key_len);
+  new_cmdline[offset + key_len] = '=';
+  avb_memcpy(new_cmdline + offset + key_len + 1, value, value_len);
+  if (slot_data->cmdline != NULL) {
+    avb_free(slot_data->cmdline);
+  }
+  slot_data->cmdline = new_cmdline;
+
+  return 1;
+}
+
+static int cmdline_append_uint64_base10(AvbSlotVerifyData* slot_data,
+                                        const char* key, uint64_t value) {
+  const int MAX_DIGITS = 32;
+  char rev_digits[MAX_DIGITS];
+  char digits[MAX_DIGITS];
+  size_t n, num_digits;
+
+  for (num_digits = 0; num_digits < MAX_DIGITS - 1;) {
+    rev_digits[num_digits++] = (value % 10) + '0';
+    value /= 10;
+    if (value == 0) {
+      break;
+    }
+  }
+
+  for (n = 0; n < num_digits; n++) {
+    digits[n] = rev_digits[num_digits - 1 - n];
+  }
+  digits[n] = '\0';
+
+  return cmdline_append_option(slot_data, key, digits);
+}
+
+static int cmdline_append_hex(AvbSlotVerifyData* slot_data, const char* key,
+                              const uint8_t* data, size_t data_len) {
+  char hex_digits[17] = "0123456789abcdef";
+  char* hex_data;
+  int ret;
+  size_t n;
+
+  hex_data = avb_malloc(data_len * 2 + 1);
+  if (hex_data == NULL) return 0;
+
+  for (n = 0; n < data_len; n++) {
+    hex_data[n * 2] = hex_digits[data[n] >> 4];
+    hex_data[n * 2 + 1] = hex_digits[data[n] & 0x0f];
+  }
+  hex_data[n] = '\0';
+
+  ret = cmdline_append_option(slot_data, key, hex_data);
+  avb_free(hex_data);
+  return ret;
+}
+
+AvbSlotVerifyResult avb_slot_verify(AvbOps* ops, const char* ab_suffix,
+                                    AvbSlotVerifyData** out_data) {
+  AvbSlotVerifyResult ret;
+  AvbSlotVerifyData* slot_data = NULL;
+  AvbAlgorithmType algorithm_type = AVB_ALGORITHM_TYPE_NONE;
+
+  if (out_data != NULL) {
+    *out_data = NULL;
+  }
+
+  slot_data = avb_calloc(sizeof(AvbSlotVerifyData));
+  if (slot_data == NULL) {
+    ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
+    goto fail;
+  }
+
+  ret = load_and_verify_vbmeta(
+      ops, ab_suffix, 0 /* rollback_index_slot */, "vbmeta",
+      avb_strlen("vbmeta"), NULL /* expected_public_key */,
+      0 /* expected_public_key_length */, slot_data, &algorithm_type);
+
+  /* If things check out, mangle the kernel command-line as needed. */
+  if (ret == AVB_SLOT_VERIFY_RESULT_OK) {
+    /* Substitute $(ANDROID_SYSTEM_PARTUUID) and friends. */
+    if (slot_data->cmdline != NULL) {
+      char* new_cmdline = sub_cmdline(ops, slot_data->cmdline, ab_suffix);
+      if (new_cmdline == NULL) {
+        ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
+        goto fail;
+      }
+      avb_free(slot_data->cmdline);
+      slot_data->cmdline = new_cmdline;
+    }
+
+    /* Add androidboot.slot_suffix, if applicable. */
+    if (avb_strlen(ab_suffix) > 0) {
+      if (!cmdline_append_option(slot_data, "androidboot.slot_suffix",
+                                 ab_suffix)) {
+        ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
+        goto fail;
+      }
+    }
+
+    /* Set androidboot.avb.device_state to "locked" or "unlocked". */
+    bool is_device_unlocked;
+    if (!ops->read_is_device_unlocked(ops, &is_device_unlocked)) {
+      avb_error("Error getting device state.\n");
+      ret = AVB_SLOT_VERIFY_RESULT_ERROR_IO;
+      goto fail;
+    }
+    if (!cmdline_append_option(slot_data, "androidboot.vbmeta.device_state",
+                               is_device_unlocked ? "unlocked" : "locked")) {
+      ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
+      goto fail;
+    }
+
+    /* Set androidboot.vbmeta.{hash_alg, size, digest} - use same hash
+     * function as is used to sign vbmeta.
+     */
+    switch (algorithm_type) {
+      /* Explicit fallthrough. */
+      case AVB_ALGORITHM_TYPE_NONE:
+      case AVB_ALGORITHM_TYPE_SHA256_RSA2048:
+      case AVB_ALGORITHM_TYPE_SHA256_RSA4096:
+      case AVB_ALGORITHM_TYPE_SHA256_RSA8192: {
+        AvbSHA256Ctx ctx;
+        avb_sha256_init(&ctx);
+        avb_sha256_update(&ctx, slot_data->vbmeta_data, slot_data->vbmeta_size);
+        if (!cmdline_append_option(slot_data, "androidboot.vbmeta.hash_alg",
+                                   "sha256") ||
+            !cmdline_append_uint64_base10(slot_data, "androidboot.vbmeta.size",
+                                          slot_data->vbmeta_size) ||
+            !cmdline_append_hex(slot_data, "androidboot.vbmeta.digest",
+                                avb_sha256_final(&ctx),
+                                AVB_SHA256_DIGEST_SIZE)) {
+          ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
+          goto fail;
+        }
+      } break;
+      /* Explicit fallthrough. */
+      case AVB_ALGORITHM_TYPE_SHA512_RSA2048:
+      case AVB_ALGORITHM_TYPE_SHA512_RSA4096:
+      case AVB_ALGORITHM_TYPE_SHA512_RSA8192: {
+        AvbSHA512Ctx ctx;
+        avb_sha512_init(&ctx);
+        avb_sha512_update(&ctx, slot_data->vbmeta_data, slot_data->vbmeta_size);
+        if (!cmdline_append_option(slot_data, "androidboot.vbmeta.hash_alg",
+                                   "sha512") ||
+            !cmdline_append_uint64_base10(slot_data, "androidboot.vbmeta.size",
+                                          slot_data->vbmeta_size) ||
+            !cmdline_append_hex(slot_data, "androidboot.vbmeta.digest",
+                                avb_sha512_final(&ctx),
+                                AVB_SHA512_DIGEST_SIZE)) {
+          ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;
+          goto fail;
+        }
+      } break;
+      case _AVB_ALGORITHM_NUM_TYPES:
+        avb_assert_not_reached();
+        break;
+    }
+
+    if (out_data != NULL) {
+      *out_data = slot_data;
+    } else {
+      avb_slot_verify_data_free(slot_data);
+    }
+  }
+
+  return ret;
+
+fail:
+  if (slot_data != NULL) {
+    avb_slot_verify_data_free(slot_data);
+  }
+  return ret;
+}
+
+void avb_slot_verify_data_free(AvbSlotVerifyData* data) {
+  if (data->boot_data != NULL) {
+    avb_free(data->boot_data);
+  }
+  if (data->cmdline != NULL) {
+    avb_free(data->cmdline);
+  }
+  avb_free(data);
+}
diff --git a/avb/libavb/avb_slot_verify.h b/avb/libavb/avb_slot_verify.h
new file mode 100644
index 0000000..a4668f7
--- /dev/null
+++ b/avb/libavb/avb_slot_verify.h
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#if !defined(AVB_INSIDE_LIBAVB_H) && !defined(AVB_COMPILATION)
+#error "Never include this file directly, include libavb.h instead."
+#endif
+
+#ifndef AVB_SLOT_VERIFY_H_
+#define AVB_SLOT_VERIFY_H_
+
+#include "avb_ops.h"
+#include "avb_vbmeta_image.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Return codes used in avb_slot_verify(), see that function for
+ * documentation for each field.
+ */
+typedef enum {
+  AVB_SLOT_VERIFY_RESULT_OK,
+  AVB_SLOT_VERIFY_RESULT_ERROR_OOM,
+  AVB_SLOT_VERIFY_RESULT_ERROR_IO,
+  AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION,
+  AVB_SLOT_VERIFY_RESULT_ERROR_ROLLBACK_INDEX,
+  AVB_SLOT_VERIFY_RESULT_ERROR_PUBLIC_KEY_REJECTED,
+  AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA
+} AvbSlotVerifyResult;
+
+/* Maximum number of rollback index slots number supported. */
+#define AVB_MAX_NUMBER_OF_ROLLBACK_INDEX_SLOTS 32
+
+/* AvbSlotVerifyData contains data needed to boot a particular slot
+ * and is returned by avb_slot_verify() if partitions in a slot are
+ * successfully verified.
+ *
+ * All data pointed to by this struct will be freed when the
+ * avb_slot_verify_data_free() function is called.
+ *
+ * The image loaded and verified from the boot partition of the slot
+ * is accessible via the |boot_data| and is of length |boot_size|
+ * bytes. Note that this is strictly less than the partition size -
+ * it's only the image stored there, not the entire partition nor any
+ * of the metadata.
+ *
+ * The verified vbmeta image in the 'vbmeta' partition of the slot is
+ * accessible from the |vbmeta_data| field and is of length
+ * |vbmeta_size| bytes. You can use this data with
+ * e.g. avb_descriptor_get_all().
+ *
+ * Rollback indexes for the slot are stored in the |rollback_indexes|
+ * field.
+ *
+ * The |cmdline| field is a NUL-terminated string in UTF-8 resulting
+ * from concatenating all |AvbKernelCmdlineDescriptor| and then
+ * performing proper substitution of the variables
+ * $(ANDROID_SYSTEM_PARTUUID) and $(ANDROID_BOOT_PARTUUID) using the
+ * get_unique_guid_for_partition() operation in |AvbOps|.
+ *
+ * Additionally, the |cmdline| field will have the following kernel
+ * command-line options set:
+ *
+ *   androidboot.avb.device_state: set to "locked" or "unlocked"
+ *   depending on the result of the result of AvbOps's
+ *   read_is_unlocked() function.
+ *
+ *   androidboot.slot_suffix: If |ab_suffix| as passed into
+ *   avb_slot_verify() is non-empty, this variable will be set to its
+ *   value.
+ *
+ *   androidboot.vbmeta.{hash_alg, size, digest}: Will be set to
+ *   the digest of the vbmeta image.
+ */
+typedef struct {
+  uint8_t* boot_data;
+  size_t boot_size;
+  uint8_t* vbmeta_data;
+  size_t vbmeta_size;
+  char* cmdline;
+  uint64_t rollback_indexes[AVB_MAX_NUMBER_OF_ROLLBACK_INDEX_SLOTS];
+} AvbSlotVerifyData;
+
+/* Frees a |AvbSlotVerifyData| including all data it points to. */
+void avb_slot_verify_data_free(AvbSlotVerifyData* data);
+
+/* Performs a full verification of the slot identified by
+ * |ab_suffix|. If not using A/B, pass an empty string (e.g. "", not
+ * NULL) for |ab_suffix|.
+ *
+ * This includes loading data from the 'vbmeta', 'boot', and possibly
+ * other partitions (with |ab_suffix| appended), inspecting rollback
+ * indexes, and checking if the public key used to sign the data is
+ * acceptable. The functions in |ops| will be used to do this.
+ *
+ * If |out_data| is not NULL, it will be set to a newly allocated
+ * |AvbSlotVerifyData| struct containing all the data needed to
+ * actually boot the slot. This data structure should be freed with
+ * avb_slot_verify_data_free() when you are done with it.
+ *
+ * AVB_SLOT_VERIFY_RESULT_OK is returned if everything is verified
+ * correctly and all public keys are accepted.
+ *
+ * AVB_SLOT_VERIFY_RESULT_ERROR_PUBLIC_KEY_REJECTED is returned if
+ * everything is verified correctly out but one or more public keys
+ * are not accepted. This includes the case where integrity data is
+ * not signed.
+ *
+ * AVB_SLOT_VERIFY_RESULT_ERROR_OOM is returned if unable to
+ * allocate memory.
+ *
+ * AVB_SLOT_VERIFY_RESULT_ERROR_IO is returned if an I/O error
+ * occurred while trying to load data or get a rollback index.
+ *
+ * AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION is returned if the data
+ * did not verify, e.g. the digest didn't match or signature checks
+ * failed.
+ *
+ * AVB_SLOT_VERIFY_RESULT_ERROR_ROLLBACK_INDEX is returned if a
+ * rollback index was less than its stored value.
+ *
+ * AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA is returned if some
+ * of the metadata is invalid or inconsistent.
+ */
+AvbSlotVerifyResult avb_slot_verify(AvbOps* ops, const char* ab_suffix,
+                                    AvbSlotVerifyData** out_data);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* AVB_SLOT_VERIFY_H_ */
diff --git a/avb/libavb/avb_sysdeps.h b/avb/libavb/avb_sysdeps.h
new file mode 100644
index 0000000..83c3057
--- /dev/null
+++ b/avb/libavb/avb_sysdeps.h
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#if !defined(AVB_INSIDE_LIBAVB_H) && !defined(AVB_COMPILATION)
+#error "Never include this file directly, include libavb.h instead."
+#endif
+
+#ifndef AVB_SYSDEPS_H_
+#define AVB_SYSDEPS_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Change these includes to match your platform to bring in the
+ * equivalent types available in a normal C runtime. At least things
+ * like uint8_t, uint64_t, and bool (with |false|, |true| keywords)
+ * must be present.
+ */
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+/* If you don't have gcc or clang, these attribute macros may need to
+ * be adjusted.
+ */
+#define AVB_ATTR_WARN_UNUSED_RESULT __attribute__((warn_unused_result))
+#define AVB_ATTR_PACKED __attribute__((packed))
+#define AVB_ATTR_NO_RETURN __attribute__((noreturn))
+#define AVB_ATTR_SENTINEL __attribute__((__sentinel__));
+
+/* Size in bytes used for word-alignment.
+ *
+ * Change this to match your architecture - must be a power of two.
+ */
+#define AVB_WORD_ALIGNMENT_SIZE 8
+
+/* Compare |n| bytes in |src1| and |src2|.
+ *
+ * Returns an integer less than, equal to, or greater than zero if the
+ * first |n| bytes of |src1| is found, respectively, to be less than,
+ * to match, or be greater than the first |n| bytes of |src2|. */
+int avb_memcmp(const void* src1, const void* src2,
+               size_t n) AVB_ATTR_WARN_UNUSED_RESULT;
+
+/* Compare two strings.
+ *
+ * Return an integer less than, equal to, or greater than zero if |s1|
+ * is found, respectively, to be less than, to match, or be greater
+ * than |s2|.
+ */
+int avb_strcmp(const char* s1, const char* s2);
+
+/* Copy |n| bytes from |src| to |dest|. */
+void* avb_memcpy(void* dest, const void* src, size_t n);
+
+/* Set |n| bytes starting at |s| to |c|.  Returns |dest|. */
+void* avb_memset(void* dest, const int c, size_t n);
+
+/* Prints out a message. The string passed must be a NUL-terminated
+ * UTF-8 string.
+ */
+void avb_print(const char* message);
+
+/* Prints out a vector of strings. Each argument must point to a
+ * NUL-terminated UTF-8 string and NULL should be the last argument.
+ */
+void avb_printv(const char* message, ...) AVB_ATTR_SENTINEL;
+
+/* Aborts the program or reboots the device. */
+void avb_abort(void) AVB_ATTR_NO_RETURN;
+
+/* Allocates |size| bytes. Returns NULL if no memory is available,
+ * otherwise a pointer to the allocated memory.
+ *
+ * The memory is not initialized.
+ *
+ * The pointer returned is guaranteed to be word-aligned.
+ *
+ * The memory should be freed with avb_free() when you are done with it.
+ */
+void* avb_malloc_(size_t size) AVB_ATTR_WARN_UNUSED_RESULT;
+
+/* Frees memory previously allocated with avb_malloc(). */
+void avb_free(void* ptr);
+
+/* Returns the lenght of |str|, excluding the terminating NUL-byte. */
+size_t avb_strlen(const char* str) AVB_ATTR_WARN_UNUSED_RESULT;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* AVB_SYSDEPS_H_ */
diff --git a/avb/libavb/avb_sysdeps_posix.c b/avb/libavb/avb_sysdeps_posix.c
new file mode 100644
index 0000000..f84a2ba
--- /dev/null
+++ b/avb/libavb/avb_sysdeps_posix.c
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <endian.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "avb_sysdeps.h"
+
+int avb_memcmp(const void* src1, const void* src2, size_t n) {
+  return memcmp(src1, src2, n);
+}
+
+void* avb_memcpy(void* dest, const void* src, size_t n) {
+  return memcpy(dest, src, n);
+}
+
+void* avb_memset(void* dest, const int c, size_t n) {
+  return memset(dest, c, n);
+}
+
+int avb_strcmp(const char* s1, const char* s2) { return strcmp(s1, s2); }
+
+size_t avb_strlen(const char* str) { return strlen(str); }
+
+void avb_abort(void) { abort(); }
+
+void avb_print(const char* message) { fprintf(stderr, "%s", message); }
+
+void avb_printv(const char* message, ...) {
+  va_list ap;
+  const char* m;
+
+  va_start(ap, message);
+  for (m = message; m != NULL; m = va_arg(ap, const char*)) {
+    fprintf(stderr, "%s", m);
+  }
+  va_end(ap);
+}
+
+void* avb_malloc_(size_t size) { return malloc(size); }
+
+void avb_free(void* ptr) { free(ptr); }
diff --git a/avb/libavb/avb_util.c b/avb/libavb/avb_util.c
new file mode 100644
index 0000000..737f1aa
--- /dev/null
+++ b/avb/libavb/avb_util.c
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "avb_util.h"
+
+uint32_t avb_be32toh(uint32_t in) {
+  uint8_t* d = (uint8_t*)&in;
+  uint32_t ret;
+  ret = ((uint32_t)d[0]) << 24;
+  ret |= ((uint32_t)d[1]) << 16;
+  ret |= ((uint32_t)d[2]) << 8;
+  ret |= ((uint32_t)d[3]);
+  return ret;
+}
+
+uint64_t avb_be64toh(uint64_t in) {
+  uint8_t* d = (uint8_t*)&in;
+  uint64_t ret;
+  ret = ((uint64_t)d[0]) << 56;
+  ret |= ((uint64_t)d[1]) << 48;
+  ret |= ((uint64_t)d[2]) << 40;
+  ret |= ((uint64_t)d[3]) << 32;
+  ret |= ((uint64_t)d[4]) << 24;
+  ret |= ((uint64_t)d[5]) << 16;
+  ret |= ((uint64_t)d[6]) << 8;
+  ret |= ((uint64_t)d[7]);
+  return ret;
+}
+
+int avb_safe_memcmp(const void* s1, const void* s2, size_t n) {
+  const unsigned char* us1 = s1;
+  const unsigned char* us2 = s2;
+  int result = 0;
+
+  if (0 == n) return 0;
+
+  /*
+   * Code snippet without data-dependent branch due to Nate Lawson
+   * (nate@root.org) of Root Labs.
+   */
+  while (n--) result |= *us1++ ^ *us2++;
+
+  return result != 0;
+}
+
+bool avb_safe_add_to(uint64_t* value, uint64_t value_to_add) {
+  uint64_t original_value;
+
+  avb_assert(value != NULL);
+
+  original_value = *value;
+
+  *value += value_to_add;
+  if (*value < original_value) {
+    avb_error("Overflow when adding values.\n");
+    return false;
+  }
+
+  return true;
+}
+
+bool avb_safe_add(uint64_t* out_result, uint64_t a, uint64_t b) {
+  uint64_t dummy;
+  if (out_result == NULL) out_result = &dummy;
+  *out_result = a;
+  return avb_safe_add_to(out_result, b);
+}
+
+bool avb_validate_utf8(const uint8_t* data, size_t num_bytes) {
+  size_t n;
+  unsigned int num_cc;
+
+  for (n = 0, num_cc = 0; n < num_bytes; n++) {
+    uint8_t c = data[n];
+
+    if (num_cc > 0) {
+      if ((c & (0x80 | 0x40)) == 0x80) {
+        /* 10xx xxxx */
+      } else {
+        goto fail;
+      }
+      num_cc--;
+    } else {
+      if (c < 0x80) {
+        num_cc = 0;
+      } else if ((c & (0x80 | 0x40 | 0x20)) == (0x80 | 0x40)) {
+        /* 110x xxxx */
+        num_cc = 1;
+      } else if ((c & (0x80 | 0x40 | 0x20 | 0x10)) == (0x80 | 0x40 | 0x20)) {
+        /* 1110 xxxx */
+        num_cc = 2;
+      } else if ((c & (0x80 | 0x40 | 0x20 | 0x10 | 0x08)) ==
+                 (0x80 | 0x40 | 0x20 | 0x10)) {
+        /* 1111 0xxx */
+        num_cc = 3;
+      } else {
+        goto fail;
+      }
+    }
+  }
+
+  if (num_cc != 0) {
+    goto fail;
+  }
+
+  return true;
+
+fail:
+  return false;
+}
+
+bool avb_str_concat(char* buf, size_t buf_size, const char* str1,
+                    size_t str1_len, const char* str2, size_t str2_len) {
+  uint64_t combined_len;
+
+  if (!avb_safe_add(&combined_len, str1_len, str2_len)) {
+    avb_error("Overflow when adding string sizes.\n");
+    return false;
+  }
+
+  if (combined_len > buf_size - 1) {
+    avb_error("Insufficient buffer space.\n");
+    return false;
+  }
+
+  avb_memcpy(buf, str1, str1_len);
+  avb_memcpy(buf + str1_len, str2, str2_len);
+  buf[combined_len] = '\0';
+
+  return true;
+}
+
+void* avb_malloc(size_t size) {
+  void* ret = avb_malloc_(size);
+  if (ret == NULL) {
+    avb_error("Failed to allocate memory.\n");
+    return NULL;
+  }
+  return ret;
+}
+
+void* avb_calloc(size_t size) {
+  void* ret = avb_malloc(size);
+  if (ret == NULL) {
+    return NULL;
+  }
+
+  avb_memset(ret, '\0', size);
+  return ret;
+}
+
+char* avb_strdup(const char* str) {
+  size_t len = avb_strlen(str);
+  char* ret = avb_malloc(len + 1);
+  if (ret == NULL) {
+    return NULL;
+  }
+
+  avb_memcpy(ret, str, len);
+  ret[len] = '\0';
+
+  return ret;
+}
+
+const char* avb_strstr(const char* haystack, const char* needle) {
+  size_t n, m;
+
+  /* Look through |haystack| and check if the first character of
+   * |needle| matches. If so, check the rest of |needle|.
+   */
+  for (n = 0; haystack[n] != '\0'; n++) {
+    if (haystack[n] != needle[0]) continue;
+
+    for (m = 1;; m++) {
+      if (needle[m] == '\0') {
+        return haystack + n;
+      }
+
+      if (haystack[n + m] != needle[m]) break;
+    }
+  }
+
+  return NULL;
+}
+
+char* avb_replace(const char* str, const char* search, const char* replace) {
+  char* ret = NULL;
+  size_t ret_len = 0;
+  size_t search_len, replace_len;
+  const char* str_after_last_replace;
+
+  search_len = avb_strlen(search);
+  replace_len = avb_strlen(replace);
+
+  str_after_last_replace = str;
+  while (*str != '\0') {
+    const char* s;
+    size_t num_before;
+    size_t num_new;
+
+    s = avb_strstr(str, search);
+    if (s == NULL) break;
+
+    num_before = s - str;
+
+    if (ret == NULL) {
+      num_new = num_before + replace_len + 1;
+      ret = avb_malloc(num_new);
+      if (ret == NULL) {
+        goto out;
+      }
+      avb_memcpy(ret, str, num_before);
+      avb_memcpy(ret + num_before, replace, replace_len);
+      ret[num_new - 1] = '\0';
+      ret_len = num_new - 1;
+    } else {
+      char* new_str;
+      num_new = ret_len + num_before + replace_len + 1;
+      new_str = avb_malloc(num_new);
+      if (ret == NULL) {
+        goto out;
+      }
+      avb_memcpy(new_str, ret, ret_len);
+      avb_memcpy(new_str + ret_len, str, num_before);
+      avb_memcpy(new_str + ret_len + num_before, replace, replace_len);
+      new_str[num_new - 1] = '\0';
+      avb_free(ret);
+      ret = new_str;
+      ret_len = num_new - 1;
+    }
+
+    str = s + search_len;
+    str_after_last_replace = str;
+  }
+
+  if (ret == NULL) {
+    ret = avb_strdup(str_after_last_replace);
+    if (ret == NULL) {
+      goto out;
+    }
+  } else {
+    size_t num_remaining = avb_strlen(str_after_last_replace);
+    size_t num_new = ret_len + num_remaining + 1;
+    char* new_str = avb_malloc(num_new);
+    if (ret == NULL) goto out;
+    avb_memcpy(new_str, ret, ret_len);
+    avb_memcpy(new_str + ret_len, str_after_last_replace, num_remaining);
+    new_str[num_new - 1] = '\0';
+    avb_free(ret);
+    ret = new_str;
+    ret_len = num_new - 1;
+  }
+
+out:
+  return ret;
+}
diff --git a/avb/libavb/avb_util.h b/avb/libavb/avb_util.h
new file mode 100644
index 0000000..f7c6d4a
--- /dev/null
+++ b/avb/libavb/avb_util.h
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#if !defined(AVB_INSIDE_LIBAVB_H) && !defined(AVB_COMPILATION)
+#error "Never include this file directly, include libavb.h instead."
+#endif
+
+#ifndef AVB_UTIL_H_
+#define AVB_UTIL_H_
+
+#include "avb_sysdeps.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define AVB_STRINGIFY(x) #x
+#define AVB_TO_STRING(x) AVB_STRINGIFY(x)
+
+#ifdef AVB_ENABLE_DEBUG
+/* Aborts the program if |expr| is false.
+ *
+ * This has no effect unless AVB_ENABLE_DEBUG is defined.
+ */
+#define avb_assert(expr)                     \
+  do {                                       \
+    if (!(expr)) {                           \
+      avb_fatal("assert fail: " #expr "\n"); \
+    }                                        \
+  } while (0)
+#else
+#define avb_assert(expr)
+#endif
+
+/* Aborts the program if reached.
+ *
+ * This has no effect unless AVB_ENABLE_DEBUG is defined.
+ */
+#ifdef AVB_ENABLE_DEBUG
+#define avb_assert_not_reached()         \
+  do {                                   \
+    avb_fatal("assert_not_reached()\n"); \
+  } while (0)
+#else
+#define avb_assert_not_reached()
+#endif
+
+/* Aborts the program if |addr| is not word-aligned.
+ *
+ * This has no effect unless AVB_ENABLE_DEBUG is defined.
+ */
+#define avb_assert_word_aligned(addr) \
+  avb_assert((((uintptr_t)addr) & (AVB_WORD_ALIGNMENT_SIZE - 1)) == 0)
+
+#ifdef AVB_ENABLE_DEBUG
+/* Print functions, used for diagnostics.
+ *
+ * These have no effect unless AVB_ENABLE_DEBUG is defined.
+ */
+#define avb_debug(message)                                       \
+  do {                                                           \
+    avb_print(__FILE__ ":" AVB_TO_STRING(__LINE__) ": DEBUG: "); \
+    avb_print(message);                                          \
+  } while (0)
+#define avb_debugv(message, ...)                                 \
+  do {                                                           \
+    avb_print(__FILE__ ":" AVB_TO_STRING(__LINE__) ": DEBUG: "); \
+    avb_printv(message, ##__VA_ARGS__);                          \
+  } while (0)
+#else
+#define avb_debug(message)
+#define avb_debugv(message, ...)
+#endif
+
+/* Prints out a message. This is typically used if a runtime-error
+ * occurs.
+ */
+#define avb_error(message)                                       \
+  do {                                                           \
+    avb_print(__FILE__ ":" AVB_TO_STRING(__LINE__) ": ERROR: "); \
+    avb_print(message);                                          \
+  } while (0)
+#define avb_errorv(message, ...)                                 \
+  do {                                                           \
+    avb_print(__FILE__ ":" AVB_TO_STRING(__LINE__) ": ERROR: "); \
+    avb_printv(message, ##__VA_ARGS__);                          \
+  } while (0)
+
+/* Prints out a message and calls avb_abort().
+ */
+#define avb_fatal(message)                                       \
+  do {                                                           \
+    avb_print(__FILE__ ":" AVB_TO_STRING(__LINE__) ": FATAL: "); \
+    avb_print(message);                                          \
+    avb_abort();                                                 \
+  } while (0)
+#define avb_fatalv(message, ...)                                 \
+  do {                                                           \
+    avb_print(__FILE__ ":" AVB_TO_STRING(__LINE__) ": FATAL: "); \
+    avb_printv(message, ##__VA_ARGS__);                          \
+    avb_abort();                                                 \
+  } while (0)
+
+/* Converts a 32-bit unsigned integer from big-endian to host byte order. */
+uint32_t avb_be32toh(uint32_t in) AVB_ATTR_WARN_UNUSED_RESULT;
+
+/* Converts a 64-bit unsigned integer from big-endian to host byte order. */
+uint64_t avb_be64toh(uint64_t in) AVB_ATTR_WARN_UNUSED_RESULT;
+
+/* Compare |n| bytes starting at |s1| with |s2| and return 0 if they
+ * match, 1 if they don't.  Returns 0 if |n|==0, since no bytes
+ * mismatched.
+ *
+ * Time taken to perform the comparison is only dependent on |n| and
+ * not on the relationship of the match between |s1| and |s2|.
+ *
+ * Note that unlike avb_memcmp(), this only indicates inequality, not
+ * whether |s1| is less than or greater than |s2|.
+ */
+int avb_safe_memcmp(const void* s1, const void* s2,
+                    size_t n) AVB_ATTR_WARN_UNUSED_RESULT;
+
+/* Adds |value_to_add| to |value| with overflow protection.
+ *
+ * Returns false if the addition overflows, true otherwise. In either
+ * case, |value| is always modified.
+ */
+bool avb_safe_add_to(uint64_t* value,
+                     uint64_t value_to_add) AVB_ATTR_WARN_UNUSED_RESULT;
+
+/* Adds |a| and |b| with overflow protection, returning the value in
+ * |out_result|.
+ *
+ * It's permissible to pass NULL for |out_result| if you just want to
+ * check that the addition would not overflow.
+ *
+ * Returns false if the addition overflows, true otherwise.
+ */
+bool avb_safe_add(uint64_t* out_result, uint64_t a,
+                  uint64_t b) AVB_ATTR_WARN_UNUSED_RESULT;
+
+/* Checks if |num_bytes| data at |data| is a valid UTF-8
+ * string. Returns true if valid UTF-8, false otherwise.
+ */
+bool avb_validate_utf8(const uint8_t* data,
+                       size_t num_bytes) AVB_ATTR_WARN_UNUSED_RESULT;
+
+/* Concatenates |str1| (of |str1_len| bytes) and |str2| (of |str2_len|
+ * bytes) and puts the result in |buf| which holds |buf_size|
+ * bytes. The result is also guaranteed to be NUL terminated. Fail if
+ * there is not enough room in |buf| for the resulting string plus
+ * terminating NUL byte.
+ *
+ * Returns true if the operation succeeds, false otherwise.
+ */
+bool avb_str_concat(char* buf, size_t buf_size, const char* str1,
+                    size_t str1_len, const char* str2, size_t str2_len);
+
+/* Like avb_malloc_() but prints a error using avb_error() if memory
+ * allocation fails.
+ */
+void* avb_malloc(size_t size) AVB_ATTR_WARN_UNUSED_RESULT;
+
+/* Like avb_malloc() but sets the memory with zeroes. */
+void* avb_calloc(size_t size) AVB_ATTR_WARN_UNUSED_RESULT;
+
+/* Duplicates a NUL-terminated string. Returns NULL on OOM. */
+char* avb_strdup(const char* str) AVB_ATTR_WARN_UNUSED_RESULT;
+
+/* Finds the first occurrence of |needle| in the string |haystack|
+ * where both strings are NUL-terminated strings. The terminating NUL
+ * bytes are not compared.
+ *
+ * Returns NULL if not found, otherwise points into |haystack| for the
+ * first occurrence of |needle|.
+ */
+const char* avb_strstr(const char* haystack,
+                       const char* needle) AVB_ATTR_WARN_UNUSED_RESULT;
+
+/* Replaces all occurrences of |search| with |replace| in |str|.
+ *
+ * Returns a newly allocated string or NULL if out of memory.
+ */
+char* avb_replace(const char* str, const char* search,
+                  const char* replace) AVB_ATTR_WARN_UNUSED_RESULT;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* AVB_UTIL_H_ */
diff --git a/avb/libavb/avb_vbmeta_image.c b/avb/libavb/avb_vbmeta_image.c
new file mode 100644
index 0000000..4d885ad
--- /dev/null
+++ b/avb/libavb/avb_vbmeta_image.c
@@ -0,0 +1,554 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "avb_vbmeta_image.h"
+#include "avb_rsa.h"
+#include "avb_sha.h"
+#include "avb_util.h"
+
+/* NOTE: The PKC1-v1.5 padding is a blob of binary DER of ASN.1 and is
+ * obtained from section 5.2.2 of RFC 4880.
+ */
+
+static const uint8_t
+    padding_RSA2048_SHA256[AVB_RSA2048_NUM_BYTES - AVB_SHA256_DIGEST_SIZE] = {
+        0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0x00, 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65,
+        0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20};
+
+static const uint8_t
+    padding_RSA4096_SHA256[AVB_RSA4096_NUM_BYTES - AVB_SHA256_DIGEST_SIZE] = {
+        0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0x00, 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60,
+        0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20};
+
+static const uint8_t
+    padding_RSA8192_SHA256[AVB_RSA8192_NUM_BYTES - AVB_SHA256_DIGEST_SIZE] = {
+        0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0x00, 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65,
+        0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20};
+
+static const uint8_t
+    padding_RSA2048_SHA512[AVB_RSA2048_NUM_BYTES - AVB_SHA512_DIGEST_SIZE] = {
+        0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0x00, 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60,
+        0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00, 0x04, 0x40};
+
+static const uint8_t
+    padding_RSA4096_SHA512[AVB_RSA4096_NUM_BYTES - AVB_SHA512_DIGEST_SIZE] = {
+        0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x30, 0x51, 0x30,
+        0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03,
+        0x05, 0x00, 0x04, 0x40};
+
+static const uint8_t
+    padding_RSA8192_SHA512[AVB_RSA8192_NUM_BYTES - AVB_SHA512_DIGEST_SIZE] = {
+        0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+        0xff, 0xff, 0xff, 0xff, 0x00, 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60,
+        0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00, 0x04, 0x40};
+
+typedef struct {
+  const uint8_t* padding;
+  size_t padding_len;
+  size_t hash_len;
+} AvbAlgorithmData;
+
+static AvbAlgorithmData algorithm_data[_AVB_ALGORITHM_NUM_TYPES] = {
+    /* AVB_ALGORITHM_TYPE_NONE */
+    {.padding = NULL, .padding_len = 0, .hash_len = 0},
+    /* AVB_ALGORITHM_TYPE_SHA256_RSA2048 */
+    {.padding = padding_RSA2048_SHA256,
+     .padding_len = sizeof(padding_RSA2048_SHA256),
+     .hash_len = AVB_SHA256_DIGEST_SIZE},
+    /* AVB_ALGORITHM_TYPE_SHA256_RSA4096 */
+    {.padding = padding_RSA4096_SHA256,
+     .padding_len = sizeof(padding_RSA4096_SHA256),
+     .hash_len = AVB_SHA256_DIGEST_SIZE},
+    /* AVB_ALGORITHM_TYPE_SHA256_RSA8192 */
+    {.padding = padding_RSA8192_SHA256,
+     .padding_len = sizeof(padding_RSA8192_SHA256),
+     .hash_len = AVB_SHA256_DIGEST_SIZE},
+    /* AVB_ALGORITHM_TYPE_SHA512_RSA2048 */
+    {.padding = padding_RSA2048_SHA512,
+     .padding_len = sizeof(padding_RSA2048_SHA512),
+     .hash_len = AVB_SHA512_DIGEST_SIZE},
+    /* AVB_ALGORITHM_TYPE_SHA512_RSA4096 */
+    {.padding = padding_RSA4096_SHA512,
+     .padding_len = sizeof(padding_RSA4096_SHA512),
+     .hash_len = AVB_SHA512_DIGEST_SIZE},
+    /* AVB_ALGORITHM_TYPE_SHA512_RSA8192 */
+    {.padding = padding_RSA8192_SHA512,
+     .padding_len = sizeof(padding_RSA8192_SHA512),
+     .hash_len = AVB_SHA512_DIGEST_SIZE},
+};
+
+AvbVBMetaVerifyResult avb_vbmeta_image_verify(
+    const uint8_t* data, size_t length, const uint8_t** out_public_key_data,
+    size_t* out_public_key_length) {
+  AvbVBMetaVerifyResult ret;
+  AvbVBMetaImageHeader h;
+  uint8_t* computed_hash;
+  AvbAlgorithmData* algorithm;
+  AvbSHA256Ctx sha256_ctx;
+  AvbSHA512Ctx sha512_ctx;
+  const uint8_t* header_block;
+  const uint8_t* authentication_block;
+  const uint8_t* auxiliary_block;
+  int verification_result;
+
+  ret = AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER;
+
+  if (out_public_key_data != NULL) *out_public_key_data = NULL;
+  if (out_public_key_length != NULL) *out_public_key_length = 0;
+
+  /* Ensure magic is correct. */
+  if (avb_safe_memcmp(data, AVB_MAGIC, AVB_MAGIC_LEN) != 0) {
+    avb_error("Magic is incorrect.\n");
+    goto out;
+  }
+
+  /* Before we byteswap, ensure length is long enough. */
+  if (length < sizeof(AvbVBMetaImageHeader)) {
+    avb_error("Length is smaller than header.\n");
+    goto out;
+  }
+  avb_vbmeta_image_header_to_host_byte_order((const AvbVBMetaImageHeader*)data,
+                                             &h);
+
+  /* Ensure we don't attempt to access any fields if the major version
+   * is not supported.
+   */
+  if (h.header_version_major > AVB_MAJOR_VERSION) {
+    avb_error("No support for given major version.\n");
+    goto out;
+  }
+
+  /* Ensure inner block sizes are multiple of 64. */
+  if ((h.authentication_data_block_size & 0x3f) != 0 ||
+      (h.auxiliary_data_block_size & 0x3f) != 0) {
+    avb_error("Block size is not a multiple of 64.\n");
+    goto out;
+  }
+
+  /* Ensure block sizes all add up to at most |length|. */
+  uint64_t block_total = sizeof(AvbVBMetaImageHeader);
+  if (!avb_safe_add_to(&block_total, h.authentication_data_block_size) ||
+      !avb_safe_add_to(&block_total, h.auxiliary_data_block_size)) {
+    avb_error("Overflow while computing size of boot image.\n");
+    goto out;
+  }
+  if (block_total > length) {
+    avb_error("Block sizes add up to more than given length.\n");
+    goto out;
+  }
+
+  uintptr_t data_ptr = (uintptr_t)data;
+  /* Ensure passed in memory doesn't wrap. */
+  if (!avb_safe_add(NULL, (uint64_t)data_ptr, length)) {
+    avb_error("Boot image location and length mismatch.\n");
+    goto out;
+  }
+
+  /* Ensure hash and signature are entirely in the Authentication data block. */
+  uint64_t hash_end;
+  if (!avb_safe_add(&hash_end, h.hash_offset, h.hash_size) ||
+      hash_end > h.authentication_data_block_size) {
+    avb_error("Hash is not entirely in its block.\n");
+    goto out;
+  }
+  uint64_t signature_end;
+  if (!avb_safe_add(&signature_end, h.signature_offset, h.signature_size) ||
+      signature_end > h.authentication_data_block_size) {
+    avb_error("Signature is not entirely in its block.\n");
+    goto out;
+  }
+
+  /* Ensure public key is entirely in the Auxiliary data block. */
+  uint64_t pubkey_end;
+  if (!avb_safe_add(&pubkey_end, h.public_key_offset, h.public_key_size) ||
+      pubkey_end > h.auxiliary_data_block_size) {
+    avb_error("Public key is not entirely in its block.\n");
+    goto out;
+  }
+
+  /* Ensure algorithm field is supported. */
+  if (h.algorithm_type >= _AVB_ALGORITHM_NUM_TYPES) {
+    avb_error("Invalid or unknown algorithm.\n");
+    goto out;
+  }
+  algorithm = &algorithm_data[h.algorithm_type];
+
+  /* Bail early if there's no hash or signature. */
+  if (h.algorithm_type == AVB_ALGORITHM_TYPE_NONE) {
+    ret = AVB_VBMETA_VERIFY_RESULT_OK_NOT_SIGNED;
+    goto out;
+  }
+
+  /* Bail if the embedded hash size doesn't match the chosen algorithm. */
+  if (h.hash_size != algorithm->hash_len) {
+    avb_error("Embedded hash has wrong size.\n");
+    goto out;
+  }
+
+  /* No overflow checks needed from here-on after since all block
+   * sizes and offsets have been verified above.
+   */
+
+  header_block = data;
+  authentication_block = header_block + sizeof(AvbVBMetaImageHeader);
+  auxiliary_block = authentication_block + h.authentication_data_block_size;
+
+  switch (h.algorithm_type) {
+    /* Explicit fall-through: */
+    case AVB_ALGORITHM_TYPE_SHA256_RSA2048:
+    case AVB_ALGORITHM_TYPE_SHA256_RSA4096:
+    case AVB_ALGORITHM_TYPE_SHA256_RSA8192:
+      avb_sha256_init(&sha256_ctx);
+      avb_sha256_update(&sha256_ctx, header_block,
+                        sizeof(AvbVBMetaImageHeader));
+      avb_sha256_update(&sha256_ctx, auxiliary_block,
+                        h.auxiliary_data_block_size);
+      computed_hash = avb_sha256_final(&sha256_ctx);
+      break;
+    /* Explicit fall-through: */
+    case AVB_ALGORITHM_TYPE_SHA512_RSA2048:
+    case AVB_ALGORITHM_TYPE_SHA512_RSA4096:
+    case AVB_ALGORITHM_TYPE_SHA512_RSA8192:
+      avb_sha512_init(&sha512_ctx);
+      avb_sha512_update(&sha512_ctx, header_block,
+                        sizeof(AvbVBMetaImageHeader));
+      avb_sha512_update(&sha512_ctx, auxiliary_block,
+                        h.auxiliary_data_block_size);
+      computed_hash = avb_sha512_final(&sha512_ctx);
+      break;
+    default:
+      avb_error("Unknown algorithm.\n");
+      goto out;
+  }
+
+  if (avb_safe_memcmp(authentication_block + h.hash_offset, computed_hash,
+                      h.hash_size) != 0) {
+    avb_error("Hash does not match!\n");
+    ret = AVB_VBMETA_VERIFY_RESULT_HASH_MISMATCH;
+    goto out;
+  }
+
+  verification_result =
+      avb_rsa_verify(auxiliary_block + h.public_key_offset, h.public_key_size,
+                     authentication_block + h.signature_offset,
+                     h.signature_size, authentication_block + h.hash_offset,
+                     h.hash_size, algorithm->padding, algorithm->padding_len);
+
+  if (verification_result == 0) {
+    ret = AVB_VBMETA_VERIFY_RESULT_SIGNATURE_MISMATCH;
+    goto out;
+  }
+
+  if (out_public_key_data != NULL)
+    *out_public_key_data = auxiliary_block + h.public_key_offset;
+  if (out_public_key_length != NULL) *out_public_key_length = h.public_key_size;
+
+  ret = AVB_VBMETA_VERIFY_RESULT_OK;
+
+out:
+  return ret;
+}
+
+void avb_vbmeta_image_header_to_host_byte_order(const AvbVBMetaImageHeader* src,
+                                                AvbVBMetaImageHeader* dest) {
+  avb_memcpy(dest, src, sizeof(AvbVBMetaImageHeader));
+
+  dest->header_version_major = avb_be32toh(dest->header_version_major);
+  dest->header_version_minor = avb_be32toh(dest->header_version_minor);
+
+  dest->authentication_data_block_size =
+      avb_be64toh(dest->authentication_data_block_size);
+  dest->auxiliary_data_block_size =
+      avb_be64toh(dest->auxiliary_data_block_size);
+
+  dest->algorithm_type = avb_be32toh(dest->algorithm_type);
+
+  dest->hash_offset = avb_be64toh(dest->hash_offset);
+  dest->hash_size = avb_be64toh(dest->hash_size);
+
+  dest->signature_offset = avb_be64toh(dest->signature_offset);
+  dest->signature_size = avb_be64toh(dest->signature_size);
+
+  dest->public_key_offset = avb_be64toh(dest->public_key_offset);
+  dest->public_key_size = avb_be64toh(dest->public_key_size);
+
+  dest->descriptors_offset = avb_be64toh(dest->descriptors_offset);
+  dest->descriptors_size = avb_be64toh(dest->descriptors_size);
+
+  dest->rollback_index = avb_be64toh(dest->rollback_index);
+}
diff --git a/avb/libavb/avb_vbmeta_image.h b/avb/libavb/avb_vbmeta_image.h
new file mode 100644
index 0000000..ad93d6a
--- /dev/null
+++ b/avb/libavb/avb_vbmeta_image.h
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#if !defined(AVB_INSIDE_LIBAVB_H) && !defined(AVB_COMPILATION)
+#error "Never include this file directly, include libavb.h instead."
+#endif
+
+#ifndef AVB_VBMETA_IMAGE_H_
+#define AVB_VBMETA_IMAGE_H_
+
+#include "avb_sysdeps.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "avb_crypto.h"
+#include "avb_descriptor.h"
+
+/* Size of the vbmeta image header. */
+#define AVB_VBMETA_IMAGE_HEADER_SIZE 256
+
+/* Magic for the vbmeta image header. */
+#define AVB_MAGIC "AVB0"
+#define AVB_MAGIC_LEN 4
+
+/* The current MAJOR and MINOR versions used - keep in sync with avbtool. */
+#define AVB_MAJOR_VERSION 1
+#define AVB_MINOR_VERSION 0
+
+/* Binary format for header of the vbmeta image.
+ *
+ * The vbmeta image consists of three blocks:
+ *
+ *  +-----------------------------------------+
+ *  | Header data - fixed size                |
+ *  +-----------------------------------------+
+ *  | Authentication data - variable size     |
+ *  +-----------------------------------------+
+ *  | Auxiliary data - variable size          |
+ *  +-----------------------------------------+
+ *
+ * The "Header data" block is described by this struct and is always
+ * |AVB_VBMETA_IMAGE_HEADER_SIZE| bytes long.
+ *
+ * The "Authentication data" block is |authentication_data_block_size|
+ * bytes long and contains the hash and signature used to authenticate
+ * the vbmeta image. The type of the hash and signature is defined by
+ * the |algorithm_type| field.
+ *
+ * The "Auxiliary data" is |auxiliary_data_block_size| bytes long and
+ * contains the auxiliary data including the public key used to make
+ * the signature and descriptors.
+ *
+ * The public key is at offset |public_key_offset| with size
+ * |public_key_size| in this block. The size of the public key data is
+ * defined by the |algorithm_type| field. The format of the public key
+ * data is described in the |AvbRSAPublicKeyHeader| struct.
+ *
+ * The descriptors starts at |descriptors_offset| from the beginning
+ * of the "Auxiliary Data" block and take up |descriptors_size|
+ * bytes. Each descriptor is stored as a |AvbDescriptor| with tag and
+ * number of bytes following. The number of descriptors can be
+ * determined by walking this data until |descriptors_size| is
+ * exhausted.
+ *
+ * The size of each of the "Authentication data" and "Auxiliary data"
+ * blocks must be divisible by 64. This is to ensure proper alignment.
+ *
+ * Descriptors are free-form blocks stored in a part of the vbmeta
+ * image subject to the same integrity checks as the rest of the
+ * image. See the documentation for |AvbDescriptor| for well-known
+ * descriptors. See avb_descriptor_foreach() for a convenience
+ * function to iterate over descriptors.
+ *
+ * This struct is versioned, see the |header_version_major| and
+ * |header_version_minor| fields. Compatibility is guaranteed only
+ * within the same major version.
+ *
+ * All fields are stored in network byte order when serialized. To
+ * generate a copy with fields swapped to native byte order, use the
+ * function avb_vbmeta_image_header_to_host_byte_order().
+ *
+ * Before reading and/or using any of this data, you MUST verify it
+ * using avb_vbmeta_image_verify() and reject it unless it's signed by
+ * a known good public key.
+ */
+typedef struct AvbVBMetaImageHeader {
+  /*   0: Four bytes equal to "AVB0" (AVB_MAGIC). */
+  uint8_t magic[AVB_MAGIC_LEN];
+  /*   4: The major version of the vbmeta image header. */
+  uint32_t header_version_major;
+  /*   8: The minor version of the vbmeta image header. */
+  uint32_t header_version_minor;
+
+  /*  12: The size of the signature block. */
+  uint64_t authentication_data_block_size;
+  /*  20: The size of the auxiliary data block. */
+  uint64_t auxiliary_data_block_size;
+
+  /*  28: The verification algorithm used, see |AvbAlgorithmType| enum. */
+  uint32_t algorithm_type;
+
+  /*  32: Offset into the "Authentication data" block of hash data. */
+  uint64_t hash_offset;
+  /*  40: Length of the hash data. */
+  uint64_t hash_size;
+
+  /*  48: Offset into the "Authentication data" block of signature data. */
+  uint64_t signature_offset;
+  /*  56: Length of the signature data. */
+  uint64_t signature_size;
+
+  /*  64: Offset into the "Auxiliary data" block of public key data. */
+  uint64_t public_key_offset;
+  /*  72: Length of the public key data. */
+  uint64_t public_key_size;
+
+  /*  80: Offset into the "Auxiliary data" block of descriptor data. */
+  uint64_t descriptors_offset;
+  /*  88: Length of descriptor data. */
+  uint64_t descriptors_size;
+
+  /*  96: The rollback index which can be used to prevent rollback to
+   *  older versions.
+   */
+  uint64_t rollback_index;
+
+  /* 104: Padding to ensure struct is size AVB_VBMETA_IMAGE_HEADER_SIZE
+   * bytes. This must be set to zeroes.
+   */
+  uint8_t reserved[152];
+} AVB_ATTR_PACKED AvbVBMetaImageHeader;
+
+/* Copies |src| to |dest|, byte-swapping fields in the process.
+ *
+ * Make sure you've verified |src| using avb_vbmeta_image_verify()
+ * before accessing the data and/or using this function.
+ */
+void avb_vbmeta_image_header_to_host_byte_order(const AvbVBMetaImageHeader* src,
+                                                AvbVBMetaImageHeader* dest);
+
+/* Return codes used in avb_vbmeta_image_verify().
+ *
+ * AVB_VBMETA_VERIFY_RESULT_OK is returned if the vbmeta image header
+ * is valid, the hash is correct and the signature is correct. Keep in
+ * mind that you still need to check that you know the public key used
+ * to sign the image, see avb_vbmeta_image_verify() for details.
+ *
+ * AVB_VBMETA_VERIFY_RESULT_OK_NOT_SIGNED is returned if the vbmeta
+ * image header is valid but there is no signature or hash.
+ *
+ * AVB_VERIFY_INVALID_VBMETA_HEADER is returned if the header of
+ * the vbmeta image is invalid, for example, invalid magic or
+ * inconsistent data.
+ *
+ * AVB_VERIFY_HASH_MISMATCH is returned if the hash stored in the
+ * "Authentication data" block does not match the calculated hash.
+ *
+ * AVB_VERIFY_SIGNATURE_MISMATCH is returned if the signature stored
+ * in the "Authentication data" block is invalid or doesn't match the
+ * public key stored in the vbmeta image.
+ */
+typedef enum {
+  AVB_VBMETA_VERIFY_RESULT_OK,
+  AVB_VBMETA_VERIFY_RESULT_OK_NOT_SIGNED,
+  AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER,
+  AVB_VBMETA_VERIFY_RESULT_HASH_MISMATCH,
+  AVB_VBMETA_VERIFY_RESULT_SIGNATURE_MISMATCH,
+} AvbVBMetaVerifyResult;
+
+/* Checks that vbmeta image at |data| of size |length| is a valid
+ * vbmeta image. The complete contents of the vbmeta image must be
+ * passed in. It's fine if |length| is bigger than the actual image,
+ * typically callers of this function will load the entire contents of
+ * the 'vbmeta_a' or 'vbmeta_b' partition and pass in its length (for
+ * example, 1 MiB).
+ *
+ * See the |AvbVBMetaImageHeader| struct for information about the
+ * three blocks (header, authentication, auxiliary) that make up a
+ * vbmeta image.
+ *
+ * If the function returns |AVB_VBMETA_VERIFY_RESULT_OK| and
+ * |out_public_key_data| is non-NULL, it will be set to point inside
+ * |data| for where the serialized public key data is stored and
+ * |out_public_key_length|, if non-NULL, will be set to the length of
+ * the public key data.
+ *
+ * See the |AvbVBMetaVerifyResult| enum for possible return values.
+ *
+ * VERY IMPORTANT:
+ *
+ *   1. Even if |AVB_VBMETA_VERIFY_RESULT_OK| is returned, you still
+ *      need to check that the public key embedded in the image
+ *      matches a known key! You can use 'avbtool extract_public_key'
+ *      to extract the key (at build time, then store it along your
+ *      code) and compare it to what is returned in
+ *      |out_public_key_data|.
+ *
+ *   2. You need to check the |rollback_index| field against a stored
+ *      value in NVRAM and reject the vbmeta image if the value in
+ *      NVRAM is bigger than |rollback_index|. You must also update
+ *      the value stored in NVRAM to the smallest value of
+ *      |rollback_index| field from boot images in all bootable and
+ *      authentic slots marked as GOOD.
+ *
+ * This is a low-level function to only verify the vbmeta data - you
+ * are likely looking for avb_slot_verify() instead for verifying
+ * integrity data for a whole set of partitions.
+ */
+AvbVBMetaVerifyResult avb_vbmeta_image_verify(
+    const uint8_t* data, size_t length, const uint8_t** out_public_key_data,
+    size_t* out_public_key_length) AVB_ATTR_WARN_UNUSED_RESULT;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* AVB_VBMETA_IMAGE_H_ */
diff --git a/avb/libavb/libavb.h b/avb/libavb/libavb.h
new file mode 100644
index 0000000..c6ba982
--- /dev/null
+++ b/avb/libavb/libavb.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LIBAVB_H_
+#define LIBAVB_H_
+
+/* The AVB_INSIDE_LIBAVB_H preprocessor symbol is used to enforce
+ * library users to include only this file. All public interfaces, and
+ * only public interfaces, must be included here.
+ */
+
+#define AVB_INSIDE_LIBAVB_H
+#include "avb_chain_partition_descriptor.h"
+#include "avb_crypto.h"
+#include "avb_descriptor.h"
+#include "avb_footer.h"
+#include "avb_hash_descriptor.h"
+#include "avb_hashtree_descriptor.h"
+#include "avb_kernel_cmdline_descriptor.h"
+#include "avb_ops.h"
+#include "avb_property_descriptor.h"
+#include "avb_slot_verify.h"
+#include "avb_sysdeps.h"
+#include "avb_util.h"
+#include "avb_vbmeta_image.h"
+#undef AVB_INSIDE_LIBAVB_H
+
+#endif /* LIBAVB_H_ */
diff --git a/avb/test/avb_slot_verify_unittest.cc b/avb/test/avb_slot_verify_unittest.cc
new file mode 100644
index 0000000..0ffc95f
--- /dev/null
+++ b/avb/test/avb_slot_verify_unittest.cc
@@ -0,0 +1,866 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <iostream>
+
+#include <endian.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <string.h>
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <base/files/file_util.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+
+#include "avb_unittest_util.h"
+#include "libavb.h"
+
+struct MyAvbOps;
+typedef struct MyAvbOps MyAvbOps;
+
+class MyOps {
+ public:
+  MyOps();
+  ~MyOps();
+
+  AvbOps* avb_ops() { return (AvbOps*)avb_ops_; }
+
+  void set_partition_dir(const base::FilePath& partition_dir) {
+    partition_dir_ = partition_dir;
+  }
+
+  void set_expected_public_key(const std::string& expected_public_key) {
+    expected_public_key_ = expected_public_key;
+  }
+
+  void set_stored_rollback_indexes(
+      const std::vector<uint64_t>& stored_rollback_indexes) {
+    stored_rollback_indexes_ = stored_rollback_indexes;
+  }
+
+  void set_stored_is_device_unlocked(bool stored_is_device_unlocked) {
+    stored_is_device_unlocked_ = stored_is_device_unlocked;
+  }
+
+  AvbIOResult read_from_partition(const char* partition, int64_t offset,
+                                  size_t num_bytes, void* buffer,
+                                  size_t* out_num_read) {
+    base::FilePath path =
+        partition_dir_.Append(std::string(partition)).AddExtension("img");
+
+    if (offset < 0) {
+      int64_t file_size;
+      if (!base::GetFileSize(path, &file_size)) {
+        fprintf(stderr, "Error getting size of file '%s'\n",
+                path.value().c_str());
+        return AVB_IO_RESULT_ERROR_IO;
+      }
+      offset = file_size - (-offset);
+    }
+
+    int fd = open(path.value().c_str(), O_RDONLY);
+    if (fd < 0) {
+      fprintf(stderr, "Error opening file '%s': %s\n", path.value().c_str(),
+              strerror(errno));
+      return AVB_IO_RESULT_ERROR_IO;
+    }
+    if (lseek(fd, offset, SEEK_SET) != offset) {
+      fprintf(stderr, "Error seeking to pos %zd in file %s: %s\n", offset,
+              path.value().c_str(), strerror(errno));
+      close(fd);
+      return AVB_IO_RESULT_ERROR_IO;
+    }
+    ssize_t num_read = read(fd, buffer, num_bytes);
+    if (num_read < 0) {
+      fprintf(stderr,
+              "Error reading %zd bytes from pos %" PRId64 " in file %s: %s\n",
+              num_bytes, offset, path.value().c_str(), strerror(errno));
+      close(fd);
+      return AVB_IO_RESULT_ERROR_IO;
+    }
+    close(fd);
+
+    if (out_num_read != NULL) {
+      *out_num_read = num_read;
+    }
+
+    return AVB_IO_RESULT_OK;
+  }
+
+  AvbIOResult write_to_partition(const char* partition, int64_t offset,
+                                 size_t num_bytes, const void* buffer) {
+    base::FilePath path =
+        partition_dir_.Append(std::string(partition)).AddExtension("img");
+
+    if (offset < 0) {
+      int64_t file_size;
+      if (!base::GetFileSize(path, &file_size)) {
+        fprintf(stderr, "Error getting size of file '%s'\n",
+                path.value().c_str());
+        return AVB_IO_RESULT_ERROR_IO;
+      }
+      offset = file_size - (-offset);
+    }
+
+    int fd = open(path.value().c_str(), O_WRONLY);
+    if (fd < 0) {
+      fprintf(stderr, "Error opening file '%s': %s\n", path.value().c_str(),
+              strerror(errno));
+      return AVB_IO_RESULT_ERROR_IO;
+    }
+    if (lseek(fd, offset, SEEK_SET) != offset) {
+      fprintf(stderr, "Error seeking to pos %zd in file %s: %s\n", offset,
+              path.value().c_str(), strerror(errno));
+      close(fd);
+      return AVB_IO_RESULT_ERROR_IO;
+    }
+    ssize_t num_written = write(fd, buffer, num_bytes);
+    if (num_written < 0) {
+      fprintf(stderr,
+              "Error writing %zd bytes at pos %" PRId64 " in file %s: %s\n",
+              num_bytes, offset, path.value().c_str(), strerror(errno));
+      close(fd);
+      return AVB_IO_RESULT_ERROR_IO;
+    }
+    close(fd);
+
+    return AVB_IO_RESULT_OK;
+  }
+
+  int validate_vbmeta_public_key(AvbOps* ops, const uint8_t* public_key_data,
+                                 size_t public_key_length) {
+    if (public_key_length != expected_public_key_.size()) return 0;
+
+    return memcmp(expected_public_key_.c_str(), public_key_data,
+                  public_key_length) == 0;
+  }
+
+  bool read_rollback_index(AvbOps* ops, size_t rollback_index_slot,
+                           uint64_t* out_rollback_index) {
+    if (rollback_index_slot >= stored_rollback_indexes_.size()) {
+      fprintf(stderr, "No rollback index for slot %zd (has %zd slots).\n",
+              rollback_index_slot, stored_rollback_indexes_.size());
+      return false;
+    }
+    *out_rollback_index = stored_rollback_indexes_[rollback_index_slot];
+    return true;
+  }
+
+  bool write_rollback_index(AvbOps* ops, size_t rollback_index_slot,
+                            uint64_t rollback_index) {
+    fprintf(stderr, "write_rollback_index not yet implemented.\n");
+    return false;
+  }
+
+  bool read_is_device_unlocked(AvbOps* ops, bool* out_is_device_unlocked) {
+    *out_is_device_unlocked = stored_is_device_unlocked_ ? 1 : 0;
+    return true;
+  }
+
+  bool get_unique_guid_for_partition(AvbOps* ops, const char* partition,
+                                     char* guid_buf, size_t guid_buf_size) {
+    // This is faking it a bit but makes testing easy. It works
+    // because avb_slot_verify.c doesn't check that the returned GUID
+    // is wellformed.
+    snprintf(guid_buf, guid_buf_size, "1234-fake-guid-for:%s", partition);
+    return true;
+  }
+
+  MyAvbOps* avb_ops_;
+
+  base::FilePath partition_dir_;
+
+  std::string expected_public_key_;
+
+  std::vector<uint64_t> stored_rollback_indexes_;
+
+  bool stored_is_device_unlocked_;
+};
+
+struct MyAvbOps {
+  AvbOps parent;
+  MyOps* my_ops;
+};
+
+static AvbIOResult my_ops_read_from_partition(AvbOps* ops,
+                                              const char* partition,
+                                              int64_t offset, size_t num_bytes,
+                                              void* buffer,
+                                              size_t* out_num_read) {
+  return ((MyAvbOps*)ops)
+      ->my_ops->read_from_partition(partition, offset, num_bytes, buffer,
+                                    out_num_read);
+}
+
+static AvbIOResult my_ops_write_to_partition(AvbOps* ops, const char* partition,
+                                             int64_t offset, size_t num_bytes,
+                                             const void* buffer) {
+  return ((MyAvbOps*)ops)
+      ->my_ops->write_to_partition(partition, offset, num_bytes, buffer);
+}
+
+static bool my_ops_validate_vbmeta_public_key(AvbOps* ops,
+                                              const uint8_t* public_key_data,
+                                              size_t public_key_length) {
+  return ((MyAvbOps*)ops)
+      ->my_ops->validate_vbmeta_public_key(ops, public_key_data,
+                                           public_key_length);
+}
+
+static bool my_ops_read_rollback_index(AvbOps* ops, size_t rollback_index_slot,
+                                       uint64_t* out_rollback_index) {
+  return ((MyAvbOps*)ops)
+      ->my_ops->read_rollback_index(ops, rollback_index_slot,
+                                    out_rollback_index);
+}
+
+static bool my_ops_write_rollback_index(AvbOps* ops, size_t rollback_index_slot,
+                                        uint64_t rollback_index) {
+  return ((MyAvbOps*)ops)
+      ->my_ops->write_rollback_index(ops, rollback_index_slot, rollback_index);
+}
+
+static bool my_ops_read_is_device_unlocked(AvbOps* ops,
+                                           bool* out_is_device_unlocked) {
+  return ((MyAvbOps*)ops)
+      ->my_ops->read_is_device_unlocked(ops, out_is_device_unlocked);
+}
+
+static bool my_ops_get_unique_guid_for_partition(AvbOps* ops,
+                                                 const char* partition,
+                                                 char* guid_buf,
+                                                 size_t guid_buf_size) {
+  return ((MyAvbOps*)ops)
+      ->my_ops->get_unique_guid_for_partition(ops, partition, guid_buf,
+                                              guid_buf_size);
+}
+
+MyOps::MyOps() {
+  avb_ops_ = new MyAvbOps;
+  avb_ops_->parent.read_from_partition = my_ops_read_from_partition;
+  avb_ops_->parent.write_to_partition = my_ops_write_to_partition;
+  avb_ops_->parent.validate_vbmeta_public_key =
+      my_ops_validate_vbmeta_public_key;
+  avb_ops_->parent.read_rollback_index = my_ops_read_rollback_index;
+  avb_ops_->parent.write_rollback_index = my_ops_write_rollback_index;
+  avb_ops_->parent.read_is_device_unlocked = my_ops_read_is_device_unlocked;
+  avb_ops_->parent.get_unique_guid_for_partition =
+      my_ops_get_unique_guid_for_partition;
+  avb_ops_->my_ops = this;
+}
+
+MyOps::~MyOps() { delete avb_ops_; }
+
+class AvbSlotVerifyTest : public BaseAvbToolTest {
+ public:
+  AvbSlotVerifyTest() {}
+
+  virtual void SetUp() override {
+    BaseAvbToolTest::SetUp();
+    ops_.set_partition_dir(testdir_);
+    ops_.set_stored_rollback_indexes({0, 0, 0, 0});
+    ops_.set_stored_is_device_unlocked(false);
+  }
+
+  base::FilePath GenerateImage(const std::string file_name, size_t image_size) {
+    // Generate a 1025 KiB file with known content.
+    std::vector<uint8_t> image;
+    image.resize(image_size);
+    for (size_t n = 0; n < image_size; n++) {
+      image[n] = uint8_t(n);
+    }
+    base::FilePath image_path = testdir_.Append(file_name);
+    EXPECT_EQ(image_size,
+              static_cast<const size_t>(base::WriteFile(
+                  image_path, reinterpret_cast<const char*>(image.data()),
+                  image.size())));
+    return image_path;
+  }
+
+  MyOps ops_;
+};
+
+TEST_F(AvbSlotVerifyTest, Basic) {
+  GenerateVBMetaImage("vbmeta_a.img", "SHA256_RSA2048", 0,
+                      base::FilePath("test/data/testkey_rsa2048.pem"));
+
+  ops_.set_expected_public_key(
+      PublicKeyAVB(base::FilePath("test/data/testkey_rsa2048.pem")));
+
+  AvbSlotVerifyData* slot_data = NULL;
+  EXPECT_EQ(AVB_SLOT_VERIFY_RESULT_OK,
+            avb_slot_verify(ops_.avb_ops(), "_a", &slot_data));
+  EXPECT_NE(nullptr, slot_data);
+  EXPECT_EQ(
+      "androidboot.slot_suffix=_a androidboot.vbmeta.device_state=locked "
+      "androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=1408 "
+      "androidboot.vbmeta.digest=22cda7342f5ba915f41662975f96f081",
+      std::string(slot_data->cmdline));
+  avb_slot_verify_data_free(slot_data);
+}
+
+TEST_F(AvbSlotVerifyTest, BasicSha512) {
+  GenerateVBMetaImage("vbmeta_a.img", "SHA512_RSA2048", 0,
+                      base::FilePath("test/data/testkey_rsa2048.pem"));
+
+  ops_.set_expected_public_key(
+      PublicKeyAVB(base::FilePath("test/data/testkey_rsa2048.pem")));
+
+  AvbSlotVerifyData* slot_data = NULL;
+  EXPECT_EQ(AVB_SLOT_VERIFY_RESULT_OK,
+            avb_slot_verify(ops_.avb_ops(), "_a", &slot_data));
+  EXPECT_NE(nullptr, slot_data);
+  EXPECT_EQ(
+      "androidboot.slot_suffix=_a androidboot.vbmeta.device_state=locked "
+      "androidboot.vbmeta.hash_alg=sha512 androidboot.vbmeta.size=1472 "
+      "androidboot.vbmeta.digest="
+      "125592a19a266efe6683de1afee53e2585ccfcf33adb5d6485e6fbfeabccf571",
+      std::string(slot_data->cmdline));
+  avb_slot_verify_data_free(slot_data);
+}
+
+TEST_F(AvbSlotVerifyTest, BasicUnlocked) {
+  GenerateVBMetaImage("vbmeta_a.img", "SHA256_RSA2048", 0,
+                      base::FilePath("test/data/testkey_rsa2048.pem"));
+
+  ops_.set_expected_public_key(
+      PublicKeyAVB(base::FilePath("test/data/testkey_rsa2048.pem")));
+
+  ops_.set_stored_is_device_unlocked(true);
+
+  AvbSlotVerifyData* slot_data = NULL;
+  EXPECT_EQ(AVB_SLOT_VERIFY_RESULT_OK,
+            avb_slot_verify(ops_.avb_ops(), "_a", &slot_data));
+  EXPECT_NE(nullptr, slot_data);
+  EXPECT_EQ(
+      "androidboot.slot_suffix=_a androidboot.vbmeta.device_state=unlocked "
+      "androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=1408 "
+      "androidboot.vbmeta.digest=22cda7342f5ba915f41662975f96f081",
+      std::string(slot_data->cmdline));
+  avb_slot_verify_data_free(slot_data);
+}
+
+TEST_F(AvbSlotVerifyTest, SlotDataIsCorrect) {
+  GenerateVBMetaImage("vbmeta_a.img", "SHA256_RSA2048", 0,
+                      base::FilePath("test/data/testkey_rsa2048.pem"));
+
+  ops_.set_expected_public_key(
+      PublicKeyAVB(base::FilePath("test/data/testkey_rsa2048.pem")));
+
+  EXPECT_EQ(AVB_SLOT_VERIFY_RESULT_OK,
+            avb_slot_verify(ops_.avb_ops(), "_a", NULL));
+}
+
+TEST_F(AvbSlotVerifyTest, WrongPublicKey) {
+  GenerateVBMetaImage("vbmeta_a.img", "SHA256_RSA2048", 0,
+                      base::FilePath("test/data/testkey_rsa2048.pem"));
+
+  EXPECT_EQ(AVB_SLOT_VERIFY_RESULT_ERROR_PUBLIC_KEY_REJECTED,
+            avb_slot_verify(ops_.avb_ops(), "_a", NULL));
+}
+
+TEST_F(AvbSlotVerifyTest, NoImage) {
+  EXPECT_EQ(AVB_SLOT_VERIFY_RESULT_ERROR_IO,
+            avb_slot_verify(ops_.avb_ops(), "_a", NULL));
+}
+
+TEST_F(AvbSlotVerifyTest, UnsignedVBMeta) {
+  GenerateVBMetaImage("vbmeta_a.img", "", 0, base::FilePath(""));
+
+  EXPECT_EQ(AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION,
+            avb_slot_verify(ops_.avb_ops(), "_a", NULL));
+}
+
+TEST_F(AvbSlotVerifyTest, CorruptedImage) {
+  GenerateVBMetaImage("vbmeta_a.img", "SHA256_RSA2048", 0,
+                      base::FilePath("test/data/testkey_rsa2048.pem"));
+
+  // Corrupt four bytes of data in the end of the image. Since the aux
+  // data is at the end and this data is signed, this will change the
+  // value of the computed hash.
+  uint8_t corrupt_data[4] = {0xff, 0xff, 0xff, 0xff};
+  EXPECT_EQ(AVB_IO_RESULT_OK, ops_.avb_ops()->write_to_partition(
+                                  ops_.avb_ops(), "vbmeta_a",
+                                  -sizeof corrupt_data,  // offset from end
+                                  sizeof corrupt_data, corrupt_data));
+
+  EXPECT_EQ(AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION,
+            avb_slot_verify(ops_.avb_ops(), "_a", NULL));
+}
+
+TEST_F(AvbSlotVerifyTest, RollbackIndex) {
+  GenerateVBMetaImage("vbmeta_a.img", "SHA256_RSA2048", 42,
+                      base::FilePath("test/data/testkey_rsa2048.pem"));
+
+  ops_.set_expected_public_key(
+      PublicKeyAVB(base::FilePath("test/data/testkey_rsa2048.pem")));
+
+  // First try with 42 as the stored rollback index - this should
+  // succeed since the image rollback index is 42 (as set above).
+  ops_.set_stored_rollback_indexes({42});
+  EXPECT_EQ(AVB_SLOT_VERIFY_RESULT_OK,
+            avb_slot_verify(ops_.avb_ops(), "_a", NULL));
+
+  // Then try with 43 for the stored rollback index - this should fail
+  // because the image has rollback index 42 which is less than 43.
+  ops_.set_stored_rollback_indexes({43});
+  EXPECT_EQ(AVB_SLOT_VERIFY_RESULT_ERROR_ROLLBACK_INDEX,
+            avb_slot_verify(ops_.avb_ops(), "_a", NULL));
+}
+
+TEST_F(AvbSlotVerifyTest, HashDescriptorInVBMeta) {
+  const size_t boot_partition_size = 16 * 1024 * 1024;
+  const size_t boot_image_size = 5 * 1024 * 1024;
+  base::FilePath boot_path = GenerateImage("boot_a.img", boot_image_size);
+
+  EXPECT_COMMAND(
+      0,
+      "./avbtool add_hash_footer"
+      " --image %s"
+      " --rollback_index 0"
+      " --partition_name boot"
+      " --partition_size %zd"
+      " --kernel_cmdline 'cmdline in hash footer $(ANDROID_SYSTEM_PARTUUID)'"
+      " --salt deadbeef",
+      boot_path.value().c_str(), boot_partition_size);
+
+  GenerateVBMetaImage(
+      "vbmeta_a.img", "SHA256_RSA2048", 4,
+      base::FilePath("test/data/testkey_rsa2048.pem"),
+      base::StringPrintf(
+          "--include_descriptors_from_image %s"
+          " --kernel_cmdline 'cmdline in vbmeta $(ANDROID_BOOT_PARTUUID)'",
+          boot_path.value().c_str()));
+
+  EXPECT_EQ(
+      "VBMeta image version:     1.0\n"
+      "Header Block:             256 bytes\n"
+      "Authentication Block:     576 bytes\n"
+      "Auxiliary Block:          768 bytes\n"
+      "Algorithm:                SHA256_RSA2048\n"
+      "Rollback Index:           4\n"
+      "Descriptors:\n"
+      "    Kernel Cmdline descriptor:\n"
+      "      Kernel Cmdline:        'cmdline in vbmeta "
+      "$(ANDROID_BOOT_PARTUUID)'\n"
+      "    Hash descriptor:\n"
+      "      Image Size:            5242880 bytes\n"
+      "      Hash Algorithm:        sha256\n"
+      "      Partition Name:        boot\n"
+      "      Salt:                  deadbeef\n"
+      "      Digest:                "
+      "184cb36243adb8b87d2d8c4802de32125fe294ec46753d732144ee65df68a23d\n"
+      "    Kernel Cmdline descriptor:\n"
+      "      Kernel Cmdline:        'cmdline in hash footer "
+      "$(ANDROID_SYSTEM_PARTUUID)'\n",
+      InfoImage(vbmeta_image_path_));
+
+  EXPECT_COMMAND(0,
+                 "./avbtool erase_footer"
+                 " --image %s",
+                 boot_path.value().c_str());
+
+  // With no footer, 'avbtool info_image' should fail (exit status 1).
+  EXPECT_COMMAND(1, "./avbtool info_image --image %s",
+                 boot_path.value().c_str());
+
+  ops_.set_expected_public_key(
+      PublicKeyAVB(base::FilePath("test/data/testkey_rsa2048.pem")));
+
+  AvbSlotVerifyData* slot_data = NULL;
+  EXPECT_EQ(AVB_SLOT_VERIFY_RESULT_OK,
+            avb_slot_verify(ops_.avb_ops(), "_a", &slot_data));
+  EXPECT_NE(nullptr, slot_data);
+
+  // Now verify the slot data. The vbmeta data should match our
+  // vbmeta_image_ member.
+  EXPECT_EQ(slot_data->vbmeta_size, vbmeta_image_.size());
+  EXPECT_EQ(0, memcmp(vbmeta_image_.data(), slot_data->vbmeta_data,
+                      slot_data->vbmeta_size));
+
+  // The boot image data should match what is generated above with
+  // GenerateImage().
+  EXPECT_EQ(boot_image_size, slot_data->boot_size);
+  for (size_t n = 0; n < slot_data->boot_size; n++) {
+    EXPECT_EQ(slot_data->boot_data[n], uint8_t(n));
+  }
+
+  // This should match the two cmdlines with a space (U+0020) between
+  // them and the $(ANDROID_SYSTEM_PARTUUID) and
+  // $(ANDROID_BOOT_PARTUUID) variables replaced.
+  EXPECT_EQ(
+      "cmdline in vbmeta 1234-fake-guid-for:boot_a "
+      "cmdline in hash footer 1234-fake-guid-for:system_a "
+      "androidboot.slot_suffix=_a "
+      "androidboot.vbmeta.device_state=locked "
+      "androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=1600 "
+      "androidboot.vbmeta.digest=844308149e43d5db7b14cd5747def40a",
+      std::string(slot_data->cmdline));
+  EXPECT_EQ(4UL, slot_data->rollback_indexes[0]);
+  for (size_t n = 1; n < AVB_MAX_NUMBER_OF_ROLLBACK_INDEX_SLOTS; n++) {
+    EXPECT_EQ(0UL, slot_data->rollback_indexes[n]);
+  }
+  avb_slot_verify_data_free(slot_data);
+}
+
+TEST_F(AvbSlotVerifyTest, HashDescriptorInVBMetaCorruptBoot) {
+  size_t boot_partition_size = 16 * 1024 * 1024;
+  base::FilePath boot_path = GenerateImage("boot_a.img", 5 * 1024 * 1024);
+
+  EXPECT_COMMAND(0,
+                 "./avbtool add_hash_footer"
+                 " --image %s"
+                 " --rollback_index 0"
+                 " --partition_name boot"
+                 " --partition_size %zd"
+                 " --salt deadbeef",
+                 boot_path.value().c_str(), boot_partition_size);
+
+  GenerateVBMetaImage("vbmeta_a.img", "SHA256_RSA2048", 0,
+                      base::FilePath("test/data/testkey_rsa2048.pem"),
+                      base::StringPrintf("--include_descriptors_from_image %s",
+                                         boot_path.value().c_str()));
+
+  EXPECT_COMMAND(0,
+                 "./avbtool erase_footer"
+                 " --image %s",
+                 boot_path.value().c_str());
+
+  ops_.set_expected_public_key(
+      PublicKeyAVB(base::FilePath("test/data/testkey_rsa2048.pem")));
+
+  // So far, so good.
+  EXPECT_EQ(AVB_SLOT_VERIFY_RESULT_OK,
+            avb_slot_verify(ops_.avb_ops(), "_a", NULL));
+
+  // Now corrupt boot_a.img and expect verification error.
+  uint8_t corrupt_data[4] = {0xff, 0xff, 0xff, 0xff};
+  EXPECT_EQ(AVB_IO_RESULT_OK, ops_.avb_ops()->write_to_partition(
+                                  ops_.avb_ops(), "boot_a",
+                                  1024 * 1024,  // offset: 1 MiB
+                                  sizeof corrupt_data, corrupt_data));
+
+  EXPECT_EQ(AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION,
+            avb_slot_verify(ops_.avb_ops(), "_a", NULL));
+}
+
+TEST_F(AvbSlotVerifyTest, HashDescriptorInChainedPartition) {
+  size_t boot_partition_size = 16 * 1024 * 1024;
+  const size_t boot_image_size = 5 * 1024 * 1024;
+  base::FilePath boot_path = GenerateImage("boot_a.img", boot_image_size);
+
+  EXPECT_COMMAND(0,
+                 "./avbtool add_hash_footer"
+                 " --image %s"
+                 " --kernel_cmdline 'cmdline2 in hash footer'"
+                 " --rollback_index 12"
+                 " --partition_name boot"
+                 " --partition_size %zd"
+                 " --algorithm SHA256_RSA4096"
+                 " --key test/data/testkey_rsa4096.pem"
+                 " --salt deadbeef",
+                 boot_path.value().c_str(), boot_partition_size);
+
+  base::FilePath pk_path = testdir_.Append("testkey_rsa4096.avbpubkey");
+  EXPECT_COMMAND(
+      0,
+      "./avbtool extract_public_key --key test/data/testkey_rsa4096.pem"
+      " --output %s",
+      pk_path.value().c_str());
+
+  GenerateVBMetaImage(
+      "vbmeta_a.img", "SHA256_RSA2048", 11,
+      base::FilePath("test/data/testkey_rsa2048.pem"),
+      base::StringPrintf("--chain_partition boot:1:%s"
+                         " --kernel_cmdline 'cmdline2 in vbmeta'",
+                         pk_path.value().c_str()));
+
+  EXPECT_EQ(
+      "VBMeta image version:     1.0\n"
+      "Header Block:             256 bytes\n"
+      "Authentication Block:     576 bytes\n"
+      "Auxiliary Block:          1664 bytes\n"
+      "Algorithm:                SHA256_RSA2048\n"
+      "Rollback Index:           11\n"
+      "Descriptors:\n"
+      "    Chain Partition descriptor:\n"
+      "      Partition Name:        boot\n"
+      "      Rollback Index Slot:   1\n"
+      "      Public key (sha1):     2597c218aae470a130f61162feaae70afd97f011\n"
+      "    Kernel Cmdline descriptor:\n"
+      "      Kernel Cmdline:        'cmdline2 in vbmeta'\n",
+      InfoImage(vbmeta_image_path_));
+
+  ops_.set_expected_public_key(
+      PublicKeyAVB(base::FilePath("test/data/testkey_rsa2048.pem")));
+
+  AvbSlotVerifyData* slot_data = NULL;
+  EXPECT_EQ(AVB_SLOT_VERIFY_RESULT_OK,
+            avb_slot_verify(ops_.avb_ops(), "_a", &slot_data));
+  EXPECT_NE(nullptr, slot_data);
+
+  // Now verify the slot data. The vbmeta data should match our
+  // vbmeta_image_ member.
+  EXPECT_EQ(slot_data->vbmeta_size, vbmeta_image_.size());
+  EXPECT_EQ(0, memcmp(vbmeta_image_.data(), slot_data->vbmeta_data,
+                      slot_data->vbmeta_size));
+
+  // The boot image data should match what is generated above with
+  // GenerateImage().
+  EXPECT_EQ(boot_image_size, slot_data->boot_size);
+  for (size_t n = 0; n < slot_data->boot_size; n++) {
+    EXPECT_EQ(slot_data->boot_data[n], uint8_t(n));
+  }
+
+  // This should match the two cmdlines with a space (U+0020) between them.
+  EXPECT_EQ(
+      "cmdline2 in hash footer cmdline2 in vbmeta "
+      "androidboot.slot_suffix=_a "
+      "androidboot.vbmeta.device_state=locked "
+      "androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=2496 "
+      "androidboot.vbmeta.digest=3924a4e4cdf9a4e6e77b0d87e6e9b464",
+      std::string(slot_data->cmdline));
+  EXPECT_EQ(11UL, slot_data->rollback_indexes[0]);
+  EXPECT_EQ(12UL, slot_data->rollback_indexes[1]);
+  for (size_t n = 2; n < AVB_MAX_NUMBER_OF_ROLLBACK_INDEX_SLOTS; n++) {
+    EXPECT_EQ(0UL, slot_data->rollback_indexes[n]);
+  }
+  avb_slot_verify_data_free(slot_data);
+}
+
+TEST_F(AvbSlotVerifyTest, HashDescriptorInChainedPartitionCorruptBoot) {
+  size_t boot_partition_size = 16 * 1024 * 1024;
+  base::FilePath boot_path = GenerateImage("boot_a.img", 5 * 1024 * 1024);
+
+  EXPECT_COMMAND(0,
+                 "./avbtool add_hash_footer"
+                 " --image %s"
+                 " --rollback_index 0"
+                 " --partition_name boot"
+                 " --partition_size %zd"
+                 " --algorithm SHA256_RSA4096"
+                 " --key test/data/testkey_rsa4096.pem"
+                 " --salt deadbeef",
+                 boot_path.value().c_str(), boot_partition_size);
+
+  base::FilePath pk_path = testdir_.Append("testkey_rsa4096.avbpubkey");
+  EXPECT_COMMAND(
+      0,
+      "./avbtool extract_public_key --key test/data/testkey_rsa4096.pem"
+      " --output %s",
+      pk_path.value().c_str());
+
+  GenerateVBMetaImage("vbmeta_a.img", "SHA256_RSA2048", 0,
+                      base::FilePath("test/data/testkey_rsa2048.pem"),
+                      base::StringPrintf("--chain_partition boot:1:%s",
+                                         pk_path.value().c_str()));
+
+  ops_.set_expected_public_key(
+      PublicKeyAVB(base::FilePath("test/data/testkey_rsa2048.pem")));
+
+  EXPECT_EQ(AVB_SLOT_VERIFY_RESULT_OK,
+            avb_slot_verify(ops_.avb_ops(), "_a", NULL));
+
+  // Now corrupt boot_a.img and expect verification error.
+  uint8_t corrupt_data[4] = {0xff, 0xff, 0xff, 0xff};
+  EXPECT_EQ(AVB_IO_RESULT_OK, ops_.avb_ops()->write_to_partition(
+                                  ops_.avb_ops(), "boot_a",
+                                  1024 * 1024,  // offset: 1 MiB
+                                  sizeof corrupt_data, corrupt_data));
+
+  EXPECT_EQ(AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION,
+            avb_slot_verify(ops_.avb_ops(), "_a", NULL));
+}
+
+TEST_F(AvbSlotVerifyTest, HashDescriptorInChainedPartitionKeyMismatch) {
+  size_t boot_partition_size = 16 * 1024 * 1024;
+  base::FilePath boot_path = GenerateImage("boot_a.img", 5 * 1024 * 1024);
+
+  // Use different key to sign vbmeta in boot_a (we use the 8192 bit
+  // key) than what's in the chained partition descriptor (which is
+  // the 4096 bit key) and expect
+  // AVB_SLOT_VERIFY_RESULT_ERROR_PUBLIC_KEY_REJECTED.
+
+  EXPECT_COMMAND(0,
+                 "./avbtool add_hash_footer"
+                 " --image %s"
+                 " --rollback_index 0"
+                 " --partition_name boot"
+                 " --partition_size %zd"
+                 " --algorithm SHA256_RSA8192"
+                 " --key test/data/testkey_rsa8192.pem"
+                 " --salt deadbeef",
+                 boot_path.value().c_str(), boot_partition_size);
+
+  base::FilePath pk_path = testdir_.Append("testkey_rsa4096.avbpubkey");
+  EXPECT_COMMAND(
+      0,
+      "./avbtool extract_public_key --key test/data/testkey_rsa4096.pem"
+      " --output %s",
+      pk_path.value().c_str());
+
+  GenerateVBMetaImage("vbmeta_a.img", "SHA256_RSA2048", 0,
+                      base::FilePath("test/data/testkey_rsa2048.pem"),
+                      base::StringPrintf("--chain_partition boot:1:%s",
+                                         pk_path.value().c_str()));
+
+  ops_.set_expected_public_key(
+      PublicKeyAVB(base::FilePath("test/data/testkey_rsa2048.pem")));
+
+  EXPECT_EQ(AVB_SLOT_VERIFY_RESULT_ERROR_PUBLIC_KEY_REJECTED,
+            avb_slot_verify(ops_.avb_ops(), "_a", NULL));
+}
+
+TEST_F(AvbSlotVerifyTest, HashDescriptorInChainedPartitionRollbackIndexFail) {
+  size_t boot_partition_size = 16 * 1024 * 1024;
+  base::FilePath boot_path = GenerateImage("boot_a.img", 5 * 1024 * 1024);
+
+  EXPECT_COMMAND(0,
+                 "./avbtool add_hash_footer"
+                 " --image %s"
+                 " --rollback_index 10"
+                 " --partition_name boot"
+                 " --partition_size %zd"
+                 " --algorithm SHA256_RSA4096"
+                 " --key test/data/testkey_rsa4096.pem"
+                 " --salt deadbeef",
+                 boot_path.value().c_str(), boot_partition_size);
+
+  base::FilePath pk_path = testdir_.Append("testkey_rsa4096.avbpubkey");
+  EXPECT_COMMAND(
+      0,
+      "./avbtool extract_public_key --key test/data/testkey_rsa4096.pem"
+      " --output %s",
+      pk_path.value().c_str());
+
+  GenerateVBMetaImage("vbmeta_a.img", "SHA256_RSA2048", 110,
+                      base::FilePath("test/data/testkey_rsa2048.pem"),
+                      base::StringPrintf("--chain_partition boot:1:%s",
+                                         pk_path.value().c_str()));
+
+  ops_.set_expected_public_key(
+      PublicKeyAVB(base::FilePath("test/data/testkey_rsa2048.pem")));
+
+  // Both images (vbmeta_a and boot_a) have rollback index 10 and 11
+  // so it should work if the stored rollback indexes are 0 and 0.
+  ops_.set_stored_rollback_indexes({0, 0});
+  EXPECT_EQ(AVB_SLOT_VERIFY_RESULT_OK,
+            avb_slot_verify(ops_.avb_ops(), "_a", NULL));
+
+  // Check failure if we set the stored rollback index of the chained
+  // partition to 20 (see AvbSlotVerifyTest.RollbackIndex above
+  // where we test rollback index checks for the vbmeta partition).
+  ops_.set_stored_rollback_indexes({0, 20});
+  EXPECT_EQ(AVB_SLOT_VERIFY_RESULT_ERROR_ROLLBACK_INDEX,
+            avb_slot_verify(ops_.avb_ops(), "_a", NULL));
+
+  // Check failure if there is no rollback index slot 1 - in that case
+  // we expect an I/O error since ops->read_rollback_index() will
+  // fail.
+  ops_.set_stored_rollback_indexes({0});
+  EXPECT_EQ(AVB_SLOT_VERIFY_RESULT_ERROR_IO,
+            avb_slot_verify(ops_.avb_ops(), "_a", NULL));
+}
+
+TEST_F(AvbSlotVerifyTest, ChainedPartitionNoSlots) {
+  size_t boot_partition_size = 16 * 1024 * 1024;
+  const size_t boot_image_size = 5 * 1024 * 1024;
+  base::FilePath boot_path = GenerateImage("boot.img", boot_image_size);
+
+  EXPECT_COMMAND(0,
+                 "./avbtool add_hash_footer"
+                 " --image %s"
+                 " --kernel_cmdline 'cmdline2 in hash footer'"
+                 " --rollback_index 12"
+                 " --partition_name boot"
+                 " --partition_size %zd"
+                 " --algorithm SHA256_RSA4096"
+                 " --key test/data/testkey_rsa4096.pem"
+                 " --salt deadbeef",
+                 boot_path.value().c_str(), boot_partition_size);
+
+  base::FilePath pk_path = testdir_.Append("testkey_rsa4096.avbpubkey");
+  EXPECT_COMMAND(
+      0,
+      "./avbtool extract_public_key --key test/data/testkey_rsa4096.pem"
+      " --output %s",
+      pk_path.value().c_str());
+
+  GenerateVBMetaImage(
+      "vbmeta.img", "SHA256_RSA2048", 11,
+      base::FilePath("test/data/testkey_rsa2048.pem"),
+      base::StringPrintf("--chain_partition boot:1:%s"
+                         " --kernel_cmdline 'cmdline2 in vbmeta'",
+                         pk_path.value().c_str()));
+
+  EXPECT_EQ(
+      "VBMeta image version:     1.0\n"
+      "Header Block:             256 bytes\n"
+      "Authentication Block:     576 bytes\n"
+      "Auxiliary Block:          1664 bytes\n"
+      "Algorithm:                SHA256_RSA2048\n"
+      "Rollback Index:           11\n"
+      "Descriptors:\n"
+      "    Chain Partition descriptor:\n"
+      "      Partition Name:        boot\n"
+      "      Rollback Index Slot:   1\n"
+      "      Public key (sha1):     2597c218aae470a130f61162feaae70afd97f011\n"
+      "    Kernel Cmdline descriptor:\n"
+      "      Kernel Cmdline:        'cmdline2 in vbmeta'\n",
+      InfoImage(vbmeta_image_path_));
+
+  ops_.set_expected_public_key(
+      PublicKeyAVB(base::FilePath("test/data/testkey_rsa2048.pem")));
+
+  AvbSlotVerifyData* slot_data = NULL;
+  EXPECT_EQ(AVB_SLOT_VERIFY_RESULT_OK,
+            avb_slot_verify(ops_.avb_ops(), "", &slot_data));
+  EXPECT_NE(nullptr, slot_data);
+
+  // Now verify the slot data. The vbmeta data should match our
+  // vbmeta_image_ member.
+  EXPECT_EQ(slot_data->vbmeta_size, vbmeta_image_.size());
+  EXPECT_EQ(0, memcmp(vbmeta_image_.data(), slot_data->vbmeta_data,
+                      slot_data->vbmeta_size));
+
+  // The boot image data should match what is generated above with
+  // GenerateImage().
+  EXPECT_EQ(boot_image_size, slot_data->boot_size);
+  for (size_t n = 0; n < slot_data->boot_size; n++) {
+    EXPECT_EQ(slot_data->boot_data[n], uint8_t(n));
+  }
+
+  // This should match the two cmdlines with a space (U+0020) between
+  // them - note that androidboot.slot_suffix is not set since we
+  // don't have any slots in this setup.
+  EXPECT_EQ(
+      "cmdline2 in hash footer cmdline2 in vbmeta "
+      "androidboot.vbmeta.device_state=locked "
+      "androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=2496 "
+      "androidboot.vbmeta.digest=3924a4e4cdf9a4e6e77b0d87e6e9b464",
+      std::string(slot_data->cmdline));
+  EXPECT_EQ(11UL, slot_data->rollback_indexes[0]);
+  EXPECT_EQ(12UL, slot_data->rollback_indexes[1]);
+  for (size_t n = 2; n < AVB_MAX_NUMBER_OF_ROLLBACK_INDEX_SLOTS; n++) {
+    EXPECT_EQ(0UL, slot_data->rollback_indexes[n]);
+  }
+  avb_slot_verify_data_free(slot_data);
+}
diff --git a/avb/test/avb_unittest_util.h b/avb/test/avb_unittest_util.h
new file mode 100644
index 0000000..df6b658
--- /dev/null
+++ b/avb/test/avb_unittest_util.h
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AVB_UNITTEST_UTIL_H_
+#define AVB_UNITTEST_UTIL_H_
+
+#include <inttypes.h>
+
+#include <gtest/gtest.h>
+
+#include <base/files/file_util.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+
+/* Utility macro to run the command expressed by the printf()-style string
+ * |command_format| using the system(3) utility function. Will assert unless
+ * the command exits normally with exit status |expected_exit_status|.
+ */
+#define EXPECT_COMMAND(expected_exit_status, command_format, ...)          \
+  do {                                                                     \
+    int rc =                                                               \
+        system(base::StringPrintf(command_format, ##__VA_ARGS__).c_str()); \
+    EXPECT_TRUE(WIFEXITED(rc));                                            \
+    EXPECT_EQ(WEXITSTATUS(rc), expected_exit_status);                      \
+  } while (0);
+
+/* Base-class used for unit test. */
+class BaseAvbToolTest : public ::testing::Test {
+ public:
+  BaseAvbToolTest() {}
+
+ protected:
+  virtual ~BaseAvbToolTest() {}
+
+  /* Generates a Brillo vbmeta image, using avbtoool, with file name
+   * |image_name|. The generated vbmeta image will written to disk,
+   * see the |vbmeta_image_path_| variable for its path and
+   * |vbmeta_image_| for the content.
+   */
+  void GenerateVBMetaImage(const std::string& image_name,
+                           const std::string& algorithm,
+                           uint64_t rollback_index,
+                           const base::FilePath& key_path,
+                           const std::string& additional_options = "") {
+    std::string signing_options;
+    if (algorithm == "") {
+      signing_options = " --algorithm NONE ";
+    } else {
+      signing_options = std::string(" --algorithm ") + algorithm + " --key " +
+                        key_path.value() + " ";
+    }
+    vbmeta_image_path_ = testdir_.Append(image_name);
+    EXPECT_COMMAND(0,
+                   "./avbtool make_vbmeta_image"
+                   " --rollback_index %" PRIu64
+                   " %s %s "
+                   " --output %s",
+                   rollback_index, additional_options.c_str(),
+                   signing_options.c_str(), vbmeta_image_path_.value().c_str());
+    int64_t file_size;
+    ASSERT_TRUE(base::GetFileSize(vbmeta_image_path_, &file_size));
+    vbmeta_image_.resize(file_size);
+    ASSERT_TRUE(base::ReadFile(vbmeta_image_path_,
+                               reinterpret_cast<char*>(vbmeta_image_.data()),
+                               vbmeta_image_.size()));
+  }
+
+  /* Returns the output of 'avbtool info_image' for a given image. */
+  std::string InfoImage(const base::FilePath& image_path) {
+    base::FilePath tmp_path = testdir_.Append("info_output.txt");
+    EXPECT_COMMAND(0, "./avbtool info_image --image %s --output %s",
+                   image_path.value().c_str(), tmp_path.value().c_str());
+    std::string info_data;
+    EXPECT_TRUE(base::ReadFileToString(tmp_path, &info_data));
+    return info_data;
+  }
+
+  /* Returns public key in AVB format for a .pem key */
+  std::string PublicKeyAVB(const base::FilePath& key_path) {
+    base::FilePath tmp_path = testdir_.Append("public_key.bin");
+    EXPECT_COMMAND(0,
+                   "./avbtool extract_public_key --key %s"
+                   " --output %s",
+                   key_path.value().c_str(), tmp_path.value().c_str());
+    std::string key_data;
+    EXPECT_TRUE(base::ReadFileToString(tmp_path, &key_data));
+    return key_data;
+  }
+
+  /* Create temporary directory to stash images in. */
+  virtual void SetUp() override {
+    base::FilePath ret;
+    char* buf = strdup("/tmp/libavb-tests.XXXXXX");
+    ASSERT_TRUE(mkdtemp(buf) != nullptr);
+    testdir_ = base::FilePath(buf);
+    free(buf);
+  }
+
+  /* Nuke temporary directory. */
+  virtual void TearDown() override {
+    ASSERT_EQ(0U, testdir_.value().find("/tmp/libavb-tests"));
+    ASSERT_TRUE(base::DeleteFile(testdir_, true /* recursive */));
+  }
+
+  /* Temporary directory created in SetUp(). */
+  base::FilePath testdir_;
+
+  /* Path to vbmeta image generated with GenerateVBMetaImage(). */
+  base::FilePath vbmeta_image_path_;
+
+  /* Contents of the image generated with GenerateVBMetaImage(). */
+  std::vector<uint8_t> vbmeta_image_;
+};
+
+#endif /* AVB_UNITTEST_UTIL_H_ */
diff --git a/avb/test/avb_util_unittest.cc b/avb/test/avb_util_unittest.cc
new file mode 100644
index 0000000..02c485e
--- /dev/null
+++ b/avb/test/avb_util_unittest.cc
@@ -0,0 +1,433 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <string.h>
+
+#include <gtest/gtest.h>
+
+#include "libavb.h"
+
+TEST(UtilTest, RSAPublicKeyHeaderByteswap) {
+  AvbRSAPublicKeyHeader h;
+  AvbRSAPublicKeyHeader s;
+  uint32_t n32;
+  uint64_t n64;
+
+  n32 = 0x11223344;
+  n64 = 0x1122334455667788;
+
+  h.key_num_bits = htobe32(n32);
+  n32++;
+  h.n0inv = htobe32(n32);
+  n32++;
+
+  EXPECT_NE(0, avb_rsa_public_key_header_validate_and_byteswap(&h, &s));
+
+  n32 = 0x11223344;
+  n64 = 0x1122334455667788;
+
+  EXPECT_EQ(n32, s.key_num_bits);
+  n32++;
+  EXPECT_EQ(n32, s.n0inv);
+  n32++;
+}
+
+TEST(UtilTest, FooterByteswap) {
+  AvbFooter h;
+  AvbFooter s;
+  AvbFooter other;
+  AvbFooter bad;
+  uint64_t n64;
+
+  n64 = 0x1122334455667788;
+
+  memcpy(h.magic, AVB_FOOTER_MAGIC, AVB_FOOTER_MAGIC_LEN);
+  h.version_major = htobe32(AVB_FOOTER_MAJOR_VERSION);
+  h.version_minor = htobe32(AVB_FOOTER_MINOR_VERSION);
+  h.original_image_size = htobe64(n64);
+  n64++;
+  h.vbmeta_offset = htobe64(n64);
+  n64++;
+  h.vbmeta_size = htobe64(n64);
+  n64++;
+
+  EXPECT_NE(0, avb_footer_validate_and_byteswap(&h, &s));
+
+  n64 = 0x1122334455667788;
+
+  EXPECT_EQ((uint32_t)AVB_FOOTER_MAJOR_VERSION, s.version_major);
+  EXPECT_EQ((uint32_t)AVB_FOOTER_MINOR_VERSION, s.version_minor);
+  EXPECT_EQ(n64, s.original_image_size);
+  n64++;
+  EXPECT_EQ(n64, s.vbmeta_offset);
+  n64++;
+  EXPECT_EQ(n64, s.vbmeta_size);
+  n64++;
+
+  // Check that the struct still validates if minor is bigger than
+  // what we expect.
+  other = h;
+  h.version_minor = htobe32(AVB_FOOTER_MINOR_VERSION + 1);
+  EXPECT_NE(0, avb_footer_validate_and_byteswap(&other, &s));
+
+  // Check for bad magic.
+  bad = h;
+  bad.magic[0] = 'x';
+  EXPECT_EQ(0, avb_footer_validate_and_byteswap(&bad, &s));
+
+  // Check for bad major version.
+  bad = h;
+  bad.version_major = htobe32(AVB_FOOTER_MAJOR_VERSION + 1);
+  EXPECT_EQ(0, avb_footer_validate_and_byteswap(&bad, &s));
+}
+
+TEST(UtilTest, KernelCmdlineDescriptorByteswap) {
+  AvbKernelCmdlineDescriptor h;
+  AvbKernelCmdlineDescriptor s;
+  AvbKernelCmdlineDescriptor bad;
+  uint64_t nbf;
+
+  // Specify 44 bytes of data past the end of the descriptor struct.
+  nbf = 44 + sizeof(AvbKernelCmdlineDescriptor) - sizeof(AvbDescriptor);
+  h.parent_descriptor.num_bytes_following = htobe64(nbf);
+  h.parent_descriptor.tag = htobe64(AVB_DESCRIPTOR_TAG_KERNEL_CMDLINE);
+  h.kernel_cmdline_length = htobe32(44);
+
+  EXPECT_NE(0, avb_kernel_cmdline_descriptor_validate_and_byteswap(&h, &s));
+
+  EXPECT_EQ(AVB_DESCRIPTOR_TAG_KERNEL_CMDLINE, s.parent_descriptor.tag);
+  EXPECT_EQ(nbf, s.parent_descriptor.num_bytes_following);
+  EXPECT_EQ(44UL, s.kernel_cmdline_length);
+
+  // Check for bad tag.
+  bad = h;
+  bad.parent_descriptor.tag = htobe64(0xf00dd00d);
+  EXPECT_EQ(0, avb_kernel_cmdline_descriptor_validate_and_byteswap(&bad, &s));
+
+  // Doesn't fit in 45 bytes.
+  bad = h;
+  bad.kernel_cmdline_length = htobe32(45);
+  EXPECT_EQ(0, avb_kernel_cmdline_descriptor_validate_and_byteswap(&bad, &s));
+}
+
+TEST(UtilTest, HashtreeDescriptorByteswap) {
+  AvbHashtreeDescriptor h;
+  AvbHashtreeDescriptor s;
+  AvbHashtreeDescriptor bad;
+  uint64_t nbf;
+  uint32_t n32;
+  uint64_t n64;
+
+  // Specify 40 bytes of data past the end of the descriptor struct.
+  nbf = 40 + sizeof(AvbHashtreeDescriptor) - sizeof(AvbDescriptor);
+  h.parent_descriptor.num_bytes_following = htobe64(nbf);
+  h.parent_descriptor.tag = htobe64(AVB_DESCRIPTOR_TAG_HASHTREE);
+  h.partition_name_len = htobe32(10);
+  h.salt_len = htobe32(10);
+  h.root_digest_len = htobe32(10);
+
+  n32 = 0x11223344;
+  n64 = 0x1122334455667788;
+
+  h.dm_verity_version = htobe32(n32);
+  n32++;
+  h.image_size = htobe64(n64);
+  n64++;
+  h.tree_offset = htobe64(n64);
+  n64++;
+  h.tree_size = htobe64(n64);
+  n64++;
+  h.data_block_size = htobe32(n32);
+  n32++;
+  h.hash_block_size = htobe32(n32);
+  n32++;
+
+  EXPECT_NE(0, avb_hashtree_descriptor_validate_and_byteswap(&h, &s));
+
+  n32 = 0x11223344;
+  n64 = 0x1122334455667788;
+
+  EXPECT_EQ(n32, s.dm_verity_version);
+  n32++;
+  EXPECT_EQ(n64, s.image_size);
+  n64++;
+  EXPECT_EQ(n64, s.tree_offset);
+  n64++;
+  EXPECT_EQ(n64, s.tree_size);
+  n64++;
+  EXPECT_EQ(n32, s.data_block_size);
+  n32++;
+  EXPECT_EQ(n32, s.hash_block_size);
+  n32++;
+
+  EXPECT_EQ(AVB_DESCRIPTOR_TAG_HASHTREE, s.parent_descriptor.tag);
+  EXPECT_EQ(nbf, s.parent_descriptor.num_bytes_following);
+  EXPECT_EQ(10UL, s.partition_name_len);
+  EXPECT_EQ(10UL, s.salt_len);
+  EXPECT_EQ(10UL, s.root_digest_len);
+
+  // Check for bad tag.
+  bad = h;
+  bad.parent_descriptor.tag = htobe64(0xf00dd00d);
+  EXPECT_EQ(0, avb_hashtree_descriptor_validate_and_byteswap(&bad, &s));
+
+  // Doesn't fit in 40 bytes (30 + 10 + 10 = 50).
+  bad = h;
+  bad.partition_name_len = htobe32(30);
+  EXPECT_EQ(0, avb_hashtree_descriptor_validate_and_byteswap(&bad, &s));
+
+  // Doesn't fit in 40 bytes (10 + 30 + 10 = 50).
+  bad = h;
+  bad.salt_len = htobe32(30);
+  EXPECT_EQ(0, avb_hashtree_descriptor_validate_and_byteswap(&bad, &s));
+
+  // Doesn't fit in 40 bytes (10 + 10 + 30 = 50).
+  bad = h;
+  bad.root_digest_len = htobe32(30);
+  EXPECT_EQ(0, avb_hashtree_descriptor_validate_and_byteswap(&bad, &s));
+}
+
+TEST(UtilTest, HashDescriptorByteswap) {
+  AvbHashDescriptor h;
+  AvbHashDescriptor s;
+  AvbHashDescriptor bad;
+  uint64_t nbf;
+
+  // Specify 44 bytes of data past the end of the descriptor struct.
+  nbf = 44 + sizeof(AvbHashDescriptor) - sizeof(AvbDescriptor);
+  h.parent_descriptor.num_bytes_following = htobe64(nbf);
+  h.parent_descriptor.tag = htobe64(AVB_DESCRIPTOR_TAG_HASH);
+  h.partition_name_len = htobe32(10);
+  h.salt_len = htobe32(10);
+  h.digest_len = htobe32(10);
+
+  EXPECT_NE(0, avb_hash_descriptor_validate_and_byteswap(&h, &s));
+
+  EXPECT_EQ(AVB_DESCRIPTOR_TAG_HASH, s.parent_descriptor.tag);
+  EXPECT_EQ(nbf, s.parent_descriptor.num_bytes_following);
+  EXPECT_EQ(10UL, s.partition_name_len);
+  EXPECT_EQ(10UL, s.salt_len);
+  EXPECT_EQ(10UL, s.digest_len);
+
+  // Check for bad tag.
+  bad = h;
+  bad.parent_descriptor.tag = htobe64(0xf00dd00d);
+  EXPECT_EQ(0, avb_hash_descriptor_validate_and_byteswap(&bad, &s));
+
+  // Doesn't fit in 44 bytes (30 + 10 + 10 = 50).
+  bad = h;
+  bad.partition_name_len = htobe32(30);
+  EXPECT_EQ(0, avb_hash_descriptor_validate_and_byteswap(&bad, &s));
+
+  // Doesn't fit in 44 bytes (10 + 30 + 10 = 50).
+  bad = h;
+  bad.salt_len = htobe32(30);
+  EXPECT_EQ(0, avb_hash_descriptor_validate_and_byteswap(&bad, &s));
+
+  // Doesn't fit in 44 bytes (10 + 10 + 30 = 50).
+  bad = h;
+  bad.digest_len = htobe32(30);
+  EXPECT_EQ(0, avb_hash_descriptor_validate_and_byteswap(&bad, &s));
+}
+
+TEST(UtilTest, ChainPartitionDescriptorByteswap) {
+  AvbChainPartitionDescriptor h;
+  AvbChainPartitionDescriptor s;
+  AvbChainPartitionDescriptor bad;
+  uint64_t nbf;
+
+  // Specify 36 bytes of data past the end of the descriptor struct.
+  nbf = 36 + sizeof(AvbChainPartitionDescriptor) - sizeof(AvbDescriptor);
+  h.parent_descriptor.num_bytes_following = htobe64(nbf);
+  h.parent_descriptor.tag = htobe64(AVB_DESCRIPTOR_TAG_CHAIN_PARTITION);
+  h.rollback_index_slot = htobe32(42);
+  h.partition_name_len = htobe32(16);
+  h.public_key_len = htobe32(17);
+
+  EXPECT_NE(0, avb_chain_partition_descriptor_validate_and_byteswap(&h, &s));
+
+  EXPECT_EQ(AVB_DESCRIPTOR_TAG_CHAIN_PARTITION, s.parent_descriptor.tag);
+  EXPECT_EQ(nbf, s.parent_descriptor.num_bytes_following);
+  EXPECT_EQ(42UL, s.rollback_index_slot);
+  EXPECT_EQ(16UL, s.partition_name_len);
+  EXPECT_EQ(17UL, s.public_key_len);
+
+  // Check for bad tag.
+  bad = h;
+  bad.parent_descriptor.tag = htobe64(0xf00dd00d);
+  EXPECT_EQ(0, avb_chain_partition_descriptor_validate_and_byteswap(&bad, &s));
+
+  // Check for bad rollback index slot (must be at least 1).
+  bad = h;
+  bad.rollback_index_slot = htobe32(0);
+  EXPECT_EQ(0, avb_chain_partition_descriptor_validate_and_byteswap(&bad, &s));
+
+  // Doesn't fit in 40 bytes (24 + 17 = 41).
+  bad = h;
+  bad.partition_name_len = htobe32(24);
+  EXPECT_EQ(0, avb_chain_partition_descriptor_validate_and_byteswap(&bad, &s));
+
+  // Doesn't fit in 40 bytes (16 + 25 = 41).
+  bad = h;
+  bad.public_key_len = htobe32(25);
+  EXPECT_EQ(0, avb_chain_partition_descriptor_validate_and_byteswap(&bad, &s));
+}
+
+TEST(UtilTest, PropertyDescriptorByteswap) {
+  AvbPropertyDescriptor h;
+  AvbPropertyDescriptor s;
+  AvbPropertyDescriptor bad;
+  uint64_t nbf;
+
+  // Specify 40 bytes of data past the end of the descriptor struct.
+  nbf = 40 + sizeof(AvbPropertyDescriptor) - sizeof(AvbDescriptor);
+  h.parent_descriptor.num_bytes_following = htobe64(nbf);
+  h.parent_descriptor.tag = htobe64(AVB_DESCRIPTOR_TAG_PROPERTY);
+  h.key_num_bytes = htobe64(16);
+  h.value_num_bytes = htobe64(17);
+
+  EXPECT_NE(0, avb_property_descriptor_validate_and_byteswap(&h, &s));
+
+  EXPECT_EQ(AVB_DESCRIPTOR_TAG_PROPERTY, s.parent_descriptor.tag);
+  EXPECT_EQ(nbf, s.parent_descriptor.num_bytes_following);
+  EXPECT_EQ(16UL, s.key_num_bytes);
+  EXPECT_EQ(17UL, s.value_num_bytes);
+
+  // Check for bad tag.
+  bad = h;
+  bad.parent_descriptor.tag = htobe64(0xf00dd00d);
+  EXPECT_EQ(0, avb_property_descriptor_validate_and_byteswap(&bad, &s));
+
+  // Doesn't fit in 40 bytes (22 + 17 + 2 = 41).
+  bad = h;
+  bad.key_num_bytes = htobe64(22);
+  EXPECT_EQ(0, avb_property_descriptor_validate_and_byteswap(&bad, &s));
+
+  // Doesn't fit in 40 bytes (16 + 23 + 2 = 41).
+  bad = h;
+  bad.value_num_bytes = htobe64(23);
+  EXPECT_EQ(0, avb_property_descriptor_validate_and_byteswap(&bad, &s));
+}
+
+TEST(UtilTest, DescriptorByteswap) {
+  AvbDescriptor h;
+  AvbDescriptor s;
+  uint64_t n64;
+
+  n64 = 0x1122334455667788;
+
+  h.num_bytes_following = htobe64(n64);
+  n64++;
+  h.tag = htobe64(n64);
+  n64++;
+
+  EXPECT_NE(0, avb_descriptor_validate_and_byteswap(&h, &s));
+
+  n64 = 0x1122334455667788;
+
+  EXPECT_EQ(n64, s.num_bytes_following);
+  n64++;
+  EXPECT_EQ(n64, s.tag);
+  n64++;
+
+  // Check that we catch if |num_bytes_following| isn't divisble by 8.
+  h.num_bytes_following = htobe64(7);
+  EXPECT_EQ(0, avb_descriptor_validate_and_byteswap(&h, &s));
+}
+
+TEST(UtilTest, SafeAddition) {
+  uint64_t value;
+  uint64_t pow2_60 = 1ULL << 60;
+
+  value = 2;
+  EXPECT_NE(0, avb_safe_add_to(&value, 5));
+  EXPECT_EQ(7UL, value);
+
+  /* These should not overflow */
+  value = 1 * pow2_60;
+  EXPECT_NE(0, avb_safe_add_to(&value, 2 * pow2_60));
+  EXPECT_EQ(3 * pow2_60, value);
+  value = 7 * pow2_60;
+  EXPECT_NE(0, avb_safe_add_to(&value, 8 * pow2_60));
+  EXPECT_EQ(15 * pow2_60, value);
+  value = 9 * pow2_60;
+  EXPECT_NE(0, avb_safe_add_to(&value, 3 * pow2_60));
+  EXPECT_EQ(12 * pow2_60, value);
+  value = 0xfffffffffffffffcUL;
+  EXPECT_NE(0, avb_safe_add_to(&value, 2));
+  EXPECT_EQ(0xfffffffffffffffeUL, value);
+
+  /* These should overflow. */
+  value = 8 * pow2_60;
+  EXPECT_EQ(0, avb_safe_add_to(&value, 8 * pow2_60));
+  value = 0xfffffffffffffffcUL;
+  EXPECT_EQ(0, avb_safe_add_to(&value, 4));
+}
+
+static int avb_validate_utf8z(const char* data) {
+  return avb_validate_utf8(reinterpret_cast<const uint8_t*>(data),
+                           strlen(data));
+}
+
+TEST(UtilTest, UTF8Validation) {
+  // These should succeed.
+  EXPECT_NE(0, avb_validate_utf8z("foo bar"));
+  // Encoding of U+00E6 LATIN SMALL LETTER AE: æ
+  EXPECT_NE(0, avb_validate_utf8z("foo \xC3\xA6 bar"));
+  // Encoding of U+20AC EURO SIGN: €
+  EXPECT_NE(0, avb_validate_utf8z("foo \xE2\x82\xAC bar"));
+  // Encoding of U+1F466 BOY: 👦
+  EXPECT_NE(0, avb_validate_utf8z("foo \xF0\x9F\x91\xA6 bar"));
+  // All three runes following each other.
+  EXPECT_NE(0, avb_validate_utf8z("\xC3\xA6\xE2\x82\xAC\xF0\x9F\x91\xA6"));
+
+  // These should fail.
+  EXPECT_EQ(0, avb_validate_utf8z("foo \xF8 bar"));
+  EXPECT_EQ(0, avb_validate_utf8z("\xF8"));
+  // Stops in the middle of Unicode rune.
+  EXPECT_EQ(0, avb_validate_utf8z("foo \xC3"));
+}
+
+TEST(UtilTest, StrConcat) {
+  char buf[8];
+
+  // These should succeed.
+  EXPECT_NE(0, avb_str_concat(buf, sizeof buf, "foo", 3, "bar1", 4));
+
+  // This should fail: Insufficient space.
+  EXPECT_EQ(0, avb_str_concat(buf, sizeof buf, "foo0", 4, "bar1", 4));
+}
+
+TEST(UtilTest, StrStr) {
+  const char* haystack = "abc def abcabc";
+
+  EXPECT_EQ(nullptr, avb_strstr(haystack, "needle"));
+  EXPECT_EQ(haystack, avb_strstr(haystack, "abc"));
+  EXPECT_EQ(haystack + 4, avb_strstr(haystack, "def"));
+  EXPECT_EQ(haystack, avb_strstr(haystack, haystack));
+}
+
+TEST(UtilTest, StrReplace) {
+  // We don't care about leaking strings from avb_replace().
+  EXPECT_EQ("OK blah bah $(FOO OK blah",
+            std::string(avb_replace("$(FOO) blah bah $(FOO $(FOO) blah",
+                                    "$(FOO)", "OK")));
+  EXPECT_EQ("OK", std::string(avb_replace("$(FOO)", "$(FOO)", "OK")));
+  EXPECT_EQ(" OK", std::string(avb_replace(" $(FOO)", "$(FOO)", "OK")));
+  EXPECT_EQ("OK ", std::string(avb_replace("$(FOO) ", "$(FOO)", "OK")));
+  EXPECT_EQ("LONGSTRINGLONGSTRING",
+            std::string(avb_replace("$(FOO)$(FOO)", "$(FOO)", "LONGSTRING")));
+}
diff --git a/avb/test/avb_vbmeta_image_unittest.cc b/avb/test/avb_vbmeta_image_unittest.cc
new file mode 100644
index 0000000..7d8b80d
--- /dev/null
+++ b/avb/test/avb_vbmeta_image_unittest.cc
@@ -0,0 +1,478 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <iostream>
+
+#include <endian.h>
+#include <inttypes.h>
+#include <string.h>
+
+#include <base/files/file_util.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+
+#include "avb_unittest_util.h"
+#include "libavb.h"
+
+class VerifyTest : public BaseAvbToolTest {
+ public:
+  VerifyTest() {}
+
+ protected:
+  // Helper function for ModificationDetection test. Modifies
+  // boot_image_ in a number of places in the sub-array at |offset| of
+  // size |length| and checks that avb_vbmeta_image_verify() returns
+  // |expected_result|.
+  bool test_modification(AvbVBMetaVerifyResult expected_result, size_t offset,
+                         size_t length);
+};
+
+TEST_F(VerifyTest, BootImageStructSize) {
+  EXPECT_EQ(256UL, sizeof(AvbVBMetaImageHeader));
+}
+
+TEST_F(VerifyTest, CheckSHA256RSA2048) {
+  GenerateVBMetaImage("vbmeta.img", "SHA256_RSA2048", 0,
+                      base::FilePath("test/data/testkey_rsa2048.pem"));
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_OK,
+            avb_vbmeta_image_verify(vbmeta_image_.data(), vbmeta_image_.size(),
+                                    NULL, NULL));
+}
+
+TEST_F(VerifyTest, CheckSHA256RSA4096) {
+  GenerateVBMetaImage("vbmeta.img", "SHA256_RSA4096", 0,
+                      base::FilePath("test/data/testkey_rsa4096.pem"));
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_OK,
+            avb_vbmeta_image_verify(vbmeta_image_.data(), vbmeta_image_.size(),
+                                    NULL, NULL));
+}
+
+TEST_F(VerifyTest, CheckSHA256RSA8192) {
+  GenerateVBMetaImage("vbmeta.img", "SHA256_RSA8192", 0,
+                      base::FilePath("test/data/testkey_rsa8192.pem"));
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_OK,
+            avb_vbmeta_image_verify(vbmeta_image_.data(), vbmeta_image_.size(),
+                                    NULL, NULL));
+}
+
+TEST_F(VerifyTest, CheckSHA512RSA2048) {
+  GenerateVBMetaImage("vbmeta.img", "SHA512_RSA2048", 0,
+                      base::FilePath("test/data/testkey_rsa2048.pem"));
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_OK,
+            avb_vbmeta_image_verify(vbmeta_image_.data(), vbmeta_image_.size(),
+                                    NULL, NULL));
+}
+
+TEST_F(VerifyTest, CheckSHA512RSA4096) {
+  GenerateVBMetaImage("vbmeta.img", "SHA512_RSA4096", 0,
+                      base::FilePath("test/data/testkey_rsa4096.pem"));
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_OK,
+            avb_vbmeta_image_verify(vbmeta_image_.data(), vbmeta_image_.size(),
+                                    NULL, NULL));
+}
+
+TEST_F(VerifyTest, CheckSHA512RSA8192) {
+  GenerateVBMetaImage("vbmeta.img", "SHA512_RSA8192", 0,
+                      base::FilePath("test/data/testkey_rsa8192.pem"));
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_OK,
+            avb_vbmeta_image_verify(vbmeta_image_.data(), vbmeta_image_.size(),
+                                    NULL, NULL));
+}
+
+TEST_F(VerifyTest, CheckUnsigned) {
+  GenerateVBMetaImage("vbmeta.img", "", 0, base::FilePath(""));
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_OK_NOT_SIGNED,
+            avb_vbmeta_image_verify(vbmeta_image_.data(), vbmeta_image_.size(),
+                                    NULL, NULL));
+}
+
+TEST_F(VerifyTest, CheckBiggerLength) {
+  GenerateVBMetaImage("vbmeta.img", "SHA256_RSA2048", 0,
+                      base::FilePath("test/data/testkey_rsa2048.pem"));
+  // Check that it's OK if we pass a bigger length than what the
+  // header indicates.
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_OK,
+            avb_vbmeta_image_verify(vbmeta_image_.data(),
+                                    vbmeta_image_.size() + 8192, NULL, NULL));
+}
+
+TEST_F(VerifyTest, BadMagic) {
+  GenerateVBMetaImage("vbmeta.img", "SHA256_RSA2048", 0,
+                      base::FilePath("test/data/testkey_rsa2048.pem"));
+  vbmeta_image_[0] = 'Z';
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER,
+            avb_vbmeta_image_verify(vbmeta_image_.data(), vbmeta_image_.size(),
+                                    NULL, NULL));
+}
+
+TEST_F(VerifyTest, MajorVersionCheck) {
+  GenerateVBMetaImage("vbmeta.img", "SHA256_RSA2048", 0,
+                      base::FilePath("test/data/testkey_rsa2048.pem"));
+
+  AvbVBMetaImageHeader *h =
+      reinterpret_cast<AvbVBMetaImageHeader *>(vbmeta_image_.data());
+  h->header_version_major = htobe32(1 + be32toh(h->header_version_major));
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER,
+            avb_vbmeta_image_verify(vbmeta_image_.data(), vbmeta_image_.size(),
+                                    NULL, NULL));
+}
+
+TEST_F(VerifyTest, MinorVersionCheck) {
+  GenerateVBMetaImage("vbmeta.img", "", 0, base::FilePath(""));
+
+  AvbVBMetaImageHeader *h =
+      reinterpret_cast<AvbVBMetaImageHeader *>(vbmeta_image_.data());
+  h->header_version_minor = htobe32(1 + be32toh(h->header_version_minor));
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_OK_NOT_SIGNED,
+            avb_vbmeta_image_verify(vbmeta_image_.data(), vbmeta_image_.size(),
+                                    NULL, NULL));
+}
+
+TEST_F(VerifyTest, BlockSizesAddUpToLessThanLength) {
+  GenerateVBMetaImage("vbmeta.img", "SHA256_RSA2048", 0,
+                      base::FilePath("test/data/testkey_rsa2048.pem"));
+
+  AvbVBMetaImageHeader *h =
+      reinterpret_cast<AvbVBMetaImageHeader *>(vbmeta_image_.data());
+  AvbVBMetaImageHeader backup = *h;
+
+  // Check that the sum of the two block lengths is less than passed
+  // in size. Use a size that's a multiple of 64 to avoid failure on
+  // earlier check.
+  uint64_t size = vbmeta_image_.size() & (~0x3f);
+
+  h->authentication_data_block_size = htobe64(size);
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER,
+            avb_vbmeta_image_verify(vbmeta_image_.data(), vbmeta_image_.size(),
+                                    NULL, NULL));
+  *h = backup;
+
+  h->auxiliary_data_block_size = htobe64(size);
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER,
+            avb_vbmeta_image_verify(vbmeta_image_.data(), vbmeta_image_.size(),
+                                    NULL, NULL));
+  *h = backup;
+
+  // Overflow checks - choose overflow candidate so it's a multiple of
+  // 64 otherwise we'll fail on an earlier check.
+  size = 0xffffffffffffffc0UL;
+
+  h->authentication_data_block_size = htobe64(size);
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER,
+            avb_vbmeta_image_verify(vbmeta_image_.data(), vbmeta_image_.size(),
+                                    NULL, NULL));
+  *h = backup;
+
+  h->auxiliary_data_block_size = htobe64(size);
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER,
+            avb_vbmeta_image_verify(vbmeta_image_.data(), vbmeta_image_.size(),
+                                    NULL, NULL));
+  *h = backup;
+
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_OK,
+            avb_vbmeta_image_verify(vbmeta_image_.data(), vbmeta_image_.size(),
+                                    NULL, NULL));
+}
+
+TEST_F(VerifyTest, BlockSizesMultipleOf64) {
+  GenerateVBMetaImage("vbmeta.img", "SHA256_RSA2048", 0,
+                      base::FilePath("test/data/testkey_rsa2048.pem"));
+
+  AvbVBMetaImageHeader *h =
+      reinterpret_cast<AvbVBMetaImageHeader *>(vbmeta_image_.data());
+  AvbVBMetaImageHeader backup = *h;
+
+  h->authentication_data_block_size =
+      htobe32(be32toh(h->authentication_data_block_size) - 32);
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER,
+            avb_vbmeta_image_verify(vbmeta_image_.data(),
+                                    vbmeta_image_.size() - 32, NULL, NULL));
+  *h = backup;
+
+  h->auxiliary_data_block_size =
+      htobe32(be32toh(h->auxiliary_data_block_size) - 32);
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER,
+            avb_vbmeta_image_verify(vbmeta_image_.data(),
+                                    vbmeta_image_.size() - 32, NULL, NULL));
+  *h = backup;
+
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_OK,
+            avb_vbmeta_image_verify(vbmeta_image_.data(), vbmeta_image_.size(),
+                                    NULL, NULL));
+}
+
+TEST_F(VerifyTest, HashOutOfBounds) {
+  GenerateVBMetaImage("vbmeta.img", "SHA256_RSA2048", 0,
+                      base::FilePath("test/data/testkey_rsa2048.pem"));
+
+  AvbVBMetaImageHeader *h =
+      reinterpret_cast<AvbVBMetaImageHeader *>(vbmeta_image_.data());
+
+  // Check we catch when hash data goes out of bounds.
+  h->hash_offset = htobe64(4);
+  h->hash_size = htobe64(be64toh(h->authentication_data_block_size));
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER,
+            avb_vbmeta_image_verify(vbmeta_image_.data(), vbmeta_image_.size(),
+                                    NULL, NULL));
+
+  // Overflow checks.
+  h->hash_offset = htobe64(4);
+  h->hash_size = htobe64(0xfffffffffffffffeUL);
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER,
+            avb_vbmeta_image_verify(vbmeta_image_.data(), vbmeta_image_.size(),
+                                    NULL, NULL));
+}
+
+TEST_F(VerifyTest, SignatureOutOfBounds) {
+  GenerateVBMetaImage("vbmeta.img", "SHA256_RSA2048", 0,
+                      base::FilePath("test/data/testkey_rsa2048.pem"));
+
+  AvbVBMetaImageHeader *h =
+      reinterpret_cast<AvbVBMetaImageHeader *>(vbmeta_image_.data());
+
+  // Check we catch when signature data goes out of bounds.
+  h->signature_offset = htobe64(4);
+  h->signature_size = htobe64(be64toh(h->authentication_data_block_size));
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER,
+            avb_vbmeta_image_verify(vbmeta_image_.data(), vbmeta_image_.size(),
+                                    NULL, NULL));
+
+  // Overflow checks.
+  h->signature_offset = htobe64(4);
+  h->signature_size = htobe64(0xfffffffffffffffeUL);
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER,
+            avb_vbmeta_image_verify(vbmeta_image_.data(), vbmeta_image_.size(),
+                                    NULL, NULL));
+}
+
+TEST_F(VerifyTest, PublicKeyOutOfBounds) {
+  GenerateVBMetaImage("vbmeta.img", "SHA256_RSA2048", 0,
+                      base::FilePath("test/data/testkey_rsa2048.pem"));
+
+  AvbVBMetaImageHeader *h =
+      reinterpret_cast<AvbVBMetaImageHeader *>(vbmeta_image_.data());
+
+  // Check we catch when public key data goes out of bounds.
+  h->public_key_offset = htobe64(4);
+  h->public_key_size = htobe64(be64toh(h->auxiliary_data_block_size));
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER,
+            avb_vbmeta_image_verify(vbmeta_image_.data(), vbmeta_image_.size(),
+                                    NULL, NULL));
+
+  // Overflow checks.
+  h->public_key_offset = htobe64(4);
+  h->public_key_size = htobe64(0xfffffffffffffffeUL);
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER,
+            avb_vbmeta_image_verify(vbmeta_image_.data(), vbmeta_image_.size(),
+                                    NULL, NULL));
+}
+
+TEST_F(VerifyTest, InvalidAlgorithmField) {
+  GenerateVBMetaImage("vbmeta.img", "SHA256_RSA2048", 0,
+                      base::FilePath("test/data/testkey_rsa2048.pem"));
+
+  AvbVBMetaImageHeader *h =
+      reinterpret_cast<AvbVBMetaImageHeader *>(vbmeta_image_.data());
+  AvbVBMetaImageHeader backup = *h;
+
+  // Check we bail on unknown algorithm.
+  h->algorithm_type = htobe32(_AVB_ALGORITHM_NUM_TYPES);
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER,
+            avb_vbmeta_image_verify(vbmeta_image_.data(), vbmeta_image_.size(),
+                                    NULL, NULL));
+  *h = backup;
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_OK,
+            avb_vbmeta_image_verify(vbmeta_image_.data(), vbmeta_image_.size(),
+                                    NULL, NULL));
+}
+
+TEST_F(VerifyTest, PublicKeyBlockTooSmall) {
+  GenerateVBMetaImage("vbmeta.img", "SHA256_RSA2048", 0,
+                      base::FilePath("test/data/testkey_rsa2048.pem"));
+
+  AvbVBMetaImageHeader *h =
+      reinterpret_cast<AvbVBMetaImageHeader *>(vbmeta_image_.data());
+  AvbVBMetaImageHeader backup = *h;
+
+  // Check we bail if the auxiliary data block is too small.
+  uint64_t change = be64toh(h->auxiliary_data_block_size) - 64;
+  h->auxiliary_data_block_size = htobe64(change);
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER,
+            avb_vbmeta_image_verify(vbmeta_image_.data(),
+                                    vbmeta_image_.size() - change, NULL, NULL));
+  *h = backup;
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_OK,
+            avb_vbmeta_image_verify(vbmeta_image_.data(), vbmeta_image_.size(),
+                                    NULL, NULL));
+}
+
+bool VerifyTest::test_modification(AvbVBMetaVerifyResult expected_result,
+                                   size_t offset, size_t length) {
+  uint8_t *d = reinterpret_cast<uint8_t *>(vbmeta_image_.data());
+  const int kNumCheckpoints = 16;
+
+  // Test |kNumCheckpoints| modifications in the start, middle, and
+  // end of given sub-array.
+  for (int n = 0; n <= kNumCheckpoints; n++) {
+    size_t o = std::min(length * n / kNumCheckpoints, length - 1) + offset;
+    d[o] ^= 0x80;
+    AvbVBMetaVerifyResult result = avb_vbmeta_image_verify(
+        vbmeta_image_.data(), vbmeta_image_.size(), NULL, NULL);
+    d[o] ^= 0x80;
+    if (result != expected_result) return false;
+  }
+
+  return true;
+}
+
+TEST_F(VerifyTest, ModificationDetection) {
+  GenerateVBMetaImage("vbmeta.img", "SHA256_RSA2048", 0,
+                      base::FilePath("test/data/testkey_rsa2048.pem"));
+
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_OK,
+            avb_vbmeta_image_verify(vbmeta_image_.data(), vbmeta_image_.size(),
+                                    NULL, NULL));
+
+  AvbVBMetaImageHeader h;
+  avb_vbmeta_image_header_to_host_byte_order(
+      reinterpret_cast<AvbVBMetaImageHeader *>(vbmeta_image_.data()), &h);
+
+  size_t header_block_offset = 0;
+  size_t authentication_block_offset =
+      header_block_offset + sizeof(AvbVBMetaImageHeader);
+  size_t auxiliary_block_offset =
+      authentication_block_offset + h.authentication_data_block_size;
+
+  // Ensure we detect modification of the header data block. Do this
+  // in a field that's not validated so INVALID_VBMETA_HEADER
+  // isn't returned.
+  EXPECT_TRUE(test_modification(
+      AVB_VBMETA_VERIFY_RESULT_HASH_MISMATCH,
+      offsetof(AvbVBMetaImageHeader, reserved),
+      sizeof(AvbVBMetaImageHeader) - offsetof(AvbVBMetaImageHeader, reserved)));
+  // Also check the |reserved| field.
+  EXPECT_TRUE(test_modification(AVB_VBMETA_VERIFY_RESULT_HASH_MISMATCH,
+                                offsetof(AvbVBMetaImageHeader, reserved),
+                                sizeof(AvbVBMetaImageHeader().reserved)));
+
+  // Ensure we detect modifications in the auxiliary data block.
+  EXPECT_TRUE(test_modification(AVB_VBMETA_VERIFY_RESULT_HASH_MISMATCH,
+                                auxiliary_block_offset,
+                                h.auxiliary_data_block_size));
+
+  // Modifications in the hash part of the Authentication data block
+  // should also yield HASH_MISMATCH. This is because the hash check
+  // compares the calculated hash against the stored hash.
+  EXPECT_TRUE(test_modification(AVB_VBMETA_VERIFY_RESULT_HASH_MISMATCH,
+                                authentication_block_offset + h.hash_offset,
+                                h.hash_size));
+
+  // Modifications in the signature part of the Authentication data
+  // block, should not cause a hash mismatch ... but will cause a
+  // signature mismatch.
+  EXPECT_TRUE(test_modification(
+      AVB_VBMETA_VERIFY_RESULT_SIGNATURE_MISMATCH,
+      authentication_block_offset + h.signature_offset, h.signature_size));
+
+  // Mofications outside the hash and signature parts of the
+  // Authentication data block are not detected. This is because it's
+  // not part of the hash calculation.
+  uint64_t offset = h.signature_offset + h.signature_size;
+  ASSERT_LT(h.hash_offset, h.signature_offset);
+  ASSERT_LT(offset + 1, h.authentication_data_block_size);
+  EXPECT_TRUE(test_modification(AVB_VBMETA_VERIFY_RESULT_OK,
+                                authentication_block_offset + offset,
+                                h.authentication_data_block_size - offset));
+}
+
+TEST_F(VerifyTest, VBMetaHeaderByteswap) {
+  AvbVBMetaImageHeader h;
+  AvbVBMetaImageHeader s;
+  uint32_t n32;
+  uint64_t n64;
+
+  n32 = 0x11223344;
+  n64 = 0x1122334455667788;
+
+  h.header_version_major = htobe32(n32);
+  n32++;
+  h.header_version_minor = htobe32(n32);
+  n32++;
+  h.authentication_data_block_size = htobe64(n64);
+  n64++;
+  h.auxiliary_data_block_size = htobe64(n64);
+  n64++;
+  h.algorithm_type = htobe32(n32);
+  n32++;
+  h.hash_offset = htobe64(n64);
+  n64++;
+  h.hash_size = htobe64(n64);
+  n64++;
+  h.signature_offset = htobe64(n64);
+  n64++;
+  h.signature_size = htobe64(n64);
+  n64++;
+  h.public_key_offset = htobe64(n64);
+  n64++;
+  h.public_key_size = htobe64(n64);
+  n64++;
+  h.descriptors_offset = htobe64(n64);
+  n64++;
+  h.descriptors_size = htobe64(n64);
+  n64++;
+  h.rollback_index = htobe64(n64);
+  n64++;
+
+  avb_vbmeta_image_header_to_host_byte_order(&h, &s);
+
+  n32 = 0x11223344;
+  n64 = 0x1122334455667788;
+
+  EXPECT_EQ(n32, s.header_version_major);
+  n32++;
+  EXPECT_EQ(n32, s.header_version_minor);
+  n32++;
+  EXPECT_EQ(n64, s.authentication_data_block_size);
+  n64++;
+  EXPECT_EQ(n64, s.auxiliary_data_block_size);
+  n64++;
+  EXPECT_EQ(n32, s.algorithm_type);
+  n32++;
+  EXPECT_EQ(n64, s.hash_offset);
+  n64++;
+  EXPECT_EQ(n64, s.hash_size);
+  n64++;
+  EXPECT_EQ(n64, s.signature_offset);
+  n64++;
+  EXPECT_EQ(n64, s.signature_size);
+  n64++;
+  EXPECT_EQ(n64, s.public_key_offset);
+  n64++;
+  EXPECT_EQ(n64, s.public_key_size);
+  n64++;
+  EXPECT_EQ(n64, s.descriptors_offset);
+  n64++;
+  EXPECT_EQ(n64, s.descriptors_size);
+  n64++;
+  EXPECT_EQ(n64, s.rollback_index);
+  n64++;
+
+  // If new fields are added, the following will fail. This is to
+  // remind that byteswapping code (in avb_util.c) and unittests for
+  // this should be updated.
+  static_assert(offsetof(AvbVBMetaImageHeader, reserved) == 104,
+                "Remember to unittest byteswapping of newly added fields");
+}
diff --git a/avb/test/avbtool_unittest.cc b/avb/test/avbtool_unittest.cc
new file mode 100644
index 0000000..fa99cb3
--- /dev/null
+++ b/avb/test/avbtool_unittest.cc
@@ -0,0 +1,816 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <iostream>
+
+#include <endian.h>
+#include <inttypes.h>
+#include <string.h>
+
+#include <base/files/file_util.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+
+#include "avb_sha.h"
+#include "avb_unittest_util.h"
+#include "libavb.h"
+
+class AvbToolTest : public BaseAvbToolTest {
+ public:
+  AvbToolTest() {}
+
+  void AddHashFooterTest(bool sparse_image);
+  void AddHashtreeFooterTest(bool sparse_image);
+};
+
+// This test ensure that the version is increased in both
+// avb_boot_image.h and the avb tool.
+TEST_F(AvbToolTest, AvbVersionInSync) {
+  base::FilePath path = testdir_.Append("version.txt");
+  EXPECT_COMMAND(0, "./avbtool version > %s", path.value().c_str());
+  std::string printed_version;
+  ASSERT_TRUE(base::ReadFileToString(path, &printed_version));
+  base::TrimWhitespaceASCII(printed_version, base::TRIM_ALL, &printed_version);
+  std::string expected_version =
+      base::StringPrintf("%d.%d", AVB_MAJOR_VERSION, AVB_MINOR_VERSION);
+  EXPECT_EQ(printed_version, expected_version);
+}
+
+TEST_F(AvbToolTest, ExtractPublicKey) {
+  GenerateVBMetaImage("vbmeta.img", "SHA256_RSA2048", 0,
+                      base::FilePath("test/data/testkey_rsa2048.pem"));
+
+  std::string key_data =
+      PublicKeyAVB(base::FilePath("test/data/testkey_rsa2048.pem"));
+
+  AvbVBMetaImageHeader h;
+  avb_vbmeta_image_header_to_host_byte_order(
+      reinterpret_cast<AvbVBMetaImageHeader*>(vbmeta_image_.data()), &h);
+  uint8_t* d = reinterpret_cast<uint8_t*>(vbmeta_image_.data());
+  size_t auxiliary_data_block_offset =
+      sizeof(AvbVBMetaImageHeader) + h.authentication_data_block_size;
+  EXPECT_GT(h.auxiliary_data_block_size, key_data.size());
+  EXPECT_EQ(0, memcmp(key_data.data(),
+                      d + auxiliary_data_block_offset + h.public_key_offset,
+                      key_data.size()));
+}
+
+TEST_F(AvbToolTest, CheckDescriptors) {
+  GenerateVBMetaImage("vbmeta.img", "SHA256_RSA2048", 0,
+                      base::FilePath("test/data/testkey_rsa2048.pem"),
+                      "--prop foo:brillo "
+                      "--prop bar:chromeos "
+                      "--prop prisoner:24601 "
+                      "--prop hexnumber:0xcafe "
+                      "--prop hexnumber_capital:0xCAFE "
+                      "--prop large_hexnumber:0xfedcba9876543210 "
+                      "--prop larger_than_uint64:0xfedcba98765432101 "
+                      "--prop almost_a_number:423x "
+                      "--prop_from_file blob:test/data/small_blob.bin ");
+
+  AvbVBMetaImageHeader h;
+  avb_vbmeta_image_header_to_host_byte_order(
+      reinterpret_cast<AvbVBMetaImageHeader*>(vbmeta_image_.data()), &h);
+
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_OK,
+            avb_vbmeta_image_verify(vbmeta_image_.data(), vbmeta_image_.size(),
+                                    nullptr, nullptr));
+
+  const char* s;
+  size_t len;
+  uint64_t val;
+
+  // Basic.
+  s = avb_property_lookup(vbmeta_image_.data(), vbmeta_image_.size(), "foo", 0,
+                          &len);
+  EXPECT_EQ(0, strcmp(s, "brillo"));
+  EXPECT_EQ(6U, len);
+  s = avb_property_lookup(vbmeta_image_.data(), vbmeta_image_.size(), "bar", 0,
+                          &len);
+  EXPECT_EQ(0, strcmp(s, "chromeos"));
+  EXPECT_EQ(8U, len);
+  s = avb_property_lookup(vbmeta_image_.data(), vbmeta_image_.size(),
+                          "non-existant", 0, &len);
+  EXPECT_EQ(0U, len);
+  EXPECT_EQ(NULL, s);
+
+  // Numbers.
+  EXPECT_NE(
+      0, avb_property_lookup_uint64(vbmeta_image_.data(), vbmeta_image_.size(),
+                                    "prisoner", 0, &val));
+  EXPECT_EQ(24601U, val);
+
+  EXPECT_NE(
+      0, avb_property_lookup_uint64(vbmeta_image_.data(), vbmeta_image_.size(),
+                                    "hexnumber", 0, &val));
+  EXPECT_EQ(0xcafeU, val);
+
+  EXPECT_NE(
+      0, avb_property_lookup_uint64(vbmeta_image_.data(), vbmeta_image_.size(),
+                                    "hexnumber_capital", 0, &val));
+  EXPECT_EQ(0xcafeU, val);
+
+  EXPECT_NE(
+      0, avb_property_lookup_uint64(vbmeta_image_.data(), vbmeta_image_.size(),
+                                    "large_hexnumber", 0, &val));
+  EXPECT_EQ(0xfedcba9876543210U, val);
+
+  // We could catch overflows and return an error ... but we currently don't.
+  EXPECT_NE(
+      0, avb_property_lookup_uint64(vbmeta_image_.data(), vbmeta_image_.size(),
+                                    "larger_than_uint64", 0, &val));
+  EXPECT_EQ(0xedcba98765432101U, val);
+
+  // Number-parsing failures.
+  EXPECT_EQ(0, avb_property_lookup_uint64(
+                   vbmeta_image_.data(), vbmeta_image_.size(), "foo", 0, &val));
+
+  EXPECT_EQ(
+      0, avb_property_lookup_uint64(vbmeta_image_.data(), vbmeta_image_.size(),
+                                    "almost_a_number", 0, &val));
+
+  // Blobs.
+  //
+  // test/data/small_blob.bin is 21 byte file full of NUL-bytes except
+  // for the string "brillo ftw!" at index 2 and '\n' at the last
+  // byte.
+  s = avb_property_lookup(vbmeta_image_.data(), vbmeta_image_.size(), "blob", 0,
+                          &len);
+  EXPECT_EQ(21U, len);
+  EXPECT_EQ(0, memcmp(s, "\0\0", 2));
+  EXPECT_EQ(0, memcmp(s + 2, "brillo ftw!", 11));
+  EXPECT_EQ(0, memcmp(s + 13, "\0\0\0\0\0\0\0", 7));
+  EXPECT_EQ('\n', s[20]);
+}
+
+TEST_F(AvbToolTest, CheckRollbackIndex) {
+  uint64_t rollback_index = 42;
+  GenerateVBMetaImage("vbmeta.img", "SHA256_RSA2048", rollback_index,
+                      base::FilePath("test/data/testkey_rsa2048.pem"));
+
+  AvbVBMetaImageHeader h;
+  avb_vbmeta_image_header_to_host_byte_order(
+      reinterpret_cast<AvbVBMetaImageHeader*>(vbmeta_image_.data()), &h);
+
+  EXPECT_EQ(rollback_index, h.rollback_index);
+}
+
+TEST_F(AvbToolTest, CheckPubkeyReturned) {
+  GenerateVBMetaImage("vbmeta.img", "SHA256_RSA2048", 0,
+                      base::FilePath("test/data/testkey_rsa2048.pem"));
+
+  const uint8_t* pubkey = NULL;
+  size_t pubkey_length = 0;
+
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_OK,
+            avb_vbmeta_image_verify(vbmeta_image_.data(), vbmeta_image_.size(),
+                                    &pubkey, &pubkey_length));
+
+  AvbVBMetaImageHeader h;
+  avb_vbmeta_image_header_to_host_byte_order(
+      reinterpret_cast<AvbVBMetaImageHeader*>(vbmeta_image_.data()), &h);
+
+  EXPECT_EQ(pubkey_length, h.public_key_size);
+
+  const uint8_t* expected_pubkey =
+      vbmeta_image_.data() + sizeof(AvbVBMetaImageHeader) +
+      h.authentication_data_block_size + h.public_key_offset;
+  EXPECT_EQ(pubkey, expected_pubkey);
+}
+
+TEST_F(AvbToolTest, Info) {
+  GenerateVBMetaImage("vbmeta.img", "SHA256_RSA2048", 0,
+                      base::FilePath("test/data/testkey_rsa2048.pem"),
+                      "--prop foo:brillo "
+                      "--prop bar:chromeos "
+                      "--prop prisoner:24601 "
+                      "--prop hexnumber:0xcafe "
+                      "--prop hexnumber_capital:0xCAFE "
+                      "--prop large_hexnumber:0xfedcba9876543210 "
+                      "--prop larger_than_uint64:0xfedcba98765432101 "
+                      "--prop almost_a_number:423x "
+                      "--prop_from_file blob:test/data/small_blob.bin "
+                      "--prop_from_file large_blob:test/data/large_blob.bin");
+
+  ASSERT_EQ(
+      "VBMeta image version:     1.0\n"
+      "Header Block:             256 bytes\n"
+      "Authentication Block:     576 bytes\n"
+      "Auxiliary Block:          3200 bytes\n"
+      "Algorithm:                SHA256_RSA2048\n"
+      "Rollback Index:           0\n"
+      "Descriptors:\n"
+      "    Prop: foo -> 'brillo'\n"
+      "    Prop: bar -> 'chromeos'\n"
+      "    Prop: prisoner -> '24601'\n"
+      "    Prop: hexnumber -> '0xcafe'\n"
+      "    Prop: hexnumber_capital -> '0xCAFE'\n"
+      "    Prop: large_hexnumber -> '0xfedcba9876543210'\n"
+      "    Prop: larger_than_uint64 -> '0xfedcba98765432101'\n"
+      "    Prop: almost_a_number -> '423x'\n"
+      "    Prop: blob -> '\\x00\\x00brillo "
+      "ftw!\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\n'\n"
+      "    Prop: large_blob -> (2048 bytes)\n",
+      InfoImage(vbmeta_image_path_));
+}
+
+static bool collect_descriptors(const AvbDescriptor* descriptor,
+                                void* user_data) {
+  std::vector<const AvbDescriptor*>* descriptors =
+      reinterpret_cast<std::vector<const AvbDescriptor*>*>(user_data);
+  descriptors->push_back(descriptor);
+  return true;  // Keep iterating.
+}
+
+static std::string mem_to_hexstring(const uint8_t* data, size_t len) {
+  std::string ret;
+  char digits[17] = "0123456789abcdef";
+  for (size_t n = 0; n < len; n++) {
+    ret.push_back(digits[data[n] >> 4]);
+    ret.push_back(digits[data[n] & 0x0f]);
+  }
+  return ret;
+}
+
+void AvbToolTest::AddHashFooterTest(bool sparse_image) {
+  const size_t rootfs_size = 1028 * 1024;
+  const size_t partition_size = 1536 * 1024;
+
+  // Generate a 1028 KiB file with known content. Some content have
+  // been arranged to ensure FILL_DATA segments in the sparse file.
+  std::vector<uint8_t> rootfs;
+  rootfs.resize(rootfs_size);
+  for (size_t n = 0; n < rootfs_size; n++) {
+    if ((n >= 5*1000 && n < 105*1000) ||
+        (n >= 205*1000 && n < 305*1000) ||
+        (n >= 505*1000 && n < 605*1000)) {
+      rootfs[n] = uint8_t(n) & 0x03;
+    } else {
+      rootfs[n] = uint8_t(n);
+    }
+  }
+  base::FilePath rootfs_path = testdir_.Append("rootfs.bin");
+  EXPECT_EQ(rootfs_size,
+            static_cast<const size_t>(base::WriteFile(
+                rootfs_path, reinterpret_cast<const char*>(rootfs.data()),
+                rootfs.size())));
+
+  if (sparse_image) {
+    EXPECT_COMMAND(0, "mv %s %s.unsparse", rootfs_path.value().c_str(),
+                   rootfs_path.value().c_str());
+    EXPECT_COMMAND(0, "img2simg %s.unsparse %s", rootfs_path.value().c_str(),
+                   rootfs_path.value().c_str());
+    EXPECT_COMMAND(0, "rm -f %s.unsparse", rootfs_path.value().c_str());
+  }
+
+  /* Do this twice to check that 'add_hash_footer' is idempotent. */
+  for (int n = 0; n < 2; n++) {
+    EXPECT_COMMAND(0,
+                   "./avbtool add_hash_footer --salt d00df00d "
+                   "--hash_algorithm sha256 --image %s "
+                   "--partition_size %d --partition_name foobar "
+                   "--algorithm SHA256_RSA2048 "
+                   "--key test/data/testkey_rsa2048.pem",
+                   rootfs_path.value().c_str(), (int)partition_size);
+
+    ASSERT_EQ(base::StringPrintf("Footer version:           1.0\n"
+                                 "Image size:               1572864 bytes\n"
+                                 "Original image size:      1052672 bytes\n"
+                                 "VBMeta offset:            1052672\n"
+                                 "VBMeta size:              1472 bytes\n"
+                                 "--\n"
+                                 "VBMeta image version:     1.0%s\n"
+                                 "Header Block:             256 bytes\n"
+                                 "Authentication Block:     576 bytes\n"
+                                 "Auxiliary Block:          640 bytes\n"
+                                 "Algorithm:                SHA256_RSA2048\n"
+                                 "Rollback Index:           0\n"
+                                 "Descriptors:\n"
+                                 "    Hash descriptor:\n"
+                                 "      Image Size:            1052672 bytes\n"
+                                 "      Hash Algorithm:        sha256\n"
+                                 "      Partition Name:        foobar\n"
+                                 "      Salt:                  d00df00d\n"
+                                 "      Digest:                9a58cc996d405e08a1e00f96dbfe9104fedf41cb83b1f5e4ed357fbcf58d88d9\n",
+                                 sparse_image ? " (Sparse)" : ""),
+              InfoImage(rootfs_path));
+  }
+
+  if (sparse_image) {
+    EXPECT_COMMAND(0, "mv %s %s.sparse", rootfs_path.value().c_str(),
+                   rootfs_path.value().c_str());
+    EXPECT_COMMAND(0, "simg2img %s.sparse %s", rootfs_path.value().c_str(),
+                   rootfs_path.value().c_str());
+    EXPECT_COMMAND(0, "rm -f %s.sparse", rootfs_path.value().c_str());
+  }
+
+  // Manually calculate the hash to check that it agrees with avbtool.
+  AvbSHA256Ctx hasher_ctx;
+  const uint8_t hasher_salt[4] = {0xd0, 0x0d, 0xf0, 0x0d};
+  avb_sha256_init(&hasher_ctx);
+  avb_sha256_update(&hasher_ctx, hasher_salt, 4);
+  avb_sha256_update(&hasher_ctx, rootfs.data(), rootfs_size);
+  uint8_t* hasher_digest = avb_sha256_final(&hasher_ctx);
+  EXPECT_EQ("9a58cc996d405e08a1e00f96dbfe9104fedf41cb83b1f5e4ed357fbcf58d88d9",
+            mem_to_hexstring(hasher_digest, AVB_SHA256_DIGEST_SIZE));
+
+  // Now check that we can find the VBMeta block again from the footer.
+  std::string part_data;
+  ASSERT_TRUE(base::ReadFileToString(rootfs_path, &part_data));
+
+  // Check footer contains correct data.
+  AvbFooter f;
+  EXPECT_NE(0, avb_footer_validate_and_byteswap(
+                   reinterpret_cast<const AvbFooter*>(
+                       part_data.data() + part_data.size() - AVB_FOOTER_SIZE),
+                   &f));
+  EXPECT_EQ(
+      std::string(reinterpret_cast<const char*>(f.magic), AVB_FOOTER_MAGIC_LEN),
+      AVB_FOOTER_MAGIC);
+  EXPECT_EQ(AVB_FOOTER_MAJOR_VERSION, (int)f.version_major);
+  EXPECT_EQ(AVB_FOOTER_MINOR_VERSION, (int)f.version_minor);
+  EXPECT_EQ(1052672UL, f.original_image_size);
+  EXPECT_EQ(1052672UL, f.vbmeta_offset);
+  EXPECT_EQ(1472UL, f.vbmeta_size);
+
+  // Check that the vbmeta image at |f.vbmeta_offset| checks out.
+  const uint8_t* vbmeta_data =
+      reinterpret_cast<const uint8_t*>(part_data.data() + f.vbmeta_offset);
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_OK,
+            avb_vbmeta_image_verify(vbmeta_data, f.vbmeta_size, NULL, NULL));
+
+  // Collect all descriptors.
+  std::vector<const AvbDescriptor*> descriptors;
+  avb_descriptor_foreach(vbmeta_data, f.vbmeta_size, collect_descriptors,
+                         &descriptors);
+
+  // We should only have a single descriptor and it should be a
+  // hash descriptor.
+  EXPECT_EQ(1UL, descriptors.size());
+  EXPECT_EQ(AVB_DESCRIPTOR_TAG_HASH, avb_be64toh(descriptors[0]->tag));
+  AvbHashDescriptor d;
+  EXPECT_NE(
+      0, avb_hash_descriptor_validate_and_byteswap(
+             reinterpret_cast<const AvbHashDescriptor*>(descriptors[0]), &d));
+  EXPECT_EQ(1052672UL, d.image_size);
+  EXPECT_EQ(6UL, d.partition_name_len);
+  EXPECT_EQ(4UL, d.salt_len);
+  EXPECT_EQ(32UL, d.digest_len);
+  const uint8_t* desc_end = reinterpret_cast<const uint8_t*>(descriptors[0]) +
+                            sizeof(AvbHashDescriptor);
+  uint64_t o = 0;
+  EXPECT_EQ("foobar", std::string(reinterpret_cast<const char*>(desc_end + o),
+                                  d.partition_name_len));
+  o += d.partition_name_len;
+  EXPECT_EQ("d00df00d", mem_to_hexstring(desc_end + o, d.salt_len));
+  o += d.salt_len;
+  EXPECT_EQ("9a58cc996d405e08a1e00f96dbfe9104fedf41cb83b1f5e4ed357fbcf58d88d9",
+            mem_to_hexstring(desc_end + o, d.digest_len));
+
+  // Check that the footer is correctly erased.
+  EXPECT_COMMAND(0, "./avbtool erase_footer --image %s",
+                 rootfs_path.value().c_str());
+  int64_t erased_footer_file_size;
+  ASSERT_TRUE(base::GetFileSize(rootfs_path, &erased_footer_file_size));
+  EXPECT_EQ(static_cast<size_t>(erased_footer_file_size), rootfs_size);
+}
+
+TEST_F(AvbToolTest, AddHashFooter) { AddHashFooterTest(false); }
+
+TEST_F(AvbToolTest, AddHashFooterSparse) { AddHashFooterTest(true); }
+
+TEST_F(AvbToolTest, AddHashFooterSparseWithHoleAtTheEnd) {
+  const size_t partition_size = 10 * 1024 * 1024;
+
+  // It's not enough to run img2simg on a file with a lot of zeroes at
+  // the end since that will turn up as "Fill with value (for value =
+  // 0x00000000)" and not "Don't care". Instead, use make_ext4fs for
+  // this since it will put a big hole (e.g. "Don't care" chunk) at
+  // the end.
+  base::FilePath partition_path = testdir_.Append("partition.bin");
+  EXPECT_COMMAND(0, "make_ext4fs -s -L test -l %zd %s", partition_size,
+                 partition_path.value().c_str());
+
+  EXPECT_COMMAND(0,
+                 "./avbtool add_hash_footer --salt d00df00d "
+                 "--hash_algorithm sha256 --image %s "
+                 "--partition_size %d --partition_name foobar "
+                 "--algorithm SHA256_RSA2048 "
+                 "--key test/data/testkey_rsa2048.pem",
+                 partition_path.value().c_str(), (int)partition_size);
+
+  // NOTE: This could start failing if there are changes to
+  // make_ext4fs. If that happens, just update this test.
+  ASSERT_EQ(
+      "Footer version:           1.0\n"
+      "Image size:               10485760 bytes\n"
+      "Original image size:      4415488 bytes\n"
+      "VBMeta offset:            4415488\n"
+      "VBMeta size:              1472 bytes\n"
+      "--\n"
+      "VBMeta image version:     1.0 (Sparse)\n"
+      "Header Block:             256 bytes\n"
+      "Authentication Block:     576 bytes\n"
+      "Auxiliary Block:          640 bytes\n"
+      "Algorithm:                SHA256_RSA2048\n"
+      "Rollback Index:           0\n"
+      "Descriptors:\n"
+      "    Hash descriptor:\n"
+      "      Image Size:            4415488 bytes\n"
+      "      Hash Algorithm:        sha256\n"
+      "      Partition Name:        foobar\n"
+      "      Salt:                  d00df00d\n"
+      "      Digest:                "
+      "4cc8f283b40e8d13651ec6ca2d258ace88a61b6286f2a2e149204745f43b2285\n",
+      InfoImage(partition_path));
+
+  EXPECT_COMMAND(0, "mv %s %s.sparse", partition_path.value().c_str(),
+                 partition_path.value().c_str());
+  EXPECT_COMMAND(0, "simg2img %s.sparse %s", partition_path.value().c_str(),
+                 partition_path.value().c_str());
+  EXPECT_COMMAND(0, "rm -f %s.sparse", partition_path.value().c_str());
+}
+
+void AvbToolTest::AddHashtreeFooterTest(bool sparse_image) {
+  const size_t rootfs_size = 1028 * 1024;
+  const size_t partition_size = 1536 * 1024;
+
+  // Generate a 1028 KiB file with known content.
+  std::vector<uint8_t> rootfs;
+  rootfs.resize(rootfs_size);
+  for (size_t n = 0; n < rootfs_size; n++) rootfs[n] = uint8_t(n);
+  base::FilePath rootfs_path = testdir_.Append("rootfs.bin");
+  EXPECT_EQ(rootfs_size,
+            static_cast<const size_t>(base::WriteFile(
+                rootfs_path, reinterpret_cast<const char*>(rootfs.data()),
+                rootfs.size())));
+
+  if (sparse_image) {
+    EXPECT_COMMAND(0, "mv %s %s.unsparse", rootfs_path.value().c_str(),
+                   rootfs_path.value().c_str());
+    EXPECT_COMMAND(0, "img2simg %s.unsparse %s", rootfs_path.value().c_str(),
+                   rootfs_path.value().c_str());
+    EXPECT_COMMAND(0, "rm -f %s.unsparse", rootfs_path.value().c_str());
+  }
+
+  /* Do this twice to check that 'add_hashtree_footer' is idempotent. */
+  for (int n = 0; n < 2; n++) {
+    EXPECT_COMMAND(0,
+                   "./avbtool add_hashtree_footer --salt d00df00d --image %s "
+                   "--partition_size %d --partition_name foobar "
+                   "--algorithm SHA256_RSA2048 "
+                   "--key test/data/testkey_rsa2048.pem",
+                   rootfs_path.value().c_str(), (int)partition_size);
+
+    ASSERT_EQ(base::StringPrintf("Footer version:           1.0\n"
+                                 "Image size:               1572864 bytes\n"
+                                 "Original image size:      1052672 bytes\n"
+                                 "VBMeta offset:            1069056\n"
+                                 "VBMeta size:              1536 bytes\n"
+                                 "--\n"
+                                 "VBMeta image version:     1.0%s\n"
+                                 "Header Block:             256 bytes\n"
+                                 "Authentication Block:     576 bytes\n"
+                                 "Auxiliary Block:          704 bytes\n"
+                                 "Algorithm:                SHA256_RSA2048\n"
+                                 "Rollback Index:           0\n"
+                                 "Descriptors:\n"
+                                 "    Hashtree descriptor:\n"
+                                 "      Version of dm-verity:  1\n"
+                                 "      Image Size:            1052672 bytes\n"
+                                 "      Tree Offset:           1052672\n"
+                                 "      Tree Size:             16384 bytes\n"
+                                 "      Data Block Size:       4096 bytes\n"
+                                 "      Hash Block Size:       4096 bytes\n"
+                                 "      Hash Algorithm:        sha1\n"
+                                 "      Partition Name:        foobar\n"
+                                 "      Salt:                  d00df00d\n"
+                                 "      Root Digest:           "
+                                 "e811611467dcd6e8dc4324e45f706c2bdd51db67\n",
+                                 sparse_image ? " (Sparse)" : ""),
+              InfoImage(rootfs_path));
+  }
+
+  if (sparse_image) {
+    EXPECT_COMMAND(0, "mv %s %s.sparse", rootfs_path.value().c_str(),
+                   rootfs_path.value().c_str());
+    EXPECT_COMMAND(0, "simg2img %s.sparse %s", rootfs_path.value().c_str(),
+                   rootfs_path.value().c_str());
+    EXPECT_COMMAND(0, "rm -f %s.sparse", rootfs_path.value().c_str());
+  }
+
+  // To check that we generate the correct hashtree we can use
+  // veritysetup(1) - another codebase for working with dm-verity
+  // hashtrees - to verify it.
+  //
+  // If we don't want to impose the requirement of having the
+  // veritysetup(1) command available on builders we can comment this
+  // out.
+  EXPECT_COMMAND(0,
+                 "veritysetup --no-superblock --format=1 --hash=sha1 "
+                 "--data-block-size=4096 --hash-block-size=4096 "
+                 "--salt=d00df00d "
+                 "--data-blocks=257 "
+                 "--hash-offset=1052672 "
+                 "verify "
+                 "%s %s "
+                 "e811611467dcd6e8dc4324e45f706c2bdd51db67",
+                 rootfs_path.value().c_str(), rootfs_path.value().c_str());
+
+  // Now check that we can find the VBMeta block again from the footer.
+  std::string part_data;
+  ASSERT_TRUE(base::ReadFileToString(rootfs_path, &part_data));
+
+  // Check footer contains correct data.
+  AvbFooter f;
+  EXPECT_NE(0, avb_footer_validate_and_byteswap(
+                   reinterpret_cast<const AvbFooter*>(
+                       part_data.data() + part_data.size() - AVB_FOOTER_SIZE),
+                   &f));
+  EXPECT_EQ(
+      std::string(reinterpret_cast<const char*>(f.magic), AVB_FOOTER_MAGIC_LEN),
+      AVB_FOOTER_MAGIC);
+  EXPECT_EQ(AVB_FOOTER_MAJOR_VERSION, (int)f.version_major);
+  EXPECT_EQ(AVB_FOOTER_MINOR_VERSION, (int)f.version_minor);
+  EXPECT_EQ(1052672UL, f.original_image_size);
+  EXPECT_EQ(1069056UL, f.vbmeta_offset);
+  EXPECT_EQ(1536UL, f.vbmeta_size);
+
+  // Check that the vbmeta image at |f.vbmeta_offset| checks out.
+  const uint8_t* vbmeta_data =
+      reinterpret_cast<const uint8_t*>(part_data.data() + f.vbmeta_offset);
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_OK,
+            avb_vbmeta_image_verify(vbmeta_data, f.vbmeta_size, NULL, NULL));
+
+  // Collect all descriptors.
+  std::vector<const AvbDescriptor*> descriptors;
+  avb_descriptor_foreach(vbmeta_data, f.vbmeta_size, collect_descriptors,
+                         &descriptors);
+
+  // We should only have a single descriptor and it should be a
+  // hashtree descriptor.
+  EXPECT_EQ(1UL, descriptors.size());
+  EXPECT_EQ(AVB_DESCRIPTOR_TAG_HASHTREE, avb_be64toh(descriptors[0]->tag));
+  AvbHashtreeDescriptor d;
+  EXPECT_NE(
+      0,
+      avb_hashtree_descriptor_validate_and_byteswap(
+          reinterpret_cast<const AvbHashtreeDescriptor*>(descriptors[0]), &d));
+  EXPECT_EQ(1UL, d.dm_verity_version);
+  EXPECT_EQ(1052672UL, d.image_size);
+  EXPECT_EQ(1052672UL, d.tree_offset);
+  EXPECT_EQ(16384UL, d.tree_size);
+  EXPECT_EQ(4096UL, d.data_block_size);
+  EXPECT_EQ(4096UL, d.hash_block_size);
+  EXPECT_EQ(6UL, d.partition_name_len);
+  EXPECT_EQ(4UL, d.salt_len);
+  EXPECT_EQ(20UL, d.root_digest_len);
+  const uint8_t* desc_end = reinterpret_cast<const uint8_t*>(descriptors[0]) +
+                            sizeof(AvbHashtreeDescriptor);
+  uint64_t o = 0;
+  EXPECT_EQ("foobar", std::string(reinterpret_cast<const char*>(desc_end + o),
+                                  d.partition_name_len));
+  o += d.partition_name_len;
+  EXPECT_EQ("d00df00d", mem_to_hexstring(desc_end + o, d.salt_len));
+  o += d.salt_len;
+  EXPECT_EQ("e811611467dcd6e8dc4324e45f706c2bdd51db67",
+            mem_to_hexstring(desc_end + o, d.root_digest_len));
+
+  // Check that we correctly generate dm-verity kernel cmdline
+  // snippets, if requested.
+  base::FilePath vbmeta_dmv_path = testdir_.Append("vbmeta_dm_verity_desc.bin");
+  EXPECT_COMMAND(0,
+                 "./avbtool make_vbmeta_image "
+                 "--output %s "
+                 "--generate_dm_verity_cmdline_from_hashtree %s "
+                 "--algorithm SHA256_RSA2048 "
+                 "--key test/data/testkey_rsa2048.pem",
+                 vbmeta_dmv_path.value().c_str(), rootfs_path.value().c_str());
+
+  ASSERT_EQ(
+      "VBMeta image version:     1.0\n"
+      "Header Block:             256 bytes\n"
+      "Authentication Block:     576 bytes\n"
+      "Auxiliary Block:          768 bytes\n"
+      "Algorithm:                SHA256_RSA2048\n"
+      "Rollback Index:           0\n"
+      "Descriptors:\n"
+      "    Kernel Cmdline descriptor:\n"
+      "      Kernel Cmdline:        'dm=\"1 vroot none ro 1,0 2056 verity 1 "
+      "PARTUUID=$(ANDROID_SYSTEM_PARTUUID) PARTUUID=$(ANDROID_SYSTEM_PARTUUID) "
+      "4096 4096 257 257 sha1 e811611467dcd6e8dc4324e45f706c2bdd51db67 "
+      "d00df00d\"'\n",
+      InfoImage(vbmeta_dmv_path));
+
+  // Check that the footer is correctly erased and the hashtree
+  // remains - see above for why the constant 1069056 is used.
+  EXPECT_COMMAND(0, "./avbtool erase_footer --image %s --keep_hashtree",
+                 rootfs_path.value().c_str());
+  int64_t erased_footer_file_size;
+  ASSERT_TRUE(base::GetFileSize(rootfs_path, &erased_footer_file_size));
+  EXPECT_EQ(static_cast<size_t>(erased_footer_file_size), 1069056UL);
+}
+
+TEST_F(AvbToolTest, AddHashtreeFooter) { AddHashtreeFooterTest(false); }
+
+TEST_F(AvbToolTest, AddHashtreeFooterSparse) { AddHashtreeFooterTest(true); }
+
+TEST_F(AvbToolTest, KernelCmdlineDescriptor) {
+  base::FilePath vbmeta_path =
+      testdir_.Append("vbmeta_kernel_cmdline_desc.bin");
+
+  EXPECT_COMMAND(0,
+                 "./avbtool make_vbmeta_image "
+                 "--output %s "
+                 "--kernel_cmdline 'foo bar baz' "
+                 "--kernel_cmdline 'second cmdline' "
+                 "--algorithm SHA256_RSA2048 "
+                 "--key test/data/testkey_rsa2048.pem",
+                 vbmeta_path.value().c_str());
+
+  ASSERT_EQ(
+      "VBMeta image version:     1.0\n"
+      "Header Block:             256 bytes\n"
+      "Authentication Block:     576 bytes\n"
+      "Auxiliary Block:          640 bytes\n"
+      "Algorithm:                SHA256_RSA2048\n"
+      "Rollback Index:           0\n"
+      "Descriptors:\n"
+      "    Kernel Cmdline descriptor:\n"
+      "      Kernel Cmdline:        'foo bar baz'\n"
+      "    Kernel Cmdline descriptor:\n"
+      "      Kernel Cmdline:        'second cmdline'\n",
+      InfoImage(vbmeta_path));
+
+  // Now check the VBMeta image.
+  std::string image_data;
+  ASSERT_TRUE(base::ReadFileToString(vbmeta_path, &image_data));
+
+  const uint8_t* vbmeta_data =
+      reinterpret_cast<const uint8_t*>(image_data.data());
+  const size_t vbmeta_size = image_data.length();
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_OK,
+            avb_vbmeta_image_verify(vbmeta_data, vbmeta_size, NULL, NULL));
+
+  // Collect all descriptors.
+  std::vector<const AvbDescriptor*> descriptors;
+  avb_descriptor_foreach(vbmeta_data, vbmeta_size, collect_descriptors,
+                         &descriptors);
+
+  // We should have two descriptors - check them.
+  EXPECT_EQ(2UL, descriptors.size());
+  AvbKernelCmdlineDescriptor d;
+  EXPECT_EQ(AVB_DESCRIPTOR_TAG_KERNEL_CMDLINE,
+            avb_be64toh(descriptors[0]->tag));
+  EXPECT_NE(
+      0,
+      avb_kernel_cmdline_descriptor_validate_and_byteswap(
+          reinterpret_cast<const AvbKernelCmdlineDescriptor*>(descriptors[0]),
+          &d));
+  EXPECT_EQ("foo bar baz",
+            std::string(reinterpret_cast<const char*>(descriptors[0]) +
+                            sizeof(AvbKernelCmdlineDescriptor),
+                        d.kernel_cmdline_length));
+  EXPECT_EQ(AVB_DESCRIPTOR_TAG_KERNEL_CMDLINE,
+            avb_be64toh(descriptors[1]->tag));
+  EXPECT_NE(
+      0,
+      avb_kernel_cmdline_descriptor_validate_and_byteswap(
+          reinterpret_cast<const AvbKernelCmdlineDescriptor*>(descriptors[1]),
+          &d));
+  EXPECT_EQ("second cmdline",
+            std::string(reinterpret_cast<const char*>(descriptors[1]) +
+                            sizeof(AvbKernelCmdlineDescriptor),
+                        d.kernel_cmdline_length));
+}
+
+TEST_F(AvbToolTest, IncludeDescriptor) {
+  base::FilePath vbmeta1_path = testdir_.Append("vbmeta_id1.bin");
+  base::FilePath vbmeta2_path = testdir_.Append("vbmeta_id2.bin");
+  base::FilePath vbmeta3_path = testdir_.Append("vbmeta_id3.bin");
+
+  EXPECT_COMMAND(0,
+                 "./avbtool make_vbmeta_image "
+                 "--output %s "
+                 "--kernel_cmdline 'something' "
+                 "--prop name:value ",
+                 vbmeta1_path.value().c_str());
+
+  EXPECT_COMMAND(0,
+                 "./avbtool make_vbmeta_image "
+                 "--output %s "
+                 "--prop name2:value2 "
+                 "--prop name3:value3 ",
+                 vbmeta2_path.value().c_str());
+
+  EXPECT_COMMAND(0,
+                 "./avbtool make_vbmeta_image "
+                 "--output %s "
+                 "--prop name4:value4 "
+                 "--include_descriptors_from_image %s "
+                 "--include_descriptors_from_image %s ",
+                 vbmeta3_path.value().c_str(), vbmeta1_path.value().c_str(),
+                 vbmeta2_path.value().c_str());
+
+  ASSERT_EQ(
+      "VBMeta image version:     1.0\n"
+      "Header Block:             256 bytes\n"
+      "Authentication Block:     0 bytes\n"
+      "Auxiliary Block:          256 bytes\n"
+      "Algorithm:                NONE\n"
+      "Rollback Index:           0\n"
+      "Descriptors:\n"
+      "    Prop: name4 -> 'value4'\n"
+      "    Prop: name -> 'value'\n"
+      "    Kernel Cmdline descriptor:\n"
+      "      Kernel Cmdline:        'something'\n"
+      "    Prop: name2 -> 'value2'\n"
+      "    Prop: name3 -> 'value3'\n",
+      InfoImage(vbmeta3_path));
+}
+
+TEST_F(AvbToolTest, ChainedPartition) {
+  base::FilePath vbmeta_path = testdir_.Append("vbmeta_cp.bin");
+
+  base::FilePath pk_path = testdir_.Append("testkey_rsa2048.avbpubkey");
+
+  EXPECT_COMMAND(
+      0,
+      "./avbtool extract_public_key --key test/data/testkey_rsa2048.pem"
+      " --output %s",
+      pk_path.value().c_str());
+
+  EXPECT_COMMAND(
+      0,
+      "./avbtool make_vbmeta_image "
+      "--output %s "
+      "--chain_partition system:1:%s "
+      "--algorithm SHA256_RSA2048 --key test/data/testkey_rsa2048.pem",
+      vbmeta_path.value().c_str(), pk_path.value().c_str());
+
+  ASSERT_EQ(
+      "VBMeta image version:     1.0\n"
+      "Header Block:             256 bytes\n"
+      "Authentication Block:     576 bytes\n"
+      "Auxiliary Block:          1088 bytes\n"
+      "Algorithm:                SHA256_RSA2048\n"
+      "Rollback Index:           0\n"
+      "Descriptors:\n"
+      "    Chain Partition descriptor:\n"
+      "      Partition Name:        system\n"
+      "      Rollback Index Slot:   1\n"
+      "      Public key (sha1):     cdbb77177f731920bbe0a0f94f84d9038ae0617d\n",
+      InfoImage(vbmeta_path));
+
+  // Now check the VBMeta image.
+  std::string image_data;
+  ASSERT_TRUE(base::ReadFileToString(vbmeta_path, &image_data));
+
+  const uint8_t* vbmeta_data =
+      reinterpret_cast<const uint8_t*>(image_data.data());
+  const size_t vbmeta_size = image_data.length();
+  EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_OK,
+            avb_vbmeta_image_verify(vbmeta_data, vbmeta_size, NULL, NULL));
+
+  // Collect all descriptors.
+  std::vector<const AvbDescriptor*> descriptors;
+  avb_descriptor_foreach(vbmeta_data, vbmeta_size, collect_descriptors,
+                         &descriptors);
+
+  // We should have one descriptor - check it.
+  EXPECT_EQ(1UL, descriptors.size());
+
+  std::string pk_data;
+  ASSERT_TRUE(base::ReadFileToString(pk_path, &pk_data));
+
+  AvbChainPartitionDescriptor d;
+  EXPECT_EQ(AVB_DESCRIPTOR_TAG_CHAIN_PARTITION,
+            avb_be64toh(descriptors[0]->tag));
+  EXPECT_NE(
+      0,
+      avb_chain_partition_descriptor_validate_and_byteswap(
+          reinterpret_cast<const AvbChainPartitionDescriptor*>(descriptors[0]),
+          &d));
+  const uint8_t* desc_end = reinterpret_cast<const uint8_t*>(descriptors[0]) +
+                            sizeof(AvbChainPartitionDescriptor);
+  uint64_t o = 0;
+  EXPECT_EQ("system", std::string(reinterpret_cast<const char*>(desc_end + o),
+                                  d.partition_name_len));
+  o += d.partition_name_len;
+  EXPECT_EQ(pk_data, std::string(reinterpret_cast<const char*>(descriptors[0]) +
+                                     sizeof(AvbChainPartitionDescriptor) + o,
+                                 d.public_key_len));
+}
diff --git a/avb/test/data/large_blob.bin b/avb/test/data/large_blob.bin
new file mode 100644
index 0000000..56feeb9
--- /dev/null
+++ b/avb/test/data/large_blob.bin
Binary files differ
diff --git a/avb/test/data/small_blob.bin b/avb/test/data/small_blob.bin
new file mode 100644
index 0000000..d712de6
--- /dev/null
+++ b/avb/test/data/small_blob.bin
Binary files differ
diff --git a/avb/test/data/test_file.bin b/avb/test/data/test_file.bin
new file mode 100644
index 0000000..a3fc7fb
--- /dev/null
+++ b/avb/test/data/test_file.bin
Binary files differ
diff --git a/avb/test/data/test_file.bin.sparse b/avb/test/data/test_file.bin.sparse
new file mode 100644
index 0000000..c4962b2
--- /dev/null
+++ b/avb/test/data/test_file.bin.sparse
Binary files differ
diff --git a/avb/test/data/testkey_rsa2048.pem b/avb/test/data/testkey_rsa2048.pem
new file mode 100644
index 0000000..867dcff
--- /dev/null
+++ b/avb/test/data/testkey_rsa2048.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAxlVR3TIkouAOvH79vaJTgFhpfvVKQIeVkFRZPVXK/zY0Gvrh
+4JAqGjJoW/PfrQv5sdD36qtHH3a+G5hLZ6Ni+t/mtfjucxZfuLGC3kmJ1T3XqEKZ
+gXXI2IR7vVSoImREvDQGEDyJwtHzLANlkbGg0cghVhWZSCAndO8BenalC2v94/rt
+DfkPekH6dgU3Sf40T0sBSeSY94mOzTaqOR2pfV1rWlLRdWmo33zeHBv52Rlbt0dM
+uXAureXWiHztkm5GCBC1dgM+CaxNtizNEgC91KcD0xuRCCM2WxH+r1lpszyIJDct
+YbrFmVEYl/kjQpafhy7Nsk1fqSTyRdriZSYmTQIDAQABAoIBAQC+kJgaCuX8wYAn
+SXWQ0fmdZlXnMNRpcF0a0pD0SAzGb1RdYBXMaXiqtyhiwc53PPxsCDdNecjayIMd
+jJVXPTwLhTruOgMS/bp3gcgWwV34UHV4LJXGOGAE+jbS0hbDBMiudOYmj6RmVshp
+z9G1zZCSQNMXHaWsEYkX59XpzzoB384nRul2QgEtwzUNR9XlpzgtJBLk3SACkvsN
+mQ/DW8IWHXLg8vLn1LzVJ2e3B16H4MoE2TCHxqfMgr03IDRRJogkenQuQsFhevYT
+o/mJyHSWavVgzMHG9I5m+eepF4Wyhj1Y4WyKAuMI+9dHAX/h7Lt8XFCQCh5DbkVG
+zGr34sWBAoGBAOs7n7YZqNaaguovfIdRRsxxZr1yJAyDsr6w3yGImDZYju4c4WY9
+5esO2kP3FA4p0c7FhQF5oOb1rBuHEPp36cpL4aGeK87caqTfq63WZAujoTZpr9Lp
+BRbkL7w/xG7jpQ/clpA8sHzHGQs/nelxoOtC7E118FiRgvD/jdhlMyL9AoGBANfX
+vyoN1pplfT2xR8QOjSZ+Q35S/+SAtMuBnHx3l0qH2bbBjcvM1MNDWjnRDyaYhiRu
+i+KA7tqfib09+XpB3g5D6Ov7ls/Ldx0S/VcmVWtia2HK8y8iLGtokoBZKQ5AaFX2
+iQU8+tC4h69GnJYQKqNwgCUzh8+gHX5Y46oDiTmRAoGAYpOx8lX+czB8/Da6MNrW
+mIZNT8atZLEsDs2ANEVRxDSIcTCZJId7+m1W+nRoaycLTWNowZ1+2ErLvR10+AGY
+b7Ys79Wg9idYaY9yGn9lnZsMzAiuLeyIvXcSqgjvAKlVWrhOQFOughvNWvFl85Yy
+oWSCMlPiTLtt7CCsCKsgKuECgYBgdIp6GZsIfkgclKe0hqgvRoeU4TR3gcjJlM9A
+lBTo+pKhaBectplx9RxR8AnsPobbqwcaHnIfAuKDzjk5mEvKZjClnFXF4HAHbyAF
+nRzZEy9XkWFhc80T5rRpZO7C7qdxmu2aiKixM3V3L3/0U58qULEDbubHMw9bEhAT
+PudI8QKBgHEEiMm/hr9T41hbQi/LYanWnlFw1ue+osKuF8bXQuxnnHNuFT/c+9/A
+vWhgqG6bOEHu+p/IPrYm4tBMYlwsyh4nXCyGgDJLbLIfzKwKAWCtH9LwnyDVhOow
+GH9shdR+sW3Ew97xef02KAH4VlNANEmBV4sQNqWWvsYrcFm2rOdL
+-----END RSA PRIVATE KEY-----
diff --git a/avb/test/data/testkey_rsa4096.pem b/avb/test/data/testkey_rsa4096.pem
new file mode 100644
index 0000000..26db5c3
--- /dev/null
+++ b/avb/test/data/testkey_rsa4096.pem
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIJKQIBAAKCAgEA2ASv49OEbH4NiT3CjNMSVeliyfEPXswWcqtEfCxlSpS1FisA
+uwbvEwdTTPlkuSh6G4SYiNhnpCP5p0vcSg/3OhiuVKgV/rCtrDXaO60nvK/o0y83
+NNZRK2xaJ9eWBq9ruIDK+jC0sYWzTaqqwxY0Grjnx/r5CXerl5PrRK7PILzwgBHb
+IwxHcblt1ntgR4cWVpO3wiqasEwBDDDYk4fw7W6LvjBb9qav3YB8RV6PkZNeRP64
+ggfuecq/MXNiWOPNxLzCER2hSr/+J32h9jWjXsrcVy8+8Mldhmr4r2an7c247aFf
+upuFGtUJrpROO8/LXMl5gPfMpkqoatjTMRH59gJjKhot0RpmGxZBvb33TcBK5SdJ
+X39Y4yct5clmDlI4Fjj7FutTP+b96aJeJVnYeUX/A0wmogBajsJRoRX5e/RcgZsY
+RzXYLQXprQ81dBWjjovMJ9p8XeT6BNMFC7o6sklFL0fHDUE/l4BNP8G1u3Bfpzev
+SCISRS71D4eS4oQB+RIPFBUkzomZ7rnEF3BwFeq+xmwfYrP0LRaH+1YeRauuMuRe
+ke1TZl697a3mEjkNg8noa2wtpe7EWmaujJfXDWxJx/XEkjGLCe4z2qk3tkkY+A5g
+Rcgzke8gVxC+eC2DJtbKYfkv4L8FMFJaEhwAp13MfC7FlYujO/BDLl7dANsCAwEA
+AQKCAgAWoL8P/WsktjuSwb5sY/vKtgzcHH1Ar942GsysuTXPDy686LpF3R8T/jNy
+n7k2UBAia8xSoWCR6BbRuHeV5oA+PLGeOpE7QaSfonB+yc+cy0x3Or3ssfqEsu/q
+toGHp75/8DXS6WE0K04x94u1rdC9b9sPrrGBlWCLGzqM0kbuJfyHXdd3n2SofAUO
+b5QRSgxD+2tHUpEroHqHnWJCaf4J0QegX45yktlfOYNK/PHLDQXV8ly/ejc32M4Y
+Tv7hUtOOJTuq8VCg9OWZm2Zo1QuM9XEJTPCp5l3+o5vzO6yhk2gotDvD32CdA+3k
+tLJRP54M1Sn+IXb1gGKN9rKAtGJbenWIPlNObhQgkbwG89Qd+5rfMXsiPv1Hl1tK
++tqwjD82/H3/ElaaMnwHCpeoGSp95OblAoBjzjMP2KsbvKSdL8O/rf1c3uOw9+DF
+cth0SA8y3ZzI11gJtb2QMGUrCny5n4sPGGbc3x38NdLhwbkPKZy60OiT4g2kNpdY
+dIitmAML2otttiF4AJM6AraPk8YVzkPLTksoL3azPBya5lIoDI2H3QvTtSvpXkXP
+yKchsDSWYbdqfplqC/X0Djp2/Zd8jpN5I6+1aSmpTmbwx/JTllY1N89FRZLIdxoh
+2k81LPiXhE6uRbjioJUlbnEWIpY2y2N2Clmxpjh0/IcXd1XImQKCAQEA7Zai+yjj
+8xit24aO9Tf3mZBXBjSaDodjC2KS1yCcAIXp6S7aH0wZipyZpQjys3zaBQyMRYFG
+bQqIfVAa6inWyDoofbAJHMu5BVcHFBPZvSS5YhDjc8XZ5dqSCxzIz9opIqAbm+b4
+aEV/3A3Jki5Dy8y/5j21GAK4Y4mqQOYzne7bDGi3Hyu041MGM4qfIcIkS5N1eHW4
+sDZJh6+K5tuxN5TX3nDZSpm9luNH8mLGgKAZ15b1LqXAtM5ycoBY9Hv082suPPom
+O+r0ybdRX6nDSH8+11y2KiP2kdVIUHCGkwlqgrux5YZyjCZPwOvEPhzSoOS+vBiF
+UVXA8idnxNLk1QKCAQEA6MIihDSXx+350fWqhQ/3Qc6gA/t2C15JwJ9+uFWA+gjd
+c/hn5HcmnmBJN4R04nLG/aU9SQur87a4mnC/Mp9JIARjHlZ/WNT4U0sJyPEVRg5U
+Z9VajAucWwi0JyJYCO1EMMy68Jp8qlTriK/L7nbD86JJ5ASxjojiN/0psK/Pk60F
+Rr+shKPi3jRQ1BDjDtAxOfo4ctf/nFbUM4bY0FNPQMP7WesoSKU0NBCRR6d0d2tq
+YflMjIQHx+N74P5jEdSCHTVGQm+dj47pUt3lLPLWc0bX1G/GekwXP4NUsR/70Hsi
+bwxkNnK2TSGzkt2rcOnutP125rJu6WpV7SNrq9rm7wKCAQAfMROcnbWviKHqnDPQ
+hdR/2K9UJTvEhInASOS2UZWpi+s1rez9BuSjigOx4wbaAZ4t44PW7C3uyt84dHfU
+HkIQb3I5bg8ENMrJpK9NN33ykwuzkDwMSwFcZ+Gci97hSubzoMl/IkeiiN1MapL4
+GhLUgsD+3UMVL+Y9SymK8637IgyoCGdiND6/SXsa8SwLJo3VTjqx4eKpX7cvlSBL
+RrRxc50TmwUsAhsd4CDl9YnSATLjVvJBeYlfM2tbFPaYwl1aR8v+PWkfnK0efm60
+fHki33HEnGteBPKuGq4vwVYpn6bYGwQz+f6335/A2DMfZHFSpjVURHPcRcHbCMla
+0cUxAoIBAQC25eYNkO478mo+bBbEXJlkoqLmvjAyGrNFo48F9lpVH6Y0vNuWkXJN
+PUgLUhAu6RYotjGENqG17rz8zt/PPY9Ok2P3sOx8t00y1mIn/hlDZXs55FM0fOMu
+PZaiscAPs7HDzvyOmDah+fzi+ZD8H2M3DS2W+YE0iaeJa2vZJS2t02W0BGXiDI33
+IZDqMyLYvwwPjOnShJydEzXID4xLl0tNjzLxo3GSNA7jYqlmbtV8CXIc7rMSL6WV
+ktIDKKJcnmpn3TcKeX6MEjaSIT82pNOS3fY3PmXuL+CMzfw8+u77Eecq78fHaTiL
+P5JGM93F6mzi19EY0tmInUBMCWtQLcENAoIBAQCg0KaOkb8T36qzPrtgbfou0E2D
+ufdpL1ugmD4edOFKQB5fDFQhLnSEVSJq3KUg4kWsXapQdsBd6kLdxS+K6MQrLBzr
+4tf0c7UCF1AzWk6wXMExZ8mRb2RkGZYQB2DdyhFB3TPmnq9CW8JCq+6kxg/wkU4s
+vM4JXzgcqVoSf42QJl+B9waeWhg0BTWx01lal4ds88HvEKmE0ik5GwiDbr7EvDDw
+E6UbZtQcIoSTIIZDgYqVFfR2DAho3wXJRsOXh433lEJ8X7cCDzrngFbQnlKrpwML
+Xgm0SIUc+Nf5poMM3rfLFK77t/ob4w+5PwRKcoSniyAxrHd6bwykYA8Vuydv
+-----END RSA PRIVATE KEY-----
diff --git a/avb/test/data/testkey_rsa8192.pem b/avb/test/data/testkey_rsa8192.pem
new file mode 100644
index 0000000..a383428
--- /dev/null
+++ b/avb/test/data/testkey_rsa8192.pem
@@ -0,0 +1,99 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIISKgIBAAKCBAEA0D3T+dISsmCHm797wsX0vVfqUWDJ/3mvDYozlCabDhnGLlSE
+pAQbf1Z8Ts+OM4pVRHOJUJL0WebNdmPPGjsyWQz6zZE96lQZL3avCEXqYVQR66V5
+3wdK/ohaMSRnGyEMBrqkVVbF3gCr+/irxD3YK+VowO2WKs/6GrMdqTA8Y5CTF/Je
+ptwsSg5MMjr6UaK4qDcrej3hkgBVGvRV3cj1snK6Br8HuYdFnpGGTS0d7UJlHFgl
+trGHU/CBO923hkHgJaWEjC0giSGjhKKtLzrVcpDV2y/lWQP9T/T4djEAIaHqQ++P
+SdOSR6psIGR6hVgSigt7HCnE7nW711/rfV5Ur9EiVpB040mDImKZcy8//TMnXydN
+1KYTVd/34fdpzMpSw5iblErbwOLXVTUmOztYnpl41feHSv/jPesHstPlfklIF2vo
+GZEohf9scQvcuM7wEBfC/aTA9K39zMmkBbcvSZjLyhmcSZWMPPOZyIcl3zY53QhW
+QC/abmIcBfI1S4+r7mC4i2Jn++oEvuGNVGr2SY2Z0ZZxXGL1HI/08D/3+Tcumrcn
+4YjPK/DMFi0F+e+1x41lipuf+cx/2qRNQX/m02STrLYdM6e0g33KvlnFdi2b752y
+/OIaMwxDaJvunMh6EMDWKM1AHbY/ioAoK7eS26HeJLEDllqO4+SWP37c8lMvSEWy
+1GiErR0HcsOj/QwWGPFseoVroMiA2sUQ0Ic/tgVjCTlXg+12XpUnouIweCi8KcL/
+ad2zJkju9hBhJLBQ/2GnivJi3lFgF4Gd//TSJ6rgWuXFfMKt/9z2Sz35ohEX4yA0
+flqlCeLInFEoevbz+XT9aRfDe65MZ79yw3TfP9CrV74hf1RRzveD4zpi3F+hcY2i
+JWsH7gROZeCm6fAX5Trecd3hOxJOfA4N4rvSSCq6BwCvebT8FY25Z/VF7cQrHYDS
+ij5w6lqhMzXHeUEY90Ga9AK4XzaWwGgezq+R7Zs00YSKqFv9qYNKdR7tz3cjijWf
+9q/3R1uh6EQKTMZKo4SEClJiGyjOBvmPK09jMFZTJv00hDxagDPZBl7XpLDJ5/Ln
+1uppvLCNWWY1zeJfaElMyq3/PqKZLidF9rVoA1SIwk2lpdUvPote2oFiwCZoXlwZ
+J2ncjmXgQNs76/8unDJA0rj4JPqccw4M5GxQ7okbgm3F4rmzriCuv8BeMSCkr2ry
+0mY3UhpohX4wCMq0G4x5sEUAz9FVVPZKjxnYBmLDzrJAR+4+G7gZsct01XDJYgDd
+JVYInFP22/cIre8VrFWYtHbgOFdNqUiVq58de6PdZG/E+uaWmEThSlRrgEjTxupi
+OXfgdKW/20j1qAtjOlqFwsY094Q5rqULQ6wPxQIDAQABAoIEAQChmkmlhrRBv42d
+fYUiyxK52b8ath0saJdDz6tlXmxYDgJxM9/XlORt9oTzeDknoEO5olu+rrx4BBgQ
+tzYiaiwRVXRREVTWQ7tjzRvaNL/GFkLt93XTccpuKwyrNE/bitLVagRbwcI+HZFa
+MknCOihHMHoRto8h3FKAY94xzSAgODMek1WG8jhgpCXXmVNnBPt+d4oDDIDAGAfz
+qgf03J5nhIb+80KgZOzPOKnbvJaL6EmlLHbgB3c42dzAw7hHtVmofYGWcvLb2MIY
+DVKO435/sQx1U/8NDH6JjVdACZjLgObXH9K3/Tt46DWPEcrPLmD8xhoc6gFM+Qr0
+AhkzKoBYDNk0CljbhdIBXjktXU6wRQFZ45uP2e4JZ4zrzGBLr/t4lTavZ0SQtLld
+A6kOsGh+dCWFDtnshxYnl/xad/yR+3a5zmDJbo/fJTBXrlf1B4rfQkFtK20etOPQ
+B++FC/rjh3Mm/Kb/p9Gz/2upZdArH97ZvD2LBFfj77lFmAhqAi3wCRlN+ekuYxaZ
+t1pBV9yXig8Dyldg1d7X8pOn2kyrF3rQUDDf4pa7x9vpnbkUlEUifoV9gnYsmdni
+qDzYBtTv2g6MKqwQySXaIUW0YOBPbOellWEwxJqGYQ7y4IfVHfM0iyHnehk2tZcr
++XazLnwGe+Bz4vcguFhJXLyIu//lAOhZtbk6r1QJEUuxaOOQX3wzyceE6nkDsgmr
+P5dj3Zpd7fS2VV2vyGHIFnBJ88LRxreVvgr6Q28UT27SB82zMb7mRZTVE2zeuubT
+5D2D1XbZ0wBo6WiK6eRRrDQ2Haeetkj/uoRy6PWXwnAaTmmIrrXwLqaoJh/U1e+D
+tfsDLWd6IxLjfXvGglrHsrtAz0oprpixUTeVhgTrGk9IQRd5rvxuGUYhFujVaYI6
++QUf+33AFdtncb8y9C9jZmgx8AKbJk+e73SLhB5JVos+WteU7b8d/Mim5mALjnO6
+Z1n/uimsT79sSDqy3XSymtKWXo/22UlrvGCpoEuELPMb6dSFWR7vwrsvhFngY4/K
+UnitnvxboEflQnaIQ4IfRLRzZsX+sC5Esqw9U5tHt4oI+91Dv3KbdbcERgV73K6B
+ZQgC4lkAQquFXiZ5AICkxjiMyZwTtU9KJ7xv17Xu6oywF/3AtbVGETW1D+3maHsD
+y3DASWojyqZdLj+WGzKQRa+swgCDAYKeek2fIAXFSdF63zxJ2RxOJ4GijSaoh+mr
+4HVvcpDaTj+A8T1+QdByM4s98gu4GD7kVtVQGBZdWjutyHvh0hWv1gtVmbhQ/413
+gDMFFDzHIjLTYGYes4hHL22169jVR9sZ1eQxwvTIg3N4pD5cFm0rRuZZTS+oJToF
+G27aBFihAoICAQDyVB62ZDnbxQthk+zITKIzRUrJbLoXrUcANcSHfaN7inF87Ova
+ze7ejT9DNSEhbtfZFJ1G6diOYoSw+2MzFXv0gEkLKY0dETydKgHEu6nVq5eivMgv
+D4hc9YkJMHDSlmv2FDkpL3AXCAmnW9rKp+ddttBZECnmlPEpHLoj6xgBw3pNa1Xs
+IcLVfdugH86Hexj6o0oKgYfcqrX8UUHtUI2/XQqgFrIj8ksjf1fFVWJRJFWmBXqp
+nMEsYarzATeM1kQ/kDeT1ZUpoGPQt02/XqXT4B5A3ATiEtpM2u+l48xtogWWg2Ry
+G9l938StAmhUiW1m7GnKE6EIFvQY85WvbzxOR0JYVUSr7MrasF6nnQlhYxFuIJoJ
+2h/KJQao5GCTvG4+GtbJJm4c2nyZgwyhizMsdgsdcls79aXiMkrZZkamLVUZWOtE
+3pA/oBuz2qnO9HwjbH1HGOccq0TXfmpFScEV3CQGYJdno6Fy7cbmupaL4U9agQ4e
+w+ygL18nq5HV++LStFnVrgs5YijjskfRdE9GUMVDh5pCsd9Y23Fymaad4O/2SRCC
+YkSsyH5OvyDOLpoyUJ6g6Q+45Hqm/3lG4YjNpzFUiMcnp7+3xU35qC0LK8xEfeei
+Ms1mTVEiHNIp6xH/TqRdX73WD7+YuKZSLIfRG7dgrirU6w+mhhvxD51uHQKCAgEA
+2/1mBCR5qm3/0Lt++RQbeyE3tiw40UeyQqucG/+VvY77sSLkI/Lx8iwRlywXcLBn
++A4TvgukmAdWzCs8ndgKNxPA+gfohvBsMOGN9KOB1Ug5vvg2J2kiI64vwYCwzhdZ
+NTUUmL+GMFHUqSsWYg6i7iBFcZmznr4W2T3bBxyTMZki7JStB86e35KXrzc2/W/b
++/p5U2HCSazDHI5mMyuClHc6GmUSVJ7f7LHjL94jviNqobp0Vj603tScHISmNrZw
+TBavkvZGYXsoWKvqavk7jBB9QzaBL+unaFRslg5jTaiKnISj44Us1fjFKu84xifL
+nJaEzjDPt7PBxko7LPgEY7wF39nM9VpoetI7bwR6NwDLSX8UU97MGd+HY+MO1Wi1
+pd2Lapwrx/EK7Oxz335VRK4Je0aZna4j2TyQdMJac9fsGPXv4ZsLfDLj/wD6l1j+
+lLLbBv3ImdSj32LBbhsgF4iCGeXO8HpPO+Q/h9XVsnY52Um2XdNMn03PCGm6ZvtM
+7DXiS+lPF90HjolJVHZTBNtdVRrLr53zLuWEfqT4FeKrDaxdtiXkxLjrB+5/VYu7
+ntyk01ZQ63VNfEwS1irmKl9+qZkTHk3HHV9jNV5RzWViwmJI7Wpr1YzBwmcKCB1O
+oGUADDs8QpnkCz0xkMVtYwHj9qKZlqfbHzrFDUUcF8kCggIAdYvUcgjf//ju8mA8
+5VQ3AcPE6TvycPW+kR2DvW12VcDsF/sc1UA7dHzziPhGn98SmNxlBjb8suSbFPZ8
+QhVT0WBBDkcTilwIGPx9ax7U3S6lGW2VdS6FqQH5fRmgQKZyrCVXLOEz8BgYBrSJ
+xu/3TQAWxH0QtibdbGHg8Pdi58gYlWFRhn9B8Slh1aRYHGPb1AhNLBd0/ddY+5G2
+9xSyDXdmZg1cUA+B3zAwNSqbzFxhp2zU+V1uXsbpk4KtnYV6CZM9QlrCRjTk9iNU
+dVXF/qaiRjfzrm4SsmEpCkEbsrp7F22Y1bkooORglMOsNAWNqfVXw4wN+syXj1ro
+6vZ8PERYrFyAOR1dsQMIhymnmTPjCpaJ4emKrhWTy20sY71thHakZWJc22YoNpbZ
+E6tgIVsJPTlxg/4+fyCCKj5wWr92nhsB1KBZPGO/zFhvMlJpvQ0tH8W2pbN2a0mI
+5x9FqALm/qjwCHfZItSwPM+ZozSht3cOkGHdcD5KXAXfcfsDJc4SHZKVIzq4NusN
+504R/jvD1GP8sglyG7omp75ckgzAmakLdxOP2HhQvIX9tcXpSirNJ6Sl2bwKuuMF
+wxo3r/o/9Y97e4LlfpEYp9eqMdcG+NpR993IwK0UhAWS9H5wdnWBSUHd5e4xtDUt
+iILNRuO46g7R/AIhz1cSSraWWQkCggIBAMhhPP5C9yt9PIm1b0eTwCBctnFSQIKo
+KsA9rll2ab+bMLk9jc8M6MLszy0CtWso09sHf4YY9tifvrkEHRethEh8zscwUuYu
+sm2n1fTixk0ul6LSVgl54uXbMJayENn4PIKRkew8cA8tSma43497w37hmD+MgCb1
+ALzqcco9hfmkgkI6fo1g8Ce3UEECKy2YKSmREdgYcK9JFQO61W6AkFWJcDxAmfzI
+JjFkKwsb7TSw79zWiEdSoM9jm7sCPKATd6Bm/ZAAkUUTuEFkfobn9Ax1rJN/Xxb2
+MKuAUtQv0NYY0gEVdG62jItuKLId6nncH8PG+rsRjPLIYpWqYdJpKx5pUnR+4AkQ
+S6CsRASwcF4PdBvDDBIFG6XpjFo4pPdQhDzL2sTF8b8SWSBLlJQbb7G6UNqgCSau
+SusCFpazvU5NfDmUMuctob2EYVaSXq9jGaj6bTUmDwXHwWilfIk9XfLxnYfXYrJ6
+xhdIpXGmHhuLQtAgK2O1JtLoPc9s9qP8/SkfP7xjjG6xHsP/WvL7QE1pPs9ZM/UI
+C01JNHFi9LKCn8o5mbZjN8jUowi7ffK+76wZUG1L7zM5ytWQOYwo0TQBfc8fpmFw
++RBRJX2kJyDO27ExczoGOKjwqEDaODIB9+9zcCK0BgSoRibSm4ZBvoxzWWD65Kls
+xdPhZUHcFGW5AoICAQC8iG27aD8aRUt94Oek66gFOJx84QVZehWPqtZjWyVenDuc
+T8dink8oejGjcK2UJuQDa83azv90ocVqE0n0ronYyszt9Ib1jlYC+CK1Ar9TYGFg
+WU5OWEDyCzCpqW/w/aG68U8qhKm0MvkLJR+G6evan9TwEhFEVAm3iWllNXs9x29s
+BucwyMMC23zsimxYlS7dA4DtyvVA+zL1omLpSWHbU/qtuI3HV1NeJzsy+gC4mwPh
+j52tdl669fyWLzHzBRLeq6dVOedjnCo+jlU3dL20DEk9SaW08D1CPuZekV1jVPMw
+JoaDcIRh4KLtQ0BYZ7UJeFUTsx1CS/+UqzqYSPOi57a5kvr0Y8YwRnSB8dHVFttX
+JTv83wTQXHPFSBgfnHNe7lsRTfIQfuIkr2bpiU7h85UQ7LsqcI6YHaC07URcsGFF
+FrLWGh91qzAd1diSHla2RnY3n8PPuMnCkguNhLUrYdmyMol7FfWFa9lwplsuTzBq
+B6yj8iaiE3LL+Q/eulJ7S6QPfAI2bU0UJO23Y4koeoIibEEDMSCQ6KYZ2NClRRRT
+ga5fS1YfkDFEcHUQ1/KIkdYHGBKBjoKGExzi8+CgiSySVSYDZl6wIOhLjH2OZ3ol
+ldPN7iNAHirrxg9v8QO6OQlpLUk5Lhp/1dSlZ6sy3UjFqvax3tw6ZjrL88YP5g==
+-----END RSA PRIVATE KEY-----
diff --git a/avb/test/image_handler_unittest.py b/avb/test/image_handler_unittest.py
new file mode 100755
index 0000000..c8014e6
--- /dev/null
+++ b/avb/test/image_handler_unittest.py
@@ -0,0 +1,203 @@
+#!/usr/bin/python
+
+# Copyright 2016, The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+"""Unit-test for ImageHandler."""
+
+
+import imp
+import os
+import sys
+import tempfile
+import unittest
+
+sys.dont_write_bytecode = True
+avbtool = imp.load_source('avbtool', './avbtool')
+
+# The file test_file.bin and test_file.bin.sparse are generated using
+# the following python code:
+#
+#  with open('test_file.bin', 'w+b') as f:
+#    f.write('Barfoo43'*128*12)
+#  os.system('img2simg test_file.bin test_file.bin.sparse')
+#  image = avbtool.ImageHandler('test_file.bin.sparse')
+#  image.append_dont_care(12*1024)
+#  image.append_fill('\x01\x02\x03\x04', 12*1024)
+#  image.append_raw('Foobar42'*128*12)
+#  image.append_dont_care(12*1024)
+#  del image
+#  os.system('rm -f test_file.bin')
+#  os.system('simg2img test_file.bin.sparse test_file.bin')
+#
+# and manually verified to be correct. The content of the raw and
+# sparse files are as follows (the line with "Fill with 0x04030201" is
+# a simg_dump.py bug):
+#
+# $ hexdump -C test_file.bin
+# 00000000  42 61 72 66 6f 6f 34 33  42 61 72 66 6f 6f 34 33  |Barfoo43Barfoo43|
+# *
+# 00003000  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
+# *
+# 00006000  01 02 03 04 01 02 03 04  01 02 03 04 01 02 03 04  |................|
+# *
+# 00009000  46 6f 6f 62 61 72 34 32  46 6f 6f 62 61 72 34 32  |Foobar42Foobar42|
+# *
+# 0000c000  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
+# *
+# 0000f000
+#
+# $ system/core/libsparse/simg_dump.py -v test_file.bin.sparse
+# test_file.bin.sparse: Total of 15 4096-byte output blocks in 5 input chunks.
+#             input_bytes      output_blocks
+# chunk    offset     number  offset  number
+#    1         40      12288       0       3 Raw data
+#    2      12340          0       3       3 Don't care
+#    3      12352          4       6       3 Fill with 0x04030201
+#    4      12368      12288       9       3 Raw data
+#    5      24668          0      12       3 Don't care
+#           24668                 15         End
+#
+
+
+class ImageHandler(unittest.TestCase):
+
+  TEST_FILE_SPARSE_PATH = 'test/data/test_file.bin.sparse'
+  TEST_FILE_PATH = 'test/data/test_file.bin'
+  TEST_FILE_SIZE = 61440
+  TEST_FILE_BLOCK_SIZE = 4096
+
+  def _file_contents_equal(self, path1, path2, size):
+    f1 = open(path1, 'r')
+    f2 = open(path2, 'r')
+    if f1.read(size) != f2.read(size):
+      return False
+    return True
+
+  def _file_size(self, f):
+    old_pos = f.tell()
+    f.seek(0, os.SEEK_END)
+    size = f.tell()
+    f.seek(old_pos)
+    return size
+
+  def _clone_sparse_file(self):
+    f = tempfile.NamedTemporaryFile()
+    f.write(open(self.TEST_FILE_SPARSE_PATH).read())
+    f.flush()
+    return f
+
+  def _unsparsify(self, path):
+    f = tempfile.NamedTemporaryFile()
+    os.system('simg2img {} {}'.format(path, f.name))
+    return f
+
+  def testRead(self):
+    """Checks that reading from a sparse file works as intended."""
+    ih = avbtool.ImageHandler(self.TEST_FILE_SPARSE_PATH)
+
+    # Check that we start at offset 0.
+    self.assertEqual(ih.tell(), 0)
+
+    # Check that reading advances the cursor.
+    self.assertEqual(ih.read(14), bytearray('Barfoo43Barfoo'))
+    self.assertEqual(ih.tell(), 14)
+    self.assertEqual(ih.read(2), bytearray('43'))
+    self.assertEqual(ih.tell(), 16)
+
+    # Check reading in the middle of a fill chunk gets the right data.
+    ih.seek(0x6000 + 1)
+    self.assertEqual(ih.read(4), bytearray('\x02\x03\x04\x01'))
+
+    # Check we can cross the chunk boundary correctly.
+    ih.seek(0x3000 - 10)
+    self.assertEqual(ih.read(12), bytearray('43Barfoo43\x00\x00'))
+    ih.seek(0x9000 - 3)
+    self.assertEqual(ih.read(5), bytearray('\x02\x03\x04Fo'))
+
+    # Check reading at end of file is a partial read.
+    ih.seek(0xf000 - 2)
+    self.assertEqual(ih.read(16), bytearray('\x00\x00'))
+
+  def testTruncate(self):
+    """Checks that we can truncate a sparse file correctly."""
+    # Check truncation at all possible boundaries (including start and end).
+    for size in range(0, self.TEST_FILE_SIZE + self.TEST_FILE_BLOCK_SIZE,
+                      self.TEST_FILE_BLOCK_SIZE):
+      sparse_file = self._clone_sparse_file()
+      ih = avbtool.ImageHandler(sparse_file.name)
+      ih.truncate(size)
+      unsparse_file = self._unsparsify(sparse_file.name)
+      self.assertEqual(self._file_size(unsparse_file), size)
+      self.assertTrue(self._file_contents_equal(unsparse_file.name,
+                                                self.TEST_FILE_PATH,
+                                                size))
+
+    # Check truncation to grow the file.
+    grow_size = 8192
+    sparse_file = self._clone_sparse_file()
+    ih = avbtool.ImageHandler(sparse_file.name)
+    ih.truncate(self.TEST_FILE_SIZE + grow_size)
+    unsparse_file = self._unsparsify(sparse_file.name)
+    self.assertEqual(self._file_size(unsparse_file),
+                     self.TEST_FILE_SIZE + grow_size)
+    self.assertTrue(self._file_contents_equal(unsparse_file.name,
+                                              self.TEST_FILE_PATH,
+                                              self.TEST_FILE_SIZE))
+    unsparse_file.seek(self.TEST_FILE_SIZE)
+    self.assertEqual(unsparse_file.read(), '\0'*grow_size)
+
+  def testAppendRaw(self):
+    """Checks that we can append raw data correctly."""
+    sparse_file = self._clone_sparse_file()
+    ih = avbtool.ImageHandler(sparse_file.name)
+    data = 'SomeData'*4096
+    ih.append_raw(data)
+    unsparse_file = self._unsparsify(sparse_file.name)
+    self.assertTrue(self._file_contents_equal(unsparse_file.name,
+                                              self.TEST_FILE_PATH,
+                                              self.TEST_FILE_SIZE))
+    unsparse_file.seek(self.TEST_FILE_SIZE)
+    self.assertEqual(unsparse_file.read(), data)
+
+  def testAppendFill(self):
+    """Checks that we can append fill data correctly."""
+    sparse_file = self._clone_sparse_file()
+    ih = avbtool.ImageHandler(sparse_file.name)
+    data = 'ABCD'*4096
+    ih.append_fill('ABCD', len(data))
+    unsparse_file = self._unsparsify(sparse_file.name)
+    self.assertTrue(self._file_contents_equal(unsparse_file.name,
+                                              self.TEST_FILE_PATH,
+                                              self.TEST_FILE_SIZE))
+    unsparse_file.seek(self.TEST_FILE_SIZE)
+    self.assertEqual(unsparse_file.read(), data)
+
+  def testDontCare(self):
+    """Checks that we can append DONT_CARE data correctly."""
+    sparse_file = self._clone_sparse_file()
+    ih = avbtool.ImageHandler(sparse_file.name)
+    data = '\0'*40960
+    ih.append_dont_care(len(data))
+    unsparse_file = self._unsparsify(sparse_file.name)
+    self.assertTrue(self._file_contents_equal(unsparse_file.name,
+                                              self.TEST_FILE_PATH,
+                                              self.TEST_FILE_SIZE))
+    unsparse_file.seek(self.TEST_FILE_SIZE)
+    self.assertEqual(unsparse_file.read(), data)
+
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/avb/test/libavb_host_symbols_test b/avb/test/libavb_host_symbols_test
new file mode 100755
index 0000000..89b836a
--- /dev/null
+++ b/avb/test/libavb_host_symbols_test
@@ -0,0 +1,41 @@
+#!/bin/bash
+
+#
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# This shell-script checks the symbols in libavb_host.a and fails
+# if a reference not starting with avb_ is referenced. It's intended
+# to catch mistakes where the standard C library is inadvertently
+# used.
+
+set -e
+
+SYMBOLS_FILE=$(mktemp /tmp/libavb_host_symbols.XXXXXXXXXX)
+
+trap "rm -f '${SYMBOLS_FILE}'" EXIT
+
+readelf --symbols --wide "${ANDROID_HOST_OUT}/obj/STATIC_LIBRARIES/libavb_host_intermediates/libavb_host.a" | \
+  awk '$7 == "UND" && $8 != "" {print $8}' | \
+  grep -v ^avb_ | \
+  sort -u > "${SYMBOLS_FILE}"
+
+# If this file is non-empty, it means that the library is using
+# symbols not starting with "avb_".
+if [ -s "${SYMBOLS_FILE}" ] ; then
+  echo "ERROR: $0: Unexpected symbols in libavb_host:" >&2
+  cat "${SYMBOLS_FILE}" >&2
+  exit 1
+fi
diff --git a/boot_control_copy/boot_control_copy.c b/boot_control_copy/boot_control_copy.c
index 644e7de..7302243 100644
--- a/boot_control_copy/boot_control_copy.c
+++ b/boot_control_copy/boot_control_copy.c
@@ -92,7 +92,7 @@
   return 0;
 }
 
-#define COPY_BUF_SIZE 1024*1024
+#define COPY_BUF_SIZE (1024*1024)
 
 static bool copy_data(int src_fd, int dst_fd, size_t num_bytes)
 {
diff --git a/boot_control_copy/bootinfo.c b/boot_control_copy/bootinfo.c
index 396dd81..82a134f 100644
--- a/boot_control_copy/bootinfo.c
+++ b/boot_control_copy/bootinfo.c
@@ -116,12 +116,12 @@
   return fd;
 }
 
-// As per struct bootloader_message which is defined in
+// As per struct bootloader_message_ab which is defined in
 // bootable/recovery/bootloader.h we can use the 32 bytes in the
 // bootctrl_suffix field provided that they start with the active slot
 // suffix terminated by NUL. It just so happens that BrilloBootInfo is
 // laid out this way.
-#define BOOTINFO_OFFSET offsetof(struct bootloader_message, slot_suffix)
+#define BOOTINFO_OFFSET offsetof(struct bootloader_message_ab, slot_suffix)
 
 bool boot_info_load(BrilloBootInfo *out_info)
 {
diff --git a/ext4_utils/ext4.h b/ext4_utils/ext4.h
index ac6f97e..974fb2d 100644
--- a/ext4_utils/ext4.h
+++ b/ext4_utils/ext4.h
@@ -302,11 +302,12 @@
 #define EXT4_EPOCH_MASK ((1 << EXT4_EPOCH_BITS) - 1)
 #define EXT4_NSEC_MASK (~0UL << EXT4_EPOCH_BITS)
 
-#define EXT4_FITS_IN_INODE(ext4_inode, einode, field)   ((offsetof(typeof(*ext4_inode), field) +   sizeof((ext4_inode)->field))   <= (EXT4_GOOD_OLD_INODE_SIZE +   (einode)->i_extra_isize))  
+#define EXT4_FITS_IN_INODE(ext4_inode, einode, field) ((offsetof(typeof(*(ext4_inode)), field) +   sizeof((ext4_inode)->field))   <= (EXT4_GOOD_OLD_INODE_SIZE +   (einode)->i_extra_isize))
 #define EXT4_INODE_SET_XTIME(xtime, inode, raw_inode)  do {   (raw_inode)->xtime = cpu_to_le32((inode)->xtime.tv_sec);   if (EXT4_FITS_IN_INODE(raw_inode, EXT4_I(inode), xtime ## _extra))   (raw_inode)->xtime ## _extra =   ext4_encode_extra_time(&(inode)->xtime);  } while (0)
 #define EXT4_EINODE_SET_XTIME(xtime, einode, raw_inode)  do {   if (EXT4_FITS_IN_INODE(raw_inode, einode, xtime))   (raw_inode)->xtime = cpu_to_le32((einode)->xtime.tv_sec);   if (EXT4_FITS_IN_INODE(raw_inode, einode, xtime ## _extra))   (raw_inode)->xtime ## _extra =   ext4_encode_extra_time(&(einode)->xtime);  } while (0)
-#define EXT4_INODE_GET_XTIME(xtime, inode, raw_inode)  do {   (inode)->xtime.tv_sec = (signed)le32_to_cpu((raw_inode)->xtime);   if (EXT4_FITS_IN_INODE(raw_inode, EXT4_I(inode), xtime ## _extra))   ext4_decode_extra_time(&(inode)->xtime,   raw_inode->xtime ## _extra);  } while (0)
-#define EXT4_EINODE_GET_XTIME(xtime, einode, raw_inode)  do {   if (EXT4_FITS_IN_INODE(raw_inode, einode, xtime))   (einode)->xtime.tv_sec =   (signed)le32_to_cpu((raw_inode)->xtime);   if (EXT4_FITS_IN_INODE(raw_inode, einode, xtime ## _extra))   ext4_decode_extra_time(&(einode)->xtime,   raw_inode->xtime ## _extra);  } while (0)
+#define EXT4_INODE_GET_XTIME(xtime, inode, raw_inode)  do {   (inode)->xtime.tv_sec = (signed)le32_to_cpu((raw_inode)->xtime);   if (EXT4_FITS_IN_INODE(raw_inode, EXT4_I(inode), xtime ## _extra)) ext4_decode_extra_time(&(inode)->xtime,   (raw_inode)->xtime ## _extra);  } while (0)
+
+#define EXT4_EINODE_GET_XTIME(xtime, einode, raw_inode)  do {   if (EXT4_FITS_IN_INODE(raw_inode, einode, xtime))   (einode)->xtime.tv_sec =   (signed)le32_to_cpu((raw_inode)->xtime);   if (EXT4_FITS_IN_INODE(raw_inode, einode, xtime ## _extra))   ext4_decode_extra_time(&(einode)->xtime,   (raw_inode)->xtime ## _extra);  } while (0)
 #define i_disk_version osd1.linux1.l_i_version
 
 #define i_reserved1 osd1.linux1.l_i_reserved1
@@ -562,7 +563,7 @@
 #define EXT4_DIR_REC_LEN(name_len) (((name_len) + 8 + EXT4_DIR_ROUND) &   ~EXT4_DIR_ROUND)
 #define EXT4_MAX_REC_LEN ((1<<16)-1)
 
-#define is_dx(dir) (EXT4_HAS_COMPAT_FEATURE(dir->i_sb,   EXT4_FEATURE_COMPAT_DIR_INDEX) &&   (EXT4_I(dir)->i_flags & EXT4_INDEX_FL))
+#define is_dx(dir) (EXT4_HAS_COMPAT_FEATURE((dir)->i_sb,   EXT4_FEATURE_COMPAT_DIR_INDEX) &&   (EXT4_I(dir)->i_flags & EXT4_INDEX_FL))
 #define EXT4_DIR_LINK_MAX(dir) (!is_dx(dir) && (dir)->i_nlink >= EXT4_LINK_MAX)
 #define EXT4_DIR_LINK_EMPTY(dir) ((dir)->i_nlink == 2 || (dir)->i_nlink == 1)
 
diff --git a/ext4_utils/ext4fixup.c b/ext4_utils/ext4fixup.c
index 184cd0d..4b40207 100644
--- a/ext4_utils/ext4fixup.c
+++ b/ext4_utils/ext4fixup.c
@@ -806,6 +806,7 @@
     }
 
     close(fd);
+    free(dirbuf);
 
     return 0;
 }
diff --git a/ext4_utils/key_control.h b/ext4_utils/key_control.h
index bbf0ace..748c32d 100644
--- a/ext4_utils/key_control.h
+++ b/ext4_utils/key_control.h
@@ -8,12 +8,12 @@
 typedef int32_t key_serial_t;
 
 // special process keyring shortcut IDs
-#define KEY_SPEC_THREAD_KEYRING       -1 // key ID for thread-specific keyring
-#define KEY_SPEC_PROCESS_KEYRING      -2 // key ID for process-specific keyring
-#define KEY_SPEC_SESSION_KEYRING      -3 // key ID for session-specific keyring
-#define KEY_SPEC_USER_KEYRING         -4 // key ID for UID-specific keyring
-#define KEY_SPEC_USER_SESSION_KEYRING -5 // key ID for UID-session keyring
-#define KEY_SPEC_GROUP_KEYRING        -6 // key ID for GID-specific keyring
+#define KEY_SPEC_THREAD_KEYRING       (-1) // key ID for thread-specific keyring
+#define KEY_SPEC_PROCESS_KEYRING      (-2) // key ID for process-specific keyring
+#define KEY_SPEC_SESSION_KEYRING      (-3) // key ID for session-specific keyring
+#define KEY_SPEC_USER_KEYRING         (-4) // key ID for UID-specific keyring
+#define KEY_SPEC_USER_SESSION_KEYRING (-5) // key ID for UID-session keyring
+#define KEY_SPEC_GROUP_KEYRING        (-6) // key ID for GID-specific keyring
 
 key_serial_t add_key(const char *type,
                      const char *description,
diff --git a/ext4_utils/sha1.c b/ext4_utils/sha1.c
index 463ec38..5a8a02f 100644
--- a/ext4_utils/sha1.c
+++ b/ext4_utils/sha1.c
@@ -44,17 +44,17 @@
 #else
 # define blk0(i) block->l[i]
 #endif
-#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \
-    ^block->l[(i+2)&15]^block->l[i&15],1))
+#define blk(i) (block->l[(i)&15] = rol(block->l[((i)+13)&15]^block->l[((i)+8)&15] \
+    ^block->l[((i)+2)&15]^block->l[(i)&15],1))
 
 /*
  * (R0+R1), R2, R3, R4 are the different operations (rounds) used in SHA1
  */
-#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30);
-#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30);
-#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30);
-#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30);
-#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30);
+#define R0(v,w,x,y,z,i) z+=(((w)&((x)^(y)))^(y))+blk0(i)+0x5A827999+rol(v,5);(w)=rol(w,30);
+#define R1(v,w,x,y,z,i) z+=(((w)&((x)^(y)))^(y))+blk(i)+0x5A827999+rol(v,5);(w)=rol(w,30);
+#define R2(v,w,x,y,z,i) z+=((w)^(x)^(y))+blk(i)+0x6ED9EBA1+rol(v,5);(w)=rol(w,30);
+#define R3(v,w,x,y,z,i) z+=((((w)|(x))&(y))|((w)&(x)))+blk(i)+0x8F1BBCDC+rol(v,5);(w)=rol(w,30);
+#define R4(v,w,x,y,z,i) z+=((w)^(x)^(y))+blk(i)+0xCA62C1D6+rol(v,5);(w)=rol(w,30);
 
 typedef union {
     u_char c[64];
diff --git a/f2fs_utils/Android.mk b/f2fs_utils/Android.mk
index 647c390..82c3ee0 100644
--- a/f2fs_utils/Android.mk
+++ b/f2fs_utils/Android.mk
@@ -76,7 +76,7 @@
 include $(CLEAR_VARS)
 LOCAL_MODULE := libf2fs_sparseblock
 LOCAL_SRC_FILES := f2fs_sparseblock.c
-LOCAL_SHARED_LIBRARIES := libcutils
+LOCAL_SHARED_LIBRARIES := liblog libcutils
 LOCAL_C_INCLUDES := external/f2fs-tools/include \
 		system/core/include/log
 include $(BUILD_SHARED_LIBRARY)
@@ -84,7 +84,7 @@
 include $(CLEAR_VARS)
 LOCAL_MODULE := f2fs_sparseblock
 LOCAL_SRC_FILES := f2fs_sparseblock.c
-LOCAL_SHARED_LIBRARIES := libcutils
+LOCAL_SHARED_LIBRARIES := liblog libcutils
 LOCAL_C_INCLUDES := external/f2fs-tools/include \
 		system/core/include/log
 include $(BUILD_EXECUTABLE)
diff --git a/f2fs_utils/f2fs_sparseblock.c b/f2fs_utils/f2fs_sparseblock.c
index e39a61f..e968ce0 100644
--- a/f2fs_utils/f2fs_sparseblock.c
+++ b/f2fs_utils/f2fs_sparseblock.c
@@ -26,9 +26,9 @@
       #member, le64_to_cpu((ptr)->member), le64_to_cpu((ptr)->member) );  \
   } while (0);
 
-#define segno_in_journal(sum, i)    (sum->sit_j.entries[i].segno)
+#define segno_in_journal(sum, i)    ((sum)->sit_j.entries[i].segno)
 
-#define sit_in_journal(sum, i)      (sum->sit_j.entries[i].se)
+#define sit_in_journal(sum, i)      ((sum)->sit_j.entries[i].se)
 
 static void dbg_print_raw_sb_info(struct f2fs_super_block *sb)
 {
diff --git a/iotop/iotop.cpp b/iotop/iotop.cpp
index 828fb32..e2582e8 100644
--- a/iotop/iotop.cpp
+++ b/iotop/iotop.cpp
@@ -53,7 +53,7 @@
 }
 
 using Sorter = std::function<void(std::vector<TaskStatistics>&)>;
-static Sorter GetSorter(const std::string field) {
+static Sorter GetSorter(const std::string& field) {
   // Generic comparator
   static auto comparator = [](auto& lhs, auto& rhs, auto field, bool ascending) -> bool {
     auto a = (lhs.*field)();
diff --git a/iotop/tasklist.cpp b/iotop/tasklist.cpp
index f81143f..0708741 100644
--- a/iotop/tasklist.cpp
+++ b/iotop/tasklist.cpp
@@ -28,7 +28,7 @@
 #include "tasklist.h"
 
 template<typename Func>
-static bool ScanPidsInDir(std::string name, Func f) {
+static bool ScanPidsInDir(const std::string& name, Func f) {
   std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(name.c_str()), closedir);
   if (!dir) {
     return false;
diff --git a/iotop/taskstats.h b/iotop/taskstats.h
index f5ad2d2..bbf7e9e 100644
--- a/iotop/taskstats.h
+++ b/iotop/taskstats.h
@@ -25,7 +25,7 @@
 
 class TaskStatistics {
 public:
-  TaskStatistics(const taskstats&);
+  explicit TaskStatistics(const taskstats&);
   TaskStatistics() = default;
   TaskStatistics(const TaskStatistics&) = default;
   void AddPidToTgid(const TaskStatistics&);
diff --git a/kexec_tools/kexecload.c b/kexec_tools/kexecload.c
index 18f5e64..2bf3d18 100644
--- a/kexec_tools/kexecload.c
+++ b/kexec_tools/kexecload.c
@@ -21,7 +21,7 @@
 // Physical buffer address cannot overlap with other regions
 #define START_ADDRESS 0x44000000
 
-#define ROUND_TO_PAGE(address,pagesize) ((address + pagesize - 1) & (~(pagesize - 1)))
+#define ROUND_TO_PAGE(address,pagesize) (((address) + (pagesize) - 1) & (~((pagesize) - 1)))
 
 /*
  * Gives file position and resets current position to begining of file
diff --git a/ksmutils/ksminfo.c b/ksmutils/ksminfo.c
index 278692f..a0b03a1 100644
--- a/ksmutils/ksminfo.c
+++ b/ksmutils/ksminfo.c
@@ -241,7 +241,7 @@
                         pagemap[i]);
                 continue;
             }
-            if (!(flags & PM_PAGE_KSM)) {
+            if (!(flags & KPF_KSM)) {
                 continue;
             }
             vaddr = pm_map_start(maps[i]) + j * pm_kernel_pagesize(ker);
@@ -482,4 +482,3 @@
 
     return rc;
 }
-
diff --git a/ksmutils/lookup3.c b/ksmutils/lookup3.c
index e607295..8fcc325 100644
--- a/ksmutils/lookup3.c
+++ b/ksmutils/lookup3.c
@@ -114,12 +114,12 @@
 */
 #define mix(a,b,c) \
 { \
-  a -= c;  a ^= rot(c, 4);  c += b; \
-  b -= a;  b ^= rot(a, 6);  a += c; \
-  c -= b;  c ^= rot(b, 8);  b += a; \
-  a -= c;  a ^= rot(c,16);  c += b; \
-  b -= a;  b ^= rot(a,19);  a += c; \
-  c -= b;  c ^= rot(b, 4);  b += a; \
+  (a) -= (c);  (a) ^= rot(c, 4);  (c) += (b); \
+  (b) -= (a);  (b) ^= rot(a, 6);  (a) += (c); \
+  (c) -= (b);  (c) ^= rot(b, 8);  (b) += (a); \
+  (a) -= (c);  (a) ^= rot(c,16);  (c) += (b); \
+  (b) -= (a);  (b) ^= rot(a,19);  (a) += (c); \
+  (c) -= (b);  (c) ^= rot(b, 4);  (b) += (a); \
 }
 
 /*
@@ -149,13 +149,13 @@
 */
 #define final(a,b,c) \
 { \
-  c ^= b; c -= rot(b,14); \
-  a ^= c; a -= rot(c,11); \
-  b ^= a; b -= rot(a,25); \
-  c ^= b; c -= rot(b,16); \
-  a ^= c; a -= rot(c,4);  \
-  b ^= a; b -= rot(a,14); \
-  c ^= b; c -= rot(b,24); \
+  (c) ^= (b); (c) -= rot(b,14); \
+  (a) ^= (c); (a) -= rot(c,11); \
+  (b) ^= (a); (b) -= rot(a,25); \
+  (c) ^= (b); (c) -= rot(b,16); \
+  (a) ^= (c); (a) -= rot(c,4);  \
+  (b) ^= (a); (b) -= rot(a,14); \
+  (c) ^= (b); (c) -= rot(b,24); \
 }
 
 /*
diff --git a/libfec/Android.mk b/libfec/Android.mk
index 45fb19e..b92dd4e 100644
--- a/libfec/Android.mk
+++ b/libfec/Android.mk
@@ -17,8 +17,8 @@
     fec_process.cpp
 
 common_static_libraries := \
-    libmincrypt \
-    libcrypto_static \
+    libcrypto_utils \
+    libcrypto \
     libcutils \
     libbase
 
diff --git a/libfec/fec_open.cpp b/libfec/fec_open.cpp
index 0e41bf4..caec370 100644
--- a/libfec/fec_open.cpp
+++ b/libfec/fec_open.cpp
@@ -145,13 +145,6 @@
         error("inconsistent ecc size %u", header.fec_size);
         return -1;
     }
-    /* structure: data | ecc | header */
-    if (offset < header.fec_size ||
-            offset - header.fec_size != header.inp_size) {
-        error("unexpected input size: %" PRIu64 " vs %" PRIu64, offset,
-            header.inp_size);
-        return -1;
-    }
 
     f->data_size = header.inp_size;
     f->ecc.blocks = fec_div_round_up(f->data_size, FEC_BLOCKSIZE);
diff --git a/libfec/fec_private.h b/libfec/fec_private.h
index 238c4e2..0d94228 100644
--- a/libfec/fec_private.h
+++ b/libfec/fec_private.h
@@ -23,17 +23,17 @@
 #include <new>
 #include <pthread.h>
 #include <stdio.h>
-#include <string>
 #include <string.h>
+#include <string>
 #include <sys/syscall.h>
 #include <unistd.h>
 #include <vector>
 
-#include <utils/Compat.h>
-#include <mincrypt/rsa.h>
-#include <openssl/sha.h>
-#include <fec/io.h>
+#include <crypto_utils/android_pubkey.h>
 #include <fec/ecc.h>
+#include <fec/io.h>
+#include <openssl/sha.h>
+#include <utils/Compat.h>
 
 /* processing parameters */
 #define WORK_MIN_THREADS 1
@@ -59,7 +59,7 @@
 struct verity_header {
     uint32_t magic;
     uint32_t version;
-    uint8_t signature[RSANUMBYTES];
+    uint8_t signature[ANDROID_PUBKEY_MODULUS_SIZE];
     uint32_t length;
 };
 
diff --git a/libfec/fec_process.cpp b/libfec/fec_process.cpp
index 3b2846c..6e0ddd1 100644
--- a/libfec/fec_process.cpp
+++ b/libfec/fec_process.cpp
@@ -62,11 +62,13 @@
     uint64_t start = (offset / FEC_BLOCKSIZE) * FEC_BLOCKSIZE;
     size_t blocks = fec_div_round_up(count, FEC_BLOCKSIZE);
 
-    if ((size_t)threads > blocks) {
-        threads = (int)blocks;
+    size_t count_per_thread = fec_div_round_up(blocks, threads) * FEC_BLOCKSIZE;
+    size_t max_threads = fec_div_round_up(count, count_per_thread);
+
+    if ((size_t)threads > max_threads) {
+        threads = (int)max_threads;
     }
 
-    size_t count_per_thread = fec_div_round_up(blocks, threads) * FEC_BLOCKSIZE;
     size_t left = count;
     uint64_t pos = offset;
     uint64_t end = start + count_per_thread;
diff --git a/libfec/fec_read.cpp b/libfec/fec_read.cpp
index 2d29da8..0f5ec99 100644
--- a/libfec/fec_read.cpp
+++ b/libfec/fec_read.cpp
@@ -47,7 +47,9 @@
 
         for (size_t m = 0; m < bytes_per_line; ++m) {
             if (n + m < size) {
-                sprintf(&hex[m * 3], "%02x ", data[n + m]);
+                ptrdiff_t offset = &hex[m * 3] - hex;
+                snprintf(hex + offset, sizeof(hex) - offset, "%02x ",
+                         data[n + m]);
 
                 if (isprint(data[n + m])) {
                     prn[m] = data[n + m];
diff --git a/libfec/fec_verity.cpp b/libfec/fec_verity.cpp
index 393e962..5dea53d 100644
--- a/libfec/fec_verity.cpp
+++ b/libfec/fec_verity.cpp
@@ -349,7 +349,7 @@
 
     auto tokens = android::base::Split(table.get(), " ");
 
-    for (const auto token : tokens) {
+    for (const auto& token : tokens) {
         switch (i++) {
         case 0: /* version */
             if (token != stringify(VERITY_TABLE_VERSION)) {
diff --git a/libfec/include/fec/io.h b/libfec/include/fec/io.h
index 670a5d7..5e597ac 100644
--- a/libfec/include/fec/io.h
+++ b/libfec/include/fec/io.h
@@ -24,7 +24,8 @@
 #include <stdio.h>
 #include <sys/types.h>
 #include <unistd.h>
-#include <mincrypt/rsa.h>
+
+#include <crypto_utils/android_pubkey.h>
 
 #ifdef __cplusplus
 extern "C" {
@@ -70,8 +71,8 @@
 struct fec_verity_metadata {
     bool disabled;
     uint64_t data_size;
-    uint8_t signature[RSANUMBYTES];
-    uint8_t ecc_signature[RSANUMBYTES];
+    uint8_t signature[ANDROID_PUBKEY_MODULUS_SIZE];
+    uint8_t ecc_signature[ANDROID_PUBKEY_MODULUS_SIZE];
     const char *table;
     uint32_t table_length;
 };
@@ -122,7 +123,7 @@
     public:
         io() : handle_(nullptr, fec_close) {}
 
-        io(const std::string& fn, int mode = O_RDONLY, int flags = 0,
+        explicit io(const std::string& fn, int mode = O_RDONLY, int flags = 0,
                 int roots = FEC_DEFAULT_ROOTS) : handle_(nullptr, fec_close) {
             open(fn, mode, flags, roots);
         }
diff --git a/libfec/test/Android.mk b/libfec/test/Android.mk
index a2bba55..fce07cf 100644
--- a/libfec/test/Android.mk
+++ b/libfec/test/Android.mk
@@ -11,7 +11,8 @@
 LOCAL_STATIC_LIBRARIES := \
     libfec_host \
     libfec_rs_host \
-    libcrypto_static \
+    libcrypto_utils \
+    libcrypto \
     libext4_utils_host \
     libsquashfs_utils_host \
     libbase
diff --git a/libpagemap/Android.bp b/libpagemap/Android.bp
new file mode 100644
index 0000000..e8cc4af
--- /dev/null
+++ b/libpagemap/Android.bp
@@ -0,0 +1,32 @@
+// Copyright (C) 2008 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_library_shared {
+    name: "libpagemap",
+    srcs: [
+        "pm_kernel.c",
+        "pm_process.c",
+        "pm_map.c",
+        "pm_memusage.c",
+    ],
+    local_include_dirs: ["include"],
+    cflags: ["-Wno-unused-parameter"],
+    export_include_dirs: ["include"],
+}
+
+cc_test {
+    name: "pagemap_test",
+    srcs: ["pagemap_test.cpp"],
+    shared_libs: ["libpagemap"],
+}
diff --git a/libpagemap/Android.mk b/libpagemap/Android.mk
deleted file mode 100644
index 65d466c..0000000
--- a/libpagemap/Android.mk
+++ /dev/null
@@ -1,36 +0,0 @@
-# Copyright (C) 2008 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH := $(call my-dir)
-
-pagemap_src_files := \
-    pm_kernel.c \
-    pm_process.c \
-    pm_map.c \
-    pm_memusage.c \
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := libpagemap
-LOCAL_MODULE_TAGS := debug
-LOCAL_SRC_FILES := $(pagemap_src_files)
-LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
-LOCAL_CFLAGS := -Wno-unused-parameter
-LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
-include $(BUILD_SHARED_LIBRARY)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := pagemap_test
-LOCAL_SRC_FILES := pagemap_test.cpp
-LOCAL_SHARED_LIBRARIES := libpagemap
-include $(BUILD_NATIVE_TEST)
diff --git a/libpagemap/include/pagemap/pagemap.h b/libpagemap/include/pagemap/pagemap.h
index 4de2b4b..b03614e 100644
--- a/libpagemap/include/pagemap/pagemap.h
+++ b/libpagemap/include/pagemap/pagemap.h
@@ -23,6 +23,8 @@
 #include <sys/types.h>
 #include <sys/queue.h>
 
+#include <linux/kernel-page-flags.h>
+
 __BEGIN_DECLS
 
 typedef struct pm_proportional_swap pm_proportional_swap_t;
@@ -124,39 +126,11 @@
 int pm_kernel_count(pm_kernel_t *ker, uint64_t pfn, uint64_t *count_out);
 
 /* Get the page flags (from /proc/kpageflags) of a physical frame.
- * The count is returned through *flags_out. */
+ * Flag constants are in <linux/kernel-page-flags.h>.
+ * The count is returned through *flags_out.
+ */
 int pm_kernel_flags(pm_kernel_t *ker, uint64_t pfn, uint64_t *flags_out);
 
-#define PM_PAGE_LOCKED     (1 <<  0)
-#define PM_PAGE_ERROR      (1 <<  1)
-#define PM_PAGE_REFERENCED (1 <<  2)
-#define PM_PAGE_UPTODATE   (1 <<  3)
-#define PM_PAGE_DIRTY      (1 <<  4)
-#define PM_PAGE_LRU        (1 <<  5)
-#define PM_PAGE_ACTIVE     (1 <<  6)
-#define PM_PAGE_SLAB       (1 <<  7)
-#define PM_PAGE_WRITEBACK  (1 <<  8)
-#define PM_PAGE_RECLAIM    (1 <<  9)
-#define PM_PAGE_BUDDY      (1 << 10)
-
-/* for kernels >= 2.6.31 */
-#define PM_PAGE_MMAP          (1 << 11)
-#define PM_PAGE_ANON          (1 << 12)
-#define PM_PAGE_SWAPCACHE     (1 << 13)
-#define PM_PAGE_SWAPBACKED    (1 << 14)
-#define PM_PAGE_COMPOUND_HEAD (1 << 15)
-#define PM_PAGE_COMPOUND_TAIL (1 << 16)
-#define PM_PAGE_HUGE          (1 << 17)
-#define PM_PAGE_UNEVICTABLE   (1 << 18)
-#define PM_PAGE_HWPOISON      (1 << 19)
-#define PM_PAGE_NOPAGE        (1 << 20)
-
-/* for kernels >= 2.6.32 */
-#define PM_PAGE_KSM           (1 << 21)
-
-/* for kernels >= 3.4 */
-#define PM_PAGE_THP           (1 << 22)
-
 /* Destroy a pm_kernel_t. */
 int pm_kernel_destroy(pm_kernel_t *ker);
 
@@ -180,13 +154,13 @@
 int pm_process_workingset(pm_process_t *proc, pm_memusage_t *ws_out, int reset);
 
 /* Get the PFNs corresponding to a range of virtual addresses.
- * The array of PFNs is returned through *range_out, and the caller has the 
+ * The array of PFNs is returned through *range_out, and the caller has the
  * responsibility to free it. */
 int pm_process_pagemap_range(pm_process_t *proc,
                              uint64_t low, uint64_t hi,
                              uint64_t **range_out, size_t *len);
 
-#define _BITS(x, offset, bits) (((x) >> offset) & ((1LL << (bits)) - 1))
+#define _BITS(x, offset, bits) (((x) >> (offset)) & ((1LL << (bits)) - 1))
 
 #define PM_PAGEMAP_PRESENT(x)     (_BITS(x, 63, 1))
 #define PM_PAGEMAP_SWAPPED(x)     (_BITS(x, 62, 1))
@@ -197,7 +171,7 @@
 
 /* Get the maps in the virtual address space of this process.
  * Returns an array of pointers to pm_map_t through *maps.
- * The array should be freed by the caller, but the maps should not be 
+ * The array should be freed by the caller, but the maps should not be
  * modified or destroyed. */
 int pm_process_maps(pm_process_t *proc, pm_map_t ***maps_out, size_t *len);
 
diff --git a/libpagemap/pm_map.c b/libpagemap/pm_map.c
index 301a1cc..0060a98 100644
--- a/libpagemap/pm_map.c
+++ b/libpagemap/pm_map.c
@@ -109,7 +109,7 @@
                                 &flags);
         if (error) goto out;
 
-        if (!(flags & PM_PAGE_REFERENCED))
+        if (!(flags & KPF_REFERENCED))
             continue;
 
         error = pm_kernel_count(map->proc->ker, PM_PAGEMAP_PFN(pagemap[i]),
diff --git a/librank/librank.c b/librank/librank.c
index 5c7c64e..a525f23 100644
--- a/librank/librank.c
+++ b/librank/librank.c
@@ -135,7 +135,7 @@
 
     if (library->mappings_count >= library->mappings_size) {
         library->mappings = realloc(library->mappings,
-            2 * library->mappings_size * sizeof(struct mapping*));
+            2 * library->mappings_size * sizeof(struct mapping_info*));
         if (!library->mappings) {
             fprintf(stderr, "Couldn't resize mappings array: %s\n", strerror(errno));
             exit(EXIT_FAILURE);
@@ -264,15 +264,15 @@
             break;
         case 'c':
             required_flags = 0;
-            flags_mask = PM_PAGE_SWAPBACKED;
+            flags_mask = KPF_SWAPBACKED;
             break;
         case 'C':
-            required_flags = PM_PAGE_SWAPBACKED;
-            flags_mask = PM_PAGE_SWAPBACKED;
+            required_flags = KPF_SWAPBACKED;
+            flags_mask = KPF_SWAPBACKED;
             break;
         case 'k':
-            required_flags = PM_PAGE_KSM;
-            flags_mask = PM_PAGE_KSM;
+            required_flags = KPF_KSM;
+            flags_mask = KPF_KSM;
             break;
         case 'h':
             usage(argv[0]);
diff --git a/memory_replay/Action.cpp b/memory_replay/Action.cpp
index c9671e1..216ff9d 100644
--- a/memory_replay/Action.cpp
+++ b/memory_replay/Action.cpp
@@ -47,7 +47,7 @@
 
 class AllocAction : public Action {
  public:
-  AllocAction(uintptr_t key_pointer) : key_pointer_(key_pointer) {}
+  explicit AllocAction(uintptr_t key_pointer) : key_pointer_(key_pointer) {}
 
  protected:
   uintptr_t key_pointer_ = 0;
@@ -152,7 +152,7 @@
 
 class FreeAction : public AllocAction {
  public:
-  FreeAction(uintptr_t key_pointer) : AllocAction(key_pointer) {
+  explicit FreeAction(uintptr_t key_pointer) : AllocAction(key_pointer) {
   }
 
   bool DoesFree() override { return key_pointer_ != 0; }
diff --git a/memory_replay/Pointers.h b/memory_replay/Pointers.h
index 2491716..c7cd825 100644
--- a/memory_replay/Pointers.h
+++ b/memory_replay/Pointers.h
@@ -27,7 +27,7 @@
 
 class Pointers {
  public:
-  Pointers(size_t max_allocs);
+  explicit Pointers(size_t max_allocs);
   virtual ~Pointers();
 
   void Add(uintptr_t key_pointer, void* pointer);
diff --git a/micro_bench/micro_bench.cpp b/micro_bench/micro_bench.cpp
index c20f7d8..4d3177d 100644
--- a/micro_bench/micro_bench.cpp
+++ b/micro_bench/micro_bench.cpp
@@ -33,7 +33,7 @@
 #define DEFAULT_DATA_SIZE       1000000000
 
 // The amount of memory allocated for the cold benchmarks to use.
-#define DEFAULT_COLD_DATA_SIZE  128*1024*1024
+#define DEFAULT_COLD_DATA_SIZE  (128*1024*1024)
 
 // The default size of the stride between each buffer for cold benchmarks.
 #define DEFAULT_COLD_STRIDE_SIZE  4096
@@ -197,9 +197,9 @@
 
 #define MAINLOOP(cmd_data, BENCH, COMPUTE_AVG, PRINT_ITER, PRINT_AVG) \
     uint64_t time_ns;                                                 \
-    int iters = cmd_data.args[1];                                     \
-    bool print_average = cmd_data.print_average;                      \
-    bool print_each_iter = cmd_data.print_each_iter;                  \
+    int iters = (cmd_data).args[1];                                   \
+    bool print_average = (cmd_data).print_average;                    \
+    bool print_each_iter = (cmd_data).print_each_iter;                \
     double min = 0.0, max = 0.0, running_avg = 0.0, square_avg = 0.0; \
     double avg;                                                       \
     for (int i = 0; iters == -1 || i < iters; i++) {                  \
@@ -226,7 +226,7 @@
     }
 
 #define MAINLOOP_DATA(name, cmd_data, size, BENCH)                    \
-    size_t copies = cmd_data.data_size/size;                          \
+    size_t copies = (cmd_data).data_size/(size);                      \
     size_t j;                                                         \
     MAINLOOP(cmd_data,                                                \
              for (j = 0; j < copies; j++) {                           \
@@ -239,14 +239,14 @@
                           std_dev, min, max));
 
 #define MAINLOOP_COLD(name, cmd_data, size, num_incrs, BENCH)                 \
-    size_t num_strides = num_buffers / num_incrs;                             \
-    if ((num_buffers % num_incrs) != 0) {                                     \
+    size_t num_strides = num_buffers / (num_incrs);                           \
+    if ((num_buffers % (num_incrs)) != 0) {                                   \
         num_strides--;                                                        \
     }                                                                         \
     size_t copies = 1;                                                        \
-    num_buffers = num_incrs * num_strides;                                    \
-    if (num_buffers * size < static_cast<size_t>(cmd_data.data_size)) {       \
-        copies = cmd_data.data_size / (num_buffers * size);                   \
+    num_buffers = (num_incrs) * num_strides;                                  \
+    if (num_buffers * (size) < static_cast<size_t>((cmd_data).data_size)) {   \
+        copies = (cmd_data).data_size / (num_buffers * (size));               \
     }                                                                         \
     if (num_strides == 0) {                                                   \
         printf("%s: Chosen options lead to no copies, aborting.\n", name);    \
@@ -255,7 +255,7 @@
     size_t j, k;                                                              \
     MAINLOOP(cmd_data,                                                        \
              for (j = 0; j < copies; j++) {                                   \
-                 for (k = 0; k < num_incrs; k++) {                            \
+                 for (k = 0; k < (num_incrs); k++) {                          \
                      BENCH;                                                   \
                 }                                                             \
             },                                                                \
@@ -271,8 +271,8 @@
 //        be executed once.
 // BENCH - The actual code to benchmark and is timed.
 #define BENCH_ONE_BUF(name, cmd_data, INIT, BENCH)                            \
-    size_t size = cmd_data.args[0]; \
-    uint8_t *buf = allocateAlignedMemory(size, cmd_data.dst_align, cmd_data.dst_or_mask); \
+    size_t size = (cmd_data).args[0];                                         \
+    uint8_t *buf = allocateAlignedMemory(size, (cmd_data).dst_align, (cmd_data).dst_or_mask); \
     if (!buf)                                                                 \
         return -1;                                                            \
     INIT;                                                                     \
@@ -285,14 +285,14 @@
 //        be executed once.
 // BENCH - The actual code to benchmark and is timed.
 #define BENCH_TWO_BUFS(name, cmd_data, INIT, BENCH)                           \
-    size_t size = cmd_data.args[0];                                           \
-    uint8_t *buf1 = allocateAlignedMemory(size, cmd_data.src_align, cmd_data.src_or_mask); \
+    size_t size = (cmd_data).args[0];                                         \
+    uint8_t *buf1 = allocateAlignedMemory(size, (cmd_data).src_align, (cmd_data).src_or_mask); \
     if (!buf1)                                                                \
         return -1;                                                            \
     size_t total_size = size;                                                 \
-    if (cmd_data.dst_str_size > 0)                                            \
-        total_size += cmd_data.dst_str_size;                                  \
-    uint8_t *buf2 = allocateAlignedMemory(total_size, cmd_data.dst_align, cmd_data.dst_or_mask); \
+    if ((cmd_data).dst_str_size > 0)                                          \
+        total_size += (cmd_data).dst_str_size;                                \
+    uint8_t *buf2 = allocateAlignedMemory(total_size, (cmd_data).dst_align, (cmd_data).dst_or_mask); \
     if (!buf2)                                                                \
         return -1;                                                            \
     INIT;                                                                     \
@@ -312,19 +312,19 @@
 //        be executed once.
 // BENCH - The actual code to benchmark and is timed.
 #define COLD_ONE_BUF(name, cmd_data, INIT, BENCH)                             \
-    size_t size = cmd_data.args[0];                                           \
-    size_t incr = getAlignmentIncrement(size, cmd_data.dst_align);            \
-    size_t num_buffers = cmd_data.cold_data_size / incr;                      \
+    size_t size = (cmd_data).args[0];                                         \
+    size_t incr = getAlignmentIncrement(size, (cmd_data).dst_align);          \
+    size_t num_buffers = (cmd_data).cold_data_size / incr;                    \
     size_t buffer_size = num_buffers * incr;                                  \
-    uint8_t *buffer = getColdBuffer(num_buffers, incr, cmd_data.dst_align, cmd_data.dst_or_mask); \
+    uint8_t *buffer = getColdBuffer(num_buffers, incr, (cmd_data).dst_align, (cmd_data).dst_or_mask); \
     if (!buffer)                                                              \
         return -1;                                                            \
-    size_t num_incrs = cmd_data.cold_stride_size / incr + 1;                  \
+    size_t num_incrs = (cmd_data).cold_stride_size / incr + 1;                \
     size_t stride_incr = incr * num_incrs;                                    \
     uint8_t *buf;                                                             \
     size_t l;                                                                 \
     INIT;                                                                     \
-    MAINLOOP_COLD(name, cmd_data, size, num_incrs,                            \
+    MAINLOOP_COLD(name, (cmd_data), size, num_incrs,                          \
                   buf = buffer + k * incr;                                    \
                   for (l = 0; l < num_strides; l++) {                         \
                       BENCH;                                                  \
@@ -345,31 +345,31 @@
 //        be executed once.
 // BENCH - The actual code to benchmark and is timed.
 #define COLD_TWO_BUFS(name, cmd_data, INIT, BENCH)                            \
-    size_t size = cmd_data.args[0];                                           \
-    size_t buf1_incr = getAlignmentIncrement(size, cmd_data.src_align);       \
+    size_t size = (cmd_data).args[0];                                         \
+    size_t buf1_incr = getAlignmentIncrement(size, (cmd_data).src_align);     \
     size_t total_size = size;                                                 \
-    if (cmd_data.dst_str_size > 0)                                            \
-        total_size += cmd_data.dst_str_size;                                  \
-    size_t buf2_incr = getAlignmentIncrement(total_size, cmd_data.dst_align); \
+    if ((cmd_data).dst_str_size > 0)                                          \
+        total_size += (cmd_data).dst_str_size;                                \
+    size_t buf2_incr = getAlignmentIncrement(total_size, (cmd_data).dst_align); \
     size_t max_incr = (buf1_incr > buf2_incr) ? buf1_incr : buf2_incr;        \
-    size_t num_buffers = cmd_data.cold_data_size / max_incr;                  \
+    size_t num_buffers = (cmd_data).cold_data_size / max_incr;                \
     size_t buffer1_size = num_buffers * buf1_incr;                            \
     size_t buffer2_size = num_buffers * buf2_incr;                            \
-    uint8_t *buffer1 = getColdBuffer(num_buffers, buf1_incr, cmd_data.src_align, cmd_data.src_or_mask); \
+    uint8_t *buffer1 = getColdBuffer(num_buffers, buf1_incr, (cmd_data).src_align, (cmd_data).src_or_mask); \
     if (!buffer1)                                                             \
         return -1;                                                            \
-    uint8_t *buffer2 = getColdBuffer(num_buffers, buf2_incr, cmd_data.dst_align, cmd_data.dst_or_mask); \
+    uint8_t *buffer2 = getColdBuffer(num_buffers, buf2_incr, (cmd_data).dst_align, (cmd_data).dst_or_mask); \
     if (!buffer2)                                                             \
         return -1;                                                            \
     size_t min_incr = (buf1_incr < buf2_incr) ? buf1_incr : buf2_incr;        \
-    size_t num_incrs = cmd_data.cold_stride_size / min_incr + 1;              \
+    size_t num_incrs = (cmd_data).cold_stride_size / min_incr + 1;            \
     size_t buf1_stride_incr = buf1_incr * num_incrs;                          \
     size_t buf2_stride_incr = buf2_incr * num_incrs;                          \
     size_t l;                                                                 \
     uint8_t *buf1;                                                            \
     uint8_t *buf2;                                                            \
     INIT;                                                                     \
-    MAINLOOP_COLD(name, cmd_data, size, num_incrs,                            \
+    MAINLOOP_COLD(name, (cmd_data), size, num_incrs,                          \
                   buf1 = buffer1 + k * buf1_incr;                             \
                   buf2 = buffer2 + k * buf2_incr;                             \
                   for (l = 0; l < num_strides; l++) {                         \
diff --git a/mmap-perf/mmapPerf.cpp b/mmap-perf/mmapPerf.cpp
index c3bacf5..d195850 100644
--- a/mmap-perf/mmapPerf.cpp
+++ b/mmap-perf/mmapPerf.cpp
@@ -23,7 +23,7 @@
     int get() { return m_fd; }
     void set(int fd) { m_fd = fd; }
     Fd() {}
-    Fd(int fd) : m_fd{fd} {}
+    explicit Fd(int fd) : m_fd{fd} {}
     ~Fd() {
         if (m_fd >= 0)
             close(m_fd);
diff --git a/multinetwork/Android.mk b/multinetwork/Android.mk
index 28e56d4..16ca6c5 100644
--- a/multinetwork/Android.mk
+++ b/multinetwork/Android.mk
@@ -10,7 +10,9 @@
 LOCAL_C_INCLUDES += frameworks/native/include external/libcxx/include
 LOCAL_CPPFLAGS += -std=c++11
 LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
-LOCAL_MODULE_TAGS := debug
+ifndef BRILLO
+    LOCAL_MODULE_TAGS := debug
+endif  #!BRILLO
 LOCAL_SHARED_LIBRARIES := libandroid libbase libc++
 LOCAL_SRC_FILES := dnschk.cpp common.cpp
 include $(BUILD_EXECUTABLE)
@@ -21,7 +23,9 @@
 LOCAL_C_INCLUDES += frameworks/native/include external/libcxx/include
 LOCAL_CPPFLAGS += -std=c++11
 LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
-LOCAL_MODULE_TAGS := debug
+ifndef BRILLO
+    LOCAL_MODULE_TAGS := debug
+endif  #!BRILLO
 LOCAL_SHARED_LIBRARIES := libandroid libbase libc++
 LOCAL_SRC_FILES := httpurl.cpp common.cpp
 include $(BUILD_EXECUTABLE)
diff --git a/perfprofd/Android.mk b/perfprofd/Android.mk
index 6409f83..0a07949 100644
--- a/perfprofd/Android.mk
+++ b/perfprofd/Android.mk
@@ -73,3 +73,5 @@
 # Clean temp vars
 perfprofd_cppflags :=
 proto_header_dir :=
+
+include $(call first-makefiles-under,$(LOCAL_PATH))
diff --git a/perfprofd/cpuconfig.h b/perfprofd/cpuconfig.h
index 11a52f0..bc5b5cf 100644
--- a/perfprofd/cpuconfig.h
+++ b/perfprofd/cpuconfig.h
@@ -27,7 +27,7 @@
   // (anything listed in /sys/devices/system/cpu/possible). The
   // destructor will re-enable the mpdecision service if it was
   // previously disabled.
-  HardwireCpuHelper(bool perform);
+  explicit HardwireCpuHelper(bool perform);
   virtual ~HardwireCpuHelper();
 
  private:
diff --git a/perfprofd/perf_data_converter.cc b/perfprofd/perf_data_converter.cc
index 9d99753..e3d3737 100644
--- a/perfprofd/perf_data_converter.cc
+++ b/perfprofd/perf_data_converter.cc
@@ -7,6 +7,29 @@
 
 namespace wireless_android_logging_awp {
 
+typedef quipper::ParsedEvent::DSOAndOffset DSOAndOffset;
+typedef std::vector<DSOAndOffset> callchain;
+
+struct callchain_lt {
+  bool operator()(const callchain *c1, const callchain *c2) const {
+    if (c1->size() != c2->size()) {
+      return c1->size() < c2->size();
+    }
+    for (unsigned idx = 0; idx < c1->size(); ++idx) {
+      const DSOAndOffset *do1 = &(*c1)[idx];
+      const DSOAndOffset *do2 = &(*c2)[idx];
+      if (do1->offset() != do2->offset()) {
+        return do1->offset() < do2->offset();
+      }
+      int rc = do1->dso_name().compare(do2->dso_name());
+      if (rc) {
+        return rc < 0;
+      }
+    }
+    return false;
+  }
+};
+
 struct RangeTarget {
   RangeTarget(uint64 start, uint64 end, uint64 to)
       : start(start), end(end), to(to) {}
@@ -28,6 +51,7 @@
 struct BinaryProfile {
   map<uint64, uint64> address_count_map;
   map<RangeTarget, uint64> range_count_map;
+  map<const callchain *, uint64, callchain_lt> callchain_count_map;
 };
 
 wireless_android_play_playlog::AndroidPerfProfile
@@ -40,8 +64,16 @@
 
   typedef map<string, BinaryProfile> ModuleProfileMap;
   typedef map<string, ModuleProfileMap> ProgramProfileMap;
+
+  // Note: the callchain_count_map member in BinaryProfile contains
+  // pointers into callchains owned by "parser" above, meaning
+  // that once the parser is destroyed, callchain pointers in
+  // name_profile_map will become stale (e.g. keep these two
+  // together in the same region).
   ProgramProfileMap name_profile_map;
   uint64 total_samples = 0;
+  bool seen_branch_stack = false;
+  bool seen_callchain = false;
   for (const auto &event : parser.parsed_events()) {
     if (!event.raw_event ||
         event.raw_event->header.type != PERF_RECORD_SAMPLE) {
@@ -49,17 +81,35 @@
     }
     string dso_name = event.dso_and_offset.dso_name();
     string program_name;
-    if (dso_name == "[kernel.kallsyms]_text") {
-      program_name = "kernel";
-      dso_name = "[kernel.kallsyms]";
+    const string kernel_name = "[kernel.kallsyms]";
+    if (dso_name.substr(0, kernel_name.length()) == kernel_name) {
+      dso_name = kernel_name;
+      program_name = "[kernel.kallsyms]";
     } else if (event.command() == "") {
       program_name = "unknown_program";
     } else {
       program_name = event.command();
     }
-    name_profile_map[program_name][dso_name].address_count_map[
-        event.dso_and_offset.offset()]++;
     total_samples++;
+    // We expect to see either all callchain events, all branch stack
+    // events, or all flat sample events, not a mix. For callchains,
+    // however, it can be the case that none of the IPs in a chain
+    // are mappable, in which case the parsed/mapped chain will appear
+    // empty (appearing as a flat sample).
+    if (!event.callchain.empty()) {
+      CHECK(!seen_branch_stack && "examining callchain");
+      seen_callchain = true;
+      const callchain *cc = &event.callchain;
+      name_profile_map[program_name][dso_name].callchain_count_map[cc]++;
+    } else if (!event.branch_stack.empty()) {
+      CHECK(!seen_callchain && "examining branch stack");
+      seen_branch_stack = true;
+      name_profile_map[program_name][dso_name].address_count_map[
+          event.dso_and_offset.offset()]++;
+    } else {
+      name_profile_map[program_name][dso_name].address_count_map[
+          event.dso_and_offset.offset()]++;
+    }
     for (size_t i = 1; i < event.branch_stack.size(); i++) {
       if (dso_name == event.branch_stack[i - 1].to.dso_name()) {
         uint64 start = event.branch_stack[i].to.offset();
@@ -122,6 +172,16 @@
         range_samples->set_to(range_count.first.to);
         range_samples->set_count(range_count.second);
       }
+      for (const auto &callchain_count :
+               module_profile.second.callchain_count_map) {
+        auto address_samples = module->add_address_samples();
+        address_samples->set_count(callchain_count.second);
+        for (const auto &d_o : *callchain_count.first) {
+          int32 module_id = name_id_map[d_o.dso_name()];
+          address_samples->add_load_module_id(module_id);
+          address_samples->add_address(d_o.offset());
+        }
+      }
     }
   }
   return ret;
diff --git a/perfprofd/perfprofdcore.cc b/perfprofd/perfprofdcore.cc
index 134c05c..746ed41 100644
--- a/perfprofd/perfprofdcore.cc
+++ b/perfprofd/perfprofdcore.cc
@@ -549,7 +549,7 @@
     }
 
     // marshall arguments
-    constexpr unsigned max_args = 12;
+    constexpr unsigned max_args = 13;
     const char *argv[max_args];
     unsigned slot = 0;
     argv[slot++] = perf_path.c_str();
@@ -571,6 +571,9 @@
     // system wide profiling
     argv[slot++] = "-a";
 
+    // no need for kernel symbols
+    argv[slot++] = "--no-dump-kernel-symbols";
+
     // sleep <duration>
     argv[slot++] = "/system/bin/sleep";
     std::string d_str = android::base::StringPrintf("%u", duration);
diff --git a/perfprofd/quipper/base/logging.h b/perfprofd/quipper/base/logging.h
index 2851d91..aaf01c1 100644
--- a/perfprofd/quipper/base/logging.h
+++ b/perfprofd/quipper/base/logging.h
@@ -173,7 +173,7 @@
 // Helper macro which avoids evaluating the arguments to a stream if
 // the condition doesn't hold.
 #define LAZY_STREAM(stream, condition)                                  \
-  !(condition) ? (void) 0 : ::logging::LogMessageVoidify() & (stream)
+  !(condition) ? (void) 0 : ::logging::LogMessageVoidify() & (stream) /* NOLINT */
 
 // We use the preprocessor's merging operator, "##", so that, e.g.,
 // LOG(INFO) becomes the token COMPACT_GOOGLE_LOG_INFO.  There's some funny
@@ -191,7 +191,7 @@
 
 // The VLOG macros log with negative verbosities.
 #define VLOG_STREAM(verbose_level) \
-  logging::LogMessage(__FILE__, __LINE__, -verbose_level).stream()
+  logging::LogMessage(__FILE__, __LINE__, -(verbose_level)).stream()
 
 #define VLOG(verbose_level) \
   LAZY_STREAM(VLOG_STREAM(verbose_level), VLOG_IS_ON(verbose_level))
@@ -215,7 +215,7 @@
 
 // The actual stream used isn't important.
 #define EAT_STREAM_PARAMETERS                                           \
-  true ? (void) 0 : ::logging::LogMessageVoidify() & LOG_STREAM(FATAL)
+  true ? (void) 0 : ::logging::LogMessageVoidify() & LOG_STREAM(FATAL) /* NOLINT */
 
 // CHECK dies with a fatal error if condition is not true.  It is *not*
 // controlled by NDEBUG, so the check will be executed regardless of
diff --git a/perfprofd/quipper/base/macros.h b/perfprofd/quipper/base/macros.h
index 57eaa81..be14792 100644
--- a/perfprofd/quipper/base/macros.h
+++ b/perfprofd/quipper/base/macros.h
@@ -250,7 +250,7 @@
 // it is leaked so that its destructors are not called at exit. If you need
 // thread-safe initialization, use base/lazy_instance.h instead.
 #define CR_DEFINE_STATIC_LOCAL(type, name, arguments) \
-  static type& name = *new type arguments
+  static type& name = *new type arguments /* NOLINT */
 
 }  // base
 
diff --git a/perfprofd/quipper/kernel-headers/tools/perf/util/include/linux/types.h b/perfprofd/quipper/kernel-headers/tools/perf/util/include/linux/types.h
index 2ac2799..9f13906 100644
--- a/perfprofd/quipper/kernel-headers/tools/perf/util/include/linux/types.h
+++ b/perfprofd/quipper/kernel-headers/tools/perf/util/include/linux/types.h
@@ -26,7 +26,7 @@
 typedef __u32 __bitwise __le32;
 /* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
 #endif
-#define DECLARE_BITMAP(name,bits) unsigned long name[BITS_TO_LONGS(bits)]
+#define DECLARE_BITMAP(name,bits) unsigned long (name)[BITS_TO_LONGS(bits)]
 struct list_head {
   struct list_head * next, * prev;
 /* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
diff --git a/perfprofd/quipper/perf_internals.h b/perfprofd/quipper/perf_internals.h
index ef5a785..a779d3c 100644
--- a/perfprofd/quipper/perf_internals.h
+++ b/perfprofd/quipper/perf_internals.h
@@ -61,4 +61,17 @@
 
 typedef perf_event event_t;
 
+//
+// Custom / user-specific records emitted by simpleperf.
+// These need to be kept in sync with the simpleperf sources.
+//
+enum simpleperf_record_type {
+  SIMPLE_PERF_RECORD_TYPE_START = 32768,
+  SIMPLE_PERF_RECORD_KERNEL_SYMBOL,
+  SIMPLE_PERF_RECORD_DSO,
+  SIMPLE_PERF_RECORD_SYMBOL,
+  SIMPLE_PERF_RECORD_SPLIT,
+  SIMPLE_PERF_RECORD_SPLIT_END,
+};
+
 #endif
diff --git a/perfprofd/quipper/perf_parser.cc b/perfprofd/quipper/perf_parser.cc
index 504b4f0..c9ec189 100644
--- a/perfprofd/quipper/perf_parser.cc
+++ b/perfprofd/quipper/perf_parser.cc
@@ -215,6 +215,12 @@
         VLOG(1) << "Parsed event type: " << event.header.type
                 << ". Doing nothing.";
         break;
+    case SIMPLE_PERF_RECORD_KERNEL_SYMBOL:
+    case SIMPLE_PERF_RECORD_DSO:
+    case SIMPLE_PERF_RECORD_SYMBOL:
+    case SIMPLE_PERF_RECORD_SPLIT:
+    case SIMPLE_PERF_RECORD_SPLIT_END:
+      break;
       default:
         LOG(ERROR) << "Unknown event type: " << event.header.type;
         return false;
@@ -286,12 +292,14 @@
     mapping_failed = true;
   }
 
-  // Write the remapped data back to the raw event regardless of whether it was
-  // entirely successfully remapped.  A single failed remap should not
-  // invalidate all the other remapped entries.
-  if (!WritePerfSampleInfo(sample_info, parsed_event->raw_event)) {
-    LOG(ERROR) << "Failed to write back remapped sample info.";
-    return false;
+  if (options_.do_remap) {
+    // Write the remapped data back to the raw event regardless of
+    // whether it was entirely successfully remapped.  A single failed
+    // remap should not invalidate all the other remapped entries.
+    if (!WritePerfSampleInfo(sample_info, parsed_event->raw_event)) {
+      LOG(ERROR) << "Failed to write back remapped sample info.";
+      return false;
+    }
   }
 
   return !mapping_failed;
diff --git a/perfprofd/quipper/perf_reader.cc b/perfprofd/quipper/perf_reader.cc
index 99731d4..48497d0 100644
--- a/perfprofd/quipper/perf_reader.cc
+++ b/perfprofd/quipper/perf_reader.cc
@@ -353,6 +353,8 @@
                               const uint64_t sample_fields,
                               const uint64_t read_format,
                               bool swap_bytes,
+                              const perf_event_attr &attr0,
+                              size_t n_attrs,
                               struct perf_sample* sample) {
   const uint64_t* initial_array_ptr = array;
 
@@ -460,9 +462,34 @@
     array = ReadBranchStack(array, swap_bytes, sample);
   }
 
+  // { u64                   abi,
+  //   u64                   regs[nr];  } && PERF_SAMPLE_REGS_USER
+  if (sample_fields & PERF_SAMPLE_REGS_USER) {
+    uint64_t abi = MaybeSwap(*array++, swap_bytes);
+    if (abi != 0) {
+      assert(n_attrs == 1);
+      uint64_t reg_mask = attr0.sample_regs_user;
+      size_t bit_nr = 0;
+      for (size_t i = 0; i < 64; ++i) {
+        if ((reg_mask >> i) & 1) {
+          bit_nr++;
+        }
+      }
+      array += bit_nr;
+    }
+  }
+
+  // { u64                   size,
+  //   u64                   regs[nr];  } && PERF_SAMPLE_STACK_USER
+  if (sample_fields & PERF_SAMPLE_STACK_USER) {
+    uint64_t size = MaybeSwap(*array++, swap_bytes);
+    if (size != 0) {
+      array += (size / sizeof(uint64_t));
+      array += 1;  // for dyn_size
+    }
+  }
+
   static const u64 kUnimplementedSampleFields =
-      PERF_SAMPLE_REGS_USER  |
-      PERF_SAMPLE_STACK_USER |
       PERF_SAMPLE_WEIGHT     |
       PERF_SAMPLE_DATA_SRC   |
       PERF_SAMPLE_TRANSACTION;
@@ -616,6 +643,11 @@
     }
   }
 
+  //
+  // Unsupported sample types.
+  //
+  CHECK(!(sample_fields & PERF_SAMPLE_STACK_USER|PERF_SAMPLE_REGS_USER));
+
   return (array - initial_array_ptr) * sizeof(uint64_t);
 }
 
@@ -769,6 +801,11 @@
   case PERF_RECORD_LOST:
   case PERF_RECORD_THROTTLE:
   case PERF_RECORD_UNTHROTTLE:
+  case SIMPLE_PERF_RECORD_KERNEL_SYMBOL:
+  case SIMPLE_PERF_RECORD_DSO:
+  case SIMPLE_PERF_RECORD_SYMBOL:
+  case SIMPLE_PERF_RECORD_SPLIT:
+  case SIMPLE_PERF_RECORD_SPLIT_END:
     return true;
   case PERF_RECORD_READ:
   case PERF_RECORD_MAX:
@@ -788,6 +825,14 @@
     return false;
   }
 
+  // We want to completely ignore these records
+  if (event.header.type == SIMPLE_PERF_RECORD_KERNEL_SYMBOL ||
+      event.header.type == SIMPLE_PERF_RECORD_DSO ||
+      event.header.type == SIMPLE_PERF_RECORD_SYMBOL ||
+      event.header.type == SIMPLE_PERF_RECORD_SPLIT ||
+      event.header.type == SIMPLE_PERF_RECORD_SPLIT_END)
+    return true;
+
   uint64_t sample_format = GetSampleFieldsForEventType(event.header.type,
                                                        sample_type_);
   uint64_t offset = GetPerfSampleDataOffset(event);
@@ -797,6 +842,8 @@
       sample_format,
       read_format_,
       is_cross_endian_,
+      attrs_[0].attr,
+      attrs_.size(),
       sample);
 
   size_t expected_size = event.header.size - offset;
@@ -1391,7 +1438,22 @@
   if (is_cross_endian_)
     ByteSwap(&size);
 
-  if (size > sizeof(event_t)) {
+  //
+  // Upstream linux perf limits the size of an event record to 2^16 bytes,
+  // however simpleperf includes extensions to support larger (2^32) record
+  // sizes via a split record scheme (the larger records are split up
+  // into chunks and then embedded into a series of SIMPLE_PERF_RECORD_SPLIT
+  // records followed by a terminating SIMPLE_PERF_RECORD_SPLIT_END record.
+  // At the moment none of the larger records are of interest to perfprofd, so
+  // the main thing we're doing here is ignoring/bypassing them.
+  //
+  if (event.header.type == SIMPLE_PERF_RECORD_KERNEL_SYMBOL ||
+      event.header.type == SIMPLE_PERF_RECORD_DSO ||
+      event.header.type == SIMPLE_PERF_RECORD_SYMBOL ||
+      event.header.type == SIMPLE_PERF_RECORD_SPLIT ||
+      event.header.type == SIMPLE_PERF_RECORD_SPLIT_END)
+    size = sizeof(event_t);
+  else if (size > sizeof(event_t)) {
     LOG(INFO) << "Data size: " << size << " sizeof(event_t): "
               << sizeof(event_t);
     return false;
@@ -1452,6 +1514,8 @@
       ByteSwap(&event_copy->read.time_running);
       ByteSwap(&event_copy->read.id);
       break;
+    case SIMPLE_PERF_RECORD_KERNEL_SYMBOL:
+      break;
     default:
       LOG(FATAL) << "Unknown event type: " << type;
     }
diff --git a/perfprofd/quipper/perf_utils.cc b/perfprofd/quipper/perf_utils.cc
index 02fa9e0..4f6fdc3 100644
--- a/perfprofd/quipper/perf_utils.cc
+++ b/perfprofd/quipper/perf_utils.cc
@@ -105,6 +105,7 @@
            PERF_SAMPLE_STREAM_ID | PERF_SAMPLE_CPU | PERF_SAMPLE_IDENTIFIER;
     break;
   case PERF_RECORD_SAMPLE:
+  case SIMPLE_PERF_RECORD_KERNEL_SYMBOL:
     break;
   default:
     LOG(FATAL) << "Unknown event type " << event_type;
@@ -140,6 +141,9 @@
     offset = sizeof(event.mmap2) - sizeof(event.mmap2.filename) +
              GetUint64AlignedStringLength(event.mmap2.filename);
     break;
+  case SIMPLE_PERF_RECORD_KERNEL_SYMBOL:
+    offset = 0;
+    break;
   default:
     LOG(FATAL) << "Unknown/unsupported event type " << event.header.type;
     break;
diff --git a/perfprofd/tests/Android.mk b/perfprofd/tests/Android.mk
index bdd82e0..45c2779 100644
--- a/perfprofd/tests/Android.mk
+++ b/perfprofd/tests/Android.mk
@@ -17,7 +17,7 @@
 include $(BUILD_STATIC_LIBRARY)
 
 #
-# Canned perf.data files needed by unit test.
+# Canned perf.data file needed by unit test.
 #
 include $(CLEAR_VARS)
 LOCAL_MODULE := canned.perf.data
@@ -28,6 +28,17 @@
 include $(BUILD_PREBUILT)
 
 #
+# Second canned perf.data file needed by unit test.
+#
+include $(CLEAR_VARS)
+LOCAL_MODULE := callchain.canned.perf.data
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_CLASS := DATA
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA)/nativetest/perfprofd_test
+LOCAL_SRC_FILES := callchain.canned.perf.data
+include $(BUILD_PREBUILT)
+
+#
 # Unit test for perfprofd
 #
 include $(CLEAR_VARS)
diff --git a/perfprofd/tests/README.txt b/perfprofd/tests/README.txt
index 4d8db99..949e73f 100644
--- a/perfprofd/tests/README.txt
+++ b/perfprofd/tests/README.txt
@@ -1,13 +1,19 @@
-Native tests for 'perfprofd'. Please run with 'runtest perfprofd'
-(a.k.a. "$ANDROID_BUILD_TOP"/development/testrunner/runtest.py).
+Native tests for 'perfprofd'. Please run with
+
+   runtest --path=system/extras/perfprofd/tests
+
+(where runtest == $ANDROID_BUILD_TOP"/development/testrunner/runtest.py).
 
 Notes:
 
-1. One of the testpoints in this test suite performs a live 'perf'
-run on the device; before invoking the test be sure that 'perf'
-has been built and installed on the device in /system/bin/perf
+1. Several of the testpoints in this unit tests perform a live 'simpleperf'
+run on the device (if you are using a userdebug build, simpleperf should
+already be available in /system/xbin/simpleperf).
 
-2. The daemon under test, perfprofd, is broken into a main function, a
+2. Part of the test is a system-wide profile, meaning that you will
+need to run 'adb root' prior to test execution.
+
+3. The daemon under test, perfprofd, is broken into a main function, a
 "core" library, and a "utils library. Picture:
 
 	+-----------+   perfprofdmain.o
diff --git a/perfprofd/tests/callchain.canned.perf.data b/perfprofd/tests/callchain.canned.perf.data
new file mode 100644
index 0000000..8d84393
--- /dev/null
+++ b/perfprofd/tests/callchain.canned.perf.data
Binary files differ
diff --git a/perfprofd/tests/perfprofd_test.cc b/perfprofd/tests/perfprofd_test.cc
index 3a32204..3114190 100644
--- a/perfprofd/tests/perfprofd_test.cc
+++ b/perfprofd/tests/perfprofd_test.cc
@@ -25,6 +25,7 @@
 #include <fcntl.h>
 
 #include <android-base/stringprintf.h>
+#include <cutils/properties.h>
 
 #include "perfprofdcore.h"
 #include "configreader.h"
@@ -102,7 +103,7 @@
     if (test_dir == "") {
       ASSERT_TRUE(executable_path != nullptr);
       std::string s(executable_path);
-      auto found = s.find_last_of("/");
+      auto found = s.find_last_of('/');
       test_dir = s.substr(0,found);
       dest_dir = test_dir;
       dest_dir += "/tmp";
@@ -570,6 +571,81 @@
   }
 }
 
+TEST_F(PerfProfdTest, CallchainRunWithCannedPerf)
+{
+  // This test makes sure that the perf.data converter
+  // can handle call chains.
+  //
+  std::string input_perf_data(test_dir);
+  input_perf_data += "/callchain.canned.perf.data";
+
+  // Set up config to avoid these annotations (they are tested elsewhere)
+  ConfigReader config;
+  config.overrideUnsignedEntry("collect_cpu_utilization", 0);
+  config.overrideUnsignedEntry("collect_charging_state", 0);
+  config.overrideUnsignedEntry("collect_camera_active", 0);
+
+  // Kick off encoder and check return code
+  PROFILE_RESULT result =
+      encode_to_proto(input_perf_data, encoded_file_path(0).c_str(), config, 0);
+  EXPECT_EQ(OK_PROFILE_COLLECTION, result);
+
+  // Read and decode the resulting perf.data.encoded file
+  wireless_android_play_playlog::AndroidPerfProfile encodedProfile;
+  readEncodedProfile("BasicRunWithCannedPerf",
+                     encodedProfile);
+
+
+  // Expect 4 programs 8 load modules
+  EXPECT_EQ(4, encodedProfile.programs_size());
+  EXPECT_EQ(8, encodedProfile.load_modules_size());
+
+  // Check a couple of load modules
+  { const auto &lm0 = encodedProfile.load_modules(0);
+    std::string act_lm0 = encodedLoadModuleToString(lm0);
+    std::string sqact0 = squeezeWhite(act_lm0, "actual for lm 0");
+    const std::string expected_lm0 = RAW_RESULT(
+        name: "/system/bin/dex2oat"
+        build_id: "ee12bd1a1de39422d848f249add0afc4"
+                                                );
+    std::string sqexp0 = squeezeWhite(expected_lm0, "expected_lm0");
+    EXPECT_STREQ(sqexp0.c_str(), sqact0.c_str());
+  }
+  { const auto &lm1 = encodedProfile.load_modules(1);
+    std::string act_lm1 = encodedLoadModuleToString(lm1);
+    std::string sqact1 = squeezeWhite(act_lm1, "actual for lm 1");
+    const std::string expected_lm1 = RAW_RESULT(
+        name: "/system/bin/linker"
+        build_id: "a36715f673a4a0aa76ef290124c516cc"
+                                                );
+    std::string sqexp1 = squeezeWhite(expected_lm1, "expected_lm1");
+    EXPECT_STREQ(sqexp1.c_str(), sqact1.c_str());
+  }
+
+  // Examine some of the samples now
+  { const auto &p0 = encodedProfile.programs(0);
+    const auto &lm1 = p0.modules(0);
+    std::string act_lm1 = encodedModuleSamplesToString(lm1);
+    std::string sqact1 = squeezeWhite(act_lm1, "actual for lm1");
+    const std::string expected_lm1 = RAW_RESULT(
+        load_module_id: 0
+        address_samples { address: 108552 count: 2 }
+                                                );
+    std::string sqexp1 = squeezeWhite(expected_lm1, "expected_lm1");
+    EXPECT_STREQ(sqexp1.c_str(), sqact1.c_str());
+  }
+  { const auto &p4 = encodedProfile.programs(3);
+    const auto &lm2 = p4.modules(1);
+    std::string act_lm2 = encodedModuleSamplesToString(lm2);
+    std::string sqact2 = squeezeWhite(act_lm2, "actual for lm2");
+    const std::string expected_lm2 = RAW_RESULT(
+        load_module_id: 2 address_samples { address: 403913 count: 1 } address_samples { address: 840761 count: 1 } address_samples { address: 846481 count: 1 } address_samples { address: 999053 count: 1 } address_samples { address: 1012959 count: 1 } address_samples { address: 1524309 count: 1 } address_samples { address: 1580779 count: 1 } address_samples { address: 4287986288 count: 1 }
+                                                );
+    std::string sqexp2 = squeezeWhite(expected_lm2, "expected_lm2");
+    EXPECT_STREQ(sqexp2.c_str(), sqact2.c_str());
+  }
+}
+
 TEST_F(PerfProfdTest, BasicRunWithLivePerf)
 {
   //
@@ -685,6 +761,68 @@
                      expected, "BasicRunWithLivePerf", true);
 }
 
+TEST_F(PerfProfdTest, CallChainRunWithLivePerf)
+{
+  //
+  // Callchain profiles are only supported on certain devices.
+  // For now this test is stubbed out except when run on "angler".
+  //
+  char propBuf[PROPERTY_VALUE_MAX];
+  propBuf[0] = '\0';
+  property_get("ro.hardware", propBuf, "");
+  if (strcmp(propBuf, "angler")) {
+    return;
+  }
+
+  //
+  // Collect a callchain profile, so as to exercise the code in
+  // perf_data post-processing that digests callchains.
+  //
+  PerfProfdRunner runner;
+  std::string ddparam("destination_directory="); ddparam += dest_dir;
+  runner.addToConfig(ddparam);
+  std::string cfparam("config_directory="); cfparam += test_dir;
+  runner.addToConfig(cfparam);
+  runner.addToConfig("main_loop_iterations=1");
+  runner.addToConfig("use_fixed_seed=12345678");
+  runner.addToConfig("max_unprocessed_profiles=100");
+  runner.addToConfig("collection_interval=9999");
+  runner.addToConfig("stack_profile=1");
+  runner.addToConfig("sample_duration=2");
+
+  // Create semaphore file
+  runner.create_semaphore_file();
+
+  // Kick off daemon
+  int daemon_main_return_code = runner.invoke();
+
+  // Check return code from daemon
+  EXPECT_EQ(0, daemon_main_return_code);
+
+  // Read and decode the resulting perf.data.encoded file
+  wireless_android_play_playlog::AndroidPerfProfile encodedProfile;
+  readEncodedProfile("CallChainRunWithLivePerf", encodedProfile);
+
+  // Examine what we get back. Since it's a live profile, we can't
+  // really do much in terms of verifying the contents.
+  EXPECT_LT(0, encodedProfile.programs_size());
+
+  // Verify log contents
+  const std::string expected = RAW_RESULT(
+      I: starting Android Wide Profiling daemon
+      I: config file path set to /data/nativetest/perfprofd_test/perfprofd.conf
+      I: random seed set to 12345678
+      I: sleep 674 seconds
+      I: initiating profile collection
+      I: profile collection complete
+      I: sleep 9325 seconds
+      I: finishing Android Wide Profiling daemon
+                                          );
+  // check to make sure log excerpt matches
+  compareLogMessages(mock_perfprofdutils_getlogged(),
+                     expected, "CallChainRunWithLivePerf", true);
+}
+
 int main(int argc, char **argv) {
   executable_path = argv[0];
   // switch to / before starting testing (perfprofd
diff --git a/postinst/Android.mk b/postinst/Android.mk
index b0dec05..c804cfc 100644
--- a/postinst/Android.mk
+++ b/postinst/Android.mk
@@ -21,10 +21,4 @@
 LOCAL_MODULE_TAGS := optional
 LOCAL_MODULE_CLASS := EXECUTABLES
 LOCAL_SRC_FILES := postinst.sh
-
-# Create a symlink from /postinst to our default post-install script in the
-# same filesystem as /postinst.
-# TODO(deymo): Remove this symlink and add the path to the product config.
-LOCAL_POST_INSTALL_CMD := \
-    $(hide) ln -sf bin/postinst_example $(TARGET_OUT)/postinst
 include $(BUILD_PREBUILT)
diff --git a/postinst/postinst.sh b/postinst/postinst.sh
index eb98e79..5bbcf9c 100644
--- a/postinst/postinst.sh
+++ b/postinst/postinst.sh
@@ -18,18 +18,54 @@
 
 # This is an example post-install script. This script will be executed by the
 # update_engine right after finishing writing all the partitions, but before
-# marking the new slot as active.
-#
+# marking the new slot as active. To enable running this program, insert these
+# lines in your product's .mk file (without the # at the beginning):
+
+# AB_OTA_POSTINSTALL_CONFIG += \
+#   RUN_POSTINSTALL_system=true \
+#   POSTINSTALL_PATH_system=bin/postinst_example \
+#   FILESYSTEM_TYPE_system=ext4 \
+
 # This script receives no arguments. argv[0] will include the absolute path to
-# the script, including the temporary directory where the new partition was
-# mounted.
+# the script, including the directory where the new partition was mounted.
 #
-# This script will run in the context of the old kernel and old system. Note
-# that the absolute path used in first line of this script (/system/bin/sh) is
-# indeed the old system's sh binary. If you use a compiled program, you might
-# want to link it statically or use a wrapper script to use the new ldso to run
-# your program (see the --generate-wrappers option in lddtree.py for example).
-#
+# The script will run from the "postinstall" SELinux domain, from the old system
+# environment (kernel, SELinux rules, etc). New rules and domains introduced by
+# the new system won't be available when this script runs, instead, all the
+# files in the mounted directory will have the attribute "postinstall_file". All
+# the files accessed from here would need to be allowed in the old system or
+# those accesses will fail. For example, the absolute path used in the first
+# line of this script (/system/bin/sh) is indeed the old system's sh binary. If
+# you use a compiled program, you might want to link it statically or use a
+# wrapper script to use the new ldso to run your program (see the
+# --generate-wrappers option in lddtree.py for an example).
+
+# We get called with two parameters: <target_slot> <status_fd>
+# * <target_slot> is the slot where the new system was just copied. This is
+#   normally either 0 or 1. You can get the target suffix running
+#   `bootctl get-suffix ${target_slot}`
+# * <status_fd> is a file descriptor number where this script can write to to
+#   report the progress of the process. See examples below.
+
+target_slot="$1"
+status_fd="$2"
+
+my_dir=$(dirname "$0")
+
+# We can notify the updater of the progress of our program by writing to the
+# status file descriptor "global_progress <frac>\n".
+print -u${status_fd} "global_progress 0"
+
+echo "The output of this program will show up in the logs." >&2
+
+# We are half way done, so we set 0.5.
+print -u${status_fd} "global_progress 0.5"
+
+echo "Note that this program runs from ${my_dir}"
+
+# Actually, we were done.
+print -u${status_fd} "global_progress 1.0"
+
 # If the exit code of this program is an error code (different from 0), the
 # update will fail and the new slot will not be marked as active.
 
diff --git a/procmem/procmem.c b/procmem/procmem.c
index 28055d8..45459ae 100644
--- a/procmem/procmem.c
+++ b/procmem/procmem.c
@@ -50,12 +50,12 @@
     /* maps and such */
     pm_map_t **maps; size_t num_maps;
 
-    struct map_info **mis;
+    struct map_info **mis = NULL;
     struct map_info *mi;
 
     /* pagemap information */
     uint64_t *pagemap; size_t num_pages;
-    unsigned long address; uint64_t mapentry;
+    uint64_t mapentry;
     uint64_t count, flags;
 
     /* totals */
@@ -190,7 +190,6 @@
         mi->shared_clean = mi->shared_dirty = mi->private_clean = mi->private_dirty = 0;
 
         for (j = 0; j < num_pages; j++) {
-            address = pm_map_start(mi->map) + j * ker->pagesize;
             mapentry = pagemap[j];
 
             if (PM_PAGEMAP_PRESENT(mapentry) && !PM_PAGEMAP_SWAPPED(mapentry)) {
@@ -207,14 +206,14 @@
                     fprintf(stderr, "error getting flags for frame.\n");
                 }
 
-                if ((ws != WS_ONLY) || (flags & PM_PAGE_REFERENCED)) {
+                if ((ws != WS_ONLY) || (flags & KPF_REFERENCED)) {
                     if (count > 1) {
-                        if (flags & PM_PAGE_DIRTY)
+                        if (flags & KPF_DIRTY)
                             mi->shared_dirty++;
                         else
                             mi->shared_clean++;
                     } else {
-                        if (flags & PM_PAGE_DIRTY)
+                        if (flags & KPF_DIRTY)
                             mi->private_dirty++;
                         else
                             mi->private_clean++;
@@ -298,6 +297,7 @@
         );
     }
 
+    free(mis);
     return 0;
 }
 
diff --git a/procrank/Android.mk b/procrank/Android.mk
index f1eb3b6..8a235fd 100644
--- a/procrank/Android.mk
+++ b/procrank/Android.mk
@@ -15,9 +15,9 @@
 LOCAL_PATH:= $(call my-dir)
 
 include $(CLEAR_VARS)
-LOCAL_SRC_FILES := procrank.c
+LOCAL_SRC_FILES := procrank.cpp
 LOCAL_CFLAGS := -Wall -Wextra -Wformat=2 -Werror
-LOCAL_SHARED_LIBRARIES := libpagemap
+LOCAL_SHARED_LIBRARIES := libpagemap libbase
 LOCAL_MODULE := procrank
 LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
 LOCAL_MODULE_TAGS := debug
diff --git a/procrank/procrank.c b/procrank/procrank.cpp
similarity index 61%
rename from procrank/procrank.c
rename to procrank/procrank.cpp
index a6f8342..36a2043 100644
--- a/procrank/procrank.c
+++ b/procrank/procrank.cpp
@@ -1,18 +1,18 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
+//
+// Copyright (C) 2008 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
 
 #include <dirent.h>
 #include <errno.h>
@@ -24,16 +24,24 @@
 #include <sys/types.h>
 #include <unistd.h>
 
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
 #include <pagemap/pagemap.h>
 
 struct proc_info {
     pid_t pid;
     pm_memusage_t usage;
     uint64_t wss;
+    int oomadj;
 };
 
 static void usage(char *myname);
-static int getprocname(pid_t pid, char *buf, int len);
+static std::string getprocname(pid_t pid);
+static int getoomadj(pid_t pid);
+static bool getminfree(std::vector<uint64_t>* minfree, std::vector<int>* adj);
 static int numcmp(uint64_t a, uint64_t b);
 
 #define declare_sort(field) \
@@ -44,6 +52,7 @@
 declare_sort(pss);
 declare_sort(uss);
 declare_sort(swap);
+declare_sort(oomadj);
 
 int (*compfn)(const void *a, const void *b);
 static int order;
@@ -94,7 +103,7 @@
             "Slab:",
             "SwapTotal:",
             "SwapFree:",
-            "ZRam:",            /* not read from meminfo but from /sys/block/zram0 */
+            "ZRam:",            // not read from meminfo but from /sys/block/zram0
             "Mapped:",
             "VmallocUsed:",
             "PageTables:",
@@ -177,7 +186,6 @@
     pm_kernel_t *ker;
     pm_process_t *proc;
     pid_t *pids;
-    struct proc_info **procs;
     size_t num_procs;
     uint64_t total_pss;
     uint64_t total_uss;
@@ -185,19 +193,19 @@
     uint64_t total_pswap;
     uint64_t total_uswap;
     uint64_t total_zswap;
-    char cmdline[256]; // this must be within the range of int
     int error;
     bool has_swap = false, has_zram = false;
     uint64_t required_flags = 0;
     uint64_t flags_mask = 0;
 
-    #define WS_OFF   0
-    #define WS_ONLY  1
-    #define WS_RESET 2
-    int ws;
-
     int arg;
-    size_t i, j;
+    size_t i;
+
+    enum {
+        WS_OFF,
+        WS_ONLY,
+        WS_RESET,
+    } ws;
 
     uint64_t mem[MEMINFO_COUNT] = { };
     pm_proportional_swap_t *p_swap;
@@ -207,6 +215,7 @@
     compfn = &sort_by_pss;
     order = -1;
     ws = WS_OFF;
+    bool oomadj = false;
 
     for (arg = 1; arg < argc; arg++) {
         if (!strcmp(argv[arg], "-v")) { compfn = &sort_by_vss; continue; }
@@ -214,9 +223,10 @@
         if (!strcmp(argv[arg], "-p")) { compfn = &sort_by_pss; continue; }
         if (!strcmp(argv[arg], "-u")) { compfn = &sort_by_uss; continue; }
         if (!strcmp(argv[arg], "-s")) { compfn = &sort_by_swap; continue; }
-        if (!strcmp(argv[arg], "-c")) { required_flags = 0; flags_mask = PM_PAGE_SWAPBACKED; continue; }
-        if (!strcmp(argv[arg], "-C")) { required_flags = flags_mask = PM_PAGE_SWAPBACKED; continue; }
-        if (!strcmp(argv[arg], "-k")) { required_flags = flags_mask = PM_PAGE_KSM; continue; }
+        if (!strcmp(argv[arg], "-o")) { compfn = &sort_by_oomadj; oomadj = true; continue; }
+        if (!strcmp(argv[arg], "-c")) { required_flags = 0; flags_mask = KPF_SWAPBACKED; continue; }
+        if (!strcmp(argv[arg], "-C")) { required_flags = flags_mask = KPF_SWAPBACKED; continue; }
+        if (!strcmp(argv[arg], "-k")) { required_flags = flags_mask = KPF_KSM; continue; }
         if (!strcmp(argv[arg], "-w")) { ws = WS_ONLY; continue; }
         if (!strcmp(argv[arg], "-W")) { ws = WS_RESET; continue; }
         if (!strcmp(argv[arg], "-R")) { order *= -1; continue; }
@@ -242,21 +252,12 @@
         exit(EXIT_FAILURE);
     }
 
-    procs = calloc(num_procs, sizeof(struct proc_info*));
-    if (procs == NULL) {
-        fprintf(stderr, "calloc: %s", strerror(errno));
-        exit(EXIT_FAILURE);
-    }
-
+    std::vector<proc_info> procs(num_procs);
     for (i = 0; i < num_procs; i++) {
-        procs[i] = malloc(sizeof(struct proc_info));
-        if (procs[i] == NULL) {
-            fprintf(stderr, "malloc: %s\n", strerror(errno));
-            exit(EXIT_FAILURE);
-        }
-        procs[i]->pid = pids[i];
-        pm_memusage_zero(&procs[i]->usage);
-        pm_memusage_pswap_init_handle(&procs[i]->usage, p_swap);
+        procs[i].pid = pids[i];
+        procs[i].oomadj = getoomadj(pids[i]);
+        pm_memusage_zero(&procs[i].usage);
+        pm_memusage_pswap_init_handle(&procs[i].usage, p_swap);
         error = pm_process_create(ker, pids[i], &proc);
         if (error) {
             fprintf(stderr, "warning: could not create process interface for %d\n", pids[i]);
@@ -265,11 +266,11 @@
 
         switch (ws) {
         case WS_OFF:
-            error = pm_process_usage_flags(proc, &procs[i]->usage, flags_mask,
+            error = pm_process_usage_flags(proc, &procs[i].usage, flags_mask,
                                            required_flags);
             break;
         case WS_ONLY:
-            error = pm_process_workingset(proc, &procs[i]->usage, 0);
+            error = pm_process_workingset(proc, &procs[i].usage, 0);
             break;
         case WS_RESET:
             error = pm_process_workingset(proc, NULL, 1);
@@ -280,7 +281,7 @@
             fprintf(stderr, "warning: could not read usage for %d\n", pids[i]);
         }
 
-        if (ws != WS_RESET && procs[i]->usage.swap) {
+        if (ws != WS_RESET && procs[i].usage.swap) {
             has_swap = true;
         }
 
@@ -291,17 +292,14 @@
 
     if (ws == WS_RESET) exit(0);
 
-    j = 0;
-    for (i = 0; i < num_procs; i++) {
-        if (procs[i]->usage.vss) {
-            procs[j++] = procs[i];
-        } else {
-            free(procs[i]);
-        }
-    }
-    num_procs = j;
+    procs.erase(std::remove_if(procs.begin(),
+                               procs.end(),
+                               [](auto proc){
+                                   return proc.usage.vss == 0;
+                               }),
+                procs.end());
 
-    qsort(procs, num_procs, sizeof(procs[0]), compfn);
+    qsort(procs.data(), procs.size(), sizeof(procs[0]), compfn);
 
     if (has_swap) {
         uint64_t zram_mem_used = get_zram_mem_used();
@@ -314,6 +312,9 @@
     }
 
     printf("%5s  ", "PID");
+    if (oomadj) {
+        printf("%5s  ", "oom");
+    }
     if (ws) {
         printf("%7s  %7s  %7s  ", "WRss", "WPss", "WUss");
         if (has_swap) {
@@ -341,47 +342,85 @@
     total_uswap = 0;
     total_zswap = 0;
 
-    for (i = 0; i < num_procs; i++) {
-        if (getprocname(procs[i]->pid, cmdline, (int)sizeof(cmdline)) < 0) {
-            /*
-             * Something is probably seriously wrong if writing to the stack
-             * failed.
-             */
-            free(procs[i]);
-            continue;
+    std::vector<uint64_t> lmk_minfree;
+    std::vector<int> lmk_adj;
+    if (oomadj) {
+        getminfree(&lmk_minfree, &lmk_adj);
+    }
+    auto lmk_minfree_it = lmk_minfree.cbegin();
+    auto lmk_adj_it = lmk_adj.cbegin();
+
+    auto print_oomadj_totals = [&](int adj){
+        for (; lmk_adj_it != lmk_adj.cend() && lmk_minfree_it != lmk_minfree.cend() &&
+                 adj > *lmk_adj_it; lmk_adj_it++, lmk_minfree_it++) {
+            // Print the cumulative total line
+            printf("%5s  ", ""); // pid
+
+            printf("%5s  ", ""); // oomadj
+
+            if (ws) {
+                printf("%7s  %6" PRIu64 "K  %6" PRIu64 "K  ",
+                       "", total_pss / 1024, total_uss / 1024);
+            } else {
+                printf("%8s  %7s  %6" PRIu64 "K  %6" PRIu64 "K  ",
+                       "", "", total_pss / 1024, total_uss / 1024);
+            }
+
+            if (has_swap) {
+                printf("%6" PRIu64 "K  ", total_swap / 1024);
+                printf("%6" PRIu64 "K  ", total_pswap / 1024);
+                printf("%6" PRIu64 "K  ", total_uswap / 1024);
+                if (has_zram) {
+                    printf("%6" PRIu64 "K  ", total_zswap / 1024);
+                }
+            }
+
+            printf("TOTAL for oomadj < %d (%6" PRIu64 "K)\n", *lmk_adj_it, *lmk_minfree_it / 1024);
+        }
+    };
+
+    for (auto& proc: procs) {
+        if (oomadj) {
+            print_oomadj_totals(proc.oomadj);
         }
 
-        total_pss += procs[i]->usage.pss;
-        total_uss += procs[i]->usage.uss;
-        total_swap += procs[i]->usage.swap;
+        std::string cmdline = getprocname(proc.pid);
 
-        printf("%5d  ", procs[i]->pid);
+        total_pss += proc.usage.pss;
+        total_uss += proc.usage.uss;
+        total_swap += proc.usage.swap;
+
+        printf("%5d  ", proc.pid);
+
+        if (oomadj) {
+            printf("%5d  ", proc.oomadj);
+        }
 
         if (ws) {
             printf("%6zuK  %6zuK  %6zuK  ",
-                procs[i]->usage.rss / 1024,
-                procs[i]->usage.pss / 1024,
-                procs[i]->usage.uss / 1024
+                proc.usage.rss / 1024,
+                proc.usage.pss / 1024,
+                proc.usage.uss / 1024
             );
         } else {
             printf("%7zuK  %6zuK  %6zuK  %6zuK  ",
-                procs[i]->usage.vss / 1024,
-                procs[i]->usage.rss / 1024,
-                procs[i]->usage.pss / 1024,
-                procs[i]->usage.uss / 1024
+                proc.usage.vss / 1024,
+                proc.usage.rss / 1024,
+                proc.usage.pss / 1024,
+                proc.usage.uss / 1024
             );
         }
 
         if (has_swap) {
             pm_swapusage_t su;
 
-            pm_memusage_pswap_get_usage(&procs[i]->usage, &su);
-            printf("%6zuK  ", procs[i]->usage.swap / 1024);
+            pm_memusage_pswap_get_usage(&proc.usage, &su);
+            printf("%6zuK  ", proc.usage.swap / 1024);
             printf("%6zuK  ", su.proportional / 1024);
             printf("%6zuK  ", su.unique / 1024);
             total_pswap += su.proportional;
             total_uswap += su.unique;
-            pm_memusage_pswap_free(&procs[i]->usage);
+            pm_memusage_pswap_free(&proc.usage);
             if (has_zram) {
                 size_t zpswap = su.proportional * zram_cr;
                 printf("%6zuK  ", zpswap / 1024);
@@ -389,17 +428,22 @@
             }
         }
 
-        printf("%s\n", cmdline);
-
-        free(procs[i]);
+        printf("%s\n", cmdline.c_str());
     }
 
-    free(procs);
     pm_memusage_pswap_destroy(p_swap);
 
-    /* Print the separator line */
+    if (oomadj) {
+        print_oomadj_totals(INT_MAX);
+    }
+
+    // Print the separator line
     printf("%5s  ", "");
 
+    if (oomadj) {
+        printf("%5s  ", "");
+    }
+
     if (ws) {
         printf("%7s  %7s  %7s  ", "", "------", "------");
     } else {
@@ -415,8 +459,13 @@
 
     printf("%s\n", "------");
 
-    /* Print the total line */
+    // Print the total line
     printf("%5s  ", "");
+
+    if (oomadj) {
+        printf("%5s  ", "");
+    }
+
     if (ws) {
         printf("%7s  %6" PRIu64 "K  %6" PRIu64 "K  ",
             "", total_pss / 1024, total_uss / 1024);
@@ -466,67 +515,67 @@
                     "    -k  Only show pages collapsed by KSM\n"
                     "    -w  Display statistics for working set only.\n"
                     "    -W  Reset working set of all processes.\n"
+                    "    -o  Show and sort by oom score against lowmemorykiller thresholds.\n"
                     "    -h  Display this help screen.\n",
     myname);
 }
 
-/*
- * Get the process name for a given PID. Inserts the process name into buffer
- * buf of length len. The size of the buffer must be greater than zero to get
- * any useful output.
- *
- * Note that fgets(3) only declares length as an int, so our buffer size is
- * also declared as an int.
- *
- * Returns 0 on success, a positive value on partial success, and -1 on
- * failure. Other interesting values:
- *   1 on failure to create string to examine proc cmdline entry
- *   2 on failure to open proc cmdline entry
- *   3 on failure to read proc cmdline entry
- */
-static int getprocname(pid_t pid, char *buf, int len) {
-    char *filename;
-    FILE *f;
-    int rc = 0;
-    static const char* unknown_cmdline = "<unknown>";
+// Get the process name for a given PID.
+static std::string getprocname(pid_t pid) {
+    std::string filename = android::base::StringPrintf("/proc/%d/cmdline", pid);
 
-    if (len <= 0) {
-        return -1;
+    std::string procname;
+
+    if (!android::base::ReadFileToString(filename, &procname)) {
+        // The process went away before we could read its process name.
+        procname = "<unknown>";
     }
 
-    if (asprintf(&filename, "/proc/%d/cmdline", pid) < 0) {
-        rc = 1;
-        goto exit;
+    return procname;
+}
+
+static int getoomadj(pid_t pid) {
+    std::string filename = android::base::StringPrintf("/proc/%d/oom_score_adj", pid);
+    std::string oomadj;
+
+    if (!android::base::ReadFileToString(filename, &oomadj)) {
+        return -1001;
     }
 
-    f = fopen(filename, "r");
-    if (f == NULL) {
-        rc = 2;
-        goto releasefilename;
+    return strtol(oomadj.c_str(), NULL, 10);
+}
+
+static bool getminfree(std::vector<uint64_t>* minfree, std::vector<int>* adj) {
+    std::string minfree_str;
+    std::string adj_str;
+
+    if (!android::base::ReadFileToString("/sys/module/lowmemorykiller/parameters/minfree", &minfree_str)) {
+        return false;
     }
 
-    if (fgets(buf, len, f) == NULL) {
-        rc = 3;
-        goto closefile;
+    if (!android::base::ReadFileToString("/sys/module/lowmemorykiller/parameters/adj", &adj_str)) {
+        return false;
     }
 
-closefile:
-    (void) fclose(f);
-releasefilename:
-    free(filename);
-exit:
-    if (rc != 0) {
-        /*
-         * The process went away before we could read its process name. Try
-         * to give the user "<unknown>" here, but otherwise they get to look
-         * at a blank.
-         */
-        if (strlcpy(buf, unknown_cmdline, (size_t)len) >= (size_t)len) {
-            rc = 4;
-        }
-    }
+    std::vector<std::string> minfree_vec = android::base::Split(minfree_str, ",");
+    std::vector<std::string> adj_vec = android::base::Split(adj_str, ",");
 
-    return rc;
+    minfree->clear();
+    minfree->resize(minfree_vec.size());
+    adj->clear();
+    adj->resize(adj_vec.size());
+
+    std::transform(minfree_vec.begin(), minfree_vec.end(), minfree->begin(),
+                   [](const std::string& s) -> uint64_t {
+                       return strtoull(s.c_str(), NULL, 10) * PAGE_SIZE;
+                   });
+
+    std::transform(adj_vec.begin(), adj_vec.end(), adj->begin(),
+                   [](const std::string& s) -> int {
+                       return strtol(s.c_str(), NULL, 10);
+                   });
+
+    return true;
 }
 
 static int numcmp(uint64_t a, uint64_t b) {
@@ -535,11 +584,17 @@
     return 0;
 }
 
+static int snumcmp(int64_t a, int64_t b) {
+    if (a < b) return -1;
+    if (a > b) return 1;
+    return 0;
+}
+
 #define create_sort(field, compfn) \
     static int sort_by_ ## field (const void *a, const void *b) { \
         return order * compfn( \
-            (*((struct proc_info**)a))->usage.field, \
-            (*((struct proc_info**)b))->usage.field \
+            ((struct proc_info*)(a))->usage.field, \
+            ((struct proc_info*)(b))->usage.field  \
         ); \
     }
 
@@ -548,3 +603,11 @@
 create_sort(pss, numcmp)
 create_sort(uss, numcmp)
 create_sort(swap, numcmp)
+
+static int sort_by_oomadj (const void *a, const void *b) {
+    // Negative oomadj is higher priority, reverse the sort order
+    return -1 * order * snumcmp(
+        ((struct proc_info*)a)->oomadj,
+        ((struct proc_info*)b)->oomadj
+        );
+}
diff --git a/runconuid/Android.mk b/runconuid/Android.mk
new file mode 100644
index 0000000..c36baae
--- /dev/null
+++ b/runconuid/Android.mk
@@ -0,0 +1,15 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_CFLAGS := -std=c11 -Wall -Werror
+
+LOCAL_SRC_FILES:= runconuid.cpp
+
+LOCAL_MODULE:= runconuid
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
+LOCAL_MODULE_TAGS := debug
+
+LOCAL_STATIC_LIBRARIES := libselinux
+
+include $(BUILD_EXECUTABLE)
diff --git a/runconuid/MODULE_LICENSE_APACHE2 b/runconuid/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/runconuid/MODULE_LICENSE_APACHE2
diff --git a/runconuid/NOTICE b/runconuid/NOTICE
new file mode 100644
index 0000000..c5b1efa
--- /dev/null
+++ b/runconuid/NOTICE
@@ -0,0 +1,190 @@
+
+   Copyright (c) 2005-2008, The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
diff --git a/runconuid/runconuid.cpp b/runconuid/runconuid.cpp
new file mode 100644
index 0000000..496d51f
--- /dev/null
+++ b/runconuid/runconuid.cpp
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+const char* optstr = "<1u:g:G:c:s";
+const char* usage =
+    R"(usage: runconuid [-s] [-u UID] [-g GID] [-G GROUPS] [-c CONTEXT] COMMAND ARGS
+
+Run a command in the specified security context, as the specified user,
+with the specified group membership.
+
+-c  SELinux context
+-g  Group ID by name or numeric value
+-G  List of groups by name or numeric value
+-s  Set enforcing mode
+-u  User ID by name or numeric value
+)";
+
+#include <assert.h>
+#include <grp.h>
+#include <pwd.h>
+#include <selinux/selinux.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <sys/ptrace.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+static uid_t uid = -1;
+static gid_t gid = -1;
+static gid_t* groups = nullptr;
+static size_t ngroups = 0;
+static char* context = nullptr;
+static bool setenforce = false;
+static char** child_argv = nullptr;
+
+[[noreturn]] void perror_exit(const char* message) {
+  perror(message);
+  exit(1);
+}
+
+void do_child(void) {
+  if (context && setexeccon(context) < 0) {
+    perror_exit("Setting context to failed");
+  }
+
+  if (ngroups && setgroups(ngroups, groups) < 0) {
+    perror_exit("Setting supplementary groups failed.");
+  }
+
+  if (gid != (gid_t) -1 && setresgid(gid, gid, gid) < 0) {
+    perror_exit("Setting group failed.");
+  }
+
+  if (uid != (uid_t) -1 && setresuid(uid, uid, uid) < 0) {
+    perror_exit("Setting user failed.");
+  }
+
+  ptrace(PTRACE_TRACEME, 0, 0, 0);
+  raise(SIGSTOP);
+  execvp(child_argv[0], child_argv);
+  perror_exit("Failed to execve");
+}
+
+uid_t lookup_uid(char* c) {
+  struct passwd* pw;
+  uid_t u;
+
+  if (sscanf(c, "%d", &u) == 1) {
+    return u;
+  }
+
+  if ((pw = getpwnam(c)) != 0) {
+    return pw->pw_uid;
+  }
+
+  perror_exit("Could not resolve user ID by name");
+}
+
+gid_t lookup_gid(char* c) {
+  struct group* gr;
+  gid_t g;
+
+  if (sscanf(c, "%d", &g) == 1) {
+    return g;
+  }
+
+  if ((gr = getgrnam(c)) != 0) {
+    return gr->gr_gid;
+  }
+
+  perror_exit("Could not resolve group ID by name");
+}
+
+void lookup_groups(char* c) {
+  char* group;
+
+  // Count the number of groups
+  for (group = c; *group; group++) {
+    if (*group == ',') {
+      ngroups++;
+      *group = '\0';
+    }
+  }
+
+  // The last group is not followed by a comma.
+  ngroups++;
+
+  // Allocate enough space for all of them
+  groups = (gid_t*)calloc(ngroups, sizeof(gid_t));
+  group = c;
+
+  // Fill in the group IDs
+  for (size_t n = 0; n < ngroups; n++) {
+    groups[n] = lookup_gid(group);
+    group += strlen(group) + 1;
+  }
+}
+
+void parse_arguments(int argc, char** argv) {
+  int c;
+
+  while ((c = getopt(argc, argv, optstr)) != -1) {
+    switch (c) {
+      case 'u':
+        uid = lookup_uid(optarg);
+        break;
+      case 'g':
+        gid = lookup_gid(optarg);
+        break;
+      case 'G':
+        lookup_groups(optarg);
+        break;
+      case 's':
+        setenforce = true;
+        break;
+      case 'c':
+        context = optarg;
+        break;
+      default:
+        perror_exit(usage);
+        break;
+    }
+  }
+
+  child_argv = &argv[optind];
+
+  if (optind == argc) {
+    perror_exit(usage);
+  }
+}
+
+int main(int argc, char** argv) {
+  pid_t child;
+
+  parse_arguments(argc, argv);
+  child = fork();
+
+  if (child < 0) {
+    perror_exit("Could not fork.");
+  }
+
+  if (setenforce && is_selinux_enabled()) {
+    if (security_setenforce(0) < 0) {
+      perror("Couldn't set enforcing status to 0");
+    }
+  }
+
+  if (child == 0) {
+    do_child();
+  }
+
+  if (ptrace(PTRACE_ATTACH, child, 0, 0) < 0) {
+    int err = errno;
+    kill(SIGKILL, child);
+    errno = err;
+    perror_exit("Could not ptrace child.");
+  }
+
+  // Wait for the SIGSTOP
+  int status = 0;
+  if (-1 == wait(&status)) {
+    perror_exit("Could not wait for child SIGSTOP");
+  }
+
+  // Trace all syscalls.
+  ptrace(PTRACE_SETOPTIONS, child, 0, PTRACE_O_TRACESYSGOOD);
+
+  while (1) {
+    ptrace(PTRACE_SYSCALL, child, 0, 0);
+    waitpid(child, &status, 0);
+
+    // Child raises SIGINT after the execve, on the first instruction.
+    if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP) {
+      break;
+    }
+
+    // Child did some other syscall.
+    if (WIFSTOPPED(status) && WSTOPSIG(status) & 0x80) {
+      continue;
+    }
+
+    // Child exited.
+    if (WIFEXITED(status)) {
+      exit(WEXITSTATUS(status));
+    }
+  }
+
+  if (setenforce && is_selinux_enabled()) {
+    if (security_setenforce(1) < 0) {
+      perror("Couldn't set enforcing status to 1");
+    }
+  }
+
+  ptrace(PTRACE_DETACH, child, 0, 0);
+  return 0;
+}
diff --git a/simpleperf/Android.mk b/simpleperf/Android.mk
index 9eb4be8..c9108be 100644
--- a/simpleperf/Android.mk
+++ b/simpleperf/Android.mk
@@ -16,12 +16,16 @@
 
 LOCAL_PATH := $(call my-dir)
 
-simpleperf_common_cppflags := -Wextra -Wunused -Wno-unknown-pragmas
+simpleperf_version :=  $(shell git -C $(LOCAL_PATH) rev-parse --short=12 HEAD 2>/dev/null)
+
+simpleperf_common_cppflags := -Wextra -Wunused -Wno-unknown-pragmas \
+                              -DSIMPLEPERF_REVISION='"$(simpleperf_version)"'
 
 simpleperf_cppflags_target := $(simpleperf_common_cppflags)
 
 simpleperf_cppflags_host := $(simpleperf_common_cppflags) \
                             -DUSE_BIONIC_UAPI_HEADERS -I bionic/libc/kernel \
+                            -fvisibility=hidden \
 
 simpleperf_cppflags_host_darwin := -I $(LOCAL_PATH)/nonlinux_support/include
 simpleperf_cppflags_host_windows := -I $(LOCAL_PATH)/nonlinux_support/include
@@ -30,24 +34,32 @@
 LLVM_ROOT_PATH := external/llvm
 include $(LLVM_ROOT_PATH)/llvm.mk
 
-simpleperf_shared_libraries_target := \
-  libbacktrace \
-  libunwind \
-  libbase \
-  liblog \
-  libutils \
-  libLLVM \
-
 simpleperf_static_libraries_target := \
   libbacktrace_offline \
-  liblzma \
+  libbacktrace \
+  libunwind \
   libziparchive \
   libz \
+  libbase \
+  libcutils \
+  liblog \
+  libutils \
+  liblzma \
+  libLLVMObject \
+  libLLVMBitReader \
+  libLLVMMC \
+  libLLVMMCParser \
+  libLLVMCore \
+  libLLVMSupport \
+  libprotobuf-cpp-lite \
+  libevent \
+  libc \
 
 simpleperf_static_libraries_host := \
-  libziparchive-host \
+  libziparchive \
   libbase \
   liblog \
+  liblzma \
   libz \
   libutils \
   libLLVMObject \
@@ -56,23 +68,25 @@
   libLLVMMCParser \
   libLLVMCore \
   libLLVMSupport \
+  libprotobuf-cpp-lite \
 
 simpleperf_static_libraries_host_linux := \
   libbacktrace_offline \
   libbacktrace \
   libunwind \
   libcutils \
-  liblzma \
+  libevent \
 
 simpleperf_ldlibs_host_linux := -lrt
 
 # libsimpleperf
 # =========================================================
 libsimpleperf_src_files := \
-  callchain.cpp \
   cmd_dumprecord.cpp \
   cmd_help.cpp \
+  cmd_kmem.cpp \
   cmd_report.cpp \
+  cmd_report_sample.cpp \
   command.cpp \
   dso.cpp \
   event_attr.cpp \
@@ -82,8 +96,9 @@
   read_elf.cpp \
   record.cpp \
   record_file_reader.cpp \
-  sample_tree.cpp \
+  report_sample.proto \
   thread_tree.cpp \
+  tracing.cpp \
   utils.cpp \
 
 libsimpleperf_src_files_linux := \
@@ -94,6 +109,7 @@
   environment.cpp \
   event_fd.cpp \
   event_selection_set.cpp \
+  IOEventLoop.cpp \
   record_file_writer.cpp \
   workload.cpp \
 
@@ -115,8 +131,8 @@
   $(libsimpleperf_src_files_linux) \
 
 LOCAL_STATIC_LIBRARIES := $(simpleperf_static_libraries_target)
-LOCAL_SHARED_LIBRARIES := $(simpleperf_shared_libraries_target)
-LOCAL_MULTILIB := first
+LOCAL_MULTILIB := both
+LOCAL_PROTOC_OPTIMIZE_TYPE := lite-static
 include $(LLVM_DEVICE_BUILD_MK)
 include $(BUILD_STATIC_LIBRARY)
 
@@ -137,6 +153,8 @@
 LOCAL_STATIC_LIBRARIES_linux := $(simpleperf_static_libraries_host_linux)
 LOCAL_LDLIBS_linux := $(simpleperf_ldlibs_host_linux)
 LOCAL_MULTILIB := first
+LOCAL_PROTOC_OPTIMIZE_TYPE := lite-static
+LOCAL_CXX_STL := libc++_static
 include $(LLVM_HOST_BUILD_MK)
 include $(BUILD_HOST_STATIC_LIBRARY)
 
@@ -153,10 +171,20 @@
 LOCAL_CPPFLAGS := $(simpleperf_cppflags_target)
 LOCAL_SRC_FILES := main.cpp
 LOCAL_STATIC_LIBRARIES := libsimpleperf $(simpleperf_static_libraries_target)
-LOCAL_SHARED_LIBRARIES := $(simpleperf_shared_libraries_target)
-LOCAL_MULTILIB := first
+ifdef TARGET_2ND_ARCH
+LOCAL_MULTILIB := both
+LOCAL_MODULE_STEM_32 := simpleperf32
+LOCAL_MODULE_STEM_64 := simpleperf
+endif
+LOCAL_FORCE_STATIC_EXECUTABLE := true
+include $(LLVM_DEVICE_BUILD_MK)
 include $(BUILD_EXECUTABLE)
 
+$(call dist-for-goals,sdk,$(ALL_MODULES.simpleperf.BUILT))
+ifdef TARGET_2ND_ARCH
+$(call dist-for-goals,sdk,$(ALL_MODULES.simpleperf$(TARGET_2ND_ARCH_MODULE_SUFFIX).BUILT))
+endif
+
 # simpleperf host
 include $(CLEAR_VARS)
 LOCAL_MODULE := simpleperf
@@ -170,20 +198,56 @@
 LOCAL_STATIC_LIBRARIES_linux := $(simpleperf_static_libraries_host_linux)
 LOCAL_LDLIBS_linux := $(simpleperf_ldlibs_host_linux)
 LOCAL_MULTILIB := first
+LOCAL_CXX_STL := libc++_static
 include $(LLVM_HOST_BUILD_MK)
 include $(BUILD_HOST_EXECUTABLE)
 
+$(call dist-for-goals,sdk,$(LOCAL_BUILT_MODULE):simpleperf_host)
+$(call dist-for-goals,win_sdk,$(ALL_MODULES.host_cross_simpleperf.BUILT))
+
+# simpleperf report script
+include $(CLEAR_VARS)
+LOCAL_PREBUILT_EXECUTABLES := simpleperf_report.py
+include $(BUILD_HOST_PREBUILT)
+
+$(call dist-for-goals,sdk,$(ALL_MODULES.simpleperf_report.BUILT))
+
+
+# libsimpleperf_report.so
+# It is the shared library used on host by python scripts
+# to report samples in different ways.
+# =========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := libsimpleperf_report
+LOCAL_MODULE_HOST_OS := darwin linux windows
+LOCAL_CPPFLAGS := $(simpleperf_cppflags_host)
+LOCAL_CPPFLAGS := $(simpleperf_cppflags_host)
+LOCAL_CPPFLAGS_darwin := $(simpleperf_cppflags_host_darwin)
+LOCAL_CPPFLAGS_linux := $(simpleperf_cppflags_host_linux)
+LOCAL_CPPFLAGS_windows := $(simpleperf_cppflags_host_windows)
+LOCAL_SRC_FILES := report_lib_interface.cpp
+LOCAL_STATIC_LIBRARIES := libsimpleperf $(simpleperf_static_libraries_host)
+LOCAL_STATIC_LIBRARIES_linux := $(simpleperf_static_libraries_host_linux)
+LOCAL_LDLIBS_linux := $(simpleperf_ldlibs_host_linux) -Wl,--exclude-libs,ALL
+LOCAL_MULTILIB := first
+LOCAL_CXX_STL := libc++_static
+include $(LLVM_HOST_BUILD_MK)
+include $(BUILD_HOST_SHARED_LIBRARY)
+
 
 # simpleperf_unit_test
 # =========================================================
 simpleperf_unit_test_src_files := \
+  cmd_kmem_test.cpp \
   cmd_report_test.cpp \
+  cmd_report_sample_test.cpp \
   command_test.cpp \
   gtest_main.cpp \
   read_apk_test.cpp \
   read_elf_test.cpp \
   record_test.cpp \
   sample_tree_test.cpp \
+  utils_test.cpp \
 
 simpleperf_unit_test_src_files_linux := \
   cmd_dumprecord_test.cpp \
@@ -191,6 +255,7 @@
   cmd_record_test.cpp \
   cmd_stat_test.cpp \
   environment_test.cpp \
+  IOEventLoop_test.cpp \
   record_file_test.cpp \
   workload_test.cpp \
 
@@ -204,8 +269,15 @@
   $(simpleperf_unit_test_src_files_linux) \
 
 LOCAL_STATIC_LIBRARIES += libsimpleperf $(simpleperf_static_libraries_target)
-LOCAL_SHARED_LIBRARIES := $(simpleperf_shared_libraries_target)
+LOCAL_POST_LINK_CMD = \
+  TMP_FILE=`mktemp $(OUT_DIR)/simpleperf-post-link-XXXXXXXXXX` && \
+  (cd $(LOCAL_PATH)/testdata && zip - -0 -r .) > $$TMP_FILE && \
+  $($(LOCAL_2ND_ARCH_VAR_PREFIX)TARGET_OBJCOPY) --add-section .testzipdata=$$TMP_FILE $(linked_module) && \
+  rm -f $$TMP_FILE
+
 LOCAL_MULTILIB := first
+LOCAL_FORCE_STATIC_EXECUTABLE := true
+include $(LLVM_DEVICE_BUILD_MK)
 include $(BUILD_NATIVE_TEST)
 
 # simpleperf_unit_test host
@@ -238,8 +310,9 @@
 LOCAL_CPPFLAGS := $(simpleperf_cppflags_target)
 LOCAL_SRC_FILES := $(simpleperf_cpu_hotplug_test_src_files)
 LOCAL_STATIC_LIBRARIES := libsimpleperf $(simpleperf_static_libraries_target)
-LOCAL_SHARED_LIBRARIES := $(simpleperf_shared_libraries_target)
-LOCAL_MULTILIB := first
+LOCAL_MULTILIB := both
+LOCAL_FORCE_STATIC_EXECUTABLE := true
+include $(LLVM_DEVICE_BUILD_MK)
 include $(BUILD_NATIVE_TEST)
 
 # simpleperf_cpu_hotplug_test linux host
@@ -270,11 +343,11 @@
 include $(CLEAR_VARS)
 LOCAL_CLANG := true
 LOCAL_MODULE := libsimpleperf_cts_test
-LOCAL_CPPFLAGS := $(simpleperf_cppflags_target) -DIN_CTS_TEST
+LOCAL_CPPFLAGS := $(simpleperf_cppflags_target)
 LOCAL_SRC_FILES := $(libsimpleperf_cts_test_src_files)
 LOCAL_STATIC_LIBRARIES := $(simpleperf_static_libraries_target)
-LOCAL_SHARED_LIBRARIES := $(simpleperf_shared_libraries_target)
 LOCAL_MULTILIB := both
+LOCAL_FORCE_STATIC_EXECUTABLE := true
 include $(LLVM_DEVICE_BUILD_MK)
 include $(BUILD_STATIC_TEST_LIBRARY)
 
@@ -283,7 +356,7 @@
 LOCAL_CLANG := true
 LOCAL_MODULE := libsimpleperf_cts_test
 LOCAL_MODULE_HOST_OS := linux
-LOCAL_CPPFLAGS := $(simpleperf_cppflags_host) -DIN_CTS_TEST
+LOCAL_CPPFLAGS := $(simpleperf_cppflags_host)
 LOCAL_CPPFLAGS_linux := $(simpleperf_cppflags_host_linux)
 LOCAL_SRC_FILES := $(libsimpleperf_cts_test_src_files)
 LOCAL_STATIC_LIBRARIES := $(simpleperf_static_libraries_host)
diff --git a/simpleperf/IOEventLoop.cpp b/simpleperf/IOEventLoop.cpp
new file mode 100644
index 0000000..de232ea
--- /dev/null
+++ b/simpleperf/IOEventLoop.cpp
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "IOEventLoop.h"
+
+#include <event2/event.h>
+#include <fcntl.h>
+
+#include <android-base/logging.h>
+
+struct IOEvent {
+  IOEventLoop* loop;
+  event* e;
+  std::function<bool()> callback;
+
+  IOEvent(IOEventLoop* loop, const std::function<bool()>& callback)
+      : loop(loop), e(nullptr), callback(callback) {}
+
+  ~IOEvent() {
+    if (e != nullptr) {
+      event_free(e);
+    }
+  }
+};
+
+IOEventLoop::IOEventLoop() : ebase_(nullptr), has_error_(false) {}
+
+IOEventLoop::~IOEventLoop() {
+  if (ebase_ != nullptr) {
+    event_base_free(ebase_);
+  }
+}
+
+bool IOEventLoop::EnsureInit() {
+  if (ebase_ == nullptr) {
+    ebase_ = event_base_new();
+    if (ebase_ == nullptr) {
+      LOG(ERROR) << "failed to call event_base_new()";
+      return false;
+    }
+  }
+  return true;
+}
+
+void IOEventLoop::EventCallbackFn(int, short, void* arg) {
+  IOEvent* e = static_cast<IOEvent*>(arg);
+  if (!e->callback()) {
+    e->loop->has_error_ = true;
+    e->loop->ExitLoop();
+  }
+}
+
+static bool MakeFdNonBlocking(int fd) {
+  int flags = fcntl(fd, F_GETFL, 0);
+  if (flags == -1 || fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
+    PLOG(ERROR) << "fcntl() failed";
+    return false;
+  }
+  return true;
+}
+
+IOEventRef IOEventLoop::AddReadEvent(int fd,
+                                     const std::function<bool()>& callback) {
+  if (!MakeFdNonBlocking(fd)) {
+    return nullptr;
+  }
+  return AddEvent(fd, EV_READ | EV_PERSIST, nullptr, callback);
+}
+
+bool IOEventLoop::AddSignalEvent(int sig,
+                                 const std::function<bool()>& callback) {
+  return AddEvent(sig, EV_SIGNAL | EV_PERSIST, nullptr, callback) != nullptr;
+}
+
+bool IOEventLoop::AddSignalEvents(std::vector<int> sigs,
+                                  const std::function<bool()>& callback) {
+  for (auto sig : sigs) {
+    if (!AddSignalEvent(sig, callback)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool IOEventLoop::AddPeriodicEvent(timeval duration,
+                                   const std::function<bool()>& callback) {
+  return AddEvent(-1, EV_PERSIST, &duration, callback) != nullptr;
+}
+
+IOEventRef IOEventLoop::AddEvent(int fd_or_sig, short events, timeval* timeout,
+                                 const std::function<bool()>& callback) {
+  if (!EnsureInit()) {
+    return nullptr;
+  }
+  std::unique_ptr<IOEvent> e(new IOEvent(this, callback));
+  e->e = event_new(ebase_, fd_or_sig, events, EventCallbackFn, e.get());
+  if (e->e == nullptr) {
+    LOG(ERROR) << "event_new() failed";
+    return nullptr;
+  }
+  if (event_add(e->e, timeout) != 0) {
+    LOG(ERROR) << "event_add() failed";
+    return nullptr;
+  }
+  events_.push_back(std::move(e));
+  return events_.back().get();
+}
+
+bool IOEventLoop::RunLoop() {
+  if (event_base_dispatch(ebase_) == -1) {
+    LOG(ERROR) << "event_base_dispatch() failed";
+    return false;
+  }
+  if (has_error_) {
+    return false;
+  }
+  return true;
+}
+
+bool IOEventLoop::ExitLoop() {
+  if (event_base_loopbreak(ebase_) == -1) {
+    LOG(ERROR) << "event_base_loopbreak() failed";
+    return false;
+  }
+  return true;
+}
+
+bool IOEventLoop::DelEvent(IOEventRef ref) {
+  IOEventLoop* loop = ref->loop;
+  for (auto it = loop->events_.begin(); it != loop->events_.end(); ++it) {
+    if (it->get() == ref) {
+      if (event_del((*it)->e) != 0) {
+        LOG(ERROR) << "event_del() failed";
+        return false;
+      }
+      loop->events_.erase(it);
+      break;
+    }
+  }
+  return true;
+}
diff --git a/simpleperf/IOEventLoop.h b/simpleperf/IOEventLoop.h
new file mode 100644
index 0000000..f35e9e0
--- /dev/null
+++ b/simpleperf/IOEventLoop.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SIMPLE_PERF_IOEVENT_LOOP_H_
+#define SIMPLE_PERF_IOEVENT_LOOP_H_
+
+#include <time.h>
+
+#include <functional>
+#include <memory>
+#include <vector>
+
+struct IOEvent;
+typedef IOEvent* IOEventRef;
+struct event_base;
+
+// IOEventLoop is a class wrapper of libevent, it monitors events happened,
+// and calls the corresponding callbacks. Possible events are: file ready to
+// read, file ready to write, signal happens, periodic timer timeout.
+class IOEventLoop {
+ public:
+  IOEventLoop();
+  ~IOEventLoop();
+
+  // Register a read Event, so [callback] is called when [fd] can be read
+  // without blocking. If registered successfully, return the reference
+  // to control the Event, otherwise return nullptr.
+  IOEventRef AddReadEvent(int fd, const std::function<bool()>& callback);
+
+  // Register a signal Event, so [callback] is called each time signal [sig]
+  // happens.
+  bool AddSignalEvent(int sig, const std::function<bool()>& callback);
+
+  // Register a vector of signal Events.
+  bool AddSignalEvents(std::vector<int> sigs,
+                       const std::function<bool()>& callback);
+
+  // Register a periodic Event, so [callback] is called periodically every
+  // [duration].
+  bool AddPeriodicEvent(timeval duration,
+                        const std::function<bool()>& callback);
+
+  // Run a loop polling for Events. It only exits when ExitLoop() is called
+  // in a callback function of registered Events.
+  bool RunLoop();
+
+  // Exit the loop started by RunLoop().
+  bool ExitLoop();
+
+  // Unregister an Event.
+  static bool DelEvent(IOEventRef ref);
+
+ private:
+  bool EnsureInit();
+  IOEventRef AddEvent(int fd_or_sig, short events, timeval* timeout,
+                      const std::function<bool()>& callback);
+  static void EventCallbackFn(int, short, void*);
+
+  event_base* ebase_;
+  std::vector<std::unique_ptr<IOEvent>> events_;
+  bool has_error_;
+};
+
+#endif  // SIMPLE_PERF_IOEVENT_LOOP_H_
diff --git a/simpleperf/IOEventLoop_test.cpp b/simpleperf/IOEventLoop_test.cpp
new file mode 100644
index 0000000..68475c7
--- /dev/null
+++ b/simpleperf/IOEventLoop_test.cpp
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "IOEventLoop.h"
+
+#include <gtest/gtest.h>
+
+#include <chrono>
+#include <thread>
+
+TEST(IOEventLoop, read) {
+  int fd[2];
+  ASSERT_EQ(0, pipe(fd));
+  IOEventLoop loop;
+  static int count;
+  static int retry_count;
+  count = 0;
+  retry_count = 0;
+  ASSERT_NE(nullptr, loop.AddReadEvent(fd[0], [&]() {
+    while (true) {
+      char c;
+      int ret = read(fd[0], &c, 1);
+      if (ret == 1) {
+        if (++count == 100) {
+          return loop.ExitLoop();
+        }
+      } else if (ret == -1 && errno == EAGAIN) {
+        retry_count++;
+        break;
+      } else {
+        return false;
+      }
+    }
+    return true;
+  }));
+  std::thread thread([&]() {
+    for (int i = 0; i < 100; ++i) {
+      usleep(1000);
+      char c;
+      write(fd[1], &c, 1);
+    }
+  });
+  ASSERT_TRUE(loop.RunLoop());
+  thread.join();
+  ASSERT_EQ(100, count);
+  // Test retry_count to make sure we are not doing blocking read.
+  ASSERT_GT(retry_count, 0);
+  close(fd[0]);
+  close(fd[1]);
+}
+
+TEST(IOEventLoop, signal) {
+  IOEventLoop loop;
+  static int count;
+  count = 0;
+  ASSERT_TRUE(loop.AddSignalEvent(SIGINT, [&]() {
+    if (++count == 100) {
+      loop.ExitLoop();
+    }
+    return true;
+  }));
+  std::thread thread([]() {
+    for (int i = 0; i < 100; ++i) {
+      usleep(1000);
+      kill(getpid(), SIGINT);
+    }
+  });
+  ASSERT_TRUE(loop.RunLoop());
+  thread.join();
+  ASSERT_EQ(100, count);
+}
+
+TEST(IOEventLoop, periodic) {
+  timeval tv;
+  tv.tv_sec = 0;
+  tv.tv_usec = 1000;
+  static int count;
+  count = 0;
+  IOEventLoop loop;
+  ASSERT_TRUE(loop.AddPeriodicEvent(tv, [&]() {
+    if (++count == 100) {
+      loop.ExitLoop();
+    }
+    return true;
+  }));
+  auto start_time = std::chrono::steady_clock::now();
+  ASSERT_TRUE(loop.RunLoop());
+  auto end_time = std::chrono::steady_clock::now();
+  ASSERT_EQ(100, count);
+  double time_used = std::chrono::duration_cast<std::chrono::duration<double>>(
+                         end_time - start_time)
+                         .count();
+  // time_used is 0.1 if running precisely, and we accept small errors by using
+  // a range [0.1, 0.15).
+  ASSERT_GE(time_used, 0.1);
+  ASSERT_LT(time_used, 0.15);
+}
+
+TEST(IOEventLoop, read_and_del_event) {
+  int fd[2];
+  ASSERT_EQ(0, pipe(fd));
+  IOEventLoop loop;
+  static int count;
+  count = 0;
+  IOEventRef ref = loop.AddReadEvent(fd[0], [&]() {
+    count++;
+    return IOEventLoop::DelEvent(ref);
+  });
+  ASSERT_NE(nullptr, ref);
+
+  std::thread thread([&]() {
+    for (int i = 0; i < 100; ++i) {
+      usleep(1000);
+      char c;
+      write(fd[1], &c, 1);
+    }
+  });
+  ASSERT_TRUE(loop.RunLoop());
+  thread.join();
+  ASSERT_EQ(1, count);
+  close(fd[0]);
+  close(fd[1]);
+}
diff --git a/simpleperf/SampleComparator.h b/simpleperf/SampleComparator.h
new file mode 100644
index 0000000..9eefeb4
--- /dev/null
+++ b/simpleperf/SampleComparator.h
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SIMPLE_PERF_SAMPLE_COMPARATOR_H_
+#define SIMPLE_PERF_SAMPLE_COMPARATOR_H_
+
+#include <string.h>
+
+#include <vector>
+
+// The compare functions below are used to compare two samples by their item
+// content.
+
+template <typename T>
+int Compare(const T& a, const T& b) {
+  if (a != b) {
+    return a < b ? -1 : 1;
+  }
+  return 0;
+}
+
+#define BUILD_COMPARE_VALUE_FUNCTION(function_name, compare_part)   \
+  template <typename EntryT>                                        \
+  int function_name(const EntryT* sample1, const EntryT* sample2) { \
+    return Compare(sample1->compare_part, sample2->compare_part);   \
+  }
+
+#define BUILD_COMPARE_VALUE_FUNCTION_REVERSE(function_name, compare_part) \
+  template <typename EntryT>                                              \
+  int function_name(const EntryT* sample1, const EntryT* sample2) {       \
+    return Compare(sample2->compare_part, sample1->compare_part);         \
+  }
+
+#define BUILD_COMPARE_STRING_FUNCTION(function_name, compare_part)  \
+  template <typename EntryT>                                        \
+  int function_name(const EntryT* sample1, const EntryT* sample2) { \
+    return strcmp(sample1->compare_part, sample2->compare_part);    \
+  }
+
+BUILD_COMPARE_VALUE_FUNCTION(ComparePid, thread->pid);
+BUILD_COMPARE_VALUE_FUNCTION(CompareTid, thread->tid);
+BUILD_COMPARE_VALUE_FUNCTION_REVERSE(CompareSampleCount, sample_count);
+BUILD_COMPARE_STRING_FUNCTION(CompareComm, thread_comm);
+BUILD_COMPARE_STRING_FUNCTION(CompareDso, map->dso->Path().c_str());
+BUILD_COMPARE_STRING_FUNCTION(CompareSymbol, symbol->DemangledName());
+BUILD_COMPARE_STRING_FUNCTION(CompareDsoFrom,
+                              branch_from.map->dso->Path().c_str());
+BUILD_COMPARE_STRING_FUNCTION(CompareSymbolFrom,
+                              branch_from.symbol->DemangledName());
+
+template <typename EntryT>
+int CompareTotalPeriod(const EntryT* sample1, const EntryT* sample2) {
+  uint64_t period1 = sample1->period + sample1->accumulated_period;
+  uint64_t period2 = sample2->period + sample2->accumulated_period;
+  return Compare(period2, period1);
+}
+
+// SampleComparator is a class using a collection of compare functions to
+// compare two samples.
+
+template <typename EntryT>
+class SampleComparator {
+ public:
+  typedef int (*compare_sample_func_t)(const EntryT*, const EntryT*);
+
+  void AddCompareFunction(compare_sample_func_t func) {
+    compare_v_.push_back(func);
+  }
+
+  void AddComparator(const SampleComparator<EntryT>& other) {
+    compare_v_.insert(compare_v_.end(), other.compare_v_.begin(),
+                      other.compare_v_.end());
+  }
+
+  bool operator()(const EntryT* sample1, const EntryT* sample2) const {
+    for (const auto& func : compare_v_) {
+      int ret = func(sample1, sample2);
+      if (ret != 0) {
+        return ret < 0;
+      }
+    }
+    return false;
+  }
+
+  bool IsSameSample(const EntryT* sample1, const EntryT* sample2) const {
+    for (const auto& func : compare_v_) {
+      if (func(sample1, sample2) != 0) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  bool empty() const { return compare_v_.empty(); }
+
+ private:
+  std::vector<compare_sample_func_t> compare_v_;
+};
+
+#endif  // SIMPLE_PERF_SAMPLE_COMPARATOR_H_
diff --git a/simpleperf/SampleDisplayer.h b/simpleperf/SampleDisplayer.h
new file mode 100644
index 0000000..606f639
--- /dev/null
+++ b/simpleperf/SampleDisplayer.h
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SIMPLE_PERF_SAMPLE_DISPLAYER_H_
+#define SIMPLE_PERF_SAMPLE_DISPLAYER_H_
+
+#include <inttypes.h>
+
+#include <functional>
+#include <string>
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+
+// The display functions below are used to show items in a sample.
+
+template <typename EntryT, typename InfoT>
+std::string DisplayAccumulatedOverhead(const EntryT* sample,
+                                       const InfoT* info) {
+  uint64_t period = sample->period + sample->accumulated_period;
+  uint64_t total_period = info->total_period;
+  double percentage = (total_period != 0) ? 100.0 * period / total_period : 0.0;
+  return android::base::StringPrintf("%.2f%%", percentage);
+}
+
+template <typename EntryT, typename InfoT>
+std::string DisplaySelfOverhead(const EntryT* sample, const InfoT* info) {
+  uint64_t period = sample->period;
+  uint64_t total_period = info->total_period;
+  double percentage = (total_period != 0) ? 100.0 * period / total_period : 0.0;
+  return android::base::StringPrintf("%.2f%%", percentage);
+}
+
+#define BUILD_DISPLAY_UINT64_FUNCTION(function_name, display_part)        \
+  template <typename EntryT>                                              \
+  std::string function_name(const EntryT* sample) {                       \
+    return android::base::StringPrintf("%" PRIu64, sample->display_part); \
+  }
+
+#define BUILD_DISPLAY_HEX64_FUNCTION(function_name, display_part)           \
+  template <typename EntryT>                                                \
+  std::string function_name(const EntryT* sample) {                         \
+    return android::base::StringPrintf("0x%" PRIx64, sample->display_part); \
+  }
+
+BUILD_DISPLAY_UINT64_FUNCTION(DisplaySampleCount, sample_count);
+
+template <typename EntryT>
+std::string DisplayPid(const EntryT* sample) {
+  return android::base::StringPrintf("%d", sample->thread->pid);
+}
+
+template <typename EntryT>
+std::string DisplayTid(const EntryT* sample) {
+  return android::base::StringPrintf("%d", sample->thread->tid);
+}
+
+template <typename EntryT>
+std::string DisplayComm(const EntryT* sample) {
+  return sample->thread_comm;
+}
+
+template <typename EntryT>
+std::string DisplayDso(const EntryT* sample) {
+  return sample->map->dso->Path();
+}
+
+template <typename EntryT>
+std::string DisplaySymbol(const EntryT* sample) {
+  return sample->symbol->DemangledName();
+}
+
+template <typename EntryT>
+std::string DisplayDsoFrom(const EntryT* sample) {
+  return sample->branch_from.map->dso->Path();
+}
+
+template <typename EntryT>
+std::string DisplaySymbolFrom(const EntryT* sample) {
+  return sample->branch_from.symbol->DemangledName();
+}
+
+template <typename SampleT, typename CallChainNodeT>
+class CallgraphDisplayer {
+ public:
+  virtual ~CallgraphDisplayer() {}
+
+  void operator()(FILE* fp, const SampleT* sample) {
+    std::string prefix = "       ";
+    fprintf(fp, "%s|\n", prefix.c_str());
+    fprintf(fp, "%s-- %s\n", prefix.c_str(), PrintSampleName(sample).c_str());
+    prefix.append(3, ' ');
+    for (size_t i = 0; i < sample->callchain.children.size(); ++i) {
+      DisplayCallGraphEntry(fp, 1, prefix, sample->callchain.children[i],
+                            sample->callchain.children_period,
+                            (i + 1 == sample->callchain.children.size()));
+    }
+  }
+
+  void DisplayCallGraphEntry(FILE* fp, size_t depth, std::string prefix,
+                             const std::unique_ptr<CallChainNodeT>& node,
+                             uint64_t parent_period, bool last) {
+    if (depth > 20) {
+      LOG(WARNING) << "truncated callgraph at depth " << depth;
+      return;
+    }
+    prefix += "|";
+    fprintf(fp, "%s\n", prefix.c_str());
+    if (last) {
+      prefix.back() = ' ';
+    }
+    std::string percentage_s = "-- ";
+    if (node->period + node->children_period != parent_period) {
+      double percentage =
+          100.0 * (node->period + node->children_period) / parent_period;
+      percentage_s = android::base::StringPrintf("--%.2f%%-- ", percentage);
+    }
+    fprintf(fp, "%s%s%s\n", prefix.c_str(), percentage_s.c_str(),
+            PrintSampleName(node->chain[0]).c_str());
+    prefix.append(percentage_s.size(), ' ');
+    for (size_t i = 1; i < node->chain.size(); ++i) {
+      fprintf(fp, "%s%s\n", prefix.c_str(),
+              PrintSampleName(node->chain[i]).c_str());
+    }
+    for (size_t i = 0; i < node->children.size(); ++i) {
+      DisplayCallGraphEntry(fp, depth + 1, prefix, node->children[i],
+                            node->children_period,
+                            (i + 1 == node->children.size()));
+    }
+  }
+
+ protected:
+  virtual std::string PrintSampleName(const SampleT* sample) {
+    return sample->symbol->DemangledName();
+  }
+};
+
+// SampleDisplayer is a class using a collections of display functions to show a
+// sample.
+
+template <typename EntryT, typename InfoT>
+class SampleDisplayer {
+ public:
+  typedef std::string (*display_sample_func_t)(const EntryT*);
+  typedef std::string (*display_sample_with_info_func_t)(const EntryT*,
+                                                         const InfoT*);
+  using exclusive_display_sample_func_t =
+      std::function<void(FILE*, const EntryT*)>;
+
+ private:
+  struct Item {
+    std::string name;
+    size_t width;
+    display_sample_func_t func;
+    display_sample_with_info_func_t func_with_info;
+  };
+
+ public:
+  void SetInfo(const InfoT* info) { info_ = info; }
+
+  void AddDisplayFunction(const std::string& name, display_sample_func_t func) {
+    Item item;
+    item.name = name;
+    item.width = name.size();
+    item.func = func;
+    item.func_with_info = nullptr;
+    display_v_.push_back(item);
+  }
+
+  void AddDisplayFunction(const std::string& name,
+                          display_sample_with_info_func_t func_with_info) {
+    Item item;
+    item.name = name;
+    item.width = name.size();
+    item.func = nullptr;
+    item.func_with_info = func_with_info;
+    display_v_.push_back(item);
+  }
+
+  void AddExclusiveDisplayFunction(exclusive_display_sample_func_t func) {
+    exclusive_display_v_.push_back(func);
+  }
+
+  void AdjustWidth(const EntryT* sample) {
+    for (auto& item : display_v_) {
+      std::string data = (item.func != nullptr)
+                             ? item.func(sample)
+                             : item.func_with_info(sample, info_);
+      item.width = std::max(item.width, data.size());
+    }
+  }
+
+  void PrintNames(FILE* fp) {
+    for (size_t i = 0; i < display_v_.size(); ++i) {
+      auto& item = display_v_[i];
+      if (i != display_v_.size() - 1) {
+        fprintf(fp, "%-*s  ", static_cast<int>(item.width), item.name.c_str());
+      } else {
+        fprintf(fp, "%s\n", item.name.c_str());
+      }
+    }
+  }
+
+  void PrintSample(FILE* fp, const EntryT* sample) {
+    for (size_t i = 0; i < display_v_.size(); ++i) {
+      auto& item = display_v_[i];
+      std::string data = (item.func != nullptr)
+                             ? item.func(sample)
+                             : item.func_with_info(sample, info_);
+      if (i != display_v_.size() - 1) {
+        fprintf(fp, "%-*s  ", static_cast<int>(item.width), data.c_str());
+      } else {
+        fprintf(fp, "%s\n", data.c_str());
+      }
+    }
+    for (auto& func : exclusive_display_v_) {
+      func(fp, sample);
+    }
+  }
+
+ private:
+  const InfoT* info_;
+  std::vector<Item> display_v_;
+  std::vector<exclusive_display_sample_func_t> exclusive_display_v_;
+};
+
+#endif  // SIMPLE_PERF_SAMPLE_DISPLAYER_H_
diff --git a/simpleperf/build_id.h b/simpleperf/build_id.h
index bbd13c4..9f360bd 100644
--- a/simpleperf/build_id.h
+++ b/simpleperf/build_id.h
@@ -23,6 +23,10 @@
 
 constexpr size_t BUILD_ID_SIZE = 20;
 
+// Shared libraries can have a section called .note.gnu.build-id, containing
+// a ~20 bytes unique id. Build id is used to compare if two shared libraries
+// are actually the same. BuildId class is the representation of build id in
+// memory.
 class BuildId {
  public:
   static size_t Size() {
diff --git a/simpleperf/callchain.cpp b/simpleperf/callchain.cpp
deleted file mode 100644
index 8fdafc4..0000000
--- a/simpleperf/callchain.cpp
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "callchain.h"
-
-#include <string.h>
-
-#include <queue>
-
-#include <android-base/logging.h>
-#include "sample_tree.h"
-
-static bool MatchSampleByName(const SampleEntry* sample1, const SampleEntry* sample2) {
-  return strcmp(sample1->symbol->Name(), sample2->symbol->Name()) == 0;
-}
-
-static size_t GetMatchingLengthInNode(const CallChainNode* node,
-                                      const std::vector<SampleEntry*>& chain, size_t chain_start) {
-  size_t i, j;
-  for (i = 0, j = chain_start; i < node->chain.size() && j < chain.size(); ++i, ++j) {
-    if (!MatchSampleByName(node->chain[i], chain[j])) {
-      break;
-    }
-  }
-  return i;
-}
-
-static CallChainNode* FindMatchingNode(const std::vector<std::unique_ptr<CallChainNode>>& nodes,
-                                       const SampleEntry* sample) {
-  for (auto& node : nodes) {
-    if (MatchSampleByName(node->chain.front(), sample)) {
-      return node.get();
-    }
-  }
-  return nullptr;
-}
-
-static std::unique_ptr<CallChainNode> AllocateNode(const std::vector<SampleEntry*>& chain,
-                                                   size_t chain_start, uint64_t period,
-                                                   uint64_t children_period) {
-  std::unique_ptr<CallChainNode> node(new CallChainNode);
-  for (size_t i = chain_start; i < chain.size(); ++i) {
-    node->chain.push_back(chain[i]);
-  }
-  node->period = period;
-  node->children_period = children_period;
-  return node;
-}
-
-static void SplitNode(CallChainNode* parent, size_t parent_length) {
-  std::unique_ptr<CallChainNode> child =
-      AllocateNode(parent->chain, parent_length, parent->period, parent->children_period);
-  child->children = std::move(parent->children);
-  parent->period = 0;
-  parent->children_period = child->period + child->children_period;
-  parent->chain.resize(parent_length);
-  parent->children.clear();
-  parent->children.push_back(std::move(child));
-}
-
-void CallChainRoot::AddCallChain(const std::vector<SampleEntry*>& callchain, uint64_t period) {
-  children_period += period;
-  CallChainNode* p = FindMatchingNode(children, callchain[0]);
-  if (p == nullptr) {
-    std::unique_ptr<CallChainNode> new_node = AllocateNode(callchain, 0, period, 0);
-    children.push_back(std::move(new_node));
-    return;
-  }
-  size_t callchain_pos = 0;
-  while (true) {
-    size_t match_length = GetMatchingLengthInNode(p, callchain, callchain_pos);
-    CHECK_GT(match_length, 0u);
-    callchain_pos += match_length;
-    bool find_child = true;
-    if (match_length < p->chain.size()) {
-      SplitNode(p, match_length);
-      find_child = false;  // No need to find matching node in p->children.
-    }
-    if (callchain_pos == callchain.size()) {
-      p->period += period;
-      return;
-    }
-    p->children_period += period;
-    if (find_child) {
-      CallChainNode* np = FindMatchingNode(p->children, callchain[callchain_pos]);
-      if (np != nullptr) {
-        p = np;
-        continue;
-      }
-    }
-    std::unique_ptr<CallChainNode> new_node = AllocateNode(callchain, callchain_pos, period, 0);
-    p->children.push_back(std::move(new_node));
-    break;
-  }
-}
-
-static bool CompareNodeByPeriod(const std::unique_ptr<CallChainNode>& n1,
-                                const std::unique_ptr<CallChainNode>& n2) {
-  uint64_t period1 = n1->period + n1->children_period;
-  uint64_t period2 = n2->period + n2->children_period;
-  return period1 > period2;
-}
-
-void CallChainRoot::SortByPeriod() {
-  std::queue<std::vector<std::unique_ptr<CallChainNode>>*> queue;
-  queue.push(&children);
-  while (!queue.empty()) {
-    std::vector<std::unique_ptr<CallChainNode>>* v = queue.front();
-    queue.pop();
-    std::sort(v->begin(), v->end(), CompareNodeByPeriod);
-    for (auto& node : *v) {
-      queue.push(&node->children);
-    }
-  }
-}
diff --git a/simpleperf/callchain.h b/simpleperf/callchain.h
index 4b8a9d4..2267fec 100644
--- a/simpleperf/callchain.h
+++ b/simpleperf/callchain.h
@@ -17,27 +17,142 @@
 #ifndef SIMPLE_PERF_CALLCHAIN_H_
 #define SIMPLE_PERF_CALLCHAIN_H_
 
+#include <string.h>
+
+#include <algorithm>
+#include <functional>
 #include <memory>
+#include <queue>
 #include <vector>
 
-struct SampleEntry;
+#include <android-base/logging.h>
 
+template <typename EntryT>
 struct CallChainNode {
   uint64_t period;
   uint64_t children_period;
-  std::vector<SampleEntry*> chain;
+  std::vector<EntryT*> chain;
   std::vector<std::unique_ptr<CallChainNode>> children;
 };
 
+template <typename EntryT>
 struct CallChainRoot {
+  typedef CallChainNode<EntryT> NodeT;
   uint64_t children_period;
-  std::vector<std::unique_ptr<CallChainNode>> children;
+  std::vector<std::unique_ptr<NodeT>> children;
 
-  CallChainRoot() : children_period(0) {
+  CallChainRoot() : children_period(0) {}
+
+  void AddCallChain(
+      const std::vector<EntryT*>& callchain, uint64_t period,
+      std::function<bool(const EntryT*, const EntryT*)> is_same_sample) {
+    children_period += period;
+    NodeT* p = FindMatchingNode(children, callchain[0], is_same_sample);
+    if (p == nullptr) {
+      std::unique_ptr<NodeT> new_node = AllocateNode(callchain, 0, period, 0);
+      children.push_back(std::move(new_node));
+      return;
+    }
+    size_t callchain_pos = 0;
+    while (true) {
+      size_t match_length =
+          GetMatchingLengthInNode(p, callchain, callchain_pos, is_same_sample);
+      CHECK_GT(match_length, 0u);
+      callchain_pos += match_length;
+      bool find_child = true;
+      if (match_length < p->chain.size()) {
+        SplitNode(p, match_length);
+        find_child = false;  // No need to find matching node in p->children.
+      }
+      if (callchain_pos == callchain.size()) {
+        p->period += period;
+        return;
+      }
+      p->children_period += period;
+      if (find_child) {
+        NodeT* np = FindMatchingNode(p->children, callchain[callchain_pos],
+                                     is_same_sample);
+        if (np != nullptr) {
+          p = np;
+          continue;
+        }
+      }
+      std::unique_ptr<NodeT> new_node =
+          AllocateNode(callchain, callchain_pos, period, 0);
+      p->children.push_back(std::move(new_node));
+      break;
+    }
   }
 
-  void AddCallChain(const std::vector<SampleEntry*>& callchain, uint64_t period);
-  void SortByPeriod();
+  void SortByPeriod() {
+    std::queue<std::vector<std::unique_ptr<NodeT>>*> queue;
+    queue.push(&children);
+    while (!queue.empty()) {
+      std::vector<std::unique_ptr<NodeT>>* v = queue.front();
+      queue.pop();
+      std::sort(v->begin(), v->end(), CallChainRoot::CompareNodeByPeriod);
+      for (auto& node : *v) {
+        if (!node->children.empty()) {
+          queue.push(&node->children);
+        }
+      }
+    }
+  }
+
+ private:
+  NodeT* FindMatchingNode(
+      const std::vector<std::unique_ptr<NodeT>>& nodes, const EntryT* sample,
+      std::function<bool(const EntryT*, const EntryT*)> is_same_sample) {
+    for (auto& node : nodes) {
+      if (is_same_sample(node->chain.front(), sample)) {
+        return node.get();
+      }
+    }
+    return nullptr;
+  }
+
+  size_t GetMatchingLengthInNode(
+      NodeT* node, const std::vector<EntryT*>& chain, size_t chain_start,
+      std::function<bool(const EntryT*, const EntryT*)> is_same_sample) {
+    size_t i, j;
+    for (i = 0, j = chain_start; i < node->chain.size() && j < chain.size();
+         ++i, ++j) {
+      if (!is_same_sample(node->chain[i], chain[j])) {
+        break;
+      }
+    }
+    return i;
+  }
+
+  void SplitNode(NodeT* parent, size_t parent_length) {
+    std::unique_ptr<NodeT> child = AllocateNode(
+        parent->chain, parent_length, parent->period, parent->children_period);
+    child->children = std::move(parent->children);
+    parent->period = 0;
+    parent->children_period = child->period + child->children_period;
+    parent->chain.resize(parent_length);
+    parent->children.clear();
+    parent->children.push_back(std::move(child));
+  }
+
+  std::unique_ptr<NodeT> AllocateNode(const std::vector<EntryT*>& chain,
+                                      size_t chain_start, uint64_t period,
+                                      uint64_t children_period) {
+    std::unique_ptr<NodeT> node(new NodeT);
+    for (size_t i = chain_start; i < chain.size(); ++i) {
+      node->chain.push_back(chain[i]);
+    }
+    node->period = period;
+    node->children_period = children_period;
+    return node;
+  }
+
+  static bool CompareNodeByPeriod(const std::unique_ptr<NodeT>& n1,
+                                  const std::unique_ptr<NodeT>& n2) {
+    uint64_t period1 = n1->period + n1->children_period;
+    uint64_t period2 = n2->period + n2->children_period;
+    return period1 > period2;
+  }
 };
 
 #endif  // SIMPLE_PERF_CALLCHAIN_H_
diff --git a/simpleperf/cmd_dumprecord.cpp b/simpleperf/cmd_dumprecord.cpp
index f19cfd3..3e89b82 100644
--- a/simpleperf/cmd_dumprecord.cpp
+++ b/simpleperf/cmd_dumprecord.cpp
@@ -157,20 +157,14 @@
 }
 
 void DumpRecordCommand::DumpAttrSection() {
-  const std::vector<FileAttr>& attrs = record_file_reader_->AttrSection();
+  std::vector<AttrWithId> attrs = record_file_reader_->AttrSection();
   for (size_t i = 0; i < attrs.size(); ++i) {
     const auto& attr = attrs[i];
-    printf("file_attr %zu:\n", i + 1);
-    DumpPerfEventAttr(attr.attr, 1);
-    printf("  ids[file_section]: offset %" PRId64 ", size %" PRId64 "\n", attr.ids.offset,
-           attr.ids.size);
-    std::vector<uint64_t> ids;
-    if (!record_file_reader_->ReadIdsForAttr(attr, &ids)) {
-      return;
-    }
-    if (!ids.empty()) {
+    printf("attr %zu:\n", i + 1);
+    DumpPerfEventAttr(*attr.attr, 1);
+    if (!attr.ids.empty()) {
       printf("  ids:");
-      for (const auto& id : ids) {
+      for (const auto& id : attr.ids) {
         printf(" %" PRId64, id);
       }
       printf("\n");
diff --git a/simpleperf/cmd_dumprecord_test.cpp b/simpleperf/cmd_dumprecord_test.cpp
index 441851f..b61942b 100644
--- a/simpleperf/cmd_dumprecord_test.cpp
+++ b/simpleperf/cmd_dumprecord_test.cpp
@@ -26,3 +26,7 @@
 TEST(cmd_dump, record_file_option) {
   ASSERT_TRUE(DumpCmd()->Run({GetTestData("perf.data")}));
 }
+
+TEST(cmd_dump, dump_data_generated_by_linux_perf) {
+  ASSERT_TRUE(DumpCmd()->Run({GetTestData(PERF_DATA_GENERATED_BY_LINUX_PERF)}));
+}
diff --git a/simpleperf/cmd_help.cpp b/simpleperf/cmd_help.cpp
index 0bb6231..7054e65 100644
--- a/simpleperf/cmd_help.cpp
+++ b/simpleperf/cmd_help.cpp
@@ -26,10 +26,12 @@
  public:
   HelpCommand()
       : Command("help", "print help information for simpleperf",
-                "Usage: simpleperf help [subcommand]\n"
-                "    Without subcommand, print short help string for every subcommand.\n"
-                "    With subcommand, print long help string for the subcommand.\n\n") {
-  }
+                // clang-format off
+"Usage: simpleperf help [subcommand]\n"
+"    Without subcommand, print short help string for every subcommand.\n"
+"    With subcommand, print long help string for the subcommand.\n\n"
+                // clang-format on
+                ) {}
 
   bool Run(const std::vector<std::string>& args) override;
 
@@ -44,7 +46,9 @@
   } else {
     std::unique_ptr<Command> cmd = CreateCommandInstance(args[0]);
     if (cmd == nullptr) {
-      LOG(ERROR) << "malformed command line: can't find help string for unknown command " << args[0];
+      LOG(ERROR) << "malformed command line: can't find help string for "
+                    "unknown command "
+                 << args[0];
       LOG(ERROR) << "try using \"--help\"";
       return false;
     } else {
@@ -56,13 +60,17 @@
 
 void HelpCommand::PrintShortHelp() {
   printf(
-      "Usage: simpleperf [common options] subcommand [args_for_subcommand]\n"
-      "common options:\n"
-      "    -h/--help     Print this help information.\n"
-      "    --log <severity> Set the minimum severity of logging. Possible severities\n"
-      "                     include verbose, debug, warning, error, fatal. Default is\n"
-      "                     warning.\n"
-      "subcommands:\n");
+      // clang-format off
+"Usage: simpleperf [common options] subcommand [args_for_subcommand]\n"
+"common options:\n"
+"    -h/--help     Print this help information.\n"
+"    --log <severity> Set the minimum severity of logging. Possible severities\n"
+"                     include verbose, debug, warning, info, error, fatal.\n"
+"                     Default is info.\n"
+"    --version     Print version of simpleperf.\n"
+      "subcommands:\n"
+      // clang-format on
+      );
   for (auto& cmd_name : GetAllCommandNames()) {
     std::unique_ptr<Command> cmd = CreateCommandInstance(cmd_name);
     printf("    %-20s%s\n", cmd_name.c_str(), cmd->ShortHelpString().c_str());
@@ -74,5 +82,6 @@
 }
 
 void RegisterHelpCommand() {
-  RegisterCommand("help", [] { return std::unique_ptr<Command>(new HelpCommand); });
+  RegisterCommand("help",
+                  [] { return std::unique_ptr<Command>(new HelpCommand); });
 }
diff --git a/simpleperf/cmd_kmem.cpp b/simpleperf/cmd_kmem.cpp
new file mode 100644
index 0000000..16559a2
--- /dev/null
+++ b/simpleperf/cmd_kmem.cpp
@@ -0,0 +1,711 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "command.h"
+
+#include <unordered_map>
+
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+
+#include "callchain.h"
+#include "event_attr.h"
+#include "event_type.h"
+#include "record_file.h"
+#include "sample_tree.h"
+#include "tracing.h"
+#include "utils.h"
+
+namespace {
+
+struct SlabSample {
+  const Symbol* symbol;            // the function making allocation
+  uint64_t ptr;                    // the start address of the allocated space
+  uint64_t bytes_req;              // requested space size
+  uint64_t bytes_alloc;            // allocated space size
+  uint64_t sample_count;           // count of allocations
+  uint64_t gfp_flags;              // flags used for allocation
+  uint64_t cross_cpu_allocations;  // count of allocations freed not on the
+                                   // cpu allocating them
+  CallChainRoot<SlabSample> callchain;  // a callchain tree representing all
+                                        // callchains in this sample
+  SlabSample(const Symbol* symbol, uint64_t ptr, uint64_t bytes_req,
+             uint64_t bytes_alloc, uint64_t sample_count, uint64_t gfp_flags,
+             uint64_t cross_cpu_allocations)
+      : symbol(symbol),
+        ptr(ptr),
+        bytes_req(bytes_req),
+        bytes_alloc(bytes_alloc),
+        sample_count(sample_count),
+        gfp_flags(gfp_flags),
+        cross_cpu_allocations(cross_cpu_allocations) {}
+};
+
+struct SlabAccumulateInfo {
+  uint64_t bytes_req;
+  uint64_t bytes_alloc;
+};
+
+BUILD_COMPARE_VALUE_FUNCTION(ComparePtr, ptr);
+BUILD_COMPARE_VALUE_FUNCTION_REVERSE(CompareBytesReq, bytes_req);
+BUILD_COMPARE_VALUE_FUNCTION_REVERSE(CompareBytesAlloc, bytes_alloc);
+BUILD_COMPARE_VALUE_FUNCTION(CompareGfpFlags, gfp_flags);
+BUILD_COMPARE_VALUE_FUNCTION_REVERSE(CompareCrossCpuAllocations,
+                                     cross_cpu_allocations);
+
+BUILD_DISPLAY_HEX64_FUNCTION(DisplayPtr, ptr);
+BUILD_DISPLAY_UINT64_FUNCTION(DisplayBytesReq, bytes_req);
+BUILD_DISPLAY_UINT64_FUNCTION(DisplayBytesAlloc, bytes_alloc);
+BUILD_DISPLAY_HEX64_FUNCTION(DisplayGfpFlags, gfp_flags);
+BUILD_DISPLAY_UINT64_FUNCTION(DisplayCrossCpuAllocations,
+                              cross_cpu_allocations);
+
+static int CompareFragment(const SlabSample* sample1,
+                           const SlabSample* sample2) {
+  uint64_t frag1 = sample1->bytes_alloc - sample1->bytes_req;
+  uint64_t frag2 = sample2->bytes_alloc - sample2->bytes_req;
+  return Compare(frag2, frag1);
+}
+
+static std::string DisplayFragment(const SlabSample* sample) {
+  return android::base::StringPrintf("%" PRIu64,
+                                     sample->bytes_alloc - sample->bytes_req);
+}
+
+struct SlabSampleTree {
+  std::vector<SlabSample*> samples;
+  uint64_t total_requested_bytes;
+  uint64_t total_allocated_bytes;
+  uint64_t nr_allocations;
+  uint64_t nr_frees;
+  uint64_t nr_cross_cpu_allocations;
+};
+
+struct SlabFormat {
+  enum {
+    KMEM_ALLOC,
+    KMEM_FREE,
+  } type;
+  TracingFieldPlace call_site;
+  TracingFieldPlace ptr;
+  TracingFieldPlace bytes_req;
+  TracingFieldPlace bytes_alloc;
+  TracingFieldPlace gfp_flags;
+};
+
+class SlabSampleTreeBuilder
+    : public SampleTreeBuilder<SlabSample, SlabAccumulateInfo> {
+ public:
+  SlabSampleTreeBuilder(SampleComparator<SlabSample> sample_comparator,
+                        ThreadTree* thread_tree)
+      : SampleTreeBuilder(sample_comparator),
+        thread_tree_(thread_tree),
+        total_requested_bytes_(0),
+        total_allocated_bytes_(0),
+        nr_allocations_(0),
+        nr_cross_cpu_allocations_(0) {}
+
+  SlabSampleTree GetSampleTree() const {
+    SlabSampleTree sample_tree;
+    sample_tree.samples = GetSamples();
+    sample_tree.total_requested_bytes = total_requested_bytes_;
+    sample_tree.total_allocated_bytes = total_allocated_bytes_;
+    sample_tree.nr_allocations = nr_allocations_;
+    sample_tree.nr_frees = nr_frees_;
+    sample_tree.nr_cross_cpu_allocations = nr_cross_cpu_allocations_;
+    return sample_tree;
+  }
+
+  void AddSlabFormat(const std::vector<uint64_t>& event_ids,
+                     SlabFormat format) {
+    std::unique_ptr<SlabFormat> p(new SlabFormat(format));
+    for (auto id : event_ids) {
+      event_id_to_format_map_[id] = p.get();
+    }
+    formats_.push_back(std::move(p));
+  }
+
+ protected:
+  SlabSample* CreateSample(const SampleRecord& r, bool in_kernel,
+                           SlabAccumulateInfo* acc_info) override {
+    if (!in_kernel) {
+      // Normally we don't parse records in user space because tracepoint
+      // events all happen in kernel. But if r.ip_data.ip == 0, it may be
+      // a kernel record failed to dump ip register and is still useful.
+      if (r.ip_data.ip == 0) {
+        // It seems we are on a kernel can't dump regset for tracepoint events
+        // because of lacking perf_arch_fetch_caller_regs(). We can't get
+        // callchain, but we can still do a normal report.
+        static bool first = true;
+        if (first) {
+          first = false;
+          if (accumulate_callchain_) {
+            // The kernel doesn't seem to support dumping registers for
+            // tracepoint events because of lacking
+            // perf_arch_fetch_caller_regs().
+            LOG(WARNING) << "simpleperf may not get callchains for tracepoint"
+                         << " events because of lacking kernel support.";
+          }
+        }
+      } else {
+        return nullptr;
+      }
+    }
+    uint64_t id = r.id_data.id;
+    auto it = event_id_to_format_map_.find(id);
+    if (it == event_id_to_format_map_.end()) {
+      return nullptr;
+    }
+    const char* raw_data = r.raw_data.data;
+    SlabFormat* format = it->second;
+    if (format->type == SlabFormat::KMEM_ALLOC) {
+      uint64_t call_site = format->call_site.ReadFromData(raw_data);
+      const Symbol* symbol = thread_tree_->FindKernelSymbol(call_site);
+      uint64_t ptr = format->ptr.ReadFromData(raw_data);
+      uint64_t bytes_req = format->bytes_req.ReadFromData(raw_data);
+      uint64_t bytes_alloc = format->bytes_alloc.ReadFromData(raw_data);
+      uint64_t gfp_flags = format->gfp_flags.ReadFromData(raw_data);
+      SlabSample* sample =
+          InsertSample(std::unique_ptr<SlabSample>(new SlabSample(
+              symbol, ptr, bytes_req, bytes_alloc, 1, gfp_flags, 0)));
+      alloc_cpu_record_map_.insert(
+          std::make_pair(ptr, std::make_pair(r.cpu_data.cpu, sample)));
+      acc_info->bytes_req = bytes_req;
+      acc_info->bytes_alloc = bytes_alloc;
+      return sample;
+    } else if (format->type == SlabFormat::KMEM_FREE) {
+      uint64_t ptr = format->ptr.ReadFromData(raw_data);
+      auto it = alloc_cpu_record_map_.find(ptr);
+      if (it != alloc_cpu_record_map_.end()) {
+        SlabSample* sample = it->second.second;
+        if (r.cpu_data.cpu != it->second.first) {
+          sample->cross_cpu_allocations++;
+          nr_cross_cpu_allocations_++;
+        }
+        alloc_cpu_record_map_.erase(it);
+      }
+      nr_frees_++;
+    }
+    return nullptr;
+  }
+
+  SlabSample* CreateBranchSample(const SampleRecord&,
+                                 const BranchStackItemType&) override {
+    return nullptr;
+  }
+
+  SlabSample* CreateCallChainSample(
+      const SlabSample* sample, uint64_t ip, bool in_kernel,
+      const std::vector<SlabSample*>& callchain,
+      const SlabAccumulateInfo& acc_info) override {
+    if (!in_kernel) {
+      return nullptr;
+    }
+    const Symbol* symbol = thread_tree_->FindKernelSymbol(ip);
+    return InsertCallChainSample(
+        std::unique_ptr<SlabSample>(
+            new SlabSample(symbol, sample->ptr, acc_info.bytes_req,
+                           acc_info.bytes_alloc, 1, sample->gfp_flags, 0)),
+        callchain);
+  }
+
+  const ThreadEntry* GetThreadOfSample(SlabSample*) override { return nullptr; }
+
+  uint64_t GetPeriodForCallChain(const SlabAccumulateInfo&) override {
+    // Decide the percentage of callchain by the sample_count, so use 1 as the
+    // period when calling AddCallChain().
+    return 1;
+  }
+
+  void UpdateSummary(const SlabSample* sample) override {
+    total_requested_bytes_ += sample->bytes_req;
+    total_allocated_bytes_ += sample->bytes_alloc;
+    nr_allocations_++;
+  }
+
+  void MergeSample(SlabSample* sample1, SlabSample* sample2) override {
+    sample1->bytes_req += sample2->bytes_req;
+    sample1->bytes_alloc += sample2->bytes_alloc;
+    sample1->sample_count += sample2->sample_count;
+  }
+
+ private:
+  ThreadTree* thread_tree_;
+  uint64_t total_requested_bytes_;
+  uint64_t total_allocated_bytes_;
+  uint64_t nr_allocations_;
+  uint64_t nr_frees_;
+  uint64_t nr_cross_cpu_allocations_;
+
+  std::unordered_map<uint64_t, SlabFormat*> event_id_to_format_map_;
+  std::vector<std::unique_ptr<SlabFormat>> formats_;
+  std::unordered_map<uint64_t, std::pair<uint32_t, SlabSample*>>
+      alloc_cpu_record_map_;
+};
+
+using SlabSampleTreeSorter = SampleTreeSorter<SlabSample>;
+using SlabSampleTreeDisplayer = SampleTreeDisplayer<SlabSample, SlabSampleTree>;
+using SlabSampleCallgraphDisplayer =
+    CallgraphDisplayer<SlabSample, CallChainNode<SlabSample>>;
+
+struct EventAttrWithName {
+  perf_event_attr attr;
+  std::string name;
+  std::vector<uint64_t> event_ids;
+};
+
+class KmemCommand : public Command {
+ public:
+  KmemCommand()
+      : Command(
+            "kmem", "collect kernel memory allocation information",
+            // clang-format off
+"Usage: kmem (record [record options] | report [report options])\n"
+"kmem record\n"
+"-g        Enable call graph recording. Same as '--call-graph fp'.\n"
+"--slab    Collect slab allocation information. Default option.\n"
+"Other record options provided by simpleperf record command are also available.\n"
+"kmem report\n"
+"--children  Print the accumulated allocation info appeared in the callchain.\n"
+"            Can be used on perf.data recorded with `--call-graph fp` option.\n"
+"-g [callee|caller]  Print call graph for perf.data recorded with\n"
+"                    `--call-graph fp` option. If callee mode is used, the graph\n"
+"                     shows how functions are called from others. Otherwise, the\n"
+"                     graph shows how functions call others. Default is callee\n"
+"                     mode. The percentage shown in the graph is determined by\n"
+"                     the hit count of the callchain.\n"
+"-i          Specify path of record file, default is perf.data\n"
+"-o report_file_name  Set report file name, default is stdout.\n"
+"--slab      Report slab allocation information. Default option.\n"
+"--slab-sort key1,key2,...\n"
+"            Select the keys to sort and print slab allocation information.\n"
+"            Should be used with --slab option. Possible keys include:\n"
+"              hit         -- the allocation count.\n"
+"              caller      -- the function calling allocation.\n"
+"              ptr         -- the address of the allocated space.\n"
+"              bytes_req   -- the total requested space size.\n"
+"              bytes_alloc -- the total allocated space size.\n"
+"              fragment    -- the extra allocated space size\n"
+"                             (bytes_alloc - bytes_req).\n"
+"              gfp_flags   -- the flags used for allocation.\n"
+"              pingpong    -- the count of allocations that are freed not on\n"
+"                             the cpu allocating them.\n"
+"            The default slab sort keys are:\n"
+"              hit,caller,bytes_req,bytes_alloc,fragment,pingpong.\n"
+            // clang-format on
+            ),
+        is_record_(false),
+        use_slab_(false),
+        accumulate_callchain_(false),
+        print_callgraph_(false),
+        callgraph_show_callee_(false),
+        record_filename_("perf.data"),
+        record_file_arch_(GetBuildArch()) {}
+
+  bool Run(const std::vector<std::string>& args);
+
+ private:
+  bool ParseOptions(const std::vector<std::string>& args,
+                    std::vector<std::string>* left_args);
+  bool RecordKmemInfo(const std::vector<std::string>& record_args);
+  bool ReportKmemInfo();
+  bool PrepareToBuildSampleTree();
+  void ReadEventAttrsFromRecordFile();
+  bool ReadFeaturesFromRecordFile();
+  bool ReadSampleTreeFromRecordFile();
+  bool ProcessRecord(std::unique_ptr<Record> record);
+  void ProcessTracingData(const std::vector<char>& data);
+  bool PrintReport();
+  void PrintReportContext(FILE* fp);
+  void PrintSlabReportContext(FILE* fp);
+
+  bool is_record_;
+  bool use_slab_;
+  std::vector<std::string> slab_sort_keys_;
+  bool accumulate_callchain_;
+  bool print_callgraph_;
+  bool callgraph_show_callee_;
+
+  std::string record_filename_;
+  std::unique_ptr<RecordFileReader> record_file_reader_;
+  std::vector<EventAttrWithName> event_attrs_;
+  std::string record_cmdline_;
+  ArchType record_file_arch_;
+
+  ThreadTree thread_tree_;
+  SlabSampleTree slab_sample_tree_;
+  std::unique_ptr<SlabSampleTreeBuilder> slab_sample_tree_builder_;
+  std::unique_ptr<SlabSampleTreeSorter> slab_sample_tree_sorter_;
+  std::unique_ptr<SlabSampleTreeDisplayer> slab_sample_tree_displayer_;
+
+  std::string report_filename_;
+};
+
+bool KmemCommand::Run(const std::vector<std::string>& args) {
+  std::vector<std::string> left_args;
+  if (!ParseOptions(args, &left_args)) {
+    return false;
+  }
+  if (!use_slab_) {
+    use_slab_ = true;
+  }
+  if (is_record_) {
+    return RecordKmemInfo(left_args);
+  }
+  return ReportKmemInfo();
+}
+
+bool KmemCommand::ParseOptions(const std::vector<std::string>& args,
+                               std::vector<std::string>* left_args) {
+  if (args.empty()) {
+    LOG(ERROR) << "No subcommand specified";
+    return false;
+  }
+  if (args[0] == "record") {
+    if (!IsRoot()) {
+      LOG(ERROR) << "simpleperf kmem record command needs root privilege";
+      return false;
+    }
+    is_record_ = true;
+    size_t i;
+    for (i = 1; i < args.size() && !args[i].empty() && args[i][0] == '-'; ++i) {
+      if (args[i] == "-g") {
+        left_args->push_back("--call-graph");
+        left_args->push_back("fp");
+      } else if (args[i] == "--slab") {
+        use_slab_ = true;
+      } else {
+        left_args->push_back(args[i]);
+      }
+    }
+    left_args->insert(left_args->end(), args.begin() + i, args.end());
+  } else if (args[0] == "report") {
+    is_record_ = false;
+    for (size_t i = 1; i < args.size(); ++i) {
+      if (args[i] == "--children") {
+        accumulate_callchain_ = true;
+      } else if (args[i] == "-g") {
+        print_callgraph_ = true;
+        accumulate_callchain_ = true;
+        callgraph_show_callee_ = true;
+        if (i + 1 < args.size() && args[i + 1][0] != '-') {
+          ++i;
+          if (args[i] == "callee") {
+            callgraph_show_callee_ = true;
+          } else if (args[i] == "caller") {
+            callgraph_show_callee_ = false;
+          } else {
+            LOG(ERROR) << "Unknown argument with -g option: " << args[i];
+            return false;
+          }
+        }
+      } else if (args[i] == "-i") {
+        if (!NextArgumentOrError(args, &i)) {
+          return false;
+        }
+        record_filename_ = args[i];
+      } else if (args[i] == "-o") {
+        if (!NextArgumentOrError(args, &i)) {
+          return false;
+        }
+        report_filename_ = args[i];
+      } else if (args[i] == "--slab") {
+        use_slab_ = true;
+      } else if (args[i] == "--slab-sort") {
+        if (!NextArgumentOrError(args, &i)) {
+          return false;
+        }
+        slab_sort_keys_ = android::base::Split(args[i], ",");
+      } else {
+        ReportUnknownOption(args, i);
+        return false;
+      }
+    }
+  } else {
+    LOG(ERROR) << "Unknown subcommand for " << Name() << ": " << args[0]
+               << ". Try `simpleperf help " << Name() << "`";
+    return false;
+  }
+  return true;
+}
+
+bool KmemCommand::RecordKmemInfo(const std::vector<std::string>& record_args) {
+  std::vector<std::string> args;
+  if (use_slab_) {
+    std::vector<std::string> trace_events = {
+        "kmem:kmalloc",      "kmem:kmem_cache_alloc",
+        "kmem:kmalloc_node", "kmem:kmem_cache_alloc_node",
+        "kmem:kfree",        "kmem:kmem_cache_free"};
+    for (const auto& name : trace_events) {
+      if (ParseEventType(name)) {
+        args.insert(args.end(), {"-e", name});
+      }
+    }
+  }
+  if (args.empty()) {
+    LOG(ERROR) << "Kernel allocation related trace events are not supported.";
+    return false;
+  }
+  args.push_back("-a");
+  args.insert(args.end(), record_args.begin(), record_args.end());
+  std::unique_ptr<Command> record_cmd = CreateCommandInstance("record");
+  if (record_cmd == nullptr) {
+    LOG(ERROR) << "record command isn't available";
+    return false;
+  }
+  return record_cmd->Run(args);
+}
+
+bool KmemCommand::ReportKmemInfo() {
+  if (!PrepareToBuildSampleTree()) {
+    return false;
+  }
+  record_file_reader_ = RecordFileReader::CreateInstance(record_filename_);
+  if (record_file_reader_ == nullptr) {
+    return false;
+  }
+  ReadEventAttrsFromRecordFile();
+  if (!ReadFeaturesFromRecordFile()) {
+    return false;
+  }
+  if (!ReadSampleTreeFromRecordFile()) {
+    return false;
+  }
+  if (!PrintReport()) {
+    return false;
+  }
+  return true;
+}
+
+bool KmemCommand::PrepareToBuildSampleTree() {
+  if (use_slab_) {
+    if (slab_sort_keys_.empty()) {
+      slab_sort_keys_ = {"hit",         "caller",   "bytes_req",
+                         "bytes_alloc", "fragment", "pingpong"};
+    }
+    SampleComparator<SlabSample> comparator;
+    SampleComparator<SlabSample> sort_comparator;
+    SampleDisplayer<SlabSample, SlabSampleTree> displayer;
+    std::string accumulated_name = accumulate_callchain_ ? "Accumulated_" : "";
+
+    if (print_callgraph_) {
+      displayer.AddExclusiveDisplayFunction(SlabSampleCallgraphDisplayer());
+    }
+
+    for (const auto& key : slab_sort_keys_) {
+      if (key == "hit") {
+        sort_comparator.AddCompareFunction(CompareSampleCount);
+        displayer.AddDisplayFunction(accumulated_name + "Hit",
+                                     DisplaySampleCount);
+      } else if (key == "caller") {
+        comparator.AddCompareFunction(CompareSymbol);
+        displayer.AddDisplayFunction("Caller", DisplaySymbol);
+      } else if (key == "ptr") {
+        comparator.AddCompareFunction(ComparePtr);
+        displayer.AddDisplayFunction("Ptr", DisplayPtr);
+      } else if (key == "bytes_req") {
+        sort_comparator.AddCompareFunction(CompareBytesReq);
+        displayer.AddDisplayFunction(accumulated_name + "BytesReq",
+                                     DisplayBytesReq);
+      } else if (key == "bytes_alloc") {
+        sort_comparator.AddCompareFunction(CompareBytesAlloc);
+        displayer.AddDisplayFunction(accumulated_name + "BytesAlloc",
+                                     DisplayBytesAlloc);
+      } else if (key == "fragment") {
+        sort_comparator.AddCompareFunction(CompareFragment);
+        displayer.AddDisplayFunction(accumulated_name + "Fragment",
+                                     DisplayFragment);
+      } else if (key == "gfp_flags") {
+        comparator.AddCompareFunction(CompareGfpFlags);
+        displayer.AddDisplayFunction("GfpFlags", DisplayGfpFlags);
+      } else if (key == "pingpong") {
+        sort_comparator.AddCompareFunction(CompareCrossCpuAllocations);
+        displayer.AddDisplayFunction("Pingpong", DisplayCrossCpuAllocations);
+      } else {
+        LOG(ERROR) << "Unknown sort key for slab allocation: " << key;
+        return false;
+      }
+      slab_sample_tree_builder_.reset(
+          new SlabSampleTreeBuilder(comparator, &thread_tree_));
+      slab_sample_tree_builder_->SetCallChainSampleOptions(
+          accumulate_callchain_, print_callgraph_, !callgraph_show_callee_,
+          false);
+      sort_comparator.AddComparator(comparator);
+      slab_sample_tree_sorter_.reset(new SlabSampleTreeSorter(sort_comparator));
+      slab_sample_tree_displayer_.reset(new SlabSampleTreeDisplayer(displayer));
+    }
+  }
+  return true;
+}
+
+void KmemCommand::ReadEventAttrsFromRecordFile() {
+  std::vector<AttrWithId> attrs = record_file_reader_->AttrSection();
+  for (const auto& attr_with_id : attrs) {
+    EventAttrWithName attr;
+    attr.attr = *attr_with_id.attr;
+    attr.event_ids = attr_with_id.ids;
+    attr.name = GetEventNameByAttr(attr.attr);
+    event_attrs_.push_back(attr);
+  }
+}
+
+bool KmemCommand::ReadFeaturesFromRecordFile() {
+  std::string arch =
+      record_file_reader_->ReadFeatureString(PerfFileFormat::FEAT_ARCH);
+  if (!arch.empty()) {
+    record_file_arch_ = GetArchType(arch);
+    if (record_file_arch_ == ARCH_UNSUPPORTED) {
+      return false;
+    }
+  }
+  std::vector<std::string> cmdline = record_file_reader_->ReadCmdlineFeature();
+  if (!cmdline.empty()) {
+    record_cmdline_ = android::base::Join(cmdline, ' ');
+  }
+  if (record_file_reader_->HasFeature(PerfFileFormat::FEAT_TRACING_DATA)) {
+    std::vector<char> tracing_data;
+    if (!record_file_reader_->ReadFeatureSection(
+            PerfFileFormat::FEAT_TRACING_DATA, &tracing_data)) {
+      return false;
+    }
+    ProcessTracingData(tracing_data);
+  }
+  return true;
+}
+
+bool KmemCommand::ReadSampleTreeFromRecordFile() {
+  if (!record_file_reader_->ReadDataSection(
+          [this](std::unique_ptr<Record> record) {
+            return ProcessRecord(std::move(record));
+          })) {
+    return false;
+  }
+  if (use_slab_) {
+    slab_sample_tree_ = slab_sample_tree_builder_->GetSampleTree();
+    slab_sample_tree_sorter_->Sort(slab_sample_tree_.samples, print_callgraph_);
+  }
+  return true;
+}
+
+bool KmemCommand::ProcessRecord(std::unique_ptr<Record> record) {
+  thread_tree_.Update(*record);
+  if (record->type() == PERF_RECORD_SAMPLE) {
+    if (use_slab_) {
+      slab_sample_tree_builder_->ProcessSampleRecord(
+          *static_cast<const SampleRecord*>(record.get()));
+    }
+  } else if (record->type() == PERF_RECORD_TRACING_DATA) {
+    const auto& r = *static_cast<TracingDataRecord*>(record.get());
+    ProcessTracingData(std::vector<char>(r.data, r.data + r.data_size));
+  }
+  return true;
+}
+
+void KmemCommand::ProcessTracingData(const std::vector<char>& data) {
+  Tracing tracing(data);
+  for (auto& attr : event_attrs_) {
+    if (attr.attr.type == PERF_TYPE_TRACEPOINT) {
+      uint64_t trace_event_id = attr.attr.config;
+      attr.name = tracing.GetTracingEventNameHavingId(trace_event_id);
+      TracingFormat format = tracing.GetTracingFormatHavingId(trace_event_id);
+      if (use_slab_) {
+        if (format.name == "kmalloc" || format.name == "kmem_cache_alloc" ||
+            format.name == "kmalloc_node" ||
+            format.name == "kmem_cache_alloc_node") {
+          SlabFormat f;
+          f.type = SlabFormat::KMEM_ALLOC;
+          format.GetField("call_site", f.call_site);
+          format.GetField("ptr", f.ptr);
+          format.GetField("bytes_req", f.bytes_req);
+          format.GetField("bytes_alloc", f.bytes_alloc);
+          format.GetField("gfp_flags", f.gfp_flags);
+          slab_sample_tree_builder_->AddSlabFormat(attr.event_ids, f);
+        } else if (format.name == "kfree" || format.name == "kmem_cache_free") {
+          SlabFormat f;
+          f.type = SlabFormat::KMEM_FREE;
+          format.GetField("call_site", f.call_site);
+          format.GetField("ptr", f.ptr);
+          slab_sample_tree_builder_->AddSlabFormat(attr.event_ids, f);
+        }
+      }
+    }
+  }
+}
+
+bool KmemCommand::PrintReport() {
+  std::unique_ptr<FILE, decltype(&fclose)> file_handler(nullptr, fclose);
+  FILE* report_fp = stdout;
+  if (!report_filename_.empty()) {
+    file_handler.reset(fopen(report_filename_.c_str(), "w"));
+    if (file_handler == nullptr) {
+      PLOG(ERROR) << "failed to open " << report_filename_;
+      return false;
+    }
+    report_fp = file_handler.get();
+  }
+  PrintReportContext(report_fp);
+  if (use_slab_) {
+    fprintf(report_fp, "\n\n");
+    PrintSlabReportContext(report_fp);
+    slab_sample_tree_displayer_->DisplaySamples(
+        report_fp, slab_sample_tree_.samples, &slab_sample_tree_);
+  }
+  return true;
+}
+
+void KmemCommand::PrintReportContext(FILE* fp) {
+  if (!record_cmdline_.empty()) {
+    fprintf(fp, "Cmdline: %s\n", record_cmdline_.c_str());
+  }
+  fprintf(fp, "Arch: %s\n", GetArchString(record_file_arch_).c_str());
+  for (const auto& attr : event_attrs_) {
+    fprintf(fp, "Event: %s (type %u, config %llu)\n", attr.name.c_str(),
+            attr.attr.type, attr.attr.config);
+  }
+}
+
+void KmemCommand::PrintSlabReportContext(FILE* fp) {
+  fprintf(fp, "Slab allocation information:\n");
+  fprintf(fp, "Total requested bytes: %" PRIu64 "\n",
+          slab_sample_tree_.total_requested_bytes);
+  fprintf(fp, "Total allocated bytes: %" PRIu64 "\n",
+          slab_sample_tree_.total_allocated_bytes);
+  uint64_t fragment = slab_sample_tree_.total_allocated_bytes -
+                      slab_sample_tree_.total_requested_bytes;
+  double percentage = 0.0;
+  if (slab_sample_tree_.total_allocated_bytes != 0) {
+    percentage = 100.0 * fragment / slab_sample_tree_.total_allocated_bytes;
+  }
+  fprintf(fp, "Total fragment: %" PRIu64 ", %f%%\n", fragment, percentage);
+  fprintf(fp, "Total allocations: %" PRIu64 "\n",
+          slab_sample_tree_.nr_allocations);
+  fprintf(fp, "Total frees: %" PRIu64 "\n", slab_sample_tree_.nr_frees);
+  percentage = 0.0;
+  if (slab_sample_tree_.nr_allocations != 0) {
+    percentage = 100.0 * slab_sample_tree_.nr_cross_cpu_allocations /
+                 slab_sample_tree_.nr_allocations;
+  }
+  fprintf(fp, "Total cross cpu allocation/free: %" PRIu64 ", %f%%\n",
+          slab_sample_tree_.nr_cross_cpu_allocations, percentage);
+  fprintf(fp, "\n");
+}
+
+}  // namespace
+
+void RegisterKmemCommand() {
+  RegisterCommand("kmem",
+                  [] { return std::unique_ptr<Command>(new KmemCommand()); });
+}
diff --git a/simpleperf/cmd_kmem_test.cpp b/simpleperf/cmd_kmem_test.cpp
new file mode 100644
index 0000000..dd18858
--- /dev/null
+++ b/simpleperf/cmd_kmem_test.cpp
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+
+#include <android-base/file.h>
+#include <android-base/strings.h>
+#include <android-base/test_utils.h>
+
+#include <memory>
+
+#include "command.h"
+#include "environment.h"
+#include "event_selection_set.h"
+#include "get_test_data.h"
+#include "record.h"
+#include "record_file.h"
+#include "test_util.h"
+
+static std::unique_ptr<Command> KmemCmd() {
+  return CreateCommandInstance("kmem");
+}
+
+struct ReportResult {
+  bool success;
+  std::string content;
+  std::vector<std::string> lines;
+};
+
+static void KmemReportRawFile(const std::string& perf_data,
+                              const std::vector<std::string>& additional_args,
+                              ReportResult* result) {
+  result->success = false;
+  TemporaryFile tmp_file;
+  std::vector<std::string> args = {"report", "-i", perf_data, "-o",
+                                   tmp_file.path};
+  args.insert(args.end(), additional_args.begin(), additional_args.end());
+  ASSERT_TRUE(KmemCmd()->Run(args));
+  ASSERT_TRUE(android::base::ReadFileToString(tmp_file.path, &result->content));
+  ASSERT_TRUE(!result->content.empty());
+  std::vector<std::string> raw_lines =
+      android::base::Split(result->content, "\n");
+  result->lines.clear();
+  for (const auto& line : raw_lines) {
+    std::string s = android::base::Trim(line);
+    if (!s.empty()) {
+      result->lines.push_back(s);
+    }
+  }
+  ASSERT_GE(result->lines.size(), 2u);
+  result->success = true;
+}
+
+static void KmemReportFile(const std::string& perf_data,
+                           const std::vector<std::string>& additional_args,
+                           ReportResult* result) {
+  KmemReportRawFile(GetTestData(perf_data), additional_args, result);
+}
+
+#if defined(__linux__)
+
+static bool RunKmemRecordCmd(std::vector<std::string> v,
+                             const char* output_file = nullptr) {
+  std::unique_ptr<TemporaryFile> tmpfile;
+  std::string out_file;
+  if (output_file != nullptr) {
+    out_file = output_file;
+  } else {
+    tmpfile.reset(new TemporaryFile);
+    out_file = tmpfile->path;
+  }
+  v.insert(v.begin(), "record");
+  v.insert(v.end(), {"-o", out_file, "sleep", SLEEP_SEC});
+  return KmemCmd()->Run(v);
+}
+
+TEST(kmem_cmd, record_slab) {
+  TEST_IN_ROOT(ASSERT_TRUE(RunKmemRecordCmd({"--slab"})));
+}
+
+TEST(kmem_cmd, record_fp_callchain_sampling) {
+  TEST_IN_ROOT(ASSERT_TRUE(RunKmemRecordCmd({"--slab", "-g"})));
+  TEST_IN_ROOT(ASSERT_TRUE(RunKmemRecordCmd({"--slab", "--call-graph", "fp"})));
+}
+
+TEST(kmem_cmd, record_and_report) {
+  TemporaryFile tmp_file;
+  TEST_IN_ROOT({
+    ASSERT_TRUE(RunKmemRecordCmd({"--slab"}, tmp_file.path));
+    ReportResult result;
+    KmemReportRawFile(tmp_file.path, {}, &result);
+    ASSERT_TRUE(result.success);
+  });
+}
+
+TEST(kmem_cmd, record_and_report_callgraph) {
+  TemporaryFile tmp_file;
+  TEST_IN_ROOT({
+    ASSERT_TRUE(RunKmemRecordCmd({"--slab", "-g"}, tmp_file.path));
+    ReportResult result;
+    KmemReportRawFile(tmp_file.path, {"-g"}, &result);
+    ASSERT_TRUE(result.success);
+  });
+}
+
+#endif
+
+TEST(kmem_cmd, report) {
+  ReportResult result;
+  KmemReportFile(PERF_DATA_WITH_KMEM_SLAB_CALLGRAPH_RECORD, {}, &result);
+  ASSERT_TRUE(result.success);
+  ASSERT_NE(result.content.find("kmem:kmalloc"), std::string::npos);
+  ASSERT_NE(result.content.find("__alloc_skb"), std::string::npos);
+}
+
+TEST(kmem_cmd, report_all_sort_options) {
+  ReportResult result;
+  KmemReportFile(
+      PERF_DATA_WITH_KMEM_SLAB_CALLGRAPH_RECORD,
+      {"--slab-sort",
+       "hit,caller,ptr,bytes_req,bytes_alloc,fragment,gfp_flags,pingpong"},
+      &result);
+  ASSERT_TRUE(result.success);
+  ASSERT_NE(result.content.find("Ptr"), std::string::npos);
+  ASSERT_NE(result.content.find("GfpFlags"), std::string::npos);
+}
+
+TEST(kmem_cmd, report_callgraph) {
+  ReportResult result;
+  KmemReportFile(PERF_DATA_WITH_KMEM_SLAB_CALLGRAPH_RECORD, {"-g"}, &result);
+  ASSERT_TRUE(result.success);
+  ASSERT_NE(result.content.find("kmem:kmalloc"), std::string::npos);
+  ASSERT_NE(result.content.find("__alloc_skb"), std::string::npos);
+  ASSERT_NE(result.content.find("system_call_fastpath"), std::string::npos);
+}
diff --git a/simpleperf/cmd_record.cpp b/simpleperf/cmd_record.cpp
index ae025b0..a28cdb8 100644
--- a/simpleperf/cmd_record.cpp
+++ b/simpleperf/cmd_record.cpp
@@ -15,8 +15,8 @@
  */
 
 #include <libgen.h>
-#include <poll.h>
 #include <signal.h>
+#include <sys/prctl.h>
 #include <sys/utsname.h>
 #include <unistd.h>
 #include <set>
@@ -25,6 +25,8 @@
 #include <vector>
 
 #include <android-base/logging.h>
+#include <android-base/file.h>
+#include <android-base/parseint.h>
 #include <android-base/strings.h>
 
 #include "command.h"
@@ -32,12 +34,13 @@
 #include "environment.h"
 #include "event_selection_set.h"
 #include "event_type.h"
+#include "IOEventLoop.h"
 #include "read_apk.h"
 #include "read_elf.h"
 #include "record.h"
 #include "record_file.h"
-#include "scoped_signal_handler.h"
 #include "thread_tree.h"
+#include "tracing.h"
 #include "utils.h"
 #include "workload.h"
 
@@ -52,111 +55,145 @@
     {"ind_call", PERF_SAMPLE_BRANCH_IND_CALL},
 };
 
-static volatile bool signaled;
-static void signal_handler(int) {
-  signaled = true;
-}
+constexpr uint64_t DEFAULT_SAMPLE_FREQ_FOR_NONTRACEPOINT_EVENT = 4000;
+constexpr uint64_t DEFAULT_SAMPLE_PERIOD_FOR_TRACEPOINT_EVENT = 1;
 
-// Used in cpu-hotplug test.
-bool system_wide_perf_event_open_failed = false;
+// The max size of records dumped by kernel is 65535, and dump stack size
+// should be a multiply of 8, so MAX_DUMP_STACK_SIZE is 65528.
+constexpr uint32_t MAX_DUMP_STACK_SIZE = 65528;
+
+// The max allowed pages in mapped buffer is decided by rlimit(RLIMIT_MEMLOCK).
+// Here 1024 is a desired value for pages in mapped buffer. If mapped
+// successfully, the buffer size = 1024 * 4K (page size) = 4M.
+constexpr size_t DESIRED_PAGES_IN_MAPPED_BUFFER = 1024;
 
 class RecordCommand : public Command {
  public:
   RecordCommand()
       : Command(
             "record", "record sampling info in perf.data",
-            "Usage: simpleperf record [options] [command [command-args]]\n"
-            "    Gather sampling information when running [command].\n"
-            "    -a           System-wide collection.\n"
-            "    -b           Enable take branch stack sampling. Same as '-j any'\n"
-            "    -c count     Set event sample period.\n"
-            "    --call-graph fp | dwarf[,<dump_stack_size>]\n"
-            "                 Enable call graph recording. Use frame pointer or dwarf as the\n"
-            "                 method to parse call graph in stack. Default is dwarf,8192.\n"
-            "    --cpu cpu_item1,cpu_item2,...\n"
-            "                 Collect samples only on the selected cpus. cpu_item can be cpu\n"
-            "                 number like 1, or cpu range like 0-3.\n"
-            "    -e event1[:modifier1],event2[:modifier2],...\n"
-            "                 Select the event list to sample. Use `simpleperf list` to find\n"
-            "                 all possible event names. Modifiers can be added to define\n"
-            "                 how the event should be monitored. Possible modifiers are:\n"
-            "                   u - monitor user space events only\n"
-            "                   k - monitor kernel space events only\n"
-            "    -f freq      Set event sample frequency.\n"
-            "    -F freq      Same as '-f freq'.\n"
-            "    -g           Same as '--call-graph dwarf'.\n"
-            "    -j branch_filter1,branch_filter2,...\n"
-            "                 Enable taken branch stack sampling. Each sample\n"
-            "                 captures a series of consecutive taken branches.\n"
-            "                 The following filters are defined:\n"
-            "                   any: any type of branch\n"
-            "                   any_call: any function call or system call\n"
-            "                   any_ret: any function return or system call return\n"
-            "                   ind_call: any indirect branch\n"
-            "                   u: only when the branch target is at the user level\n"
-            "                   k: only when the branch target is in the kernel\n"
-            "                 This option requires at least one branch type among any,\n"
-            "                 any_call, any_ret, ind_call.\n"
-            "    -m mmap_pages\n"
-            "                 Set the size of the buffer used to receiving sample data from\n"
-            "                 the kernel. It should be a power of 2. The default value is 16.\n"
-            "    --no-inherit\n"
-            "                 Don't record created child threads/processes.\n"
-            "    --no-unwind  If `--call-graph dwarf` option is used, then the user's stack will\n"
-            "                 be unwound by default. Use this option to disable the unwinding of\n"
-            "                 the user's stack.\n"
-            "    -o record_file_name    Set record file name, default is perf.data.\n"
-            "    -p pid1,pid2,...\n"
-            "                 Record events on existing processes. Mutually exclusive with -a.\n"
-            "    --post-unwind\n"
-            "                 If `--call-graph dwarf` option is used, then the user's stack will\n"
-            "                 be unwound while recording by default. But it may lose records as\n"
-            "                 stacking unwinding can be time consuming. Use this option to unwind\n"
-            "                 the user's stack after recording.\n"
-            "    -t tid1,tid2,...\n"
-            "                 Record events on existing threads. Mutually exclusive with -a.\n"),
-        use_sample_freq_(true),
-        sample_freq_(4000),
+            // clang-format off
+"Usage: simpleperf record [options] [command [command-args]]\n"
+"       Gather sampling information of running [command]. And -a/-p/-t option\n"
+"       can be used to change target of sampling information.\n"
+"-a     System-wide collection.\n"
+"-b     Enable take branch stack sampling. Same as '-j any'\n"
+"-c count     Set event sample period. It means recording one sample when\n"
+"             [count] events happen. Can't be used with -f/-F option.\n"
+"             For tracepoint events, the default option is -c 1.\n"
+"--call-graph fp | dwarf[,<dump_stack_size>]\n"
+"             Enable call graph recording. Use frame pointer or dwarf debug\n"
+"             frame as the method to parse call graph in stack.\n"
+"             Default is dwarf,65528.\n"
+"--cpu cpu_item1,cpu_item2,...\n"
+"             Collect samples only on the selected cpus. cpu_item can be cpu\n"
+"             number like 1, or cpu range like 0-3.\n"
+"--dump-symbols  Dump symbols in perf.data. By default perf.data doesn't contain\n"
+"                symbol information for samples. This option is used when there\n"
+"                is no symbol information in report environment.\n"
+"--duration time_in_sec  Monitor for time_in_sec seconds instead of running\n"
+"                        [command]. Here time_in_sec may be any positive\n"
+"                        floating point number.\n"
+"-e event1[:modifier1],event2[:modifier2],...\n"
+"             Select the event list to sample. Use `simpleperf list` to find\n"
+"             all possible event names. Modifiers can be added to define how\n"
+"             the event should be monitored.\n"
+"             Possible modifiers are:\n"
+"                u - monitor user space events only\n"
+"                k - monitor kernel space events only\n"
+"-f freq      Set event sample frequency. It means recording at most [freq]\n"
+"             samples every second. For non-tracepoint events, the default\n"
+"             option is -f 4000.\n"
+"-F freq      Same as '-f freq'.\n"
+"-g           Same as '--call-graph dwarf'.\n"
+"--group event1[:modifier],event2[:modifier2],...\n"
+"             Similar to -e option. But events specified in the same --group\n"
+"             option are monitored as a group, and scheduled in and out at the\n"
+"             same time.\n"
+"-j branch_filter1,branch_filter2,...\n"
+"             Enable taken branch stack sampling. Each sample captures a series\n"
+"             of consecutive taken branches.\n"
+"             The following filters are defined:\n"
+"                any: any type of branch\n"
+"                any_call: any function call or system call\n"
+"                any_ret: any function return or system call return\n"
+"                ind_call: any indirect branch\n"
+"                u: only when the branch target is at the user level\n"
+"                k: only when the branch target is in the kernel\n"
+"             This option requires at least one branch type among any, any_call,\n"
+"             any_ret, ind_call.\n"
+"-m mmap_pages   Set the size of the buffer used to receiving sample data from\n"
+"                the kernel. It should be a power of 2. If not set, the max\n"
+"                possible value <= 1024 will be used.\n"
+"--no-dump-kernel-symbols  Don't dump kernel symbols in perf.data. By default\n"
+"                          kernel symbols will be dumped when needed.\n"
+"--no-inherit  Don't record created child threads/processes.\n"
+"--no-unwind   If `--call-graph dwarf` option is used, then the user's stack\n"
+"              will be unwound by default. Use this option to disable the\n"
+"              unwinding of the user's stack.\n"
+"-o record_file_name    Set record file name, default is perf.data.\n"
+"-p pid1,pid2,...       Record events on existing processes. Mutually exclusive\n"
+"                       with -a.\n"
+"--post-unwind  If `--call-graph dwarf` option is used, then the user's stack\n"
+"               will be unwound while recording by default. But it may lose\n"
+"               records as stacking unwinding can be time consuming. Use this\n"
+"               option to unwind the user's stack after recording.\n"
+"--symfs <dir>    Look for files with symbols relative to this directory.\n"
+"                 This option is used to provide files with symbol table and\n"
+"                 debug information, which are used by --dump-symbols and -g.\n"
+"-t tid1,tid2,... Record events on existing threads. Mutually exclusive with -a.\n"
+            // clang-format on
+            ),
+        use_sample_freq_(false),
+        sample_freq_(0),
+        use_sample_period_(false),
+        sample_period_(0),
         system_wide_collection_(false),
         branch_sampling_(0),
         fp_callchain_sampling_(false),
         dwarf_callchain_sampling_(false),
-        dump_stack_size_in_dwarf_sampling_(8192),
+        dump_stack_size_in_dwarf_sampling_(MAX_DUMP_STACK_SIZE),
         unwind_dwarf_callchain_(true),
         post_unwind_(false),
         child_inherit_(true),
-        perf_mmap_pages_(16),
+        duration_in_sec_(0),
+        can_dump_kernel_symbols_(true),
+        dump_symbols_(false),
+        event_selection_set_(false),
+        mmap_page_range_(std::make_pair(1, DESIRED_PAGES_IN_MAPPED_BUFFER)),
         record_filename_("perf.data"),
-        sample_record_count_(0) {
-    signaled = false;
-    scoped_signal_handler_.reset(
-        new ScopedSignalHandler({SIGCHLD, SIGINT, SIGTERM}, signal_handler));
+        start_sampling_time_in_ns_(0),
+        sample_record_count_(0),
+        lost_record_count_(0) {
+    // Die if parent exits.
+    prctl(PR_SET_PDEATHSIG, SIGHUP, 0, 0, 0);
   }
 
   bool Run(const std::vector<std::string>& args);
 
-  static bool ReadMmapDataCallback(const char* data, size_t size);
-
  private:
-  bool ParseOptions(const std::vector<std::string>& args, std::vector<std::string>* non_option_args);
-  bool AddMeasuredEventType(const std::string& event_type_name);
-  bool SetEventSelection();
+  bool ParseOptions(const std::vector<std::string>& args,
+                    std::vector<std::string>* non_option_args);
+  bool SetEventSelectionFlags();
   bool CreateAndInitRecordFile();
-  std::unique_ptr<RecordFileWriter> CreateRecordFile(const std::string& filename);
-  bool DumpKernelAndModuleMmaps();
-  bool DumpThreadCommAndMmaps(bool all_threads, const std::vector<pid_t>& selected_threads);
-  bool CollectRecordsFromKernel(const char* data, size_t size);
+  std::unique_ptr<RecordFileWriter> CreateRecordFile(
+      const std::string& filename);
+  bool DumpKernelSymbol();
+  bool DumpTracingData();
+  bool DumpKernelAndModuleMmaps(const perf_event_attr& attr, uint64_t event_id);
+  bool DumpThreadCommAndMmaps(const perf_event_attr& attr, uint64_t event_id);
   bool ProcessRecord(Record* record);
+  bool DumpSymbolForRecord(const SampleRecord& r, bool for_callchain);
   void UpdateRecordForEmbeddedElfPath(Record* record);
-  void UnwindRecord(Record* record);
+  bool UnwindRecord(Record* record);
   bool PostUnwind(const std::vector<std::string>& args);
   bool DumpAdditionalFeatures(const std::vector<std::string>& args);
   bool DumpBuildIdFeature();
   void CollectHitFileInfo(Record* record);
-  std::pair<std::string, uint64_t> TestForEmbeddedElf(Dso *dso, uint64_t pgoff);
 
-  bool use_sample_freq_;    // Use sample_freq_ when true, otherwise using sample_period_.
-  uint64_t sample_freq_;    // Sample 'sample_freq_' times per second.
+  bool use_sample_freq_;
+  uint64_t sample_freq_;  // Sample 'sample_freq_' times per second.
+  bool use_sample_period_;
   uint64_t sample_period_;  // Sample once when 'sample_period_' events occur.
 
   bool system_wide_collection_;
@@ -167,24 +204,25 @@
   bool unwind_dwarf_callchain_;
   bool post_unwind_;
   bool child_inherit_;
-  std::vector<pid_t> monitored_threads_;
+  double duration_in_sec_;
+  bool can_dump_kernel_symbols_;
+  bool dump_symbols_;
   std::vector<int> cpus_;
-  std::vector<EventTypeAndModifier> measured_event_types_;
   EventSelectionSet event_selection_set_;
 
-  // mmap pages used by each perf event file, should be a power of 2.
-  size_t perf_mmap_pages_;
+  std::pair<size_t, size_t> mmap_page_range_;
 
-  std::unique_ptr<RecordCache> record_cache_;
   ThreadTree thread_tree_;
   std::string record_filename_;
   std::unique_ptr<RecordFileWriter> record_file_writer_;
 
+  uint64_t start_sampling_time_in_ns_;  // nanoseconds from machine starting
+
   std::set<std::string> hit_kernel_modules_;
   std::set<std::string> hit_user_files_;
 
-  std::unique_ptr<ScopedSignalHandler> scoped_signal_handler_;
   uint64_t sample_record_count_;
+  uint64_t lost_record_count_;
 };
 
 bool RecordCommand::Run(const std::vector<std::string>& args) {
@@ -197,12 +235,12 @@
   if (!ParseOptions(args, &workload_args)) {
     return false;
   }
-  if (measured_event_types_.empty()) {
-    if (!AddMeasuredEventType(default_measured_event_type)) {
+  if (event_selection_set_.empty()) {
+    if (!event_selection_set_.AddEventType(default_measured_event_type)) {
       return false;
     }
   }
-  if (!SetEventSelection()) {
+  if (!SetEventSelectionFlags()) {
     return false;
   }
 
@@ -214,64 +252,76 @@
       return false;
     }
   }
-  if (!system_wide_collection_ && monitored_threads_.empty()) {
+  if (system_wide_collection_) {
+    event_selection_set_.AddMonitoredThreads({-1});
+  } else if (!event_selection_set_.HasMonitoredTarget()) {
     if (workload != nullptr) {
-      monitored_threads_.push_back(workload->GetPid());
+      event_selection_set_.AddMonitoredProcesses({workload->GetPid()});
       event_selection_set_.SetEnableOnExec(true);
     } else {
-      LOG(ERROR) << "No threads to monitor. Try `simpleperf help record` for help\n";
+      LOG(ERROR)
+          << "No threads to monitor. Try `simpleperf help record` for help";
       return false;
     }
   }
 
-  // 3. Open perf_event_files, create memory mapped buffers for perf_event_files, add prepare poll
-  //    for perf_event_files.
-  if (system_wide_collection_) {
-    if (!event_selection_set_.OpenEventFilesForCpus(cpus_)) {
-      system_wide_perf_event_open_failed = true;
-      return false;
-    }
-  } else {
-    if (!event_selection_set_.OpenEventFilesForThreadsOnCpus(monitored_threads_, cpus_)) {
-      return false;
-    }
-  }
-  if (!event_selection_set_.MmapEventFiles(perf_mmap_pages_)) {
+  // 3. Open perf_event_files, create mapped buffers for perf_event_files.
+  if (!event_selection_set_.OpenEventFiles(cpus_)) {
     return false;
   }
-  std::vector<pollfd> pollfds;
-  event_selection_set_.PreparePollForEventFiles(&pollfds);
+  if (!event_selection_set_.MmapEventFiles(mmap_page_range_.first,
+                                           mmap_page_range_.second)) {
+    return false;
+  }
 
   // 4. Create perf.data.
   if (!CreateAndInitRecordFile()) {
     return false;
   }
 
-  // 5. Write records in mmap buffers of perf_event_files to output file while workload is running.
-  if (workload != nullptr && !workload->Start()) {
+  // 5. Create IOEventLoop and add read/signal/periodic Events.
+  IOEventLoop loop;
+  auto callback =
+      std::bind(&RecordCommand::ProcessRecord, this, std::placeholders::_1);
+  if (!event_selection_set_.PrepareToReadMmapEventData(loop, callback)) {
     return false;
   }
-  record_cache_.reset(
-      new RecordCache(*event_selection_set_.FindEventAttrByType(measured_event_types_[0])));
-  auto callback = std::bind(&RecordCommand::CollectRecordsFromKernel, this, std::placeholders::_1,
-                            std::placeholders::_2);
-  while (true) {
-    if (!event_selection_set_.ReadMmapEventData(callback)) {
-      return false;
-    }
-    if (signaled) {
-      break;
-    }
-    poll(&pollfds[0], pollfds.size(), -1);
+  if (!event_selection_set_.HandleCpuHotplugEvents(loop, cpus_)) {
+    return false;
   }
-  std::vector<std::unique_ptr<Record>> records = record_cache_->PopAll();
-  for (auto& r : records) {
-    if (!ProcessRecord(r.get())) {
+  if (!loop.AddSignalEvents({SIGCHLD, SIGINT, SIGTERM},
+                            [&]() { return loop.ExitLoop(); })) {
+    return false;
+  }
+  if (duration_in_sec_ != 0) {
+    if (!loop.AddPeriodicEvent(SecondToTimeval(duration_in_sec_),
+                               [&]() { return loop.ExitLoop(); })) {
       return false;
     }
   }
 
-  // 6. Dump additional features, and close record file.
+  // 6. Write records in mapped buffers of perf_event_files to output file while
+  //    workload is running.
+  timespec ts;
+  if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) {
+    PLOG(ERROR) << "clock_gettime failed";
+    return false;
+  }
+  start_sampling_time_in_ns_ = ts.tv_sec * 1000000000ULL + ts.tv_nsec;
+  if (workload != nullptr && !workload->Start()) {
+    return false;
+  }
+  if (!loop.RunLoop()) {
+    return false;
+  }
+  if (!event_selection_set_.FinishReadMmapEventData()) {
+    return false;
+  }
+  if (!record_file_writer_->SortDataSection()) {
+    return false;
+  }
+
+  // 7. Dump additional features, and close record file.
   if (!DumpAdditionalFeatures(args)) {
     return false;
   }
@@ -279,21 +329,34 @@
     return false;
   }
 
-  // 7. Unwind dwarf callchain.
+  // 8. Unwind dwarf callchain.
   if (post_unwind_) {
     if (!PostUnwind(args)) {
       return false;
     }
   }
-  LOG(VERBOSE) << "Record " << sample_record_count_ << " samples.";
+
+  // 9. Show brief record result.
+  LOG(INFO) << "Samples recorded: " << sample_record_count_
+            << ". Samples lost: " << lost_record_count_ << ".";
+  if (sample_record_count_ + lost_record_count_ != 0) {
+    double lost_percent = static_cast<double>(lost_record_count_) /
+                          (lost_record_count_ + sample_record_count_);
+    constexpr double LOST_PERCENT_WARNING_BAR = 0.1;
+    if (lost_percent >= LOST_PERCENT_WARNING_BAR) {
+      LOG(WARNING) << "Lost " << (lost_percent * 100) << "% of samples, "
+                   << "consider increasing mmap_pages(-m), "
+                   << "or decreasing sample frequency(-f), "
+                   << "or increasing sample period(-c).";
+    }
+  }
   return true;
 }
 
 bool RecordCommand::ParseOptions(const std::vector<std::string>& args,
                                  std::vector<std::string>* non_option_args) {
-  std::set<pid_t> tid_set;
   size_t i;
-  for (i = 0; i < args.size() && args[i].size() > 0 && args[i][0] == '-'; ++i) {
+  for (i = 0; i < args.size() && !args[i].empty() && args[i][0] == '-'; ++i) {
     if (args[i] == "-a") {
       system_wide_collection_ = true;
     } else if (args[i] == "-b") {
@@ -308,7 +371,7 @@
         LOG(ERROR) << "Invalid sample period: '" << args[i] << "'";
         return false;
       }
-      use_sample_freq_ = false;
+      use_sample_period_ = true;
     } else if (args[i] == "--call-graph") {
       if (!NextArgumentOrError(args, &i)) {
         return false;
@@ -324,17 +387,26 @@
           char* endptr;
           uint64_t size = strtoull(strs[1].c_str(), &endptr, 0);
           if (*endptr != '\0' || size > UINT_MAX) {
-            LOG(ERROR) << "invalid dump stack size in --call-graph option: " << strs[1];
+            LOG(ERROR) << "invalid dump stack size in --call-graph option: "
+                       << strs[1];
             return false;
           }
           if ((size & 7) != 0) {
-            LOG(ERROR) << "dump stack size " << size << " is not 8-byte aligned.";
+            LOG(ERROR) << "dump stack size " << size
+                       << " is not 8-byte aligned.";
+            return false;
+          }
+          if (size >= MAX_DUMP_STACK_SIZE) {
+            LOG(ERROR) << "dump stack size " << size
+                       << " is bigger than max allowed size "
+                       << MAX_DUMP_STACK_SIZE << ".";
             return false;
           }
           dump_stack_size_in_dwarf_sampling_ = static_cast<uint32_t>(size);
         }
       } else {
-        LOG(ERROR) << "unexpected argument for --call-graph option: " << args[i];
+        LOG(ERROR) << "unexpected argument for --call-graph option: "
+                   << args[i];
         return false;
       }
     } else if (args[i] == "--cpu") {
@@ -342,13 +414,26 @@
         return false;
       }
       cpus_ = GetCpusFromString(args[i]);
+    } else if (args[i] == "--dump-symbols") {
+      dump_symbols_ = true;
+    } else if (args[i] == "--duration") {
+      if (!NextArgumentOrError(args, &i)) {
+        return false;
+      }
+      errno = 0;
+      char* endptr;
+      duration_in_sec_ = strtod(args[i].c_str(), &endptr);
+      if (duration_in_sec_ <= 0 || *endptr != '\0' || errno == ERANGE) {
+        LOG(ERROR) << "Invalid duration: " << args[i].c_str();
+        return false;
+      }
     } else if (args[i] == "-e") {
       if (!NextArgumentOrError(args, &i)) {
         return false;
       }
       std::vector<std::string> event_types = android::base::Split(args[i], ",");
       for (auto& event_type : event_types) {
-        if (!AddMeasuredEventType(event_type)) {
+        if (!event_selection_set_.AddEventType(event_type)) {
           return false;
         }
       }
@@ -356,21 +441,31 @@
       if (!NextArgumentOrError(args, &i)) {
         return false;
       }
-      char* endptr;
-      sample_freq_ = strtoull(args[i].c_str(), &endptr, 0);
-      if (*endptr != '\0' || sample_freq_ == 0) {
-        LOG(ERROR) << "Invalid sample frequency: '" << args[i] << "'";
+      if (!android::base::ParseUint(args[i].c_str(), &sample_freq_)) {
+        LOG(ERROR) << "Invalid sample frequency: " << args[i];
+        return false;
+      }
+      if (!CheckSampleFrequency(sample_freq_)) {
         return false;
       }
       use_sample_freq_ = true;
     } else if (args[i] == "-g") {
       fp_callchain_sampling_ = false;
       dwarf_callchain_sampling_ = true;
+    } else if (args[i] == "--group") {
+      if (!NextArgumentOrError(args, &i)) {
+        return false;
+      }
+      std::vector<std::string> event_types = android::base::Split(args[i], ",");
+      if (!event_selection_set_.AddEventGroup(event_types)) {
+        return false;
+      }
     } else if (args[i] == "-j") {
       if (!NextArgumentOrError(args, &i)) {
         return false;
       }
-      std::vector<std::string> branch_sampling_types = android::base::Split(args[i], ",");
+      std::vector<std::string> branch_sampling_types =
+          android::base::Split(args[i], ",");
       for (auto& type : branch_sampling_types) {
         auto it = branch_sampling_type_map.find(type);
         if (it == branch_sampling_type_map.end()) {
@@ -389,7 +484,9 @@
         LOG(ERROR) << "Invalid mmap_pages: '" << args[i] << "'";
         return false;
       }
-      perf_mmap_pages_ = pages;
+      mmap_page_range_.first = mmap_page_range_.second = pages;
+    } else if (args[i] == "--no-dump-kernel-symbols") {
+      can_dump_kernel_symbols_ = false;
     } else if (args[i] == "--no-inherit") {
       child_inherit_ = false;
     } else if (args[i] == "--no-unwind") {
@@ -403,34 +500,52 @@
       if (!NextArgumentOrError(args, &i)) {
         return false;
       }
-      if (!GetValidThreadsFromProcessString(args[i], &tid_set)) {
+      std::set<pid_t> pids;
+      if (!GetValidThreadsFromThreadString(args[i], &pids)) {
         return false;
       }
+      event_selection_set_.AddMonitoredProcesses(pids);
     } else if (args[i] == "--post-unwind") {
       post_unwind_ = true;
+    } else if (args[i] == "--symfs") {
+      if (!NextArgumentOrError(args, &i)) {
+        return false;
+      }
+      if (!Dso::SetSymFsDir(args[i])) {
+        return false;
+      }
     } else if (args[i] == "-t") {
       if (!NextArgumentOrError(args, &i)) {
         return false;
       }
-      if (!GetValidThreadsFromThreadString(args[i], &tid_set)) {
+      std::set<pid_t> tids;
+      if (!GetValidThreadsFromThreadString(args[i], &tids)) {
         return false;
       }
+      event_selection_set_.AddMonitoredThreads(tids);
     } else {
       ReportUnknownOption(args, i);
       return false;
     }
   }
 
+  if (use_sample_freq_ && use_sample_period_) {
+    LOG(ERROR) << "-f option can't be used with -c option.";
+    return false;
+  }
+
   if (!dwarf_callchain_sampling_) {
     if (!unwind_dwarf_callchain_) {
-      LOG(ERROR) << "--no-unwind is only used with `--call-graph dwarf` option.";
+      LOG(ERROR)
+          << "--no-unwind is only used with `--call-graph dwarf` option.";
       return false;
     }
     unwind_dwarf_callchain_ = false;
   }
   if (post_unwind_) {
     if (!dwarf_callchain_sampling_) {
-      LOG(ERROR) << "--post-unwind is only used with `--call-graph dwarf` option.";
+      LOG(ERROR)
+          << "--post-unwind is only used with `--call-graph dwarf` option.";
       return false;
     }
     if (!unwind_dwarf_callchain_) {
@@ -439,42 +554,48 @@
     }
   }
 
-  monitored_threads_.insert(monitored_threads_.end(), tid_set.begin(), tid_set.end());
-  if (system_wide_collection_ && !monitored_threads_.empty()) {
-    LOG(ERROR)
-        << "Record system wide and existing processes/threads can't be used at the same time.";
+  if (system_wide_collection_ && event_selection_set_.HasMonitoredTarget()) {
+    LOG(ERROR) << "Record system wide and existing processes/threads can't be "
+                  "used at the same time.";
     return false;
   }
 
-  if (non_option_args != nullptr) {
-    non_option_args->clear();
-    for (; i < args.size(); ++i) {
-      non_option_args->push_back(args[i]);
-    }
+  if (system_wide_collection_ && !IsRoot()) {
+    LOG(ERROR) << "System wide profiling needs root privilege.";
+    return false;
+  }
+
+  if (dump_symbols_ && can_dump_kernel_symbols_) {
+    // No need to dump kernel symbols as we will dump all required symbols.
+    can_dump_kernel_symbols_ = false;
+  }
+
+  non_option_args->clear();
+  for (; i < args.size(); ++i) {
+    non_option_args->push_back(args[i]);
   }
   return true;
 }
 
-bool RecordCommand::AddMeasuredEventType(const std::string& event_type_name) {
-  std::unique_ptr<EventTypeAndModifier> event_type_modifier = ParseEventType(event_type_name);
-  if (event_type_modifier == nullptr) {
-    return false;
-  }
-  measured_event_types_.push_back(*event_type_modifier);
-  return true;
-}
-
-bool RecordCommand::SetEventSelection() {
-  for (auto& event_type : measured_event_types_) {
-    if (!event_selection_set_.AddEventType(event_type)) {
-      return false;
+bool RecordCommand::SetEventSelectionFlags() {
+  for (const auto& group : event_selection_set_.groups()) {
+    for (const auto& selection : group) {
+      if (use_sample_freq_) {
+        event_selection_set_.SetSampleFreq(selection, sample_freq_);
+      } else if (use_sample_period_) {
+        event_selection_set_.SetSamplePeriod(selection, sample_period_);
+      } else {
+        if (selection.event_type_modifier.event_type.type ==
+            PERF_TYPE_TRACEPOINT) {
+          event_selection_set_.SetSamplePeriod(
+              selection, DEFAULT_SAMPLE_PERIOD_FOR_TRACEPOINT_EVENT);
+        } else {
+          event_selection_set_.SetSampleFreq(
+              selection, DEFAULT_SAMPLE_FREQ_FOR_NONTRACEPOINT_EVENT);
+        }
+      }
     }
   }
-  if (use_sample_freq_) {
-    event_selection_set_.SetSampleFreq(sample_freq_);
-  } else {
-    event_selection_set_.SetSamplePeriod(sample_period_);
-  }
   event_selection_set_.SampleIdAll();
   if (!event_selection_set_.SetBranchSampling(branch_sampling_)) {
     return false;
@@ -482,11 +603,17 @@
   if (fp_callchain_sampling_) {
     event_selection_set_.EnableFpCallChainSampling();
   } else if (dwarf_callchain_sampling_) {
-    if (!event_selection_set_.EnableDwarfCallChainSampling(dump_stack_size_in_dwarf_sampling_)) {
+    if (!event_selection_set_.EnableDwarfCallChainSampling(
+            dump_stack_size_in_dwarf_sampling_)) {
       return false;
     }
   }
   event_selection_set_.SetInherit(child_inherit_);
+  // If Unwinding while recording, records are used before being sorted.
+  // By using low watermark, records are almost sorted when read from kernel.
+  if (dwarf_callchain_sampling_ && unwind_dwarf_callchain_ && !post_unwind_) {
+    event_selection_set_.SetLowWatermark();
+  }
   return true;
 }
 
@@ -495,33 +622,46 @@
   if (record_file_writer_ == nullptr) {
     return false;
   }
-  if (!DumpKernelAndModuleMmaps()) {
+  // Use first perf_event_attr and first event id to dump mmap and comm records.
+  const EventSelection& selection = event_selection_set_.groups()[0][0];
+  const perf_event_attr& attr = selection.event_attr;
+  const std::vector<std::unique_ptr<EventFd>>& fds = selection.event_fds;
+  uint64_t event_id = fds[0]->Id();
+  if (!DumpKernelSymbol()) {
     return false;
   }
-  if (!DumpThreadCommAndMmaps(system_wide_collection_, monitored_threads_)) {
+  if (!DumpTracingData()) {
+    return false;
+  }
+  if (!DumpKernelAndModuleMmaps(attr, event_id)) {
+    return false;
+  }
+  if (!DumpThreadCommAndMmaps(attr, event_id)) {
     return false;
   }
   return true;
 }
 
-std::unique_ptr<RecordFileWriter> RecordCommand::CreateRecordFile(const std::string& filename) {
-  std::unique_ptr<RecordFileWriter> writer = RecordFileWriter::CreateInstance(filename);
+std::unique_ptr<RecordFileWriter> RecordCommand::CreateRecordFile(
+    const std::string& filename) {
+  std::unique_ptr<RecordFileWriter> writer =
+      RecordFileWriter::CreateInstance(filename);
   if (writer == nullptr) {
     return nullptr;
   }
 
   std::vector<AttrWithId> attr_ids;
-  for (auto& event_type : measured_event_types_) {
-    AttrWithId attr_id;
-    attr_id.attr = event_selection_set_.FindEventAttrByType(event_type);
-    CHECK(attr_id.attr != nullptr);
-    const std::vector<std::unique_ptr<EventFd>>* fds =
-        event_selection_set_.FindEventFdsByType(event_type);
-    CHECK(fds != nullptr);
-    for (auto& fd : *fds) {
-      attr_id.ids.push_back(fd->Id());
+  for (const auto& group : event_selection_set_.groups()) {
+    for (const auto& selection : group) {
+      AttrWithId attr_id;
+      attr_id.attr = &selection.event_attr;
+      CHECK(attr_id.attr != nullptr);
+      const std::vector<std::unique_ptr<EventFd>>& fds = selection.event_fds;
+      for (const auto& fd : fds) {
+        attr_id.ids.push_back(fd->Id());
+      }
+      attr_ids.push_back(attr_id);
     }
-    attr_ids.push_back(attr_id);
   }
   if (!writer->WriteAttrSection(attr_ids)) {
     return nullptr;
@@ -529,21 +669,70 @@
   return writer;
 }
 
-bool RecordCommand::DumpKernelAndModuleMmaps() {
+bool RecordCommand::DumpKernelSymbol() {
+  if (can_dump_kernel_symbols_) {
+    std::string kallsyms;
+    bool need_kernel_symbol = false;
+    for (const auto& group : event_selection_set_.groups()) {
+      for (const auto& selection : group) {
+        if (!selection.event_type_modifier.exclude_kernel) {
+          need_kernel_symbol = true;
+        }
+      }
+    }
+    if (need_kernel_symbol && CheckKernelSymbolAddresses()) {
+      if (!android::base::ReadFileToString("/proc/kallsyms", &kallsyms)) {
+        PLOG(ERROR) << "failed to read /proc/kallsyms";
+        return false;
+      }
+    }
+    KernelSymbolRecord r(kallsyms);
+    if (!ProcessRecord(&r)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool RecordCommand::DumpTracingData() {
+  std::vector<const EventType*> tracepoint_event_types;
+  for (const auto& group : event_selection_set_.groups()) {
+    for (const auto& selection : group) {
+      if (selection.event_type_modifier.event_type.type ==
+          PERF_TYPE_TRACEPOINT) {
+        tracepoint_event_types.push_back(
+            &selection.event_type_modifier.event_type);
+      }
+    }
+  }
+  if (tracepoint_event_types.empty()) {
+    return true;  // No need to dump tracing data.
+  }
+  std::vector<char> tracing_data;
+  if (!GetTracingData(tracepoint_event_types, &tracing_data)) {
+    return false;
+  }
+  TracingDataRecord record(tracing_data);
+  if (!ProcessRecord(&record)) {
+    return false;
+  }
+  return true;
+}
+
+bool RecordCommand::DumpKernelAndModuleMmaps(const perf_event_attr& attr,
+                                             uint64_t event_id) {
   KernelMmap kernel_mmap;
   std::vector<KernelMmap> module_mmaps;
   GetKernelAndModuleMmaps(&kernel_mmap, &module_mmaps);
 
-  const perf_event_attr* attr = event_selection_set_.FindEventAttrByType(measured_event_types_[0]);
-  CHECK(attr != nullptr);
-  MmapRecord mmap_record = CreateMmapRecord(*attr, true, UINT_MAX, 0, kernel_mmap.start_addr,
-                                            kernel_mmap.len, 0, kernel_mmap.filepath);
+  MmapRecord mmap_record(attr, true, UINT_MAX, 0, kernel_mmap.start_addr,
+                         kernel_mmap.len, 0, kernel_mmap.filepath, event_id);
   if (!ProcessRecord(&mmap_record)) {
     return false;
   }
   for (auto& module_mmap : module_mmaps) {
-    MmapRecord mmap_record = CreateMmapRecord(*attr, true, UINT_MAX, 0, module_mmap.start_addr,
-                                              module_mmap.len, 0, module_mmap.filepath);
+    MmapRecord mmap_record(attr, true, UINT_MAX, 0, module_mmap.start_addr,
+                           module_mmap.len, 0, module_mmap.filepath, event_id);
     if (!ProcessRecord(&mmap_record)) {
       return false;
     }
@@ -551,36 +740,36 @@
   return true;
 }
 
-bool RecordCommand::DumpThreadCommAndMmaps(bool all_threads,
-                                           const std::vector<pid_t>& selected_threads) {
+bool RecordCommand::DumpThreadCommAndMmaps(const perf_event_attr& attr,
+                                           uint64_t event_id) {
   std::vector<ThreadComm> thread_comms;
   if (!GetThreadComms(&thread_comms)) {
     return false;
   }
   // Decide which processes and threads to dump.
-  std::set<pid_t> dump_processes;
-  std::set<pid_t> dump_threads;
-  for (auto& tid : selected_threads) {
-    dump_threads.insert(tid);
+  bool all_threads = system_wide_collection_;
+  std::set<pid_t> dump_threads = event_selection_set_.GetMonitoredThreads();
+  for (const auto& pid : event_selection_set_.GetMonitoredProcesses()) {
+    std::vector<pid_t> tids = GetThreadsInProcess(pid);
+    dump_threads.insert(tids.begin(), tids.end());
   }
+  std::set<pid_t> dump_processes;
   for (auto& thread : thread_comms) {
     if (dump_threads.find(thread.tid) != dump_threads.end()) {
       dump_processes.insert(thread.pid);
     }
   }
 
-  const perf_event_attr* attr = event_selection_set_.FindEventAttrByType(measured_event_types_[0]);
-  CHECK(attr != nullptr);
-
   // Dump processes.
   for (auto& thread : thread_comms) {
     if (thread.pid != thread.tid) {
       continue;
     }
-    if (!all_threads && dump_processes.find(thread.pid) == dump_processes.end()) {
+    if (!all_threads &&
+        dump_processes.find(thread.pid) == dump_processes.end()) {
       continue;
     }
-    CommRecord record = CreateCommRecord(*attr, thread.pid, thread.tid, thread.comm);
+    CommRecord record(attr, thread.pid, thread.tid, thread.comm, event_id);
     if (!ProcessRecord(&record)) {
       return false;
     }
@@ -593,9 +782,9 @@
       if (thread_mmap.executable == 0) {
         continue;  // No need to dump non-executable mmap info.
       }
-      MmapRecord record =
-          CreateMmapRecord(*attr, false, thread.pid, thread.tid, thread_mmap.start_addr,
-                           thread_mmap.len, thread_mmap.pgoff, thread_mmap.name);
+      MmapRecord record(attr, false, thread.pid, thread.tid,
+                        thread_mmap.start_addr, thread_mmap.len,
+                        thread_mmap.pgoff, thread_mmap.name, event_id);
       if (!ProcessRecord(&record)) {
         return false;
       }
@@ -610,11 +799,12 @@
     if (!all_threads && dump_threads.find(thread.tid) == dump_threads.end()) {
       continue;
     }
-    ForkRecord fork_record = CreateForkRecord(*attr, thread.pid, thread.tid, thread.pid, thread.pid);
+    ForkRecord fork_record(attr, thread.pid, thread.tid, thread.pid, thread.pid,
+                           event_id);
     if (!ProcessRecord(&fork_record)) {
       return false;
     }
-    CommRecord comm_record = CreateCommRecord(*attr, thread.pid, thread.tid, thread.comm);
+    CommRecord comm_record(attr, thread.pid, thread.tid, thread.comm, event_id);
     if (!ProcessRecord(&comm_record)) {
       return false;
     }
@@ -622,39 +812,82 @@
   return true;
 }
 
-bool RecordCommand::CollectRecordsFromKernel(const char* data, size_t size) {
-  record_cache_->Push(data, size);
-  while (true) {
-    std::unique_ptr<Record> r = record_cache_->Pop();
-    if (r == nullptr) {
-      break;
+bool RecordCommand::ProcessRecord(Record* record) {
+  if (record->type() == PERF_RECORD_SAMPLE) {
+    auto& r = *static_cast<SampleRecord*>(record);
+    // Omit samples get before start sampling time.
+    if (r.time_data.time < start_sampling_time_in_ns_) {
+      return true;
     }
-    if (!ProcessRecord(r.get())) {
+  }
+  UpdateRecordForEmbeddedElfPath(record);
+  thread_tree_.Update(*record);
+  CollectHitFileInfo(record);
+  if (unwind_dwarf_callchain_ && !post_unwind_) {
+    if (!UnwindRecord(record)) {
       return false;
     }
   }
+  if (record->type() == PERF_RECORD_SAMPLE) {
+    sample_record_count_++;
+    if (dump_symbols_) {
+      auto& r = *static_cast<SampleRecord*>(record);
+      if (!DumpSymbolForRecord(r, false)) {
+        return false;
+      }
+      if (fp_callchain_sampling_) {
+        if (!DumpSymbolForRecord(r, true)) {
+          return false;
+        }
+      }
+    }
+  } else if (record->type() == PERF_RECORD_LOST) {
+    lost_record_count_ += static_cast<LostRecord*>(record)->lost;
+  }
+  bool result = record_file_writer_->WriteRecord(*record);
+  return result;
+}
+
+bool RecordCommand::DumpSymbolForRecord(const SampleRecord& r,
+                                        bool for_callchain) {
+  const ThreadEntry* thread =
+      thread_tree_.FindThreadOrNew(r.tid_data.pid, r.tid_data.tid);
+  uint64_t ip_nr = 1;
+  const uint64_t* ips = &r.ip_data.ip;
+  if (for_callchain) {
+    ip_nr = r.callchain_data.ip_nr;
+    ips = r.callchain_data.ips;
+  }
+  for (uint64_t i = 0; i < ip_nr; ++i) {
+    const MapEntry* map = thread_tree_.FindMap(thread, ips[i], r.InKernel());
+    const Symbol* symbol = thread_tree_.FindSymbol(map, ips[i], nullptr);
+    if (symbol == thread_tree_.UnknownSymbol()) {
+      continue;
+    }
+    if (!map->dso->HasDumped()) {
+      map->dso->SetDumped();
+      DsoRecord dso_record(map->dso->type(), map->dso->id(), map->dso->Path(),
+                           map->dso->MinVirtualAddress());
+      if (!record_file_writer_->WriteRecord(dso_record)) {
+        return false;
+      }
+    }
+    if (!symbol->HasDumped()) {
+      symbol->SetDumped();
+      SymbolRecord symbol_record(symbol->addr, symbol->len, symbol->Name(),
+                                 map->dso->id());
+      if (!record_file_writer_->WriteRecord(symbol_record)) {
+        return false;
+      }
+    }
+  }
   return true;
 }
 
-bool RecordCommand::ProcessRecord(Record* record) {
-  UpdateRecordForEmbeddedElfPath(record);
-  BuildThreadTree(*record, &thread_tree_);
-  CollectHitFileInfo(record);
-  if (unwind_dwarf_callchain_ && !post_unwind_) {
-    UnwindRecord(record);
-  }
-  if (record->type() == PERF_RECORD_SAMPLE) {
-    sample_record_count_++;
-  }
-  bool result = record_file_writer_->WriteData(record->BinaryFormat());
-  return result;
-}
-
-template<class RecordType>
+template <class RecordType>
 void UpdateMmapRecordForEmbeddedElfPath(RecordType* record) {
   RecordType& r = *record;
-  bool in_kernel = ((r.header.misc & PERF_RECORD_MISC_CPUMODE_MASK) == PERF_RECORD_MISC_KERNEL);
-  if (!in_kernel && r.data.pgoff != 0) {
+  if (!r.InKernel() && r.data->pgoff != 0) {
     // For the case of a shared library "foobar.so" embedded
     // inside an APK, we rewrite the original MMAP from
     // ["path.apk" offset=X] to ["path.apk!/foobar.so" offset=W]
@@ -665,12 +898,13 @@
     // is not present on the host. The new offset W is
     // calculated to be with respect to the start of foobar.so,
     // not to the start of path.apk.
-    EmbeddedElf* ee = ApkInspector::FindElfInApkByOffset(r.filename, r.data.pgoff);
+    EmbeddedElf* ee =
+        ApkInspector::FindElfInApkByOffset(r.filename, r.data->pgoff);
     if (ee != nullptr) {
       // Compute new offset relative to start of elf in APK.
-      r.data.pgoff -= ee->entry_offset();
-      r.filename = GetUrlInApk(r.filename, ee->entry_name());
-      r.AdjustSizeBasedOnData();
+      auto data = *r.data;
+      data.pgoff -= ee->entry_offset();
+      r.SetDataAndFilename(data, GetUrlInApk(r.filename, ee->entry_name()));
     }
   }
 }
@@ -683,31 +917,40 @@
   }
 }
 
-void RecordCommand::UnwindRecord(Record* record) {
+bool RecordCommand::UnwindRecord(Record* record) {
   if (record->type() == PERF_RECORD_SAMPLE) {
     SampleRecord& r = *static_cast<SampleRecord*>(record);
-    if ((r.sample_type & PERF_SAMPLE_CALLCHAIN) && (r.sample_type & PERF_SAMPLE_REGS_USER) &&
-        (r.regs_user_data.reg_mask != 0) && (r.sample_type & PERF_SAMPLE_STACK_USER) &&
-        (!r.stack_user_data.data.empty())) {
-      ThreadEntry* thread = thread_tree_.FindThreadOrNew(r.tid_data.pid, r.tid_data.tid);
-      RegSet regs = CreateRegSet(r.regs_user_data.reg_mask, r.regs_user_data.regs);
-      std::vector<char>& stack = r.stack_user_data.data;
-      std::vector<uint64_t> unwind_ips = UnwindCallChain(GetBuildArch(), *thread, regs, stack);
-      r.callchain_data.ips.push_back(PERF_CONTEXT_USER);
-      r.callchain_data.ips.insert(r.callchain_data.ips.end(), unwind_ips.begin(), unwind_ips.end());
-      r.regs_user_data.abi = 0;
-      r.regs_user_data.reg_mask = 0;
-      r.regs_user_data.regs.clear();
-      r.stack_user_data.data.clear();
-      r.stack_user_data.dyn_size = 0;
-      r.AdjustSizeBasedOnData();
+    if ((r.sample_type & PERF_SAMPLE_CALLCHAIN) &&
+        (r.sample_type & PERF_SAMPLE_REGS_USER) &&
+        (r.regs_user_data.reg_mask != 0) &&
+        (r.sample_type & PERF_SAMPLE_STACK_USER) &&
+        (r.GetValidStackSize() > 0)) {
+      ThreadEntry* thread =
+          thread_tree_.FindThreadOrNew(r.tid_data.pid, r.tid_data.tid);
+      RegSet regs =
+          CreateRegSet(r.regs_user_data.reg_mask, r.regs_user_data.regs);
+      ArchType arch = GetArchForAbi(GetBuildArch(), r.regs_user_data.abi);
+      // Normally do strict arch check when unwinding stack. But allow unwinding
+      // 32-bit processes on 64-bit devices for system wide profiling.
+      bool strict_arch_check = !system_wide_collection_;
+      std::vector<uint64_t> unwind_ips =
+          UnwindCallChain(arch, *thread, regs, r.stack_user_data.data,
+                          r.GetValidStackSize(), strict_arch_check);
+      r.ReplaceRegAndStackWithCallChain(unwind_ips);
+      if (dump_symbols_) {
+        if (!DumpSymbolForRecord(r, true)) {
+          return false;
+        }
+      }
     }
   }
+  return true;
 }
 
 bool RecordCommand::PostUnwind(const std::vector<std::string>& args) {
-  thread_tree_.Clear();
-  std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(record_filename_);
+  thread_tree_.ClearThreadAndMap();
+  std::unique_ptr<RecordFileReader> reader =
+      RecordFileReader::CreateInstance(record_filename_);
   if (reader == nullptr) {
     return false;
   }
@@ -718,9 +961,11 @@
   }
   bool result = reader->ReadDataSection(
       [this](std::unique_ptr<Record> record) {
-        BuildThreadTree(*record, &thread_tree_);
-        UnwindRecord(record.get());
-        return record_file_writer_->WriteData(record->BinaryFormat());
+        thread_tree_.Update(*record);
+        if (!UnwindRecord(record.get())) {
+          return false;
+        }
+        return record_file_writer_->WriteRecord(*record);
       },
       false);
   if (!result) {
@@ -738,13 +983,15 @@
     return false;
   }
   if (rename(tmp_filename.c_str(), record_filename_.c_str()) != 0) {
-    PLOG(ERROR) << "failed to rename " << tmp_filename << " to " << record_filename_;
+    PLOG(ERROR) << "failed to rename " << tmp_filename << " to "
+                << record_filename_;
     return false;
   }
   return true;
 }
 
-bool RecordCommand::DumpAdditionalFeatures(const std::vector<std::string>& args) {
+bool RecordCommand::DumpAdditionalFeatures(
+    const std::vector<std::string>& args) {
   size_t feature_count = (branch_sampling_ != 0 ? 5 : 4);
   if (!record_file_writer_->WriteFeatureHeader(feature_count)) {
     return false;
@@ -757,15 +1004,17 @@
     PLOG(ERROR) << "uname() failed";
     return false;
   }
-  if (!record_file_writer_->WriteFeatureString(PerfFileFormat::FEAT_OSRELEASE, uname_buf.release)) {
+  if (!record_file_writer_->WriteFeatureString(PerfFileFormat::FEAT_OSRELEASE,
+                                               uname_buf.release)) {
     return false;
   }
-  if (!record_file_writer_->WriteFeatureString(PerfFileFormat::FEAT_ARCH, uname_buf.machine)) {
+  if (!record_file_writer_->WriteFeatureString(PerfFileFormat::FEAT_ARCH,
+                                               uname_buf.machine)) {
     return false;
   }
 
-  std::string exec_path = "simpleperf";
-  GetExecPath(&exec_path);
+  std::string exec_path = android::base::GetExecutablePath();
+  if (exec_path.empty()) exec_path = "simpleperf";
   std::vector<std::string> cmdline;
   cmdline.push_back(exec_path);
   cmdline.push_back("record");
@@ -773,7 +1022,8 @@
   if (!record_file_writer_->WriteCmdlineFeature(cmdline)) {
     return false;
   }
-  if (branch_sampling_ != 0 && !record_file_writer_->WriteBranchStackFeature()) {
+  if (branch_sampling_ != 0 &&
+      !record_file_writer_->WriteBranchStackFeature()) {
     return false;
   }
   return true;
@@ -786,11 +1036,10 @@
   for (const auto& filename : hit_kernel_modules_) {
     if (filename == DEFAULT_KERNEL_FILENAME_FOR_BUILD_ID) {
       if (!GetKernelBuildId(&build_id)) {
-        LOG(DEBUG) << "can't read build_id for kernel";
         continue;
       }
-      build_id_records.push_back(
-          CreateBuildIdRecord(true, UINT_MAX, build_id, DEFAULT_KERNEL_FILENAME_FOR_BUILD_ID));
+      build_id_records.push_back(BuildIdRecord(
+          true, UINT_MAX, build_id, DEFAULT_KERNEL_FILENAME_FOR_BUILD_ID));
     } else {
       std::string path = filename;
       std::string module_name = basename(&path[0]);
@@ -801,7 +1050,8 @@
         LOG(DEBUG) << "can't read build_id for module " << module_name;
         continue;
       }
-      build_id_records.push_back(CreateBuildIdRecord(true, UINT_MAX, build_id, filename));
+      build_id_records.push_back(
+          BuildIdRecord(true, UINT_MAX, build_id, filename));
     }
   }
   // Add build_ids for user elf files.
@@ -811,17 +1061,23 @@
     }
     auto tuple = SplitUrlInApk(filename);
     if (std::get<0>(tuple)) {
-      if (!GetBuildIdFromApkFile(std::get<1>(tuple), std::get<2>(tuple), &build_id)) {
-        LOG(DEBUG) << "can't read build_id from file " << filename;
+      ElfStatus result = GetBuildIdFromApkFile(std::get<1>(tuple),
+                                               std::get<2>(tuple), &build_id);
+      if (result != ElfStatus::NO_ERROR) {
+        LOG(DEBUG) << "can't read build_id from file " << filename << ": "
+                   << result;
         continue;
       }
     } else {
-      if (!GetBuildIdFromElfFile(filename, &build_id)) {
-        LOG(DEBUG) << "can't read build_id from file " << filename;
+      ElfStatus result = GetBuildIdFromElfFile(filename, &build_id);
+      if (result != ElfStatus::NO_ERROR) {
+        LOG(DEBUG) << "can't read build_id from file " << filename << ": "
+                   << result;
         continue;
       }
     }
-    build_id_records.push_back(CreateBuildIdRecord(false, UINT_MAX, build_id, filename));
+    build_id_records.push_back(
+        BuildIdRecord(false, UINT_MAX, build_id, filename));
   }
   if (!record_file_writer_->WriteBuildIdFeature(build_id_records)) {
     return false;
@@ -831,9 +1087,10 @@
 
 void RecordCommand::CollectHitFileInfo(Record* record) {
   if (record->type() == PERF_RECORD_SAMPLE) {
-    auto r = *static_cast<SampleRecord*>(record);
-    bool in_kernel = ((r.header.misc & PERF_RECORD_MISC_CPUMODE_MASK) == PERF_RECORD_MISC_KERNEL);
-    const ThreadEntry* thread = thread_tree_.FindThreadOrNew(r.tid_data.pid, r.tid_data.tid);
+    const auto& r = *static_cast<SampleRecord*>(record);
+    bool in_kernel = r.InKernel();
+    const ThreadEntry* thread =
+        thread_tree_.FindThreadOrNew(r.tid_data.pid, r.tid_data.tid);
     const MapEntry* map = thread_tree_.FindMap(thread, r.ip_data.ip, in_kernel);
     if (in_kernel) {
       hit_kernel_modules_.insert(map->dso->Path());
@@ -844,5 +1101,6 @@
 }
 
 void RegisterRecordCommand() {
-  RegisterCommand("record", [] { return std::unique_ptr<Command>(new RecordCommand()); });
+  RegisterCommand("record",
+                  [] { return std::unique_ptr<Command>(new RecordCommand()); });
 }
diff --git a/simpleperf/cmd_record_test.cpp b/simpleperf/cmd_record_test.cpp
index 1a468e2..9729904 100644
--- a/simpleperf/cmd_record_test.cpp
+++ b/simpleperf/cmd_record_test.cpp
@@ -19,6 +19,7 @@
 #include <android-base/stringprintf.h>
 #include <android-base/test_utils.h>
 
+#include <map>
 #include <memory>
 
 #include "command.h"
@@ -35,7 +36,8 @@
   return CreateCommandInstance("record");
 }
 
-static bool RunRecordCmd(std::vector<std::string> v, const char* output_file = nullptr) {
+static bool RunRecordCmd(std::vector<std::string> v,
+                         const char* output_file = nullptr) {
   std::unique_ptr<TemporaryFile> tmpfile;
   std::string out_file;
   if (output_file != nullptr) {
@@ -48,14 +50,10 @@
   return RecordCmd()->Run(v);
 }
 
-TEST(record_cmd, no_options) {
-  ASSERT_TRUE(RunRecordCmd({}));
-}
+TEST(record_cmd, no_options) { ASSERT_TRUE(RunRecordCmd({})); }
 
 TEST(record_cmd, system_wide_option) {
-  if (IsRoot()) {
-    ASSERT_TRUE(RunRecordCmd({"-a"}));
-  }
+  TEST_IN_ROOT(ASSERT_TRUE(RunRecordCmd({"-a"})));
 }
 
 TEST(record_cmd, sample_period_option) {
@@ -79,15 +77,17 @@
 TEST(record_cmd, dump_kernel_mmap) {
   TemporaryFile tmpfile;
   ASSERT_TRUE(RunRecordCmd({}, tmpfile.path));
-  std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(tmpfile.path);
+  std::unique_ptr<RecordFileReader> reader =
+      RecordFileReader::CreateInstance(tmpfile.path);
   ASSERT_TRUE(reader != nullptr);
   std::vector<std::unique_ptr<Record>> records = reader->DataSection();
   ASSERT_GT(records.size(), 0U);
   bool have_kernel_mmap = false;
   for (auto& record : records) {
-    if (record->header.type == PERF_RECORD_MMAP) {
-      const MmapRecord* mmap_record = static_cast<const MmapRecord*>(record.get());
-      if (mmap_record->filename == DEFAULT_KERNEL_MMAP_NAME) {
+    if (record->type() == PERF_RECORD_MMAP) {
+      const MmapRecord* mmap_record =
+          static_cast<const MmapRecord*>(record.get());
+      if (strcmp(mmap_record->filename, DEFAULT_KERNEL_MMAP_NAME) == 0) {
         have_kernel_mmap = true;
         break;
       }
@@ -99,17 +99,17 @@
 TEST(record_cmd, dump_build_id_feature) {
   TemporaryFile tmpfile;
   ASSERT_TRUE(RunRecordCmd({}, tmpfile.path));
-  std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(tmpfile.path);
+  std::unique_ptr<RecordFileReader> reader =
+      RecordFileReader::CreateInstance(tmpfile.path);
   ASSERT_TRUE(reader != nullptr);
   const FileHeader& file_header = reader->FileHeader();
-  ASSERT_TRUE(file_header.features[FEAT_BUILD_ID / 8] & (1 << (FEAT_BUILD_ID % 8)));
+  ASSERT_TRUE(file_header.features[FEAT_BUILD_ID / 8] &
+              (1 << (FEAT_BUILD_ID % 8)));
   ASSERT_GT(reader->FeatureSectionDescriptors().size(), 0u);
 }
 
 TEST(record_cmd, tracepoint_event) {
-  if (IsRoot()) {
-    ASSERT_TRUE(RunRecordCmd({"-a", "-e", "sched:sched_switch"}));
-  }
+  TEST_IN_ROOT(ASSERT_TRUE(RunRecordCmd({"-a", "-e", "sched:sched_switch"})));
 }
 
 TEST(record_cmd, branch_sampling) {
@@ -120,8 +120,8 @@
     ASSERT_TRUE(RunRecordCmd({"-j", "any,u"}));
     ASSERT_FALSE(RunRecordCmd({"-j", "u"}));
   } else {
-    GTEST_LOG_(INFO)
-        << "This test does nothing as branch stack sampling is not supported on this device.";
+    GTEST_LOG_(INFO) << "This test does nothing as branch stack sampling is "
+                        "not supported on this device.";
   }
 }
 
@@ -133,14 +133,28 @@
   ASSERT_TRUE(RunRecordCmd({"--call-graph", "fp"}));
 }
 
+TEST(record_cmd, system_wide_fp_callchain_sampling) {
+  TEST_IN_ROOT(ASSERT_TRUE(RunRecordCmd({"-a", "--call-graph", "fp"})));
+}
+
 TEST(record_cmd, dwarf_callchain_sampling) {
   if (IsDwarfCallChainSamplingSupported()) {
     ASSERT_TRUE(RunRecordCmd({"--call-graph", "dwarf"}));
     ASSERT_TRUE(RunRecordCmd({"--call-graph", "dwarf,16384"}));
+    ASSERT_FALSE(RunRecordCmd({"--call-graph", "dwarf,65536"}));
     ASSERT_TRUE(RunRecordCmd({"-g"}));
   } else {
-    GTEST_LOG_(INFO)
-        << "This test does nothing as dwarf callchain sampling is not supported on this device.";
+    GTEST_LOG_(INFO) << "This test does nothing as dwarf callchain sampling is "
+                        "not supported on this device.";
+  }
+}
+
+TEST(record_cmd, system_wide_dwarf_callchain_sampling) {
+  if (IsDwarfCallChainSamplingSupported()) {
+    TEST_IN_ROOT(RunRecordCmd({"-a", "--call-graph", "dwarf"}));
+  } else {
+    GTEST_LOG_(INFO) << "This test does nothing as dwarf callchain sampling is "
+                        "not supported on this device.";
   }
 }
 
@@ -148,8 +162,8 @@
   if (IsDwarfCallChainSamplingSupported()) {
     ASSERT_TRUE(RunRecordCmd({"--call-graph", "dwarf", "--no-unwind"}));
   } else {
-    GTEST_LOG_(INFO)
-        << "This test does nothing as dwarf callchain sampling is not supported on this device.";
+    GTEST_LOG_(INFO) << "This test does nothing as dwarf callchain sampling is "
+                        "not supported on this device.";
   }
   ASSERT_FALSE(RunRecordCmd({"--no-unwind"}));
 }
@@ -158,8 +172,8 @@
   if (IsDwarfCallChainSamplingSupported()) {
     ASSERT_TRUE(RunRecordCmd({"--call-graph", "dwarf", "--post-unwind"}));
   } else {
-    GTEST_LOG_(INFO)
-        << "This test does nothing as dwarf callchain sampling is not supported on this device.";
+    GTEST_LOG_(INFO) << "This test does nothing as dwarf callchain sampling is "
+                        "not supported on this device.";
   }
   ASSERT_FALSE(RunRecordCmd({"--post-unwind"}));
   ASSERT_FALSE(
@@ -169,8 +183,8 @@
 TEST(record_cmd, existing_processes) {
   std::vector<std::unique_ptr<Workload>> workloads;
   CreateProcesses(2, &workloads);
-  std::string pid_list =
-      android::base::StringPrintf("%d,%d", workloads[0]->GetPid(), workloads[1]->GetPid());
+  std::string pid_list = android::base::StringPrintf(
+      "%d,%d", workloads[0]->GetPid(), workloads[1]->GetPid());
   ASSERT_TRUE(RunRecordCmd({"-p", pid_list}));
 }
 
@@ -178,30 +192,134 @@
   std::vector<std::unique_ptr<Workload>> workloads;
   CreateProcesses(2, &workloads);
   // Process id can also be used as thread id in linux.
-  std::string tid_list =
-      android::base::StringPrintf("%d,%d", workloads[0]->GetPid(), workloads[1]->GetPid());
-  TemporaryFile tmpfile;
+  std::string tid_list = android::base::StringPrintf(
+      "%d,%d", workloads[0]->GetPid(), workloads[1]->GetPid());
   ASSERT_TRUE(RunRecordCmd({"-t", tid_list}));
 }
 
-TEST(record_cmd, no_monitored_threads) {
-  ASSERT_FALSE(RecordCmd()->Run({""}));
-}
+TEST(record_cmd, no_monitored_threads) { ASSERT_FALSE(RecordCmd()->Run({""})); }
 
 TEST(record_cmd, more_than_one_event_types) {
   ASSERT_TRUE(RunRecordCmd({"-e", "cpu-cycles,cpu-clock"}));
   ASSERT_TRUE(RunRecordCmd({"-e", "cpu-cycles", "-e", "cpu-clock"}));
 }
 
-TEST(record_cmd, cpu_option) {
-  ASSERT_TRUE(RunRecordCmd({"--cpu", "0"}));
-  if (IsRoot()) {
-    ASSERT_TRUE(RunRecordCmd({"--cpu", "0", "-a"}));
-  }
-}
-
 TEST(record_cmd, mmap_page_option) {
   ASSERT_TRUE(RunRecordCmd({"-m", "1"}));
   ASSERT_FALSE(RunRecordCmd({"-m", "0"}));
   ASSERT_FALSE(RunRecordCmd({"-m", "7"}));
 }
+
+static void CheckKernelSymbol(const std::string& path, bool need_kallsyms,
+                              bool* success) {
+  *success = false;
+  std::unique_ptr<RecordFileReader> reader =
+      RecordFileReader::CreateInstance(path);
+  ASSERT_TRUE(reader != nullptr);
+  std::vector<std::unique_ptr<Record>> records = reader->DataSection();
+  bool has_kernel_symbol_records = false;
+  for (const auto& record : records) {
+    if (record->type() == SIMPLE_PERF_RECORD_KERNEL_SYMBOL) {
+      has_kernel_symbol_records = true;
+    }
+  }
+  ASSERT_EQ(need_kallsyms, has_kernel_symbol_records);
+  *success = true;
+}
+
+TEST(record_cmd, kernel_symbol) {
+  TemporaryFile tmpfile;
+  ASSERT_TRUE(RunRecordCmd({}, tmpfile.path));
+  bool success;
+  CheckKernelSymbol(tmpfile.path, true, &success);
+  ASSERT_TRUE(success);
+  ASSERT_TRUE(RunRecordCmd({"--no-dump-kernel-symbols"}, tmpfile.path));
+  CheckKernelSymbol(tmpfile.path, false, &success);
+  ASSERT_TRUE(success);
+}
+
+// Check if the dso/symbol records in perf.data matches our expectation.
+static void CheckDsoSymbolRecords(const std::string& path,
+                                  bool can_have_dso_symbol_records,
+                                  bool* success) {
+  *success = false;
+  std::unique_ptr<RecordFileReader> reader =
+      RecordFileReader::CreateInstance(path);
+  ASSERT_TRUE(reader != nullptr);
+  std::vector<std::unique_ptr<Record>> records = reader->DataSection();
+  bool has_dso_record = false;
+  bool has_symbol_record = false;
+  std::map<uint64_t, bool> dso_hit_map;
+  for (const auto& record : records) {
+    if (record->type() == SIMPLE_PERF_RECORD_DSO) {
+      has_dso_record = true;
+      uint64_t dso_id = static_cast<const DsoRecord*>(record.get())->dso_id;
+      ASSERT_EQ(dso_hit_map.end(), dso_hit_map.find(dso_id));
+      dso_hit_map.insert(std::make_pair(dso_id, false));
+    } else if (record->type() == SIMPLE_PERF_RECORD_SYMBOL) {
+      has_symbol_record = true;
+      uint64_t dso_id = static_cast<const SymbolRecord*>(record.get())->dso_id;
+      auto it = dso_hit_map.find(dso_id);
+      ASSERT_NE(dso_hit_map.end(), it);
+      it->second = true;
+    }
+  }
+  if (can_have_dso_symbol_records) {
+    // It is possible that there are no samples hitting functions having symbol.
+    // In that case, there are no dso/symbol records.
+    ASSERT_EQ(has_dso_record, has_symbol_record);
+    for (auto& pair : dso_hit_map) {
+      ASSERT_TRUE(pair.second);
+    }
+  } else {
+    ASSERT_FALSE(has_dso_record);
+    ASSERT_FALSE(has_symbol_record);
+  }
+  *success = true;
+}
+
+TEST(record_cmd, dump_symbols) {
+  TemporaryFile tmpfile;
+  ASSERT_TRUE(RunRecordCmd({}, tmpfile.path));
+  bool success;
+  CheckDsoSymbolRecords(tmpfile.path, false, &success);
+  ASSERT_TRUE(success);
+  ASSERT_TRUE(RunRecordCmd({"--dump-symbols"}, tmpfile.path));
+  CheckDsoSymbolRecords(tmpfile.path, true, &success);
+  ASSERT_TRUE(success);
+  if (IsDwarfCallChainSamplingSupported()) {
+    ASSERT_TRUE(RunRecordCmd({"-g"}, tmpfile.path));
+    bool success;
+    CheckDsoSymbolRecords(tmpfile.path, false, &success);
+    ASSERT_TRUE(success);
+    ASSERT_TRUE(RunRecordCmd({"-g", "--dump-symbols"}, tmpfile.path));
+    CheckDsoSymbolRecords(tmpfile.path, true, &success);
+    ASSERT_TRUE(success);
+  }
+}
+
+TEST(record_cmd, group_option) {
+  ASSERT_TRUE(RunRecordCmd({"--group", "cpu-cycles,cpu-clock", "-m", "16"}));
+  ASSERT_TRUE(RunRecordCmd({"--group", "cpu-cycles,cpu-clock", "--group",
+                            "cpu-cycles:u,cpu-clock:u", "--group",
+                            "cpu-cycles:k,cpu-clock:k", "-m", "16"}));
+}
+
+TEST(record_cmd, symfs_option) { ASSERT_TRUE(RunRecordCmd({"--symfs", "/"})); }
+
+TEST(record_cmd, duration_option) {
+  TemporaryFile tmpfile;
+  ASSERT_TRUE(RecordCmd()->Run({"--duration", "1.2", "-p",
+                                std::to_string(getpid()), "-o", tmpfile.path}));
+  ASSERT_TRUE(
+      RecordCmd()->Run({"--duration", "1", "-o", tmpfile.path, "sleep", "2"}));
+}
+
+TEST(record_cmd, support_modifier_for_clock_events) {
+  for (const std::string& e : {"cpu-clock", "task-clock"}) {
+    for (const std::string& m : {"u", "k"}) {
+      ASSERT_TRUE(RunRecordCmd({"-e", e + ":" + m})) << "event " << e << ":"
+                                                     << m;
+    }
+  }
+}
diff --git a/simpleperf/cmd_report.cpp b/simpleperf/cmd_report.cpp
index 3d778ab..fe93dbc 100644
--- a/simpleperf/cmd_report.cpp
+++ b/simpleperf/cmd_report.cpp
@@ -39,285 +39,311 @@
 #include "record_file.h"
 #include "sample_tree.h"
 #include "thread_tree.h"
+#include "tracing.h"
 #include "utils.h"
 
-class Displayable {
- public:
-  Displayable(const std::string& name) : name_(name), width_(name.size()) {
-  }
-
-  virtual ~Displayable() {
-  }
-
-  const std::string& Name() const {
-    return name_;
-  }
-  size_t Width() const {
-    return width_;
-  }
-
-  virtual std::string Show(const SampleEntry& sample) const = 0;
-  void AdjustWidth(const SampleEntry& sample) {
-    size_t size = Show(sample).size();
-    width_ = std::max(width_, size);
-  }
-
- private:
-  const std::string name_;
-  size_t width_;
-};
-
-class AccumulatedOverheadItem : public Displayable {
- public:
-  AccumulatedOverheadItem(const SampleTree& sample_tree)
-      : Displayable("Children"), sample_tree_(sample_tree) {
-  }
-
-  std::string Show(const SampleEntry& sample) const override {
-    uint64_t period = sample.period + sample.accumulated_period;
-    uint64_t total_period = sample_tree_.TotalPeriod();
-    double percentage = (total_period != 0) ? 100.0 * period / total_period : 0.0;
-    return android::base::StringPrintf("%.2lf%%", percentage);
-  }
-
- private:
-  const SampleTree& sample_tree_;
-};
-
-class SelfOverheadItem : public Displayable {
- public:
-  SelfOverheadItem(const SampleTree& sample_tree, const std::string& name = "Self")
-      : Displayable(name), sample_tree_(sample_tree) {
-  }
-
-  std::string Show(const SampleEntry& sample) const override {
-    uint64_t period = sample.period;
-    uint64_t total_period = sample_tree_.TotalPeriod();
-    double percentage = (total_period != 0) ? 100.0 * period / total_period : 0.0;
-    return android::base::StringPrintf("%.2lf%%", percentage);
-  }
-
- private:
-  const SampleTree& sample_tree_;
-};
-
-class SampleCountItem : public Displayable {
- public:
-  SampleCountItem() : Displayable("Sample") {
-  }
-
-  std::string Show(const SampleEntry& sample) const override {
-    return android::base::StringPrintf("%" PRId64, sample.sample_count);
-  }
-};
-
-class Comparable {
- public:
-  virtual ~Comparable() {
-  }
-
-  virtual int Compare(const SampleEntry& sample1, const SampleEntry& sample2) const = 0;
-};
-
-class PidItem : public Displayable, public Comparable {
- public:
-  PidItem() : Displayable("Pid") {
-  }
-
-  int Compare(const SampleEntry& sample1, const SampleEntry& sample2) const override {
-    return sample1.thread->pid - sample2.thread->pid;
-  }
-
-  std::string Show(const SampleEntry& sample) const override {
-    return android::base::StringPrintf("%d", sample.thread->pid);
-  }
-};
-
-class TidItem : public Displayable, public Comparable {
- public:
-  TidItem() : Displayable("Tid") {
-  }
-
-  int Compare(const SampleEntry& sample1, const SampleEntry& sample2) const override {
-    return sample1.thread->tid - sample2.thread->tid;
-  }
-
-  std::string Show(const SampleEntry& sample) const override {
-    return android::base::StringPrintf("%d", sample.thread->tid);
-  }
-};
-
-class CommItem : public Displayable, public Comparable {
- public:
-  CommItem() : Displayable("Command") {
-  }
-
-  int Compare(const SampleEntry& sample1, const SampleEntry& sample2) const override {
-    return strcmp(sample1.thread_comm, sample2.thread_comm);
-  }
-
-  std::string Show(const SampleEntry& sample) const override {
-    return sample.thread_comm;
-  }
-};
-
-class DsoItem : public Displayable, public Comparable {
- public:
-  DsoItem(const std::string& name = "Shared Object") : Displayable(name) {
-  }
-
-  int Compare(const SampleEntry& sample1, const SampleEntry& sample2) const override {
-    return strcmp(sample1.map->dso->Path().c_str(), sample2.map->dso->Path().c_str());
-  }
-
-  std::string Show(const SampleEntry& sample) const override {
-    return sample.map->dso->Path();
-  }
-};
-
-class SymbolItem : public Displayable, public Comparable {
- public:
-  SymbolItem(const std::string& name = "Symbol") : Displayable(name) {
-  }
-
-  int Compare(const SampleEntry& sample1, const SampleEntry& sample2) const override {
-    return strcmp(sample1.symbol->DemangledName(), sample2.symbol->DemangledName());
-  }
-
-  std::string Show(const SampleEntry& sample) const override {
-    return sample.symbol->DemangledName();
-  }
-};
-
-class DsoFromItem : public Displayable, public Comparable {
- public:
-  DsoFromItem() : Displayable("Source Shared Object") {
-  }
-
-  int Compare(const SampleEntry& sample1, const SampleEntry& sample2) const override {
-    return strcmp(sample1.branch_from.map->dso->Path().c_str(),
-                  sample2.branch_from.map->dso->Path().c_str());
-  }
-
-  std::string Show(const SampleEntry& sample) const override {
-    return sample.branch_from.map->dso->Path();
-  }
-};
-
-class DsoToItem : public DsoItem {
- public:
-  DsoToItem() : DsoItem("Target Shared Object") {
-  }
-};
-
-class SymbolFromItem : public Displayable, public Comparable {
- public:
-  SymbolFromItem() : Displayable("Source Symbol") {
-  }
-
-  int Compare(const SampleEntry& sample1, const SampleEntry& sample2) const override {
-    return strcmp(sample1.branch_from.symbol->DemangledName(),
-                  sample2.branch_from.symbol->DemangledName());
-  }
-
-  std::string Show(const SampleEntry& sample) const override {
-    return sample.branch_from.symbol->DemangledName();
-  }
-};
-
-class SymbolToItem : public SymbolItem {
- public:
-  SymbolToItem() : SymbolItem("Target Symbol") {
-  }
-};
+namespace {
 
 static std::set<std::string> branch_sort_keys = {
     "dso_from", "dso_to", "symbol_from", "symbol_to",
 };
+struct BranchFromEntry {
+  const MapEntry* map;
+  const Symbol* symbol;
+  uint64_t vaddr_in_file;
+  uint64_t flags;
+
+  BranchFromEntry()
+      : map(nullptr), symbol(nullptr), vaddr_in_file(0), flags(0) {}
+};
+
+struct SampleEntry {
+  uint64_t time;
+  uint64_t period;
+  // accumuated when appearing in other sample's callchain
+  uint64_t accumulated_period;
+  uint64_t sample_count;
+  const ThreadEntry* thread;
+  const char* thread_comm;
+  const MapEntry* map;
+  const Symbol* symbol;
+  uint64_t vaddr_in_file;
+  BranchFromEntry branch_from;
+  // a callchain tree representing all callchains in the sample
+  CallChainRoot<SampleEntry> callchain;
+
+  SampleEntry(uint64_t time, uint64_t period, uint64_t accumulated_period,
+              uint64_t sample_count, const ThreadEntry* thread,
+              const MapEntry* map, const Symbol* symbol, uint64_t vaddr_in_file)
+      : time(time),
+        period(period),
+        accumulated_period(accumulated_period),
+        sample_count(sample_count),
+        thread(thread),
+        thread_comm(thread->comm),
+        map(map),
+        symbol(symbol),
+        vaddr_in_file(vaddr_in_file) {}
+
+  // The data member 'callchain' can only move, not copy.
+  SampleEntry(SampleEntry&&) = default;
+  SampleEntry(SampleEntry&) = delete;
+};
+
+struct SampleTree {
+  std::vector<SampleEntry*> samples;
+  uint64_t total_samples;
+  uint64_t total_period;
+};
+
+BUILD_COMPARE_VALUE_FUNCTION(CompareVaddrInFile, vaddr_in_file);
+BUILD_DISPLAY_HEX64_FUNCTION(DisplayVaddrInFile, vaddr_in_file);
+
+class ReportCmdSampleTreeBuilder
+    : public SampleTreeBuilder<SampleEntry, uint64_t> {
+ public:
+  ReportCmdSampleTreeBuilder(SampleComparator<SampleEntry> sample_comparator,
+                             ThreadTree* thread_tree)
+      : SampleTreeBuilder(sample_comparator),
+        thread_tree_(thread_tree),
+        total_samples_(0),
+        total_period_(0) {}
+
+  void SetFilters(const std::unordered_set<int>& pid_filter,
+                  const std::unordered_set<int>& tid_filter,
+                  const std::unordered_set<std::string>& comm_filter,
+                  const std::unordered_set<std::string>& dso_filter,
+                  const std::unordered_set<std::string>& symbol_filter) {
+    pid_filter_ = pid_filter;
+    tid_filter_ = tid_filter;
+    comm_filter_ = comm_filter;
+    dso_filter_ = dso_filter;
+    symbol_filter_ = symbol_filter;
+  }
+
+  SampleTree GetSampleTree() const {
+    SampleTree sample_tree;
+    sample_tree.samples = GetSamples();
+    sample_tree.total_samples = total_samples_;
+    sample_tree.total_period = total_period_;
+    return sample_tree;
+  }
+
+ protected:
+  SampleEntry* CreateSample(const SampleRecord& r, bool in_kernel,
+                            uint64_t* acc_info) override {
+    const ThreadEntry* thread =
+        thread_tree_->FindThreadOrNew(r.tid_data.pid, r.tid_data.tid);
+    const MapEntry* map =
+        thread_tree_->FindMap(thread, r.ip_data.ip, in_kernel);
+    uint64_t vaddr_in_file;
+    const Symbol* symbol =
+        thread_tree_->FindSymbol(map, r.ip_data.ip, &vaddr_in_file);
+    *acc_info = r.period_data.period;
+    return InsertSample(std::unique_ptr<SampleEntry>(
+        new SampleEntry(r.time_data.time, r.period_data.period, 0, 1, thread,
+                        map, symbol, vaddr_in_file)));
+  }
+
+  SampleEntry* CreateBranchSample(const SampleRecord& r,
+                                  const BranchStackItemType& item) override {
+    const ThreadEntry* thread =
+        thread_tree_->FindThreadOrNew(r.tid_data.pid, r.tid_data.tid);
+    const MapEntry* from_map = thread_tree_->FindMap(thread, item.from);
+    uint64_t from_vaddr_in_file;
+    const Symbol* from_symbol =
+        thread_tree_->FindSymbol(from_map, item.from, &from_vaddr_in_file);
+    const MapEntry* to_map = thread_tree_->FindMap(thread, item.to);
+    uint64_t to_vaddr_in_file;
+    const Symbol* to_symbol =
+        thread_tree_->FindSymbol(to_map, item.to, &to_vaddr_in_file);
+    std::unique_ptr<SampleEntry> sample(
+        new SampleEntry(r.time_data.time, r.period_data.period, 0, 1, thread,
+                        to_map, to_symbol, to_vaddr_in_file));
+    sample->branch_from.map = from_map;
+    sample->branch_from.symbol = from_symbol;
+    sample->branch_from.vaddr_in_file = from_vaddr_in_file;
+    sample->branch_from.flags = item.flags;
+    return InsertSample(std::move(sample));
+  }
+
+  SampleEntry* CreateCallChainSample(const SampleEntry* sample, uint64_t ip,
+                                     bool in_kernel,
+                                     const std::vector<SampleEntry*>& callchain,
+                                     const uint64_t& acc_info) override {
+    const ThreadEntry* thread = sample->thread;
+    const MapEntry* map = thread_tree_->FindMap(thread, ip, in_kernel);
+    uint64_t vaddr_in_file;
+    const Symbol* symbol = thread_tree_->FindSymbol(map, ip, &vaddr_in_file);
+    std::unique_ptr<SampleEntry> callchain_sample(new SampleEntry(
+        sample->time, 0, acc_info, 0, thread, map, symbol, vaddr_in_file));
+    return InsertCallChainSample(std::move(callchain_sample), callchain);
+  }
+
+  const ThreadEntry* GetThreadOfSample(SampleEntry* sample) override {
+    return sample->thread;
+  }
+
+  uint64_t GetPeriodForCallChain(const uint64_t& acc_info) override {
+    return acc_info;
+  }
+
+  bool FilterSample(const SampleEntry* sample) override {
+    if (!pid_filter_.empty() &&
+        pid_filter_.find(sample->thread->pid) == pid_filter_.end()) {
+      return false;
+    }
+    if (!tid_filter_.empty() &&
+        tid_filter_.find(sample->thread->tid) == tid_filter_.end()) {
+      return false;
+    }
+    if (!comm_filter_.empty() &&
+        comm_filter_.find(sample->thread_comm) == comm_filter_.end()) {
+      return false;
+    }
+    if (!dso_filter_.empty() &&
+        dso_filter_.find(sample->map->dso->Path()) == dso_filter_.end()) {
+      return false;
+    }
+    if (!symbol_filter_.empty() &&
+        symbol_filter_.find(sample->symbol->DemangledName()) ==
+            symbol_filter_.end()) {
+      return false;
+    }
+    return true;
+  }
+
+  void UpdateSummary(const SampleEntry* sample) override {
+    total_samples_ += sample->sample_count;
+    total_period_ += sample->period;
+  }
+
+  void MergeSample(SampleEntry* sample1, SampleEntry* sample2) override {
+    sample1->period += sample2->period;
+    sample1->accumulated_period += sample2->accumulated_period;
+    sample1->sample_count += sample2->sample_count;
+  }
+
+ private:
+  ThreadTree* thread_tree_;
+
+  std::unordered_set<int> pid_filter_;
+  std::unordered_set<int> tid_filter_;
+  std::unordered_set<std::string> comm_filter_;
+  std::unordered_set<std::string> dso_filter_;
+  std::unordered_set<std::string> symbol_filter_;
+
+  uint64_t total_samples_;
+  uint64_t total_period_;
+};
+
+using ReportCmdSampleTreeSorter = SampleTreeSorter<SampleEntry>;
+using ReportCmdSampleTreeDisplayer =
+    SampleTreeDisplayer<SampleEntry, SampleTree>;
+
+using ReportCmdCallgraphDisplayer =
+    CallgraphDisplayer<SampleEntry, CallChainNode<SampleEntry>>;
+
+class ReportCmdCallgraphDisplayerWithVaddrInFile
+    : public ReportCmdCallgraphDisplayer {
+ protected:
+  std::string PrintSampleName(const SampleEntry* sample) override {
+    return android::base::StringPrintf("%s [+0x%" PRIx64 "]",
+                                       sample->symbol->DemangledName(),
+                                       sample->vaddr_in_file);
+  }
+};
+
+struct EventAttrWithName {
+  perf_event_attr attr;
+  std::string name;
+};
 
 class ReportCommand : public Command {
  public:
   ReportCommand()
       : Command(
             "report", "report sampling information in perf.data",
-            "Usage: simpleperf report [options]\n"
-            "    -b            Use the branch-to addresses in sampled take branches instead of\n"
-            "                  the instruction addresses. Only valid for perf.data recorded with\n"
-            "                  -b/-j option.\n"
-            "    --children    Print the overhead accumulated by appearing in the callchain.\n"
-            "    --comms comm1,comm2,...\n"
-            "                  Report only for selected comms.\n"
-            "    --dsos dso1,dso2,...\n"
-            "                  Report only for selected dsos.\n"
-            "    -g [callee|caller]\n"
-            "                  Print call graph. If callee mode is used, the graph shows how\n"
-            "                  functions are called from others. Otherwise, the graph shows how\n"
-            "                  functions call others. Default is callee mode.\n"
-            "    -i <file>     Specify path of record file, default is perf.data.\n"
-            "    -n            Print the sample count for each item.\n"
-            "    --no-demangle        Don't demangle symbol names.\n"
-            "    -o report_file_name  Set report file name, default is stdout.\n"
-            "    --pid pid1,pid2,...\n"
-            "                  Report only for selected pids.\n"
-            "    --sort key1,key2,...\n"
-            "                  Select the keys to sort and print the report. Possible keys\n"
-            "                  include pid, tid, comm, dso, symbol, dso_from, dso_to, symbol_from\n"
-            "                  symbol_to. dso_from, dso_to, symbol_from, symbol_to can only be\n"
-            "                  used with -b option. Default keys are \"comm,pid,tid,dso,symbol\"\n"
-            "    --symfs <dir> Look for files with symbols relative to this directory.\n"
-            "    --tids tid1,tid2,...\n"
-            "                  Report only for selected tids.\n"
-            "    --vmlinux <file>\n"
-            "                  Parse kernel symbols from <file>.\n"),
+            // clang-format off
+"Usage: simpleperf report [options]\n"
+"-b    Use the branch-to addresses in sampled take branches instead of the\n"
+"      instruction addresses. Only valid for perf.data recorded with -b/-j\n"
+"      option.\n"
+"--children    Print the overhead accumulated by appearing in the callchain.\n"
+"--comms comm1,comm2,...   Report only for selected comms.\n"
+"--dsos dso1,dso2,...      Report only for selected dsos.\n"
+"-g [callee|caller]    Print call graph. If callee mode is used, the graph\n"
+"                      shows how functions are called from others. Otherwise,\n"
+"                      the graph shows how functions call others.\n"
+"                      Default is caller mode.\n"
+"-i <file>  Specify path of record file, default is perf.data.\n"
+"-n         Print the sample count for each item.\n"
+"--no-demangle         Don't demangle symbol names.\n"
+"--no-show-ip          Don't show vaddr in file for unknown symbols.\n"
+"-o report_file_name   Set report file name, default is stdout.\n"
+"--pids pid1,pid2,...  Report only for selected pids.\n"
+"--sort key1,key2,...  Select keys used to sort and print the report. The\n"
+"                      appearance order of keys decides the order of keys used\n"
+"                      to sort and print the report.\n"
+"                      Possible keys include:\n"
+"                        pid             -- process id\n"
+"                        tid             -- thread id\n"
+"                        comm            -- thread name (can be changed during\n"
+"                                           the lifetime of a thread)\n"
+"                        dso             -- shared library\n"
+"                        symbol          -- function name in the shared library\n"
+"                        vaddr_in_file   -- virtual address in the shared\n"
+"                                           library\n"
+"                      Keys can only be used with -b option:\n"
+"                        dso_from        -- shared library branched from\n"
+"                        dso_to          -- shared library branched to\n"
+"                        symbol_from     -- name of function branched from\n"
+"                        symbol_to       -- name of function branched to\n"
+"                      The default sort keys are:\n"
+"                        comm,pid,tid,dso,symbol\n"
+"--symbols symbol1;symbol2;...    Report only for selected symbols.\n"
+"--symfs <dir>         Look for files with symbols relative to this directory.\n"
+"--tids tid1,tid2,...  Report only for selected tids.\n"
+"--vmlinux <file>      Parse kernel symbols from <file>.\n"
+            // clang-format on
+            ),
         record_filename_("perf.data"),
         record_file_arch_(GetBuildArch()),
         use_branch_address_(false),
+        system_wide_collection_(false),
         accumulate_callchain_(false),
         print_callgraph_(false),
-        callgraph_show_callee_(true),
-        report_fp_(nullptr) {
-    compare_sample_func_t compare_sample_callback = std::bind(
-        &ReportCommand::CompareSampleEntry, this, std::placeholders::_1, std::placeholders::_2);
-    sample_tree_ =
-        std::unique_ptr<SampleTree>(new SampleTree(&thread_tree_, compare_sample_callback));
-  }
+        callgraph_show_callee_(false) {}
 
   bool Run(const std::vector<std::string>& args);
 
  private:
   bool ParseOptions(const std::vector<std::string>& args);
   bool ReadEventAttrFromRecordFile();
-  void ReadSampleTreeFromRecordFile();
-  void ProcessRecord(std::unique_ptr<Record> record);
-  void ProcessSampleRecord(const SampleRecord& r);
   bool ReadFeaturesFromRecordFile();
-  int CompareSampleEntry(const SampleEntry& sample1, const SampleEntry& sample2);
+  bool ReadSampleTreeFromRecordFile();
+  bool ProcessRecord(std::unique_ptr<Record> record);
+  bool ProcessTracingData(const std::vector<char>& data);
   bool PrintReport();
-  void PrintReportContext();
-  void CollectReportWidth();
-  void CollectReportEntryWidth(const SampleEntry& sample);
-  void PrintReportHeader();
-  void PrintReportEntry(const SampleEntry& sample);
-  void PrintCallGraph(const SampleEntry& sample);
-  void PrintCallGraphEntry(size_t depth, std::string prefix, const std::unique_ptr<CallChainNode>& node,
-                           uint64_t parent_period, bool last);
+  void PrintReportContext(FILE* fp);
 
   std::string record_filename_;
   ArchType record_file_arch_;
   std::unique_ptr<RecordFileReader> record_file_reader_;
-  perf_event_attr event_attr_;
-  std::vector<std::unique_ptr<Displayable>> displayable_items_;
-  std::vector<Comparable*> comparable_items_;
+  std::vector<EventAttrWithName> event_attrs_;
   ThreadTree thread_tree_;
-  std::unique_ptr<SampleTree> sample_tree_;
+  SampleTree sample_tree_;
+  std::unique_ptr<ReportCmdSampleTreeBuilder> sample_tree_builder_;
+  std::unique_ptr<ReportCmdSampleTreeSorter> sample_tree_sorter_;
+  std::unique_ptr<ReportCmdSampleTreeDisplayer> sample_tree_displayer_;
   bool use_branch_address_;
   std::string record_cmdline_;
+  bool system_wide_collection_;
   bool accumulate_callchain_;
   bool print_callgraph_;
   bool callgraph_show_callee_;
 
   std::string report_filename_;
-  FILE* report_fp_;
 };
 
 bool ReportCommand::Run(const std::vector<std::string>& args) {
@@ -339,7 +365,9 @@
     return false;
   }
   ScopedCurrentArch scoped_arch(record_file_arch_);
-  ReadSampleTreeFromRecordFile();
+  if (!ReadSampleTreeFromRecordFile()) {
+    return false;
+  }
 
   // 3. Show collected information.
   if (!PrintReport()) {
@@ -351,12 +379,14 @@
 
 bool ReportCommand::ParseOptions(const std::vector<std::string>& args) {
   bool demangle = true;
+  bool show_ip_for_unknown_symbol = true;
   std::string symfs_dir;
   std::string vmlinux;
   bool print_sample_count = false;
   std::vector<std::string> sort_keys = {"comm", "pid", "tid", "dso", "symbol"};
   std::unordered_set<std::string> comm_filter;
   std::unordered_set<std::string> dso_filter;
+  std::unordered_set<std::string> symbol_filter;
   std::unordered_set<int> pid_filter;
   std::unordered_set<int> tid_filter;
 
@@ -366,7 +396,8 @@
     } else if (args[i] == "--children") {
       accumulate_callchain_ = true;
     } else if (args[i] == "--comms" || args[i] == "--dsos") {
-      std::unordered_set<std::string>& filter = (args[i] == "--comms" ? comm_filter : dso_filter);
+      std::unordered_set<std::string>& filter =
+          (args[i] == "--comms" ? comm_filter : dso_filter);
       if (!NextArgumentOrError(args, &i)) {
         return false;
       }
@@ -398,6 +429,8 @@
 
     } else if (args[i] == "--no-demangle") {
       demangle = false;
+    } else if (args[i] == "--no-show-ip") {
+      show_ip_for_unknown_symbol = false;
     } else if (args[i] == "-o") {
       if (!NextArgumentOrError(args, &i)) {
         return false;
@@ -405,27 +438,33 @@
       report_filename_ = args[i];
 
     } else if (args[i] == "--pids" || args[i] == "--tids") {
+      const std::string& option = args[i];
+      std::unordered_set<int>& filter =
+          (option == "--pids" ? pid_filter : tid_filter);
       if (!NextArgumentOrError(args, &i)) {
         return false;
       }
       std::vector<std::string> strs = android::base::Split(args[i], ",");
-      std::vector<int> ids;
       for (const auto& s : strs) {
         int id;
         if (!android::base::ParseInt(s.c_str(), &id, 0)) {
-          LOG(ERROR) << "invalid id in " << args[i] << " option: " << s;
+          LOG(ERROR) << "invalid id in " << option << " option: " << s;
           return false;
         }
-        ids.push_back(id);
+        filter.insert(id);
       }
-      std::unordered_set<int>& filter = (args[i] == "--pids" ? pid_filter : tid_filter);
-      filter.insert(ids.begin(), ids.end());
 
     } else if (args[i] == "--sort") {
       if (!NextArgumentOrError(args, &i)) {
         return false;
       }
       sort_keys = android::base::Split(args[i], ",");
+    } else if (args[i] == "--symbols") {
+      if (!NextArgumentOrError(args, &i)) {
+        return false;
+      }
+      std::vector<std::string> strs = android::base::Split(args[i], ";");
+      symbol_filter.insert(strs.begin(), strs.end());
     } else if (args[i] == "--symfs") {
       if (!NextArgumentOrError(args, &i)) {
         return false;
@@ -451,191 +490,133 @@
     Dso::SetVmlinux(vmlinux);
   }
 
-  if (!accumulate_callchain_) {
-    displayable_items_.push_back(
-        std::unique_ptr<Displayable>(new SelfOverheadItem(*sample_tree_, "Overhead")));
+  if (show_ip_for_unknown_symbol) {
+    thread_tree_.ShowIpForUnknownSymbol();
+  }
+
+  SampleDisplayer<SampleEntry, SampleTree> displayer;
+  SampleComparator<SampleEntry> comparator;
+
+  if (accumulate_callchain_) {
+    displayer.AddDisplayFunction("Children", DisplayAccumulatedOverhead);
+    displayer.AddDisplayFunction("Self", DisplaySelfOverhead);
   } else {
-    displayable_items_.push_back(
-        std::unique_ptr<Displayable>(new AccumulatedOverheadItem(*sample_tree_)));
-    displayable_items_.push_back(std::unique_ptr<Displayable>(new SelfOverheadItem(*sample_tree_)));
+    displayer.AddDisplayFunction("Overhead", DisplaySelfOverhead);
   }
   if (print_sample_count) {
-    displayable_items_.push_back(std::unique_ptr<Displayable>(new SampleCountItem));
+    displayer.AddDisplayFunction("Sample", DisplaySampleCount);
   }
+
   for (auto& key : sort_keys) {
-    if (!use_branch_address_ && branch_sort_keys.find(key) != branch_sort_keys.end()) {
+    if (!use_branch_address_ &&
+        branch_sort_keys.find(key) != branch_sort_keys.end()) {
       LOG(ERROR) << "sort key '" << key << "' can only be used with -b option.";
       return false;
     }
     if (key == "pid") {
-      PidItem* item = new PidItem;
-      displayable_items_.push_back(std::unique_ptr<Displayable>(item));
-      comparable_items_.push_back(item);
+      comparator.AddCompareFunction(ComparePid);
+      displayer.AddDisplayFunction("Pid", DisplayPid);
     } else if (key == "tid") {
-      TidItem* item = new TidItem;
-      displayable_items_.push_back(std::unique_ptr<Displayable>(item));
-      comparable_items_.push_back(item);
+      comparator.AddCompareFunction(CompareTid);
+      displayer.AddDisplayFunction("Tid", DisplayTid);
     } else if (key == "comm") {
-      CommItem* item = new CommItem;
-      displayable_items_.push_back(std::unique_ptr<Displayable>(item));
-      comparable_items_.push_back(item);
+      comparator.AddCompareFunction(CompareComm);
+      displayer.AddDisplayFunction("Command", DisplayComm);
     } else if (key == "dso") {
-      DsoItem* item = new DsoItem;
-      displayable_items_.push_back(std::unique_ptr<Displayable>(item));
-      comparable_items_.push_back(item);
+      comparator.AddCompareFunction(CompareDso);
+      displayer.AddDisplayFunction("Shared Object", DisplayDso);
     } else if (key == "symbol") {
-      SymbolItem* item = new SymbolItem;
-      displayable_items_.push_back(std::unique_ptr<Displayable>(item));
-      comparable_items_.push_back(item);
+      comparator.AddCompareFunction(CompareSymbol);
+      displayer.AddDisplayFunction("Symbol", DisplaySymbol);
+    } else if (key == "vaddr_in_file") {
+      comparator.AddCompareFunction(CompareVaddrInFile);
+      displayer.AddDisplayFunction("VaddrInFile", DisplayVaddrInFile);
     } else if (key == "dso_from") {
-      DsoFromItem* item = new DsoFromItem;
-      displayable_items_.push_back(std::unique_ptr<Displayable>(item));
-      comparable_items_.push_back(item);
+      comparator.AddCompareFunction(CompareDsoFrom);
+      displayer.AddDisplayFunction("Source Shared Object", DisplayDsoFrom);
     } else if (key == "dso_to") {
-      DsoToItem* item = new DsoToItem;
-      displayable_items_.push_back(std::unique_ptr<Displayable>(item));
-      comparable_items_.push_back(item);
+      comparator.AddCompareFunction(CompareDso);
+      displayer.AddDisplayFunction("Target Shared Object", DisplayDso);
     } else if (key == "symbol_from") {
-      SymbolFromItem* item = new SymbolFromItem;
-      displayable_items_.push_back(std::unique_ptr<Displayable>(item));
-      comparable_items_.push_back(item);
+      comparator.AddCompareFunction(CompareSymbolFrom);
+      displayer.AddDisplayFunction("Source Symbol", DisplaySymbolFrom);
     } else if (key == "symbol_to") {
-      SymbolToItem* item = new SymbolToItem;
-      displayable_items_.push_back(std::unique_ptr<Displayable>(item));
-      comparable_items_.push_back(item);
+      comparator.AddCompareFunction(CompareSymbol);
+      displayer.AddDisplayFunction("Target Symbol", DisplaySymbol);
     } else {
       LOG(ERROR) << "Unknown sort key: " << key;
       return false;
     }
   }
-  sample_tree_->SetFilters(pid_filter, tid_filter, comm_filter, dso_filter);
+  if (print_callgraph_) {
+    bool has_symbol_key = false;
+    bool has_vaddr_in_file_key = false;
+    for (const auto& key : sort_keys) {
+      if (key == "symbol") {
+        has_symbol_key = true;
+      } else if (key == "vaddr_in_file") {
+        has_vaddr_in_file_key = true;
+      }
+    }
+    if (has_symbol_key) {
+      if (has_vaddr_in_file_key) {
+        displayer.AddExclusiveDisplayFunction(
+            ReportCmdCallgraphDisplayerWithVaddrInFile());
+      } else {
+        displayer.AddExclusiveDisplayFunction(ReportCmdCallgraphDisplayer());
+      }
+    }
+  }
+
+  sample_tree_builder_.reset(
+      new ReportCmdSampleTreeBuilder(comparator, &thread_tree_));
+  sample_tree_builder_->SetFilters(pid_filter, tid_filter, comm_filter,
+                                   dso_filter, symbol_filter);
+
+  SampleComparator<SampleEntry> sort_comparator;
+  sort_comparator.AddCompareFunction(CompareTotalPeriod);
+  sort_comparator.AddComparator(comparator);
+  sample_tree_sorter_.reset(new ReportCmdSampleTreeSorter(sort_comparator));
+  sample_tree_displayer_.reset(new ReportCmdSampleTreeDisplayer(displayer));
   return true;
 }
 
 bool ReportCommand::ReadEventAttrFromRecordFile() {
-  const std::vector<PerfFileFormat::FileAttr>& attrs = record_file_reader_->AttrSection();
-  if (attrs.size() != 1) {
-    LOG(ERROR) << "record file contains " << attrs.size() << " attrs";
-    return false;
+  std::vector<AttrWithId> attrs = record_file_reader_->AttrSection();
+  for (const auto& attr_with_id : attrs) {
+    EventAttrWithName attr;
+    attr.attr = *attr_with_id.attr;
+    attr.name = GetEventNameByAttr(attr.attr);
+    event_attrs_.push_back(attr);
   }
-  event_attr_ = attrs[0].attr;
-  if (use_branch_address_ && (event_attr_.sample_type & PERF_SAMPLE_BRANCH_STACK) == 0) {
-    LOG(ERROR) << record_filename_ << " is not recorded with branch stack sampling option.";
-    return false;
+  if (use_branch_address_) {
+    bool has_branch_stack = true;
+    for (const auto& attr : event_attrs_) {
+      if ((attr.attr.sample_type & PERF_SAMPLE_BRANCH_STACK) == 0) {
+        has_branch_stack = false;
+        break;
+      }
+    }
+    if (!has_branch_stack) {
+      LOG(ERROR) << record_filename_
+                 << " is not recorded with branch stack sampling option.";
+      return false;
+    }
   }
   return true;
 }
 
-void ReportCommand::ReadSampleTreeFromRecordFile() {
-  thread_tree_.AddThread(0, 0, "swapper");
-  record_file_reader_->ReadDataSection([this](std::unique_ptr<Record> record) {
-    ProcessRecord(std::move(record));
-    return true;
-  });
-}
-
-void ReportCommand::ProcessRecord(std::unique_ptr<Record> record) {
-  BuildThreadTree(*record, &thread_tree_);
-  if (record->header.type == PERF_RECORD_SAMPLE) {
-    ProcessSampleRecord(*static_cast<const SampleRecord*>(record.get()));
-  }
-}
-
-void ReportCommand::ProcessSampleRecord(const SampleRecord& r) {
-  if (use_branch_address_ && (r.sample_type & PERF_SAMPLE_BRANCH_STACK)) {
-    for (auto& item : r.branch_stack_data.stack) {
-      if (item.from != 0 && item.to != 0) {
-        sample_tree_->AddBranchSample(r.tid_data.pid, r.tid_data.tid, item.from, item.to,
-                                      item.flags, r.time_data.time, r.period_data.period);
-      }
-    }
-  } else {
-    bool in_kernel = (r.header.misc & PERF_RECORD_MISC_CPUMODE_MASK) == PERF_RECORD_MISC_KERNEL;
-    SampleEntry* sample = sample_tree_->AddSample(r.tid_data.pid, r.tid_data.tid, r.ip_data.ip,
-                                                  r.time_data.time, r.period_data.period, in_kernel);
-    if (sample == nullptr) {
-      return;
-    }
-    if (accumulate_callchain_) {
-      std::vector<uint64_t> ips;
-      if (r.sample_type & PERF_SAMPLE_CALLCHAIN) {
-        ips.insert(ips.end(), r.callchain_data.ips.begin(), r.callchain_data.ips.end());
-      }
-      // Use stack_user_data.data.size() instead of stack_user_data.dyn_size, to make up for
-      // the missing kernel patch in N9. See b/22612370.
-      if ((r.sample_type & PERF_SAMPLE_REGS_USER) && (r.regs_user_data.reg_mask != 0) &&
-          (r.sample_type & PERF_SAMPLE_STACK_USER) && (!r.stack_user_data.data.empty())) {
-        RegSet regs = CreateRegSet(r.regs_user_data.reg_mask, r.regs_user_data.regs);
-        std::vector<char> stack(r.stack_user_data.data.begin(),
-                                r.stack_user_data.data.begin() + r.stack_user_data.data.size());
-        std::vector<uint64_t> unwind_ips =
-            UnwindCallChain(ScopedCurrentArch::GetCurrentArch(), *sample->thread, regs, stack);
-        if (!unwind_ips.empty()) {
-          ips.push_back(PERF_CONTEXT_USER);
-          ips.insert(ips.end(), unwind_ips.begin(), unwind_ips.end());
-        }
-      }
-
-      std::vector<SampleEntry*> callchain;
-      callchain.push_back(sample);
-
-      bool first_ip = true;
-      for (auto& ip : ips) {
-        if (ip >= PERF_CONTEXT_MAX) {
-          switch (ip) {
-            case PERF_CONTEXT_KERNEL:
-              in_kernel = true;
-              break;
-            case PERF_CONTEXT_USER:
-              in_kernel = false;
-              break;
-            default:
-              LOG(ERROR) << "Unexpected perf_context in callchain: " << ip;
-          }
-        } else {
-          if (first_ip) {
-            first_ip = false;
-            // Remove duplication with sampled ip.
-            if (ip == r.ip_data.ip) {
-              continue;
-            }
-          }
-          SampleEntry* sample =
-              sample_tree_->AddCallChainSample(r.tid_data.pid, r.tid_data.tid, ip, r.time_data.time,
-                                               r.period_data.period, in_kernel, callchain);
-          callchain.push_back(sample);
-        }
-      }
-
-      if (print_callgraph_) {
-        std::set<SampleEntry*> added_set;
-        if (!callgraph_show_callee_) {
-          std::reverse(callchain.begin(), callchain.end());
-        }
-        while (callchain.size() >= 2) {
-          SampleEntry* sample = callchain[0];
-          callchain.erase(callchain.begin());
-          // Add only once for recursive calls on callchain.
-          if (added_set.find(sample) != added_set.end()) {
-            continue;
-          }
-          added_set.insert(sample);
-          sample_tree_->InsertCallChainForSample(sample, callchain, r.period_data.period);
-        }
-      }
-    }
-  }
-}
-
 bool ReportCommand::ReadFeaturesFromRecordFile() {
-  std::vector<BuildIdRecord> records = record_file_reader_->ReadBuildIdFeature();
+  std::vector<BuildIdRecord> records =
+      record_file_reader_->ReadBuildIdFeature();
   std::vector<std::pair<std::string, BuildId>> build_ids;
   for (auto& r : records) {
     build_ids.push_back(std::make_pair(r.filename, r.build_id));
   }
   Dso::SetBuildIds(build_ids);
 
-  std::string arch = record_file_reader_->ReadFeatureString(PerfFileFormat::FEAT_ARCH);
+  std::string arch =
+      record_file_reader_->ReadFeatureString(PerfFileFormat::FEAT_ARCH);
   if (!arch.empty()) {
     record_file_arch_ = GetArchType(arch);
     if (record_file_arch_ == ARCH_UNSUPPORTED) {
@@ -646,138 +627,117 @@
   std::vector<std::string> cmdline = record_file_reader_->ReadCmdlineFeature();
   if (!cmdline.empty()) {
     record_cmdline_ = android::base::Join(cmdline, ' ');
+    // TODO: the code to detect system wide collection option is fragile, remove
+    // it once we can do cross unwinding.
+    for (size_t i = 0; i < cmdline.size(); i++) {
+      std::string& s = cmdline[i];
+      if (s == "-a") {
+        system_wide_collection_ = true;
+        break;
+      } else if (s == "--call-graph" || s == "--cpu" || s == "-e" ||
+                 s == "-f" || s == "-F" || s == "-j" || s == "-m" ||
+                 s == "-o" || s == "-p" || s == "-t") {
+        i++;
+      } else if (!s.empty() && s[0] != '-') {
+        break;
+      }
+    }
+  }
+  if (record_file_reader_->HasFeature(PerfFileFormat::FEAT_TRACING_DATA)) {
+    std::vector<char> tracing_data;
+    if (!record_file_reader_->ReadFeatureSection(
+            PerfFileFormat::FEAT_TRACING_DATA, &tracing_data)) {
+      return false;
+    }
+    if (!ProcessTracingData(tracing_data)) {
+      return false;
+    }
   }
   return true;
 }
 
-int ReportCommand::CompareSampleEntry(const SampleEntry& sample1, const SampleEntry& sample2) {
-  for (auto& item : comparable_items_) {
-    int result = item->Compare(sample1, sample2);
-    if (result != 0) {
-      return result;
+bool ReportCommand::ReadSampleTreeFromRecordFile() {
+  sample_tree_builder_->SetBranchSampleOption(use_branch_address_);
+  // Normally do strict arch check when unwinding stack. But allow unwinding
+  // 32-bit processes on 64-bit devices for system wide profiling.
+  bool strict_unwind_arch_check = !system_wide_collection_;
+  sample_tree_builder_->SetCallChainSampleOptions(
+      accumulate_callchain_, print_callgraph_, !callgraph_show_callee_,
+      strict_unwind_arch_check);
+  if (!record_file_reader_->ReadDataSection(
+          [this](std::unique_ptr<Record> record) {
+            return ProcessRecord(std::move(record));
+          })) {
+    return false;
+  }
+  sample_tree_ = sample_tree_builder_->GetSampleTree();
+  sample_tree_sorter_->Sort(sample_tree_.samples, print_callgraph_);
+  return true;
+}
+
+bool ReportCommand::ProcessRecord(std::unique_ptr<Record> record) {
+  thread_tree_.Update(*record);
+  if (record->type() == PERF_RECORD_SAMPLE) {
+    sample_tree_builder_->ProcessSampleRecord(
+        *static_cast<const SampleRecord*>(record.get()));
+  } else if (record->type() == PERF_RECORD_TRACING_DATA) {
+    const auto& r = *static_cast<TracingDataRecord*>(record.get());
+    if (!ProcessTracingData(std::vector<char>(r.data, r.data + r.data_size))) {
+      return false;
     }
   }
-  return 0;
+  return true;
+}
+
+bool ReportCommand::ProcessTracingData(const std::vector<char>& data) {
+  Tracing tracing(data);
+  for (auto& attr : event_attrs_) {
+    if (attr.attr.type == PERF_TYPE_TRACEPOINT) {
+      uint64_t trace_event_id = attr.attr.config;
+      attr.name = tracing.GetTracingEventNameHavingId(trace_event_id);
+    }
+  }
+  return true;
 }
 
 bool ReportCommand::PrintReport() {
   std::unique_ptr<FILE, decltype(&fclose)> file_handler(nullptr, fclose);
-  if (report_filename_.empty()) {
-    report_fp_ = stdout;
-  } else {
-    report_fp_ = fopen(report_filename_.c_str(), "w");
-    if (report_fp_ == nullptr) {
+  FILE* report_fp = stdout;
+  if (!report_filename_.empty()) {
+    report_fp = fopen(report_filename_.c_str(), "w");
+    if (report_fp == nullptr) {
       PLOG(ERROR) << "failed to open file " << report_filename_;
       return false;
     }
-    file_handler.reset(report_fp_);
+    file_handler.reset(report_fp);
   }
-  PrintReportContext();
-  CollectReportWidth();
-  PrintReportHeader();
-  sample_tree_->VisitAllSamples(
-      std::bind(&ReportCommand::PrintReportEntry, this, std::placeholders::_1));
-  fflush(report_fp_);
-  if (ferror(report_fp_) != 0) {
+  PrintReportContext(report_fp);
+  sample_tree_displayer_->DisplaySamples(report_fp, sample_tree_.samples,
+                                         &sample_tree_);
+  fflush(report_fp);
+  if (ferror(report_fp) != 0) {
     PLOG(ERROR) << "print report failed";
     return false;
   }
   return true;
 }
 
-void ReportCommand::PrintReportContext() {
-  const EventType* event_type = FindEventTypeByConfig(event_attr_.type, event_attr_.config);
-  std::string event_type_name;
-  if (event_type != nullptr) {
-    event_type_name = event_type->name;
-  } else {
-    event_type_name =
-        android::base::StringPrintf("(type %u, config %llu)", event_attr_.type, event_attr_.config);
-  }
+void ReportCommand::PrintReportContext(FILE* report_fp) {
   if (!record_cmdline_.empty()) {
-    fprintf(report_fp_, "Cmdline: %s\n", record_cmdline_.c_str());
+    fprintf(report_fp, "Cmdline: %s\n", record_cmdline_.c_str());
   }
-  fprintf(report_fp_, "Samples: %" PRIu64 " of event '%s'\n", sample_tree_->TotalSamples(),
-          event_type_name.c_str());
-  fprintf(report_fp_, "Event count: %" PRIu64 "\n\n", sample_tree_->TotalPeriod());
+  fprintf(report_fp, "Arch: %s\n", GetArchString(record_file_arch_).c_str());
+  for (const auto& attr : event_attrs_) {
+    fprintf(report_fp, "Event: %s (type %u, config %llu)\n", attr.name.c_str(),
+            attr.attr.type, attr.attr.config);
+  }
+  fprintf(report_fp, "Samples: %" PRIu64 "\n", sample_tree_.total_samples);
+  fprintf(report_fp, "Event count: %" PRIu64 "\n\n", sample_tree_.total_period);
 }
 
-void ReportCommand::CollectReportWidth() {
-  sample_tree_->VisitAllSamples(
-      std::bind(&ReportCommand::CollectReportEntryWidth, this, std::placeholders::_1));
-}
-
-void ReportCommand::CollectReportEntryWidth(const SampleEntry& sample) {
-  for (auto& item : displayable_items_) {
-    item->AdjustWidth(sample);
-  }
-}
-
-void ReportCommand::PrintReportHeader() {
-  for (size_t i = 0; i < displayable_items_.size(); ++i) {
-    auto& item = displayable_items_[i];
-    if (i != displayable_items_.size() - 1) {
-      fprintf(report_fp_, "%-*s  ", static_cast<int>(item->Width()), item->Name().c_str());
-    } else {
-      fprintf(report_fp_, "%s\n", item->Name().c_str());
-    }
-  }
-}
-
-void ReportCommand::PrintReportEntry(const SampleEntry& sample) {
-  for (size_t i = 0; i < displayable_items_.size(); ++i) {
-    auto& item = displayable_items_[i];
-    if (i != displayable_items_.size() - 1) {
-      fprintf(report_fp_, "%-*s  ", static_cast<int>(item->Width()), item->Show(sample).c_str());
-    } else {
-      fprintf(report_fp_, "%s\n", item->Show(sample).c_str());
-    }
-  }
-  if (print_callgraph_) {
-    PrintCallGraph(sample);
-  }
-}
-
-void ReportCommand::PrintCallGraph(const SampleEntry& sample) {
-  std::string prefix = "       ";
-  fprintf(report_fp_, "%s|\n", prefix.c_str());
-  fprintf(report_fp_, "%s-- %s\n", prefix.c_str(), sample.symbol->DemangledName());
-  prefix.append(3, ' ');
-  for (size_t i = 0; i < sample.callchain.children.size(); ++i) {
-    PrintCallGraphEntry(1, prefix, sample.callchain.children[i], sample.callchain.children_period,
-                        (i + 1 == sample.callchain.children.size()));
-  }
-}
-
-void ReportCommand::PrintCallGraphEntry(size_t depth, std::string prefix,
-                                        const std::unique_ptr<CallChainNode>& node,
-                                        uint64_t parent_period, bool last) {
-  if (depth > 20) {
-    LOG(WARNING) << "truncated callgraph at depth " << depth;
-    return;
-  }
-  prefix += "|";
-  fprintf(report_fp_, "%s\n", prefix.c_str());
-  if (last) {
-    prefix.back() = ' ';
-  }
-  std::string percentage_s = "-- ";
-  if (node->period + node->children_period != parent_period) {
-    double percentage = 100.0 * (node->period + node->children_period) / parent_period;
-    percentage_s = android::base::StringPrintf("--%.2lf%%-- ", percentage);
-  }
-  fprintf(report_fp_, "%s%s%s\n", prefix.c_str(), percentage_s.c_str(), node->chain[0]->symbol->DemangledName());
-  prefix.append(percentage_s.size(), ' ');
-  for (size_t i = 1; i < node->chain.size(); ++i) {
-    fprintf(report_fp_, "%s%s\n", prefix.c_str(), node->chain[i]->symbol->DemangledName());
-  }
-
-  for (size_t i = 0; i < node->children.size(); ++i) {
-    PrintCallGraphEntry(depth + 1, prefix, node->children[i], node->children_period,
-                        (i + 1 == node->children.size()));
-  }
-}
+}  // namespace
 
 void RegisterReportCommand() {
-  RegisterCommand("report", [] { return std::unique_ptr<Command>(new ReportCommand()); });
+  RegisterCommand("report",
+                  [] { return std::unique_ptr<Command>(new ReportCommand()); });
 }
diff --git a/simpleperf/cmd_report_sample.cpp b/simpleperf/cmd_report_sample.cpp
new file mode 100644
index 0000000..a58595a
--- /dev/null
+++ b/simpleperf/cmd_report_sample.cpp
@@ -0,0 +1,419 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <inttypes.h>
+
+#include <memory>
+
+#include "system/extras/simpleperf/report_sample.pb.h"
+
+#include <google/protobuf/io/coded_stream.h>
+#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
+
+#include "command.h"
+#include "record_file.h"
+#include "thread_tree.h"
+#include "utils.h"
+
+namespace proto = simpleperf_report_proto;
+
+namespace {
+
+class ProtobufFileWriter : public google::protobuf::io::CopyingOutputStream {
+ public:
+  explicit ProtobufFileWriter(FILE* out_fp) : out_fp_(out_fp) {}
+
+  bool Write(const void* buffer, int size) override {
+    return fwrite(buffer, size, 1, out_fp_) == 1;
+  }
+
+ private:
+  FILE* out_fp_;
+};
+
+class ProtobufFileReader : public google::protobuf::io::CopyingInputStream {
+ public:
+  explicit ProtobufFileReader(FILE* in_fp) : in_fp_(in_fp) {}
+
+  int Read(void* buffer, int size) override {
+    return fread(buffer, 1, size, in_fp_);
+  }
+
+ private:
+  FILE* in_fp_;
+};
+
+class ReportSampleCommand : public Command {
+ public:
+  ReportSampleCommand()
+      : Command(
+            "report-sample", "report raw sample information in perf.data",
+            // clang-format off
+"Usage: simpleperf report-sample [options]\n"
+"--dump-protobuf-report  <file>\n"
+"           Dump report file generated by\n"
+"           `simpleperf report-sample --protobuf -o <file>`.\n"
+"-i <file>  Specify path of record file, default is perf.data.\n"
+"-o report_file_name  Set report file name, default is stdout.\n"
+"--protobuf  Use protobuf format in report_sample.proto to output samples.\n"
+"            Need to set a report_file_name when using this option.\n"
+"--show-callchain  Print callchain samples.\n"
+            // clang-format on
+            ),
+        record_filename_("perf.data"),
+        show_callchain_(false),
+        use_protobuf_(false),
+        report_fp_(nullptr),
+        coded_os_(nullptr),
+        sample_count_(0),
+        lost_count_(0) {
+    thread_tree_.ShowMarkForUnknownSymbol();
+    thread_tree_.ShowIpForUnknownSymbol();
+  }
+
+  bool Run(const std::vector<std::string>& args) override;
+
+ private:
+  bool ParseOptions(const std::vector<std::string>& args);
+  bool DumpProtobufReport(const std::string& filename);
+  bool ProcessRecord(std::unique_ptr<Record> record);
+  bool PrintSampleRecordInProtobuf(const SampleRecord& record);
+  bool PrintLostSituationInProtobuf();
+  bool PrintSampleRecord(const SampleRecord& record);
+  void PrintLostSituation();
+
+  std::string record_filename_;
+  std::unique_ptr<RecordFileReader> record_file_reader_;
+  std::string dump_protobuf_report_file_;
+  bool show_callchain_;
+  bool use_protobuf_;
+  ThreadTree thread_tree_;
+  std::string report_filename_;
+  FILE* report_fp_;
+  google::protobuf::io::CodedOutputStream* coded_os_;
+  size_t sample_count_;
+  size_t lost_count_;
+};
+
+bool ReportSampleCommand::Run(const std::vector<std::string>& args) {
+  // 1. Parse options.
+  if (!ParseOptions(args)) {
+    return false;
+  }
+  if (!dump_protobuf_report_file_.empty()) {
+    return DumpProtobufReport(dump_protobuf_report_file_);
+  }
+  if (use_protobuf_) {
+    GOOGLE_PROTOBUF_VERIFY_VERSION;
+  }
+
+  // 2. Open record file.
+  record_file_reader_ = RecordFileReader::CreateInstance(record_filename_);
+  if (record_file_reader_ == nullptr) {
+    return false;
+  }
+
+  // 3. Prepare report output stream.
+  report_fp_ = stdout;
+  std::unique_ptr<FILE, decltype(&fclose)> fp(nullptr, fclose);
+  std::unique_ptr<ProtobufFileWriter> protobuf_writer;
+  std::unique_ptr<google::protobuf::io::CopyingOutputStreamAdaptor> protobuf_os;
+  std::unique_ptr<google::protobuf::io::CodedOutputStream> protobuf_coded_os;
+  if (!report_filename_.empty()) {
+    fp.reset(fopen(report_filename_.c_str(), use_protobuf_ ? "wb" : "w"));
+    if (fp == nullptr) {
+      PLOG(ERROR) << "failed to open " << report_filename_;
+      return false;
+    }
+    report_fp_ = fp.get();
+  }
+  if (use_protobuf_) {
+    protobuf_writer.reset(new ProtobufFileWriter(report_fp_));
+    protobuf_os.reset(new google::protobuf::io::CopyingOutputStreamAdaptor(
+        protobuf_writer.get()));
+    protobuf_coded_os.reset(
+        new google::protobuf::io::CodedOutputStream(protobuf_os.get()));
+    coded_os_ = protobuf_coded_os.get();
+  }
+
+  // 4. Read record file, and print samples online.
+  if (!record_file_reader_->ReadDataSection(
+          [this](std::unique_ptr<Record> record) {
+            return ProcessRecord(std::move(record));
+          })) {
+    return false;
+  }
+
+  if (use_protobuf_) {
+    if (!PrintLostSituationInProtobuf()) {
+      return false;
+    }
+    coded_os_->WriteLittleEndian32(0);
+    if (coded_os_->HadError()) {
+      LOG(ERROR) << "print protobuf report failed";
+      return false;
+    }
+    protobuf_coded_os.reset(nullptr);
+    google::protobuf::ShutdownProtobufLibrary();
+  } else {
+    PrintLostSituation();
+    fflush(report_fp_);
+  }
+  if (ferror(report_fp_) != 0) {
+    PLOG(ERROR) << "print report failed";
+    return false;
+  }
+  return true;
+}
+
+bool ReportSampleCommand::ParseOptions(const std::vector<std::string>& args) {
+  for (size_t i = 0; i < args.size(); ++i) {
+    if (args[i] == "--dump-protobuf-report") {
+      if (!NextArgumentOrError(args, &i)) {
+        return false;
+      }
+      dump_protobuf_report_file_ = args[i];
+    } else if (args[i] == "-i") {
+      if (!NextArgumentOrError(args, &i)) {
+        return false;
+      }
+      record_filename_ = args[i];
+    } else if (args[i] == "-o") {
+      if (!NextArgumentOrError(args, &i)) {
+        return false;
+      }
+      report_filename_ = args[i];
+    } else if (args[i] == "--protobuf") {
+      use_protobuf_ = true;
+    } else if (args[i] == "--show-callchain") {
+      show_callchain_ = true;
+    } else {
+      ReportUnknownOption(args, i);
+      return false;
+    }
+  }
+
+  if (use_protobuf_ && report_filename_.empty()) {
+    LOG(ERROR) << "please specify a report filename to write protobuf data";
+    return false;
+  }
+  return true;
+}
+
+bool ReportSampleCommand::DumpProtobufReport(const std::string& filename) {
+  GOOGLE_PROTOBUF_VERIFY_VERSION;
+  std::unique_ptr<FILE, decltype(&fclose)> fp(fopen(filename.c_str(), "rb"),
+                                              fclose);
+  if (fp == nullptr) {
+    PLOG(ERROR) << "failed to open " << filename;
+    return false;
+  }
+  ProtobufFileReader protobuf_reader(fp.get());
+  google::protobuf::io::CopyingInputStreamAdaptor adaptor(&protobuf_reader);
+  google::protobuf::io::CodedInputStream coded_is(&adaptor);
+  while (true) {
+    uint32_t size;
+    if (!coded_is.ReadLittleEndian32(&size)) {
+      PLOG(ERROR) << "failed to read " << filename;
+      return false;
+    }
+    if (size == 0) {
+      break;
+    }
+    auto limit = coded_is.PushLimit(size);
+    proto::Record proto_record;
+    if (!proto_record.ParseFromCodedStream(&coded_is)) {
+      PLOG(ERROR) << "failed to read " << filename;
+      return false;
+    }
+    coded_is.PopLimit(limit);
+    if (proto_record.type() == proto::Record_Type_SAMPLE) {
+      auto& sample = proto_record.sample();
+      static size_t sample_count = 0;
+      PrintIndented(0, "sample %zu:\n", ++sample_count);
+      PrintIndented(1, "time: %" PRIu64 "\n", sample.time());
+      PrintIndented(1, "thread_id: %d\n", sample.thread_id());
+      PrintIndented(1, "callchain:\n");
+      for (int j = 0; j < sample.callchain_size(); ++j) {
+        const proto::Sample_CallChainEntry& callchain = sample.callchain(j);
+        PrintIndented(2, "ip: %" PRIx64 "\n", callchain.ip());
+        PrintIndented(2, "dso: %s\n", callchain.file().c_str());
+        PrintIndented(2, "symbol: %s\n", callchain.symbol().c_str());
+      }
+    } else if (proto_record.type() == proto::Record_Type_LOST_SITUATION) {
+      auto& lost = proto_record.lost();
+      PrintIndented(0, "lost_situation:\n");
+      PrintIndented(1, "sample_count: %" PRIu64 "\n", lost.sample_count());
+      PrintIndented(1, "lost_count: %" PRIu64 "\n", lost.lost_count());
+    } else {
+      LOG(ERROR) << "unexpected record type " << proto_record.type();
+      return false;
+    }
+  }
+  google::protobuf::ShutdownProtobufLibrary();
+  return true;
+}
+
+bool ReportSampleCommand::ProcessRecord(std::unique_ptr<Record> record) {
+  thread_tree_.Update(*record);
+  if (record->type() == PERF_RECORD_SAMPLE) {
+    sample_count_++;
+    auto& r = *static_cast<const SampleRecord*>(record.get());
+    if (use_protobuf_) {
+      return PrintSampleRecordInProtobuf(r);
+    } else {
+      return PrintSampleRecord(r);
+    }
+  } else if (record->type() == PERF_RECORD_LOST) {
+    lost_count_ += static_cast<const LostRecord*>(record.get())->lost;
+  }
+  return true;
+}
+
+bool ReportSampleCommand::PrintSampleRecordInProtobuf(const SampleRecord& r) {
+  proto::Record proto_record;
+  proto_record.set_type(proto::Record_Type_SAMPLE);
+  proto::Sample* sample = proto_record.mutable_sample();
+  sample->set_time(r.time_data.time);
+  sample->set_thread_id(r.tid_data.tid);
+  proto::Sample_CallChainEntry* callchain = sample->add_callchain();
+  callchain->set_ip(r.ip_data.ip);
+
+  bool in_kernel = r.InKernel();
+  const ThreadEntry* thread =
+      thread_tree_.FindThreadOrNew(r.tid_data.pid, r.tid_data.tid);
+  const MapEntry* map = thread_tree_.FindMap(thread, r.ip_data.ip, in_kernel);
+  const Symbol* symbol = thread_tree_.FindSymbol(map, r.ip_data.ip, nullptr);
+  callchain->set_symbol(symbol->DemangledName());
+  callchain->set_file(map->dso->Path());
+
+  if (show_callchain_ && (r.sample_type & PERF_SAMPLE_CALLCHAIN)) {
+    bool first_ip = true;
+    for (uint64_t i = 0; i < r.callchain_data.ip_nr; ++i) {
+      uint64_t ip = r.callchain_data.ips[i];
+      if (ip >= PERF_CONTEXT_MAX) {
+        switch (ip) {
+          case PERF_CONTEXT_KERNEL:
+            in_kernel = true;
+            break;
+          case PERF_CONTEXT_USER:
+            in_kernel = false;
+            break;
+          default:
+            LOG(DEBUG) << "Unexpected perf_context in callchain: " << std::hex
+                       << ip << std::dec;
+        }
+      } else {
+        if (first_ip) {
+          first_ip = false;
+          // Remove duplication with sample ip.
+          if (ip == r.ip_data.ip) {
+            continue;
+          }
+        }
+        const MapEntry* map = thread_tree_.FindMap(thread, ip, in_kernel);
+        const Symbol* symbol = thread_tree_.FindSymbol(map, ip, nullptr);
+        callchain = sample->add_callchain();
+        callchain->set_ip(ip);
+        callchain->set_symbol(symbol->DemangledName());
+        callchain->set_file(map->dso->Path());
+      }
+    }
+  }
+  coded_os_->WriteLittleEndian32(proto_record.ByteSize());
+  if (!proto_record.SerializeToCodedStream(coded_os_)) {
+    LOG(ERROR) << "failed to write sample to protobuf";
+    return false;
+  }
+  return true;
+}
+
+bool ReportSampleCommand::PrintLostSituationInProtobuf() {
+  proto::Record proto_record;
+  proto_record.set_type(proto::Record_Type_LOST_SITUATION);
+  proto::LostSituation* lost = proto_record.mutable_lost();
+  lost->set_sample_count(sample_count_);
+  lost->set_lost_count(lost_count_);
+  coded_os_->WriteLittleEndian32(proto_record.ByteSize());
+  if (!proto_record.SerializeToCodedStream(coded_os_)) {
+    LOG(ERROR) << "failed to write lost situation to protobuf";
+    return false;
+  }
+  return true;
+}
+
+bool ReportSampleCommand::PrintSampleRecord(const SampleRecord& r) {
+  bool in_kernel = r.InKernel();
+  const ThreadEntry* thread =
+      thread_tree_.FindThreadOrNew(r.tid_data.pid, r.tid_data.tid);
+  const MapEntry* map = thread_tree_.FindMap(thread, r.ip_data.ip, in_kernel);
+  const Symbol* symbol = thread_tree_.FindSymbol(map, r.ip_data.ip, nullptr);
+  FprintIndented(report_fp_, 0, "sample:\n");
+  FprintIndented(report_fp_, 1, "time: %" PRIu64 "\n", r.time_data.time);
+  FprintIndented(report_fp_, 1, "thread_id: %d\n", r.tid_data.tid);
+  FprintIndented(report_fp_, 1, "ip: %" PRIx64 "\n", r.ip_data.ip);
+  FprintIndented(report_fp_, 1, "dso: %s\n", map->dso->Path().c_str());
+  FprintIndented(report_fp_, 1, "symbol: %s\n", symbol->DemangledName());
+
+  if (show_callchain_ && (r.sample_type & PERF_SAMPLE_CALLCHAIN)) {
+    FprintIndented(report_fp_, 1, "callchain:\n");
+    bool first_ip = true;
+    for (uint64_t i = 0; i < r.callchain_data.ip_nr; ++i) {
+      uint64_t ip = r.callchain_data.ips[i];
+      if (ip >= PERF_CONTEXT_MAX) {
+        switch (ip) {
+          case PERF_CONTEXT_KERNEL:
+            in_kernel = true;
+            break;
+          case PERF_CONTEXT_USER:
+            in_kernel = false;
+            break;
+          default:
+            LOG(DEBUG) << "Unexpected perf_context in callchain: " << std::hex
+                       << ip;
+        }
+      } else {
+        if (first_ip) {
+          first_ip = false;
+          // Remove duplication with sample ip.
+          if (ip == r.ip_data.ip) {
+            continue;
+          }
+        }
+        const MapEntry* map = thread_tree_.FindMap(thread, ip, in_kernel);
+        const Symbol* symbol = thread_tree_.FindSymbol(map, ip, nullptr);
+        FprintIndented(report_fp_, 2, "ip: %" PRIx64 "\n", ip);
+        FprintIndented(report_fp_, 2, "dso: %s\n", map->dso->Path().c_str());
+        FprintIndented(report_fp_, 2, "symbol: %s\n", symbol->DemangledName());
+      }
+    }
+  }
+  return true;
+}
+
+void ReportSampleCommand::PrintLostSituation() {
+  FprintIndented(report_fp_, 0, "lost_situation:\n");
+  FprintIndented(report_fp_, 1, "sample_count: %" PRIu64 "\n", sample_count_);
+  FprintIndented(report_fp_, 1, "lost_count: %" PRIu64 "\n", sample_count_);
+}
+
+}  // namespace
+
+void RegisterReportSampleCommand() {
+  RegisterCommand("report-sample", [] {
+    return std::unique_ptr<Command>(new ReportSampleCommand());
+  });
+}
diff --git a/simpleperf/cmd_report_sample_test.cpp b/simpleperf/cmd_report_sample_test.cpp
new file mode 100644
index 0000000..42df179
--- /dev/null
+++ b/simpleperf/cmd_report_sample_test.cpp
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+
+#include <android-base/test_utils.h>
+
+#include "command.h"
+#include "get_test_data.h"
+
+static std::unique_ptr<Command> ReportSampleCmd() {
+  return CreateCommandInstance("report-sample");
+}
+
+TEST(cmd_report_sample, text) {
+  ASSERT_TRUE(
+      ReportSampleCmd()->Run({"-i", GetTestData(PERF_DATA_WITH_SYMBOLS)}));
+}
+
+TEST(cmd_report_sample, output_option) {
+  TemporaryFile tmpfile;
+  ASSERT_TRUE(ReportSampleCmd()->Run(
+      {"-i", GetTestData(PERF_DATA_WITH_SYMBOLS), "-o", tmpfile.path}));
+}
+
+TEST(cmd_report_sample, show_callchain_option) {
+  TemporaryFile tmpfile;
+  ASSERT_TRUE(ReportSampleCmd()->Run({"-i", GetTestData(CALLGRAPH_FP_PERF_DATA),
+                                      "-o", tmpfile.path, "--show-callchain"}));
+}
+
+TEST(cmd_report_sample, protobuf_option) {
+  TemporaryFile tmpfile;
+  ASSERT_TRUE(ReportSampleCmd()->Run({"-i", GetTestData(PERF_DATA_WITH_SYMBOLS),
+                                      "-o", tmpfile.path, "--protobuf"}));
+  ASSERT_TRUE(ReportSampleCmd()->Run({"--dump-protobuf-report", tmpfile.path}));
+}
diff --git a/simpleperf/cmd_report_test.cpp b/simpleperf/cmd_report_test.cpp
index bcf8b6e..a889775 100644
--- a/simpleperf/cmd_report_test.cpp
+++ b/simpleperf/cmd_report_test.cpp
@@ -36,16 +36,18 @@
 
 class ReportCommandTest : public ::testing::Test {
  protected:
-  void Report(const std::string perf_data,
-              const std::vector<std::string>& add_args = std::vector<std::string>()) {
+  void Report(
+      const std::string& perf_data,
+      const std::vector<std::string>& add_args = std::vector<std::string>()) {
     ReportRaw(GetTestData(perf_data), add_args);
   }
 
-  void ReportRaw(const std::string perf_data,
-                 const std::vector<std::string>& add_args = std::vector<std::string>()) {
+  void ReportRaw(
+      const std::string& perf_data,
+      const std::vector<std::string>& add_args = std::vector<std::string>()) {
     success = false;
-    std::vector<std::string> args = {"-i", perf_data,
-        "--symfs", GetTestDataDir(), "-o", tmp_file.path};
+    std::vector<std::string> args = {
+        "-i", perf_data, "--symfs", GetTestDataDir(), "-o", tmp_file.path};
     args.insert(args.end(), add_args.begin(), add_args.end());
     ASSERT_TRUE(ReportCmd()->Run(args));
     ASSERT_TRUE(android::base::ReadFileToString(tmp_file.path, &content));
@@ -74,11 +76,18 @@
   ASSERT_NE(content.find("GlobalFunc"), std::string::npos);
 }
 
+TEST_F(ReportCommandTest, report_symbol_from_elf_file_with_mini_debug_info) {
+  Report(PERF_DATA_WITH_MINI_DEBUG_INFO);
+  ASSERT_TRUE(success);
+  ASSERT_NE(content.find("GlobalFunc"), std::string::npos);
+}
+
 TEST_F(ReportCommandTest, sort_option_pid) {
   Report(PERF_DATA, {"--sort", "pid"});
   ASSERT_TRUE(success);
   size_t line_index = 0;
-  while (line_index < lines.size() && lines[line_index].find("Pid") == std::string::npos) {
+  while (line_index < lines.size() &&
+         lines[line_index].find("Pid") == std::string::npos) {
     line_index++;
   }
   ASSERT_LT(line_index + 2, lines.size());
@@ -88,7 +97,8 @@
   Report(PERF_DATA, {"--sort", "comm,pid,dso,symbol"});
   ASSERT_TRUE(success);
   size_t line_index = 0;
-  while (line_index < lines.size() && lines[line_index].find("Overhead") == std::string::npos) {
+  while (line_index < lines.size() &&
+         lines[line_index].find("Overhead") == std::string::npos) {
     line_index++;
   }
   ASSERT_LT(line_index + 1, lines.size());
@@ -106,7 +116,8 @@
   for (size_t i = 0; i < lines.size(); ++i) {
     char name[1024];
     std::pair<double, double> pair;
-    if (sscanf(lines[i].c_str(), "%lf%%%lf%%%s", &pair.first, &pair.second, name) == 3) {
+    if (sscanf(lines[i].c_str(), "%lf%%%lf%%%s", &pair.first, &pair.second,
+               name) == 3) {
       map.insert(std::make_pair(name, pair));
     }
   }
@@ -123,7 +134,7 @@
   bool found = false;
   for (size_t i = 0; i + 2 < lines.size(); ++i) {
     if (lines[i].find("GlobalFunc") != std::string::npos &&
-        lines[i + 1].find("|") != std::string::npos &&
+        lines[i + 1].find('|') != std::string::npos &&
         lines[i + 2].find("main") != std::string::npos) {
       found = true;
       break;
@@ -136,7 +147,7 @@
   bool found = false;
   for (size_t i = 0; i + 2 < lines.size(); ++i) {
     if (lines[i].find("main") != std::string::npos &&
-        lines[i + 1].find("|") != std::string::npos &&
+        lines[i + 1].find('|') != std::string::npos &&
         lines[i + 2].find("GlobalFunc") != std::string::npos) {
       found = true;
       break;
@@ -148,7 +159,7 @@
 TEST_F(ReportCommandTest, callgraph_option) {
   Report(CALLGRAPH_FP_PERF_DATA, {"-g"});
   ASSERT_TRUE(success);
-  ASSERT_TRUE(CheckCalleeMode(lines));
+  ASSERT_TRUE(CheckCallerMode(lines));
   Report(CALLGRAPH_FP_PERF_DATA, {"-g", "callee"});
   ASSERT_TRUE(success);
   ASSERT_TRUE(CheckCalleeMode(lines));
@@ -157,9 +168,11 @@
   ASSERT_TRUE(CheckCallerMode(lines));
 }
 
-static bool AllItemsWithString(std::vector<std::string>& lines, const std::vector<std::string>& strs) {
+static bool AllItemsWithString(std::vector<std::string>& lines,
+                               const std::vector<std::string>& strs) {
   size_t line_index = 0;
-  while (line_index < lines.size() && lines[line_index].find("Overhead") == std::string::npos) {
+  while (line_index < lines.size() &&
+         lines[line_index].find("Overhead") == std::string::npos) {
     line_index++;
   }
   if (line_index == lines.size() || line_index + 1 == lines.size()) {
@@ -182,29 +195,59 @@
 }
 
 TEST_F(ReportCommandTest, pid_filter_option) {
-  Report(PERF_DATA);
-  ASSERT_TRUE("success");
-  ASSERT_FALSE(AllItemsWithString(lines, {"26083"}));
-  ASSERT_FALSE(AllItemsWithString(lines, {"26083", "26090"}));
-  Report(PERF_DATA, {"--pids", "26083"});
+  Report(PERF_DATA_WITH_MULTIPLE_PIDS_AND_TIDS, {"--sort", "pid"});
   ASSERT_TRUE(success);
-  ASSERT_TRUE(AllItemsWithString(lines, {"26083"}));
-  Report(PERF_DATA, {"--pids", "26083,26090"});
+  ASSERT_FALSE(AllItemsWithString(lines, {"17441"}));
+  ASSERT_FALSE(AllItemsWithString(lines, {"17441", "17443"}));
+  Report(PERF_DATA_WITH_MULTIPLE_PIDS_AND_TIDS,
+         {"--sort", "pid", "--pids", "17441"});
   ASSERT_TRUE(success);
-  ASSERT_TRUE(AllItemsWithString(lines, {"26083", "26090"}));
+  ASSERT_TRUE(AllItemsWithString(lines, {"17441"}));
+  Report(PERF_DATA_WITH_MULTIPLE_PIDS_AND_TIDS,
+         {"--sort", "pid", "--pids", "17441,17443"});
+  ASSERT_TRUE(success);
+  ASSERT_TRUE(AllItemsWithString(lines, {"17441", "17443"}));
+
+  // Test that --pids option is not the same as --tids option.
+  // Thread 17445 and 17441 are in process 17441.
+  Report(PERF_DATA_WITH_MULTIPLE_PIDS_AND_TIDS,
+         {"--sort", "tid", "--pids", "17441"});
+  ASSERT_TRUE(success);
+  ASSERT_NE(content.find("17441"), std::string::npos);
+  ASSERT_NE(content.find("17445"), std::string::npos);
+}
+
+TEST_F(ReportCommandTest, wrong_pid_filter_option) {
+  ASSERT_EXIT(
+      {
+        Report(PERF_DATA_WITH_MULTIPLE_PIDS_AND_TIDS, {"--pids", "2,bogus"});
+        exit(success ? 0 : 1);
+      },
+      testing::ExitedWithCode(1), "invalid id in --pids option: bogus");
 }
 
 TEST_F(ReportCommandTest, tid_filter_option) {
-  Report(PERF_DATA);
-  ASSERT_TRUE("success");
-  ASSERT_FALSE(AllItemsWithString(lines, {"26083"}));
-  ASSERT_FALSE(AllItemsWithString(lines, {"26083", "26090"}));
-  Report(PERF_DATA, {"--tids", "26083"});
+  Report(PERF_DATA_WITH_MULTIPLE_PIDS_AND_TIDS, {"--sort", "tid"});
   ASSERT_TRUE(success);
-  ASSERT_TRUE(AllItemsWithString(lines, {"26083"}));
-  Report(PERF_DATA, {"--tids", "26083,26090"});
+  ASSERT_FALSE(AllItemsWithString(lines, {"17441"}));
+  ASSERT_FALSE(AllItemsWithString(lines, {"17441", "17445"}));
+  Report(PERF_DATA_WITH_MULTIPLE_PIDS_AND_TIDS,
+         {"--sort", "tid", "--tids", "17441"});
   ASSERT_TRUE(success);
-  ASSERT_TRUE(AllItemsWithString(lines, {"26083", "26090"}));
+  ASSERT_TRUE(AllItemsWithString(lines, {"17441"}));
+  Report(PERF_DATA_WITH_MULTIPLE_PIDS_AND_TIDS,
+         {"--sort", "tid", "--tids", "17441,17445"});
+  ASSERT_TRUE(success);
+  ASSERT_TRUE(AllItemsWithString(lines, {"17441", "17445"}));
+}
+
+TEST_F(ReportCommandTest, wrong_tid_filter_option) {
+  ASSERT_EXIT(
+      {
+        Report(PERF_DATA_WITH_MULTIPLE_PIDS_AND_TIDS, {"--tids", "2,bogus"});
+        exit(success ? 0 : 1);
+      },
+      testing::ExitedWithCode(1), "invalid id in --tids option: bogus");
 }
 
 TEST_F(ReportCommandTest, comm_filter_option) {
@@ -233,6 +276,21 @@
   ASSERT_TRUE(AllItemsWithString(lines, {"/t1", "/t2"}));
 }
 
+TEST_F(ReportCommandTest, symbol_filter_option) {
+  Report(PERF_DATA_WITH_SYMBOLS, {"--sort", "symbol"});
+  ASSERT_TRUE(success);
+  ASSERT_FALSE(AllItemsWithString(lines, {"func2(int, int)"}));
+  ASSERT_FALSE(AllItemsWithString(lines, {"main", "func2(int, int)"}));
+  Report(PERF_DATA_WITH_SYMBOLS,
+         {"--sort", "symbol", "--symbols", "func2(int, int)"});
+  ASSERT_TRUE(success);
+  ASSERT_TRUE(AllItemsWithString(lines, {"func2(int, int)"}));
+  Report(PERF_DATA_WITH_SYMBOLS,
+         {"--sort", "symbol", "--symbols", "main;func2(int, int)"});
+  ASSERT_TRUE(success);
+  ASSERT_TRUE(AllItemsWithString(lines, {"main", "func2(int, int)"}));
+}
+
 TEST_F(ReportCommandTest, use_branch_address) {
   Report(BRANCH_PERF_DATA, {"-b", "--sort", "symbol_from,symbol_to"});
   std::set<std::pair<std::string, std::string>> hit_set;
@@ -248,19 +306,116 @@
       }
     }
   }
-  ASSERT_NE(hit_set.find(std::make_pair<std::string, std::string>("GlobalFunc", "CalledFunc")),
+  ASSERT_NE(hit_set.find(std::make_pair<std::string, std::string>(
+                "GlobalFunc", "CalledFunc")),
             hit_set.end());
-  ASSERT_NE(hit_set.find(std::make_pair<std::string, std::string>("CalledFunc", "GlobalFunc")),
+  ASSERT_NE(hit_set.find(std::make_pair<std::string, std::string>(
+                "CalledFunc", "GlobalFunc")),
             hit_set.end());
 }
 
 TEST_F(ReportCommandTest, report_symbols_of_nativelib_in_apk) {
   Report(NATIVELIB_IN_APK_PERF_DATA);
   ASSERT_TRUE(success);
-  ASSERT_NE(content.find(GetUrlInApk(APK_FILE, NATIVELIB_IN_APK)), std::string::npos);
+  ASSERT_NE(content.find(GetUrlInApk(APK_FILE, NATIVELIB_IN_APK)),
+            std::string::npos);
   ASSERT_NE(content.find("Func2"), std::string::npos);
 }
 
+TEST_F(ReportCommandTest, report_more_than_one_event_types) {
+  Report(PERF_DATA_WITH_TWO_EVENT_TYPES);
+  ASSERT_TRUE(success);
+  ASSERT_NE(content.find("cpu-cycles"), std::string::npos);
+  ASSERT_NE(content.find("cpu-clock"), std::string::npos);
+}
+
+TEST_F(ReportCommandTest, report_kernel_symbol) {
+  Report(PERF_DATA_WITH_KERNEL_SYMBOL);
+  ASSERT_TRUE(success);
+  ASSERT_NE(content.find("perf_event_aux"), std::string::npos);
+}
+
+TEST_F(ReportCommandTest, report_dumped_symbols) {
+  Report(PERF_DATA_WITH_SYMBOLS);
+  ASSERT_TRUE(success);
+  ASSERT_NE(content.find("main"), std::string::npos);
+  Report(PERF_DATA_WITH_SYMBOLS_FOR_NONZERO_MINVADDR_DSO);
+  ASSERT_TRUE(success);
+  ASSERT_NE(content.find("main"), std::string::npos);
+}
+
+TEST_F(ReportCommandTest, report_sort_vaddr_in_file) {
+  Report(PERF_DATA, {"--sort", "vaddr_in_file"});
+  ASSERT_TRUE(success);
+  ASSERT_NE(content.find("VaddrInFile"), std::string::npos);
+}
+
+TEST_F(ReportCommandTest, check_build_id) {
+  Report(PERF_DATA_FOR_BUILD_ID_CHECK,
+         {"--symfs", GetTestData(CORRECT_SYMFS_FOR_BUILD_ID_CHECK)});
+  ASSERT_TRUE(success);
+  ASSERT_NE(content.find("main"), std::string::npos);
+  ASSERT_EXIT(
+      {
+        Report(PERF_DATA_FOR_BUILD_ID_CHECK,
+               {"--symfs", GetTestData(WRONG_SYMFS_FOR_BUILD_ID_CHECK)});
+        if (!success) {
+          exit(1);
+        }
+        if (content.find("main") != std::string::npos) {
+          exit(2);
+        }
+        exit(0);
+      },
+      testing::ExitedWithCode(0), "Build id mismatch");
+}
+
+TEST_F(ReportCommandTest, no_show_ip_option) {
+  Report(PERF_DATA);
+  ASSERT_TRUE(success);
+  ASSERT_EQ(content.find("unknown"), std::string::npos);
+  Report(PERF_DATA, {"--no-show-ip"});
+  ASSERT_TRUE(success);
+  ASSERT_NE(content.find("unknown"), std::string::npos);
+}
+
+TEST_F(ReportCommandTest, no_symbol_table_warning) {
+  ASSERT_EXIT(
+      {
+        Report(PERF_DATA,
+               {"--symfs", GetTestData(SYMFS_FOR_NO_SYMBOL_TABLE_WARNING)});
+        if (!success) {
+          exit(1);
+        }
+        if (content.find("GlobalFunc") != std::string::npos) {
+          exit(2);
+        }
+        exit(0);
+      },
+      testing::ExitedWithCode(0), "elf doesn't contain symbol table");
+}
+
+TEST_F(ReportCommandTest, read_elf_file_warning) {
+  ASSERT_EXIT(
+      {
+        Report(PERF_DATA,
+               {"--symfs", GetTestData(SYMFS_FOR_READ_ELF_FILE_WARNING)});
+        if (!success) {
+          exit(1);
+        }
+        if (content.find("GlobalFunc") != std::string::npos) {
+          exit(2);
+        }
+        exit(0);
+      },
+      testing::ExitedWithCode(0), "elf: Read failed");
+}
+
+TEST_F(ReportCommandTest, report_data_generated_by_linux_perf) {
+  Report(PERF_DATA_GENERATED_BY_LINUX_PERF);
+  ASSERT_TRUE(success);
+}
+
 #if defined(__linux__)
 
 static std::unique_ptr<Command> RecordCmd() {
@@ -270,27 +425,30 @@
 TEST_F(ReportCommandTest, dwarf_callgraph) {
   if (IsDwarfCallChainSamplingSupported()) {
     TemporaryFile tmp_file;
-    ASSERT_TRUE(RecordCmd()->Run({"-g", "-o", tmp_file.path, "sleep", SLEEP_SEC}));
+    ASSERT_TRUE(
+        RecordCmd()->Run({"-g", "-o", tmp_file.path, "sleep", SLEEP_SEC}));
     ReportRaw(tmp_file.path, {"-g"});
     ASSERT_TRUE(success);
   } else {
-    GTEST_LOG_(INFO)
-        << "This test does nothing as dwarf callchain sampling is not supported on this device.";
+    GTEST_LOG_(INFO) << "This test does nothing as dwarf callchain sampling is "
+                        "not supported on this device.";
   }
 }
 
 TEST_F(ReportCommandTest, report_dwarf_callgraph_of_nativelib_in_apk) {
-  // NATIVELIB_IN_APK_PERF_DATA is recorded on arm64, so can only report callgraph on arm64.
+  // NATIVELIB_IN_APK_PERF_DATA is recorded on arm64, so can only report
+  // callgraph on arm64.
   if (GetBuildArch() == ARCH_ARM64) {
     Report(NATIVELIB_IN_APK_PERF_DATA, {"-g"});
-    ASSERT_NE(content.find(GetUrlInApk(APK_FILE, NATIVELIB_IN_APK)), std::string::npos);
+    ASSERT_NE(content.find(GetUrlInApk(APK_FILE, NATIVELIB_IN_APK)),
+              std::string::npos);
     ASSERT_NE(content.find("Func2"), std::string::npos);
     ASSERT_NE(content.find("Func1"), std::string::npos);
     ASSERT_NE(content.find("GlobalFunc"), std::string::npos);
   } else {
-    GTEST_LOG_(INFO) << "This test does nothing as it is only run on arm64 devices";
+    GTEST_LOG_(INFO)
+        << "This test does nothing as it is only run on arm64 devices";
   }
 }
 
 #endif
-
diff --git a/simpleperf/cmd_stat.cpp b/simpleperf/cmd_stat.cpp
index 488e731..93054d2 100644
--- a/simpleperf/cmd_stat.cpp
+++ b/simpleperf/cmd_stat.cpp
@@ -18,6 +18,7 @@
 #include <signal.h>
 #include <stdio.h>
 #include <string.h>
+#include <sys/prctl.h>
 
 #include <algorithm>
 #include <chrono>
@@ -34,70 +35,287 @@
 #include "event_fd.h"
 #include "event_selection_set.h"
 #include "event_type.h"
-#include "scoped_signal_handler.h"
+#include "IOEventLoop.h"
 #include "utils.h"
 #include "workload.h"
 
+namespace {
+
 static std::vector<std::string> default_measured_event_types{
     "cpu-cycles",   "stalled-cycles-frontend", "stalled-cycles-backend",
     "instructions", "branch-instructions",     "branch-misses",
     "task-clock",   "context-switches",        "page-faults",
 };
 
-static volatile bool signaled;
-static void signal_handler(int) {
-  signaled = true;
-}
+struct CounterSummary {
+  std::string type_name;
+  std::string modifier;
+  uint32_t group_id;
+  uint64_t count;
+  double scale;
+  std::string readable_count;
+  std::string comment;
+  bool auto_generated;
+
+  CounterSummary(const std::string& type_name, const std::string& modifier,
+                 uint32_t group_id, uint64_t count, double scale,
+                 bool auto_generated, bool csv)
+      : type_name(type_name),
+        modifier(modifier),
+        group_id(group_id),
+        count(count),
+        scale(scale),
+        auto_generated(auto_generated) {
+    readable_count = ReadableCountValue(csv);
+  }
+
+  bool IsMonitoredAtTheSameTime(const CounterSummary& other) const {
+    // Two summaries are monitored at the same time if they are in the same
+    // group or are monitored all the time.
+    if (group_id == other.group_id) {
+      return true;
+    }
+    return IsMonitoredAllTheTime() && other.IsMonitoredAllTheTime();
+  }
+
+  std::string Name() const {
+    if (modifier.empty()) {
+      return type_name;
+    }
+    return type_name + ":" + modifier;
+  }
+
+ private:
+  std::string ReadableCountValue(bool csv) {
+    if (type_name == "cpu-clock" || type_name == "task-clock") {
+      // Convert nanoseconds to milliseconds.
+      double value = count / 1e6;
+      return android::base::StringPrintf("%lf(ms)", value);
+    } else {
+      // Convert big numbers to human friendly mode. For example,
+      // 1000000 will be converted to 1,000,000.
+      std::string s = android::base::StringPrintf("%" PRIu64, count);
+      if (csv) {
+        return s;
+      } else {
+        for (size_t i = s.size() - 1, j = 1; i > 0; --i, ++j) {
+          if (j == 3) {
+            s.insert(s.begin() + i, ',');
+            j = 0;
+          }
+        }
+        return s;
+      }
+    }
+  }
+
+  bool IsMonitoredAllTheTime() const {
+    // If an event runs all the time it is enabled (by not sharing hardware
+    // counters with other events), the scale of its summary is usually within
+    // [1, 1 + 1e-5]. By setting SCALE_ERROR_LIMIT to 1e-5, We can identify
+    // events monitored all the time in most cases while keeping the report
+    // error rate <= 1e-5.
+    constexpr double SCALE_ERROR_LIMIT = 1e-5;
+    return (fabs(scale - 1.0) < SCALE_ERROR_LIMIT);
+  }
+};
+
+class CounterSummaries {
+ public:
+  explicit CounterSummaries(bool csv) : csv_(csv) {}
+  void AddSummary(const CounterSummary& summary) {
+    summaries_.push_back(summary);
+  }
+
+  const CounterSummary* FindSummary(const std::string& type_name,
+                                    const std::string& modifier) {
+    for (const auto& s : summaries_) {
+      if (s.type_name == type_name && s.modifier == modifier) {
+        return &s;
+      }
+    }
+    return nullptr;
+  }
+
+  // If we have two summaries monitoring the same event type at the same time,
+  // that one is for user space only, and the other is for kernel space only;
+  // then we can automatically generate a summary combining the two results.
+  // For example, a summary of branch-misses:u and a summary for branch-misses:k
+  // can generate a summary of branch-misses.
+  void AutoGenerateSummaries() {
+    for (size_t i = 0; i < summaries_.size(); ++i) {
+      const CounterSummary& s = summaries_[i];
+      if (s.modifier == "u") {
+        const CounterSummary* other = FindSummary(s.type_name, "k");
+        if (other != nullptr && other->IsMonitoredAtTheSameTime(s)) {
+          if (FindSummary(s.type_name, "") == nullptr) {
+            AddSummary(CounterSummary(s.type_name, "", s.group_id,
+                                      s.count + other->count, s.scale, true,
+                                      csv_));
+          }
+        }
+      }
+    }
+  }
+
+  void GenerateComments(double duration_in_sec) {
+    for (auto& s : summaries_) {
+      s.comment = GetCommentForSummary(s, duration_in_sec);
+    }
+  }
+
+  void Show(FILE* fp) {
+    size_t count_column_width = 0;
+    size_t name_column_width = 0;
+    size_t comment_column_width = 0;
+    for (auto& s : summaries_) {
+      count_column_width =
+          std::max(count_column_width, s.readable_count.size());
+      name_column_width = std::max(name_column_width, s.Name().size());
+      comment_column_width = std::max(comment_column_width, s.comment.size());
+    }
+
+    for (auto& s : summaries_) {
+      if (csv_) {
+        fprintf(fp, "%s,%s,%s,(%.0lf%%)%s\n", s.readable_count.c_str(),
+                s.Name().c_str(), s.comment.c_str(), 1.0 / s.scale * 100,
+                (s.auto_generated ? " (generated)," : ","));
+      } else {
+        fprintf(fp, "  %*s  %-*s   # %-*s  (%.0lf%%)%s\n",
+                static_cast<int>(count_column_width), s.readable_count.c_str(),
+                static_cast<int>(name_column_width), s.Name().c_str(),
+                static_cast<int>(comment_column_width), s.comment.c_str(),
+                1.0 / s.scale * 100, (s.auto_generated ? " (generated)" : ""));
+      }
+    }
+  }
+
+ private:
+  std::string GetCommentForSummary(const CounterSummary& s,
+                                   double duration_in_sec) {
+    char sap_mid;
+    if (csv_) {
+      sap_mid = ',';
+    } else {
+      sap_mid = ' ';
+    }
+    if (s.type_name == "task-clock") {
+      double run_sec = s.count / 1e9;
+      double used_cpus = run_sec / (duration_in_sec / s.scale);
+      return android::base::StringPrintf("%lf%ccpus used", used_cpus, sap_mid);
+    }
+    if (s.type_name == "cpu-clock") {
+      return "";
+    }
+    if (s.type_name == "cpu-cycles") {
+      double hz = s.count / (duration_in_sec / s.scale);
+      return android::base::StringPrintf("%lf%cGHz", hz / 1e9, sap_mid);
+    }
+    if (s.type_name == "instructions" && s.count != 0) {
+      const CounterSummary* other = FindSummary("cpu-cycles", s.modifier);
+      if (other != nullptr && other->IsMonitoredAtTheSameTime(s)) {
+        double cpi = static_cast<double>(other->count) / s.count;
+        return android::base::StringPrintf("%lf%ccycles per instruction", cpi,
+                                           sap_mid);
+      }
+    }
+    if (android::base::EndsWith(s.type_name, "-misses")) {
+      std::string other_name;
+      if (s.type_name == "cache-misses") {
+        other_name = "cache-references";
+      } else if (s.type_name == "branch-misses") {
+        other_name = "branch-instructions";
+      } else {
+        other_name =
+            s.type_name.substr(0, s.type_name.size() - strlen("-misses")) + "s";
+      }
+      const CounterSummary* other = FindSummary(other_name, s.modifier);
+      if (other != nullptr && other->IsMonitoredAtTheSameTime(s) &&
+          other->count != 0) {
+        double miss_rate = static_cast<double>(s.count) / other->count;
+        return android::base::StringPrintf("%lf%%%cmiss rate", miss_rate * 100,
+                                           sap_mid);
+      }
+    }
+    double rate = s.count / (duration_in_sec / s.scale);
+    if (rate > 1e9) {
+      return android::base::StringPrintf("%.3lf%cG/sec", rate / 1e9, sap_mid);
+    }
+    if (rate > 1e6) {
+      return android::base::StringPrintf("%.3lf%cM/sec", rate / 1e6, sap_mid);
+    }
+    if (rate > 1e3) {
+      return android::base::StringPrintf("%.3lf%cK/sec", rate / 1e3, sap_mid);
+    }
+    return android::base::StringPrintf("%.3lf%c/sec", rate, sap_mid);
+  }
+
+ private:
+  std::vector<CounterSummary> summaries_;
+  bool csv_;
+};
 
 class StatCommand : public Command {
  public:
   StatCommand()
       : Command("stat", "gather performance counter information",
-                "Usage: simpleperf stat [options] [command [command-args]]\n"
-                "    Gather performance counter information of running [command].\n"
-                "    -a           Collect system-wide information.\n"
-                "    --cpu cpu_item1,cpu_item2,...\n"
-                "                 Collect information only on the selected cpus. cpu_item can\n"
-                "                 be a cpu number like 1, or a cpu range like 0-3.\n"
-                "    -e event1[:modifier1],event2[:modifier2],...\n"
-                "                 Select the event list to count. Use `simpleperf list` to find\n"
-                "                 all possible event names. Modifiers can be added to define\n"
-                "                 how the event should be monitored. Possible modifiers are:\n"
-                "                   u - monitor user space events only\n"
-                "                   k - monitor kernel space events only\n"
-                "    --no-inherit\n"
-                "                 Don't stat created child threads/processes.\n"
-                "    -p pid1,pid2,...\n"
-                "                 Stat events on existing processes. Mutually exclusive with -a.\n"
-                "    -t tid1,tid2,...\n"
-                "                 Stat events on existing threads. Mutually exclusive with -a.\n"
-                "    --verbose    Show result in verbose mode.\n"),
+                // clang-format off
+"Usage: simpleperf stat [options] [command [command-args]]\n"
+"       Gather performance counter information of running [command].\n"
+"       And -a/-p/-t option can be used to change target of counter information.\n"
+"-a           Collect system-wide information.\n"
+"--cpu cpu_item1,cpu_item2,...\n"
+"                 Collect information only on the selected cpus. cpu_item can\n"
+"                 be a cpu number like 1, or a cpu range like 0-3.\n"
+"--csv            Write report in comma separate form.\n"
+"--duration time_in_sec  Monitor for time_in_sec seconds instead of running\n"
+"                        [command]. Here time_in_sec may be any positive\n"
+"                        floating point number.\n"
+"-e event1[:modifier1],event2[:modifier2],...\n"
+"                 Select the event list to count. Use `simpleperf list` to find\n"
+"                 all possible event names. Modifiers can be added to define\n"
+"                 how the event should be monitored. Possible modifiers are:\n"
+"                   u - monitor user space events only\n"
+"                   k - monitor kernel space events only\n"
+"--group event1[:modifier],event2[:modifier2],...\n"
+"             Similar to -e option. But events specified in the same --group\n"
+"             option are monitored as a group, and scheduled in and out at the\n"
+"             same time.\n"
+"--no-inherit     Don't stat created child threads/processes.\n"
+"-o output_filename  Write report to output_filename instead of standard output.\n"
+"-p pid1,pid2,... Stat events on existing processes. Mutually exclusive with -a.\n"
+"-t tid1,tid2,... Stat events on existing threads. Mutually exclusive with -a.\n"
+"--verbose        Show result in verbose mode.\n"
+                // clang-format on
+                ),
         verbose_mode_(false),
         system_wide_collection_(false),
-        child_inherit_(true) {
-    signaled = false;
-    scoped_signal_handler_.reset(
-        new ScopedSignalHandler({SIGCHLD, SIGINT, SIGTERM}, signal_handler));
+        child_inherit_(true),
+        duration_in_sec_(0),
+        event_selection_set_(true),
+        csv_(false) {
+    // Die if parent exits.
+    prctl(PR_SET_PDEATHSIG, SIGHUP, 0, 0, 0);
   }
 
   bool Run(const std::vector<std::string>& args);
 
  private:
-  bool ParseOptions(const std::vector<std::string>& args, std::vector<std::string>* non_option_args);
-  bool AddMeasuredEventType(const std::string& event_type_name);
+  bool ParseOptions(const std::vector<std::string>& args,
+                    std::vector<std::string>* non_option_args);
   bool AddDefaultMeasuredEventTypes();
-  bool SetEventSelection();
-  bool ShowCounters(const std::vector<CountersInfo>& counters, double duration_in_sec);
+  void SetEventSelectionFlags();
+  bool ShowCounters(const std::vector<CountersInfo>& counters,
+                    double duration_in_sec);
 
   bool verbose_mode_;
   bool system_wide_collection_;
   bool child_inherit_;
-  std::vector<pid_t> monitored_threads_;
+  double duration_in_sec_;
   std::vector<int> cpus_;
-  std::vector<EventTypeAndModifier> measured_event_types_;
   EventSelectionSet event_selection_set_;
-
-  std::unique_ptr<ScopedSignalHandler> scoped_signal_handler_;
+  std::string output_filename_;
+  bool csv_;
 };
 
 bool StatCommand::Run(const std::vector<std::string>& args) {
@@ -110,14 +328,12 @@
   if (!ParseOptions(args, &workload_args)) {
     return false;
   }
-  if (measured_event_types_.empty()) {
+  if (event_selection_set_.empty()) {
     if (!AddDefaultMeasuredEventTypes()) {
       return false;
     }
   }
-  if (!SetEventSelection()) {
-    return false;
-  }
+  SetEventSelectionFlags();
 
   // 2. Create workload.
   std::unique_ptr<Workload> workload;
@@ -127,44 +343,64 @@
       return false;
     }
   }
-  if (!system_wide_collection_ && monitored_threads_.empty()) {
+  if (system_wide_collection_) {
+    event_selection_set_.AddMonitoredThreads({-1});
+  } else if (!event_selection_set_.HasMonitoredTarget()) {
     if (workload != nullptr) {
-      monitored_threads_.push_back(workload->GetPid());
+      event_selection_set_.AddMonitoredProcesses({workload->GetPid()});
       event_selection_set_.SetEnableOnExec(true);
     } else {
-      LOG(ERROR) << "No threads to monitor. Try `simpleperf help stat` for help\n";
+      LOG(ERROR)
+          << "No threads to monitor. Try `simpleperf help stat` for help\n";
       return false;
     }
   }
 
   // 3. Open perf_event_files.
-  if (system_wide_collection_) {
-    if (!event_selection_set_.OpenEventFilesForCpus(cpus_)) {
+  if (!system_wide_collection_ && cpus_.empty()) {
+    cpus_.push_back(-1);  // Monitor on all cpus.
+  }
+  if (!event_selection_set_.OpenEventFiles(cpus_)) {
+    return false;
+  }
+
+  // 4. Create IOEventLoop and add signal/periodic Events.
+  IOEventLoop loop;
+  if (system_wide_collection_ || (!cpus_.empty() && cpus_[0] != -1)) {
+    if (!event_selection_set_.HandleCpuHotplugEvents(loop, cpus_)) {
       return false;
     }
-  } else {
-    if (!event_selection_set_.OpenEventFilesForThreadsOnCpus(monitored_threads_, cpus_)) {
+  }
+  if (!loop.AddSignalEvents({SIGCHLD, SIGINT, SIGTERM},
+                            [&]() { return loop.ExitLoop(); })) {
+    return false;
+  }
+  if (duration_in_sec_ != 0) {
+    if (!loop.AddPeriodicEvent(SecondToTimeval(duration_in_sec_),
+                               [&]() { return loop.ExitLoop(); })) {
       return false;
     }
   }
 
-  // 4. Count events while workload running.
+  // 5. Count events while workload running.
   auto start_time = std::chrono::steady_clock::now();
   if (workload != nullptr && !workload->Start()) {
     return false;
   }
-  while (!signaled) {
-    sleep(1);
+  if (!loop.RunLoop()) {
+    return false;
   }
   auto end_time = std::chrono::steady_clock::now();
 
-  // 5. Read and print counters.
+  // 6. Read and print counters.
   std::vector<CountersInfo> counters;
   if (!event_selection_set_.ReadCounters(&counters)) {
     return false;
   }
   double duration_in_sec =
-      std::chrono::duration_cast<std::chrono::duration<double>>(end_time - start_time).count();
+      std::chrono::duration_cast<std::chrono::duration<double>>(end_time -
+                                                                start_time)
+          .count();
   if (!ShowCounters(counters, duration_in_sec)) {
     return false;
   }
@@ -183,32 +419,62 @@
         return false;
       }
       cpus_ = GetCpusFromString(args[i]);
+    } else if (args[i] == "--csv") {
+      csv_ = true;
+    } else if (args[i] == "--duration") {
+      if (!NextArgumentOrError(args, &i)) {
+        return false;
+      }
+      errno = 0;
+      char* endptr;
+      duration_in_sec_ = strtod(args[i].c_str(), &endptr);
+      if (duration_in_sec_ <= 0 || *endptr != '\0' || errno == ERANGE) {
+        LOG(ERROR) << "Invalid duration: " << args[i].c_str();
+        return false;
+      }
     } else if (args[i] == "-e") {
       if (!NextArgumentOrError(args, &i)) {
         return false;
       }
       std::vector<std::string> event_types = android::base::Split(args[i], ",");
       for (auto& event_type : event_types) {
-        if (!AddMeasuredEventType(event_type)) {
+        if (!event_selection_set_.AddEventType(event_type)) {
           return false;
         }
       }
+    } else if (args[i] == "--group") {
+      if (!NextArgumentOrError(args, &i)) {
+        return false;
+      }
+      std::vector<std::string> event_types = android::base::Split(args[i], ",");
+      if (!event_selection_set_.AddEventGroup(event_types)) {
+        return false;
+      }
     } else if (args[i] == "--no-inherit") {
       child_inherit_ = false;
+    } else if (args[i] == "-o") {
+      if (!NextArgumentOrError(args, &i)) {
+        return false;
+      }
+      output_filename_ = args[i];
     } else if (args[i] == "-p") {
       if (!NextArgumentOrError(args, &i)) {
         return false;
       }
-      if (!GetValidThreadsFromProcessString(args[i], &tid_set)) {
+      std::set<pid_t> pids;
+      if (!GetValidThreadsFromThreadString(args[i], &pids)) {
         return false;
       }
+      event_selection_set_.AddMonitoredProcesses(pids);
     } else if (args[i] == "-t") {
       if (!NextArgumentOrError(args, &i)) {
         return false;
       }
-      if (!GetValidThreadsFromThreadString(args[i], &tid_set)) {
+      std::set<pid_t> tids;
+      if (!GetValidThreadsFromThreadString(args[i], &tids)) {
         return false;
       }
+      event_selection_set_.AddMonitoredThreads(tids);
     } else if (args[i] == "--verbose") {
       verbose_mode_ = true;
     } else {
@@ -217,153 +483,88 @@
     }
   }
 
-  monitored_threads_.insert(monitored_threads_.end(), tid_set.begin(), tid_set.end());
-  if (system_wide_collection_ && !monitored_threads_.empty()) {
-    LOG(ERROR) << "Stat system wide and existing processes/threads can't be used at the same time.";
+  if (system_wide_collection_ && event_selection_set_.HasMonitoredTarget()) {
+    LOG(ERROR) << "Stat system wide and existing processes/threads can't be "
+                  "used at the same time.";
+    return false;
+  }
+  if (system_wide_collection_ && !IsRoot()) {
+    LOG(ERROR) << "System wide profiling needs root privilege.";
     return false;
   }
 
-  if (non_option_args != nullptr) {
-    non_option_args->clear();
-    for (; i < args.size(); ++i) {
-      non_option_args->push_back(args[i]);
-    }
+  non_option_args->clear();
+  for (; i < args.size(); ++i) {
+    non_option_args->push_back(args[i]);
   }
   return true;
 }
 
-bool StatCommand::AddMeasuredEventType(const std::string& event_type_name) {
-  std::unique_ptr<EventTypeAndModifier> event_type_modifier = ParseEventType(event_type_name);
-  if (event_type_modifier == nullptr) {
-    return false;
-  }
-  measured_event_types_.push_back(*event_type_modifier);
-  return true;
-}
-
 bool StatCommand::AddDefaultMeasuredEventTypes() {
   for (auto& name : default_measured_event_types) {
-    // It is not an error when some event types in the default list are not supported by the kernel.
+    // It is not an error when some event types in the default list are not
+    // supported by the kernel.
     const EventType* type = FindEventTypeByName(name);
-    if (type != nullptr && IsEventAttrSupportedByKernel(CreateDefaultPerfEventAttr(*type))) {
-      AddMeasuredEventType(name);
+    if (type != nullptr &&
+        IsEventAttrSupportedByKernel(CreateDefaultPerfEventAttr(*type))) {
+      if (!event_selection_set_.AddEventType(name)) {
+        return false;
+      }
     }
   }
-  if (measured_event_types_.empty()) {
+  if (event_selection_set_.empty()) {
     LOG(ERROR) << "Failed to add any supported default measured types";
     return false;
   }
   return true;
 }
 
-bool StatCommand::SetEventSelection() {
-  for (auto& event_type : measured_event_types_) {
-    if (!event_selection_set_.AddEventType(event_type)) {
+void StatCommand::SetEventSelectionFlags() {
+  event_selection_set_.SetInherit(child_inherit_);
+}
+
+bool StatCommand::ShowCounters(const std::vector<CountersInfo>& counters,
+                               double duration_in_sec) {
+  std::unique_ptr<FILE, decltype(&fclose)> fp_holder(nullptr, fclose);
+  FILE* fp = stdout;
+  if (!output_filename_.empty()) {
+    fp_holder.reset(fopen(output_filename_.c_str(), "w"));
+    if (fp_holder == nullptr) {
+      PLOG(ERROR) << "failed to open " << output_filename_;
       return false;
     }
+    fp = fp_holder.get();
   }
-  event_selection_set_.SetInherit(child_inherit_);
-  return true;
-}
-
-static std::string ReadableCountValue(uint64_t count,
-                                      const EventTypeAndModifier& event_type_modifier) {
-  if (event_type_modifier.event_type.name == "cpu-clock" ||
-      event_type_modifier.event_type.name == "task-clock") {
-    double value = count / 1e6;
-    return android::base::StringPrintf("%lf(ms)", value);
+  if (csv_) {
+    fprintf(fp, "Performance counter statistics,\n");
   } else {
-    std::string s = android::base::StringPrintf("%" PRIu64, count);
-    for (size_t i = s.size() - 1, j = 1; i > 0; --i, ++j) {
-      if (j == 3) {
-        s.insert(s.begin() + i, ',');
-        j = 0;
-      }
-    }
-    return s;
+    fprintf(fp, "Performance counter statistics:\n\n");
   }
-}
-
-struct CounterSummary {
-  const EventTypeAndModifier* event_type;
-  uint64_t count;
-  double scale;
-  std::string readable_count_str;
-  std::string comment;
-};
-
-static std::string GetCommentForSummary(const CounterSummary& summary,
-                                        const std::vector<CounterSummary>& summaries,
-                                        double duration_in_sec) {
-  const std::string& type_name = summary.event_type->event_type.name;
-  const std::string& modifier = summary.event_type->modifier;
-  if (type_name == "task-clock") {
-    double run_sec = summary.count / 1e9;
-    double cpu_usage = run_sec / duration_in_sec;
-    return android::base::StringPrintf("%lf%% cpu usage", cpu_usage * 100);
-  }
-  if (type_name == "cpu-clock") {
-    return "";
-  }
-  if (type_name == "cpu-cycles") {
-    double hz = summary.count / duration_in_sec;
-    return android::base::StringPrintf("%lf GHz", hz / 1e9);
-  }
-  if (type_name == "instructions" && summary.count != 0) {
-    for (auto& t : summaries) {
-      if (t.event_type->event_type.name == "cpu-cycles" && t.event_type->modifier == modifier) {
-        double cycles_per_instruction = t.count * 1.0 / summary.count;
-        return android::base::StringPrintf("%lf cycles per instruction", cycles_per_instruction);
-      }
-    }
-  }
-  if (android::base::EndsWith(type_name, "-misses")) {
-    std::string s;
-    if (type_name == "cache-misses") {
-      s = "cache-references";
-    } else if (type_name == "branch-misses") {
-      s = "branch-instructions";
-    } else {
-      s = type_name.substr(0, type_name.size() - strlen("-misses")) + "s";
-    }
-    for (auto& t : summaries) {
-      if (t.event_type->event_type.name == s && t.event_type->modifier == modifier && t.count != 0) {
-        double miss_rate = summary.count * 1.0 / t.count;
-        return android::base::StringPrintf("%lf%% miss rate", miss_rate * 100);
-      }
-    }
-  }
-  double rate = summary.count / duration_in_sec;
-  if (rate > 1e9) {
-    return android::base::StringPrintf("%.3lf G/sec", rate / 1e9);
-  }
-  if (rate > 1e6) {
-    return android::base::StringPrintf("%.3lf M/sec", rate / 1e6);
-  }
-  if (rate > 1e3) {
-    return android::base::StringPrintf("%.3lf K/sec", rate / 1e3);
-  }
-  return android::base::StringPrintf("%.3lf /sec", rate);
-}
-
-bool StatCommand::ShowCounters(const std::vector<CountersInfo>& counters, double duration_in_sec) {
-  printf("Performance counter statistics:\n\n");
 
   if (verbose_mode_) {
     for (auto& counters_info : counters) {
-      const EventTypeAndModifier* event_type = counters_info.event_type;
+      const EventTypeAndModifier& event_type =
+          counters_info.selection->event_type_modifier;
       for (auto& counter_info : counters_info.counters) {
-        printf("%s(tid %d, cpu %d): count %s, time_enabled %" PRIu64 ", time running %" PRIu64
-               ", id %" PRIu64 "\n",
-               event_type->name.c_str(), counter_info.tid, counter_info.cpu,
-               ReadableCountValue(counter_info.counter.value, *event_type).c_str(),
-               counter_info.counter.time_enabled, counter_info.counter.time_running,
-               counter_info.counter.id);
+        if (csv_) {
+          fprintf(fp, "%s,tid,%d,cpu,%d,count,%" PRIu64 ",time_enabled,%" PRIu64
+                      ",time running,%" PRIu64 ",id,%" PRIu64 ",\n",
+                  event_type.name.c_str(), counter_info.tid, counter_info.cpu,
+                  counter_info.counter.value, counter_info.counter.time_enabled,
+                  counter_info.counter.time_running, counter_info.counter.id);
+        } else {
+          fprintf(fp,
+                  "%s(tid %d, cpu %d): count %" PRIu64 ", time_enabled %" PRIu64
+                  ", time running %" PRIu64 ", id %" PRIu64 "\n",
+                  event_type.name.c_str(), counter_info.tid, counter_info.cpu,
+                  counter_info.counter.value, counter_info.counter.time_enabled,
+                  counter_info.counter.time_running, counter_info.counter.id);
+        }
       }
     }
   }
 
-  std::vector<CounterSummary> summaries;
+  CounterSummaries summaries(csv_);
   for (auto& counters_info : counters) {
     uint64_t value_sum = 0;
     uint64_t time_enabled_sum = 0;
@@ -374,47 +575,28 @@
       time_running_sum += counter_info.counter.time_running;
     }
     double scale = 1.0;
-    uint64_t scaled_count = value_sum;
-    if (time_running_sum < time_enabled_sum) {
-      if (time_running_sum == 0) {
-        scaled_count = 0;
-      } else {
-        scale = static_cast<double>(time_enabled_sum) / time_running_sum;
-        scaled_count = static_cast<uint64_t>(scale * value_sum);
-      }
+    if (time_running_sum < time_enabled_sum && time_running_sum != 0) {
+      scale = static_cast<double>(time_enabled_sum) / time_running_sum;
     }
-    CounterSummary summary;
-    summary.event_type = counters_info.event_type;
-    summary.count = scaled_count;
-    summary.scale = scale;
-    summary.readable_count_str = ReadableCountValue(summary.count, *summary.event_type);
-    summaries.push_back(summary);
+    summaries.AddSummary(CounterSummary(
+        counters_info.selection->event_type_modifier.event_type.name,
+        counters_info.selection->event_type_modifier.modifier,
+        counters_info.selection->group_id, value_sum, scale, false, csv_));
   }
+  summaries.AutoGenerateSummaries();
+  summaries.GenerateComments(duration_in_sec);
+  summaries.Show(fp);
 
-  for (auto& summary : summaries) {
-    summary.comment = GetCommentForSummary(summary, summaries, duration_in_sec);
-  }
-
-  size_t count_column_width = 0;
-  size_t name_column_width = 0;
-  size_t comment_column_width = 0;
-  for (auto& summary : summaries) {
-    count_column_width = std::max(count_column_width, summary.readable_count_str.size());
-    name_column_width = std::max(name_column_width, summary.event_type->name.size());
-    comment_column_width = std::max(comment_column_width, summary.comment.size());
-  }
-
-  for (auto& summary : summaries) {
-    printf("  %*s  %-*s   # %-*s   (%.0lf%%)\n", static_cast<int>(count_column_width),
-           summary.readable_count_str.c_str(), static_cast<int>(name_column_width),
-           summary.event_type->name.c_str(), static_cast<int>(comment_column_width),
-           summary.comment.c_str(), 1.0 / summary.scale * 100);
-  }
-
-  printf("\nTotal test time: %lf seconds.\n", duration_in_sec);
+  if (csv_)
+    fprintf(fp, "Total test time,%lf,seconds,\n", duration_in_sec);
+  else
+    fprintf(fp, "\nTotal test time: %lf seconds.\n", duration_in_sec);
   return true;
 }
 
+}  // namespace
+
 void RegisterStatCommand() {
-  RegisterCommand("stat", [] { return std::unique_ptr<Command>(new StatCommand); });
+  RegisterCommand("stat",
+                  [] { return std::unique_ptr<Command>(new StatCommand); });
 }
diff --git a/simpleperf/cmd_stat_test.cpp b/simpleperf/cmd_stat_test.cpp
index 45ed3dc..5748b7a 100644
--- a/simpleperf/cmd_stat_test.cpp
+++ b/simpleperf/cmd_stat_test.cpp
@@ -16,7 +16,9 @@
 
 #include <gtest/gtest.h>
 
+#include <android-base/file.h>
 #include <android-base/stringprintf.h>
+#include <android-base/test_utils.h>
 
 #include "command.h"
 #include "get_test_data.h"
@@ -26,18 +28,14 @@
   return CreateCommandInstance("stat");
 }
 
-TEST(stat_cmd, no_options) {
-  ASSERT_TRUE(StatCmd()->Run({"sleep", "1"}));
-}
+TEST(stat_cmd, no_options) { ASSERT_TRUE(StatCmd()->Run({"sleep", "1"})); }
 
 TEST(stat_cmd, event_option) {
   ASSERT_TRUE(StatCmd()->Run({"-e", "cpu-clock,task-clock", "sleep", "1"}));
 }
 
 TEST(stat_cmd, system_wide_option) {
-  if (IsRoot()) {
-    ASSERT_TRUE(StatCmd()->Run({"-a", "sleep", "1"}));
-  }
+  TEST_IN_ROOT(ASSERT_TRUE(StatCmd()->Run({"-a", "sleep", "1"})));
 }
 
 TEST(stat_cmd, verbose_option) {
@@ -45,16 +43,17 @@
 }
 
 TEST(stat_cmd, tracepoint_event) {
-  if (IsRoot()) {
-    ASSERT_TRUE(StatCmd()->Run({"-a", "-e", "sched:sched_switch", "sleep", "1"}));
-  }
+  TEST_IN_ROOT(ASSERT_TRUE(
+      StatCmd()->Run({"-a", "-e", "sched:sched_switch", "sleep", "1"})));
 }
 
 TEST(stat_cmd, event_modifier) {
-  ASSERT_TRUE(StatCmd()->Run({"-e", "cpu-cycles:u,cpu-cycles:k", "sleep", "1"}));
+  ASSERT_TRUE(
+      StatCmd()->Run({"-e", "cpu-cycles:u,cpu-cycles:k", "sleep", "1"}));
 }
 
-void CreateProcesses(size_t count, std::vector<std::unique_ptr<Workload>>* workloads) {
+void CreateProcesses(size_t count,
+                     std::vector<std::unique_ptr<Workload>>* workloads) {
   workloads->clear();
   for (size_t i = 0; i < count; ++i) {
     // Create a workload runs longer than profiling time.
@@ -68,8 +67,8 @@
 TEST(stat_cmd, existing_processes) {
   std::vector<std::unique_ptr<Workload>> workloads;
   CreateProcesses(2, &workloads);
-  std::string pid_list =
-      android::base::StringPrintf("%d,%d", workloads[0]->GetPid(), workloads[1]->GetPid());
+  std::string pid_list = android::base::StringPrintf(
+      "%d,%d", workloads[0]->GetPid(), workloads[1]->GetPid());
   ASSERT_TRUE(StatCmd()->Run({"-p", pid_list, "sleep", "1"}));
 }
 
@@ -77,18 +76,47 @@
   std::vector<std::unique_ptr<Workload>> workloads;
   CreateProcesses(2, &workloads);
   // Process id can be used as thread id in linux.
-  std::string tid_list =
-      android::base::StringPrintf("%d,%d", workloads[0]->GetPid(), workloads[1]->GetPid());
+  std::string tid_list = android::base::StringPrintf(
+      "%d,%d", workloads[0]->GetPid(), workloads[1]->GetPid());
   ASSERT_TRUE(StatCmd()->Run({"-t", tid_list, "sleep", "1"}));
 }
 
-TEST(stat_cmd, no_monitored_threads) {
-  ASSERT_FALSE(StatCmd()->Run({""}));
+TEST(stat_cmd, no_monitored_threads) { ASSERT_FALSE(StatCmd()->Run({""})); }
+
+TEST(stat_cmd, group_option) {
+  ASSERT_TRUE(
+      StatCmd()->Run({"--group", "cpu-cycles,cpu-clock", "sleep", "1"}));
+  ASSERT_TRUE(StatCmd()->Run({"--group", "cpu-cycles,instructions", "--group",
+                              "cpu-cycles:u,instructions:u", "--group",
+                              "cpu-cycles:k,instructions:k", "sleep", "1"}));
 }
 
-TEST(stat_cmd, cpu_option) {
-  ASSERT_TRUE(StatCmd()->Run({"--cpu", "0", "sleep", "1"}));
-  if (IsRoot()) {
-    ASSERT_TRUE(StatCmd()->Run({"--cpu", "0", "-a", "sleep", "1"}));
+TEST(stat_cmd, auto_generated_summary) {
+  TemporaryFile tmp_file;
+  ASSERT_TRUE(StatCmd()->Run({"--group", "instructions:u,instructions:k", "-o",
+                              tmp_file.path, "sleep", "1"}));
+  std::string s;
+  ASSERT_TRUE(android::base::ReadFileToString(tmp_file.path, &s));
+  size_t pos = s.find("instructions:u");
+  ASSERT_NE(s.npos, pos);
+  pos = s.find("instructions:k", pos);
+  ASSERT_NE(s.npos, pos);
+  pos += strlen("instructions:k");
+  // Check if the summary of instructions is generated.
+  ASSERT_NE(s.npos, s.find("instructions", pos));
+}
+
+TEST(stat_cmd, duration_option) {
+  ASSERT_TRUE(
+      StatCmd()->Run({"--duration", "1.2", "-p", std::to_string(getpid())}));
+  ASSERT_TRUE(StatCmd()->Run({"--duration", "1", "sleep", "2"}));
+}
+
+TEST(stat_cmd, no_modifier_for_clock_events) {
+  for (const std::string& e : {"cpu-clock", "task-clock"}) {
+    for (const std::string& m : {"u", "k"}) {
+      ASSERT_FALSE(StatCmd()->Run({"-e", e + ":" + m, "sleep", "0.1"}))
+          << "event " << e << ":" << m;
+    }
   }
 }
diff --git a/simpleperf/command.cpp b/simpleperf/command.cpp
index 3416653..f5d4e8b 100644
--- a/simpleperf/command.cpp
+++ b/simpleperf/command.cpp
@@ -48,7 +48,7 @@
 }
 
 void RegisterCommand(const std::string& cmd_name,
-                     std::function<std::unique_ptr<Command>(void)> callback) {
+                     const std::function<std::unique_ptr<Command>(void)>& callback) {
   CommandMap().insert(std::make_pair(cmd_name, callback));
 }
 
@@ -72,8 +72,10 @@
 extern void RegisterDumpRecordCommand();
 extern void RegisterHelpCommand();
 extern void RegisterListCommand();
+extern void RegisterKmemCommand();
 extern void RegisterRecordCommand();
 extern void RegisterReportCommand();
+extern void RegisterReportSampleCommand();
 extern void RegisterStatCommand();
 
 class CommandRegister {
@@ -81,7 +83,9 @@
   CommandRegister() {
     RegisterDumpRecordCommand();
     RegisterHelpCommand();
+    RegisterKmemCommand();
     RegisterReportCommand();
+    RegisterReportSampleCommand();
 #if defined(__linux__)
     RegisterListCommand();
     RegisterRecordCommand();
diff --git a/simpleperf/command.h b/simpleperf/command.h
index e8fef45..2311f33 100644
--- a/simpleperf/command.h
+++ b/simpleperf/command.h
@@ -61,7 +61,7 @@
 };
 
 void RegisterCommand(const std::string& cmd_name,
-                     std::function<std::unique_ptr<Command>(void)> callback);
+                     const std::function<std::unique_ptr<Command>(void)>& callback);
 void UnRegisterCommand(const std::string& cmd_name);
 std::unique_ptr<Command> CreateCommandInstance(const std::string& cmd_name);
 const std::vector<std::string> GetAllCommandNames();
diff --git a/simpleperf/cpu_hotplug_test.cpp b/simpleperf/cpu_hotplug_test.cpp
index 61cbc80..166ec3a 100644
--- a/simpleperf/cpu_hotplug_test.cpp
+++ b/simpleperf/cpu_hotplug_test.cpp
@@ -17,6 +17,7 @@
 #include <gtest/gtest.h>
 
 #include <sys/stat.h>
+#include <sys/syscall.h>
 #include <unistd.h>
 #if defined(__BIONIC__)
 #include <sys/system_properties.h>
@@ -24,7 +25,6 @@
 
 #include <atomic>
 #include <chrono>
-#include <condition_variable>
 #include <thread>
 #include <unordered_map>
 
@@ -32,14 +32,13 @@
 #include <android-base/logging.h>
 #include <android-base/stringprintf.h>
 
-#include "command.h"
 #include "event_attr.h"
 #include "event_fd.h"
 #include "event_type.h"
+#include "utils.h"
 
-static std::unique_ptr<Command> RecordCmd() {
-  return CreateCommandInstance("record");
-}
+static auto test_duration_for_long_tests = std::chrono::seconds(120);
+static auto cpu_hotplug_interval = std::chrono::microseconds(1000);
 
 #if defined(__BIONIC__)
 class ScopedMpdecisionKiller {
@@ -71,14 +70,14 @@
     int ret = __system_property_set("ctl.stop", "mpdecision");
     CHECK_EQ(0, ret);
     // Need to wait until mpdecision is actually stopped.
-    usleep(500000);
+    std::this_thread::sleep_for(std::chrono::milliseconds(500));
     CHECK(!IsMpdecisionRunning());
   }
 
   void EnableMpdecision() {
     int ret = __system_property_set("ctl.start", "mpdecision");
     CHECK_EQ(0, ret);
-    usleep(500000);
+    std::this_thread::sleep_for(std::chrono::milliseconds(500));
     CHECK(IsMpdecisionRunning());
   }
 
@@ -92,23 +91,61 @@
 };
 #endif
 
-static bool IsCpuOnline(int cpu) {
+static bool IsCpuOnline(int cpu, bool* has_error) {
   std::string filename = android::base::StringPrintf("/sys/devices/system/cpu/cpu%d/online", cpu);
   std::string content;
-  CHECK(android::base::ReadFileToString(filename, &content)) << "failed to read file " << filename;
+  bool ret = android::base::ReadFileToString(filename, &content);
+  if (!ret) {
+    PLOG(ERROR) << "failed to read file " << filename;
+    *has_error = true;
+    return false;
+  }
+  *has_error = false;
   return (content.find('1') != std::string::npos);
 }
 
-static void SetCpuOnline(int cpu, bool online) {
-  if (IsCpuOnline(cpu) == online) {
-    return;
+static bool SetCpuOnline(int cpu, bool online) {
+  bool has_error;
+  bool ret = IsCpuOnline(cpu, &has_error);
+  if (has_error) {
+    return false;
+  }
+  if (ret == online) {
+    return true;
   }
   std::string filename = android::base::StringPrintf("/sys/devices/system/cpu/cpu%d/online", cpu);
   std::string content = online ? "1" : "0";
-  CHECK(android::base::WriteStringToFile(content, filename)) << "Write " << content << " to "
-                                                             << filename << " failed";
-  CHECK_EQ(online, IsCpuOnline(cpu)) << "set cpu " << cpu << (online ? " online" : " offline")
-                                     << " failed";
+  ret = android::base::WriteStringToFile(content, filename);
+  if (!ret) {
+    ret = IsCpuOnline(cpu, &has_error);
+    if (has_error) {
+      return false;
+    }
+    if (online == ret) {
+      return true;
+    }
+    PLOG(ERROR) << "failed to write " << content << " to " << filename;
+    return false;
+  }
+  // Kernel needs time to offline/online cpus, so use a loop to wait here.
+  size_t retry_count = 0;
+  while (true) {
+    ret = IsCpuOnline(cpu, &has_error);
+    if (has_error) {
+      return false;
+    }
+    if (ret == online) {
+      break;
+    }
+    LOG(ERROR) << "reading cpu retry count = " << retry_count << ", requested = " << online
+        << ", real = " << ret;
+    if (++retry_count == 10000) {
+      LOG(ERROR) << "setting cpu " << cpu << (online ? " online" : " offline") << " seems not to take effect";
+      return false;
+    }
+    std::this_thread::sleep_for(std::chrono::milliseconds(1));
+  }
+  return true;
 }
 
 static int GetCpuCount() {
@@ -119,7 +156,12 @@
  public:
   CpuOnlineRestorer() {
     for (int cpu = 1; cpu < GetCpuCount(); ++cpu) {
-      online_map_[cpu] = IsCpuOnline(cpu);
+      bool has_error;
+      bool ret = IsCpuOnline(cpu, &has_error);
+      if (has_error) {
+        continue;
+      }
+      online_map_[cpu] = ret;
     }
   }
 
@@ -133,6 +175,26 @@
   std::unordered_map<int, bool> online_map_;
 };
 
+bool FindAHotpluggableCpu(int* hotpluggable_cpu) {
+  if (!IsRoot()) {
+    GTEST_LOG_(INFO) << "This test needs root privilege to hotplug cpu.";
+    return false;
+  }
+  for (int cpu = 1; cpu < GetCpuCount(); ++cpu) {
+    bool has_error;
+    bool online = IsCpuOnline(cpu, &has_error);
+    if (has_error) {
+      continue;
+    }
+    if (SetCpuOnline(cpu, !online)) {
+      *hotpluggable_cpu = cpu;
+      return true;
+    }
+  }
+  GTEST_LOG_(INFO) << "There is no hotpluggable cpu.";
+  return false;
+}
+
 struct CpuToggleThreadArg {
   int toggle_cpu;
   std::atomic<bool> end_flag;
@@ -140,93 +202,201 @@
 
 static void CpuToggleThread(CpuToggleThreadArg* arg) {
   while (!arg->end_flag) {
-    SetCpuOnline(arg->toggle_cpu, true);
-    sleep(1);
-    SetCpuOnline(arg->toggle_cpu, false);
-    sleep(1);
+    CHECK(SetCpuOnline(arg->toggle_cpu, true));
+    std::this_thread::sleep_for(cpu_hotplug_interval);
+    CHECK(SetCpuOnline(arg->toggle_cpu, false));
+    std::this_thread::sleep_for(cpu_hotplug_interval);
   }
 }
 
-static bool RecordInChildProcess(int record_cpu, int record_duration_in_second) {
-  pid_t pid = fork();
-  CHECK(pid != -1);
-  if (pid == 0) {
-    std::string cpu_str = android::base::StringPrintf("%d", record_cpu);
-    std::string record_duration_str = android::base::StringPrintf("%d", record_duration_in_second);
-    bool ret = RecordCmd()->Run({"-a", "--cpu", cpu_str, "sleep", record_duration_str});
-    extern bool system_wide_perf_event_open_failed;
-    // It is not an error if perf_event_open failed because of cpu-hotplug.
-    if (!ret && !system_wide_perf_event_open_failed) {
-      exit(1);
-    }
-    exit(0);
-  }
-  int timeout = record_duration_in_second + 10;
-  auto end_time = std::chrono::steady_clock::now() + std::chrono::seconds(timeout);
-  bool child_success = false;
-  while (std::chrono::steady_clock::now() < end_time) {
-    int exit_state;
-    pid_t ret = waitpid(pid, &exit_state, WNOHANG);
-    if (ret == pid) {
-      if (WIFSIGNALED(exit_state) || (WIFEXITED(exit_state) && WEXITSTATUS(exit_state) != 0)) {
-        child_success = false;
-      } else {
-        child_success = true;
-      }
-      break;
-    } else if (ret == -1) {
-      child_success = false;
-      break;
-    }
-    sleep(1);
-  }
-  return child_success;
-}
-
 // http://b/25193162.
 TEST(cpu_offline, offline_while_recording) {
   ScopedMpdecisionKiller scoped_mpdecision_killer;
   CpuOnlineRestorer cpuonline_restorer;
-
   if (GetCpuCount() == 1) {
     GTEST_LOG_(INFO) << "This test does nothing, because there is only one cpu in the system.";
     return;
   }
-  for (int i = 1; i < GetCpuCount(); ++i) {
-    if (!IsCpuOnline(i)) {
-      SetCpuOnline(i, true);
-    }
+  // Start cpu hotpluger.
+  int test_cpu;
+  if (!FindAHotpluggableCpu(&test_cpu)) {
+    return;
   }
-  // Start cpu hotplugger.
-  int test_cpu = GetCpuCount() - 1;
   CpuToggleThreadArg cpu_toggle_arg;
   cpu_toggle_arg.toggle_cpu = test_cpu;
   cpu_toggle_arg.end_flag = false;
   std::thread cpu_toggle_thread(CpuToggleThread, &cpu_toggle_arg);
 
-  const std::chrono::hours test_duration(10);  // Test for 10 hours.
-  const double RECORD_DURATION_IN_SEC = 2.9;
-  const double SLEEP_DURATION_IN_SEC = 1.3;
+  std::unique_ptr<EventTypeAndModifier> event_type_modifier = ParseEventType("cpu-cycles");
+  ASSERT_TRUE(event_type_modifier != nullptr);
+  perf_event_attr attr = CreateDefaultPerfEventAttr(event_type_modifier->event_type);
+  attr.disabled = 0;
+  attr.enable_on_exec = 0;
 
-  auto end_time = std::chrono::steady_clock::now() + test_duration;
+  auto start_time = std::chrono::steady_clock::now();
+  auto cur_time = start_time;
+  auto end_time = std::chrono::steady_clock::now() + test_duration_for_long_tests;
+  auto report_step = std::chrono::seconds(15);
   size_t iterations = 0;
-  while (std::chrono::steady_clock::now() < end_time) {
+
+  while (cur_time < end_time) {
+    if (cur_time + report_step < std::chrono::steady_clock::now()) {
+      // Report test time.
+      auto diff = std::chrono::duration_cast<std::chrono::seconds>(
+          std::chrono::steady_clock::now() - start_time);
+      GTEST_LOG_(INFO) << "Have Tested " << (diff.count() / 60.0) << " minutes.";
+      cur_time = std::chrono::steady_clock::now();
+    }
+
+    std::unique_ptr<EventFd> event_fd = EventFd::OpenEventFile(attr, -1, test_cpu, nullptr, false);
+    if (event_fd == nullptr) {
+      // Failed to open because the test_cpu is offline.
+      continue;
+    }
     iterations++;
-    GTEST_LOG_(INFO) << "Test for " << iterations << " times.";
-    ASSERT_TRUE(RecordInChildProcess(test_cpu, RECORD_DURATION_IN_SEC));
-    usleep(static_cast<useconds_t>(SLEEP_DURATION_IN_SEC * 1e6));
+    GTEST_LOG_(INFO) << "Test offline while recording for " << iterations << " times.";
   }
   cpu_toggle_arg.end_flag = true;
   cpu_toggle_thread.join();
 }
 
-static std::unique_ptr<EventFd> OpenHardwareEventOnCpu(int cpu) {
-  std::unique_ptr<EventTypeAndModifier> event_type_modifier = ParseEventType("cpu-cycles");
-  if (event_type_modifier == nullptr) {
-    return nullptr;
+// http://b/25193162.
+TEST(cpu_offline, offline_while_ioctl_enable) {
+  ScopedMpdecisionKiller scoped_mpdecision_killer;
+  CpuOnlineRestorer cpuonline_restorer;
+  if (GetCpuCount() == 1) {
+    GTEST_LOG_(INFO) << "This test does nothing, because there is only one cpu in the system.";
+    return;
   }
+  // Start cpu hotpluger.
+  int test_cpu;
+  if (!FindAHotpluggableCpu(&test_cpu)) {
+    return;
+  }
+  CpuToggleThreadArg cpu_toggle_arg;
+  cpu_toggle_arg.toggle_cpu = test_cpu;
+  cpu_toggle_arg.end_flag = false;
+  std::thread cpu_toggle_thread(CpuToggleThread, &cpu_toggle_arg);
+
+  std::unique_ptr<EventTypeAndModifier> event_type_modifier = ParseEventType("cpu-cycles");
+  ASSERT_TRUE(event_type_modifier != nullptr);
   perf_event_attr attr = CreateDefaultPerfEventAttr(event_type_modifier->event_type);
-  return EventFd::OpenEventFile(attr, getpid(), cpu);
+  attr.disabled = 1;
+  attr.enable_on_exec = 0;
+
+  auto start_time = std::chrono::steady_clock::now();
+  auto cur_time = start_time;
+  auto end_time = std::chrono::steady_clock::now() + test_duration_for_long_tests;
+  auto report_step = std::chrono::seconds(15);
+  size_t iterations = 0;
+
+  while (cur_time < end_time) {
+    if (cur_time + report_step < std::chrono::steady_clock::now()) {
+      // Report test time.
+      auto diff = std::chrono::duration_cast<std::chrono::seconds>(
+          std::chrono::steady_clock::now() - start_time);
+      GTEST_LOG_(INFO) << "Have Tested " << (diff.count() / 60.0) << " minutes.";
+      cur_time = std::chrono::steady_clock::now();
+
+    }
+    std::unique_ptr<EventFd> event_fd = EventFd::OpenEventFile(attr, -1, test_cpu, nullptr, false);
+    if (event_fd == nullptr) {
+      // Failed to open because the test_cpu is offline.
+      continue;
+    }
+    // Wait a little for the event to be installed on test_cpu's perf context.
+    std::this_thread::sleep_for(std::chrono::milliseconds(1));
+    ASSERT_TRUE(event_fd->EnableEvent());
+    iterations++;
+    GTEST_LOG_(INFO) << "Test offline while ioctl(PERF_EVENT_IOC_ENABLE) for " << iterations << " times.";
+  }
+  cpu_toggle_arg.end_flag = true;
+  cpu_toggle_thread.join();
+}
+
+struct CpuSpinThreadArg {
+  int spin_cpu;
+  std::atomic<pid_t> tid;
+  std::atomic<bool> end_flag;
+};
+
+static void CpuSpinThread(CpuSpinThreadArg* arg) {
+  arg->tid = syscall(__NR_gettid);
+  while (!arg->end_flag) {
+    cpu_set_t mask;
+    CPU_ZERO(&mask);
+    CPU_SET(arg->spin_cpu, &mask);
+    // If toggle_cpu is offline, setaffinity fails. So call it in a loop to
+    // make sure current thread mostly runs on toggle_cpu.
+    sched_setaffinity(arg->tid, sizeof(mask), &mask);
+  }
+}
+
+// http://b/28086229.
+TEST(cpu_offline, offline_while_user_process_profiling) {
+  ScopedMpdecisionKiller scoped_mpdecision_killer;
+  CpuOnlineRestorer cpuonline_restorer;
+  // Start cpu hotpluger.
+  int test_cpu;
+  if (!FindAHotpluggableCpu(&test_cpu)) {
+    return;
+  }
+  CpuToggleThreadArg cpu_toggle_arg;
+  cpu_toggle_arg.toggle_cpu = test_cpu;
+  cpu_toggle_arg.end_flag = false;
+  std::thread cpu_toggle_thread(CpuToggleThread, &cpu_toggle_arg);
+
+  // Start cpu spinner.
+  CpuSpinThreadArg cpu_spin_arg;
+  cpu_spin_arg.spin_cpu = test_cpu;
+  cpu_spin_arg.tid = 0;
+  cpu_spin_arg.end_flag = false;
+  std::thread cpu_spin_thread(CpuSpinThread, &cpu_spin_arg);
+  while (cpu_spin_arg.tid == 0) {
+    std::this_thread::sleep_for(std::chrono::milliseconds(1));
+  }
+
+  std::unique_ptr<EventTypeAndModifier> event_type_modifier = ParseEventType("cpu-cycles");
+  ASSERT_TRUE(event_type_modifier != nullptr);
+  perf_event_attr attr = CreateDefaultPerfEventAttr(event_type_modifier->event_type);
+  // Enable profiling in perf_event_open system call.
+  attr.disabled = 0;
+  attr.enable_on_exec = 0;
+
+  auto start_time = std::chrono::steady_clock::now();
+  auto cur_time = start_time;
+  auto end_time = start_time + test_duration_for_long_tests;
+  auto report_step = std::chrono::seconds(15);
+  size_t iterations = 0;
+
+  while (cur_time < end_time) {
+    if (cur_time + report_step < std::chrono::steady_clock::now()) {
+      auto diff = std::chrono::duration_cast<std::chrono::seconds>(
+          std::chrono::steady_clock::now() - start_time);
+      GTEST_LOG_(INFO) << "Have Tested " <<  (diff.count() / 60.0) << " minutes.";
+      cur_time = std::chrono::steady_clock::now();
+    }
+    // Test if the cpu pmu is still usable.
+    ASSERT_TRUE(EventFd::OpenEventFile(attr, 0, -1, nullptr, true) != nullptr);
+
+    std::unique_ptr<EventFd> event_fd = EventFd::OpenEventFile(attr, cpu_spin_arg.tid,
+                                                               test_cpu, nullptr, false);
+    if (event_fd == nullptr) {
+      // Failed to open because the test_cpu is offline.
+      continue;
+    }
+    // profile for a while.
+    std::this_thread::sleep_for(std::chrono::milliseconds(1));
+    iterations++;
+    GTEST_LOG_(INFO) << "Test offline while user process profiling for " << iterations << " times.";
+  }
+  cpu_toggle_arg.end_flag = true;
+  cpu_toggle_thread.join();
+  cpu_spin_arg.end_flag = true;
+  cpu_spin_thread.join();
+  // Check if the cpu-cycle event is still available on test_cpu.
+  ASSERT_TRUE(SetCpuOnline(test_cpu, true));
+  ASSERT_TRUE(EventFd::OpenEventFile(attr, -1, test_cpu, nullptr, true) != nullptr);
 }
 
 // http://b/19863147.
@@ -238,22 +408,55 @@
     GTEST_LOG_(INFO) << "This test does nothing, because there is only one cpu in the system.";
     return;
   }
+  int test_cpu;
+  if (!FindAHotpluggableCpu(&test_cpu)) {
+    return;
+  }
+  std::unique_ptr<EventTypeAndModifier> event_type_modifier = ParseEventType("cpu-cycles");
+  perf_event_attr attr = CreateDefaultPerfEventAttr(event_type_modifier->event_type);
+  attr.disabled = 0;
+  attr.enable_on_exec = 0;
 
   const size_t TEST_ITERATION_COUNT = 10u;
   for (size_t i = 0; i < TEST_ITERATION_COUNT; ++i) {
     int record_cpu = 0;
-    int toggle_cpu = GetCpuCount() - 1;
-    SetCpuOnline(toggle_cpu, true);
-    std::unique_ptr<EventFd> event_fd = OpenHardwareEventOnCpu(record_cpu);
+    ASSERT_TRUE(SetCpuOnline(test_cpu, true));
+    std::unique_ptr<EventFd> event_fd = EventFd::OpenEventFile(attr, getpid(), record_cpu, nullptr);
     ASSERT_TRUE(event_fd != nullptr);
-    SetCpuOnline(toggle_cpu, false);
+    ASSERT_TRUE(SetCpuOnline(test_cpu, false));
     event_fd = nullptr;
-    event_fd = OpenHardwareEventOnCpu(record_cpu);
+    event_fd = EventFd::OpenEventFile(attr, getpid(), record_cpu, nullptr);
     ASSERT_TRUE(event_fd != nullptr);
   }
 }
 
 int main(int argc, char** argv) {
+  for (int i = 1; i < argc; ++i) {
+    if (strcmp(argv[i], "--help") == 0) {
+      printf("--long_test_duration <second> Set test duration for long tests. Default is 120s.\n");
+      printf("--cpu_hotplug_interval <microseconds> Set cpu hotplug interval. Default is 1000us.\n");
+    } else if (strcmp(argv[i], "--long_test_duration") == 0) {
+      if (i + 1 < argc) {
+        int second_count = atoi(argv[i+1]);
+        if (second_count <= 0) {
+          fprintf(stderr, "Invalid arg for --long_test_duration.\n");
+          return 1;
+        }
+        test_duration_for_long_tests = std::chrono::seconds(second_count);
+        i++;
+      }
+    } else if (strcmp(argv[i], "--cpu_hotplug_interval") == 0) {
+      if (i + 1 < argc) {
+        int microsecond_count = atoi(argv[i+1]);
+        if (microsecond_count <= 0) {
+          fprintf(stderr, "Invalid arg for --cpu_hotplug_interval\n");
+          return 1;
+        }
+        cpu_hotplug_interval = std::chrono::microseconds(microsecond_count);
+        i++;
+      }
+    }
+  }
   InitLogging(argv, android::base::StderrLogger);
   testing::InitGoogleTest(&argc, argv);
   return RUN_ALL_TESTS();
diff --git a/simpleperf/dso.cpp b/simpleperf/dso.cpp
index 9c33667..bd6c1ba 100644
--- a/simpleperf/dso.cpp
+++ b/simpleperf/dso.cpp
@@ -23,6 +23,7 @@
 #include <limits>
 #include <vector>
 
+#include <android-base/file.h>
 #include <android-base/logging.h>
 
 #include "environment.h"
@@ -36,8 +37,8 @@
     : addr(addr),
       len(len),
       name_(symbol_name_allocator.AllocateString(name)),
-      demangled_name_(nullptr) {
-}
+      demangled_name_(nullptr),
+      has_dumped_(false) {}
 
 const char* Symbol::DemangledName() const {
   if (demangled_name_ == nullptr) {
@@ -54,14 +55,14 @@
 bool Dso::demangle_ = true;
 std::string Dso::symfs_dir_;
 std::string Dso::vmlinux_;
+std::string Dso::kallsyms_;
 std::unordered_map<std::string, BuildId> Dso::build_id_map_;
 size_t Dso::dso_count_;
 
-void Dso::SetDemangle(bool demangle) {
-  demangle_ = demangle;
-}
+void Dso::SetDemangle(bool demangle) { demangle_ = demangle; }
 
-extern "C" char* __cxa_demangle(const char* mangled_name, char* buf, size_t* n, int* status);
+extern "C" char* __cxa_demangle(const char* mangled_name, char* buf, size_t* n,
+                                int* status);
 
 std::string Dso::Demangle(const std::string& name) {
   if (!demangle_) {
@@ -94,10 +95,7 @@
     if (dirname.back() != '/') {
       dirname.push_back('/');
     }
-    std::vector<std::string> files;
-    std::vector<std::string> subdirs;
-    GetEntriesInDir(symfs_dir, &files, &subdirs);
-    if (files.empty() && subdirs.empty()) {
+    if (GetEntriesInDir(symfs_dir).empty()) {
       LOG(ERROR) << "Invalid symfs_dir '" << symfs_dir << "'";
       return false;
     }
@@ -106,67 +104,91 @@
   return true;
 }
 
-void Dso::SetVmlinux(const std::string& vmlinux) {
-  vmlinux_ = vmlinux;
-}
+void Dso::SetVmlinux(const std::string& vmlinux) { vmlinux_ = vmlinux; }
 
-void Dso::SetBuildIds(const std::vector<std::pair<std::string, BuildId>>& build_ids) {
+void Dso::SetBuildIds(
+    const std::vector<std::pair<std::string, BuildId>>& build_ids) {
   std::unordered_map<std::string, BuildId> map;
   for (auto& pair : build_ids) {
-    LOG(DEBUG) << "build_id_map: " << pair.first << ", " << pair.second.ToString();
+    LOG(DEBUG) << "build_id_map: " << pair.first << ", "
+               << pair.second.ToString();
     map.insert(pair);
   }
   build_id_map_ = std::move(map);
 }
 
-BuildId Dso::GetExpectedBuildId(const std::string& filename) {
-  auto it = build_id_map_.find(filename);
+BuildId Dso::GetExpectedBuildId() {
+  auto it = build_id_map_.find(path_);
   if (it != build_id_map_.end()) {
     return it->second;
   }
   return BuildId();
 }
 
-std::unique_ptr<Dso> Dso::CreateDso(DsoType dso_type, const std::string& dso_path) {
-  std::string path = dso_path;
-  if (dso_type == DSO_KERNEL) {
-    path = "[kernel.kallsyms]";
-  }
-  return std::unique_ptr<Dso>(new Dso(dso_type, path));
+std::unique_ptr<Dso> Dso::CreateDso(DsoType dso_type,
+                                    const std::string& dso_path) {
+  static uint64_t id = 0;
+  return std::unique_ptr<Dso>(new Dso(dso_type, ++id, dso_path));
 }
 
-Dso::Dso(DsoType type, const std::string& path)
-    : type_(type), path_(path), min_vaddr_(std::numeric_limits<uint64_t>::max()), is_loaded_(false) {
+Dso::Dso(DsoType type, uint64_t id, const std::string& path)
+    : type_(type),
+      id_(id),
+      path_(path),
+      debug_file_path_(path),
+      min_vaddr_(std::numeric_limits<uint64_t>::max()),
+      is_loaded_(false),
+      has_dumped_(false) {
+  // Check if file matching path_ exists in symfs directory before using it as
+  // debug_file_path_.
+  if (!symfs_dir_.empty()) {
+    std::string path_in_symfs = symfs_dir_ + path_;
+    std::tuple<bool, std::string, std::string> tuple =
+        SplitUrlInApk(path_in_symfs);
+    std::string file_path =
+        std::get<0>(tuple) ? std::get<1>(tuple) : path_in_symfs;
+    if (IsRegularFile(file_path)) {
+      debug_file_path_ = path_in_symfs;
+    }
+  }
+  size_t pos = path.find_last_of("/\\");
+  if (pos != std::string::npos) {
+    file_name_ = path.substr(pos + 1);
+  } else {
+    file_name_ = path;
+  }
   dso_count_++;
 }
 
 Dso::~Dso() {
   if (--dso_count_ == 0) {
+    // Clean up global variables when no longer used.
     symbol_name_allocator.Clear();
+    demangle_ = true;
+    symfs_dir_.clear();
+    vmlinux_.clear();
+    kallsyms_.clear();
+    build_id_map_.clear();
   }
 }
 
-struct SymbolComparator {
-  bool operator()(const Symbol& symbol1, const Symbol& symbol2) {
-    return symbol1.addr < symbol2.addr;
-  }
-};
-
-std::string Dso::GetAccessiblePath() const {
-  return symfs_dir_ + path_;
-}
-
 const Symbol* Dso::FindSymbol(uint64_t vaddr_in_dso) {
   if (!is_loaded_) {
     is_loaded_ = true;
-    if (!Load()) {
-      LOG(DEBUG) << "failed to load dso: " << path_;
-      return nullptr;
+    // If symbols has been read from SymbolRecords, no need to load them from
+    // dso.
+    if (symbols_.empty()) {
+      if (!Load()) {
+        LOG(DEBUG) << "failed to load dso: " << path_;
+        return nullptr;
+      }
     }
   }
+  if (symbols_.empty()) {
+    return nullptr;
+  }
 
-  auto it = std::upper_bound(symbols_.begin(), symbols_.end(), Symbol("", vaddr_in_dso, 0),
-                             SymbolComparator());
+  auto it = symbols_.upper_bound(Symbol("", vaddr_in_dso, 0));
   if (it != symbols_.begin()) {
     --it;
     if (it->addr <= vaddr_in_dso && it->addr + it->len > vaddr_in_dso) {
@@ -180,10 +202,15 @@
   if (min_vaddr_ == std::numeric_limits<uint64_t>::max()) {
     min_vaddr_ = 0;
     if (type_ == DSO_ELF_FILE) {
-      BuildId build_id = GetExpectedBuildId(GetAccessiblePath());
+      BuildId build_id = GetExpectedBuildId();
 
       uint64_t addr;
-      if (ReadMinExecutableVirtualAddressFromElfFile(GetAccessiblePath(), build_id, &addr)) {
+      ElfStatus result = ReadMinExecutableVirtualAddressFromElfFile(
+          GetDebugFilePath(), build_id, &addr);
+      if (result != ElfStatus::NO_ERROR) {
+        LOG(WARNING) << "failed to read min virtual address of "
+                     << GetDebugFilePath() << ": " << result;
+      } else {
         min_vaddr_ = addr;
       }
     }
@@ -210,58 +237,101 @@
     }
   }
   if (result) {
-    std::sort(symbols_.begin(), symbols_.end(), SymbolComparator());
     FixupSymbolLength();
+  } else {
+    symbols_.clear();
   }
   return result;
 }
 
 static bool IsKernelFunctionSymbol(const KernelSymbol& symbol) {
-  return (symbol.type == 'T' || symbol.type == 't' || symbol.type == 'W' || symbol.type == 'w');
+  return (symbol.type == 'T' || symbol.type == 't' || symbol.type == 'W' ||
+          symbol.type == 'w');
 }
 
-bool Dso::KernelSymbolCallback(const KernelSymbol& kernel_symbol, Dso* dso) {
+static bool KernelSymbolCallback(const KernelSymbol& kernel_symbol, Dso* dso) {
   if (IsKernelFunctionSymbol(kernel_symbol)) {
     dso->InsertSymbol(Symbol(kernel_symbol.name, kernel_symbol.addr, 0));
   }
   return false;
 }
 
-void Dso::VmlinuxSymbolCallback(const ElfFileSymbol& elf_symbol, Dso* dso) {
+static void VmlinuxSymbolCallback(const ElfFileSymbol& elf_symbol, Dso* dso) {
   if (elf_symbol.is_func) {
-    dso->InsertSymbol(Symbol(elf_symbol.name, elf_symbol.vaddr, elf_symbol.len));
+    dso->InsertSymbol(
+        Symbol(elf_symbol.name, elf_symbol.vaddr, elf_symbol.len));
+  }
+}
+
+bool CheckReadSymbolResult(ElfStatus result, const std::string& filename) {
+  if (result == ElfStatus::NO_ERROR) {
+    LOG(VERBOSE) << "Read symbols from " << filename << " successfully";
+    return true;
+  } else if (result == ElfStatus::NO_SYMBOL_TABLE) {
+    // Lacking symbol table isn't considered as an error but worth reporting.
+    LOG(WARNING) << filename << " doesn't contain symbol table";
+    return true;
+  } else {
+    LOG(WARNING) << "failed to read symbols from " << filename
+                 << ": " << result;
+    return false;
   }
 }
 
 bool Dso::LoadKernel() {
-  BuildId build_id = GetExpectedBuildId(DEFAULT_KERNEL_FILENAME_FOR_BUILD_ID);
+  BuildId build_id = GetExpectedBuildId();
   if (!vmlinux_.empty()) {
-    ParseSymbolsFromElfFile(vmlinux_, build_id,
-                            std::bind(VmlinuxSymbolCallback, std::placeholders::_1, this));
+    ElfStatus result = ParseSymbolsFromElfFile(vmlinux_, build_id,
+        std::bind(VmlinuxSymbolCallback, std::placeholders::_1, this));
+    return CheckReadSymbolResult(result, vmlinux_);
+  } else if (!kallsyms_.empty()) {
+    ProcessKernelSymbols(kallsyms_, std::bind(&KernelSymbolCallback,
+                                              std::placeholders::_1, this));
+    bool all_zero = true;
+    for (const auto& symbol : symbols_) {
+      if (symbol.addr != 0) {
+        all_zero = false;
+        break;
+      }
+    }
+    if (all_zero) {
+      LOG(WARNING)
+          << "Symbol addresses in /proc/kallsyms on device are all zero. "
+             "`echo 0 >/proc/sys/kernel/kptr_restrict` if possible.";
+      symbols_.clear();
+      return false;
+    }
   } else {
     if (!build_id.IsEmpty()) {
       BuildId real_build_id;
-      GetKernelBuildId(&real_build_id);
+      if (!GetKernelBuildId(&real_build_id)) {
+        return false;
+      }
       bool match = (build_id == real_build_id);
-      LOG(DEBUG) << "check kernel build id (" << (match ? "match" : "mismatch") << "): expected "
-                 << build_id.ToString() << ", real " << real_build_id.ToString();
       if (!match) {
+        LOG(WARNING) << "failed to read symbols from /proc/kallsyms: Build id "
+                     << "mismatch";
         return false;
       }
     }
 
-    ProcessKernelSymbols("/proc/kallsyms",
-                         std::bind(&KernelSymbolCallback, std::placeholders::_1, this));
-    bool allZero = true;
-    for (auto& symbol : symbols_) {
+    std::string kallsyms;
+    if (!android::base::ReadFileToString("/proc/kallsyms", &kallsyms)) {
+      LOG(DEBUG) << "failed to read /proc/kallsyms";
+      return false;
+    }
+    ProcessKernelSymbols(kallsyms, std::bind(&KernelSymbolCallback,
+                                             std::placeholders::_1, this));
+    bool all_zero = true;
+    for (const auto& symbol : symbols_) {
       if (symbol.addr != 0) {
-        allZero = false;
+        all_zero = false;
         break;
       }
     }
-    if (allZero) {
-      LOG(WARNING) << "Symbol addresses in /proc/kallsyms are all zero. Check "
-                      "/proc/sys/kernel/kptr_restrict if possible.";
+    if (all_zero) {
+      LOG(WARNING) << "Symbol addresses in /proc/kallsyms are all zero. "
+                      "`echo 0 >/proc/sys/kernel/kptr_restrict` if possible.";
       symbols_.clear();
       return false;
     }
@@ -269,10 +339,11 @@
   return true;
 }
 
-void Dso::ElfFileSymbolCallback(const ElfFileSymbol& elf_symbol, Dso* dso,
-                                bool (*filter)(const ElfFileSymbol&)) {
+static void ElfFileSymbolCallback(const ElfFileSymbol& elf_symbol, Dso* dso,
+                                  bool (*filter)(const ElfFileSymbol&)) {
   if (filter(elf_symbol)) {
-    dso->InsertSymbol(Symbol(elf_symbol.name, elf_symbol.vaddr, elf_symbol.len));
+    dso->InsertSymbol(
+        Symbol(elf_symbol.name, elf_symbol.vaddr, elf_symbol.len));
   }
 }
 
@@ -282,48 +353,50 @@
 }
 
 bool Dso::LoadKernelModule() {
-  BuildId build_id = GetExpectedBuildId(path_);
-  ParseSymbolsFromElfFile(
-      symfs_dir_ + path_, build_id,
-      std::bind(ElfFileSymbolCallback, std::placeholders::_1, this, SymbolFilterForKernelModule));
-  return true;
+  BuildId build_id = GetExpectedBuildId();
+  ElfStatus result = ParseSymbolsFromElfFile(GetDebugFilePath(), build_id,
+      std::bind(ElfFileSymbolCallback, std::placeholders::_1, this,
+                SymbolFilterForKernelModule));
+  return CheckReadSymbolResult(result, GetDebugFilePath());
 }
 
 static bool SymbolFilterForDso(const ElfFileSymbol& elf_symbol) {
-  return elf_symbol.is_func || (elf_symbol.is_label && elf_symbol.is_in_text_section);
+  return elf_symbol.is_func ||
+         (elf_symbol.is_label && elf_symbol.is_in_text_section);
 }
 
 bool Dso::LoadElfFile() {
-  bool loaded = false;
-  BuildId build_id = GetExpectedBuildId(GetAccessiblePath());
+  BuildId build_id = GetExpectedBuildId();
 
   if (symfs_dir_.empty()) {
     // Linux host can store debug shared libraries in /usr/lib/debug.
-    loaded = ParseSymbolsFromElfFile(
+    ElfStatus result = ParseSymbolsFromElfFile(
         "/usr/lib/debug" + path_, build_id,
-        std::bind(ElfFileSymbolCallback, std::placeholders::_1, this, SymbolFilterForDso));
+        std::bind(ElfFileSymbolCallback, std::placeholders::_1, this,
+                  SymbolFilterForDso));
+    if (result == ElfStatus::NO_ERROR) {
+      return CheckReadSymbolResult(result, "/usr/lib/debug" + path_);
+    }
   }
-  if (!loaded) {
-    loaded = ParseSymbolsFromElfFile(
-        GetAccessiblePath(), build_id,
-        std::bind(ElfFileSymbolCallback, std::placeholders::_1, this, SymbolFilterForDso));
-  }
-  return loaded;
+  ElfStatus result = ParseSymbolsFromElfFile(
+      GetDebugFilePath(), build_id,
+      std::bind(ElfFileSymbolCallback, std::placeholders::_1, this,
+                SymbolFilterForDso));
+  return CheckReadSymbolResult(result, GetDebugFilePath());
 }
 
 bool Dso::LoadEmbeddedElfFile() {
-  std::string path = GetAccessiblePath();
-  BuildId build_id = GetExpectedBuildId(path);
-  auto tuple = SplitUrlInApk(path);
+  BuildId build_id = GetExpectedBuildId();
+  auto tuple = SplitUrlInApk(GetDebugFilePath());
   CHECK(std::get<0>(tuple));
-  return ParseSymbolsFromApkFile(std::get<1>(tuple), std::get<2>(tuple), build_id,
-                                 std::bind(ElfFileSymbolCallback, std::placeholders::_1,
-                                           this, SymbolFilterForDso));
+  ElfStatus result = ParseSymbolsFromApkFile(
+      std::get<1>(tuple), std::get<2>(tuple), build_id,
+      std::bind(ElfFileSymbolCallback, std::placeholders::_1, this,
+                SymbolFilterForDso));
+  return CheckReadSymbolResult(result, GetDebugFilePath());
 }
 
-void Dso::InsertSymbol(const Symbol& symbol) {
-  symbols_.push_back(symbol);
-}
+void Dso::InsertSymbol(const Symbol& symbol) { symbols_.insert(symbol); }
 
 void Dso::FixupSymbolLength() {
   Symbol* prev_symbol = nullptr;
@@ -331,9 +404,22 @@
     if (prev_symbol != nullptr && prev_symbol->len == 0) {
       prev_symbol->len = symbol.addr - prev_symbol->addr;
     }
-    prev_symbol = &symbol;
+    prev_symbol = const_cast<Symbol*>(&symbol);
   }
   if (prev_symbol != nullptr && prev_symbol->len == 0) {
-    prev_symbol->len = std::numeric_limits<unsigned long long>::max() - prev_symbol->addr;
+    prev_symbol->len = std::numeric_limits<uint64_t>::max() - prev_symbol->addr;
+  }
+}
+
+const char* DsoTypeToString(DsoType dso_type) {
+  switch (dso_type) {
+    case DSO_KERNEL:
+      return "dso_kernel";
+    case DSO_KERNEL_MODULE:
+      return "dso_kernel_module";
+    case DSO_ELF_FILE:
+      return "dso_elf_file";
+    default:
+      return "unknown";
   }
 }
diff --git a/simpleperf/dso.h b/simpleperf/dso.h
index 9697319..c381e6d 100644
--- a/simpleperf/dso.h
+++ b/simpleperf/dso.h
@@ -18,6 +18,7 @@
 #define SIMPLE_PERF_DSO_H_
 
 #include <memory>
+#include <set>
 #include <string>
 #include <unordered_map>
 #include <vector>
@@ -29,15 +30,24 @@
   uint64_t len;
 
   Symbol(const std::string& name, uint64_t addr, uint64_t len);
-  const char* Name() const {
-    return name_;
-  }
+  const char* Name() const { return name_; }
 
   const char* DemangledName() const;
 
+  bool HasDumped() const { return has_dumped_; }
+
+  void SetDumped() const { has_dumped_ = true; }
+
  private:
   const char* name_;
   mutable const char* demangled_name_;
+  mutable bool has_dumped_;
+};
+
+struct SymbolComparator {
+  bool operator()(const Symbol& symbol1, const Symbol& symbol2) {
+    return symbol1.addr < symbol2.addr;
+  }
 };
 
 enum DsoType {
@@ -55,53 +65,73 @@
   static std::string Demangle(const std::string& name);
   static bool SetSymFsDir(const std::string& symfs_dir);
   static void SetVmlinux(const std::string& vmlinux);
-  static void SetBuildIds(const std::vector<std::pair<std::string, BuildId>>& build_ids);
+  static void SetKallsyms(std::string kallsyms) {
+    if (!kallsyms.empty()) {
+      kallsyms_ = std::move(kallsyms);
+    }
+  }
+  static void SetBuildIds(
+      const std::vector<std::pair<std::string, BuildId>>& build_ids);
 
-  static std::unique_ptr<Dso> CreateDso(DsoType dso_type, const std::string& dso_path = "");
+  static std::unique_ptr<Dso> CreateDso(DsoType dso_type,
+                                        const std::string& dso_path);
 
   ~Dso();
 
-  // Return the path recorded in perf.data.
-  const std::string& Path() const {
-    return path_;
-  }
+  DsoType type() const { return type_; }
 
-  // Return the accessible path. It may be the same as Path(), or
-  // return the path with prefix set by SetSymFsDir().
-  std::string GetAccessiblePath() const;
+  uint64_t id() const { return id_; }
+
+  // Return the path recorded in perf.data.
+  const std::string& Path() const { return path_; }
+  // Return the path containing symbol table and debug information.
+  const std::string& GetDebugFilePath() const { return debug_file_path_; }
+  // Return the file name without directory info.
+  const std::string& FileName() const { return file_name_; }
+
+  bool HasDumped() const { return has_dumped_; }
+
+  void SetDumped() { has_dumped_ = true; }
 
   // Return the minimum virtual address in program header.
   uint64_t MinVirtualAddress();
+  void SetMinVirtualAddress(uint64_t min_vaddr) { min_vaddr_ = min_vaddr; }
 
   const Symbol* FindSymbol(uint64_t vaddr_in_dso);
+  void InsertSymbol(const Symbol& symbol);
 
  private:
-  static BuildId GetExpectedBuildId(const std::string& filename);
-  static bool KernelSymbolCallback(const KernelSymbol& kernel_symbol, Dso* dso);
-  static void VmlinuxSymbolCallback(const ElfFileSymbol& elf_symbol, Dso* dso);
-  static void ElfFileSymbolCallback(const ElfFileSymbol& elf_symbol, Dso* dso,
-                                    bool (*filter)(const ElfFileSymbol&));
-
   static bool demangle_;
   static std::string symfs_dir_;
   static std::string vmlinux_;
+  static std::string kallsyms_;
   static std::unordered_map<std::string, BuildId> build_id_map_;
   static size_t dso_count_;
 
-  Dso(DsoType type, const std::string& path);
+  Dso(DsoType type, uint64_t id, const std::string& path);
   bool Load();
   bool LoadKernel();
   bool LoadKernelModule();
   bool LoadElfFile();
   bool LoadEmbeddedElfFile();
-  void InsertSymbol(const Symbol& symbol);
   void FixupSymbolLength();
+  BuildId GetExpectedBuildId();
 
   const DsoType type_;
+  const uint64_t id_;
+  // path of the shared library used by the profiled program
   const std::string path_;
+  // path of the shared library having symbol table and debug information
+  // It is the same as path_, or has the same build id as path_.
+  std::string debug_file_path_;
+  // File name of the shared library, got by removing directories in path_.
+  std::string file_name_;
   uint64_t min_vaddr_;
-  std::vector<Symbol> symbols_;
+  std::set<Symbol, SymbolComparator> symbols_;
   bool is_loaded_;
+  bool has_dumped_;
 };
 
+const char* DsoTypeToString(DsoType dso_type);
+
 #endif  // SIMPLE_PERF_DSO_H_
diff --git a/simpleperf/dwarf_unwind.cpp b/simpleperf/dwarf_unwind.cpp
index ae2e1a1..5444ef9 100644
--- a/simpleperf/dwarf_unwind.cpp
+++ b/simpleperf/dwarf_unwind.cpp
@@ -27,7 +27,7 @@
   do {                                           \
     uint64_t value;                              \
     if (GetRegValue(regs, perf_regno, &value)) { \
-      dst = value;                               \
+      (dst) = value;                             \
     }                                            \
   } while (0)
 
@@ -95,10 +95,12 @@
 }
 
 std::vector<uint64_t> UnwindCallChain(ArchType arch, const ThreadEntry& thread,
-                                      const RegSet& regs, const std::vector<char>& stack) {
+                                      const RegSet& regs, const char* stack,
+                                      size_t stack_size, bool strict_arch_check) {
   std::vector<uint64_t> result;
-  if (arch != GetBuildArch()) {
-    LOG(ERROR) << "can't unwind data recorded on a different architecture";
+  if (!IsArchTheSame(arch, GetBuildArch(), strict_arch_check)) {
+    LOG(FATAL) << "simpleperf is built in arch " << GetArchString(GetBuildArch())
+            << ", and can't do stack unwinding for arch " << GetArchString(arch);
     return result;
   }
   uint64_t sp_reg_value;
@@ -115,20 +117,24 @@
     bt_map.start = map->start_addr;
     bt_map.end = map->start_addr + map->len;
     bt_map.offset = map->pgoff;
-    bt_map.name = map->dso->GetAccessiblePath();
+    bt_map.name = map->dso->GetDebugFilePath();
   }
   std::unique_ptr<BacktraceMap> backtrace_map(BacktraceMap::Create(thread.pid, bt_maps));
 
   backtrace_stackinfo_t stack_info;
   stack_info.start = stack_addr;
-  stack_info.end = stack_addr + stack.size();
-  stack_info.data = reinterpret_cast<const uint8_t*>(stack.data());
+  stack_info.end = stack_addr + stack_size;
+  stack_info.data = reinterpret_cast<const uint8_t*>(stack);
 
   std::unique_ptr<Backtrace> backtrace(
       Backtrace::CreateOffline(thread.pid, thread.tid, backtrace_map.get(), stack_info, true));
   ucontext_t ucontext = BuildUContextFromRegs(regs);
   if (backtrace->Unwind(0, &ucontext)) {
     for (auto it = backtrace->begin(); it != backtrace->end(); ++it) {
+      // Unwinding in arm architecture can return 0 pc address.
+      if (it->pc == 0) {
+        break;
+      }
       result.push_back(it->pc);
     }
   }
diff --git a/simpleperf/dwarf_unwind.h b/simpleperf/dwarf_unwind.h
index 6982b05..2a28a9e 100644
--- a/simpleperf/dwarf_unwind.h
+++ b/simpleperf/dwarf_unwind.h
@@ -28,6 +28,6 @@
 using ThreadEntry = simpleperf::ThreadEntry;
 
 std::vector<uint64_t> UnwindCallChain(ArchType arch, const ThreadEntry& thread, const RegSet& regs,
-                                      const std::vector<char>& stack);
+                                      const char* stack, size_t stack_size, bool strict_arch_check);
 
 #endif  // SIMPLE_PERF_DWARF_UNWIND_H_
diff --git a/simpleperf/environment.cpp b/simpleperf/environment.cpp
index 5a8552a..b7b0355 100644
--- a/simpleperf/environment.cpp
+++ b/simpleperf/environment.cpp
@@ -40,7 +40,7 @@
 
 class LineReader {
  public:
-  LineReader(FILE* fp) : fp_(fp), buf_(nullptr), bufsize_(0) {
+  explicit LineReader(FILE* fp) : fp_(fp), buf_(nullptr), bufsize_(0) {
   }
 
   ~LineReader() {
@@ -88,9 +88,9 @@
   const char* p = s.c_str();
   char* endp;
   int last_cpu;
-  long cpu;
+  int cpu;
   // Parse line like: 0,1-3, 5, 7-8
-  while ((cpu = strtol(p, &endp, 10)) != 0 || endp != p) {
+  while ((cpu = static_cast<int>(strtol(p, &endp, 10))) != 0 || endp != p) {
     if (have_dash && !cpu_set.empty()) {
       for (int t = last_cpu + 1; t < cpu; ++t) {
         cpu_set.insert(t);
@@ -110,41 +110,6 @@
   return std::vector<int>(cpu_set.begin(), cpu_set.end());
 }
 
-bool ProcessKernelSymbols(const std::string& symbol_file,
-                          std::function<bool(const KernelSymbol&)> callback) {
-  FILE* fp = fopen(symbol_file.c_str(), "re");
-  if (fp == nullptr) {
-    PLOG(ERROR) << "failed to open file " << symbol_file;
-    return false;
-  }
-  LineReader reader(fp);
-  char* line;
-  while ((line = reader.ReadLine()) != nullptr) {
-    // Parse line like: ffffffffa005c4e4 d __warned.41698       [libsas]
-    char name[reader.MaxLineSize()];
-    char module[reader.MaxLineSize()];
-    strcpy(module, "");
-
-    KernelSymbol symbol;
-    if (sscanf(line, "%" PRIx64 " %c %s%s", &symbol.addr, &symbol.type, name, module) < 3) {
-      continue;
-    }
-    symbol.name = name;
-    size_t module_len = strlen(module);
-    if (module_len > 2 && module[0] == '[' && module[module_len - 1] == ']') {
-      module[module_len - 1] = '\0';
-      symbol.module = &module[1];
-    } else {
-      symbol.module = nullptr;
-    }
-
-    if (callback(symbol)) {
-      return true;
-    }
-  }
-  return false;
-}
-
 static std::vector<KernelMmap> GetLoadedModules() {
   std::vector<KernelMmap> result;
   FILE* fp = fopen("/proc/modules", "re");
@@ -166,6 +131,16 @@
       result.push_back(map);
     }
   }
+  bool all_zero = true;
+  for (const auto& map : result) {
+    if (map.start_addr != 0) {
+      all_zero = false;
+    }
+  }
+  if (all_zero) {
+    LOG(DEBUG) << "addresses in /proc/modules are all zero, so ignore kernel modules";
+    return std::vector<KernelMmap>();
+  }
   return result;
 }
 
@@ -183,19 +158,16 @@
 
 static void GetAllModuleFiles(const std::string& path,
                               std::unordered_map<std::string, std::string>* module_file_map) {
-  std::vector<std::string> files;
-  std::vector<std::string> subdirs;
-  GetEntriesInDir(path, &files, &subdirs);
-  for (auto& name : files) {
-    if (android::base::EndsWith(name, ".ko")) {
+  for (const auto& name : GetEntriesInDir(path)) {
+    std::string entry_path = path + "/" + name;
+    if (IsRegularFile(entry_path) && android::base::EndsWith(name, ".ko")) {
       std::string module_name = name.substr(0, name.size() - 3);
       std::replace(module_name.begin(), module_name.end(), '-', '_');
-      module_file_map->insert(std::make_pair(module_name, path + "/" + name));
+      module_file_map->insert(std::make_pair(module_name, entry_path));
+    } else if (IsDir(entry_path)) {
+      GetAllModuleFiles(entry_path, module_file_map);
     }
   }
-  for (auto& name : subdirs) {
-    GetAllModuleFiles(path + "/" + name, module_file_map);
-  }
 }
 
 static std::vector<KernelMmap> GetModulesInUse() {
@@ -226,7 +198,7 @@
   }
 
   if (module_mmaps->size() == 0) {
-    kernel_mmap->len = std::numeric_limits<unsigned long long>::max() - kernel_mmap->start_addr;
+    kernel_mmap->len = std::numeric_limits<uint64_t>::max() - kernel_mmap->start_addr;
   } else {
     std::sort(
         module_mmaps->begin(), module_mmaps->end(),
@@ -246,7 +218,7 @@
       }
     }
     module_mmaps->back().len =
-        std::numeric_limits<unsigned long long>::max() - module_mmaps->back().start_addr;
+        std::numeric_limits<uint64_t>::max() - module_mmaps->back().start_addr;
   }
 }
 
@@ -274,12 +246,10 @@
   return false;
 }
 
-static std::vector<pid_t> GetThreadsInProcess(pid_t pid) {
+std::vector<pid_t> GetThreadsInProcess(pid_t pid) {
   std::vector<pid_t> result;
   std::string task_dirname = android::base::StringPrintf("/proc/%d/task", pid);
-  std::vector<std::string> subdirs;
-  GetEntriesInDir(task_dirname, nullptr, &subdirs);
-  for (const auto& name : subdirs) {
+  for (const auto& name : GetSubDirs(task_dirname)) {
     int tid;
     if (!android::base::ParseInt(name.c_str(), &tid, 0)) {
       continue;
@@ -311,9 +281,7 @@
 
 bool GetThreadComms(std::vector<ThreadComm>* thread_comms) {
   thread_comms->clear();
-  std::vector<std::string> subdirs;
-  GetEntriesInDir("/proc", nullptr, &subdirs);
-  for (auto& name : subdirs) {
+  for (const auto& name : GetSubDirs("/proc")) {
     int pid;
     if (!android::base::ParseInt(name.c_str(), &pid, 0)) {
       continue;
@@ -360,7 +328,11 @@
 }
 
 bool GetKernelBuildId(BuildId* build_id) {
-  return GetBuildIdFromNoteFile("/sys/kernel/notes", build_id);
+  ElfStatus result = GetBuildIdFromNoteFile("/sys/kernel/notes", build_id);
+  if (result != ElfStatus::NO_ERROR) {
+    LOG(WARNING) << "failed to read /sys/kernel/notes: " << result;
+  }
+  return result == ElfStatus::NO_ERROR;
 }
 
 bool GetModuleBuildId(const std::string& module_name, BuildId* build_id) {
@@ -368,24 +340,6 @@
   return GetBuildIdFromNoteFile(notefile, build_id);
 }
 
-bool GetValidThreadsFromProcessString(const std::string& pid_str, std::set<pid_t>* tid_set) {
-  std::vector<std::string> strs = android::base::Split(pid_str, ",");
-  for (const auto& s : strs) {
-    int pid;
-    if (!android::base::ParseInt(s.c_str(), &pid, 0)) {
-      LOG(ERROR) << "Invalid pid '" << s << "'";
-      return false;
-    }
-    std::vector<pid_t> tids = GetThreadsInProcess(pid);
-    if (tids.empty()) {
-      LOG(ERROR) << "Non existing process '" << pid << "'";
-      return false;
-    }
-    tid_set->insert(tids.begin(), tids.end());
-  }
-  return true;
-}
-
 bool GetValidThreadsFromThreadString(const std::string& tid_str, std::set<pid_t>* tid_set) {
   std::vector<std::string> strs = android::base::Split(tid_str, ",");
   for (const auto& s : strs) {
@@ -403,18 +357,6 @@
   return true;
 }
 
-bool GetExecPath(std::string* exec_path) {
-  char path[PATH_MAX];
-  ssize_t path_len = readlink("/proc/self/exe", path, sizeof(path));
-  if (path_len <= 0 || path_len >= static_cast<ssize_t>(sizeof(path))) {
-    PLOG(ERROR) << "readlink failed";
-    return false;
-  }
-  path[path_len] = '\0';
-  *exec_path = path;
-  return true;
-}
-
 /*
  * perf event paranoia level:
  *  -1 - not paranoid at all
@@ -477,3 +419,53 @@
 #endif
   return true;
 }
+
+bool CheckSampleFrequency(uint64_t sample_freq) {
+  if (sample_freq == 0) {
+    LOG(ERROR) << "Sample frequency can't be zero.";
+    return false;
+  }
+  std::string s;
+  if (!android::base::ReadFileToString("/proc/sys/kernel/perf_event_max_sample_rate", &s)) {
+    PLOG(WARNING) << "failed to read /proc/sys/kernel/perf_event_max_sample_rate";
+    // Omit the check if perf_event_max_sample_rate doesn't exist.
+    return true;
+  }
+  s = android::base::Trim(s);
+  uint64_t max_sample_freq;
+  if (!android::base::ParseUint(s.c_str(), &max_sample_freq)) {
+    LOG(ERROR) << "failed to parse /proc/sys/kernel/perf_event_max_sample_rate: " << s;
+    return false;
+  }
+  if (sample_freq > max_sample_freq) {
+    LOG(ERROR) << "Sample frequency " << sample_freq << " is out of range [1, "
+        << max_sample_freq << "]";
+    return false;
+  }
+  return true;
+}
+
+bool CheckKernelSymbolAddresses() {
+  const std::string kptr_restrict_file = "/proc/sys/kernel/kptr_restrict";
+  std::string s;
+  if (!android::base::ReadFileToString(kptr_restrict_file, &s)) {
+    PLOG(WARNING) << "failed to read " << kptr_restrict_file;
+    return false;
+  }
+  s = android::base::Trim(s);
+  int value;
+  if (!android::base::ParseInt(s.c_str(), &value)) {
+    LOG(ERROR) << "failed to parse " << kptr_restrict_file << ": " << s;
+    return false;
+  }
+  if (value == 0) {
+    return true;
+  }
+  if (value == 1 && IsRoot()) {
+    return true;
+  }
+  LOG(WARNING) << "Access to kernel symbol addresses is restricted. If "
+      << "possible, please do `echo 0 >/proc/sys/kernel/kptr_restrict` "
+      << "to fix this.";
+  return false;
+}
diff --git a/simpleperf/environment.h b/simpleperf/environment.h
index 6da632b..9746720 100644
--- a/simpleperf/environment.h
+++ b/simpleperf/environment.h
@@ -29,7 +29,7 @@
 std::vector<int> GetOnlineCpus();
 std::vector<int> GetCpusFromString(const std::string& s);
 
-constexpr char DEFAULT_KERNEL_MMAP_NAME[] = "[kernel.kallsyms]_text";
+constexpr char DEFAULT_KERNEL_MMAP_NAME[] = "[kernel.kallsyms]";
 
 struct KernelMmap {
   std::string name;
@@ -64,21 +64,11 @@
 bool GetKernelBuildId(BuildId* build_id);
 bool GetModuleBuildId(const std::string& module_name, BuildId* build_id);
 
-bool GetValidThreadsFromProcessString(const std::string& pid_str, std::set<pid_t>* tid_set);
+std::vector<pid_t> GetThreadsInProcess(pid_t pid);
 bool GetValidThreadsFromThreadString(const std::string& tid_str, std::set<pid_t>* tid_set);
 
-bool GetExecPath(std::string* exec_path);
-
-// Expose the following functions for unit tests.
-struct KernelSymbol {
-  uint64_t addr;
-  char type;
-  const char* name;
-  const char* module;  // If nullptr, the symbol is not in a kernel module.
-};
-
-bool ProcessKernelSymbols(const std::string& symbol_file,
-                          std::function<bool(const KernelSymbol&)> callback);
 bool CheckPerfEventLimit();
+bool CheckSampleFrequency(uint64_t sample_freq);
+bool CheckKernelSymbolAddresses();
 
 #endif  // SIMPLE_PERF_ENVIRONMENT_H_
diff --git a/simpleperf/environment_fake.cpp b/simpleperf/environment_fake.cpp
deleted file mode 100644
index fdcf814..0000000
--- a/simpleperf/environment_fake.cpp
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-// Add fake functions to build successfully on non-linux environments.
-#include "environment.h"
-
-bool ProcessKernelSymbols(const std::string&, std::function<bool(const KernelSymbol&)>) {
-  return false;
-}
-
-bool GetKernelBuildId(BuildId*) {
-  return false;
-}
diff --git a/simpleperf/environment_test.cpp b/simpleperf/environment_test.cpp
index 6bca7b8..9b4cbab 100644
--- a/simpleperf/environment_test.cpp
+++ b/simpleperf/environment_test.cpp
@@ -16,10 +16,6 @@
 
 #include <gtest/gtest.h>
 
-#include <functional>
-#include <android-base/file.h>
-#include <android-base/test_utils.h>
-
 #include "environment.h"
 
 TEST(environment, GetCpusFromString) {
@@ -28,47 +24,3 @@
   ASSERT_EQ(GetCpusFromString("0,2-3"), std::vector<int>({0, 2, 3}));
   ASSERT_EQ(GetCpusFromString("1,0-3,3,4"), std::vector<int>({0, 1, 2, 3, 4}));
 }
-
-static bool ModulesMatch(const char* p, const char* q) {
-  if (p == nullptr && q == nullptr) {
-    return true;
-  }
-  if (p != nullptr && q != nullptr) {
-    return strcmp(p, q) == 0;
-  }
-  return false;
-}
-
-static bool KernelSymbolsMatch(const KernelSymbol& sym1, const KernelSymbol& sym2) {
-  return sym1.addr == sym2.addr &&
-         sym1.type == sym2.type &&
-         strcmp(sym1.name, sym2.name) == 0 &&
-         ModulesMatch(sym1.module, sym2.module);
-}
-
-TEST(environment, ProcessKernelSymbols) {
-  std::string data =
-      "ffffffffa005c4e4 d __warned.41698   [libsas]\n"
-      "aaaaaaaaaaaaaaaa T _text\n"
-      "cccccccccccccccc c ccccc\n";
-  TemporaryFile tempfile;
-  ASSERT_TRUE(android::base::WriteStringToFile(data, tempfile.path));
-  KernelSymbol expected_symbol;
-  expected_symbol.addr = 0xffffffffa005c4e4ULL;
-  expected_symbol.type = 'd';
-  expected_symbol.name = "__warned.41698";
-  expected_symbol.module = "libsas";
-  ASSERT_TRUE(ProcessKernelSymbols(
-      tempfile.path, std::bind(&KernelSymbolsMatch, std::placeholders::_1, expected_symbol)));
-
-  expected_symbol.addr = 0xaaaaaaaaaaaaaaaaULL;
-  expected_symbol.type = 'T';
-  expected_symbol.name = "_text";
-  expected_symbol.module = nullptr;
-  ASSERT_TRUE(ProcessKernelSymbols(
-      tempfile.path, std::bind(&KernelSymbolsMatch, std::placeholders::_1, expected_symbol)));
-
-  expected_symbol.name = "non_existent_symbol";
-  ASSERT_FALSE(ProcessKernelSymbols(
-      tempfile.path, std::bind(&KernelSymbolsMatch, std::placeholders::_1, expected_symbol)));
-}
diff --git a/simpleperf/event_attr.cpp b/simpleperf/event_attr.cpp
index c9449b1..1936448 100644
--- a/simpleperf/event_attr.cpp
+++ b/simpleperf/event_attr.cpp
@@ -87,12 +87,10 @@
   // PerfCounter in event_fd.h.
   attr.read_format =
       PERF_FORMAT_TOTAL_TIME_ENABLED | PERF_FORMAT_TOTAL_TIME_RUNNING | PERF_FORMAT_ID;
-  attr.sample_type |=
-      PERF_SAMPLE_IP | PERF_SAMPLE_TID | PERF_SAMPLE_TIME | PERF_SAMPLE_PERIOD | PERF_SAMPLE_CPU;
+  attr.sample_type |= PERF_SAMPLE_IP | PERF_SAMPLE_TID | PERF_SAMPLE_TIME | PERF_SAMPLE_PERIOD |
+      PERF_SAMPLE_CPU | PERF_SAMPLE_ID;
 
   if (attr.type == PERF_TYPE_TRACEPOINT) {
-    attr.sample_freq = 0;
-    attr.sample_period = 1;
     // Tracepoint information are stored in raw data in sample records.
     attr.sample_type |= PERF_SAMPLE_RAW;
   }
@@ -100,12 +98,7 @@
 }
 
 void DumpPerfEventAttr(const perf_event_attr& attr, size_t indent) {
-  std::string event_name = "unknown";
-  const EventType* event_type = FindEventTypeByConfig(attr.type, attr.config);
-  if (event_type != nullptr) {
-    event_name = event_type->name;
-  }
-
+  std::string event_name = GetEventNameByAttr(attr);
   PrintIndented(indent, "event_attr: for event type %s\n", event_name.c_str());
 
   PrintIndented(indent + 1, "type %u, size %u, config %llu\n", attr.type, attr.size, attr.config);
@@ -145,3 +138,105 @@
   PrintIndented(indent + 1, "sample_regs_user 0x%" PRIx64 "\n", attr.sample_regs_user);
   PrintIndented(indent + 1, "sample_stack_user 0x%" PRIx64 "\n", attr.sample_stack_user);
 }
+
+bool GetCommonEventIdPositionsForAttrs(std::vector<perf_event_attr>& attrs,
+                                           size_t* event_id_pos_in_sample_records,
+                                           size_t* event_id_reverse_pos_in_non_sample_records) {
+  // When there are more than one perf_event_attrs, we need to read event id
+  // in each record to decide current record should use which attr. So
+  // we need to determine the event id position in a record here.
+  std::vector<uint64_t> sample_types;
+  for (const auto& attr : attrs) {
+    sample_types.push_back(attr.sample_type);
+  }
+  // First determine event_id_pos_in_sample_records.
+  // If PERF_SAMPLE_IDENTIFIER is enabled, it is just after perf_event_header.
+  // If PERF_SAMPLE_ID is enabled, then PERF_SAMPLE_IDENTIFIER | IP | TID | TIME | ADDR
+  // should also be the same.
+  bool identifier_enabled = true;
+  bool id_enabled = true;
+  uint64_t flags_before_id_mask = PERF_SAMPLE_IDENTIFIER | PERF_SAMPLE_IP | PERF_SAMPLE_TID |
+      PERF_SAMPLE_TIME | PERF_SAMPLE_ADDR;
+  uint64_t flags_before_id = sample_types[0] & flags_before_id_mask;
+  bool flags_before_id_are_the_same = true;
+  for (auto type : sample_types) {
+    identifier_enabled &= (type & PERF_SAMPLE_IDENTIFIER) != 0;
+    id_enabled &= (type & PERF_SAMPLE_ID) != 0;
+    flags_before_id_are_the_same &= (type & flags_before_id_mask) == flags_before_id;
+  }
+  if (identifier_enabled) {
+    *event_id_pos_in_sample_records = sizeof(perf_event_header);
+  } else if (id_enabled && flags_before_id_are_the_same) {
+    uint64_t pos = sizeof(perf_event_header);
+    while (flags_before_id != 0) {
+      // Each flags takes 8 bytes in sample records.
+      flags_before_id &= flags_before_id - 1;
+      pos += 8;
+    }
+    *event_id_pos_in_sample_records = pos;
+  } else {
+    LOG(ERROR) << "perf_event_attrs don't have a common event id position in sample records";
+    return false;
+  }
+
+  // Secondly determine event_id_reverse_pos_in_non_sample_record.
+  // If sample_id_all is not enabled, there is no event id in non sample records.
+  // If PERF_SAMPLE_IDENTIFIER is enabled, it is at the last 8 bytes of the record.
+  // If PERF_SAMPLE_ID is enabled, then PERF_SAMPLE_IDENTIFIER | CPU | STREAM_ID should
+  // also be the same.
+  bool sample_id_all_enabled = true;
+  for (const auto& attr : attrs) {
+    if (attr.sample_id_all == 0) {
+      sample_id_all_enabled = false;
+    }
+  }
+  if (!sample_id_all_enabled) {
+    LOG(ERROR) << "there are perf_event_attrs not enabling sample_id_all, so can't determine "
+               << "perf_event_attr for non sample records";
+    return false;
+  }
+  uint64_t flags_after_id_mask = PERF_SAMPLE_IDENTIFIER | PERF_SAMPLE_CPU | PERF_SAMPLE_STREAM_ID;
+  uint64_t flags_after_id = sample_types[0] & flags_after_id_mask;
+  bool flags_after_id_are_the_same = true;
+  for (auto type : sample_types) {
+    flags_after_id_are_the_same &= (type & flags_after_id_mask) == flags_after_id;
+  }
+  if (identifier_enabled) {
+    *event_id_reverse_pos_in_non_sample_records = 8;
+  } else if (id_enabled && flags_after_id_are_the_same) {
+    uint64_t pos = 8;
+    while (flags_after_id != 0) {
+      // Each flag takes 8 bytes in sample_id of non sample records.
+      flags_after_id &= flags_after_id - 1;
+      pos += 8;
+    }
+    *event_id_reverse_pos_in_non_sample_records = pos;
+  } else {
+    LOG(ERROR) << "perf_event_attrs don't have a common event id reverse position in non sample records";
+    return false;
+  }
+  return true;
+}
+
+bool IsTimestampSupported(const perf_event_attr& attr) {
+  return attr.sample_id_all && (attr.sample_type & PERF_SAMPLE_TIME);
+}
+
+bool IsCpuSupported(const perf_event_attr& attr) {
+  return attr.sample_id_all && (attr.sample_type & PERF_SAMPLE_CPU);
+}
+
+std::string GetEventNameByAttr(const perf_event_attr& attr) {
+  for (const auto& event_type : GetAllEventTypes()) {
+    if (event_type.type == attr.type && event_type.config == attr.config) {
+      std::string name = event_type.name;
+      if (attr.exclude_user && !attr.exclude_kernel) {
+        name += ":k";
+      } else if (attr.exclude_kernel && !attr.exclude_user) {
+        name += ":u";
+      }
+      return name;
+    }
+  }
+  return "unknown";
+}
diff --git a/simpleperf/event_attr.h b/simpleperf/event_attr.h
index 79d3df4..9182bb9 100644
--- a/simpleperf/event_attr.h
+++ b/simpleperf/event_attr.h
@@ -19,11 +19,21 @@
 
 #include <stddef.h>
 
+#include <string>
+#include <vector>
+
 #include "perf_event.h"
 
 struct EventType;
 
 perf_event_attr CreateDefaultPerfEventAttr(const EventType& event_type);
 void DumpPerfEventAttr(const perf_event_attr& attr, size_t indent = 0);
+bool GetCommonEventIdPositionsForAttrs(std::vector<perf_event_attr>& attrs,
+                                       size_t* event_id_pos_in_sample_records,
+                                       size_t* event_id_reverse_pos_in_non_sample_records);
+bool IsTimestampSupported(const perf_event_attr& attr);
+bool IsCpuSupported(const perf_event_attr& attr);
+// Return event name with modifier if the event is found, otherwise return "unknown".
+std::string GetEventNameByAttr(const perf_event_attr& attr);
 
 #endif  // SIMPLE_PERF_EVENT_ATTR_H_
diff --git a/simpleperf/event_fd.cpp b/simpleperf/event_fd.cpp
index 808639b..5d8dffa 100644
--- a/simpleperf/event_fd.cpp
+++ b/simpleperf/event_fd.cpp
@@ -17,7 +17,6 @@
 #include "event_fd.h"
 
 #include <fcntl.h>
-#include <poll.h>
 #include <stdio.h>
 #include <string.h>
 #include <sys/ioctl.h>
@@ -31,59 +30,65 @@
 #include <android-base/logging.h>
 #include <android-base/stringprintf.h>
 
+#include "event_attr.h"
 #include "event_type.h"
 #include "perf_event.h"
 #include "utils.h"
 
 std::vector<char> EventFd::data_process_buffer_;
 
-static int perf_event_open(perf_event_attr* attr, pid_t pid, int cpu, int group_fd,
-                           unsigned long flags) {
-  return syscall(__NR_perf_event_open, attr, pid, cpu, group_fd, flags);
+static int perf_event_open(const perf_event_attr& attr, pid_t pid, int cpu,
+                           int group_fd, unsigned long flags) {  // NOLINT
+  return syscall(__NR_perf_event_open, &attr, pid, cpu, group_fd, flags);
 }
 
-std::unique_ptr<EventFd> EventFd::OpenEventFile(const perf_event_attr& attr, pid_t tid, int cpu,
+std::unique_ptr<EventFd> EventFd::OpenEventFile(const perf_event_attr& attr,
+                                                pid_t tid, int cpu,
+                                                EventFd* group_event_fd,
                                                 bool report_error) {
-  perf_event_attr perf_attr = attr;
-  std::string event_name = "unknown event";
-  const EventType* event_type = FindEventTypeByConfig(perf_attr.type, perf_attr.config);
-  if (event_type != nullptr) {
-    event_name = event_type->name;
+  std::string event_name = GetEventNameByAttr(attr);
+  int group_fd = -1;
+  if (group_event_fd != nullptr) {
+    group_fd = group_event_fd->perf_event_fd_;
   }
-  int perf_event_fd = perf_event_open(&perf_attr, tid, cpu, -1, 0);
+  int perf_event_fd = perf_event_open(attr, tid, cpu, group_fd, 0);
   if (perf_event_fd == -1) {
     if (report_error) {
-      PLOG(ERROR) << "open perf_event_file (event " << event_name << ", tid " << tid << ", cpu "
-                  << cpu << ") failed";
+      PLOG(ERROR) << "open perf_event_file (event " << event_name << ", tid "
+                  << tid << ", cpu " << cpu << ", group_fd " << group_fd
+                  << ") failed";
     } else {
-      PLOG(DEBUG) << "open perf_event_file (event " << event_name << ", tid " << tid << ", cpu "
-                  << cpu << ") failed";
+      PLOG(DEBUG) << "open perf_event_file (event " << event_name << ", tid "
+                  << tid << ", cpu " << cpu << ", group_fd " << group_fd
+                  << ") failed";
     }
     return nullptr;
   }
   if (fcntl(perf_event_fd, F_SETFD, FD_CLOEXEC) == -1) {
     if (report_error) {
-      PLOG(ERROR) << "fcntl(FD_CLOEXEC) for perf_event_file (event " << event_name << ", tid "
-                  << tid << ", cpu " << cpu << ") failed";
+      PLOG(ERROR) << "fcntl(FD_CLOEXEC) for perf_event_file (event "
+                  << event_name << ", tid " << tid << ", cpu " << cpu
+                  << ", group_fd " << group_fd << ") failed";
     } else {
-      PLOG(DEBUG) << "fcntl(FD_CLOEXEC) for perf_event_file (event " << event_name << ", tid "
-                  << tid << ", cpu " << cpu << ") failed";
+      PLOG(DEBUG) << "fcntl(FD_CLOEXEC) for perf_event_file (event "
+                  << event_name << ", tid " << tid << ", cpu " << cpu
+                  << ", group_fd " << group_fd << ") failed";
     }
     return nullptr;
   }
-  return std::unique_ptr<EventFd>(new EventFd(perf_event_fd, event_name, tid, cpu));
+  return std::unique_ptr<EventFd>(
+      new EventFd(attr, perf_event_fd, event_name, tid, cpu));
 }
 
 EventFd::~EventFd() {
-  if (mmap_addr_ != nullptr) {
-    munmap(mmap_addr_, mmap_len_);
-  }
+  DestroyMappedBuffer();
   close(perf_event_fd_);
 }
 
 std::string EventFd::Name() const {
-  return android::base::StringPrintf("perf_event_file(event %s, tid %d, cpu %d)",
-                                     event_name_.c_str(), tid_, cpu_);
+  return android::base::StringPrintf(
+      "perf_event_file(event %s, tid %d, cpu %d)", event_name_.c_str(), tid_,
+      cpu_);
 }
 
 uint64_t EventFd::Id() const {
@@ -96,6 +101,15 @@
   return id_;
 }
 
+bool EventFd::EnableEvent() {
+  int result = ioctl(perf_event_fd_, PERF_EVENT_IOC_ENABLE, 0);
+  if (result < 0) {
+    PLOG(ERROR) << "ioctl(enable) " << Name() << " failed";
+    return false;
+  }
+  return true;
+}
+
 bool EventFd::ReadCounter(PerfCounter* counter) const {
   CHECK(counter != nullptr);
   if (!android::base::ReadFully(perf_event_fd_, counter, sizeof(*counter))) {
@@ -105,13 +119,24 @@
   return true;
 }
 
-bool EventFd::MmapContent(size_t mmap_pages) {
+bool EventFd::CreateMappedBuffer(size_t mmap_pages, bool report_error) {
   CHECK(IsPowerOfTwo(mmap_pages));
   size_t page_size = sysconf(_SC_PAGE_SIZE);
   size_t mmap_len = (mmap_pages + 1) * page_size;
-  void* mmap_addr = mmap(nullptr, mmap_len, PROT_READ | PROT_WRITE, MAP_SHARED, perf_event_fd_, 0);
+  void* mmap_addr = mmap(nullptr, mmap_len, PROT_READ | PROT_WRITE, MAP_SHARED,
+                         perf_event_fd_, 0);
   if (mmap_addr == MAP_FAILED) {
-    PLOG(ERROR) << "mmap() failed for " << Name();
+    bool is_perm_error = (errno == EPERM);
+    if (report_error) {
+      PLOG(ERROR) << "mmap(" << mmap_pages << ") failed for " << Name();
+    } else {
+      PLOG(DEBUG) << "mmap(" << mmap_pages << ") failed for " << Name();
+    }
+    if (report_error && is_perm_error) {
+      LOG(ERROR)
+          << "It seems the kernel doesn't allow allocating enough "
+          << "buffer for dumping samples, consider decreasing mmap pages(-m).";
+    }
     return false;
   }
   mmap_addr_ = mmap_addr;
@@ -125,21 +150,54 @@
   return true;
 }
 
-size_t EventFd::GetAvailableMmapData(char** pdata) {
-  // The mmap_data_buffer is used as a ring buffer like below. The kernel continuously writes
-  // records to the buffer, and the user continuously read records out.
+bool EventFd::ShareMappedBuffer(const EventFd& event_fd, bool report_error) {
+  CHECK(!HasMappedBuffer());
+  CHECK(event_fd.HasMappedBuffer());
+  int result =
+      ioctl(perf_event_fd_, PERF_EVENT_IOC_SET_OUTPUT, event_fd.perf_event_fd_);
+  if (result != 0) {
+    if (report_error) {
+      PLOG(ERROR) << "failed to share mapped buffer of "
+                  << event_fd.perf_event_fd_ << " with " << perf_event_fd_;
+    }
+    return false;
+  }
+  return true;
+}
+
+void EventFd::DestroyMappedBuffer() {
+  if (HasMappedBuffer()) {
+    munmap(mmap_addr_, mmap_len_);
+    mmap_addr_ = nullptr;
+    mmap_len_ = 0;
+    mmap_metadata_page_ = nullptr;
+    mmap_data_buffer_ = nullptr;
+    mmap_data_buffer_size_ = 0;
+  }
+}
+
+size_t EventFd::GetAvailableMmapData(const char** pdata) {
+  if (!HasMappedBuffer()) {
+    return 0;
+  }
+  // The mmap_data_buffer is used as a ring buffer between the kernel and
+  // simpleperf. The kernel continuously writes records to the buffer, and
+  // simpleperf continuously read records out.
   //         _________________________________________
   // buffer | can write   |   can read   |  can write |
   //                      ^              ^
   //                    read_head       write_head
   //
-  // So the user can read records in [read_head, write_head), and the kernel can write records
-  // in [write_head, read_head). The kernel is responsible for updating write_head, and the user
-  // is responsible for updating read_head.
+  // So simpleperf can read records in [read_head, write_head), and the kernel
+  // can write records in [write_head, read_head). The kernel is responsible
+  // for updating write_head, and simpleperf is responsible for updating
+  // read_head.
 
   size_t buf_mask = mmap_data_buffer_size_ - 1;
-  size_t write_head = static_cast<size_t>(mmap_metadata_page_->data_head & buf_mask);
-  size_t read_head = static_cast<size_t>(mmap_metadata_page_->data_tail & buf_mask);
+  size_t write_head =
+      static_cast<size_t>(mmap_metadata_page_->data_head & buf_mask);
+  size_t read_head =
+      static_cast<size_t>(mmap_metadata_page_->data_tail & buf_mask);
 
   if (read_head == write_head) {
     // No available data.
@@ -149,8 +207,8 @@
   // Make sure we can see the data after the fence.
   std::atomic_thread_fence(std::memory_order_acquire);
 
-  // Copy records from mapped buffer to data_process_buffer. Note that records can be wrapped
-  // at the end of the mapped buffer.
+  // Copy records from mapped buffer to data_process_buffer. Note that records
+  // can be wrapped at the end of the mapped buffer.
   char* to = data_process_buffer_.data();
   if (read_head < write_head) {
     char* from = mmap_data_buffer_ + read_head;
@@ -177,13 +235,15 @@
   mmap_metadata_page_->data_tail += discard_size;
 }
 
-void EventFd::PreparePollForMmapData(pollfd* poll_fd) {
-  memset(poll_fd, 0, sizeof(pollfd));
-  poll_fd->fd = perf_event_fd_;
-  poll_fd->events = POLLIN;
+bool EventFd::StartPolling(IOEventLoop& loop,
+                           const std::function<bool()>& callback) {
+  ioevent_ref_ = loop.AddReadEvent(perf_event_fd_, callback);
+  return ioevent_ref_ != nullptr;
 }
 
+bool EventFd::StopPolling() { return IOEventLoop::DelEvent(ioevent_ref_); }
+
 bool IsEventAttrSupportedByKernel(perf_event_attr attr) {
-  auto event_fd = EventFd::OpenEventFile(attr, getpid(), -1, false);
+  auto event_fd = EventFd::OpenEventFile(attr, getpid(), -1, nullptr, false);
   return event_fd != nullptr;
 }
diff --git a/simpleperf/event_fd.h b/simpleperf/event_fd.h
index c1a7d75..bb5fb54 100644
--- a/simpleperf/event_fd.h
+++ b/simpleperf/event_fd.h
@@ -25,66 +25,84 @@
 
 #include <android-base/macros.h>
 
+#include "IOEventLoop.h"
 #include "perf_event.h"
 
 struct PerfCounter {
-  uint64_t value;         // The value of the event specified by the perf_event_file.
+  uint64_t value;  // The value of the event specified by the perf_event_file.
   uint64_t time_enabled;  // The enabled time.
   uint64_t time_running;  // The running time.
   uint64_t id;            // The id of the perf_event_file.
 };
 
-struct pollfd;
-
 // EventFd represents an opened perf_event_file.
 class EventFd {
  public:
-  static std::unique_ptr<EventFd> OpenEventFile(const perf_event_attr& attr, pid_t tid, int cpu,
+  static std::unique_ptr<EventFd> OpenEventFile(const perf_event_attr& attr,
+                                                pid_t tid, int cpu,
+                                                EventFd* group_event_fd,
                                                 bool report_error = true);
 
   ~EventFd();
 
+  // Give information about this perf_event_file, like (event_name, tid, cpu).
+  std::string Name() const;
+
   uint64_t Id() const;
 
-  pid_t ThreadId() const {
-    return tid_;
-  }
+  pid_t ThreadId() const { return tid_; }
 
-  int Cpu() const {
-    return cpu_;
-  }
+  int Cpu() const { return cpu_; }
+
+  const perf_event_attr& attr() const { return attr_; }
+
+  // It tells the kernel to start counting and recording events specified by
+  // this file.
+  bool EnableEvent();
 
   bool ReadCounter(PerfCounter* counter) const;
 
-  // Call mmap() for this perf_event_file, so we can read sampled records from mapped area.
+  // Create mapped buffer used to receive records sent by the kernel.
   // mmap_pages should be power of 2.
-  bool MmapContent(size_t mmap_pages);
+  bool CreateMappedBuffer(size_t mmap_pages, bool report_error);
 
-  // When the kernel writes new sampled records to the mapped area, we can get them by returning
-  // the start address and size of the data.
-  size_t GetAvailableMmapData(char** pdata);
+  // Share the mapped buffer used by event_fd. The two EventFds should monitor
+  // the same event on the same cpu, but have different thread ids.
+  bool ShareMappedBuffer(const EventFd& event_fd, bool report_error);
 
-  // Prepare pollfd for poll() to wait on available mmap_data.
-  void PreparePollForMmapData(pollfd* poll_fd);
+  bool HasMappedBuffer() const { return mmap_data_buffer_size_ != 0; }
+
+  void DestroyMappedBuffer();
+
+  // When the kernel writes new sampled records to the mapped area, we can get
+  // them by returning the start address and size of the data.
+  size_t GetAvailableMmapData(const char** pdata);
+
+  // [callback] is called when there is data available in the mapped buffer.
+  bool StartPolling(IOEventLoop& loop, const std::function<bool()>& callback);
+  bool StopPolling();
 
  private:
-  EventFd(int perf_event_fd, const std::string& event_name, pid_t tid, int cpu)
-      : perf_event_fd_(perf_event_fd),
+  EventFd(const perf_event_attr& attr, int perf_event_fd,
+          const std::string& event_name, pid_t tid, int cpu)
+      : attr_(attr),
+        perf_event_fd_(perf_event_fd),
         id_(0),
         event_name_(event_name),
         tid_(tid),
         cpu_(cpu),
         mmap_addr_(nullptr),
-        mmap_len_(0) {
-  }
+        mmap_len_(0),
+        mmap_metadata_page_(nullptr),
+        mmap_data_buffer_(nullptr),
+        mmap_data_buffer_size_(0),
+        ioevent_ref_(nullptr) {}
 
-  // Give information about this perf_event_file, like (event_name, tid, cpu).
-  std::string Name() const;
-
-  // Discard how much data we have read, so the kernel can reuse this part of mapped area to store
-  // new data.
+  // Discard how much data we have read, so the kernel can reuse this part of
+  // mapped area to store new data.
   void DiscardMmapData(size_t discard_size);
 
+  const perf_event_attr attr_;
   int perf_event_fd_;
   mutable uint64_t id_;
   const std::string event_name_;
@@ -94,15 +112,17 @@
   void* mmap_addr_;
   size_t mmap_len_;
   perf_event_mmap_page* mmap_metadata_page_;  // The first page of mmap_area.
-  char* mmap_data_buffer_;  // Starts from the second page of mmap_area, containing records written
-                            // by then kernel.
+  char* mmap_data_buffer_;  // Starting from the second page of mmap_area,
+                            // containing records written by then kernel.
   size_t mmap_data_buffer_size_;
 
-  // As mmap_data_buffer is a ring buffer, it is possible that one record is wrapped at the
-  // end of the buffer. So we need to copy records from mmap_data_buffer to data_process_buffer
-  // before processing them.
+  // As mmap_data_buffer is a ring buffer, it is possible that one record is
+  // wrapped at the end of the buffer. So we need to copy records from
+  // mmap_data_buffer to data_process_buffer before processing them.
   static std::vector<char> data_process_buffer_;
 
+  IOEventRef ioevent_ref_;
+
   DISALLOW_COPY_AND_ASSIGN(EventFd);
 };
 
diff --git a/simpleperf/event_selection_set.cpp b/simpleperf/event_selection_set.cpp
index fad8b1e..d6b1fe6 100644
--- a/simpleperf/event_selection_set.cpp
+++ b/simpleperf/event_selection_set.cpp
@@ -16,15 +16,14 @@
 
 #include "event_selection_set.h"
 
-#include <poll.h>
-
 #include <android-base/logging.h>
-#include <android-base/stringprintf.h>
 
 #include "environment.h"
 #include "event_attr.h"
 #include "event_type.h"
+#include "IOEventLoop.h"
 #include "perf_regs.h"
+#include "utils.h"
 
 bool IsBranchSamplingSupported() {
   const EventType* type = FindEventTypeByName("cpu-cycles");
@@ -43,117 +42,179 @@
     return false;
   }
   perf_event_attr attr = CreateDefaultPerfEventAttr(*type);
-  attr.sample_type |= PERF_SAMPLE_CALLCHAIN | PERF_SAMPLE_REGS_USER | PERF_SAMPLE_STACK_USER;
+  attr.sample_type |=
+      PERF_SAMPLE_CALLCHAIN | PERF_SAMPLE_REGS_USER | PERF_SAMPLE_STACK_USER;
   attr.exclude_callchain_user = 1;
   attr.sample_regs_user = GetSupportedRegMask(GetBuildArch());
   attr.sample_stack_user = 8192;
   return IsEventAttrSupportedByKernel(attr);
 }
 
-bool EventSelectionSet::AddEventType(const EventTypeAndModifier& event_type_modifier) {
-  EventSelection selection;
-  selection.event_type_modifier = event_type_modifier;
-  selection.event_attr = CreateDefaultPerfEventAttr(event_type_modifier.event_type);
-  selection.event_attr.exclude_user = event_type_modifier.exclude_user;
-  selection.event_attr.exclude_kernel = event_type_modifier.exclude_kernel;
-  selection.event_attr.exclude_hv = event_type_modifier.exclude_hv;
-  selection.event_attr.exclude_host = event_type_modifier.exclude_host;
-  selection.event_attr.exclude_guest = event_type_modifier.exclude_guest;
-  selection.event_attr.precise_ip = event_type_modifier.precise_ip;
-  if (!IsEventAttrSupportedByKernel(selection.event_attr)) {
-    LOG(ERROR) << "Event type '" << event_type_modifier.name << "' is not supported by the kernel";
+bool EventSelectionSet::BuildAndCheckEventSelection(
+    const std::string& event_name, EventSelection* selection) {
+  std::unique_ptr<EventTypeAndModifier> event_type = ParseEventType(event_name);
+  if (event_type == nullptr) {
     return false;
   }
-  selections_.push_back(std::move(selection));
+  if (for_stat_cmd_) {
+    if (event_type->event_type.name == "cpu-clock" ||
+        event_type->event_type.name == "task-clock") {
+      if (event_type->exclude_user || event_type->exclude_kernel) {
+        LOG(ERROR) << "Modifier u and modifier k used in event type "
+                   << event_type->event_type.name
+                   << " are not supported by the kernel.";
+        return false;
+      }
+    }
+  }
+  selection->event_type_modifier = *event_type;
+  selection->event_attr = CreateDefaultPerfEventAttr(event_type->event_type);
+  selection->event_attr.exclude_user = event_type->exclude_user;
+  selection->event_attr.exclude_kernel = event_type->exclude_kernel;
+  selection->event_attr.exclude_hv = event_type->exclude_hv;
+  selection->event_attr.exclude_host = event_type->exclude_host;
+  selection->event_attr.exclude_guest = event_type->exclude_guest;
+  selection->event_attr.precise_ip = event_type->precise_ip;
+  if (!IsEventAttrSupportedByKernel(selection->event_attr)) {
+    LOG(ERROR) << "Event type '" << event_type->name
+               << "' is not supported by the kernel";
+    return false;
+  }
+  selection->event_fds.clear();
+
+  for (const auto& group : groups_) {
+    for (const auto& sel : group) {
+      if (sel.event_type_modifier.name == selection->event_type_modifier.name) {
+        LOG(ERROR) << "Event type '" << sel.event_type_modifier.name
+                   << "' appears more than once";
+        return false;
+      }
+    }
+  }
+  return true;
+}
+
+bool EventSelectionSet::AddEventType(const std::string& event_name) {
+  return AddEventGroup(std::vector<std::string>(1, event_name));
+}
+
+bool EventSelectionSet::AddEventGroup(
+    const std::vector<std::string>& event_names) {
+  EventSelectionGroup group;
+  for (const auto& event_name : event_names) {
+    EventSelection selection;
+    if (!BuildAndCheckEventSelection(event_name, &selection)) {
+      return false;
+    }
+    selection.selection_id = group.size();
+    selection.group_id = groups_.size();
+    group.push_back(std::move(selection));
+  }
+  groups_.push_back(std::move(group));
   UnionSampleType();
   return true;
 }
 
-// Union the sample type of different event attrs can make reading sample records in perf.data
-// easier.
+// Union the sample type of different event attrs can make reading sample
+// records in perf.data easier.
 void EventSelectionSet::UnionSampleType() {
   uint64_t sample_type = 0;
-  for (auto& selection : selections_) {
-    sample_type |= selection.event_attr.sample_type;
+  for (const auto& group : groups_) {
+    for (const auto& selection : group) {
+      sample_type |= selection.event_attr.sample_type;
+    }
   }
-  for (auto& selection : selections_) {
-    selection.event_attr.sample_type = sample_type;
+  for (auto& group : groups_) {
+    for (auto& selection : group) {
+      selection.event_attr.sample_type = sample_type;
+    }
   }
 }
 
 void EventSelectionSet::SetEnableOnExec(bool enable) {
-  for (auto& selection : selections_) {
-    // If sampling is enabled on exec, then it is disabled at startup, otherwise
-    // it should be enabled at startup. Don't use ioctl(PERF_EVENT_IOC_ENABLE)
-    // to enable it after perf_event_open(). Because some android kernels can't
-    // handle ioctl() well when cpu-hotplug happens. See http://b/25193162.
-    if (enable) {
-      selection.event_attr.enable_on_exec = 1;
-      selection.event_attr.disabled = 1;
-    } else {
-      selection.event_attr.enable_on_exec = 0;
-      selection.event_attr.disabled = 0;
+  for (auto& group : groups_) {
+    for (auto& selection : group) {
+      // If sampling is enabled on exec, then it is disabled at startup,
+      // otherwise it should be enabled at startup. Don't use
+      // ioctl(PERF_EVENT_IOC_ENABLE) to enable it after perf_event_open().
+      // Because some android kernels can't handle ioctl() well when cpu-hotplug
+      // happens. See http://b/25193162.
+      if (enable) {
+        selection.event_attr.enable_on_exec = 1;
+        selection.event_attr.disabled = 1;
+      } else {
+        selection.event_attr.enable_on_exec = 0;
+        selection.event_attr.disabled = 0;
+      }
     }
   }
 }
 
 bool EventSelectionSet::GetEnableOnExec() {
-  for (auto& selection : selections_) {
-    if (selection.event_attr.enable_on_exec == 0) {
-      return false;
+  for (const auto& group : groups_) {
+    for (const auto& selection : group) {
+      if (selection.event_attr.enable_on_exec == 0) {
+        return false;
+      }
     }
   }
   return true;
 }
 
 void EventSelectionSet::SampleIdAll() {
-  for (auto& selection : selections_) {
-    selection.event_attr.sample_id_all = 1;
+  for (auto& group : groups_) {
+    for (auto& selection : group) {
+      selection.event_attr.sample_id_all = 1;
+    }
   }
 }
 
-void EventSelectionSet::SetSampleFreq(uint64_t sample_freq) {
-  for (auto& selection : selections_) {
-    perf_event_attr& attr = selection.event_attr;
-    attr.freq = 1;
-    attr.sample_freq = sample_freq;
-  }
+void EventSelectionSet::SetSampleFreq(const EventSelection& selection,
+                                      uint64_t sample_freq) {
+  EventSelection& sel = groups_[selection.group_id][selection.selection_id];
+  sel.event_attr.freq = 1;
+  sel.event_attr.sample_freq = sample_freq;
 }
 
-void EventSelectionSet::SetSamplePeriod(uint64_t sample_period) {
-  for (auto& selection : selections_) {
-    perf_event_attr& attr = selection.event_attr;
-    attr.freq = 0;
-    attr.sample_period = sample_period;
-  }
+void EventSelectionSet::SetSamplePeriod(const EventSelection& selection,
+                                        uint64_t sample_period) {
+  EventSelection& sel = groups_[selection.group_id][selection.selection_id];
+  sel.event_attr.freq = 0;
+  sel.event_attr.sample_period = sample_period;
 }
 
 bool EventSelectionSet::SetBranchSampling(uint64_t branch_sample_type) {
   if (branch_sample_type != 0 &&
-      (branch_sample_type & (PERF_SAMPLE_BRANCH_ANY | PERF_SAMPLE_BRANCH_ANY_CALL |
-                             PERF_SAMPLE_BRANCH_ANY_RETURN | PERF_SAMPLE_BRANCH_IND_CALL)) == 0) {
-    LOG(ERROR) << "Invalid branch_sample_type: 0x" << std::hex << branch_sample_type;
+      (branch_sample_type &
+       (PERF_SAMPLE_BRANCH_ANY | PERF_SAMPLE_BRANCH_ANY_CALL |
+        PERF_SAMPLE_BRANCH_ANY_RETURN | PERF_SAMPLE_BRANCH_IND_CALL)) == 0) {
+    LOG(ERROR) << "Invalid branch_sample_type: 0x" << std::hex
+               << branch_sample_type;
     return false;
   }
   if (branch_sample_type != 0 && !IsBranchSamplingSupported()) {
     LOG(ERROR) << "branch stack sampling is not supported on this device.";
     return false;
   }
-  for (auto& selection : selections_) {
-    perf_event_attr& attr = selection.event_attr;
-    if (branch_sample_type != 0) {
-      attr.sample_type |= PERF_SAMPLE_BRANCH_STACK;
-    } else {
-      attr.sample_type &= ~PERF_SAMPLE_BRANCH_STACK;
+  for (auto& group : groups_) {
+    for (auto& selection : group) {
+      perf_event_attr& attr = selection.event_attr;
+      if (branch_sample_type != 0) {
+        attr.sample_type |= PERF_SAMPLE_BRANCH_STACK;
+      } else {
+        attr.sample_type &= ~PERF_SAMPLE_BRANCH_STACK;
+      }
+      attr.branch_sample_type = branch_sample_type;
     }
-    attr.branch_sample_type = branch_sample_type;
   }
   return true;
 }
 
 void EventSelectionSet::EnableFpCallChainSampling() {
-  for (auto& selection : selections_) {
-    selection.event_attr.sample_type |= PERF_SAMPLE_CALLCHAIN;
+  for (auto& group : groups_) {
+    for (auto& selection : group) {
+      selection.event_attr.sample_type |= PERF_SAMPLE_CALLCHAIN;
+    }
   }
 }
 
@@ -162,26 +223,41 @@
     LOG(ERROR) << "dwarf callchain sampling is not supported on this device.";
     return false;
   }
-  for (auto& selection : selections_) {
-    selection.event_attr.sample_type |=
-        PERF_SAMPLE_CALLCHAIN | PERF_SAMPLE_REGS_USER | PERF_SAMPLE_STACK_USER;
-    selection.event_attr.exclude_callchain_user = 1;
-    selection.event_attr.sample_regs_user = GetSupportedRegMask(GetBuildArch());
-    selection.event_attr.sample_stack_user = dump_stack_size;
+  for (auto& group : groups_) {
+    for (auto& selection : group) {
+      selection.event_attr.sample_type |= PERF_SAMPLE_CALLCHAIN |
+                                          PERF_SAMPLE_REGS_USER |
+                                          PERF_SAMPLE_STACK_USER;
+      selection.event_attr.exclude_callchain_user = 1;
+      selection.event_attr.sample_regs_user =
+          GetSupportedRegMask(GetBuildArch());
+      selection.event_attr.sample_stack_user = dump_stack_size;
+    }
   }
   return true;
 }
 
 void EventSelectionSet::SetInherit(bool enable) {
-  for (auto& selection : selections_) {
-    selection.event_attr.inherit = (enable ? 1 : 0);
+  for (auto& group : groups_) {
+    for (auto& selection : group) {
+      selection.event_attr.inherit = (enable ? 1 : 0);
+    }
+  }
+}
+
+void EventSelectionSet::SetLowWatermark() {
+  for (auto& group : groups_) {
+    for (auto& selection : group) {
+      selection.event_attr.wakeup_events = 1;
+    }
   }
 }
 
 static bool CheckIfCpusOnline(const std::vector<int>& cpus) {
   std::vector<int> online_cpus = GetOnlineCpus();
   for (const auto& cpu : cpus) {
-    if (std::find(online_cpus.begin(), online_cpus.end(), cpu) == online_cpus.end()) {
+    if (std::find(online_cpus.begin(), online_cpus.end(), cpu) ==
+        online_cpus.end()) {
       LOG(ERROR) << "cpu " << cpu << " is not online.";
       return false;
     }
@@ -189,41 +265,73 @@
   return true;
 }
 
-bool EventSelectionSet::OpenEventFilesForCpus(const std::vector<int>& cpus) {
-  return OpenEventFilesForThreadsOnCpus({-1}, cpus);
+static bool OpenEventFile(EventSelectionGroup& group, pid_t tid, int cpu,
+                          std::string* failed_event_type) {
+  std::vector<std::unique_ptr<EventFd>> event_fds;
+  // Given a tid and cpu, events on the same group should be all opened
+  // successfully or all failed to open.
+  for (auto& selection : group) {
+    EventFd* group_fd = nullptr;
+    if (selection.selection_id != 0) {
+      group_fd = event_fds[0].get();
+    }
+    std::unique_ptr<EventFd> event_fd =
+        EventFd::OpenEventFile(selection.event_attr, tid, cpu, group_fd);
+    if (event_fd != nullptr) {
+      LOG(VERBOSE) << "OpenEventFile for " << event_fd->Name();
+      event_fds.push_back(std::move(event_fd));
+    } else {
+      if (failed_event_type != nullptr) {
+        *failed_event_type = selection.event_type_modifier.name;
+        return false;
+      }
+    }
+  }
+  for (size_t i = 0; i < group.size(); ++i) {
+    group[i].event_fds.push_back(std::move(event_fds[i]));
+  }
+  return true;
 }
 
-bool EventSelectionSet::OpenEventFilesForThreadsOnCpus(const std::vector<pid_t>& threads,
-                                                       std::vector<int> cpus) {
+static std::set<pid_t> PrepareThreads(const std::set<pid_t>& processes,
+                                      const std::set<pid_t>& threads) {
+  std::set<pid_t> result = threads;
+  for (const auto& pid : processes) {
+    std::vector<pid_t> tids = GetThreadsInProcess(pid);
+    result.insert(tids.begin(), tids.end());
+  }
+  return result;
+}
+
+bool EventSelectionSet::OpenEventFiles(const std::vector<int>& on_cpus) {
+  std::vector<int> cpus = on_cpus;
   if (!cpus.empty()) {
-    if (!CheckIfCpusOnline(cpus)) {
+    // cpus = {-1} means open an event file for all cpus.
+    if (!(cpus.size() == 1 && cpus[0] == -1) && !CheckIfCpusOnline(cpus)) {
       return false;
     }
   } else {
     cpus = GetOnlineCpus();
   }
-  return OpenEventFiles(threads, cpus);
-}
-
-bool EventSelectionSet::OpenEventFiles(const std::vector<pid_t>& threads,
-                                       const std::vector<int>& cpus) {
-  for (auto& selection : selections_) {
-    for (auto& tid : threads) {
-      size_t open_per_thread = 0;
-      for (auto& cpu : cpus) {
-        auto event_fd = EventFd::OpenEventFile(selection.event_attr, tid, cpu);
-        if (event_fd != nullptr) {
-          LOG(VERBOSE) << "OpenEventFile for tid " << tid << ", cpu " << cpu;
-          selection.event_fds.push_back(std::move(event_fd));
-          ++open_per_thread;
+  std::set<pid_t> threads = PrepareThreads(processes_, threads_);
+  for (auto& group : groups_) {
+    for (const auto& tid : threads) {
+      size_t success_cpu_count = 0;
+      std::string failed_event_type;
+      for (const auto& cpu : cpus) {
+        if (OpenEventFile(group, tid, cpu, &failed_event_type)) {
+          success_cpu_count++;
         }
       }
-      // As the online cpus can be enabled or disabled at runtime, we may not open event file for
-      // all cpus successfully. But we should open at least one cpu successfully.
-      if (open_per_thread == 0) {
+      // As the online cpus can be enabled or disabled at runtime, we may not
+      // open event file for all cpus successfully. But we should open at least
+      // one cpu successfully.
+      if (success_cpu_count == 0) {
         PLOG(ERROR) << "failed to open perf event file for event_type "
-                    << selection.event_type_modifier.name << " for "
-                    << (tid == -1 ? "all threads" : android::base::StringPrintf(" thread %d", tid));
+                    << failed_event_type << " for "
+                    << (tid == -1 ? "all threads"
+                                  : "thread " + std::to_string(tid))
+                    << " on all cpus";
         return false;
       }
     }
@@ -231,74 +339,70 @@
   return true;
 }
 
+static bool ReadCounter(const EventFd* event_fd, CounterInfo* counter) {
+  if (!event_fd->ReadCounter(&counter->counter)) {
+    return false;
+  }
+  counter->tid = event_fd->ThreadId();
+  counter->cpu = event_fd->Cpu();
+  return true;
+}
+
 bool EventSelectionSet::ReadCounters(std::vector<CountersInfo>* counters) {
   counters->clear();
-  for (auto& selection : selections_) {
-    CountersInfo counters_info;
-    counters_info.event_type = &selection.event_type_modifier;
-    for (auto& event_fd : selection.event_fds) {
-      CountersInfo::CounterInfo counter_info;
-      if (!event_fd->ReadCounter(&counter_info.counter)) {
-        return false;
-      }
-      counter_info.tid = event_fd->ThreadId();
-      counter_info.cpu = event_fd->Cpu();
-      counters_info.counters.push_back(counter_info);
-    }
-    counters->push_back(counters_info);
-  }
-  return true;
-}
-
-void EventSelectionSet::PreparePollForEventFiles(std::vector<pollfd>* pollfds) {
-  for (auto& selection : selections_) {
-    for (auto& event_fd : selection.event_fds) {
-      pollfd poll_fd;
-      event_fd->PreparePollForMmapData(&poll_fd);
-      pollfds->push_back(poll_fd);
-    }
-  }
-}
-
-bool EventSelectionSet::MmapEventFiles(size_t mmap_pages) {
-  for (auto& selection : selections_) {
-    for (auto& event_fd : selection.event_fds) {
-      if (!event_fd->MmapContent(mmap_pages)) {
-        return false;
-      }
-    }
-  }
-  return true;
-}
-
-static bool ReadMmapEventDataForFd(std::unique_ptr<EventFd>& event_fd,
-                                   std::function<bool(const char*, size_t)> callback,
-                                   bool* have_data) {
-  *have_data = false;
-  while (true) {
-    char* data;
-    size_t size = event_fd->GetAvailableMmapData(&data);
-    if (size == 0) {
-      break;
-    }
-    if (!callback(data, size)) {
-      return false;
-    }
-    *have_data = true;
-  }
-  return true;
-}
-
-bool EventSelectionSet::ReadMmapEventData(std::function<bool(const char*, size_t)> callback) {
-  for (auto& selection : selections_) {
-    for (auto& event_fd : selection.event_fds) {
-      while (true) {
-        bool have_data;
-        if (!ReadMmapEventDataForFd(event_fd, callback, &have_data)) {
+  for (auto& group : groups_) {
+    for (auto& selection : group) {
+      CountersInfo counters_info;
+      counters_info.selection = &selection;
+      counters_info.counters = selection.hotplugged_counters;
+      for (auto& event_fd : selection.event_fds) {
+        CounterInfo counter;
+        if (!ReadCounter(event_fd.get(), &counter)) {
           return false;
         }
-        if (!have_data) {
-          break;
+        counters_info.counters.push_back(counter);
+      }
+      counters->push_back(counters_info);
+    }
+  }
+  return true;
+}
+
+bool EventSelectionSet::MmapEventFiles(size_t min_mmap_pages,
+                                       size_t max_mmap_pages) {
+  for (size_t i = max_mmap_pages; i >= min_mmap_pages; i >>= 1) {
+    if (MmapEventFiles(i, i == min_mmap_pages)) {
+      LOG(VERBOSE) << "Mapped buffer size is " << i << " pages.";
+      mmap_pages_ = i;
+      return true;
+    }
+    for (auto& group : groups_) {
+      for (auto& selection : group) {
+        for (auto& event_fd : selection.event_fds) {
+          event_fd->DestroyMappedBuffer();
+        }
+      }
+    }
+  }
+  return false;
+}
+
+bool EventSelectionSet::MmapEventFiles(size_t mmap_pages, bool report_error) {
+  // Allocate a mapped buffer for each cpu.
+  std::map<int, EventFd*> cpu_map;
+  for (auto& group : groups_) {
+    for (auto& selection : group) {
+      for (auto& event_fd : selection.event_fds) {
+        auto it = cpu_map.find(event_fd->Cpu());
+        if (it != cpu_map.end()) {
+          if (!event_fd->ShareMappedBuffer(*(it->second), report_error)) {
+            return false;
+          }
+        } else {
+          if (!event_fd->CreateMappedBuffer(mmap_pages, report_error)) {
+            return false;
+          }
+          cpu_map[event_fd->Cpu()] = event_fd.get();
         }
       }
     }
@@ -306,24 +410,210 @@
   return true;
 }
 
-EventSelectionSet::EventSelection* EventSelectionSet::FindSelectionByType(
-    const EventTypeAndModifier& event_type_modifier) {
-  for (auto& selection : selections_) {
-    if (selection.event_type_modifier.name == event_type_modifier.name) {
-      return &selection;
+bool EventSelectionSet::PrepareToReadMmapEventData(
+    IOEventLoop& loop, const std::function<bool(Record*)>& callback) {
+  // Add read Events for perf event files having mapped buffer.
+  for (auto& group : groups_) {
+    for (auto& selection : group) {
+      for (auto& event_fd : selection.event_fds) {
+        if (event_fd->HasMappedBuffer()) {
+          if (!event_fd->StartPolling(loop, [&]() {
+                return ReadMmapEventDataForFd(event_fd.get());
+              })) {
+            return false;
+          }
+        }
+      }
     }
   }
-  return nullptr;
+  loop_ = &loop;
+
+  // Prepare record callback function.
+  record_callback_ = callback;
+  return true;
 }
 
-const perf_event_attr* EventSelectionSet::FindEventAttrByType(
-    const EventTypeAndModifier& event_type_modifier) {
-  EventSelection* selection = FindSelectionByType(event_type_modifier);
-  return (selection != nullptr) ? &selection->event_attr : nullptr;
+bool EventSelectionSet::ReadMmapEventDataForFd(EventFd* event_fd) {
+  const char* data;
+  // Call GetAvailableMmapData() only once instead of calling in a loop, because
+  // 1) A mapped buffer caches data before needing to be read again. By default
+  //    it raises read Event when half full.
+  // 2) Spinning on one mapped buffer can make other mapped buffers overflow.
+  size_t size = event_fd->GetAvailableMmapData(&data);
+  if (size == 0) {
+    return true;
+  }
+  std::vector<std::unique_ptr<Record>> records =
+      ReadRecordsFromBuffer(event_fd->attr(), data, size);
+  for (auto& r : records) {
+    if (!record_callback_(r.get())) {
+      return false;
+    }
+  }
+  return true;
 }
 
-const std::vector<std::unique_ptr<EventFd>>* EventSelectionSet::FindEventFdsByType(
-    const EventTypeAndModifier& event_type_modifier) {
-  EventSelection* selection = FindSelectionByType(event_type_modifier);
-  return (selection != nullptr) ? &selection->event_fds : nullptr;
+bool EventSelectionSet::FinishReadMmapEventData() {
+  // Read each mapped buffer once, because some data may exist in the buffers
+  // but is not much enough to raise read Events.
+  for (auto& group : groups_) {
+    for (auto& selection : group) {
+      for (auto& event_fd : selection.event_fds) {
+        if (event_fd->HasMappedBuffer()) {
+          if (!ReadMmapEventDataForFd(event_fd.get())) {
+            return false;
+          }
+        }
+      }
+    }
+  }
+  return true;
+}
+
+bool EventSelectionSet::HandleCpuHotplugEvents(
+    IOEventLoop& loop, const std::vector<int>& monitored_cpus,
+    double check_interval_in_sec) {
+  monitored_cpus_.insert(monitored_cpus.begin(), monitored_cpus.end());
+  online_cpus_ = GetOnlineCpus();
+  if (!loop.AddPeriodicEvent(SecondToTimeval(check_interval_in_sec),
+                             [&]() { return DetectCpuHotplugEvents(); })) {
+    return false;
+  }
+  return true;
+}
+
+bool EventSelectionSet::DetectCpuHotplugEvents() {
+  std::vector<int> new_cpus = GetOnlineCpus();
+  for (const auto& cpu : online_cpus_) {
+    if (std::find(new_cpus.begin(), new_cpus.end(), cpu) == new_cpus.end()) {
+      if (monitored_cpus_.empty() ||
+          monitored_cpus_.find(cpu) != monitored_cpus_.end()) {
+        LOG(INFO) << "Cpu " << cpu << " is offlined";
+        if (!HandleCpuOfflineEvent(cpu)) {
+          return false;
+        }
+      }
+    }
+  }
+  for (const auto& cpu : new_cpus) {
+    if (std::find(online_cpus_.begin(), online_cpus_.end(), cpu) ==
+        online_cpus_.end()) {
+      if (monitored_cpus_.empty() ||
+          monitored_cpus_.find(cpu) != monitored_cpus_.end()) {
+        LOG(INFO) << "Cpu " << cpu << " is onlined";
+        if (!HandleCpuOnlineEvent(cpu)) {
+          return false;
+        }
+      }
+    }
+  }
+  online_cpus_ = new_cpus;
+  return true;
+}
+
+bool EventSelectionSet::HandleCpuOfflineEvent(int cpu) {
+  for (auto& group : groups_) {
+    for (auto& selection : group) {
+      for (auto it = selection.event_fds.begin();
+           it != selection.event_fds.end();) {
+        if ((*it)->Cpu() == cpu) {
+          if (for_stat_cmd_) {
+            CounterInfo counter;
+            if (!ReadCounter(it->get(), &counter)) {
+              return false;
+            }
+            selection.hotplugged_counters.push_back(counter);
+          } else {
+            if ((*it)->HasMappedBuffer()) {
+              if (!ReadMmapEventDataForFd(it->get())) {
+                return false;
+              }
+              if (!(*it)->StopPolling()) {
+                return false;
+              }
+            }
+          }
+          it = selection.event_fds.erase(it);
+        } else {
+          ++it;
+        }
+      }
+    }
+  }
+  return true;
+}
+
+bool EventSelectionSet::HandleCpuOnlineEvent(int cpu) {
+  // We need to start profiling when opening new event files.
+  SetEnableOnExec(false);
+  std::set<pid_t> threads = PrepareThreads(processes_, threads_);
+  for (auto& group : groups_) {
+    for (const auto& tid : threads) {
+      std::string failed_event_type;
+      if (!OpenEventFile(group, tid, cpu, &failed_event_type)) {
+        // If failed to open event files, maybe the cpu has been offlined.
+        PLOG(WARNING) << "failed to open perf event file for event_type "
+                      << failed_event_type << " for "
+                      << (tid == -1 ? "all threads"
+                                    : "thread " + std::to_string(tid))
+                      << " on cpu " << cpu;
+      }
+    }
+  }
+  if (!for_stat_cmd_) {
+    // Prepare mapped buffer.
+    if (!CreateMappedBufferForCpu(cpu)) {
+      return false;
+    }
+    // Send a EventIdRecord.
+    std::vector<uint64_t> event_id_data;
+    uint64_t attr_id = 0;
+    for (const auto& group : groups_) {
+      for (const auto& selection : group) {
+        for (const auto& event_fd : selection.event_fds) {
+          if (event_fd->Cpu() == cpu) {
+            event_id_data.push_back(attr_id);
+            event_id_data.push_back(event_fd->Id());
+          }
+        }
+        ++attr_id;
+      }
+    }
+    EventIdRecord r(event_id_data);
+    if (!record_callback_(&r)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool EventSelectionSet::CreateMappedBufferForCpu(int cpu) {
+  EventFd* fd_with_buffer = nullptr;
+  for (auto& group : groups_) {
+    for (auto& selection : group) {
+      for (auto& event_fd : selection.event_fds) {
+        if (event_fd->Cpu() != cpu) {
+          continue;
+        }
+        if (fd_with_buffer == nullptr) {
+          if (!event_fd->CreateMappedBuffer(mmap_pages_, true)) {
+            return false;
+          }
+          fd_with_buffer = event_fd.get();
+        } else {
+          if (!event_fd->ShareMappedBuffer(*fd_with_buffer, true)) {
+            fd_with_buffer->DestroyMappedBuffer();
+            return false;
+          }
+        }
+      }
+    }
+  }
+  if (fd_with_buffer != nullptr &&
+      !fd_with_buffer->StartPolling(*loop_, [this, fd_with_buffer]() {
+        return ReadMmapEventDataForFd(fd_with_buffer);
+      })) {
+    return false;
+  }
+  return true;
 }
diff --git a/simpleperf/event_selection_set.h b/simpleperf/event_selection_set.h
index 746abfa..53c8edd 100644
--- a/simpleperf/event_selection_set.h
+++ b/simpleperf/event_selection_set.h
@@ -19,6 +19,8 @@
 
 #include <functional>
 #include <map>
+#include <set>
+#include <unordered_map>
 #include <vector>
 
 #include <android-base/macros.h>
@@ -26,72 +28,132 @@
 #include "event_fd.h"
 #include "event_type.h"
 #include "perf_event.h"
+#include "record.h"
+
+constexpr double DEFAULT_PERIOD_TO_DETECT_CPU_HOTPLUG_EVENTS_IN_SEC = 0.5;
+
+struct CounterInfo {
+  pid_t tid;
+  int cpu;
+  PerfCounter counter;
+};
+
+struct EventSelection;
 
 struct CountersInfo {
-  const EventTypeAndModifier* event_type;
-  struct CounterInfo {
-    pid_t tid;
-    int cpu;
-    PerfCounter counter;
-  };
+  const EventSelection* selection;
   std::vector<CounterInfo> counters;
 };
 
-struct pollfd;
+struct EventSelection {
+  uint32_t group_id;
+  uint32_t selection_id;
+  EventTypeAndModifier event_type_modifier;
+  perf_event_attr event_attr;
+  std::vector<std::unique_ptr<EventFd>> event_fds;
+  // counters for event files closed for cpu hotplug events
+  std::vector<CounterInfo> hotplugged_counters;
+};
 
-// EventSelectionSet helps to monitor events.
-// Firstly, the user creates an EventSelectionSet, and adds the specific event types to monitor.
-// Secondly, the user defines how to monitor the events (by setting enable_on_exec flag,
-// sample frequency, etc).
-// Then, the user can start monitoring by ordering the EventSelectionSet to open perf event files
-// and enable events (if enable_on_exec flag isn't used).
-// After that, the user can read counters or read mapped event records.
-// At last, the EventSelectionSet will clean up resources at destruction automatically.
+typedef std::vector<EventSelection> EventSelectionGroup;
+
+class IOEventLoop;
+
+// EventSelectionSet helps to monitor events. It is used in following steps:
+// 1. Create an EventSelectionSet, and add event types to monitor by calling
+//    AddEventType() or AddEventGroup().
+// 2. Define how to monitor events by calling SetEnableOnExec(), SampleIdAll(),
+//    SetSampleFreq(), etc.
+// 3. Start monitoring by calling OpenEventFilesForCpus() or
+//    OpenEventFilesForThreadsOnCpus(). If SetEnableOnExec() has been called
+//    in step 2, monitor will be delayed until the monitored thread calls
+//    exec().
+// 4. Read counters by calling ReadCounters(), or read mapped event records
+//    by calling MmapEventFiles(), PrepareToReadMmapEventData() and
+//    FinishReadMmapEventData().
+// 5. Stop monitoring automatically in the destructor of EventSelectionSet by
+//    closing perf event files.
 
 class EventSelectionSet {
  public:
-  EventSelectionSet() {
-  }
+  EventSelectionSet(bool for_stat_cmd)
+      : for_stat_cmd_(for_stat_cmd), mmap_pages_(0), loop_(nullptr) {}
 
-  bool Empty() const {
-    return selections_.empty();
-  }
+  bool empty() const { return groups_.empty(); }
 
-  bool AddEventType(const EventTypeAndModifier& event_type_modifier);
+  const std::vector<EventSelectionGroup>& groups() { return groups_; }
+
+  bool AddEventType(const std::string& event_name);
+  bool AddEventGroup(const std::vector<std::string>& event_names);
 
   void SetEnableOnExec(bool enable);
   bool GetEnableOnExec();
   void SampleIdAll();
-  void SetSampleFreq(uint64_t sample_freq);
-  void SetSamplePeriod(uint64_t sample_period);
+  void SetSampleFreq(const EventSelection& selection, uint64_t sample_freq);
+  void SetSamplePeriod(const EventSelection& selection, uint64_t sample_period);
   bool SetBranchSampling(uint64_t branch_sample_type);
   void EnableFpCallChainSampling();
   bool EnableDwarfCallChainSampling(uint32_t dump_stack_size);
   void SetInherit(bool enable);
+  void SetLowWatermark();
 
-  bool OpenEventFilesForCpus(const std::vector<int>& cpus);
-  bool OpenEventFilesForThreadsOnCpus(const std::vector<pid_t>& threads, std::vector<int> cpus);
+  void AddMonitoredProcesses(const std::set<pid_t>& processes) {
+    processes_.insert(processes.begin(), processes.end());
+  }
+
+  void AddMonitoredThreads(const std::set<pid_t>& threads) {
+    threads_.insert(threads.begin(), threads.end());
+  }
+
+  const std::set<pid_t>& GetMonitoredProcesses() const {
+    return processes_;
+  }
+
+  const std::set<pid_t>& GetMonitoredThreads() const {
+    return threads_;
+  }
+
+  bool HasMonitoredTarget() const {
+    return !processes_.empty() || !threads_.empty();
+  }
+
+  bool OpenEventFiles(const std::vector<int>& on_cpus);
   bool ReadCounters(std::vector<CountersInfo>* counters);
-  void PreparePollForEventFiles(std::vector<pollfd>* pollfds);
-  bool MmapEventFiles(size_t mmap_pages);
-  bool ReadMmapEventData(std::function<bool(const char*, size_t)> callback);
+  bool MmapEventFiles(size_t min_mmap_pages, size_t max_mmap_pages);
+  bool PrepareToReadMmapEventData(IOEventLoop& loop,
+                                  const std::function<bool(Record*)>& callback);
+  bool FinishReadMmapEventData();
 
-  const perf_event_attr* FindEventAttrByType(const EventTypeAndModifier& event_type_modifier);
-  const std::vector<std::unique_ptr<EventFd>>* FindEventFdsByType(
-      const EventTypeAndModifier& event_type_modifier);
+  // If monitored_cpus is empty, monitor all cpus.
+  bool HandleCpuHotplugEvents(
+      IOEventLoop& loop, const std::vector<int>& monitored_cpus,
+      double check_interval_in_sec =
+          DEFAULT_PERIOD_TO_DETECT_CPU_HOTPLUG_EVENTS_IN_SEC);
 
  private:
+  bool BuildAndCheckEventSelection(const std::string& event_name,
+                                   EventSelection* selection);
   void UnionSampleType();
-  bool OpenEventFiles(const std::vector<pid_t>& threads, const std::vector<int>& cpus);
+  bool MmapEventFiles(size_t mmap_pages, bool report_error);
+  bool ReadMmapEventDataForFd(EventFd* event_fd);
 
-  struct EventSelection {
-    EventTypeAndModifier event_type_modifier;
-    perf_event_attr event_attr;
-    std::vector<std::unique_ptr<EventFd>> event_fds;
-  };
-  EventSelection* FindSelectionByType(const EventTypeAndModifier& event_type_modifier);
+  bool DetectCpuHotplugEvents();
+  bool HandleCpuOnlineEvent(int cpu);
+  bool HandleCpuOfflineEvent(int cpu);
+  bool CreateMappedBufferForCpu(int cpu);
 
-  std::vector<EventSelection> selections_;
+  const bool for_stat_cmd_;
+
+  std::vector<EventSelectionGroup> groups_;
+  std::set<pid_t> processes_;
+  std::set<pid_t> threads_;
+  size_t mmap_pages_;
+
+  IOEventLoop* loop_;
+  std::function<bool(Record*)> record_callback_;
+
+  std::set<int> monitored_cpus_;
+  std::vector<int> online_cpus_;
 
   DISALLOW_COPY_AND_ASSIGN(EventSelectionSet);
 };
diff --git a/simpleperf/event_type.cpp b/simpleperf/event_type.cpp
index 2eaafa2..bfa6aac 100644
--- a/simpleperf/event_type.cpp
+++ b/simpleperf/event_type.cpp
@@ -35,14 +35,14 @@
 
 static const std::vector<EventType> GetTracepointEventTypes() {
   std::vector<EventType> result;
+  if (!IsRoot()) {
+    // Not having permission to profile tracing events.
+    return result;
+  }
   const std::string tracepoint_dirname = "/sys/kernel/debug/tracing/events";
-  std::vector<std::string> system_dirs;
-  GetEntriesInDir(tracepoint_dirname, nullptr, &system_dirs);
-  for (auto& system_name : system_dirs) {
+  for (const auto& system_name : GetSubDirs(tracepoint_dirname)) {
     std::string system_path = tracepoint_dirname + "/" + system_name;
-    std::vector<std::string> event_dirs;
-    GetEntriesInDir(system_path, nullptr, &event_dirs);
-    for (auto& event_name : event_dirs) {
+    for (const auto& event_name : GetSubDirs(system_path)) {
       std::string id_path = system_path + "/" + event_name + "/id";
       std::string id_content;
       if (!android::base::ReadFileToString(id_path, &id_content)) {
@@ -74,15 +74,6 @@
   return event_type_array;
 }
 
-const EventType* FindEventTypeByConfig(uint32_t type, uint64_t config) {
-  for (auto& event_type : GetAllEventTypes()) {
-    if (event_type.type == type && event_type.config == config) {
-      return &event_type;
-    }
-  }
-  return nullptr;
-}
-
 const EventType* FindEventTypeByName(const std::string& name) {
   const EventType* result = nullptr;
   for (auto& event_type : GetAllEventTypes()) {
diff --git a/simpleperf/event_type.h b/simpleperf/event_type.h
index 4100125..12d83b3 100644
--- a/simpleperf/event_type.h
+++ b/simpleperf/event_type.h
@@ -41,7 +41,6 @@
 };
 
 const std::vector<EventType>& GetAllEventTypes();
-const EventType* FindEventTypeByConfig(uint32_t type, uint64_t config);
 const EventType* FindEventTypeByName(const std::string& name);
 
 struct EventTypeAndModifier {
diff --git a/simpleperf/get_test_data.h b/simpleperf/get_test_data.h
index 4aba379..339871e 100644
--- a/simpleperf/get_test_data.h
+++ b/simpleperf/get_test_data.h
@@ -24,18 +24,25 @@
 std::string GetTestData(const std::string& filename);
 const std::string& GetTestDataDir();
 
-bool IsRoot();
-
-// The source code of elf is testdata/elf_file_source.cpp.
+// The source code of elf and elf_with_mini_debug_info is testdata/elf_file_source.cpp.
 static const std::string ELF_FILE = "elf";
+static const std::string ELF_FILE_WITH_MINI_DEBUG_INFO = "elf_with_mini_debug_info";
 // perf.data is generated by sampling on three processes running different
 // executables: elf, t1, t2 (all generated by elf_file_source.cpp, but with different
 // executable name).
 static const std::string PERF_DATA = "perf.data";
+
+// perf_with_multiple_pids_and_tids.data is generated by sampling on two processes, each
+// process running two threads.
+static const std::string PERF_DATA_WITH_MULTIPLE_PIDS_AND_TIDS = "perf_with_multiple_pids_and_tids.data";
+
 // perf_g_fp.data is generated by sampling on one process running elf using --call-graph fp option.
 static const std::string CALLGRAPH_FP_PERF_DATA = "perf_g_fp.data";
 // perf_b.data is generated by sampling on one process running elf using -b option.
 static const std::string BRANCH_PERF_DATA = "perf_b.data";
+// perf_with_mini_debug_info.data is generated by sampling on one process running
+// elf_with_mini_debug_info.
+static const std::string PERF_DATA_WITH_MINI_DEBUG_INFO = "perf_with_mini_debug_info.data";
 
 static BuildId elf_file_build_id("0b12a384a9f4a3f3659b7171ca615dbec3a81f71");
 
@@ -63,4 +70,33 @@
 
 static BuildId native_lib_build_id("8ed5755a7fdc07586ca228b8ee21621bce2c7a97");
 
+// perf_with_two_event_types.data is generated by sampling using -e cpu-cycles,cpu-clock option.
+static const std::string PERF_DATA_WITH_TWO_EVENT_TYPES = "perf_with_two_event_types.data";
+
+// perf_with_kernel_symbol.data is generated by `sudo simpleperf record ls -l`.
+static const std::string PERF_DATA_WITH_KERNEL_SYMBOL = "perf_with_kernel_symbol.data";
+
+// perf_with_symbols.data is generated by `sudo simpleperf record --dump-symbols` a process calling func2(int,int).
+static const std::string PERF_DATA_WITH_SYMBOLS = "perf_with_symbols.data";
+// perf_with_symbols.data is generated by `sudo simpleperf record --dump-symbols` a process using
+// a binary having non zero min virtual address.
+static const std::string PERF_DATA_WITH_SYMBOLS_FOR_NONZERO_MINVADDR_DSO =
+    "perf_with_symbols_for_nonzero_minvaddr_dso.data";
+
+// perf_kmem_slab_callgraph.data is generated by `simpleperf kmem record --slab --call-graph fp -f 100 sleep 0.0001`.
+static const std::string PERF_DATA_WITH_KMEM_SLAB_CALLGRAPH_RECORD = "perf_with_kmem_slab_callgraph.data";
+
+
+// perf_for_build_id_check.data is generated by recording a process running
+// testdata/data/correct_symfs_for_build_id_check/elf_for_build_id_check.
+static const std::string PERF_DATA_FOR_BUILD_ID_CHECK = "perf_for_build_id_check.data";
+static const std::string CORRECT_SYMFS_FOR_BUILD_ID_CHECK = "data/correct_symfs_for_build_id_check";
+static const std::string WRONG_SYMFS_FOR_BUILD_ID_CHECK = "data/wrong_symfs_for_build_id_check";
+
+static const std::string SYMFS_FOR_NO_SYMBOL_TABLE_WARNING = "data/symfs_for_no_symbol_table_warning";
+static const std::string SYMFS_FOR_READ_ELF_FILE_WARNING = "data/symfs_for_read_elf_file_warning";
+
+// generated_by_linux_perf.data is generated by `perf record -F 1 -a -g -- sleep 0.1`.
+static const std::string PERF_DATA_GENERATED_BY_LINUX_PERF = "generated_by_linux_perf.data";
+
 #endif  // SIMPLE_PERF_GET_TEST_DATA_H_
diff --git a/simpleperf/gtest_main.cpp b/simpleperf/gtest_main.cpp
index 444a1c2..395bba1 100644
--- a/simpleperf/gtest_main.cpp
+++ b/simpleperf/gtest_main.cpp
@@ -33,7 +33,7 @@
 
 static std::string testdata_dir;
 
-#if defined(IN_CTS_TEST)
+#if defined(__ANDROID__)
 static const std::string testdata_section = ".testzipdata";
 
 static bool ExtractTestDataFromElfSection() {
@@ -42,8 +42,10 @@
     return false;
   }
   std::string content;
-  if (!ReadSectionFromElfFile("/proc/self/exe", testdata_section, &content)) {
-    LOG(ERROR) << "failed to read section " << testdata_section;
+  ElfStatus result = ReadSectionFromElfFile("/proc/self/exe", testdata_section, &content);
+  if (result != ElfStatus::NO_ERROR) {
+    LOG(ERROR) << "failed to read section " << testdata_section
+               << ": " << result;
     return false;
   }
   TemporaryFile tmp_file;
@@ -95,7 +97,6 @@
   return true;
 }
 
-#if defined(__ANDROID__)
 class SavedPerfHardenProperty {
  public:
   SavedPerfHardenProperty() {
@@ -108,10 +109,7 @@
 
   ~SavedPerfHardenProperty() {
     if (strlen(prop_value_) != 0) {
-      if (__system_property_set("security.perf_harden", prop_value_) != 0) {
-        PLOG(ERROR) << "failed to set security.perf_harden";
-        return;
-      }
+      __system_property_set("security.perf_harden", prop_value_);
       // Sleep one second to wait for security.perf_harden changing
       // /proc/sys/kernel/perf_event_paranoid.
       sleep(1);
@@ -131,8 +129,8 @@
   char prop_value_[PROP_VALUE_MAX];
   std::string paranoid_value_;
 };
+
 #endif  // defined(__ANDROID__)
-#endif  // defined(IN_CTS_TEST)
 
 int main(int argc, char** argv) {
   InitLogging(argv, android::base::StderrLogger);
@@ -158,7 +156,7 @@
   }
   android::base::ScopedLogSeverity severity(log_severity);
 
-#if defined(IN_CTS_TEST)
+#if defined(__ANDROID__)
   std::unique_ptr<TemporaryDir> tmp_dir;
   if (!::testing::GTEST_FLAG(list_tests) && testdata_dir.empty()) {
     tmp_dir.reset(new TemporaryDir);
@@ -169,13 +167,12 @@
     }
   }
 
-#if defined(__ANDROID__)
   // A cts test PerfEventParanoidTest.java is testing if
   // /proc/sys/kernel/perf_event_paranoid is 3, so restore perf_harden
   // value after current test to not break that test.
   SavedPerfHardenProperty saved_perf_harden;
 #endif
-#endif
+
   if (!::testing::GTEST_FLAG(list_tests) && testdata_dir.empty()) {
     printf("Usage: %s -t <testdata_dir>\n", argv[0]);
     return 1;
diff --git a/simpleperf/main.cpp b/simpleperf/main.cpp
index 0a73c2d..a8a8935 100644
--- a/simpleperf/main.cpp
+++ b/simpleperf/main.cpp
@@ -24,10 +24,12 @@
 #include "command.h"
 #include "utils.h"
 
+constexpr int SIMPLEPERF_VERSION = 1;
+
 int main(int argc, char** argv) {
   InitLogging(argv, android::base::StderrLogger);
   std::vector<std::string> args;
-  android::base::LogSeverity log_severity = android::base::WARNING;
+  android::base::LogSeverity log_severity = android::base::INFO;
 
   for (int i = 1; i < argc; ++i) {
     if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) {
@@ -43,6 +45,10 @@
         LOG(ERROR) << "Missing argument for --log option.\n";
         return 1;
       }
+    } else if (strcmp(argv[i], "--version") == 0) {
+      LOG(INFO) << "Simpleperf version " << SIMPLEPERF_VERSION << ", revision "
+                << SIMPLEPERF_REVISION;
+      return 0;
     } else {
       args.push_back(argv[i]);
     }
diff --git a/simpleperf/nonlinux_support/nonlinux_support.cpp b/simpleperf/nonlinux_support/nonlinux_support.cpp
index 7551d36..58c1ba0 100644
--- a/simpleperf/nonlinux_support/nonlinux_support.cpp
+++ b/simpleperf/nonlinux_support/nonlinux_support.cpp
@@ -21,14 +21,10 @@
 #include "environment.h"
 
 std::vector<uint64_t> UnwindCallChain(ArchType, const ThreadEntry&, const RegSet&,
-                                      const std::vector<char>&) {
+                                      const char*, size_t, bool) {
   return std::vector<uint64_t>();
 }
 
-bool ProcessKernelSymbols(const std::string&, std::function<bool(const KernelSymbol&)>) {
-  return false;
-}
-
 bool GetKernelBuildId(BuildId*) {
   return false;
 }
diff --git a/simpleperf/perf_regs.cpp b/simpleperf/perf_regs.cpp
index 29d144e..0b15398 100644
--- a/simpleperf/perf_regs.cpp
+++ b/simpleperf/perf_regs.cpp
@@ -21,6 +21,8 @@
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
 
+#include "perf_event.h"
+
 ArchType ScopedCurrentArch::current_arch = GetBuildArch();
 
 ArchType GetArchType(const std::string& arch) {
@@ -37,6 +39,53 @@
   return ARCH_UNSUPPORTED;
 }
 
+ArchType GetArchForAbi(ArchType machine_arch, int abi) {
+  if (abi == PERF_SAMPLE_REGS_ABI_32) {
+    if (machine_arch == ARCH_X86_64) {
+      return ARCH_X86_32;
+    }
+    if (machine_arch == ARCH_ARM64) {
+      return ARCH_ARM;
+    }
+  }
+  return machine_arch;
+}
+
+std::string GetArchString(ArchType arch) {
+  switch (arch) {
+    case ARCH_X86_32:
+      return "x86";
+    case ARCH_X86_64:
+      return "x86_64";
+    case ARCH_ARM64:
+      return "arm64";
+    case ARCH_ARM:
+      return "arm";
+    default:
+      break;
+  }
+  return "unknown";
+}
+
+// If strict_check, must have arch1 == arch2.
+// Otherwise, allow X86_32 with X86_64, ARM with ARM64.
+bool IsArchTheSame(ArchType arch1, ArchType arch2, bool strict_check) {
+  if (strict_check) {
+    return arch1 == arch2;
+  }
+  switch (arch1) {
+    case ARCH_X86_32:
+    case ARCH_X86_64:
+      return arch2 == ARCH_X86_32 || arch2 == ARCH_X86_64;
+    case ARCH_ARM64:
+    case ARCH_ARM:
+      return arch2 == ARCH_ARM64 || arch2 == ARCH_ARM;
+    default:
+      break;
+  }
+  return arch1 == arch2;
+}
+
 uint64_t GetSupportedRegMask(ArchType arch) {
   switch (arch) {
     case ARCH_X86_32:
@@ -107,7 +156,7 @@
   }
 }
 
-RegSet CreateRegSet(uint64_t valid_mask, const std::vector<uint64_t>& valid_regs) {
+RegSet CreateRegSet(uint64_t valid_mask, const uint64_t* valid_regs) {
   RegSet regs;
   regs.valid_mask = valid_mask;
   for (int i = 0, j = 0; i < 64; ++i) {
diff --git a/simpleperf/perf_regs.h b/simpleperf/perf_regs.h
index 9fc610f..ff13d4f 100644
--- a/simpleperf/perf_regs.h
+++ b/simpleperf/perf_regs.h
@@ -56,12 +56,15 @@
 }
 
 ArchType GetArchType(const std::string& arch);
+ArchType GetArchForAbi(ArchType machine_arch, int abi);
+std::string GetArchString(ArchType arch);
+bool IsArchTheSame(ArchType arch1, ArchType arch2, bool strict_check);
 uint64_t GetSupportedRegMask(ArchType arch);
 std::string GetRegName(size_t regno, ArchType arch);
 
 class ScopedCurrentArch {
  public:
-  ScopedCurrentArch(ArchType arch) : saved_arch(current_arch) {
+  explicit ScopedCurrentArch(ArchType arch) : saved_arch(current_arch) {
     current_arch = arch;
   }
   ~ScopedCurrentArch() {
@@ -81,7 +84,7 @@
   uint64_t data[64];
 };
 
-RegSet CreateRegSet(uint64_t valid_mask, const std::vector<uint64_t>& valid_regs);
+RegSet CreateRegSet(uint64_t valid_mask, const uint64_t* valid_regs);
 
 bool GetRegValue(const RegSet& regs, size_t regno, uint64_t* value);
 bool GetSpRegValue(const RegSet& regs, ArchType arch, uint64_t* value);
diff --git a/simpleperf/read_apk.cpp b/simpleperf/read_apk.cpp
index af72cdf..6a6b55f 100644
--- a/simpleperf/read_apk.cpp
+++ b/simpleperf/read_apk.cpp
@@ -98,9 +98,10 @@
   std::string entry_name;
   entry_name.resize(zname.name_length,'\0');
   memcpy(&entry_name[0], zname.name, zname.name_length);
-  if (!IsValidElfFile(fhelper.fd())) {
-    LOG(ERROR) << "problems reading ELF from in " << apk_path << " entry '"
-               << entry_name << "'";
+  ElfStatus result = IsValidElfFile(fhelper.fd());
+  if (result != ElfStatus::NO_ERROR) {
+    LOG(ERROR) << "problems reading ELF from " << apk_path << " entry '"
+               << entry_name << "': " << result;
     return nullptr;
   }
 
@@ -170,21 +171,21 @@
   return std::make_tuple(true, path.substr(0, pos), path.substr(pos + 2));
 }
 
-bool GetBuildIdFromApkFile(const std::string& apk_path, const std::string& elf_filename,
+ElfStatus GetBuildIdFromApkFile(const std::string& apk_path, const std::string& elf_filename,
                            BuildId* build_id) {
   std::unique_ptr<EmbeddedElf> ee = ApkInspector::FindElfInApkByName(apk_path, elf_filename);
   if (ee == nullptr) {
-    return false;
+    return ElfStatus::FILE_NOT_FOUND;
   }
   return GetBuildIdFromEmbeddedElfFile(apk_path, ee->entry_offset(), ee->entry_size(), build_id);
 }
 
-bool ParseSymbolsFromApkFile(const std::string& apk_path, const std::string& elf_filename,
+ElfStatus ParseSymbolsFromApkFile(const std::string& apk_path, const std::string& elf_filename,
                              const BuildId& expected_build_id,
-                             std::function<void(const ElfFileSymbol&)> callback) {
+                             const std::function<void(const ElfFileSymbol&)>& callback) {
   std::unique_ptr<EmbeddedElf> ee = ApkInspector::FindElfInApkByName(apk_path, elf_filename);
   if (ee == nullptr) {
-    return false;
+    return ElfStatus::FILE_NOT_FOUND;
   }
   return ParseSymbolsFromEmbeddedElfFile(apk_path, ee->entry_offset(), ee->entry_size(),
                                          expected_build_id, callback);
diff --git a/simpleperf/read_apk.h b/simpleperf/read_apk.h
index 82531f4..65b5df8 100644
--- a/simpleperf/read_apk.h
+++ b/simpleperf/read_apk.h
@@ -91,12 +91,12 @@
 std::string GetUrlInApk(const std::string& apk_path, const std::string& elf_filename);
 std::tuple<bool, std::string, std::string> SplitUrlInApk(const std::string& path);
 
-bool GetBuildIdFromApkFile(const std::string& apk_path, const std::string& elf_filename,
-                           BuildId* build_id);
+ElfStatus GetBuildIdFromApkFile(const std::string& apk_path, const std::string& elf_filename,
+                                BuildId* build_id);
 
-bool ParseSymbolsFromApkFile(const std::string& apk_path, const std::string& elf_filename,
-                             const BuildId& expected_build_id,
-                             std::function<void(const ElfFileSymbol&)> callback);
+ElfStatus ParseSymbolsFromApkFile(const std::string& apk_path, const std::string& elf_filename,
+                                  const BuildId& expected_build_id,
+                                  const std::function<void(const ElfFileSymbol&)>& callback);
 
 
 #endif  // SIMPLE_PERF_READ_APK_H_
diff --git a/simpleperf/read_apk_test.cpp b/simpleperf/read_apk_test.cpp
index d7b30c5..651e17e 100644
--- a/simpleperf/read_apk_test.cpp
+++ b/simpleperf/read_apk_test.cpp
@@ -52,13 +52,14 @@
 
 TEST(read_apk, GetBuildIdFromApkFile) {
   BuildId build_id;
-  ASSERT_TRUE(GetBuildIdFromApkFile(GetTestData(APK_FILE), NATIVELIB_IN_APK, &build_id));
+  ASSERT_EQ(ElfStatus::NO_ERROR, GetBuildIdFromApkFile(GetTestData(APK_FILE), NATIVELIB_IN_APK, &build_id));
   ASSERT_EQ(build_id, native_lib_build_id);
 }
 
 TEST(read_apk, ParseSymbolsFromApkFile) {
   std::map<std::string, ElfFileSymbol> symbols;
-  ASSERT_TRUE(ParseSymbolsFromApkFile(GetTestData(APK_FILE), NATIVELIB_IN_APK, native_lib_build_id,
-                                      std::bind(ParseSymbol, std::placeholders::_1, &symbols)));
+  ASSERT_EQ(ElfStatus::NO_SYMBOL_TABLE,
+            ParseSymbolsFromApkFile(GetTestData(APK_FILE), NATIVELIB_IN_APK, native_lib_build_id,
+                                    std::bind(ParseSymbol, std::placeholders::_1, &symbols)));
   CheckElfFileSymbols(symbols);
 }
diff --git a/simpleperf/read_elf.cpp b/simpleperf/read_elf.cpp
index 05b06aa..71d055e 100644
--- a/simpleperf/read_elf.cpp
+++ b/simpleperf/read_elf.cpp
@@ -43,23 +43,58 @@
 #define ELF_NOTE_GNU "GNU"
 #define NT_GNU_BUILD_ID 3
 
-
-bool IsValidElfFile(int fd) {
-  static const char elf_magic[] = {0x7f, 'E', 'L', 'F'};
-  char buf[4];
-  return android::base::ReadFully(fd, buf, 4) && memcmp(buf, elf_magic, 4) == 0;
+std::ostream& operator<<(std::ostream& os, const ElfStatus& status) {
+  switch (status) {
+    case ElfStatus::NO_ERROR:
+      os << "No error";
+      break;
+    case ElfStatus::FILE_NOT_FOUND:
+      os << "File not found";
+      break;
+    case ElfStatus::READ_FAILED:
+      os << "Read failed";
+      break;
+    case ElfStatus::FILE_MALFORMED:
+      os << "Malformed file";
+      break;
+    case ElfStatus::NO_SYMBOL_TABLE:
+      os << "No symbol table";
+      break;
+    case ElfStatus::NO_BUILD_ID:
+      os << "No build id";
+      break;
+    case ElfStatus::BUILD_ID_MISMATCH:
+      os << "Build id mismatch";
+      break;
+    case ElfStatus::SECTION_NOT_FOUND:
+      os << "Section not found";
+      break;
+  }
+  return os;
 }
 
-bool IsValidElfPath(const std::string& filename) {
+ElfStatus IsValidElfFile(int fd) {
+  static const char elf_magic[] = {0x7f, 'E', 'L', 'F'};
+  char buf[4];
+  if (!android::base::ReadFully(fd, buf, 4)) {
+    return ElfStatus::READ_FAILED;
+  }
+  if (memcmp(buf, elf_magic, 4) != 0) {
+    return ElfStatus::FILE_MALFORMED;
+  }
+  return ElfStatus::NO_ERROR;
+}
+
+ElfStatus IsValidElfPath(const std::string& filename) {
   if (!IsRegularFile(filename)) {
-    return false;
+    return ElfStatus::FILE_NOT_FOUND;
   }
   std::string mode = std::string("rb") + CLOSE_ON_EXEC_MODE;
   FILE* fp = fopen(filename.c_str(), mode.c_str());
   if (fp == nullptr) {
-    return false;
+    return ElfStatus::READ_FAILED;
   }
-  bool result = IsValidElfFile(fileno(fp));
+  ElfStatus result = IsValidElfFile(fileno(fp));
   fclose(fp);
   return result;
 }
@@ -75,8 +110,8 @@
     p += 4;
     uint32_t type = *reinterpret_cast<const uint32_t*>(p);
     p += 4;
-    namesz = ALIGN(namesz, 4);
-    descsz = ALIGN(descsz, 4);
+    namesz = Align(namesz, 4);
+    descsz = Align(descsz, 4);
     CHECK_LE(p + namesz + descsz, end);
     if ((type == NT_GNU_BUILD_ID) && (strcmp(p, ELF_NOTE_GNU) == 0)) {
       *build_id = BuildId(p + namesz, descsz);
@@ -87,115 +122,131 @@
   return false;
 }
 
-bool GetBuildIdFromNoteFile(const std::string& filename, BuildId* build_id) {
+ElfStatus GetBuildIdFromNoteFile(const std::string& filename, BuildId* build_id) {
   std::string content;
   if (!android::base::ReadFileToString(filename, &content)) {
-    LOG(DEBUG) << "can't read note file " << filename;
-    return false;
+    return ElfStatus::READ_FAILED;
   }
-  if (GetBuildIdFromNoteSection(content.c_str(), content.size(), build_id) == false) {
-    LOG(DEBUG) << "can't read build_id from note file " << filename;
-    return false;
+  if (!GetBuildIdFromNoteSection(content.c_str(), content.size(), build_id)) {
+    return ElfStatus::NO_BUILD_ID;
   }
-  return true;
+  return ElfStatus::NO_ERROR;
 }
 
 template <class ELFT>
-bool GetBuildIdFromELFFile(const llvm::object::ELFFile<ELFT>* elf, BuildId* build_id) {
-  for (auto section_iterator = elf->section_begin(); section_iterator != elf->section_end();
-       ++section_iterator) {
-    if (section_iterator->sh_type == llvm::ELF::SHT_NOTE) {
-      auto contents = elf->getSectionContents(&*section_iterator);
-      if (contents.getError()) {
-        LOG(DEBUG) << "read note section error";
-        continue;
+ElfStatus GetBuildIdFromELFFile(const llvm::object::ELFObjectFile<ELFT>* elf, BuildId* build_id) {
+  for (auto it = elf->section_begin(); it != elf->section_end(); ++it) {
+    const llvm::object::ELFSectionRef& section_ref = *it;
+    if (section_ref.getType() == llvm::ELF::SHT_NOTE) {
+      llvm::StringRef data;
+      if (it->getContents(data)) {
+        return ElfStatus::READ_FAILED;
       }
-      if (GetBuildIdFromNoteSection(reinterpret_cast<const char*>(contents->data()),
-                                    contents->size(), build_id)) {
-        return true;
+      if (GetBuildIdFromNoteSection(reinterpret_cast<const char*>(data.data()),
+                                    data.size(), build_id)) {
+        return ElfStatus::NO_ERROR;
       }
     }
   }
-  return false;
+  return ElfStatus::NO_BUILD_ID;
 }
 
-static bool GetBuildIdFromObjectFile(llvm::object::ObjectFile* obj, BuildId* build_id) {
-  bool result = false;
+static ElfStatus GetBuildIdFromObjectFile(llvm::object::ObjectFile* obj, BuildId* build_id) {
   if (auto elf = llvm::dyn_cast<llvm::object::ELF32LEObjectFile>(obj)) {
-    result = GetBuildIdFromELFFile(elf->getELFFile(), build_id);
+    return GetBuildIdFromELFFile(elf, build_id);
   } else if (auto elf = llvm::dyn_cast<llvm::object::ELF64LEObjectFile>(obj)) {
-    result = GetBuildIdFromELFFile(elf->getELFFile(), build_id);
-  } else {
-    LOG(ERROR) << "unknown elf format in file " << obj->getFileName().data();
-    return false;
+    return GetBuildIdFromELFFile(elf, build_id);
   }
-  if (!result) {
-    LOG(DEBUG) << "no build id present in file " << obj->getFileName().data();
-  }
-  return result;
+  return ElfStatus::FILE_MALFORMED;
 }
 
-struct BinaryRet {
+struct BinaryWrapper {
   llvm::object::OwningBinary<llvm::object::Binary> binary;
   llvm::object::ObjectFile* obj;
 
-  BinaryRet() : obj(nullptr) {
+  BinaryWrapper() : obj(nullptr) {
   }
 };
 
-static BinaryRet OpenObjectFile(const std::string& filename, uint64_t file_offset = 0,
-                                uint64_t file_size = 0) {
-  BinaryRet ret;
+static ElfStatus OpenObjectFile(const std::string& filename, uint64_t file_offset,
+                                uint64_t file_size, BinaryWrapper* wrapper) {
   FileHelper fhelper = FileHelper::OpenReadOnly(filename);
   if (!fhelper) {
-    PLOG(DEBUG) << "failed to open " << filename;
-    return ret;
+    return ElfStatus::READ_FAILED;
   }
   if (file_size == 0) {
     file_size = GetFileSize(filename);
     if (file_size == 0) {
-      PLOG(ERROR) << "failed to get size of file " << filename;
-      return ret;
+      return ElfStatus::READ_FAILED;
     }
   }
   auto buffer_or_err = llvm::MemoryBuffer::getOpenFileSlice(fhelper.fd(), filename, file_size, file_offset);
   if (!buffer_or_err) {
-    LOG(ERROR) << "failed to read " << filename << " [" << file_offset << "-" << (file_offset + file_size)
-        << "]: " << buffer_or_err.getError().message();
-    return ret;
+    return ElfStatus::READ_FAILED;
   }
   auto binary_or_err = llvm::object::createBinary(buffer_or_err.get()->getMemBufferRef());
   if (!binary_or_err) {
-    LOG(ERROR) << filename << " [" << file_offset << "-" << (file_offset + file_size)
-        << "] is not a binary file: " << binary_or_err.getError().message();
-    return ret;
+    return ElfStatus::READ_FAILED;
   }
-  ret.binary = llvm::object::OwningBinary<llvm::object::Binary>(std::move(binary_or_err.get()),
-                                                                std::move(buffer_or_err.get()));
-  ret.obj = llvm::dyn_cast<llvm::object::ObjectFile>(ret.binary.getBinary());
-  if (ret.obj == nullptr) {
-    LOG(ERROR) << filename << " [" << file_offset << "-" << (file_offset + file_size)
-        << "] is not an object file";
+  wrapper->binary = llvm::object::OwningBinary<llvm::object::Binary>(std::move(binary_or_err.get()),
+                                                                        std::move(buffer_or_err.get()));
+  wrapper->obj = llvm::dyn_cast<llvm::object::ObjectFile>(wrapper->binary.getBinary());
+  if (wrapper->obj == nullptr) {
+    return ElfStatus::FILE_MALFORMED;
   }
-  return ret;
+  return ElfStatus::NO_ERROR;
 }
 
-bool GetBuildIdFromElfFile(const std::string& filename, BuildId* build_id) {
-  if (!IsValidElfPath(filename)) {
-    return false;
+static ElfStatus OpenObjectFileFromString(const std::string& s, BinaryWrapper* wrapper) {
+  auto buffer = llvm::MemoryBuffer::getMemBuffer(s);
+  auto binary_or_err = llvm::object::createBinary(buffer->getMemBufferRef());
+  if (!binary_or_err) {
+    return ElfStatus::FILE_MALFORMED;
   }
-  bool result = GetBuildIdFromEmbeddedElfFile(filename, 0, 0, build_id);
-  LOG(VERBOSE) << "GetBuildIdFromElfFile(" << filename << ") => " << build_id->ToString();
-  return result;
+  wrapper->binary = llvm::object::OwningBinary<llvm::object::Binary>(std::move(binary_or_err.get()),
+                                                                std::move(buffer));
+  wrapper->obj = llvm::dyn_cast<llvm::object::ObjectFile>(wrapper->binary.getBinary());
+  if (wrapper->obj == nullptr) {
+    return ElfStatus::FILE_MALFORMED;
+  }
+  return ElfStatus::NO_ERROR;
 }
 
-bool GetBuildIdFromEmbeddedElfFile(const std::string& filename, uint64_t file_offset,
-                                   uint32_t file_size, BuildId* build_id) {
-  BinaryRet ret = OpenObjectFile(filename, file_offset, file_size);
-  if (ret.obj == nullptr) {
-    return false;
+ElfStatus GetBuildIdFromElfFile(const std::string& filename, BuildId* build_id) {
+  ElfStatus result = IsValidElfPath(filename);
+  if (result != ElfStatus::NO_ERROR) {
+    return result;
   }
-  return GetBuildIdFromObjectFile(ret.obj, build_id);
+  return GetBuildIdFromEmbeddedElfFile(filename, 0, 0, build_id);
+}
+
+ElfStatus GetBuildIdFromEmbeddedElfFile(const std::string& filename, uint64_t file_offset,
+                                        uint32_t file_size, BuildId* build_id) {
+  BinaryWrapper wrapper;
+  ElfStatus result = OpenObjectFile(filename, file_offset, file_size, &wrapper);
+  if (result != ElfStatus::NO_ERROR) {
+    return result;
+  }
+  return GetBuildIdFromObjectFile(wrapper.obj, build_id);
+}
+
+template <class ELFT>
+ElfStatus ReadSectionFromELFFile(const llvm::object::ELFObjectFile<ELFT>* elf, const std::string& section_name,
+                                 std::string* content) {
+  for (llvm::object::section_iterator it = elf->section_begin(); it != elf->section_end(); ++it) {
+    llvm::StringRef name;
+    if (it->getName(name) || name != section_name) {
+      continue;
+    }
+    llvm::StringRef data;
+    std::error_code err = it->getContents(data);
+    if (err) {
+      return ElfStatus::READ_FAILED;
+    }
+    *content = data;
+    return ElfStatus::NO_ERROR;
+  }
+  return ElfStatus::SECTION_NOT_FOUND;
 }
 
 bool IsArmMappingSymbol(const char* name) {
@@ -205,48 +256,41 @@
   return name[0] == '$' && strchr("adtx", name[1]) != nullptr && (name[2] == '\0' || name[2] == '.');
 }
 
-template <class ELFT>
-void ParseSymbolsFromELFFile(const llvm::object::ELFObjectFile<ELFT>* elf_obj,
-                             std::function<void(const ElfFileSymbol&)> callback) {
-  auto elf = elf_obj->getELFFile();
-  bool is_arm = (elf->getHeader()->e_machine == llvm::ELF::EM_ARM ||
-                 elf->getHeader()->e_machine == llvm::ELF::EM_AARCH64);
-  auto begin = elf_obj->symbol_begin();
-  auto end = elf_obj->symbol_end();
-  if (begin == end) {
-    begin = elf_obj->dynamic_symbol_begin();
-    end = elf_obj->dynamic_symbol_end();
-  }
-  for (; begin != end; ++begin) {
+void ReadSymbolTable(llvm::object::symbol_iterator sym_begin,
+                     llvm::object::symbol_iterator sym_end,
+                     const std::function<void(const ElfFileSymbol&)>& callback,
+                     bool is_arm) {
+  for (; sym_begin != sym_end; ++sym_begin) {
     ElfFileSymbol symbol;
-    auto elf_symbol = static_cast<const llvm::object::ELFSymbolRef*>(&*begin);
-    auto section_it = elf_symbol->getSection();
-    if (!section_it) {
+    auto symbol_ref = static_cast<const llvm::object::ELFSymbolRef*>(&*sym_begin);
+    llvm::ErrorOr<llvm::object::section_iterator> section_it_or_err = symbol_ref->getSection();
+    if (!section_it_or_err) {
       continue;
     }
-    llvm::StringRef section_name;
-    if (section_it.get()->getName(section_name) || section_name.empty()) {
-      continue;
-    }
-    if (section_name.str() == ".text") {
-      symbol.is_in_text_section = true;
-    }
 
-    auto symbol_name = elf_symbol->getName();
-    if (!symbol_name || symbol_name.get().empty()) {
+    llvm::StringRef section_name;
+    if (section_it_or_err.get()->getName(section_name) || section_name.empty()) {
       continue;
     }
-    symbol.name = symbol_name.get();
-    symbol.vaddr = elf_symbol->getValue();
+    if (section_name == ".text") {
+      symbol.is_in_text_section = true;
+    }
+    llvm::ErrorOr<llvm::StringRef> symbol_name_or_err = symbol_ref->getName();
+    if (!symbol_name_or_err || symbol_name_or_err.get().empty()) {
+      continue;
+    }
+
+    symbol.name = symbol_name_or_err.get();
+    symbol.vaddr = symbol_ref->getValue();
     if ((symbol.vaddr & 1) != 0 && is_arm) {
       // Arm sets bit 0 to mark it as thumb code, remove the flag.
       symbol.vaddr &= ~1;
     }
-    symbol.len = elf_symbol->getSize();
-    int type = elf_symbol->getELFType();
-    if (type == llvm::ELF::STT_FUNC) {
+    symbol.len = symbol_ref->getSize();
+    llvm::object::SymbolRef::Type symbol_type = symbol_ref->getType();
+    if (symbol_type == llvm::object::SymbolRef::ST_Function) {
       symbol.is_func = true;
-    } else if (type == llvm::ELF::STT_NOTYPE) {
+    } else if (symbol_type == llvm::object::SymbolRef::ST_Unknown) {
       if (symbol.is_in_text_section) {
         symbol.is_label = true;
         if (is_arm) {
@@ -265,52 +309,120 @@
   }
 }
 
-bool MatchBuildId(llvm::object::ObjectFile* obj, const BuildId& expected_build_id,
-                  const std::string& debug_filename) {
-  if (expected_build_id.IsEmpty()) {
-    return true;
+template <class ELFT>
+void AddSymbolForPltSection(const llvm::object::ELFObjectFile<ELFT>* elf,
+                            const std::function<void(const ElfFileSymbol&)>& callback) {
+  // We may sample instructions in .plt section if the program
+  // calls functions from shared libraries. Different architectures use
+  // different formats to store .plt section, so it needs a lot of work to match
+  // instructions in .plt section to symbols. As samples in .plt section rarely
+  // happen, and .plt section can hardly be a performance bottleneck, we can
+  // just use a symbol @plt to represent instructions in .plt section.
+  for (auto it = elf->section_begin(); it != elf->section_end(); ++it) {
+    const llvm::object::ELFSectionRef& section_ref = *it;
+    llvm::StringRef section_name;
+    std::error_code err = section_ref.getName(section_name);
+    if (err || section_name != ".plt") {
+      continue;
+    }
+    const auto* shdr = elf->getSection(section_ref.getRawDataRefImpl());
+    if (shdr == nullptr) {
+      return;
+    }
+    ElfFileSymbol symbol;
+    symbol.vaddr = shdr->sh_addr;
+    symbol.len = shdr->sh_size;
+    symbol.is_func = true;
+    symbol.is_label = true;
+    symbol.is_in_text_section = true;
+    symbol.name = "@plt";
+    callback(symbol);
+    return;
   }
-  BuildId real_build_id;
-  if (!GetBuildIdFromObjectFile(obj, &real_build_id)) {
-    return false;
-  }
-  if (expected_build_id != real_build_id) {
-    LOG(DEBUG) << "build id for " << debug_filename << " mismatch: "
-               << "expected " << expected_build_id.ToString()
-               << ", real " << real_build_id.ToString();
-    return false;
-  }
-  return true;
 }
 
-bool ParseSymbolsFromElfFile(const std::string& filename, const BuildId& expected_build_id,
-                             std::function<void(const ElfFileSymbol&)> callback) {
-  if (!IsValidElfPath(filename)) {
-    return false;
+template <class ELFT>
+ElfStatus ParseSymbolsFromELFFile(const llvm::object::ELFObjectFile<ELFT>* elf,
+                                  const std::function<void(const ElfFileSymbol&)>& callback) {
+  auto machine = elf->getELFFile()->getHeader()->e_machine;
+  bool is_arm = (machine == llvm::ELF::EM_ARM || machine == llvm::ELF::EM_AARCH64);
+  AddSymbolForPltSection(elf, callback);
+  if (elf->symbol_begin() != elf->symbol_end()) {
+    ReadSymbolTable(elf->symbol_begin(), elf->symbol_end(), callback, is_arm);
+    return ElfStatus::NO_ERROR;
+  } else if (elf->dynamic_symbol_begin()->getRawDataRefImpl() != llvm::object::DataRefImpl()) {
+    ReadSymbolTable(elf->dynamic_symbol_begin(), elf->dynamic_symbol_end(), callback, is_arm);
+  }
+  std::string debugdata;
+  ElfStatus result = ReadSectionFromELFFile(elf, ".gnu_debugdata", &debugdata);
+  if (result == ElfStatus::SECTION_NOT_FOUND) {
+    return ElfStatus::NO_SYMBOL_TABLE;
+  } else if (result == ElfStatus::NO_ERROR) {
+    std::string decompressed_data;
+    if (XzDecompress(debugdata, &decompressed_data)) {
+      BinaryWrapper wrapper;
+      result = OpenObjectFileFromString(decompressed_data, &wrapper);
+      if (result == ElfStatus::NO_ERROR) {
+        if (auto elf = llvm::dyn_cast<llvm::object::ELF32LEObjectFile>(wrapper.obj)) {
+          return ParseSymbolsFromELFFile(elf, callback);
+        } else if (auto elf = llvm::dyn_cast<llvm::object::ELF64LEObjectFile>(wrapper.obj)) {
+          return ParseSymbolsFromELFFile(elf, callback);
+        } else {
+          return ElfStatus::FILE_MALFORMED;
+        }
+      }
+    }
+  }
+  return result;
+}
+
+ElfStatus MatchBuildId(llvm::object::ObjectFile* obj, const BuildId& expected_build_id) {
+  if (expected_build_id.IsEmpty()) {
+    return ElfStatus::NO_ERROR;
+  }
+  BuildId real_build_id;
+  ElfStatus result = GetBuildIdFromObjectFile(obj, &real_build_id);
+  if (result != ElfStatus::NO_ERROR) {
+    return result;
+  }
+  if (expected_build_id != real_build_id) {
+    return ElfStatus::BUILD_ID_MISMATCH;
+  }
+  return ElfStatus::NO_ERROR;
+}
+
+ElfStatus ParseSymbolsFromElfFile(const std::string& filename,
+                                  const BuildId& expected_build_id,
+                                  const std::function<void(const ElfFileSymbol&)>& callback) {
+  ElfStatus result = IsValidElfPath(filename);
+  if (result != ElfStatus::NO_ERROR) {
+    return result;
   }
   return ParseSymbolsFromEmbeddedElfFile(filename, 0, 0, expected_build_id, callback);
 }
 
-bool ParseSymbolsFromEmbeddedElfFile(const std::string& filename, uint64_t file_offset,
+ElfStatus ParseSymbolsFromEmbeddedElfFile(const std::string& filename, uint64_t file_offset,
                                      uint32_t file_size, const BuildId& expected_build_id,
-                                     std::function<void(const ElfFileSymbol&)> callback) {
-  BinaryRet ret = OpenObjectFile(filename, file_offset, file_size);
-  if (ret.obj == nullptr || !MatchBuildId(ret.obj, expected_build_id, filename)) {
-    return false;
+                                     const std::function<void(const ElfFileSymbol&)>& callback) {
+  BinaryWrapper wrapper;
+  ElfStatus result = OpenObjectFile(filename, file_offset, file_size, &wrapper);
+  if (result != ElfStatus::NO_ERROR) {
+    return result;
   }
-  if (auto elf = llvm::dyn_cast<llvm::object::ELF32LEObjectFile>(ret.obj)) {
-    ParseSymbolsFromELFFile(elf, callback);
-  } else if (auto elf = llvm::dyn_cast<llvm::object::ELF64LEObjectFile>(ret.obj)) {
-    ParseSymbolsFromELFFile(elf, callback);
-  } else {
-    LOG(ERROR) << "unknown elf format in file " << filename;
-    return false;
+  result = MatchBuildId(wrapper.obj, expected_build_id);
+  if (result != ElfStatus::NO_ERROR) {
+    return result;
   }
-  return true;
+  if (auto elf = llvm::dyn_cast<llvm::object::ELF32LEObjectFile>(wrapper.obj)) {
+    return ParseSymbolsFromELFFile(elf, callback);
+  } else if (auto elf = llvm::dyn_cast<llvm::object::ELF64LEObjectFile>(wrapper.obj)) {
+    return ParseSymbolsFromELFFile(elf, callback);
+  }
+  return ElfStatus::FILE_MALFORMED;
 }
 
 template <class ELFT>
-bool ReadMinExecutableVirtualAddress(const llvm::object::ELFFile<ELFT>* elf, uint64_t* p_vaddr) {
+ElfStatus ReadMinExecutableVirtualAddress(const llvm::object::ELFFile<ELFT>* elf, uint64_t* p_vaddr) {
   bool has_vaddr = false;
   uint64_t min_addr = std::numeric_limits<uint64_t>::max();
   for (auto it = elf->program_header_begin(); it != elf->program_header_end(); ++it) {
@@ -321,75 +433,55 @@
       }
     }
   }
-  if (has_vaddr) {
-    *p_vaddr = min_addr;
+  if (!has_vaddr) {
+    return ElfStatus::FILE_MALFORMED;
   }
-  return has_vaddr;
+  *p_vaddr = min_addr;
+  return ElfStatus::NO_ERROR;
 }
 
-bool ReadMinExecutableVirtualAddressFromElfFile(const std::string& filename,
-                                                const BuildId& expected_build_id,
-                                                uint64_t* min_vaddr) {
-  if (!IsValidElfPath(filename)) {
-    return false;
+ElfStatus ReadMinExecutableVirtualAddressFromElfFile(const std::string& filename,
+                                                     const BuildId& expected_build_id,
+                                                     uint64_t* min_vaddr) {
+  ElfStatus result = IsValidElfPath(filename);
+  if (result != ElfStatus::NO_ERROR) {
+    return result;
   }
-  BinaryRet ret = OpenObjectFile(filename);
-  if (ret.obj == nullptr || !MatchBuildId(ret.obj, expected_build_id, filename)) {
-    return false;
+  BinaryWrapper wrapper;
+  result = OpenObjectFile(filename, 0, 0, &wrapper);
+  if (result != ElfStatus::NO_ERROR) {
+    return result;
+  }
+  result = MatchBuildId(wrapper.obj, expected_build_id);
+  if (result != ElfStatus::NO_ERROR) {
+    return result;
   }
 
-  bool result = false;
-  if (auto elf = llvm::dyn_cast<llvm::object::ELF32LEObjectFile>(ret.obj)) {
-    result = ReadMinExecutableVirtualAddress(elf->getELFFile(), min_vaddr);
-  } else if (auto elf = llvm::dyn_cast<llvm::object::ELF64LEObjectFile>(ret.obj)) {
-    result = ReadMinExecutableVirtualAddress(elf->getELFFile(), min_vaddr);
+  if (auto elf = llvm::dyn_cast<llvm::object::ELF32LEObjectFile>(wrapper.obj)) {
+    return ReadMinExecutableVirtualAddress(elf->getELFFile(), min_vaddr);
+  } else if (auto elf = llvm::dyn_cast<llvm::object::ELF64LEObjectFile>(wrapper.obj)) {
+    return ReadMinExecutableVirtualAddress(elf->getELFFile(), min_vaddr);
   } else {
-    LOG(ERROR) << "unknown elf format in file" << filename;
-    return false;
+    return ElfStatus::FILE_MALFORMED;
   }
-
-  if (!result) {
-    LOG(ERROR) << "no program header in file " << filename;
-  }
-  return result;
 }
 
-template <class ELFT>
-bool ReadSectionFromELFFile(const llvm::object::ELFFile<ELFT>* elf, const std::string& section_name,
-                            std::string* content) {
-  for (auto it = elf->section_begin(); it != elf->section_end(); ++it) {
-    auto name_or_err = elf->getSectionName(&*it);
-    if (name_or_err && *name_or_err == section_name) {
-      auto data_or_err = elf->getSectionContents(&*it);
-      if (!data_or_err) {
-        LOG(ERROR) << "failed to read section " << section_name;
-        return false;
-      }
-      content->append(data_or_err->begin(), data_or_err->end());
-      return true;
-    }
+ElfStatus ReadSectionFromElfFile(const std::string& filename, const std::string& section_name,
+                                 std::string* content) {
+  ElfStatus result = IsValidElfPath(filename);
+  if (result != ElfStatus::NO_ERROR) {
+    return result;
   }
-  LOG(ERROR) << "can't find section " << section_name;
-  return false;
-}
-
-bool ReadSectionFromElfFile(const std::string& filename, const std::string& section_name,
-                            std::string* content) {
-  if (!IsValidElfPath(filename)) {
-    return false;
+  BinaryWrapper wrapper;
+  result = OpenObjectFile(filename, 0, 0, &wrapper);
+  if (result != ElfStatus::NO_ERROR) {
+    return result;
   }
-  BinaryRet ret = OpenObjectFile(filename);
-  if (ret.obj == nullptr) {
-    return false;
-  }
-  bool result = false;
-  if (auto elf = llvm::dyn_cast<llvm::object::ELF32LEObjectFile>(ret.obj)) {
-    result = ReadSectionFromELFFile(elf->getELFFile(), section_name, content);
-  } else if (auto elf = llvm::dyn_cast<llvm::object::ELF64LEObjectFile>(ret.obj)) {
-    result = ReadSectionFromELFFile(elf->getELFFile(), section_name, content);
+  if (auto elf = llvm::dyn_cast<llvm::object::ELF32LEObjectFile>(wrapper.obj)) {
+    return ReadSectionFromELFFile(elf, section_name, content);
+  } else if (auto elf = llvm::dyn_cast<llvm::object::ELF64LEObjectFile>(wrapper.obj)) {
+    return ReadSectionFromELFFile(elf, section_name, content);
   } else {
-    LOG(ERROR) << "unknown elf format in file" << filename;
-    return false;
+    return ElfStatus::FILE_MALFORMED;
   }
-  return result;
 }
diff --git a/simpleperf/read_elf.h b/simpleperf/read_elf.h
index a6c73c3..5a916ad 100644
--- a/simpleperf/read_elf.h
+++ b/simpleperf/read_elf.h
@@ -18,13 +18,31 @@
 #define SIMPLE_PERF_READ_ELF_H_
 
 #include <functional>
+#include <ostream>
 #include <string>
 #include "build_id.h"
 
-bool GetBuildIdFromNoteFile(const std::string& filename, BuildId* build_id);
-bool GetBuildIdFromElfFile(const std::string& filename, BuildId* build_id);
-bool GetBuildIdFromEmbeddedElfFile(const std::string& filename, uint64_t file_offset,
-                                   uint32_t file_size, BuildId* build_id);
+// Read ELF functions are called in different situations, so it is hard to
+// decide whether to report error or not. So read ELF functions don't report
+// error when something wrong happens, instead they return ElfStatus, which
+// identifies different errors met while reading elf file.
+enum ElfStatus {
+  NO_ERROR,
+  FILE_NOT_FOUND,
+  READ_FAILED,
+  FILE_MALFORMED,
+  NO_SYMBOL_TABLE,
+  NO_BUILD_ID,
+  BUILD_ID_MISMATCH,
+  SECTION_NOT_FOUND,
+};
+
+std::ostream& operator<<(std::ostream& os, const ElfStatus& status);
+
+ElfStatus GetBuildIdFromNoteFile(const std::string& filename, BuildId* build_id);
+ElfStatus GetBuildIdFromElfFile(const std::string& filename, BuildId* build_id);
+ElfStatus GetBuildIdFromEmbeddedElfFile(const std::string& filename, uint64_t file_offset,
+                                        uint32_t file_size, BuildId* build_id);
 
 // The symbol prefix used to indicate that the symbol belongs to android linker.
 static const std::string linker_prefix = "__dl_";
@@ -41,22 +59,23 @@
   }
 };
 
-bool ParseSymbolsFromElfFile(const std::string& filename, const BuildId& expected_build_id,
-                             std::function<void(const ElfFileSymbol&)> callback);
-bool ParseSymbolsFromEmbeddedElfFile(const std::string& filename, uint64_t file_offset,
-                                     uint32_t file_size, const BuildId& expected_build_id,
-                                     std::function<void(const ElfFileSymbol&)> callback);
+ElfStatus ParseSymbolsFromElfFile(const std::string& filename,
+                                  const BuildId& expected_build_id,
+                                  const std::function<void(const ElfFileSymbol&)>& callback);
+ElfStatus ParseSymbolsFromEmbeddedElfFile(const std::string& filename, uint64_t file_offset,
+                                          uint32_t file_size, const BuildId& expected_build_id,
+                                          const std::function<void(const ElfFileSymbol&)>& callback);
 
-bool ReadMinExecutableVirtualAddressFromElfFile(const std::string& filename,
-                                                const BuildId& expected_build_id,
-                                                uint64_t* min_addr);
+ElfStatus ReadMinExecutableVirtualAddressFromElfFile(const std::string& filename,
+                                                     const BuildId& expected_build_id,
+                                                     uint64_t* min_addr);
 
-bool ReadSectionFromElfFile(const std::string& filename, const std::string& section_name,
-                            std::string* content);
+ElfStatus ReadSectionFromElfFile(const std::string& filename, const std::string& section_name,
+                                 std::string* content);
 
 // Expose the following functions for unit tests.
 bool IsArmMappingSymbol(const char* name);
-bool IsValidElfFile(int fd);
-bool IsValidElfPath(const std::string& filename);
+ElfStatus IsValidElfFile(int fd);
+ElfStatus IsValidElfPath(const std::string& filename);
 
 #endif  // SIMPLE_PERF_READ_ELF_H_
diff --git a/simpleperf/read_elf_test.cpp b/simpleperf/read_elf_test.cpp
index 929540f..f60b552 100644
--- a/simpleperf/read_elf_test.cpp
+++ b/simpleperf/read_elf_test.cpp
@@ -19,17 +19,22 @@
 #include <gtest/gtest.h>
 
 #include <map>
+
+#include <android-base/file.h>
+#include <android-base/test_utils.h>
+
 #include "get_test_data.h"
+#include "test_util.h"
 
 TEST(read_elf, GetBuildIdFromElfFile) {
   BuildId build_id;
-  ASSERT_TRUE(GetBuildIdFromElfFile(GetTestData(ELF_FILE), &build_id));
+  ASSERT_EQ(ElfStatus::NO_ERROR, GetBuildIdFromElfFile(GetTestData(ELF_FILE), &build_id));
   ASSERT_EQ(build_id, BuildId(elf_file_build_id));
 }
 
 TEST(read_elf, GetBuildIdFromEmbeddedElfFile) {
   BuildId build_id;
-  ASSERT_TRUE(GetBuildIdFromEmbeddedElfFile(GetTestData(APK_FILE), NATIVELIB_OFFSET_IN_APK,
+  ASSERT_EQ(ElfStatus::NO_ERROR, GetBuildIdFromEmbeddedElfFile(GetTestData(APK_FILE), NATIVELIB_OFFSET_IN_APK,
                                             NATIVELIB_SIZE_IN_APK, &build_id));
   ASSERT_EQ(build_id, native_lib_build_id);
 }
@@ -38,26 +43,34 @@
   (*symbols)[symbol.name] = symbol;
 }
 
-void CheckElfFileSymbols(const std::map<std::string, ElfFileSymbol>& symbols) {
+static void CheckGlobalVariableSymbols(const std::map<std::string, ElfFileSymbol>& symbols) {
   auto pos = symbols.find("GlobalVar");
   ASSERT_NE(pos, symbols.end());
   ASSERT_FALSE(pos->second.is_func);
-  pos = symbols.find("GlobalFunc");
+}
+
+static void CheckFunctionSymbols(const std::map<std::string, ElfFileSymbol>& symbols) {
+  auto pos = symbols.find("GlobalFunc");
   ASSERT_NE(pos, symbols.end());
   ASSERT_TRUE(pos->second.is_func);
   ASSERT_TRUE(pos->second.is_in_text_section);
 }
 
+void CheckElfFileSymbols(const std::map<std::string, ElfFileSymbol>& symbols) {
+  CheckGlobalVariableSymbols(symbols);
+  CheckFunctionSymbols(symbols);
+}
+
 TEST(read_elf, parse_symbols_from_elf_file_with_correct_build_id) {
   std::map<std::string, ElfFileSymbol> symbols;
-  ASSERT_TRUE(ParseSymbolsFromElfFile(GetTestData(ELF_FILE), elf_file_build_id,
+  ASSERT_EQ(ElfStatus::NO_ERROR, ParseSymbolsFromElfFile(GetTestData(ELF_FILE), elf_file_build_id,
                                       std::bind(ParseSymbol, std::placeholders::_1, &symbols)));
   CheckElfFileSymbols(symbols);
 }
 
 TEST(read_elf, parse_symbols_from_elf_file_without_build_id) {
   std::map<std::string, ElfFileSymbol> symbols;
-  ASSERT_TRUE(ParseSymbolsFromElfFile(GetTestData(ELF_FILE), BuildId(),
+  ASSERT_EQ(ElfStatus::NO_ERROR, ParseSymbolsFromElfFile(GetTestData(ELF_FILE), BuildId(),
                                       std::bind(ParseSymbol, std::placeholders::_1, &symbols)));
   CheckElfFileSymbols(symbols);
 }
@@ -65,18 +78,25 @@
 TEST(read_elf, parse_symbols_from_elf_file_with_wrong_build_id) {
   BuildId build_id("01010101010101010101");
   std::map<std::string, ElfFileSymbol> symbols;
-  ASSERT_FALSE(ParseSymbolsFromElfFile(GetTestData(ELF_FILE), build_id,
+  ASSERT_EQ(ElfStatus::BUILD_ID_MISMATCH, ParseSymbolsFromElfFile(GetTestData(ELF_FILE), build_id,
                                        std::bind(ParseSymbol, std::placeholders::_1, &symbols)));
 }
 
 TEST(read_elf, ParseSymbolsFromEmbeddedElfFile) {
   std::map<std::string, ElfFileSymbol> symbols;
-  ASSERT_TRUE(ParseSymbolsFromEmbeddedElfFile(GetTestData(APK_FILE), NATIVELIB_OFFSET_IN_APK,
+  ASSERT_EQ(ElfStatus::NO_SYMBOL_TABLE, ParseSymbolsFromEmbeddedElfFile(GetTestData(APK_FILE), NATIVELIB_OFFSET_IN_APK,
                                               NATIVELIB_SIZE_IN_APK, native_lib_build_id,
                                               std::bind(ParseSymbol, std::placeholders::_1, &symbols)));
   CheckElfFileSymbols(symbols);
 }
 
+TEST(read_elf, ParseSymbolFromMiniDebugInfoElfFile) {
+  std::map<std::string, ElfFileSymbol> symbols;
+  ASSERT_EQ(ElfStatus::NO_ERROR, ParseSymbolsFromElfFile(GetTestData(ELF_FILE_WITH_MINI_DEBUG_INFO), BuildId(),
+                                      std::bind(ParseSymbol, std::placeholders::_1, &symbols)));
+  CheckFunctionSymbols(symbols);
+}
+
 TEST(read_elf, arm_mapping_symbol) {
   ASSERT_TRUE(IsArmMappingSymbol("$a"));
   ASSERT_FALSE(IsArmMappingSymbol("$b"));
@@ -85,7 +105,17 @@
 }
 
 TEST(read_elf, IsValidElfPath) {
-  ASSERT_FALSE(IsValidElfPath("/dev/zero"));
-  ASSERT_FALSE(IsValidElfPath("/sys/devices/system/cpu/online"));
-  ASSERT_TRUE(IsValidElfPath(GetTestData(ELF_FILE)));
+  ASSERT_NE(ElfStatus::NO_ERROR, IsValidElfPath("/dev/zero"));
+  TemporaryFile tmp_file;
+  ASSERT_EQ(ElfStatus::READ_FAILED, IsValidElfPath(tmp_file.path));
+  ASSERT_TRUE(android::base::WriteStringToFile("wrong format for elf", tmp_file.path));
+  ASSERT_EQ(ElfStatus::FILE_MALFORMED, IsValidElfPath(tmp_file.path));
+  ASSERT_EQ(ElfStatus::NO_ERROR, IsValidElfPath(GetTestData(ELF_FILE)));
+}
+
+TEST(read_elf, check_symbol_for_plt_section) {
+  std::map<std::string, ElfFileSymbol> symbols;
+  ASSERT_EQ(ElfStatus::NO_ERROR, ParseSymbolsFromElfFile(GetTestData(ELF_FILE), BuildId(),
+                                      std::bind(ParseSymbol, std::placeholders::_1, &symbols)));
+  ASSERT_NE(symbols.find("@plt"), symbols.end());
 }
diff --git a/simpleperf/record.cpp b/simpleperf/record.cpp
index fca2403..2bcba4d 100644
--- a/simpleperf/record.cpp
+++ b/simpleperf/record.cpp
@@ -23,18 +23,30 @@
 #include <android-base/logging.h>
 #include <android-base/stringprintf.h>
 
+#include "dso.h"
 #include "environment.h"
 #include "perf_regs.h"
+#include "tracing.h"
 #include "utils.h"
 
 static std::string RecordTypeToString(int record_type) {
   static std::unordered_map<int, std::string> record_type_names = {
-      {PERF_RECORD_MMAP, "mmap"},         {PERF_RECORD_LOST, "lost"},
-      {PERF_RECORD_COMM, "comm"},         {PERF_RECORD_EXIT, "exit"},
-      {PERF_RECORD_THROTTLE, "throttle"}, {PERF_RECORD_UNTHROTTLE, "unthrottle"},
-      {PERF_RECORD_FORK, "fork"},         {PERF_RECORD_READ, "read"},
-      {PERF_RECORD_SAMPLE, "sample"},     {PERF_RECORD_BUILD_ID, "build_id"},
+      {PERF_RECORD_MMAP, "mmap"},
+      {PERF_RECORD_LOST, "lost"},
+      {PERF_RECORD_COMM, "comm"},
+      {PERF_RECORD_EXIT, "exit"},
+      {PERF_RECORD_THROTTLE, "throttle"},
+      {PERF_RECORD_UNTHROTTLE, "unthrottle"},
+      {PERF_RECORD_FORK, "fork"},
+      {PERF_RECORD_READ, "read"},
+      {PERF_RECORD_SAMPLE, "sample"},
+      {PERF_RECORD_BUILD_ID, "build_id"},
       {PERF_RECORD_MMAP2, "mmap2"},
+      {PERF_RECORD_TRACING_DATA, "tracing_data"},
+      {SIMPLE_PERF_RECORD_KERNEL_SYMBOL, "kernel_symbol"},
+      {SIMPLE_PERF_RECORD_DSO, "dso"},
+      {SIMPLE_PERF_RECORD_SYMBOL, "symbol"},
+      {SIMPLE_PERF_RECORD_EVENT_ID, "event_id"},
   };
 
   auto it = record_type_names.find(record_type);
@@ -57,6 +69,11 @@
   p += sizeof(T);
 }
 
+template <>
+void MoveToBinaryFormat(const RecordHeader& data, char*& p) {
+  data.MoveToBinaryFormat(p);
+}
+
 template <class T>
 void MoveToBinaryFormat(const T* data_p, size_t n, char*& p) {
   size_t size = n * sizeof(T);
@@ -64,19 +81,19 @@
   p += size;
 }
 
-SampleId::SampleId() {
-  memset(this, 0, sizeof(SampleId));
-}
+SampleId::SampleId() { memset(this, 0, sizeof(SampleId)); }
 
 // Return sample_id size in binary format.
-size_t SampleId::CreateContent(const perf_event_attr& attr) {
+size_t SampleId::CreateContent(const perf_event_attr& attr, uint64_t event_id) {
   sample_id_all = attr.sample_id_all;
   sample_type = attr.sample_type;
+  id_data.id = event_id;
   // Other data are not necessary. TODO: Set missing SampleId data.
   return Size();
 }
 
-void SampleId::ReadFromBinaryFormat(const perf_event_attr& attr, const char* p, const char* end) {
+void SampleId::ReadFromBinaryFormat(const perf_event_attr& attr, const char* p,
+                                    const char* end) {
   sample_id_all = attr.sample_id_all;
   sample_type = attr.sample_type;
   if (sample_id_all) {
@@ -95,7 +112,9 @@
     if (sample_type & PERF_SAMPLE_CPU) {
       MoveFromBinaryFormat(cpu_data, p);
     }
-    // TODO: Add parsing of PERF_SAMPLE_IDENTIFIER.
+    if (sample_type & PERF_SAMPLE_IDENTIFIER) {
+      MoveFromBinaryFormat(id_data, p);
+    }
   }
   CHECK_LE(p, end);
   if (p < end) {
@@ -126,19 +145,22 @@
 void SampleId::Dump(size_t indent) const {
   if (sample_id_all) {
     if (sample_type & PERF_SAMPLE_TID) {
-      PrintIndented(indent, "sample_id: pid %u, tid %u\n", tid_data.pid, tid_data.tid);
+      PrintIndented(indent, "sample_id: pid %u, tid %u\n", tid_data.pid,
+                    tid_data.tid);
     }
     if (sample_type & PERF_SAMPLE_TIME) {
       PrintIndented(indent, "sample_id: time %" PRId64 "\n", time_data.time);
     }
-    if (sample_type & PERF_SAMPLE_ID) {
-      PrintIndented(indent, "sample_id: stream_id %" PRId64 "\n", id_data.id);
+    if (sample_type & (PERF_SAMPLE_ID | PERF_SAMPLE_IDENTIFIER)) {
+      PrintIndented(indent, "sample_id: id %" PRId64 "\n", id_data.id);
     }
     if (sample_type & PERF_SAMPLE_STREAM_ID) {
-      PrintIndented(indent, "sample_id: stream_id %" PRId64 "\n", stream_id_data.stream_id);
+      PrintIndented(indent, "sample_id: stream_id %" PRId64 "\n",
+                    stream_id_data.stream_id);
     }
     if (sample_type & PERF_SAMPLE_CPU) {
-      PrintIndented(indent, "sample_id: cpu %u, res %u\n", cpu_data.cpu, cpu_data.res);
+      PrintIndented(indent, "sample_id: cpu %u, res %u\n", cpu_data.cpu,
+                    cpu_data.res);
     }
   }
 }
@@ -161,152 +183,226 @@
     if (sample_type & PERF_SAMPLE_CPU) {
       size += sizeof(PerfSampleCpuType);
     }
+    if (sample_type & PERF_SAMPLE_IDENTIFIER) {
+      size += sizeof(PerfSampleIdType);
+    }
   }
   return size;
 }
 
-Record::Record() {
-  memset(&header, 0, sizeof(header));
-}
-
-Record::Record(const perf_event_header* pheader) {
-  header = *pheader;
+Record::Record(Record&& other) {
+  header = other.header;
+  sample_id = other.sample_id;
+  binary_ = other.binary_;
+  own_binary_ = other.own_binary_;
+  other.binary_ = nullptr;
+  other.own_binary_ = false;
 }
 
 void Record::Dump(size_t indent) const {
   PrintIndented(indent, "record %s: type %u, misc %u, size %u\n",
-                RecordTypeToString(header.type).c_str(), header.type, header.misc, header.size);
+                RecordTypeToString(type()).c_str(), type(), misc(), size());
   DumpData(indent + 1);
   sample_id.Dump(indent + 1);
 }
 
-uint64_t Record::Timestamp() const {
-  return sample_id.time_data.time;
+uint64_t Record::Timestamp() const { return sample_id.time_data.time; }
+uint32_t Record::Cpu() const { return sample_id.cpu_data.cpu; }
+
+void Record::UpdateBinary(const char* new_binary) {
+  if (own_binary_) {
+    delete[] binary_;
+  }
+  own_binary_ = true;
+  binary_ = new_binary;
 }
 
-MmapRecord::MmapRecord(const perf_event_attr& attr, const perf_event_header* pheader)
-    : Record(pheader) {
-  const char* p = reinterpret_cast<const char*>(pheader + 1);
-  const char* end = reinterpret_cast<const char*>(pheader) + pheader->size;
-  MoveFromBinaryFormat(data, p);
+MmapRecord::MmapRecord(const perf_event_attr& attr, const char* p) : Record(p) {
+  const char* end = p + size();
+  p += header_size();
+  data = reinterpret_cast<const MmapRecordDataType*>(p);
+  p += sizeof(*data);
   filename = p;
-  p += ALIGN(filename.size() + 1, 8);
+  p += Align(strlen(filename) + 1, 8);
   CHECK_LE(p, end);
   sample_id.ReadFromBinaryFormat(attr, p, end);
 }
 
-std::vector<char> MmapRecord::BinaryFormat() const {
-  std::vector<char> buf(header.size);
-  char* p = buf.data();
-  MoveToBinaryFormat(header, p);
-  MoveToBinaryFormat(data, p);
-  strcpy(p, filename.c_str());
-  p += ALIGN(filename.size() + 1, 8);
-  sample_id.WriteToBinaryFormat(p);
-  return buf;
+MmapRecord::MmapRecord(const perf_event_attr& attr, bool in_kernel,
+                       uint32_t pid, uint32_t tid, uint64_t addr, uint64_t len,
+                       uint64_t pgoff, const std::string& filename,
+                       uint64_t event_id, uint64_t time) {
+  SetTypeAndMisc(PERF_RECORD_MMAP,
+                 in_kernel ? PERF_RECORD_MISC_KERNEL : PERF_RECORD_MISC_USER);
+  sample_id.CreateContent(attr, event_id);
+  sample_id.time_data.time = time;
+  MmapRecordDataType data;
+  data.pid = pid;
+  data.tid = tid;
+  data.addr = addr;
+  data.len = len;
+  data.pgoff = pgoff;
+  SetDataAndFilename(data, filename);
 }
 
-void MmapRecord::AdjustSizeBasedOnData() {
-  header.size = sizeof(header) + sizeof(data) + ALIGN(filename.size() + 1, 8) + sample_id.Size();
+void MmapRecord::SetDataAndFilename(const MmapRecordDataType& data,
+                                    const std::string& filename) {
+  SetSize(header_size() + sizeof(data) + Align(filename.size() + 1, 8) +
+          sample_id.Size());
+  char* new_binary = new char[size()];
+  char* p = new_binary;
+  MoveToBinaryFormat(header, p);
+  this->data = reinterpret_cast<MmapRecordDataType*>(p);
+  MoveToBinaryFormat(data, p);
+  this->filename = p;
+  strcpy(p, filename.c_str());
+  p += Align(filename.size() + 1, 8);
+  sample_id.WriteToBinaryFormat(p);
+  UpdateBinary(new_binary);
 }
 
 void MmapRecord::DumpData(size_t indent) const {
-  PrintIndented(indent, "pid %u, tid %u, addr 0x%" PRIx64 ", len 0x%" PRIx64 "\n", data.pid,
-                data.tid, data.addr, data.len);
-  PrintIndented(indent, "pgoff 0x%" PRIx64 ", filename %s\n", data.pgoff, filename.c_str());
+  PrintIndented(indent,
+                "pid %u, tid %u, addr 0x%" PRIx64 ", len 0x%" PRIx64 "\n",
+                data->pid, data->tid, data->addr, data->len);
+  PrintIndented(indent, "pgoff 0x%" PRIx64 ", filename %s\n", data->pgoff,
+                filename);
 }
 
-Mmap2Record::Mmap2Record(const perf_event_attr& attr, const perf_event_header* pheader)
-    : Record(pheader) {
-  const char* p = reinterpret_cast<const char*>(pheader + 1);
-  const char* end = reinterpret_cast<const char*>(pheader) + pheader->size;
-  MoveFromBinaryFormat(data, p);
+Mmap2Record::Mmap2Record(const perf_event_attr& attr, const char* p)
+    : Record(p) {
+  const char* end = p + size();
+  p += header_size();
+  data = reinterpret_cast<const Mmap2RecordDataType*>(p);
+  p += sizeof(*data);
   filename = p;
-  p += ALIGN(filename.size() + 1, 8);
+  p += Align(strlen(filename) + 1, 8);
   CHECK_LE(p, end);
   sample_id.ReadFromBinaryFormat(attr, p, end);
 }
 
-std::vector<char> Mmap2Record::BinaryFormat() const {
-  std::vector<char> buf(header.size);
-  char* p = buf.data();
+void Mmap2Record::SetDataAndFilename(const Mmap2RecordDataType& data,
+                                     const std::string& filename) {
+  SetSize(header_size() + sizeof(data) + Align(filename.size() + 1, 8) +
+          sample_id.Size());
+  char* new_binary = new char[size()];
+  char* p = new_binary;
   MoveToBinaryFormat(header, p);
+  this->data = reinterpret_cast<Mmap2RecordDataType*>(p);
   MoveToBinaryFormat(data, p);
+  this->filename = p;
   strcpy(p, filename.c_str());
-  p += ALIGN(filename.size() + 1, 8);
+  p += Align(filename.size() + 1, 8);
   sample_id.WriteToBinaryFormat(p);
-  return buf;
-}
-
-void Mmap2Record::AdjustSizeBasedOnData() {
-  header.size = sizeof(header) + sizeof(data) + ALIGN(filename.size() + 1, 8) + sample_id.Size();
+  UpdateBinary(new_binary);
 }
 
 void Mmap2Record::DumpData(size_t indent) const {
-  PrintIndented(indent, "pid %u, tid %u, addr 0x%" PRIx64 ", len 0x%" PRIx64 "\n", data.pid,
-                data.tid, data.addr, data.len);
   PrintIndented(indent,
-                "pgoff 0x" PRIx64 ", maj %u, min %u, ino %" PRId64 ", ino_generation %" PRIu64 "\n",
-                data.pgoff, data.maj, data.min, data.ino, data.ino_generation);
-  PrintIndented(indent, "prot %u, flags %u, filenames %s\n", data.prot, data.flags,
-                filename.c_str());
+                "pid %u, tid %u, addr 0x%" PRIx64 ", len 0x%" PRIx64 "\n",
+                data->pid, data->tid, data->addr, data->len);
+  PrintIndented(indent, "pgoff 0x" PRIx64 ", maj %u, min %u, ino %" PRId64
+                        ", ino_generation %" PRIu64 "\n",
+                data->pgoff, data->maj, data->min, data->ino,
+                data->ino_generation);
+  PrintIndented(indent, "prot %u, flags %u, filenames %s\n", data->prot,
+                data->flags, filename);
 }
 
-CommRecord::CommRecord(const perf_event_attr& attr, const perf_event_header* pheader)
-    : Record(pheader) {
-  const char* p = reinterpret_cast<const char*>(pheader + 1);
-  const char* end = reinterpret_cast<const char*>(pheader) + pheader->size;
-  MoveFromBinaryFormat(data, p);
+CommRecord::CommRecord(const perf_event_attr& attr, const char* p) : Record(p) {
+  const char* end = p + size();
+  p += header_size();
+  data = reinterpret_cast<const CommRecordDataType*>(p);
+  p += sizeof(*data);
   comm = p;
-  p += ALIGN(strlen(p) + 1, 8);
+  p += Align(strlen(p) + 1, 8);
   CHECK_LE(p, end);
   sample_id.ReadFromBinaryFormat(attr, p, end);
 }
 
-std::vector<char> CommRecord::BinaryFormat() const {
-  std::vector<char> buf(header.size);
-  char* p = buf.data();
+CommRecord::CommRecord(const perf_event_attr& attr, uint32_t pid, uint32_t tid,
+                       const std::string& comm, uint64_t event_id) {
+  SetTypeAndMisc(PERF_RECORD_COMM, 0);
+  CommRecordDataType data;
+  data.pid = pid;
+  data.tid = tid;
+  size_t sample_id_size = sample_id.CreateContent(attr, event_id);
+  SetSize(header_size() + sizeof(data) + Align(comm.size() + 1, 8) +
+          sample_id_size);
+  char* new_binary = new char[size()];
+  char* p = new_binary;
   MoveToBinaryFormat(header, p);
+  this->data = reinterpret_cast<CommRecordDataType*>(p);
   MoveToBinaryFormat(data, p);
+  this->comm = p;
   strcpy(p, comm.c_str());
-  p += ALIGN(comm.size() + 1, 8);
+  p += Align(comm.size() + 1, 8);
   sample_id.WriteToBinaryFormat(p);
-  return buf;
+  UpdateBinary(new_binary);
 }
 
 void CommRecord::DumpData(size_t indent) const {
-  PrintIndented(indent, "pid %u, tid %u, comm %s\n", data.pid, data.tid, comm.c_str());
+  PrintIndented(indent, "pid %u, tid %u, comm %s\n", data->pid, data->tid,
+                comm);
 }
 
-ExitOrForkRecord::ExitOrForkRecord(const perf_event_attr& attr, const perf_event_header* pheader)
-    : Record(pheader) {
-  const char* p = reinterpret_cast<const char*>(pheader + 1);
-  const char* end = reinterpret_cast<const char*>(pheader) + pheader->size;
-  MoveFromBinaryFormat(data, p);
+ExitOrForkRecord::ExitOrForkRecord(const perf_event_attr& attr, const char* p)
+    : Record(p) {
+  const char* end = p + size();
+  p += header_size();
+  data = reinterpret_cast<const ExitOrForkRecordDataType*>(p);
+  p += sizeof(*data);
   CHECK_LE(p, end);
   sample_id.ReadFromBinaryFormat(attr, p, end);
 }
 
-std::vector<char> ExitOrForkRecord::BinaryFormat() const {
-  std::vector<char> buf(header.size);
-  char* p = buf.data();
+void ExitOrForkRecord::DumpData(size_t indent) const {
+  PrintIndented(indent, "pid %u, ppid %u, tid %u, ptid %u\n", data->pid,
+                data->ppid, data->tid, data->ptid);
+}
+
+ForkRecord::ForkRecord(const perf_event_attr& attr, uint32_t pid, uint32_t tid,
+                       uint32_t ppid, uint32_t ptid, uint64_t event_id) {
+  SetTypeAndMisc(PERF_RECORD_FORK, 0);
+  ExitOrForkRecordDataType data;
+  data.pid = pid;
+  data.ppid = ppid;
+  data.tid = tid;
+  data.ptid = ptid;
+  data.time = 0;
+  size_t sample_id_size = sample_id.CreateContent(attr, event_id);
+  SetSize(header_size() + sizeof(data) + sample_id_size);
+  char* new_binary = new char[size()];
+  char* p = new_binary;
   MoveToBinaryFormat(header, p);
+  this->data = reinterpret_cast<ExitOrForkRecordDataType*>(p);
   MoveToBinaryFormat(data, p);
   sample_id.WriteToBinaryFormat(p);
-  return buf;
+  UpdateBinary(new_binary);
 }
 
-void ExitOrForkRecord::DumpData(size_t indent) const {
-  PrintIndented(indent, "pid %u, ppid %u, tid %u, ptid %u\n", data.pid, data.ppid, data.tid,
-                data.ptid);
+LostRecord::LostRecord(const perf_event_attr& attr, const char* p) : Record(p) {
+  const char* end = p + size();
+  p += header_size();
+  MoveFromBinaryFormat(id, p);
+  MoveFromBinaryFormat(lost, p);
+  CHECK_LE(p, end);
+  sample_id.ReadFromBinaryFormat(attr, p, end);
 }
 
-SampleRecord::SampleRecord(const perf_event_attr& attr, const perf_event_header* pheader)
-    : Record(pheader) {
-  const char* p = reinterpret_cast<const char*>(pheader + 1);
-  const char* end = reinterpret_cast<const char*>(pheader) + pheader->size;
+void LostRecord::DumpData(size_t indent) const {
+  PrintIndented(indent, "id %" PRIu64 ", lost %" PRIu64 "\n", id, lost);
+}
+
+SampleRecord::SampleRecord(const perf_event_attr& attr, const char* p)
+    : Record(p) {
+  const char* end = p + size();
+  p += header_size();
   sample_type = attr.sample_type;
 
+  if (sample_type & PERF_SAMPLE_IDENTIFIER) {
+    MoveFromBinaryFormat(id_data, p);
+  }
   if (sample_type & PERF_SAMPLE_IP) {
     MoveFromBinaryFormat(ip_data, p);
   }
@@ -332,22 +428,19 @@
     MoveFromBinaryFormat(period_data, p);
   }
   if (sample_type & PERF_SAMPLE_CALLCHAIN) {
-    uint64_t nr;
-    MoveFromBinaryFormat(nr, p);
-    callchain_data.ips.resize(nr);
-    MoveFromBinaryFormat(callchain_data.ips.data(), nr, p);
+    MoveFromBinaryFormat(callchain_data.ip_nr, p);
+    callchain_data.ips = reinterpret_cast<const uint64_t*>(p);
+    p += callchain_data.ip_nr * sizeof(uint64_t);
   }
   if (sample_type & PERF_SAMPLE_RAW) {
-    uint32_t size;
-    MoveFromBinaryFormat(size, p);
-    raw_data.data.resize(size);
-    MoveFromBinaryFormat(raw_data.data.data(), size, p);
+    MoveFromBinaryFormat(raw_data.size, p);
+    raw_data.data = p;
+    p += raw_data.size;
   }
   if (sample_type & PERF_SAMPLE_BRANCH_STACK) {
-    uint64_t nr;
-    MoveFromBinaryFormat(nr, p);
-    branch_stack_data.stack.resize(nr);
-    MoveFromBinaryFormat(branch_stack_data.stack.data(), nr, p);
+    MoveFromBinaryFormat(branch_stack_data.stack_nr, p);
+    branch_stack_data.stack = reinterpret_cast<const BranchStackItemType*>(p);
+    p += branch_stack_data.stack_nr * sizeof(BranchStackItemType);
   }
   if (sample_type & PERF_SAMPLE_REGS_USER) {
     MoveFromBinaryFormat(regs_user_data.abi, p);
@@ -361,18 +454,18 @@
           bit_nr++;
         }
       }
-      regs_user_data.regs.resize(bit_nr);
-      MoveFromBinaryFormat(regs_user_data.regs.data(), bit_nr, p);
+      regs_user_data.reg_nr = bit_nr;
+      regs_user_data.regs = reinterpret_cast<const uint64_t*>(p);
+      p += bit_nr * sizeof(uint64_t);
     }
   }
   if (sample_type & PERF_SAMPLE_STACK_USER) {
-    uint64_t size;
-    MoveFromBinaryFormat(size, p);
-    if (size == 0) {
+    MoveFromBinaryFormat(stack_user_data.size, p);
+    if (stack_user_data.size == 0) {
       stack_user_data.dyn_size = 0;
     } else {
-      stack_user_data.data.resize(size);
-      MoveFromBinaryFormat(stack_user_data.data.data(), size, p);
+      stack_user_data.data = p;
+      p += stack_user_data.size;
       MoveFromBinaryFormat(stack_user_data.dyn_size, p);
     }
   }
@@ -383,75 +476,48 @@
   }
 }
 
-std::vector<char> SampleRecord::BinaryFormat() const {
-  std::vector<char> buf(header.size);
-  char* p = buf.data();
+void SampleRecord::ReplaceRegAndStackWithCallChain(
+    const std::vector<uint64_t>& ips) {
+  uint32_t size_added_in_callchain = sizeof(uint64_t) * (ips.size() + 1);
+  uint32_t size_reduced_in_reg_stack =
+      regs_user_data.reg_nr * sizeof(uint64_t) + stack_user_data.size +
+      sizeof(uint64_t);
+  CHECK_LE(size_added_in_callchain, size_reduced_in_reg_stack);
+  uint32_t size_reduced = size_reduced_in_reg_stack - size_added_in_callchain;
+  SetSize(size() - size_reduced);
+  char* p = const_cast<char*>(binary_);
   MoveToBinaryFormat(header, p);
-  if (sample_type & PERF_SAMPLE_IP) {
-    MoveToBinaryFormat(ip_data, p);
-  }
-  if (sample_type & PERF_SAMPLE_TID) {
-    MoveToBinaryFormat(tid_data, p);
-  }
-  if (sample_type & PERF_SAMPLE_TIME) {
-    MoveToBinaryFormat(time_data, p);
-  }
-  if (sample_type & PERF_SAMPLE_ADDR) {
-    MoveToBinaryFormat(addr_data, p);
-  }
-  if (sample_type & PERF_SAMPLE_ID) {
-    MoveToBinaryFormat(id_data, p);
-  }
-  if (sample_type & PERF_SAMPLE_STREAM_ID) {
-    MoveToBinaryFormat(stream_id_data, p);
-  }
-  if (sample_type & PERF_SAMPLE_CPU) {
-    MoveToBinaryFormat(cpu_data, p);
-  }
-  if (sample_type & PERF_SAMPLE_PERIOD) {
-    MoveToBinaryFormat(period_data, p);
-  }
-  if (sample_type & PERF_SAMPLE_CALLCHAIN) {
-    uint64_t nr = callchain_data.ips.size();
-    MoveToBinaryFormat(nr, p);
-    MoveToBinaryFormat(callchain_data.ips.data(), nr, p);
+  p = const_cast<char*>(stack_user_data.data + stack_user_data.size +
+                        sizeof(uint64_t)) -
+      (size_reduced_in_reg_stack - size_added_in_callchain);
+  stack_user_data.size = 0;
+  regs_user_data.abi = 0;
+  p -= sizeof(uint64_t);
+  *reinterpret_cast<uint64_t*>(p) = stack_user_data.size;
+  p -= sizeof(uint64_t);
+  *reinterpret_cast<uint64_t*>(p) = regs_user_data.abi;
+  if (sample_type & PERF_SAMPLE_BRANCH_STACK) {
+    p -= branch_stack_data.stack_nr * sizeof(BranchStackItemType);
+    memmove(p, branch_stack_data.stack,
+            branch_stack_data.stack_nr * sizeof(BranchStackItemType));
+    p -= sizeof(uint64_t);
+    *reinterpret_cast<uint64_t*>(p) = branch_stack_data.stack_nr;
   }
   if (sample_type & PERF_SAMPLE_RAW) {
-    uint32_t size = raw_data.data.size();
-    MoveToBinaryFormat(size, p);
-    MoveToBinaryFormat(raw_data.data.data(), size, p);
+    p -= raw_data.size;
+    memmove(p, raw_data.data, raw_data.size);
+    p -= sizeof(uint32_t);
+    *reinterpret_cast<uint32_t*>(p) = raw_data.size;
   }
-  if (sample_type & PERF_SAMPLE_BRANCH_STACK) {
-    uint64_t nr = branch_stack_data.stack.size();
-    MoveToBinaryFormat(nr, p);
-    MoveToBinaryFormat(branch_stack_data.stack.data(), nr, p);
-  }
-  if (sample_type & PERF_SAMPLE_REGS_USER) {
-    MoveToBinaryFormat(regs_user_data.abi, p);
-    if (regs_user_data.abi != 0) {
-      MoveToBinaryFormat(regs_user_data.regs.data(), regs_user_data.regs.size(), p);
-    }
-  }
-  if (sample_type & PERF_SAMPLE_STACK_USER) {
-    uint64_t size = stack_user_data.data.size();
-    MoveToBinaryFormat(size, p);
-    if (size != 0) {
-      MoveToBinaryFormat(stack_user_data.data.data(), size, p);
-      MoveToBinaryFormat(stack_user_data.dyn_size, p);
-    }
-  }
-
-  // If record command does stack unwinding, sample records' size may be decreased.
-  // So we can't trust header.size here, and should adjust buffer size based on real need.
-  buf.resize(p - buf.data());
-  return buf;
-}
-
-void SampleRecord::AdjustSizeBasedOnData() {
-  size_t size = BinaryFormat().size();
-  LOG(DEBUG) << "Record (type " << RecordTypeToString(header.type) << ") size is changed from "
-      << header.size << " to " << size;
-  header.size = size;
+  p -= ips.size() * sizeof(uint64_t);
+  memcpy(p, ips.data(), ips.size() * sizeof(uint64_t));
+  p -= sizeof(uint64_t);
+  *reinterpret_cast<uint64_t*>(p) = PERF_CONTEXT_USER;
+  p -= sizeof(uint64_t) * (callchain_data.ip_nr);
+  callchain_data.ips = reinterpret_cast<uint64_t*>(p);
+  callchain_data.ip_nr += ips.size() + 1;
+  p -= sizeof(uint64_t);
+  *reinterpret_cast<uint64_t*>(p) = callchain_data.ip_nr;
 }
 
 void SampleRecord::DumpData(size_t indent) const {
@@ -468,7 +534,7 @@
   if (sample_type & PERF_SAMPLE_ADDR) {
     PrintIndented(indent, "addr %p\n", reinterpret_cast<void*>(addr_data.addr));
   }
-  if (sample_type & PERF_SAMPLE_ID) {
+  if (sample_type & (PERF_SAMPLE_ID | PERF_SAMPLE_IDENTIFIER)) {
     PrintIndented(indent, "id %" PRId64 "\n", id_data.id);
   }
   if (sample_type & PERF_SAMPLE_STREAM_ID) {
@@ -481,23 +547,26 @@
     PrintIndented(indent, "period %" PRId64 "\n", period_data.period);
   }
   if (sample_type & PERF_SAMPLE_CALLCHAIN) {
-    PrintIndented(indent, "callchain nr=%" PRIu64 "\n", callchain_data.ips.size());
-    for (auto& ip : callchain_data.ips) {
-      PrintIndented(indent + 1, "0x%" PRIx64 "\n", ip);
+    PrintIndented(indent, "callchain nr=%" PRIu64 "\n", callchain_data.ip_nr);
+    for (uint64_t i = 0; i < callchain_data.ip_nr; ++i) {
+      PrintIndented(indent + 1, "0x%" PRIx64 "\n", callchain_data.ips[i]);
     }
   }
   if (sample_type & PERF_SAMPLE_RAW) {
-    PrintIndented(indent, "raw size=%zu\n", raw_data.data.size());
-    const uint32_t* data = reinterpret_cast<const uint32_t*>(raw_data.data.data());
-    size_t size = raw_data.data.size() / sizeof(uint32_t);
+    PrintIndented(indent, "raw size=%zu\n", raw_data.size);
+    const uint32_t* data = reinterpret_cast<const uint32_t*>(raw_data.data);
+    size_t size = raw_data.size / sizeof(uint32_t);
     for (size_t i = 0; i < size; ++i) {
       PrintIndented(indent + 1, "0x%08x (%zu)\n", data[i], data[i]);
     }
   }
   if (sample_type & PERF_SAMPLE_BRANCH_STACK) {
-    PrintIndented(indent, "branch_stack nr=%" PRIu64 "\n", branch_stack_data.stack.size());
-    for (auto& item : branch_stack_data.stack) {
-      PrintIndented(indent + 1, "from 0x%" PRIx64 ", to 0x%" PRIx64 ", flags 0x%" PRIx64 "\n",
+    PrintIndented(indent, "branch_stack nr=%" PRIu64 "\n",
+                  branch_stack_data.stack_nr);
+    for (uint64_t i = 0; i < branch_stack_data.stack_nr; ++i) {
+      auto& item = branch_stack_data.stack[i];
+      PrintIndented(indent + 1, "from 0x%" PRIx64 ", to 0x%" PRIx64
+                                ", flags 0x%" PRIx64 "\n",
                     item.from, item.to, item.flags);
     }
   }
@@ -505,17 +574,18 @@
     PrintIndented(indent, "user regs: abi=%" PRId64 "\n", regs_user_data.abi);
     for (size_t i = 0, pos = 0; i < 64; ++i) {
       if ((regs_user_data.reg_mask >> i) & 1) {
-        PrintIndented(indent + 1, "reg (%s) 0x%016" PRIx64 "\n",
-                      GetRegName(i, ScopedCurrentArch::GetCurrentArch()).c_str(),
-                      regs_user_data.regs[pos++]);
+        PrintIndented(
+            indent + 1, "reg (%s) 0x%016" PRIx64 "\n",
+            GetRegName(i, ScopedCurrentArch::GetCurrentArch()).c_str(),
+            regs_user_data.regs[pos++]);
       }
     }
   }
   if (sample_type & PERF_SAMPLE_STACK_USER) {
     PrintIndented(indent, "user stack: size %zu dyn_size %" PRIu64 "\n",
-                  stack_user_data.data.size(), stack_user_data.dyn_size);
-    const uint64_t* p = reinterpret_cast<const uint64_t*>(stack_user_data.data.data());
-    const uint64_t* end = p + (stack_user_data.data.size() / sizeof(uint64_t));
+                  stack_user_data.size, stack_user_data.dyn_size);
+    const uint64_t* p = reinterpret_cast<const uint64_t*>(stack_user_data.data);
+    const uint64_t* end = p + (stack_user_data.size / sizeof(uint64_t));
     while (p < end) {
       PrintIndented(indent + 1, "");
       for (size_t i = 0; i < 4 && p < end; ++i, ++p) {
@@ -527,178 +597,283 @@
   }
 }
 
-uint64_t SampleRecord::Timestamp() const {
-  return time_data.time;
-}
+uint64_t SampleRecord::Timestamp() const { return time_data.time; }
+uint32_t SampleRecord::Cpu() const { return cpu_data.cpu; }
 
-BuildIdRecord::BuildIdRecord(const perf_event_header* pheader) : Record(pheader) {
-  const char* p = reinterpret_cast<const char*>(pheader + 1);
-  const char* end = reinterpret_cast<const char*>(pheader) + pheader->size;
+BuildIdRecord::BuildIdRecord(const char* p) : Record(p) {
+  const char* end = p + size();
+  p += header_size();
   MoveFromBinaryFormat(pid, p);
   build_id = BuildId(p, BUILD_ID_SIZE);
-  p += ALIGN(build_id.Size(), 8);
+  p += Align(build_id.Size(), 8);
   filename = p;
-  p += ALIGN(filename.size() + 1, 64);
+  p += Align(strlen(filename) + 1, 64);
   CHECK_EQ(p, end);
 }
 
-std::vector<char> BuildIdRecord::BinaryFormat() const {
-  std::vector<char> buf(header.size);
-  char* p = buf.data();
-  MoveToBinaryFormat(header, p);
-  MoveToBinaryFormat(pid, p);
-  memcpy(p, build_id.Data(), build_id.Size());
-  p += ALIGN(build_id.Size(), 8);
-  strcpy(p, filename.c_str());
-  p += ALIGN(filename.size() + 1, 64);
-  return buf;
-}
-
 void BuildIdRecord::DumpData(size_t indent) const {
   PrintIndented(indent, "pid %u\n", pid);
   PrintIndented(indent, "build_id %s\n", build_id.ToString().c_str());
-  PrintIndented(indent, "filename %s\n", filename.c_str());
+  PrintIndented(indent, "filename %s\n", filename);
 }
 
-UnknownRecord::UnknownRecord(const perf_event_header* pheader) : Record(pheader) {
-  const char* p = reinterpret_cast<const char*>(pheader + 1);
-  const char* end = reinterpret_cast<const char*>(pheader) + pheader->size;
-  data.insert(data.end(), p, end);
-}
-
-std::vector<char> UnknownRecord::BinaryFormat() const {
-  std::vector<char> buf(header.size);
-  char* p = buf.data();
+BuildIdRecord::BuildIdRecord(bool in_kernel, pid_t pid, const BuildId& build_id,
+                             const std::string& filename) {
+  SetTypeAndMisc(PERF_RECORD_BUILD_ID,
+                 in_kernel ? PERF_RECORD_MISC_KERNEL : PERF_RECORD_MISC_USER);
+  this->pid = pid;
+  this->build_id = build_id;
+  SetSize(header_size() + sizeof(pid) + Align(build_id.Size(), 8) +
+          Align(filename.size() + 1, 64));
+  char* new_binary = new char[size()];
+  char* p = new_binary;
   MoveToBinaryFormat(header, p);
-  MoveToBinaryFormat(data.data(), data.size(), p);
-  return buf;
+  MoveToBinaryFormat(pid, p);
+  memcpy(p, build_id.Data(), build_id.Size());
+  p += Align(build_id.Size(), 8);
+  this->filename = p;
+  strcpy(p, filename.c_str());
+  UpdateBinary(new_binary);
 }
 
-void UnknownRecord::DumpData(size_t) const {
+KernelSymbolRecord::KernelSymbolRecord(const char* p) : Record(p) {
+  const char* end = p + size();
+  p += header_size();
+  MoveFromBinaryFormat(kallsyms_size, p);
+  kallsyms = p;
+  p += Align(kallsyms_size, 8);
+  CHECK_EQ(p, end);
 }
 
-static std::unique_ptr<Record> ReadRecordFromBuffer(const perf_event_attr& attr,
-                                                    const perf_event_header* pheader) {
-  switch (pheader->type) {
-    case PERF_RECORD_MMAP:
-      return std::unique_ptr<Record>(new MmapRecord(attr, pheader));
-    case PERF_RECORD_MMAP2:
-      return std::unique_ptr<Record>(new Mmap2Record(attr, pheader));
-    case PERF_RECORD_COMM:
-      return std::unique_ptr<Record>(new CommRecord(attr, pheader));
-    case PERF_RECORD_EXIT:
-      return std::unique_ptr<Record>(new ExitRecord(attr, pheader));
-    case PERF_RECORD_FORK:
-      return std::unique_ptr<Record>(new ForkRecord(attr, pheader));
-    case PERF_RECORD_SAMPLE:
-      return std::unique_ptr<Record>(new SampleRecord(attr, pheader));
-    default:
-      return std::unique_ptr<Record>(new UnknownRecord(pheader));
+void KernelSymbolRecord::DumpData(size_t indent) const {
+  PrintIndented(indent, "kallsyms: %s\n",
+                std::string(kallsyms, kallsyms + kallsyms_size).c_str());
+}
+
+KernelSymbolRecord::KernelSymbolRecord(const std::string& kallsyms) {
+  SetTypeAndMisc(SIMPLE_PERF_RECORD_KERNEL_SYMBOL, 0);
+  kallsyms_size = kallsyms.size();
+  SetSize(header_size() + 4 + Align(kallsyms.size(), 8));
+  char* new_binary = new char[size()];
+  char* p = new_binary;
+  MoveToBinaryFormat(header, p);
+  MoveToBinaryFormat(kallsyms_size, p);
+  this->kallsyms = p;
+  memcpy(p, kallsyms.data(), kallsyms_size);
+  UpdateBinary(new_binary);
+}
+
+DsoRecord::DsoRecord(const char* p) : Record(p) {
+  const char* end = p + size();
+  p += header_size();
+  MoveFromBinaryFormat(dso_type, p);
+  MoveFromBinaryFormat(dso_id, p);
+  MoveFromBinaryFormat(min_vaddr, p);
+  dso_name = p;
+  p += Align(strlen(dso_name) + 1, 8);
+  CHECK_EQ(p, end);
+}
+
+DsoRecord::DsoRecord(uint64_t dso_type, uint64_t dso_id,
+                     const std::string& dso_name, uint64_t min_vaddr) {
+  SetTypeAndMisc(SIMPLE_PERF_RECORD_DSO, 0);
+  this->dso_type = dso_type;
+  this->dso_id = dso_id;
+  this->min_vaddr = min_vaddr;
+  SetSize(header_size() + 3 * sizeof(uint64_t) + Align(dso_name.size() + 1, 8));
+  char* new_binary = new char[size()];
+  char* p = new_binary;
+  MoveToBinaryFormat(header, p);
+  MoveToBinaryFormat(dso_type, p);
+  MoveToBinaryFormat(dso_id, p);
+  MoveToBinaryFormat(min_vaddr, p);
+  this->dso_name = p;
+  strcpy(p, dso_name.c_str());
+  UpdateBinary(new_binary);
+}
+
+void DsoRecord::DumpData(size_t indent) const {
+  PrintIndented(indent, "dso_type: %s(%" PRIu64 ")\n",
+                DsoTypeToString(static_cast<DsoType>(dso_type)), dso_type);
+  PrintIndented(indent, "dso_id: %" PRIu64 "\n", dso_id);
+  PrintIndented(indent, "min_vaddr: 0x%" PRIx64 "\n", min_vaddr);
+  PrintIndented(indent, "dso_name: %s\n", dso_name);
+}
+
+SymbolRecord::SymbolRecord(const char* p) : Record(p) {
+  const char* end = p + size();
+  p += header_size();
+  MoveFromBinaryFormat(addr, p);
+  MoveFromBinaryFormat(len, p);
+  MoveFromBinaryFormat(dso_id, p);
+  name = p;
+  p += Align(strlen(name) + 1, 8);
+  CHECK_EQ(p, end);
+}
+
+SymbolRecord::SymbolRecord(uint64_t addr, uint64_t len, const std::string& name,
+                           uint64_t dso_id) {
+  SetTypeAndMisc(SIMPLE_PERF_RECORD_SYMBOL, 0);
+  this->addr = addr;
+  this->len = len;
+  this->dso_id = dso_id;
+  SetSize(header_size() + 3 * sizeof(uint64_t) + Align(name.size() + 1, 8));
+  char* new_binary = new char[size()];
+  char* p = new_binary;
+  MoveToBinaryFormat(header, p);
+  MoveToBinaryFormat(addr, p);
+  MoveToBinaryFormat(len, p);
+  MoveToBinaryFormat(dso_id, p);
+  this->name = p;
+  strcpy(p, name.c_str());
+  UpdateBinary(new_binary);
+}
+
+void SymbolRecord::DumpData(size_t indent) const {
+  PrintIndented(indent, "name: %s\n", name);
+  PrintIndented(indent, "addr: 0x%" PRIx64 "\n", addr);
+  PrintIndented(indent, "len: 0x%" PRIx64 "\n", len);
+  PrintIndented(indent, "dso_id: %" PRIu64 "\n", dso_id);
+}
+
+TracingDataRecord::TracingDataRecord(const char* p) : Record(p) {
+  const char* end = p + size();
+  p += header_size();
+  MoveFromBinaryFormat(data_size, p);
+  data = p;
+  p += Align(data_size, 64);
+  CHECK_EQ(p, end);
+}
+
+TracingDataRecord::TracingDataRecord(const std::vector<char>& tracing_data) {
+  SetTypeAndMisc(PERF_RECORD_TRACING_DATA, 0);
+  data_size = tracing_data.size();
+  SetSize(header_size() + sizeof(uint32_t) + Align(tracing_data.size(), 64));
+  char* new_binary = new char[size()];
+  char* p = new_binary;
+  MoveToBinaryFormat(header, p);
+  MoveToBinaryFormat(data_size, p);
+  data = p;
+  memcpy(p, tracing_data.data(), data_size);
+  UpdateBinary(new_binary);
+}
+
+void TracingDataRecord::DumpData(size_t indent) const {
+  Tracing tracing(std::vector<char>(data, data + data_size));
+  tracing.Dump(indent);
+}
+
+EventIdRecord::EventIdRecord(const char* p) : Record(p) {
+  const char* end = p + size();
+  p += header_size();
+  MoveFromBinaryFormat(count, p);
+  data = reinterpret_cast<const EventIdData*>(p);
+  p += sizeof(data[0]) * count;
+  CHECK_EQ(p, end);
+}
+
+EventIdRecord::EventIdRecord(const std::vector<uint64_t>& data) {
+  SetTypeAndMisc(SIMPLE_PERF_RECORD_EVENT_ID, 0);
+  SetSize(header_size() + sizeof(uint64_t) * (1 + data.size()));
+  char* new_binary = new char[size()];
+  char* p = new_binary;
+  MoveToBinaryFormat(header, p);
+  count = data.size() / 2;
+  MoveToBinaryFormat(count, p);
+  this->data = reinterpret_cast<EventIdData*>(p);
+  memcpy(p, data.data(), sizeof(uint64_t) * data.size());
+  UpdateBinary(new_binary);
+}
+
+void EventIdRecord::DumpData(size_t indent) const {
+  PrintIndented(indent, "count: %" PRIu64 "\n", count);
+  for (size_t i = 0; i < count; ++i) {
+    PrintIndented(indent, "attr_id[%" PRIu64 "]: %" PRIu64 "\n", i,
+                  data[i].attr_id);
+    PrintIndented(indent, "event_id[%" PRIu64 "]: %" PRIu64 "\n", i,
+                  data[i].event_id);
   }
 }
 
-std::vector<std::unique_ptr<Record>> ReadRecordsFromBuffer(const perf_event_attr& attr,
-                                                           const char* buf, size_t buf_size) {
+UnknownRecord::UnknownRecord(const char* p) : Record(p) {
+  p += header_size();
+  data = p;
+}
+
+void UnknownRecord::DumpData(size_t) const {}
+
+std::unique_ptr<Record> ReadRecordFromBuffer(const perf_event_attr& attr,
+                                             uint32_t type, const char* p) {
+  switch (type) {
+    case PERF_RECORD_MMAP:
+      return std::unique_ptr<Record>(new MmapRecord(attr, p));
+    case PERF_RECORD_MMAP2:
+      return std::unique_ptr<Record>(new Mmap2Record(attr, p));
+    case PERF_RECORD_COMM:
+      return std::unique_ptr<Record>(new CommRecord(attr, p));
+    case PERF_RECORD_EXIT:
+      return std::unique_ptr<Record>(new ExitRecord(attr, p));
+    case PERF_RECORD_FORK:
+      return std::unique_ptr<Record>(new ForkRecord(attr, p));
+    case PERF_RECORD_LOST:
+      return std::unique_ptr<Record>(new LostRecord(attr, p));
+    case PERF_RECORD_SAMPLE:
+      return std::unique_ptr<Record>(new SampleRecord(attr, p));
+    case PERF_RECORD_TRACING_DATA:
+      return std::unique_ptr<Record>(new TracingDataRecord(p));
+    case SIMPLE_PERF_RECORD_KERNEL_SYMBOL:
+      return std::unique_ptr<Record>(new KernelSymbolRecord(p));
+    case SIMPLE_PERF_RECORD_DSO:
+      return std::unique_ptr<Record>(new DsoRecord(p));
+    case SIMPLE_PERF_RECORD_SYMBOL:
+      return std::unique_ptr<Record>(new SymbolRecord(p));
+    case SIMPLE_PERF_RECORD_EVENT_ID:
+      return std::unique_ptr<Record>(new EventIdRecord(p));
+    default:
+      return std::unique_ptr<Record>(new UnknownRecord(p));
+  }
+}
+
+std::unique_ptr<Record> ReadRecordFromOwnedBuffer(const perf_event_attr& attr,
+                                                  uint32_t type,
+                                                  const char* p) {
+  std::unique_ptr<Record> record = ReadRecordFromBuffer(attr, type, p);
+  if (record != nullptr) {
+    record->OwnBinary();
+  } else {
+    delete[] p;
+  }
+  return record;
+}
+
+std::vector<std::unique_ptr<Record>> ReadRecordsFromBuffer(
+    const perf_event_attr& attr, const char* buf, size_t buf_size) {
   std::vector<std::unique_ptr<Record>> result;
   const char* p = buf;
   const char* end = buf + buf_size;
   while (p < end) {
-    const perf_event_header* header = reinterpret_cast<const perf_event_header*>(p);
-    CHECK_LE(p + header->size, end);
-    CHECK_NE(0u, header->size);
-    result.push_back(ReadRecordFromBuffer(attr, header));
-    p += header->size;
+    RecordHeader header(p);
+    CHECK_LE(p + header.size, end);
+    CHECK_NE(0u, header.size);
+    result.push_back(ReadRecordFromBuffer(attr, header.type, p));
+    p += header.size;
   }
   return result;
 }
 
-std::unique_ptr<Record> ReadRecordFromFile(const perf_event_attr& attr, FILE* fp) {
-  std::vector<char> buf(sizeof(perf_event_header));
-  perf_event_header* header = reinterpret_cast<perf_event_header*>(&buf[0]);
-  if (fread(header, sizeof(perf_event_header), 1, fp) != 1) {
-    PLOG(ERROR) << "Failed to read record file";
-    return nullptr;
-  }
-  buf.resize(header->size);
-  header = reinterpret_cast<perf_event_header*>(&buf[0]);
-  if (fread(&buf[sizeof(perf_event_header)], buf.size() - sizeof(perf_event_header), 1, fp) != 1) {
-    PLOG(ERROR) << "Failed to read record file";
-    return nullptr;
-  }
-  return ReadRecordFromBuffer(attr, header);
-}
-
-MmapRecord CreateMmapRecord(const perf_event_attr& attr, bool in_kernel, uint32_t pid, uint32_t tid,
-                            uint64_t addr, uint64_t len, uint64_t pgoff,
-                            const std::string& filename) {
-  MmapRecord record;
-  record.header.type = PERF_RECORD_MMAP;
-  record.header.misc = (in_kernel ? PERF_RECORD_MISC_KERNEL : PERF_RECORD_MISC_USER);
-  record.data.pid = pid;
-  record.data.tid = tid;
-  record.data.addr = addr;
-  record.data.len = len;
-  record.data.pgoff = pgoff;
-  record.filename = filename;
-  size_t sample_id_size = record.sample_id.CreateContent(attr);
-  record.header.size = sizeof(record.header) + sizeof(record.data) +
-                       ALIGN(record.filename.size() + 1, 8) + sample_id_size;
-  return record;
-}
-
-CommRecord CreateCommRecord(const perf_event_attr& attr, uint32_t pid, uint32_t tid,
-                            const std::string& comm) {
-  CommRecord record;
-  record.header.type = PERF_RECORD_COMM;
-  record.header.misc = 0;
-  record.data.pid = pid;
-  record.data.tid = tid;
-  record.comm = comm;
-  size_t sample_id_size = record.sample_id.CreateContent(attr);
-  record.header.size = sizeof(record.header) + sizeof(record.data) +
-                       ALIGN(record.comm.size() + 1, 8) + sample_id_size;
-  return record;
-}
-
-ForkRecord CreateForkRecord(const perf_event_attr& attr, uint32_t pid, uint32_t tid, uint32_t ppid,
-                            uint32_t ptid) {
-  ForkRecord record;
-  record.header.type = PERF_RECORD_FORK;
-  record.header.misc = 0;
-  record.data.pid = pid;
-  record.data.ppid = ppid;
-  record.data.tid = tid;
-  record.data.ptid = ptid;
-  record.data.time = 0;
-  size_t sample_id_size = record.sample_id.CreateContent(attr);
-  record.header.size = sizeof(record.header) + sizeof(record.data) + sample_id_size;
-  return record;
-}
-
-BuildIdRecord CreateBuildIdRecord(bool in_kernel, pid_t pid, const BuildId& build_id,
-                                  const std::string& filename) {
-  BuildIdRecord record;
-  record.header.type = PERF_RECORD_BUILD_ID;
-  record.header.misc = (in_kernel ? PERF_RECORD_MISC_KERNEL : PERF_RECORD_MISC_USER);
-  record.pid = pid;
-  record.build_id = build_id;
-  record.filename = filename;
-  record.header.size = sizeof(record.header) + sizeof(record.pid) +
-                       ALIGN(record.build_id.Size(), 8) + ALIGN(filename.size() + 1, 64);
-  return record;
-}
-
-bool RecordCache::RecordWithSeq::IsHappensBefore(const RecordWithSeq& other) const {
-  bool is_sample = (record->header.type == PERF_RECORD_SAMPLE);
-  bool is_other_sample = (other.record->header.type == PERF_RECORD_SAMPLE);
+bool RecordCache::RecordWithSeq::IsHappensBefore(
+    const RecordWithSeq& other) const {
+  bool is_sample = (record->type() == PERF_RECORD_SAMPLE);
+  bool is_other_sample = (other.record->type() == PERF_RECORD_SAMPLE);
   uint64_t time = record->Timestamp();
   uint64_t other_time = other.record->Timestamp();
   // The record with smaller time happens first.
   if (time != other_time) {
     return time < other_time;
   }
-  // If happening at the same time, make non-sample records before sample records,
-  // because non-sample records may contain useful information to parse sample records.
+  // If happening at the same time, make non-sample records before sample
+  // records, because non-sample records may contain useful information to
+  // parse sample records.
   if (is_sample != is_other_sample) {
     return is_sample ? false : true;
   }
@@ -711,35 +886,28 @@
   return r2.IsHappensBefore(r1);
 }
 
-RecordCache::RecordCache(const perf_event_attr& attr, size_t min_cache_size,
+RecordCache::RecordCache(bool has_timestamp, size_t min_cache_size,
                          uint64_t min_time_diff_in_ns)
-    : attr_(attr),
-      has_timestamp_(attr.sample_id_all && (attr.sample_type & PERF_SAMPLE_TIME)),
+    : has_timestamp_(has_timestamp),
       min_cache_size_(min_cache_size),
       min_time_diff_in_ns_(min_time_diff_in_ns),
       last_time_(0),
       cur_seq_(0),
-      queue_(RecordComparator()) {
-}
+      queue_(RecordComparator()) {}
 
-RecordCache::~RecordCache() {
-  PopAll();
-}
-
-void RecordCache::Push(const char* data, size_t size) {
-  std::vector<std::unique_ptr<Record>> records = ReadRecordsFromBuffer(attr_, data, size);
-  if (has_timestamp_) {
-    for (const auto& r : records) {
-      last_time_ = std::max(last_time_, r->Timestamp());
-    }
-  }
-  for (auto& r : records) {
-    queue_.push(CreateRecordWithSeq(r.release()));
-  }
-}
+RecordCache::~RecordCache() { PopAll(); }
 
 void RecordCache::Push(std::unique_ptr<Record> record) {
-  queue_.push(CreateRecordWithSeq(record.release()));
+  if (has_timestamp_) {
+    last_time_ = std::max(last_time_, record->Timestamp());
+  }
+  queue_.push(RecordWithSeq(cur_seq_++, record.release()));
+}
+
+void RecordCache::Push(std::vector<std::unique_ptr<Record>> records) {
+  for (auto& r : records) {
+    Push(std::move(r));
+  }
 }
 
 std::unique_ptr<Record> RecordCache::Pop() {
@@ -765,9 +933,11 @@
   return result;
 }
 
-RecordCache::RecordWithSeq RecordCache::CreateRecordWithSeq(Record *r) {
-  RecordWithSeq result;
-  result.seq = cur_seq_++;
-  result.record = r;
-  return result;
+std::unique_ptr<Record> RecordCache::ForcedPop() {
+  if (queue_.empty()) {
+    return nullptr;
+  }
+  Record* r = queue_.top().record;
+  queue_.pop();
+  return std::unique_ptr<Record>(r);
 }
diff --git a/simpleperf/record.h b/simpleperf/record.h
index a94a917..02854b4 100644
--- a/simpleperf/record.h
+++ b/simpleperf/record.h
@@ -25,6 +25,8 @@
 #include <string>
 #include <vector>
 
+#include <android-base/logging.h>
+
 #include "build_id.h"
 #include "perf_event.h"
 
@@ -34,13 +36,36 @@
 struct ThreadMmap;
 
 enum user_record_type {
+  PERF_RECORD_USER_DEFINED_TYPE_START = 64,
   PERF_RECORD_ATTR = 64,
   PERF_RECORD_EVENT_TYPE,
   PERF_RECORD_TRACING_DATA,
   PERF_RECORD_BUILD_ID,
   PERF_RECORD_FINISHED_ROUND,
+
+  SIMPLE_PERF_RECORD_TYPE_START = 32768,
+  SIMPLE_PERF_RECORD_KERNEL_SYMBOL,
+  SIMPLE_PERF_RECORD_DSO,
+  SIMPLE_PERF_RECORD_SYMBOL,
+  SIMPLE_PERF_RECORD_SPLIT,
+  SIMPLE_PERF_RECORD_SPLIT_END,
+  SIMPLE_PERF_RECORD_EVENT_ID,
 };
 
+// perf_event_header uses u16 to store record size. However, that is not
+// enough for storing records like KERNEL_SYMBOL or TRACING_DATA. So define
+// a simpleperf_record_header struct to store record header for simpleperf
+// defined records (type > SIMPLE_PERF_RECORD_TYPE_START).
+struct simpleperf_record_header {
+  uint32_t type;
+  uint16_t size1;
+  uint16_t size0;
+};
+
+static_assert(
+    sizeof(simpleperf_record_header) == sizeof(perf_event_header),
+    "simpleperf_record_header should have the same size as perf_event_header");
+
 struct PerfSampleIpType {
   uint64_t ip;
 };
@@ -74,53 +99,102 @@
 };
 
 struct PerfSampleCallChainType {
-  std::vector<uint64_t> ips;
+  uint64_t ip_nr;
+  const uint64_t* ips;
 };
 
 struct PerfSampleRawType {
-  std::vector<char> data;
+  uint32_t size;
+  const char* data;
+};
+
+struct BranchStackItemType {
+  uint64_t from;
+  uint64_t to;
+  uint64_t flags;
 };
 
 struct PerfSampleBranchStackType {
-  struct BranchStackItemType {
-    uint64_t from;
-    uint64_t to;
-    uint64_t flags;
-  };
-  std::vector<BranchStackItemType> stack;
+  uint64_t stack_nr;
+  const BranchStackItemType* stack;
 };
 
 struct PerfSampleRegsUserType {
   uint64_t abi;
   uint64_t reg_mask;
-  std::vector<uint64_t> regs;
+  uint64_t reg_nr;
+  const uint64_t* regs;
 };
 
 struct PerfSampleStackUserType {
-  std::vector<char> data;
+  uint64_t size;
+  const char* data;
   uint64_t dyn_size;
 };
 
-// SampleId is optional at the end of a record in binary format. Its content is determined by
-// sample_id_all and sample_type in perf_event_attr. To avoid the complexity of referring to
-// perf_event_attr each time, we copy sample_id_all and sample_type inside the SampleId structure.
+struct RecordHeader {
+ public:
+  uint32_t type;
+  uint16_t misc;
+  uint32_t size;
+
+  RecordHeader() : type(0), misc(0), size(0) {}
+
+  explicit RecordHeader(const char* p) {
+    auto pheader = reinterpret_cast<const perf_event_header*>(p);
+    if (pheader->type < SIMPLE_PERF_RECORD_TYPE_START) {
+      type = pheader->type;
+      misc = pheader->misc;
+      size = pheader->size;
+    } else {
+      auto sheader = reinterpret_cast<const simpleperf_record_header*>(p);
+      type = sheader->type;
+      misc = 0;
+      size = (sheader->size1 << 16) | sheader->size0;
+    }
+  }
+
+  void MoveToBinaryFormat(char*& p) const {
+    if (type < SIMPLE_PERF_RECORD_TYPE_START) {
+      auto pheader = reinterpret_cast<perf_event_header*>(p);
+      pheader->type = type;
+      pheader->misc = misc;
+      CHECK_LT(size, 1u << 16);
+      pheader->size = static_cast<uint16_t>(size);
+    } else {
+      auto sheader = reinterpret_cast<simpleperf_record_header*>(p);
+      sheader->type = type;
+      CHECK_EQ(misc, 0u);
+      sheader->size1 = size >> 16;
+      sheader->size0 = size & 0xffff;
+    }
+    p += sizeof(perf_event_header);
+  }
+};
+
+// SampleId is optional at the end of a record in binary format. Its content is
+// determined by sample_id_all and sample_type in perf_event_attr. To avoid the
+// complexity of referring to perf_event_attr each time, we copy sample_id_all
+// and sample_type inside the SampleId structure.
 struct SampleId {
   bool sample_id_all;
   uint64_t sample_type;
 
-  PerfSampleTidType tid_data;             // Valid if sample_id_all && PERF_SAMPLE_TID.
-  PerfSampleTimeType time_data;           // Valid if sample_id_all && PERF_SAMPLE_TIME.
-  PerfSampleIdType id_data;               // Valid if sample_id_all && PERF_SAMPLE_ID.
-  PerfSampleStreamIdType stream_id_data;  // Valid if sample_id_all && PERF_SAMPLE_STREAM_ID.
-  PerfSampleCpuType cpu_data;             // Valid if sample_id_all && PERF_SAMPLE_CPU.
+  PerfSampleTidType tid_data;    // Valid if sample_id_all && PERF_SAMPLE_TID.
+  PerfSampleTimeType time_data;  // Valid if sample_id_all && PERF_SAMPLE_TIME.
+  PerfSampleIdType id_data;      // Valid if sample_id_all && PERF_SAMPLE_ID.
+  PerfSampleStreamIdType
+      stream_id_data;  // Valid if sample_id_all && PERF_SAMPLE_STREAM_ID.
+  PerfSampleCpuType cpu_data;  // Valid if sample_id_all && PERF_SAMPLE_CPU.
 
   SampleId();
 
   // Create the content of sample_id. It depends on the attr we use.
-  size_t CreateContent(const perf_event_attr& attr);
+  size_t CreateContent(const perf_event_attr& attr, uint64_t event_id);
 
   // Parse sample_id from binary format in the buffer pointed by p.
-  void ReadFromBinaryFormat(const perf_event_attr& attr, const char* p, const char* end);
+  void ReadFromBinaryFormat(const perf_event_attr& attr, const char* p,
+                            const char* end);
 
   // Write the binary format of sample_id to the buffer pointed by p.
   void WriteToBinaryFormat(char*& p) const;
@@ -128,36 +202,64 @@
   size_t Size() const;
 };
 
-// Usually one record contains the following three parts in order in binary format:
-//   perf_event_header (at the head of a record, containing type and size information)
+// Usually one record contains the following three parts in order in binary
+// format:
+//   RecordHeader (at the head of a record, containing type and size info)
 //   data depends on the record type
-//   sample_id (optional part at the end of a record)
-// We hold the common parts (perf_event_header and sample_id) in the base class Record, and
-// hold the type specific data part in classes derived from Record.
+//   SampleId (optional part at the end of a record)
+// We hold the common parts (RecordHeader and SampleId) in the base class
+// Record, and hold the type specific data part in classes derived from Record.
 struct Record {
-  perf_event_header header;
+  RecordHeader header;
   SampleId sample_id;
 
-  Record();
-  Record(const perf_event_header* pheader);
+  Record() : binary_(nullptr), own_binary_(false) {}
+  explicit Record(const char* p) : header(p), binary_(p), own_binary_(false) {}
+  Record(Record&& other);
 
   virtual ~Record() {
+    if (own_binary_) {
+      delete[] binary_;
+    }
   }
 
-  size_t size() const {
-    return header.size;
+  void OwnBinary() { own_binary_ = true; }
+
+  uint32_t type() const { return header.type; }
+
+  uint16_t misc() const { return header.misc; }
+
+  uint32_t size() const { return header.size; }
+
+  static uint32_t header_size() { return sizeof(perf_event_header); }
+
+  bool InKernel() const {
+    return (header.misc & PERF_RECORD_MISC_CPUMODE_MASK) ==
+           PERF_RECORD_MISC_KERNEL;
   }
 
-  uint32_t type() const {
-    return header.type;
+  void SetTypeAndMisc(uint32_t type, uint16_t misc) {
+    header.type = type;
+    header.misc = misc;
   }
 
+  void SetSize(uint32_t size) { header.size = size; }
+
   void Dump(size_t indent = 0) const;
-  virtual std::vector<char> BinaryFormat() const = 0;
+
+  const char* Binary() const { return binary_; }
+
   virtual uint64_t Timestamp() const;
+  virtual uint32_t Cpu() const;
 
  protected:
+  void UpdateBinary(const char* new_binary);
   virtual void DumpData(size_t) const = 0;
+
+  const char* binary_;
+  bool own_binary_;
+
+  DISALLOW_COPY_AND_ASSIGN(Record);
 };
 
 struct MmapRecord : public Record {
@@ -166,15 +268,18 @@
     uint64_t addr;
     uint64_t len;
     uint64_t pgoff;
-  } data;
-  std::string filename;
+  };
+  const MmapRecordDataType* data;
+  const char* filename;
 
-  MmapRecord() {  // For CreateMmapRecord.
-  }
+  MmapRecord(const perf_event_attr& attr, const char* p);
 
-  MmapRecord(const perf_event_attr& attr, const perf_event_header* pheader);
-  std::vector<char> BinaryFormat() const override;
-  void AdjustSizeBasedOnData();
+  MmapRecord(const perf_event_attr& attr, bool in_kernel, uint32_t pid,
+             uint32_t tid, uint64_t addr, uint64_t len, uint64_t pgoff,
+             const std::string& filename, uint64_t event_id, uint64_t time = 0);
+
+  void SetDataAndFilename(const MmapRecordDataType& data,
+                          const std::string& filename);
 
  protected:
   void DumpData(size_t indent) const override;
@@ -191,15 +296,14 @@
     uint64_t ino;
     uint64_t ino_generation;
     uint32_t prot, flags;
-  } data;
-  std::string filename;
+  };
+  const Mmap2RecordDataType* data;
+  const char* filename;
 
-  Mmap2Record() {
-  }
+  Mmap2Record(const perf_event_attr& attr, const char* p);
 
-  Mmap2Record(const perf_event_attr& attr, const perf_event_header* pheader);
-  std::vector<char> BinaryFormat() const override;
-  void AdjustSizeBasedOnData();
+  void SetDataAndFilename(const Mmap2RecordDataType& data,
+                          const std::string& filename);
 
  protected:
   void DumpData(size_t indent) const override;
@@ -208,14 +312,14 @@
 struct CommRecord : public Record {
   struct CommRecordDataType {
     uint32_t pid, tid;
-  } data;
-  std::string comm;
+  };
+  const CommRecordDataType* data;
+  const char* comm;
 
-  CommRecord() {
-  }
+  CommRecord(const perf_event_attr& attr, const char* p);
 
-  CommRecord(const perf_event_attr& attr, const perf_event_header* pheader);
-  std::vector<char> BinaryFormat() const override;
+  CommRecord(const perf_event_attr& attr, uint32_t pid, uint32_t tid,
+             const std::string& comm, uint64_t event_id);
 
  protected:
   void DumpData(size_t indent) const override;
@@ -226,33 +330,43 @@
     uint32_t pid, ppid;
     uint32_t tid, ptid;
     uint64_t time;
-  } data;
+  };
+  const ExitOrForkRecordDataType* data;
 
-  ExitOrForkRecord() {
-  }
-  ExitOrForkRecord(const perf_event_attr& attr, const perf_event_header* pheader);
-  std::vector<char> BinaryFormat() const override;
+  ExitOrForkRecord(const perf_event_attr& attr, const char* p);
+
+  ExitOrForkRecord() : data(nullptr) {}
 
  protected:
   void DumpData(size_t indent) const override;
 };
 
 struct ExitRecord : public ExitOrForkRecord {
-  ExitRecord(const perf_event_attr& attr, const perf_event_header* pheader)
-      : ExitOrForkRecord(attr, pheader) {
-  }
+  ExitRecord(const perf_event_attr& attr, const char* p)
+      : ExitOrForkRecord(attr, p) {}
 };
 
 struct ForkRecord : public ExitOrForkRecord {
-  ForkRecord() {
-  }
-  ForkRecord(const perf_event_attr& attr, const perf_event_header* pheader)
-      : ExitOrForkRecord(attr, pheader) {
-  }
+  ForkRecord(const perf_event_attr& attr, const char* p)
+      : ExitOrForkRecord(attr, p) {}
+
+  ForkRecord(const perf_event_attr& attr, uint32_t pid, uint32_t tid,
+             uint32_t ppid, uint32_t ptid, uint64_t event_id);
+};
+
+struct LostRecord : public Record {
+  uint64_t id;
+  uint64_t lost;
+
+  LostRecord(const perf_event_attr& attr, const char* p);
+
+ protected:
+  void DumpData(size_t indent) const override;
 };
 
 struct SampleRecord : public Record {
-  uint64_t sample_type;  // sample_type is a bit mask determining which fields below are valid.
+  uint64_t sample_type;  // sample_type is a bit mask determining which fields
+                         // below are valid.
 
   PerfSampleIpType ip_data;               // Valid if PERF_SAMPLE_IP.
   PerfSampleTidType tid_data;             // Valid if PERF_SAMPLE_TID.
@@ -263,78 +377,175 @@
   PerfSampleCpuType cpu_data;             // Valid if PERF_SAMPLE_CPU.
   PerfSamplePeriodType period_data;       // Valid if PERF_SAMPLE_PERIOD.
 
-  PerfSampleCallChainType callchain_data;       // Valid if PERF_SAMPLE_CALLCHAIN.
-  PerfSampleRawType raw_data;                   // Valid if PERF_SAMPLE_RAW.
-  PerfSampleBranchStackType branch_stack_data;  // Valid if PERF_SAMPLE_BRANCH_STACK.
-  PerfSampleRegsUserType regs_user_data;        // Valid if PERF_SAMPLE_REGS_USER.
-  PerfSampleStackUserType stack_user_data;      // Valid if PERF_SAMPLE_STACK_USER.
+  PerfSampleCallChainType callchain_data;  // Valid if PERF_SAMPLE_CALLCHAIN.
+  PerfSampleRawType raw_data;              // Valid if PERF_SAMPLE_RAW.
+  PerfSampleBranchStackType
+      branch_stack_data;                  // Valid if PERF_SAMPLE_BRANCH_STACK.
+  PerfSampleRegsUserType regs_user_data;  // Valid if PERF_SAMPLE_REGS_USER.
+  PerfSampleStackUserType stack_user_data;  // Valid if PERF_SAMPLE_STACK_USER.
 
-  SampleRecord(const perf_event_attr& attr, const perf_event_header* pheader);
-  std::vector<char> BinaryFormat() const override;
-  void AdjustSizeBasedOnData();
+  SampleRecord(const perf_event_attr& attr, const char* p);
+  void ReplaceRegAndStackWithCallChain(const std::vector<uint64_t>& ips);
   uint64_t Timestamp() const override;
+  uint32_t Cpu() const override;
+
+  uint64_t GetValidStackSize() const {
+    // If stack_user_data.dyn_size == 0, it may be because the kernel misses
+    // the patch to update dyn_size, like in N9 (See b/22612370). So assume
+    // all stack data is valid if dyn_size == 0.
+    if (stack_user_data.dyn_size == 0) {
+      return stack_user_data.size;
+    }
+    return stack_user_data.dyn_size;
+  }
 
  protected:
   void DumpData(size_t indent) const override;
 };
 
-// BuildIdRecord is defined in user-space, stored in BuildId feature section in record file.
+// BuildIdRecord is defined in user-space, stored in BuildId feature section in
+// record file.
 struct BuildIdRecord : public Record {
   uint32_t pid;
   BuildId build_id;
-  std::string filename;
+  const char* filename;
 
-  BuildIdRecord() {
-  }
+  explicit BuildIdRecord(const char* p);
 
-  BuildIdRecord(const perf_event_header* pheader);
-  std::vector<char> BinaryFormat() const override;
+  BuildIdRecord(bool in_kernel, pid_t pid, const BuildId& build_id,
+                const std::string& filename);
 
  protected:
   void DumpData(size_t indent) const override;
 };
 
-// UnknownRecord is used for unknown record types, it makes sure all unknown records
-// are not changed when modifying perf.data.
+struct KernelSymbolRecord : public Record {
+  uint32_t kallsyms_size;
+  const char* kallsyms;
+
+  explicit KernelSymbolRecord(const char* p);
+
+  explicit KernelSymbolRecord(const std::string& kallsyms);
+
+ protected:
+  void DumpData(size_t indent) const override;
+};
+
+struct DsoRecord : public Record {
+  uint64_t dso_type;
+  uint64_t dso_id;
+  uint64_t min_vaddr;
+  const char* dso_name;
+
+  explicit DsoRecord(const char* p);
+
+  DsoRecord(uint64_t dso_type, uint64_t dso_id, const std::string& dso_name,
+            uint64_t min_vaddr);
+
+ protected:
+  void DumpData(size_t indent) const override;
+};
+
+struct SymbolRecord : public Record {
+  uint64_t addr;
+  uint64_t len;
+  uint64_t dso_id;
+  const char* name;
+
+  explicit SymbolRecord(const char* p);
+
+  SymbolRecord(uint64_t addr, uint64_t len, const std::string& name,
+               uint64_t dso_id);
+
+ protected:
+  void DumpData(size_t indent) const override;
+};
+
+struct TracingDataRecord : public Record {
+  uint32_t data_size;
+  const char* data;
+
+  explicit TracingDataRecord(const char* p);
+
+  explicit TracingDataRecord(const std::vector<char>& tracing_data);
+
+ protected:
+  void DumpData(size_t indent) const override;
+};
+
+struct EventIdRecord : public Record {
+  uint64_t count;
+  struct EventIdData {
+    uint64_t attr_id;
+    uint64_t event_id;
+  } const* data;
+
+  explicit EventIdRecord(const char* p);
+
+  explicit EventIdRecord(const std::vector<uint64_t>& data);
+
+ protected:
+  void DumpData(size_t indent) const override;
+};
+
+// UnknownRecord is used for unknown record types, it makes sure all unknown
+// records are not changed when modifying perf.data.
 struct UnknownRecord : public Record {
-  std::vector<char> data;
+  const char* data;
 
-  UnknownRecord(const perf_event_header* pheader);
-  std::vector<char> BinaryFormat() const override;
+  explicit UnknownRecord(const char* p);
 
  protected:
   void DumpData(size_t indent) const override;
 };
 
+// Read record from the buffer pointed by [p]. But the record doesn't own
+// the buffer.
+std::unique_ptr<Record> ReadRecordFromBuffer(const perf_event_attr& attr,
+                                             uint32_t type, const char* p);
+
+// Read record from the buffer pointed by [p]. And the record owns the buffer.
+std::unique_ptr<Record> ReadRecordFromOwnedBuffer(const perf_event_attr& attr,
+                                                  uint32_t type, const char* p);
+
+// Read records from the buffer pointed by [buf]. None of the records own
+// the buffer.
+std::vector<std::unique_ptr<Record>> ReadRecordsFromBuffer(
+    const perf_event_attr& attr, const char* buf, size_t buf_size);
+
 // RecordCache is a cache used when receiving records from the kernel.
 // It sorts received records based on type and timestamp, and pops records
 // in sorted order. Records from the kernel need to be sorted because
 // records may come from different cpus at the same time, and it is affected
 // by the order in which we collect records from different cpus.
-// RecordCache pushes records and pops sorted record online. It uses two checks to help
-// ensure that records are popped in order. Each time we pop a record A, it is the earliest record
-// among all records in the cache. In addition, we have checks for min_cache_size and
-// min_time_diff. For min_cache_size check, we check if the cache size >= min_cache_size,
-// which is based on the assumption that if we have received (min_cache_size - 1) records
-// after record A, we are not likely to receive a record earlier than A. For min_time_diff
-// check, we check if record A is generated min_time_diff ns earlier than the latest
-// record, which is based on the assumption that if we have received a record for time t,
-// we are not likely to receive a record for time (t - min_time_diff) or earlier.
+// RecordCache pushes records and pops sorted record online. It uses two checks
+// to help ensure that records are popped in order. Each time we pop a record A,
+// it is the earliest record among all records in the cache. In addition, we
+// have checks for min_cache_size and min_time_diff. For min_cache_size check,
+// we check if the cache size >= min_cache_size, which is based on the
+// assumption that if we have received (min_cache_size - 1) records after
+// record A, we are not likely to receive a record earlier than A. For
+// min_time_diff check, we check if record A is generated min_time_diff ns
+// earlier than the latest record, which is based on the assumption that if we
+// have received a record for time t, we are not likely to receive a record for
+// time (t - min_time_diff) or earlier.
 class RecordCache {
  public:
-  RecordCache(const perf_event_attr& attr, size_t min_cache_size = 1000u,
-              uint64_t min_time_diff_in_ns = 1000000u);
+  explicit RecordCache(bool has_timestamp, size_t min_cache_size = 1000u,
+                       uint64_t min_time_diff_in_ns = 1000000u);
   ~RecordCache();
-  void Push(const char* data, size_t size);
   void Push(std::unique_ptr<Record> record);
+  void Push(std::vector<std::unique_ptr<Record>> records);
   std::unique_ptr<Record> Pop();
   std::vector<std::unique_ptr<Record>> PopAll();
+  std::unique_ptr<Record> ForcedPop();
 
  private:
   struct RecordWithSeq {
     uint32_t seq;
-    Record *record;
+    Record* record;
 
+    RecordWithSeq(uint32_t seq, Record* record) : seq(seq), record(record) {}
     bool IsHappensBefore(const RecordWithSeq& other) const;
   };
 
@@ -342,29 +553,13 @@
     bool operator()(const RecordWithSeq& r1, const RecordWithSeq& r2);
   };
 
-  RecordWithSeq CreateRecordWithSeq(Record *r);
-
-  const perf_event_attr attr_;
   bool has_timestamp_;
   size_t min_cache_size_;
   uint64_t min_time_diff_in_ns_;
   uint64_t last_time_;
   uint32_t cur_seq_;
   std::priority_queue<RecordWithSeq, std::vector<RecordWithSeq>,
-      RecordComparator> queue_;
+                      RecordComparator> queue_;
 };
 
-std::vector<std::unique_ptr<Record>> ReadRecordsFromBuffer(const perf_event_attr& attr,
-                                                           const char* buf, size_t buf_size);
-std::unique_ptr<Record> ReadRecordFromFile(const perf_event_attr& attr, FILE* fp);
-MmapRecord CreateMmapRecord(const perf_event_attr& attr, bool in_kernel, uint32_t pid, uint32_t tid,
-                            uint64_t addr, uint64_t len, uint64_t pgoff,
-                            const std::string& filename);
-CommRecord CreateCommRecord(const perf_event_attr& attr, uint32_t pid, uint32_t tid,
-                            const std::string& comm);
-ForkRecord CreateForkRecord(const perf_event_attr& attr, uint32_t pid, uint32_t tid, uint32_t ppid,
-                            uint32_t ptid);
-BuildIdRecord CreateBuildIdRecord(bool in_kernel, pid_t pid, const BuildId& build_id,
-                                  const std::string& filename);
-
 #endif  // SIMPLE_PERF_RECORD_H_
diff --git a/simpleperf/record_equal_test.h b/simpleperf/record_equal_test.h
index 03768dc..bf0568e 100644
--- a/simpleperf/record_equal_test.h
+++ b/simpleperf/record_equal_test.h
@@ -15,29 +15,31 @@
  */
 
 static void CheckMmapRecordDataEqual(const MmapRecord& r1, const MmapRecord& r2) {
-  ASSERT_EQ(0, memcmp(&r1.data, &r2.data, sizeof(r1.data)));
-  ASSERT_EQ(r1.filename, r2.filename);
+  ASSERT_EQ(0, memcmp(r1.data, r2.data, sizeof(*r1.data)));
+  ASSERT_STREQ(r1.filename, r2.filename);
 }
 
 static void CheckCommRecordDataEqual(const CommRecord& r1, const CommRecord& r2) {
-  ASSERT_EQ(0, memcmp(&r1.data, &r2.data, sizeof(r1.data)));
-  ASSERT_EQ(r1.comm, r2.comm);
+  ASSERT_EQ(0, memcmp(r1.data, r2.data, sizeof(*r1.data)));
+  ASSERT_STREQ(r1.comm, r2.comm);
 }
 
 static void CheckBuildIdRecordDataEqual(const BuildIdRecord& r1, const BuildIdRecord& r2) {
   ASSERT_EQ(r1.pid, r2.pid);
   ASSERT_EQ(r1.build_id, r2.build_id);
-  ASSERT_EQ(r1.filename, r2.filename);
+  ASSERT_STREQ(r1.filename, r2.filename);
 }
 
 static void CheckRecordEqual(const Record& r1, const Record& r2) {
-  ASSERT_EQ(0, memcmp(&r1.header, &r2.header, sizeof(r1.header)));
+  ASSERT_EQ(r1.type(), r2.type());
+  ASSERT_EQ(r1.misc(), r2.misc());
+  ASSERT_EQ(r1.size(), r2.size());
   ASSERT_EQ(0, memcmp(&r1.sample_id, &r2.sample_id, sizeof(r1.sample_id)));
-  if (r1.header.type == PERF_RECORD_MMAP) {
+  if (r1.type() == PERF_RECORD_MMAP) {
     CheckMmapRecordDataEqual(static_cast<const MmapRecord&>(r1), static_cast<const MmapRecord&>(r2));
-  } else if (r1.header.type == PERF_RECORD_COMM) {
+  } else if (r1.type() == PERF_RECORD_COMM) {
     CheckCommRecordDataEqual(static_cast<const CommRecord&>(r1), static_cast<const CommRecord&>(r2));
-  } else if (r1.header.type == PERF_RECORD_BUILD_ID) {
+  } else if (r1.type() == PERF_RECORD_BUILD_ID) {
     CheckBuildIdRecordDataEqual(static_cast<const BuildIdRecord&>(r1),
                                 static_cast<const BuildIdRecord&>(r2));
   }
diff --git a/simpleperf/record_file.h b/simpleperf/record_file.h
index c0f53b1..9277570 100644
--- a/simpleperf/record_file.h
+++ b/simpleperf/record_file.h
@@ -23,6 +23,7 @@
 #include <map>
 #include <memory>
 #include <string>
+#include <unordered_map>
 #include <vector>
 
 #include <android-base/macros.h>
@@ -44,11 +45,8 @@
   ~RecordFileWriter();
 
   bool WriteAttrSection(const std::vector<AttrWithId>& attr_ids);
-  bool WriteData(const void* buf, size_t len);
-
-  bool WriteData(const std::vector<char>& data) {
-    return WriteData(data.data(), data.size());
-  }
+  bool WriteRecord(const Record& record);
+  bool SortDataSection();
 
   bool WriteFeatureHeader(size_t feature_count);
   bool WriteBuildIdFeature(const std::vector<BuildIdRecord>& build_id_records);
@@ -67,7 +65,10 @@
                              std::vector<std::string>* hit_kernel_modules,
                              std::vector<std::string>* hit_user_files);
   bool WriteFileHeader();
+  bool WriteData(const void* buf, size_t len);
   bool Write(const void* buf, size_t len);
+  std::unique_ptr<Record> ReadRecordFromFile(FILE* fp, std::vector<char>& buf);
+  bool WriteRecordToFile(FILE* fp, std::unique_ptr<Record> r);
   bool SeekFileEnd(uint64_t* file_end);
   bool WriteFeatureBegin(uint64_t* start_offset);
   bool WriteFeatureEnd(int feature, uint64_t start_offset);
@@ -99,17 +100,38 @@
     return header_;
   }
 
-  const std::vector<PerfFileFormat::FileAttr>& AttrSection() const {
-    return file_attrs_;
+  std::vector<AttrWithId> AttrSection() const {
+    std::vector<AttrWithId> result(file_attrs_.size());
+    for (size_t i = 0; i < file_attrs_.size(); ++i) {
+      result[i].attr = &file_attrs_[i].attr;
+      result[i].ids = event_ids_for_file_attrs_[i];
+    }
+    return result;
   }
 
   const std::map<int, PerfFileFormat::SectionDesc>& FeatureSectionDescriptors() const {
     return feature_section_descriptors_;
   }
+  bool HasFeature(int feature) const {
+    return feature_section_descriptors_.find(feature) != feature_section_descriptors_.end();
+  }
+  bool ReadFeatureSection(int feature, std::vector<char>* data);
 
-  bool ReadIdsForAttr(const PerfFileFormat::FileAttr& attr, std::vector<uint64_t>* ids);
+  // There are two ways to read records in data section: one is by calling
+  // ReadDataSection(), and [callback] is called for each Record. the other
+  // is by calling ReadRecord() in a loop.
+
   // If sorted is true, sort records before passing them to callback function.
-  bool ReadDataSection(std::function<bool(std::unique_ptr<Record>)> callback, bool sorted = true);
+  bool ReadDataSection(const std::function<bool(std::unique_ptr<Record>)>& callback,
+                       bool sorted = true);
+
+  // Read next record. If read successfully, set [record] and return true.
+  // If there is no more records, set [record] to nullptr and return true.
+  // Otherwise return false.
+  bool ReadRecord(std::unique_ptr<Record>& record, bool sorted = true);
+
+  size_t GetAttrIndexOfRecord(const SampleRecord& record);
+
   std::vector<std::string> ReadCmdlineFeature();
   std::vector<BuildIdRecord> ReadBuildIdFeature();
   std::string ReadFeatureString(int feature);
@@ -122,16 +144,27 @@
   RecordFileReader(const std::string& filename, FILE* fp);
   bool ReadHeader();
   bool ReadAttrSection();
+  bool ReadIdsForAttr(const PerfFileFormat::FileAttr& attr, std::vector<uint64_t>* ids);
   bool ReadFeatureSectionDescriptors();
-  bool ReadFeatureSection(int feature, std::vector<char>* data);
+  std::unique_ptr<Record> ReadRecord(uint64_t* nbytes_read);
+  bool Read(void* buf, size_t len);
+  void ProcessEventIdRecord(const EventIdRecord& r);
 
   const std::string filename_;
   FILE* record_fp_;
 
   PerfFileFormat::FileHeader header_;
   std::vector<PerfFileFormat::FileAttr> file_attrs_;
+  std::vector<std::vector<uint64_t>> event_ids_for_file_attrs_;
+  std::unordered_map<uint64_t, size_t> event_id_to_attr_map_;
   std::map<int, PerfFileFormat::SectionDesc> feature_section_descriptors_;
 
+  size_t event_id_pos_in_sample_records_;
+  size_t event_id_reverse_pos_in_non_sample_records_;
+
+  std::unique_ptr<RecordCache> record_cache_;
+  uint64_t read_record_size_;
+
   DISALLOW_COPY_AND_ASSIGN(RecordFileReader);
 };
 
diff --git a/simpleperf/record_file_reader.cpp b/simpleperf/record_file_reader.cpp
index f126a6b..68fcc4c 100644
--- a/simpleperf/record_file_reader.cpp
+++ b/simpleperf/record_file_reader.cpp
@@ -23,7 +23,7 @@
 
 #include <android-base/logging.h>
 
-#include "perf_event.h"
+#include "event_attr.h"
 #include "record.h"
 #include "utils.h"
 
@@ -45,7 +45,8 @@
 }
 
 RecordFileReader::RecordFileReader(const std::string& filename, FILE* fp)
-    : filename_(filename), record_fp_(fp) {
+    : filename_(filename), record_fp_(fp), event_id_pos_in_sample_records_(0),
+      event_id_reverse_pos_in_non_sample_records_(0), read_record_size_(0) {
 }
 
 RecordFileReader::~RecordFileReader() {
@@ -65,11 +66,7 @@
 }
 
 bool RecordFileReader::ReadHeader() {
-  if (fread(&header_, sizeof(header_), 1, record_fp_) != 1) {
-    PLOG(ERROR) << "failed to read file " << filename_;
-    return false;
-  }
-  return true;
+  return Read(&header_, sizeof(header_));
 }
 
 bool RecordFileReader::ReadAttrSection() {
@@ -83,13 +80,12 @@
     return false;
   }
   if (fseek(record_fp_, header_.attrs.offset, SEEK_SET) != 0) {
-    PLOG(ERROR) << "failed to fseek()";
+    PLOG(ERROR) << "fseek() failed";
     return false;
   }
   for (size_t i = 0; i < attr_count; ++i) {
     std::vector<char> buf(header_.attr_size);
-    if (fread(&buf[0], buf.size(), 1, record_fp_) != 1) {
-      PLOG(ERROR) << "failed to read " << filename_;
+    if (!Read(buf.data(), buf.size())) {
       return false;
     }
     // The size of perf_event_attr is changing between different linux kernel versions.
@@ -102,6 +98,26 @@
     memcpy(&attr.ids, &buf[perf_event_attr_size], section_desc_size);
     file_attrs_.push_back(attr);
   }
+  if (file_attrs_.size() > 1) {
+    std::vector<perf_event_attr> attrs;
+    for (const auto& file_attr : file_attrs_) {
+      attrs.push_back(file_attr.attr);
+    }
+    if (!GetCommonEventIdPositionsForAttrs(attrs, &event_id_pos_in_sample_records_,
+                                               &event_id_reverse_pos_in_non_sample_records_)) {
+      return false;
+    }
+  }
+  for (size_t i = 0; i < file_attrs_.size(); ++i) {
+    std::vector<uint64_t> ids;
+    if (!ReadIdsForAttr(file_attrs_[i], &ids)) {
+      return false;
+    }
+    event_ids_for_file_attrs_.push_back(ids);
+    for (auto id : ids) {
+      event_id_to_attr_map_[id] = i;
+    }
+  }
   return true;
 }
 
@@ -116,13 +132,12 @@
   }
   uint64_t feature_section_offset = header_.data.offset + header_.data.size;
   if (fseek(record_fp_, feature_section_offset, SEEK_SET) != 0) {
-    PLOG(ERROR) << "failed to fseek()";
+    PLOG(ERROR) << "fseek() failed";
     return false;
   }
   for (const auto& id : features) {
     SectionDesc desc;
-    if (fread(&desc, sizeof(desc), 1, record_fp_) != 1) {
-      PLOG(ERROR) << "failed to read " << filename_;
+    if (!Read(&desc, sizeof(desc))) {
       return false;
     }
     feature_section_descriptors_.emplace(id, desc);
@@ -133,53 +148,158 @@
 bool RecordFileReader::ReadIdsForAttr(const FileAttr& attr, std::vector<uint64_t>* ids) {
   size_t id_count = attr.ids.size / sizeof(uint64_t);
   if (fseek(record_fp_, attr.ids.offset, SEEK_SET) != 0) {
-    PLOG(ERROR) << "failed to fseek()";
+    PLOG(ERROR) << "fseek() failed";
     return false;
   }
   ids->resize(id_count);
-  if (fread(ids->data(), attr.ids.size, 1, record_fp_) != 1) {
-    PLOG(ERROR) << "failed to read file " << filename_;
+  if (!Read(ids->data(), attr.ids.size)) {
     return false;
   }
   return true;
 }
 
-bool RecordFileReader::ReadDataSection(std::function<bool(std::unique_ptr<Record>)> callback,
-                                       bool sorted) {
-  if (fseek(record_fp_, header_.data.offset, SEEK_SET) != 0) {
-    PLOG(ERROR) << "failed to fseek()";
-    return false;
-  }
-  RecordCache cache(file_attrs_[0].attr);
-  for (size_t nbytes_read = 0; nbytes_read < header_.data.size;) {
-    std::unique_ptr<Record> record = ReadRecordFromFile(file_attrs_[0].attr, record_fp_);
+bool RecordFileReader::ReadDataSection(
+    const std::function<bool(std::unique_ptr<Record>)>& callback, bool sorted) {
+  std::unique_ptr<Record> record;
+  while (ReadRecord(record, sorted)) {
     if (record == nullptr) {
-      return false;
+      return true;
     }
-    nbytes_read += record->size();
-    if (sorted) {
-      cache.Push(std::move(record));
-      record = cache.Pop();
-      if (record != nullptr) {
-        if (!callback(std::move(record))) {
-          return false;
-        }
-      }
-    } else {
-      if (!callback(std::move(record))) {
-        return false;
-      }
-    }
-  }
-  std::vector<std::unique_ptr<Record>> records = cache.PopAll();
-  for (auto& record : records) {
     if (!callback(std::move(record))) {
       return false;
     }
   }
+  return false;
+}
+
+bool RecordFileReader::ReadRecord(std::unique_ptr<Record>& record,
+                                  bool sorted) {
+  if (read_record_size_ == 0) {
+    if (fseek(record_fp_, header_.data.offset, SEEK_SET) != 0) {
+      PLOG(ERROR) << "fseek() failed";
+      return false;
+    }
+    bool has_timestamp = true;
+    for (const auto& attr : file_attrs_) {
+      if (!IsTimestampSupported(attr.attr)) {
+        has_timestamp = false;
+        break;
+      }
+    }
+    record_cache_.reset(new RecordCache(has_timestamp));
+  }
+  record = nullptr;
+  while (read_record_size_ < header_.data.size && record == nullptr) {
+    record = ReadRecord(&read_record_size_);
+    if (record == nullptr) {
+      return false;
+    }
+    if (record->type() == SIMPLE_PERF_RECORD_EVENT_ID) {
+      ProcessEventIdRecord(*static_cast<EventIdRecord*>(record.get()));
+    }
+    if (sorted) {
+      record_cache_->Push(std::move(record));
+      record = record_cache_->Pop();
+    }
+  }
+  if (record == nullptr) {
+    record = record_cache_->ForcedPop();
+  }
   return true;
 }
 
+std::unique_ptr<Record> RecordFileReader::ReadRecord(uint64_t* nbytes_read) {
+  char header_buf[Record::header_size()];
+  if (!Read(header_buf, Record::header_size())) {
+    return nullptr;
+  }
+  RecordHeader header(header_buf);
+  std::unique_ptr<char[]> p;
+  if (header.type == SIMPLE_PERF_RECORD_SPLIT) {
+    // Read until meeting a RECORD_SPLIT_END record.
+    std::vector<char> buf;
+    size_t cur_size = 0;
+    char header_buf[Record::header_size()];
+    while (header.type == SIMPLE_PERF_RECORD_SPLIT) {
+      size_t bytes_to_read = header.size - Record::header_size();
+      buf.resize(cur_size + bytes_to_read);
+      if (!Read(&buf[cur_size], bytes_to_read)) {
+        return nullptr;
+      }
+      cur_size += bytes_to_read;
+      *nbytes_read += header.size;
+      if (!Read(header_buf, Record::header_size())) {
+        return nullptr;
+      }
+      header = RecordHeader(header_buf);
+    }
+    if (header.type != SIMPLE_PERF_RECORD_SPLIT_END) {
+      LOG(ERROR) << "SPLIT records are not followed by a SPLIT_END record.";
+      return nullptr;
+    }
+    *nbytes_read += header.size;
+    header = RecordHeader(buf.data());
+    p.reset(new char[header.size]);
+    memcpy(p.get(), buf.data(), buf.size());
+  } else {
+    p.reset(new char[header.size]);
+    memcpy(p.get(), header_buf, Record::header_size());
+    if (header.size > Record::header_size()) {
+      if (!Read(p.get() + Record::header_size(), header.size - Record::header_size())) {
+        return nullptr;
+      }
+    }
+    *nbytes_read += header.size;
+  }
+
+  const perf_event_attr* attr = &file_attrs_[0].attr;
+  if (file_attrs_.size() > 1 && header.type < PERF_RECORD_USER_DEFINED_TYPE_START) {
+    bool has_event_id = false;
+    uint64_t event_id;
+    if (header.type == PERF_RECORD_SAMPLE) {
+      if (header.size > event_id_pos_in_sample_records_ + sizeof(uint64_t)) {
+        has_event_id = true;
+        event_id = *reinterpret_cast<uint64_t*>(p.get() + event_id_pos_in_sample_records_);
+      }
+    } else {
+      if (header.size > event_id_reverse_pos_in_non_sample_records_) {
+        has_event_id = true;
+        event_id = *reinterpret_cast<uint64_t*>(p.get() + header.size - event_id_reverse_pos_in_non_sample_records_);
+      }
+    }
+    if (has_event_id) {
+      auto it = event_id_to_attr_map_.find(event_id);
+      if (it != event_id_to_attr_map_.end()) {
+        attr = &file_attrs_[it->second].attr;
+      }
+    }
+  }
+  return ReadRecordFromOwnedBuffer(*attr, header.type, p.release());
+}
+
+bool RecordFileReader::Read(void* buf, size_t len) {
+  if (len != 0 && fread(buf, len, 1, record_fp_) != 1) {
+    PLOG(FATAL) << "failed to read file " << filename_;
+    return false;
+  }
+  return true;
+}
+
+void RecordFileReader::ProcessEventIdRecord(const EventIdRecord& r) {
+  for (size_t i = 0; i < r.count; ++i) {
+    event_ids_for_file_attrs_[r.data[i].attr_id].push_back(r.data[i].event_id);
+    event_id_to_attr_map_[r.data[i].event_id] = r.data[i].attr_id;
+  }
+}
+
+size_t RecordFileReader::GetAttrIndexOfRecord(const SampleRecord& record) {
+  auto it = event_id_to_attr_map_.find(record.id_data.id);
+  if (it != event_id_to_attr_map_.end()) {
+    return it->second;
+  }
+  return 0;
+}
+
 bool RecordFileReader::ReadFeatureSection(int feature, std::vector<char>* data) {
   const std::map<int, SectionDesc>& section_map = FeatureSectionDescriptors();
   auto it = section_map.find(feature);
@@ -188,12 +308,14 @@
   }
   SectionDesc section = it->second;
   data->resize(section.size);
+  if (section.size == 0) {
+    return true;
+  }
   if (fseek(record_fp_, section.offset, SEEK_SET) != 0) {
-    PLOG(ERROR) << "failed to fseek()";
+    PLOG(ERROR) << "fseek() failed";
     return false;
   }
-  if (fread(data->data(), data->size(), 1, record_fp_) != 1) {
-    PLOG(ERROR) << "failed to read " << filename_;
+  if (!Read(data->data(), data->size())) {
     return false;
   }
   return true;
@@ -229,13 +351,12 @@
   const char* end = buf.data() + buf.size();
   std::vector<BuildIdRecord> result;
   while (p < end) {
-    const perf_event_header* header = reinterpret_cast<const perf_event_header*>(p);
-    CHECK_LE(p + header->size, end);
-    BuildIdRecord record(header);
+    BuildIdRecord record(p);
     // Set type explicitly as the perf.data produced by perf doesn't set it.
-    record.header.type = PERF_RECORD_BUILD_ID;
-    result.push_back(record);
-    p += header->size;
+    record.SetTypeAndMisc(PERF_RECORD_BUILD_ID, record.misc());
+    CHECK_LE(p + record.size(), end);
+    p += record.size();
+    result.push_back(std::move(record));
   }
   return result;
 }
diff --git a/simpleperf/record_file_test.cpp b/simpleperf/record_file_test.cpp
index 4648a64..f67894d 100644
--- a/simpleperf/record_file_test.cpp
+++ b/simpleperf/record_file_test.cpp
@@ -38,6 +38,7 @@
     std::unique_ptr<EventTypeAndModifier> event_type_modifier = ParseEventType(event_type_str);
     ASSERT_TRUE(event_type_modifier != nullptr);
     perf_event_attr attr = CreateDefaultPerfEventAttr(event_type_modifier->event_type);
+    attr.sample_id_all = 1;
     attrs_.push_back(std::unique_ptr<perf_event_attr>(new perf_event_attr(attr)));
     AttrWithId attr_id;
     attr_id.attr = attrs_.back().get();
@@ -60,9 +61,9 @@
   ASSERT_TRUE(writer->WriteAttrSection(attr_ids_));
 
   // Write data section.
-  MmapRecord mmap_record = CreateMmapRecord(*(attr_ids_[0].attr), true, 1, 1, 0x1000, 0x2000,
-                                            0x3000, "mmap_record_example");
-  ASSERT_TRUE(writer->WriteData(mmap_record.BinaryFormat()));
+  MmapRecord mmap_record(*(attr_ids_[0].attr), true, 1, 1, 0x1000, 0x2000,
+                         0x3000, "mmap_record_example", attr_ids_[0].ids[0]);
+  ASSERT_TRUE(writer->WriteRecord(mmap_record));
 
   // Write feature section.
   ASSERT_TRUE(writer->WriteFeatureHeader(1));
@@ -71,19 +72,18 @@
     p[i] = i;
   }
   BuildId build_id(p);
-  BuildIdRecord build_id_record = CreateBuildIdRecord(false, getpid(), build_id, "init");
-  ASSERT_TRUE(writer->WriteBuildIdFeature({build_id_record}));
+  std::vector<BuildIdRecord> build_id_records;
+  build_id_records.push_back(BuildIdRecord(false, getpid(), build_id, "init"));
+  ASSERT_TRUE(writer->WriteBuildIdFeature(build_id_records));
   ASSERT_TRUE(writer->Close());
 
   // Read from a record file.
   std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(tmpfile_.path);
   ASSERT_TRUE(reader != nullptr);
-  const std::vector<FileAttr>& file_attrs = reader->AttrSection();
-  ASSERT_EQ(1u, file_attrs.size());
-  ASSERT_EQ(0, memcmp(&file_attrs[0].attr, attr_ids_[0].attr, sizeof(perf_event_attr)));
-  std::vector<uint64_t> ids;
-  ASSERT_TRUE(reader->ReadIdsForAttr(file_attrs[0], &ids));
-  ASSERT_EQ(ids, attr_ids_[0].ids);
+  std::vector<AttrWithId> attrs = reader->AttrSection();
+  ASSERT_EQ(1u, attrs.size());
+  ASSERT_EQ(0, memcmp(attrs[0].attr, attr_ids_[0].attr, sizeof(perf_event_attr)));
+  ASSERT_EQ(attrs[0].ids, attr_ids_[0].ids);
 
   // Read and check data section.
   std::vector<std::unique_ptr<Record>> records = reader->DataSection();
@@ -91,9 +91,9 @@
   CheckRecordEqual(mmap_record, *records[0]);
 
   // Read and check feature section.
-  std::vector<BuildIdRecord> build_id_records = reader->ReadBuildIdFeature();
-  ASSERT_EQ(1u, build_id_records.size());
-  CheckRecordEqual(build_id_record, build_id_records[0]);
+  std::vector<BuildIdRecord> read_build_id_records = reader->ReadBuildIdFeature();
+  ASSERT_EQ(1u, read_build_id_records.size());
+  CheckRecordEqual(read_build_id_records[0], build_id_records[0]);
 
   ASSERT_TRUE(reader->Close());
 }
@@ -110,16 +110,15 @@
   ASSERT_TRUE(writer->WriteAttrSection(attr_ids_));
 
   // Write data section.
-  MmapRecord r1 =
-      CreateMmapRecord(*(attr_ids_[0].attr), true, 1, 1, 0x100, 0x2000, 0x3000, "mmap_record1");
-  MmapRecord r2 = r1;
-  MmapRecord r3 = r1;
-  r1.sample_id.time_data.time = 2;
-  r2.sample_id.time_data.time = 1;
-  r3.sample_id.time_data.time = 3;
-  ASSERT_TRUE(writer->WriteData(r1.BinaryFormat()));
-  ASSERT_TRUE(writer->WriteData(r2.BinaryFormat()));
-  ASSERT_TRUE(writer->WriteData(r3.BinaryFormat()));
+  MmapRecord r1(*(attr_ids_[0].attr), true, 1, 1, 0x100, 0x2000, 0x3000, "mmap_record1",
+                attr_ids_[0].ids[0], 2);
+  MmapRecord r2(*(attr_ids_[0].attr), true, 1, 1, 0x100, 0x2000, 0x3000, "mmap_record1",
+                attr_ids_[0].ids[0], 1);
+  MmapRecord r3(*(attr_ids_[0].attr), true, 1, 1, 0x100, 0x2000, 0x3000, "mmap_record1",
+                attr_ids_[0].ids[0], 3);
+  ASSERT_TRUE(writer->WriteRecord(r1));
+  ASSERT_TRUE(writer->WriteRecord(r2));
+  ASSERT_TRUE(writer->WriteRecord(r3));
   ASSERT_TRUE(writer->Close());
 
   // Read from a record file.
@@ -150,12 +149,10 @@
   // Read from a record file.
   std::unique_ptr<RecordFileReader> reader = RecordFileReader::CreateInstance(tmpfile_.path);
   ASSERT_TRUE(reader != nullptr);
-  const std::vector<FileAttr>& file_attrs = reader->AttrSection();
-  ASSERT_EQ(3u, file_attrs.size());
-  for (size_t i = 0; i < file_attrs.size(); ++i) {
-    ASSERT_EQ(0, memcmp(&file_attrs[i].attr, attr_ids_[i].attr, sizeof(perf_event_attr)));
-    std::vector<uint64_t> ids;
-    ASSERT_TRUE(reader->ReadIdsForAttr(file_attrs[i], &ids));
-    ASSERT_EQ(ids, attr_ids_[i].ids);
+  std::vector<AttrWithId> attrs = reader->AttrSection();
+  ASSERT_EQ(3u, attrs.size());
+  for (size_t i = 0; i < attrs.size(); ++i) {
+    ASSERT_EQ(0, memcmp(attrs[i].attr, attr_ids_[i].attr, sizeof(perf_event_attr)));
+    ASSERT_EQ(attrs[i].ids, attr_ids_[i].ids);
   }
 }
diff --git a/simpleperf/record_file_writer.cpp b/simpleperf/record_file_writer.cpp
index dddd0b0..94de4a7 100644
--- a/simpleperf/record_file_writer.cpp
+++ b/simpleperf/record_file_writer.cpp
@@ -22,11 +22,13 @@
 #include <unistd.h>
 #include <set>
 #include <string>
+#include <unordered_map>
 #include <vector>
 
 #include <android-base/file.h>
 #include <android-base/logging.h>
 
+#include "event_attr.h"
 #include "perf_event.h"
 #include "record.h"
 #include "utils.h"
@@ -77,7 +79,7 @@
   }
 
   // Write id section.
-  long id_section_offset = ftell(record_fp_);
+  off_t id_section_offset = ftello(record_fp_);
   if (id_section_offset == -1) {
     return false;
   }
@@ -88,7 +90,7 @@
   }
 
   // Write attr section.
-  long attr_section_offset = ftell(record_fp_);
+  off_t attr_section_offset = ftello(record_fp_);
   if (attr_section_offset == -1) {
     return false;
   }
@@ -103,7 +105,7 @@
     }
   }
 
-  long data_section_offset = ftell(record_fp_);
+  off_t data_section_offset = ftello(record_fp_);
   if (data_section_offset == -1) {
     return false;
   }
@@ -117,6 +119,44 @@
   return true;
 }
 
+bool RecordFileWriter::WriteRecord(const Record& record) {
+  // linux-tools-perf only accepts records with size <= 65535 bytes. To make
+  // perf.data generated by simpleperf be able to be parsed by linux-tools-perf,
+  // Split simpleperf custom records which are > 65535 into a bunch of
+  // RECORD_SPLIT records, followed by a RECORD_SPLIT_END record.
+  constexpr uint32_t RECORD_SIZE_LIMIT = 65535;
+  if (record.size() <= RECORD_SIZE_LIMIT) {
+    WriteData(record.Binary(), record.size());
+    return true;
+  }
+  CHECK_GT(record.type(), SIMPLE_PERF_RECORD_TYPE_START);
+  const char* p = record.Binary();
+  uint32_t left_bytes = static_cast<uint32_t>(record.size());
+  RecordHeader header;
+  header.type = SIMPLE_PERF_RECORD_SPLIT;
+  char header_buf[Record::header_size()];
+  char* header_p;
+  while (left_bytes > 0) {
+    uint32_t bytes_to_write = std::min(RECORD_SIZE_LIMIT - Record::header_size(), left_bytes);
+    header.size = bytes_to_write + Record::header_size();
+    header_p = header_buf;
+    header.MoveToBinaryFormat(header_p);
+    if (!WriteData(header_buf, Record::header_size())) {
+      return false;
+    }
+    if (!WriteData(p, bytes_to_write)) {
+      return false;
+    }
+    p += bytes_to_write;
+    left_bytes -= bytes_to_write;
+  }
+  header.type = SIMPLE_PERF_RECORD_SPLIT_END;
+  header.size = Record::header_size();
+  header_p = header_buf;
+  header.MoveToBinaryFormat(header_p);
+  return WriteData(header_buf, Record::header_size());
+}
+
 bool RecordFileWriter::WriteData(const void* buf, size_t len) {
   if (!Write(buf, len)) {
     return false;
@@ -133,14 +173,140 @@
   return true;
 }
 
+std::unique_ptr<Record> RecordFileWriter::ReadRecordFromFile(FILE* fp, std::vector<char>& buf) {
+  if (buf.size() < sizeof(perf_event_header)) {
+    buf.resize(sizeof(perf_event_header));
+  }
+  auto pheader = reinterpret_cast<perf_event_header*>(buf.data());
+  if (fread(pheader, sizeof(*pheader), 1, fp) != 1) {
+    PLOG(ERROR) << "read failed";
+    return nullptr;
+  }
+  if (pheader->size > sizeof(*pheader)) {
+    if (pheader->size > buf.size()) {
+      buf.resize(pheader->size);
+    }
+    pheader = reinterpret_cast<perf_event_header*>(buf.data());
+    if (fread(pheader + 1, pheader->size - sizeof(*pheader), 1, fp) != 1) {
+      PLOG(ERROR) << "read failed";
+      return nullptr;
+    }
+  }
+  return ReadRecordFromBuffer(event_attr_, pheader->type, buf.data());
+}
+
+bool RecordFileWriter::WriteRecordToFile(FILE* fp, std::unique_ptr<Record> r) {
+  if (fwrite(r->Binary(), r->size(), 1, fp) != 1) {
+    PLOG(ERROR) << "write failed";
+    return false;
+  }
+  return true;
+}
+
+// SortDataSection() sorts records in data section in time order.
+// This method is suitable for the situation that there is only one buffer
+// between kernel and simpleperf for each cpu. The order of records in each
+// cpu buffer is already sorted, so we only need to merge records from different
+// cpu buffers.
+// 1. Create one temporary file for each cpu, and write records to different
+//    temporary files according to their cpu value.
+// 2. Use RecordCache to merge records from different temporary files.
+bool RecordFileWriter::SortDataSection() {
+  if (!IsTimestampSupported(event_attr_) || !IsCpuSupported(event_attr_)) {
+    // Omit the sort if either timestamp or cpu is not recorded.
+    return true;
+  }
+  struct CpuData {
+    std::string path;
+    FILE* fp;
+    std::vector<char> buf;
+    uint64_t data_size;
+
+    explicit CpuData(const std::string& path) : path(path), fp(nullptr), data_size(0) {
+      fp = fopen(path.c_str(), "web+");
+    }
+    ~CpuData() {
+      fclose(fp);
+      unlink(path.c_str());
+    }
+  };
+  std::unordered_map<uint32_t, std::unique_ptr<CpuData>> cpu_map;
+  if (fseek(record_fp_, data_section_offset_, SEEK_SET) == -1) {
+    PLOG(ERROR) << "fseek() failed";
+    return false;
+  }
+  uint64_t cur_size = 0;
+  std::vector<char> global_buf;
+  while (cur_size < data_section_size_) {
+    std::unique_ptr<Record> r = ReadRecordFromFile(record_fp_, global_buf);
+    if (r == nullptr) {
+      return false;
+    }
+    cur_size += r->size();
+    std::unique_ptr<CpuData>& cpu_data = cpu_map[r->Cpu()];
+    if (cpu_data == nullptr) {
+      // Create temporary file in the same directory as filename_, because we
+      // may not have permission to create temporary file in other directories.
+      cpu_data.reset(new CpuData(filename_ + "." + std::to_string(r->Cpu())));
+      if (cpu_data->fp == nullptr) {
+        PLOG(ERROR) << "failed to open tmpfile " << cpu_data->path;
+        return false;
+      }
+    }
+    cpu_data->data_size += r->size();
+    if (!WriteRecordToFile(cpu_data->fp, std::move(r))) {
+      return false;
+    }
+  }
+  if (fseek(record_fp_, data_section_offset_, SEEK_SET) == -1) {
+    PLOG(ERROR) << "fseek() failed";
+    return false;
+  }
+  RecordCache global_cache(true);
+  for (auto it = cpu_map.begin(); it != cpu_map.end(); ++it) {
+    if (fseek(it->second->fp, 0, SEEK_SET) == -1) {
+      PLOG(ERROR) << "fseek() failed";
+      return false;
+    }
+    std::unique_ptr<Record> r = ReadRecordFromFile(it->second->fp, it->second->buf);
+    if (r == nullptr) {
+      return false;
+    }
+    it->second->data_size -= r->size();
+    global_cache.Push(std::move(r));
+  }
+  while (true) {
+    std::unique_ptr<Record> r = global_cache.ForcedPop();
+    if (r == nullptr) {
+      break;
+    }
+    uint32_t cpu = r->Cpu();
+    if (!WriteRecordToFile(record_fp_, std::move(r))) {
+      return false;
+    }
+    // Each time writing one record of a cpu, push the next record from the
+    // temporary file belong to that cpu into the record cache.
+    std::unique_ptr<CpuData>& cpu_data = cpu_map[cpu];
+    if (cpu_data->data_size > 0) {
+      r = ReadRecordFromFile(cpu_data->fp, cpu_data->buf);
+      if (r == nullptr) {
+        return false;
+      }
+      cpu_data->data_size -= r->size();
+      global_cache.Push(std::move(r));
+    }
+  }
+  return true;
+}
+
 bool RecordFileWriter::SeekFileEnd(uint64_t* file_end) {
   if (fseek(record_fp_, 0, SEEK_END) == -1) {
     PLOG(ERROR) << "fseek() failed";
     return false;
   }
-  long offset = ftell(record_fp_);
+  off_t offset = ftello(record_fp_);
   if (offset == -1) {
-    PLOG(ERROR) << "ftell() failed";
+    PLOG(ERROR) << "ftello() failed";
     return false;
   }
   *file_end = static_cast<uint64_t>(offset);
@@ -167,8 +333,7 @@
     return false;
   }
   for (auto& record : build_id_records) {
-    std::vector<char> data = record.BinaryFormat();
-    if (!Write(data.data(), data.size())) {
+    if (!Write(record.Binary(), record.size())) {
       return false;
     }
   }
@@ -180,7 +345,7 @@
   if (!WriteFeatureBegin(&start_offset)) {
     return false;
   }
-  uint32_t len = static_cast<uint32_t>(ALIGN(s.size() + 1, 64));
+  uint32_t len = static_cast<uint32_t>(Align(s.size() + 1, 64));
   if (!Write(&len, sizeof(len))) {
     return false;
   }
@@ -202,7 +367,7 @@
     return false;
   }
   for (auto& arg : cmdline) {
-    uint32_t len = static_cast<uint32_t>(ALIGN(arg.size() + 1, 64));
+    uint32_t len = static_cast<uint32_t>(Align(arg.size() + 1, 64));
     if (!Write(&len, sizeof(len))) {
       return false;
     }
diff --git a/simpleperf/record_test.cpp b/simpleperf/record_test.cpp
index 76eebe9..da5a3ce 100644
--- a/simpleperf/record_test.cpp
+++ b/simpleperf/record_test.cpp
@@ -29,92 +29,103 @@
     event_attr = CreateDefaultPerfEventAttr(*type);
   }
 
-  template <class RecordType>
-  void CheckRecordMatchBinary(const RecordType& record);
+  void CheckRecordMatchBinary(const Record& record) {
+    const char* p = record.Binary();
+    std::vector<std::unique_ptr<Record>> records =
+        ReadRecordsFromBuffer(event_attr, p, record.size());
+    ASSERT_EQ(1u, records.size());
+    CheckRecordEqual(record, *records[0]);
+  }
 
   perf_event_attr event_attr;
 };
 
-template <class RecordType>
-void RecordTest::CheckRecordMatchBinary(const RecordType& record) {
-  std::vector<char> binary = record.BinaryFormat();
-  std::vector<std::unique_ptr<Record>> records =
-      ReadRecordsFromBuffer(event_attr, binary.data(), binary.size());
-  ASSERT_EQ(1u, records.size());
-  CheckRecordEqual(record, *records[0]);
-}
-
 TEST_F(RecordTest, MmapRecordMatchBinary) {
-  MmapRecord record =
-      CreateMmapRecord(event_attr, true, 1, 2, 0x1000, 0x2000, 0x3000, "MmapRecord");
+  MmapRecord record(event_attr, true, 1, 2, 0x1000, 0x2000, 0x3000,
+                    "MmapRecord", 0);
   CheckRecordMatchBinary(record);
 }
 
 TEST_F(RecordTest, CommRecordMatchBinary) {
-  CommRecord record = CreateCommRecord(event_attr, 1, 2, "CommRecord");
+  CommRecord record(event_attr, 1, 2, "CommRecord", 0);
   CheckRecordMatchBinary(record);
 }
 
 TEST_F(RecordTest, RecordCache_smoke) {
   event_attr.sample_id_all = 1;
   event_attr.sample_type |= PERF_SAMPLE_TIME;
-  RecordCache cache(event_attr, 2, 2);
-  MmapRecord r1 = CreateMmapRecord(event_attr, true, 1, 1, 0x100, 0x200, 0x300, "mmap_record1");
-  MmapRecord r2 = r1;
-  MmapRecord r3 = r1;
-  MmapRecord r4 = r1;
-  r1.sample_id.time_data.time = 3;
-  r2.sample_id.time_data.time = 1;
-  r3.sample_id.time_data.time = 4;
-  r4.sample_id.time_data.time = 6;
-  std::vector<char> buf1 = r1.BinaryFormat();
-  std::vector<char> buf2 = r2.BinaryFormat();
-  std::vector<char> buf3 = r3.BinaryFormat();
-  std::vector<char> buf4 = r4.BinaryFormat();
+  RecordCache cache(true, 2, 2);
+  MmapRecord* r1 = new MmapRecord(event_attr, true, 1, 1, 0x100, 0x200, 0x300,
+                                  "mmap_record1", 0, 3);
+  MmapRecord* r2 = new MmapRecord(event_attr, true, 1, 1, 0x100, 0x200, 0x300,
+                                  "mmap_record1", 0, 1);
+  MmapRecord* r3 = new MmapRecord(event_attr, true, 1, 1, 0x100, 0x200, 0x300,
+                                  "mmap_record1", 0, 4);
+  MmapRecord* r4 = new MmapRecord(event_attr, true, 1, 1, 0x100, 0x200, 0x300,
+                                  "mmap_record1", 0, 6);
   // Push r1.
-  cache.Push(buf1.data(), buf1.size());
+  cache.Push(std::unique_ptr<Record>(r1));
   ASSERT_EQ(nullptr, cache.Pop());
   // Push r2.
-  cache.Push(buf2.data(), buf2.size());
+  cache.Push(std::unique_ptr<Record>(r2));
   // Pop r2.
   std::unique_ptr<Record> popped_r = cache.Pop();
   ASSERT_TRUE(popped_r != nullptr);
-  CheckRecordEqual(r2, *popped_r);
+  ASSERT_EQ(r2, popped_r.get());
   ASSERT_EQ(nullptr, cache.Pop());
   // Push r3.
-  cache.Push(buf3.data(), buf3.size());
+  cache.Push(std::unique_ptr<Record>(r3));
   ASSERT_EQ(nullptr, cache.Pop());
   // Push r4.
-  cache.Push(buf4.data(), buf4.size());
+  cache.Push(std::unique_ptr<Record>(r4));
   // Pop r1.
   popped_r = cache.Pop();
   ASSERT_TRUE(popped_r != nullptr);
-  CheckRecordEqual(r1, *popped_r);
+  ASSERT_EQ(r1, popped_r.get());
   // Pop r3.
   popped_r = cache.Pop();
   ASSERT_TRUE(popped_r != nullptr);
-  CheckRecordEqual(r3, *popped_r);
+  ASSERT_EQ(r3, popped_r.get());
   ASSERT_EQ(nullptr, cache.Pop());
   // Pop r4.
   std::vector<std::unique_ptr<Record>> last_records = cache.PopAll();
   ASSERT_EQ(1u, last_records.size());
-  CheckRecordEqual(r4, *last_records[0]);
+  ASSERT_EQ(r4, last_records[0].get());
 }
 
 TEST_F(RecordTest, RecordCache_FIFO) {
   event_attr.sample_id_all = 1;
   event_attr.sample_type |= PERF_SAMPLE_TIME;
-  RecordCache cache(event_attr, 2, 2);
-  std::vector<MmapRecord> records;
+  RecordCache cache(true, 2, 2);
+  std::vector<MmapRecord*> records;
   for (size_t i = 0; i < 10; ++i) {
-    MmapRecord r = CreateMmapRecord(event_attr, true, 1, i, 0x100, 0x200, 0x300, "mmap_record1");
-    records.push_back(r);
-    std::vector<char> buf = r.BinaryFormat();
-    cache.Push(buf.data(), buf.size());
+    records.push_back(new MmapRecord(event_attr, true, 1, i, 0x100, 0x200,
+                                     0x300, "mmap_record1", 0));
+    cache.Push(std::unique_ptr<Record>(records.back()));
   }
   std::vector<std::unique_ptr<Record>> out_records = cache.PopAll();
   ASSERT_EQ(records.size(), out_records.size());
   for (size_t i = 0; i < records.size(); ++i) {
-    CheckRecordEqual(records[i], *out_records[i]);
+    ASSERT_EQ(records[i], out_records[i].get());
   }
 }
+
+TEST_F(RecordTest, RecordCache_PushRecordVector) {
+  event_attr.sample_id_all = 1;
+  event_attr.sample_type |= PERF_SAMPLE_TIME;
+  RecordCache cache(true, 2, 2);
+  MmapRecord* r1 = new MmapRecord(event_attr, true, 1, 1, 0x100, 0x200, 0x300,
+                                  "mmap_record1", 0, 1);
+  MmapRecord* r2 = new MmapRecord(event_attr, true, 1, 1, 0x100, 0x200, 0x300,
+                                  "mmap_record1", 0, 3);
+  std::vector<std::unique_ptr<Record>> records;
+  records.push_back(std::unique_ptr<Record>(r1));
+  records.push_back(std::unique_ptr<Record>(r2));
+  cache.Push(std::move(records));
+  std::unique_ptr<Record> popped_r = cache.Pop();
+  ASSERT_TRUE(popped_r != nullptr);
+  ASSERT_EQ(r1, popped_r.get());
+  std::vector<std::unique_ptr<Record>> last_records = cache.PopAll();
+  ASSERT_EQ(1u, last_records.size());
+  ASSERT_EQ(r2, last_records[0].get());
+}
diff --git a/simpleperf/report_lib_interface.cpp b/simpleperf/report_lib_interface.cpp
new file mode 100644
index 0000000..096cf49
--- /dev/null
+++ b/simpleperf/report_lib_interface.cpp
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <memory>
+
+#include <android-base/logging.h>
+
+#include "dso.h"
+#include "event_attr.h"
+#include "record_file.h"
+#include "thread_tree.h"
+#include "utils.h"
+
+extern "C" {
+
+#define EXPORT __attribute__((visibility("default")))
+
+struct Sample {
+  uint64_t ip;
+  uint32_t pid;
+  uint32_t tid;
+  const char* thread_comm;
+  uint64_t time;
+  uint32_t in_kernel;
+  uint32_t cpu;
+  uint64_t period;
+};
+
+struct Event {
+  const char* name;
+};
+
+struct SymbolEntry {
+  const char* dso_name;
+  uint64_t vaddr_in_file;
+  const char* symbol_name;
+};
+
+struct CallChainEntry {
+  uint64_t ip;
+  SymbolEntry symbol;
+};
+
+struct CallChain {
+  uint32_t nr;
+  CallChainEntry* entries;
+};
+
+// Set log severity, different levels are:
+// verbose, debug, info, warning, error, fatal.
+bool SetLogSeverity(const char* log_level) EXPORT;
+bool SetSymfs(const char* symfs_dir) EXPORT;
+bool SetRecordFile(const char* record_file) EXPORT;
+void ShowIpForUnknownSymbol() EXPORT;
+
+Sample* GetNextSample() EXPORT;
+Event* GetEventOfCurrentSample() EXPORT;
+SymbolEntry* GetSymbolOfCurrentSample() EXPORT;
+CallChain* GetCallChainOfCurrentSample() EXPORT;
+}
+
+struct EventAttrWithName {
+  perf_event_attr attr;
+  std::string name;
+};
+
+enum {
+  UPDATE_FLAG_OF_SAMPLE = 1 << 0,
+  UPDATE_FLAG_OF_EVENT = 1 << 1,
+  UPDATE_FLAG_OF_SYMBOL = 1 << 2,
+  UPDATE_FLAG_OF_CALLCHAIN = 1 << 3,
+};
+
+class ReportLib {
+ public:
+  ReportLib()
+      : log_severity_(
+            new android::base::ScopedLogSeverity(android::base::INFO)),
+        record_filename_("perf.data"),
+        update_flag_(0) {}
+
+  static ReportLib& GetInstance() {
+    static ReportLib lib;
+    return lib;
+  }
+
+  bool SetLogSeverity(const char* log_level);
+
+  bool SetSymfs(const char* symfs_dir) { return Dso::SetSymFsDir(symfs_dir); }
+
+  bool SetRecordFile(const char* record_file) {
+    record_filename_ = record_file;
+    return true;
+  }
+
+  void ShowIpForUnknownSymbol() { thread_tree_.ShowIpForUnknownSymbol(); }
+
+  Sample* GetNextSample();
+  Event* GetEventOfCurrentSample();
+  SymbolEntry* GetSymbolOfCurrentSample();
+  CallChain* GetCallChainOfCurrentSample();
+
+ private:
+  Sample* GetCurrentSample();
+
+  std::unique_ptr<android::base::ScopedLogSeverity> log_severity_;
+  std::string record_filename_;
+  std::unique_ptr<RecordFileReader> record_file_reader_;
+  ThreadTree thread_tree_;
+  std::unique_ptr<SampleRecord> current_record_;
+  const ThreadEntry* current_thread_;
+  Sample current_sample_;
+  Event current_event_;
+  SymbolEntry current_symbol_;
+  CallChain current_callchain_;
+  std::vector<CallChainEntry> callchain_entries_;
+  int update_flag_;
+  std::vector<EventAttrWithName> event_attrs_;
+};
+
+bool ReportLib::SetLogSeverity(const char* log_level) {
+  android::base::LogSeverity severity;
+  if (!GetLogSeverity(log_level, &severity)) {
+    LOG(ERROR) << "Unknown log severity: " << log_level;
+    return false;
+  }
+  log_severity_ = nullptr;
+  log_severity_.reset(new android::base::ScopedLogSeverity(severity));
+  return true;
+}
+
+Sample* ReportLib::GetNextSample() {
+  if (record_file_reader_ == nullptr) {
+    record_file_reader_ = RecordFileReader::CreateInstance(record_filename_);
+    if (record_file_reader_ == nullptr) {
+      return nullptr;
+    }
+  }
+  while (true) {
+    std::unique_ptr<Record> record;
+    if (!record_file_reader_->ReadRecord(record)) {
+      return nullptr;
+    }
+    if (record == nullptr) {
+      return nullptr;
+    }
+    thread_tree_.Update(*record);
+    if (record->type() == PERF_RECORD_SAMPLE) {
+      current_record_.reset(static_cast<SampleRecord*>(record.release()));
+      break;
+    }
+  }
+  update_flag_ = 0;
+  return GetCurrentSample();
+}
+
+Sample* ReportLib::GetCurrentSample() {
+  if (!(update_flag_ & UPDATE_FLAG_OF_SAMPLE)) {
+    SampleRecord& r = *current_record_;
+    current_sample_.ip = r.ip_data.ip;
+    current_sample_.pid = r.tid_data.pid;
+    current_sample_.tid = r.tid_data.tid;
+    current_thread_ =
+        thread_tree_.FindThreadOrNew(r.tid_data.pid, r.tid_data.tid);
+    current_sample_.thread_comm = current_thread_->comm;
+    current_sample_.time = r.time_data.time;
+    current_sample_.in_kernel = r.InKernel();
+    current_sample_.cpu = r.cpu_data.cpu;
+    current_sample_.period = r.period_data.period;
+    update_flag_ |= UPDATE_FLAG_OF_SAMPLE;
+  }
+  return &current_sample_;
+}
+
+Event* ReportLib::GetEventOfCurrentSample() {
+  if (!(update_flag_ & UPDATE_FLAG_OF_EVENT)) {
+    if (event_attrs_.empty()) {
+      std::vector<AttrWithId> attrs = record_file_reader_->AttrSection();
+      for (const auto& attr_with_id : attrs) {
+        EventAttrWithName attr;
+        attr.attr = *attr_with_id.attr;
+        attr.name = GetEventNameByAttr(attr.attr);
+        event_attrs_.push_back(attr);
+      }
+    }
+    size_t attr_index =
+        record_file_reader_->GetAttrIndexOfRecord(*current_record_);
+    current_event_.name = event_attrs_[attr_index].name.c_str();
+    update_flag_ |= UPDATE_FLAG_OF_EVENT;
+  }
+  return &current_event_;
+}
+
+SymbolEntry* ReportLib::GetSymbolOfCurrentSample() {
+  if (!(update_flag_ & UPDATE_FLAG_OF_SYMBOL)) {
+    SampleRecord& r = *current_record_;
+    const MapEntry* map =
+        thread_tree_.FindMap(current_thread_, r.ip_data.ip, r.InKernel());
+    uint64_t vaddr_in_file;
+    const Symbol* symbol =
+        thread_tree_.FindSymbol(map, r.ip_data.ip, &vaddr_in_file);
+    current_symbol_.dso_name = map->dso->Path().c_str();
+    current_symbol_.vaddr_in_file = vaddr_in_file;
+    current_symbol_.symbol_name = symbol->DemangledName();
+    update_flag_ |= UPDATE_FLAG_OF_SYMBOL;
+  }
+  return &current_symbol_;
+}
+
+CallChain* ReportLib::GetCallChainOfCurrentSample() {
+  if (!(update_flag_ & UPDATE_FLAG_OF_CALLCHAIN)) {
+    SampleRecord& r = *current_record_;
+    callchain_entries_.clear();
+
+    if (r.sample_type & PERF_SAMPLE_CALLCHAIN) {
+      bool first_ip = true;
+      bool in_kernel = r.InKernel();
+      for (uint64_t i = 0; i < r.callchain_data.ip_nr; ++i) {
+        uint64_t ip = r.callchain_data.ips[i];
+        if (ip >= PERF_CONTEXT_MAX) {
+          switch (ip) {
+            case PERF_CONTEXT_KERNEL:
+              in_kernel = true;
+              break;
+            case PERF_CONTEXT_USER:
+              in_kernel = false;
+              break;
+            default:
+              LOG(DEBUG) << "Unexpected perf_context in callchain: " << std::hex
+                         << ip;
+          }
+        } else {
+          if (first_ip) {
+            first_ip = false;
+            // Remove duplication with sample ip.
+            if (ip == r.ip_data.ip) {
+              continue;
+            }
+          }
+          const MapEntry* map =
+              thread_tree_.FindMap(current_thread_, ip, in_kernel);
+          uint64_t vaddr_in_file;
+          const Symbol* symbol =
+              thread_tree_.FindSymbol(map, ip, &vaddr_in_file);
+          CallChainEntry entry;
+          entry.ip = ip;
+          entry.symbol.dso_name = map->dso->Path().c_str();
+          entry.symbol.vaddr_in_file = vaddr_in_file;
+          entry.symbol.symbol_name = symbol->DemangledName();
+          callchain_entries_.push_back(entry);
+        }
+      }
+    }
+    current_callchain_.nr = callchain_entries_.size();
+    current_callchain_.entries = callchain_entries_.data();
+    update_flag_ |= UPDATE_FLAG_OF_CALLCHAIN;
+  }
+  return &current_callchain_;
+}
+
+bool SetLogSeverity(const char* log_level) {
+  return ReportLib::GetInstance().SetLogSeverity(log_level);
+}
+
+bool SetSymfs(const char* symfs_dir) {
+  return ReportLib::GetInstance().SetSymfs(symfs_dir);
+}
+
+bool SetRecordFile(const char* record_file) {
+  return ReportLib::GetInstance().SetRecordFile(record_file);
+}
+
+void ShowIpForUnknownSymbol() {
+  return ReportLib::GetInstance().ShowIpForUnknownSymbol();
+}
+
+Sample* GetNextSample() { return ReportLib::GetInstance().GetNextSample(); }
+
+Event* GetEventOfCurrentSample() {
+  return ReportLib::GetInstance().GetEventOfCurrentSample();
+}
+
+SymbolEntry* GetSymbolOfCurrentSample() {
+  return ReportLib::GetInstance().GetSymbolOfCurrentSample();
+}
+
+CallChain* GetCallChainOfCurrentSample() {
+  return ReportLib::GetInstance().GetCallChainOfCurrentSample();
+}
diff --git a/simpleperf/report_sample.proto b/simpleperf/report_sample.proto
new file mode 100644
index 0000000..d6c42e6
--- /dev/null
+++ b/simpleperf/report_sample.proto
@@ -0,0 +1,48 @@
+// The file format generated by report_sample.proto is as below:
+// LittleEndian32(record_size_0)
+// message Record(record_0) (having record_size_0 bytes)
+// LittleEndian32(record_size_1)
+// message Record(record_1) (having record_size_1 bytes)
+// ...
+// LittleEndian32(record_size_N)
+// message Record(record_N) (having record_size_N bytes)
+// LittleEndian32(0)
+
+syntax = "proto2";
+option optimize_for = LITE_RUNTIME;
+package simpleperf_report_proto;
+option java_package = "com.android.tools.profiler.proto";
+option java_outer_classname = "SimpleperfReport";
+
+message Sample {
+  optional uint64 time = 1;
+
+  message CallChainEntry {
+    optional uint64 ip = 1;
+    optional string symbol = 2;
+    optional string file = 3;
+  }
+
+  repeated CallChainEntry callchain = 2;
+  optional int32 thread_id = 3;
+}
+
+message LostSituation {
+  optional uint64 sample_count = 1;
+  optional uint64 lost_count = 2;
+}
+
+message Record {
+  enum Type {
+    UNKOWN = 0;
+    SAMPLE = 1;
+    LOST_SITUATION = 2;
+  }
+
+  // Identifies which field is filled in.
+  optional Type type = 1;
+
+  // One of the following will be filled in.
+  optional Sample sample = 2;
+  optional LostSituation lost = 3;
+}
\ No newline at end of file
diff --git a/simpleperf/runtest/comm_change.cpp b/simpleperf/runtest/comm_change.cpp
index f8b2ae6..12d64fa 100644
--- a/simpleperf/runtest/comm_change.cpp
+++ b/simpleperf/runtest/comm_change.cpp
@@ -8,9 +8,9 @@
 }
 
 int main() {
-  prctl(PR_SET_NAME, reinterpret_cast<unsigned long>("RUN_COMM1"), 0, 0, 0);
+  prctl(PR_SET_NAME, reinterpret_cast<unsigned long>("RUN_COMM1"), 0, 0, 0); // NOLINT
   Function1();
-  prctl(PR_SET_NAME, reinterpret_cast<unsigned long>("RUN_COMM2"), 0, 0, 0);
+  prctl(PR_SET_NAME, reinterpret_cast<unsigned long>("RUN_COMM2"), 0, 0, 0); // NOLINT
   Function1();
   return 0;
 }
diff --git a/simpleperf/runtest/function_fork.cpp b/simpleperf/runtest/function_fork.cpp
index b813702..b1477a6 100644
--- a/simpleperf/runtest/function_fork.cpp
+++ b/simpleperf/runtest/function_fork.cpp
@@ -1,14 +1,20 @@
+#include <stdlib.h>
 #include <unistd.h>
 
 constexpr int LOOP_COUNT = 100000000;
 
+volatile int a[2];
 void ParentFunction() {
-  for (volatile int i = 0; i < LOOP_COUNT; ++i) {
+  volatile int* p = a + atoi("0");
+  for (int i = 0; i < LOOP_COUNT; ++i) {
+    *p = i;
   }
 }
 
 void ChildFunction() {
-  for (volatile int i = 0; i < LOOP_COUNT; ++i) {
+  volatile int* p = a + atoi("1");
+  for (int i = 0; i < LOOP_COUNT; ++i) {
+    *p = i;
   }
 }
 
diff --git a/simpleperf/runtest/runtest.py b/simpleperf/runtest/runtest.py
index e4cc0d2..bbfdc48 100644
--- a/simpleperf/runtest/runtest.py
+++ b/simpleperf/runtest/runtest.py
@@ -25,6 +25,8 @@
 The information of all runtests is stored in runtest.conf.
 """
 
+import os
+import os.path
 import re
 import subprocess
 import sys
@@ -314,6 +316,11 @@
 
   """Run perf test on device."""
 
+  def __init__(self, perf_path):
+    self.tmpdir = '/data/local/tmp/'
+    self._download(os.environ['OUT'] + '/system/xbin/' + perf_path, self.tmpdir)
+    self.perf_path = self.tmpdir + perf_path
+
   def _call(self, args, output_file=None):
     output_fh = None
     if output_file is not None:
@@ -324,6 +331,21 @@
     if output_fh is not None:
       output_fh.close()
 
+  def _download(self, file, to_dir):
+    args = ['adb', 'push', file, to_dir]
+    subprocess.check_call(args)
+
+  def record(self, test_executable_name, record_file, additional_options=[]):
+    self._download(os.environ['OUT'] + '/system/bin/' + test_executable_name,
+                   self.tmpdir)
+    super(DeviceRunner, self).record(self.tmpdir + test_executable_name,
+                                     self.tmpdir + record_file,
+                                     additional_options)
+
+  def report(self, record_file, report_file, additional_options=[]):
+    super(DeviceRunner, self).report(self.tmpdir + record_file,
+                                     report_file,
+                                     additional_options)
 
 class ReportAnalyzer(object):
 
@@ -497,7 +519,8 @@
 
 
 def runtest(host, device, normal, callgraph, selected_tests):
-  tests = load_config_file('runtest.conf')
+  tests = load_config_file(os.path.dirname(os.path.realpath(__file__)) + \
+                           '/runtest.conf')
   host_runner = HostRunner('simpleperf')
   device_runner = DeviceRunner('simpleperf')
   report_analyzer = ReportAnalyzer()
@@ -517,8 +540,8 @@
         exit(1)
 
     if device and normal:
-      device_runner.record(test.executable_name, '/data/perf.data')
-      device_runner.report('/data/perf.data', 'perf.report',
+      device_runner.record(test.executable_name, 'perf.data')
+      device_runner.report('perf.data', 'perf.report',
                            additional_options = test.report_options)
       result = report_analyzer.check_report_file(test, 'perf.report', False)
       print 'test %s on device %s' % (
@@ -530,11 +553,11 @@
       host_runner.record(
           test.executable_name,
           'perf_g.data',
-          additional_options=['-g'])
+          additional_options=['-g', '-f', '1000'])
       host_runner.report(
           'perf_g.data',
           'perf_g.report',
-          additional_options=['-g'] + test.report_options)
+          additional_options=['-g', 'callee'] + test.report_options)
       result = report_analyzer.check_report_file(test, 'perf_g.report', True)
       print 'call-graph test %s on host %s' % (
           test.test_name, 'Succeeded' if result else 'Failed')
@@ -542,14 +565,16 @@
         exit(1)
 
     if device and callgraph:
+      # Decrease sampling frequency by -f 1000 to avoid losing records
+      # while recording call-graph.
       device_runner.record(
           test.executable_name,
-          '/data/perf_g.data',
-          additional_options=['-g'])
+          'perf_g.data',
+          additional_options=['-g', '-f', '1000'])
       device_runner.report(
-          '/data/perf_g.data',
+          'perf_g.data',
           'perf_g.report',
-          additional_options=['-g'] + test.report_options)
+          additional_options=['-g', 'callee'] + test.report_options)
       result = report_analyzer.check_report_file(test, 'perf_g.report', True)
       print 'call-graph test %s on device %s' % (
           test.test_name, 'Succeeded' if result else 'Failed')
diff --git a/simpleperf/runtest/two_functions.cpp b/simpleperf/runtest/two_functions.cpp
index 1511102..1d3e389 100644
--- a/simpleperf/runtest/two_functions.cpp
+++ b/simpleperf/runtest/two_functions.cpp
@@ -1,12 +1,19 @@
+#include <stdlib.h>
+
 constexpr int LOOP_COUNT = 100000000;
 
+volatile int a[2];
 void Function1() {
-  for (volatile int i = 0; i < LOOP_COUNT; ++i) {
+  volatile int* p = a + atoi("0");
+  for (int i = 0; i < LOOP_COUNT; ++i) {
+    *p = i;
   }
 }
 
 void Function2() {
-  for (volatile int i = 0; i < LOOP_COUNT; ++i) {
+  volatile int* p = a + atoi("1");
+  for (int i = 0; i < LOOP_COUNT; ++i) {
+    *p = i;
   }
 }
 
diff --git a/simpleperf/sample_tree.cpp b/simpleperf/sample_tree.cpp
deleted file mode 100644
index a34107b..0000000
--- a/simpleperf/sample_tree.cpp
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "sample_tree.h"
-
-#include <android-base/logging.h>
-
-#include "environment.h"
-
-void SampleTree::SetFilters(const std::unordered_set<int>& pid_filter,
-                            const std::unordered_set<int>& tid_filter,
-                            const std::unordered_set<std::string>& comm_filter,
-                            const std::unordered_set<std::string>& dso_filter) {
-  pid_filter_ = pid_filter;
-  tid_filter_ = tid_filter;
-  comm_filter_ = comm_filter;
-  dso_filter_ = dso_filter;
-}
-
-SampleEntry* SampleTree::AddSample(int pid, int tid, uint64_t ip, uint64_t time, uint64_t period,
-                                   bool in_kernel) {
-  const ThreadEntry* thread = thread_tree_->FindThreadOrNew(pid, tid);
-  const MapEntry* map = thread_tree_->FindMap(thread, ip, in_kernel);
-  const Symbol* symbol = thread_tree_->FindSymbol(map, ip);
-
-  SampleEntry value(ip, time, period, 0, 1, thread, map, symbol);
-
-  if (IsFilteredOut(value)) {
-    return nullptr;
-  }
-  return InsertSample(value);
-}
-
-void SampleTree::AddBranchSample(int pid, int tid, uint64_t from_ip, uint64_t to_ip,
-                                 uint64_t branch_flags, uint64_t time, uint64_t period) {
-  const ThreadEntry* thread = thread_tree_->FindThreadOrNew(pid, tid);
-  const MapEntry* from_map = thread_tree_->FindMap(thread, from_ip, false);
-  if (from_map == thread_tree_->UnknownMap()) {
-    from_map = thread_tree_->FindMap(thread, from_ip, true);
-  }
-  const Symbol* from_symbol = thread_tree_->FindSymbol(from_map, from_ip);
-  const MapEntry* to_map = thread_tree_->FindMap(thread, to_ip, false);
-  if (to_map == thread_tree_->UnknownMap()) {
-    to_map = thread_tree_->FindMap(thread, to_ip, true);
-  }
-  const Symbol* to_symbol = thread_tree_->FindSymbol(to_map, to_ip);
-
-  SampleEntry value(to_ip, time, period, 0, 1, thread, to_map, to_symbol);
-  value.branch_from.ip = from_ip;
-  value.branch_from.map = from_map;
-  value.branch_from.symbol = from_symbol;
-  value.branch_from.flags = branch_flags;
-
-  if (IsFilteredOut(value)) {
-    return;
-  }
-  InsertSample(value);
-}
-
-SampleEntry* SampleTree::AddCallChainSample(int pid, int tid, uint64_t ip, uint64_t time,
-                                            uint64_t period, bool in_kernel,
-                                            const std::vector<SampleEntry*>& callchain) {
-  const ThreadEntry* thread = thread_tree_->FindThreadOrNew(pid, tid);
-  const MapEntry* map = thread_tree_->FindMap(thread, ip, in_kernel);
-  const Symbol* symbol = thread_tree_->FindSymbol(map, ip);
-
-  SampleEntry value(ip, time, 0, period, 0, thread, map, symbol);
-
-  if (IsFilteredOut(value)) {
-    // Store in callchain_sample_tree_ for use in other SampleEntry's callchain.
-    auto it = callchain_sample_tree_.find(&value);
-    if (it != callchain_sample_tree_.end()) {
-      return *it;
-    }
-    SampleEntry* sample = AllocateSample(value);
-    callchain_sample_tree_.insert(sample);
-    return sample;
-  }
-
-  auto it = sample_tree_.find(&value);
-  if (it != sample_tree_.end()) {
-    SampleEntry* sample = *it;
-    // Process only once for recursive function call.
-    if (std::find(callchain.begin(), callchain.end(), sample) != callchain.end()) {
-      return sample;
-    }
-  }
-  return InsertSample(value);
-}
-
-bool SampleTree::IsFilteredOut(const SampleEntry& value) {
-  if (!pid_filter_.empty() && pid_filter_.find(value.thread->pid) == pid_filter_.end()) {
-    return true;
-  }
-  if (!tid_filter_.empty() && tid_filter_.find(value.thread->tid) == tid_filter_.end()) {
-    return true;
-  }
-  if (!comm_filter_.empty() && comm_filter_.find(value.thread_comm) == comm_filter_.end()) {
-    return true;
-  }
-  if (!dso_filter_.empty() && dso_filter_.find(value.map->dso->Path()) == dso_filter_.end()) {
-    return true;
-  }
-  return false;
-}
-
-SampleEntry* SampleTree::InsertSample(SampleEntry& value) {
-  SampleEntry* result;
-  auto it = sample_tree_.find(&value);
-  if (it == sample_tree_.end()) {
-    result = AllocateSample(value);
-    auto pair = sample_tree_.insert(result);
-    CHECK(pair.second);
-  } else {
-    result = *it;
-    result->period += value.period;
-    result->accumulated_period += value.accumulated_period;
-    result->sample_count += value.sample_count;
-  }
-  total_samples_ += value.sample_count;
-  total_period_ += value.period;
-  return result;
-}
-
-SampleEntry* SampleTree::AllocateSample(SampleEntry& value) {
-  SampleEntry* sample = new SampleEntry(std::move(value));
-  sample_storage_.push_back(std::unique_ptr<SampleEntry>(sample));
-  return sample;
-}
-
-void SampleTree::InsertCallChainForSample(SampleEntry* sample,
-                                          const std::vector<SampleEntry*>& callchain,
-                                          uint64_t period) {
-  sample->callchain.AddCallChain(callchain, period);
-}
-
-void SampleTree::VisitAllSamples(std::function<void(const SampleEntry&)> callback) {
-  if (sorted_sample_tree_.size() != sample_tree_.size()) {
-    sorted_sample_tree_.clear();
-    for (auto& sample : sample_tree_) {
-      sample->callchain.SortByPeriod();
-      sorted_sample_tree_.insert(sample);
-    }
-  }
-  for (auto& sample : sorted_sample_tree_) {
-    callback(*sample);
-  }
-}
diff --git a/simpleperf/sample_tree.h b/simpleperf/sample_tree.h
index 6eb7372..7091ce0 100644
--- a/simpleperf/sample_tree.h
+++ b/simpleperf/sample_tree.h
@@ -17,146 +17,308 @@
 #ifndef SIMPLE_PERF_SAMPLE_TREE_H_
 #define SIMPLE_PERF_SAMPLE_TREE_H_
 
-#include <limits.h>
-#include <functional>
-#include <set>
-#include <string>
-#include <unordered_map>
-#include <unordered_set>
-#include <vector>
-
 #include "callchain.h"
+#include "dwarf_unwind.h"
+#include "perf_regs.h"
+#include "record.h"
+#include "SampleComparator.h"
+#include "SampleDisplayer.h"
 #include "thread_tree.h"
 
-struct BranchFromEntry {
-  uint64_t ip;
-  const MapEntry* map;
-  const Symbol* symbol;
-  uint64_t flags;
+// A SampleTree is a collection of samples. A profiling report is mainly about
+// constructing a SampleTree and display it. There are three steps involved:
+// build the tree, sort the tree, and display it. For example, if we want to
+// show how many cpu-cycles are spent in different functions, we should do as
+// follows:
+// 1. Build a SampleTree from SampleRecords with each sample containing
+//    (cpu-cycles, function name). When building the tree, we should merge
+//    samples containing the same function name.
+// 2. Sort the SampleTree by cpu-cycles in the sample. As we want to display the
+//    samples in a decreasing order of cpu-cycles, we should sort it like this.
+// 3. Display the SampleTree, each sample prints its (cpu-cycles, function name)
+//    pair.
+//
+// We represent the three steps with three template classes.
+// 1. A SampleTree is built by SampleTreeBuilder. The comparator passed in
+//    SampleTreeBuilder's constructor decides the property of samples should be
+//    merged together.
+// 2. After a SampleTree is built and got from SampleTreeBuilder, it should be
+//    sorted by SampleTreeSorter. The sort result decides the order to show
+//    samples.
+// 3. At last, the sorted SampleTree is passed to SampleTreeDisplayer, which
+//    displays each sample in the SampleTree.
 
-  BranchFromEntry() : ip(0), map(nullptr), symbol(nullptr), flags(0) {
-  }
-};
-
-struct SampleEntry {
-  uint64_t ip;
-  uint64_t time;
-  uint64_t period;
-  uint64_t accumulated_period;  // Accumulated when appearing in other samples' callchain.
-  uint64_t sample_count;
-  const ThreadEntry* thread;
-  const char* thread_comm;  // It refers to the thread comm when the sample happens.
-  const MapEntry* map;
-  const Symbol* symbol;
-  BranchFromEntry branch_from;
-  CallChainRoot callchain;  // A callchain tree representing all callchains in the sample records.
-
-  SampleEntry(uint64_t ip, uint64_t time, uint64_t period, uint64_t accumulated_period,
-              uint64_t sample_count, const ThreadEntry* thread, const MapEntry* map,
-              const Symbol* symbol)
-      : ip(ip),
-        time(time),
-        period(period),
-        accumulated_period(accumulated_period),
-        sample_count(sample_count),
-        thread(thread),
-        thread_comm(thread->comm),
-        map(map),
-        symbol(symbol) {
-  }
-
-  // The data member 'callchain' can only move, not copy.
-  SampleEntry(SampleEntry&&) = default;
-  SampleEntry(SampleEntry&) = delete;
-};
-
-typedef std::function<int(const SampleEntry&, const SampleEntry&)> compare_sample_func_t;
-
-class SampleTree {
+template <typename EntryT, typename AccumulateInfoT>
+class SampleTreeBuilder {
  public:
-  SampleTree(ThreadTree* thread_tree, compare_sample_func_t sample_compare_function)
-      : thread_tree_(thread_tree),
-        sample_comparator_(sample_compare_function),
-        sample_tree_(sample_comparator_),
-        callchain_sample_tree_(sample_comparator_),
-        sorted_sample_comparator_(sample_compare_function),
-        sorted_sample_tree_(sorted_sample_comparator_),
-        total_samples_(0),
-        total_period_(0) {
+  explicit SampleTreeBuilder(SampleComparator<EntryT> comparator)
+      : sample_set_(comparator),
+        accumulate_callchain_(false),
+        sample_comparator_(comparator),
+        callchain_sample_set_(comparator),
+        use_branch_address_(false),
+        build_callchain_(false),
+        use_caller_as_callchain_root_(false),
+        strict_unwind_arch_check_(false) {}
+
+  virtual ~SampleTreeBuilder() {}
+
+  void SetBranchSampleOption(bool use_branch_address) {
+    use_branch_address_ = use_branch_address;
   }
 
-  void SetFilters(const std::unordered_set<int>& pid_filter,
-                  const std::unordered_set<int>& tid_filter,
-                  const std::unordered_set<std::string>& comm_filter,
-                  const std::unordered_set<std::string>& dso_filter);
-
-  SampleEntry* AddSample(int pid, int tid, uint64_t ip, uint64_t time, uint64_t period,
-                         bool in_kernel);
-  void AddBranchSample(int pid, int tid, uint64_t from_ip, uint64_t to_ip, uint64_t branch_flags,
-                       uint64_t time, uint64_t period);
-  SampleEntry* AddCallChainSample(int pid, int tid, uint64_t ip, uint64_t time, uint64_t period,
-                                  bool in_kernel, const std::vector<SampleEntry*>& callchain);
-  void InsertCallChainForSample(SampleEntry* sample, const std::vector<SampleEntry*>& callchain,
-                                uint64_t period);
-  void VisitAllSamples(std::function<void(const SampleEntry&)> callback);
-
-  uint64_t TotalSamples() const {
-    return total_samples_;
+  void SetCallChainSampleOptions(bool accumulate_callchain,
+                                 bool build_callchain,
+                                 bool use_caller_as_callchain_root,
+                                 bool strict_unwind_arch_check) {
+    accumulate_callchain_ = accumulate_callchain;
+    build_callchain_ = build_callchain;
+    use_caller_as_callchain_root_ = use_caller_as_callchain_root;
+    strict_unwind_arch_check_ = strict_unwind_arch_check;
   }
 
-  uint64_t TotalPeriod() const {
-    return total_period_;
+  void ProcessSampleRecord(const SampleRecord& r) {
+    if (use_branch_address_ && (r.sample_type & PERF_SAMPLE_BRANCH_STACK)) {
+      for (uint64_t i = 0; i < r.branch_stack_data.stack_nr; ++i) {
+        auto& item = r.branch_stack_data.stack[i];
+        if (item.from != 0 && item.to != 0) {
+          CreateBranchSample(r, item);
+        }
+      }
+      return;
+    }
+    bool in_kernel = r.InKernel();
+    AccumulateInfoT acc_info;
+    EntryT* sample = CreateSample(r, in_kernel, &acc_info);
+    if (sample == nullptr) {
+      return;
+    }
+    if (accumulate_callchain_) {
+      std::vector<uint64_t> ips;
+      if (r.sample_type & PERF_SAMPLE_CALLCHAIN) {
+        ips.insert(ips.end(), r.callchain_data.ips,
+                   r.callchain_data.ips + r.callchain_data.ip_nr);
+      }
+      const ThreadEntry* thread = GetThreadOfSample(sample);
+      // Use stack_user_data.data.size() instead of stack_user_data.dyn_size, to
+      // make up for the missing kernel patch in N9. See b/22612370.
+      if (thread != nullptr && (r.sample_type & PERF_SAMPLE_REGS_USER) &&
+          (r.regs_user_data.reg_mask != 0) &&
+          (r.sample_type & PERF_SAMPLE_STACK_USER) &&
+          (r.GetValidStackSize() > 0)) {
+        RegSet regs =
+            CreateRegSet(r.regs_user_data.reg_mask, r.regs_user_data.regs);
+        ArchType arch = GetArchForAbi(ScopedCurrentArch::GetCurrentArch(),
+                                      r.regs_user_data.abi);
+        std::vector<uint64_t> unwind_ips =
+            UnwindCallChain(arch, *thread, regs, r.stack_user_data.data,
+                            r.GetValidStackSize(), strict_unwind_arch_check_);
+        if (!unwind_ips.empty()) {
+          ips.push_back(PERF_CONTEXT_USER);
+          ips.insert(ips.end(), unwind_ips.begin(), unwind_ips.end());
+        }
+      }
+
+      std::vector<EntryT*> callchain;
+      callchain.push_back(sample);
+
+      bool first_ip = true;
+      for (auto& ip : ips) {
+        if (ip >= PERF_CONTEXT_MAX) {
+          switch (ip) {
+            case PERF_CONTEXT_KERNEL:
+              in_kernel = true;
+              break;
+            case PERF_CONTEXT_USER:
+              in_kernel = false;
+              break;
+            default:
+              LOG(DEBUG) << "Unexpected perf_context in callchain: " << ip;
+          }
+        } else {
+          if (first_ip) {
+            first_ip = false;
+            // Remove duplication with sampled ip.
+            if (ip == r.ip_data.ip) {
+              continue;
+            }
+          }
+          EntryT* callchain_sample =
+              CreateCallChainSample(sample, ip, in_kernel, callchain, acc_info);
+          if (callchain_sample == nullptr) {
+            break;
+          }
+          callchain.push_back(callchain_sample);
+        }
+      }
+
+      if (build_callchain_) {
+        std::set<EntryT*> added_set;
+        if (use_caller_as_callchain_root_) {
+          std::reverse(callchain.begin(), callchain.end());
+        }
+        while (callchain.size() >= 2) {
+          EntryT* sample = callchain[0];
+          callchain.erase(callchain.begin());
+          // Add only once for recursive calls on callchain.
+          if (added_set.find(sample) != added_set.end()) {
+            continue;
+          }
+          added_set.insert(sample);
+          InsertCallChainForSample(sample, callchain, acc_info);
+        }
+      }
+    }
+  }
+
+  std::vector<EntryT*> GetSamples() const {
+    std::vector<EntryT*> result;
+    for (auto& entry : sample_set_) {
+      result.push_back(entry);
+    }
+    return result;
+  }
+
+ protected:
+  virtual EntryT* CreateSample(const SampleRecord& r, bool in_kernel,
+                               AccumulateInfoT* acc_info) = 0;
+  virtual EntryT* CreateBranchSample(const SampleRecord& r,
+                                     const BranchStackItemType& item) = 0;
+  virtual EntryT* CreateCallChainSample(const EntryT* sample, uint64_t ip,
+                                        bool in_kernel,
+                                        const std::vector<EntryT*>& callchain,
+                                        const AccumulateInfoT& acc_info) = 0;
+  virtual const ThreadEntry* GetThreadOfSample(EntryT*) = 0;
+  virtual uint64_t GetPeriodForCallChain(const AccumulateInfoT& acc_info) = 0;
+  virtual bool FilterSample(const EntryT*) { return true; }
+
+  virtual void UpdateSummary(const EntryT*) {}
+
+  virtual void MergeSample(EntryT* sample1, EntryT* sample2) = 0;
+
+  EntryT* InsertSample(std::unique_ptr<EntryT> sample) {
+    if (sample == nullptr || !FilterSample(sample.get())) {
+      return nullptr;
+    }
+    UpdateSummary(sample.get());
+    EntryT* result;
+    auto it = sample_set_.find(sample.get());
+    if (it == sample_set_.end()) {
+      result = sample.get();
+      sample_set_.insert(sample.get());
+      sample_storage_.push_back(std::move(sample));
+    } else {
+      result = *it;
+      MergeSample(*it, sample.get());
+    }
+    return result;
+  }
+
+  EntryT* InsertCallChainSample(std::unique_ptr<EntryT> sample,
+                                const std::vector<EntryT*>& callchain) {
+    if (sample == nullptr) {
+      return nullptr;
+    }
+    if (!FilterSample(sample.get())) {
+      // Store in callchain_sample_set_ for use in other EntryT's callchain.
+      auto it = callchain_sample_set_.find(sample.get());
+      if (it != callchain_sample_set_.end()) {
+        return *it;
+      }
+      EntryT* result = sample.get();
+      callchain_sample_set_.insert(sample.get());
+      sample_storage_.push_back(std::move(sample));
+      return result;
+    }
+
+    auto it = sample_set_.find(sample.get());
+    if (it != sample_set_.end()) {
+      EntryT* sample = *it;
+      // Process only once for recursive function call.
+      if (std::find(callchain.begin(), callchain.end(), sample) !=
+          callchain.end()) {
+        return sample;
+      }
+    }
+    return InsertSample(std::move(sample));
+  }
+
+  void InsertCallChainForSample(EntryT* sample,
+                                const std::vector<EntryT*>& callchain,
+                                const AccumulateInfoT& acc_info) {
+    uint64_t period = GetPeriodForCallChain(acc_info);
+    sample->callchain.AddCallChain(
+        callchain, period, [&](const EntryT* s1, const EntryT* s2) {
+          return sample_comparator_.IsSameSample(s1, s2);
+        });
+  }
+
+  std::set<EntryT*, SampleComparator<EntryT>> sample_set_;
+  bool accumulate_callchain_;
+
+ private:
+  const SampleComparator<EntryT> sample_comparator_;
+  // If a CallChainSample is filtered out, it is stored in callchain_sample_set_
+  // and only used in other EntryT's callchain.
+  std::set<EntryT*, SampleComparator<EntryT>> callchain_sample_set_;
+  std::vector<std::unique_ptr<EntryT>> sample_storage_;
+
+  bool use_branch_address_;
+  bool build_callchain_;
+  bool use_caller_as_callchain_root_;
+  bool strict_unwind_arch_check_;
+};
+
+template <typename EntryT>
+class SampleTreeSorter {
+ public:
+  explicit SampleTreeSorter(SampleComparator<EntryT> comparator)
+      : comparator_(comparator) {}
+
+  virtual ~SampleTreeSorter() {}
+
+  void Sort(std::vector<EntryT*>& v, bool sort_callchain) {
+    if (sort_callchain) {
+      for (auto& sample : v) {
+        SortCallChain(sample);
+      }
+    }
+    if (!comparator_.empty()) {
+      std::sort(v.begin(), v.end(), [this](const EntryT* s1, const EntryT* s2) {
+        return comparator_(s1, s2);
+      });
+    }
+  }
+
+ protected:
+  void SortCallChain(EntryT* sample) { sample->callchain.SortByPeriod(); }
+
+ private:
+  SampleComparator<EntryT> comparator_;
+};
+
+template <typename EntryT, typename InfoT>
+class SampleTreeDisplayer {
+ public:
+  explicit SampleTreeDisplayer(SampleDisplayer<EntryT, InfoT> displayer)
+      : displayer_(displayer) {}
+
+  virtual ~SampleTreeDisplayer() {}
+
+  void DisplaySamples(FILE* fp, const std::vector<EntryT*>& samples,
+                      const InfoT* info) {
+    displayer_.SetInfo(info);
+    for (const auto& sample : samples) {
+      displayer_.AdjustWidth(sample);
+    }
+    displayer_.PrintNames(fp);
+    for (const auto& sample : samples) {
+      displayer_.PrintSample(fp, sample);
+    }
   }
 
  private:
-  bool IsFilteredOut(const SampleEntry& value);
-  SampleEntry* InsertSample(SampleEntry& value);
-  SampleEntry* AllocateSample(SampleEntry& value);
-
-  struct SampleComparator {
-    bool operator()(SampleEntry* sample1, SampleEntry* sample2) const {
-      return compare_function(*sample1, *sample2) < 0;
-    }
-    SampleComparator(compare_sample_func_t compare_function) : compare_function(compare_function) {
-    }
-
-    compare_sample_func_t compare_function;
-  };
-
-  struct SortedSampleComparator {
-    bool operator()(SampleEntry* sample1, SampleEntry* sample2) const {
-      uint64_t period1 = sample1->period + sample1->accumulated_period;
-      uint64_t period2 = sample2->period + sample2->accumulated_period;
-      if (period1 != period2) {
-        return period1 > period2;
-      }
-      return compare_function(*sample1, *sample2) < 0;
-    }
-    SortedSampleComparator(compare_sample_func_t compare_function)
-        : compare_function(compare_function) {
-    }
-
-    compare_sample_func_t compare_function;
-  };
-
-  ThreadTree* thread_tree_;
-  SampleComparator sample_comparator_;
-  std::set<SampleEntry*, SampleComparator> sample_tree_;
-  // If a CallChainSample is filtered out, it is stored in callchain_sample_tree_ and only used
-  // in other SampleEntry's callchain.
-  std::set<SampleEntry*, SampleComparator> callchain_sample_tree_;
-
-  SortedSampleComparator sorted_sample_comparator_;
-  std::set<SampleEntry*, SortedSampleComparator> sorted_sample_tree_;
-  std::vector<std::unique_ptr<SampleEntry>> sample_storage_;
-
-  std::unordered_set<int> pid_filter_;
-  std::unordered_set<int> tid_filter_;
-  std::unordered_set<std::string> comm_filter_;
-  std::unordered_set<std::string> dso_filter_;
-
-  uint64_t total_samples_;
-  uint64_t total_period_;
+  SampleDisplayer<EntryT, InfoT> displayer_;
 };
 
 #endif  // SIMPLE_PERF_SAMPLE_TREE_H_
diff --git a/simpleperf/sample_tree_test.cpp b/simpleperf/sample_tree_test.cpp
index 434ee71..e288968 100644
--- a/simpleperf/sample_tree_test.cpp
+++ b/simpleperf/sample_tree_test.cpp
@@ -17,64 +17,104 @@
 #include <gtest/gtest.h>
 
 #include "sample_tree.h"
+#include "thread_tree.h"
 
-struct ExpectedSampleInMap {
+namespace {
+
+struct SampleEntry {
   int pid;
   int tid;
-  const char* comm;
+  const char* thread_comm;
   std::string dso_name;
   uint64_t map_start_addr;
   size_t sample_count;
+
+  SampleEntry(int pid, int tid, const char* thread_comm,
+              const std::string& dso_name, uint64_t map_start_addr,
+              size_t sample_count = 1u)
+      : pid(pid),
+        tid(tid),
+        thread_comm(thread_comm),
+        dso_name(dso_name),
+        map_start_addr(map_start_addr),
+        sample_count(sample_count) {}
 };
 
-static void SampleMatchExpectation(const SampleEntry& sample, const ExpectedSampleInMap& expected,
+BUILD_COMPARE_VALUE_FUNCTION(TestComparePid, pid);
+BUILD_COMPARE_VALUE_FUNCTION(TestCompareTid, tid);
+BUILD_COMPARE_STRING_FUNCTION(TestCompareDsoName, dso_name.c_str());
+BUILD_COMPARE_VALUE_FUNCTION(TestCompareMapStartAddr, map_start_addr);
+
+class TestSampleComparator : public SampleComparator<SampleEntry> {
+ public:
+  TestSampleComparator() {
+    AddCompareFunction(TestComparePid);
+    AddCompareFunction(TestCompareTid);
+    AddCompareFunction(CompareComm);
+    AddCompareFunction(TestCompareDsoName);
+    AddCompareFunction(TestCompareMapStartAddr);
+  }
+};
+
+class TestSampleTreeBuilder : public SampleTreeBuilder<SampleEntry, int> {
+ public:
+  explicit TestSampleTreeBuilder(ThreadTree* thread_tree)
+      : SampleTreeBuilder(TestSampleComparator()), thread_tree_(thread_tree) {}
+
+  void AddSample(int pid, int tid, uint64_t ip, bool in_kernel) {
+    const ThreadEntry* thread = thread_tree_->FindThreadOrNew(pid, tid);
+    const MapEntry* map = thread_tree_->FindMap(thread, ip, in_kernel);
+    InsertSample(std::unique_ptr<SampleEntry>(new SampleEntry(
+        pid, tid, thread->comm, map->dso->Path(), map->start_addr)));
+  }
+
+ protected:
+  SampleEntry* CreateSample(const SampleRecord&, bool, int*) override {
+    return nullptr;
+  }
+  SampleEntry* CreateBranchSample(const SampleRecord&,
+                                  const BranchStackItemType&) override {
+    return nullptr;
+  };
+  SampleEntry* CreateCallChainSample(const SampleEntry*, uint64_t, bool,
+                                     const std::vector<SampleEntry*>&,
+                                     const int&) override {
+    return nullptr;
+  }
+  const ThreadEntry* GetThreadOfSample(SampleEntry*) override {
+    return nullptr;
+  }
+  uint64_t GetPeriodForCallChain(const int&) override { return 0; }
+  void MergeSample(SampleEntry* sample1, SampleEntry* sample2) override {
+    sample1->sample_count += sample2->sample_count;
+  }
+
+ private:
+  ThreadTree* thread_tree_;
+};
+
+static void SampleMatchExpectation(const SampleEntry& sample,
+                                   const SampleEntry& expected,
                                    bool* has_error) {
   *has_error = true;
-  ASSERT_TRUE(sample.thread != nullptr);
-  ASSERT_EQ(expected.pid, sample.thread->pid);
-  ASSERT_EQ(expected.tid, sample.thread->tid);
-  ASSERT_STREQ(expected.comm, sample.thread_comm);
-  ASSERT_TRUE(sample.map != nullptr);
-  ASSERT_EQ(expected.dso_name, sample.map->dso->Path());
-  ASSERT_EQ(expected.map_start_addr, sample.map->start_addr);
+  ASSERT_EQ(expected.pid, sample.pid);
+  ASSERT_EQ(expected.tid, sample.tid);
+  ASSERT_STREQ(expected.thread_comm, sample.thread_comm);
+  ASSERT_EQ(expected.dso_name, sample.dso_name);
+  ASSERT_EQ(expected.map_start_addr, sample.map_start_addr);
   ASSERT_EQ(expected.sample_count, sample.sample_count);
   *has_error = false;
 }
 
-static void CheckSampleCallback(const SampleEntry& sample,
-                                std::vector<ExpectedSampleInMap>& expected_samples, size_t* pos) {
-  ASSERT_LT(*pos, expected_samples.size());
-  bool has_error;
-  SampleMatchExpectation(sample, expected_samples[*pos], &has_error);
-  ASSERT_FALSE(has_error) << "Error matching sample at pos " << *pos;
-  ++*pos;
+static void CheckSamples(const std::vector<SampleEntry*>& samples,
+                         const std::vector<SampleEntry>& expected_samples) {
+  ASSERT_EQ(samples.size(), expected_samples.size());
+  for (size_t i = 0; i < samples.size(); ++i) {
+    bool has_error;
+    SampleMatchExpectation(*samples[i], expected_samples[i], &has_error);
+    ASSERT_FALSE(has_error) << "Error matching sample at pos " << i;
+  }
 }
-
-static int CompareSampleFunction(const SampleEntry& sample1, const SampleEntry& sample2) {
-  if (sample1.thread->pid != sample2.thread->pid) {
-    return sample1.thread->pid - sample2.thread->pid;
-  }
-  if (sample1.thread->tid != sample2.thread->tid) {
-    return sample1.thread->tid - sample2.thread->tid;
-  }
-  if (strcmp(sample1.thread_comm, sample2.thread_comm) != 0) {
-    return strcmp(sample1.thread_comm, sample2.thread_comm);
-  }
-  if (sample1.map->dso->Path() != sample2.map->dso->Path()) {
-    return sample1.map->dso->Path() > sample2.map->dso->Path() ? 1 : -1;
-  }
-  if (sample1.map->start_addr != sample2.map->start_addr) {
-    return sample1.map->start_addr - sample2.map->start_addr;
-  }
-  return 0;
-}
-
-void VisitSampleTree(SampleTree* sample_tree,
-                     const std::vector<ExpectedSampleInMap>& expected_samples) {
-  size_t pos = 0;
-  sample_tree->VisitAllSamples(
-      std::bind(&CheckSampleCallback, std::placeholders::_1, expected_samples, &pos));
-  ASSERT_EQ(expected_samples.size(), pos);
 }
 
 class SampleTreeTest : public testing::Test {
@@ -88,102 +128,107 @@
     thread_tree.AddThreadMap(1, 11, 1, 10, 0, 0, "process1_thread11");
     thread_tree.AddThreadMap(2, 2, 1, 20, 0, 0, "process2_thread2");
     thread_tree.AddKernelMap(10, 20, 0, 0, "kernel");
-    sample_tree = std::unique_ptr<SampleTree>(new SampleTree(&thread_tree, CompareSampleFunction));
+    sample_tree_builder.reset(new TestSampleTreeBuilder(&thread_tree));
   }
 
-  void VisitSampleTree(const std::vector<ExpectedSampleInMap>& expected_samples) {
-    ::VisitSampleTree(sample_tree.get(), expected_samples);
+  void CheckSamples(const std::vector<SampleEntry>& expected_samples) {
+    ::CheckSamples(sample_tree_builder->GetSamples(), expected_samples);
   }
 
   ThreadTree thread_tree;
-  std::unique_ptr<SampleTree> sample_tree;
+  std::unique_ptr<TestSampleTreeBuilder> sample_tree_builder;
 };
 
 TEST_F(SampleTreeTest, ip_in_map) {
-  sample_tree->AddSample(1, 1, 1, 0, 0, false);
-  sample_tree->AddSample(1, 1, 2, 0, 0, false);
-  sample_tree->AddSample(1, 1, 5, 0, 0, false);
-  std::vector<ExpectedSampleInMap> expected_samples = {
-      {1, 1, "p1t1", "process1_thread1", 1, 3},
+  sample_tree_builder->AddSample(1, 1, 1, false);
+  sample_tree_builder->AddSample(1, 1, 2, false);
+  sample_tree_builder->AddSample(1, 1, 5, false);
+  std::vector<SampleEntry> expected_samples = {
+      SampleEntry(1, 1, "p1t1", "process1_thread1", 1, 3),
   };
-  VisitSampleTree(expected_samples);
+  CheckSamples(expected_samples);
 }
 
 TEST_F(SampleTreeTest, different_pid) {
-  sample_tree->AddSample(1, 1, 1, 0, 0, false);
-  sample_tree->AddSample(2, 2, 1, 0, 0, false);
-  std::vector<ExpectedSampleInMap> expected_samples = {
-      {1, 1, "p1t1", "process1_thread1", 1, 1}, {2, 2, "p2t2", "process2_thread2", 1, 1},
+  sample_tree_builder->AddSample(1, 1, 1, false);
+  sample_tree_builder->AddSample(2, 2, 1, false);
+  std::vector<SampleEntry> expected_samples = {
+      SampleEntry(1, 1, "p1t1", "process1_thread1", 1, 1),
+      SampleEntry(2, 2, "p2t2", "process2_thread2", 1, 1),
   };
-  VisitSampleTree(expected_samples);
+  CheckSamples(expected_samples);
 }
 
 TEST_F(SampleTreeTest, different_tid) {
-  sample_tree->AddSample(1, 1, 1, 0, 0, false);
-  sample_tree->AddSample(1, 11, 1, 0, 0, false);
-  std::vector<ExpectedSampleInMap> expected_samples = {
-      {1, 1, "p1t1", "process1_thread1", 1, 1}, {1, 11, "p1t11", "process1_thread11", 1, 1},
+  sample_tree_builder->AddSample(1, 1, 1, false);
+  sample_tree_builder->AddSample(1, 11, 1, false);
+  std::vector<SampleEntry> expected_samples = {
+      SampleEntry(1, 1, "p1t1", "process1_thread1", 1, 1),
+      SampleEntry(1, 11, "p1t11", "process1_thread11", 1, 1),
   };
-  VisitSampleTree(expected_samples);
+  CheckSamples(expected_samples);
 }
 
 TEST_F(SampleTreeTest, different_comm) {
-  sample_tree->AddSample(1, 1, 1, 0, 0, false);
+  sample_tree_builder->AddSample(1, 1, 1, false);
   thread_tree.AddThread(1, 1, "p1t1_comm2");
-  sample_tree->AddSample(1, 1, 1, 0, 0, false);
-  std::vector<ExpectedSampleInMap> expected_samples = {
-      {1, 1, "p1t1", "process1_thread1", 1, 1}, {1, 1, "p1t1_comm2", "process1_thread1", 1, 1},
+  sample_tree_builder->AddSample(1, 1, 1, false);
+  std::vector<SampleEntry> expected_samples = {
+      SampleEntry(1, 1, "p1t1", "process1_thread1", 1, 1),
+      SampleEntry(1, 1, "p1t1_comm2", "process1_thread1", 1, 1),
   };
-  VisitSampleTree(expected_samples);
+  CheckSamples(expected_samples);
 }
 
 TEST_F(SampleTreeTest, different_map) {
-  sample_tree->AddSample(1, 1, 1, 0, 0, false);
-  sample_tree->AddSample(1, 1, 6, 0, 0, false);
-  std::vector<ExpectedSampleInMap> expected_samples = {
-      {1, 1, "p1t1", "process1_thread1", 1, 1}, {1, 1, "p1t1", "process1_thread1_map2", 6, 1},
+  sample_tree_builder->AddSample(1, 1, 1, false);
+  sample_tree_builder->AddSample(1, 1, 6, false);
+  std::vector<SampleEntry> expected_samples = {
+      SampleEntry(1, 1, "p1t1", "process1_thread1", 1, 1),
+      SampleEntry(1, 1, "p1t1", "process1_thread1_map2", 6, 1),
   };
-  VisitSampleTree(expected_samples);
+  CheckSamples(expected_samples);
 }
 
 TEST_F(SampleTreeTest, unmapped_sample) {
-  sample_tree->AddSample(1, 1, 0, 0, 0, false);
-  sample_tree->AddSample(1, 1, 31, 0, 0, false);
-  sample_tree->AddSample(1, 1, 70, 0, 0, false);
+  sample_tree_builder->AddSample(1, 1, 0, false);
+  sample_tree_builder->AddSample(1, 1, 31, false);
+  sample_tree_builder->AddSample(1, 1, 70, false);
   // Match the unknown map.
-  std::vector<ExpectedSampleInMap> expected_samples = {
-      {1, 1, "p1t1", "unknown", 0, 3},
+  std::vector<SampleEntry> expected_samples = {
+      SampleEntry(1, 1, "p1t1", "unknown", 0, 3),
   };
-  VisitSampleTree(expected_samples);
+  CheckSamples(expected_samples);
 }
 
 TEST_F(SampleTreeTest, map_kernel) {
-  sample_tree->AddSample(1, 1, 10, 0, 0, true);
-  sample_tree->AddSample(1, 1, 10, 0, 0, false);
-  std::vector<ExpectedSampleInMap> expected_samples = {
-      {1, 1, "p1t1", "kernel", 10, 1}, {1, 1, "p1t1", "process1_thread1_map2", 6, 1},
+  sample_tree_builder->AddSample(1, 1, 10, true);
+  sample_tree_builder->AddSample(1, 1, 10, false);
+  std::vector<SampleEntry> expected_samples = {
+      SampleEntry(1, 1, "p1t1", "kernel", 10, 1),
+      SampleEntry(1, 1, "p1t1", "process1_thread1_map2", 6, 1),
   };
-  VisitSampleTree(expected_samples);
+  CheckSamples(expected_samples);
 }
 
 TEST(sample_tree, overlapped_map) {
   ThreadTree thread_tree;
-  SampleTree sample_tree(&thread_tree, CompareSampleFunction);
+  TestSampleTreeBuilder sample_tree_builder(&thread_tree);
   thread_tree.AddThread(1, 1, "thread1");
   thread_tree.AddThreadMap(1, 1, 1, 10, 0, 0, "map1");  // Add map 1.
-  sample_tree.AddSample(1, 1, 5, 0, 0, false);          // Hit map 1.
+  sample_tree_builder.AddSample(1, 1, 5, false);        // Hit map 1.
   thread_tree.AddThreadMap(1, 1, 5, 20, 0, 0, "map2");  // Add map 2.
-  sample_tree.AddSample(1, 1, 6, 0, 0, false);          // Hit map 2.
-  sample_tree.AddSample(1, 1, 4, 0, 0, false);          // Hit map 1.
+  sample_tree_builder.AddSample(1, 1, 6, false);        // Hit map 2.
+  sample_tree_builder.AddSample(1, 1, 4, false);        // Hit map 1.
   thread_tree.AddThreadMap(1, 1, 2, 7, 0, 0, "map3");   // Add map 3.
-  sample_tree.AddSample(1, 1, 7, 0, 0, false);          // Hit map 3.
-  sample_tree.AddSample(1, 1, 10, 0, 0, false);         // Hit map 2.
+  sample_tree_builder.AddSample(1, 1, 7, false);        // Hit map 3.
+  sample_tree_builder.AddSample(1, 1, 10, false);       // Hit map 2.
 
-  std::vector<ExpectedSampleInMap> expected_samples = {
-      {1, 1, "thread1", "map1", 1, 2},
-      {1, 1, "thread1", "map2", 5, 1},
-      {1, 1, "thread1", "map2", 9, 1},
-      {1, 1, "thread1", "map3", 2, 1},
+  std::vector<SampleEntry> expected_samples = {
+      SampleEntry(1, 1, "thread1", "map1", 1, 2),
+      SampleEntry(1, 1, "thread1", "map2", 5, 1),
+      SampleEntry(1, 1, "thread1", "map2", 9, 1),
+      SampleEntry(1, 1, "thread1", "map3", 2, 1),
   };
-  VisitSampleTree(&sample_tree, expected_samples);
+  CheckSamples(sample_tree_builder.GetSamples(), expected_samples);
 }
diff --git a/simpleperf/scoped_signal_handler.h b/simpleperf/scoped_signal_handler.h
deleted file mode 100644
index 15beee7..0000000
--- a/simpleperf/scoped_signal_handler.h
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef SIMPLE_PERF_SCOPED_SIGNAL_HANDLER_H_
-#define SIMPLE_PERF_SCOPED_SIGNAL_HANDLER_H_
-
-#include <signal.h>
-
-#include <vector>
-
-class ScopedSignalHandler {
- public:
-  ScopedSignalHandler(const std::vector<int>& signums, void (*handler)(int)) {
-    for (auto& sig : signums) {
-      sig_t old_handler = signal(sig, handler);
-      saved_signal_handlers_.push_back(std::make_pair(sig, old_handler));
-    }
-  }
-
-  ~ScopedSignalHandler() {
-    for (auto& pair : saved_signal_handlers_) {
-      signal(pair.first, pair.second);
-    }
-  }
-
- private:
-  std::vector<std::pair<int, sig_t>> saved_signal_handlers_;
-};
-
-#endif  // SIMPLE_PERF_SCOPED_SIGNAL_HANDLER_H_
diff --git a/simpleperf/scripts/libsimpleperf_report.so b/simpleperf/scripts/libsimpleperf_report.so
new file mode 100755
index 0000000..6e16376
--- /dev/null
+++ b/simpleperf/scripts/libsimpleperf_report.so
Binary files differ
diff --git a/simpleperf/scripts/report_sample.py b/simpleperf/scripts/report_sample.py
new file mode 100644
index 0000000..d0605cd
--- /dev/null
+++ b/simpleperf/scripts/report_sample.py
@@ -0,0 +1,78 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+"""report_sample.py: report samples in the same format as `perf script`.
+"""
+
+import sys
+from simpleperf_report_lib import *
+
+
+def usage():
+    print 'python report_sample.py [options] <record_file>'
+    print '-h/--help print this help message'
+    print '--symfs <symfs_dir>  Set the path to looking for symbols'
+    print 'If record file is not given, use default file perf.data.'
+
+
+def report_sample(record_file, symfs_dir):
+    """ read record_file, and print each sample"""
+    lib = ReportLib()
+    lib.ShowIpForUnknownSymbol()
+    if symfs_dir is not None:
+        lib.SetSymfs(symfs_dir)
+    if record_file is not None:
+        lib.SetRecordFile(record_file)
+
+    while True:
+        sample = lib.GetNextSample()
+        if sample is None:
+            break
+        event = lib.GetEventOfCurrentSample()
+        symbol = lib.GetSymbolOfCurrentSample()
+        callchain = lib.GetCallChainOfCurrentSample()
+
+        sec = sample[0].time / 1000000000
+        usec = (sample[0].time - sec * 1000000000) / 1000
+        print '%s\t%d [%03d] %d.%d:\t\t%d %s:' % (sample[0].thread_comm, sample[0].tid, sample[0].cpu, sec, usec, sample[0].period, event[0].name)
+        print '%16x\t%s (%s)' % (sample[0].ip, symbol[0].symbol_name, symbol[0].dso_name)
+        for i in range(callchain[0].nr):
+            entry = callchain[0].entries[i]
+            print '%16x\t%s (%s)' % (entry.ip, entry.symbol.symbol_name, entry.symbol.dso_name)
+        print
+
+
+if __name__ == '__main__':
+    record_file = 'perf.data'
+    symfs_dir = None
+    i = 1
+    while i < len(sys.argv):
+        if sys.argv[i] == '-h' or sys.argv[i] == '--help':
+            usage()
+            sys.exit(0)
+        elif sys.argv[i] == '--symfs':
+            if i + 1 < len(sys.argv):
+                symfs_dir = sys.argv[i + 1]
+                i += 1
+            else:
+                print 'argument for --symfs is missing'
+                sys.exit(1)
+        else:
+          record_file = sys.argv[i]
+        i += 1
+
+    report_sample(record_file, symfs_dir)
diff --git a/simpleperf/scripts/simpleperf_report_lib.py b/simpleperf/scripts/simpleperf_report_lib.py
new file mode 100644
index 0000000..d4724b9
--- /dev/null
+++ b/simpleperf/scripts/simpleperf_report_lib.py
@@ -0,0 +1,119 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+"""simpleperf_report_lib.py: a python wrapper of libsimpleperf_report.so.
+   Used to access samples in perf.data.
+"""
+
+import ctypes as ct
+import os
+
+
+def _get_script_path():
+    return os.path.dirname(os.path.realpath(__file__))
+
+
+def _is_null(p):
+    return ct.cast(p, ct.c_void_p).value is None
+
+
+class SampleStruct(ct.Structure):
+    _fields_ = [('ip', ct.c_uint64),
+                ('pid', ct.c_uint32),
+                ('tid', ct.c_uint32),
+                ('thread_comm', ct.c_char_p),
+                ('time', ct.c_uint64),
+                ('in_kernel', ct.c_uint32),
+                ('cpu', ct.c_uint32),
+                ('period', ct.c_uint64)]
+
+
+class EventStruct(ct.Structure):
+    _fields_ = [('name', ct.c_char_p)]
+
+
+class SymbolStruct(ct.Structure):
+    _fields_ = [('dso_name', ct.c_char_p),
+                ('vaddr_in_file', ct.c_uint64),
+                ('symbol_name', ct.c_char_p)]
+
+
+class CallChainEntryStructure(ct.Structure):
+    _fields_ = [('ip', ct.c_uint64),
+                ('symbol', SymbolStruct)]
+
+
+class CallChainStructure(ct.Structure):
+    _fields_ = [('nr', ct.c_uint32),
+                ('entries', ct.POINTER(CallChainEntryStructure))]
+
+
+class ReportLib(object):
+
+    def __init__(self, native_lib_path=None):
+        if native_lib_path is None:
+            native_lib_path = _get_script_path() + "/libsimpleperf_report.so"
+        self._lib = ct.CDLL(native_lib_path)
+        self._SetLogSeverityFunc = self._lib.SetLogSeverity
+        self._SetSymfsFunc = self._lib.SetSymfs
+        self._SetRecordFileFunc = self._lib.SetRecordFile
+        self._ShowIpForUnknownSymbolFunc = self._lib.ShowIpForUnknownSymbol
+        self._GetNextSampleFunc = self._lib.GetNextSample
+        self._GetNextSampleFunc.restype = ct.POINTER(SampleStruct)
+        self._GetEventOfCurrentSampleFunc = self._lib.GetEventOfCurrentSample
+        self._GetEventOfCurrentSampleFunc.restype = ct.POINTER(EventStruct)
+        self._GetSymbolOfCurrentSampleFunc = self._lib.GetSymbolOfCurrentSample
+        self._GetSymbolOfCurrentSampleFunc.restype = ct.POINTER(SymbolStruct)
+        self._GetCallChainOfCurrentSampleFunc = self._lib.GetCallChainOfCurrentSample
+        self._GetCallChainOfCurrentSampleFunc.restype = ct.POINTER(
+            CallChainStructure)
+
+    def SetLogSeverity(self, log_level='info'):
+        """ Set log severity of native lib, can be verbose,debug,info,error,fatal."""
+        assert(self._SetLogSeverityFunc(log_level))
+
+    def SetSymfs(self, symfs_dir):
+        """ Set directory used to find symbols."""
+        assert(self._SetSymfsFunc(symfs_dir))
+
+    def SetRecordFile(self, record_file):
+        """ Set the path of record file, like perf.data."""
+        assert(self._SetRecordFileFunc(record_file))
+
+    def ShowIpForUnknownSymbol(self):
+        self._ShowIpForUnknownSymbolFunc()
+
+    def GetNextSample(self):
+        sample = self._GetNextSampleFunc()
+        if _is_null(sample):
+            return None
+        return sample
+
+    def GetEventOfCurrentSample(self):
+        event = self._GetEventOfCurrentSampleFunc()
+        assert(not _is_null(event))
+        return event
+
+    def GetSymbolOfCurrentSample(self):
+        symbol = self._GetSymbolOfCurrentSampleFunc()
+        assert(not _is_null(symbol))
+        return symbol
+
+    def GetCallChainOfCurrentSample(self):
+        callchain = self._GetCallChainOfCurrentSampleFunc()
+        assert(not _is_null(callchain))
+        return callchain
diff --git a/simpleperf/report.py b/simpleperf/simpleperf_report.py
similarity index 100%
rename from simpleperf/report.py
rename to simpleperf/simpleperf_report.py
diff --git a/simpleperf/test_util.h b/simpleperf/test_util.h
index cfbe493..15d11cb 100644
--- a/simpleperf/test_util.h
+++ b/simpleperf/test_util.h
@@ -15,7 +15,9 @@
  */
 
 #include <map>
+#include <memory>
 #include <string>
+#include <vector>
 
 #include "read_elf.h"
 #include "workload.h"
@@ -26,3 +28,14 @@
 
 void ParseSymbol(const ElfFileSymbol& symbol, std::map<std::string, ElfFileSymbol>* symbols);
 void CheckElfFileSymbols(const std::map<std::string, ElfFileSymbol>& symbols);
+
+bool IsRoot();
+
+#define TEST_IN_ROOT(TestStatement)                                                                \
+  do {                                                                                             \
+    if (IsRoot()) {                                                                                \
+      TestStatement;                                                                               \
+    } else {                                                                                       \
+      GTEST_LOG_(INFO) << "Didn't test \"" << #TestStatement << "\" requires root privileges";     \
+    }                                                                                              \
+  } while (0)
diff --git a/simpleperf/testdata/data/correct_symfs_for_build_id_check/elf_for_build_id_check b/simpleperf/testdata/data/correct_symfs_for_build_id_check/elf_for_build_id_check
new file mode 100755
index 0000000..5c1a9dd
--- /dev/null
+++ b/simpleperf/testdata/data/correct_symfs_for_build_id_check/elf_for_build_id_check
Binary files differ
diff --git a/simpleperf/testdata/data/symfs_for_no_symbol_table_warning/elf b/simpleperf/testdata/data/symfs_for_no_symbol_table_warning/elf
new file mode 100644
index 0000000..a92e41f
--- /dev/null
+++ b/simpleperf/testdata/data/symfs_for_no_symbol_table_warning/elf
Binary files differ
diff --git a/simpleperf/testdata/data/symfs_for_read_elf_file_warning/elf b/simpleperf/testdata/data/symfs_for_read_elf_file_warning/elf
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/simpleperf/testdata/data/symfs_for_read_elf_file_warning/elf
diff --git a/simpleperf/testdata/data/wrong_symfs_for_build_id_check/elf_for_build_id_check b/simpleperf/testdata/data/wrong_symfs_for_build_id_check/elf_for_build_id_check
new file mode 100755
index 0000000..0489a22
--- /dev/null
+++ b/simpleperf/testdata/data/wrong_symfs_for_build_id_check/elf_for_build_id_check
Binary files differ
diff --git a/simpleperf/testdata/elf_with_mini_debug_info b/simpleperf/testdata/elf_with_mini_debug_info
new file mode 100644
index 0000000..b3aa967
--- /dev/null
+++ b/simpleperf/testdata/elf_with_mini_debug_info
Binary files differ
diff --git a/simpleperf/testdata/generated_by_linux_perf.data b/simpleperf/testdata/generated_by_linux_perf.data
new file mode 100644
index 0000000..da62584
--- /dev/null
+++ b/simpleperf/testdata/generated_by_linux_perf.data
Binary files differ
diff --git a/simpleperf/testdata/perf_for_build_id_check.data b/simpleperf/testdata/perf_for_build_id_check.data
new file mode 100644
index 0000000..1012d4b
--- /dev/null
+++ b/simpleperf/testdata/perf_for_build_id_check.data
Binary files differ
diff --git a/simpleperf/testdata/perf_with_kernel_symbol.data b/simpleperf/testdata/perf_with_kernel_symbol.data
new file mode 100644
index 0000000..8b1fda1
--- /dev/null
+++ b/simpleperf/testdata/perf_with_kernel_symbol.data
Binary files differ
diff --git a/simpleperf/testdata/perf_with_kmem_slab_callgraph.data b/simpleperf/testdata/perf_with_kmem_slab_callgraph.data
new file mode 100644
index 0000000..cdb691f
--- /dev/null
+++ b/simpleperf/testdata/perf_with_kmem_slab_callgraph.data
Binary files differ
diff --git a/simpleperf/testdata/perf_with_mini_debug_info.data b/simpleperf/testdata/perf_with_mini_debug_info.data
new file mode 100644
index 0000000..0b02b3b
--- /dev/null
+++ b/simpleperf/testdata/perf_with_mini_debug_info.data
Binary files differ
diff --git a/simpleperf/testdata/perf_with_multiple_pids_and_tids.data b/simpleperf/testdata/perf_with_multiple_pids_and_tids.data
new file mode 100644
index 0000000..ef4f0d4
--- /dev/null
+++ b/simpleperf/testdata/perf_with_multiple_pids_and_tids.data
Binary files differ
diff --git a/simpleperf/testdata/perf_with_symbols.data b/simpleperf/testdata/perf_with_symbols.data
new file mode 100644
index 0000000..8571a96
--- /dev/null
+++ b/simpleperf/testdata/perf_with_symbols.data
Binary files differ
diff --git a/simpleperf/testdata/perf_with_symbols_for_nonzero_minvaddr_dso.data b/simpleperf/testdata/perf_with_symbols_for_nonzero_minvaddr_dso.data
new file mode 100644
index 0000000..cf45d3a
--- /dev/null
+++ b/simpleperf/testdata/perf_with_symbols_for_nonzero_minvaddr_dso.data
Binary files differ
diff --git a/simpleperf/testdata/perf_with_two_event_types.data b/simpleperf/testdata/perf_with_two_event_types.data
new file mode 100644
index 0000000..ba9a606
--- /dev/null
+++ b/simpleperf/testdata/perf_with_two_event_types.data
Binary files differ
diff --git a/simpleperf/thread_tree.cpp b/simpleperf/thread_tree.cpp
index daf3ff5..981a5dd 100644
--- a/simpleperf/thread_tree.cpp
+++ b/simpleperf/thread_tree.cpp
@@ -16,9 +16,12 @@
 
 #include "thread_tree.h"
 
+#include <inttypes.h>
+
 #include <limits>
 
 #include <android-base/logging.h>
+#include <android-base/stringprintf.h>
 
 #include "environment.h"
 #include "perf_event.h"
@@ -26,12 +29,13 @@
 
 namespace simpleperf {
 
-bool MapComparator::operator()(const MapEntry* map1, const MapEntry* map2) const {
+bool MapComparator::operator()(const MapEntry* map1,
+                               const MapEntry* map2) const {
   if (map1->start_addr != map2->start_addr) {
     return map1->start_addr < map2->start_addr;
   }
-  // Compare map->len instead of map->get_end_addr() here. Because we set map's len
-  // to std::numeric_limits<uint64_t>::max() in FindMapByAddr(), which makes
+  // Compare map->len instead of map->get_end_addr() here. Because we set map's
+  // len to std::numeric_limits<uint64_t>::max() in FindMapByAddr(), which makes
   // map->get_end_addr() overflow.
   if (map1->len != map2->len) {
     return map1->len < map2->len;
@@ -50,11 +54,13 @@
         "unknown",                             // comm
         std::set<MapEntry*, MapComparator>(),  // maps
     };
-    auto pair = thread_tree_.insert(std::make_pair(tid, std::unique_ptr<ThreadEntry>(thread)));
+    auto pair = thread_tree_.insert(
+        std::make_pair(tid, std::unique_ptr<ThreadEntry>(thread)));
     CHECK(pair.second);
     it = pair.first;
   }
-  thread_comm_storage_.push_back(std::unique_ptr<std::string>(new std::string(comm)));
+  thread_comm_storage_.push_back(
+      std::unique_ptr<std::string>(new std::string(comm)));
   it->second->comm = thread_comm_storage_.back()->c_str();
 }
 
@@ -73,21 +79,23 @@
   } else {
     if (pid != it->second.get()->pid) {
       // TODO: b/22185053.
-      LOG(DEBUG) << "unexpected (pid, tid) pair: expected (" << it->second.get()->pid << ", " << tid
-                 << "), actual (" << pid << ", " << tid << ")";
+      LOG(DEBUG) << "unexpected (pid, tid) pair: expected ("
+                 << it->second.get()->pid << ", " << tid << "), actual (" << pid
+                 << ", " << tid << ")";
     }
   }
   return it->second.get();
 }
 
-void ThreadTree::AddKernelMap(uint64_t start_addr, uint64_t len, uint64_t pgoff, uint64_t time,
-                              const std::string& filename) {
+void ThreadTree::AddKernelMap(uint64_t start_addr, uint64_t len, uint64_t pgoff,
+                              uint64_t time, const std::string& filename) {
   // kernel map len can be 0 when record command is not run in supervisor mode.
   if (len == 0) {
     return;
   }
   Dso* dso = FindKernelDsoOrNew(filename);
-  MapEntry* map = AllocateMap(MapEntry(start_addr, len, pgoff, time, dso));
+  MapEntry* map =
+      AllocateMap(MapEntry(start_addr, len, pgoff, time, dso, true));
   FixOverlappedMap(&kernel_map_tree_, map);
   auto pair = kernel_map_tree_.insert(map);
   CHECK(pair.second);
@@ -95,9 +103,6 @@
 
 Dso* ThreadTree::FindKernelDsoOrNew(const std::string& filename) {
   if (filename == DEFAULT_KERNEL_MMAP_NAME) {
-    if (kernel_dso_ == nullptr) {
-      kernel_dso_ = Dso::CreateDso(DSO_KERNEL);
-    }
     return kernel_dso_.get();
   }
   auto it = module_dso_tree_.find(filename);
@@ -108,11 +113,13 @@
   return it->second.get();
 }
 
-void ThreadTree::AddThreadMap(int pid, int tid, uint64_t start_addr, uint64_t len, uint64_t pgoff,
-                              uint64_t time, const std::string& filename) {
+void ThreadTree::AddThreadMap(int pid, int tid, uint64_t start_addr,
+                              uint64_t len, uint64_t pgoff, uint64_t time,
+                              const std::string& filename) {
   ThreadEntry* thread = FindThreadOrNew(pid, tid);
   Dso* dso = FindUserDsoOrNew(filename);
-  MapEntry* map = AllocateMap(MapEntry(start_addr, len, pgoff, time, dso));
+  MapEntry* map =
+      AllocateMap(MapEntry(start_addr, len, pgoff, time, dso, false));
   FixOverlappedMap(&thread->maps, map);
   auto pair = thread->maps.insert(map);
   CHECK(pair.second);
@@ -133,7 +140,8 @@
   return map;
 }
 
-void ThreadTree::FixOverlappedMap(std::set<MapEntry*, MapComparator>* map_set, const MapEntry* map) {
+void ThreadTree::FixOverlappedMap(std::set<MapEntry*, MapComparator>* map_set,
+                                  const MapEntry* map) {
   for (auto it = map_set->begin(); it != map_set->end();) {
     if ((*it)->start_addr >= map->get_end_addr()) {
       // No more overlapped maps.
@@ -144,14 +152,16 @@
     } else {
       MapEntry* old = *it;
       if (old->start_addr < map->start_addr) {
-        MapEntry* before = AllocateMap(MapEntry(old->start_addr, map->start_addr - old->start_addr,
-                                                old->pgoff, old->time, old->dso));
+        MapEntry* before = AllocateMap(
+            MapEntry(old->start_addr, map->start_addr - old->start_addr,
+                     old->pgoff, old->time, old->dso, old->in_kernel));
         map_set->insert(before);
       }
       if (old->get_end_addr() > map->get_end_addr()) {
-        MapEntry* after = AllocateMap(
-            MapEntry(map->get_end_addr(), old->get_end_addr() - map->get_end_addr(),
-                     map->get_end_addr() - old->start_addr + old->pgoff, old->time, old->dso));
+        MapEntry* after = AllocateMap(MapEntry(
+            map->get_end_addr(), old->get_end_addr() - map->get_end_addr(),
+            map->get_end_addr() - old->start_addr + old->pgoff, old->time,
+            old->dso, old->in_kernel));
         map_set->insert(after);
       }
 
@@ -164,10 +174,12 @@
   return (addr >= map->start_addr && addr < map->get_end_addr());
 }
 
-static MapEntry* FindMapByAddr(const std::set<MapEntry*, MapComparator>& maps, uint64_t addr) {
-  // Construct a map_entry which is strictly after the searched map_entry, based on MapComparator.
+static MapEntry* FindMapByAddr(const std::set<MapEntry*, MapComparator>& maps,
+                               uint64_t addr) {
+  // Construct a map_entry which is strictly after the searched map_entry, based
+  // on MapComparator.
   MapEntry find_map(addr, std::numeric_limits<uint64_t>::max(), 0,
-                    std::numeric_limits<uint64_t>::max(), nullptr);
+                    std::numeric_limits<uint64_t>::max(), nullptr, false);
   auto it = maps.upper_bound(&find_map);
   if (it != maps.begin() && IsAddrInMap(addr, *--it)) {
     return *it;
@@ -175,7 +187,8 @@
   return nullptr;
 }
 
-const MapEntry* ThreadTree::FindMap(const ThreadEntry* thread, uint64_t ip, bool in_kernel) {
+const MapEntry* ThreadTree::FindMap(const ThreadEntry* thread, uint64_t ip,
+                                    bool in_kernel) {
   MapEntry* result = nullptr;
   if (!in_kernel) {
     result = FindMapByAddr(thread->maps, ip);
@@ -185,58 +198,110 @@
   return result != nullptr ? result : &unknown_map_;
 }
 
-const Symbol* ThreadTree::FindSymbol(const MapEntry* map, uint64_t ip) {
+const MapEntry* ThreadTree::FindMap(const ThreadEntry* thread, uint64_t ip) {
+  MapEntry* result = FindMapByAddr(thread->maps, ip);
+  if (result != nullptr) {
+    return result;
+  }
+  result = FindMapByAddr(kernel_map_tree_, ip);
+  return result != nullptr ? result : &unknown_map_;
+}
+
+const Symbol* ThreadTree::FindSymbol(const MapEntry* map, uint64_t ip,
+                                     uint64_t* pvaddr_in_file) {
   uint64_t vaddr_in_file;
-  if (map->dso == kernel_dso_.get()) {
+  Dso* dso = map->dso;
+  if (dso == kernel_dso_.get()) {
     vaddr_in_file = ip;
   } else {
     vaddr_in_file = ip - map->start_addr + map->dso->MinVirtualAddress();
   }
-  const Symbol* symbol = map->dso->FindSymbol(vaddr_in_file);
+  const Symbol* symbol = dso->FindSymbol(vaddr_in_file);
+  if (symbol == nullptr && map->in_kernel && dso != kernel_dso_.get()) {
+    // It is in a kernel module, but we can't find the kernel module file, or
+    // the kernel module file contains no symbol. Try finding the symbol in
+    // /proc/kallsyms.
+    vaddr_in_file = ip;
+    dso = kernel_dso_.get();
+    symbol = dso->FindSymbol(vaddr_in_file);
+  }
   if (symbol == nullptr) {
-    symbol = &unknown_symbol_;
+    if (show_ip_for_unknown_symbol_) {
+      std::string name = android::base::StringPrintf(
+          "%s%s[+%" PRIx64 "]", (show_mark_for_unknown_symbol_ ? "*" : ""),
+          dso->FileName().c_str(), vaddr_in_file);
+      dso->InsertSymbol(Symbol(name, vaddr_in_file, 1));
+      symbol = dso->FindSymbol(vaddr_in_file);
+      CHECK(symbol != nullptr);
+    } else {
+      symbol = &unknown_symbol_;
+    }
+  }
+  if (pvaddr_in_file != nullptr) {
+    *pvaddr_in_file = vaddr_in_file;
   }
   return symbol;
 }
 
-void ThreadTree::Clear() {
+const Symbol* ThreadTree::FindKernelSymbol(uint64_t ip) {
+  const MapEntry* map = FindMap(nullptr, ip, true);
+  return FindSymbol(map, ip, nullptr);
+}
+
+void ThreadTree::ClearThreadAndMap() {
   thread_tree_.clear();
   thread_comm_storage_.clear();
   kernel_map_tree_.clear();
   map_storage_.clear();
-  kernel_dso_.reset();
-  module_dso_tree_.clear();
-  user_dso_tree_.clear();
+}
+
+void ThreadTree::Update(const Record& record) {
+  if (record.type() == PERF_RECORD_MMAP) {
+    const MmapRecord& r = *static_cast<const MmapRecord*>(&record);
+    if (r.InKernel()) {
+      AddKernelMap(r.data->addr, r.data->len, r.data->pgoff,
+                   r.sample_id.time_data.time, r.filename);
+    } else {
+      AddThreadMap(r.data->pid, r.data->tid, r.data->addr, r.data->len,
+                   r.data->pgoff, r.sample_id.time_data.time, r.filename);
+    }
+  } else if (record.type() == PERF_RECORD_MMAP2) {
+    const Mmap2Record& r = *static_cast<const Mmap2Record*>(&record);
+    if (r.InKernel()) {
+      AddKernelMap(r.data->addr, r.data->len, r.data->pgoff,
+                   r.sample_id.time_data.time, r.filename);
+    } else {
+      std::string filename = (r.filename == DEFAULT_EXECNAME_FOR_THREAD_MMAP)
+                                 ? "[unknown]"
+                                 : r.filename;
+      AddThreadMap(r.data->pid, r.data->tid, r.data->addr, r.data->len,
+                   r.data->pgoff, r.sample_id.time_data.time, filename);
+    }
+  } else if (record.type() == PERF_RECORD_COMM) {
+    const CommRecord& r = *static_cast<const CommRecord*>(&record);
+    AddThread(r.data->pid, r.data->tid, r.comm);
+  } else if (record.type() == PERF_RECORD_FORK) {
+    const ForkRecord& r = *static_cast<const ForkRecord*>(&record);
+    ForkThread(r.data->pid, r.data->tid, r.data->ppid, r.data->ptid);
+  } else if (record.type() == SIMPLE_PERF_RECORD_KERNEL_SYMBOL) {
+    const auto& r = *static_cast<const KernelSymbolRecord*>(&record);
+    Dso::SetKallsyms(std::move(r.kallsyms));
+  } else if (record.type() == SIMPLE_PERF_RECORD_DSO) {
+    auto& r = *static_cast<const DsoRecord*>(&record);
+    Dso* dso = nullptr;
+    if (r.dso_type == DSO_KERNEL || r.dso_type == DSO_KERNEL_MODULE) {
+      dso = FindKernelDsoOrNew(r.dso_name);
+    } else {
+      dso = FindUserDsoOrNew(r.dso_name);
+    }
+    dso->SetMinVirtualAddress(r.min_vaddr);
+    dso_id_to_dso_map_[r.dso_id] = dso;
+  } else if (record.type() == SIMPLE_PERF_RECORD_SYMBOL) {
+    auto& r = *static_cast<const SymbolRecord*>(&record);
+    Dso* dso = dso_id_to_dso_map_[r.dso_id];
+    CHECK(dso != nullptr);
+    dso->InsertSymbol(Symbol(r.name, r.addr, r.len));
+  }
 }
 
 }  // namespace simpleperf
-
-void BuildThreadTree(const Record& record, ThreadTree* thread_tree) {
-  if (record.header.type == PERF_RECORD_MMAP) {
-    const MmapRecord& r = *static_cast<const MmapRecord*>(&record);
-    if ((r.header.misc & PERF_RECORD_MISC_CPUMODE_MASK) == PERF_RECORD_MISC_KERNEL) {
-      thread_tree->AddKernelMap(r.data.addr, r.data.len, r.data.pgoff, r.sample_id.time_data.time,
-                                r.filename);
-    } else {
-      thread_tree->AddThreadMap(r.data.pid, r.data.tid, r.data.addr, r.data.len, r.data.pgoff,
-                                r.sample_id.time_data.time, r.filename);
-    }
-  } else if (record.header.type == PERF_RECORD_MMAP2) {
-    const Mmap2Record& r = *static_cast<const Mmap2Record*>(&record);
-    if ((r.header.misc & PERF_RECORD_MISC_CPUMODE_MASK) == PERF_RECORD_MISC_KERNEL) {
-      thread_tree->AddKernelMap(r.data.addr, r.data.len, r.data.pgoff, r.sample_id.time_data.time,
-                                r.filename);
-    } else {
-      std::string filename =
-          (r.filename == DEFAULT_EXECNAME_FOR_THREAD_MMAP) ? "[unknown]" : r.filename;
-      thread_tree->AddThreadMap(r.data.pid, r.data.tid, r.data.addr, r.data.len, r.data.pgoff,
-                                r.sample_id.time_data.time, filename);
-    }
-  } else if (record.header.type == PERF_RECORD_COMM) {
-    const CommRecord& r = *static_cast<const CommRecord*>(&record);
-    thread_tree->AddThread(r.data.pid, r.data.tid, r.comm);
-  } else if (record.header.type == PERF_RECORD_FORK) {
-    const ForkRecord& r = *static_cast<const ForkRecord*>(&record);
-    thread_tree->ForkThread(r.data.pid, r.data.tid, r.data.ppid, r.data.ptid);
-  }
-}
diff --git a/simpleperf/thread_tree.h b/simpleperf/thread_tree.h
index de10138..da32da1 100644
--- a/simpleperf/thread_tree.h
+++ b/simpleperf/thread_tree.h
@@ -24,6 +24,9 @@
 #include <set>
 
 #include "dso.h"
+#include "environment.h"
+
+struct Record;
 
 namespace simpleperf {
 
@@ -33,16 +36,19 @@
   uint64_t pgoff;
   uint64_t time;  // Map creation time.
   Dso* dso;
+  bool in_kernel;
 
-  MapEntry(uint64_t start_addr, uint64_t len, uint64_t pgoff, uint64_t time, Dso* dso)
-      : start_addr(start_addr), len(len), pgoff(pgoff), time(time), dso(dso) {
-  }
-  MapEntry() {
-  }
+  MapEntry(uint64_t start_addr, uint64_t len, uint64_t pgoff, uint64_t time,
+           Dso* dso, bool in_kernel)
+      : start_addr(start_addr),
+        len(len),
+        pgoff(pgoff),
+        time(time),
+        dso(dso),
+        in_kernel(in_kernel) {}
+  MapEntry() {}
 
-  uint64_t get_end_addr() const {
-    return start_addr + len;
-  }
+  uint64_t get_end_addr() const { return start_addr + len; }
 };
 
 struct MapComparator {
@@ -56,34 +62,58 @@
   std::set<MapEntry*, MapComparator> maps;
 };
 
+// ThreadTree contains thread information (in ThreadEntry) and mmap information
+// (in MapEntry) of the monitored threads. It also has interface to access
+// symbols in executable binaries mapped in the monitored threads.
 class ThreadTree {
  public:
-  ThreadTree() : unknown_symbol_("unknown", 0, std::numeric_limits<unsigned long long>::max()) {
+  ThreadTree()
+      : show_ip_for_unknown_symbol_(false),
+        show_mark_for_unknown_symbol_(false),
+        unknown_symbol_("unknown", 0,
+                        std::numeric_limits<unsigned long long>::max()) {
     unknown_dso_ = Dso::CreateDso(DSO_ELF_FILE, "unknown");
-    unknown_map_ =
-        MapEntry(0, std::numeric_limits<unsigned long long>::max(), 0, 0, unknown_dso_.get());
+    unknown_map_ = MapEntry(0, std::numeric_limits<unsigned long long>::max(),
+                            0, 0, unknown_dso_.get(), false);
+    kernel_dso_ = Dso::CreateDso(DSO_KERNEL, DEFAULT_KERNEL_MMAP_NAME);
+    // We can't dump comm for pid 0 from /proc, so add it's name here.
+    AddThread(0, 0, "swapper");
   }
 
   void AddThread(int pid, int tid, const std::string& comm);
   void ForkThread(int pid, int tid, int ppid, int ptid);
   ThreadEntry* FindThreadOrNew(int pid, int tid);
-  void AddKernelMap(uint64_t start_addr, uint64_t len, uint64_t pgoff, uint64_t time,
-                    const std::string& filename);
-  void AddThreadMap(int pid, int tid, uint64_t start_addr, uint64_t len, uint64_t pgoff,
+  void AddKernelMap(uint64_t start_addr, uint64_t len, uint64_t pgoff,
                     uint64_t time, const std::string& filename);
-  const MapEntry* FindMap(const ThreadEntry* thread, uint64_t ip, bool in_kernel);
-  const Symbol* FindSymbol(const MapEntry* map, uint64_t ip);
-  const MapEntry* UnknownMap() const {
-    return &unknown_map_;
-  }
+  void AddThreadMap(int pid, int tid, uint64_t start_addr, uint64_t len,
+                    uint64_t pgoff, uint64_t time, const std::string& filename);
+  const MapEntry* FindMap(const ThreadEntry* thread, uint64_t ip,
+                          bool in_kernel);
+  // Find map for an ip address when we don't know whether it is in kernel.
+  const MapEntry* FindMap(const ThreadEntry* thread, uint64_t ip);
+  const Symbol* FindSymbol(const MapEntry* map, uint64_t ip,
+                           uint64_t* pvaddr_in_file);
+  const Symbol* FindKernelSymbol(uint64_t ip);
+  const Symbol* UnknownSymbol() const { return &unknown_symbol_; }
 
-  void Clear();
+  void ShowIpForUnknownSymbol() { show_ip_for_unknown_symbol_ = true; }
+  void ShowMarkForUnknownSymbol() {
+    show_mark_for_unknown_symbol_ = true;
+    unknown_symbol_ = Symbol("*unknown", 0, ULLONG_MAX);
+  }
+  // Clear thread and map information, but keep loaded dso information. It saves
+  // the time to reload dso information.
+  void ClearThreadAndMap();
+
+  // Update thread tree with information provided by record.
+  void Update(const Record& record);
 
  private:
   Dso* FindKernelDsoOrNew(const std::string& filename);
   Dso* FindUserDsoOrNew(const std::string& filename);
   MapEntry* AllocateMap(const MapEntry& value);
-  void FixOverlappedMap(std::set<MapEntry*, MapComparator>* map_set, const MapEntry* map);
+  void FixOverlappedMap(std::set<MapEntry*, MapComparator>* map_set,
+                        const MapEntry* map);
 
   std::unordered_map<int, std::unique_ptr<ThreadEntry>> thread_tree_;
   std::vector<std::unique_ptr<std::string>> thread_comm_storage_;
@@ -96,7 +126,10 @@
   std::unordered_map<std::string, std::unique_ptr<Dso>> module_dso_tree_;
   std::unordered_map<std::string, std::unique_ptr<Dso>> user_dso_tree_;
   std::unique_ptr<Dso> unknown_dso_;
+  bool show_ip_for_unknown_symbol_;
+  bool show_mark_for_unknown_symbol_;
   Symbol unknown_symbol_;
+  std::unordered_map<uint64_t, Dso*> dso_id_to_dso_map_;
 };
 
 }  // namespace simpleperf
@@ -105,8 +138,4 @@
 using ThreadEntry = simpleperf::ThreadEntry;
 using ThreadTree = simpleperf::ThreadTree;
 
-struct Record;
-
-void BuildThreadTree(const Record& record, ThreadTree* thread_tree);
-
 #endif  // SIMPLE_PERF_THREAD_TREE_H_
diff --git a/simpleperf/tracing.cpp b/simpleperf/tracing.cpp
new file mode 100644
index 0000000..884a883
--- /dev/null
+++ b/simpleperf/tracing.cpp
@@ -0,0 +1,426 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "tracing.h"
+
+#include <string.h>
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+
+#include "perf_event.h"
+#include "utils.h"
+
+const char TRACING_INFO_MAGIC[10] = {23,  8,   68,  't', 'r',
+                                     'a', 'c', 'i', 'n', 'g'};
+
+template <class T>
+void AppendData(std::vector<char>& data, const T& s) {
+  const char* p = reinterpret_cast<const char*>(&s);
+  data.insert(data.end(), p, p + sizeof(T));
+}
+
+static void AppendData(std::vector<char>& data, const char* s) {
+  data.insert(data.end(), s, s + strlen(s) + 1);
+}
+
+template <>
+void AppendData(std::vector<char>& data, const std::string& s) {
+  data.insert(data.end(), s.c_str(), s.c_str() + s.size() + 1);
+}
+
+template <>
+void MoveFromBinaryFormat(std::string& data, const char*& p) {
+  data.clear();
+  while (*p != '\0') {
+    data.push_back(*p++);
+  }
+  p++;
+}
+
+static void AppendFile(std::vector<char>& data, const std::string& file,
+                       uint32_t file_size_bytes = 8) {
+  if (file_size_bytes == 8) {
+    uint64_t file_size = file.size();
+    AppendData(data, file_size);
+  } else if (file_size_bytes == 4) {
+    uint32_t file_size = file.size();
+    AppendData(data, file_size);
+  }
+  data.insert(data.end(), file.begin(), file.end());
+}
+
+static void DetachFile(const char*& p, std::string& file,
+                       uint32_t file_size_bytes = 8) {
+  uint64_t file_size = ConvertBytesToValue(p, file_size_bytes);
+  p += file_size_bytes;
+  file.clear();
+  file.insert(file.end(), p, p + file_size);
+  p += file_size;
+}
+
+struct TraceType {
+  std::string system;
+  std::string name;
+};
+
+class TracingFile {
+ public:
+  TracingFile();
+  bool RecordHeaderFiles();
+  void RecordFtraceFiles(const std::vector<TraceType>& trace_types);
+  bool RecordEventFiles(const std::vector<TraceType>& trace_types);
+  bool RecordKallsymsFile();
+  bool RecordPrintkFormatsFile();
+  std::vector<char> BinaryFormat() const;
+  void LoadFromBinary(const std::vector<char>& data);
+  void Dump(size_t indent) const;
+  std::vector<TracingFormat> LoadTracingFormatsFromEventFiles() const;
+  const std::string& GetKallsymsFile() const { return kallsyms_file; }
+  uint32_t GetPageSize() const { return page_size; }
+
+ private:
+  char magic[10];
+  std::string version;
+  char endian;
+  uint8_t size_of_long;
+  uint32_t page_size;
+  std::string header_page_file;
+  std::string header_event_file;
+
+  std::vector<std::string> ftrace_format_files;
+  // pair of system, format_file_data.
+  std::vector<std::pair<std::string, std::string>> event_format_files;
+
+  std::string kallsyms_file;
+  std::string printk_formats_file;
+};
+
+TracingFile::TracingFile() {
+  memcpy(magic, TRACING_INFO_MAGIC, sizeof(TRACING_INFO_MAGIC));
+  version = "0.5";
+  endian = 0;
+  size_of_long = static_cast<int>(sizeof(long));
+  page_size = static_cast<uint32_t>(::GetPageSize());
+}
+
+bool TracingFile::RecordHeaderFiles() {
+  if (!android::base::ReadFileToString(
+          "/sys/kernel/debug/tracing/events/header_page", &header_page_file)) {
+    PLOG(ERROR)
+        << "failed to read /sys/kernel/debug/tracing/events/header_page";
+    return false;
+  }
+  if (!android::base::ReadFileToString(
+          "/sys/kernel/debug/tracing/events/header_event",
+          &header_event_file)) {
+    PLOG(ERROR)
+        << "failed to read /sys/kernel/debug/tracing/events/header_event";
+    return false;
+  }
+  return true;
+}
+
+void TracingFile::RecordFtraceFiles(const std::vector<TraceType>& trace_types) {
+  for (const auto& type : trace_types) {
+    std::string format_path = android::base::StringPrintf(
+        "/sys/kernel/debug/tracing/events/ftrace/%s/format", type.name.c_str());
+    std::string format_data;
+    if (android::base::ReadFileToString(format_path, &format_data)) {
+      ftrace_format_files.push_back(std::move(format_data));
+    }
+  }
+}
+
+bool TracingFile::RecordEventFiles(const std::vector<TraceType>& trace_types) {
+  for (const auto& type : trace_types) {
+    std::string format_path = android::base::StringPrintf(
+        "/sys/kernel/debug/tracing/events/%s/%s/format", type.system.c_str(),
+        type.name.c_str());
+    std::string format_data;
+    if (!android::base::ReadFileToString(format_path, &format_data)) {
+      PLOG(ERROR) << "failed to read " << format_path;
+      return false;
+    }
+    event_format_files.push_back(
+        std::make_pair(type.system, std::move(format_data)));
+  }
+  return true;
+}
+
+bool TracingFile::RecordPrintkFormatsFile() {
+  if (!android::base::ReadFileToString(
+          "/sys/kernel/debug/tracing/printk_formats", &printk_formats_file)) {
+    PLOG(ERROR) << "failed to read /sys/kernel/debug/tracing/printk_formats";
+    return false;
+  }
+  return true;
+}
+
+std::vector<char> TracingFile::BinaryFormat() const {
+  std::vector<char> ret;
+  ret.insert(ret.end(), magic, magic + sizeof(magic));
+  AppendData(ret, version);
+  ret.push_back(endian);
+  AppendData(ret, size_of_long);
+  AppendData(ret, page_size);
+  AppendData(ret, "header_page");
+  AppendFile(ret, header_page_file);
+  AppendData(ret, "header_event");
+  AppendFile(ret, header_event_file);
+  int count = static_cast<int>(ftrace_format_files.size());
+  AppendData(ret, count);
+  for (const auto& format : ftrace_format_files) {
+    AppendFile(ret, format);
+  }
+  count = static_cast<int>(event_format_files.size());
+  AppendData(ret, count);
+  for (const auto& pair : event_format_files) {
+    AppendData(ret, pair.first);
+    AppendData(ret, 1);
+    AppendFile(ret, pair.second);
+  }
+  AppendFile(ret, kallsyms_file, 4);
+  AppendFile(ret, printk_formats_file, 4);
+  return ret;
+}
+
+void TracingFile::LoadFromBinary(const std::vector<char>& data) {
+  const char* p = data.data();
+  const char* end = data.data() + data.size();
+  CHECK(memcmp(p, magic, sizeof(magic)) == 0);
+  p += sizeof(magic);
+  MoveFromBinaryFormat(version, p);
+  MoveFromBinaryFormat(endian, p);
+  MoveFromBinaryFormat(size_of_long, p);
+  MoveFromBinaryFormat(page_size, p);
+  std::string filename;
+  MoveFromBinaryFormat(filename, p);
+  CHECK_EQ(filename, "header_page");
+  DetachFile(p, header_page_file);
+  MoveFromBinaryFormat(filename, p);
+  CHECK_EQ(filename, "header_event");
+  DetachFile(p, header_event_file);
+  uint32_t count;
+  MoveFromBinaryFormat(count, p);
+  ftrace_format_files.resize(count);
+  for (uint32_t i = 0; i < count; ++i) {
+    DetachFile(p, ftrace_format_files[i]);
+  }
+  MoveFromBinaryFormat(count, p);
+  event_format_files.clear();
+  for (uint32_t i = 0; i < count; ++i) {
+    std::string system;
+    MoveFromBinaryFormat(system, p);
+    uint32_t count_in_system;
+    MoveFromBinaryFormat(count_in_system, p);
+    for (uint32_t i = 0; i < count_in_system; ++i) {
+      std::string format;
+      DetachFile(p, format);
+      event_format_files.push_back(std::make_pair(system, std::move(format)));
+    }
+  }
+  DetachFile(p, kallsyms_file, 4);
+  DetachFile(p, printk_formats_file, 4);
+  CHECK_EQ(p, end);
+}
+
+void TracingFile::Dump(size_t indent) const {
+  PrintIndented(indent, "tracing data:\n");
+  PrintIndented(indent + 1, "magic: ");
+  for (size_t i = 0; i < 3u; ++i) {
+    printf("0x%x ", magic[i]);
+  }
+  for (size_t i = 3; i < sizeof(magic); ++i) {
+    printf("%c", magic[i]);
+  }
+  printf("\n");
+  PrintIndented(indent + 1, "version: %s\n", version.c_str());
+  PrintIndented(indent + 1, "endian: %d\n", endian);
+  PrintIndented(indent + 1, "header_page:\n%s\n\n", header_page_file.c_str());
+  PrintIndented(indent + 1, "header_event:\n%s\n\n", header_event_file.c_str());
+  for (size_t i = 0; i < ftrace_format_files.size(); ++i) {
+    PrintIndented(indent + 1, "ftrace format file %zu/%zu:\n%s\n\n", i + 1,
+                  ftrace_format_files.size(), ftrace_format_files[i].c_str());
+  }
+  for (size_t i = 0; i < event_format_files.size(); ++i) {
+    PrintIndented(indent + 1, "event format file %zu/%zu %s:\n%s\n\n", i + 1,
+                  event_format_files.size(),
+                  event_format_files[i].first.c_str(),
+                  event_format_files[i].second.c_str());
+  }
+  PrintIndented(indent + 1, "kallsyms:\n%s\n\n", kallsyms_file.c_str());
+  PrintIndented(indent + 1, "printk_formats:\n%s\n\n",
+                printk_formats_file.c_str());
+}
+
+enum class FormatParsingState {
+  READ_NAME,
+  READ_ID,
+  READ_FIELDS,
+  READ_PRINTFMT,
+};
+
+// Parse lines like: field:char comm[16]; offset:8; size:16;  signed:1;
+static TracingField ParseTracingField(const std::string& s) {
+  TracingField field;
+  size_t start = 0;
+  std::string name;
+  std::string value;
+  for (size_t i = 0; i < s.size(); ++i) {
+    if (!isspace(s[i]) && (i == 0 || isspace(s[i - 1]))) {
+      start = i;
+    } else if (s[i] == ':') {
+      name = s.substr(start, i - start);
+      start = i + 1;
+    } else if (s[i] == ';') {
+      value = s.substr(start, i - start);
+      if (name == "field") {
+        size_t pos = value.find_first_of('[');
+        if (pos == std::string::npos) {
+          field.name = value;
+          field.elem_count = 1;
+        } else {
+          field.name = value.substr(0, pos);
+          field.elem_count =
+              static_cast<size_t>(strtoull(&value[pos + 1], nullptr, 10));
+        }
+      } else if (name == "offset") {
+        field.offset =
+            static_cast<size_t>(strtoull(value.c_str(), nullptr, 10));
+      } else if (name == "size") {
+        size_t size = static_cast<size_t>(strtoull(value.c_str(), nullptr, 10));
+        CHECK_EQ(size % field.elem_count, 0u);
+        field.elem_size = size / field.elem_count;
+      } else if (name == "signed") {
+        int is_signed = static_cast<int>(strtoull(value.c_str(), nullptr, 10));
+        field.is_signed = (is_signed == 1);
+      }
+    }
+  }
+  return field;
+}
+
+std::vector<TracingFormat> TracingFile::LoadTracingFormatsFromEventFiles()
+    const {
+  std::vector<TracingFormat> formats;
+  for (const auto& pair : event_format_files) {
+    TracingFormat format;
+    format.system_name = pair.first;
+    std::vector<std::string> strs = android::base::Split(pair.second, "\n");
+    FormatParsingState state = FormatParsingState::READ_NAME;
+    for (const auto& s : strs) {
+      if (state == FormatParsingState::READ_NAME) {
+        size_t pos = s.find_first_of("name:");
+        if (pos != std::string::npos) {
+          format.name = android::base::Trim(s.substr(pos + strlen("name:")));
+          state = FormatParsingState::READ_ID;
+        }
+      } else if (state == FormatParsingState::READ_ID) {
+        size_t pos = s.find_first_of("ID:");
+        if (pos != std::string::npos) {
+          format.id =
+              strtoull(s.substr(pos + strlen("ID:")).c_str(), nullptr, 10);
+          state = FormatParsingState::READ_FIELDS;
+        }
+      } else if (state == FormatParsingState::READ_FIELDS) {
+        size_t pos = s.find_first_of("field:");
+        if (pos != std::string::npos) {
+          TracingField field = ParseTracingField(s);
+          format.fields.push_back(field);
+        }
+      }
+    }
+    formats.push_back(format);
+  }
+  return formats;
+}
+
+Tracing::Tracing(const std::vector<char>& data) {
+  tracing_file_ = new TracingFile;
+  tracing_file_->LoadFromBinary(data);
+}
+
+Tracing::~Tracing() { delete tracing_file_; }
+
+void Tracing::Dump(size_t indent) { tracing_file_->Dump(indent); }
+
+TracingFormat Tracing::GetTracingFormatHavingId(uint64_t trace_event_id) {
+  if (tracing_formats_.empty()) {
+    tracing_formats_ = tracing_file_->LoadTracingFormatsFromEventFiles();
+  }
+  for (const auto& format : tracing_formats_) {
+    if (format.id == trace_event_id) {
+      return format;
+    }
+  }
+  LOG(FATAL) << "no tracing format for id " << trace_event_id;
+  return TracingFormat();
+}
+
+std::string Tracing::GetTracingEventNameHavingId(uint64_t trace_event_id) {
+  if (tracing_formats_.empty()) {
+    tracing_formats_ = tracing_file_->LoadTracingFormatsFromEventFiles();
+  }
+  for (const auto& format : tracing_formats_) {
+    if (format.id == trace_event_id) {
+      return android::base::StringPrintf("%s:%s", format.system_name.c_str(),
+                                         format.name.c_str());
+    }
+  }
+  return "";
+}
+
+const std::string& Tracing::GetKallsyms() const {
+  return tracing_file_->GetKallsymsFile();
+}
+
+uint32_t Tracing::GetPageSize() const { return tracing_file_->GetPageSize(); }
+
+bool GetTracingData(const std::vector<const EventType*>& event_types,
+                    std::vector<char>* data) {
+  data->clear();
+  std::vector<TraceType> trace_types;
+  for (const auto& type : event_types) {
+    CHECK_EQ(PERF_TYPE_TRACEPOINT, type->type);
+    size_t pos = type->name.find(':');
+    TraceType trace_type;
+    trace_type.system = type->name.substr(0, pos);
+    trace_type.name = type->name.substr(pos + 1);
+    trace_types.push_back(trace_type);
+  }
+  TracingFile tracing_file;
+  if (!tracing_file.RecordHeaderFiles()) {
+    return false;
+  }
+  tracing_file.RecordFtraceFiles(trace_types);
+  if (!tracing_file.RecordEventFiles(trace_types)) {
+    return false;
+  }
+  // Don't record /proc/kallsyms here, as it will be contained in
+  // KernelSymbolRecord.
+  if (!tracing_file.RecordPrintkFormatsFile()) {
+    return false;
+  }
+  *data = tracing_file.BinaryFormat();
+  return true;
+}
diff --git a/simpleperf/tracing.h b/simpleperf/tracing.h
new file mode 100644
index 0000000..e4c375b
--- /dev/null
+++ b/simpleperf/tracing.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SIMPLE_PERF_TRACING_H_
+#define SIMPLE_PERF_TRACING_H_
+
+#include <vector>
+
+#include <android-base/logging.h>
+
+#include "event_type.h"
+#include "utils.h"
+
+struct TracingField {
+  std::string name;
+  size_t offset;
+  size_t elem_size;
+  size_t elem_count;
+  bool is_signed;
+};
+
+struct TracingFieldPlace {
+  uint32_t offset;
+  uint32_t size;
+
+  uint64_t ReadFromData(const char* raw_data) {
+    return ConvertBytesToValue(raw_data + offset, size);
+  }
+};
+
+struct TracingFormat {
+  std::string system_name;
+  std::string name;
+  uint64_t id;
+  std::vector<TracingField> fields;
+
+  void GetField(const std::string& name, TracingFieldPlace& place) {
+    const TracingField& field = GetField(name);
+    place.offset = field.offset;
+    place.size = field.elem_size;
+  }
+
+ private:
+  const TracingField& GetField(const std::string& name) {
+    for (const auto& field : fields) {
+      if (field.name == name) {
+        return field;
+      }
+    }
+    LOG(FATAL) << "Couldn't find field " << name << "in TracingFormat of "
+               << this->name;
+    return fields[0];
+  }
+};
+
+class TracingFile;
+
+class Tracing {
+ public:
+  explicit Tracing(const std::vector<char>& data);
+  ~Tracing();
+  void Dump(size_t indent);
+  TracingFormat GetTracingFormatHavingId(uint64_t trace_event_id);
+  std::string GetTracingEventNameHavingId(uint64_t trace_event_id);
+  const std::string& GetKallsyms() const;
+  uint32_t GetPageSize() const;
+
+ private:
+  TracingFile* tracing_file_;
+  std::vector<TracingFormat> tracing_formats_;
+};
+
+bool GetTracingData(const std::vector<const EventType*>& event_types,
+                    std::vector<char>* data);
+
+#endif  // SIMPLE_PERF_TRACING_H_
diff --git a/simpleperf/utils.cpp b/simpleperf/utils.cpp
index d14d20c..e2d25aa 100644
--- a/simpleperf/utils.cpp
+++ b/simpleperf/utils.cpp
@@ -19,6 +19,7 @@
 #include <dirent.h>
 #include <errno.h>
 #include <fcntl.h>
+#include <inttypes.h>
 #include <stdarg.h>
 #include <stdio.h>
 #include <sys/stat.h>
@@ -31,6 +32,10 @@
 #include <android-base/file.h>
 #include <android-base/logging.h>
 
+#include <7zCrc.h>
+#include <Xz.h>
+#include <XzCrc64.h>
+
 void OneTimeFreeAllocator::Clear() {
   for (auto& p : v_) {
     delete[] p;
@@ -95,39 +100,45 @@
   va_end(ap);
 }
 
+void FprintIndented(FILE* fp, size_t indent, const char* fmt, ...) {
+  va_list ap;
+  va_start(ap, fmt);
+  fprintf(fp, "%*s", static_cast<int>(indent * 2), "");
+  vfprintf(fp, fmt, ap);
+  va_end(ap);
+}
+
 bool IsPowerOfTwo(uint64_t value) {
   return (value != 0 && ((value & (value - 1)) == 0));
 }
 
-void GetEntriesInDir(const std::string& dirpath, std::vector<std::string>* files,
-                     std::vector<std::string>* subdirs) {
-  if (files != nullptr) {
-    files->clear();
-  }
-  if (subdirs != nullptr) {
-    subdirs->clear();
-  }
+std::vector<std::string> GetEntriesInDir(const std::string& dirpath) {
+  std::vector<std::string> result;
   DIR* dir = opendir(dirpath.c_str());
   if (dir == nullptr) {
     PLOG(DEBUG) << "can't open dir " << dirpath;
-    return;
+    return result;
   }
   dirent* entry;
   while ((entry = readdir(dir)) != nullptr) {
     if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
       continue;
     }
-    if (IsDir(dirpath + std::string("/") + entry->d_name)) {
-      if (subdirs != nullptr) {
-        subdirs->push_back(entry->d_name);
-      }
-    } else {
-      if (files != nullptr) {
-        files->push_back(entry->d_name);
-      }
-    }
+    result.push_back(entry->d_name);
   }
   closedir(dir);
+  return result;
+}
+
+std::vector<std::string> GetSubDirs(const std::string& dirpath) {
+  std::vector<std::string> entries = GetEntriesInDir(dirpath);
+  std::vector<std::string> result;
+  for (size_t i = 0; i < entries.size(); ++i) {
+    if (IsDir(dirpath + "/" + entries[i])) {
+      result.push_back(std::move(entries[i]));
+    }
+  }
+  return result;
 }
 
 bool IsDir(const std::string& dirpath) {
@@ -182,10 +193,57 @@
   return true;
 }
 
+static void* xz_alloc(void*, size_t size) {
+  return malloc(size);
+}
+
+static void xz_free(void*, void* address) {
+  free(address);
+}
+
+bool XzDecompress(const std::string& compressed_data, std::string* decompressed_data) {
+  ISzAlloc alloc;
+  CXzUnpacker state;
+  alloc.Alloc = xz_alloc;
+  alloc.Free = xz_free;
+  XzUnpacker_Construct(&state, &alloc);
+  CrcGenerateTable();
+  Crc64GenerateTable();
+  size_t src_offset = 0;
+  size_t dst_offset = 0;
+  std::string dst(compressed_data.size(), ' ');
+
+  ECoderStatus status = CODER_STATUS_NOT_FINISHED;
+  while (status == CODER_STATUS_NOT_FINISHED) {
+    dst.resize(dst.size() * 2);
+    size_t src_remaining = compressed_data.size() - src_offset;
+    size_t dst_remaining = dst.size() - dst_offset;
+    int res = XzUnpacker_Code(&state, reinterpret_cast<Byte*>(&dst[dst_offset]), &dst_remaining,
+                              reinterpret_cast<const Byte*>(&compressed_data[src_offset]),
+                              &src_remaining, CODER_FINISH_ANY, &status);
+    if (res != SZ_OK) {
+      LOG(ERROR) << "LZMA decompression failed with error " << res;
+      XzUnpacker_Free(&state);
+      return false;
+    }
+    src_offset += src_remaining;
+    dst_offset += dst_remaining;
+  }
+  XzUnpacker_Free(&state);
+  if (!XzUnpacker_IsStreamWasFinished(&state)) {
+    LOG(ERROR) << "LZMA decompresstion failed due to incomplete stream";
+    return false;
+  }
+  dst.resize(dst_offset);
+  *decompressed_data = std::move(dst);
+  return true;
+}
+
 bool GetLogSeverity(const std::string& name, android::base::LogSeverity* severity) {
   static std::map<std::string, android::base::LogSeverity> log_severity_map = {
       {"verbose", android::base::VERBOSE},
       {"debug", android::base::DEBUG},
+      {"info", android::base::INFO},
       {"warning", android::base::WARNING},
       {"error", android::base::ERROR},
       {"fatal", android::base::FATAL},
@@ -209,3 +267,74 @@
   }
   return is_root == 1;
 }
+
+bool ProcessKernelSymbols(std::string& symbol_data,
+                          const std::function<bool(const KernelSymbol&)>& callback) {
+  char* p = &symbol_data[0];
+  char* data_end = p + symbol_data.size();
+  while (p < data_end) {
+    char* line_end = strchr(p, '\n');
+    if (line_end != nullptr) {
+      *line_end = '\0';
+    }
+    size_t line_size = (line_end != nullptr) ? (line_end - p) : (data_end - p);
+    // Parse line like: ffffffffa005c4e4 d __warned.41698       [libsas]
+    char name[line_size];
+    char module[line_size];
+    strcpy(module, "");
+
+    KernelSymbol symbol;
+    int ret = sscanf(p, "%" PRIx64 " %c %s%s", &symbol.addr, &symbol.type, name, module);
+    if (line_end != nullptr) {
+      *line_end = '\n';
+      p = line_end + 1;
+    } else {
+      p = data_end;
+    }
+    if (ret >= 3) {
+      symbol.name = name;
+      size_t module_len = strlen(module);
+      if (module_len > 2 && module[0] == '[' && module[module_len - 1] == ']') {
+        module[module_len - 1] = '\0';
+        symbol.module = &module[1];
+      } else {
+        symbol.module = nullptr;
+      }
+
+      if (callback(symbol)) {
+        return true;
+      }
+    }
+  }
+  return false;
+}
+
+size_t GetPageSize() {
+#if defined(__linux__)
+  return sysconf(_SC_PAGE_SIZE);
+#else
+  return 4096;
+#endif
+}
+
+uint64_t ConvertBytesToValue(const char* bytes, uint32_t size) {
+  switch (size) {
+    case 1:
+      return *reinterpret_cast<const uint8_t*>(bytes);
+    case 2:
+      return *reinterpret_cast<const uint16_t*>(bytes);
+    case 4:
+      return *reinterpret_cast<const uint32_t*>(bytes);
+    case 8:
+      return *reinterpret_cast<const uint64_t*>(bytes);
+  }
+  LOG(FATAL) << "unexpected size " << size << " in ConvertBytesToValue";
+  return 0;
+}
+
+timeval SecondToTimeval(double time_in_sec) {
+  timeval tv;
+  tv.tv_sec = static_cast<time_t>(time_in_sec);
+  tv.tv_usec = static_cast<int>((time_in_sec - tv.tv_sec) * 1000000);
+  return tv;
+}
diff --git a/simpleperf/utils.h b/simpleperf/utils.h
index 420fcc9..a032681 100644
--- a/simpleperf/utils.h
+++ b/simpleperf/utils.h
@@ -18,6 +18,7 @@
 #define SIMPLE_PERF_UTILS_H_
 
 #include <stddef.h>
+#include <time.h>
 
 #include <string>
 #include <vector>
@@ -26,7 +27,9 @@
 #include <android-base/macros.h>
 #include <ziparchive/zip_archive.h>
 
-#define ALIGN(value, alignment) (((value) + (alignment)-1) & ~((alignment)-1))
+static inline uint64_t Align(uint64_t value, uint64_t alignment) {
+  return (value + alignment - 1) & ~(alignment - 1);
+}
 
 #ifdef _WIN32
 #define CLOSE_ON_EXEC_MODE ""
@@ -38,7 +41,7 @@
 // It reduces the cost to free each allocated memory.
 class OneTimeFreeAllocator {
  public:
-  OneTimeFreeAllocator(size_t unit_size = 8192u)
+  explicit OneTimeFreeAllocator(size_t unit_size = 8192u)
       : unit_size_(unit_size), cur_(nullptr), end_(nullptr) {
   }
 
@@ -77,7 +80,7 @@
   }
 
  private:
-  FileHelper(int fd) : fd_(fd) {}
+  explicit FileHelper(int fd) : fd_(fd) {}
   int fd_;
 
   DISALLOW_COPY_AND_ASSIGN(FileHelper);
@@ -109,17 +112,37 @@
 }
 
 void PrintIndented(size_t indent, const char* fmt, ...);
+void FprintIndented(FILE* fp, size_t indent, const char* fmt, ...);
 
 bool IsPowerOfTwo(uint64_t value);
 
-void GetEntriesInDir(const std::string& dirpath, std::vector<std::string>* files,
-                     std::vector<std::string>* subdirs);
+std::vector<std::string> GetEntriesInDir(const std::string& dirpath);
+std::vector<std::string> GetSubDirs(const std::string& dirpath);
 bool IsDir(const std::string& dirpath);
 bool IsRegularFile(const std::string& filename);
 uint64_t GetFileSize(const std::string& filename);
 bool MkdirWithParents(const std::string& path);
 
+bool XzDecompress(const std::string& compressed_data, std::string* decompressed_data);
+
 bool GetLogSeverity(const std::string& name, android::base::LogSeverity* severity);
 
 bool IsRoot();
+
+struct KernelSymbol {
+  uint64_t addr;
+  char type;
+  const char* name;
+  const char* module;  // If nullptr, the symbol is not in a kernel module.
+};
+
+bool ProcessKernelSymbols(std::string& symbol_data,
+                          const std::function<bool(const KernelSymbol&)>& callback);
+
+size_t GetPageSize();
+
+uint64_t ConvertBytesToValue(const char* bytes, uint32_t size);
+
+timeval SecondToTimeval(double time_in_sec);
+
 #endif  // SIMPLE_PERF_UTILS_H_
diff --git a/simpleperf/utils_test.cpp b/simpleperf/utils_test.cpp
new file mode 100644
index 0000000..23c669e
--- /dev/null
+++ b/simpleperf/utils_test.cpp
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+
+#include "utils.h"
+
+static bool ModulesMatch(const char* p, const char* q) {
+  if (p == nullptr && q == nullptr) {
+    return true;
+  }
+  if (p != nullptr && q != nullptr) {
+    return strcmp(p, q) == 0;
+  }
+  return false;
+}
+
+static bool KernelSymbolsMatch(const KernelSymbol& sym1,
+                               const KernelSymbol& sym2) {
+  return sym1.addr == sym2.addr && sym1.type == sym2.type &&
+         strcmp(sym1.name, sym2.name) == 0 &&
+         ModulesMatch(sym1.module, sym2.module);
+}
+
+TEST(environment, ProcessKernelSymbols) {
+  std::string data =
+      "ffffffffa005c4e4 d __warned.41698   [libsas]\n"
+      "aaaaaaaaaaaaaaaa T _text\n"
+      "cccccccccccccccc c ccccc\n";
+  KernelSymbol expected_symbol;
+  expected_symbol.addr = 0xffffffffa005c4e4ULL;
+  expected_symbol.type = 'd';
+  expected_symbol.name = "__warned.41698";
+  expected_symbol.module = "libsas";
+  ASSERT_TRUE(ProcessKernelSymbols(
+      data,
+      std::bind(&KernelSymbolsMatch, std::placeholders::_1, expected_symbol)));
+
+  expected_symbol.addr = 0xaaaaaaaaaaaaaaaaULL;
+  expected_symbol.type = 'T';
+  expected_symbol.name = "_text";
+  expected_symbol.module = nullptr;
+  ASSERT_TRUE(ProcessKernelSymbols(
+      data,
+      std::bind(&KernelSymbolsMatch, std::placeholders::_1, expected_symbol)));
+
+  expected_symbol.name = "non_existent_symbol";
+  ASSERT_FALSE(ProcessKernelSymbols(
+      data,
+      std::bind(&KernelSymbolsMatch, std::placeholders::_1, expected_symbol)));
+}
diff --git a/simpleperf/workload.cpp b/simpleperf/workload.cpp
index 35617fd..1d34c11 100644
--- a/simpleperf/workload.cpp
+++ b/simpleperf/workload.cpp
@@ -18,6 +18,7 @@
 
 #include <errno.h>
 #include <fcntl.h>
+#include <sys/prctl.h>
 #include <sys/wait.h>
 #include <unistd.h>
 
@@ -33,9 +34,9 @@
 
 Workload::~Workload() {
   if (work_pid_ != -1 && work_state_ != NotYetCreateNewProcess) {
-    if (!Workload::WaitChildProcess(false)) {
+    if (!Workload::WaitChildProcess(false, false)) {
       kill(work_pid_, SIGKILL);
-      Workload::WaitChildProcess(true);
+      Workload::WaitChildProcess(true, true);
     }
   }
   if (start_signal_fd_ != -1) {
@@ -91,6 +92,8 @@
 }
 
 static void ChildProcessFn(std::vector<std::string>& args, int start_signal_fd, int exec_child_fd) {
+  // Die if parent exits.
+  prctl(PR_SET_PDEATHSIG, SIGHUP, 0, 0, 0);
   std::vector<char*> argv(args.size() + 1);
   for (size_t i = 0; i < args.size(); ++i) {
     argv[i] = &args[i][0];
@@ -137,14 +140,16 @@
   return true;
 }
 
-bool Workload::WaitChildProcess(bool wait_forever) {
+bool Workload::WaitChildProcess(bool wait_forever, bool is_child_killed) {
   bool finished = false;
   int status;
   pid_t result = TEMP_FAILURE_RETRY(waitpid(work_pid_, &status, (wait_forever ? 0 : WNOHANG)));
   if (result == work_pid_) {
     finished = true;
     if (WIFSIGNALED(status)) {
-      LOG(WARNING) << "child process was terminated by signal " << strsignal(WTERMSIG(status));
+      if (!(is_child_killed && WTERMSIG(status) == SIGKILL)) {
+        LOG(WARNING) << "child process was terminated by signal " << strsignal(WTERMSIG(status));
+      }
     } else if (WIFEXITED(status) && WEXITSTATUS(status) != 0) {
       LOG(WARNING) << "child process exited with exit code " << WEXITSTATUS(status);
     }
diff --git a/simpleperf/workload.h b/simpleperf/workload.h
index 60b9ee8..fa754b5 100644
--- a/simpleperf/workload.h
+++ b/simpleperf/workload.h
@@ -43,7 +43,7 @@
   }
 
  private:
-  Workload(const std::vector<std::string>& args)
+  explicit Workload(const std::vector<std::string>& args)
       : work_state_(NotYetCreateNewProcess),
         args_(args),
         work_pid_(-1),
@@ -52,7 +52,7 @@
   }
 
   bool CreateNewProcess();
-  bool WaitChildProcess(bool wait_forever);
+  bool WaitChildProcess(bool wait_forever, bool is_child_killed);
 
   WorkState work_state_;
   std::vector<std::string> args_;
diff --git a/simpleperf/workload_test.cpp b/simpleperf/workload_test.cpp
index eb1e345..9824143 100644
--- a/simpleperf/workload_test.cpp
+++ b/simpleperf/workload_test.cpp
@@ -18,24 +18,20 @@
 
 #include <signal.h>
 
-#include "scoped_signal_handler.h"
+#include "IOEventLoop.h"
 #include "utils.h"
 #include "workload.h"
 
-static volatile bool signaled;
-static void signal_handler(int) {
-  signaled = true;
-}
-
 TEST(workload, success) {
-  signaled = false;
-  ScopedSignalHandler scoped_signal_handler({SIGCHLD}, signal_handler);
+  IOEventLoop loop;
+  ASSERT_TRUE(loop.AddSignalEvent(SIGCHLD, [&]() {
+    return loop.ExitLoop();
+  }));
   auto workload = Workload::CreateWorkload({"sleep", "1"});
   ASSERT_TRUE(workload != nullptr);
   ASSERT_TRUE(workload->GetPid() != 0);
   ASSERT_TRUE(workload->Start());
-  while (!signaled) {
-  }
+  ASSERT_TRUE(loop.RunLoop());
 }
 
 TEST(workload, execvp_failure) {
@@ -46,14 +42,15 @@
 
 static void run_signaled_workload() {
   {
-    signaled = false;
-    ScopedSignalHandler scoped_signal_handler({SIGCHLD}, signal_handler);
+    IOEventLoop loop;
+    ASSERT_TRUE(loop.AddSignalEvent(SIGCHLD, [&]() {
+      return loop.ExitLoop();
+    }));
     auto workload = Workload::CreateWorkload({"sleep", "10"});
     ASSERT_TRUE(workload != nullptr);
     ASSERT_TRUE(workload->Start());
     ASSERT_EQ(0, kill(workload->GetPid(), SIGKILL));
-    while (!signaled) {
-    }
+    ASSERT_TRUE(loop.RunLoop());
   }
   // Make sure all destructors are called before exit().
   exit(0);
@@ -66,13 +63,14 @@
 
 static void run_exit_nonzero_workload() {
   {
-    signaled = false;
-    ScopedSignalHandler scoped_signal_handler({SIGCHLD}, signal_handler);
+    IOEventLoop loop;
+    ASSERT_TRUE(loop.AddSignalEvent(SIGCHLD, [&]() {
+      return loop.ExitLoop();
+    }));
     auto workload = Workload::CreateWorkload({"ls", "nonexistdir"});
     ASSERT_TRUE(workload != nullptr);
     ASSERT_TRUE(workload->Start());
-    while (!signaled) {
-    }
+    ASSERT_TRUE(loop.RunLoop());
   }
   // Make sure all destructors are called before exit().
   exit(0);
diff --git a/tests/binder/benchmarks/binderAddInts.cpp b/tests/binder/benchmarks/binderAddInts.cpp
index 7ac5360..1951331 100644
--- a/tests/binder/benchmarks/binderAddInts.cpp
+++ b/tests/binder/benchmarks/binderAddInts.cpp
@@ -62,7 +62,7 @@
 class AddIntsService : public BBinder
 {
   public:
-    AddIntsService(int cpu = unbound);
+    explicit AddIntsService(int cpu = unbound);
     virtual ~AddIntsService() {}
 
     enum command {
diff --git a/tests/bootloader/bootloadertest.py b/tests/bootloader/bootloadertest.py
new file mode 100644
index 0000000..5923343
--- /dev/null
+++ b/tests/bootloader/bootloadertest.py
@@ -0,0 +1,198 @@
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import adb
+import os
+import unittest
+import fastboot
+import subprocess
+
+class ShellTest(unittest.TestCase):
+    def __init__(self, *args, **kwargs):
+        super(ShellTest, self).__init__(*args, **kwargs)
+        self.fastboot = fastboot.FastbootDevice()
+
+    def exists_validvals(self, varname, varlist, validlist):
+        self.assertIn(varname, varlist)
+        self.assertIn(varlist[varname], validlist)
+        return varlist[varname]
+
+    def exists_yes_no(self, varname, varlist):
+        return self.exists_validvals(varname, varlist, ["yes", "no"])
+
+    def exists_nonempty(self, varname, varlist):
+        self.assertIn(varname, varlist)
+        self.assertGreater(len(varlist[varname]), 0)
+        return varlist[varname]
+
+    def exists_integer(self, varname, varlist, base=10):
+        val = 0
+        self.assertIn(varname, varlist)
+        try:
+            val = int(varlist[varname], base)
+        except ValueError:
+            self.fail("%s (%s) is not an integer" % (varname, varlist[varname]))
+        return val
+
+    def get_exists(self, varname):
+        val = self.fastboot.getvar(varname)
+        self.assertIsNotNone(val)
+        return val
+
+    def get_exists_validvals(self, varname, validlist):
+        val = self.get_exists(varname)
+        self.assertIn(val, validlist)
+        return val
+
+    def get_exists_yes_no(self, varname):
+        return self.get_exists_validvals(varname, ["yes", "no"])
+
+    def get_exists_nonempty(self, varname):
+        val = self.get_exists(varname)
+        self.assertGreater(len(val), 0)
+        return val
+
+    def get_exists_integer(self, varname, base=10):
+        val = self.get_exists(varname)
+        try:
+            num = int(val, base)
+        except ValueError:
+            self.fail("%s (%s) is not an integer" % (varname, val))
+        return num
+
+    def test_getvarall(self):
+        """Tests that required variables are reported by getvar all"""
+
+        var_all = self.fastboot.getvar_all()
+        self.exists_nonempty("version-baseband", var_all)
+        self.exists_nonempty("version-bootloader", var_all)
+        self.exists_nonempty("product", var_all)
+        self.exists_yes_no("secure", var_all)
+        self.exists_yes_no("unlocked", var_all)
+        self.exists_validvals("off-mode-charge", var_all, ["0", "1"])
+        self.assertIn("variant", var_all)
+        voltage = self.exists_nonempty("battery-voltage", var_all)
+        if voltage[-2:].lower() == "mv":
+            voltage = voltage[:-2]
+        try:
+            voltnum = float(voltage)
+        except ValueError:
+            self.fail("battery-voltage (%s) is not a number" % (varname, voltage))
+        self.exists_yes_no("battery-soc-ok", var_all)
+        maxdl = self.exists_integer("max-download-size", var_all, 16)
+        self.assertGreater(maxdl, 0)
+
+        if "slot-count" in var_all:
+            try:
+                slotcount = int(var_all["slot-count"])
+            except ValueError:
+                self.fail("slot-count (%s) is not an integer" % var_all["slot-count"])
+            if slotcount > 1:
+                # test for A/B variables
+                slots = [chr(slotnum+ord('a')) for slotnum in range(slotcount)]
+                self.exists_validvals("current-slot", var_all, slots)
+
+                # test for slot metadata
+                for slot in slots:
+                    self.exists_yes_no("slot-unbootable:"+slot, var_all)
+                    self.exists_yes_no("slot-unbootable:"+slot, var_all)
+                    self.exists_integer("slot-retry-count:"+slot, var_all)
+            else:
+                print "This does not appear to be an A/B device."
+
+    def test_getvar_nonexistent(self):
+        """Tests behaviour of nonexistent variables."""
+
+        self.assertIsNone(self.fastboot.getvar("fhqwhgads"))
+
+    def test_getvar(self):
+        """Tests all variables separately"""
+
+        self.get_exists_nonempty("version-baseband")
+        self.get_exists_nonempty("version-bootloader")
+        self.get_exists_nonempty("product")
+        self.get_exists_yes_no("secure")
+        self.get_exists_yes_no("unlocked")
+        self.get_exists_validvals("off-mode-charge", ["0", "1"])
+        self.get_exists("variant")
+        voltage = self.get_exists_nonempty("battery-voltage")
+        if voltage[-2:].lower() == "mv":
+            voltage = voltage[:-2]
+        try:
+            voltnum = float(voltage)
+        except ValueError:
+            self.fail("battery-voltage (%s) is not a number" % voltage)
+        self.get_exists_yes_no("battery-soc-ok")
+        maxdl = self.get_exists_integer("max-download-size", 16)
+        self.assertGreater(maxdl, 0)
+
+        slotcount = 0
+        try:
+            slotcountString = self.fastboot.getvar("slot-count")
+            if slotcountString != None:
+                slotcount = int(slotcountString)
+        except ValueError:
+            self.fail("slot-count (%s) is not an integer" % slotcountString)
+        if slotcount  > 1:
+            # test for A/B variables
+            slots = [chr(slotnum+ord('a')) for slotnum in range(slotcount)]
+            self.get_exists_validvals("current-slot", slots)
+
+            # test for slot metadata
+            for slot in slots:
+                self.get_exists_yes_no("slot-unbootable:"+slot)
+                self.get_exists_yes_no("slot-successful:"+slot)
+                self.get_exists_integer("slot-retry-count:"+slot)
+        else:
+            print "This does not appear to be an A/B device."
+
+    def test_setactive(self):
+        """Tests that A/B devices can switch to each slot, and the change persists over a reboot."""
+
+        slotcount = 0
+        try:
+            val = self.fastboot.getvar("slot-count")
+            if val != None:
+                slotcount = int(val)
+        except ValueError:
+            self.fail("slot-count (%s) is not an integer" % val)
+        except subprocess.CalledProcessError:
+            print "Does not appear to be an A/B device."
+        maxtries = 0
+        if slotcount > 1:
+            slots = [chr(slotnum+ord('a')) for slotnum in range(slotcount)]
+            for slot in slots:
+                self.fastboot.set_active(slot)
+                self.assertEqual(slot, self.fastboot.getvar("current-slot"))
+                self.assertEqual("no", self.fastboot.getvar("slot-unbootable:"+slot))
+                self.assertEqual("no", self.fastboot.getvar("slot-successful:"+slot))
+                retry = self.get_exists_integer("slot-retry-count:"+slot)
+                if maxtries == 0:
+                   maxtries = retry
+                else:
+                   self.assertEqual(maxtries, retry)
+                self.fastboot.reboot(True)
+                self.assertEqual(slot, self.fastboot.getvar("current-slot"))
+                self.assertEqual("no", self.fastboot.getvar("slot-unbootable:"+slot))
+                self.assertEqual("no", self.fastboot.getvar("slot-successful:"+slot))
+                retry = self.get_exists_integer("slot-retry-count:"+slot)
+                if maxtries == 0:
+                   maxtries = retry
+                else:
+                   self.assertEqual(maxtries, retry)
+        else:
+            print "Does not appear to be an A/B device."
+
+if __name__ == '__main__':
+    unittest.main(verbosity=3)
diff --git a/tests/ext4/rand_emmc_perf.c b/tests/ext4/rand_emmc_perf.c
index ebd10c8..fed7a54 100644
--- a/tests/ext4/rand_emmc_perf.c
+++ b/tests/ext4/rand_emmc_perf.c
@@ -51,8 +51,8 @@
 {
     int i;
     struct timeval t;
-    struct timeval sum = { 0 };
-    struct timeval max = { 0 };
+    struct timeval sum = { 0, 0 };
+    struct timeval max = { 0, 0 };
     long long total_usecs;
     long long avg_usecs;
     long long max_usecs;
@@ -217,6 +217,7 @@
             break;
 
           case 'f':
+            free(full_stats_file);
             full_stats_file = strdup(optarg);
             if (full_stats_file == NULL) {
                 fprintf(stderr, "Cannot get full stats filename\n");
@@ -258,6 +259,7 @@
     } else {
         perf_test(fd, write_mode, max_blocks);
     }
+    free(full_stats_file);
 
     exit(0);
 }
diff --git a/tests/fstest/recovery_test.cpp b/tests/fstest/recovery_test.cpp
index b93de83..02c7e8f 100644
--- a/tests/fstest/recovery_test.cpp
+++ b/tests/fstest/recovery_test.cpp
@@ -48,7 +48,7 @@
 
 class DataFileVerifier {
  public:
-  DataFileVerifier(const char* file_name) {
+  explicit DataFileVerifier(const char* file_name) {
     strncpy(test_file_, file_name, FILENAME_MAX);
   }
 
diff --git a/tests/icachetest/Android.mk b/tests/icachetest/Android.mk
index f50f790..132efd3 100644
--- a/tests/icachetest/Android.mk
+++ b/tests/icachetest/Android.mk
@@ -1,5 +1,4 @@
 # Copyright 2006 The Android Open Source Project
-ifeq ($(TARGET_ARCH),arm)
 LOCAL_PATH:= $(call my-dir)
 include $(CLEAR_VARS)
 
@@ -11,5 +10,6 @@
 
 LOCAL_MODULE_TAGS := optional
 
+LOCAL_MODULE_TARGET_ARCH := arm
+
 include $(BUILD_EXECUTABLE)
-endif
diff --git a/tests/kernel.config/Android.mk b/tests/kernel.config/Android.mk
index fc90f66..c05fcc8 100644
--- a/tests/kernel.config/Android.mk
+++ b/tests/kernel.config/Android.mk
@@ -38,6 +38,22 @@
 LOCAL_SRC_FILES := $(test_src_files)
 include $(BUILD_NATIVE_TEST)
 
+ifeq ($(HOST_OS)-$(HOST_ARCH),$(filter $(HOST_OS)-$(HOST_ARCH),linux-x86 linux-x86_64))
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := $(cts_executable)_list
+LOCAL_MODULE_TAGS := optional
+LOCAL_CFLAGS := $(test_c_flags)
+LOCAL_SRC_FILES := $(cts_src_files)
+LOCAL_MULTILIB := both
+LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)
+LOCAL_MODULE_STEM_64 := $(LOCAL_MODULE)64
+LOCAL_CXX_STL := libc++
+LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
+include $(BUILD_HOST_NATIVE_TEST)
+
+endif  # ifeq ($(HOST_OS)-$(HOST_ARCH),$(filter $(HOST_OS)-$(HOST_ARCH),linux-x86 linux-x86_64))
+
 include $(CLEAR_VARS)
 LOCAL_MODULE := $(cts_executable)
 LOCAL_MODULE_TAGS := optional
@@ -52,25 +68,9 @@
 
 LOCAL_COMPATIBILITY_SUITE := cts_v2
 LOCAL_CTS_TEST_PACKAGE := android.kernel.config
+LOCAL_CTS_GTEST_LIST_EXECUTABLE := $(ALL_MODULES.$(cts_executable)_list$(HOST_2ND_ARCH_MODULE_SUFFIX).INSTALLED)
 include $(BUILD_CTS_EXECUTABLE)
 
-ifeq ($(HOST_OS)-$(HOST_ARCH),$(filter $(HOST_OS)-$(HOST_ARCH),linux-x86 linux-x86_64))
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := $(cts_executable)_list
-LOCAL_MODULE_TAGS := optional
-LOCAL_CFLAGS := $(test_c_flags)
-LOCAL_C_INCLUDES := external/gtest/include
-LOCAL_SRC_FILES := $(cts_src_files)
-LOCAL_MULTILIB := both
-LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)
-LOCAL_MODULE_STEM_64 := $(LOCAL_MODULE)64
-LOCAL_CXX_STL := libc++
-LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
-include $(BUILD_HOST_NATIVE_TEST)
-
-endif  # ifeq ($(HOST_OS)-$(HOST_ARCH),$(filter $(HOST_OS)-$(HOST_ARCH),linux-x86 linux-x86_64))
-
 include $(CLEAR_VARS)
 LOCAL_SRC_FILES := \
     scrape_mmap_addr.cpp
diff --git a/tests/lib/testUtil/testUtil.c b/tests/lib/testUtil/testUtil.c
index 983e7a4..d4dcea2 100644
--- a/tests/lib/testUtil/testUtil.c
+++ b/tests/lib/testUtil/testUtil.c
@@ -32,7 +32,7 @@
 
 #include <cutils/log.h>
 
-#define ALEN(a) (sizeof(a) / sizeof(a [0]))  // Array length
+#define ALEN(a) (sizeof(a) / sizeof((a)[0]))  // Array length
 typedef unsigned int bool_t;
 #define true (0 == 0)
 #define false (!true)
diff --git a/tests/net_test/README b/tests/net_test/README
deleted file mode 100644
index f45c3d5..0000000
--- a/tests/net_test/README
+++ /dev/null
@@ -1,77 +0,0 @@
-                                net_test v0.1
-                                =============
-
-A simple framework for blackbox testing of kernel networking code.
-
-
-Why use it?
-===========
-
-- Fast test / boot cycle.
-- Access to host filesystem and networking via L2 bridging.
-- Full Linux userland including Python, etc.
-- Kernel bugs don't crash the system.
-
-
-How to use it
-=============
-
-cd <kerneldir>
-path/to/net_test/run_net_test.sh <test>
-
-where <test> is the name of a test binary in the net_test directory. This can
-be an x86 binary, a shell script, a Python script. etc.
-
-
-How it works
-============
-
-net_test compiles the kernel to a user-mode linux binary, which runs as a
-process on the host machine. It runs the binary to start a Linux "virtual
-machine" whose root filesystem is the supplied Debian disk image. The machine
-boots, mounts the root filesystem read-only, runs the specified test from init, and then drops to a shell.
-
-
-Access to host filesystem
-=========================
-
-The VM mounts the host filesystem at /host, so the test can be modified and
-re-run without rebooting the VM.
-
-
-Access to host networking
-=========================
-
-Access to host networking is provided by tap interfaces. On the host, the
-interfaces are named <user>TAP0, <user>TAP1, etc., where <user> is the first
-10 characters of the username running net_test. (10 characters because
-IFNAMSIZ = 16). On the guest, they are named eth0, eth1, etc.
-
-net_test does not do any networking setup beyond creating the tap interfaces.
-IP connectivity can be provided on the host side by setting up a DHCP server
-and NAT, sending IPv6 router advertisements, etc. By default, the VM has IPv6
-privacy addresses disabled, so its IPv6 addresses can be predicted using a tool
-such as ipv6calc.
-
-The provided filesystem contains a DHCPv4 client and simple networking
-utilities such as ping[6], traceroute[6], and wget.
-
-The number of tap interfaces is currently hardcoded to two. To change this
-number, modify run_net_test.sh.
-
-
-Logging into the VM, installing packages, etc.
-==============================================
-
-net_test mounts the root filesystem read-only, and runs the test from init, but
-since the filesystem contains a full Linux userland, it's possible to boot into
-userland and modify the filesystem, for example to install packages using
-apt-get install. Log in as root with no password. By default, the filesystem is
-configured to perform DHCPv4 on eth0 and listen to RAs.
-
-
-Bugs
-====
-
-Since the test mounts the filesystem read-only, tests cannot modify
-/etc/resolv.conf and the system resolver is hardcoded to 8.8.8.8.
diff --git a/tests/net_test/all_tests.sh b/tests/net_test/all_tests.sh
deleted file mode 100755
index ce147d3..0000000
--- a/tests/net_test/all_tests.sh
+++ /dev/null
@@ -1,48 +0,0 @@
-#!/bin/bash
-
-# Copyright 2014 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-readonly PREFIX="#####"
-
-function maybePlural() {
-  # $1 = integer to use for plural check
-  # $2 = singular string
-  # $3 = plural string
-  if [ $1 -ne 1 ]; then
-    echo "$3"
-  else
-    echo "$2"
-  fi
-}
-
-
-readonly tests=$(find . -name '*_test.py' -type f -executable)
-readonly count=$(echo $tests | wc -w)
-echo "$PREFIX Found $count $(maybePlural $count test tests)."
-
-exit_code=0
-
-i=0
-for test in $tests; do
-  i=$((i + 1))
-  echo ""
-  echo "$PREFIX $test ($i/$count)"
-  echo ""
-  $test || exit_code=$(( exit_code + 1 ))
-  echo ""
-done
-
-echo "$PREFIX $exit_code failed $(maybePlural $exit_code test tests)."
-exit $exit_code
diff --git a/tests/net_test/anycast_test.py b/tests/net_test/anycast_test.py
deleted file mode 100755
index 82130db..0000000
--- a/tests/net_test/anycast_test.py
+++ /dev/null
@@ -1,113 +0,0 @@
-#!/usr/bin/python
-#
-# Copyright 2014 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import os
-from socket import *  # pylint: disable=wildcard-import
-import threading
-import time
-import unittest
-
-import cstruct
-import multinetwork_base
-import net_test
-
-IPV6_JOIN_ANYCAST = 27
-IPV6_LEAVE_ANYCAST = 28
-
-# pylint: disable=invalid-name
-IPv6Mreq = cstruct.Struct("IPv6Mreq", "=16si", "multiaddr ifindex")
-
-
-_CLOSE_HUNG = False
-
-
-def CauseOops():
-  open("/proc/sysrq-trigger", "w").write("c")
-
-
-class CloseFileDescriptorThread(threading.Thread):
-
-  def __init__(self, fd):
-    super(CloseFileDescriptorThread, self).__init__()
-    self.daemon = True
-    self._fd = fd
-    self.finished = False
-
-  def run(self):
-    global _CLOSE_HUNG
-    _CLOSE_HUNG = True
-    self._fd.close()
-    _CLOSE_HUNG = False
-    self.finished = True
-
-
-class AnycastTest(multinetwork_base.MultiNetworkBaseTest):
-  """Tests for IPv6 anycast addresses.
-
-  Relevant kernel commits:
-    upstream net-next:
-      381f4dc ipv6: clean up anycast when an interface is destroyed
-
-    android-3.10:
-      86a47ad ipv6: clean up anycast when an interface is destroyed
-  """
-  _TEST_NETID = 123
-
-  def AnycastSetsockopt(self, s, is_add, netid, addr):
-    ifindex = self.ifindices[netid]
-    self.assertTrue(ifindex)
-    ipv6mreq = IPv6Mreq((addr, ifindex))
-    option = IPV6_JOIN_ANYCAST if is_add else IPV6_LEAVE_ANYCAST
-    s.setsockopt(IPPROTO_IPV6, option, ipv6mreq.Pack())
-
-  def testAnycastNetdeviceUnregister(self):
-    netid = self._TEST_NETID
-    self.assertNotIn(netid, self.tuns)
-    self.tuns[netid] = self.CreateTunInterface(netid)
-    self.SendRA(netid)
-    iface = self.GetInterfaceName(netid)
-    self.ifindices[netid] = net_test.GetInterfaceIndex(iface)
-
-    s = socket(AF_INET6, SOCK_DGRAM, 0)
-    addr = self.MyAddress(6, netid)
-    self.assertIsNotNone(addr)
-
-    addr = inet_pton(AF_INET6, addr)
-    addr = addr[:8] + os.urandom(8)
-    self.AnycastSetsockopt(s, True, netid, addr)
-
-    # Close the tun fd in the background.
-    # This will hang if the kernel has the bug.
-    thread = CloseFileDescriptorThread(self.tuns[netid])
-    thread.start()
-    time.sleep(0.1)
-
-    # Make teardown work.
-    del self.tuns[netid]
-    # Check that the interface is gone.
-    try:
-      self.assertIsNone(self.MyAddress(6, netid))
-    finally:
-      # This doesn't seem to help, but still.
-      self.AnycastSetsockopt(s, False, netid, addr)
-    self.assertTrue(thread.finished)
-
-
-if __name__ == "__main__":
-  unittest.main(exit=False)
-  if _CLOSE_HUNG:
-    time.sleep(3)
-    CauseOops()
diff --git a/tests/net_test/csocket.py b/tests/net_test/csocket.py
deleted file mode 100644
index 5dc495c..0000000
--- a/tests/net_test/csocket.py
+++ /dev/null
@@ -1,182 +0,0 @@
-# Copyright 2014 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-"""Python wrapper for C socket calls and data structures."""
-
-import ctypes
-import ctypes.util
-import os
-import socket
-import struct
-
-import cstruct
-
-
-# Data structures.
-# These aren't constants, they're classes. So, pylint: disable=invalid-name
-CMsgHdr = cstruct.Struct("cmsghdr", "@Lii", "len level type")
-Iovec = cstruct.Struct("iovec", "@LL", "base len")
-MsgHdr = cstruct.Struct("msghdr", "@LLLLLLi",
-                        "name namelen iov iovlen control msg_controllen flags")
-SockaddrIn = cstruct.Struct("sockaddr_in", "=HH4sxxxxxxxx", "family port addr")
-SockaddrIn6 = cstruct.Struct("sockaddr_in6", "=HHI16sI",
-                             "family port flowinfo addr scope_id")
-
-# Constants.
-CMSG_ALIGNTO = struct.calcsize("@L")  # The kernel defines this as sizeof(long).
-MSG_CONFIRM = 0X800
-
-# Find the C library.
-libc = ctypes.CDLL(ctypes.util.find_library("c"), use_errno=True)
-
-
-def PaddedLength(length):
-  return CMSG_ALIGNTO * ((length / CMSG_ALIGNTO) + (length % CMSG_ALIGNTO != 0))
-
-
-def MaybeRaiseSocketError(ret):
-  if ret < 0:
-    errno = ctypes.get_errno()
-    raise socket.error(errno, os.strerror(errno))
-
-
-def Sockaddr(addr):
-  if ":" in addr[0]:
-    family = socket.AF_INET6
-    if len(addr) == 4:
-      addr, port, flowinfo, scope_id = addr
-    else:
-      (addr, port), flowinfo, scope_id = addr, 0, 0
-    addr = socket.inet_pton(family, addr)
-    return SockaddrIn6((family, socket.ntohs(port), socket.ntohl(flowinfo),
-                        addr, scope_id))
-  else:
-    family = socket.AF_INET
-    addr, port = addr
-    addr = socket.inet_pton(family, addr)
-    return SockaddrIn((family, socket.ntohs(port), addr))
-
-
-def _MakeMsgControl(optlist):
-  """Creates a msg_control blob from a list of cmsg attributes.
-
-  Takes a list of cmsg attributes. Each attribute is a tuple of:
-   - level: An integer, e.g., SOL_IPV6.
-   - type: An integer, the option identifier, e.g., IPV6_HOPLIMIT.
-   - data: The option data. This is either a string or an integer. If it's an
-     integer it will be written as an unsigned integer in host byte order. If
-     it's a string, it's used as is.
-
-  Data is padded to an integer multiple of CMSG_ALIGNTO.
-
-  Args:
-    optlist: A list of tuples describing cmsg options.
-
-  Returns:
-    A string, a binary blob usable as the control data for a sendmsg call.
-
-  Raises:
-    TypeError: Option data is neither an integer nor a string.
-  """
-  msg_control = ""
-
-  for i, opt in enumerate(optlist):
-    msg_level, msg_type, data = opt
-    if isinstance(data, int):
-      data = struct.pack("=I", data)
-    elif not isinstance(data, str):
-      raise TypeError("unknown data type for opt %i: %s" % (i, type(data)))
-
-    datalen = len(data)
-    msg_len = len(CMsgHdr) + datalen
-    padding = "\x00" * (PaddedLength(datalen) - datalen)
-    msg_control += CMsgHdr((msg_len, msg_level, msg_type)).Pack()
-    msg_control += data + padding
-
-  return msg_control
-
-
-def Bind(s, to):
-  """Python wrapper for connect."""
-  ret = libc.bind(s.fileno(), to.CPointer(), len(to))
-  MaybeRaiseSocketError(ret)
-  return ret
-
-
-def Connect(s, to):
-  """Python wrapper for connect."""
-  ret = libc.connect(s.fileno(), to.CPointer(), len(to))
-  MaybeRaiseSocketError(ret)
-  return ret
-
-
-def Sendmsg(s, to, data, control, flags):
-  """Python wrapper for sendmsg.
-
-  Args:
-    s: A Python socket object. Becomes sockfd.
-    to: An address tuple, or a SockaddrIn[6] struct. Becomes msg->msg_name.
-    data: A string, the data to write. Goes into msg->msg_iov.
-    control: A list of cmsg options. Becomes msg->msg_control.
-    flags: An integer. Becomes msg->msg_flags.
-
-  Returns:
-    If sendmsg succeeds, returns the number of bytes written as an integer.
-
-  Raises:
-    socket.error: If sendmsg fails.
-  """
-  # Create ctypes buffers and pointers from our structures. We need to hang on
-  # to the underlying Python objects, because we don't want them to be garbage
-  # collected and freed while we have C pointers to them.
-
-  # Convert the destination address into a struct sockaddr.
-  if to:
-    if isinstance(to, tuple):
-      to = Sockaddr(to)
-    msg_name = to.CPointer()
-    msg_namelen = len(to)
-  else:
-    msg_name = 0
-    msg_namelen = 0
-
-  # Convert the data to a data buffer and a struct iovec pointing at it.
-  if data:
-    databuf = ctypes.create_string_buffer(data)
-    iov = Iovec((ctypes.addressof(databuf), len(data)))
-    msg_iov = iov.CPointer()
-    msg_iovlen = 1
-  else:
-    msg_iov = 0
-    msg_iovlen = 0
-
-  # Marshal the cmsg options.
-  if control:
-    control = _MakeMsgControl(control)
-    controlbuf = ctypes.create_string_buffer(control)
-    msg_control = ctypes.addressof(controlbuf)
-    msg_controllen = len(control)
-  else:
-    msg_control = 0
-    msg_controllen = 0
-
-  # Assemble the struct msghdr.
-  msghdr = MsgHdr((msg_name, msg_namelen, msg_iov, msg_iovlen,
-                   msg_control, msg_controllen, flags)).Pack()
-
-  # Call sendmsg.
-  ret = libc.sendmsg(s.fileno(), msghdr, 0)
-  MaybeRaiseSocketError(ret)
-
-  return ret
diff --git a/tests/net_test/cstruct.py b/tests/net_test/cstruct.py
deleted file mode 100644
index 91cd72e..0000000
--- a/tests/net_test/cstruct.py
+++ /dev/null
@@ -1,194 +0,0 @@
-# Copyright 2014 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-"""A simple module for declaring C-like structures.
-
-Example usage:
-
->>> # Declare a struct type by specifying name, field formats and field names.
-... # Field formats are the same as those used in the struct module.
-... import cstruct
->>> NLMsgHdr = cstruct.Struct("NLMsgHdr", "=LHHLL", "length type flags seq pid")
->>>
->>>
->>> # Create instances from tuples or raw bytes. Data past the end is ignored.
-... n1 = NLMsgHdr((44, 32, 0x2, 0, 491))
->>> print n1
-NLMsgHdr(length=44, type=32, flags=2, seq=0, pid=491)
->>>
->>> n2 = NLMsgHdr("\x2c\x00\x00\x00\x21\x00\x02\x00"
-...               "\x00\x00\x00\x00\xfe\x01\x00\x00" + "junk at end")
->>> print n2
-NLMsgHdr(length=44, type=33, flags=2, seq=0, pid=510)
->>>
->>> # Serialize to raw bytes.
-... print n1.Pack().encode("hex")
-2c0000002000020000000000eb010000
->>>
->>> # Parse the beginning of a byte stream as a struct, and return the struct
-... # and the remainder of the stream for further reading.
-... data = ("\x2c\x00\x00\x00\x21\x00\x02\x00"
-...         "\x00\x00\x00\x00\xfe\x01\x00\x00"
-...         "more data")
->>> cstruct.Read(data, NLMsgHdr)
-(NLMsgHdr(length=44, type=33, flags=2, seq=0, pid=510), 'more data')
->>>
-"""
-
-import ctypes
-import string
-import struct
-
-
-def CalcNumElements(fmt):
-  size = struct.calcsize(fmt)
-  elements = struct.unpack(fmt, "\x00" * size)
-  return len(elements)
-
-
-def Struct(name, fmt, fieldnames, substructs={}):
-  """Function that returns struct classes."""
-
-  class Meta(type):
-
-    def __len__(cls):
-      return cls._length
-
-    def __init__(cls, unused_name, unused_bases, namespace):
-      # Make the class object have the name that's passed in.
-      type.__init__(cls, namespace["_name"], unused_bases, namespace)
-
-  class CStruct(object):
-    """Class representing a C-like structure."""
-
-    __metaclass__ = Meta
-
-    # Name of the struct.
-    _name = name
-    # List of field names.
-    _fieldnames = fieldnames
-    # Dict mapping field indices to nested struct classes.
-    _nested = {}
-
-    if isinstance(_fieldnames, str):
-      _fieldnames = _fieldnames.split(" ")
-
-    # Parse fmt into _format, converting any S format characters to "XXs",
-    # where XX is the length of the struct type's packed representation.
-    _format = ""
-    laststructindex = 0
-    for i in xrange(len(fmt)):
-      if fmt[i] == "S":
-        # Nested struct. Record the index in our struct it should go into.
-        index = CalcNumElements(fmt[:i])
-        _nested[index] = substructs[laststructindex]
-        laststructindex += 1
-        _format += "%ds" % len(_nested[index])
-      else:
-         # Standard struct format character.
-        _format += fmt[i]
-
-    _length = struct.calcsize(_format)
-
-    def _SetValues(self, values):
-      super(CStruct, self).__setattr__("_values", list(values))
-
-    def _Parse(self, data):
-      data = data[:self._length]
-      values = list(struct.unpack(self._format, data))
-      for index, value in enumerate(values):
-        if isinstance(value, str) and index in self._nested:
-          values[index] = self._nested[index](value)
-      self._SetValues(values)
-
-    def __init__(self, values):
-      # Initializing from a string.
-      if isinstance(values, str):
-        if len(values) < self._length:
-          raise TypeError("%s requires string of length %d, got %d" %
-                          (self._name, self._length, len(values)))
-        self._Parse(values)
-      else:
-        # Initializing from a tuple.
-        if len(values) != len(self._fieldnames):
-          raise TypeError("%s has exactly %d fieldnames (%d given)" %
-                          (self._name, len(self._fieldnames), len(values)))
-        self._SetValues(values)
-
-    def _FieldIndex(self, attr):
-      try:
-        return self._fieldnames.index(attr)
-      except ValueError:
-        raise AttributeError("'%s' has no attribute '%s'" %
-                             (self._name, attr))
-
-    def __getattr__(self, name):
-      return self._values[self._FieldIndex(name)]
-
-    def __setattr__(self, name, value):
-      self._values[self._FieldIndex(name)] = value
-
-    @classmethod
-    def __len__(cls):
-      return cls._length
-
-    def __ne__(self, other):
-      return not self.__eq__(other)
-
-    def __eq__(self, other):
-      return (isinstance(other, self.__class__) and
-              self._name == other._name and
-              self._fieldnames == other._fieldnames and
-              self._values == other._values)
-
-    @staticmethod
-    def _MaybePackStruct(value):
-      if hasattr(value, "__metaclass__"):# and value.__metaclass__ == Meta:
-        return value.Pack()
-      else:
-        return value
-
-    def Pack(self):
-      values = [self._MaybePackStruct(v) for v in self._values]
-      return struct.pack(self._format, *values)
-
-    def __str__(self):
-      def FieldDesc(index, name, value):
-        if isinstance(value, str) and any(
-            c not in string.printable for c in value):
-          value = value.encode("hex")
-        return "%s=%s" % (name, value)
-
-      descriptions = [
-          FieldDesc(i, n, v) for i, (n, v) in
-          enumerate(zip(self._fieldnames, self._values))]
-
-      return "%s(%s)" % (self._name, ", ".join(descriptions))
-
-    def __repr__(self):
-      return str(self)
-
-    def CPointer(self):
-      """Returns a C pointer to the serialized structure."""
-      buf = ctypes.create_string_buffer(self.Pack())
-      # Store the C buffer in the object so it doesn't get garbage collected.
-      super(CStruct, self).__setattr__("_buffer", buf)
-      return ctypes.addressof(self._buffer)
-
-  return CStruct
-
-
-def Read(data, struct_type):
-  length = len(struct_type)
-  return struct_type(data), data[length:]
diff --git a/tests/net_test/cstruct_test.py b/tests/net_test/cstruct_test.py
deleted file mode 100755
index 2d5a408..0000000
--- a/tests/net_test/cstruct_test.py
+++ /dev/null
@@ -1,60 +0,0 @@
-#!/usr/bin/python
-#
-# Copyright 2016 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import unittest
-
-import cstruct
-
-
-# These aren't constants, they're classes. So, pylint: disable=invalid-name
-TestStructA = cstruct.Struct("TestStructA", "=BI", "byte1 int2")
-TestStructB = cstruct.Struct("TestStructB", "=BI", "byte1 int2")
-
-
-class CstructTest(unittest.TestCase):
-
-  def CheckEquals(self, a, b):
-    self.assertEquals(a, b)
-    self.assertEquals(b, a)
-    assert a == b
-    assert b == a
-    assert not (a != b)  # pylint: disable=g-comparison-negation,superfluous-parens
-    assert not (b != a)  # pylint: disable=g-comparison-negation,superfluous-parens
-
-  def CheckNotEquals(self, a, b):
-    self.assertNotEquals(a, b)
-    self.assertNotEquals(b, a)
-    assert a != b
-    assert b != a
-    assert not (a == b)  # pylint: disable=g-comparison-negation,superfluous-parens
-    assert not (b == a)  # pylint: disable=g-comparison-negation,superfluous-parens
-
-  def testEqAndNe(self):
-    a1 = TestStructA((1, 2))
-    a2 = TestStructA((2, 3))
-    a3 = TestStructA((1, 2))
-    b = TestStructB((1, 2))
-    self.CheckNotEquals(a1, b)
-    self.CheckNotEquals(a2, b)
-    self.CheckNotEquals(a1, a2)
-    self.CheckNotEquals(a2, a3)
-    for i in [a1, a2, a3, b]:
-      self.CheckEquals(i, i)
-    self.CheckEquals(a1, a3)
-
-
-if __name__ == "__main__":
-  unittest.main()
diff --git a/tests/net_test/forwarding_test.py b/tests/net_test/forwarding_test.py
deleted file mode 100755
index 185e477..0000000
--- a/tests/net_test/forwarding_test.py
+++ /dev/null
@@ -1,109 +0,0 @@
-#!/usr/bin/python
-#
-# Copyright 2015 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import itertools
-import random
-import unittest
-
-from socket import *
-
-import iproute
-import multinetwork_base
-import net_test
-import packets
-
-
-class ForwardingTest(multinetwork_base.MultiNetworkBaseTest):
-
-  TCP_TIME_WAIT = 6
-
-  def ForwardBetweenInterfaces(self, enabled, iface1, iface2):
-    for iif, oif in itertools.permutations([iface1, iface2]):
-      self.iproute.IifRule(6, enabled, self.GetInterfaceName(iif),
-                           self._TableForNetid(oif), self.PRIORITY_IIF)
-
-  def setUp(self):
-    self.SetSysctl("/proc/sys/net/ipv6/conf/all/forwarding", 1)
-
-  def tearDown(self):
-    self.SetSysctl("/proc/sys/net/ipv6/conf/all/forwarding", 0)
-
-  def CheckForwardingCrash(self, netid, iface1, iface2):
-    listenport = packets.RandomPort()
-    listensocket = net_test.IPv6TCPSocket()
-    listensocket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
-    listensocket.bind(("::", listenport))
-    listensocket.listen(100)
-    self.SetSocketMark(listensocket, netid)
-
-    version = 6
-    remoteaddr = self.GetRemoteAddress(version)
-    myaddr = self.MyAddress(version, netid)
-
-    desc, syn = packets.SYN(listenport, version, remoteaddr, myaddr)
-    synack_desc, synack = packets.SYNACK(version, myaddr, remoteaddr, syn)
-    msg = "Sent %s, expected %s" % (desc, synack_desc)
-    reply = self._ReceiveAndExpectResponse(netid, syn, synack, msg)
-
-    establishing_ack = packets.ACK(version, remoteaddr, myaddr, reply)[1]
-    self.ReceivePacketOn(netid, establishing_ack)
-    accepted, peer = listensocket.accept()
-    remoteport = accepted.getpeername()[1]
-
-    accepted.close()
-    desc, fin = packets.FIN(version, myaddr, remoteaddr, establishing_ack)
-    self.ExpectPacketOn(netid, msg + ": expecting %s after close" % desc, fin)
-
-    desc, finack = packets.FIN(version, remoteaddr, myaddr, fin)
-    self.ReceivePacketOn(netid, finack)
-
-    # Check our socket is now in TIME_WAIT.
-    sockets = self.ReadProcNetSocket("tcp6")
-    mysrc = "%s:%04X" % (net_test.FormatSockStatAddress(myaddr), listenport)
-    mydst = "%s:%04X" % (net_test.FormatSockStatAddress(remoteaddr), remoteport)
-    state = None
-    sockets = [s for s in sockets if s[0] == mysrc and s[1] == mydst]
-    self.assertEquals(1, len(sockets))
-    self.assertEquals("%02X" % self.TCP_TIME_WAIT, sockets[0][2])
-
-    # Remove our IP address.
-    try:
-      self.iproute.DelAddress(myaddr, 64, self.ifindices[netid])
-
-      self.ReceivePacketOn(iface1, finack)
-      self.ReceivePacketOn(iface1, establishing_ack)
-      self.ReceivePacketOn(iface1, establishing_ack)
-      # No crashes? Good.
-
-    finally:
-      # Put back our IP address.
-      self.SendRA(netid)
-      listensocket.close()
-
-  def testCrash(self):
-    # Run the test a few times as it doesn't crash/hang the first time.
-    for netids in itertools.permutations(self.tuns):
-      # Pick an interface to send traffic on and two to forward traffic between.
-      netid, iface1, iface2 = random.sample(netids, 3)
-      self.ForwardBetweenInterfaces(True, iface1, iface2)
-      try:
-        self.CheckForwardingCrash(netid, iface1, iface2)
-      finally:
-        self.ForwardBetweenInterfaces(False, iface1, iface2)
-
-
-if __name__ == "__main__":
-  unittest.main()
diff --git a/tests/net_test/iproute.py b/tests/net_test/iproute.py
deleted file mode 100644
index 2c63993..0000000
--- a/tests/net_test/iproute.py
+++ /dev/null
@@ -1,541 +0,0 @@
-#!/usr/bin/python
-#
-# Copyright 2014 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-"""Partial Python implementation of iproute functionality."""
-
-# pylint: disable=g-bad-todo
-
-import errno
-import os
-import socket
-import struct
-import sys
-
-import cstruct
-import netlink
-
-
-### Base netlink constants. See include/uapi/linux/netlink.h.
-NETLINK_ROUTE = 0
-
-# Request constants.
-NLM_F_REQUEST = 1
-NLM_F_ACK = 4
-NLM_F_REPLACE = 0x100
-NLM_F_EXCL = 0x200
-NLM_F_CREATE = 0x400
-NLM_F_DUMP = 0x300
-
-# Message types.
-NLMSG_ERROR = 2
-NLMSG_DONE = 3
-
-# Data structure formats.
-# These aren't constants, they're classes. So, pylint: disable=invalid-name
-NLMsgHdr = cstruct.Struct("NLMsgHdr", "=LHHLL", "length type flags seq pid")
-NLMsgErr = cstruct.Struct("NLMsgErr", "=i", "error")
-NLAttr = cstruct.Struct("NLAttr", "=HH", "nla_len nla_type")
-
-# Alignment / padding.
-NLA_ALIGNTO = 4
-
-
-### rtnetlink constants. See include/uapi/linux/rtnetlink.h.
-# Message types.
-RTM_NEWLINK = 16
-RTM_DELLINK = 17
-RTM_GETLINK = 18
-RTM_NEWADDR = 20
-RTM_DELADDR = 21
-RTM_GETADDR = 22
-RTM_NEWROUTE = 24
-RTM_DELROUTE = 25
-RTM_GETROUTE = 26
-RTM_NEWNEIGH = 28
-RTM_DELNEIGH = 29
-RTM_GETNEIGH = 30
-RTM_NEWRULE = 32
-RTM_DELRULE = 33
-RTM_GETRULE = 34
-
-# Routing message type values (rtm_type).
-RTN_UNSPEC = 0
-RTN_UNICAST = 1
-RTN_UNREACHABLE = 7
-
-# Routing protocol values (rtm_protocol).
-RTPROT_UNSPEC = 0
-RTPROT_STATIC = 4
-
-# Route scope values (rtm_scope).
-RT_SCOPE_UNIVERSE = 0
-RT_SCOPE_LINK = 253
-
-# Named routing tables.
-RT_TABLE_UNSPEC = 0
-
-# Routing attributes.
-RTA_DST = 1
-RTA_SRC = 2
-RTA_OIF = 4
-RTA_GATEWAY = 5
-RTA_PRIORITY = 6
-RTA_PREFSRC = 7
-RTA_METRICS = 8
-RTA_CACHEINFO = 12
-RTA_TABLE = 15
-RTA_MARK = 16
-RTA_UID = 18
-
-# Route metric attributes.
-RTAX_MTU = 2
-RTAX_HOPLIMIT = 10
-
-# Data structure formats.
-IfinfoMsg = cstruct.Struct(
-    "IfinfoMsg", "=BBHiII", "family pad type index flags change")
-RTMsg = cstruct.Struct(
-    "RTMsg", "=BBBBBBBBI",
-    "family dst_len src_len tos table protocol scope type flags")
-RTACacheinfo = cstruct.Struct(
-    "RTACacheinfo", "=IIiiI", "clntref lastuse expires error used")
-
-
-### Interface address constants. See include/uapi/linux/if_addr.h.
-# Interface address attributes.
-IFA_ADDRESS = 1
-IFA_LOCAL = 2
-IFA_CACHEINFO = 6
-
-# Address flags.
-IFA_F_SECONDARY = 0x01
-IFA_F_TEMPORARY = IFA_F_SECONDARY
-IFA_F_NODAD = 0x02
-IFA_F_OPTIMISTIC = 0x04
-IFA_F_DADFAILED = 0x08
-IFA_F_HOMEADDRESS = 0x10
-IFA_F_DEPRECATED = 0x20
-IFA_F_TENTATIVE = 0x40
-IFA_F_PERMANENT = 0x80
-
-# Data structure formats.
-IfAddrMsg = cstruct.Struct(
-    "IfAddrMsg", "=BBBBI",
-    "family prefixlen flags scope index")
-IFACacheinfo = cstruct.Struct(
-    "IFACacheinfo", "=IIII", "prefered valid cstamp tstamp")
-NDACacheinfo = cstruct.Struct(
-    "NDACacheinfo", "=IIII", "confirmed used updated refcnt")
-
-
-### Neighbour table entry constants. See include/uapi/linux/neighbour.h.
-# Neighbour cache entry attributes.
-NDA_DST = 1
-NDA_LLADDR = 2
-NDA_CACHEINFO = 3
-NDA_PROBES = 4
-
-# Neighbour cache entry states.
-NUD_PERMANENT = 0x80
-
-# Data structure formats.
-NdMsg = cstruct.Struct(
-    "NdMsg", "=BxxxiHBB",
-    "family ifindex state flags type")
-
-
-### FIB rule constants. See include/uapi/linux/fib_rules.h.
-FRA_IIFNAME = 3
-FRA_PRIORITY = 6
-FRA_FWMARK = 10
-FRA_SUPPRESS_PREFIXLEN = 14
-FRA_TABLE = 15
-FRA_FWMASK = 16
-FRA_OIFNAME = 17
-FRA_UID_START = 18
-FRA_UID_END = 19
-
-
-# Link constants. See include/uapi/linux/if_link.h.
-IFLA_ADDRESS = 1
-IFLA_BROADCAST = 2
-IFLA_IFNAME = 3
-IFLA_MTU = 4
-IFLA_QDISC = 6
-IFLA_STATS = 7
-IFLA_TXQLEN = 13
-IFLA_MAP = 14
-IFLA_OPERSTATE = 16
-IFLA_LINKMODE = 17
-IFLA_STATS64 = 23
-IFLA_AF_SPEC = 26
-IFLA_GROUP = 27
-IFLA_EXT_MASK = 29
-IFLA_PROMISCUITY = 30
-IFLA_NUM_TX_QUEUES = 31
-IFLA_NUM_RX_QUEUES = 32
-IFLA_CARRIER = 33
-
-
-def CommandVerb(command):
-  return ["NEW", "DEL", "GET", "SET"][command % 4]
-
-
-def CommandSubject(command):
-  return ["LINK", "ADDR", "ROUTE", "NEIGH", "RULE"][(command - 16) / 4]
-
-
-def CommandName(command):
-  try:
-    return "RTM_%s%s" % (CommandVerb(command), CommandSubject(command))
-  except IndexError:
-    return "RTM_%d" % command
-
-
-class IPRoute(netlink.NetlinkSocket):
-  """Provides a tiny subset of iproute functionality."""
-
-  FAMILY = NETLINK_ROUTE
-
-  def _NlAttrIPAddress(self, nla_type, family, address):
-    return self._NlAttr(nla_type, socket.inet_pton(family, address))
-
-  def _NlAttrInterfaceName(self, nla_type, interface):
-    return self._NlAttr(nla_type, interface + "\x00")
-
-  def _GetConstantName(self, value, prefix):
-    return super(IPRoute, self)._GetConstantName(__name__, value, prefix)
-
-  def _Decode(self, command, msg, nla_type, nla_data):
-    """Decodes netlink attributes to Python types.
-
-    Values for which the code knows the type (e.g., the fwmark ID in a
-    RTM_NEWRULE command) are decoded to Python integers, strings, etc. Values
-    of unknown type are returned as raw byte strings.
-
-    Args:
-      command: An integer.
-        - If positive, the number of the rtnetlink command being carried out.
-          This is used to interpret the attributes. For example, for an
-          RTM_NEWROUTE command, attribute type 3 is the incoming interface and
-          is an integer, but for a RTM_NEWRULE command, attribute type 3 is the
-          incoming interface name and is a string.
-        - If negative, one of the following (negative) values:
-          - RTA_METRICS: Interpret as nested route metrics.
-      family: The address family. Used to convert IP addresses into strings.
-      nla_type: An integer, then netlink attribute type.
-      nla_data: A byte string, the netlink attribute data.
-
-    Returns:
-      A tuple (name, data):
-       - name is a string (e.g., "FRA_PRIORITY") if we understood the attribute,
-         or an integer if we didn't.
-       - data can be an integer, a string, a nested dict of attributes as
-         returned by _ParseAttributes (e.g., for RTA_METRICS), a cstruct.Struct
-         (e.g., RTACacheinfo), etc. If we didn't understand the attribute, it
-         will be the raw byte string.
-    """
-    if command == -RTA_METRICS:
-      name = self._GetConstantName(nla_type, "RTAX_")
-    elif CommandSubject(command) == "ADDR":
-      name = self._GetConstantName(nla_type, "IFA_")
-    elif CommandSubject(command) == "LINK":
-      name = self._GetConstantName(nla_type, "IFLA_")
-    elif CommandSubject(command) == "RULE":
-      name = self._GetConstantName(nla_type, "FRA_")
-    elif CommandSubject(command) == "ROUTE":
-      name = self._GetConstantName(nla_type, "RTA_")
-    elif CommandSubject(command) == "NEIGH":
-      name = self._GetConstantName(nla_type, "NDA_")
-    else:
-      # Don't know what this is. Leave it as an integer.
-      name = nla_type
-
-    if name in ["FRA_PRIORITY", "FRA_FWMARK", "FRA_TABLE", "FRA_FWMASK",
-                "FRA_UID_START", "FRA_UID_END",
-                "RTA_OIF", "RTA_PRIORITY", "RTA_TABLE", "RTA_MARK",
-                "IFLA_MTU", "IFLA_TXQLEN", "IFLA_GROUP", "IFLA_EXT_MASK",
-                "IFLA_PROMISCUITY", "IFLA_NUM_RX_QUEUES",
-                "IFLA_NUM_TX_QUEUES", "NDA_PROBES", "RTAX_MTU",
-                "RTAX_HOPLIMIT"]:
-      data = struct.unpack("=I", nla_data)[0]
-    elif name == "FRA_SUPPRESS_PREFIXLEN":
-      data = struct.unpack("=i", nla_data)[0]
-    elif name in ["IFLA_LINKMODE", "IFLA_OPERSTATE", "IFLA_CARRIER"]:
-      data = ord(nla_data)
-    elif name in ["IFA_ADDRESS", "IFA_LOCAL", "RTA_DST", "RTA_SRC",
-                  "RTA_GATEWAY", "RTA_PREFSRC", "RTA_UID",
-                  "NDA_DST"]:
-      data = socket.inet_ntop(msg.family, nla_data)
-    elif name in ["FRA_IIFNAME", "FRA_OIFNAME", "IFLA_IFNAME", "IFLA_QDISC"]:
-      data = nla_data.strip("\x00")
-    elif name == "RTA_METRICS":
-      data = self._ParseAttributes(-RTA_METRICS, msg.family, None, nla_data)
-    elif name == "RTA_CACHEINFO":
-      data = RTACacheinfo(nla_data)
-    elif name == "IFA_CACHEINFO":
-      data = IFACacheinfo(nla_data)
-    elif name == "NDA_CACHEINFO":
-      data = NDACacheinfo(nla_data)
-    elif name in ["NDA_LLADDR", "IFLA_ADDRESS"]:
-      data = ":".join(x.encode("hex") for x in nla_data)
-    else:
-      data = nla_data
-
-    return name, data
-
-  def __init__(self):
-    super(IPRoute, self).__init__()
-
-  def _AddressFamily(self, version):
-    return {4: socket.AF_INET, 6: socket.AF_INET6}[version]
-
-  def _SendNlRequest(self, command, data, flags=0):
-    """Sends a netlink request and expects an ack."""
-
-    flags |= NLM_F_REQUEST
-    if CommandVerb(command) != "GET":
-      flags |= NLM_F_ACK
-    if CommandVerb(command) == "NEW":
-      if not flags & NLM_F_REPLACE:
-        flags |= (NLM_F_EXCL | NLM_F_CREATE)
-
-    super(IPRoute, self)._SendNlRequest(command, data, flags)
-
-  def _Rule(self, version, is_add, rule_type, table, match_nlattr, priority):
-    """Python equivalent of "ip rule <add|del> <match_cond> lookup <table>".
-
-    Args:
-      version: An integer, 4 or 6.
-      is_add: True to add a rule, False to delete it.
-      rule_type: Type of rule, e.g., RTN_UNICAST or RTN_UNREACHABLE.
-      table: If nonzero, rule looks up this table.
-      match_nlattr: A blob of struct nlattrs that express the match condition.
-        If None, match everything.
-      priority: An integer, the priority.
-
-    Raises:
-      IOError: If the netlink request returns an error.
-      ValueError: If the kernel's response could not be parsed.
-    """
-    # Create a struct rtmsg specifying the table and the given match attributes.
-    family = self._AddressFamily(version)
-    rtmsg = RTMsg((family, 0, 0, 0, RT_TABLE_UNSPEC,
-                   RTPROT_STATIC, RT_SCOPE_UNIVERSE, rule_type, 0)).Pack()
-    rtmsg += self._NlAttrU32(FRA_PRIORITY, priority)
-    if match_nlattr:
-      rtmsg += match_nlattr
-    if table:
-      rtmsg += self._NlAttrU32(FRA_TABLE, table)
-
-    # Create a netlink request containing the rtmsg.
-    command = RTM_NEWRULE if is_add else RTM_DELRULE
-    self._SendNlRequest(command, rtmsg)
-
-  def DeleteRulesAtPriority(self, version, priority):
-    family = self._AddressFamily(version)
-    rtmsg = RTMsg((family, 0, 0, 0, RT_TABLE_UNSPEC,
-                   RTPROT_STATIC, RT_SCOPE_UNIVERSE, RTN_UNICAST, 0)).Pack()
-    rtmsg += self._NlAttrU32(FRA_PRIORITY, priority)
-    while True:
-      try:
-        self._SendNlRequest(RTM_DELRULE, rtmsg)
-      except IOError, e:
-        if e.errno == -errno.ENOENT:
-          break
-        else:
-          raise
-
-  def FwmarkRule(self, version, is_add, fwmark, table, priority):
-    nlattr = self._NlAttrU32(FRA_FWMARK, fwmark)
-    return self._Rule(version, is_add, RTN_UNICAST, table, nlattr, priority)
-
-  def IifRule(self, version, is_add, iif, table, priority):
-    nlattr = self._NlAttrInterfaceName(FRA_IIFNAME, iif)
-    return self._Rule(version, is_add, RTN_UNICAST, table, nlattr, priority)
-
-  def OifRule(self, version, is_add, oif, table, priority):
-    nlattr = self._NlAttrInterfaceName(FRA_OIFNAME, oif)
-    return self._Rule(version, is_add, RTN_UNICAST, table, nlattr, priority)
-
-  def UidRangeRule(self, version, is_add, start, end, table, priority):
-    nlattr = (self._NlAttrInterfaceName(FRA_IIFNAME, "lo") +
-              self._NlAttrU32(FRA_UID_START, start) +
-              self._NlAttrU32(FRA_UID_END, end))
-    return self._Rule(version, is_add, RTN_UNICAST, table, nlattr, priority)
-
-  def UnreachableRule(self, version, is_add, priority):
-    return self._Rule(version, is_add, RTN_UNREACHABLE, None, None, priority)
-
-  def DefaultRule(self, version, is_add, table, priority):
-    return self.FwmarkRule(version, is_add, 0, table, priority)
-
-  def CommandToString(self, command, data):
-    try:
-      name = CommandName(command)
-      subject = CommandSubject(command)
-      struct_type = {
-          "ADDR": IfAddrMsg,
-          "LINK": IfinfoMsg,
-          "NEIGH": NdMsg,
-          "ROUTE": RTMsg,
-          "RULE": RTMsg,
-      }[subject]
-      parsed = self._ParseNLMsg(data, struct_type)
-      return "%s %s" % (name, str(parsed))
-    except IndexError:
-      raise ValueError("Don't know how to print command type %s" % name)
-
-  def MaybeDebugCommand(self, command, data):
-    subject = CommandSubject(command)
-    if "ALL" not in self.NL_DEBUG and subject not in self.NL_DEBUG:
-      return
-    print self.CommandToString(command, data)
-
-  def MaybeDebugMessage(self, message):
-    hdr = NLMsgHdr(message)
-    self.MaybeDebugCommand(hdr.type, message)
-
-  def PrintMessage(self, message):
-    hdr = NLMsgHdr(message)
-    print self.CommandToString(hdr.type, message)
-
-  def DumpRules(self, version):
-    """Returns the IP rules for the specified IP version."""
-    # Create a struct rtmsg specifying the table and the given match attributes.
-    family = self._AddressFamily(version)
-    rtmsg = RTMsg((family, 0, 0, 0, 0, 0, 0, 0, 0))
-    return self._Dump(RTM_GETRULE, rtmsg, RTMsg, "")
-
-  def DumpLinks(self):
-    ifinfomsg = IfinfoMsg((0, 0, 0, 0, 0, 0))
-    return self._Dump(RTM_GETLINK, ifinfomsg, IfinfoMsg, "")
-
-  def _Address(self, version, command, addr, prefixlen, flags, scope, ifindex):
-    """Adds or deletes an IP address."""
-    family = self._AddressFamily(version)
-    ifaddrmsg = IfAddrMsg((family, prefixlen, flags, scope, ifindex)).Pack()
-    ifaddrmsg += self._NlAttrIPAddress(IFA_ADDRESS, family, addr)
-    if version == 4:
-      ifaddrmsg += self._NlAttrIPAddress(IFA_LOCAL, family, addr)
-    self._SendNlRequest(command, ifaddrmsg)
-
-  def AddAddress(self, address, prefixlen, ifindex):
-    self._Address(6 if ":" in address else 4,
-                  RTM_NEWADDR, address, prefixlen,
-                  IFA_F_PERMANENT, RT_SCOPE_UNIVERSE, ifindex)
-
-  def DelAddress(self, address, prefixlen, ifindex):
-    self._Address(6 if ":" in address else 4,
-                  RTM_DELADDR, address, prefixlen, 0, 0, ifindex)
-
-  def GetAddress(self, address, ifindex=0):
-    """Returns an ifaddrmsg for the requested address."""
-    if ":" not in address:
-      # The address is likely an IPv4 address.  RTM_GETADDR without the
-      # NLM_F_DUMP flag is not supported by the kernel.  We do not currently
-      # implement parsing dump results.
-      raise NotImplementedError("IPv4 RTM_GETADDR not implemented.")
-    self._Address(6, RTM_GETADDR, address, 0, 0, RT_SCOPE_UNIVERSE, ifindex)
-    return self._GetMsg(IfAddrMsg)
-
-  def _Route(self, version, command, table, dest, prefixlen, nexthop, dev,
-             mark, uid):
-    """Adds, deletes, or queries a route."""
-    family = self._AddressFamily(version)
-    scope = RT_SCOPE_UNIVERSE if nexthop else RT_SCOPE_LINK
-    rtmsg = RTMsg((family, prefixlen, 0, 0, RT_TABLE_UNSPEC,
-                   RTPROT_STATIC, scope, RTN_UNICAST, 0)).Pack()
-    if command == RTM_NEWROUTE and not table:
-      # Don't allow setting routes in table 0, since its behaviour is confusing
-      # and differs between IPv4 and IPv6.
-      raise ValueError("Cowardly refusing to add a route to table 0")
-    if table:
-      rtmsg += self._NlAttrU32(FRA_TABLE, table)
-    if dest != "default":  # The default is the default route.
-      rtmsg += self._NlAttrIPAddress(RTA_DST, family, dest)
-    if nexthop:
-      rtmsg += self._NlAttrIPAddress(RTA_GATEWAY, family, nexthop)
-    if dev:
-      rtmsg += self._NlAttrU32(RTA_OIF, dev)
-    if mark is not None:
-      rtmsg += self._NlAttrU32(RTA_MARK, mark)
-    if uid is not None:
-      rtmsg += self._NlAttrU32(RTA_UID, uid)
-    self._SendNlRequest(command, rtmsg)
-
-  def AddRoute(self, version, table, dest, prefixlen, nexthop, dev):
-    self._Route(version, RTM_NEWROUTE, table, dest, prefixlen, nexthop, dev,
-                None, None)
-
-  def DelRoute(self, version, table, dest, prefixlen, nexthop, dev):
-    self._Route(version, RTM_DELROUTE, table, dest, prefixlen, nexthop, dev,
-                None, None)
-
-  def GetRoutes(self, dest, oif, mark, uid):
-    version = 6 if ":" in dest else 4
-    prefixlen = {4: 32, 6: 128}[version]
-    self._Route(version, RTM_GETROUTE, 0, dest, prefixlen, None, oif, mark, uid)
-    data = self._Recv()
-    # The response will either be an error or a list of routes.
-    if NLMsgHdr(data).type == NLMSG_ERROR:
-      self._ParseAck(data)
-    routes = self._GetMsgList(RTMsg, data, False)
-    return routes
-
-  def _Neighbour(self, version, is_add, addr, lladdr, dev, state, flags=0):
-    """Adds or deletes a neighbour cache entry."""
-    family = self._AddressFamily(version)
-
-    # Convert the link-layer address to a raw byte string.
-    if is_add and lladdr:
-      lladdr = lladdr.split(":")
-      if len(lladdr) != 6:
-        raise ValueError("Invalid lladdr %s" % ":".join(lladdr))
-      lladdr = "".join(chr(int(hexbyte, 16)) for hexbyte in lladdr)
-
-    ndmsg = NdMsg((family, dev, state, 0, RTN_UNICAST)).Pack()
-    ndmsg += self._NlAttrIPAddress(NDA_DST, family, addr)
-    if is_add and lladdr:
-      ndmsg += self._NlAttr(NDA_LLADDR, lladdr)
-    command = RTM_NEWNEIGH if is_add else RTM_DELNEIGH
-    self._SendNlRequest(command, ndmsg, flags)
-
-  def AddNeighbour(self, version, addr, lladdr, dev):
-    self._Neighbour(version, True, addr, lladdr, dev, NUD_PERMANENT)
-
-  def DelNeighbour(self, version, addr, lladdr, dev):
-    self._Neighbour(version, False, addr, lladdr, dev, 0)
-
-  def UpdateNeighbour(self, version, addr, lladdr, dev, state):
-    self._Neighbour(version, True, addr, lladdr, dev, state,
-                    flags=NLM_F_REPLACE)
-
-  def DumpNeighbours(self, version):
-    ndmsg = NdMsg((self._AddressFamily(version), 0, 0, 0, 0))
-    return self._Dump(RTM_GETNEIGH, ndmsg, NdMsg, "")
-
-  def ParseNeighbourMessage(self, msg):
-    msg, _ = self._ParseNLMsg(msg, NdMsg)
-    return msg
-
-
-if __name__ == "__main__":
-  iproute = IPRoute()
-  iproute.DEBUG = True
-  iproute.DumpRules(6)
-  iproute.DumpLinks()
-  print iproute.GetRoutes("2001:4860:4860::8888", 0, 0, None)
diff --git a/tests/net_test/multinetwork_base.py b/tests/net_test/multinetwork_base.py
deleted file mode 100644
index 31fcc4c..0000000
--- a/tests/net_test/multinetwork_base.py
+++ /dev/null
@@ -1,642 +0,0 @@
-#!/usr/bin/python
-#
-# Copyright 2014 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-"""Base module for multinetwork tests."""
-
-import errno
-import fcntl
-import os
-import posix
-import random
-import re
-from socket import *  # pylint: disable=wildcard-import
-import struct
-
-from scapy import all as scapy
-
-import csocket
-import cstruct
-import iproute
-import net_test
-
-
-IFF_TUN = 1
-IFF_TAP = 2
-IFF_NO_PI = 0x1000
-TUNSETIFF = 0x400454ca
-
-SO_BINDTODEVICE = 25
-
-# Setsockopt values.
-IP_UNICAST_IF = 50
-IPV6_MULTICAST_IF = 17
-IPV6_UNICAST_IF = 76
-
-# Cmsg values.
-IP_TTL = 2
-IP_PKTINFO = 8
-IPV6_2292PKTOPTIONS = 6
-IPV6_FLOWINFO = 11
-IPV6_PKTINFO = 50
-IPV6_HOPLIMIT = 52  # Different from IPV6_UNICAST_HOPS, this is cmsg only.
-
-# Data structures.
-# These aren't constants, they're classes. So, pylint: disable=invalid-name
-InPktinfo = cstruct.Struct("in_pktinfo", "@i4s4s", "ifindex spec_dst addr")
-In6Pktinfo = cstruct.Struct("in6_pktinfo", "@16si", "addr ifindex")
-
-
-def HaveUidRouting():
-  """Checks whether the kernel supports UID routing."""
-  # Create a rule with the UID range selector. If the kernel doesn't understand
-  # the selector, it will create a rule with no selectors.
-  try:
-    iproute.IPRoute().UidRangeRule(6, True, 1000, 2000, 100, 10000)
-  except IOError:
-    return False
-
-  # Dump all the rules. If we find a rule using the UID range selector, then the
-  # kernel supports UID range routing.
-  rules = iproute.IPRoute().DumpRules(6)
-  result = any("FRA_UID_START" in attrs for rule, attrs in rules)
-
-  # Delete the rule.
-  iproute.IPRoute().UidRangeRule(6, False, 1000, 2000, 100, 10000)
-  return result
-
-AUTOCONF_TABLE_SYSCTL = "/proc/sys/net/ipv6/conf/default/accept_ra_rt_table"
-
-HAVE_AUTOCONF_TABLE = os.path.isfile(AUTOCONF_TABLE_SYSCTL)
-HAVE_UID_ROUTING = HaveUidRouting()
-
-
-class UnexpectedPacketError(AssertionError):
-  pass
-
-
-def MakePktInfo(version, addr, ifindex):
-  family = {4: AF_INET, 6: AF_INET6}[version]
-  if not addr:
-    addr = {4: "0.0.0.0", 6: "::"}[version]
-  if addr:
-    addr = inet_pton(family, addr)
-  if version == 6:
-    return In6Pktinfo((addr, ifindex)).Pack()
-  else:
-    return InPktinfo((ifindex, addr, "\x00" * 4)).Pack()
-
-
-class MultiNetworkBaseTest(net_test.NetworkTest):
-  """Base class for all multinetwork tests.
-
-  This class does not contain any test code, but contains code to set up and
-  tear a multi-network environment using multiple tun interfaces. The
-  environment is designed to be similar to a real Android device in terms of
-  rules and routes, and supports IPv4 and IPv6.
-
-  Tests wishing to use this environment should inherit from this class and
-  ensure that any setupClass, tearDownClass, setUp, and tearDown methods they
-  implement also call the superclass versions.
-  """
-
-  # Must be between 1 and 256, since we put them in MAC addresses and IIDs.
-  NETIDS = [100, 150, 200, 250]
-
-  # Stores sysctl values to write back when the test completes.
-  saved_sysctls = {}
-
-  # Wether to output setup commands.
-  DEBUG = False
-
-  # The size of our UID ranges.
-  UID_RANGE_SIZE = 1000
-
-  # Rule priorities.
-  PRIORITY_UID = 100
-  PRIORITY_OIF = 200
-  PRIORITY_FWMARK = 300
-  PRIORITY_IIF = 400
-  PRIORITY_DEFAULT = 999
-  PRIORITY_UNREACHABLE = 1000
-
-  # For convenience.
-  IPV4_ADDR = net_test.IPV4_ADDR
-  IPV6_ADDR = net_test.IPV6_ADDR
-  IPV4_PING = net_test.IPV4_PING
-  IPV6_PING = net_test.IPV6_PING
-
-  @classmethod
-  def UidRangeForNetid(cls, netid):
-    return (
-        cls.UID_RANGE_SIZE * netid,
-        cls.UID_RANGE_SIZE * (netid + 1) - 1
-    )
-
-  @classmethod
-  def UidForNetid(cls, netid):
-    return random.randint(*cls.UidRangeForNetid(netid))
-
-  @classmethod
-  def _TableForNetid(cls, netid):
-    if cls.AUTOCONF_TABLE_OFFSET and netid in cls.ifindices:
-      return cls.ifindices[netid] + (-cls.AUTOCONF_TABLE_OFFSET)
-    else:
-      return netid
-
-  @staticmethod
-  def GetInterfaceName(netid):
-    return "nettest%d" % netid
-
-  @staticmethod
-  def RouterMacAddress(netid):
-    return "02:00:00:00:%02x:00" % netid
-
-  @staticmethod
-  def MyMacAddress(netid):
-    return "02:00:00:00:%02x:01" % netid
-
-  @staticmethod
-  def _RouterAddress(netid, version):
-    if version == 6:
-      return "fe80::%02x00" % netid
-    elif version == 4:
-      return "10.0.%d.1" % netid
-    else:
-      raise ValueError("Don't support IPv%s" % version)
-
-  @classmethod
-  def _MyIPv4Address(cls, netid):
-    return "10.0.%d.2" % netid
-
-  @classmethod
-  def _MyIPv6Address(cls, netid):
-    return net_test.GetLinkAddress(cls.GetInterfaceName(netid), False)
-
-  @classmethod
-  def MyAddress(cls, version, netid):
-    return {4: cls._MyIPv4Address(netid),
-            5: "::ffff:" + cls._MyIPv4Address(netid),
-            6: cls._MyIPv6Address(netid)}[version]
-
-  @classmethod
-  def MyLinkLocalAddress(cls, netid):
-    return net_test.GetLinkAddress(cls.GetInterfaceName(netid), True)
-
-  @staticmethod
-  def IPv6Prefix(netid):
-    return "2001:db8:%02x::" % netid
-
-  @staticmethod
-  def GetRandomDestination(prefix):
-    if "." in prefix:
-      return prefix + "%d.%d" % (random.randint(0, 31), random.randint(0, 255))
-    else:
-      return prefix + "%x:%x" % (random.randint(0, 65535),
-                                 random.randint(0, 65535))
-
-  def GetProtocolFamily(self, version):
-    return {4: AF_INET, 6: AF_INET6}[version]
-
-  @classmethod
-  def CreateTunInterface(cls, netid):
-    iface = cls.GetInterfaceName(netid)
-    f = open("/dev/net/tun", "r+b")
-    ifr = struct.pack("16sH", iface, IFF_TAP | IFF_NO_PI)
-    ifr += "\x00" * (40 - len(ifr))
-    fcntl.ioctl(f, TUNSETIFF, ifr)
-    # Give ourselves a predictable MAC address.
-    net_test.SetInterfaceHWAddr(iface, cls.MyMacAddress(netid))
-    # Disable DAD so we don't have to wait for it.
-    cls.SetSysctl("/proc/sys/net/ipv6/conf/%s/accept_dad" % iface, 0)
-    # Set accept_ra to 2, because that's what we use.
-    cls.SetSysctl("/proc/sys/net/ipv6/conf/%s/accept_ra" % iface, 2)
-    net_test.SetInterfaceUp(iface)
-    net_test.SetNonBlocking(f)
-    return f
-
-  @classmethod
-  def SendRA(cls, netid, retranstimer=None, reachabletime=0):
-    validity = 300                 # seconds
-    macaddr = cls.RouterMacAddress(netid)
-    lladdr = cls._RouterAddress(netid, 6)
-
-    if retranstimer is None:
-      # If no retrans timer was specified, pick one that's as long as the
-      # router lifetime. This ensures that no spurious ND retransmits
-      # will interfere with test expectations.
-      retranstimer = validity
-
-    # We don't want any routes in the main table. If the kernel doesn't support
-    # putting RA routes into per-interface tables, configure routing manually.
-    routerlifetime = validity if HAVE_AUTOCONF_TABLE else 0
-
-    ra = (scapy.Ether(src=macaddr, dst="33:33:00:00:00:01") /
-          scapy.IPv6(src=lladdr, hlim=255) /
-          scapy.ICMPv6ND_RA(reachabletime=reachabletime,
-                            retranstimer=retranstimer,
-                            routerlifetime=routerlifetime) /
-          scapy.ICMPv6NDOptSrcLLAddr(lladdr=macaddr) /
-          scapy.ICMPv6NDOptPrefixInfo(prefix=cls.IPv6Prefix(netid),
-                                      prefixlen=64,
-                                      L=1, A=1,
-                                      validlifetime=validity,
-                                      preferredlifetime=validity))
-    posix.write(cls.tuns[netid].fileno(), str(ra))
-
-  @classmethod
-  def _RunSetupCommands(cls, netid, is_add):
-    for version in [4, 6]:
-      # Find out how to configure things.
-      iface = cls.GetInterfaceName(netid)
-      ifindex = cls.ifindices[netid]
-      macaddr = cls.RouterMacAddress(netid)
-      router = cls._RouterAddress(netid, version)
-      table = cls._TableForNetid(netid)
-
-      # Set up routing rules.
-      if HAVE_UID_ROUTING:
-        start, end = cls.UidRangeForNetid(netid)
-        cls.iproute.UidRangeRule(version, is_add, start, end, table,
-                                 cls.PRIORITY_UID)
-      cls.iproute.OifRule(version, is_add, iface, table, cls.PRIORITY_OIF)
-      cls.iproute.FwmarkRule(version, is_add, netid, table,
-                             cls.PRIORITY_FWMARK)
-
-      # Configure routing and addressing.
-      #
-      # IPv6 uses autoconf for everything, except if per-device autoconf routing
-      # tables are not supported, in which case the default route (only) is
-      # configured manually. For IPv4 we have to manually configure addresses,
-      # routes, and neighbour cache entries (since we don't reply to ARP or ND).
-      #
-      # Since deleting addresses also causes routes to be deleted, we need to
-      # be careful with ordering or the delete commands will fail with ENOENT.
-      do_routing = (version == 4 or cls.AUTOCONF_TABLE_OFFSET is None)
-      if is_add:
-        if version == 4:
-          cls.iproute.AddAddress(cls._MyIPv4Address(netid), 24, ifindex)
-          cls.iproute.AddNeighbour(version, router, macaddr, ifindex)
-        if do_routing:
-          cls.iproute.AddRoute(version, table, "default", 0, router, ifindex)
-          if version == 6:
-            cls.iproute.AddRoute(version, table,
-                                 cls.IPv6Prefix(netid), 64, None, ifindex)
-      else:
-        if do_routing:
-          cls.iproute.DelRoute(version, table, "default", 0, router, ifindex)
-          if version == 6:
-            cls.iproute.DelRoute(version, table,
-                                 cls.IPv6Prefix(netid), 64, None, ifindex)
-        if version == 4:
-          cls.iproute.DelNeighbour(version, router, macaddr, ifindex)
-          cls.iproute.DelAddress(cls._MyIPv4Address(netid), 24, ifindex)
-
-  @classmethod
-  def SetDefaultNetwork(cls, netid):
-    table = cls._TableForNetid(netid) if netid else None
-    for version in [4, 6]:
-      is_add = table is not None
-      cls.iproute.DefaultRule(version, is_add, table, cls.PRIORITY_DEFAULT)
-
-  @classmethod
-  def ClearDefaultNetwork(cls):
-    cls.SetDefaultNetwork(None)
-
-  @classmethod
-  def GetSysctl(cls, sysctl):
-    return open(sysctl, "r").read()
-
-  @classmethod
-  def SetSysctl(cls, sysctl, value):
-    # Only save each sysctl value the first time we set it. This is so we can
-    # set it to arbitrary values multiple times and still write it back
-    # correctly at the end.
-    if sysctl not in cls.saved_sysctls:
-      cls.saved_sysctls[sysctl] = cls.GetSysctl(sysctl)
-    open(sysctl, "w").write(str(value) + "\n")
-
-  @classmethod
-  def SetIPv6SysctlOnAllIfaces(cls, sysctl, value):
-    for netid in cls.tuns:
-      iface = cls.GetInterfaceName(netid)
-      name = "/proc/sys/net/ipv6/conf/%s/%s" % (iface, sysctl)
-      cls.SetSysctl(name, value)
-
-  @classmethod
-  def _RestoreSysctls(cls):
-    for sysctl, value in cls.saved_sysctls.iteritems():
-      try:
-        open(sysctl, "w").write(value)
-      except IOError:
-        pass
-
-  @classmethod
-  def _ICMPRatelimitFilename(cls, version):
-    return "/proc/sys/net/" + {4: "ipv4/icmp_ratelimit",
-                               6: "ipv6/icmp/ratelimit"}[version]
-
-  @classmethod
-  def _SetICMPRatelimit(cls, version, limit):
-    cls.SetSysctl(cls._ICMPRatelimitFilename(version), limit)
-
-  @classmethod
-  def setUpClass(cls):
-    # This is per-class setup instead of per-testcase setup because shelling out
-    # to ip and iptables is slow, and because routing configuration doesn't
-    # change during the test.
-    cls.iproute = iproute.IPRoute()
-    cls.tuns = {}
-    cls.ifindices = {}
-    if HAVE_AUTOCONF_TABLE:
-      cls.SetSysctl(AUTOCONF_TABLE_SYSCTL, -1000)
-      cls.AUTOCONF_TABLE_OFFSET = -1000
-    else:
-      cls.AUTOCONF_TABLE_OFFSET = None
-
-    # Disable ICMP rate limits. These will be restored by _RestoreSysctls.
-    for version in [4, 6]:
-      cls._SetICMPRatelimit(version, 0)
-
-    for netid in cls.NETIDS:
-      cls.tuns[netid] = cls.CreateTunInterface(netid)
-      iface = cls.GetInterfaceName(netid)
-      cls.ifindices[netid] = net_test.GetInterfaceIndex(iface)
-
-      cls.SendRA(netid)
-      cls._RunSetupCommands(netid, True)
-
-    for version in [4, 6]:
-      cls.iproute.UnreachableRule(version, True, 1000)
-
-    # Uncomment to look around at interface and rule configuration while
-    # running in the background. (Once the test finishes running, all the
-    # interfaces and rules are gone.)
-    # time.sleep(30)
-
-  @classmethod
-  def tearDownClass(cls):
-    for version in [4, 6]:
-      try:
-        cls.iproute.UnreachableRule(version, False, 1000)
-      except IOError:
-        pass
-
-    for netid in cls.tuns:
-      cls._RunSetupCommands(netid, False)
-      cls.tuns[netid].close()
-    cls._RestoreSysctls()
-
-  def setUp(self):
-    self.ClearTunQueues()
-
-  def SetSocketMark(self, s, netid):
-    if netid is None:
-      netid = 0
-    s.setsockopt(SOL_SOCKET, net_test.SO_MARK, netid)
-
-  def GetSocketMark(self, s):
-    return s.getsockopt(SOL_SOCKET, net_test.SO_MARK)
-
-  def ClearSocketMark(self, s):
-    self.SetSocketMark(s, 0)
-
-  def BindToDevice(self, s, iface):
-    if not iface:
-      iface = ""
-    s.setsockopt(SOL_SOCKET, SO_BINDTODEVICE, iface)
-
-  def SetUnicastInterface(self, s, ifindex):
-    # Otherwise, Python thinks it's a 1-byte option.
-    ifindex = struct.pack("!I", ifindex)
-
-    # Always set the IPv4 interface, because it will be used even on IPv6
-    # sockets if the destination address is a mapped address.
-    s.setsockopt(net_test.SOL_IP, IP_UNICAST_IF, ifindex)
-    if s.family == AF_INET6:
-      s.setsockopt(net_test.SOL_IPV6, IPV6_UNICAST_IF, ifindex)
-
-  def GetRemoteAddress(self, version):
-    return {4: self.IPV4_ADDR,
-            5: "::ffff:" + self.IPV4_ADDR,
-            6: self.IPV6_ADDR}[version]
-
-  def SelectInterface(self, s, netid, mode):
-    if mode == "uid":
-      raise ValueError("Can't change UID on an existing socket")
-    elif mode == "mark":
-      self.SetSocketMark(s, netid)
-    elif mode == "oif":
-      iface = self.GetInterfaceName(netid) if netid else ""
-      self.BindToDevice(s, iface)
-    elif mode == "ucast_oif":
-      self.SetUnicastInterface(s, self.ifindices.get(netid, 0))
-    else:
-      raise ValueError("Unknown interface selection mode %s" % mode)
-
-  def BuildSocket(self, version, constructor, netid, routing_mode):
-    s = constructor(self.GetProtocolFamily(version))
-
-    if routing_mode not in [None, "uid"]:
-      self.SelectInterface(s, netid, routing_mode)
-    elif routing_mode == "uid":
-      os.fchown(s.fileno(), self.UidForNetid(netid), -1)
-
-    return s
-
-  def SendOnNetid(self, version, s, dstaddr, dstport, netid, payload, cmsgs):
-    if netid is not None:
-      pktinfo = MakePktInfo(version, None, self.ifindices[netid])
-      cmsg_level, cmsg_name = {
-          4: (net_test.SOL_IP, IP_PKTINFO),
-          6: (net_test.SOL_IPV6, IPV6_PKTINFO)}[version]
-      cmsgs.append((cmsg_level, cmsg_name, pktinfo))
-    csocket.Sendmsg(s, (dstaddr, dstport), payload, cmsgs, csocket.MSG_CONFIRM)
-
-  def ReceiveEtherPacketOn(self, netid, packet):
-    posix.write(self.tuns[netid].fileno(), str(packet))
-
-  def ReceivePacketOn(self, netid, ip_packet):
-    routermac = self.RouterMacAddress(netid)
-    mymac = self.MyMacAddress(netid)
-    packet = scapy.Ether(src=routermac, dst=mymac) / ip_packet
-    self.ReceiveEtherPacketOn(netid, packet)
-
-  def ReadAllPacketsOn(self, netid, include_multicast=False):
-    packets = []
-    while True:
-      try:
-        packet = posix.read(self.tuns[netid].fileno(), 4096)
-        if not packet:
-          break
-        ether = scapy.Ether(packet)
-        # Multicast frames are frames where the first byte of the destination
-        # MAC address has 1 in the least-significant bit.
-        if include_multicast or not int(ether.dst.split(":")[0], 16) & 0x1:
-          packets.append(ether.payload)
-      except OSError, e:
-        # EAGAIN means there are no more packets waiting.
-        if re.match(e.message, os.strerror(errno.EAGAIN)):
-          break
-        # Anything else is unexpected.
-        else:
-          raise e
-    return packets
-
-  def ClearTunQueues(self):
-    # Keep reading packets on all netids until we get no packets on any of them.
-    waiting = None
-    while waiting != 0:
-      waiting = sum(len(self.ReadAllPacketsOn(netid)) for netid in self.NETIDS)
-
-  def assertPacketMatches(self, expected, actual):
-    # The expected packet is just a rough sketch of the packet we expect to
-    # receive. For example, it doesn't contain fields we can't predict, such as
-    # initial TCP sequence numbers, or that depend on the host implementation
-    # and settings, such as TCP options. To check whether the packet matches
-    # what we expect, instead of just checking all the known fields one by one,
-    # we blank out fields in the actual packet and then compare the whole
-    # packets to each other as strings. Because we modify the actual packet,
-    # make a copy here.
-    actual = actual.copy()
-
-    # Blank out IPv4 fields that we can't predict, like ID and the DF bit.
-    actualip = actual.getlayer("IP")
-    expectedip = expected.getlayer("IP")
-    if actualip and expectedip:
-      actualip.id = expectedip.id
-      actualip.flags &= 5
-      actualip.chksum = None  # Change the header, recalculate the checksum.
-
-    # Blank out the flow label, since new kernels randomize it by default.
-    actualipv6 = actual.getlayer("IPv6")
-    expectedipv6 = expected.getlayer("IPv6")
-    if actualipv6 and expectedipv6:
-      actualipv6.fl = expectedipv6.fl
-
-    # Blank out UDP fields that we can't predict (e.g., the source port for
-    # kernel-originated packets).
-    actualudp = actual.getlayer("UDP")
-    expectedudp = expected.getlayer("UDP")
-    if actualudp and expectedudp:
-      if expectedudp.sport is None:
-        actualudp.sport = None
-        actualudp.chksum = None
-
-    # Since the TCP code below messes with options, recalculate the length.
-    if actualip:
-      actualip.len = None
-    if actualipv6:
-      actualipv6.plen = None
-
-    # Blank out TCP fields that we can't predict.
-    actualtcp = actual.getlayer("TCP")
-    expectedtcp = expected.getlayer("TCP")
-    if actualtcp and expectedtcp:
-      actualtcp.dataofs = expectedtcp.dataofs
-      actualtcp.options = expectedtcp.options
-      actualtcp.window = expectedtcp.window
-      if expectedtcp.sport is None:
-        actualtcp.sport = None
-      if expectedtcp.seq is None:
-        actualtcp.seq = None
-      if expectedtcp.ack is None:
-        actualtcp.ack = None
-      actualtcp.chksum = None
-
-    # Serialize the packet so that expected packet fields that are only set when
-    # a packet is serialized e.g., the checksum) are filled in.
-    expected_real = expected.__class__(str(expected))
-    actual_real = actual.__class__(str(actual))
-    # repr() can be expensive. Call it only if the test is going to fail and we
-    # want to see the error.
-    if expected_real != actual_real:
-      self.assertEquals(repr(expected_real), repr(actual_real))
-
-  def PacketMatches(self, expected, actual):
-    try:
-      self.assertPacketMatches(expected, actual)
-      return True
-    except AssertionError:
-      return False
-
-  def ExpectNoPacketsOn(self, netid, msg):
-    packets = self.ReadAllPacketsOn(netid)
-    if packets:
-      firstpacket = repr(packets[0])
-    else:
-      firstpacket = ""
-    self.assertFalse(packets, msg + ": unexpected packet: " + firstpacket)
-
-  def ExpectPacketOn(self, netid, msg, expected):
-    # To avoid confusion due to lots of ICMPv6 ND going on all the time, drop
-    # multicast packets unless the packet we expect to see is a multicast
-    # packet. For now the only tests that use this are IPv6.
-    ipv6 = expected.getlayer("IPv6")
-    if ipv6 and ipv6.dst.startswith("ff"):
-      include_multicast = True
-    else:
-      include_multicast = False
-
-    packets = self.ReadAllPacketsOn(netid, include_multicast=include_multicast)
-    self.assertTrue(packets, msg + ": received no packets")
-
-    # If we receive a packet that matches what we expected, return it.
-    for packet in packets:
-      if self.PacketMatches(expected, packet):
-        return packet
-
-    # None of the packets matched. Call assertPacketMatches to output a diff
-    # between the expected packet and the last packet we received. In theory,
-    # we'd output a diff to the packet that's the best match for what we
-    # expected, but this is good enough for now.
-    try:
-      self.assertPacketMatches(expected, packets[-1])
-    except Exception, e:
-      raise UnexpectedPacketError(
-          "%s: diff with last packet:\n%s" % (msg, e.message))
-
-  def Combinations(self, version):
-    """Produces a list of combinations to test."""
-    combinations = []
-
-    # Check packets addressed to the IP addresses of all our interfaces...
-    for dest_ip_netid in self.tuns:
-      ip_if = self.GetInterfaceName(dest_ip_netid)
-      myaddr = self.MyAddress(version, dest_ip_netid)
-      remoteaddr = self.GetRemoteAddress(version)
-
-      # ... coming in on all our interfaces.
-      for netid in self.tuns:
-        iif = self.GetInterfaceName(netid)
-        combinations.append((netid, iif, ip_if, myaddr, remoteaddr))
-
-    return combinations
-
-  def _FormatMessage(self, iif, ip_if, extra, desc, reply_desc):
-    msg = "Receiving %s on %s to %s IP, %s" % (desc, iif, ip_if, extra)
-    if reply_desc:
-      msg += ": Expecting %s on %s" % (reply_desc, iif)
-    else:
-      msg += ": Expecting no packets on %s" % iif
-    return msg
-
-  def _ReceiveAndExpectResponse(self, netid, packet, reply, msg):
-    self.ReceivePacketOn(netid, packet)
-    if reply:
-      return self.ExpectPacketOn(netid, msg, reply)
-    else:
-      self.ExpectNoPacketsOn(netid, msg)
-      return None
diff --git a/tests/net_test/multinetwork_test.py b/tests/net_test/multinetwork_test.py
deleted file mode 100755
index 660fdf6..0000000
--- a/tests/net_test/multinetwork_test.py
+++ /dev/null
@@ -1,926 +0,0 @@
-#!/usr/bin/python
-#
-# Copyright 2014 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import errno
-import os
-import random
-from socket import *  # pylint: disable=wildcard-import
-import struct
-import time           # pylint: disable=unused-import
-import unittest
-
-from scapy import all as scapy
-
-import iproute
-import multinetwork_base
-import net_test
-import packets
-
-# For brevity.
-UDP_PAYLOAD = net_test.UDP_PAYLOAD
-
-IPV6_FLOWINFO = 11
-
-IPV4_MARK_REFLECT_SYSCTL = "/proc/sys/net/ipv4/fwmark_reflect"
-IPV6_MARK_REFLECT_SYSCTL = "/proc/sys/net/ipv6/fwmark_reflect"
-SYNCOOKIES_SYSCTL = "/proc/sys/net/ipv4/tcp_syncookies"
-TCP_MARK_ACCEPT_SYSCTL = "/proc/sys/net/ipv4/tcp_fwmark_accept"
-
-# The IP[V6]UNICAST_IF socket option was added between 3.1 and 3.4.
-HAVE_UNICAST_IF = net_test.LINUX_VERSION >= (3, 4, 0)
-
-
-class ConfigurationError(AssertionError):
-  pass
-
-
-class InboundMarkingTest(multinetwork_base.MultiNetworkBaseTest):
-
-  @classmethod
-  def _SetInboundMarking(cls, netid, is_add):
-    for version in [4, 6]:
-      # Run iptables to set up incoming packet marking.
-      iface = cls.GetInterfaceName(netid)
-      add_del = "-A" if is_add else "-D"
-      iptables = {4: "iptables", 6: "ip6tables"}[version]
-      args = "%s %s INPUT -t mangle -i %s -j MARK --set-mark %d" % (
-          iptables, add_del, iface, netid)
-      iptables = "/sbin/" + iptables
-      ret = os.spawnvp(os.P_WAIT, iptables, args.split(" "))
-      if ret:
-        raise ConfigurationError("Setup command failed: %s" % args)
-
-  @classmethod
-  def setUpClass(cls):
-    super(InboundMarkingTest, cls).setUpClass()
-    for netid in cls.tuns:
-      cls._SetInboundMarking(netid, True)
-
-  @classmethod
-  def tearDownClass(cls):
-    for netid in cls.tuns:
-      cls._SetInboundMarking(netid, False)
-    super(InboundMarkingTest, cls).tearDownClass()
-
-  @classmethod
-  def SetMarkReflectSysctls(cls, value):
-    cls.SetSysctl(IPV4_MARK_REFLECT_SYSCTL, value)
-    try:
-      cls.SetSysctl(IPV6_MARK_REFLECT_SYSCTL, value)
-    except IOError:
-      # This does not exist if we use the version of the patch that uses a
-      # common sysctl for IPv4 and IPv6.
-      pass
-
-
-class OutgoingTest(multinetwork_base.MultiNetworkBaseTest):
-
-  # How many times to run outgoing packet tests.
-  ITERATIONS = 5
-
-  def CheckPingPacket(self, version, netid, routing_mode, dstaddr, packet):
-    s = self.BuildSocket(version, net_test.PingSocket, netid, routing_mode)
-
-    myaddr = self.MyAddress(version, netid)
-    s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
-    s.bind((myaddr, packets.PING_IDENT))
-    net_test.SetSocketTos(s, packets.PING_TOS)
-
-    desc, expected = packets.ICMPEcho(version, myaddr, dstaddr)
-    msg = "IPv%d ping: expected %s on %s" % (
-        version, desc, self.GetInterfaceName(netid))
-
-    s.sendto(packet + packets.PING_PAYLOAD, (dstaddr, 19321))
-
-    self.ExpectPacketOn(netid, msg, expected)
-
-  def CheckTCPSYNPacket(self, version, netid, routing_mode, dstaddr):
-    s = self.BuildSocket(version, net_test.TCPSocket, netid, routing_mode)
-
-    if version == 6 and dstaddr.startswith("::ffff"):
-      version = 4
-    myaddr = self.MyAddress(version, netid)
-    desc, expected = packets.SYN(53, version, myaddr, dstaddr,
-                                 sport=None, seq=None)
-
-    # Non-blocking TCP connects always return EINPROGRESS.
-    self.assertRaisesErrno(errno.EINPROGRESS, s.connect, (dstaddr, 53))
-    msg = "IPv%s TCP connect: expected %s on %s" % (
-        version, desc, self.GetInterfaceName(netid))
-    self.ExpectPacketOn(netid, msg, expected)
-    s.close()
-
-  def CheckUDPPacket(self, version, netid, routing_mode, dstaddr):
-    s = self.BuildSocket(version, net_test.UDPSocket, netid, routing_mode)
-
-    if version == 6 and dstaddr.startswith("::ffff"):
-      version = 4
-    myaddr = self.MyAddress(version, netid)
-    desc, expected = packets.UDP(version, myaddr, dstaddr, sport=None)
-    msg = "IPv%s UDP %%s: expected %s on %s" % (
-        version, desc, self.GetInterfaceName(netid))
-
-    s.sendto(UDP_PAYLOAD, (dstaddr, 53))
-    self.ExpectPacketOn(netid, msg % "sendto", expected)
-
-    # IP_UNICAST_IF doesn't seem to work on connected sockets, so no TCP.
-    if routing_mode != "ucast_oif":
-      s.connect((dstaddr, 53))
-      s.send(UDP_PAYLOAD)
-      self.ExpectPacketOn(netid, msg % "connect/send", expected)
-      s.close()
-
-  def CheckRawGrePacket(self, version, netid, routing_mode, dstaddr):
-    s = self.BuildSocket(version, net_test.RawGRESocket, netid, routing_mode)
-
-    inner_version = {4: 6, 6: 4}[version]
-    inner_src = self.MyAddress(inner_version, netid)
-    inner_dst = self.GetRemoteAddress(inner_version)
-    inner = str(packets.UDP(inner_version, inner_src, inner_dst, sport=None)[1])
-
-    ethertype = {4: net_test.ETH_P_IP, 6: net_test.ETH_P_IPV6}[inner_version]
-    # A GRE header can be as simple as two zero bytes and the ethertype.
-    packet = struct.pack("!i", ethertype) + inner
-    myaddr = self.MyAddress(version, netid)
-
-    s.sendto(packet, (dstaddr, IPPROTO_GRE))
-    desc, expected = packets.GRE(version, myaddr, dstaddr, ethertype, inner)
-    msg = "Raw IPv%d GRE with inner IPv%d UDP: expected %s on %s" % (
-        version, inner_version, desc, self.GetInterfaceName(netid))
-    self.ExpectPacketOn(netid, msg, expected)
-
-  def CheckOutgoingPackets(self, routing_mode):
-    v4addr = self.IPV4_ADDR
-    v6addr = self.IPV6_ADDR
-    v4mapped = "::ffff:" + v4addr
-
-    for _ in xrange(self.ITERATIONS):
-      for netid in self.tuns:
-
-        self.CheckPingPacket(4, netid, routing_mode, v4addr, self.IPV4_PING)
-        # Kernel bug.
-        if routing_mode != "oif":
-          self.CheckPingPacket(6, netid, routing_mode, v6addr, self.IPV6_PING)
-
-        # IP_UNICAST_IF doesn't seem to work on connected sockets, so no TCP.
-        if routing_mode != "ucast_oif":
-          self.CheckTCPSYNPacket(4, netid, routing_mode, v4addr)
-          self.CheckTCPSYNPacket(6, netid, routing_mode, v6addr)
-          self.CheckTCPSYNPacket(6, netid, routing_mode, v4mapped)
-
-        self.CheckUDPPacket(4, netid, routing_mode, v4addr)
-        self.CheckUDPPacket(6, netid, routing_mode, v6addr)
-        self.CheckUDPPacket(6, netid, routing_mode, v4mapped)
-
-        # Creating raw sockets on non-root UIDs requires properly setting
-        # capabilities, which is hard to do from Python.
-        # IP_UNICAST_IF is not supported on raw sockets.
-        if routing_mode not in ["uid", "ucast_oif"]:
-          self.CheckRawGrePacket(4, netid, routing_mode, v4addr)
-          self.CheckRawGrePacket(6, netid, routing_mode, v6addr)
-
-  def testMarkRouting(self):
-    """Checks that socket marking selects the right outgoing interface."""
-    self.CheckOutgoingPackets("mark")
-
-  @unittest.skipUnless(multinetwork_base.HAVE_UID_ROUTING, "no UID routes")
-  def testUidRouting(self):
-    """Checks that UID routing selects the right outgoing interface."""
-    self.CheckOutgoingPackets("uid")
-
-  def testOifRouting(self):
-    """Checks that oif routing selects the right outgoing interface."""
-    self.CheckOutgoingPackets("oif")
-
-  @unittest.skipUnless(HAVE_UNICAST_IF, "no support for UNICAST_IF")
-  def testUcastOifRouting(self):
-    """Checks that ucast oif routing selects the right outgoing interface."""
-    self.CheckOutgoingPackets("ucast_oif")
-
-  def CheckRemarking(self, version, use_connect):
-    # Remarking or resetting UNICAST_IF on connected sockets does not work.
-    if use_connect:
-      modes = ["oif"]
-    else:
-      modes = ["mark", "oif"]
-      if HAVE_UNICAST_IF:
-        modes += ["ucast_oif"]
-
-    for mode in modes:
-      s = net_test.UDPSocket(self.GetProtocolFamily(version))
-
-      # Figure out what packets to expect.
-      unspec = {4: "0.0.0.0", 6: "::"}[version]
-      sport = packets.RandomPort()
-      s.bind((unspec, sport))
-      dstaddr = {4: self.IPV4_ADDR, 6: self.IPV6_ADDR}[version]
-      desc, expected = packets.UDP(version, unspec, dstaddr, sport)
-
-      # If we're testing connected sockets, connect the socket on the first
-      # netid now.
-      if use_connect:
-        netid = self.tuns.keys()[0]
-        self.SelectInterface(s, netid, mode)
-        s.connect((dstaddr, 53))
-        expected.src = self.MyAddress(version, netid)
-
-      # For each netid, select that network without closing the socket, and
-      # check that the packets sent on that socket go out on the right network.
-      for netid in self.tuns:
-        self.SelectInterface(s, netid, mode)
-        if not use_connect:
-          expected.src = self.MyAddress(version, netid)
-        s.sendto(UDP_PAYLOAD, (dstaddr, 53))
-        connected_str = "Connected" if use_connect else "Unconnected"
-        msg = "%s UDPv%d socket remarked using %s: expecting %s on %s" % (
-            connected_str, version, mode, desc, self.GetInterfaceName(netid))
-        self.ExpectPacketOn(netid, msg, expected)
-        self.SelectInterface(s, None, mode)
-
-  def testIPv4Remarking(self):
-    """Checks that updating the mark on an IPv4 socket changes routing."""
-    self.CheckRemarking(4, False)
-    self.CheckRemarking(4, True)
-
-  def testIPv6Remarking(self):
-    """Checks that updating the mark on an IPv6 socket changes routing."""
-    self.CheckRemarking(6, False)
-    self.CheckRemarking(6, True)
-
-  def testIPv6StickyPktinfo(self):
-    for _ in xrange(self.ITERATIONS):
-      for netid in self.tuns:
-        s = net_test.UDPSocket(AF_INET6)
-
-        # Set a flowlabel.
-        net_test.SetFlowLabel(s, net_test.IPV6_ADDR, 0xdead)
-        s.setsockopt(net_test.SOL_IPV6, net_test.IPV6_FLOWINFO_SEND, 1)
-
-        # Set some destination options.
-        nonce = "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c"
-        dstopts = "".join([
-            "\x11\x02",              # Next header=UDP, 24 bytes of options.
-            "\x01\x06", "\x00" * 6,  # PadN, 6 bytes of padding.
-            "\x8b\x0c",              # ILNP nonce, 12 bytes.
-            nonce
-        ])
-        s.setsockopt(net_test.SOL_IPV6, IPV6_DSTOPTS, dstopts)
-        s.setsockopt(net_test.SOL_IPV6, IPV6_UNICAST_HOPS, 255)
-
-        pktinfo = multinetwork_base.MakePktInfo(6, None, self.ifindices[netid])
-
-        # Set the sticky pktinfo option.
-        s.setsockopt(net_test.SOL_IPV6, IPV6_PKTINFO, pktinfo)
-
-        # Specify the flowlabel in the destination address.
-        s.sendto(UDP_PAYLOAD, (net_test.IPV6_ADDR, 53, 0xdead, 0))
-
-        sport = s.getsockname()[1]
-        srcaddr = self.MyAddress(6, netid)
-        expected = (scapy.IPv6(src=srcaddr, dst=net_test.IPV6_ADDR,
-                               fl=0xdead, hlim=255) /
-                    scapy.IPv6ExtHdrDestOpt(
-                        options=[scapy.PadN(optdata="\x00\x00\x00\x00\x00\x00"),
-                                 scapy.HBHOptUnknown(otype=0x8b,
-                                                     optdata=nonce)]) /
-                    scapy.UDP(sport=sport, dport=53) /
-                    UDP_PAYLOAD)
-        msg = "IPv6 UDP using sticky pktinfo: expected UDP packet on %s" % (
-            self.GetInterfaceName(netid))
-        self.ExpectPacketOn(netid, msg, expected)
-
-  def CheckPktinfoRouting(self, version):
-    for _ in xrange(self.ITERATIONS):
-      for netid in self.tuns:
-        family = self.GetProtocolFamily(version)
-        s = net_test.UDPSocket(family)
-
-        if version == 6:
-          # Create a flowlabel so we can use it.
-          net_test.SetFlowLabel(s, net_test.IPV6_ADDR, 0xbeef)
-
-          # Specify some arbitrary options.
-          cmsgs = [
-              (net_test.SOL_IPV6, IPV6_HOPLIMIT, 39),
-              (net_test.SOL_IPV6, IPV6_TCLASS, 0x83),
-              (net_test.SOL_IPV6, IPV6_FLOWINFO, int(htonl(0xbeef))),
-          ]
-        else:
-          # Support for setting IPv4 TOS and TTL via cmsg only appeared in 3.13.
-          cmsgs = []
-          s.setsockopt(net_test.SOL_IP, IP_TTL, 39)
-          s.setsockopt(net_test.SOL_IP, IP_TOS, 0x83)
-
-        dstaddr = self.GetRemoteAddress(version)
-        self.SendOnNetid(version, s, dstaddr, 53, netid, UDP_PAYLOAD, cmsgs)
-
-        sport = s.getsockname()[1]
-        srcaddr = self.MyAddress(version, netid)
-
-        desc, expected = packets.UDPWithOptions(version, srcaddr, dstaddr,
-                                                sport=sport)
-
-        msg = "IPv%d UDP using pktinfo routing: expected %s on %s" % (
-            version, desc, self.GetInterfaceName(netid))
-        self.ExpectPacketOn(netid, msg, expected)
-
-  def testIPv4PktinfoRouting(self):
-    self.CheckPktinfoRouting(4)
-
-  def testIPv6PktinfoRouting(self):
-    self.CheckPktinfoRouting(6)
-
-
-class MarkTest(InboundMarkingTest):
-
-  def CheckReflection(self, version, gen_packet, gen_reply):
-    """Checks that replies go out on the same interface as the original.
-
-    For each combination:
-     - Calls gen_packet to generate a packet to that IP address.
-     - Writes the packet generated by gen_packet on the given tun
-       interface, causing the kernel to receive it.
-     - Checks that the kernel's reply matches the packet generated by
-       gen_reply.
-
-    Args:
-      version: An integer, 4 or 6.
-      gen_packet: A function taking an IP version (an integer), a source
-        address and a destination address (strings), and returning a scapy
-        packet.
-      gen_reply: A function taking the same arguments as gen_packet,
-        plus a scapy packet, and returning a scapy packet.
-    """
-    for netid, iif, ip_if, myaddr, remoteaddr in self.Combinations(version):
-      # Generate a test packet.
-      desc, packet = gen_packet(version, remoteaddr, myaddr)
-
-      # Test with mark reflection enabled and disabled.
-      for reflect in [0, 1]:
-        self.SetMarkReflectSysctls(reflect)
-        # HACK: IPv6 ping replies always do a routing lookup with the
-        # interface the ping came in on. So even if mark reflection is not
-        # working, IPv6 ping replies will be properly reflected. Don't
-        # fail when that happens.
-        if reflect or desc == "ICMPv6 echo":
-          reply_desc, reply = gen_reply(version, myaddr, remoteaddr, packet)
-        else:
-          reply_desc, reply = None, None
-
-        msg = self._FormatMessage(iif, ip_if, "reflect=%d" % reflect,
-                                  desc, reply_desc)
-        self._ReceiveAndExpectResponse(netid, packet, reply, msg)
-
-  def SYNToClosedPort(self, *args):
-    return packets.SYN(999, *args)
-
-  def testIPv4ICMPErrorsReflectMark(self):
-    self.CheckReflection(4, packets.UDP, packets.ICMPPortUnreachable)
-
-  def testIPv6ICMPErrorsReflectMark(self):
-    self.CheckReflection(6, packets.UDP, packets.ICMPPortUnreachable)
-
-  def testIPv4PingRepliesReflectMarkAndTos(self):
-    self.CheckReflection(4, packets.ICMPEcho, packets.ICMPReply)
-
-  def testIPv6PingRepliesReflectMarkAndTos(self):
-    self.CheckReflection(6, packets.ICMPEcho, packets.ICMPReply)
-
-  def testIPv4RSTsReflectMark(self):
-    self.CheckReflection(4, self.SYNToClosedPort, packets.RST)
-
-  def testIPv6RSTsReflectMark(self):
-    self.CheckReflection(6, self.SYNToClosedPort, packets.RST)
-
-
-class TCPAcceptTest(InboundMarkingTest):
-
-  MODE_BINDTODEVICE = "SO_BINDTODEVICE"
-  MODE_INCOMING_MARK = "incoming mark"
-  MODE_EXPLICIT_MARK = "explicit mark"
-  MODE_UID = "uid"
-
-  @classmethod
-  def setUpClass(cls):
-    super(TCPAcceptTest, cls).setUpClass()
-
-    # Open a port so we can observe SYN+ACKs. Since it's a dual-stack socket it
-    # will accept both IPv4 and IPv6 connections. We do this here instead of in
-    # each test so we can use the same socket every time. That way, if a kernel
-    # bug causes incoming packets to mark the listening socket instead of the
-    # accepted socket, the test will fail as soon as the next address/interface
-    # combination is tried.
-    cls.listenport = 1234
-    cls.listensocket = net_test.IPv6TCPSocket()
-    cls.listensocket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
-    cls.listensocket.bind(("::", cls.listenport))
-    cls.listensocket.listen(100)
-
-  def BounceSocket(self, s):
-    """Attempts to invalidate a socket's destination cache entry."""
-    if s.family == AF_INET:
-      tos = s.getsockopt(SOL_IP, IP_TOS)
-      s.setsockopt(net_test.SOL_IP, IP_TOS, 53)
-      s.setsockopt(net_test.SOL_IP, IP_TOS, tos)
-    else:
-      # UDP, 8 bytes dstopts; PAD1, 4 bytes padding; 4 bytes zeros.
-      pad8 = "".join(["\x11\x00", "\x01\x04", "\x00" * 4])
-      s.setsockopt(net_test.SOL_IPV6, IPV6_DSTOPTS, pad8)
-      s.setsockopt(net_test.SOL_IPV6, IPV6_DSTOPTS, "")
-
-  def _SetTCPMarkAcceptSysctl(self, value):
-    self.SetSysctl(TCP_MARK_ACCEPT_SYSCTL, value)
-
-  def CheckTCPConnection(self, mode, listensocket, netid, version,
-                         myaddr, remoteaddr, packet, reply, msg):
-    establishing_ack = packets.ACK(version, remoteaddr, myaddr, reply)[1]
-
-    # Attempt to confuse the kernel.
-    self.BounceSocket(listensocket)
-
-    self.ReceivePacketOn(netid, establishing_ack)
-
-    # If we're using UID routing, the accept() call has to be run as a UID that
-    # is routed to the specified netid, because the UID of the socket returned
-    # by accept() is the effective UID of the process that calls it. It doesn't
-    # need to be the same UID; any UID that selects the same interface will do.
-    with net_test.RunAsUid(self.UidForNetid(netid)):
-      s, _ = listensocket.accept()
-
-    try:
-      # Check that data sent on the connection goes out on the right interface.
-      desc, data = packets.ACK(version, myaddr, remoteaddr, establishing_ack,
-                               payload=UDP_PAYLOAD)
-      s.send(UDP_PAYLOAD)
-      self.ExpectPacketOn(netid, msg + ": expecting %s" % desc, data)
-      self.BounceSocket(s)
-
-      # Keep up our end of the conversation.
-      ack = packets.ACK(version, remoteaddr, myaddr, data)[1]
-      self.BounceSocket(listensocket)
-      self.ReceivePacketOn(netid, ack)
-
-      mark = self.GetSocketMark(s)
-    finally:
-      self.BounceSocket(s)
-      s.close()
-
-    if mode == self.MODE_INCOMING_MARK:
-      self.assertEquals(netid, mark,
-                        msg + ": Accepted socket: Expected mark %d, got %d" % (
-                            netid, mark))
-    elif mode != self.MODE_EXPLICIT_MARK:
-      self.assertEquals(0, self.GetSocketMark(listensocket))
-
-    # Check the FIN was sent on the right interface, and ack it. We don't expect
-    # this to fail because by the time the connection is established things are
-    # likely working, but a) extra tests are always good and b) extra packets
-    # like the FIN (and retransmitted FINs) could cause later tests that expect
-    # no packets to fail.
-    desc, fin = packets.FIN(version, myaddr, remoteaddr, ack)
-    self.ExpectPacketOn(netid, msg + ": expecting %s after close" % desc, fin)
-
-    desc, finack = packets.FIN(version, remoteaddr, myaddr, fin)
-    self.ReceivePacketOn(netid, finack)
-
-    # Since we called close() earlier, the userspace socket object is gone, so
-    # the socket has no UID. If we're doing UID routing, the ack might be routed
-    # incorrectly. Not much we can do here.
-    desc, finackack = packets.ACK(version, myaddr, remoteaddr, finack)
-    if mode != self.MODE_UID:
-      self.ExpectPacketOn(netid, msg + ": expecting final ack", finackack)
-    else:
-      self.ClearTunQueues()
-
-  def CheckTCP(self, version, modes):
-    """Checks that incoming TCP connections work.
-
-    Args:
-      version: An integer, 4 or 6.
-      modes: A list of modes to excercise.
-    """
-    for syncookies in [0, 2]:
-      for mode in modes:
-        for netid, iif, ip_if, myaddr, remoteaddr in self.Combinations(version):
-          if mode == self.MODE_UID:
-            listensocket = self.BuildSocket(6, net_test.TCPSocket, netid, mode)
-            listensocket.listen(100)
-          else:
-            listensocket = self.listensocket
-
-          listenport = listensocket.getsockname()[1]
-
-          accept_sysctl = 1 if mode == self.MODE_INCOMING_MARK else 0
-          self._SetTCPMarkAcceptSysctl(accept_sysctl)
-
-          bound_dev = iif if mode == self.MODE_BINDTODEVICE else None
-          self.BindToDevice(listensocket, bound_dev)
-
-          mark = netid if mode == self.MODE_EXPLICIT_MARK else 0
-          self.SetSocketMark(listensocket, mark)
-
-          # Generate the packet here instead of in the outer loop, so
-          # subsequent TCP connections use different source ports and
-          # retransmissions from old connections don't confuse subsequent
-          # tests.
-          desc, packet = packets.SYN(listenport, version, remoteaddr, myaddr)
-
-          if mode:
-            reply_desc, reply = packets.SYNACK(version, myaddr, remoteaddr,
-                                               packet)
-          else:
-            reply_desc, reply = None, None
-
-          extra = "mode=%s, syncookies=%d" % (mode, syncookies)
-          msg = self._FormatMessage(iif, ip_if, extra, desc, reply_desc)
-          reply = self._ReceiveAndExpectResponse(netid, packet, reply, msg)
-          if reply:
-            self.CheckTCPConnection(mode, listensocket, netid, version, myaddr,
-                                    remoteaddr, packet, reply, msg)
-
-  def testBasicTCP(self):
-    self.CheckTCP(4, [None, self.MODE_BINDTODEVICE, self.MODE_EXPLICIT_MARK])
-    self.CheckTCP(6, [None, self.MODE_BINDTODEVICE, self.MODE_EXPLICIT_MARK])
-
-  def testIPv4MarkAccept(self):
-    self.CheckTCP(4, [self.MODE_INCOMING_MARK])
-
-  def testIPv6MarkAccept(self):
-    self.CheckTCP(6, [self.MODE_INCOMING_MARK])
-
-  @unittest.skipUnless(multinetwork_base.HAVE_UID_ROUTING, "no UID routes")
-  def testIPv4UidAccept(self):
-    self.CheckTCP(4, [self.MODE_UID])
-
-  @unittest.skipUnless(multinetwork_base.HAVE_UID_ROUTING, "no UID routes")
-  def testIPv6UidAccept(self):
-    self.CheckTCP(6, [self.MODE_UID])
-
-  def testIPv6ExplicitMark(self):
-    self.CheckTCP(6, [self.MODE_EXPLICIT_MARK])
-
-
-class RATest(multinetwork_base.MultiNetworkBaseTest):
-
-  def testDoesNotHaveObsoleteSysctl(self):
-    self.assertFalse(os.path.isfile(
-        "/proc/sys/net/ipv6/route/autoconf_table_offset"))
-
-  @unittest.skipUnless(multinetwork_base.HAVE_AUTOCONF_TABLE,
-                       "no support for per-table autoconf")
-  def testPurgeDefaultRouters(self):
-
-    def CheckIPv6Connectivity(expect_connectivity):
-      for netid in self.NETIDS:
-        s = net_test.UDPSocket(AF_INET6)
-        self.SetSocketMark(s, netid)
-        if expect_connectivity:
-          self.assertTrue(s.sendto(UDP_PAYLOAD, (net_test.IPV6_ADDR, 1234)))
-        else:
-          self.assertRaisesErrno(errno.ENETUNREACH, s.sendto, UDP_PAYLOAD,
-                                 (net_test.IPV6_ADDR, 1234))
-
-    try:
-      CheckIPv6Connectivity(True)
-      self.SetIPv6SysctlOnAllIfaces("accept_ra", 1)
-      self.SetSysctl("/proc/sys/net/ipv6/conf/all/forwarding", 1)
-      CheckIPv6Connectivity(False)
-    finally:
-      self.SetSysctl("/proc/sys/net/ipv6/conf/all/forwarding", 0)
-      for netid in self.NETIDS:
-        self.SendRA(netid)
-      CheckIPv6Connectivity(True)
-
-  def testOnlinkCommunication(self):
-    """Checks that on-link communication goes direct and not through routers."""
-    for netid in self.tuns:
-      # Send a UDP packet to a random on-link destination.
-      s = net_test.UDPSocket(AF_INET6)
-      iface = self.GetInterfaceName(netid)
-      self.BindToDevice(s, iface)
-      # dstaddr can never be our address because GetRandomDestination only fills
-      # in the lower 32 bits, but our address has 0xff in the byte before that
-      # (since it's constructed from the EUI-64 and so has ff:fe in the middle).
-      dstaddr = self.GetRandomDestination(self.IPv6Prefix(netid))
-      s.sendto(UDP_PAYLOAD, (dstaddr, 53))
-
-      # Expect an NS for that destination on the interface.
-      myaddr = self.MyAddress(6, netid)
-      mymac = self.MyMacAddress(netid)
-      desc, expected = packets.NS(myaddr, dstaddr, mymac)
-      msg = "Sending UDP packet to on-link destination: expecting %s" % desc
-      time.sleep(0.0001)  # Required to make the test work on kernel 3.1(!)
-      self.ExpectPacketOn(netid, msg, expected)
-
-      # Send an NA.
-      tgtmac = "02:00:00:00:%02x:99" % netid
-      _, reply = packets.NA(dstaddr, myaddr, tgtmac)
-      # Don't use ReceivePacketOn, since that uses the router's MAC address as
-      # the source. Instead, construct our own Ethernet header with source
-      # MAC of tgtmac.
-      reply = scapy.Ether(src=tgtmac, dst=mymac) / reply
-      self.ReceiveEtherPacketOn(netid, reply)
-
-      # Expect the kernel to send the original UDP packet now that the ND cache
-      # entry has been populated.
-      sport = s.getsockname()[1]
-      desc, expected = packets.UDP(6, myaddr, dstaddr, sport=sport)
-      msg = "After NA response, expecting %s" % desc
-      self.ExpectPacketOn(netid, msg, expected)
-
-  # This test documents a known issue: routing tables are never deleted.
-  @unittest.skipUnless(multinetwork_base.HAVE_AUTOCONF_TABLE,
-                       "no support for per-table autoconf")
-  def testLeftoverRoutes(self):
-    def GetNumRoutes():
-      return len(open("/proc/net/ipv6_route").readlines())
-
-    num_routes = GetNumRoutes()
-    for i in xrange(10, 20):
-      try:
-        self.tuns[i] = self.CreateTunInterface(i)
-        self.SendRA(i)
-        self.tuns[i].close()
-      finally:
-        del self.tuns[i]
-    self.assertLess(num_routes, GetNumRoutes())
-
-
-class PMTUTest(InboundMarkingTest):
-
-  PAYLOAD_SIZE = 1400
-
-  # Socket options to change PMTU behaviour.
-  IP_MTU_DISCOVER = 10
-  IP_PMTUDISC_DO = 1
-  IPV6_DONTFRAG = 62
-
-  # Socket options to get the MTU.
-  IP_MTU = 14
-  IPV6_PATHMTU = 61
-
-  def GetSocketMTU(self, version, s):
-    if version == 6:
-      ip6_mtuinfo = s.getsockopt(net_test.SOL_IPV6, self.IPV6_PATHMTU, 32)
-      unused_sockaddr, mtu = struct.unpack("=28sI", ip6_mtuinfo)
-      return mtu
-    else:
-      return s.getsockopt(net_test.SOL_IP, self.IP_MTU)
-
-  def DisableFragmentationAndReportErrors(self, version, s):
-    if version == 4:
-      s.setsockopt(net_test.SOL_IP, self.IP_MTU_DISCOVER, self.IP_PMTUDISC_DO)
-      s.setsockopt(net_test.SOL_IP, net_test.IP_RECVERR, 1)
-    else:
-      s.setsockopt(net_test.SOL_IPV6, self.IPV6_DONTFRAG, 1)
-      s.setsockopt(net_test.SOL_IPV6, net_test.IPV6_RECVERR, 1)
-
-  def CheckPMTU(self, version, use_connect, modes):
-
-    def SendBigPacket(version, s, dstaddr, netid, payload):
-      if use_connect:
-        s.send(payload)
-      else:
-        self.SendOnNetid(version, s, dstaddr, 1234, netid, payload, [])
-
-    for netid in self.tuns:
-      for mode in modes:
-        s = self.BuildSocket(version, net_test.UDPSocket, netid, mode)
-        self.DisableFragmentationAndReportErrors(version, s)
-
-        srcaddr = self.MyAddress(version, netid)
-        dst_prefix, intermediate = {
-            4: ("172.19.", "172.16.9.12"),
-            6: ("2001:db8::", "2001:db8::1")
-        }[version]
-        dstaddr = self.GetRandomDestination(dst_prefix)
-
-        if use_connect:
-          s.connect((dstaddr, 1234))
-
-        payload = self.PAYLOAD_SIZE * "a"
-
-        # Send a packet and receive a packet too big.
-        SendBigPacket(version, s, dstaddr, netid, payload)
-        received = self.ReadAllPacketsOn(netid)
-        self.assertEquals(1, len(received))
-        _, toobig = packets.ICMPPacketTooBig(version, intermediate, srcaddr,
-                                             received[0])
-        self.ReceivePacketOn(netid, toobig)
-
-        # Check that another send on the same socket returns EMSGSIZE.
-        self.assertRaisesErrno(
-            errno.EMSGSIZE,
-            SendBigPacket, version, s, dstaddr, netid, payload)
-
-        # If this is a connected socket, make sure the socket MTU was set.
-        # Note that in IPv4 this only started working in Linux 3.6!
-        if use_connect and (version == 6 or net_test.LINUX_VERSION >= (3, 6)):
-          self.assertEquals(1280, self.GetSocketMTU(version, s))
-
-        s.close()
-
-        # Check that other sockets pick up the PMTU we have been told about by
-        # connecting another socket to the same destination and getting its MTU.
-        # This new socket can use any method to select its outgoing interface;
-        # here we use a mark for simplicity.
-        s2 = self.BuildSocket(version, net_test.UDPSocket, netid, "mark")
-        s2.connect((dstaddr, 1234))
-        self.assertEquals(1280, self.GetSocketMTU(version, s2))
-
-        # Also check the MTU reported by ip route get, this time using the oif.
-        routes = self.iproute.GetRoutes(dstaddr, self.ifindices[netid], 0, None)
-        self.assertTrue(routes)
-        route = routes[0]
-        rtmsg, attributes = route
-        self.assertEquals(iproute.RTN_UNICAST, rtmsg.type)
-        metrics = attributes["RTA_METRICS"]
-        self.assertEquals(metrics["RTAX_MTU"], 1280)
-
-  def testIPv4BasicPMTU(self):
-    """Tests IPv4 path MTU discovery.
-
-    Relevant kernel commits:
-      upstream net-next:
-        6a66271 ipv4, fib: pass LOOPBACK_IFINDEX instead of 0 to flowi4_iif
-
-      android-3.10:
-        4bc64dd ipv4, fib: pass LOOPBACK_IFINDEX instead of 0 to flowi4_iif
-    """
-
-    self.CheckPMTU(4, True, ["mark", "oif"])
-    self.CheckPMTU(4, False, ["mark", "oif"])
-
-  def testIPv6BasicPMTU(self):
-    self.CheckPMTU(6, True, ["mark", "oif"])
-    self.CheckPMTU(6, False, ["mark", "oif"])
-
-  @unittest.skipUnless(multinetwork_base.HAVE_UID_ROUTING, "no UID routes")
-  def testIPv4UIDPMTU(self):
-    self.CheckPMTU(4, True, ["uid"])
-    self.CheckPMTU(4, False, ["uid"])
-
-  @unittest.skipUnless(multinetwork_base.HAVE_UID_ROUTING, "no UID routes")
-  def testIPv6UIDPMTU(self):
-    self.CheckPMTU(6, True, ["uid"])
-    self.CheckPMTU(6, False, ["uid"])
-
-  # Making Path MTU Discovery work on unmarked  sockets requires that mark
-  # reflection be enabled. Otherwise the kernel has no way to know what routing
-  # table the original packet used, and thus it won't be able to clone the
-  # correct route.
-
-  def testIPv4UnmarkedSocketPMTU(self):
-    self.SetMarkReflectSysctls(1)
-    try:
-      self.CheckPMTU(4, False, [None])
-    finally:
-      self.SetMarkReflectSysctls(0)
-
-  def testIPv6UnmarkedSocketPMTU(self):
-    self.SetMarkReflectSysctls(1)
-    try:
-      self.CheckPMTU(6, False, [None])
-    finally:
-      self.SetMarkReflectSysctls(0)
-
-
-@unittest.skipUnless(multinetwork_base.HAVE_UID_ROUTING, "no UID routes")
-class UidRoutingTest(multinetwork_base.MultiNetworkBaseTest):
-  """Tests that per-UID routing works properly.
-
-  Relevant kernel commits:
-    android-3.4:
-      0b42874 net: core: Support UID-based routing.
-      0836a0c Handle 'sk' being NULL in UID-based routing.
-
-    android-3.10:
-      99a6ea4 net: core: Support UID-based routing.
-      455b09d Handle 'sk' being NULL in UID-based routing.
-  """
-
-  def GetRulesAtPriority(self, version, priority):
-    rules = self.iproute.DumpRules(version)
-    out = [(rule, attributes) for rule, attributes in rules
-           if attributes.get("FRA_PRIORITY", 0) == priority]
-    return out
-
-  def CheckInitialTablesHaveNoUIDs(self, version):
-    rules = []
-    for priority in [0, 32766, 32767]:
-      rules.extend(self.GetRulesAtPriority(version, priority))
-    for _, attributes in rules:
-      self.assertNotIn("FRA_UID_START", attributes)
-      self.assertNotIn("FRA_UID_END", attributes)
-
-  def testIPv4InitialTablesHaveNoUIDs(self):
-    self.CheckInitialTablesHaveNoUIDs(4)
-
-  def testIPv6InitialTablesHaveNoUIDs(self):
-    self.CheckInitialTablesHaveNoUIDs(6)
-
-  def CheckGetAndSetRules(self, version):
-    def Random():
-      return random.randint(1000000, 2000000)
-
-    start, end = tuple(sorted([Random(), Random()]))
-    table = Random()
-    priority = Random()
-
-    try:
-      self.iproute.UidRangeRule(version, True, start, end, table,
-                                priority=priority)
-
-      rules = self.GetRulesAtPriority(version, priority)
-      self.assertTrue(rules)
-      _, attributes = rules[-1]
-      self.assertEquals(priority, attributes["FRA_PRIORITY"])
-      self.assertEquals(start, attributes["FRA_UID_START"])
-      self.assertEquals(end, attributes["FRA_UID_END"])
-      self.assertEquals(table, attributes["FRA_TABLE"])
-    finally:
-      self.iproute.UidRangeRule(version, False, start, end, table,
-                                priority=priority)
-
-  def testIPv4GetAndSetRules(self):
-    self.CheckGetAndSetRules(4)
-
-  def testIPv6GetAndSetRules(self):
-    self.CheckGetAndSetRules(6)
-
-  def ExpectNoRoute(self, addr, oif, mark, uid):
-    # The lack of a route may be either an error, or an unreachable route.
-    try:
-      routes = self.iproute.GetRoutes(addr, oif, mark, uid)
-      rtmsg, _ = routes[0]
-      self.assertEquals(iproute.RTN_UNREACHABLE, rtmsg.type)
-    except IOError, e:
-      if int(e.errno) != -int(errno.ENETUNREACH):
-        raise e
-
-  def ExpectRoute(self, addr, oif, mark, uid):
-    routes = self.iproute.GetRoutes(addr, oif, mark, uid)
-    rtmsg, _ = routes[0]
-    self.assertEquals(iproute.RTN_UNICAST, rtmsg.type)
-
-  def CheckGetRoute(self, version, addr):
-    self.ExpectNoRoute(addr, 0, 0, 0)
-    for netid in self.NETIDS:
-      uid = self.UidForNetid(netid)
-      self.ExpectRoute(addr, 0, 0, uid)
-    self.ExpectNoRoute(addr, 0, 0, 0)
-
-  def testIPv4RouteGet(self):
-    self.CheckGetRoute(4, net_test.IPV4_ADDR)
-
-  def testIPv6RouteGet(self):
-    self.CheckGetRoute(6, net_test.IPV6_ADDR)
-
-
-class RulesTest(net_test.NetworkTest):
-
-  RULE_PRIORITY = 99999
-
-  def setUp(self):
-    self.iproute = iproute.IPRoute()
-    for version in [4, 6]:
-      self.iproute.DeleteRulesAtPriority(version, self.RULE_PRIORITY)
-
-  def tearDown(self):
-    for version in [4, 6]:
-      self.iproute.DeleteRulesAtPriority(version, self.RULE_PRIORITY)
-
-  def testRuleDeletionMatchesTable(self):
-    for version in [4, 6]:
-      # Add rules with mark 300 pointing at tables 301 and 302.
-      # This checks for a kernel bug where deletion request for tables > 256
-      # ignored the table.
-      self.iproute.FwmarkRule(version, True, 300, 301,
-                              priority=self.RULE_PRIORITY)
-      self.iproute.FwmarkRule(version, True, 300, 302,
-                              priority=self.RULE_PRIORITY)
-      # Delete rule with mark 300 pointing at table 302.
-      self.iproute.FwmarkRule(version, False, 300, 302,
-                              priority=self.RULE_PRIORITY)
-      # Check that the rule pointing at table 301 is still around.
-      attributes = [a for _, a in self.iproute.DumpRules(version)
-                    if a.get("FRA_PRIORITY", 0) == self.RULE_PRIORITY]
-      self.assertEquals(1, len(attributes))
-      self.assertEquals(301, attributes[0]["FRA_TABLE"])
-
-
-if __name__ == "__main__":
-  unittest.main()
diff --git a/tests/net_test/neighbour_test.py b/tests/net_test/neighbour_test.py
deleted file mode 100755
index 1e7739e..0000000
--- a/tests/net_test/neighbour_test.py
+++ /dev/null
@@ -1,297 +0,0 @@
-#!/usr/bin/python
-#
-# Copyright 2015 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import errno
-import random
-from socket import *  # pylint: disable=wildcard-import
-import time
-import unittest
-
-from scapy import all as scapy
-
-import multinetwork_base
-import net_test
-
-
-RTMGRP_NEIGH = 4
-
-NUD_INCOMPLETE = 0x01
-NUD_REACHABLE = 0x02
-NUD_STALE = 0x04
-NUD_DELAY = 0x08
-NUD_PROBE = 0x10
-NUD_FAILED = 0x20
-NUD_PERMANENT = 0x80
-
-
-# TODO: Support IPv4.
-class NeighbourTest(multinetwork_base.MultiNetworkBaseTest):
-
-  # Set a 100-ms retrans timer so we can test for ND retransmits without
-  # waiting too long. Apparently this cannot go below 500ms.
-  RETRANS_TIME_MS = 500
-
-  # This can only be in seconds, so 1000 is the minimum.
-  DELAY_TIME_MS = 1000
-
-  # Unfortunately, this must be above the delay timer or the kernel ND code will
-  # not behave correctly (e.g., go straight from REACHABLE into DELAY). This is
-  # is fuzzed by the kernel from 0.5x to 1.5x of its value, so we need a value
-  # that's 2x the delay timer.
-  REACHABLE_TIME_MS = 2 * DELAY_TIME_MS
-
-  @classmethod
-  def setUpClass(cls):
-    super(NeighbourTest, cls).setUpClass()
-    for netid in cls.tuns:
-      iface = cls.GetInterfaceName(netid)
-      # This can't be set in an RA.
-      cls.SetSysctl(
-          "/proc/sys/net/ipv6/neigh/%s/delay_first_probe_time" % iface,
-          cls.DELAY_TIME_MS / 1000)
-
-  def setUp(self):
-    super(NeighbourTest, self).setUp()
-
-    for netid in self.tuns:
-      # Clear the ND cache entries for all routers, so each test starts with
-      # the IPv6 default router in state STALE.
-      addr = self._RouterAddress(netid, 6)
-      ifindex = self.ifindices[netid]
-      self.iproute.UpdateNeighbour(6, addr, None, ifindex, NUD_FAILED)
-
-      # Configure IPv6 by sending an RA.
-      self.SendRA(netid,
-                  retranstimer=self.RETRANS_TIME_MS,
-                  reachabletime=self.REACHABLE_TIME_MS)
-
-    self.sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)
-    self.sock.bind((0, RTMGRP_NEIGH))
-    net_test.SetNonBlocking(self.sock)
-
-    self.netid = random.choice(self.tuns.keys())
-    self.ifindex = self.ifindices[self.netid]
-
-  def GetNeighbour(self, addr):
-    version = 6 if ":" in addr else 4
-    for msg, args in self.iproute.DumpNeighbours(version):
-      if args["NDA_DST"] == addr:
-        return msg, args
-
-  def GetNdEntry(self, addr):
-    return self.GetNeighbour(addr)
-
-  def CheckNoNdEvents(self):
-    self.assertRaisesErrno(errno.EAGAIN, self.sock.recvfrom, 4096, MSG_PEEK)
-
-  def assertNeighbourState(self, state, addr):
-    self.assertEquals(state, self.GetNdEntry(addr)[0].state)
-
-  def assertNeighbourAttr(self, addr, name, value):
-    self.assertEquals(value, self.GetNdEntry(addr)[1][name])
-
-  def ExpectNeighbourNotification(self, addr, state, attrs=None):
-    msg = self.sock.recv(4096)
-    msg, actual_attrs = self.iproute.ParseNeighbourMessage(msg)
-    self.assertEquals(addr, actual_attrs["NDA_DST"])
-    self.assertEquals(state, msg.state)
-    if attrs:
-      for name in attrs:
-        self.assertEquals(attrs[name], actual_attrs[name])
-
-  def ExpectProbe(self, is_unicast, addr):
-    version = 6 if ":" in addr else 4
-    if version == 6:
-      llsrc = self.MyMacAddress(self.netid)
-      if is_unicast:
-        src = self.MyLinkLocalAddress(self.netid)
-        dst = addr
-      else:
-        solicited = inet_pton(AF_INET6, addr)
-        last3bytes = tuple([ord(b) for b in solicited[-3:]])
-        dst = "ff02::1:ff%02x:%02x%02x" % last3bytes
-        src = self.MyAddress(6, self.netid)
-      expected = (
-          scapy.IPv6(src=src, dst=dst) /
-          scapy.ICMPv6ND_NS(tgt=addr) /
-          scapy.ICMPv6NDOptSrcLLAddr(lladdr=llsrc)
-      )
-      msg = "%s probe" % ("Unicast" if is_unicast else "Multicast")
-      self.ExpectPacketOn(self.netid, msg, expected)
-    else:
-      raise NotImplementedError
-
-  def ExpectUnicastProbe(self, addr):
-    self.ExpectProbe(True, addr)
-
-  def ExpectMulticastNS(self, addr):
-    self.ExpectProbe(False, addr)
-
-  def ReceiveUnicastAdvertisement(self, addr, mac, srcaddr=None, dstaddr=None,
-                                  S=1, O=0, R=1):
-    version = 6 if ":" in addr else 4
-    if srcaddr is None:
-      srcaddr = addr
-    if dstaddr is None:
-      dstaddr = self.MyLinkLocalAddress(self.netid)
-    if version == 6:
-      packet = (
-          scapy.Ether(src=mac, dst=self.MyMacAddress(self.netid)) /
-          scapy.IPv6(src=srcaddr, dst=dstaddr) /
-          scapy.ICMPv6ND_NA(tgt=addr, S=S, O=O, R=R) /
-          scapy.ICMPv6NDOptDstLLAddr(lladdr=mac)
-      )
-      self.ReceiveEtherPacketOn(self.netid, packet)
-    else:
-      raise NotImplementedError
-
-  def MonitorSleepMs(self, interval, addr):
-    slept = 0
-    while slept < interval:
-      sleep_ms = min(100, interval - slept)
-      time.sleep(sleep_ms / 1000.0)
-      slept += sleep_ms
-      print self.GetNdEntry(addr)
-
-  def MonitorSleep(self, intervalseconds, addr):
-    self.MonitorSleepMs(intervalseconds * 1000, addr)
-
-  def SleepMs(self, ms):
-    time.sleep(ms / 1000.0)
-
-  def testNotifications(self):
-    """Tests neighbour notifications.
-
-    Relevant kernel commits:
-      upstream net-next:
-        765c9c6 neigh: Better handling of transition to NUD_PROBE state
-        53385d2 neigh: Netlink notification for administrative NUD state change
-          (only checked on kernel v3.13+, not on v3.10)
-
-      android-3.10:
-        e4a6d6b neigh: Better handling of transition to NUD_PROBE state
-
-      android-3.18:
-        2011e72 neigh: Better handling of transition to NUD_PROBE state
-    """
-
-    router4 = self._RouterAddress(self.netid, 4)
-    router6 = self._RouterAddress(self.netid, 6)
-    self.assertNeighbourState(NUD_PERMANENT, router4)
-    self.assertNeighbourState(NUD_STALE, router6)
-
-    # Send a packet and check that we go into DELAY.
-    routing_mode = random.choice(["mark", "oif", "uid"])
-    s = self.BuildSocket(6, net_test.UDPSocket, self.netid, routing_mode)
-    s.connect((net_test.IPV6_ADDR, 53))
-    s.send(net_test.UDP_PAYLOAD)
-    self.assertNeighbourState(NUD_DELAY, router6)
-
-    # Wait for the probe interval, then check that we're in PROBE, and that the
-    # kernel has notified us.
-    self.SleepMs(self.DELAY_TIME_MS)
-    self.ExpectNeighbourNotification(router6, NUD_PROBE)
-    self.assertNeighbourState(NUD_PROBE, router6)
-    self.ExpectUnicastProbe(router6)
-
-    # Respond to the NS and verify we're in REACHABLE again.
-    self.ReceiveUnicastAdvertisement(router6, self.RouterMacAddress(self.netid))
-    self.assertNeighbourState(NUD_REACHABLE, router6)
-    if net_test.LINUX_VERSION >= (3, 13, 0):
-      # commit 53385d2 (v3.13) "neigh: Netlink notification for administrative
-      # NUD state change" produces notifications for NUD_REACHABLE, but these
-      # are not generated on earlier kernels.
-      self.ExpectNeighbourNotification(router6, NUD_REACHABLE)
-
-    # Wait until the reachable time has passed, and verify we're in STALE.
-    self.SleepMs(self.REACHABLE_TIME_MS * 1.5)
-    self.assertNeighbourState(NUD_STALE, router6)
-    self.ExpectNeighbourNotification(router6, NUD_STALE)
-
-    # Send a packet, and verify we go into DELAY and then to PROBE.
-    s.send(net_test.UDP_PAYLOAD)
-    self.assertNeighbourState(NUD_DELAY, router6)
-    self.SleepMs(self.DELAY_TIME_MS)
-    self.assertNeighbourState(NUD_PROBE, router6)
-    self.ExpectNeighbourNotification(router6, NUD_PROBE)
-
-    # Wait for the probes to time out, and expect a FAILED notification.
-    self.assertNeighbourAttr(router6, "NDA_PROBES", 1)
-    self.ExpectUnicastProbe(router6)
-
-    self.SleepMs(self.RETRANS_TIME_MS)
-    self.ExpectUnicastProbe(router6)
-    self.assertNeighbourAttr(router6, "NDA_PROBES", 2)
-
-    self.SleepMs(self.RETRANS_TIME_MS)
-    self.ExpectUnicastProbe(router6)
-    self.assertNeighbourAttr(router6, "NDA_PROBES", 3)
-
-    self.SleepMs(self.RETRANS_TIME_MS)
-    self.assertNeighbourState(NUD_FAILED, router6)
-    self.ExpectNeighbourNotification(router6, NUD_FAILED, {"NDA_PROBES": 3})
-
-  def testRepeatedProbes(self):
-    router4 = self._RouterAddress(self.netid, 4)
-    router6 = self._RouterAddress(self.netid, 6)
-    routermac = self.RouterMacAddress(self.netid)
-    self.assertNeighbourState(NUD_PERMANENT, router4)
-    self.assertNeighbourState(NUD_STALE, router6)
-
-    def ForceProbe(addr, mac):
-      self.iproute.UpdateNeighbour(6, addr, None, self.ifindex, NUD_PROBE)
-      self.assertNeighbourState(NUD_PROBE, addr)
-      self.SleepMs(1)  # TODO: Why is this necessary?
-      self.assertNeighbourState(NUD_PROBE, addr)
-      self.ExpectUnicastProbe(addr)
-      self.ReceiveUnicastAdvertisement(addr, mac)
-      self.assertNeighbourState(NUD_REACHABLE, addr)
-
-    for _ in xrange(5):
-      ForceProbe(router6, routermac)
-
-  def testIsRouterFlag(self):
-    router6 = self._RouterAddress(self.netid, 6)
-    self.assertNeighbourState(NUD_STALE, router6)
-
-    # Get into FAILED.
-    ifindex = self.ifindices[self.netid]
-    self.iproute.UpdateNeighbour(6, router6, None, ifindex, NUD_FAILED)
-    self.ExpectNeighbourNotification(router6, NUD_FAILED)
-    self.assertNeighbourState(NUD_FAILED, router6)
-
-    time.sleep(1)
-
-    # Send another packet and expect a multicast NS.
-    routing_mode = random.choice(["mark", "oif", "uid"])
-    s = self.BuildSocket(6, net_test.UDPSocket, self.netid, routing_mode)
-    s.connect((net_test.IPV6_ADDR, 53))
-    s.send(net_test.UDP_PAYLOAD)
-    self.ExpectMulticastNS(router6)
-
-    # Receive a unicast NA with the R flag set to 0.
-    self.ReceiveUnicastAdvertisement(router6, self.RouterMacAddress(self.netid),
-                                     srcaddr=self._RouterAddress(self.netid, 6),
-                                     dstaddr=self.MyAddress(6, self.netid),
-                                     S=1, O=0, R=0)
-
-    # Expect that this takes us to REACHABLE.
-    self.ExpectNeighbourNotification(router6, NUD_REACHABLE)
-    self.assertNeighbourState(NUD_REACHABLE, router6)
-
-
-if __name__ == "__main__":
-  unittest.main()
diff --git a/tests/net_test/net_test.py b/tests/net_test/net_test.py
deleted file mode 100755
index d7ea013..0000000
--- a/tests/net_test/net_test.py
+++ /dev/null
@@ -1,394 +0,0 @@
-#!/usr/bin/python
-#
-# Copyright 2014 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import fcntl
-import os
-import random
-import re
-from socket import *  # pylint: disable=wildcard-import
-import struct
-import unittest
-
-from scapy import all as scapy
-
-SOL_IPV6 = 41
-IP_RECVERR = 11
-IPV6_RECVERR = 25
-IP_TRANSPARENT = 19
-IPV6_TRANSPARENT = 75
-IPV6_TCLASS = 67
-IPV6_FLOWLABEL_MGR = 32
-IPV6_FLOWINFO_SEND = 33
-
-SO_BINDTODEVICE = 25
-SO_MARK = 36
-SO_PROTOCOL = 38
-SO_DOMAIN = 39
-
-ETH_P_IP = 0x0800
-ETH_P_IPV6 = 0x86dd
-
-IPPROTO_GRE = 47
-
-SIOCSIFHWADDR = 0x8924
-
-IPV6_FL_A_GET = 0
-IPV6_FL_A_PUT = 1
-IPV6_FL_A_RENEW = 1
-
-IPV6_FL_F_CREATE = 1
-IPV6_FL_F_EXCL = 2
-
-IPV6_FL_S_NONE = 0
-IPV6_FL_S_EXCL = 1
-IPV6_FL_S_ANY = 255
-
-IFNAMSIZ = 16
-
-IPV4_PING = "\x08\x00\x00\x00\x0a\xce\x00\x03"
-IPV6_PING = "\x80\x00\x00\x00\x0a\xce\x00\x03"
-
-IPV4_ADDR = "8.8.8.8"
-IPV6_ADDR = "2001:4860:4860::8888"
-
-IPV6_SEQ_DGRAM_HEADER = ("  sl  "
-                         "local_address                         "
-                         "remote_address                        "
-                         "st tx_queue rx_queue tr tm->when retrnsmt"
-                         "   uid  timeout inode ref pointer drops\n")
-
-# Arbitrary packet payload.
-UDP_PAYLOAD = str(scapy.DNS(rd=1,
-                            id=random.randint(0, 65535),
-                            qd=scapy.DNSQR(qname="wWW.GoOGle.CoM",
-                                           qtype="AAAA")))
-
-# Unix group to use if we want to open sockets as non-root.
-AID_INET = 3003
-
-
-def LinuxVersion():
-  # Example: "3.4.67-00753-gb7a556f".
-  # Get the part before the dash.
-  version = os.uname()[2].split("-")[0]
-  # Convert it into a tuple such as (3, 4, 67). That allows comparing versions
-  # using < and >, since tuples are compared lexicographically.
-  version = tuple(int(i) for i in version.split("."))
-  return version
-
-
-LINUX_VERSION = LinuxVersion()
-
-
-def SetSocketTimeout(sock, ms):
-  s = ms / 1000
-  us = (ms % 1000) * 1000
-  sock.setsockopt(SOL_SOCKET, SO_RCVTIMEO, struct.pack("LL", s, us))
-
-
-def SetSocketTos(s, tos):
-  level = {AF_INET: SOL_IP, AF_INET6: SOL_IPV6}[s.family]
-  option = {AF_INET: IP_TOS, AF_INET6: IPV6_TCLASS}[s.family]
-  s.setsockopt(level, option, tos)
-
-
-def SetNonBlocking(fd):
-  flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0)
-  fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)
-
-
-# Convenience functions to create sockets.
-def Socket(family, sock_type, protocol):
-  s = socket(family, sock_type, protocol)
-  SetSocketTimeout(s, 1000)
-  return s
-
-
-def PingSocket(family):
-  proto = {AF_INET: IPPROTO_ICMP, AF_INET6: IPPROTO_ICMPV6}[family]
-  return Socket(family, SOCK_DGRAM, proto)
-
-
-def IPv4PingSocket():
-  return PingSocket(AF_INET)
-
-
-def IPv6PingSocket():
-  return PingSocket(AF_INET6)
-
-
-def TCPSocket(family):
-  s = Socket(family, SOCK_STREAM, IPPROTO_TCP)
-  SetNonBlocking(s.fileno())
-  return s
-
-
-def IPv4TCPSocket():
-  return TCPSocket(AF_INET)
-
-
-def IPv6TCPSocket():
-  return TCPSocket(AF_INET6)
-
-
-def UDPSocket(family):
-  return Socket(family, SOCK_DGRAM, IPPROTO_UDP)
-
-
-def RawGRESocket(family):
-  s = Socket(family, SOCK_RAW, IPPROTO_GRE)
-  return s
-
-
-def DisableLinger(sock):
-  sock.setsockopt(SOL_SOCKET, SO_LINGER, struct.pack("ii", 1, 0))
-
-
-def CreateSocketPair(family, socktype, addr):
-  clientsock = socket(family, socktype, 0)
-  listensock = socket(family, socktype, 0)
-  listensock.bind((addr, 0))
-  addr = listensock.getsockname()
-  listensock.listen(1)
-  clientsock.connect(addr)
-  acceptedsock, _ = listensock.accept()
-  DisableLinger(clientsock)
-  DisableLinger(acceptedsock)
-  listensock.close()
-  return clientsock, acceptedsock
-
-
-def GetInterfaceIndex(ifname):
-  s = IPv4PingSocket()
-  ifr = struct.pack("%dsi" % IFNAMSIZ, ifname, 0)
-  ifr = fcntl.ioctl(s, scapy.SIOCGIFINDEX, ifr)
-  return struct.unpack("%dsi" % IFNAMSIZ, ifr)[1]
-
-
-def SetInterfaceHWAddr(ifname, hwaddr):
-  s = IPv4PingSocket()
-  hwaddr = hwaddr.replace(":", "")
-  hwaddr = hwaddr.decode("hex")
-  if len(hwaddr) != 6:
-    raise ValueError("Unknown hardware address length %d" % len(hwaddr))
-  ifr = struct.pack("%dsH6s" % IFNAMSIZ, ifname, scapy.ARPHDR_ETHER, hwaddr)
-  fcntl.ioctl(s, SIOCSIFHWADDR, ifr)
-
-
-def SetInterfaceState(ifname, up):
-  s = IPv4PingSocket()
-  ifr = struct.pack("%dsH" % IFNAMSIZ, ifname, 0)
-  ifr = fcntl.ioctl(s, scapy.SIOCGIFFLAGS, ifr)
-  _, flags = struct.unpack("%dsH" % IFNAMSIZ, ifr)
-  if up:
-    flags |= scapy.IFF_UP
-  else:
-    flags &= ~scapy.IFF_UP
-  ifr = struct.pack("%dsH" % IFNAMSIZ, ifname, flags)
-  ifr = fcntl.ioctl(s, scapy.SIOCSIFFLAGS, ifr)
-
-
-def SetInterfaceUp(ifname):
-  return SetInterfaceState(ifname, True)
-
-
-def SetInterfaceDown(ifname):
-  return SetInterfaceState(ifname, False)
-
-
-def FormatProcAddress(unformatted):
-  groups = []
-  for i in xrange(0, len(unformatted), 4):
-    groups.append(unformatted[i:i+4])
-  formatted = ":".join(groups)
-  # Compress the address.
-  address = inet_ntop(AF_INET6, inet_pton(AF_INET6, formatted))
-  return address
-
-
-def FormatSockStatAddress(address):
-  if ":" in address:
-    family = AF_INET6
-  else:
-    family = AF_INET
-  binary = inet_pton(family, address)
-  out = ""
-  for i in xrange(0, len(binary), 4):
-    out += "%08X" % struct.unpack("=L", binary[i:i+4])
-  return out
-
-
-def GetLinkAddress(ifname, linklocal):
-  addresses = open("/proc/net/if_inet6").readlines()
-  for address in addresses:
-    address = [s for s in address.strip().split(" ") if s]
-    if address[5] == ifname:
-      if (linklocal and address[0].startswith("fe80")
-          or not linklocal and not address[0].startswith("fe80")):
-        # Convert the address from raw hex to something with colons in it.
-        return FormatProcAddress(address[0])
-  return None
-
-
-def GetDefaultRoute(version=6):
-  if version == 6:
-    routes = open("/proc/net/ipv6_route").readlines()
-    for route in routes:
-      route = [s for s in route.strip().split(" ") if s]
-      if (route[0] == "00000000000000000000000000000000" and route[1] == "00"
-          # Routes in non-default tables end up in /proc/net/ipv6_route!!!
-          and route[9] != "lo" and not route[9].startswith("nettest")):
-        return FormatProcAddress(route[4]), route[9]
-    raise ValueError("No IPv6 default route found")
-  elif version == 4:
-    routes = open("/proc/net/route").readlines()
-    for route in routes:
-      route = [s for s in route.strip().split("\t") if s]
-      if route[1] == "00000000" and route[7] == "00000000":
-        gw, iface = route[2], route[0]
-        gw = inet_ntop(AF_INET, gw.decode("hex")[::-1])
-        return gw, iface
-    raise ValueError("No IPv4 default route found")
-  else:
-    raise ValueError("Don't know about IPv%s" % version)
-
-
-def GetDefaultRouteInterface():
-  unused_gw, iface = GetDefaultRoute()
-  return iface
-
-
-def MakeFlowLabelOption(addr, label):
-  # struct in6_flowlabel_req {
-  #         struct in6_addr flr_dst;
-  #         __be32  flr_label;
-  #         __u8    flr_action;
-  #         __u8    flr_share;
-  #         __u16   flr_flags;
-  #         __u16   flr_expires;
-  #         __u16   flr_linger;
-  #         __u32   __flr_pad;
-  #         /* Options in format of IPV6_PKTOPTIONS */
-  # };
-  fmt = "16sIBBHHH4s"
-  assert struct.calcsize(fmt) == 32
-  addr = inet_pton(AF_INET6, addr)
-  assert len(addr) == 16
-  label = htonl(label & 0xfffff)
-  action = IPV6_FL_A_GET
-  share = IPV6_FL_S_ANY
-  flags = IPV6_FL_F_CREATE
-  pad = "\x00" * 4
-  return struct.pack(fmt, addr, label, action, share, flags, 0, 0, pad)
-
-
-def SetFlowLabel(s, addr, label):
-  opt = MakeFlowLabelOption(addr, label)
-  s.setsockopt(SOL_IPV6, IPV6_FLOWLABEL_MGR, opt)
-  # Caller also needs to do s.setsockopt(SOL_IPV6, IPV6_FLOWINFO_SEND, 1).
-
-
-# Determine network configuration.
-try:
-  GetDefaultRoute(version=4)
-  HAVE_IPV4 = True
-except ValueError:
-  HAVE_IPV4 = False
-
-try:
-  GetDefaultRoute(version=6)
-  HAVE_IPV6 = True
-except ValueError:
-  HAVE_IPV6 = False
-
-
-class RunAsUid(object):
-  """Context guard to run a code block as a given UID."""
-
-  def __init__(self, uid):
-    self.uid = uid
-
-  def __enter__(self):
-    if self.uid:
-      self.saved_uid = os.geteuid()
-      self.saved_groups = os.getgroups()
-      if self.uid:
-        os.setgroups(self.saved_groups + [AID_INET])
-        os.seteuid(self.uid)
-
-  def __exit__(self, unused_type, unused_value, unused_traceback):
-    if self.uid:
-      os.seteuid(self.saved_uid)
-      os.setgroups(self.saved_groups)
-
-
-class NetworkTest(unittest.TestCase):
-
-  def assertRaisesErrno(self, err_num, f, *args):
-    msg = os.strerror(err_num)
-    self.assertRaisesRegexp(EnvironmentError, msg, f, *args)
-
-  def ReadProcNetSocket(self, protocol):
-    # Read file.
-    filename = "/proc/net/%s" % protocol
-    lines = open(filename).readlines()
-
-    # Possibly check, and strip, header.
-    if protocol in ["icmp6", "raw6", "udp6"]:
-      self.assertEqual(IPV6_SEQ_DGRAM_HEADER, lines[0])
-    lines = lines[1:]
-
-    # Check contents.
-    if protocol.endswith("6"):
-      addrlen = 32
-    else:
-      addrlen = 8
-
-    if protocol.startswith("tcp"):
-      # Real sockets have 5 extra numbers, timewait sockets have none.
-      end_regexp = "(| +[0-9]+ [0-9]+ [0-9]+ [0-9]+ -?[0-9]+|)$"
-    elif re.match("icmp|udp|raw", protocol):
-      # Drops.
-      end_regexp = " +([0-9]+) *$"
-    else:
-      raise ValueError("Don't know how to parse %s" % filename)
-
-    regexp = re.compile(r" *(\d+): "                    # bucket
-                        "([0-9A-F]{%d}:[0-9A-F]{4}) "   # srcaddr, port
-                        "([0-9A-F]{%d}:[0-9A-F]{4}) "   # dstaddr, port
-                        "([0-9A-F][0-9A-F]) "           # state
-                        "([0-9A-F]{8}:[0-9A-F]{8}) "    # mem
-                        "([0-9A-F]{2}:[0-9A-F]{8}) "    # ?
-                        "([0-9A-F]{8}) +"               # ?
-                        "([0-9]+) +"                    # uid
-                        "([0-9]+) +"                    # timeout
-                        "([0-9]+) +"                    # inode
-                        "([0-9]+) +"                    # refcnt
-                        "([0-9a-f]+)"                   # sp
-                        "%s"                            # icmp has spaces
-                        % (addrlen, addrlen, end_regexp))
-    # Return a list of lists with only source / dest addresses for now.
-    # TODO: consider returning a dict or namedtuple instead.
-    out = []
-    for line in lines:
-      (_, src, dst, state, mem,
-       _, _, uid, _, _, refcnt, _, extra) = regexp.match(line).groups()
-      out.append([src, dst, state, mem, uid, refcnt, extra])
-    return out
-
-
-if __name__ == "__main__":
-  unittest.main()
diff --git a/tests/net_test/net_test.sh b/tests/net_test/net_test.sh
deleted file mode 100755
index acac660..0000000
--- a/tests/net_test/net_test.sh
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-
-# In case IPv6 is compiled as a module.
-[ -f /proc/net/if_inet6 ] || insmod $DIR/kernel/net-next/net/ipv6/ipv6.ko
-
-# Minimal network setup.
-ip link set lo up
-ip link set lo mtu 16436
-ip link set eth0 up
-
-# Allow people to run ping.
-echo "0 65536" > /proc/sys/net/ipv4/ping_group_range
-
-# Fall out to a shell once the test completes or if there's an error.
-trap "exec /bin/bash" ERR EXIT
-
-# Find and run the test.
-test=$(cat /proc/cmdline | sed -re 's/.*net_test=([^ ]*).*/\1/g')
-echo -e "Running $test\n"
-$test
diff --git a/tests/net_test/netlink.py b/tests/net_test/netlink.py
deleted file mode 100644
index 2b8f744..0000000
--- a/tests/net_test/netlink.py
+++ /dev/null
@@ -1,255 +0,0 @@
-#!/usr/bin/python
-#
-# Copyright 2014 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-"""Partial Python implementation of iproute functionality."""
-
-# pylint: disable=g-bad-todo
-
-import errno
-import os
-import socket
-import struct
-import sys
-
-import cstruct
-
-
-# Request constants.
-NLM_F_REQUEST = 1
-NLM_F_ACK = 4
-NLM_F_REPLACE = 0x100
-NLM_F_EXCL = 0x200
-NLM_F_CREATE = 0x400
-NLM_F_DUMP = 0x300
-
-# Message types.
-NLMSG_ERROR = 2
-NLMSG_DONE = 3
-
-# Data structure formats.
-# These aren't constants, they're classes. So, pylint: disable=invalid-name
-NLMsgHdr = cstruct.Struct("NLMsgHdr", "=LHHLL", "length type flags seq pid")
-NLMsgErr = cstruct.Struct("NLMsgErr", "=i", "error")
-NLAttr = cstruct.Struct("NLAttr", "=HH", "nla_len nla_type")
-
-# Alignment / padding.
-NLA_ALIGNTO = 4
-
-
-def PaddedLength(length):
-  # TODO: This padding is probably overly simplistic.
-  return NLA_ALIGNTO * ((length / NLA_ALIGNTO) + (length % NLA_ALIGNTO != 0))
-
-
-class NetlinkSocket(object):
-  """A basic netlink socket object."""
-
-  BUFSIZE = 65536
-  DEBUG = False
-  # List of netlink messages to print, e.g., [], ["NEIGH", "ROUTE"], or ["ALL"]
-  NL_DEBUG = []
-
-  def _Debug(self, s):
-    if self.DEBUG:
-      print s
-
-  def _NlAttr(self, nla_type, data):
-    datalen = len(data)
-    # Pad the data if it's not a multiple of NLA_ALIGNTO bytes long.
-    padding = "\x00" * (PaddedLength(datalen) - datalen)
-    nla_len = datalen + len(NLAttr)
-    return NLAttr((nla_len, nla_type)).Pack() + data + padding
-
-  def _NlAttrU32(self, nla_type, value):
-    return self._NlAttr(nla_type, struct.pack("=I", value))
-
-  def _GetConstantName(self, module, value, prefix):
-    thismodule = sys.modules[module]
-    for name in dir(thismodule):
-      if name.startswith("INET_DIAG_BC"):
-        break
-      if (name.startswith(prefix) and
-          not name.startswith(prefix + "F_") and
-          name.isupper() and getattr(thismodule, name) == value):
-          return name
-    return value
-
-  def _Decode(self, command, msg, nla_type, nla_data):
-    """No-op, nonspecific version of decode."""
-    return nla_type, nla_data
-
-  def _ParseAttributes(self, command, family, msg, data):
-    """Parses and decodes netlink attributes.
-
-    Takes a block of NLAttr data structures, decodes them using Decode, and
-    returns the result in a dict keyed by attribute number.
-
-    Args:
-      command: An integer, the rtnetlink command being carried out.
-      family: The address family.
-      msg: A Struct, the type of the data after the netlink header.
-      data: A byte string containing a sequence of NLAttr data structures.
-
-    Returns:
-      A dictionary mapping attribute types (integers) to decoded values.
-
-    Raises:
-      ValueError: There was a duplicate attribute type.
-    """
-    attributes = {}
-    while data:
-      # Read the nlattr header.
-      nla, data = cstruct.Read(data, NLAttr)
-
-      # Read the data.
-      datalen = nla.nla_len - len(nla)
-      padded_len = PaddedLength(nla.nla_len) - len(nla)
-      nla_data, data = data[:datalen], data[padded_len:]
-
-      # If it's an attribute we know about, try to decode it.
-      nla_name, nla_data = self._Decode(command, msg, nla.nla_type, nla_data)
-
-      # We only support unique attributes for now, except for INET_DIAG_NONE,
-      # which can appear more than once but doesn't seem to contain any data.
-      if nla_name in attributes and nla_name != "INET_DIAG_NONE":
-        raise ValueError("Duplicate attribute %s" % nla_name)
-
-      attributes[nla_name] = nla_data
-      self._Debug("      %s" % str((nla_name, nla_data)))
-
-    return attributes
-
-  def __init__(self):
-    # Global sequence number.
-    self.seq = 0
-    self.sock = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, self.FAMILY)
-    self.sock.connect((0, 0))  # The kernel.
-    self.pid = self.sock.getsockname()[1]
-
-  def _Send(self, msg):
-    # self._Debug(msg.encode("hex"))
-    self.seq += 1
-    self.sock.send(msg)
-
-  def _Recv(self):
-    data = self.sock.recv(self.BUFSIZE)
-    # self._Debug(data.encode("hex"))
-    return data
-
-  def _ExpectDone(self):
-    response = self._Recv()
-    hdr = NLMsgHdr(response)
-    if hdr.type != NLMSG_DONE:
-      raise ValueError("Expected DONE, got type %d" % hdr.type)
-
-  def _ParseAck(self, response):
-    # Find the error code.
-    hdr, data = cstruct.Read(response, NLMsgHdr)
-    if hdr.type == NLMSG_ERROR:
-      error = NLMsgErr(data).error
-      if error:
-        raise IOError(error, os.strerror(-error))
-    else:
-      raise ValueError("Expected ACK, got type %d" % hdr.type)
-
-  def _ExpectAck(self):
-    response = self._Recv()
-    self._ParseAck(response)
-
-  def _SendNlRequest(self, command, data, flags):
-    """Sends a netlink request and expects an ack."""
-    length = len(NLMsgHdr) + len(data)
-    nlmsg = NLMsgHdr((length, command, flags, self.seq, self.pid)).Pack()
-
-    self.MaybeDebugCommand(command, nlmsg + data)
-
-    # Send the message.
-    self._Send(nlmsg + data)
-
-    if flags & NLM_F_ACK:
-      self._ExpectAck()
-
-  def _ParseNLMsg(self, data, msgtype):
-    """Parses a Netlink message into a header and a dictionary of attributes."""
-    nlmsghdr, data = cstruct.Read(data, NLMsgHdr)
-    self._Debug("  %s" % nlmsghdr)
-
-    if nlmsghdr.type == NLMSG_ERROR or nlmsghdr.type == NLMSG_DONE:
-      print "done"
-      return (None, None), data
-
-    nlmsg, data = cstruct.Read(data, msgtype)
-    self._Debug("    %s" % nlmsg)
-
-    # Parse the attributes in the nlmsg.
-    attrlen = nlmsghdr.length - len(nlmsghdr) - len(nlmsg)
-    attributes = self._ParseAttributes(nlmsghdr.type, nlmsg.family,
-                                       nlmsg, data[:attrlen])
-    data = data[attrlen:]
-    return (nlmsg, attributes), data
-
-  def _GetMsg(self, msgtype):
-    data = self._Recv()
-    if NLMsgHdr(data).type == NLMSG_ERROR:
-      self._ParseAck(data)
-    return self._ParseNLMsg(data, msgtype)[0]
-
-  def _GetMsgList(self, msgtype, data, expect_done):
-    out = []
-    while data:
-      msg, data = self._ParseNLMsg(data, msgtype)
-      if msg is None:
-        break
-      out.append(msg)
-    if expect_done:
-      self._ExpectDone()
-    return out
-
-  def _Dump(self, command, msg, msgtype, attrs):
-    """Sends a dump request and returns a list of decoded messages.
-
-    Args:
-      command: An integer, the command to run (e.g., RTM_NEWADDR).
-      msg: A string, the raw bytes of the request (e.g., a packed RTMsg).
-      msgtype: A cstruct.Struct, the data type to parse the dump results as.
-      attrs: A string, the raw bytes of any request attributes to include.
-
-    Returns:
-      A list of (msg, attrs) tuples where msg is of type msgtype and attrs is
-      a dict of attributes.
-    """
-    # Create a netlink dump request containing the msg.
-    flags = NLM_F_DUMP | NLM_F_REQUEST
-    length = len(NLMsgHdr) + len(msg) + len(attrs)
-    nlmsghdr = NLMsgHdr((length, command, flags, self.seq, self.pid))
-
-    # Send the request.
-    self._Send(nlmsghdr.Pack() + msg.Pack() + attrs)
-
-    # Keep reading netlink messages until we get a NLMSG_DONE.
-    out = []
-    while True:
-      data = self._Recv()
-      response_type = NLMsgHdr(data).type
-      if response_type == NLMSG_DONE:
-        break
-      elif response_type == NLMSG_ERROR:
-        # Likely means that the kernel didn't like our dump request.
-        # Parse the error and throw an exception.
-        self._ParseAck(data)
-      out.extend(self._GetMsgList(msgtype, data, False))
-
-    return out
diff --git a/tests/net_test/packets.py b/tests/net_test/packets.py
deleted file mode 100644
index c02adc0..0000000
--- a/tests/net_test/packets.py
+++ /dev/null
@@ -1,197 +0,0 @@
-#!/usr/bin/python
-#
-# Copyright 2015 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import random
-
-from scapy import all as scapy
-from socket import *
-
-import net_test
-
-TCP_FIN = 1
-TCP_SYN = 2
-TCP_RST = 4
-TCP_PSH = 8
-TCP_ACK = 16
-
-TCP_SEQ = 1692871236
-TCP_WINDOW = 14400
-
-PING_IDENT = 0xff19
-PING_PAYLOAD = "foobarbaz"
-PING_SEQ = 3
-PING_TOS = 0x83
-
-# For brevity.
-UDP_PAYLOAD = net_test.UDP_PAYLOAD
-
-
-def RandomPort():
-  return random.randint(1025, 65535)
-
-def _GetIpLayer(version):
-  return {4: scapy.IP, 6: scapy.IPv6}[version]
-
-def _SetPacketTos(packet, tos):
-  if isinstance(packet, scapy.IPv6):
-    packet.tc = tos
-  elif isinstance(packet, scapy.IP):
-    packet.tos = tos
-  else:
-    raise ValueError("Can't find ToS Field")
-
-def UDP(version, srcaddr, dstaddr, sport=0):
-  ip = _GetIpLayer(version)
-  # Can't just use "if sport" because None has meaning (it means unspecified).
-  if sport == 0:
-    sport = RandomPort()
-  return ("UDPv%d packet" % version,
-          ip(src=srcaddr, dst=dstaddr) /
-          scapy.UDP(sport=sport, dport=53) / UDP_PAYLOAD)
-
-def UDPWithOptions(version, srcaddr, dstaddr, sport=0):
-  if version == 4:
-    packet = (scapy.IP(src=srcaddr, dst=dstaddr, ttl=39, tos=0x83) /
-              scapy.UDP(sport=sport, dport=53) /
-              UDP_PAYLOAD)
-  else:
-    packet = (scapy.IPv6(src=srcaddr, dst=dstaddr,
-                         fl=0xbeef, hlim=39, tc=0x83) /
-              scapy.UDP(sport=sport, dport=53) /
-              UDP_PAYLOAD)
-  return ("UDPv%d packet with options" % version, packet)
-
-def SYN(dport, version, srcaddr, dstaddr, sport=0, seq=TCP_SEQ):
-  ip = _GetIpLayer(version)
-  if sport == 0:
-    sport = RandomPort()
-  return ("TCP SYN",
-          ip(src=srcaddr, dst=dstaddr) /
-          scapy.TCP(sport=sport, dport=dport,
-                    seq=seq, ack=0,
-                    flags=TCP_SYN, window=TCP_WINDOW))
-
-def RST(version, srcaddr, dstaddr, packet):
-  ip = _GetIpLayer(version)
-  original = packet.getlayer("TCP")
-  was_syn_or_fin = (original.flags & (TCP_SYN | TCP_FIN)) != 0
-  return ("TCP RST",
-          ip(src=srcaddr, dst=dstaddr) /
-          scapy.TCP(sport=original.dport, dport=original.sport,
-                    ack=original.seq + was_syn_or_fin, seq=None,
-                    flags=TCP_RST | TCP_ACK, window=TCP_WINDOW))
-
-def SYNACK(version, srcaddr, dstaddr, packet):
-  ip = _GetIpLayer(version)
-  original = packet.getlayer("TCP")
-  return ("TCP SYN+ACK",
-          ip(src=srcaddr, dst=dstaddr) /
-          scapy.TCP(sport=original.dport, dport=original.sport,
-                    ack=original.seq + 1, seq=None,
-                    flags=TCP_SYN | TCP_ACK, window=None))
-
-def ACK(version, srcaddr, dstaddr, packet, payload=""):
-  ip = _GetIpLayer(version)
-  original = packet.getlayer("TCP")
-  was_syn_or_fin = (original.flags & (TCP_SYN | TCP_FIN)) != 0
-  ack_delta = was_syn_or_fin + len(original.payload)
-  desc = "TCP data" if payload else "TCP ACK"
-  flags = TCP_ACK | TCP_PSH if payload else TCP_ACK
-  return (desc,
-          ip(src=srcaddr, dst=dstaddr) /
-          scapy.TCP(sport=original.dport, dport=original.sport,
-                    ack=original.seq + ack_delta, seq=original.ack,
-                    flags=flags, window=TCP_WINDOW) /
-          payload)
-
-def FIN(version, srcaddr, dstaddr, packet):
-  ip = _GetIpLayer(version)
-  original = packet.getlayer("TCP")
-  was_syn_or_fin = (original.flags & (TCP_SYN | TCP_FIN)) != 0
-  ack_delta = was_syn_or_fin + len(original.payload)
-  return ("TCP FIN",
-          ip(src=srcaddr, dst=dstaddr) /
-          scapy.TCP(sport=original.dport, dport=original.sport,
-                    ack=original.seq + ack_delta, seq=original.ack,
-                    flags=TCP_ACK | TCP_FIN, window=TCP_WINDOW))
-
-def GRE(version, srcaddr, dstaddr, proto, packet):
-  if version == 4:
-    ip = scapy.IP(src=srcaddr, dst=dstaddr, proto=net_test.IPPROTO_GRE)
-  else:
-    ip = scapy.IPv6(src=srcaddr, dst=dstaddr, nh=net_test.IPPROTO_GRE)
-  packet = ip / scapy.GRE(proto=proto) / packet
-  return ("GRE packet", packet)
-
-def ICMPPortUnreachable(version, srcaddr, dstaddr, packet):
-  if version == 4:
-    # Linux hardcodes the ToS on ICMP errors to 0xc0 or greater because of
-    # RFC 1812 4.3.2.5 (!).
-    return ("ICMPv4 port unreachable",
-            scapy.IP(src=srcaddr, dst=dstaddr, proto=1, tos=0xc0) /
-            scapy.ICMPerror(type=3, code=3) / packet)
-  else:
-    return ("ICMPv6 port unreachable",
-            scapy.IPv6(src=srcaddr, dst=dstaddr) /
-            scapy.ICMPv6DestUnreach(code=4) / packet)
-
-def ICMPPacketTooBig(version, srcaddr, dstaddr, packet):
-  if version == 4:
-    return ("ICMPv4 fragmentation needed",
-            scapy.IP(src=srcaddr, dst=dstaddr, proto=1) /
-            scapy.ICMPerror(type=3, code=4, unused=1280) / str(packet)[:64])
-  else:
-    udp = packet.getlayer("UDP")
-    udp.payload = str(udp.payload)[:1280-40-8]
-    return ("ICMPv6 Packet Too Big",
-            scapy.IPv6(src=srcaddr, dst=dstaddr) /
-            scapy.ICMPv6PacketTooBig() / str(packet)[:1232])
-
-def ICMPEcho(version, srcaddr, dstaddr):
-  ip = _GetIpLayer(version)
-  icmp = {4: scapy.ICMP, 6: scapy.ICMPv6EchoRequest}[version]
-  packet = (ip(src=srcaddr, dst=dstaddr) /
-            icmp(id=PING_IDENT, seq=PING_SEQ) / PING_PAYLOAD)
-  _SetPacketTos(packet, PING_TOS)
-  return ("ICMPv%d echo" % version, packet)
-
-def ICMPReply(version, srcaddr, dstaddr, packet):
-  ip = _GetIpLayer(version)
-  # Scapy doesn't provide an ICMP echo reply constructor.
-  icmpv4_reply = lambda **kwargs: scapy.ICMP(type=0, **kwargs)
-  icmp = {4: icmpv4_reply, 6: scapy.ICMPv6EchoReply}[version]
-  packet = (ip(src=srcaddr, dst=dstaddr) /
-            icmp(id=PING_IDENT, seq=PING_SEQ) / PING_PAYLOAD)
-  # IPv6 only started copying the tclass to echo replies in 3.14.
-  if version == 4 or net_test.LINUX_VERSION >= (3, 14):
-    _SetPacketTos(packet, PING_TOS)
-  return ("ICMPv%d echo reply" % version, packet)
-
-def NS(srcaddr, tgtaddr, srcmac):
-  solicited = inet_pton(AF_INET6, tgtaddr)
-  last3bytes = tuple([ord(b) for b in solicited[-3:]])
-  solicited = "ff02::1:ff%02x:%02x%02x" % last3bytes
-  packet = (scapy.IPv6(src=srcaddr, dst=solicited) /
-            scapy.ICMPv6ND_NS(tgt=tgtaddr) /
-            scapy.ICMPv6NDOptSrcLLAddr(lladdr=srcmac))
-  return ("ICMPv6 NS", packet)
-
-def NA(srcaddr, dstaddr, srcmac):
-  packet = (scapy.IPv6(src=srcaddr, dst=dstaddr) /
-            scapy.ICMPv6ND_NA(tgt=srcaddr, R=0, S=1, O=1) /
-            scapy.ICMPv6NDOptDstLLAddr(lladdr=srcmac))
-  return ("ICMPv6 NA", packet)
-
diff --git a/tests/net_test/ping6_test.py b/tests/net_test/ping6_test.py
deleted file mode 100755
index bf51cfa..0000000
--- a/tests/net_test/ping6_test.py
+++ /dev/null
@@ -1,709 +0,0 @@
-#!/usr/bin/python
-#
-# Copyright 2014 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# pylint: disable=g-bad-todo
-
-import errno
-import os
-import posix
-import random
-from socket import *  # pylint: disable=wildcard-import
-import threading
-import time
-import unittest
-
-from scapy import all as scapy
-
-import csocket
-import multinetwork_base
-import net_test
-
-
-HAVE_PROC_NET_ICMP6 = os.path.isfile("/proc/net/icmp6")
-
-ICMP_ECHO = 8
-ICMP_ECHOREPLY = 0
-ICMPV6_ECHO_REQUEST = 128
-ICMPV6_ECHO_REPLY = 129
-
-
-class PingReplyThread(threading.Thread):
-
-  MIN_TTL = 10
-  INTERMEDIATE_IPV4 = "192.0.2.2"
-  INTERMEDIATE_IPV6 = "2001:db8:1:2::ace:d00d"
-  NEIGHBOURS = ["fe80::1"]
-
-  def __init__(self, tun, mymac, routermac):
-    super(PingReplyThread, self).__init__()
-    self._tun = tun
-    self._stopped = False
-    self._mymac = mymac
-    self._routermac = routermac
-
-  def Stop(self):
-    self._stopped = True
-
-  def ChecksumValid(self, packet):
-    # Get and clear the checksums.
-    def GetAndClearChecksum(layer):
-      if not layer:
-        return
-      try:
-        checksum = layer.chksum
-        del layer.chksum
-      except AttributeError:
-        checksum = layer.cksum
-        del layer.cksum
-      return checksum
-
-    def GetChecksum(layer):
-      try:
-        return layer.chksum
-      except AttributeError:
-        return layer.cksum
-
-    layers = ["IP", "ICMP", scapy.ICMPv6EchoRequest]
-    sums = {}
-    for name in layers:
-      sums[name] = GetAndClearChecksum(packet.getlayer(name))
-
-    # Serialize the packet, so scapy recalculates the checksums, and compare
-    # them with the ones in the packet.
-    packet = packet.__class__(str(packet))
-    for name in layers:
-      layer = packet.getlayer(name)
-      if layer and GetChecksum(layer) != sums[name]:
-        return False
-
-    return True
-
-  def SendTimeExceeded(self, version, packet):
-    if version == 4:
-      src = packet.getlayer(scapy.IP).src
-      self.SendPacket(
-          scapy.IP(src=self.INTERMEDIATE_IPV4, dst=src) /
-          scapy.ICMP(type=11, code=0) /
-          packet)
-    elif version == 6:
-      src = packet.getlayer(scapy.IPv6).src
-      self.SendPacket(
-          scapy.IPv6(src=self.INTERMEDIATE_IPV6, dst=src) /
-          scapy.ICMPv6TimeExceeded(code=0) /
-          packet)
-
-  def IPv4Packet(self, ip):
-    icmp = ip.getlayer(scapy.ICMP)
-
-    # We only support ping for now.
-    if (ip.proto != IPPROTO_ICMP or
-        icmp.type != ICMP_ECHO or
-        icmp.code != 0):
-      return
-
-    # Check the checksums.
-    if not self.ChecksumValid(ip):
-      return
-
-    if ip.ttl < self.MIN_TTL:
-      self.SendTimeExceeded(4, ip)
-      return
-
-    icmp.type = ICMP_ECHOREPLY
-    self.SwapAddresses(ip)
-    self.SendPacket(ip)
-
-  def IPv6Packet(self, ipv6):
-    icmpv6 = ipv6.getlayer(scapy.ICMPv6EchoRequest)
-
-    # We only support ping for now.
-    if (ipv6.nh != IPPROTO_ICMPV6 or
-        not icmpv6 or
-        icmpv6.type != ICMPV6_ECHO_REQUEST or
-        icmpv6.code != 0):
-      return
-
-    # Check the checksums.
-    if not self.ChecksumValid(ipv6):
-      return
-
-    if ipv6.dst.startswith("ff02::"):
-      ipv6.dst = ipv6.src
-      for src in self.NEIGHBOURS:
-        ipv6.src = src
-        icmpv6.type = ICMPV6_ECHO_REPLY
-        self.SendPacket(ipv6)
-    elif ipv6.hlim < self.MIN_TTL:
-      self.SendTimeExceeded(6, ipv6)
-    else:
-      icmpv6.type = ICMPV6_ECHO_REPLY
-      self.SwapAddresses(ipv6)
-      self.SendPacket(ipv6)
-
-  def SwapAddresses(self, packet):
-    src = packet.src
-    packet.src = packet.dst
-    packet.dst = src
-
-  def SendPacket(self, packet):
-    packet = scapy.Ether(src=self._routermac, dst=self._mymac) / packet
-    try:
-      posix.write(self._tun.fileno(), str(packet))
-    except ValueError:
-      pass
-
-  def run(self):
-    while not self._stopped:
-
-      try:
-        packet = posix.read(self._tun.fileno(), 4096)
-      except OSError, e:
-        if e.errno == errno.EAGAIN:
-          continue
-        else:
-          break
-
-      ether = scapy.Ether(packet)
-      if ether.type == net_test.ETH_P_IPV6:
-        self.IPv6Packet(ether.payload)
-      elif ether.type == net_test.ETH_P_IP:
-        self.IPv4Packet(ether.payload)
-
-
-class Ping6Test(multinetwork_base.MultiNetworkBaseTest):
-
-  @classmethod
-  def setUpClass(cls):
-    super(Ping6Test, cls).setUpClass()
-    cls.netid = random.choice(cls.NETIDS)
-    cls.reply_thread = PingReplyThread(
-        cls.tuns[cls.netid],
-        cls.MyMacAddress(cls.netid),
-        cls.RouterMacAddress(cls.netid))
-    cls.SetDefaultNetwork(cls.netid)
-    cls.reply_thread.start()
-
-  @classmethod
-  def tearDownClass(cls):
-    cls.reply_thread.Stop()
-    cls.ClearDefaultNetwork()
-    super(Ping6Test, cls).tearDownClass()
-
-  def setUp(self):
-    self.ifname = self.GetInterfaceName(self.netid)
-    self.ifindex = self.ifindices[self.netid]
-    self.lladdr = net_test.GetLinkAddress(self.ifname, True)
-    self.globaladdr = net_test.GetLinkAddress(self.ifname, False)
-
-  def assertValidPingResponse(self, s, data):
-    family = s.family
-
-    # Receive the reply.
-    rcvd, src = s.recvfrom(32768)
-    self.assertNotEqual(0, len(rcvd), "No data received")
-
-    # If this is a dual-stack socket sending to a mapped IPv4 address, treat it
-    # as IPv4.
-    if src[0].startswith("::ffff:"):
-      family = AF_INET
-      src = (src[0].replace("::ffff:", ""), src[1:])
-
-    # Check the data being sent is valid.
-    self.assertGreater(len(data), 7, "Not enough data for ping packet")
-    if family == AF_INET:
-      self.assertTrue(data.startswith("\x08\x00"), "Not an IPv4 echo request")
-    elif family == AF_INET6:
-      self.assertTrue(data.startswith("\x80\x00"), "Not an IPv6 echo request")
-    else:
-      self.fail("Unknown socket address family %d" * s.family)
-
-    # Check address, ICMP type, and ICMP code.
-    if family == AF_INET:
-      addr, unused_port = src
-      self.assertGreaterEqual(len(addr), len("1.1.1.1"))
-      self.assertTrue(rcvd.startswith("\x00\x00"), "Not an IPv4 echo reply")
-    else:
-      addr, unused_port, flowlabel, scope_id = src  # pylint: disable=unbalanced-tuple-unpacking
-      self.assertGreaterEqual(len(addr), len("::"))
-      self.assertTrue(rcvd.startswith("\x81\x00"), "Not an IPv6 echo reply")
-      # Check that the flow label is zero and that the scope ID is sane.
-      self.assertEqual(flowlabel, 0)
-      if addr.startswith("fe80::"):
-        self.assertTrue(scope_id in self.ifindices.values())
-      else:
-        self.assertEquals(0, scope_id)
-
-    # TODO: check the checksum. We can't do this easily now for ICMPv6 because
-    # we don't have the IP addresses so we can't construct the pseudoheader.
-
-    # Check the sequence number and the data.
-    self.assertEqual(len(data), len(rcvd))
-    self.assertEqual(data[6:].encode("hex"), rcvd[6:].encode("hex"))
-
-  def CheckSockStatFile(self, name, srcaddr, srcport, dstaddr, dstport, state,
-                        txmem=0, rxmem=0):
-    expected = ["%s:%04X" % (net_test.FormatSockStatAddress(srcaddr), srcport),
-                "%s:%04X" % (net_test.FormatSockStatAddress(dstaddr), dstport),
-                "%02X" % state,
-                "%08X:%08X" % (txmem, rxmem),
-                str(os.getuid()), "2", "0"]
-    actual = self.ReadProcNetSocket(name)[-1]
-    self.assertListEqual(expected, actual)
-
-  def testIPv4SendWithNoConnection(self):
-    s = net_test.IPv4PingSocket()
-    self.assertRaisesErrno(errno.EDESTADDRREQ, s.send, net_test.IPV4_PING)
-
-  def testIPv6SendWithNoConnection(self):
-    s = net_test.IPv6PingSocket()
-    self.assertRaisesErrno(errno.EDESTADDRREQ, s.send, net_test.IPV6_PING)
-
-  def testIPv4LoopbackPingWithConnect(self):
-    s = net_test.IPv4PingSocket()
-    s.connect(("127.0.0.1", 55))
-    data = net_test.IPV4_PING + "foobarbaz"
-    s.send(data)
-    self.assertValidPingResponse(s, data)
-
-  def testIPv6LoopbackPingWithConnect(self):
-    s = net_test.IPv6PingSocket()
-    s.connect(("::1", 55))
-    s.send(net_test.IPV6_PING)
-    self.assertValidPingResponse(s, net_test.IPV6_PING)
-
-  def testIPv4PingUsingSendto(self):
-    s = net_test.IPv4PingSocket()
-    written = s.sendto(net_test.IPV4_PING, (net_test.IPV4_ADDR, 55))
-    self.assertEquals(len(net_test.IPV4_PING), written)
-    self.assertValidPingResponse(s, net_test.IPV4_PING)
-
-  def testIPv6PingUsingSendto(self):
-    s = net_test.IPv6PingSocket()
-    written = s.sendto(net_test.IPV6_PING, (net_test.IPV6_ADDR, 55))
-    self.assertEquals(len(net_test.IPV6_PING), written)
-    self.assertValidPingResponse(s, net_test.IPV6_PING)
-
-  def testIPv4NoCrash(self):
-    # Python 2.x does not provide either read() or recvmsg.
-    s = net_test.IPv4PingSocket()
-    written = s.sendto(net_test.IPV4_PING, ("127.0.0.1", 55))
-    self.assertEquals(len(net_test.IPV4_PING), written)
-    fd = s.fileno()
-    reply = posix.read(fd, 4096)
-    self.assertEquals(written, len(reply))
-
-  def testIPv6NoCrash(self):
-    # Python 2.x does not provide either read() or recvmsg.
-    s = net_test.IPv6PingSocket()
-    written = s.sendto(net_test.IPV6_PING, ("::1", 55))
-    self.assertEquals(len(net_test.IPV6_PING), written)
-    fd = s.fileno()
-    reply = posix.read(fd, 4096)
-    self.assertEquals(written, len(reply))
-
-  def testCrossProtocolCrash(self):
-    # Checks that an ICMP error containing a ping packet that matches the ID
-    # of a socket of the wrong protocol (which can happen when using 464xlat)
-    # doesn't crash the kernel.
-
-    # We can only test this using IPv6 unreachables and IPv4 ping sockets,
-    # because IPv4 packets sent by scapy.send() on loopback are not received by
-    # the kernel. So we don't actually use this function yet.
-    def GetIPv4Unreachable(port):  # pylint: disable=unused-variable
-      return (scapy.IP(src="192.0.2.1", dst="127.0.0.1") /
-              scapy.ICMP(type=3, code=0) /
-              scapy.IP(src="127.0.0.1", dst="127.0.0.1") /
-              scapy.ICMP(type=8, id=port, seq=1))
-
-    def GetIPv6Unreachable(port):
-      return (scapy.IPv6(src="::1", dst="::1") /
-              scapy.ICMPv6DestUnreach() /
-              scapy.IPv6(src="::1", dst="::1") /
-              scapy.ICMPv6EchoRequest(id=port, seq=1, data="foobarbaz"))
-
-    # An unreachable matching the ID of a socket of the wrong protocol
-    # shouldn't crash.
-    s = net_test.IPv4PingSocket()
-    s.connect(("127.0.0.1", 12345))
-    _, port = s.getsockname()
-    scapy.send(GetIPv6Unreachable(port))
-    # No crash? Good.
-
-  def testCrossProtocolCalls(self):
-    """Tests that passing in the wrong family returns EAFNOSUPPORT.
-
-    Relevant kernel commits:
-      upstream net:
-        91a0b60 net/ping: handle protocol mismatching scenario
-        9145736d net: ping: Return EAFNOSUPPORT when appropriate.
-
-      android-3.10:
-        78a6809 net/ping: handle protocol mismatching scenario
-        428e6d6 net: ping: Return EAFNOSUPPORT when appropriate.
-    """
-
-    def CheckEAFNoSupport(function, *args):
-      self.assertRaisesErrno(errno.EAFNOSUPPORT, function, *args)
-
-    ipv6sockaddr = csocket.Sockaddr((net_test.IPV6_ADDR, 53))
-
-    # In order to check that IPv6 socket calls return EAFNOSUPPORT when passed
-    # IPv4 socket address structures, we need to pass down a socket address
-    # length argument that's at least sizeof(sockaddr_in6). Otherwise, the calls
-    # will fail immediately with EINVAL because the passed-in socket length is
-    # too short. So create a sockaddr_in that's as long as a sockaddr_in6.
-    ipv4sockaddr = csocket.Sockaddr((net_test.IPV4_ADDR, 53))
-    ipv4sockaddr = csocket.SockaddrIn6(
-        ipv4sockaddr.Pack() +
-        "\x00" * (len(csocket.SockaddrIn6) - len(csocket.SockaddrIn)))
-
-    s4 = net_test.IPv4PingSocket()
-    s6 = net_test.IPv6PingSocket()
-
-    # We can't just call s.connect(), s.bind() etc. with a tuple of the wrong
-    # address family, because the Python implementation will just pass garbage
-    # down to the kernel. So call the C functions directly.
-    CheckEAFNoSupport(csocket.Bind, s4, ipv6sockaddr)
-    CheckEAFNoSupport(csocket.Bind, s6, ipv4sockaddr)
-    CheckEAFNoSupport(csocket.Connect, s4, ipv6sockaddr)
-    CheckEAFNoSupport(csocket.Connect, s6, ipv4sockaddr)
-    CheckEAFNoSupport(csocket.Sendmsg,
-                      s4, ipv6sockaddr, net_test.IPV4_PING, None, 0)
-    CheckEAFNoSupport(csocket.Sendmsg,
-                      s6, ipv4sockaddr, net_test.IPV6_PING, None, 0)
-
-  def testIPv4Bind(self):
-    # Bind to unspecified address.
-    s = net_test.IPv4PingSocket()
-    s.bind(("0.0.0.0", 544))
-    self.assertEquals(("0.0.0.0", 544), s.getsockname())
-
-    # Bind to loopback.
-    s = net_test.IPv4PingSocket()
-    s.bind(("127.0.0.1", 99))
-    self.assertEquals(("127.0.0.1", 99), s.getsockname())
-
-    # Binding twice is not allowed.
-    self.assertRaisesErrno(errno.EINVAL, s.bind, ("127.0.0.1", 22))
-
-    # But binding two different sockets to the same ID is allowed.
-    s2 = net_test.IPv4PingSocket()
-    s2.bind(("127.0.0.1", 99))
-    self.assertEquals(("127.0.0.1", 99), s2.getsockname())
-    s3 = net_test.IPv4PingSocket()
-    s3.bind(("127.0.0.1", 99))
-    self.assertEquals(("127.0.0.1", 99), s3.getsockname())
-
-    # If two sockets bind to the same port, the first one to call read() gets
-    # the response.
-    s4 = net_test.IPv4PingSocket()
-    s5 = net_test.IPv4PingSocket()
-    s4.bind(("0.0.0.0", 167))
-    s5.bind(("0.0.0.0", 167))
-    s4.sendto(net_test.IPV4_PING, (net_test.IPV4_ADDR, 44))
-    self.assertValidPingResponse(s5, net_test.IPV4_PING)
-    net_test.SetSocketTimeout(s4, 100)
-    self.assertRaisesErrno(errno.EAGAIN, s4.recv, 32768)
-
-    # If SO_REUSEADDR is turned off, then we get EADDRINUSE.
-    s6 = net_test.IPv4PingSocket()
-    s4.setsockopt(SOL_SOCKET, SO_REUSEADDR, 0)
-    self.assertRaisesErrno(errno.EADDRINUSE, s6.bind, ("0.0.0.0", 167))
-
-    # Can't bind after sendto.
-    s = net_test.IPv4PingSocket()
-    s.sendto(net_test.IPV4_PING, (net_test.IPV4_ADDR, 9132))
-    self.assertRaisesErrno(errno.EINVAL, s.bind, ("0.0.0.0", 5429))
-
-  def testIPv6Bind(self):
-    # Bind to unspecified address.
-    s = net_test.IPv6PingSocket()
-    s.bind(("::", 769))
-    self.assertEquals(("::", 769, 0, 0), s.getsockname())
-
-    # Bind to loopback.
-    s = net_test.IPv6PingSocket()
-    s.bind(("::1", 99))
-    self.assertEquals(("::1", 99, 0, 0), s.getsockname())
-
-    # Binding twice is not allowed.
-    self.assertRaisesErrno(errno.EINVAL, s.bind, ("::1", 22))
-
-    # But binding two different sockets to the same ID is allowed.
-    s2 = net_test.IPv6PingSocket()
-    s2.bind(("::1", 99))
-    self.assertEquals(("::1", 99, 0, 0), s2.getsockname())
-    s3 = net_test.IPv6PingSocket()
-    s3.bind(("::1", 99))
-    self.assertEquals(("::1", 99, 0, 0), s3.getsockname())
-
-    # Binding both IPv4 and IPv6 to the same socket works.
-    s4 = net_test.IPv4PingSocket()
-    s6 = net_test.IPv6PingSocket()
-    s4.bind(("0.0.0.0", 444))
-    s6.bind(("::", 666, 0, 0))
-
-    # Can't bind after sendto.
-    s = net_test.IPv6PingSocket()
-    s.sendto(net_test.IPV6_PING, (net_test.IPV6_ADDR, 9132))
-    self.assertRaisesErrno(errno.EINVAL, s.bind, ("::", 5429))
-
-  def testIPv4InvalidBind(self):
-    s = net_test.IPv4PingSocket()
-    self.assertRaisesErrno(errno.EADDRNOTAVAIL,
-                           s.bind, ("255.255.255.255", 1026))
-    self.assertRaisesErrno(errno.EADDRNOTAVAIL,
-                           s.bind, ("224.0.0.1", 651))
-    # Binding to an address we don't have only works with IP_TRANSPARENT.
-    self.assertRaisesErrno(errno.EADDRNOTAVAIL,
-                           s.bind, (net_test.IPV4_ADDR, 651))
-    try:
-      s.setsockopt(SOL_IP, net_test.IP_TRANSPARENT, 1)
-      s.bind((net_test.IPV4_ADDR, 651))
-    except IOError, e:
-      if e.errno == errno.EACCES:
-        pass  # We're not root. let it go for now.
-
-  def testIPv6InvalidBind(self):
-    s = net_test.IPv6PingSocket()
-    self.assertRaisesErrno(errno.EINVAL,
-                           s.bind, ("ff02::2", 1026))
-
-    # Binding to an address we don't have only works with IPV6_TRANSPARENT.
-    self.assertRaisesErrno(errno.EADDRNOTAVAIL,
-                           s.bind, (net_test.IPV6_ADDR, 651))
-    try:
-      s.setsockopt(net_test.SOL_IPV6, net_test.IPV6_TRANSPARENT, 1)
-      s.bind((net_test.IPV6_ADDR, 651))
-    except IOError, e:
-      if e.errno == errno.EACCES:
-        pass  # We're not root. let it go for now.
-
-  def testAfUnspecBind(self):
-    # Binding to AF_UNSPEC is treated as IPv4 if the address is 0.0.0.0.
-    s4 = net_test.IPv4PingSocket()
-    sockaddr = csocket.Sockaddr(("0.0.0.0", 12996))
-    sockaddr.family = AF_UNSPEC
-    csocket.Bind(s4, sockaddr)
-    self.assertEquals(("0.0.0.0", 12996), s4.getsockname())
-
-    # But not if the address is anything else.
-    sockaddr = csocket.Sockaddr(("127.0.0.1", 58234))
-    sockaddr.family = AF_UNSPEC
-    self.assertRaisesErrno(errno.EAFNOSUPPORT, csocket.Bind, s4, sockaddr)
-
-    # This doesn't work for IPv6.
-    s6 = net_test.IPv6PingSocket()
-    sockaddr = csocket.Sockaddr(("::1", 58997))
-    sockaddr.family = AF_UNSPEC
-    self.assertRaisesErrno(errno.EAFNOSUPPORT, csocket.Bind, s6, sockaddr)
-
-  def testIPv6ScopedBind(self):
-    # Can't bind to a link-local address without a scope ID.
-    s = net_test.IPv6PingSocket()
-    self.assertRaisesErrno(errno.EINVAL,
-                           s.bind, (self.lladdr, 1026, 0, 0))
-
-    # Binding to a link-local address with a scope ID works, and the scope ID is
-    # returned by a subsequent getsockname. Interestingly, Python's getsockname
-    # returns "fe80:1%foo", even though it does not understand it.
-    expected = self.lladdr + "%" + self.ifname
-    s.bind((self.lladdr, 4646, 0, self.ifindex))
-    self.assertEquals((expected, 4646, 0, self.ifindex), s.getsockname())
-
-    # Of course, for the above to work the address actually has to be configured
-    # on the machine.
-    self.assertRaisesErrno(errno.EADDRNOTAVAIL,
-                           s.bind, ("fe80::f00", 1026, 0, 1))
-
-    # Scope IDs on non-link-local addresses are silently ignored.
-    s = net_test.IPv6PingSocket()
-    s.bind(("::1", 1234, 0, 1))
-    self.assertEquals(("::1", 1234, 0, 0), s.getsockname())
-
-  def testBindAffectsIdentifier(self):
-    s = net_test.IPv6PingSocket()
-    s.bind((self.globaladdr, 0xf976))
-    s.sendto(net_test.IPV6_PING, (net_test.IPV6_ADDR, 55))
-    self.assertEquals("\xf9\x76", s.recv(32768)[4:6])
-
-    s = net_test.IPv6PingSocket()
-    s.bind((self.globaladdr, 0xace))
-    s.sendto(net_test.IPV6_PING, (net_test.IPV6_ADDR, 55))
-    self.assertEquals("\x0a\xce", s.recv(32768)[4:6])
-
-  def testLinkLocalAddress(self):
-    s = net_test.IPv6PingSocket()
-    # Sending to a link-local address with no scope fails with EINVAL.
-    self.assertRaisesErrno(errno.EINVAL,
-                           s.sendto, net_test.IPV6_PING, ("fe80::1", 55))
-    # Sending to link-local address with a scope succeeds. Note that Python
-    # doesn't understand the "fe80::1%lo" format, even though it returns it.
-    s.sendto(net_test.IPV6_PING, ("fe80::1", 55, 0, self.ifindex))
-    # No exceptions? Good.
-
-  def testMappedAddressFails(self):
-    s = net_test.IPv6PingSocket()
-    s.sendto(net_test.IPV6_PING, (net_test.IPV6_ADDR, 55))
-    self.assertValidPingResponse(s, net_test.IPV6_PING)
-    s.sendto(net_test.IPV6_PING, ("2001:4860:4860::8844", 55))
-    self.assertValidPingResponse(s, net_test.IPV6_PING)
-    self.assertRaisesErrno(errno.EINVAL, s.sendto, net_test.IPV6_PING,
-                           ("::ffff:192.0.2.1", 55))
-
-  @unittest.skipUnless(False, "skipping: does not work yet")
-  def testFlowLabel(self):
-    s = net_test.IPv6PingSocket()
-
-    # Specifying a flowlabel without having set IPV6_FLOWINFO_SEND succeeds but
-    # the flow label in the packet is not set.
-    s.sendto(net_test.IPV6_PING, (net_test.IPV6_ADDR, 93, 0xdead, 0))
-    self.assertValidPingResponse(s, net_test.IPV6_PING)  # Checks flow label==0.
-
-    # If IPV6_FLOWINFO_SEND is set on the socket, attempting to set a flow label
-    # that is not registered with the flow manager should return EINVAL...
-    s.setsockopt(net_test.SOL_IPV6, net_test.IPV6_FLOWINFO_SEND, 1)
-    # ... but this doesn't work yet.
-    if False:
-      self.assertRaisesErrno(errno.EINVAL, s.sendto, net_test.IPV6_PING,
-                             (net_test.IPV6_ADDR, 93, 0xdead, 0))
-
-    # After registering the flow label, it gets sent properly, appears in the
-    # output packet, and is returned in the response.
-    net_test.SetFlowLabel(s, net_test.IPV6_ADDR, 0xdead)
-    self.assertEqual(1, s.getsockopt(net_test.SOL_IPV6,
-                                     net_test.IPV6_FLOWINFO_SEND))
-    s.sendto(net_test.IPV6_PING, (net_test.IPV6_ADDR, 93, 0xdead, 0))
-    _, src = s.recvfrom(32768)
-    _, _, flowlabel, _ = src
-    self.assertEqual(0xdead, flowlabel & 0xfffff)
-
-  def testIPv4Error(self):
-    s = net_test.IPv4PingSocket()
-    s.setsockopt(SOL_IP, IP_TTL, 2)
-    s.setsockopt(SOL_IP, net_test.IP_RECVERR, 1)
-    s.sendto(net_test.IPV4_PING, (net_test.IPV4_ADDR, 55))
-    # We can't check the actual error because Python 2.7 doesn't implement
-    # recvmsg, but we can at least check that the socket returns an error.
-    self.assertRaisesErrno(errno.EHOSTUNREACH, s.recv, 32768)  # No response.
-
-  def testIPv6Error(self):
-    s = net_test.IPv6PingSocket()
-    s.setsockopt(net_test.SOL_IPV6, IPV6_UNICAST_HOPS, 2)
-    s.setsockopt(net_test.SOL_IPV6, net_test.IPV6_RECVERR, 1)
-    s.sendto(net_test.IPV6_PING, (net_test.IPV6_ADDR, 55))
-    # We can't check the actual error because Python 2.7 doesn't implement
-    # recvmsg, but we can at least check that the socket returns an error.
-    self.assertRaisesErrno(errno.EHOSTUNREACH, s.recv, 32768)  # No response.
-
-  def testIPv6MulticastPing(self):
-    s = net_test.IPv6PingSocket()
-    # Send a multicast ping and check we get at least one duplicate.
-    # The setsockopt should not be necessary, but ping_v6_sendmsg has a bug.
-    s.setsockopt(net_test.SOL_IPV6, net_test.IPV6_MULTICAST_IF, self.ifindex)
-    s.sendto(net_test.IPV6_PING, ("ff02::1", 55, 0, self.ifindex))
-    self.assertValidPingResponse(s, net_test.IPV6_PING)
-    self.assertValidPingResponse(s, net_test.IPV6_PING)
-
-  def testIPv4LargePacket(self):
-    s = net_test.IPv4PingSocket()
-    data = net_test.IPV4_PING + 20000 * "a"
-    s.sendto(data, ("127.0.0.1", 987))
-    self.assertValidPingResponse(s, data)
-
-  def testIPv6LargePacket(self):
-    s = net_test.IPv6PingSocket()
-    s.bind(("::", 0xace))
-    data = net_test.IPV6_PING + "\x01" + 19994 * "\x00" + "aaaaa"
-    s.sendto(data, ("::1", 953))
-
-  @unittest.skipUnless(HAVE_PROC_NET_ICMP6, "skipping: no /proc/net/icmp6")
-  def testIcmpSocketsNotInIcmp6(self):
-    numrows = len(self.ReadProcNetSocket("icmp"))
-    numrows6 = len(self.ReadProcNetSocket("icmp6"))
-    s = net_test.Socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP)
-    s.bind(("127.0.0.1", 0xace))
-    s.connect(("127.0.0.1", 0xbeef))
-    self.assertEquals(numrows + 1, len(self.ReadProcNetSocket("icmp")))
-    self.assertEquals(numrows6, len(self.ReadProcNetSocket("icmp6")))
-
-  @unittest.skipUnless(HAVE_PROC_NET_ICMP6, "skipping: no /proc/net/icmp6")
-  def testIcmp6SocketsNotInIcmp(self):
-    numrows = len(self.ReadProcNetSocket("icmp"))
-    numrows6 = len(self.ReadProcNetSocket("icmp6"))
-    s = net_test.IPv6PingSocket()
-    s.bind(("::1", 0xace))
-    s.connect(("::1", 0xbeef))
-    self.assertEquals(numrows, len(self.ReadProcNetSocket("icmp")))
-    self.assertEquals(numrows6 + 1, len(self.ReadProcNetSocket("icmp6")))
-
-  def testProcNetIcmp(self):
-    s = net_test.Socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP)
-    s.bind(("127.0.0.1", 0xace))
-    s.connect(("127.0.0.1", 0xbeef))
-    self.CheckSockStatFile("icmp", "127.0.0.1", 0xace, "127.0.0.1", 0xbeef, 1)
-
-  @unittest.skipUnless(HAVE_PROC_NET_ICMP6, "skipping: no /proc/net/icmp6")
-  def testProcNetIcmp6(self):
-    numrows6 = len(self.ReadProcNetSocket("icmp6"))
-    s = net_test.IPv6PingSocket()
-    s.bind(("::1", 0xace))
-    s.connect(("::1", 0xbeef))
-    self.CheckSockStatFile("icmp6", "::1", 0xace, "::1", 0xbeef, 1)
-
-    # Check the row goes away when the socket is closed.
-    s.close()
-    self.assertEquals(numrows6, len(self.ReadProcNetSocket("icmp6")))
-
-    # Try send, bind and connect to check the addresses and the state.
-    s = net_test.IPv6PingSocket()
-    self.assertEqual(0, len(self.ReadProcNetSocket("icmp6")))
-    s.sendto(net_test.IPV6_PING, (net_test.IPV6_ADDR, 12345))
-    self.assertEqual(1, len(self.ReadProcNetSocket("icmp6")))
-
-    # Can't bind after sendto, apparently.
-    s = net_test.IPv6PingSocket()
-    self.assertEqual(0, len(self.ReadProcNetSocket("icmp6")))
-    s.bind((self.lladdr, 0xd00d, 0, self.ifindex))
-    self.CheckSockStatFile("icmp6", self.lladdr, 0xd00d, "::", 0, 7)
-
-    # Check receive bytes.
-    s.setsockopt(net_test.SOL_IPV6, net_test.IPV6_MULTICAST_IF, self.ifindex)
-    s.connect(("ff02::1", 0xdead))
-    self.CheckSockStatFile("icmp6", self.lladdr, 0xd00d, "ff02::1", 0xdead, 1)
-    s.send(net_test.IPV6_PING)
-    time.sleep(0.01)  # Give the other thread time to reply.
-    self.CheckSockStatFile("icmp6", self.lladdr, 0xd00d, "ff02::1", 0xdead, 1,
-                           txmem=0, rxmem=0x300)
-    self.assertValidPingResponse(s, net_test.IPV6_PING)
-    self.CheckSockStatFile("icmp6", self.lladdr, 0xd00d, "ff02::1", 0xdead, 1,
-                           txmem=0, rxmem=0)
-
-  def testProcNetUdp6(self):
-    s = net_test.Socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP)
-    s.bind(("::1", 0xace))
-    s.connect(("::1", 0xbeef))
-    self.CheckSockStatFile("udp6", "::1", 0xace, "::1", 0xbeef, 1)
-
-  def testProcNetRaw6(self):
-    s = net_test.Socket(AF_INET6, SOCK_RAW, IPPROTO_RAW)
-    s.bind(("::1", 0xace))
-    s.connect(("::1", 0xbeef))
-    self.CheckSockStatFile("raw6", "::1", 0xff, "::1", 0, 1)
-
-
-if __name__ == "__main__":
-  unittest.main()
diff --git a/tests/net_test/ping6_test.sh b/tests/net_test/ping6_test.sh
deleted file mode 100755
index 41dabce..0000000
--- a/tests/net_test/ping6_test.sh
+++ /dev/null
@@ -1,16 +0,0 @@
-#!/bin/bash
-
-# Minimal network initialization.
-ip link set eth0 up
-
-# Wait for autoconf and DAD to complete.
-sleep 3 &
-
-# Block on starting DHCPv4.
-udhcpc -i eth0
-
-# If DHCPv4 took less than 3 seconds, keep waiting.
-wait
-
-# Run the test.
-$(dirname $0)/ping6_test.py
diff --git a/tests/net_test/run_net_test.sh b/tests/net_test/run_net_test.sh
deleted file mode 100755
index 080aac7..0000000
--- a/tests/net_test/run_net_test.sh
+++ /dev/null
@@ -1,125 +0,0 @@
-#!/bin/bash
-
-# Kernel configuration options.
-OPTIONS=" DEBUG_SPINLOCK DEBUG_ATOMIC_SLEEP DEBUG_MUTEXES DEBUG_RT_MUTEXES"
-OPTIONS="$OPTIONS IPV6 IPV6_ROUTER_PREF IPV6_MULTIPLE_TABLES IPV6_ROUTE_INFO"
-OPTIONS="$OPTIONS TUN SYN_COOKIES IP_ADVANCED_ROUTER IP_MULTIPLE_TABLES"
-OPTIONS="$OPTIONS NETFILTER NETFILTER_ADVANCED NETFILTER_XTABLES"
-OPTIONS="$OPTIONS NETFILTER_XT_MARK NETFILTER_XT_TARGET_MARK"
-OPTIONS="$OPTIONS IP_NF_IPTABLES IP_NF_MANGLE"
-OPTIONS="$OPTIONS IP6_NF_IPTABLES IP6_NF_MANGLE INET6_IPCOMP"
-OPTIONS="$OPTIONS IPV6_PRIVACY IPV6_OPTIMISTIC_DAD"
-OPTIONS="$OPTIONS CONFIG_NETFILTER_XT_TARGET_NFLOG"
-OPTIONS="$OPTIONS CONFIG_NETFILTER_XT_MATCH_QUOTA CONFIG_NETFILTER_XT_MATCH_QUOTA2"
-OPTIONS="$OPTIONS CONFIG_NETFILTER_XT_MATCH_QUOTA2_LOG"
-OPTIONS="$OPTIONS CONFIG_INET_UDP_DIAG CONFIG_INET_DIAG_DESTROY"
-
-# For 3.1 kernels, where devtmpfs is not on by default.
-OPTIONS="$OPTIONS DEVTMPFS DEVTMPFS_MOUNT"
-
-# These two break the flo kernel due to differences in -Werror on recent GCC.
-DISABLE_OPTIONS=" CONFIG_REISERFS_FS CONFIG_ANDROID_PMEM"
-
-# How many TAP interfaces to create to provide the VM with real network access
-# via the host. This requires privileges (e.g., root access) on the host.
-#
-# This is not needed to run the tests, but can be used, for example, to allow
-# the VM to update system packages, or to write tests that need access to a
-# real network. The VM does not set up networking by default, but it contains a
-# DHCP client and has the ability to use IPv6 autoconfiguration. This script
-# does not perform any host-level setup beyond configuring tap interfaces;
-# configuring IPv4 NAT and/or IPv6 router advertisements or ND proxying must
-# be done separately.
-NUMTAPINTERFACES=0
-
-# The root filesystem disk image we'll use.
-ROOTFS=net_test.rootfs.20150203
-COMPRESSED_ROOTFS=$ROOTFS.xz
-URL=https://dl.google.com/dl/android/$COMPRESSED_ROOTFS
-
-# Figure out which test to run.
-if [ -z "$1" ]; then
-  echo "Usage: $0 <test>" >&2
-  exit 1
-fi
-test=$1
-
-set -e
-
-# Check if we need to uncompress the disk image.
-# We use xz because it compresses better: to 42M vs 72M (gzip) / 62M (bzip2).
-cd $(dirname $0)
-if [ ! -f $ROOTFS ]; then
-  echo "Deleting $COMPRESSED_ROOTFS" >&2
-  rm -f $COMPRESSED_ROOTFS
-  echo "Downloading $URL" >&2
-  wget $URL
-  echo "Uncompressing $COMPRESSED_ROOTFS" >&2
-  unxz $COMPRESSED_ROOTFS
-fi
-echo "Using $ROOTFS"
-cd -
-
-# If network access was requested, create NUMTAPINTERFACES tap interfaces on
-# the host, and prepare UML command line params to use them. The interfaces are
-# called <user>TAP0, <user>TAP1, on the host, and eth0, eth1, ..., in the VM.
-if (( $NUMTAPINTERFACES > 0 )); then
-  user=${USER:0:10}
-  tapinterfaces=
-  netconfig=
-  for id in $(seq 0 $(( NUMTAPINTERFACES - 1 )) ); do
-    tap=${user}TAP$id
-    tapinterfaces="$tapinterfaces $tap"
-    mac=$(printf fe:fd:00:00:00:%02x $id)
-    netconfig="$netconfig eth$id=tuntap,$tap,$mac"
-  done
-
-  for tap in $tapinterfaces; do
-    if ! ip link list $tap > /dev/null; then
-      echo "Creating tap interface $tap" >&2
-      sudo tunctl -u $USER -t $tap
-      sudo ip link set $tap up
-    fi
-  done
-fi
-
-if [ -z "$KERNEL_BINARY" ]; then
-  # Exporting ARCH=um SUBARCH=x86_64 doesn't seem to work, as it "sometimes"
-  # (?) results in a 32-bit kernel.
-
-  # If there's no kernel config at all, create one or UML won't work.
-  [ -f .config ] || make defconfig ARCH=um SUBARCH=x86_64
-
-  # Enable the kernel config options listed in $OPTIONS.
-  cmdline=${OPTIONS// / -e }
-  ./scripts/config $cmdline
-
-  # Disable the kernel config options listed in $DISABLE_OPTIONS.
-  cmdline=${DISABLE_OPTIONS// / -d }
-  ./scripts/config $cmdline
-
-  # olddefconfig doesn't work on old kernels.
-  if ! make olddefconfig ARCH=um SUBARCH=x86_64 CROSS_COMPILE= ; then
-    cat >&2 << EOF
-
-Warning: "make olddefconfig" failed.
-Perhaps this kernel is too old to support it.
-You may get asked lots of questions.
-Keep enter pressed to accept the defaults.
-
-EOF
-  fi
-
-  # Compile the kernel.
-  make -j32 linux ARCH=um SUBARCH=x86_64 CROSS_COMPILE=
-  KERNEL_BINARY=./linux
-fi
-
-
-# Get the absolute path to the test file that's being run.
-dir=/host$(dirname $(readlink -f $0))
-
-# Start the VM.
-exec $KERNEL_BINARY umid=net_test ubda=$(dirname $0)/$ROOTFS \
-    mem=512M init=/sbin/net_test.sh net_test=$dir/$test \
-    $netconfig
diff --git a/tests/net_test/sock_diag.py b/tests/net_test/sock_diag.py
deleted file mode 100755
index b4d9cf6..0000000
--- a/tests/net_test/sock_diag.py
+++ /dev/null
@@ -1,342 +0,0 @@
-#!/usr/bin/python
-#
-# Copyright 2015 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-"""Partial Python implementation of sock_diag functionality."""
-
-# pylint: disable=g-bad-todo
-
-import errno
-from socket import *  # pylint: disable=wildcard-import
-import struct
-
-import cstruct
-import net_test
-import netlink
-
-### Base netlink constants. See include/uapi/linux/netlink.h.
-NETLINK_SOCK_DIAG = 4
-
-### sock_diag constants. See include/uapi/linux/sock_diag.h.
-# Message types.
-SOCK_DIAG_BY_FAMILY = 20
-SOCK_DESTROY = 21
-
-### inet_diag_constants. See include/uapi/linux/inet_diag.h
-# Message types.
-TCPDIAG_GETSOCK = 18
-
-# Request attributes.
-INET_DIAG_REQ_BYTECODE = 1
-
-# Extensions.
-INET_DIAG_NONE = 0
-INET_DIAG_MEMINFO = 1
-INET_DIAG_INFO = 2
-INET_DIAG_VEGASINFO = 3
-INET_DIAG_CONG = 4
-INET_DIAG_TOS = 5
-INET_DIAG_TCLASS = 6
-INET_DIAG_SKMEMINFO = 7
-INET_DIAG_SHUTDOWN = 8
-INET_DIAG_DCTCPINFO = 9
-
-# Bytecode operations.
-INET_DIAG_BC_NOP = 0
-INET_DIAG_BC_JMP = 1
-INET_DIAG_BC_S_GE = 2
-INET_DIAG_BC_S_LE = 3
-INET_DIAG_BC_D_GE = 4
-INET_DIAG_BC_D_LE = 5
-INET_DIAG_BC_AUTO = 6
-INET_DIAG_BC_S_COND = 7
-INET_DIAG_BC_D_COND = 8
-
-# Data structure formats.
-# These aren't constants, they're classes. So, pylint: disable=invalid-name
-InetDiagSockId = cstruct.Struct(
-    "InetDiagSockId", "!HH16s16sI8s", "sport dport src dst iface cookie")
-InetDiagReqV2 = cstruct.Struct(
-    "InetDiagReqV2", "=BBBxIS", "family protocol ext states id",
-    [InetDiagSockId])
-InetDiagMsg = cstruct.Struct(
-    "InetDiagMsg", "=BBBBSLLLLL",
-    "family state timer retrans id expires rqueue wqueue uid inode",
-    [InetDiagSockId])
-InetDiagMeminfo = cstruct.Struct(
-    "InetDiagMeminfo", "=IIII", "rmem wmem fmem tmem")
-InetDiagBcOp = cstruct.Struct("InetDiagBcOp", "BBH", "code yes no")
-InetDiagHostcond = cstruct.Struct("InetDiagHostcond", "=BBxxi",
-                                  "family prefix_len port")
-
-SkMeminfo = cstruct.Struct(
-    "SkMeminfo", "=IIIIIIII",
-    "rmem_alloc rcvbuf wmem_alloc sndbuf fwd_alloc wmem_queued optmem backlog")
-TcpInfo = cstruct.Struct(
-    "TcpInfo", "=BBBBBBBxIIIIIIIIIIIIIIIIIIIIIIII",
-    "state ca_state retransmits probes backoff options wscale "
-    "rto ato snd_mss rcv_mss "
-    "unacked sacked lost retrans fackets "
-    "last_data_sent last_ack_sent last_data_recv last_ack_recv "
-    "pmtu rcv_ssthresh rtt rttvar snd_ssthresh snd_cwnd advmss reordering "
-    "rcv_rtt rcv_space "
-    "total_retrans")  # As of linux 3.13, at least.
-
-TCP_TIME_WAIT = 6
-ALL_NON_TIME_WAIT = 0xffffffff & ~(1 << TCP_TIME_WAIT)
-
-
-class SockDiag(netlink.NetlinkSocket):
-
-  FAMILY = NETLINK_SOCK_DIAG
-  NL_DEBUG = []
-
-  def _Decode(self, command, msg, nla_type, nla_data):
-    """Decodes netlink attributes to Python types."""
-    if msg.family == AF_INET or msg.family == AF_INET6:
-      name = self._GetConstantName(__name__, nla_type, "INET_DIAG")
-    else:
-      # Don't know what this is. Leave it as an integer.
-      name = nla_type
-
-    if name in ["INET_DIAG_SHUTDOWN", "INET_DIAG_TOS", "INET_DIAG_TCLASS"]:
-      data = ord(nla_data)
-    elif name == "INET_DIAG_CONG":
-      data = nla_data.strip("\x00")
-    elif name == "INET_DIAG_MEMINFO":
-      data = InetDiagMeminfo(nla_data)
-    elif name == "INET_DIAG_INFO":
-      # TODO: Catch the exception and try something else if it's not TCP.
-      data = TcpInfo(nla_data)
-    elif name == "INET_DIAG_SKMEMINFO":
-      data = SkMeminfo(nla_data)
-    else:
-      data = nla_data
-
-    return name, data
-
-  def MaybeDebugCommand(self, command, data):
-    name = self._GetConstantName(__name__, command, "SOCK_")
-    if "ALL" not in self.NL_DEBUG and "SOCK" not in self.NL_DEBUG:
-      return
-    parsed = self._ParseNLMsg(data, InetDiagReqV2)
-    print "%s %s" % (name, str(parsed))
-
-  @staticmethod
-  def _EmptyInetDiagSockId():
-    return InetDiagSockId(("\x00" * len(InetDiagSockId)))
-
-  def PackBytecode(self, instructions):
-    """Compiles instructions to inet_diag bytecode.
-
-    The input is a list of (INET_DIAG_BC_xxx, yes, no, arg) tuples, where yes
-    and no are relative jump offsets measured in instructions. The yes branch
-    is taken if the instruction matches.
-
-    To accept, jump 1 past the last instruction. To reject, jump 2 past the
-    last instruction.
-
-    The target of a no jump is only valid if it is reachable by following
-    only yes jumps from the first instruction - see inet_diag_bc_audit and
-    valid_cc. This means that if cond1 and cond2 are two mutually exclusive
-    filter terms, it is not possible to implement cond1 OR cond2 using:
-
-      ...
-      cond1 2 1 arg
-      cond2 1 2 arg
-      accept
-      reject
-
-    but only using:
-
-      ...
-      cond1 1 2 arg
-      jmp   1 2
-      cond2 1 2 arg
-      accept
-      reject
-
-    The jmp instruction ignores yes and always jumps to no, but yes must be 1
-    or the bytecode won't validate. It doesn't have to be jmp - any instruction
-    that is guaranteed not to match on real data will do.
-
-    Args:
-      instructions: list of instruction tuples
-
-    Returns:
-      A string, the raw bytecode.
-    """
-    args = []
-    positions = [0]
-
-    for op, yes, no, arg in instructions:
-
-      if yes <= 0 or no <= 0:
-        raise ValueError("Jumps must be > 0")
-
-      if op in [INET_DIAG_BC_NOP, INET_DIAG_BC_JMP, INET_DIAG_BC_AUTO]:
-        arg = ""
-      elif op in [INET_DIAG_BC_S_GE, INET_DIAG_BC_S_LE,
-                  INET_DIAG_BC_D_GE, INET_DIAG_BC_D_LE]:
-        arg = "\x00\x00" + struct.pack("=H", arg)
-      elif op in [INET_DIAG_BC_S_COND, INET_DIAG_BC_D_COND]:
-        addr, prefixlen, port = arg
-        family = AF_INET6 if ":" in addr else AF_INET
-        addr = inet_pton(family, addr)
-        arg = InetDiagHostcond((family, prefixlen, port)).Pack() + addr
-      else:
-        raise ValueError("Unsupported opcode %d" % op)
-
-      args.append(arg)
-      length = len(InetDiagBcOp) + len(arg)
-      positions.append(positions[-1] + length)
-
-    # Reject label.
-    positions.append(positions[-1] + 4)  # Why 4? Because the kernel uses 4.
-    assert len(args) == len(instructions) == len(positions) - 2
-
-    # print positions
-
-    packed = ""
-    for i, (op, yes, no, arg) in enumerate(instructions):
-      yes = positions[i + yes] - positions[i]
-      no = positions[i + no] - positions[i]
-      instruction = InetDiagBcOp((op, yes, no)).Pack() + args[i]
-      #print "%3d: %d %3d %3d %s %s" % (positions[i], op, yes, no,
-      #                                 arg, instruction.encode("hex"))
-      packed += instruction
-    #print
-
-    return packed
-
-  def Dump(self, diag_req, bytecode=""):
-    out = self._Dump(SOCK_DIAG_BY_FAMILY, diag_req, InetDiagMsg, bytecode)
-    return out
-
-  def DumpAllInetSockets(self, protocol, bytecode, sock_id=None, ext=0,
-                         states=ALL_NON_TIME_WAIT):
-    """Dumps IPv4 or IPv6 sockets matching the specified parameters."""
-    # DumpSockets(AF_UNSPEC) does not result in dumping all inet sockets, it
-    # results in ENOENT.
-    if sock_id is None:
-      sock_id = self._EmptyInetDiagSockId()
-
-    if bytecode:
-      bytecode = self._NlAttr(INET_DIAG_REQ_BYTECODE, bytecode)
-
-    sockets = []
-    for family in [AF_INET, AF_INET6]:
-      diag_req = InetDiagReqV2((family, protocol, ext, states, sock_id))
-      sockets += self.Dump(diag_req, bytecode)
-
-    return sockets
-
-  @staticmethod
-  def GetRawAddress(family, addr):
-    """Fetches the source address from an InetDiagMsg."""
-    addrlen = {AF_INET:4, AF_INET6: 16}[family]
-    return inet_ntop(family, addr[:addrlen])
-
-  @staticmethod
-  def GetSourceAddress(diag_msg):
-    """Fetches the source address from an InetDiagMsg."""
-    return SockDiag.GetRawAddress(diag_msg.family, diag_msg.id.src)
-
-  @staticmethod
-  def GetDestinationAddress(diag_msg):
-    """Fetches the source address from an InetDiagMsg."""
-    return SockDiag.GetRawAddress(diag_msg.family, diag_msg.id.dst)
-
-  @staticmethod
-  def RawAddress(addr):
-    """Converts an IP address string to binary format."""
-    family = AF_INET6 if ":" in addr else AF_INET
-    return inet_pton(family, addr)
-
-  @staticmethod
-  def PaddedAddress(addr):
-    """Converts an IP address string to binary format for InetDiagSockId."""
-    padded = SockDiag.RawAddress(addr)
-    if len(padded) < 16:
-      padded += "\x00" * (16 - len(padded))
-    return padded
-
-  @staticmethod
-  def DiagReqFromSocket(s):
-    """Creates an InetDiagReqV2 that matches the specified socket."""
-    family = s.getsockopt(net_test.SOL_SOCKET, net_test.SO_DOMAIN)
-    protocol = s.getsockopt(net_test.SOL_SOCKET, net_test.SO_PROTOCOL)
-    if net_test.LINUX_VERSION >= (3, 8):
-      iface = s.getsockopt(SOL_SOCKET, net_test.SO_BINDTODEVICE,
-                           net_test.IFNAMSIZ)
-      iface = GetInterfaceIndex(iface) if iface else 0
-    else:
-      iface = 0
-    src, sport = s.getsockname()[:2]
-    try:
-      dst, dport = s.getpeername()[:2]
-    except error, e:
-      if e.errno == errno.ENOTCONN:
-        dport = 0
-        dst = "::" if family == AF_INET6 else "0.0.0.0"
-      else:
-        raise e
-    src = SockDiag.PaddedAddress(src)
-    dst = SockDiag.PaddedAddress(dst)
-    sock_id = InetDiagSockId((sport, dport, src, dst, iface, "\x00" * 8))
-    return InetDiagReqV2((family, protocol, 0, 0xffffffff, sock_id))
-
-  def FindSockDiagFromReq(self, req):
-    for diag_msg, attrs in self.Dump(req):
-      return diag_msg
-    raise ValueError("Dump of %s returned no sockets" % req)
-
-  def FindSockDiagFromFd(self, s):
-    """Gets an InetDiagMsg from the kernel for the specified socket."""
-    req = self.DiagReqFromSocket(s)
-    return self.FindSockDiagFromReq(req)
-
-  def GetSockDiag(self, req):
-    """Gets an InetDiagMsg from the kernel for the specified request."""
-    self._SendNlRequest(SOCK_DIAG_BY_FAMILY, req.Pack(), netlink.NLM_F_REQUEST)
-    return self._GetMsg(InetDiagMsg)[0]
-
-  @staticmethod
-  def DiagReqFromDiagMsg(d, protocol):
-    """Constructs a diag_req from a diag_msg the kernel has given us."""
-    return InetDiagReqV2((d.family, protocol, 0, 1 << d.state, d.id))
-
-  def CloseSocket(self, req):
-    self._SendNlRequest(SOCK_DESTROY, req.Pack(),
-                        netlink.NLM_F_REQUEST | netlink.NLM_F_ACK)
-
-  def CloseSocketFromFd(self, s):
-    diag_msg = self.FindSockDiagFromFd(s)
-    protocol = s.getsockopt(SOL_SOCKET, net_test.SO_PROTOCOL)
-    req = self.DiagReqFromDiagMsg(diag_msg, protocol)
-    return self.CloseSocket(req)
-
-
-if __name__ == "__main__":
-  n = SockDiag()
-  n.DEBUG = True
-  bytecode = ""
-  sock_id = n._EmptyInetDiagSockId()
-  sock_id.dport = 443
-  ext = 1 << (INET_DIAG_TOS - 1) | 1 << (INET_DIAG_TCLASS - 1)
-  states = 0xffffffff
-  diag_msgs = n.DumpAllInetSockets(IPPROTO_TCP, "",
-                                   sock_id=sock_id, ext=ext, states=states)
-  print diag_msgs
diff --git a/tests/net_test/sock_diag_test.py b/tests/net_test/sock_diag_test.py
deleted file mode 100755
index 3c5d0a9..0000000
--- a/tests/net_test/sock_diag_test.py
+++ /dev/null
@@ -1,548 +0,0 @@
-#!/usr/bin/python
-#
-# Copyright 2015 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# pylint: disable=g-bad-todo,g-bad-file-header,wildcard-import
-from errno import *  # pylint: disable=wildcard-import
-import os
-import random
-import re
-from socket import *  # pylint: disable=wildcard-import
-import threading
-import time
-import unittest
-
-import multinetwork_base
-import net_test
-import packets
-import sock_diag
-import tcp_test
-
-
-NUM_SOCKETS = 30
-NO_BYTECODE = ""
-
-
-class SockDiagBaseTest(multinetwork_base.MultiNetworkBaseTest):
-
-  @staticmethod
-  def _CreateLotsOfSockets():
-    # Dict mapping (addr, sport, dport) tuples to socketpairs.
-    socketpairs = {}
-    for _ in xrange(NUM_SOCKETS):
-      family, addr = random.choice([
-          (AF_INET, "127.0.0.1"),
-          (AF_INET6, "::1"),
-          (AF_INET6, "::ffff:127.0.0.1")])
-      socketpair = net_test.CreateSocketPair(family, SOCK_STREAM, addr)
-      sport, dport = (socketpair[0].getsockname()[1],
-                      socketpair[1].getsockname()[1])
-      socketpairs[(addr, sport, dport)] = socketpair
-    return socketpairs
-
-  def assertSocketClosed(self, sock):
-    self.assertRaisesErrno(ENOTCONN, sock.getpeername)
-
-  def assertSocketConnected(self, sock):
-    sock.getpeername()  # No errors? Socket is alive and connected.
-
-  def assertSocketsClosed(self, socketpair):
-    for sock in socketpair:
-      self.assertSocketClosed(sock)
-
-  def setUp(self):
-    super(SockDiagBaseTest, self).setUp()
-    self.sock_diag = sock_diag.SockDiag()
-    self.socketpairs = {}
-
-  def tearDown(self):
-    for socketpair in self.socketpairs.values():
-      for s in socketpair:
-        s.close()
-    super(SockDiagBaseTest, self).tearDown()
-
-
-class SockDiagTest(SockDiagBaseTest):
-
-  def assertSockDiagMatchesSocket(self, s, diag_msg):
-    family = s.getsockopt(net_test.SOL_SOCKET, net_test.SO_DOMAIN)
-    self.assertEqual(diag_msg.family, family)
-
-    src, sport = s.getsockname()[0:2]
-    self.assertEqual(diag_msg.id.src, self.sock_diag.PaddedAddress(src))
-    self.assertEqual(diag_msg.id.sport, sport)
-
-    if self.sock_diag.GetDestinationAddress(diag_msg) not in ["0.0.0.0", "::"]:
-      dst, dport = s.getpeername()[0:2]
-      self.assertEqual(diag_msg.id.dst, self.sock_diag.PaddedAddress(dst))
-      self.assertEqual(diag_msg.id.dport, dport)
-    else:
-      self.assertRaisesErrno(ENOTCONN, s.getpeername)
-
-  def testFindsMappedSockets(self):
-    """Tests that inet_diag_find_one_icsk can find mapped sockets.
-
-    Relevant kernel commits:
-      android-3.10:
-        f77e059 net: diag: support v4mapped sockets in inet_diag_find_one_icsk()
-    """
-    socketpair = net_test.CreateSocketPair(AF_INET6, SOCK_STREAM,
-                                           "::ffff:127.0.0.1")
-    for sock in socketpair:
-      diag_msg = self.sock_diag.FindSockDiagFromFd(sock)
-      diag_req = self.sock_diag.DiagReqFromDiagMsg(diag_msg, IPPROTO_TCP)
-      self.sock_diag.GetSockDiag(diag_req)
-      # No errors? Good.
-
-  def testFindsAllMySockets(self):
-    """Tests that basic socket dumping works.
-
-    Relevant commits:
-      android-3.4:
-        ab4a727 net: inet_diag: zero out uninitialized idiag_{src,dst} fields
-      android-3.10
-        3eb409b net: inet_diag: zero out uninitialized idiag_{src,dst} fields
-    """
-    self.socketpairs = self._CreateLotsOfSockets()
-    sockets = self.sock_diag.DumpAllInetSockets(IPPROTO_TCP, NO_BYTECODE)
-    self.assertGreaterEqual(len(sockets), NUM_SOCKETS)
-
-    # Find the cookies for all of our sockets.
-    cookies = {}
-    for diag_msg, unused_attrs in sockets:
-      addr = self.sock_diag.GetSourceAddress(diag_msg)
-      sport = diag_msg.id.sport
-      dport = diag_msg.id.dport
-      if (addr, sport, dport) in self.socketpairs:
-        cookies[(addr, sport, dport)] = diag_msg.id.cookie
-      elif (addr, dport, sport) in self.socketpairs:
-        cookies[(addr, sport, dport)] = diag_msg.id.cookie
-
-    # Did we find all the cookies?
-    self.assertEquals(2 * NUM_SOCKETS, len(cookies))
-
-    socketpairs = self.socketpairs.values()
-    random.shuffle(socketpairs)
-    for socketpair in socketpairs:
-      for sock in socketpair:
-        # Check that we can find a diag_msg by scanning a dump.
-        self.assertSockDiagMatchesSocket(
-            sock,
-            self.sock_diag.FindSockDiagFromFd(sock))
-        cookie = self.sock_diag.FindSockDiagFromFd(sock).id.cookie
-
-        # Check that we can find a diag_msg once we know the cookie.
-        req = self.sock_diag.DiagReqFromSocket(sock)
-        req.id.cookie = cookie
-        diag_msg = self.sock_diag.GetSockDiag(req)
-        req.states = 1 << diag_msg.state
-        self.assertSockDiagMatchesSocket(sock, diag_msg)
-
-  def testBytecodeCompilation(self):
-    # pylint: disable=bad-whitespace
-    instructions = [
-        (sock_diag.INET_DIAG_BC_S_GE,   1, 8, 0),                      # 0
-        (sock_diag.INET_DIAG_BC_D_LE,   1, 7, 0xffff),                 # 8
-        (sock_diag.INET_DIAG_BC_S_COND, 1, 2, ("::1", 128, -1)),       # 16
-        (sock_diag.INET_DIAG_BC_JMP,    1, 3, None),                   # 44
-        (sock_diag.INET_DIAG_BC_S_COND, 2, 4, ("127.0.0.1", 32, -1)),  # 48
-        (sock_diag.INET_DIAG_BC_D_LE,   1, 3, 0x6665),  # not used     # 64
-        (sock_diag.INET_DIAG_BC_NOP,    1, 1, None),                   # 72
-                                                                       # 76 acc
-                                                                       # 80 rej
-    ]
-    # pylint: enable=bad-whitespace
-    bytecode = self.sock_diag.PackBytecode(instructions)
-    expected = (
-        "0208500000000000"
-        "050848000000ffff"
-        "071c20000a800000ffffffff00000000000000000000000000000001"
-        "01041c00"
-        "0718200002200000ffffffff7f000001"
-        "0508100000006566"
-        "00040400"
-    )
-    self.assertMultiLineEqual(expected, bytecode.encode("hex"))
-    self.assertEquals(76, len(bytecode))
-    self.socketpairs = self._CreateLotsOfSockets()
-    filteredsockets = self.sock_diag.DumpAllInetSockets(IPPROTO_TCP, bytecode)
-    allsockets = self.sock_diag.DumpAllInetSockets(IPPROTO_TCP, NO_BYTECODE)
-    self.assertItemsEqual(allsockets, filteredsockets)
-
-    # Pick a few sockets in hash table order, and check that the bytecode we
-    # compiled selects them properly.
-    for socketpair in self.socketpairs.values()[:20]:
-      for s in socketpair:
-        diag_msg = self.sock_diag.FindSockDiagFromFd(s)
-        instructions = [
-            (sock_diag.INET_DIAG_BC_S_GE, 1, 5, diag_msg.id.sport),
-            (sock_diag.INET_DIAG_BC_S_LE, 1, 4, diag_msg.id.sport),
-            (sock_diag.INET_DIAG_BC_D_GE, 1, 3, diag_msg.id.dport),
-            (sock_diag.INET_DIAG_BC_D_LE, 1, 2, diag_msg.id.dport),
-        ]
-        bytecode = self.sock_diag.PackBytecode(instructions)
-        self.assertEquals(32, len(bytecode))
-        sockets = self.sock_diag.DumpAllInetSockets(IPPROTO_TCP, bytecode)
-        self.assertEquals(1, len(sockets))
-
-        # TODO: why doesn't comparing the cstructs work?
-        self.assertEquals(diag_msg.Pack(), sockets[0][0].Pack())
-
-  def testCrossFamilyBytecode(self):
-    """Checks for a cross-family bug in inet_diag_hostcond matching.
-
-    Relevant kernel commits:
-      android-3.4:
-        f67caec inet_diag: avoid unsafe and nonsensical prefix matches in inet_diag_bc_run()
-    """
-    # TODO: this is only here because the test fails if there are any open
-    # sockets other than the ones it creates itself. Make the bytecode more
-    # specific and remove it.
-    self.assertFalse(self.sock_diag.DumpAllInetSockets(IPPROTO_TCP, ""))
-
-    unused_pair4 = net_test.CreateSocketPair(AF_INET, SOCK_STREAM, "127.0.0.1")
-    unused_pair6 = net_test.CreateSocketPair(AF_INET6, SOCK_STREAM, "::1")
-
-    bytecode4 = self.sock_diag.PackBytecode([
-        (sock_diag.INET_DIAG_BC_S_COND, 1, 2, ("0.0.0.0", 0, -1))])
-    bytecode6 = self.sock_diag.PackBytecode([
-        (sock_diag.INET_DIAG_BC_S_COND, 1, 2, ("::", 0, -1))])
-
-    # IPv4/v6 filters must never match IPv6/IPv4 sockets...
-    v4sockets = self.sock_diag.DumpAllInetSockets(IPPROTO_TCP, bytecode4)
-    self.assertTrue(v4sockets)
-    self.assertTrue(all(d.family == AF_INET for d, _ in v4sockets))
-
-    v6sockets = self.sock_diag.DumpAllInetSockets(IPPROTO_TCP, bytecode6)
-    self.assertTrue(v6sockets)
-    self.assertTrue(all(d.family == AF_INET6 for d, _ in v6sockets))
-
-    # Except for mapped addresses, which match both IPv4 and IPv6.
-    pair5 = net_test.CreateSocketPair(AF_INET6, SOCK_STREAM,
-                                      "::ffff:127.0.0.1")
-    diag_msgs = [self.sock_diag.FindSockDiagFromFd(s) for s in pair5]
-    v4sockets = [d for d, _ in self.sock_diag.DumpAllInetSockets(IPPROTO_TCP,
-                                                                 bytecode4)]
-    v6sockets = [d for d, _ in self.sock_diag.DumpAllInetSockets(IPPROTO_TCP,
-                                                                 bytecode6)]
-    self.assertTrue(all(d in v4sockets for d in diag_msgs))
-    self.assertTrue(all(d in v6sockets for d in diag_msgs))
-
-  def testPortComparisonValidation(self):
-    """Checks for a bug in validating port comparison bytecode.
-
-    Relevant kernel commits:
-      android-3.4:
-        5e1f542 inet_diag: validate port comparison byte code to prevent unsafe reads
-    """
-    bytecode = sock_diag.InetDiagBcOp((sock_diag.INET_DIAG_BC_D_GE, 4, 8))
-    self.assertRaisesErrno(
-        EINVAL,
-        self.sock_diag.DumpAllInetSockets, IPPROTO_TCP, bytecode.Pack())
-
-  def testNonSockDiagCommand(self):
-    def DiagDump(code):
-      sock_id = self.sock_diag._EmptyInetDiagSockId()
-      req = sock_diag.InetDiagReqV2((AF_INET6, IPPROTO_TCP, 0, 0xffffffff,
-                                     sock_id))
-      self.sock_diag._Dump(code, req, sock_diag.InetDiagMsg, "")
-
-    op = sock_diag.SOCK_DIAG_BY_FAMILY
-    DiagDump(op)  # No errors? Good.
-    self.assertRaisesErrno(EINVAL, DiagDump, op + 17)
-
-
-class SockDestroyTest(SockDiagBaseTest):
-  """Tests that SOCK_DESTROY works correctly.
-
-  Relevant kernel commits:
-    net-next:
-      b613f56 net: diag: split inet_diag_dump_one_icsk into two
-      64be0ae net: diag: Add the ability to destroy a socket.
-      6eb5d2e net: diag: Support SOCK_DESTROY for inet sockets.
-      c1e64e2 net: diag: Support destroying TCP sockets.
-      2010b93 net: tcp: deal with listen sockets properly in tcp_abort.
-
-    android-3.4:
-      d48ec88 net: diag: split inet_diag_dump_one_icsk into two
-      2438189 net: diag: Add the ability to destroy a socket.
-      7a2ddbc net: diag: Support SOCK_DESTROY for inet sockets.
-      44047b2 net: diag: Support destroying TCP sockets.
-      200dae7 net: tcp: deal with listen sockets properly in tcp_abort.
-
-    android-3.10:
-      9eaff90 net: diag: split inet_diag_dump_one_icsk into two
-      d60326c net: diag: Add the ability to destroy a socket.
-      3d4ce85 net: diag: Support SOCK_DESTROY for inet sockets.
-      529dfc6 net: diag: Support destroying TCP sockets.
-      9c712fe net: tcp: deal with listen sockets properly in tcp_abort.
-
-    android-3.18:
-      100263d net: diag: split inet_diag_dump_one_icsk into two
-      194c5f3 net: diag: Add the ability to destroy a socket.
-      8387ea2 net: diag: Support SOCK_DESTROY for inet sockets.
-      b80585a net: diag: Support destroying TCP sockets.
-      476c6ce net: tcp: deal with listen sockets properly in tcp_abort.
-  """
-
-  def testClosesSockets(self):
-    self.socketpairs = self._CreateLotsOfSockets()
-    for _, socketpair in self.socketpairs.iteritems():
-      # Close one of the sockets.
-      # This will send a RST that will close the other side as well.
-      s = random.choice(socketpair)
-      if random.randrange(0, 2) == 1:
-        self.sock_diag.CloseSocketFromFd(s)
-      else:
-        diag_msg = self.sock_diag.FindSockDiagFromFd(s)
-
-        # Get the cookie wrong and ensure that we get an error and the socket
-        # is not closed.
-        real_cookie = diag_msg.id.cookie
-        diag_msg.id.cookie = os.urandom(len(real_cookie))
-        req = self.sock_diag.DiagReqFromDiagMsg(diag_msg, IPPROTO_TCP)
-        self.assertRaisesErrno(ENOENT, self.sock_diag.CloseSocket, req)
-        self.assertSocketConnected(s)
-
-        # Now close it with the correct cookie.
-        req.id.cookie = real_cookie
-        self.sock_diag.CloseSocket(req)
-
-      # Check that both sockets in the pair are closed.
-      self.assertSocketsClosed(socketpair)
-
-  def testNonTcpSockets(self):
-    s = socket(AF_INET6, SOCK_DGRAM, 0)
-    s.connect(("::1", 53))
-    self.sock_diag.FindSockDiagFromFd(s)  # No exceptions? Good.
-    self.assertRaisesErrno(EOPNOTSUPP, self.sock_diag.CloseSocketFromFd, s)
-
-  # TODO:
-  # Test that killing unix sockets returns EOPNOTSUPP.
-
-
-class SocketExceptionThread(threading.Thread):
-
-  def __init__(self, sock, operation):
-    self.exception = None
-    super(SocketExceptionThread, self).__init__()
-    self.daemon = True
-    self.sock = sock
-    self.operation = operation
-
-  def run(self):
-    try:
-      self.operation(self.sock)
-    except IOError, e:
-      self.exception = e
-
-
-class SockDiagTcpTest(tcp_test.TcpBaseTest, SockDiagBaseTest):
-
-  def testIpv4MappedSynRecvSocket(self):
-    """Tests for the absence of a bug with AF_INET6 TCP SYN-RECV sockets.
-
-    Relevant kernel commits:
-         android-3.4:
-           457a04b inet_diag: fix oops for IPv4 AF_INET6 TCP SYN-RECV state
-    """
-    netid = random.choice(self.tuns.keys())
-    self.IncomingConnection(5, tcp_test.TCP_SYN_RECV, netid)
-    sock_id = self.sock_diag._EmptyInetDiagSockId()
-    sock_id.sport = self.port
-    states = 1 << tcp_test.TCP_SYN_RECV
-    req = sock_diag.InetDiagReqV2((AF_INET6, IPPROTO_TCP, 0, states, sock_id))
-    children = self.sock_diag.Dump(req, NO_BYTECODE)
-
-    self.assertTrue(children)
-    for child, unused_args in children:
-      self.assertEqual(tcp_test.TCP_SYN_RECV, child.state)
-      self.assertEqual(self.sock_diag.PaddedAddress(self.remoteaddr),
-                       child.id.dst)
-      self.assertEqual(self.sock_diag.PaddedAddress(self.myaddr),
-                       child.id.src)
-
-
-class SockDestroyTcpTest(tcp_test.TcpBaseTest, SockDiagBaseTest):
-
-  def setUp(self):
-    super(SockDestroyTcpTest, self).setUp()
-    self.netid = random.choice(self.tuns.keys())
-
-  def CheckRstOnClose(self, sock, req, expect_reset, msg, do_close=True):
-    """Closes the socket and checks whether a RST is sent or not."""
-    if sock is not None:
-      self.assertIsNone(req, "Must specify sock or req, not both")
-      self.sock_diag.CloseSocketFromFd(sock)
-      self.assertRaisesErrno(EINVAL, sock.accept)
-    else:
-      self.assertIsNone(sock, "Must specify sock or req, not both")
-      self.sock_diag.CloseSocket(req)
-
-    if expect_reset:
-      desc, rst = self.RstPacket()
-      msg = "%s: expecting %s: " % (msg, desc)
-      self.ExpectPacketOn(self.netid, msg, rst)
-    else:
-      msg = "%s: " % msg
-      self.ExpectNoPacketsOn(self.netid, msg)
-
-    if sock is not None and do_close:
-      sock.close()
-
-  def CheckTcpReset(self, state, statename):
-    for version in [4, 5, 6]:
-      msg = "Closing incoming IPv%d %s socket" % (version, statename)
-      self.IncomingConnection(version, state, self.netid)
-      self.CheckRstOnClose(self.s, None, False, msg)
-      if state != tcp_test.TCP_LISTEN:
-        msg = "Closing accepted IPv%d %s socket" % (version, statename)
-        self.CheckRstOnClose(self.accepted, None, True, msg)
-
-  def testTcpResets(self):
-    """Checks that closing sockets in appropriate states sends a RST."""
-    self.CheckTcpReset(tcp_test.TCP_LISTEN, "TCP_LISTEN")
-    self.CheckTcpReset(tcp_test.TCP_ESTABLISHED, "TCP_ESTABLISHED")
-    self.CheckTcpReset(tcp_test.TCP_CLOSE_WAIT, "TCP_CLOSE_WAIT")
-
-  def FindChildSockets(self, s):
-    """Finds the SYN_RECV child sockets of a given listening socket."""
-    d = self.sock_diag.FindSockDiagFromFd(self.s)
-    req = self.sock_diag.DiagReqFromDiagMsg(d, IPPROTO_TCP)
-    req.states = 1 << tcp_test.TCP_SYN_RECV | 1 << tcp_test.TCP_ESTABLISHED
-    req.id.cookie = "\x00" * 8
-    children = self.sock_diag.Dump(req, NO_BYTECODE)
-    return [self.sock_diag.DiagReqFromDiagMsg(d, IPPROTO_TCP)
-            for d, _ in children]
-
-  def CheckChildSocket(self, version, statename, parent_first):
-    state = getattr(tcp_test, statename)
-
-    self.IncomingConnection(version, state, self.netid)
-
-    d = self.sock_diag.FindSockDiagFromFd(self.s)
-    parent = self.sock_diag.DiagReqFromDiagMsg(d, IPPROTO_TCP)
-    children = self.FindChildSockets(self.s)
-    self.assertEquals(1, len(children))
-
-    is_established = (state == tcp_test.TCP_NOT_YET_ACCEPTED)
-
-    # The new TCP listener code in 4.4 makes SYN_RECV sockets live in the
-    # regular TCP hash tables, and inet_diag_find_one_icsk can find them.
-    # Before 4.4, we can see those sockets in dumps, but we can't fetch
-    # or close them.
-    can_close_children = is_established or net_test.LINUX_VERSION >= (4, 4)
-
-    for child in children:
-      if can_close_children:
-        self.sock_diag.GetSockDiag(child)  # No errors? Good, child found.
-      else:
-        self.assertRaisesErrno(ENOENT, self.sock_diag.GetSockDiag, child)
-
-    def CloseParent(expect_reset):
-      msg = "Closing parent IPv%d %s socket %s child" % (
-          version, statename, "before" if parent_first else "after")
-      self.CheckRstOnClose(self.s, None, expect_reset, msg)
-      self.assertRaisesErrno(ENOENT, self.sock_diag.GetSockDiag, parent)
-
-    def CheckChildrenClosed():
-      for child in children:
-        self.assertRaisesErrno(ENOENT, self.sock_diag.GetSockDiag, child)
-
-    def CloseChildren():
-      for child in children:
-        msg = "Closing child IPv%d %s socket %s parent" % (
-            version, statename, "after" if parent_first else "before")
-        self.sock_diag.GetSockDiag(child)
-        self.CheckRstOnClose(None, child, is_established, msg)
-        self.assertRaisesErrno(ENOENT, self.sock_diag.GetSockDiag, child)
-      CheckChildrenClosed()
-
-    if parent_first:
-      # Closing the parent will close child sockets, which will send a RST,
-      # iff they are already established.
-      CloseParent(is_established)
-      if is_established:
-        CheckChildrenClosed()
-      elif can_close_children:
-        CloseChildren()
-        CheckChildrenClosed()
-      self.s.close()
-    else:
-      if can_close_children:
-        CloseChildren()
-      CloseParent(False)
-      self.s.close()
-
-  def testChildSockets(self):
-    for version in [4, 5, 6]:
-      self.CheckChildSocket(version, "TCP_SYN_RECV", False)
-      self.CheckChildSocket(version, "TCP_SYN_RECV", True)
-      self.CheckChildSocket(version, "TCP_NOT_YET_ACCEPTED", False)
-      self.CheckChildSocket(version, "TCP_NOT_YET_ACCEPTED", True)
-
-  def CloseDuringBlockingCall(self, sock, call, expected_errno):
-    thread = SocketExceptionThread(sock, call)
-    thread.start()
-    time.sleep(0.1)
-    self.sock_diag.CloseSocketFromFd(sock)
-    thread.join(1)
-    self.assertFalse(thread.is_alive())
-    self.assertIsNotNone(thread.exception)
-    self.assertTrue(isinstance(thread.exception, IOError),
-                    "Expected IOError, got %s" % thread.exception)
-    self.assertEqual(expected_errno, thread.exception.errno)
-    self.assertSocketClosed(sock)
-
-  def testAcceptInterrupted(self):
-    """Tests that accept() is interrupted by SOCK_DESTROY."""
-    for version in [4, 5, 6]:
-      self.IncomingConnection(version, tcp_test.TCP_LISTEN, self.netid)
-      self.CloseDuringBlockingCall(self.s, lambda sock: sock.accept(), EINVAL)
-      self.assertRaisesErrno(ECONNABORTED, self.s.send, "foo")
-      self.assertRaisesErrno(EINVAL, self.s.accept)
-
-  def testReadInterrupted(self):
-    """Tests that read() is interrupted by SOCK_DESTROY."""
-    for version in [4, 5, 6]:
-      self.IncomingConnection(version, tcp_test.TCP_ESTABLISHED, self.netid)
-      self.CloseDuringBlockingCall(self.accepted, lambda sock: sock.recv(4096),
-                                   ECONNABORTED)
-      self.assertRaisesErrno(EPIPE, self.accepted.send, "foo")
-
-  def testConnectInterrupted(self):
-    """Tests that connect() is interrupted by SOCK_DESTROY."""
-    for version in [4, 5, 6]:
-      family = {4: AF_INET, 5: AF_INET6, 6: AF_INET6}[version]
-      s = net_test.Socket(family, SOCK_STREAM, IPPROTO_TCP)
-      self.SelectInterface(s, self.netid, "mark")
-      if version == 5:
-        remoteaddr = "::ffff:" + self.GetRemoteAddress(4)
-        version = 4
-      else:
-        remoteaddr = self.GetRemoteAddress(version)
-      s.bind(("", 0))
-      _, sport = s.getsockname()[:2]
-      self.CloseDuringBlockingCall(
-          s, lambda sock: sock.connect((remoteaddr, 53)), ECONNABORTED)
-      desc, syn = packets.SYN(53, version, self.MyAddress(version, self.netid),
-                              remoteaddr, sport=sport, seq=None)
-      self.ExpectPacketOn(self.netid, desc, syn)
-      msg = "SOCK_DESTROY of socket in connect, expected no RST"
-      self.ExpectNoPacketsOn(self.netid, msg)
-
-
-if __name__ == "__main__":
-  unittest.main()
diff --git a/tests/net_test/srcaddr_selection_test.py b/tests/net_test/srcaddr_selection_test.py
deleted file mode 100755
index d3efdd9..0000000
--- a/tests/net_test/srcaddr_selection_test.py
+++ /dev/null
@@ -1,345 +0,0 @@
-#!/usr/bin/python
-#
-# Copyright 2014 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import errno
-import random
-from socket import *  # pylint: disable=wildcard-import
-import time
-import unittest
-
-from scapy import all as scapy
-
-import csocket
-import iproute
-import multinetwork_base
-import packets
-import net_test
-
-# Setsockopt values.
-IPV6_ADDR_PREFERENCES = 72
-IPV6_PREFER_SRC_PUBLIC = 0x0002
-
-
-class IPv6SourceAddressSelectionTest(multinetwork_base.MultiNetworkBaseTest):
-  """Test for IPv6 source address selection.
-
-  Relevant kernel commits:
-    upstream net-next:
-      7fd2561 net: ipv6: Add a sysctl to make optimistic addresses useful candidates
-      c58da4c net: ipv6: allow explicitly choosing optimistic addresses
-      9131f3d ipv6: Do not iterate over all interfaces when finding source address on specific interface.
-      c0b8da1 ipv6: Fix finding best source address in ipv6_dev_get_saddr().
-      c15df30 ipv6: Remove unused arguments for __ipv6_dev_get_saddr().
-      3985e8a ipv6: sysctl to restrict candidate source addresses
-
-    android-3.10:
-      2ce95507 net: ipv6: Add a sysctl to make optimistic addresses useful candidates
-      0065bf4 net: ipv6: allow choosing optimistic addresses with use_optimistic
-      0633924 ipv6: sysctl to restrict candidate source addresses
-  """
-
-  def SetIPv6Sysctl(self, ifname, sysctl, value):
-    self.SetSysctl("/proc/sys/net/ipv6/conf/%s/%s" % (ifname, sysctl), value)
-
-  def SetDAD(self, ifname, value):
-    self.SetSysctl("/proc/sys/net/ipv6/conf/%s/accept_dad" % ifname, value)
-    self.SetSysctl("/proc/sys/net/ipv6/conf/%s/dad_transmits" % ifname, value)
-
-  def SetOptimisticDAD(self, ifname, value):
-    self.SetSysctl("/proc/sys/net/ipv6/conf/%s/optimistic_dad" % ifname, value)
-
-  def SetUseTempaddrs(self, ifname, value):
-    self.SetSysctl("/proc/sys/net/ipv6/conf/%s/use_tempaddr" % ifname, value)
-
-  def SetUseOptimistic(self, ifname, value):
-    self.SetSysctl("/proc/sys/net/ipv6/conf/%s/use_optimistic" % ifname, value)
-
-  def GetSourceIP(self, netid, mode="mark"):
-    s = self.BuildSocket(6, net_test.UDPSocket, netid, mode)
-    # Because why not...testing for temporary addresses is a separate thing.
-    s.setsockopt(IPPROTO_IPV6, IPV6_ADDR_PREFERENCES, IPV6_PREFER_SRC_PUBLIC)
-
-    s.connect((net_test.IPV6_ADDR, 123))
-    src_addr = s.getsockname()[0]
-    self.assertTrue(src_addr)
-    return src_addr
-
-  def assertAddressNotPresent(self, address):
-    self.assertRaises(IOError, self.iproute.GetAddress, address)
-
-  def assertAddressHasExpectedAttributes(
-      self, address, expected_ifindex, expected_flags):
-    ifa_msg = self.iproute.GetAddress(address)[0]
-    self.assertEquals(AF_INET6 if ":" in address else AF_INET, ifa_msg.family)
-    self.assertEquals(64, ifa_msg.prefixlen)
-    self.assertEquals(iproute.RT_SCOPE_UNIVERSE, ifa_msg.scope)
-    self.assertEquals(expected_ifindex, ifa_msg.index)
-    self.assertEquals(expected_flags, ifa_msg.flags & expected_flags)
-
-  def AddressIsTentative(self, address):
-    ifa_msg = self.iproute.GetAddress(address)[0]
-    return ifa_msg.flags & iproute.IFA_F_TENTATIVE
-
-  def BindToAddress(self, address):
-    s = net_test.UDPSocket(AF_INET6)
-    s.bind((address, 0, 0, 0))
-
-  def SendWithSourceAddress(self, address, netid, dest=net_test.IPV6_ADDR):
-    pktinfo = multinetwork_base.MakePktInfo(6, address, 0)
-    cmsgs = [(net_test.SOL_IPV6, IPV6_PKTINFO, pktinfo)]
-    s = self.BuildSocket(6, net_test.UDPSocket, netid, "mark")
-    return csocket.Sendmsg(s, (dest, 53), "Hello", cmsgs, 0)
-
-  def assertAddressUsable(self, address, netid):
-    self.BindToAddress(address)
-    self.SendWithSourceAddress(address, netid)
-    # No exceptions? Good.
-
-  def assertAddressNotUsable(self, address, netid):
-    self.assertRaisesErrno(errno.EADDRNOTAVAIL, self.BindToAddress, address)
-    self.assertRaisesErrno(errno.EINVAL,
-                           self.SendWithSourceAddress, address, netid)
-
-  def assertAddressSelected(self, address, netid):
-    self.assertEquals(address, self.GetSourceIP(netid))
-
-  def assertAddressNotSelected(self, address, netid):
-    self.assertNotEquals(address, self.GetSourceIP(netid))
-
-  def WaitForDad(self, address):
-    for _ in xrange(20):
-      if not self.AddressIsTentative(address):
-        return
-      time.sleep(0.1)
-    raise AssertionError("%s did not complete DAD after 2 seconds")
-
-
-class MultiInterfaceSourceAddressSelectionTest(IPv6SourceAddressSelectionTest):
-
-  def setUp(self):
-    # [0]  Make sure DAD, optimistic DAD, and the use_optimistic option
-    # are all consistently disabled at the outset.
-    for netid in self.tuns:
-      ifname = self.GetInterfaceName(netid)
-      self.SetDAD(ifname, 0)
-      self.SetOptimisticDAD(ifname, 0)
-      self.SetUseTempaddrs(ifname, 0)
-      self.SetUseOptimistic(ifname, 0)
-      self.SetIPv6Sysctl(ifname, "use_oif_addrs_only", 0)
-
-    # [1]  Pick an interface on which to test.
-    self.test_netid = random.choice(self.tuns.keys())
-    self.test_ip = self.MyAddress(6, self.test_netid)
-    self.test_ifindex = self.ifindices[self.test_netid]
-    self.test_ifname = self.GetInterfaceName(self.test_netid)
-    self.test_lladdr = net_test.GetLinkAddress(self.test_ifname, True)
-
-    # [2]  Delete the test interface's IPv6 address.
-    self.iproute.DelAddress(self.test_ip, 64, self.test_ifindex)
-    self.assertAddressNotPresent(self.test_ip)
-
-    self.assertAddressNotUsable(self.test_ip, self.test_netid)
-    # Verify that the link-local address is not tentative.
-    self.assertFalse(self.AddressIsTentative(self.test_lladdr))
-
-
-class TentativeAddressTest(MultiInterfaceSourceAddressSelectionTest):
-
-  def testRfc6724Behaviour(self):
-    # [3]  Get an IPv6 address back, in DAD start-up.
-    self.SetDAD(self.test_ifname, 1)  # Enable DAD
-    # Send a RA to start SLAAC and subsequent DAD.
-    self.SendRA(self.test_netid, 0)
-    # Get flags and prove tentative-ness.
-    self.assertAddressHasExpectedAttributes(
-        self.test_ip, self.test_ifindex, iproute.IFA_F_TENTATIVE)
-
-    # Even though the interface has an IPv6 address, its tentative nature
-    # prevents it from being selected.
-    self.assertAddressNotUsable(self.test_ip, self.test_netid)
-    self.assertAddressNotSelected(self.test_ip, self.test_netid)
-
-    # Busy wait for DAD to complete (should be less than 1 second).
-    self.WaitForDad(self.test_ip)
-
-    # The test_ip should have completed DAD by now, and should be the
-    # chosen source address, eligible to bind to, etc.
-    self.assertAddressUsable(self.test_ip, self.test_netid)
-    self.assertAddressSelected(self.test_ip, self.test_netid)
-
-
-class OptimisticAddressTest(MultiInterfaceSourceAddressSelectionTest):
-
-  def testRfc6724Behaviour(self):
-    # [3]  Get an IPv6 address back, in optimistic DAD start-up.
-    self.SetDAD(self.test_ifname, 1)  # Enable DAD
-    self.SetOptimisticDAD(self.test_ifname, 1)
-    # Send a RA to start SLAAC and subsequent DAD.
-    self.SendRA(self.test_netid, 0)
-    # Get flags and prove optimism.
-    self.assertAddressHasExpectedAttributes(
-        self.test_ip, self.test_ifindex, iproute.IFA_F_OPTIMISTIC)
-
-    # Optimistic addresses are usable but are not selected.
-    if net_test.LinuxVersion() >= (3, 18, 0):
-      # The version checked in to android kernels <= 3.10 requires the
-      # use_optimistic sysctl to be turned on.
-      self.assertAddressUsable(self.test_ip, self.test_netid)
-    self.assertAddressNotSelected(self.test_ip, self.test_netid)
-
-    # Busy wait for DAD to complete (should be less than 1 second).
-    self.WaitForDad(self.test_ip)
-
-    # The test_ip should have completed DAD by now, and should be the
-    # chosen source address.
-    self.assertAddressUsable(self.test_ip, self.test_netid)
-    self.assertAddressSelected(self.test_ip, self.test_netid)
-
-
-class OptimisticAddressOkayTest(MultiInterfaceSourceAddressSelectionTest):
-
-  def testModifiedRfc6724Behaviour(self):
-    # [3]  Get an IPv6 address back, in optimistic DAD start-up.
-    self.SetDAD(self.test_ifname, 1)  # Enable DAD
-    self.SetOptimisticDAD(self.test_ifname, 1)
-    self.SetUseOptimistic(self.test_ifname, 1)
-    # Send a RA to start SLAAC and subsequent DAD.
-    self.SendRA(self.test_netid, 0)
-    # Get flags and prove optimistism.
-    self.assertAddressHasExpectedAttributes(
-        self.test_ip, self.test_ifindex, iproute.IFA_F_OPTIMISTIC)
-
-    # The interface has an IPv6 address and, despite its optimistic nature,
-    # the use_optimistic option allows it to be selected.
-    self.assertAddressUsable(self.test_ip, self.test_netid)
-    self.assertAddressSelected(self.test_ip, self.test_netid)
-
-
-class ValidBeforeOptimisticTest(MultiInterfaceSourceAddressSelectionTest):
-
-  def testModifiedRfc6724Behaviour(self):
-    # [3]  Add a valid IPv6 address to this interface and verify it is
-    # selected as the source address.
-    preferred_ip = self.IPv6Prefix(self.test_netid) + "cafe"
-    self.iproute.AddAddress(preferred_ip, 64, self.test_ifindex)
-    self.assertAddressHasExpectedAttributes(
-        preferred_ip, self.test_ifindex, iproute.IFA_F_PERMANENT)
-    self.assertEquals(preferred_ip, self.GetSourceIP(self.test_netid))
-
-    # [4]  Get another IPv6 address, in optimistic DAD start-up.
-    self.SetDAD(self.test_ifname, 1)  # Enable DAD
-    self.SetOptimisticDAD(self.test_ifname, 1)
-    self.SetUseOptimistic(self.test_ifname, 1)
-    # Send a RA to start SLAAC and subsequent DAD.
-    self.SendRA(self.test_netid, 0)
-    # Get flags and prove optimism.
-    self.assertAddressHasExpectedAttributes(
-        self.test_ip, self.test_ifindex, iproute.IFA_F_OPTIMISTIC)
-
-    # Since the interface has another IPv6 address, the optimistic address
-    # is not selected--the other, valid address is chosen.
-    self.assertAddressUsable(self.test_ip, self.test_netid)
-    self.assertAddressNotSelected(self.test_ip, self.test_netid)
-    self.assertAddressSelected(preferred_ip, self.test_netid)
-
-
-class DadFailureTest(MultiInterfaceSourceAddressSelectionTest):
-
-  def testDadFailure(self):
-    # [3]  Get an IPv6 address back, in optimistic DAD start-up.
-    self.SetDAD(self.test_ifname, 1)  # Enable DAD
-    self.SetOptimisticDAD(self.test_ifname, 1)
-    self.SetUseOptimistic(self.test_ifname, 1)
-    # Send a RA to start SLAAC and subsequent DAD.
-    self.SendRA(self.test_netid, 0)
-    # Prove optimism and usability.
-    self.assertAddressHasExpectedAttributes(
-        self.test_ip, self.test_ifindex, iproute.IFA_F_OPTIMISTIC)
-    self.assertAddressUsable(self.test_ip, self.test_netid)
-    self.assertAddressSelected(self.test_ip, self.test_netid)
-
-    # Send a NA for the optimistic address, indicating address conflict
-    # ("DAD defense").
-    conflict_macaddr = "02:00:0b:ad:d0:0d"
-    dad_defense = (scapy.Ether(src=conflict_macaddr, dst="33:33:33:00:00:01") /
-                   scapy.IPv6(src=self.test_ip, dst="ff02::1") /
-                   scapy.ICMPv6ND_NA(tgt=self.test_ip, R=0, S=0, O=1) /
-                   scapy.ICMPv6NDOptDstLLAddr(lladdr=conflict_macaddr))
-    self.ReceiveEtherPacketOn(self.test_netid, dad_defense)
-
-    # The address should have failed DAD, and therefore no longer be usable.
-    self.assertAddressNotUsable(self.test_ip, self.test_netid)
-    self.assertAddressNotSelected(self.test_ip, self.test_netid)
-
-    # TODO(ek): verify that an RTM_DELADDR issued for the DAD-failed address.
-
-
-class NoNsFromOptimisticTest(MultiInterfaceSourceAddressSelectionTest):
-
-  def testSendToOnlinkDestination(self):
-    # [3]  Get an IPv6 address back, in optimistic DAD start-up.
-    self.SetDAD(self.test_ifname, 1)  # Enable DAD
-    self.SetOptimisticDAD(self.test_ifname, 1)
-    self.SetUseOptimistic(self.test_ifname, 1)
-    # Send a RA to start SLAAC and subsequent DAD.
-    self.SendRA(self.test_netid, 0)
-    # Prove optimism and usability.
-    self.assertAddressHasExpectedAttributes(
-        self.test_ip, self.test_ifindex, iproute.IFA_F_OPTIMISTIC)
-    self.assertAddressUsable(self.test_ip, self.test_netid)
-    self.assertAddressSelected(self.test_ip, self.test_netid)
-
-    # [4]  Send to an on-link destination and observe a Neighbor Solicitation
-    # packet with a source address that is NOT the optimistic address.
-    # In this setup, the only usable address is the link-local address.
-    onlink_dest = self.GetRandomDestination(self.IPv6Prefix(self.test_netid))
-    self.SendWithSourceAddress(self.test_ip, self.test_netid, onlink_dest)
-
-    if net_test.LinuxVersion() >= (3, 18, 0):
-      # Older versions will actually choose the optimistic address to
-      # originate Neighbor Solications (RFC violation).
-      expected_ns = packets.NS(
-          self.test_lladdr,
-          onlink_dest,
-          self.MyMacAddress(self.test_netid))[1]
-      self.ExpectPacketOn(self.test_netid, "link-local NS", expected_ns)
-
-
-# TODO(ek): add tests listening for netlink events.
-
-
-class DefaultCandidateSrcAddrsTest(MultiInterfaceSourceAddressSelectionTest):
-
-  def testChoosesNonInterfaceSourceAddress(self):
-    self.SetIPv6Sysctl(self.test_ifname, "use_oif_addrs_only", 0)
-    src_ip = self.GetSourceIP(self.test_netid)
-    self.assertFalse(src_ip in [self.test_ip, self.test_lladdr])
-    self.assertTrue(src_ip in
-                    [self.MyAddress(6, netid)
-                     for netid in self.tuns if netid != self.test_netid])
-
-
-class RestrictedCandidateSrcAddrsTest(MultiInterfaceSourceAddressSelectionTest):
-
-  def testChoosesOnlyInterfaceSourceAddress(self):
-    self.SetIPv6Sysctl(self.test_ifname, "use_oif_addrs_only", 1)
-    # self.test_ifname does not have a global IPv6 address, so the only
-    # candidate is the existing link-local address.
-    self.assertAddressSelected(self.test_lladdr, self.test_netid)
-
-
-if __name__ == "__main__":
-  unittest.main()
diff --git a/tests/net_test/tcp_nuke_addr_test.py b/tests/net_test/tcp_nuke_addr_test.py
deleted file mode 100755
index b0ba27d..0000000
--- a/tests/net_test/tcp_nuke_addr_test.py
+++ /dev/null
@@ -1,250 +0,0 @@
-#!/usr/bin/python
-#
-# Copyright 2015 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import contextlib
-import errno
-import fcntl
-import resource
-import os
-from socket import *  # pylint: disable=wildcard-import
-import struct
-import threading
-import time
-import unittest
-
-import csocket
-import cstruct
-import net_test
-
-IPV4_LOOPBACK_ADDR = "127.0.0.1"
-IPV6_LOOPBACK_ADDR = "::1"
-LOOPBACK_DEV = "lo"
-LOOPBACK_IFINDEX = 1
-
-SIOCKILLADDR = 0x8939
-
-DEFAULT_TCP_PORT = 8001
-DEFAULT_BUFFER_SIZE = 20
-DEFAULT_TEST_MESSAGE = "TCP NUKE ADDR TEST"
-DEFAULT_TEST_RUNS = 100
-HASH_TEST_RUNS = 4000
-HASH_TEST_NOFILE = 16384
-
-
-Ifreq = cstruct.Struct("Ifreq", "=16s16s", "name data")
-In6Ifreq = cstruct.Struct("In6Ifreq", "=16sIi", "addr prefixlen ifindex")
-
-@contextlib.contextmanager
-def RunInBackground(thread):
-  """Starts a thread and waits until it joins.
-
-  Args:
-    thread: A not yet started threading.Thread object.
-  """
-  try:
-    thread.start()
-    yield thread
-  finally:
-    thread.join()
-
-
-def TcpAcceptAndReceive(listening_sock, buffer_size=DEFAULT_BUFFER_SIZE):
-  """Accepts a single connection and blocks receiving data from it.
-
-  Args:
-    listening_socket: A socket in LISTEN state.
-    buffer_size: Size of buffer where to read a message.
-  """
-  connection, _ = listening_sock.accept()
-  with contextlib.closing(connection):
-    _ = connection.recv(buffer_size)
-
-
-def ExchangeMessage(addr_family, ip_addr):
-  """Creates a listening socket, accepts a connection and sends data to it.
-
-  Args:
-    addr_family: The address family (e.g. AF_INET6).
-    ip_addr: The IP address (IPv4 or IPv6 depending on the addr_family).
-    tcp_port: The TCP port to listen on.
-  """
-  # Bind to a random port and connect to it.
-  test_addr = (ip_addr, 0)
-  with contextlib.closing(
-      socket(addr_family, SOCK_STREAM)) as listening_socket:
-    listening_socket.bind(test_addr)
-    test_addr = listening_socket.getsockname()
-    listening_socket.listen(1)
-    with RunInBackground(threading.Thread(target=TcpAcceptAndReceive,
-                                          args=(listening_socket,))):
-      with contextlib.closing(
-          socket(addr_family, SOCK_STREAM)) as client_socket:
-        client_socket.connect(test_addr)
-        client_socket.send(DEFAULT_TEST_MESSAGE)
-
-
-def KillAddrIoctl(addr):
-  """Calls the SIOCKILLADDR ioctl on the provided IP address.
-
-  Args:
-    addr The IP address to pass to the ioctl.
-
-  Raises:
-    ValueError: If addr is of an unsupported address family.
-  """
-  family, _, _, _, _ = getaddrinfo(addr, None, AF_UNSPEC, SOCK_DGRAM, 0,
-                                   AI_NUMERICHOST)[0]
-  if family == AF_INET6:
-    addr = inet_pton(AF_INET6, addr)
-    ifreq = In6Ifreq((addr, 128, LOOPBACK_IFINDEX)).Pack()
-  elif family == AF_INET:
-    addr = inet_pton(AF_INET, addr)
-    sockaddr = csocket.SockaddrIn((AF_INET, 0, addr)).Pack()
-    ifreq = Ifreq((LOOPBACK_DEV, sockaddr)).Pack()
-  else:
-    raise ValueError('Address family %r not supported.' % family)
-  datagram_socket = socket(family, SOCK_DGRAM)
-  fcntl.ioctl(datagram_socket.fileno(), SIOCKILLADDR, ifreq)
-  datagram_socket.close()
-
-
-class ExceptionalReadThread(threading.Thread):
-
-  def __init__(self, sock):
-    self.sock = sock
-    self.exception = None
-    super(ExceptionalReadThread, self).__init__()
-    self.daemon = True
-
-  def run(self):
-    try:
-      read = self.sock.recv(4096)
-    except Exception, e:
-      self.exception = e
-
-# For convenience.
-def CreateIPv4SocketPair():
-  return net_test.CreateSocketPair(AF_INET, SOCK_STREAM, IPV4_LOOPBACK_ADDR)
-
-def CreateIPv6SocketPair():
-  return net_test.CreateSocketPair(AF_INET6, SOCK_STREAM, IPV6_LOOPBACK_ADDR)
-
-
-class TcpNukeAddrTest(net_test.NetworkTest):
-
-  def testTimewaitSockets(self):
-    """Tests that SIOCKILLADDR works as expected.
-
-    Relevant kernel commits:
-      https://www.codeaurora.org/cgit/quic/la/kernel/msm-3.18/commit/net/ipv4/tcp.c?h=aosp/android-3.10&id=1dcd3a1fa2fe78251cc91700eb1d384ab02e2dd6
-    """
-    for i in xrange(DEFAULT_TEST_RUNS):
-      ExchangeMessage(AF_INET6, IPV6_LOOPBACK_ADDR)
-      KillAddrIoctl(IPV6_LOOPBACK_ADDR)
-      ExchangeMessage(AF_INET, IPV4_LOOPBACK_ADDR)
-      KillAddrIoctl(IPV4_LOOPBACK_ADDR)
-      # Test passes if kernel does not crash.
-
-  def testClosesIPv6Sockets(self):
-    """Tests that SIOCKILLADDR closes IPv6 sockets and unblocks threads."""
-
-    threadpairs = []
-
-    for i in xrange(DEFAULT_TEST_RUNS):
-      clientsock, acceptedsock = CreateIPv6SocketPair()
-      clientthread = ExceptionalReadThread(clientsock)
-      clientthread.start()
-      serverthread = ExceptionalReadThread(acceptedsock)
-      serverthread.start()
-      threadpairs.append((clientthread, serverthread))
-
-    KillAddrIoctl(IPV6_LOOPBACK_ADDR)
-
-    def CheckThreadException(thread):
-      thread.join(100)
-      self.assertFalse(thread.is_alive())
-      self.assertIsNotNone(thread.exception)
-      self.assertTrue(isinstance(thread.exception, IOError))
-      self.assertEquals(errno.ETIMEDOUT, thread.exception.errno)
-      self.assertRaisesErrno(errno.ENOTCONN, thread.sock.getpeername)
-      self.assertRaisesErrno(errno.EISCONN, thread.sock.connect,
-                             (IPV6_LOOPBACK_ADDR, 53))
-      self.assertRaisesErrno(errno.EPIPE, thread.sock.send, "foo")
-
-    for clientthread, serverthread in threadpairs:
-      CheckThreadException(clientthread)
-      CheckThreadException(serverthread)
-
-  def assertSocketsClosed(self, socketpair):
-    for sock in socketpair:
-      self.assertRaisesErrno(errno.ENOTCONN, sock.getpeername)
-
-  def assertSocketsNotClosed(self, socketpair):
-    for sock in socketpair:
-      self.assertTrue(sock.getpeername())
-
-  def testAddresses(self):
-    socketpair = CreateIPv4SocketPair()
-    KillAddrIoctl("::")
-    self.assertSocketsNotClosed(socketpair)
-    KillAddrIoctl("::1")
-    self.assertSocketsNotClosed(socketpair)
-    KillAddrIoctl("127.0.0.3")
-    self.assertSocketsNotClosed(socketpair)
-    KillAddrIoctl("0.0.0.0")
-    self.assertSocketsNotClosed(socketpair)
-    KillAddrIoctl("127.0.0.1")
-    self.assertSocketsClosed(socketpair)
-
-    socketpair = CreateIPv6SocketPair()
-    KillAddrIoctl("0.0.0.0")
-    self.assertSocketsNotClosed(socketpair)
-    KillAddrIoctl("127.0.0.1")
-    self.assertSocketsNotClosed(socketpair)
-    KillAddrIoctl("::2")
-    self.assertSocketsNotClosed(socketpair)
-    KillAddrIoctl("::")
-    self.assertSocketsNotClosed(socketpair)
-    KillAddrIoctl("::1")
-    self.assertSocketsClosed(socketpair)
-
-
-class TcpNukeAddrHashTest(net_test.NetworkTest):
-
-  def setUp(self):
-    self.nofile = resource.getrlimit(resource.RLIMIT_NOFILE)
-    resource.setrlimit(resource.RLIMIT_NOFILE, (HASH_TEST_NOFILE,
-                                                HASH_TEST_NOFILE))
-
-  def tearDown(self):
-    resource.setrlimit(resource.RLIMIT_NOFILE, self.nofile)
-
-  def testClosesAllSockets(self):
-    socketpairs = []
-    for i in xrange(HASH_TEST_RUNS):
-      socketpairs.append(CreateIPv4SocketPair())
-      socketpairs.append(CreateIPv6SocketPair())
-
-    KillAddrIoctl(IPV4_LOOPBACK_ADDR)
-    KillAddrIoctl(IPV6_LOOPBACK_ADDR)
-
-    for socketpair in socketpairs:
-      for sock in socketpair:
-        self.assertRaisesErrno(errno.ENOTCONN, sock.getpeername)
-
-
-if __name__ == "__main__":
-  unittest.main()
diff --git a/tests/net_test/tcp_test.py b/tests/net_test/tcp_test.py
deleted file mode 100644
index 81a6884..0000000
--- a/tests/net_test/tcp_test.py
+++ /dev/null
@@ -1,124 +0,0 @@
-#!/usr/bin/python
-#
-# Copyright 2015 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import time
-from socket import *  # pylint: disable=wildcard-import
-
-import net_test
-import multinetwork_base
-import packets
-
-# TCP states. See include/net/tcp_states.h.
-TCP_ESTABLISHED = 1
-TCP_SYN_SENT = 2
-TCP_SYN_RECV = 3
-TCP_FIN_WAIT1 = 4
-TCP_FIN_WAIT2 = 5
-TCP_TIME_WAIT = 6
-TCP_CLOSE = 7
-TCP_CLOSE_WAIT = 8
-TCP_LAST_ACK = 9
-TCP_LISTEN = 10
-TCP_CLOSING = 11
-TCP_NEW_SYN_RECV = 12
-
-TCP_NOT_YET_ACCEPTED = -1
-
-
-class TcpBaseTest(multinetwork_base.MultiNetworkBaseTest):
-
-  def tearDown(self):
-    if hasattr(self, "s"):
-      self.s.close()
-    super(TcpBaseTest, self).tearDown()
-
-  def OpenListenSocket(self, version, netid):
-    self.port = packets.RandomPort()
-    family = {4: AF_INET, 5: AF_INET6, 6: AF_INET6}[version]
-    address = {4: "0.0.0.0", 5: "::", 6: "::"}[version]
-    s = net_test.Socket(family, SOCK_STREAM, IPPROTO_TCP)
-    s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
-    s.bind((address, self.port))
-    # We haven't configured inbound iptables marking, so bind explicitly.
-    self.SelectInterface(s, netid, "mark")
-    s.listen(100)
-    return s
-
-  def _ReceiveAndExpectResponse(self, netid, packet, reply, msg):
-    pkt = super(TcpBaseTest, self)._ReceiveAndExpectResponse(netid, packet,
-                                                             reply, msg)
-    self.last_packet = pkt
-    return pkt
-
-  def ReceivePacketOn(self, netid, packet):
-    super(TcpBaseTest, self).ReceivePacketOn(netid, packet)
-    self.last_packet = packet
-
-  def RstPacket(self):
-    return packets.RST(self.version, self.myaddr, self.remoteaddr,
-                       self.last_packet)
-
-  def IncomingConnection(self, version, end_state, netid):
-    self.s = self.OpenListenSocket(version, netid)
-    self.end_state = end_state
-
-    remoteaddr = self.remoteaddr = self.GetRemoteAddress(version)
-    myaddr = self.myaddr = self.MyAddress(version, netid)
-
-    if version == 5: version = 4
-    self.version = version
-
-    if end_state == TCP_LISTEN:
-      return
-
-    desc, syn = packets.SYN(self.port, version, remoteaddr, myaddr)
-    synack_desc, synack = packets.SYNACK(version, myaddr, remoteaddr, syn)
-    msg = "Received %s, expected to see reply %s" % (desc, synack_desc)
-    reply = self._ReceiveAndExpectResponse(netid, syn, synack, msg)
-    if end_state == TCP_SYN_RECV:
-      return
-
-    establishing_ack = packets.ACK(version, remoteaddr, myaddr, reply)[1]
-    self.ReceivePacketOn(netid, establishing_ack)
-
-    if end_state == TCP_NOT_YET_ACCEPTED:
-      return
-
-    self.accepted, _ = self.s.accept()
-    net_test.DisableLinger(self.accepted)
-
-    if end_state == TCP_ESTABLISHED:
-      return
-
-    desc, data = packets.ACK(version, myaddr, remoteaddr, establishing_ack,
-                             payload=net_test.UDP_PAYLOAD)
-    self.accepted.send(net_test.UDP_PAYLOAD)
-    self.ExpectPacketOn(netid, msg + ": expecting %s" % desc, data)
-
-    desc, fin = packets.FIN(version, remoteaddr, myaddr, data)
-    fin = packets._GetIpLayer(version)(str(fin))
-    ack_desc, ack = packets.ACK(version, myaddr, remoteaddr, fin)
-    msg = "Received %s, expected to see reply %s" % (desc, ack_desc)
-
-    # TODO: Why can't we use this?
-    #   self._ReceiveAndExpectResponse(netid, fin, ack, msg)
-    self.ReceivePacketOn(netid, fin)
-    time.sleep(0.1)
-    self.ExpectPacketOn(netid, msg + ": expecting %s" % ack_desc, ack)
-    if end_state == TCP_CLOSE_WAIT:
-      return
-
-    raise ValueError("Invalid TCP state %d specified" % end_state)
diff --git a/tests/pagingtest/pageinout_test.c b/tests/pagingtest/pageinout_test.c
index b9b20de..887794e 100644
--- a/tests/pagingtest/pageinout_test.c
+++ b/tests/pagingtest/pageinout_test.c
@@ -1,5 +1,6 @@
 #include <errno.h>
 #include <fcntl.h>
+#include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -8,12 +9,12 @@
 
 #include "pagingtest.h"
 
-int pageinout_test(int test_runs, unsigned long long file_size) {
+int pageinout_test(int test_runs, bool cache, unsigned long long file_size) {
     int fd;
     char tmpname[] = "pageinoutXXXXXX";
     unsigned char *vec;
     int i;
-    long long j;
+    unsigned long long j;
     volatile char *buf;
     int ret = -1;
     int rc;
@@ -43,10 +44,19 @@
         goto err;
     }
 
+    if (!cache) {
+        //madvise and fadvise as random to prevent prefetching
+        rc = madvise((void *)buf, file_size, MADV_RANDOM) ||
+               posix_fadvise(fd, 0, file_size, POSIX_FADV_RANDOM);
+        if (rc) {
+            goto err;
+        }
+    }
+
     for (i = 0; i < test_runs; i++) {
         gettimeofday(&begin_time, NULL);
-        //Read backwards to prevent mmap prefetching
-        for (j = ((file_size - 1) & ~(pagesize - 1)); j >= 0; j -= pagesize) {
+        //read every page into the page cache
+        for (j = 0; j < file_size; j += pagesize) {
             buf[j];
         }
         gettimeofday(&end_time, NULL);
@@ -75,9 +85,11 @@
         }
     }
 
-    printf("page-in: %llu MB/s\n", (file_size * test_runs * USEC_PER_SEC) /
+    printf("%scached page-in: %llu MB/s\n", cache ? "" : "un",
+             (file_size * test_runs * USEC_PER_SEC) /
              (1024 * 1024 * (total_time_in.tv_sec * USEC_PER_SEC + total_time_in.tv_usec)));
-    printf("page-out (clean): %llu MB/s\n", (file_size * test_runs * USEC_PER_SEC) /
+    printf("%scached page-out (clean): %llu MB/s\n", cache ? "" : "un",
+             (file_size * test_runs * USEC_PER_SEC) /
              (1024 * 1024 * (total_time_out.tv_sec * USEC_PER_SEC + total_time_out.tv_usec)));
 
     ret = 0;
diff --git a/tests/pagingtest/pagingtest.c b/tests/pagingtest/pagingtest.c
index db8512c..158d8a3 100644
--- a/tests/pagingtest/pagingtest.c
+++ b/tests/pagingtest/pagingtest.c
@@ -2,7 +2,9 @@
 
 #include <errno.h>
 #include <fcntl.h>
+#include <stdbool.h>
 #include <stdio.h>
+#include <stdint.h>
 #include <stdlib.h>
 #include <string.h>
 #include <sys/mman.h>
@@ -16,6 +18,8 @@
 
 int create_tmp_file(char *filename, off_t size) {
     void *buf;
+    uint8_t *tmp_buf;
+    off_t tmp_size;
     ssize_t rc;
     int fd;
     int urandom;
@@ -48,17 +52,19 @@
         goto err_mmap;
     }
 
-    rc = read(urandom, buf, size);
+    tmp_buf = buf;
+    tmp_size = size;
+    do {
+        rc = read(urandom, tmp_buf, tmp_size);
 
-    if (rc < 0) {
-        fprintf(stderr, "write random data failed: %s\n", strerror(errno));
-        goto err;
-    }
+        if (rc < 0) {
+            fprintf(stderr, "write random data failed: %s\n", strerror(errno));
+            goto err;
+        }
 
-    if (rc != size) {
-        fprintf(stderr, "write random data incomplete\n");
-        goto err;
-    }
+        tmp_buf += rc;
+        tmp_size -= rc;
+    } while (tmp_size > 0);
 
     if (madvise(buf, size, MADV_DONTNEED)) {
         fprintf(stderr, "madvise DONTNEED failed: %s\n", strerror(errno));
@@ -161,11 +167,19 @@
     if (rc) {
         return rc;
     }
-    rc = pageinout_test(test_runs, file_size);
+    rc = pageinout_test(test_runs, true, file_size);
     if (rc) {
         return rc;
     }
-    rc = thrashing_test(test_runs);
+    rc = pageinout_test(test_runs, false, file_size);
+    if (rc) {
+        return rc;
+    }
+    rc = thrashing_test(test_runs, true);
+    if (rc) {
+        return rc;
+    }
+    rc = thrashing_test(test_runs, false);
 
     return rc;
 }
diff --git a/tests/pagingtest/pagingtest.h b/tests/pagingtest/pagingtest.h
index 2da9818..a6f3d03 100644
--- a/tests/pagingtest/pagingtest.h
+++ b/tests/pagingtest/pagingtest.h
@@ -14,7 +14,7 @@
 
 //Tests
 int mmap_test(int test_runs, unsigned long long alloc_size);
-int pageinout_test(int test_runs, unsigned long long file_size);
-int thrashing_test(int test_runs);
+int pageinout_test(int test_runs, bool cache, unsigned long long file_size);
+int thrashing_test(int test_runs, bool cache);
 
 #endif //__PAGINGTEST_H__
diff --git a/tests/pagingtest/thrashing_test.c b/tests/pagingtest/thrashing_test.c
index 7ecd3ad..0f4547f 100644
--- a/tests/pagingtest/thrashing_test.c
+++ b/tests/pagingtest/thrashing_test.c
@@ -1,5 +1,6 @@
 #include <errno.h>
 #include <fcntl.h>
+#include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -10,11 +11,11 @@
 
 #define LINESIZE 32
 
-int thrashing_test(int test_runs) {
+int thrashing_test(int test_runs, bool cache) {
     int fds[4] = {-1, -1, -1, -1};
     char tmpnames[4][17] = { "thrashing1XXXXXX", "thrashing2XXXXXX", "thrashing3XXXXXX", "thrashing4XXXXXX" };
     volatile char *bufs[4] = {0};
-    long long k;
+    unsigned long long k;
     int ret = -1;
     struct timeval begin_time, end_time, elapsed_time, total_time;
     unsigned long long filesize;
@@ -45,14 +46,20 @@
             fprintf(stderr, "Failed to mmap file: %s\n", strerror(errno));
             goto err;
         }
+        if (!cache) {
+            //madvise and fadvise as random to prevent prefetching
+            ret = madvise((void *)bufs[i], filesize, MADV_RANDOM) ||
+                    posix_fadvise(fds[i], 0, filesize, POSIX_FADV_RANDOM);
+            if (ret) {
+                goto err;
+            }
+        }
     }
 
     for (int i = 0; i < test_runs; i++) {
         for (size_t j = 0; j < ARRAY_SIZE(fds); j++) {
             gettimeofday(&begin_time, NULL);
-            //Unfortunately when under memory pressure, fadvise and madvise stop working...
-            //Read backwards to prevent mmap prefetching
-            for (k = ((filesize - 1) & ~(pagesize - 1)); k >= 0; k -= pagesize) {
+            for (k = 0; k < filesize; k += pagesize) {
                 bufs[j][k];
             }
             gettimeofday(&end_time, NULL);
@@ -62,7 +69,8 @@
         }
     }
 
-    printf("thrashing: %llu MB/s\n", (filesize * ARRAY_SIZE(fds) * test_runs * USEC_PER_SEC) /
+    printf("%scached thrashing: %llu MB/s\n", cache ? "" : "un",
+             (filesize * ARRAY_SIZE(fds) * test_runs * USEC_PER_SEC) /
              (1024 * 1024 * (total_time.tv_sec * USEC_PER_SEC + total_time.tv_usec)));
 
     ret = 0;
diff --git a/tests/suspend_stress/suspend_stress.cpp b/tests/suspend_stress/suspend_stress.cpp
index 08c4685..517581c 100644
--- a/tests/suspend_stress/suspend_stress.cpp
+++ b/tests/suspend_stress/suspend_stress.cpp
@@ -82,7 +82,6 @@
         }
     }
 
-    klog_init();
     klog_set_level(KLOG_INFO_LEVEL);
 
     if (optind < argc) {
diff --git a/tests/wifi/Android.mk b/tests/wifi/Android.mk
deleted file mode 100644
index 4343259..0000000
--- a/tests/wifi/Android.mk
+++ /dev/null
@@ -1,17 +0,0 @@
-#
-# Copyright (C) 2010 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-include $(call all-subdir-makefiles)
diff --git a/tests/wifi/stress/Android.mk b/tests/wifi/stress/Android.mk
deleted file mode 100644
index 2361483..0000000
--- a/tests/wifi/stress/Android.mk
+++ /dev/null
@@ -1,28 +0,0 @@
-#
-# Copyright (C) 2010 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-# Copyright The Android Open Source Project
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := tests
-LOCAL_MODULE := wifiLoadScanAssoc
-LOCAL_SRC_FILES := wifiLoadScanAssoc.c
-LOCAL_SHARED_LIBRARIES += libcutils libutils liblog libhardware_legacy
-LOCAL_STATIC_LIBRARIES += libtestUtil
-LOCAL_C_INCLUDES += system/extras/tests/include \
-    hardware/libhardware_legacy/include
-
-include $(BUILD_NATIVE_TEST)
diff --git a/tests/wifi/stress/wifiLoadScanAssoc.c b/tests/wifi/stress/wifiLoadScanAssoc.c
deleted file mode 100644
index 5dc2692..0000000
--- a/tests/wifi/stress/wifiLoadScanAssoc.c
+++ /dev/null
@@ -1,517 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-/*
- * WiFi load, scan, associate, unload stress test
- *
- * Repeatedly executes the following sequence:
- *
- *   1. Load WiFi driver
- *   2. Start supplicant
- *   3. Random delay
- *   4. Obtain supplicant status (optional)
- *   5. Stop supplicant
- *   6. Unload WiFi driver
- *
- * The "Obtain supplicant status" step is optional and is pseudo
- * randomly performed 50% of the time.  The default range of
- * delay after start supplicant is intentionally selected such
- * that the obtain supplicant status and stop supplicant steps
- * may be performed while the WiFi driver is still performing a scan
- * or associate.  The default values are given by DEFAULT_DELAY_MIN
- * and DEFAULT_DELAY_MAX.  Other values can be specified through the
- * use of the -d and -D command-line options.
- *
- * Each sequence is refered to as a pass and by default an unlimited
- * number of passes are performed.  An override of the range of passes
- * to be executed is available through the use of the -s (start) and
- * -e (end) command-line options.  Can also specify a single specific
- * pass via the -p option.  There is also a default time in which the
- * test executes, which is given by DEFAULT_DURATION and can be overriden
- * through the use of the -t command-line option.
- */
-
-#define _GNU_SOURCE
-
-#include <assert.h>
-#include <errno.h>
-#include <libgen.h>
-#include <math.h>
-#include <sched.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <time.h>
-#include <unistd.h>
-
-#include <sys/syscall.h>
-#include <sys/types.h>
-#include <sys/wait.h>
-
-#include <hardware_legacy/wifi.h>
-
-#define LOG_TAG "wifiLoadScanAssocTest"
-#include <utils/Log.h>
-#include <testUtil.h>
-
-#define DEFAULT_START_PASS     0
-#define DEFAULT_END_PASS     999
-#define DEFAULT_DURATION       FLT_MAX // A fairly long time, so that
-                                       // range of passes will have
-                                       // precedence
-#define DEFAULT_DELAY_MIN      0.0     // Min delay after start supplicant
-#define DEFAULT_DELAY_MAX     20.0     // Max delay after start supplicant
-#define DELAY_EXP            150.0     // Exponent which determines the
-                                       // amount by which values closer
-                                       // to DELAY_MIN are favored.
-
-#define CMD_STATUS           "wpa_cli status 2>&1"
-#define CMD_STOP_FRAMEWORK   "stop 2>&1"
-#define CMD_START_FRAMEWORK  "start 2>&1"
-
-#define MAXSTR      100
-#define MAXCMD      500
-
-typedef unsigned int bool_t;
-#define true (0 == 0)
-#define false (!true)
-
-// File scope variables
-cpu_set_t availCPU;
-unsigned int numAvailCPU;
-float delayMin = DEFAULT_DELAY_MIN;
-float delayMax = DEFAULT_DELAY_MAX;
-bool_t driverLoadedAtStart;
-
-// Command-line mutual exclusion detection flags.
-// Corresponding flag set true once an option is used.
-bool_t eFlag, sFlag, pFlag;
-
-// File scope prototypes
-static void init(void);
-static void randDelay(void);
-static void randBind(const cpu_set_t *availSet, int *chosenCPU);
-
-/*
- * Main
- *
- * Performs the following high-level sequence of operations:
- *
- *   1. Command-line parsing
- *
- *   2. Initialization
- *
- *   3. Execute passes that repeatedly perform the WiFi load, scan,
- *      associate, unload sequence.
- *
- *   4. Restore state of WiFi driver to state it was at the
- *      start of the test.
- *
- *   5. Restart framework
- */
-int
-main(int argc, char *argv[])
-{
-    FILE *fp;
-    int rv, opt;
-    int cpu;
-    char *chptr;
-    unsigned int pass;
-    char cmd[MAXCMD];
-    float duration = DEFAULT_DURATION;
-    unsigned int startPass = DEFAULT_START_PASS, endPass = DEFAULT_END_PASS;
-    struct timeval startTime, currentTime, delta;
-
-    testSetLogCatTag(LOG_TAG);
-
-    // Parse command line arguments
-    while ((opt = getopt(argc, argv, "d:D:s:e:p:t:?")) != -1) {
-        switch (opt) {
-        case 'd': // Minimum Delay
-            delayMin = strtod(optarg, &chptr);
-            if ((*chptr != '\0') || (delayMin < 0.0)) {
-                testPrintE("Invalid command-line specified minimum delay "
-                    "of: %s", optarg);
-                exit(1);
-            }
-            break;
-
-        case 'D': // Maximum Delay
-            delayMax = strtod(optarg, &chptr);
-            if ((*chptr != '\0') || (delayMax < 0.0)) {
-                testPrintE("Invalid command-line specified maximum delay "
-                    "of: %s", optarg);
-                exit(2);
-            }
-            break;
-
-        case 't': // Duration
-            duration = strtod(optarg, &chptr);
-            if ((*chptr != '\0') || (duration < 0.0)) {
-                testPrintE("Invalid command-line specified duration of: %s",
-                    optarg);
-                exit(3);
-            }
-            break;
-
-        case 's': // Starting Pass
-            if (sFlag || pFlag) {
-                testPrintE("Invalid combination of command-line options,");
-                if (sFlag) {
-                    testPrintE("  -s flag specified multiple times.");
-                } else {
-                    testPrintE("  -s and -p flags are mutually exclusive.");
-                }
-                exit(10);
-            }
-            sFlag = true;
-            startPass = strtoul(optarg, &chptr, 10);
-            if (*chptr != '\0') {
-                testPrintE("Invalid command-line specified starting pass "
-                    "of: %s", optarg);
-                exit(4);
-            }
-            break;
-
-        case 'e': // Ending Pass
-            if (eFlag || pFlag) {
-                testPrintE("Invalid combination of command-line options,");
-                if (sFlag) {
-                    testPrintE("  -e flag specified multiple times.");
-                } else {
-                    testPrintE("  -e and -p flags are mutually exclusive.");
-                }
-                exit(11);
-            }
-            eFlag = true;
-            endPass = strtoul(optarg, &chptr, 10);
-            if (*chptr != '\0') {
-                testPrintE("Invalid command-line specified ending pass "
-                    "of: %s", optarg);
-                exit(5);
-            }
-            break;
-
-        case 'p': // Single Specific Pass
-            if (pFlag || sFlag || eFlag) {
-                testPrintE("Invalid combination of command-line options,");
-                if (pFlag) {
-                    testPrintE("  -p flag specified multiple times.");
-                } else {
-                    testPrintE("  -p and -%c flags are mutually exclusive.",
-                        (sFlag) ? 's' : 'e');
-                }
-                exit(12);
-            }
-            pFlag = true;
-            endPass = startPass = strtoul(optarg, &chptr, 10);
-            if (*chptr != '\0') {
-                testPrintE("Invalid command-line specified pass "
-                    "of: %s", optarg);
-                exit(13);
-            }
-            break;
-
-        case '?':
-        default:
-            testPrintE("  %s [options]", basename(argv[0]));
-            testPrintE("    options:");
-            testPrintE("      -s Starting pass");
-            testPrintE("      -e Ending pass");
-            testPrintE("      -p Specific single pass");
-            testPrintE("      -t Duration");
-            testPrintE("      -d Delay min");
-            testPrintE("      -D Delay max");
-            exit(((optopt == 0) || (optopt == '?')) ? 0 : 6);
-        }
-    }
-    if (delayMax < delayMin) {
-        testPrintE("Unexpected maximum delay less than minimum delay");
-        testPrintE("  delayMin: %f delayMax: %f", delayMin, delayMax);
-        exit(7);
-    }
-    if (endPass < startPass) {
-        testPrintE("Unexpected ending pass before starting pass");
-        testPrintE("  startPass: %u endPass: %u", startPass, endPass);
-        exit(8);
-    }
-    if (argc != optind) {
-        testPrintE("Unexpected command-line postional argument");
-        testPrintE("  %s [-s start_pass] [-e end_pass] [-d duration]",
-            basename(argv[0]));
-        exit(9);
-    }
-    testPrintI("duration: %g", duration);
-    testPrintI("startPass: %u", startPass);
-    testPrintI("endPass: %u", endPass);
-    testPrintI("delayMin: %f", delayMin);
-    testPrintI("delayMax: %f", delayMax);
-
-    init();
-
-    // For each pass
-    gettimeofday(&startTime, NULL);
-    for (pass = startPass; pass <= endPass; pass++) {
-        // Stop if duration of work has already been performed
-        gettimeofday(&currentTime, NULL);
-        delta = tvDelta(&startTime, &currentTime);
-        if (tv2double(&delta) > duration) { break; }
-
-        testPrintI("==== Starting pass: %u", pass);
-
-        // Use a pass dependent sequence of random numbers
-        srand48(pass);
-
-        // Load WiFi Driver
-        randBind(&availCPU, &cpu);
-        if ((rv = wifi_load_driver()) != 0) {
-            testPrintE("CPU: %i wifi_load_driver() failed, rv: %i\n",
-                cpu, rv);
-            exit(20);
-        }
-        testPrintI("CPU: %i wifi_load_driver succeeded", cpu);
-
-        // Start Supplicant
-        randBind(&availCPU, &cpu);
-        if ((rv = wifi_start_supplicant(false)) != 0) {
-            testPrintE("CPU: %i wifi_start_supplicant() failed, rv: %i\n",
-                cpu, rv);
-            exit(21);
-        }
-        testPrintI("CPU: %i wifi_start_supplicant succeeded", cpu);
-
-        // Sleep a random amount of time
-        randDelay();
-
-        /*
-         * Obtain WiFi Status
-         * Half the time skip this step, which helps increase the
-         * level of randomization.
-         */
-        if (testRandBool()) {
-            rv = snprintf(cmd, sizeof(cmd), "%s", CMD_STATUS);
-            if (rv >= (signed) sizeof(cmd) - 1) {
-                testPrintE("Command too long for: %s\n", CMD_STATUS);
-                exit(22);
-            }
-            testExecCmd(cmd);
-        }
-
-        // Stop Supplicant
-        randBind(&availCPU, &cpu);
-        if ((rv = wifi_stop_supplicant(false)) != 0) {
-            testPrintE("CPU: %i wifi_stop_supplicant() failed, rv: %i\n",
-                cpu, rv);
-            exit(23);
-        }
-        testPrintI("CPU: %i wifi_stop_supplicant succeeded", cpu);
-
-        // Unload WiFi Module
-        randBind(&availCPU, &cpu);
-        if ((rv = wifi_unload_driver()) != 0) {
-            testPrintE("CPU: %i wifi_unload_driver() failed, rv: %i\n",
-                cpu, rv);
-            exit(24);
-        }
-        testPrintI("CPU: %i wifi_unload_driver succeeded", cpu);
-
-        testPrintI("==== Completed pass: %u", pass);
-    }
-
-    // If needed restore WiFi driver to state it was in at the
-    // start of the test.  It is assumed that it the driver
-    // was loaded, then the wpa_supplicant was also running.
-    if (driverLoadedAtStart) {
-        // Load driver
-        if ((rv = wifi_load_driver()) != 0) {
-            testPrintE("main load driver failed, rv: %i", rv);
-            exit(25);
-        }
-
-        // Start supplicant
-        if ((rv = wifi_start_supplicant(false)) != 0) {
-            testPrintE("main start supplicant failed, rv: %i", rv);
-            exit(26);
-        }
-
-        // Obtain WiFi Status
-        rv = snprintf(cmd, sizeof(cmd), "%s", CMD_STATUS);
-        if (rv >= (signed) sizeof(cmd) - 1) {
-            testPrintE("Command too long for: %s\n", CMD_STATUS);
-            exit(22);
-        }
-        testExecCmd(cmd);
-    }
-
-    // Start framework
-    rv = snprintf(cmd, sizeof(cmd), "%s", CMD_START_FRAMEWORK);
-    if (rv >= (signed) sizeof(cmd) - 1) {
-        testPrintE("Command too long for: %s\n", CMD_START_FRAMEWORK);
-        exit(27);
-    }
-    testExecCmd(cmd);
-
-    testPrintI("Successfully completed %u passes", pass - startPass);
-
-    return 0;
-}
-
-/*
- * Initialize
- *
- * Perform testcase initialization, which includes:
- *
- *   1. Determine which CPUs are available for use
- *
- *   2. Determine total number of available CPUs
- *
- *   3. Stop framework
- *
- *   4. Determine whether WiFi driver is loaded and if so
- *      stop wpa_supplicant and unload WiFi driver.
- */
-void
-init(void)
-{
-    int rv;
-    unsigned int n1;
-    char cmd[MAXCMD];
-
-    // Use whichever CPUs are available at start of test
-    rv = sched_getaffinity(0, sizeof(availCPU), &availCPU);
-    if (rv != 0) {
-        testPrintE("init sched_getaffinity failed, rv: %i errno: %i",
-            rv, errno);
-        exit(40);
-    }
-
-    // How many CPUs are available
-    numAvailCPU = 0;
-    for (n1 = 0; n1 < CPU_SETSIZE; n1++) {
-        if (CPU_ISSET(n1, &availCPU)) { numAvailCPU++; }
-    }
-    testPrintI("numAvailCPU: %u", numAvailCPU);
-
-    // Stop framework
-    rv = snprintf(cmd, sizeof(cmd), "%s", CMD_STOP_FRAMEWORK);
-    if (rv >= (signed) sizeof(cmd) - 1) {
-        testPrintE("Command too long for: %s\n", CMD_STOP_FRAMEWORK);
-        exit(41);
-    }
-    testExecCmd(cmd);
-
-    // Is WiFi driver loaded?
-    // If so stop the wpa_supplicant and unload the driver.
-    driverLoadedAtStart = is_wifi_driver_loaded();
-    testPrintI("driverLoadedAtStart: %u", driverLoadedAtStart);
-    if (driverLoadedAtStart) {
-        // Stop wpa_supplicant
-        // Might already be stopped, in which case request should
-        // return immediately with success.
-        if ((rv = wifi_stop_supplicant(false)) != 0) {
-            testPrintE("init stop supplicant failed, rv: %i", rv);
-            exit(42);
-        }
-        testPrintI("Stopped wpa_supplicant");
-
-        if ((rv = wifi_unload_driver()) != 0) {
-            testPrintE("init unload driver failed, rv: %i", rv);
-            exit(43);
-        }
-        testPrintI("WiFi driver unloaded");
-    }
-
-}
-
-/*
- * Random Delay
- *
- * Delays for a random amount of time within the range given
- * by the file scope variables delayMin and delayMax.  The
- * selected amount of delay can come from any part of the
- * range, with a bias towards values closer to delayMin.
- * The amount of bias is determined by the setting of DELAY_EXP.
- * The setting of DELAY_EXP should always be > 1.0, with higher
- * values causing a more significant bias toward the value
- * of delayMin.
- */
-void randDelay(void)
-{
-    const unsigned long nanosecspersec = 1000000000;
-    float            fract, biasedFract, amt;
-    struct timeval   startTime, endTime;
-
-    // Obtain start time
-    gettimeofday(&startTime, NULL);
-
-    // Determine random amount to sleep.
-    // Values closer to delayMin are prefered by an amount
-    // determined by the value of DELAY_EXP.
-    fract = testRandFract();
-    biasedFract = pow(DELAY_EXP, fract) / pow(DELAY_EXP, 1.0);
-    amt = delayMin + ((delayMax - delayMin) * biasedFract);
-
-    // Delay
-    testDelay(amt);
-
-    // Obtain end time and display delta
-    gettimeofday(&endTime, NULL);
-    testPrintI("delay: %.2f",
-        (float) (tv2double(&endTime) - tv2double(&startTime)));
-}
-
-static void
-randBind(const cpu_set_t *availSet, int *chosenCPU)
-{
-    int rv;
-    cpu_set_t cpuset;
-    int chosenAvail, avail, cpu, currentCPU;
-
-    // Randomly bind to a CPU
-    // Lower 16 bits from random number generator thrown away,
-    // because the low-order bits tend to have the same sequence for
-    // different seed values.
-    chosenAvail = testRandMod(numAvailCPU);
-    CPU_ZERO(&cpuset);
-    avail = 0;
-    for (cpu = 0; cpu < CPU_SETSIZE; cpu++) {
-        if (CPU_ISSET(cpu, availSet)) {
-            if (chosenAvail == avail) {
-                CPU_SET(cpu, &cpuset);
-                break;
-            }
-            avail++;
-        }
-    }
-    assert(cpu < CPU_SETSIZE);
-    sched_setaffinity(0, sizeof(cpuset), &cpuset);
-
-    // Confirm executing on requested CPU
-    if ((currentCPU = sched_getcpu()) < 0) {
-        testPrintE("randBind sched_getcpu() failed, rv: %i errno: %i",
-                   currentCPU, errno);
-        exit(80);
-
-    }
-    if (currentCPU != cpu) {
-        testPrintE("randBind executing on unexpected CPU %i, expected %i",
-            currentCPU, cpu);
-        exit(81);
-    }
-
-    // Let the caller know which CPU was chosen
-    *chosenCPU = cpu;
-}
diff --git a/tools/graph_lockdep_chains b/tools/graph_lockdep_chains
new file mode 100755
index 0000000..c0c11f4
--- /dev/null
+++ b/tools/graph_lockdep_chains
@@ -0,0 +1,284 @@
+#! /bin/sh
+progname="${0##*/}"
+progname="${progname%.sh}"
+
+usage() {
+  echo "Host side filter pipeline tool to convert kernel /proc/lockdep_chains via"
+  echo "graphviz into dependency chart for visualization. Watch out for any up-arrows"
+  echo "as they signify a circular dependency."
+  echo
+  echo "Usage: ${progname} [flags...] [regex...] < input-file > output-file"
+  echo
+  echo "flags:"
+  echo "       --format={png|ps|svg|fig|imap|cmapx} | -T<format>"
+  echo "           Output format, default png"
+  echo "       --debug | -d"
+  echo "           Leave intermediate files /tmp/${progname}.*"
+  echo "       --verbose | -v"
+  echo "           Do not strip address from lockname"
+  echo "       --focus | -f"
+  echo "           Show only primary references for regex matches"
+  echo "       --cluster"
+  echo "           Cluster the primary references for regex matches"
+  echo "       --serial=<serial> | -s <serial>"
+  echo "           Input from 'adb -s <serial> shell su 0 cat /proc/lockdep_chains'"
+  echo "       --input=<filename> | -i <filename>"
+  echo "           Input lockdeps from filename, otherwise from standard in"
+  echo "       --output=<filename> | -o <filename>"
+  echo "           Output formatted graph to filename, otherwise to standard out"
+  echo
+  echo "Chart is best viewed in portrait. ps or pdf formats tend to pixelate. png tends"
+  echo "to hit a bug in cairo rendering at scale. Not having a set of regex matches for"
+  echo "locknames will probably give you what you deserve ..."
+  echo
+  echo "Kernel Prerequisite to get /proc/lockdep_chains:"
+  echo "       CONFIG_PROVE_LOCKING=y"
+  echo "       CONFIG_LOCK_STAT=y"
+  echo "       CONFIG_DEBUG_LOCKDEP=y"
+}
+
+rm -f /tmp/${progname}.*
+
+# Indent rules and strip out address (may be overridden below)
+beautify() {
+  sed 's/^./    &/
+       s/"[[][0-9a-f]*[]] /"/g'
+}
+
+input="cat -"
+output="cat -"
+
+dot_format="-Tpng"
+filter=
+debug=
+focus=
+cluster=
+
+while [ ${#} -gt 0 ]; do
+  case ${1} in
+
+    -T | --format)
+      dot_format="-T${2}"
+      shift
+      ;;
+
+    -T*)
+      dot_format="${1}"
+      ;;
+
+    --format=*)
+      dot_format="-T${1#--format=}"
+      ;;
+
+    --debug | -d)
+      debug=1
+      ;;
+
+    --verbose | -v)
+      # indent, but do _not_ strip out addresses
+      beautify() {
+        sed 's/^./    &/'
+      }
+      ;;
+
+    --focus | -f | --primary) # reserving --primary
+      focus=1
+      ;;
+
+    --secondary) # reserving --secondary
+      focus=
+      ;;
+
+    --cluster) # reserve -c for dot (configure plugins)
+      cluster=1
+      ;;
+
+    --serial | -s)
+      if [ "${input}" != "cat -" ]; then
+        usage >&2
+        echo "ERROR: --input or --serial can only be specified once" >&2
+        exit 1
+      fi
+      input="adb -s ${2} shell su 0 cat /proc/lockdep_chains"
+      shift
+      ;;
+
+    --serial=*)
+      input="adb -s ${1#--serial=} shell su 0 cat /proc/lockdep_chains"
+      ;;
+
+    --input | -i)
+      if [ "${input}" != "cat -" ]; then
+        usage >&2
+        echo "ERROR: --input or --serial can only be specified once" >&2
+        exit 1
+      fi
+      input="cat ${2}"
+      shift
+      ;;
+
+    --input=*)
+      if [ "${input}" != "cat -" ]; then
+        usage >&2
+        echo "ERROR: --input or --serial can only be specified once" >&2
+        exit 1
+      fi
+      input="cat ${1#--input=}"
+      ;;
+
+    --output | -o)
+      if [ "${output}" != "cat -" ]; then
+        usage >&2
+        echo "ERROR: --output can only be specified once" >&2
+        exit 1
+      fi
+      output="cat - > ${2}" # run through eval
+      shift
+      ;;
+
+    --output=*)
+      if [ "${output}" != "cat -" ]; then
+        usage >&2
+        echo "ERROR: --output can only be specified once" >&2
+        exit 1
+      fi
+      output="cat - > ${1#--output=}" # run through eval
+      ;;
+
+    --help | -h | -\?)
+      usage
+      exit
+      ;;
+
+    *)
+      # Everything else is a filter, which will also hide bad option flags,
+      # which is an as-designed price we pay to allow "->rwlock" for instance.
+      if [ X"${1}" = X"${1#* }" ]; then
+        if [ -z "${filter}" ]; then
+          filter="${1}"
+        else
+          filter="${filter}|${1}"
+        fi
+      else
+        if [ -z "${filter}" ]; then
+          filter=" ${1}"
+        else
+          filter="${filter}| ${1}"
+        fi
+      fi
+      ;;
+
+  esac
+  shift
+done
+
+if [ -z "${filter}" ]; then
+  echo "WARNING: no regex specified will give you what you deserve!" >&2
+fi
+if [ -n "${focus}" -a -z "${filter}" ]; then
+  echo "WARNING: --focus without regex, ignored" >&2
+fi
+if [ -n "${cluster}" -a -z "${filter}" ]; then
+  echo "WARNING: --cluster without regex, ignored" >&2
+fi
+if [ -n "${cluster}" -a -n "${focus}" -a -n "${filter}" ]; then
+  echo "WARNING: orthogonal options --cluster & --focus, ignoring --cluster" >&2
+  cluster=
+fi
+
+# convert to dot digraph series
+${input} |
+  sed '/^all lock chains:$/d
+       / [&]__lockdep_no_validate__$/d
+       /irq_context: 0/d
+       s/irq_context: [1-9]/irq_context/
+       s/..*/"&" ->/
+       s/^$/;/' |
+    sed ': loop
+         N
+         s/ ->\n;$/ ;/
+         t
+         s/ ->\n/ -> /
+         b loop' > /tmp/${progname}.formed
+
+if [ ! -s /tmp/${progname}.formed ]; then
+  echo "ERROR: no input" >&2
+  if [ -z "${debug}" ]; then
+    rm -f /tmp/${progname}.*
+  fi
+  exit 2
+fi
+
+if [ -n "${filter}" ]; then
+  grep "${filter}" /tmp/${progname}.formed |
+    sed 's/ ;//
+         s/ -> /|/g' |
+      tr '|' '\n' |
+        sort -u > /tmp/${progname}.symbols
+fi
+
+(
+  echo 'digraph G {'
+  (
+    echo 'remincross="true";'
+    echo 'concentrate="true";'
+    echo
+
+    if [ -s /tmp/${progname}.symbols ]; then
+      if [ -n "${cluster}" ]; then
+        echo 'subgraph cluster_symbols {'
+        (
+          grep "${filter}" /tmp/${progname}.symbols |
+            sed 's/.*/& [shape=box] ;/'
+          grep -v "${filter}" /tmp/${progname}.symbols |
+            sed 's/.*/& [shape=diamond] ;/'
+        ) | beautify
+        echo '}'
+      else
+        grep "${filter}" /tmp/${progname}.symbols |
+          sed 's/.*/& [shape=box] ;/'
+        grep -v "${filter}" /tmp/${progname}.symbols |
+          sed 's/.*/& [shape=diamond] ;/'
+      fi
+
+      echo
+    fi
+  ) | beautify
+
+  if [ -s /tmp/${progname}.symbols ]; then
+    if [ -z "${focus}" ]; then
+      # Secondary relationships
+      fgrep -f /tmp/${progname}.symbols /tmp/${progname}.formed
+    else
+      # Focus only on primary relationships
+      grep "${filter}" /tmp/${progname}.formed
+    fi
+  else
+    cat /tmp/${progname}.formed
+  fi |
+    # optimize int A -> B ; single references
+    sed 's/\("[^"]*"\) -> \("[^"]*"\) ->/\1 -> \2 ;|\2 ->/g' |
+      sed 's/\("[^"]*"\) -> \("[^"]*"\) ->/\1 -> \2 ;|\2 ->/g' |
+        tr '|' '\n' |
+          beautify |
+            grep ' -> ' |
+              sort -u |
+                if [ -s /tmp/${progname}.symbols ]; then
+                  beautify < /tmp/${progname}.symbols |
+                    sed 's/^  */ /' > /tmp/${progname}.short
+                  tee /tmp/${progname}.split |
+                    fgrep -f /tmp/${progname}.short |
+                      sed 's/ ;$/ [color=red] ;/'
+                  fgrep -v -f /tmp/${progname}.short /tmp/${progname}.split
+                  rm -f /tmp/${progname}.short /tmp/${progname}.split
+                else
+                  cat -
+                fi
+
+  echo '}'
+) |
+  tee /tmp/${progname}.input |
+    if dot ${dot_format} && [ -z "${debug}" ]; then
+      rm -f /tmp/${progname}.*
+    fi |
+      eval ${output}
diff --git a/verity/Android.mk b/verity/Android.mk
index 7b3d13f..1a7351f 100644
--- a/verity/Android.mk
+++ b/verity/Android.mk
@@ -7,7 +7,7 @@
 LOCAL_SRC_FILES := verify_boot_signature.c
 LOCAL_MODULE_CLASS := EXECUTABLES
 LOCAL_MODULE_TAGS := optional
-LOCAL_SHARED_LIBRARIES := libcrypto-host
+LOCAL_SHARED_LIBRARIES := libcrypto
 LOCAL_C_INCLUDES += external/openssl/include system/extras/ext4_utils system/core/mkbootimg
 include $(BUILD_HOST_EXECUTABLE)
 
@@ -18,7 +18,7 @@
 LOCAL_SRC_FILES := generate_verity_key.c
 LOCAL_MODULE_CLASS := EXECUTABLES
 LOCAL_MODULE_TAGS := optional
-LOCAL_SHARED_LIBRARIES := libcrypto-host
+LOCAL_SHARED_LIBRARIES := libcrypto_utils libcrypto
 include $(BUILD_HOST_EXECUTABLE)
 
 include $(CLEAR_VARS)
@@ -46,14 +46,6 @@
 include $(BUILD_HOST_JAVA_LIBRARY)
 
 include $(CLEAR_VARS)
-LOCAL_SRC_FILES := BootSignature.java KeystoreSigner.java Utils.java
-LOCAL_MODULE := BootKeystoreSigner
-LOCAL_JAR_MANIFEST := KeystoreSigner.mf
-LOCAL_MODULE_TAGS := optional
-LOCAL_STATIC_JAVA_LIBRARIES := bouncycastle-host
-include $(BUILD_HOST_JAVA_LIBRARY)
-
-include $(CLEAR_VARS)
 LOCAL_SRC_FILES := verity_verifier
 LOCAL_MODULE := verity_verifier
 LOCAL_MODULE_CLASS := EXECUTABLES
@@ -81,15 +73,6 @@
 include $(BUILD_PREBUILT)
 
 include $(CLEAR_VARS)
-LOCAL_SRC_FILES := keystore_signer
-LOCAL_MODULE := keystore_signer
-LOCAL_MODULE_CLASS := EXECUTABLES
-LOCAL_IS_HOST_MODULE := true
-LOCAL_MODULE_TAGS := optional
-LOCAL_REQUIRED_MODULES := KeystoreSigner
-include $(BUILD_PREBUILT)
-
-include $(CLEAR_VARS)
 LOCAL_MODULE := build_verity_metadata.py
 LOCAL_MODULE_CLASS := EXECUTABLES
 LOCAL_SRC_FILES := build_verity_metadata.py
@@ -102,7 +85,7 @@
 LOCAL_SRC_FILES := build_verity_tree.cpp
 LOCAL_MODULE_TAGS := optional
 LOCAL_STATIC_LIBRARIES := libsparse_host libz
-LOCAL_SHARED_LIBRARIES := libcrypto-host libbase
+LOCAL_SHARED_LIBRARIES := libcrypto libbase
 LOCAL_CFLAGS += -Wall -Werror
 include $(BUILD_HOST_EXECUTABLE)
 
diff --git a/verity/KeystoreSigner.java b/verity/KeystoreSigner.java
deleted file mode 100644
index 0927d54..0000000
--- a/verity/KeystoreSigner.java
+++ /dev/null
@@ -1,207 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.verity;
-
-import java.io.IOException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.Security;
-import java.security.Signature;
-import java.security.cert.X509Certificate;
-import java.util.Enumeration;
-import org.bouncycastle.asn1.ASN1Encodable;
-import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1Integer;
-import org.bouncycastle.asn1.ASN1Object;
-import org.bouncycastle.asn1.ASN1Primitive;
-import org.bouncycastle.asn1.ASN1Sequence;
-import org.bouncycastle.asn1.DEROctetString;
-import org.bouncycastle.asn1.DERPrintableString;
-import org.bouncycastle.asn1.DERSequence;
-import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
-import org.bouncycastle.asn1.pkcs.RSAPublicKey;
-import org.bouncycastle.asn1.util.ASN1Dump;
-import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
-import org.bouncycastle.jce.provider.BouncyCastleProvider;
-
-/**
- * AndroidVerifiedBootKeystore DEFINITIONS ::=
- * BEGIN
- *     FormatVersion ::= INTEGER
- *     KeyBag ::= SEQUENCE {
- *         Key  ::= SEQUENCE {
- *             AlgorithmIdentifier  ::=  SEQUENCE {
- *                 algorithm OBJECT IDENTIFIER,
- *                 parameters ANY DEFINED BY algorithm OPTIONAL
- *             }
- *             KeyMaterial ::= RSAPublicKey
- *         }
- *     }
- *     Signature ::= AndroidVerifiedBootSignature
- * END
- */
-
-class BootKey extends ASN1Object
-{
-    private AlgorithmIdentifier algorithmIdentifier;
-    private RSAPublicKey keyMaterial;
-
-    public BootKey(PublicKey key) throws Exception {
-        java.security.interfaces.RSAPublicKey k =
-                (java.security.interfaces.RSAPublicKey) key;
-        this.keyMaterial = new RSAPublicKey(
-                k.getModulus(),
-                k.getPublicExponent());
-        this.algorithmIdentifier = Utils.getSignatureAlgorithmIdentifier(key);
-    }
-
-    public ASN1Primitive toASN1Primitive() {
-        ASN1EncodableVector v = new ASN1EncodableVector();
-        v.add(algorithmIdentifier);
-        v.add(keyMaterial);
-        return new DERSequence(v);
-    }
-
-    public void dump() throws Exception {
-        System.out.println(ASN1Dump.dumpAsString(toASN1Primitive()));
-    }
-}
-
-class BootKeystore extends ASN1Object
-{
-    private ASN1Integer             formatVersion;
-    private ASN1EncodableVector     keyBag;
-    private BootSignature           signature;
-    private X509Certificate         certificate;
-
-    private static final int FORMAT_VERSION = 0;
-
-    public BootKeystore() {
-        this.formatVersion = new ASN1Integer(FORMAT_VERSION);
-        this.keyBag = new ASN1EncodableVector();
-    }
-
-    public void addPublicKey(byte[] der) throws Exception {
-        PublicKey pubkey = Utils.loadDERPublicKey(der);
-        BootKey k = new BootKey(pubkey);
-        keyBag.add(k);
-    }
-
-    public void setCertificate(X509Certificate cert) {
-        certificate = cert;
-    }
-
-    public byte[] getInnerKeystore() throws Exception {
-        ASN1EncodableVector v = new ASN1EncodableVector();
-        v.add(formatVersion);
-        v.add(new DERSequence(keyBag));
-        return new DERSequence(v).getEncoded();
-    }
-
-    public ASN1Primitive toASN1Primitive() {
-        ASN1EncodableVector v = new ASN1EncodableVector();
-        v.add(formatVersion);
-        v.add(new DERSequence(keyBag));
-        v.add(signature);
-        return new DERSequence(v);
-    }
-
-    public void parse(byte[] input) throws Exception {
-        ASN1InputStream stream = new ASN1InputStream(input);
-        ASN1Sequence sequence = (ASN1Sequence) stream.readObject();
-
-        formatVersion = (ASN1Integer) sequence.getObjectAt(0);
-        if (formatVersion.getValue().intValue() != FORMAT_VERSION) {
-            throw new IllegalArgumentException("Unsupported format version");
-        }
-
-        ASN1Sequence keys = (ASN1Sequence) sequence.getObjectAt(1);
-        Enumeration e = keys.getObjects();
-        while (e.hasMoreElements()) {
-            keyBag.add((ASN1Encodable) e.nextElement());
-        }
-
-        ASN1Object sig = sequence.getObjectAt(2).toASN1Primitive();
-        signature = new BootSignature(sig.getEncoded());
-    }
-
-    public boolean verify() throws Exception {
-        byte[] innerKeystore = getInnerKeystore();
-        return Utils.verify(signature.getPublicKey(), innerKeystore,
-                signature.getSignature(), signature.getAlgorithmIdentifier());
-    }
-
-    public void sign(PrivateKey privateKey) throws Exception {
-        byte[] innerKeystore = getInnerKeystore();
-        byte[] rawSignature = Utils.sign(privateKey, innerKeystore);
-        signature = new BootSignature("keystore", innerKeystore.length);
-        signature.setCertificate(certificate);
-        signature.setSignature(rawSignature,
-                Utils.getSignatureAlgorithmIdentifier(privateKey));
-    }
-
-    public void dump() throws Exception {
-        System.out.println(ASN1Dump.dumpAsString(toASN1Primitive()));
-    }
-
-    private static void usage() {
-        System.err.println("usage: KeystoreSigner <privatekey.pk8> " +
-                "<certificate.x509.pem> <outfile> <publickey0.der> " +
-                "... <publickeyN-1.der> | -verify <keystore>");
-        System.exit(1);
-    }
-
-    public static void main(String[] args) throws Exception {
-        if (args.length < 2) {
-            usage();
-            return;
-        }
-
-        Security.addProvider(new BouncyCastleProvider());
-        BootKeystore ks = new BootKeystore();
-
-        if ("-verify".equals(args[0])) {
-            ks.parse(Utils.read(args[1]));
-
-            try {
-                if (ks.verify()) {
-                    System.err.println("Signature is VALID");
-                    System.exit(0);
-                } else {
-                    System.err.println("Signature is INVALID");
-                }
-            } catch (Exception e) {
-                e.printStackTrace(System.err);
-            }
-            System.exit(1);
-        } else {
-            String privkeyFname = args[0];
-            String certFname = args[1];
-            String outfileFname = args[2];
-
-            ks.setCertificate(Utils.loadPEMCertificate(certFname));
-
-            for (int i = 3; i < args.length; i++) {
-                ks.addPublicKey(Utils.read(args[i]));
-            }
-
-            ks.sign(Utils.loadDERPrivateKeyFromFile(privkeyFname));
-            Utils.write(ks.getEncoded(), outfileFname);
-        }
-    }
-}
diff --git a/verity/KeystoreSigner.mf b/verity/KeystoreSigner.mf
deleted file mode 100644
index 472b7c4..0000000
--- a/verity/KeystoreSigner.mf
+++ /dev/null
@@ -1 +0,0 @@
-Main-Class: com.android.verity.BootKeystore
diff --git a/verity/fec/Android.mk b/verity/fec/Android.mk
index c13f577..8527e0b 100644
--- a/verity/fec/Android.mk
+++ b/verity/fec/Android.mk
@@ -11,7 +11,8 @@
 LOCAL_STATIC_LIBRARIES := \
     libsparse_host \
     libz \
-    libcrypto_static \
+    libcrypto_utils \
+    libcrypto \
     libfec_host \
     libfec_rs_host \
     libext4_utils_host \
@@ -20,22 +21,3 @@
 LOCAL_CFLAGS += -Wall -Werror -O3
 LOCAL_C_INCLUDES += external/fec
 include $(BUILD_HOST_EXECUTABLE)
-
-include $(CLEAR_VARS)
-LOCAL_CLANG := true
-LOCAL_SANITIZE := integer
-LOCAL_MODULE := fec
-LOCAL_FORCE_STATIC_EXECUTABLE := true
-LOCAL_SRC_FILES := main.cpp image.cpp
-LOCAL_MODULE_TAGS := optional
-LOCAL_STATIC_LIBRARIES := \
-    libcrypto_static \
-    libfec \
-    libfec_rs \
-    libbase \
-    libext4_utils_static \
-    libsquashfs_utils \
-    libcutils
-LOCAL_CFLAGS += -Wall -Werror -O3 -DIMAGE_NO_SPARSE=1
-LOCAL_C_INCLUDES += external/fec
-include $(BUILD_EXECUTABLE)
diff --git a/verity/fec/image.cpp b/verity/fec/image.cpp
index a378c93..509b102 100644
--- a/verity/fec/image.cpp
+++ b/verity/fec/image.cpp
@@ -33,9 +33,7 @@
 #include <string.h>
 #include <sys/ioctl.h>
 #include <sys/mman.h>
-#ifndef IMAGE_NO_SPARSE
 #include <sparse/sparse.h>
-#endif
 #include "image.h"
 
 #if defined(__linux__)
@@ -51,24 +49,7 @@
     memset(ctx, 0, sizeof(*ctx));
 }
 
-static void mmap_image_free(image *ctx)
-{
-    if (ctx->input) {
-        munmap(ctx->input, (size_t)ctx->inp_size);
-        close(ctx->inp_fd);
-    }
-
-    if (ctx->fec_mmap_addr) {
-        munmap(ctx->fec_mmap_addr, FEC_BLOCKSIZE + ctx->fec_size);
-        close(ctx->fec_fd);
-    }
-
-    if (!ctx->inplace && ctx->output) {
-        delete[] ctx->output;
-    }
-}
-
-static void file_image_free(image *ctx)
+void image_free(image *ctx)
 {
     assert(ctx->input == ctx->output);
 
@@ -79,42 +60,10 @@
     if (ctx->fec) {
         delete[] ctx->fec;
     }
-}
-
-void image_free(image *ctx)
-{
-    if (ctx->mmap) {
-        mmap_image_free(ctx);
-    } else {
-        file_image_free(ctx);
-    }
 
     image_init(ctx);
 }
 
-static uint64_t get_size(int fd)
-{
-    struct stat st;
-
-    if (fstat(fd, &st) == -1) {
-        FATAL("failed to fstat: %s\n", strerror(errno));
-    }
-
-    uint64_t size = 0;
-
-    if (S_ISBLK(st.st_mode)) {
-        if (ioctl(fd, BLKGETSIZE64, &size) == -1) {
-            FATAL("failed to ioctl(BLKGETSIZE64): %s\n", strerror(errno));
-        }
-    } else if (S_ISREG(st.st_mode)) {
-        size = st.st_size;
-    } else {
-        FATAL("unknown file mode: %d\n", (int)st.st_mode);
-    }
-
-    return size;
-}
-
 static void calculate_rounds(uint64_t size, image *ctx)
 {
     if (!size) {
@@ -129,64 +78,6 @@
     ctx->rounds = fec_div_round_up(ctx->blocks, ctx->rs_n);
 }
 
-static void mmap_image_load(const std::vector<int>& fds, image *ctx,
-        bool output_needed)
-{
-    if (fds.size() != 1) {
-        FATAL("multiple input files not supported with mmap\n");
-    }
-
-    int fd = fds.front();
-
-    calculate_rounds(get_size(fd), ctx);
-
-    /* check that we can memory map the file; on 32-bit platforms we are
-       limited to encoding at most 4 GiB files */
-    if (ctx->inp_size > SIZE_MAX) {
-        FATAL("cannot mmap %" PRIu64 " bytes\n", ctx->inp_size);
-    }
-
-    if (ctx->verbose) {
-        INFO("memory mapping '%s' (size %" PRIu64 ")\n", ctx->fec_filename,
-            ctx->inp_size);
-    }
-
-    int flags = PROT_READ;
-
-    if (ctx->inplace) {
-        flags |= PROT_WRITE;
-    }
-
-    void *p = mmap(NULL, (size_t)ctx->inp_size, flags, MAP_SHARED, fd, 0);
-
-    if (p == MAP_FAILED) {
-        FATAL("failed to mmap '%s' (size %" PRIu64 "): %s\n",
-            ctx->fec_filename, ctx->inp_size, strerror(errno));
-    }
-
-    ctx->inp_fd = fd;
-    ctx->input = (uint8_t *)p;
-
-    if (ctx->inplace) {
-        ctx->output = ctx->input;
-    } else if (output_needed) {
-        if (ctx->verbose) {
-            INFO("allocating %" PRIu64 " bytes of memory\n", ctx->inp_size);
-        }
-
-        ctx->output = new uint8_t[ctx->inp_size];
-
-        if (!ctx->output) {
-                FATAL("failed to allocate memory\n");
-        }
-
-        memcpy(ctx->output, ctx->input, ctx->inp_size);
-    }
-
-    /* fd is closed in mmap_image_free */
-}
-
-#ifndef IMAGE_NO_SPARSE
 static int process_chunk(void *priv, const void *data, int len)
 {
     image *ctx = (image *)priv;
@@ -199,25 +90,14 @@
     ctx->pos += len;
     return 0;
 }
-#endif
 
 static void file_image_load(const std::vector<int>& fds, image *ctx)
 {
     uint64_t size = 0;
-#ifndef IMAGE_NO_SPARSE
     std::vector<struct sparse_file *> files;
-#endif
 
     for (auto fd : fds) {
         uint64_t len = 0;
-
-#ifdef IMAGE_NO_SPARSE
-        if (ctx->sparse) {
-            FATAL("sparse files not supported\n");
-        }
-
-        len = get_size(fd);
-#else
         struct sparse_file *file;
 
         if (ctx->sparse) {
@@ -232,7 +112,6 @@
 
         len = sparse_file_len(file, false, false);
         files.push_back(file);
-#endif /* IMAGE_NO_SPARSE */
 
         size += len;
     }
@@ -253,18 +132,6 @@
     ctx->output = ctx->input;
     ctx->pos = 0;
 
-#ifdef IMAGE_NO_SPARSE
-    for (auto fd : fds) {
-        uint64_t len = get_size(fd);
-
-        if (!android::base::ReadFully(fd, &ctx->input[ctx->pos], len)) {
-            FATAL("failed to read: %s\n", strerror(errno));
-        }
-
-        ctx->pos += len;
-        close(fd);
-    }
-#else
     for (auto file : files) {
         sparse_file_callback(file, false, false, process_chunk, ctx);
         sparse_file_destroy(file);
@@ -273,11 +140,9 @@
     for (auto fd : fds) {
         close(fd);
     }
-#endif
 }
 
-bool image_load(const std::vector<std::string>& filenames, image *ctx,
-        bool output_needed)
+bool image_load(const std::vector<std::string>& filenames, image *ctx)
 {
     assert(ctx->roots > 0 && ctx->roots < FEC_RSM);
     ctx->rs_n = FEC_RSM - ctx->roots;
@@ -290,7 +155,7 @@
 
     std::vector<int> fds;
 
-    for (auto fn : filenames) {
+    for (const auto& fn : filenames) {
         int fd = TEMP_FAILURE_RETRY(open(fn.c_str(), flags | O_LARGEFILE));
 
         if (fd < 0) {
@@ -300,21 +165,13 @@
         fds.push_back(fd);
     }
 
-    if (ctx->mmap) {
-        mmap_image_load(fds, ctx, output_needed);
-    } else {
-        file_image_load(fds, ctx);
-    }
+    file_image_load(fds, ctx);
 
     return true;
 }
 
 bool image_save(const std::string& filename, image *ctx)
 {
-    if (ctx->inplace && ctx->mmap) {
-        return true; /* nothing to do */
-    }
-
     /* TODO: support saving as a sparse file */
     int fd = TEMP_FAILURE_RETRY(open(filename.c_str(),
                 O_WRONLY | O_CREAT | O_TRUNC, 0666));
@@ -332,46 +189,13 @@
     return true;
 }
 
-static void mmap_image_ecc_new(image *ctx)
+bool image_ecc_new(const std::string& filename, image *ctx)
 {
-    if (ctx->verbose) {
-        INFO("mmaping '%s' (size %u)\n", ctx->fec_filename, ctx->fec_size);
-    }
+    assert(ctx->rounds > 0); /* image_load should be called first */
 
-    int fd = TEMP_FAILURE_RETRY(open(ctx->fec_filename,
-                O_RDWR | O_CREAT, 0666));
+    ctx->fec_filename = filename.c_str();
+    ctx->fec_size = ctx->rounds * ctx->roots * FEC_BLOCKSIZE;
 
-    if (fd < 0) {
-        FATAL("failed to open file '%s': %s\n", ctx->fec_filename,
-            strerror(errno));
-    }
-
-    assert(sizeof(fec_header) <= FEC_BLOCKSIZE);
-    size_t fec_size = FEC_BLOCKSIZE + ctx->fec_size;
-
-    if (ftruncate(fd, fec_size) == -1) {
-        FATAL("failed to ftruncate file '%s': %s\n", ctx->fec_filename,
-            strerror(errno));
-    }
-
-    if (ctx->verbose) {
-        INFO("memory mapping '%s' (size %zu)\n", ctx->fec_filename, fec_size);
-    }
-
-    void *p = mmap(NULL, fec_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
-
-    if (p == MAP_FAILED) {
-        FATAL("failed to mmap '%s' (size %zu): %s\n", ctx->fec_filename,
-            fec_size, strerror(errno));
-    }
-
-    ctx->fec_fd = fd;
-    ctx->fec_mmap_addr = (uint8_t *)p;
-    ctx->fec = ctx->fec_mmap_addr;
-}
-
-static void file_image_ecc_new(image *ctx)
-{
     if (ctx->verbose) {
         INFO("allocating %u bytes of memory\n", ctx->fec_size);
     }
@@ -381,20 +205,6 @@
     if (!ctx->fec) {
         FATAL("failed to allocate %u bytes\n", ctx->fec_size);
     }
-}
-
-bool image_ecc_new(const std::string& filename, image *ctx)
-{
-    assert(ctx->rounds > 0); /* image_load should be called first */
-
-    ctx->fec_filename = filename.c_str();
-    ctx->fec_size = ctx->rounds * ctx->roots * FEC_BLOCKSIZE;
-
-    if (ctx->mmap) {
-        mmap_image_ecc_new(ctx);
-    } else {
-        file_image_ecc_new(ctx);
-    }
 
     return true;
 }
@@ -462,7 +272,7 @@
         FATAL("failed to rewind '%s': %s", filename.c_str(), strerror(errno));
     }
 
-    if (!ctx->mmap && !android::base::ReadFully(fd, ctx->fec, ctx->fec_size)) {
+    if (!android::base::ReadFully(fd, ctx->fec, ctx->fec_size)) {
         FATAL("failed to read %u bytes from '%s': %s\n", ctx->fec_size,
             filename.c_str(), strerror(errno));
     }
@@ -481,18 +291,11 @@
 
 bool image_ecc_save(image *ctx)
 {
-    assert(sizeof(fec_header) <= FEC_BLOCKSIZE);
+    assert(2 * sizeof(fec_header) <= FEC_BLOCKSIZE);
 
-    uint8_t header[FEC_BLOCKSIZE];
-    uint8_t *p = header;
+    uint8_t header[FEC_BLOCKSIZE] = {0};
 
-    if (ctx->mmap) {
-        p = (uint8_t *)&ctx->fec_mmap_addr[ctx->fec_size];
-    }
-
-    memset(p, 0, FEC_BLOCKSIZE);
-
-    fec_header *f = (fec_header *)p;
+    fec_header *f = (fec_header *)header;
 
     f->magic = FEC_MAGIC;
     f->version = FEC_VERSION;
@@ -504,27 +307,39 @@
     SHA256(ctx->fec, ctx->fec_size, f->hash);
 
     /* store a copy of the fec_header at the end of the header block */
-    memcpy(&p[sizeof(header) - sizeof(fec_header)], p, sizeof(fec_header));
+    memcpy(&header[sizeof(header) - sizeof(fec_header)], header,
+        sizeof(fec_header));
 
-    if (!ctx->mmap) {
-        assert(ctx->fec_filename);
+    assert(ctx->fec_filename);
 
-        int fd = TEMP_FAILURE_RETRY(open(ctx->fec_filename,
-                    O_WRONLY | O_CREAT | O_TRUNC, 0666));
+    int fd = TEMP_FAILURE_RETRY(open(ctx->fec_filename,
+                O_WRONLY | O_CREAT | O_TRUNC, 0666));
 
-        if (fd < 0) {
-            FATAL("failed to open file '%s': %s\n", ctx->fec_filename,
-                strerror(errno));
-        }
-
-        if (!android::base::WriteFully(fd, ctx->fec, ctx->fec_size) ||
-            !android::base::WriteFully(fd, header, sizeof(header))) {
-            FATAL("failed to write to output: %s\n", strerror(errno));
-        }
-
-        close(fd);
+    if (fd < 0) {
+        FATAL("failed to open file '%s': %s\n", ctx->fec_filename,
+            strerror(errno));
     }
 
+    if (!android::base::WriteFully(fd, ctx->fec, ctx->fec_size)) {
+        FATAL("failed to write to output: %s\n", strerror(errno));
+    }
+
+    if (ctx->padding > 0) {
+        uint8_t padding[FEC_BLOCKSIZE] = {0};
+
+        for (uint32_t i = 0; i < ctx->padding; i += FEC_BLOCKSIZE) {
+            if (!android::base::WriteFully(fd, padding, FEC_BLOCKSIZE)) {
+                FATAL("failed to write padding: %s\n", strerror(errno));
+            }
+        }
+    }
+
+    if (!android::base::WriteFully(fd, header, sizeof(header))) {
+        FATAL("failed to write to header: %s\n", strerror(errno));
+    }
+
+    close(fd);
+
     return true;
 }
 
diff --git a/verity/fec/image.h b/verity/fec/image.h
index f0211fd..64f0e42 100644
--- a/verity/fec/image.h
+++ b/verity/fec/image.h
@@ -38,8 +38,6 @@
 struct image {
     /* if true, decode file in place instead of creating a new output file */
     bool inplace;
-    /* if true, use memory mapping instead of copying all input into memory */
-    bool mmap;
     /* if true, assume input is a sparse file */
     bool sparse;
     /* if true, print more verbose information to stderr */
@@ -54,13 +52,13 @@
     int rs_n;
     int threads;
     uint32_t fec_size;
+    uint32_t padding;
     uint64_t blocks;
     uint64_t inp_size;
     uint64_t pos;
     uint64_t rounds;
     uint64_t rv;
     uint8_t *fec;
-    uint8_t *fec_mmap_addr;
     uint8_t *input;
     uint8_t *output;
 };
@@ -79,8 +77,7 @@
     void *rs;
 };
 
-extern bool image_load(const std::vector<std::string>& filename, image *ctx,
-        bool output_needed);
+extern bool image_load(const std::vector<std::string>& filename, image *ctx);
 extern bool image_save(const std::string& filename, image *ctx);
 
 extern bool image_ecc_new(const std::string& filename, image *ctx);
diff --git a/verity/fec/main.cpp b/verity/fec/main.cpp
index 50d807e..93f1ec2 100644
--- a/verity/fec/main.cpp
+++ b/verity/fec/main.cpp
@@ -105,9 +105,10 @@
            "  -h                                show this help\n"
            "  -v                                enable verbose logging\n"
            "  -r, --roots=<bytes>               number of parity bytes\n"
-           "  -m, --mmap                        use memory mapping\n"
            "  -j, --threads=<threads>           number of threads to use\n"
            "  -S                                treat data as a sparse file\n"
+           "encoding options:\n"
+           "  -p, --padding=<bytes>             add padding after ECC data\n"
            "decoding options:\n"
            "  -i, --inplace                     correct <data> in place\n"
         );
@@ -176,7 +177,7 @@
         FATAL("invalid parameters: inplace can only used when decoding\n");
     }
 
-    if (!image_load(inp_filenames, &ctx, false)) {
+    if (!image_load(inp_filenames, &ctx)) {
         FATAL("failed to read input\n");
     }
 
@@ -189,7 +190,7 @@
 
     size_t n = 1;
 
-    for (auto fn : inp_filenames) {
+    for (const auto& fn : inp_filenames) {
         INFO("\t%zu: '%s'\n", n++, fn.c_str());
     }
 
@@ -221,8 +222,12 @@
             "files\n");
     }
 
+    if (ctx.padding) {
+        FATAL("invalid parameters: padding is only relevant when encoding\n");
+    }
+
     if (!image_ecc_load(fec_filename, &ctx) ||
-            !image_load(inp_filenames, &ctx, !out_filename.empty())) {
+            !image_load(inp_filenames, &ctx)) {
         FATAL("failed to read input\n");
     }
 
@@ -281,15 +286,15 @@
             {"sparse", no_argument, 0, 'S'},
             {"roots", required_argument, 0, 'r'},
             {"inplace", no_argument, 0, 'i'},
-            {"mmap", no_argument, 0, 'm'},
             {"threads", required_argument, 0, 'j'},
             {"print-fec-size", required_argument, 0, 's'},
             {"get-ecc-start", required_argument, 0, 'E'},
             {"get-verity-start", required_argument, 0, 'V'},
+            {"padding", required_argument, 0, 'p'},
             {"verbose", no_argument, 0, 'v'},
             {NULL, 0, 0, 0}
         };
-        int c = getopt_long(argc, argv, "hedSr:imj:s:E:V:v", long_options, NULL);
+        int c = getopt_long(argc, argv, "hedSr:ij:s:E:V:p:v", long_options, NULL);
         if (c < 0) {
             break;
         }
@@ -317,9 +322,6 @@
         case 'i':
             ctx.inplace = true;
             break;
-        case 'm':
-            ctx.mmap = true;
-            break;
         case 'j':
             ctx.threads = (int)parse_arg(optarg, "threads", IMAGE_MAX_THREADS);
             break;
@@ -344,6 +346,12 @@
             mode = MODE_GETVERITYSTART;
             inp_filenames.push_back(optarg);
             break;
+        case 'p':
+            ctx.padding = (uint32_t)parse_arg(optarg, "padding", UINT32_MAX);
+            if (ctx.padding % FEC_BLOCKSIZE) {
+                FATAL("padding must be multiple of %u\n", FEC_BLOCKSIZE);
+            }
+            break;
         case 'v':
             ctx.verbose = true;
             break;
diff --git a/verity/generate_verity_key.c b/verity/generate_verity_key.c
index 0da978f..c598afb 100644
--- a/verity/generate_verity_key.c
+++ b/verity/generate_verity_key.c
@@ -23,11 +23,7 @@
 #include <sys/types.h>
 #include <unistd.h>
 
-/* HACK: we need the RSAPublicKey struct
- * but RSA_verify conflits with openssl */
-#define RSA_verify RSA_verify_mincrypt
-#include "mincrypt/rsa.h"
-#undef RSA_verify
+#include <crypto_utils/android_pubkey.h>
 
 #include <openssl/evp.h>
 #include <openssl/objects.h>
@@ -35,58 +31,9 @@
 #include <openssl/rsa.h>
 #include <openssl/sha.h>
 
-// Convert OpenSSL RSA private key to android pre-computed RSAPublicKey format.
-// Lifted from secure adb's mincrypt key generation.
-static int convert_to_mincrypt_format(RSA *rsa, RSAPublicKey *pkey)
-{
-    int ret = -1;
-    unsigned int i;
-
-    if (RSA_size(rsa) != RSANUMBYTES)
-        goto out;
-
-    BN_CTX* ctx = BN_CTX_new();
-    BIGNUM* r32 = BN_new();
-    BIGNUM* rr = BN_new();
-    BIGNUM* r = BN_new();
-    BIGNUM* rem = BN_new();
-    BIGNUM* n = BN_new();
-    BIGNUM* n0inv = BN_new();
-
-    BN_set_bit(r32, 32);
-    BN_copy(n, rsa->n);
-    BN_set_bit(r, RSANUMWORDS * 32);
-    BN_mod_sqr(rr, r, n, ctx);
-    BN_div(NULL, rem, n, r32, ctx);
-    BN_mod_inverse(n0inv, rem, r32, ctx);
-
-    pkey->len = RSANUMWORDS;
-    pkey->n0inv = 0 - BN_get_word(n0inv);
-    for (i = 0; i < RSANUMWORDS; i++) {
-        BN_div(rr, rem, rr, r32, ctx);
-        pkey->rr[i] = BN_get_word(rem);
-        BN_div(n, rem, n, r32, ctx);
-        pkey->n[i] = BN_get_word(rem);
-    }
-    pkey->exponent = BN_get_word(rsa->e);
-
-    ret = 0;
-
-    BN_free(n0inv);
-    BN_free(n);
-    BN_free(rem);
-    BN_free(r);
-    BN_free(rr);
-    BN_free(r32);
-    BN_CTX_free(ctx);
-
-out:
-    return ret;
-}
-
 static int write_public_keyfile(RSA *private_key, const char *private_key_path)
 {
-    RSAPublicKey pkey;
+    uint8_t key_data[ANDROID_PUBKEY_ENCODED_SIZE];
     BIO *bfile = NULL;
     char *path = NULL;
     int ret = -1;
@@ -94,14 +41,14 @@
     if (asprintf(&path, "%s.pub", private_key_path) < 0)
         goto out;
 
-    if (convert_to_mincrypt_format(private_key, &pkey) < 0)
+    if (!android_pubkey_encode(private_key, key_data, sizeof(key_data)))
         goto out;
 
     bfile = BIO_new_file(path, "w");
     if (!bfile)
         goto out;
 
-    BIO_write(bfile, &pkey, sizeof(pkey));
+    BIO_write(bfile, key_data, sizeof(key_data));
     BIO_flush(bfile);
 
     ret = 0;
diff --git a/verity/keystore_signer b/verity/keystore_signer
deleted file mode 100755
index 445f0c9..0000000
--- a/verity/keystore_signer
+++ /dev/null
@@ -1,8 +0,0 @@
-#! /bin/sh
-
-# Start-up script for KeystoreSigner
-
-KEYSTORESIGNER_HOME=`dirname "$0"`
-KEYSTORESIGNER_HOME=`dirname "$KEYSTORESIGNER_HOME"`
-
-java -Xmx512M -jar "$KEYSTORESIGNER_HOME"/framework/BootKeystoreSigner.jar "$@"