Merge "symbolizer: Fix symlink handling"
diff --git a/Android.bp b/Android.bp
index 82456b8..212882f 100644
--- a/Android.bp
+++ b/Android.bp
@@ -8755,6 +8755,8 @@
     srcs: [
         "src/profiling/symbolizer/breakpad_parser.cc",
         "src/profiling/symbolizer/breakpad_symbolizer.cc",
+        "src/profiling/symbolizer/filesystem_posix.cc",
+        "src/profiling/symbolizer/filesystem_windows.cc",
         "src/profiling/symbolizer/local_symbolizer.cc",
         "src/profiling/symbolizer/scoped_read_mmap_posix.cc",
         "src/profiling/symbolizer/scoped_read_mmap_windows.cc",
@@ -11822,6 +11824,13 @@
         "src/traced/probes/filesystem/testdata/**/*",
         "src/traced/probes/ftrace/test/data/**/*",
     ],
+    target: {
+        musl: {
+            static_libs: [
+                "libfts",
+            ],
+        },
+    },
 }
 
 // GN: //test/vts:perfetto_vts_deps
@@ -12344,6 +12353,11 @@
                 ],
             },
         },
+        musl: {
+            static_libs: [
+                "libfts",
+            ],
+        },
     },
 }
 
@@ -12528,6 +12542,13 @@
     cflags: [
         "-DHAVE_HIDDEN",
     ],
+    target: {
+        musl: {
+            static_libs: [
+                "libfts",
+            ],
+        },
+    },
 }
 
 // GN: //src/traced/service:traced
diff --git a/BUILD b/BUILD
index 3d746e3..50e7ace 100644
--- a/BUILD
+++ b/BUILD
@@ -947,6 +947,9 @@
         "src/profiling/symbolizer/breakpad_symbolizer.cc",
         "src/profiling/symbolizer/breakpad_symbolizer.h",
         "src/profiling/symbolizer/elf.h",
+        "src/profiling/symbolizer/filesystem.h",
+        "src/profiling/symbolizer/filesystem_posix.cc",
+        "src/profiling/symbolizer/filesystem_windows.cc",
         "src/profiling/symbolizer/local_symbolizer.cc",
         "src/profiling/symbolizer/local_symbolizer.h",
         "src/profiling/symbolizer/scoped_read_mmap.h",
diff --git a/include/perfetto/ext/base/file_utils.h b/include/perfetto/ext/base/file_utils.h
index 7c7b8e0..d2412d0 100644
--- a/include/perfetto/ext/base/file_utils.h
+++ b/include/perfetto/ext/base/file_utils.h
@@ -27,7 +27,6 @@
 #include "perfetto/base/export.h"
 #include "perfetto/base/status.h"
 #include "perfetto/ext/base/scoped_file.h"
-#include "perfetto/ext/base/optional.h"
 #include "perfetto/ext/base/utils.h"
 
 namespace perfetto {
@@ -94,9 +93,6 @@
 base::Status ListFilesRecursive(const std::string& dir_path,
                                 std::vector<std::string>& output);
 
-// Returns the size of the file at `path` or nullopt in case of error.
-Optional<size_t> GetFileSize(const std::string& path);
-
 }  // namespace base
 }  // namespace perfetto
 
diff --git a/src/base/file_utils.cc b/src/base/file_utils.cc
index 66ab5f6..9f70ad7 100644
--- a/src/base/file_utils.cc
+++ b/src/base/file_utils.cc
@@ -346,34 +346,5 @@
   return filename.substr(ext_idx);
 }
 
-base::Optional<size_t> GetFileSize(const std::string& file_path) {
-#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
-  HANDLE file =
-      CreateFileA(file_path.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr,
-                  OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
-  if (file == INVALID_HANDLE_VALUE) {
-    return nullopt;
-  }
-  LARGE_INTEGER file_size;
-  file_size.QuadPart = 0;
-  BOOL ok = GetFileSizeEx(file, &file_size);
-  CloseHandle(file);
-  if (!ok) {
-    return nullopt;
-  }
-  return static_cast<size_t>(file_size.QuadPart);
-#else
-  base::ScopedFile fd(base::OpenFile(file_path, O_RDONLY | O_CLOEXEC));
-  if (!fd) {
-    return nullopt;
-  }
-  struct stat buf {};
-  if (fstat(*fd, &buf) == -1) {
-    return nullopt;
-  }
-  return static_cast<size_t>(buf.st_size);
-#endif
-}
-
 }  // namespace base
 }  // namespace perfetto
diff --git a/src/profiling/symbolizer/BUILD.gn b/src/profiling/symbolizer/BUILD.gn
index 94feb3e..c81dbb1 100644
--- a/src/profiling/symbolizer/BUILD.gn
+++ b/src/profiling/symbolizer/BUILD.gn
@@ -24,6 +24,9 @@
     "breakpad_symbolizer.cc",
     "breakpad_symbolizer.h",
     "elf.h",
