Update local symbolizer to support Windows.
Bug: 152017262
Change-Id: I0db88410f5a72c20f95f91913573dd2665294824
diff --git a/Android.bp b/Android.bp
index 7af5002..f13c5c6 100644
--- a/Android.bp
+++ b/Android.bp
@@ -6614,7 +6614,10 @@
filegroup {
name: "perfetto_src_profiling_symbolizer_symbolizer",
srcs: [
+ "src/profiling/symbolizer/filesystem_posix.cc",
"src/profiling/symbolizer/local_symbolizer.cc",
+ "src/profiling/symbolizer/scoped_read_mmap_posix.cc",
+ "src/profiling/symbolizer/subprocess_posix.cc",
"src/profiling/symbolizer/symbolizer.cc",
],
}
diff --git a/BUILD b/BUILD
index b84be89..cd7447d 100644
--- a/BUILD
+++ b/BUILD
@@ -663,8 +663,14 @@
filegroup(
name = "src_profiling_symbolizer_symbolizer",
srcs = [
+ "src/profiling/symbolizer/filesystem.h",
+ "src/profiling/symbolizer/filesystem_posix.cc",
"src/profiling/symbolizer/local_symbolizer.cc",
"src/profiling/symbolizer/local_symbolizer.h",
+ "src/profiling/symbolizer/scoped_read_mmap.h",
+ "src/profiling/symbolizer/scoped_read_mmap_posix.cc",
+ "src/profiling/symbolizer/subprocess.h",
+ "src/profiling/symbolizer/subprocess_posix.cc",
"src/profiling/symbolizer/symbolizer.cc",
"src/profiling/symbolizer/symbolizer.h",
],
diff --git a/gn/BUILD.gn b/gn/BUILD.gn
index d782e9f..8093071 100644
--- a/gn/BUILD.gn
+++ b/gn/BUILD.gn
@@ -55,7 +55,8 @@
if (enable_perfetto_tools) {
perfetto_local_symbolizer =
"PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_LINUX() || " +
- "PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_MAC()"
+ "PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_MAC() ||" +
+ "PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_WIN()"
} else {
perfetto_local_symbolizer = "0"
}
diff --git a/include/perfetto/base/build_configs/android_tree/perfetto_build_flags.h b/include/perfetto/base/build_configs/android_tree/perfetto_build_flags.h
index a150204..80d1ec5 100644
--- a/include/perfetto/base/build_configs/android_tree/perfetto_build_flags.h
+++ b/include/perfetto/base/build_configs/android_tree/perfetto_build_flags.h
@@ -36,7 +36,7 @@
#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_TP_LINENOISE() (0)
#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_TP_HTTPD() (0)
#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_TP_JSON() (0)
-#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_LOCAL_SYMBOLIZER() (PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_LINUX() || PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_MAC())
+#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_LOCAL_SYMBOLIZER() (PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_LINUX() || PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_MAC() ||PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_WIN())
#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_ZLIB() (1)
// clang-format on
diff --git a/include/perfetto/base/build_configs/bazel/perfetto_build_flags.h b/include/perfetto/base/build_configs/bazel/perfetto_build_flags.h
index e4391e6..fc8f06a 100644
--- a/include/perfetto/base/build_configs/bazel/perfetto_build_flags.h
+++ b/include/perfetto/base/build_configs/bazel/perfetto_build_flags.h
@@ -36,7 +36,7 @@
#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_TP_LINENOISE() (1)
#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_TP_HTTPD() (PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_ANDROID() || PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_LINUX() || PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_MAC())
#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_TP_JSON() (1)
-#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_LOCAL_SYMBOLIZER() (PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_LINUX() || PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_MAC())
+#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_LOCAL_SYMBOLIZER() (PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_LINUX() || PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_MAC() ||PERFETTO_BUILDFLAG_DEFINE_PERFETTO_OS_WIN())
#define PERFETTO_BUILDFLAG_DEFINE_PERFETTO_ZLIB() (1)
// clang-format on
diff --git a/src/profiling/symbolizer/BUILD.gn b/src/profiling/symbolizer/BUILD.gn
index 79c770e..2129cb3 100644
--- a/src/profiling/symbolizer/BUILD.gn
+++ b/src/profiling/symbolizer/BUILD.gn
@@ -24,6 +24,26 @@
"symbolizer.cc",
"symbolizer.h",
]
+ if (is_linux || is_mac || is_android) {
+ sources += [
+ "filesystem.h",
+ "filesystem_posix.cc",
+ "scoped_read_mmap.h",
+ "scoped_read_mmap_posix.cc",
+ "subprocess.h",
+ "subprocess_posix.cc",
+ ]
+ }
+ if (is_win) {
+ sources += [
+ "filesystem.h",
+ "filesystem_windows.cc",
+ "scoped_read_mmap.h",
+ "scoped_read_mmap_windows.cc",
+ "subprocess.h",
+ "subprocess_windows.cc",
+ ]
+ }
}
source_set("symbolize_database") {
@@ -50,6 +70,7 @@
":symbolizer",
"../../../gn:default_deps",
"../../../gn:gtest_and_gmock",
+ "../../base:test_support",
]
sources = [ "local_symbolizer_unittest.cc" ]
}
diff --git a/src/profiling/symbolizer/filesystem.h b/src/profiling/symbolizer/filesystem.h
new file mode 100644
index 0000000..d9cf87c
--- /dev/null
+++ b/src/profiling/symbolizer/filesystem.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_PROFILING_SYMBOLIZER_FILESYSTEM_H_
+#define SRC_PROFILING_SYMBOLIZER_FILESYSTEM_H_
+
+#include "src/profiling/symbolizer/local_symbolizer.h"
+
+namespace perfetto {
+namespace profiling {
+
+using FileCallback = std::function<void(const char*, size_t)>;
+size_t GetFileSize(const std::string& file_path);
+bool WalkDirectories(std::vector<std::string> dirs, FileCallback fn);
+
+} // namespace profiling
+} // namespace perfetto
+
+#endif // SRC_PROFILING_SYMBOLIZER_FILESYSTEM_H_
diff --git a/src/profiling/symbolizer/filesystem_posix.cc b/src/profiling/symbolizer/filesystem_posix.cc
new file mode 100644
index 0000000..d8c6e12
--- /dev/null
+++ b/src/profiling/symbolizer/filesystem_posix.cc
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/profiling/symbolizer/filesystem.h"
+
+#if PERFETTO_BUILDFLAG(PERFETTO_LOCAL_SYMBOLIZER)
+#include <fts.h>
+#include <sys/stat.h>
+#endif
+namespace perfetto {
+namespace profiling {
+#if PERFETTO_BUILDFLAG(PERFETTO_LOCAL_SYMBOLIZER)
+bool WalkDirectories(std::vector<std::string> dirs, FileCallback fn) {
+ std::vector<char*> dir_cstrs;
+ for (std::string& dir : dirs)
+ dir_cstrs.emplace_back(&dir[0]);
+ dir_cstrs.push_back(nullptr);
+ base::ScopedResource<FTS*, fts_close, nullptr> fts(
+ fts_open(&dir_cstrs[0], FTS_LOGICAL | FTS_NOCHDIR, nullptr));
+ if (!fts) {
+ PERFETTO_PLOG("fts_open");
+ return false;
+ }
+ FTSENT* ent;
+ while ((ent = fts_read(*fts))) {
+ if (ent->fts_info & FTS_F)
+ fn(ent->fts_path, static_cast<size_t>(ent->fts_statp->st_size));
+ }
+ return true;
+}
+
+size_t GetFileSize(const std::string& file_path) {
+ base::ScopedFile fd(base::OpenFile(file_path, O_RDONLY | O_CLOEXEC));
+ if (!fd) {
+ PERFETTO_PLOG("Failed to get file size %s", file_path.c_str());
+ return 0;
+ }
+ struct stat buf;
+ if (fstat(*fd, &buf) == -1) {
+ return 0;
+ }
+ return static_cast<size_t>(buf.st_size);
+}
+#else
+bool WalkDirectories(std::vector<std::string>, FileCallback) {
+ return false;
+}
+size_t GetFileSize(const std::string&) {
+ return 0;
+}
+#endif
+
+} // namespace profiling
+} // namespace perfetto
diff --git a/src/profiling/symbolizer/filesystem_windows.cc b/src/profiling/symbolizer/filesystem_windows.cc
new file mode 100644
index 0000000..3da70e7
--- /dev/null
+++ b/src/profiling/symbolizer/filesystem_windows.cc
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/profiling/symbolizer/filesystem.h"
+
+#define WIN32_MEAN_AND_LEAN
+#include <Windows.h>
+
+namespace perfetto {
+namespace profiling {
+
+bool WalkDirectories(std::vector<std::string> dirs, FileCallback fn) {
+ std::vector<std::string> sub_dirs;
+ for (const std::string& dir : dirs) {
+ WIN32_FIND_DATA file;
+ HANDLE fh = FindFirstFile((dir + "\\*").c_str(), &file);
+ if (fh != INVALID_HANDLE_VALUE) {
+ do {
+ std::string file_path = dir + "\\" + file.cFileName;
+ if (file.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
+ if (strcmp(file.cFileName, ".") != 0 &&
+ strcmp(file.cFileName, "..") != 0) {
+ sub_dirs.push_back(file_path);
+ }
+ } else {
+ ULARGE_INTEGER size;
+ size.HighPart = file.nFileSizeHigh;
+ size.LowPart = file.nFileSizeLow;
+ fn(file_path.c_str(), size.QuadPart);
+ }
+ } while (FindNextFile(fh, &file));
+ }
+ CloseHandle(fh);
+ }
+ if (!sub_dirs.empty()) {
+ WalkDirectories(sub_dirs, fn);
+ }
+ return true;
+}
+
+size_t GetFileSize(const std::string& file_path) {
+ HANDLE file = CreateFileA(file_path.c_str(), GENERIC_READ, FILE_SHARE_READ,
+ NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+ if (file == INVALID_HANDLE_VALUE) {
+ PERFETTO_PLOG("Failed to get file size %s", file_path.c_str());
+ return 0;
+ }
+ LARGE_INTEGER file_size;
+ file_size.QuadPart = 0;
+ if (!GetFileSizeEx(file, &file_size)) {
+ PERFETTO_PLOG("Failed to get file size %s", file_path.c_str());
+ }
+ CloseHandle(file);
+ return file_size.QuadPart;
+}
+
+} // namespace profiling
+} // namespace perfetto
\ No newline at end of file
diff --git a/src/profiling/symbolizer/local_symbolizer.cc b/src/profiling/symbolizer/local_symbolizer.cc
index f930447..01639bc 100644
--- a/src/profiling/symbolizer/local_symbolizer.cc
+++ b/src/profiling/symbolizer/local_symbolizer.cc
@@ -20,6 +20,7 @@
#include <fcntl.h>
#include <memory>
+#include <sstream>
#include <string>
#include <vector>
@@ -28,6 +29,8 @@
#include "perfetto/base/logging.h"
#include "perfetto/ext/base/optional.h"
#include "perfetto/ext/base/scoped_file.h"
+#include "src/profiling/symbolizer/filesystem.h"
+#include "src/profiling/symbolizer/scoped_read_mmap.h"
namespace perfetto {
namespace profiling {
@@ -61,46 +64,55 @@
// Most of this translation unit is built only on Linux and MacOS. See
// //gn/BUILD.gn.
#if PERFETTO_BUILDFLAG(PERFETTO_LOCAL_SYMBOLIZER)
-
#include "perfetto/ext/base/string_splitter.h"
#include "perfetto/ext/base/string_utils.h"
#include "perfetto/ext/base/utils.h"
-#include <fts.h>
#include <inttypes.h>
#include <signal.h>
-#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
-#include <sys/wait.h>
-#include <unistd.h>
+
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+#define F_OK 0
+#endif
namespace perfetto {
namespace profiling {
-namespace {
-
-std::vector<std::string> GetLines(FILE* f) {
+std::vector<std::string> GetLines(
+ std::function<int64_t(char*, size_t)> fn_read) {
std::vector<std::string> lines;
- size_t n = 0;
- char* line = nullptr;
- ssize_t rd = 0;
- do {
- rd = getline(&line, &n, f);
- // Do not read empty line that terminates the output.
- if (rd > 1) {
- // Remove newline character.
- PERFETTO_DCHECK(line[rd - 1] == '\n');
- line[rd - 1] = '\0';
- lines.emplace_back(line);
+ char buffer[512];
+ int64_t rd = 0;
+ // Cache the partial line of the previous read.
+ std::string last_line;
+ while ((rd = fn_read(buffer, sizeof(buffer))) > 0) {
+ std::string data(buffer, static_cast<size_t>(rd));
+ // Create stream buffer of last partial line + new data
+ std::stringstream stream(last_line + data);
+ std::string line;
+ last_line = "";
+ while (std::getline(stream, line)) {
+ // Return from reading when we read an empty line.
+ if (line.empty()) {
+ return lines;
+ } else if (stream.eof()) {
+ // Cache off the partial line when we hit end of stream.
+ last_line += line;
+ break;
+ } else {
+ lines.push_back(line);
+ }
}
- free(line);
- line = nullptr;
- n = 0;
- } while (rd > 1);
+ }
+ if (rd == -1) {
+ PERFETTO_ELOG("Failed to read data from subprocess.");
+ }
return lines;
}
+namespace {
// We cannot just include elf.h, as that only exists on Linux, and we want to
// allow symbolization on other platforms as well. As we only need a small
// subset, it is easiest to define the constants and structs ourselves.
@@ -315,27 +327,6 @@
return base::nullopt;
}
-class ScopedMmap {
- public:
- ScopedMmap(void* addr,
- size_t length,
- int prot,
- int flags,
- int fd,
- off_t offset)
- : length_(length), ptr_(mmap(addr, length, prot, flags, fd, offset)) {}
- ~ScopedMmap() {
- if (ptr_ != MAP_FAILED)
- munmap(ptr_, length_);
- }
-
- void* operator*() { return ptr_; }
-
- private:
- size_t length_;
- void* ptr_;
-};
-
std::string SplitBuildID(const std::string& hex_build_id) {
if (hex_build_id.size() < 3) {
PERFETTO_DFATAL_OR_ELOG("Invalid build-id (< 3 char) %s",
@@ -358,17 +349,13 @@
uint64_t load_bias;
};
-base::Optional<BuildIdAndLoadBias> GetBuildIdAndLoadBias(
- int fd,
- const struct stat* statbuf) {
- size_t size = static_cast<size_t>(statbuf->st_size);
-
+base::Optional<BuildIdAndLoadBias> GetBuildIdAndLoadBias(const char* fname,
+ size_t size) {
static_assert(EI_CLASS > EI_MAG3, "mem[EI_MAG?] accesses are in range.");
if (size <= EI_CLASS)
return base::nullopt;
-
- ScopedMmap map(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0);
- if (*map == MAP_FAILED) {
+ ScopedReadMmap map(fname, size);
+ if (!map.IsValid()) {
PERFETTO_PLOG("mmap");
return base::nullopt;
}
@@ -397,57 +384,36 @@
return base::nullopt;
}
-template <typename F>
-bool WalkDirectories(std::vector<std::string> dirs, F fn) {
- std::vector<char*> dir_cstrs;
- for (std::string& dir : dirs)
- dir_cstrs.emplace_back(&dir[0]);
- dir_cstrs.push_back(nullptr);
- base::ScopedResource<FTS*, fts_close, nullptr> fts(
- fts_open(&dir_cstrs[0], FTS_LOGICAL | FTS_NOCHDIR, nullptr));
- if (!fts) {
- PERFETTO_PLOG("fts_open");
- return false;
- }
- FTSENT* ent;
- while ((ent = fts_read(*fts))) {
- if (ent->fts_info & FTS_F)
- fn(ent->fts_path, ent->fts_statp);
- }
- return true;
-}
-
std::map<std::string, FoundBinary> BuildIdIndex(std::vector<std::string> dirs) {
std::map<std::string, FoundBinary> result;
- WalkDirectories(
- std::move(dirs), [&result](const char* fname, const struct stat* stat) {
- char magic[EI_MAG3 + 1];
- auto fd = base::OpenFile(fname, O_RDONLY | O_CLOEXEC);
- if (!fd) {
- PERFETTO_PLOG("Failed to open %s", fname);
- return;
- }
- ssize_t rd = PERFETTO_EINTR(read(*fd, &magic, sizeof(magic)));
- if (rd == -1) {
- PERFETTO_PLOG("Failed to read %s", fname);
- return;
- }
- if (!IsElf(magic, static_cast<size_t>(rd))) {
- PERFETTO_DLOG("%s not an ELF.", fname);
- return;
- }
- if (lseek(*fd, 0, SEEK_SET) == -1) {
- PERFETTO_PLOG("Failed to seek %s", fname);
- return;
- }
- base::Optional<BuildIdAndLoadBias> build_id_and_load_bias =
- GetBuildIdAndLoadBias(*fd, stat);
-
- if (build_id_and_load_bias) {
- result.emplace(build_id_and_load_bias->build_id,
- FoundBinary{fname, build_id_and_load_bias->load_bias});
- }
- });
+ WalkDirectories(std::move(dirs), [&result](const char* fname, size_t size) {
+ char magic[EI_MAG3 + 1];
+ // Scope file access. On windows OpenFile opens an exclusive lock.
+ // This lock needs to be released before mapping the file.
+ {
+ base::ScopedFile fd(base::OpenFile(fname, O_RDONLY));
+ if (!fd) {
+ PERFETTO_PLOG("Failed to open %s", fname);
+ return;
+ }
+ ssize_t rd = static_cast<ssize_t>(
+ PERFETTO_EINTR(read(*fd, &magic, sizeof(magic))));
+ if (rd != sizeof(magic)) {
+ PERFETTO_PLOG("Failed to read %s", fname);
+ return;
+ }
+ if (!IsElf(magic, static_cast<size_t>(rd))) {
+ PERFETTO_DLOG("%s not an ELF.", fname);
+ return;
+ }
+ }
+ base::Optional<BuildIdAndLoadBias> build_id_and_load_bias =
+ GetBuildIdAndLoadBias(fname, size);
+ if (build_id_and_load_bias) {
+ result.emplace(build_id_and_load_bias->build_id,
+ FoundBinary{fname, build_id_and_load_bias->load_bias});
+ }
+ });
return result;
}
@@ -518,15 +484,15 @@
if (access(symbol_file.c_str(), F_OK) != 0) {
return base::nullopt;
}
- base::ScopedFile fd(base::OpenFile(symbol_file, O_RDONLY));
- if (!fd)
+ // Openfile opens the file with an exclusive lock on windows.
+ size_t size = GetFileSize(symbol_file);
+
+ if (size == 0) {
return base::nullopt;
- struct stat statbuf;
- if (fstat(*fd, &statbuf) == -1)
- return base::nullopt;
+ }
base::Optional<BuildIdAndLoadBias> build_id_and_load_bias =
- GetBuildIdAndLoadBias(*fd, &statbuf);
+ GetBuildIdAndLoadBias(symbol_file.c_str(), size);
if (!build_id_and_load_bias)
return base::nullopt;
if (build_id_and_load_bias->build_id != build_id) {
@@ -618,50 +584,29 @@
LocalBinaryFinder::~LocalBinaryFinder() = default;
-Subprocess::Subprocess(const std::string& file, std::vector<std::string> args)
- : input_pipe_(base::Pipe::Create(base::Pipe::kBothBlock)),
- output_pipe_(base::Pipe::Create(base::Pipe::kBothBlock)) {
- std::vector<char*> c_str_args(args.size() + 1, nullptr);
- for (std::string& arg : args)
- c_str_args.push_back(&(arg[0]));
-
- if ((pid_ = fork()) == 0) {
- // Child
- PERFETTO_CHECK(dup2(*input_pipe_.rd, STDIN_FILENO) != -1);
- PERFETTO_CHECK(dup2(*output_pipe_.wr, STDOUT_FILENO) != -1);
- input_pipe_.wr.reset();
- output_pipe_.rd.reset();
- if (execvp(file.c_str(), &(c_str_args[0])) == -1)
- PERFETTO_FATAL("Failed to exec %s", file.c_str());
- }
- PERFETTO_CHECK(pid_ != -1);
- input_pipe_.rd.reset();
- output_pipe_.wr.reset();
-}
-
-Subprocess::~Subprocess() {
- if (pid_ != -1) {
- kill(pid_, SIGKILL);
- int wstatus;
- PERFETTO_EINTR(waitpid(pid_, &wstatus, 0));
- }
-}
-
LLVMSymbolizerProcess::LLVMSymbolizerProcess()
- : subprocess_("llvm-symbolizer", {"llvm-symbolizer"}),
- read_file_(fdopen(subprocess_.read_fd(), "r")) {}
+ :
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+ subprocess_("llvm-symbolizer.exe", {}) {
+}
+#else
+ subprocess_("llvm-symbolizer", {"llvm-symbolizer"}) {
+}
+#endif
std::vector<SymbolizedFrame> LLVMSymbolizerProcess::Symbolize(
const std::string& binary,
uint64_t address) {
std::vector<SymbolizedFrame> result;
-
- if (PERFETTO_EINTR(dprintf(subprocess_.write_fd(), "%s 0x%" PRIx64 "\n",
- binary.c_str(), address)) < 0) {
+ char buffer[1024];
+ int size = sprintf(buffer, "%s 0x%" PRIx64 "\n", binary.c_str(), address);
+ if (subprocess_.Write(buffer, static_cast<size_t>(size)) < 0) {
PERFETTO_ELOG("Failed to write to llvm-symbolizer.");
return result;
}
- auto lines = GetLines(read_file_);
+ auto lines = GetLines([&](char* read_buffer, size_t buffer_size) {
+ return subprocess_.Read(read_buffer, buffer_size);
+ });
// llvm-symbolizer writes out records in the form of
// Foo(Bar*)
// foo.cc:123
diff --git a/src/profiling/symbolizer/local_symbolizer.h b/src/profiling/symbolizer/local_symbolizer.h
index 756a327..ae403b9 100644
--- a/src/profiling/symbolizer/local_symbolizer.h
+++ b/src/profiling/symbolizer/local_symbolizer.h
@@ -23,17 +23,18 @@
#include <vector>
#include "perfetto/ext/base/optional.h"
-#include "perfetto/ext/base/pipe.h"
+#include "perfetto/ext/base/scoped_file.h"
+#include "src/profiling/symbolizer/subprocess.h"
#include "src/profiling/symbolizer/symbolizer.h"
namespace perfetto {
namespace profiling {
-#if PERFETTO_BUILDFLAG(PERFETTO_LOCAL_SYMBOLIZER)
-
bool ParseLlvmSymbolizerLine(const std::string& line,
std::string* file_name,
uint32_t* line_no);
+std::vector<std::string> GetLines(
+ std::function<int64_t(char*, size_t)> fn_read);
struct FoundBinary {
std::string file_name;
@@ -82,22 +83,6 @@
std::map<std::string, base::Optional<FoundBinary>> cache_;
};
-class Subprocess {
- public:
- Subprocess(const std::string& file, std::vector<std::string> args);
-
- ~Subprocess();
-
- int read_fd() { return output_pipe_.rd.get(); }
- int write_fd() { return input_pipe_.wr.get(); }
-
- private:
- base::Pipe input_pipe_;
- base::Pipe output_pipe_;
-
- pid_t pid_ = -1;
-};
-
class LLVMSymbolizerProcess {
public:
LLVMSymbolizerProcess();
@@ -107,7 +92,6 @@
private:
Subprocess subprocess_;
- FILE* read_file_;
};
class LocalSymbolizer : public Symbolizer {
@@ -128,8 +112,6 @@
std::unique_ptr<BinaryFinder> finder_;
};
-#endif // PERFETTO_BUILDFLAG(PERFETTO_LOCAL_SYMBOLIZER)
-
std::unique_ptr<Symbolizer> LocalSymbolizerOrDie(
std::vector<std::string> binary_path,
const char* mode);
diff --git a/src/profiling/symbolizer/local_symbolizer_unittest.cc b/src/profiling/symbolizer/local_symbolizer_unittest.cc
index b515866..72841f0 100644
--- a/src/profiling/symbolizer/local_symbolizer_unittest.cc
+++ b/src/profiling/symbolizer/local_symbolizer_unittest.cc
@@ -20,12 +20,29 @@
// This translation unit is built only on Linux and MacOS. See //gn/BUILD.gn.
#if PERFETTO_BUILDFLAG(PERFETTO_LOCAL_SYMBOLIZER)
+#include "src/base/test/utils.h"
#include "src/profiling/symbolizer/local_symbolizer.h"
+#include "src/profiling/symbolizer/subprocess.h"
namespace perfetto {
namespace profiling {
namespace {
+void RunAndValidateParseLines(std::string raw_contents) {
+ std::istringstream stream(raw_contents);
+ auto read_callback = [&stream](char* buffer, size_t size) {
+ stream.get(buffer, static_cast<int>(size), '\0');
+ return strlen(buffer);
+ };
+ std::vector<std::string> lines = perfetto::profiling::GetLines(read_callback);
+ std::istringstream validation(raw_contents);
+ for (std::string actual : lines) {
+ std::string expected;
+ getline(validation, expected);
+ EXPECT_EQ(actual, expected);
+ }
+}
+
TEST(LocalSymbolizerTest, ParseLineWindows) {
std::string file_name;
uint32_t lineno;
@@ -35,6 +52,26 @@
EXPECT_EQ(lineno, 123u);
}
+TEST(LocalSymbolizerTest, ParseLinesExpectedOutput) {
+ std::string raw_contents =
+ "FSlateRHIRenderingPolicy::DrawElements(FRHICommandListImmediate&, "
+ "FSlateBackBuffer&, TRefCountPtr<FRHITexture2D>&, "
+ "TRefCountPtr<FRHITexture2D>&, TRefCountPtr<FRHITexture2D>&, int, "
+ "TArray<FSlateRenderBatch, TSizedDefaultAllocator<32> > const&, "
+ "FSlateRenderingParams const&)\n"
+ "F:/P4/EngineReleaseA/Engine/Source/Runtime/SlateRHIRenderer/"
+ "Private\\SlateRHIRenderingPolicy.cpp:1187:19\n";
+ RunAndValidateParseLines(raw_contents);
+}
+
+TEST(LocalSymbolizerTest, ParseLinesErrorOutput) {
+ std::string raw_contents =
+ "LLVMSymbolizer: error reading file: No such file or directory\n"
+ "??\n"
+ "??:0:0\n";
+ RunAndValidateParseLines(raw_contents);
+}
+
} // namespace
} // namespace profiling
} // namespace perfetto
diff --git a/src/profiling/symbolizer/scoped_read_mmap.h b/src/profiling/symbolizer/scoped_read_mmap.h
new file mode 100644
index 0000000..69a028a
--- /dev/null
+++ b/src/profiling/symbolizer/scoped_read_mmap.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_PROFILING_SYMBOLIZER_SCOPED_READ_MMAP_H_
+#define SRC_PROFILING_SYMBOLIZER_SCOPED_READ_MMAP_H_
+
+#include "perfetto/ext/base/scoped_file.h"
+
+namespace perfetto {
+namespace profiling {
+
+class ScopedReadMmap {
+ public:
+ ScopedReadMmap(const char* fname, size_t length);
+ virtual ~ScopedReadMmap();
+
+ void* operator*() { return ptr_; }
+
+ bool IsValid();
+
+ private:
+ size_t length_;
+ void* ptr_;
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+ void* file_ = nullptr;
+ void* map_ = nullptr;
+#else
+ base::ScopedFile fd_;
+#endif
+};
+
+} // namespace profiling
+} // namespace perfetto
+
+#endif // SRC_PROFILING_SYMBOLIZER_SCOPED_READ_MMAP_H_
diff --git a/src/profiling/symbolizer/scoped_read_mmap_posix.cc b/src/profiling/symbolizer/scoped_read_mmap_posix.cc
new file mode 100644
index 0000000..93944fc
--- /dev/null
+++ b/src/profiling/symbolizer/scoped_read_mmap_posix.cc
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/profiling/symbolizer/scoped_read_mmap.h"
+
+#include "perfetto/base/logging.h"
+
+#include <sys/mman.h>
+
+namespace perfetto {
+namespace profiling {
+
+ScopedReadMmap::ScopedReadMmap(const char* fname, size_t length)
+ : length_(length), fd_(base::OpenFile(fname, O_RDONLY)) {
+ if (!fd_) {
+ PERFETTO_PLOG("Failed to open %s", fname);
+ return;
+ }
+ ptr_ = mmap(nullptr, length, PROT_READ, MAP_PRIVATE, *fd_, 0);
+}
+
+ScopedReadMmap::~ScopedReadMmap() {
+ if (ptr_ != MAP_FAILED)
+ munmap(ptr_, length_);
+}
+
+bool ScopedReadMmap::IsValid() {
+ return ptr_ != MAP_FAILED;
+}
+
+} // namespace profiling
+} // namespace perfetto
diff --git a/src/profiling/symbolizer/scoped_read_mmap_windows.cc b/src/profiling/symbolizer/scoped_read_mmap_windows.cc
new file mode 100644
index 0000000..6fc9fdd
--- /dev/null
+++ b/src/profiling/symbolizer/scoped_read_mmap_windows.cc
@@ -0,0 +1,62 @@
+
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/profiling/symbolizer/scoped_read_mmap.h"
+
+#define WIN32_MEAN_AND_LEAN
+#include <Windows.h>
+
+namespace perfetto {
+namespace profiling {
+
+ScopedReadMmap::ScopedReadMmap(const char* fName, size_t length)
+ : length_(length), ptr_(nullptr) {
+ file_ = CreateFileA(fName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
+ FILE_ATTRIBUTE_NORMAL, NULL);
+ if (file_ == INVALID_HANDLE_VALUE) {
+ PERFETTO_DLOG("Failed to open file: %s", fName);
+ return;
+ }
+ map_ = CreateFileMapping(file_, NULL, PAGE_READONLY, 0, 0, NULL);
+ if (map_ == INVALID_HANDLE_VALUE) {
+ PERFETTO_DLOG("Failed to mmap file");
+ return;
+ }
+ ptr_ = MapViewOfFile(map_, FILE_MAP_READ, 0, 0, length);
+ if (ptr_ == nullptr) {
+ PERFETTO_DLOG("Failed to map view of file");
+ }
+}
+
+ScopedReadMmap::~ScopedReadMmap() {
+ if (ptr_ != nullptr) {
+ UnmapViewOfFile(ptr_);
+ }
+ if (map_ != nullptr && map_ != INVALID_HANDLE_VALUE) {
+ CloseHandle(map_);
+ }
+ if (file_ != nullptr && file_ != INVALID_HANDLE_VALUE) {
+ CloseHandle(file_);
+ }
+}
+
+bool ScopedReadMmap::IsValid() {
+ return ptr_ != nullptr;
+}
+
+} // namespace profiling
+} // namespace perfetto
\ No newline at end of file
diff --git a/src/profiling/symbolizer/subprocess.h b/src/profiling/symbolizer/subprocess.h
new file mode 100644
index 0000000..227d4d4
--- /dev/null
+++ b/src/profiling/symbolizer/subprocess.h
@@ -0,0 +1,58 @@
+
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_PROFILING_SYMBOLIZER_SUBPROCESS_H_
+#define SRC_PROFILING_SYMBOLIZER_SUBPROCESS_H_
+
+#include <string>
+#include <vector>
+
+#include "perfetto/base/build_config.h"
+
+#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+#include "perfetto/ext/base/pipe.h"
+#endif
+
+namespace perfetto {
+namespace profiling {
+
+class Subprocess {
+ public:
+ Subprocess(const std::string& file, std::vector<std::string> args);
+ ~Subprocess();
+
+ int64_t Write(const char* buffer, size_t size);
+ int64_t Read(char* buffer, size_t size);
+
+ private:
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+ void* child_pipe_in_read_ = nullptr;
+ void* child_pipe_in_write_ = nullptr;
+ void* child_pipe_out_read_ = nullptr;
+ void* child_pipe_out_write_ = nullptr;
+#else
+ base::Pipe input_pipe_;
+ base::Pipe output_pipe_;
+
+ pid_t pid_ = -1;
+#endif
+};
+
+} // namespace profiling
+} // namespace perfetto
+
+#endif // SRC_PROFILING_SYMBOLIZER_SUBPROCESS_H_
diff --git a/src/profiling/symbolizer/subprocess_posix.cc b/src/profiling/symbolizer/subprocess_posix.cc
new file mode 100644
index 0000000..de7527a
--- /dev/null
+++ b/src/profiling/symbolizer/subprocess_posix.cc
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/profiling/symbolizer/subprocess.h"
+
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "perfetto/ext/base/utils.h"
+
+namespace perfetto {
+namespace profiling {
+
+Subprocess::Subprocess(const std::string& file, std::vector<std::string> args)
+ : input_pipe_(base::Pipe::Create(base::Pipe::kBothBlock)),
+ output_pipe_(base::Pipe::Create(base::Pipe::kBothBlock)) {
+ std::vector<char*> c_str_args;
+ for (std::string& arg : args)
+ c_str_args.push_back(&(arg[0]));
+ c_str_args.push_back(nullptr);
+
+ if ((pid_ = fork()) == 0) {
+ // Child
+ PERFETTO_CHECK(dup2(*input_pipe_.rd, STDIN_FILENO) != -1);
+ PERFETTO_CHECK(dup2(*output_pipe_.wr, STDOUT_FILENO) != -1);
+ input_pipe_.wr.reset();
+ output_pipe_.rd.reset();
+ if (execvp(file.c_str(), c_str_args.data()) == -1)
+ PERFETTO_FATAL("Failed to exec %s", file.c_str());
+ }
+ PERFETTO_CHECK(pid_ != -1);
+ input_pipe_.rd.reset();
+ output_pipe_.wr.reset();
+}
+
+Subprocess::~Subprocess() {
+ if (pid_ != -1) {
+ kill(pid_, SIGKILL);
+ int wstatus;
+ PERFETTO_EINTR(waitpid(pid_, &wstatus, 0));
+ }
+}
+
+int64_t Subprocess::Write(const char* buffer, size_t size) {
+ if (!input_pipe_.wr) {
+ return -1;
+ }
+ return PERFETTO_EINTR(write(input_pipe_.wr.get(), buffer, size));
+}
+
+int64_t Subprocess::Read(char* buffer, size_t size) {
+ if (!output_pipe_.rd) {
+ return -1;
+ }
+ return PERFETTO_EINTR(read(output_pipe_.rd.get(), buffer, size));
+}
+
+} // namespace profiling
+} // namespace perfetto
diff --git a/src/profiling/symbolizer/subprocess_windows.cc b/src/profiling/symbolizer/subprocess_windows.cc
new file mode 100644
index 0000000..01dbe95
--- /dev/null
+++ b/src/profiling/symbolizer/subprocess_windows.cc
@@ -0,0 +1,127 @@
+
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/profiling/symbolizer/subprocess.h"
+
+#include <sstream>
+#include <string>
+#define WIN32_MEAN_AND_LEAN
+#include <Windows.h>
+
+#include "perfetto/base/logging.h"
+
+namespace perfetto {
+namespace profiling {
+
+Subprocess::Subprocess(const std::string& file, std::vector<std::string> args) {
+ std::stringstream cmd;
+ cmd << file;
+ for (auto arg : args) {
+ cmd << " " << arg;
+ }
+ SECURITY_ATTRIBUTES attr;
+ attr.nLength = sizeof(SECURITY_ATTRIBUTES);
+ attr.bInheritHandle = true;
+ attr.lpSecurityDescriptor = NULL;
+ // Create a pipe for the child process's STDOUT.
+ if (!CreatePipe(&child_pipe_out_read_, &child_pipe_out_write_, &attr, 0) ||
+ !SetHandleInformation(child_pipe_out_read_, HANDLE_FLAG_INHERIT, 0)) {
+ PERFETTO_ELOG("Failed to create stdout pipe");
+ return;
+ }
+ if (!CreatePipe(&child_pipe_in_read_, &child_pipe_in_write_, &attr, 0) ||
+ !SetHandleInformation(child_pipe_in_write_, HANDLE_FLAG_INHERIT, 0)) {
+ PERFETTO_ELOG("Failed to create stdin pipe");
+ return;
+ }
+
+ PROCESS_INFORMATION proc_info;
+ STARTUPINFOA start_info;
+ bool success = false;
+ // Set up members of the PROCESS_INFORMATION structure.
+ ZeroMemory(&proc_info, sizeof(PROCESS_INFORMATION));
+
+ // Set up members of the STARTUPINFO structure.
+ // This structure specifies the STDIN and STDOUT handles for redirection.
+ ZeroMemory(&start_info, sizeof(STARTUPINFOA));
+ start_info.cb = sizeof(STARTUPINFOA);
+ start_info.hStdError = child_pipe_out_write_;
+ start_info.hStdOutput = child_pipe_out_write_;
+ start_info.hStdInput = child_pipe_in_read_;
+ start_info.dwFlags |= STARTF_USESTDHANDLES;
+
+ // Create the child process.
+ success = CreateProcessA(NULL,
+ &(cmd.str()[0]), // command line
+ NULL, // process security attributes
+ NULL, // primary thread security attributes
+ TRUE, // handles are inherited
+ 0, // creation flags
+ NULL, // use parent's environment
+ NULL, // use parent's current directory
+ &start_info, // STARTUPINFO pointer
+ &proc_info); // receives PROCESS_INFORMATION
+
+ // If an error occurs, exit the application.
+ if (success) {
+ CloseHandle(proc_info.hProcess);
+ CloseHandle(proc_info.hThread);
+
+ // Close handles to the stdin and stdout pipes no longer needed by the child
+ // process. If they are not explicitly closed, there is no way to recognize
+ // that the child process has ended.
+
+ CloseHandle(child_pipe_out_write_);
+ CloseHandle(child_pipe_in_read_);
+ } else {
+ PERFETTO_ELOG("Failed to launch: %s", cmd.str().c_str());
+ child_pipe_in_read_ = nullptr;
+ child_pipe_in_write_ = nullptr;
+ child_pipe_out_write_ = nullptr;
+ child_pipe_out_read_ = nullptr;
+ }
+}
+
+Subprocess::~Subprocess() {
+ CloseHandle(child_pipe_out_read_);
+ CloseHandle(child_pipe_in_write_);
+}
+
+size_t Subprocess::Write(const char* buffer, size_t size) {
+ if (child_pipe_in_write_ == nullptr) {
+ return -1;
+ }
+ DWORD bytes_written;
+ if (WriteFile(child_pipe_in_write_, buffer, size, &bytes_written, NULL)) {
+ return static_cast<int64_t>(bytes_written);
+ }
+ return -1;
+}
+
+int64_t Subprocess::Read(char* buffer, size_t size) {
+ if (child_pipe_out_read_ == nullptr) {
+ return -1;
+ }
+ DWORD bytes_read;
+ if (ReadFile(child_pipe_out_read_, buffer, size, &bytes_read, NULL)) {
+ return static_cast<int64_t>(bytes_read);
+ }
+ return -1;
+}
+
+} // namespace profiling
+} // namespace perfetto