Merge "fs_mgr: trim the terminating '\0' when reading from device tree"
diff --git a/debuggerd/crasher/crasher.cpp b/debuggerd/crasher/crasher.cpp
index 6970201..f57349b 100644
--- a/debuggerd/crasher/crasher.cpp
+++ b/debuggerd/crasher/crasher.cpp
@@ -134,6 +134,14 @@
     free(buf); // GCC is smart enough to warn about this, but we're doing it deliberately.
 }
 
+noinline void leak() {
+    while (true) {
+        void* mapping =
+            mmap(nullptr, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+        static_cast<volatile char*>(mapping)[0] = 'a';
+    }
+}
+
 noinline void sigsegv_non_null() {
     int* a = (int *)(&do_action);
     *a = 42;
@@ -160,8 +168,8 @@
     fprintf(stderr, "  stack-overflow        recurse until the stack overflows\n");
     fprintf(stderr, "  nostack               crash with a NULL stack pointer\n");
     fprintf(stderr, "\n");
-    fprintf(stderr, "  heap-corruption       cause a libc abort by corrupting the heap\n");
     fprintf(stderr, "  heap-usage            cause a libc abort by abusing a heap function\n");
+    fprintf(stderr, "  leak                  leak memory until we get OOM-killed\n");
     fprintf(stderr, "\n");
     fprintf(stderr, "  abort                 call abort()\n");
     fprintf(stderr, "  assert                call assert() without a function\n");
@@ -265,6 +273,8 @@
         return pthread_join(0, nullptr);
     } else if (!strcasecmp(arg, "heap-usage")) {
         abuse_heap();
+    } else if (!strcasecmp(arg, "leak")) {
+        leak();
     } else if (!strcasecmp(arg, "SIGSEGV-unmapped")) {
         char* map = reinterpret_cast<char*>(mmap(nullptr, sizeof(int), PROT_READ | PROT_WRITE,
                                                  MAP_SHARED | MAP_ANONYMOUS, -1, 0));
diff --git a/libmemunreachable/Android.bp b/libmemunreachable/Android.bp
index cdac76b..826a576 100644
--- a/libmemunreachable/Android.bp
+++ b/libmemunreachable/Android.bp
@@ -26,6 +26,7 @@
     defaults: ["libmemunreachable_defaults"],
     srcs: [
         "Allocator.cpp",
+        "Binder.cpp",
         "HeapWalker.cpp",
         "LeakFolding.cpp",
         "LeakPipe.cpp",
@@ -84,3 +85,18 @@
         },
     },
 }
+
+cc_test {
+    name: "memunreachable_binder_test",
+    defaults: ["libmemunreachable_defaults"],
+    srcs: [
+        "tests/Binder_test.cpp",
+        "tests/MemUnreachable_test.cpp",
+    ],
+    shared_libs: [
+        "libbinder",
+        "libhwbinder",
+        "libmemunreachable",
+        "libutils",
+    ],
+}
diff --git a/libmemunreachable/Binder.cpp b/libmemunreachable/Binder.cpp
new file mode 100644
index 0000000..60512a3
--- /dev/null
+++ b/libmemunreachable/Binder.cpp
@@ -0,0 +1,80 @@
+/*
+ * 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.
+ */
+
+#include <sys/cdefs.h>
+#include <unistd.h>
+
+#include <functional>
+
+#include "Binder.h"
+#include "log.h"
+
+__BEGIN_DECLS
+
+// Weak undefined references to the symbols in libbinder and libhwbinder
+// so that libmemunreachable can call them in processes that have them
+// loaded without requiring libmemunreachable to have dependencies on them.
+ssize_t __attribute__((weak)) getBinderKernelReferences(size_t, uintptr_t*);
+ssize_t __attribute__((weak)) getHWBinderKernelReferences(size_t, uintptr_t*);
+
+__END_DECLS
+
+namespace android {
+
+static bool BinderReferencesToVector(allocator::vector<uintptr_t>& refs,
+                                     std::function<ssize_t(size_t, uintptr_t*)> fn) {
+  if (fn == nullptr) {
+    return true;
+  }
+
+  size_t size = refs.size();
+
+  do {
+    refs.resize(size);
+
+    ssize_t ret = fn(refs.size(), refs.data());
+    if (ret < 0) {
+      return false;
+    }
+
+    size = ret;
+  } while (size > refs.size());
+
+  refs.resize(size);
+  return true;
+}
+
+bool BinderReferences(allocator::vector<uintptr_t>& refs) {
+  refs.clear();
+
+  allocator::vector<uintptr_t> binder_refs{refs.get_allocator()};
+  if (BinderReferencesToVector(refs, getBinderKernelReferences)) {
+    refs.insert(refs.end(), binder_refs.begin(), binder_refs.end());
+  } else {
+    MEM_ALOGE("getBinderKernelReferences failed");
+  }
+
+  allocator::vector<uintptr_t> hwbinder_refs{refs.get_allocator()};
+  if (BinderReferencesToVector(hwbinder_refs, getHWBinderKernelReferences)) {
+    refs.insert(refs.end(), hwbinder_refs.begin(), hwbinder_refs.end());
+  } else {
+    MEM_ALOGE("getHWBinderKernelReferences failed");
+  }
+
+  return true;
+}
+
+}  // namespace android
diff --git a/libmemunreachable/Binder.h b/libmemunreachable/Binder.h
new file mode 100644
index 0000000..bf4fd3e
--- /dev/null
+++ b/libmemunreachable/Binder.h
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+
+#ifndef LIBMEMUNREACHABLE_BINDER_H_
+#define LIBMEMUNREACHABLE_BINDER_H_
+
+#include "Allocator.h"
+
+namespace android {
+
+bool BinderReferences(allocator::vector<uintptr_t>& refs);
+
+}  // namespace android
+
+#endif  // LIBMEMUNREACHABLE_BINDER_H_
diff --git a/libmemunreachable/MemUnreachable.cpp b/libmemunreachable/MemUnreachable.cpp
index a1f74c3..5e062fd 100644
--- a/libmemunreachable/MemUnreachable.cpp
+++ b/libmemunreachable/MemUnreachable.cpp
@@ -28,6 +28,7 @@
 #include <backtrace.h>
 
 #include "Allocator.h"
