diff --git a/clients/drcachesim/CMakeLists.txt b/clients/drcachesim/CMakeLists.txt
index e23d5c07ed8dc710523493a87cc7dc75ed5033e8..d4d3af8d69fa91539cd4aab957811500b7246497 100644
--- a/clients/drcachesim/CMakeLists.txt
+++ b/clients/drcachesim/CMakeLists.txt
@@ -186,6 +186,12 @@ endif ()
 if (liblz4)
   target_link_libraries(drmemtrace_raw2trace lz4)
 endif ()
+if (BUILD_PT_POST_PROCESSOR)
+  add_definitions(-DBUILD_PT_POST_PROCESSOR)
+  # Add the directory containing libipt.so and libipt-sb.so to the linker search path.
+  SET(CMAKE_EXE_LINKER_FLAGS  "${CMAKE_EXE_LINKER_FLAGS} -Wl,-rpath='${PROJECT_BINARY_DIR}/lib'")
+  target_link_libraries(drmemtrace_raw2trace drpt2ir drir2trace)
+endif (BUILD_PT_POST_PROCESSOR)
 
 set(drcachesim_srcs
   launcher.cpp
@@ -337,6 +343,7 @@ macro(add_drmemtrace name type)
   if (BUILD_PT_TRACER)
     set(drmemtrace_srcs ${drmemtrace_srcs}
       tracer/syscall_pt_trace.cpp
+      tracer/kernel_image.cpp
     )
     add_definitions(-DBUILD_PT_TRACER)
   endif ()
diff --git a/clients/drcachesim/analyzer_multi.cpp b/clients/drcachesim/analyzer_multi.cpp
index 8968636c481f13ac609ab240551013157f852fd0..796cbe27687270af818a2a5327c4b09fe4bf615c 100644
--- a/clients/drcachesim/analyzer_multi.cpp
+++ b/clients/drcachesim/analyzer_multi.cpp
@@ -108,7 +108,8 @@ analyzer_multi_t::analyzer_multi_t()
             raw2trace_t raw2trace(
                 dir.modfile_bytes_, dir.in_files_, dir.out_files_, dir.out_archives_,
                 dir.encoding_file_, nullptr, op_verbose.get_value(), op_jobs.get_value(),
-                op_alt_module_dir.get_value(), op_chunk_instr_count.get_value());
+                dir.get_syscall_pt_trace_dir(), op_alt_module_dir.get_value(),
+                op_chunk_instr_count.get_value());
             std::string error = raw2trace.do_conversion();
             if (!error.empty()) {
                 success_ = false;
diff --git a/clients/drcachesim/drpt2trace/CMakeLists.txt b/clients/drcachesim/drpt2trace/CMakeLists.txt
index 687eacd48fd7ea63772f3e6547c430d978d45aa7..00af3207219e66d480b780f5b2fbcabdb5b0cb40 100644
--- a/clients/drcachesim/drpt2trace/CMakeLists.txt
+++ b/clients/drcachesim/drpt2trace/CMakeLists.txt
@@ -77,12 +77,14 @@ set(ipt-ext_srcs
 add_library(ipt-ext STATIC ${ipt-ext_srcs})
 add_dependencies(ipt-ext ipt)
 target_link_libraries(ipt-ext ipt)
+DR_export_target(ipt-ext)
+install_exported_target(ipt-ext ${PROJECT_BINARY_DIR}/lib)
 
 # drpt2ir don't include configure.h so they don't get DR defines
 add_dr_defines()
 
-# We build drpt2ir to a static library. So we can link it in the independent
-# clients drpt2trace and drraw2trace.
+# We build drpt2ir and drir2trace to static libraries. So we can link them to the
+# independent clients drpt2trace and raw2trace.
 set(drpt2ir_srcs
   pt2ir.cpp
 )
@@ -91,17 +93,29 @@ configure_DynamoRIO_decoder(drpt2ir)
 add_dependencies(drpt2ir ipt ipt-sb ipt-ext api_headers)
 target_link_libraries(drpt2ir ipt ipt-sb ipt-ext)
 install_client_nonDR_header(drmemtrace pt2ir.h)
+DR_export_target(drpt2ir)
+install_exported_target(drpt2ir ${INSTALL_CLIENTS_LIB})
 
-# TODO i#5505: Currently, drpt2trace only counts instructions.
-# In the future, we will add support for converting pt trace to memref_t.
-# So we may need to link drdecode to drpt2ir library.
+set(drir2trace_srcs
+  ir2trace.cpp
+)
+add_library(drir2trace STATIC ${drir2trace_srcs})
+configure_DynamoRIO_decoder(drir2trace)
+add_dependencies(drir2trace api_headers)
+install_client_nonDR_header(drmemtrace ir2trace.h)
+DR_export_target(drir2trace)
+install_exported_target(drir2trace ${INSTALL_CLIENTS_LIB})
+
+# drpt2trace is a client that uses drpt2ir and drir2trace to convert PT data to memref_t.
 set(drpt2trace_srcs
   drpt2trace.cpp
 )
 add_executable(drpt2trace ${drpt2trace_srcs})
 configure_DynamoRIO_standalone(drpt2trace)
 use_DynamoRIO_extension(drpt2trace droption)
-add_dependencies(drpt2trace drpt2ir)
+add_dependencies(drpt2trace drpt2ir drir2trace)
+
 # Add the directory containing libipt.so and libipt-sb.so to the linker search path.
-SET(CMAKE_EXE_LINKER_FLAGS  "${CMAKE_EXE_LINKER_FLAGS} -Wl,-rpath='${PROJECT_BINARY_DIR}/lib'")
-target_link_libraries(drpt2trace drpt2ir)
+set(CMAKE_EXE_LINKER_FLAGS
+  "${CMAKE_EXE_LINKER_FLAGS} -Wl,-rpath='${PROJECT_BINARY_DIR}/lib'")
+target_link_libraries(drpt2trace drpt2ir drir2trace)
diff --git a/clients/drcachesim/drpt2trace/drir.h b/clients/drcachesim/drpt2trace/drir.h
new file mode 100644
index 0000000000000000000000000000000000000000..27f3f5384265f1e85e97714701a7c7bed9a30e21
--- /dev/null
+++ b/clients/drcachesim/drpt2trace/drir.h
@@ -0,0 +1,69 @@
+/* **********************************************************
+ * Copyright (c) 2022 Google, Inc.  All rights reserved.
+ * **********************************************************/
+
+/*
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ *
+ * * 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.
+ *
+ * * Neither the name of Google, Inc. 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 COPYRIGHT HOLDERS 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 VMWARE, INC. 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.
+ */
+
+/* drir: the type wraps instrlist_t. */
+
+#ifndef _DRIR_H_
+#define _DRIR_H_ 1
+
+#define DR_FAST_IR 1
+#include "dr_api.h"
+#include <iostream>
+
+/* The auto cleanup wrapper of instrlist_t.
+ * This can ensure the instance of instrlist_t is cleaned up when it is out of scope.
+ */
+struct instrlist_autoclean_t {
+public:
+    instrlist_autoclean_t(void *drcontext, instrlist_t *data)
+        : drcontext(drcontext)
+        , data(data)
+    {
+    }
+    ~instrlist_autoclean_t()
+    {
+#ifdef DEBUG
+        if (drcontext == nullptr) {
+            std::cerr << "instrlist_autoclean_t: invalid drcontext" << std::endl;
+            exit(1);
+        }
+#endif
+        if (data != nullptr) {
+            instrlist_clear_and_destroy(drcontext, data);
+            data = nullptr;
+        }
+    }
+    void *drcontext = nullptr;
+    instrlist_t *data = nullptr;
+};
+
+#endif /* _DRIR_H_ */
diff --git a/clients/drcachesim/drpt2trace/drpt2trace.cpp b/clients/drcachesim/drpt2trace/drpt2trace.cpp
index 982eb731b3a22b15a87a28f3a3e9a41b79bd7b00..b1f79336025b05517038258c7b3cd4f80bd33832 100644
--- a/clients/drcachesim/drpt2trace/drpt2trace.cpp
+++ b/clients/drcachesim/drpt2trace/drpt2trace.cpp
@@ -48,6 +48,7 @@
 
 #include "droption.h"
 #include "pt2ir.h"
+#include "ir2trace.h"
 
 #define CLIENT_NAME "drpt2trace"
 #define SUCCESS 0
@@ -67,6 +68,16 @@ static droption_t<bool> op_help(DROPTION_SCOPE_FRONTEND, "help", false,
 static droption_t<bool> op_print_trace(DROPTION_SCOPE_FRONTEND, "print_trace", false,
                                        "Print trace",
                                        "Print the disassemble code of the trace.");
+static droption_t<std::string> op_mode(
+    DROPTION_SCOPE_FRONTEND, "mode", "",
+    "[Required] The mode for decoding the trace. Valid modes are: ELF, SIDEBAND, DR",
+    "Specifies the mode for decoding the trace. Valid modes are:\n"
+    "ELF: The raw bits of this PT trace are all in one elf file. \n"
+    "SIDEBAND: The raw bits of this PT trace is in different image files, and the "
+    "sideband data contains the image switching info that can be used in the decoding "
+    "process. \n"
+    "DR: The raw bits of this PT trace are in the kernel image file, and the metadata of "
+    "the code segments in kernel image file is in the kernel image metadata file.\n");
 
 static droption_t<std::string>
     op_raw_pt(DROPTION_SCOPE_FRONTEND, "raw_pt", "",
@@ -90,6 +101,16 @@ static droption_t<std::string> op_raw_pt_metadata(
     "[Optional] Path to the metadata file of PT raw trace",
     "Specifies the file path of the metadata file of PT raw trace. This file is "
     "generated by the client drcachesim.");
+static droption_t<std::string>
+    op_kernel_image(DROPTION_SCOPE_FRONTEND, "kernel_image", "",
+                    "[Optional] Path to the file of kernel image",
+                    "Specifies the file path of the file of kernel image. This file is "
+                    "generated by the client drcachesim.");
+static droption_t<std::string> op_kernel_image_metadata(
+    DROPTION_SCOPE_FRONTEND, "kernel_image_metadata", "",
+    "[Optional] Path to the metadata file of kernel image",
+    "Specifies the file path of the metadata file of kernel image. This file is "
+    "generated by the client drcachesim.");
 
 static droption_t<std::string>
     op_primary_sb(DROPTION_SCOPE_FRONTEND, "primary_sb", "",
@@ -213,25 +234,13 @@ static droption_t<unsigned long long> op_sb_kernel_start(
  */
 
 static void
-print_results(IN instrlist_autoclean_t &ilist)
+print_results(IN instrlist_autoclean_t &drir, IN std::vector<trace_entry_t> &entries)
 {
-    if (ilist.data == nullptr) {
-        std::cerr << "The list to store decoded instructions is not initialized."
-                  << std::endl;
-        return;
-    }
-    instr_t *instr = instrlist_first(ilist.data);
-    uint64_t count = 0;
-    while (instr != NULL) {
-        count++;
-        instr = instr_get_next(instr);
-    }
-
     if (op_print_trace.specified()) {
         /* Print the disassemble code of the trace. */
-        instrlist_disassemble(GLOBAL_DCONTEXT, 0, ilist.data, STDOUT);
+        instrlist_disassemble(GLOBAL_DCONTEXT, 0, drir.data, STDOUT);
     }
-    std::cout << "Number of Instructions: " << count << std::endl;
+    std::cout << "Number of Instructions: " << entries.size() << std::endl;
 }
 
 /****************************************************************************
@@ -264,6 +273,11 @@ option_init(int argc, const char *argv[])
         print_usage();
         return false;
     }
+    if (!op_mode.specified()) {
+        std::cerr << CLIENT_NAME << "Usage error: mode must be specified" << std::endl;
+        print_usage();
+        return false;
+    }
     if (!op_raw_pt.specified()) {
         std::cerr << CLIENT_NAME << ": option " << op_raw_pt.get_name() << " is required."
                   << std::endl;
@@ -274,32 +288,52 @@ option_init(int argc, const char *argv[])
     /* Because Intel PT doesn't save instruction bytes or memory contents, the converter
      * needs the instruction bytes for each IPs (Instruction Pointers) to decode the PT
      * trace.
-     * drpt2trace supports two modes to convert:
-     * (1) Sideband Mode: the user must provide sideband data and parameters. In this
+     * drpt2trace supports three modes to convert:
+     * (1) SIDEBAND Mode: the user must provide sideband data and parameters. In this
      * mode, the converter uses sideband decoders to simulate image switches during the
      * conversion. For example, we can use this mode to convert the traces where the
      * instruction bytes are located in multiple images.
-     * (2) Elf Mode: the user needs to provide an elf image for the PT trace. This mode is
+     * (2) ELF Mode: the user needs to provide an elf image for the PT trace. This mode is
      * for cases where the PT trace's instruction bytes belong to one image. So we can use
      * this mode to convert the kernel trace and the short-term user trace where it's
      * likely that we'll not have an image switch.
+     * (3) DR Mode: this mode is used to check whether the syscall PT trace generated by
+     * drcachesim is correct.
      */
-    if ((!op_elf.specified() && !op_primary_sb.specified()) ||
-        (op_elf.specified() && op_primary_sb.specified())) {
-        std::cerr << CLIENT_NAME
-                  << ": exactly one of the options --elf, --primary_sb must be specified."
+    if (op_mode.get_value() == "ELF") {
+        /* Check if the required options for ELF mode are specified. */
+        if (!op_elf.specified()) {
+            std::cerr << CLIENT_NAME << ": option " << op_elf.get_name()
+                      << " is required in " << op_mode.get_value() << " mode."
+                      << std::endl;
+            print_usage();
+            return false;
+        }
+    } else if (op_mode.get_value() == "SIDEBAND") {
+        /* Check if the required options for SIDEBAND mode are specified. */
+        if (!op_primary_sb.specified() || !op_sb_sample_type.specified()) {
+            std::cerr << CLIENT_NAME << ": option " << op_primary_sb.get_name() << " and "
+                      << op_sb_sample_type.get_name() << " are required in "
+                      << op_mode.get_value() << " mode." << std::endl;
+            print_usage();
+            return false;
+        }
+    } else if (op_mode.get_value() == "DR") {
+        /* Check if the required options for DR mode are specified. */
+        if (!op_kernel_image.specified() || !op_kernel_image_metadata.specified()) {
+            std::cerr << CLIENT_NAME << ": option " << op_kernel_image.get_name()
+                      << " and " << op_kernel_image_metadata.get_name()
+                      << " are required in " << op_mode.get_value() << " mode."
+                      << std::endl;
+            print_usage();
+            return false;
+        }
+    } else {
+        std::cerr << CLIENT_NAME << ": option " << op_mode.get_name() << " is invalid."
                   << std::endl;
         print_usage();
         return false;
     }
-
-    /* Check if the required options for sideband mode are specified. */
-    if (op_primary_sb.specified() && !op_sb_sample_type.specified()) {
-        std::cerr << CLIENT_NAME << ": option " << op_sb_sample_type.get_name()
-                  << " is required in sideband mode." << std::endl;
-        print_usage();
-        return false;
-    }
     return true;
 }
 
@@ -329,6 +363,8 @@ main(int argc, const char *argv[])
     config.raw_file_path = op_raw_pt.get_value();
     config.elf_file_path = op_elf.get_value();
     config.elf_base = op_elf_base.get_value();
+    config.kernel_image_path = op_kernel_image.get_value();
+    config.kernel_image_metadata_path = op_kernel_image_metadata.get_value();
     config.sb_primary_file_path = op_primary_sb.get_value();
     std::istringstream op_secondary_sb_stream(op_secondary_sb.get_value());
     copy(std::istream_iterator<std::string>(op_secondary_sb_stream),
@@ -362,16 +398,26 @@ main(int argc, const char *argv[])
         std::cerr << CLIENT_NAME << ": failed to initialize pt2ir_t." << std::endl;
         return FAILURE;
     }
-    instrlist_autoclean_t ilist = { GLOBAL_DCONTEXT, nullptr };
-    pt2ir_convert_status_t status = ptconverter->convert(ilist);
-    if (status != PT2IR_CONV_SUCCESS) {
+    instrlist_autoclean_t drir = { GLOBAL_DCONTEXT, nullptr };
+    pt2ir_convert_status_t pt2ir_convert_status = ptconverter->convert(drir);
+    if (pt2ir_convert_status != PT2IR_CONV_SUCCESS) {
         std::cerr << CLIENT_NAME << ": failed to convert PT raw trace to DR IR."
-                  << "[error status: " << status << "]" << std::endl;
+                  << "[error status: " << pt2ir_convert_status << "]" << std::endl;
+        return FAILURE;
+    }
+
+    /* Convert the DR IR to trace entries. */
+    std::vector<trace_entry_t> entries;
+    ir2trace_convert_status_t ir2trace_convert_status =
+        ir2trace_t::convert(drir, entries);
+    if (ir2trace_convert_status != IR2TRACE_CONV_SUCCESS) {
+        std::cerr << CLIENT_NAME << ": failed to convert DR IR to trace entries"
+                  << "[error status: " << ir2trace_convert_status << "]" << std::endl;
         return FAILURE;
     }
 
     /* Print the count and the disassemble code of DR IR. */
-    print_results(ilist);
+    print_results(drir, entries);
 
     return SUCCESS;
 }
diff --git a/clients/drcachesim/drpt2trace/ir2trace.cpp b/clients/drcachesim/drpt2trace/ir2trace.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..cf38048b51fb2ef4d14dd9ccd850ccdbbbf6a927
--- /dev/null
+++ b/clients/drcachesim/drpt2trace/ir2trace.cpp
@@ -0,0 +1,55 @@
+/* **********************************************************
+ * Copyright (c) 2022 Google, Inc.  All rights reserved.
+ * **********************************************************/
+
+/*
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ *
+ * * 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.
+ *
+ * * Neither the name of Google, Inc. 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 COPYRIGHT HOLDERS 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 VMWARE, INC. 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.
+ */
+
+/* ir2trace: convert DynamoRIO's IR format to trace entries. */
+
+#include "ir2trace.h"
+#include "dr_api.h"
+
+ir2trace_convert_status_t
+ir2trace_t::convert(IN instrlist_autoclean_t &ilist,
+                    OUT std::vector<trace_entry_t> &trace)
+{
+    if (ilist.data == NULL) {
+        return IR2TRACE_CONV_ERROR_INVALID_PARAMETER;
+    }
+    instr_t *instr = instrlist_first(ilist.data);
+    while (instr != NULL) {
+        trace_entry_t entry = {};
+        entry.type = TRACE_TYPE_INSTR;
+        entry.size = instr_length(GLOBAL_DCONTEXT, instr);
+        entry.addr = (uintptr_t)instr_get_app_pc(instr);
+        instr = instr_get_next(instr);
+        trace.push_back(entry);
+    }
+    return IR2TRACE_CONV_SUCCESS;
+}
diff --git a/clients/drcachesim/drpt2trace/ir2trace.h b/clients/drcachesim/drpt2trace/ir2trace.h
new file mode 100644
index 0000000000000000000000000000000000000000..8434b83824fc1a237f82a9144d520afba32872b7
--- /dev/null
+++ b/clients/drcachesim/drpt2trace/ir2trace.h
@@ -0,0 +1,87 @@
+/* **********************************************************
+ * Copyright (c) 2022 Google, Inc.  All rights reserved.
+ * **********************************************************/
+
+/*
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ *
+ * * 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.
+ *
+ * * Neither the name of Google, Inc. 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 COPYRIGHT HOLDERS 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 VMWARE, INC. 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.
+ */
+
+/* ir2trace: convert DynamoRIO's IR format to trace entries. */
+
+#ifndef _IR2TRACE_H_
+#define _IR2TRACE_H_ 1
+
+/**
+ * @file ir2trace.h
+ * @brief Offline DynamoRIO's IR converter. Converts DynamoRIO's IR format to trace
+ * entries.
+ */
+
+#include <vector>
+#include "drir.h"
+#include "../common/trace_entry.h"
+
+#ifndef IN
+#    define IN // nothing
+#endif
+#ifndef OUT
+#    define OUT // nothing
+#endif
+#ifndef INOUT
+#    define INOUT // nothing
+#endif
+
+/**
+ * The type of ir2trace_t::convert() return value.
+ */
+enum ir2trace_convert_status_t {
+    /** The conversion process succeeded. */
+    IR2TRACE_CONV_SUCCESS = 0,
+    /** The conversion process failed: invalid parameter. */
+    IR2TRACE_CONV_ERROR_INVALID_PARAMETER
+};
+
+class ir2trace_t {
+public:
+    ir2trace_t()
+    {
+    }
+    ~ir2trace_t()
+    {
+    }
+
+    /**
+     * Returns ir2trace_convert_status_t. If the convertion is successful, the function
+     * returns IR2TRACE_CONV_SUCCESS. Otherwise, the function returns the corresponding
+     * error code.
+     * \note The convert function convert the DR's IR format to trace entries.
+     */
+    static ir2trace_convert_status_t
+    convert(IN instrlist_autoclean_t &ilist, OUT std::vector<trace_entry_t> &trace);
+};
+
+#endif /* _IR2TRACE_H_ */
diff --git a/clients/drcachesim/drpt2trace/pt2ir.cpp b/clients/drcachesim/drpt2trace/pt2ir.cpp
index fd594d136e76679039bfe8b15f684ed1fa0dfbc1..59ad36286186c892165d6067bb6a9f362d0eeeee 100644
--- a/clients/drcachesim/drpt2trace/pt2ir.cpp
+++ b/clients/drcachesim/drpt2trace/pt2ir.cpp
@@ -34,7 +34,6 @@
 #include <stdlib.h>
 #include <iostream>
 #include <string.h>
-#include <inttypes.h>
 #include <errno.h>
 #include <fstream>
 
@@ -44,6 +43,7 @@
 extern "C" {
 #include "load_elf.h"
 }
+#include "dr_api.h"
 
 #define ERRMSG_HEADER()                       \
     do {                                      \
@@ -157,7 +157,7 @@ pt2ir_t::init(IN pt2ir_config_t &pt2ir_config)
 
     /* Load kcore to sideband kernel image cache. */
     if (!pt2ir_config.kcore_path.empty()) {
-        if (!load_kernel_image(pt2ir_config.kcore_path)) {
+        if (!load_kcore(pt2ir_config.kcore_path)) {
             ERRMSG("Failed to load kernel image: %s\n", pt2ir_config.kcore_path.c_str());
             return false;
         }
@@ -207,12 +207,24 @@ pt2ir_t::init(IN pt2ir_config_t &pt2ir_config)
         }
     }
 
+    if (!pt2ir_config.kernel_image_path.empty() &&
+        !pt2ir_config.kernel_image_metadata_path.empty()) {
+        if (!load_kernel_image(pt2ir_config.kernel_image_path,
+                               pt2ir_config.kernel_image_metadata_path)) {
+            ERRMSG("Failed to load kernel image: %s %s.\n",
+                   pt2ir_config.kernel_image_path.c_str(),
+                   pt2ir_config.kernel_image_metadata_path.c_str());
+            return false;
+        }
+    }
+
     return true;
 }
 
 pt2ir_convert_status_t
 pt2ir_t::convert(OUT instrlist_autoclean_t &ilist)
 {
+    bool append = false;
     /* Initializes an empty instruction list to store all DynamoRIO's IR list converted
      * from PT IR.
      */
@@ -336,9 +348,23 @@ pt2ir_t::convert(OUT instrlist_autoclean_t &ilist)
                 dr_fprintf(STDOUT, ">\n");
 #endif
             }
-            instrlist_append(ilist.data, instr);
+            // dr_printf("" PFX "\n", insn.ip);
+            if (append) {
+                instrlist_append(ilist.data, instr);
+            }
+            if (insn.ip == 0xffffffff8bc00191) {
+                append = !append;
+                if (append == false) {
+                    return PT2IR_CONV_SUCCESS;
+                }
+            }
         }
     }
+
+    if (append) {
+        instrlist_clear_and_destroy(GLOBAL_DCONTEXT, ilist.data);
+        ilist.data = nullptr;
+    }
     return PT2IR_CONV_SUCCESS;
 }
 
@@ -378,7 +404,7 @@ pt2ir_t::load_pt_raw_file(IN std::string &path)
 }
 
 bool
-pt2ir_t::load_kernel_image(IN std::string &path)
+pt2ir_t::load_kcore(IN std::string &path)
 {
     /* Load all ELF sections in kcore to the shared image cache.
      * XXX: load_elf() is implemented in libipt's client ptxed. Currently we directly use
@@ -394,6 +420,38 @@ pt2ir_t::load_kernel_image(IN std::string &path)
     return true;
 }
 
+bool
+pt2ir_t::load_kernel_image(IN std::string &path, IN std::string &metadata_path)
+{
+    std::ifstream metadata_f(metadata_path, std::ios::in);
+    if (!metadata_f.is_open()) {
+        ERRMSG("Failed to open metadata file: %s.\n", metadata_path.c_str());
+        return false;
+    }
+    std::string line;
+    while (std::getline(metadata_f, line)) {
+        uint64_t offset = 0, len = 0, vaddr = 0;
+        if (sscanf(line.c_str(),
+                   HEX64_FORMAT_STRING " " HEX64_FORMAT_STRING " " HEX64_FORMAT_STRING,
+                   &offset, &len, &vaddr) < 3) {
+            ERRMSG("Failed to parse metadata file: %s(%s).\n", metadata_path.c_str(),
+                   line.c_str());
+            metadata_f.close();
+            return false;
+        }
+        int errcode = pt_image_add_file(pt_insn_get_image(pt_instr_decoder_),
+                                        path.c_str(), offset, len, NULL, vaddr);
+        if (errcode < 0) {
+            ERRMSG("Failed to load kernel image %s: %s.\n", path.c_str(),
+                   pt_errstr(pt_errcode(errcode)));
+            metadata_f.close();
+            return false;
+        }
+    }
+    metadata_f.close();
+    return true;
+}
+
 bool
 pt2ir_t::alloc_sb_pevent_decoder(IN struct pt_sb_pevent_config &config)
 {
@@ -418,9 +476,10 @@ pt2ir_t::dx_decoding_error(IN int errcode, IN const char *errtype, IN uint64_t i
     err = pt_insn_get_offset(pt_instr_decoder_, &pos);
     if (err < 0) {
         ERRMSG("Could not determine offset: %s\n", pt_errstr(pt_errcode(err)));
-        ERRMSG("[?, %" PRIx64 "] %s: %s\n", ip, errtype, pt_errstr(pt_errcode(errcode)));
-    } else {
-        ERRMSG("[%" PRIx64 ", IP:%" PRIx64 "] %s: %s\n", pos, ip, errtype,
+        ERRMSG("[?, " HEX64_FORMAT_STRING "] %s: %s\n", ip, errtype,
                pt_errstr(pt_errcode(errcode)));
+    } else {
+        ERRMSG("[" HEX64_FORMAT_STRING ", IP:" HEX64_FORMAT_STRING "] %s: %s\n", pos, ip,
+               errtype, pt_errstr(pt_errcode(errcode)));
     }
 }
diff --git a/clients/drcachesim/drpt2trace/pt2ir.h b/clients/drcachesim/drpt2trace/pt2ir.h
index 00d8dd65447aaa6689ecf8aa75c3fdd0a35679b9..ebe74f23ae5f173187c9646125cf8aaba2ae83b7 100644
--- a/clients/drcachesim/drpt2trace/pt2ir.h
+++ b/clients/drcachesim/drpt2trace/pt2ir.h
@@ -45,8 +45,7 @@
 #include <iostream>
 #include <fstream>
 #include <memory>
-#define DR_FAST_IR 1
-#include "dr_api.h"
+#include "drir.h"
 
 #ifndef IN
 #    define IN // nothing
@@ -58,33 +57,6 @@
 #    define INOUT // nothing
 #endif
 
-/* The auto cleanup wrapper of instrlist_t.
- * This can ensure the instance of instrlist_t is cleaned up when it is out of scope.
- */
-struct instrlist_autoclean_t {
-public:
-    instrlist_autoclean_t(void *drcontext, instrlist_t *data)
-        : drcontext(drcontext)
-        , data(data)
-    {
-    }
-    ~instrlist_autoclean_t()
-    {
-#ifdef DEBUG
-        if (drcontext == nullptr) {
-            std::cerr << "instrlist_autoclean_t: invalid drcontext" << std::endl;
-            exit(1);
-        }
-#endif
-        if (data != nullptr) {
-            instrlist_clear_and_destroy(drcontext, data);
-            data = nullptr;
-        }
-    }
-    void *drcontext = nullptr;
-    instrlist_t *data = nullptr;
-};
-
 /**
  * The type of pt2ir_t::convert() return value.
  */
@@ -259,6 +231,18 @@ public:
      */
     std::string kcore_path;
 
+    /**
+     * The path of the kernel image file. The kernel image file is used to decode the
+     * kernel PT raw trace. This file contains all code segments in kcore.
+     */
+    std::string kernel_image_path;
+
+    /**
+     * The path of the kernel image metadata file. The metadata file contains the metadata
+     * of the code segments in kernel_image_path.
+     */
+    std::string kernel_image_metadata_path;
+
     pt2ir_config_t()
     {
         raw_file_path = "";
@@ -267,6 +251,8 @@ public:
         sb_primary_file_path = "";
         sb_secondary_file_path_list.clear();
         kcore_path = "";
+        kernel_image_path = "";
+        kernel_image_metadata_path = "";
         pt_config.cpu.vendor = CPU_VENDOR_UNKNOWN;
         pt_config.cpu.family = 0;
         pt_config.cpu.model = 0;
@@ -375,7 +361,12 @@ private:
      * information.
      */
     bool
-    load_kernel_image(IN std::string &path);
+    load_kcore(IN std::string &path);
+
+    /* Load the all code segments in kernel image to pt_insn_decoder's image cache.
+     */
+    bool
+    load_kernel_image(IN std::string &path, IN std::string &metadata_path);
 
     /* Allocate a sideband decoder in the sideband session. The sideband session may
      * allocate many decoders, which mainly work on handling sideband perf records and
diff --git a/clients/drcachesim/tests/offline-kernel-simple.templatex b/clients/drcachesim/tests/offline-kernel-simple.templatex
index 0769882703344fdc57b03b8f71cc800704d7f690..ff8307124db3170fe8618bc2500d3c61d0150267 100644
--- a/clients/drcachesim/tests/offline-kernel-simple.templatex
+++ b/clients/drcachesim/tests/offline-kernel-simple.templatex
@@ -1,3 +1,28 @@
 Hello, world!
-.*
+Cache simulation results:
+Core #0 \(1 thread\(s\)\)
+  L1I stats:
+    Hits:                      *[0-9,\.]*
+    Misses:                    *[0-9,\.]*
+    Compulsory misses:         *[0-9,\.]*
+    Invalidations:             *0
+[\s\S]*Miss rate:              *[0-9][,\.]..%
+  L1D stats:
+    Hits:                      *[0-9,\.]*
+    Misses:                    *[0-9,\.]*
+    Compulsory misses:         *[0-9,\.]*
+    Invalidations:             *0
+[\s\S]*Miss rate:              *[0-9][,\.]..%
+Core #1 \(0 thread\(s\)\)
+Core #2 \(0 thread\(s\)\)
+Core #3 \(0 thread\(s\)\)
+LL stats:
+    Hits:                      *[0-9,\.]*
+    Misses:                    *[0-9,\.]*
+    Compulsory misses:         *[0-9,\.]*
+    Invalidations:             *0
+[\s\S]*Local miss rate:        *[0-9,.]*%
+    Child hits:                *[0-9,\.]*
+    Total miss rate:           *[0-4][,\.]..%
+[\s\S]*
 Number of Instructions: *[0-9]*
diff --git a/clients/drcachesim/tracer/kernel_image.cpp b/clients/drcachesim/tracer/kernel_image.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..e9c331aba6754016dcd471294254cb3f7c700824
--- /dev/null
+++ b/clients/drcachesim/tracer/kernel_image.cpp
@@ -0,0 +1,363 @@
+/* **********************************************************
+ * Copyright (c) 2022 Google, Inc.  All rights reserved.
+ * **********************************************************/
+
+/*
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ *
+ * * 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.
+ *
+ * * Neither the name of Google, Inc. 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 COPYRIGHT HOLDERS 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 VMWARE, INC. 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 <elf.h>
+#include <inttypes.h>
+#include <string.h>
+#include <stdio.h>
+#include <fstream>
+
+#include "../common/utils.h"
+#include "dr_api.h"
+#include "kernel_image.h"
+
+#define MODULES_FILE_PATH "/proc/modules"
+#define KALLSYMS_FILE_PATH "/proc/kallsyms"
+#define KCORE_FILE_PATH "/proc/kcore"
+#define KERNEL_IMAGE_FILE_NAME "kimage"
+#define KERNEL_IMAGE_METADATA_FILE_NAME "kimage.metadata"
+
+#define MODULE_NEME_MAX_LEN 100
+#define SYMBOL_MAX_LEN 300
+
+/* This struct type defines the information of every module read from /proc/module.
+ * We store all information on a linked list.
+ */
+struct proc_module_t {
+    proc_module_t *next;
+    char name[MODULE_NEME_MAX_LEN];
+    uint64_t start;
+    uint64_t end;
+};
+
+/* This struct type defines the metadata of every code segment read from  /proc/kcore.
+ * We store all metadata on a linked list.
+ */
+struct proc_kcore_code_segment_t {
+    proc_kcore_code_segment_t *next;
+
+    /* The start offset of the code segment. */
+    uint64_t start;
+
+    /* The length of the code segment. */
+    ssize_t len;
+
+    /* The base address of the code segment. */
+    uint64_t base;
+};
+
+kernel_image_t::kernel_image_t()
+    : modules_(nullptr)
+    , kcore_code_segments_(nullptr)
+{
+}
+
+kernel_image_t::~kernel_image_t()
+{
+    /* Free the module information linked list. */
+    proc_module_t *module = modules_;
+    while (module) {
+        proc_module_t *next = module->next;
+        dr_global_free(module, sizeof(proc_module_t));
+        module = next;
+    }
+    modules_ = nullptr;
+
+    /* Free the kcore code segment metadata linked list. */
+    proc_kcore_code_segment_t *kcore_code_segment = kcore_code_segments_;
+    while (kcore_code_segment) {
+        proc_kcore_code_segment_t *next = kcore_code_segment->next;
+        dr_global_free(kcore_code_segment, sizeof(proc_kcore_code_segment_t));
+        kcore_code_segment = next;
+    }
+    kcore_code_segments_ = nullptr;
+}
+
+bool
+kernel_image_t::read_modules()
+{
+    std::ifstream f(MODULES_FILE_PATH, std::ios::in);
+    if (!f.is_open()) {
+        ASSERT(false, "failed to open " MODULES_FILE_PATH);
+        return false;
+    }
+    proc_module_t *last_module = modules_;
+    std::string line;
+    while (std::getline(f, line)) {
+        /* Each line is similar to the following line:
+         * 'scsi_dh_hp_sw 12895 0 - Live 0xffffffffa005e000'
+         * We parse the first, the second, and the last field to construct a proc_module_t
+         * type instance.
+         */
+        char mname[MODULE_NEME_MAX_LEN];
+        uint64_t addr;
+        int len;
+        if (dr_sscanf(line.c_str(), "%99s %d %*d %*s %*s " HEX64_FORMAT_STRING, mname,
+                      &len, &addr) != 3) {
+            ASSERT(false, "failed to parse " MODULES_FILE_PATH);
+            f.close();
+            return false;
+        }
+
+        proc_module_t *module = (proc_module_t *)dr_global_alloc(sizeof(proc_module_t));
+        dr_snprintf(module->name, BUFFER_SIZE_ELEMENTS(module->name), "%s", mname);
+        NULL_TERMINATE_BUFFER(module->name);
+        module->start = addr;
+        module->end = addr + len;
+        module->next = nullptr;
+        if (last_module == nullptr) {
+            modules_ = module;
+            last_module = module;
+        } else {
+            last_module->next = module;
+            last_module = module;
+        }
+    }
+    f.close();
+    return true;
+}
+
+bool
+kernel_image_t::read_kallsyms()
+{
+    std::ifstream f(KALLSYMS_FILE_PATH, std::ios::in);
+    if (!f.is_open()) {
+        ASSERT(false, "failed to open " KALLSYMS_FILE_PATH);
+        return false;
+    }
+    proc_module_t *kernel_module = nullptr;
+    std::string line;
+    while (std::getline(f, line)) {
+        char name[SYMBOL_MAX_LEN];
+        uint64_t addr;
+        if (dr_sscanf(line.c_str(), HEX64_FORMAT_STRING " %*1c %299s [%*99s", &addr,
+                      name) < 2)
+            continue;
+        if (strcmp(name, "_stext") == 0) {
+            if (kernel_module != nullptr) {
+                f.close();
+                return false;
+            }
+            kernel_module = (proc_module_t *)dr_global_alloc(sizeof(proc_module_t));
+            dr_snprintf(kernel_module->name, BUFFER_SIZE_ELEMENTS(kernel_module->name),
+                        "kernel");
+            NULL_TERMINATE_BUFFER(kernel_module->name);
+            kernel_module->start = addr;
+        } else if (!strcmp(name, "_etext")) {
+            if (kernel_module == nullptr) {
+                f.close();
+                return false;
+            }
+            kernel_module->end = addr;
+            kernel_module->next = modules_;
+            modules_ = kernel_module;
+            kernel_module = nullptr;
+        }
+    }
+    f.close();
+    return true;
+}
+
+bool
+kernel_image_t::read_kcore()
+{
+    file_t fd = dr_open_file(KCORE_FILE_PATH, DR_FILE_READ);
+    if (fd < 0) {
+        ASSERT(false, "failed to open" KCORE_FILE_PATH);
+        return false;
+    }
+
+    uint8_t e_ident[EI_NIDENT];
+    if (dr_read_file(fd, e_ident, sizeof(e_ident)) != sizeof(e_ident)) {
+        ASSERT(false, "failed to read the e_ident array of " KCORE_FILE_PATH);
+        dr_close_file(fd);
+        return false;
+    }
+
+    for (int idx = 0; idx < SELFMAG; ++idx) {
+        if (e_ident[idx] != ELFMAG[idx]) {
+            ASSERT(false, KCORE_FILE_PATH " is not an ELF file");
+            dr_close_file(fd);
+            return false;
+        }
+    }
+    if (e_ident[EI_CLASS] != ELFCLASS64) {
+        ASSERT(false, KCORE_FILE_PATH " is not a 64-bit ELF file");
+        dr_close_file(fd);
+        return false;
+    }
+
+    /* Read phdrs from kcore. */
+    if (!dr_file_seek(fd, 0, DR_SEEK_SET)) {
+        ASSERT(false, "failed to seek to the begin of " KCORE_FILE_PATH);
+        dr_close_file(fd);
+        return false;
+    }
+
+    Elf64_Ehdr ehdr;
+    if (dr_read_file(fd, &ehdr, sizeof(ehdr)) != sizeof(ehdr)) {
+        ASSERT(false, "failed to read the ehdr of " KCORE_FILE_PATH);
+        dr_close_file(fd);
+        return false;
+    }
+    if (LONG_MAX < ehdr.e_phoff) {
+        ASSERT(false, "the ELF header of " KCORE_FILE_PATH " is too big");
+        dr_close_file(fd);
+        return false;
+    }
+
+    if (!dr_file_seek(fd, (long)ehdr.e_phoff, DR_SEEK_SET)) {
+        ASSERT(false, "failed to seek the program header's end of " KCORE_FILE_PATH);
+        dr_close_file(fd);
+        return false;
+    }
+
+    for (Elf64_Half pidx = 0; pidx < ehdr.e_phnum; ++pidx) {
+        Elf64_Phdr phdr;
+        if (dr_read_file(fd, &phdr, sizeof(phdr)) != sizeof(phdr)) {
+            ASSERT(false, "failed to read the Phdr of " KCORE_FILE_PATH);
+            dr_close_file(fd);
+            return false;
+        }
+
+        if (phdr.p_type != PT_LOAD || phdr.p_filesz == 0)
+            continue;
+        /* Read code segment metadata from kcore. */
+        proc_module_t *module = modules_;
+        if (module == nullptr) {
+            ASSERT(false, "no module found in " MODULES_FILE_PATH);
+            dr_close_file(fd);
+            return false;
+        }
+        proc_kcore_code_segment_t *last_kcore_code_segment = kcore_code_segments_;
+        while (module != nullptr) {
+            if (module->start >= phdr.p_vaddr &&
+                module->end < phdr.p_vaddr + phdr.p_filesz) {
+                proc_kcore_code_segment_t *kcore_code_segment =
+                    (proc_kcore_code_segment_t *)dr_global_alloc(
+                        sizeof(proc_kcore_code_segment_t));
+                kcore_code_segment->start = module->start - phdr.p_vaddr + phdr.p_offset;
+                kcore_code_segment->len = module->end - module->start;
+                kcore_code_segment->base = module->start;
+                kcore_code_segment->next = nullptr;
+                if (last_kcore_code_segment == nullptr) {
+                    kcore_code_segments_ = kcore_code_segment;
+                    last_kcore_code_segment = kcore_code_segment;
+                } else {
+                    last_kcore_code_segment->next = kcore_code_segment;
+                    last_kcore_code_segment = kcore_code_segment;
+                }
+            }
+            module = module->next;
+        }
+    }
+
+    dr_close_file(fd);
+    return true;
+}
+
+bool
+kernel_image_t::init()
+{
+    if (!read_modules()) {
+        return false;
+    }
+    if (!read_kallsyms()) {
+        return false;
+    }
+    if (!read_kcore()) {
+        return false;
+    }
+    return true;
+}
+
+bool
+kernel_image_t::dump(const char *to_dir)
+{
+    file_t kcore_fd = dr_open_file(KCORE_FILE_PATH, DR_FILE_READ);
+    if (kcore_fd < 0) {
+        ASSERT(false, "failed to open " KCORE_FILE_PATH);
+        return false;
+    }
+
+    char image_file_name[MAXIMUM_PATH];
+    char metadata_file_name[MAXIMUM_PATH];
+    dr_snprintf(image_file_name, BUFFER_SIZE_ELEMENTS(image_file_name), "%s%s%s", to_dir,
+                DIRSEP, KERNEL_IMAGE_FILE_NAME);
+    NULL_TERMINATE_BUFFER(image_file_name);
+    dr_snprintf(metadata_file_name, BUFFER_SIZE_ELEMENTS(metadata_file_name), "%s%s%s",
+                to_dir, DIRSEP, KERNEL_IMAGE_METADATA_FILE_NAME);
+    NULL_TERMINATE_BUFFER(metadata_file_name);
+    file_t image_fd = dr_open_file(image_file_name, DR_FILE_WRITE_OVERWRITE);
+    if (image_fd < 0) {
+        ASSERT(false, "failed to open kernel image file");
+        dr_close_file(kcore_fd);
+        return false;
+    }
+    std::ofstream metadata_fd(metadata_file_name, std::ios::out);
+    if (!metadata_fd.is_open()) {
+        ASSERT(false, "failed to open kernel image metadata file");
+        dr_close_file(image_fd);
+        dr_close_file(kcore_fd);
+        return false;
+    }
+
+    uint64_t offset = 0;
+    bool dump_success = true;
+    proc_kcore_code_segment_t *kcore_code_segment = kcore_code_segments_;
+    while (kcore_code_segment != nullptr) {
+        dr_file_seek(kcore_fd, kcore_code_segment->start, DR_SEEK_SET);
+        char *buf = (char *)dr_global_alloc(kcore_code_segment->len);
+        if (dr_read_file(kcore_fd, buf, kcore_code_segment->len) !=
+            kcore_code_segment->len) {
+            ASSERT(false, "failed to read " KCORE_FILE_PATH);
+            dump_success = false;
+            break;
+        }
+        if (dr_write_file(image_fd, buf, kcore_code_segment->len) !=
+            kcore_code_segment->len) {
+            ASSERT(false, "failed to write code segment to kernel image file");
+            dump_success = false;
+            break;
+        }
+        dr_global_free(buf, kcore_code_segment->len);
+        metadata_fd << std::hex << offset << " " << kcore_code_segment->len << " "
+                    << kcore_code_segment->base << std::endl;
+        offset += kcore_code_segment->len;
+        kcore_code_segment = kcore_code_segment->next;
+    }
+
+    metadata_fd.close();
+    dr_close_file(image_fd);
+    dr_close_file(kcore_fd);
+    return dump_success;
+}
diff --git a/clients/drcachesim/tracer/kernel_image.h b/clients/drcachesim/tracer/kernel_image.h
new file mode 100644
index 0000000000000000000000000000000000000000..8bde07db9f8e364c8a17141e53c5c5ca7f939038
--- /dev/null
+++ b/clients/drcachesim/tracer/kernel_image.h
@@ -0,0 +1,89 @@
+/* **********************************************************
+ * Copyright (c) 2022 Google, Inc.  All rights reserved.
+ * **********************************************************/
+
+/*
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ *
+ * * 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.
+ *
+ * * Neither the name of Google, Inc. 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 COPYRIGHT HOLDERS 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 VMWARE, INC. 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.
+ */
+
+/* kernel_image.h: header of module for dump kernel code segments to one file.
+ * This is only for Linux x86_64.
+ */
+
+#ifndef _KCORE_H_
+#define _KCORE_H_ 1
+
+struct proc_module_t;
+struct proc_kcore_code_segment_t;
+
+/* This class is used to dump kernel code segments to one file.
+ */
+class kernel_image_t {
+public:
+    kernel_image_t();
+    ~kernel_image_t();
+
+    /* Parse the kernel code segments from /proc/kcore.
+     * This function will first read modules from /proc/modules, then read all kernel
+     * symbols from /proc/kallsyms. Then it will parse the kernel code segments from
+     * /proc/kcore.
+     */
+    bool
+    init();
+
+    /* Dump the kernel code segments to one file.
+     * This function will dump all kernel code segments to one file called kimage and dump
+     * the metadata of every code segment to kimage.metadata.
+     */
+    bool
+    dump(const char *to_dir);
+
+private:
+    /* Read the module information to module list from /proc/modules.
+     */
+    bool
+    read_modules();
+
+    /* Parse the kernel module information from /proc/kallsyms and insert them to the
+     * start of module list.
+     */
+    bool
+    read_kallsyms();
+
+    /* Read the kernel code segments from /proc/kcore.
+     */
+    bool
+    read_kcore();
+
+    /* The module list. */
+    proc_module_t *modules_;
+
+    /* The kernel code segment metadata list. */
+    proc_kcore_code_segment_t *kcore_code_segments_;
+};
+
+#endif /* _KCORE_H_ */
diff --git a/clients/drcachesim/tracer/raw2trace.cpp b/clients/drcachesim/tracer/raw2trace.cpp
index d3e40a151c55cb23d49cd5afcfd3d62920bf0a00..efa63bbee1b2d31e15d738740ba646a0a399974e 100644
--- a/clients/drcachesim/tracer/raw2trace.cpp
+++ b/clients/drcachesim/tracer/raw2trace.cpp
@@ -1296,13 +1296,20 @@ raw2trace_t::get_file_type(void *tls)
     return tdata->file_type;
 }
 
+std::string
+raw2trace_t::get_syscall_pt_trace_dir(void *tls)
+{
+    auto tdata = reinterpret_cast<raw2trace_thread_data_t *>(tls);
+    return tdata->syscall_pt_trace_dir;
+}
+
 raw2trace_t::raw2trace_t(const char *module_map,
                          const std::vector<std::istream *> &thread_files,
                          const std::vector<std::ostream *> &out_files,
                          const std::vector<archive_ostream_t *> &out_archives,
                          file_t encoding_file, void *dcontext, unsigned int verbosity,
-                         int worker_count, const std::string &alt_module_dir,
-                         uint64_t chunk_instr_count)
+                         int worker_count, const std::string &syscall_pt_trace_dir,
+                         const std::string &alt_module_dir, uint64_t chunk_instr_count)
     : trace_converter_t(dcontext)
     , worker_count_(worker_count)
     , user_process_(nullptr)
@@ -1310,6 +1317,7 @@ raw2trace_t::raw2trace_t(const char *module_map,
     , modmap_(module_map)
     , encoding_file_(encoding_file)
     , verbosity_(verbosity)
+    , syscall_pt_trace_dir_(syscall_pt_trace_dir)
     , alt_module_dir_(alt_module_dir)
     , chunk_instr_count_(chunk_instr_count)
 {
@@ -1342,6 +1350,7 @@ raw2trace_t::raw2trace_t(const char *module_map,
             thread_data_[i].out_archive = nullptr;
             thread_data_[i].out_file = out_files[i];
         }
+        thread_data_[i].syscall_pt_trace_dir = syscall_pt_trace_dir_;
     }
     // Since we know the traced-thread count up front, we use a simple round-robin
     // static work assigment.  This won't be as load balanced as a dynamic work
diff --git a/clients/drcachesim/tracer/raw2trace.h b/clients/drcachesim/tracer/raw2trace.h
index 13939c62ada0c1fe57b34b7a155f84e87ff4b19f..77314f6e385fdd8e7c278774e248f76b71ad3028 100644
--- a/clients/drcachesim/tracer/raw2trace.h
+++ b/clients/drcachesim/tracer/raw2trace.h
@@ -44,6 +44,10 @@
 #include "dr_api.h"
 #include "drmemtrace.h"
 #include "drcovlib.h"
+#ifdef BUILD_PT_POST_PROCESSOR
+#    include "../drpt2trace/pt2ir.h"
+#    include "../drpt2trace/ir2trace.h"
+#endif
 #include <array>
 #include <atomic>
 #include <memory>
@@ -73,7 +77,7 @@
 #    define OUTFILE_SUFFIX_LZ4 "raw.lz4"
 #endif
 #define OUTFILE_SUBDIR "raw"
-#ifdef BUILD_PT_TRACER
+#if defined(BUILD_PT_TRACER) || defined(BUILD_PT_POST_PROCESSOR)
 #    define KERNEL_PT_OUTFILE_SUBDIR "kernel.raw"
 #endif
 #define WINDOW_SUBDIR_PREFIX "window"
@@ -799,6 +803,16 @@ protected:
                     buf, (trace_marker_type_t)in_entry->extended.valueB, marker_val);
                 if (in_entry->extended.valueB == TRACE_MARKER_TYPE_KERNEL_EVENT) {
                     impl()->log(4, "Signal/exception between bbs\n");
+                } else if (in_entry->extended.valueB == TRACE_MARKER_TYPE_SYSCALL_ID) {
+#ifdef BUILD_PT_POST_PROCESSOR
+                    std::string error = impl()->write(tls, buf_base,
+                                       reinterpret_cast<trace_entry_t *>(buf));
+                    if (!error.empty())
+                        return error;
+                    append_syscall_pt_trace(tls, tid, marker_val);
+                    buf_base = impl()->get_write_buffer(tls);
+                    buf = reinterpret_cast<byte *>(buf_base);
+#endif
                 }
                 impl()->log(3, "Appended marker type %u value " PIFX "\n",
                             (trace_marker_type_t)in_entry->extended.valueB,
@@ -1454,6 +1468,78 @@ private:
         return "";
     }
 
+#ifdef BUILD_PT_POST_PROCESSOR
+#    define PT_DATA_FILE_NAME_SUFFIX ".pt"
+#    define PT_METADAT_FILE_NAME_SUFFIX ".metadata"
+#    define KERNEL_IMAGE_FILE_NAME "kimage"
+#    define KERNEL_IMAGE_METADATA_FILE_NAME "kimage.metadata"
+    std::string
+    append_syscall_pt_trace(void *tls, thread_id_t thread_id, uint64_t syscall_id)
+    {
+        std::string error = "";
+        pt2ir_config_t config = {};
+        config.raw_file_path = impl()->get_syscall_pt_trace_dir(tls) + "/" +
+            std::to_string(thread_id) + "." + std::to_string(syscall_id) +
+            PT_DATA_FILE_NAME_SUFFIX;
+        config.kernel_image_path =
+            impl()->get_syscall_pt_trace_dir(tls) + "/" + KERNEL_IMAGE_FILE_NAME;
+        config.kernel_image_metadata_path =
+            impl()->get_syscall_pt_trace_dir(tls) + "/" + KERNEL_IMAGE_METADATA_FILE_NAME;
+        config.init_with_metadata(config.raw_file_path + PT_METADAT_FILE_NAME_SUFFIX);
+
+        std::unique_ptr<pt2ir_t> ptconverter(new pt2ir_t());
+        if (!ptconverter->init(config)) {
+            error = "Failed to initialize pt2ir";
+            return error;
+        }
+        instrlist_autoclean_t drir = { GLOBAL_DCONTEXT, nullptr };
+        pt2ir_convert_status_t pt2ir_convert_status = ptconverter->convert(drir);
+        if (pt2ir_convert_status != PT2IR_CONV_SUCCESS) {
+            error = "Failed to convert PT raw trace to DR IR";
+            return error;
+        }
+        if (drir.data == nullptr) {
+            dr_printf("No DR IR instructions to append %d\n", syscall_id);
+            return "";
+        }
+
+        std::vector<trace_entry_t> entries;
+        ir2trace_convert_status_t ir2trace_convert_status =
+            ir2trace_t::convert(drir, entries);
+        if (ir2trace_convert_status != IR2TRACE_CONV_SUCCESS) {
+            error = "Failed to convert DR IR to trace entries";
+            return error;
+        }
+
+        trace_entry_t *buf_base = impl()->get_write_buffer(tls);
+        byte *buf = reinterpret_cast<byte *>(buf_base);
+        for (auto &entry : entries) {
+            trace_entry_t *buf_entry = reinterpret_cast<trace_entry_t *>(buf);
+            *buf_entry = entry;
+            buf += sizeof(trace_entry_t);
+            size_t size = reinterpret_cast<trace_entry_t *>(buf) - buf_base;
+            if (size >= WRITE_BUFFER_SIZE) {
+                error = impl()->write(tls, buf_base,
+                                   reinterpret_cast<trace_entry_t *>(buf));
+                if (!error.empty())
+                    break;
+                impl()->log(4, "Appended %u entries to write buffer\n", size);
+                buf_base = impl()->get_write_buffer(tls);
+                buf = reinterpret_cast<byte *>(buf_base);
+            }
+        }
+
+        if (error.empty()) {
+            error = impl()->write(tls, buf_base, reinterpret_cast<trace_entry_t *>(buf));
+            if (!error.empty())
+                return error;
+            impl()->log(4, "Appended %u entries to write buffer\n",
+                        reinterpret_cast<trace_entry_t *>(buf) - buf_base);
+        }
+        return "";
+    }
+#endif
+
     std::string
     get_marker_value(void *tls, INOUT const offline_entry_t **entry, OUT uintptr_t *value)
     {
@@ -1648,6 +1734,7 @@ public:
                 const std::vector<archive_ostream_t *> &out_archives,
                 file_t encoding_file = INVALID_FILE, void *dcontext = nullptr,
                 unsigned int verbosity = 0, int worker_count = -1,
+                const std::string &syscall_pt_trace_dir = "",
                 const std::string &alt_module_dir = "",
                 uint64_t chunk_instr_count = 10 * 1000 * 1000);
     virtual ~raw2trace_t();
@@ -1778,6 +1865,7 @@ protected:
             , last_decode_modidx(0)
             , last_decode_modoffs(0)
             , last_block_summary(nullptr)
+            , syscall_pt_trace_dir("")
         {
         }
 
@@ -1809,6 +1897,7 @@ protected:
         uint64 last_decode_modidx;
         uint64 last_decode_modoffs;
         block_summary_t *last_block_summary;
+        std::string syscall_pt_trace_dir;
         uint64 last_window = 0;
 
         // Statistics on the processing.
@@ -1893,6 +1982,8 @@ private:
     add_to_statistic(void *tls, raw2trace_statistic_t stat, int value);
     void
     log_instruction(app_pc decode_pc, app_pc orig_pc);
+    std::string
+    get_syscall_pt_trace_dir(void *tls);
 
     std::string
     append_delayed_branch(void *tls);
@@ -2015,6 +2106,8 @@ private:
 
     unsigned int verbosity_ = 0;
 
+    std::string syscall_pt_trace_dir_;
+
     std::string alt_module_dir_;
 
     // Our decode_cache duplication will not scale forever on very large code
diff --git a/clients/drcachesim/tracer/raw2trace_directory.cpp b/clients/drcachesim/tracer/raw2trace_directory.cpp
index 614432c35244abdeeac011b062a042e8a4ce0f66..ac0475a8776c7ae4ab8d5d62ba99c404657613f4 100644
--- a/clients/drcachesim/tracer/raw2trace_directory.cpp
+++ b/clients/drcachesim/tracer/raw2trace_directory.cpp
@@ -395,6 +395,16 @@ raw2trace_directory_t::initialize_funclist_file(
     return "";
 }
 
+std::string
+raw2trace_directory_t::get_syscall_pt_trace_dir()
+{
+#ifdef BUILD_PT_POST_PROCESSOR
+    return indir_ + "/../" + KERNEL_PT_OUTFILE_SUBDIR;
+#else
+    return "";
+#endif
+}
+
 raw2trace_directory_t::~raw2trace_directory_t()
 {
     if (modfile_bytes_ != nullptr)
diff --git a/clients/drcachesim/tracer/raw2trace_directory.h b/clients/drcachesim/tracer/raw2trace_directory.h
index e1aac03f91b520114133470aac50a91ff194adf6..7e6327561eb7bae26e5c660f3215bc43785fba76 100644
--- a/clients/drcachesim/tracer/raw2trace_directory.h
+++ b/clients/drcachesim/tracer/raw2trace_directory.h
@@ -86,6 +86,9 @@ public:
     std::vector<std::ostream *> out_files_;
     std::vector<archive_ostream_t *> out_archives_;
 
+    std::string
+    get_syscall_pt_trace_dir();
+
 private:
     std::string
     read_module_file(const std::string &modfilename);
diff --git a/clients/drcachesim/tracer/raw2trace_launcher.cpp b/clients/drcachesim/tracer/raw2trace_launcher.cpp
index 6aafedd3be9b4f53f95d14bffe99a6b0d6c35f86..8acb14860f4bbf862c2cf3f457bf0ca0e92831d5 100644
--- a/clients/drcachesim/tracer/raw2trace_launcher.cpp
+++ b/clients/drcachesim/tracer/raw2trace_launcher.cpp
@@ -108,10 +108,11 @@ _tmain(int argc, const TCHAR *targv[])
     std::string dir_err = dir.initialize(op_indir.get_value(), op_outdir.get_value());
     if (!dir_err.empty())
         FATAL_ERROR("Directory parsing failed: %s", dir_err.c_str());
-    raw2trace_t raw2trace(
-        dir.modfile_bytes_, dir.in_files_, dir.out_files_, dir.out_archives_,
-        dir.encoding_file_, nullptr, op_verbose.get_value(), op_jobs.get_value(),
-        op_alt_module_dir.get_value(), op_chunk_instr_count.get_value());
+    raw2trace_t raw2trace(dir.modfile_bytes_, dir.in_files_, dir.out_files_,
+                          dir.out_archives_, dir.encoding_file_, nullptr,
+                          op_verbose.get_value(), op_jobs.get_value(),
+                          dir.get_syscall_pt_trace_dir(), op_alt_module_dir.get_value(),
+                          op_chunk_instr_count.get_value());
     std::string error = raw2trace.do_conversion();
     if (!error.empty())
         FATAL_ERROR("Conversion failed: %s", error.c_str());
diff --git a/clients/drcachesim/tracer/syscall_pt_trace.cpp b/clients/drcachesim/tracer/syscall_pt_trace.cpp
index 7071d0e9d0273351702edb537294401b56bbcd9e..034272b71b764f22b20feee5dcc6d4f48d8bd36d 100644
--- a/clients/drcachesim/tracer/syscall_pt_trace.cpp
+++ b/clients/drcachesim/tracer/syscall_pt_trace.cpp
@@ -38,6 +38,7 @@
 #include "../../../core/unix/include/syscall_linux_x86.h"
 #include "dr_api.h"
 #include "drpttracer.h"
+#include "kernel_image.h"
 #include "syscall_pt_trace.h"
 
 #ifndef BUILD_PT_TRACER
@@ -65,10 +66,9 @@ syscall_pt_trace_t::~syscall_pt_trace_t()
 
 bool
 syscall_pt_trace_t::init(void *drcontext, char *pt_dir_name, size_t pt_dir_name_size,
-                         file_t (*open_file_func)(const char *fname, uint mode_flags),
-                         ssize_t (*write_file_func)(file_t file, const void *data,
-                                                    size_t count),
-                         void (*close_file_func)(file_t file))
+                         drmemtrace_open_file_func_t open_file_func,
+                         drmemtrace_write_file_func_t write_file_func,
+                         drmemtrace_close_file_func_t close_file_func)
 {
     drcontext_ = drcontext;
     memcpy(pt_dir_name_, pt_dir_name, pt_dir_name_size);
@@ -162,43 +162,25 @@ syscall_pt_trace_t::trace_data_dump(drpttracer_output_autoclean_t &output)
 }
 
 bool
-syscall_pt_trace_t::kernel_image_dump(IN const char *to_dir)
+syscall_pt_trace_t::is_syscall_pt_trace_enabled(IN int sysnum)
 {
-    /* TODO i#5505: Using the perf command to get kcore and kallsyms is not a good
-     * solution. There are three issues:
-     * 1. The perf may not be installed on the system.
-     * 2. The $PATH of system may not contain the perf command.
-     * 3. The following script will clobber/remove the user's local data.
-     * These issues may cause unexpected behaviors.
-     *
-     * We need to implement the kcore_copy() in syscall_pt_trace_t.
+    /* The following syscall's post syscall callback can't be triggered. So we don't
+     * support to recording the kernel PT of them.
      */
-#define SHELLSCRIPT_FMT                                                          \
-    "perf record --kcore -e intel_pt/cyc,noretcomp/k echo '' >/dev/null 2>&1 \n" \
-    "chmod 755 -R perf.data \n"                                                  \
-    "cp perf.data/kcore_dir/kcore %s/ \n"                                        \
-    "cp perf.data/kcore_dir/kallsyms %s/ \n"                                     \
-    "rm -rf perf.data \n"
-#define SHELLSCRIPT_MAX_LEN 512 + MAXIMUM_PATH * 2
-    char shellscript[SHELLSCRIPT_MAX_LEN];
-    dr_snprintf(shellscript, BUFFER_SIZE_ELEMENTS(shellscript), SHELLSCRIPT_FMT, to_dir,
-                to_dir);
-    NULL_TERMINATE_BUFFER(shellscript);
-    int ret = system(shellscript);
-    if (ret != 0) {
-        ASSERT(false, "failed to run shellscript to dump kcore and kallsyms");
+    if (sysnum == SYS_exit || sysnum == SYS_exit_group || sysnum == SYS_execve) {
         return false;
     }
     return true;
 }
 
 bool
-syscall_pt_trace_t::is_syscall_pt_trace_enabled(IN int sysnum)
+syscall_pt_trace_t::kernel_image_dump(IN const char *to_dir)
 {
-    /* The following syscall's post syscall callback can't be triggered. So we don't
-     * support to recording the kernel PT of them.
-     */
-    if (sysnum == SYS_exit || sysnum == SYS_exit_group || sysnum == SYS_execve) {
+    std::unique_ptr<kernel_image_t> kernel_image(new kernel_image_t());
+    if (!kernel_image->init()) {
+        return false;
+    }
+    if (!kernel_image->dump(to_dir)) {
         return false;
     }
     return true;
diff --git a/clients/drcachesim/tracer/syscall_pt_trace.h b/clients/drcachesim/tracer/syscall_pt_trace.h
index ecf4221e0d4ca70480e16e6773ef71eb790f6aad..c5cac4dc143bf3a04814163cabe7a73758ac6faf 100644
--- a/clients/drcachesim/tracer/syscall_pt_trace.h
+++ b/clients/drcachesim/tracer/syscall_pt_trace.h
@@ -40,6 +40,7 @@
 
 #include "../common/utils.h"
 #include "dr_api.h"
+#include "drmemtrace.h"
 #include "drpttracer.h"
 
 /* The auto cleanup wrapper of pttracer handle.
@@ -108,9 +109,9 @@ public:
      */
     bool
     init(void *drcontext, char *pt_dir_name, size_t pt_dir_name_size,
-         file_t (*open_file_func)(const char *fname, uint mode_flags),
-         ssize_t (*write_file_func)(file_t file, const void *data, size_t count),
-         void (*close_file_func)(file_t file));
+         drmemtrace_open_file_func_t open_file_func,
+         drmemtrace_write_file_func_t write_file_func,
+         drmemtrace_close_file_func_t close_file_func);
 
     /* Start the PT tracing for current syscall and store the sysnum of the syscall. */
     bool
@@ -153,13 +154,13 @@ private:
     trace_data_dump(drpttracer_output_autoclean_t &output);
 
     /* The shared file open function. */
-    file_t (*open_file_func_)(const char *fname, uint mode_flags);
+    drmemtrace_open_file_func_t open_file_func_;
 
     /* The shared file write function. */
-    ssize_t (*write_file_func_)(file_t file, const void *data, size_t count);
+    drmemtrace_write_file_func_t write_file_func_;
 
     /* The shared file close function. */
-    void (*close_file_func_)(file_t file);
+    drmemtrace_close_file_func_t close_file_func_;
 
     /* The pttracer handle held by this instance. */
     drpttracer_handle_autoclean_t pttracer_handle_;
diff --git a/clients/drcachesim/tracer/tracer.cpp b/clients/drcachesim/tracer/tracer.cpp
index 6ba64481bdf86bbd69bdf866106eeb709c39a638..122f5a3a6802a73531f13bbc42baf939630961a7 100644
--- a/clients/drcachesim/tracer/tracer.cpp
+++ b/clients/drcachesim/tracer/tracer.cpp
@@ -316,6 +316,7 @@ instrumentation_exit()
 {
     dr_unregister_filter_syscall_event(event_filter_syscall);
     if (!drmgr_unregister_pre_syscall_event(event_pre_syscall) ||
+        !drmgr_unregister_post_syscall_event(event_post_syscall) ||
         !drmgr_unregister_kernel_xfer_event(event_kernel_xfer) ||
         !drmgr_unregister_bb_app2app_event(event_bb_app2app))
         DR_ASSERT(false);
@@ -1285,6 +1286,10 @@ event_post_syscall(void *drcontext, int sysnum)
     /* Write a marker to userspace raw trace. */
     if (BUF_PTR(data->seg_base) == NULL)
         return; /* This thread was filtered out. */
+    // if (data->syscall_pt_trace.get_last_recorded_syscall_id() < 200) {
+    //     dr_printf("sysnum %d, id %d \n", sysnum, data->syscall_pt_trace.get_last_recorded_syscall_id());
+    // }
+    // dr_printf("sysnum %d, id %d \n", sysnum, data->syscall_pt_trace.get_last_recorded_syscall_id());
     trace_marker_type_t marker_type = TRACE_MARKER_TYPE_SYSCALL_ID;
     uintptr_t marker_val = data->syscall_pt_trace.get_last_recorded_syscall_id();
     BUF_PTR(data->seg_base) +=
@@ -1521,9 +1526,13 @@ event_exit(void)
 #ifdef BUILD_PT_TRACER
     if (op_offline.get_value() && op_enable_kernel_tracing.get_value()) {
         drpttracer_exit();
-        /* Dump kcore and kallsyms to {kernel_pt_logsubdir}. */
+        /* Dump kcore to kimage and kimage.metadata, and store the two file to
+         * {kernel_pt_logsubdir}.
+         * The Kernel dump function is only invoked at processes exit on the Linux X86_64
+         * platform.
+         */
         if (!syscall_pt_trace_t::kernel_image_dump(kernel_pt_logsubdir)) {
-            NOTIFY(0, "WARNING: failed to run shellscript to dump kernel image\n");
+            NOTIFY(0, "WARNING: failed to dump kernel image\n");
         }
     }
 #endif
diff --git a/suite/tests/CMakeLists.txt b/suite/tests/CMakeLists.txt
index 51a9bfa49da0f10b24e7aca1a0a8ac53667b5e8a..24c8a48e653e8dca7508c1e4d5c703aecd6c3e0f 100644
--- a/suite/tests/CMakeLists.txt
+++ b/suite/tests/CMakeLists.txt
@@ -3837,6 +3837,10 @@ if (BUILD_CLIENTS)
       use_DynamoRIO_drmemtrace_tracer(tool.drcacheoff.gencode)
       target_link_libraries(tool.drcacheoff.gencode drmemtrace_raw2trace
         drmemtrace_analyzer)
+      if (BUILD_PT_POST_PROCESSOR)
+        # Add the directory containing libipt.so and libipt-sb.so to the linker search path.
+        SET(CMAKE_EXE_LINKER_FLAGS  "${CMAKE_EXE_LINKER_FLAGS} -Wl,-rpath='${PROJECT_BINARY_DIR}/lib'")
+      endif (BUILD_PT_POST_PROCESSOR)
       target_include_directories(tool.drcacheoff.gencode PUBLIC
         "${PROJECT_SOURCE_DIR}/clients/drcachesim"
         "${PROJECT_SOURCE_DIR}/clients/drcachesim/common"
@@ -4078,7 +4082,8 @@ if (BUILD_CLIENTS)
   if (proc_supports_pt)
     if (BUILD_PT_TRACER AND BUILD_PT_POST_PROCESSOR)
       get_target_path_for_execution(drpt2trace_path drpt2trace "${location_suffix}")
-      macro (torunonly_drcacheoff_kernel testname exetgt extra_ops app_args)
+      get_target_path_for_execution(drcachesim_path drcachesim "${location_suffix}")
+      macro (torunonly_drcacheoff_kernel testname exetgt extra_ops sim_atops app_args)
         set(testname_full "tool.drcacheoff.kernel.${testname}_SUDO")
         torunonly_ci(${testname_full} ${exetgt} drcachesim
           "offline-kernel-${testname}.c" # for templatex basename
@@ -4094,10 +4099,12 @@ if (BUILD_CLIENTS)
         set(cmd_pfx "sudo@")
         set(${testname_full}_precmd
           "foreach@${cmd_pfx}${CMAKE_COMMAND}@-E@remove_directory@${testname_full}.*.dir")
-        set (${testname_full}_postcmd
+        set(${testname_full}_postcmd
+          "firstglob@${cmd_pfx}${drcachesim_path}@-indir@${testname_full}.*.dir${sim_atops}")
+        set (${testname_full}_postcmd2
           "${cmd_pfx}chmod@777@-R@${testname_full}.*.dir")
-        set(${testname_full}_postcmd2
-          "firstglob@${cmd_pfx}${drpt2trace_path}@-raw_pt@${testname_full}.*.dir/kernel.raw/*.1.pt@-elf@${testname_full}.*.dir/kernel.raw/kcore@-raw_pt_metadata@${testname_full}.*.dir/kernel.raw/*.1.pt.metadata")
+        set(${testname_full}_postcmd3
+          "firstglob@${cmd_pfx}${drpt2trace_path}@-mode@DR@-raw_pt@${testname_full}.*.dir/kernel.raw/*.1.pt@-raw_pt_metadata@${testname_full}.*.dir/kernel.raw/*.1.pt.metadata@-kernel_image@${testname_full}.*.dir/kernel.raw/kimage@-kernel_image_metadata@${testname_full}.*.dir/kernel.raw/kimage.metadata")
       endmacro ()
       torunonly_drcacheoff_kernel(simple ${ci_shared_app} "" "" "")
     endif (BUILD_PT_TRACER AND BUILD_PT_POST_PROCESSOR)
@@ -4198,7 +4205,7 @@ endif (BUILD_CLIENTS)
 # We use the libipt/script/perf-get-opts.bash script to get the options for drpt2trace's tests.
 if (BUILD_CLIENTS)
   if (BUILD_PT_TRACER AND BUILD_PT_POST_PROCESSOR)
-    set(drpt2trace_commong "-print_trace"
+    set(drpt2trace_common_args "-print_trace"
       "-raw_pt"
       "${PROJECT_SOURCE_DIR}/clients/drcachesim/drpt2trace/test_simple.raw/pt.bin"
       "-pt_cpu_family" "6"
@@ -4206,7 +4213,8 @@ if (BUILD_CLIENTS)
       "-pt_cpu_stepping" "4"
       "-pt_mtc_freq" "3" "-pt_nom_freq" "37"
       "-pt_cpuid_0x15_eax" "2" "-pt_cpuid_0x15_ebx" "308")
-    set(drpt2trace_sideband_args ${drpt2trace_commong}
+    set(drpt2trace_sideband_args ${drpt2trace_common_args}
+      "-mode" "SIDEBAND"
       "-primary_sb"
       "${PROJECT_SOURCE_DIR}/clients/drcachesim/drpt2trace/test_simple.raw/primary.sideband.pevent"
       "-secondary_sb"
@@ -4220,7 +4228,8 @@ if (BUILD_CLIENTS)
     torunonly_api(tool.drpt2trace.sideband drpt2trace
       "../../clients/drcachesim/drpt2trace/test_simple.expect"
       "" "${drpt2trace_sideband_args}" ON OFF)
-    set(drpt2trace_elf_args ${drpt2trace_commong}
+    set(drpt2trace_elf_args ${drpt2trace_common_args}
+      "-mode" "ELF"
       "-elf"
       "${PROJECT_SOURCE_DIR}/clients/drcachesim/drpt2trace/test_simple.raw/hello")
     torunonly_api(tool.drpt2trace.elf drpt2trace