+    "filesystem.h",
+    "filesystem_posix.cc",
+    "filesystem_windows.cc",
     "local_symbolizer.cc",
     "local_symbolizer.h",
     "scoped_read_mmap.h",
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..cc983e2
--- /dev/null
+++ b/src/profiling/symbolizer/filesystem_posix.cc
@@ -0,0 +1,78 @@
+/*
+ * 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"
+
+#include "perfetto/base/build_config.h"
+
+#if !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+#if PERFETTO_BUILDFLAG(PERFETTO_LOCAL_SYMBOLIZER)
+#include <fts.h>
+#include <sys/stat.h>
+#endif
+
+#include <string>
+
+#include "perfetto/ext/base/file_utils.h"
+
+namespace perfetto {
+namespace profiling {
+#if PERFETTO_BUILDFLAG(PERFETTO_LOCAL_SYMBOLIZER)
+bool WalkDirectories(std::vector<std::string> dirs, FileCallback fn) {
+  std::vector<char*> dir_cstrs;
+  dir_cstrs.reserve(dirs.size());
+  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
+
+#endif  // !PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
diff --git a/src/profiling/symbolizer/filesystem_windows.cc b/src/profiling/symbolizer/filesystem_windows.cc
new file mode 100644
index 0000000..04cd685
--- /dev/null
+++ b/src/profiling/symbolizer/filesystem_windows.cc
@@ -0,0 +1,75 @@
+/*
+ * 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_OS_WIN)
+
+#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_DATAA file;
+    HANDLE fh = FindFirstFileA((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 (FindNextFileA(fh, &file));
+    }
+    FindClose(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, nullptr,
+                  OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
+  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 static_cast<size_t>(file_size.QuadPart);
+}
+
+}  // namespace profiling
+}  // namespace perfetto
+
+#endif  // PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
diff --git a/src/profiling/symbolizer/local_symbolizer.cc b/src/profiling/symbolizer/local_symbolizer.cc
index e37cee7..ce11a40 100644
--- a/src/profiling/symbolizer/local_symbolizer.cc
+++ b/src/profiling/symbolizer/local_symbolizer.cc
@@ -32,6 +32,7 @@
 #include "perfetto/ext/base/scoped_file.h"
 #include "perfetto/ext/base/string_utils.h"
 #include "src/profiling/symbolizer/elf.h"
+#include "src/profiling/symbolizer/filesystem.h"
 #include "src/profiling/symbolizer/scoped_read_mmap.h"
 
 namespace perfetto {
@@ -216,36 +217,31 @@
   uint64_t load_bias;
 };
 
-base::Optional<BuildIdAndLoadBias> GetBuildIdAndLoadBias(
-    const std::string& fname) {
-  base::Optional<size_t> size = base::GetFileSize(fname);
-  if (!size.has_value()) {
-    PERFETTO_PLOG("Failed to get file size %s", fname.c_str());
-    return base::nullopt;
-  }
+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)
+  if (size <= EI_CLASS)
     return base::nullopt;
-  ScopedReadMmap map(fname.c_str(), *size);
+  ScopedReadMmap map(fname, size);
   if (!map.IsValid()) {
     PERFETTO_PLOG("mmap");
     return base::nullopt;
   }
   char* mem = static_cast<char*>(*map);
 
-  if (!IsElf(mem, *size))
+  if (!IsElf(mem, size))
     return base::nullopt;
 
   base::Optional<std::string> build_id;
   base::Optional<uint64_t> load_bias;
   switch (mem[EI_CLASS]) {
     case ELFCLASS32:
-      build_id = GetBuildId<Elf32>(mem, *size);
-      load_bias = GetLoadBias<Elf32>(mem, *size);
+      build_id = GetBuildId<Elf32>(mem, size);
+      load_bias = GetLoadBias<Elf32>(mem, size);
       break;
     case ELFCLASS64:
-      build_id = GetBuildId<Elf64>(mem, *size);
-      load_bias = GetLoadBias<Elf64>(mem, *size);
+      build_id = GetBuildId<Elf64>(mem, size);
+      load_bias = GetLoadBias<Elf64>(mem, size);
       break;
     default:
       return base::nullopt;
@@ -256,48 +252,35 @@
   return base::nullopt;
 }
 
-bool StartsWithElfMagic(const std::string& fname) {
-  base::ScopedFile fd(base::OpenFile(fname, O_RDONLY));
-  char magic[EI_MAG3 + 1];
-  if (!fd) {
-    PERFETTO_PLOG("Failed to open %s", fname.c_str());
-    return false;
-  }
-  ssize_t rd = base::Read(*fd, &magic, sizeof(magic));
-  if (rd != sizeof(magic)) {
-    PERFETTO_PLOG("Failed to read %s", fname.c_str());
-    return false;
-  }
-  if (!IsElf(magic, static_cast<size_t>(rd))) {
-    PERFETTO_DLOG("%s not an ELF.", fname.c_str());
-    return false;
-  }
-  return true;
-}
-
 std::map<std::string, FoundBinary> BuildIdIndex(std::vector<std::string> dirs) {
   std::map<std::string, FoundBinary> result;
-  for (const std::string& dir : dirs) {
-    std::vector<std::string> files;
-    base::Status status = base::ListFilesRecursive(dir, files);
-    if (!status.ok()) {
-      PERFETTO_PLOG("Failed to list directory %s", dir.c_str());
-      continue;
-    }
-    for (const std::string& basename : files) {
-      std::string fname = dir + "/" + basename;
-      if (!StartsWithElfMagic(fname)) {
-        continue;
+  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;
       }
-      base::Optional<BuildIdAndLoadBias> build_id_and_load_bias =
-          GetBuildIdAndLoadBias(fname);
-      if (build_id_and_load_bias) {
-        result.emplace(build_id_and_load_bias->build_id,
-                       FoundBinary{fname, build_id_and_load_bias->load_bias});
+      ssize_t rd = base::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;
 }
 
@@ -368,8 +351,15 @@
   if (!base::FileExists(symbol_file)) {
     return base::nullopt;
   }
+  // Openfile opens the file with an exclusive lock on windows.
+  size_t size = GetFileSize(symbol_file);
+
+  if (size == 0) {
+    return base::nullopt;
+  }
+
   base::Optional<BuildIdAndLoadBias> build_id_and_load_bias =
-      GetBuildIdAndLoadBias(symbol_file);
+      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) {
diff --git a/src/profiling/symbolizer/local_symbolizer_unittest.cc b/src/profiling/symbolizer/local_symbolizer_unittest.cc
index cb0c11d..919e3ff 100644
--- a/src/profiling/symbolizer/local_symbolizer_unittest.cc
+++ b/src/profiling/symbolizer/local_symbolizer_unittest.cc
@@ -28,6 +28,12 @@
 #include "src/profiling/symbolizer/local_symbolizer.h"
 #include "src/profiling/symbolizer/subprocess.h"
 
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) ||   \
+    PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) || \
+    PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
+#include <unistd.h>
+#endif
+
 namespace perfetto {
 namespace profiling {
 namespace {
@@ -140,7 +146,13 @@
   return std::string(reinterpret_cast<const char*>(&e), sizeof e);
 }
 
-TEST(LocalBinaryIndexerTest, SimpleTree) {
+#if defined(MEMORY_SANITIZER)
+// fts_read() causes some error under msan.
+#define NOMSAN_SimpleTree DISABLED_SimpleTree
+#else
+#define NOMSAN_SimpleTree SimpleTree
+#endif
+TEST(LocalBinaryIndexerTest, NOMSAN_SimpleTree) {
   base::TmpDirTree tmp;
   tmp.AddDir("dir1");
   tmp.AddFile("dir1/elf1", CreateElfWithBuildId("AAAAAAAAAAAAAAAAAAAA"));
@@ -154,14 +166,91 @@
   base::Optional<FoundBinary> bin1 =
       indexer.FindBinary("", "AAAAAAAAAAAAAAAAAAAA");
   ASSERT_TRUE(bin1.has_value());
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+  EXPECT_EQ(bin1.value().file_name, tmp.path() + "/dir1\\elf1");
+#else
   EXPECT_EQ(bin1.value().file_name, tmp.path() + "/dir1/elf1");
+#endif
+  base::Optional<FoundBinary> bin2 =
+      indexer.FindBinary("", "BBBBBBBBBBBBBBBBBBBB");
+  ASSERT_TRUE(bin2.has_value());
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+  EXPECT_EQ(bin2.value().file_name, tmp.path() + "/dir2\\elf1");
+#else
+  EXPECT_EQ(bin2.value().file_name, tmp.path() + "/dir2/elf1");
+#endif
+}
+
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) ||   \
+    PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) || \
+    PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
+
+#if defined(MEMORY_SANITIZER)
+// fts_read() causes some error under msan.
+#define NOMSAN_Symlinks DISABLED_Symlinks
+#else
+#define NOMSAN_Symlinks Symlinks
+#endif
+TEST(LocalBinaryIndexerTest, NOMSAN_Symlinks) {
+  base::TmpDirTree tmp;
+  tmp.AddDir("real");
+  tmp.AddFile("real/elf1", CreateElfWithBuildId("AAAAAAAAAAAAAAAAAAAA"));
+  tmp.AddDir("real/dir1");
+  tmp.AddFile("real/dir1/elf2", CreateElfWithBuildId("BBBBBBBBBBBBBBBBBBBB"));
+  tmp.AddFile("real/dir1/elf3", CreateElfWithBuildId("CCCCCCCCCCCCCCCCCCCC"));
+  tmp.AddDir("sym");
+  symlink(tmp.AbsolutePath("real/elf1").c_str(),
+          tmp.AbsolutePath("sym/elf1").c_str());
+  tmp.TrackFile("sym/elf1");
+  symlink(tmp.AbsolutePath("real/dir1").c_str(),
+          tmp.AbsolutePath("sym/dir1").c_str());
+  tmp.TrackFile("sym/dir1");
+
+  LocalBinaryIndexer indexer({tmp.AbsolutePath("sym")});
+
+  base::Optional<FoundBinary> bin1 =
+      indexer.FindBinary("", "AAAAAAAAAAAAAAAAAAAA");
+  ASSERT_TRUE(bin1.has_value());
+  EXPECT_EQ(bin1.value().file_name, tmp.AbsolutePath("sym/elf1"));
 
   base::Optional<FoundBinary> bin2 =
       indexer.FindBinary("", "BBBBBBBBBBBBBBBBBBBB");
   ASSERT_TRUE(bin2.has_value());
-  EXPECT_EQ(bin2.value().file_name, tmp.path() + "/dir2/elf1");
+  EXPECT_EQ(bin2.value().file_name, tmp.AbsolutePath("sym/dir1/elf2"));
+
+  base::Optional<FoundBinary> bin3 =
+      indexer.FindBinary("", "CCCCCCCCCCCCCCCCCCCC");
+  ASSERT_TRUE(bin3.has_value());
+  EXPECT_EQ(bin3.value().file_name, tmp.AbsolutePath("sym/dir1/elf3"));
 }
 
+#if defined(MEMORY_SANITIZER)
+// fts_read() causes some error under msan.
+#define NOMSAN_RecursiveSymlinks DISABLED_RecursiveSymlinks
+#else
+#define NOMSAN_RecursiveSymlinks RecursiveSymlinks
+#endif
+TEST(LocalBinaryIndexerTest, NOMSAN_RecursiveSymlinks) {
+  base::TmpDirTree tmp;
+  tmp.AddDir("main");
+  tmp.AddFile("main/elf1", CreateElfWithBuildId("AAAAAAAAAAAAAAAAAAAA"));
+  tmp.AddDir("main/dir1");
+  symlink(tmp.AbsolutePath("main").c_str(),
+          tmp.AbsolutePath("main/dir1/sym").c_str());
+  tmp.TrackFile("main/dir1/sym");
+
+  LocalBinaryIndexer indexer({tmp.AbsolutePath("main")});
+
+  base::Optional<FoundBinary> bin1 =
+      indexer.FindBinary("", "AAAAAAAAAAAAAAAAAAAA");
+  ASSERT_TRUE(bin1.has_value());
+  EXPECT_EQ(bin1.value().file_name, tmp.AbsolutePath("main/elf1"));
+}
+
+#endif  // PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) ||
+        // PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) ||
+        // PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
+
 TEST(LocalBinaryFinderTest, AbsolutePath) {
   base::TmpDirTree tmp;
   tmp.AddDir("root");
diff --git a/tools/gen_android_bp b/tools/gen_android_bp
index e8a5677..b7da65d 100755
--- a/tools/gen_android_bp
+++ b/tools/gen_android_bp
@@ -117,6 +117,12 @@
     ],
 }
 
+needs_libfts = [
+    '//:perfetto_unittests',
+    '//src/trace_processor:trace_processor_shell',
+    '//src/traceconv:traceconv',
+]
+
 # All module names are prefixed with this string to avoid collisions.
 module_prefix = 'perfetto_'
 
@@ -507,6 +513,7 @@
     self.tool_files: Optional[List[str]] = None
     self.android = Target('android')
     self.host = Target('host')
+    self.musl = Target('musl')
     self.lto: Optional[bool] = None
     self.stl = None
     self.dist = dict()
@@ -568,6 +575,7 @@
     target_out = []
     self._output_field(target_out, 'android')
     self._output_field(target_out, 'host')
+    self._output_field(target_out, 'musl')
     if target_out:
       output.append('    target: {')
       for line in target_out:
@@ -985,6 +993,9 @@
       for src in target.sources
       if is_supported_source_file(src))
 
+  if name_without_toolchain in needs_libfts:
+    module.musl.static_libs.add('libfts')
+
   if target.type in gn_utils.LINKER_UNIT_TYPES:
     module.cflags.update(_get_cflags(target))