+#include "Binder.h"
 #include "HeapWalker.h"
 #include "Leak.h"
 #include "LeakFolding.h"
@@ -53,7 +54,8 @@
   MemUnreachable(pid_t pid, Allocator<void> allocator)
       : pid_(pid), allocator_(allocator), heap_walker_(allocator_) {}
   bool CollectAllocations(const allocator::vector<ThreadInfo>& threads,
-                          const allocator::vector<Mapping>& mappings);
+                          const allocator::vector<Mapping>& mappings,
+                          const allocator::vector<uintptr_t>& refs);
   bool GetUnreachableMemory(allocator::vector<Leak>& leaks, size_t limit, size_t* num_leaks,
                             size_t* leak_bytes);
   size_t Allocations() { return heap_walker_.Allocations(); }
@@ -82,7 +84,8 @@
 }
 
 bool MemUnreachable::CollectAllocations(const allocator::vector<ThreadInfo>& threads,
-                                        const allocator::vector<Mapping>& mappings) {
+                                        const allocator::vector<Mapping>& mappings,
+                                        const allocator::vector<uintptr_t>& refs) {
   MEM_ALOGI("searching process %d for allocations", pid_);
   allocator::vector<Mapping> heap_mappings{mappings};
   allocator::vector<Mapping> anon_mappings{mappings};
@@ -118,6 +121,8 @@
     heap_walker_.Root(thread_it->regs);
   }
 
+  heap_walker_.Root(refs);
+
   MEM_ALOGI("searching done");
 
   return true;
@@ -282,6 +287,7 @@
     ThreadCapture thread_capture(parent_pid, heap);
     allocator::vector<ThreadInfo> thread_info(heap);
     allocator::vector<Mapping> mappings(heap);
+    allocator::vector<uintptr_t> refs(heap);
 
     // ptrace all the threads
     if (!thread_capture.CaptureThreads()) {
@@ -301,6 +307,11 @@
       return 1;
     }
 
+    if (!BinderReferences(refs)) {
+      continue_parent_sem.Post();
+      return 1;
+    }
+
     // malloc must be enabled to call fork, at_fork handlers take the same
     // locks as ScopedDisableMalloc.  All threads are paused in ptrace, so
     // memory state is still consistent.  Unfreeze the original thread so it
@@ -326,7 +337,7 @@
 
       MemUnreachable unreachable{parent_pid, heap};
 
