summaryrefslogtreecommitdiff
path: root/pinner
diff options
context:
space:
mode:
authorEdgar Arriaga <edgararriaga@google.com>2023-08-21 22:00:48 +0000
committerEdgar Arriaga <edgararriaga@google.com>2023-09-07 21:27:03 +0000
commita66cc9a61ccf58f0c4ccf7238ec8fe0d4eb39bb5 (patch)
tree6bb95f9d31bab27930b2fbceaaa17b0fd3de012d /pinner
parentb7ebe3b72435044111c8e84400c214a0b060e381 (diff)
downloadextras-a66cc9a61ccf58f0c4ccf7238ec8fe0d4eb39bb5.tar.gz
Add pintool and meminspect library
Adding a pintool which can be used to generate pinlist.meta files used by Android PinnerService and adding a meminspect library which provides APIs for inspecting resident memory used by the tool to external systems. Bug: 297095632 Test: pintool probe -p <file_to_pin> Test: pintool_tests Test: meminspect_tests Change-Id: I0bca42dd29294dd977795d9f30cb3d88deba6bee
Diffstat (limited to 'pinner')
-rw-r--r--pinner/Android.bp52
-rw-r--r--pinner/OWNERS2
-rw-r--r--pinner/README.md51
-rw-r--r--pinner/include/meminspect.h50
-rw-r--r--pinner/include/pin_utils.h24
-rw-r--r--pinner/meminspect.cpp90
-rw-r--r--pinner/pin_utils.cpp51
-rw-r--r--pinner/pintool.cpp203
-rw-r--r--pinner/tests/Android.bp84
-rw-r--r--pinner/tests/meminspect_tests.cpp105
-rw-r--r--pinner/tests/pintool_tests.cpp28
11 files changed, 740 insertions, 0 deletions
diff --git a/pinner/Android.bp b/pinner/Android.bp
new file mode 100644
index 00000000..df922e0b
--- /dev/null
+++ b/pinner/Android.bp
@@ -0,0 +1,52 @@
+//
+// Copyright (C) 2017 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library_static {
+ name: "libmeminspect",
+ srcs: [
+ "meminspect.cpp",
+ "pin_utils.cpp"
+ ],
+ shared_libs: [
+ "libbase",
+ ],
+ export_include_dirs: ["include"],
+ cppflags: [
+ "-g",
+ "-Wall",
+ "-Werror",
+ ],
+}
+
+cc_binary {
+ name: "pintool",
+ srcs: ["pintool.cpp"],
+ shared_libs: [
+ "libbase",
+ ],
+ static_libs: [
+ "libmeminspect",
+ ],
+ cppflags: [
+ "-g",
+ "-Wall",
+ "-Werror",
+ ],
+}
diff --git a/pinner/OWNERS b/pinner/OWNERS
new file mode 100644
index 00000000..0636f908
--- /dev/null
+++ b/pinner/OWNERS
@@ -0,0 +1,2 @@
+edgararriaga@google.com
+shayba@google.com \ No newline at end of file
diff --git a/pinner/README.md b/pinner/README.md
new file mode 100644
index 00000000..f7b37f88
--- /dev/null
+++ b/pinner/README.md
@@ -0,0 +1,51 @@
+# Pin Tool
+
+This tool is used for multiple operations related to memory pinning and inspection.
+
+## Build and Install the tool
+
+To build and push the binary to device
+```
+mm pintool
+and push $ANDROID_REPO/out/target/product/<lunchtarget>/system/bin/pintool /data/local/tmp/pintool
+adb shell
+cd /data/local/tmp/pintool
+```
+
+## How to use the tool to generate pinner files
+
+One of the core usages of the tool is to inspect resident memory and
+generate pinlist.meta files which are then consumed by the Android PinnerService
+to pin memory (mlock) and avoid it from being evicted.
+
+A sample usage of this tool in a PGO style fashion can be like this:
+
+
+This is a sample flow of how to use this tool:
+1. Run the app that makes use of the file you want to pin. To have resident
+memory for the file.
+
+2. Run this program to generate the pinlist.meta file.
+```
+./pintool probe -p <file_to_probe> <options>
+```
+This will inspect the memory for the provided <file_to_probe> and generate
+a pinlist.meta file with the resident memory ranges.
+
+Note: Running ./pintool to obtain more usage documentation.
+
+3. Output "pinlist.meta" can be incorporate it within your build
+process to have it bundled inside your apk (inside assets/<file_to_pin.apk>)
+to have the PinnerService know what memory ranges to pin within your apk.
+Note: The PinnerService will need to support pinning your apk, so you may
+need to explicitly request a pin.
+
+## Other potential uses
+
+Outside of pinner service, the tool can be used to inspect resident memory for
+any file in memory.
+
+## Extra information
+
+the pinlist.meta depends on the apk contents and needs to be regenrated if
+you are pushing a new version of your apk. \ No newline at end of file
diff --git a/pinner/include/meminspect.h b/pinner/include/meminspect.h
new file mode 100644
index 00000000..11cca2ba
--- /dev/null
+++ b/pinner/include/meminspect.h
@@ -0,0 +1,50 @@
+#pragma once
+
+#include <android-base/stringprintf.h>
+#include <fcntl.h>
+#include <sys/endian.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <iostream>
+#include <string>
+#include <vector>
+
+#define MEMINSPECT_FAIL_OPEN 1
+#define MEMINSPECT_FAIL_FSTAT 2
+#define MEMINSPECT_FAIL_MINCORE 3
+
+#define DEFAULT_PAGES_PER_MINCORE 1
+
+/**
+ * This class stores an offset defined vma which exists
+ * relative to another memory address.
+ */
+class VmaRange {
+ public:
+ uint32_t offset;
+ uint32_t length;
+
+ VmaRange(uint32_t off, uint32_t len) : offset(off), length(len) {}
+};
+
+struct ResidentMemResult {
+ public:
+ std::vector<VmaRange> resident_memory_ranges;
+ uint64_t file_size_bytes;
+ uint64_t total_resident_bytes;
+};
+
+/**
+ * @brief Probe resident memory for a currently opened file in the system.
+ *
+ * @param probed_file File to probe as defined by its path.
+ * @param out_resident_mem Inspection result. This is populated when called.
+ * @param pages_per_mincore Size of mincore window used, bigger means more memory but slightly
+ * faster.
+ * @return 0 on success or on failure a non-zero error code from the following list:
+ * MEMINSPECT_FAIL_OPEN, MEMINSPECT_FAIL_FSTAT, MEMINSPECT_FAIL_MINCORE
+ */
+int probe_resident_memory(std::string probed_file, ResidentMemResult& out_resident_mem,
+ int pages_per_mincore = DEFAULT_PAGES_PER_MINCORE); \ No newline at end of file
diff --git a/pinner/include/pin_utils.h b/pinner/include/pin_utils.h
new file mode 100644
index 00000000..8db0e8aa
--- /dev/null
+++ b/pinner/include/pin_utils.h
@@ -0,0 +1,24 @@
+#pragma once
+
+#include "meminspect.h"
+
+/**
+ * @brief Generate a pinlist file from a given list of vmas containing a list of 4-byte pairs
+ * representing (4-byte offset, 4-byte len) contiguous in memory and they are stored in big endian
+ * format.
+ *
+ * @param output_file Output file to write pinlist
+ * @param vmas_to_pin Set of vmas to write into pinlist file.
+ * @return 0 on success, non-zero on failure
+ */
+int write_pinlist_file(const std::string& output_file, const std::vector<VmaRange>& vmas_to_pin);
+
+/**
+ * @brief This method is the counter part of @see write_pinlist_file(). It will read an existing
+ * pinlist file.
+ *
+ * @param pinner_file Input pinlist file
+ * @param pinranges Vmas read from pinlist file. This is populated on call.
+ * @return 0 on success, non-zero on failure
+ */
+int read_pinlist_file(const std::string& pinner_file, /*out*/ std::vector<VmaRange>& pinranges); \ No newline at end of file
diff --git a/pinner/meminspect.cpp b/pinner/meminspect.cpp
new file mode 100644
index 00000000..181129ad
--- /dev/null
+++ b/pinner/meminspect.cpp
@@ -0,0 +1,90 @@
+#include "meminspect.h"
+#include <android-base/unique_fd.h>
+
+using namespace std;
+using namespace android::base;
+using namespace ::android::base;
+
+int probe_resident_memory(string probed_file,
+ /*out*/ ResidentMemResult& memresult, int pages_per_mincore) {
+ unique_fd probed_file_ufd(open(probed_file.c_str(), O_RDONLY));
+ int probe_fd = probed_file_ufd.get();
+ if (probe_fd == -1) {
+ return MEMINSPECT_FAIL_OPEN;
+ }
+
+ struct stat fstat_res;
+ int res = fstat(probe_fd, &fstat_res);
+ if (res == -1) {
+ return MEMINSPECT_FAIL_FSTAT;
+ }
+ unsigned long total_bytes = fstat_res.st_size;
+
+ memresult.file_size_bytes = total_bytes;
+
+ char* base_address = (char*)mmap(0, total_bytes, PROT_READ, MAP_SHARED, probe_fd, /*offset*/ 0);
+
+ // this determines how many pages to inspect per mincore syscall
+ unsigned char* window = new unsigned char[pages_per_mincore];
+
+ unsigned int page_size = sysconf(_SC_PAGESIZE);
+ unsigned long bytes_inspected = 0;
+ unsigned long pages_in_memory = 0;
+
+ // total bytes in inspection window
+ unsigned long window_bytes = page_size * pages_per_mincore;
+
+ char* current_window_address;
+ bool started_vma_range = false;
+ uint32_t resident_vma_start_offset = 0;
+ for (current_window_address = base_address; bytes_inspected < total_bytes;
+ current_window_address += window_bytes, bytes_inspected += window_bytes) {
+ int res = mincore(current_window_address, window_bytes, window);
+ if (res != 0) {
+ if (errno == ENOMEM) {
+ // Did not find page, maybe it's a hole.
+ continue;
+ }
+ return MEMINSPECT_FAIL_MINCORE;
+ }
+ // Inspect the provided mincore window result sequentially
+ // and as soon as a change in residency happens a range is
+ // created or finished.
+ for (int iWin = 0; iWin < pages_per_mincore; ++iWin) {
+ if ((window[iWin] & 1) > 0) {
+ // Page is resident
+ ++pages_in_memory;
+ if (!started_vma_range) {
+ // End of range
+ started_vma_range = true;
+ uint32_t window_offset = iWin * page_size;
+ resident_vma_start_offset =
+ current_window_address + window_offset - base_address;
+ }
+ } else {
+ // Page is not resident
+ if (started_vma_range) {
+ // Start of range
+ started_vma_range = false;
+ uint32_t window_offset = iWin * page_size;
+ uint32_t resident_vma_end_offset =
+ current_window_address + window_offset - base_address;
+ uint32_t resident_len = resident_vma_end_offset - resident_vma_start_offset;
+ VmaRange vma_range(resident_vma_start_offset, resident_len);
+ memresult.resident_memory_ranges.push_back(vma_range);
+ }
+ }
+ }
+ }
+ // This was the last window, so close any opened vma range
+ if (started_vma_range) {
+ started_vma_range = false;
+ uint32_t in_memory_vma_end = current_window_address - base_address;
+ uint32_t resident_len = in_memory_vma_end - resident_vma_start_offset;
+ VmaRange vma_range(resident_vma_start_offset, resident_len);
+ memresult.resident_memory_ranges.push_back(vma_range);
+ }
+
+ memresult.total_resident_bytes = pages_in_memory * page_size;
+ return 0;
+} \ No newline at end of file
diff --git a/pinner/pin_utils.cpp b/pinner/pin_utils.cpp
new file mode 100644
index 00000000..4469d781
--- /dev/null
+++ b/pinner/pin_utils.cpp
@@ -0,0 +1,51 @@
+#include "pin_utils.h"
+
+using namespace std;
+using namespace android::base;
+
+int write_pinlist_file(const std::string& output_file, const std::vector<VmaRange>& vmas_to_pin) {
+ int pinlist_fd = open(output_file.c_str(), O_RDWR | O_CREAT, "w");
+ if (pinlist_fd == -1) {
+ return 1;
+ }
+ for (int i = 0; i < vmas_to_pin.size(); ++i) {
+ uint32_t vma_start_offset = vmas_to_pin[i].offset;
+ uint32_t vma_length = vmas_to_pin[i].length;
+ // Transform to BigEndian as PinnerService requires that endianness for reading.
+ uint32_t vma_start_offset_be = htobe32(vma_start_offset);
+ uint32_t vma_length_be = htobe32(vma_length);
+ int write_res = write(pinlist_fd, &vma_start_offset_be, sizeof(vma_start_offset_be));
+ if (write_res == -1) {
+ return 1;
+ }
+ write_res = write(pinlist_fd, &vma_length_be, sizeof(vma_length_be));
+ if (write_res == -1) {
+ return 1;
+ }
+ }
+ close(pinlist_fd);
+ return 0;
+}
+
+int read_pinlist_file(const std::string& pinner_file, /*out*/ std::vector<VmaRange>& pinranges) {
+ int pinlist_fd = open(pinner_file.c_str(), O_RDWR | O_CREAT, "w");
+ if (pinlist_fd == -1) {
+ return 1;
+ }
+
+ uint32_t vma_start;
+ uint32_t vma_length;
+ while (read(pinlist_fd, &vma_start, sizeof(vma_start)) > 0) {
+ int read_res = read(pinlist_fd, &vma_length, sizeof(vma_length));
+ if (read_res == -1) {
+ return 1;
+ }
+ vma_start = betoh32(vma_start);
+ vma_length = betoh32(vma_length);
+
+ pinranges.push_back(VmaRange(vma_start, vma_length));
+ }
+
+ close(pinlist_fd);
+ return 0;
+} \ No newline at end of file
diff --git a/pinner/pintool.cpp b/pinner/pintool.cpp
new file mode 100644
index 00000000..a25106b9
--- /dev/null
+++ b/pinner/pintool.cpp
@@ -0,0 +1,203 @@
+#include <android-base/stringprintf.h>
+#include <fcntl.h>
+#include <sys/endian.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <iostream>
+#include <string>
+#include <vector>
+
+#include <meminspect.h>
+#include <pin_utils.h>
+
+using namespace std;
+using namespace android::base;
+
+enum ToolMode { PROBE, DUMP, UNKNOWN };
+
+void print_pinner_ranges(const std::vector<VmaRange>& ranges) {
+ cout << "vmas to pin:" << endl;
+ for (int i = 0; i < ranges.size(); ++i) {
+ cout << StringPrintf("start=%u length=%u", ranges[i].offset, ranges[i].length) << endl;
+ }
+}
+
+int perform_probe(const vector<string>& options) {
+ std::string probed_file;
+ std::string output_file = "/data/local/tmp/pinlist.meta";
+ bool verbose = false;
+ int pages_per_mincore = DEFAULT_PAGES_PER_MINCORE;
+
+ // Parse Args
+ for (int i = 0; i < options.size(); ++i) {
+ string option = options[i];
+ if (option == "-p") {
+ ++i;
+ probed_file = options[i];
+ continue;
+ }
+ if (option == "-o") {
+ ++i;
+ output_file = options[i];
+ continue;
+ }
+ if (option == "-v") {
+ verbose = true;
+ continue;
+ }
+ if (option == "-w") {
+ ++i;
+ pages_per_mincore = stoi(options[i]);
+ continue;
+ }
+ }
+
+ if (verbose) {
+ cout << "mincore window size=" << pages_per_mincore << endl;
+ cout << "Setting output pinlist file as " << probed_file.c_str() << endl;
+ cout << "Setting file to probe: " << probed_file.c_str() << endl;
+ }
+
+ if (probed_file.empty()) {
+ cerr << "Error: Should specify a file to probe.";
+ return 1;
+ }
+
+ ResidentMemResult memresult;
+ int res = probe_resident_memory(probed_file, memresult, pages_per_mincore);
+ if (res) {
+ if (res == MEMINSPECT_FAIL_OPEN) {
+ cerr << "Failed to open file: " << probed_file << endl;
+ }
+
+ if (res == MEMINSPECT_FAIL_FSTAT) {
+ cerr << "Failed to fstat file: " << probed_file << endl;
+ }
+
+ if (res == MEMINSPECT_FAIL_MINCORE) {
+ cerr << "Mincore failed for file: " << probed_file << endl;
+ }
+
+ return res;
+ }
+
+ cout << StringPrintf(
+ "Finished Probing. resident memory(KB)=%lu. file_size (KB)=%lu. "
+ "pin_percentage=%f",
+ memresult.total_resident_bytes / 1024, memresult.file_size_bytes / 1024,
+ memresult.total_resident_bytes / (float)memresult.file_size_bytes * 100)
+ << endl;
+
+ res = write_pinlist_file(output_file, memresult.resident_memory_ranges);
+ if (res > 0) {
+ cerr << "Failed to write pin file at: " << output_file << endl;
+ } else {
+ if (verbose) {
+ cout << "Finished writing pin file at: " << output_file << endl;
+ }
+ }
+ return res;
+}
+
+int perform_dump(const vector<string>& options) {
+ string pinner_file;
+ bool verbose = false;
+ for (int i = 0; i < options.size(); ++i) {
+ string option = options[i];
+ if (option == "-p") {
+ ++i;
+ pinner_file = options[i];
+ continue;
+ }
+
+ if (option == "-v") {
+ verbose = true;
+ }
+ }
+ if (pinner_file.empty()) {
+ cerr << "Error: Pinlist file to dump is missing. Specify it with '-p <file>'" << endl;
+ return 1;
+ }
+ if (verbose) {
+ cout << "Setting file to dump: " << pinner_file.c_str() << endl;
+ }
+ vector<VmaRange> vma_ranges;
+ if (read_pinlist_file(pinner_file, vma_ranges) == 1) {
+ cerr << "Failed reading pinlist file" << endl;
+ }
+ print_pinner_ranges(vma_ranges);
+
+ return 0;
+}
+
+int main(int argc, char** argv) {
+ if (argc == 1) {
+ const string usage = R"(
+Expected usage: pintool <mode> <required> [option]
+where:
+<file_to_pin> is a file currently mapped by another process and in memory.
+<mode> :
+ probe
+ This mode will probe resident memory for a file and generate a pinlist.meta file
+ that can be interpreted by the PinnerService.
+
+ <required>
+ -p <file_to_probe>
+ This option will probe the specified file
+ [option]:
+ -o <file>
+ Specify the output file for the pinlist file.
+ (default=/data/local/tmp/pinlist.meta)
+ -v
+ Enable verbose output.
+ -w
+ Mincore total pages per mincore window. Bigger windows
+ will use more memory but may be slightly faster. (default=1)
+ dump
+ <required>
+ -p <input_pinlist_file>
+ Specify the input pinlist file to dump
+)";
+
+ cout << usage.c_str();
+ return 0;
+ }
+
+ if (argc < 2) {
+ cerr << "<mode> is missing";
+ return 1;
+ }
+ ToolMode mode = UNKNOWN;
+ if (strcmp(argv[1], "probe") == 0) {
+ mode = PROBE;
+ } else if (strcmp(argv[1], "dump") == 0) {
+ mode = DUMP;
+ }
+
+ if (mode == UNKNOWN) {
+ cerr << "Failed to find mode: " << argv[1] << ". See usage for available modes." << endl;
+ return 1;
+ }
+
+ vector<string> options;
+ for (int i = 2; i < argc; ++i) {
+ options.push_back(argv[i]);
+ }
+
+ int res;
+ switch (mode) {
+ case PROBE:
+ res = perform_probe(options);
+ break;
+ case DUMP:
+ res = perform_dump(options);
+ break;
+ case UNKNOWN:
+ return 1;
+ break;
+ }
+
+ return res;
+}
diff --git a/pinner/tests/Android.bp b/pinner/tests/Android.bp
new file mode 100644
index 00000000..8c3f262a
--- /dev/null
+++ b/pinner/tests/Android.bp
@@ -0,0 +1,84 @@
+// Copyright (C) 2018 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_test {
+ name: "meminspect_tests",
+ shared_libs: [
+ "libbase",
+ ],
+
+ cppflags: [
+ "-g",
+ "-Wall",
+ "-Werror",
+ "-Wno-missing-field-initializers",
+ ],
+
+ static_libs: [
+ "libmeminspect",
+ ],
+
+ target: {
+ android: {
+ srcs: ["meminspect_tests.cpp"],
+ },
+ },
+
+ cflags: [
+ "-Wall",
+ "-Wextra",
+ "-Werror",
+ "-O0",
+ ],
+
+ compile_multilib: "first",
+}
+
+cc_test {
+ name: "pintool_tests",
+
+ shared_libs: [
+ "libbase",
+ ],
+
+ cppflags: [
+ "-g",
+ "-Wall",
+ "-Werror",
+ "-Wno-missing-field-initializers",
+ ],
+
+ static_libs: [
+ "libmeminspect",
+ ],
+
+ target: {
+ android: {
+ srcs: ["pintool_tests.cpp"],
+ },
+ },
+
+ cflags: [
+ "-Wall",
+ "-Wextra",
+ "-Werror",
+ "-O0",
+ ],
+
+ compile_multilib: "first",
+}
diff --git a/pinner/tests/meminspect_tests.cpp b/pinner/tests/meminspect_tests.cpp
new file mode 100644
index 00000000..da3826db
--- /dev/null
+++ b/pinner/tests/meminspect_tests.cpp
@@ -0,0 +1,105 @@
+#include <gtest/gtest.h>
+#include <stdio.h>
+
+#include <meminspect.h>
+
+using namespace std;
+
+/**
+ * This test is meant to be ran by directly pushing the test binary
+ * into the device as using atest will not provide sufficient privileges
+ * to execute drop_caches command.
+ */
+TEST(meminspect_test, inspect_matches_resident) {
+ // Create test file
+ string test_file = "/data/local/tmp/meminspect_test";
+ // If for any reason a test file already existed from a previous test, remove it.
+ remove(test_file.c_str());
+
+ int test_file_fd = open(test_file.c_str(), O_RDWR | O_CREAT, "w");
+ unsigned int page_size = sysconf(_SC_PAGESIZE);
+ if (test_file_fd == -1) {
+ ADD_FAILURE() << "Failed to open test file for writing. errno: " << std::strerror(errno);
+ close(test_file_fd);
+ remove(test_file.c_str());
+ return;
+ }
+
+ uint8_t* page_data = new uint8_t[page_size];
+ for (unsigned int i = 0; i < page_size; ++i) {
+ page_data[i] = i + 1;
+ }
+ int pages_to_write = 100;
+ for (int page = 0; page < pages_to_write; ++page) {
+ write(test_file_fd, page_data, page_size);
+ }
+ // fsync to ensure the data is flushed to disk.
+ if (fsync(test_file_fd) == -1) {
+ ADD_FAILURE() << "fsync failed errno: " << std::strerror(errno);
+ close(test_file_fd);
+ remove(test_file.c_str());
+ return;
+ }
+ close(test_file_fd);
+
+ // Drop the pagecache to ensure we do not have memory due to it staying there after write.
+ int drop_cache_fd = open("/proc/sys/vm/drop_caches", O_WRONLY);
+ if (drop_cache_fd == -1) {
+ ADD_FAILURE() << "failed opening drop caches fd errno: " << std::strerror(errno);
+ close(test_file_fd);
+ remove(test_file.c_str());
+ return;
+ }
+ write(drop_cache_fd, "3", 1);
+ fsync(drop_cache_fd);
+ close(drop_cache_fd);
+
+ // Open file and page in some memory
+ test_file_fd = open(test_file.c_str(), O_RDONLY, "r");
+ if (test_file_fd == -1) {
+ ADD_FAILURE() << "Failed to open test file for reading after creation. errno: "
+ << std::strerror(errno);
+ close(test_file_fd);
+ remove(test_file.c_str());
+ return;
+ }
+
+ char* base_address = (char*)mmap(0, page_size * pages_to_write, PROT_READ, MAP_SHARED,
+ test_file_fd, /*offset*/ 0);
+ if (base_address == (char*)-1) {
+ ADD_FAILURE() << "Failed to mmap file for reading after creation. errno: "
+ << std::strerror(errno);
+ close(test_file_fd);
+ remove(test_file.c_str());
+ return;
+ }
+
+ ResidentMemResult vmas_resident;
+ int res = probe_resident_memory(test_file, vmas_resident, 1);
+ EXPECT_TRUE(res == 0);
+
+ // Probing the file without reading anything yields no resident memory
+ EXPECT_TRUE(vmas_resident.resident_memory_ranges.empty());
+
+ // Clear our to start fresh for next probe.
+ vmas_resident = ResidentMemResult();
+
+ int pages_to_read = 1;
+ char* read_data = new char[pages_to_read];
+ for (int page = 0; page < pages_to_read; ++page) {
+ // Read 1 byte from each page to page it in.
+ read_data[page] = base_address[page * page_size];
+ }
+ res = probe_resident_memory(test_file, vmas_resident, 1);
+ EXPECT_TRUE(res == 0);
+
+ // The amount of memory paged in is outside our control, but we should have some.
+ EXPECT_TRUE(vmas_resident.total_resident_bytes > 0);
+ EXPECT_TRUE(vmas_resident.resident_memory_ranges.size() == 1);
+ EXPECT_TRUE(vmas_resident.resident_memory_ranges[0].offset == 0);
+ EXPECT_TRUE((uint64_t)vmas_resident.resident_memory_ranges[0].length ==
+ vmas_resident.total_resident_bytes);
+
+ close(test_file_fd);
+ remove(test_file.c_str());
+} \ No newline at end of file
diff --git a/pinner/tests/pintool_tests.cpp b/pinner/tests/pintool_tests.cpp
new file mode 100644
index 00000000..b76c5284
--- /dev/null
+++ b/pinner/tests/pintool_tests.cpp
@@ -0,0 +1,28 @@
+#include <gtest/gtest.h>
+#include <stdio.h>
+
+#include <pin_utils.h>
+
+using namespace std;
+
+TEST(pintool_test, pinlist_matches_memranges) {
+ vector<VmaRange> vma_ranges;
+ vma_ranges.push_back(VmaRange(0, 500));
+ vma_ranges.push_back(VmaRange(1000, 5500));
+ vma_ranges.push_back(VmaRange(10000, 13000));
+ vma_ranges.push_back(VmaRange(26000, 35000));
+
+ string test_file = "/data/local/tmp/pintool_test";
+ write_pinlist_file(test_file, vma_ranges);
+
+ vector<VmaRange> read_ranges;
+ read_pinlist_file(test_file, read_ranges);
+
+ EXPECT_EQ(vma_ranges.size(), read_ranges.size());
+ for (size_t i = 0; i < vma_ranges.size(); ++i) {
+ EXPECT_EQ(vma_ranges[i].offset, read_ranges[i].offset);
+ EXPECT_EQ(vma_ranges[i].length, read_ranges[i].length);
+ }
+
+ remove(test_file.c_str());
+} \ No newline at end of file