-      if (!unreachable.CollectAllocations(thread_info, mappings)) {
+      if (!unreachable.CollectAllocations(thread_info, mappings, refs)) {
         _exit(2);
       }
       size_t num_allocations = unreachable.Allocations();
diff --git a/libmemunreachable/tests/Binder_test.cpp b/libmemunreachable/tests/Binder_test.cpp
new file mode 100644
index 0000000..6e85d5a
--- /dev/null
+++ b/libmemunreachable/tests/Binder_test.cpp
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <signal.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <binder/Binder.h>
+#include <binder/IBinder.h>
+#include <binder/IServiceManager.h>
+#include <binder/Parcel.h>
+#include <binder/ProcessState.h>
+
+#include <gtest/gtest.h>
+
+#include "Allocator.h"
+#include "Binder.h"
+
+namespace android {
+
+static const String16 service_name("test.libmemunreachable_binder");
+
+class BinderService : public BBinder {
+ public:
+  BinderService() = default;
+  virtual ~BinderService() = default;
+
+  virtual status_t onTransact(uint32_t /*code*/, const Parcel& data, Parcel* reply,
+                              uint32_t /*flags*/ = 0) {
+    reply->writeStrongBinder(ref);
+    ref = data.readStrongBinder();
+    return 0;
+  }
+
+ private:
+  sp<IBinder> ref;
+};
+
+class BinderObject : public BBinder {
+ public:
+  BinderObject() = default;
+  ~BinderObject() = default;
+};
+
+class ServiceProcess {
+ public:
+  ServiceProcess() : child_(0) {}
+  ~ServiceProcess() { Stop(); }
+
+  bool Run() {
+    pid_t ret = fork();
+    if (ret < 0) {
+      return false;
+    } else if (ret == 0) {
+      // child
+      _exit(Service());
+    } else {
+      // parent
+      child_ = ret;
+      return true;
+    }
+  }
+
+  bool Stop() {
+    if (child_ > 0) {
+      if (kill(child_, SIGTERM)) {
+        return false;
+      }
+      int status = 0;
+      if (TEMP_FAILURE_RETRY(waitpid(child_, &status, 0)) != child_) {
+        return false;
+      }
+      child_ = 0;
+      return WIFEXITED(status) && WEXITSTATUS(status) == 0;
+    }
+
+    return true;
+  }
+
+  int Service() {
+    sp<ProcessState> proc{ProcessState::self()};
+    sp<IServiceManager> sm = defaultServiceManager();
+    if (sm == nullptr) {
+      fprintf(stderr, "Failed to get service manager\n");
+      return 1;
+    }
+    if (sm->addService(service_name, new BinderService()) != OK) {
+      fprintf(stderr, "Failed to add test service\n");
+      return 1;
+    }
+    proc->startThreadPool();
+    pause();
+    return 0;
+  }
+
+ private:
+  pid_t child_;
+};
+
+class BinderTest : public ::testing::Test {
+ protected:
+  ServiceProcess service_process_;
+};
+
+TEST_F(BinderTest, binder) {
+  ServiceProcess service_process;
+  ASSERT_TRUE(service_process.Run());
+
+  sp<IServiceManager> sm = defaultServiceManager();
+  ASSERT_TRUE(sm != nullptr);
+
+  // A small sleep allows the service to start, which
+  // prevents a longer sleep in getService.
+  usleep(100000);
+
+  sp<IBinder> service = sm->getService(service_name);
+  ASSERT_TRUE(service != nullptr);
+
+  sp<IBinder> binder{new BinderObject()};
+
+  Parcel send;
+  Parcel reply;
+
+  send.writeStrongBinder(binder);
+  status_t rv = service->transact(0, send, &reply);
+  ASSERT_EQ(static_cast<status_t>(OK), rv);
+
+  Heap heap;
+  allocator::vector<uintptr_t> refs{heap};
+
+  ASSERT_TRUE(BinderReferences(refs));
+
+  bool found_ref = false;
+  for (auto ref : refs) {
+    if (ref == reinterpret_cast<uintptr_t>(binder.get())) {
+      found_ref = true;
+    }
+  }
+
+  ASSERT_TRUE(found_ref);
+}
+
+}  // namespace android
diff --git a/libunwindstack/Android.bp b/libunwindstack/Android.bp
index a6044c8..e6d3bff 100644
--- a/libunwindstack/Android.bp
+++ b/libunwindstack/Android.bp
@@ -150,10 +150,10 @@
 }
 
 //-------------------------------------------------------------------------
-// Utility Executables
+// Tools
 //-------------------------------------------------------------------------
 cc_defaults {
-    name: "libunwindstack_executables",
+    name: "libunwindstack_tools",
     defaults: ["libunwindstack_flags"],
 
     shared_libs: [
@@ -161,18 +161,40 @@
         "libbase",
         "liblzma",
     ],
+}
 
-    static_libs: [
-        "liblog",
+cc_binary {
+    name: "unwind",
+    defaults: ["libunwindstack_tools"],
+
+    srcs: [
+        "tools/unwind.cpp",
     ],
+
+    target: {
+        linux: {
+            host_ldlibs: [
+                "-lrt",
+            ],
+        },
+    },
 }
 
 cc_binary {
     name: "unwind_info",
-    defaults: ["libunwindstack_executables"],
+    defaults: ["libunwindstack_tools"],
 
     srcs: [
-        "unwind_info.cpp",
+        "tools/unwind_info.cpp",
+    ],
+}
+
+cc_binary {
+    name: "unwind_symbols",
+    defaults: ["libunwindstack_tools"],
+
+    srcs: [
+        "tools/unwind_symbols.cpp",
     ],
 }
 
diff --git a/libunwindstack/tools/unwind.cpp b/libunwindstack/tools/unwind.cpp
new file mode 100644
index 0000000..34cd1ce
--- /dev/null
+++ b/libunwindstack/tools/unwind.cpp
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <errno.h>
+#include <inttypes.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ptrace.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <string>
+
+#include "Elf.h"
+#include "Maps.h"
+#include "Memory.h"
+#include "Regs.h"
+
+static bool Attach(pid_t pid) {
+  if (ptrace(PTRACE_ATTACH, pid, 0, 0) == -1) {
+    return false;
+  }
+
+  // Allow at least 1 second to attach properly.
+  for (size_t i = 0; i < 1000; i++) {
+    siginfo_t si;
+    if (ptrace(PTRACE_GETSIGINFO, pid, 0, &si) == 0) {
+      return true;
+    }
+    usleep(1000);
+  }
+  printf("%d: Failed to stop.\n", pid);
+  return false;
+}
+
+static bool Detach(pid_t pid) {
+  return ptrace(PTRACE_DETACH, pid, 0, 0) == 0;
+}
+
+void DoUnwind(pid_t pid) {
+  RemoteMaps remote_maps(pid);
+  if (!remote_maps.Parse()) {
+    printf("Failed to parse map data.\n");
+    return;
+  }
+
+  uint32_t machine_type;
+  Regs* regs = Regs::RemoteGet(pid, &machine_type);
+  if (regs == nullptr) {
+    printf("Unable to get remote reg data\n");
+    return;
+  }
+
+  bool bits32 = true;
+  printf("ABI: ");
+  switch (machine_type) {
+    case EM_ARM:
+      printf("arm");
+      break;
+    case EM_386:
+      printf("x86");
+      break;
+    case EM_AARCH64:
+      printf("arm64");
+      bits32 = false;
+      break;
+    case EM_X86_64:
+      printf("x86_64");
+      bits32 = false;
+      break;
+    default:
+      printf("unknown\n");
+      return;
+  }
+  printf("\n");
+
+  MemoryRemote remote_memory(pid);
+  for (size_t frame_num = 0; frame_num < 64; frame_num++) {
+    if (regs->pc() == 0) {
+      break;
+    }
+    MapInfo* map_info = remote_maps.Find(regs->pc());
+    if (map_info == nullptr) {
+      printf("Failed to find map data for the pc\n");
+      break;
+    }
+
+    Elf* elf = map_info->GetElf(pid, true);
+
+    uint64_t rel_pc = regs->GetRelPc(elf, map_info);
+    uint64_t adjusted_rel_pc = rel_pc;
+    // Don't need to adjust the first frame pc.
+    if (frame_num != 0) {
+      adjusted_rel_pc = regs->GetAdjustedPc(rel_pc, elf);
+    }
+
+    std::string name;
+    if (bits32) {
+      printf("  #%02zu pc %08" PRIx64, frame_num, adjusted_rel_pc);
+    } else {
+      printf("  #%02zu pc %016" PRIx64, frame_num, adjusted_rel_pc);
+    }
+    if (!map_info->name.empty()) {
+      printf("  %s", map_info->name.c_str());
+      if (map_info->elf_offset != 0) {
+        printf(" (offset 0x%" PRIx64 ")", map_info->elf_offset);
+      }
+    } else {
+      printf("  <anonymous:%" PRIx64 ">", map_info->offset);
+    }
+    uint64_t func_offset;
+    if (elf->GetFunctionName(adjusted_rel_pc, &name, &func_offset)) {
+      printf(" (%s", name.c_str());
+      if (func_offset != 0) {
+        printf("+%" PRId64, func_offset);
+      }
+      printf(")");
+    }
+    printf("\n");
+
+    if (!elf->Step(rel_pc + map_info->elf_offset, regs, &remote_memory)) {
+      break;
+    }
+  }
+}
+
+int main(int argc, char** argv) {
+  if (argc != 2) {
+    printf("Usage: unwind <PID>\n");
+    return 1;
+  }
+
+  pid_t pid = atoi(argv[1]);
+  if (!Attach(pid)) {
+    printf("Failed to attach to pid %d: %s\n", pid, strerror(errno));
+    return 1;
+  }
+
+  DoUnwind(pid);
+
+  Detach(pid);
+
+  return 0;
+}
diff --git a/libunwindstack/unwind_info.cpp b/libunwindstack/tools/unwind_info.cpp
similarity index 98%
rename from libunwindstack/unwind_info.cpp
rename to libunwindstack/tools/unwind_info.cpp
index da1522b..e889f8c 100644
--- a/libunwindstack/unwind_info.cpp
+++ b/libunwindstack/tools/unwind_info.cpp
@@ -21,8 +21,8 @@
 #include <stdio.h>
 #include <string.h>
 #include <sys/mman.h>
-#include <sys/types.h>
 #include <sys/stat.h>
+#include <sys/types.h>
 #include <unistd.h>
 
 #include "ArmExidx.h"
@@ -115,10 +115,6 @@
     printf("%s is not a regular file.\n", argv[1]);
     return 1;
   }
-  if (S_ISDIR(st.st_mode)) {
-    printf("%s is a directory.\n", argv[1]);
-    return 1;
-  }
 
   // Send all log messages to stdout.
   log_to_stdout(true);
diff --git a/libunwindstack/tools/unwind_symbols.cpp b/libunwindstack/tools/unwind_symbols.cpp
new file mode 100644
index 0000000..c010dfc
--- /dev/null
+++ b/libunwindstack/tools/unwind_symbols.cpp
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <errno.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "Elf.h"
+#include "ElfInterface.h"
+#include "Log.h"
+
+int main(int argc, char** argv) {
+  if (argc != 2) {
+    printf("Need to pass the name of an elf file to the program.\n");
+    return 1;
+  }
+
+  struct stat st;
+  if (stat(argv[1], &st) == -1) {
+    printf("Cannot stat %s: %s\n", argv[1], strerror(errno));
+    return 1;
+  }
+  if (!S_ISREG(st.st_mode)) {
+    printf("%s is not a regular file.\n", argv[1]);
+    return 1;
+  }
+
+  // Send all log messages to stdout.
+  log_to_stdout(true);
+
+  MemoryFileAtOffset* memory = new MemoryFileAtOffset;
+  if (!memory->Init(argv[1], 0)) {
+    printf("Failed to init\n");
+    return 1;
+  }
+
+  Elf elf(memory);
+  if (!elf.Init() || !elf.valid()) {
+    printf("%s is not a valid elf file.\n", argv[1]);
+    return 1;
+  }
+
+  switch (elf.machine_type()) {
+    case EM_ARM:
+      printf("ABI: arm\n");
+      break;
+    case EM_AARCH64:
+      printf("ABI: arm64\n");
+      break;
+    case EM_386:
+      printf("ABI: x86\n");
+      break;
+    case EM_X86_64:
+      printf("ABI: x86_64\n");
+      break;
+    default:
+      printf("ABI: unknown\n");
+      return 1;
+  }
+
+  // This is a crude way to get the symbols in order.
+  std::string name;
+  uint64_t load_bias = elf.interface()->load_bias();
+  for (const auto& entry : elf.interface()->pt_loads()) {
+    uint64_t start = entry.second.offset + load_bias;
+    uint64_t end = entry.second.table_size + load_bias;
+    for (uint64_t addr = start; addr < end; addr += 4) {
+      std::string cur_name;
+      uint64_t func_offset;
+      if (elf.GetFunctionName(addr, &cur_name, &func_offset)) {
+        if (cur_name != name) {
+          printf("<0x%" PRIx64 "> Function: %s\n", addr - func_offset, cur_name.c_str());
+        }
+        name = cur_name;
+      }
+    }
+  }
+
+  return 0;
+}