profile
viewpoint
If you are wondering where the data of this site comes from, please visit https://api.github.com/users/IrinaShkviro/events. GitMemory does not store any data, but only uses NGINX to cache data for a period of time. The idea behind GitMemory is simply to give users a better reading experience.
Irina IrinaShkviro @google Munich, Germany

pull request commentgoogle/orbit

Add and send some `MetadataEvent`s, show in new capture log, show some as message boxes

@pierricgimmig This is intended, the code for this is at the end of OrbitMainWindow::UpdateCaptureStateDependentWidgets. The reasoning is that the user has already seen the messages during the capture, when the log takes vertical space from the right but most of the data is on the left (unless you have instrumented many functions and you really really want to see all of them in the "Live" tab), while after the capture the messages are in most cases no longer interesting, and I would prefer to make space for all the data that is now on the right, without the user having the close the log every single time, which I believe would be very annoying.

I see your point. The closer the messages are to the end of the capture, the less likely the user will have time to see them however. Really not a huge deal. Eventually we could add an option to "pin" the log section.

dpallotti

comment created time in 10 hours

Pull request review commentgoogle/orbit

Add and send some `MetadataEvent`s, show in new capture log, show some as message boxes

 std::string OrbitApp::GetCaptureTime() const {   return GetPrettyTime(absl::Microseconds(time)); } +std::string OrbitApp::GetCaptureTimeAt(uint64_t timestamp_ns) const {+  const TimeGraph* time_graph = GetTimeGraph();+  if (time_graph == nullptr) {+    return GetPrettyTime(absl::ZeroDuration());+  }+  const uint64_t capture_min_timestamp_ns = time_graph->GetCaptureMin();+  if (timestamp_ns < capture_min_timestamp_ns) {+    return GetPrettyTime(absl::ZeroDuration());

The suggestion was to get more info on when the event occurred for debugging, but maybe we should be stricter here and actually CHECK since receiving an event from before the capture is an error on our side. Perhaps we could display the raw timestamp when we receive such events?

dpallotti

comment created time in 10 hours

Pull request review commentgoogle/orbit

Add and send some `MetadataEvent`s, show in new capture log, show some as message boxes

 void OrbitMainWindow::SetupTargetLabel() {                    }); } +void OrbitMainWindow::SetupStatusBarLogButton() {+  // The Qt Designer doesn't seem to support adding children to a StatusBar.+  auto* capture_log_widget = new QWidget(statusBar());  // NOLINT

Ah ok, thanks.

dpallotti

comment created time in 11 hours

Pull request review commentgoogle/orbit

Add and send some `MetadataEvent`s, show in new capture log, show some as message boxes

 message CaptureStarted {   CaptureOptions capture_options = 5; } +message WarningEvent {+  uint64 timestamp_ns = 1;+  // This is a string, we use bytes to avoid UTF-8 validation.+  bytes message = 2;+}++message InfoEvent {+  uint64 timestamp_ns = 1;+  // This is a string, we use bytes to avoid UTF-8 validation.+  bytes message = 2;+}++message ErrorEnablingOrbitApiEvent {+  uint64 timestamp_ns = 1;+  // This is a string, we use bytes to avoid UTF-8 validation.+  bytes message = 2;+}++message ClockResolutionEvent {+  uint64 timestamp_ns = 1;+  uint64 clock_resolution_ns = 2;+}++message ErrorsWithPerfEventOpenEvent {+  uint64 timestamp_ns = 1;+}

I was thinking of specifying in what context perf_event_open failed, i.e. while enabling sampling, scheduling, uprobes, etc. Maybe adding a string to the event would be enough, this way we could know exactly which events we would not receive in the capture.

dpallotti

comment created time in 11 hours

Pull request review commentgoogle/orbit

Create trampoline and instrument function.

 ErrorMessageOr<RelocatedInstruction> RelocateInstruction(cs_insn* instruction, u   return result; } +uint64_t GetMaxTrampolineSize() {+  // The maximum size of a trampoline is constant. So the calculation can be cached on first call.+  static const uint64_t trampoline_size = []() -> uint64_t {+    MachineCode unused_code;+    AppendBackupCode(unused_code);+    AppendPayloadCode(0 /* payload_address*/, 0 /* function address */, unused_code);+    AppendRestoreCode(unused_code);+    unused_code.AppendBytes(std::vector<uint8_t>(kMaxRelocatedPrologSize, 0));+    auto result =+        AppendJumpBackCode(0 /*address_after_prolog*/, 0 /*trampoline_address*/, unused_code);+    CHECK(!result.has_error());++    // Round up to the next multiple of 32 so we get aligned jump targets at the beginning of the+    // each trampoline.+    return static_cast<uint64_t>(((unused_code.GetResultAsVector().size() + 31) / 32) * 32);+  }();++  return trampoline_size;+}++ErrorMessageOr<uint64_t> CreateTrampoline(pid_t pid, uint64_t function_address,+                                          const std::vector<uint8_t>& function,+                                          uint64_t trampoline_address, uint64_t payload_address,+                                          csh capstone_handle,+                                          absl::flat_hash_map<uint64_t, uint64_t>& relocation_map) {+  MachineCode trampoline;+  // Add code to backup register state, execute the payload and restore the register state.+  AppendBackupCode(trampoline);+  AppendPayloadCode(payload_address, function_address, trampoline);+  AppendRestoreCode(trampoline);++  // Relocate prolog into trampoline.+  OUTCOME_TRY(address_after_prolog,+              AppendRelocatedPrologCode(function_address, function, trampoline_address,+                                        capstone_handle, relocation_map, trampoline));++  // Add code for jump from trampoline back into function.+  OUTCOME_TRY(AppendJumpBackCode(address_after_prolog, trampoline_address, trampoline));++  // Copy trampoline into tracee.+  auto write_result_or_error =+      WriteTraceesMemory(pid, trampoline_address, trampoline.GetResultAsVector());+  if (write_result_or_error.has_error()) {+    return write_result_or_error.error();+  }++  return address_after_prolog;+}++ErrorMessageOr<void> InstrumentFunction(pid_t pid, uint64_t function_address,

Sounds good, thanks.

danielfenner

comment created time in 11 hours

Pull request review commentgoogle/orbit

Remove intersecting modules on AddOrUpdateModuleInfo

 TEST(ProcessData, RemapModule) {   } } +class ProcessDataModuleIntersectionTest : public ::testing::Test {+ protected:+  void SetUp() override {+    ProcessInfo info;+    info.set_name(kProcessName);+    process_.SetProcessInfo(info);+    process_.UpdateModuleInfos(initial_mapping_);+  }++  static constexpr const char* kProcessName = "Test Name";++  static constexpr const char* kModulePath0 = "test/file/path0";+  static constexpr const char* kBuildId0 = "build_id0";+  static constexpr uint64_t kStartAddress0 = 50;+  static constexpr uint64_t kEndAddress0 = 100;++  static constexpr const char* kModulePath1 = "test/file/path1";+  static constexpr const char* kBuildId1 = "build_id1";+  static constexpr uint64_t kStartAddress1 = 100;+  static constexpr uint64_t kEndAddress1 = 200;++  static constexpr const char* kModulePath2 = "test/file/path2";+  static constexpr const char* kBuildId2 = "build_id2";+  static constexpr uint64_t kStartAddress2 = 200;+  static constexpr uint64_t kEndAddress2 = 300;++  static constexpr const char* kModulePath3 = "test/file/path3";+  static constexpr const char* kBuildId3 = "build_id3";+  static constexpr uint64_t kStartAddress3 = 300;+  static constexpr uint64_t kEndAddress3 = 400;++  static constexpr const char* kNewModulePath = "test/file/path";+  static constexpr const char* kNewBuildId = "build_id";++  static ModuleInfo CreateModule(const std::string& module_path, const std::string& build_id,+                                 uint64_t start_address, uint64_t end_address) {+    ModuleInfo module_info;+    module_info.set_file_path(module_path);+    module_info.set_build_id(build_id);+    module_info.set_address_start(start_address);+    module_info.set_address_end(end_address);+    return module_info;+  }++  const std::vector<ModuleInfo> initial_mapping_{+      CreateModule(kModulePath0, kBuildId0, kStartAddress0, kEndAddress0),+      CreateModule(kModulePath1, kBuildId1, kStartAddress1, kEndAddress1),+      CreateModule(kModulePath2, kBuildId2, kStartAddress2, kEndAddress2),+      CreateModule(kModulePath3, kBuildId3, kStartAddress3, kEndAddress3)};++  ProcessData process_;+};++TEST_F(ProcessDataModuleIntersectionTest, IntersectWithTwoModules) {+  EXPECT_EQ(process_.GetMemoryMapCopy().size(), 4);++  ModuleInfo intersecting_module = CreateModule(kNewModulePath, kNewBuildId, 150, 250);+  process_.AddOrUpdateModuleInfo(intersecting_module);++  EXPECT_EQ(process_.GetMemoryMapCopy().size(), 3);++  // Non intersecting modules are still there+  {+    const auto result = process_.FindModuleByAddress(kStartAddress0);+    ASSERT_THAT(result, HasNoError());+    EXPECT_EQ(result.value().file_path(), kModulePath0);+    EXPECT_EQ(result.value().build_id(), kBuildId0);+    EXPECT_EQ(result.value().start(), kStartAddress0);+    EXPECT_EQ(result.value().end(), kEndAddress0);+  }++  {+    const auto result = process_.FindModuleByAddress(kStartAddress3);+    ASSERT_THAT(result, HasNoError());+    EXPECT_EQ(result.value().file_path(), kModulePath3);+    EXPECT_EQ(result.value().build_id(), kBuildId3);+    EXPECT_EQ(result.value().start(), kStartAddress3);+    EXPECT_EQ(result.value().end(), kEndAddress3);+  }++  {+    // We can find the new module+    const auto result = process_.FindModuleByAddress(150);+    ASSERT_THAT(result, HasNoError());+    EXPECT_EQ(result.value().file_path(), kNewModulePath);+    EXPECT_EQ(result.value().build_id(), kNewBuildId);+    EXPECT_EQ(result.value().start(), 150);+    EXPECT_EQ(result.value().end(), 250);++    // Intersecting modules are gone+    EXPECT_THAT(process_.FindModuleByAddress(148), HasError("Unable to find module for address"));+    EXPECT_THAT(process_.FindModuleByAddress(250), HasError("Unable to find module for address"));+    EXPECT_THAT(process_.FindModuleByAddress(270), HasError("Unable to find module for address"));+  }+}++TEST_F(ProcessDataModuleIntersectionTest, IntersectWithTwoModulesWithMatchingBorders) {+  ModuleInfo intersecting_module = CreateModule(kNewModulePath, kNewBuildId, 100, 300);+  process_.AddOrUpdateModuleInfo(intersecting_module);++  EXPECT_EQ(process_.GetMemoryMapCopy().size(), 3);++  // Non intersecting modules are still there+  {+    const auto result = process_.FindModuleByAddress(kStartAddress0);+    ASSERT_THAT(result, HasNoError());+    EXPECT_EQ(result.value().file_path(), kModulePath0);+    EXPECT_EQ(result.value().build_id(), kBuildId0);+    EXPECT_EQ(result.value().start(), kStartAddress0);+    EXPECT_EQ(result.value().end(), kEndAddress0);+  }++  {+    const auto result = process_.FindModuleByAddress(kStartAddress3);+    ASSERT_THAT(result, HasNoError());+    EXPECT_EQ(result.value().file_path(), kModulePath3);+    EXPECT_EQ(result.value().build_id(), kBuildId3);+    EXPECT_EQ(result.value().start(), kStartAddress3);+    EXPECT_EQ(result.value().end(), kEndAddress3);+  }++  {+    // We can find the new module+    const auto result = process_.FindModuleByAddress(150);+    ASSERT_THAT(result, HasNoError());+    EXPECT_EQ(result.value().file_path(), kNewModulePath);+    EXPECT_EQ(result.value().build_id(), kNewBuildId);+    EXPECT_EQ(result.value().start(), 100);+    EXPECT_EQ(result.value().end(), 300);+  }+}++TEST_F(ProcessDataModuleIntersectionTest, FullyInsideAnotherModuleAddressRange) {+  // address range in fully inside another module address range+  ModuleInfo intersecting_module = CreateModule(kNewModulePath, kNewBuildId, 110, 190);+  process_.AddOrUpdateModuleInfo(intersecting_module);++  EXPECT_EQ(process_.GetMemoryMapCopy().size(), 4);++  // Non intersecting modules are still there+  {+    const auto result = process_.FindModuleByAddress(kStartAddress0);+    ASSERT_THAT(result, HasNoError());+    EXPECT_EQ(result.value().file_path(), kModulePath0);+    EXPECT_EQ(result.value().build_id(), kBuildId0);+    EXPECT_EQ(result.value().start(), kStartAddress0);+    EXPECT_EQ(result.value().end(), kEndAddress0);+  }++  {+    const auto result = process_.FindModuleByAddress(kStartAddress2);+    ASSERT_THAT(result, HasNoError());+    EXPECT_EQ(result.value().file_path(), kModulePath2);+    EXPECT_EQ(result.value().build_id(), kBuildId2);+    EXPECT_EQ(result.value().start(), kStartAddress2);+    EXPECT_EQ(result.value().end(), kEndAddress2);+  }++  {+    const auto result = process_.FindModuleByAddress(kStartAddress3);+    ASSERT_THAT(result, HasNoError());+    EXPECT_EQ(result.value().file_path(), kModulePath3);+    EXPECT_EQ(result.value().build_id(), kBuildId3);+    EXPECT_EQ(result.value().start(), kStartAddress3);+    EXPECT_EQ(result.value().end(), kEndAddress3);+  }++  {+    // We can find the new module+    const auto result = process_.FindModuleByAddress(150);+    ASSERT_THAT(result, HasNoError());+    EXPECT_EQ(result.value().file_path(), kNewModulePath);+    EXPECT_EQ(result.value().build_id(), kNewBuildId);+    EXPECT_EQ(result.value().start(), 110);+    EXPECT_EQ(result.value().end(), 190);++    // Original module is gone+    EXPECT_THAT(process_.FindModuleByAddress(108), HasError("Unable to find module for address"));+    EXPECT_THAT(process_.FindModuleByAddress(190), HasError("Unable to find module for address"));+  }+}++TEST_F(ProcessDataModuleIntersectionTest, OverlapsWithEverything) {+  // address range in fully inside another module address range

Likely a copy-paste leftover.

dimitry-

comment created time in 11 hours

Pull request review commentgoogle/orbit

Remove intersecting modules on AddOrUpdateModuleInfo

 TEST(ProcessData, RemapModule) {   } } +class ProcessDataModuleIntersectionTest : public ::testing::Test {+ protected:+  void SetUp() override {+    ProcessInfo info;+    info.set_name(kProcessName);+    process_.SetProcessInfo(info);+    process_.UpdateModuleInfos(initial_mapping_);+  }++  static constexpr const char* kProcessName = "Test Name";++  static constexpr const char* kModulePath0 = "test/file/path0";+  static constexpr const char* kBuildId0 = "build_id0";+  static constexpr uint64_t kStartAddress0 = 50;+  static constexpr uint64_t kEndAddress0 = 100;++  static constexpr const char* kModulePath1 = "test/file/path1";+  static constexpr const char* kBuildId1 = "build_id1";+  static constexpr uint64_t kStartAddress1 = 100;+  static constexpr uint64_t kEndAddress1 = 200;++  static constexpr const char* kModulePath2 = "test/file/path2";+  static constexpr const char* kBuildId2 = "build_id2";+  static constexpr uint64_t kStartAddress2 = 200;+  static constexpr uint64_t kEndAddress2 = 300;++  static constexpr const char* kModulePath3 = "test/file/path3";+  static constexpr const char* kBuildId3 = "build_id3";+  static constexpr uint64_t kStartAddress3 = 300;+  static constexpr uint64_t kEndAddress3 = 400;++  static constexpr const char* kNewModulePath = "test/file/path";+  static constexpr const char* kNewBuildId = "build_id";++  static ModuleInfo CreateModule(const std::string& module_path, const std::string& build_id,+                                 uint64_t start_address, uint64_t end_address) {+    ModuleInfo module_info;+    module_info.set_file_path(module_path);+    module_info.set_build_id(build_id);+    module_info.set_address_start(start_address);+    module_info.set_address_end(end_address);+    return module_info;+  }++  const std::vector<ModuleInfo> initial_mapping_{+      CreateModule(kModulePath0, kBuildId0, kStartAddress0, kEndAddress0),+      CreateModule(kModulePath1, kBuildId1, kStartAddress1, kEndAddress1),+      CreateModule(kModulePath2, kBuildId2, kStartAddress2, kEndAddress2),+      CreateModule(kModulePath3, kBuildId3, kStartAddress3, kEndAddress3)};++  ProcessData process_;+};++TEST_F(ProcessDataModuleIntersectionTest, IntersectWithTwoModules) {+  EXPECT_EQ(process_.GetMemoryMapCopy().size(), 4);++  ModuleInfo intersecting_module = CreateModule(kNewModulePath, kNewBuildId, 150, 250);+  process_.AddOrUpdateModuleInfo(intersecting_module);++  EXPECT_EQ(process_.GetMemoryMapCopy().size(), 3);++  // Non intersecting modules are still there+  {+    const auto result = process_.FindModuleByAddress(kStartAddress0);+    ASSERT_THAT(result, HasNoError());+    EXPECT_EQ(result.value().file_path(), kModulePath0);+    EXPECT_EQ(result.value().build_id(), kBuildId0);+    EXPECT_EQ(result.value().start(), kStartAddress0);+    EXPECT_EQ(result.value().end(), kEndAddress0);+  }++  {+    const auto result = process_.FindModuleByAddress(kStartAddress3);+    ASSERT_THAT(result, HasNoError());+    EXPECT_EQ(result.value().file_path(), kModulePath3);+    EXPECT_EQ(result.value().build_id(), kBuildId3);+    EXPECT_EQ(result.value().start(), kStartAddress3);+    EXPECT_EQ(result.value().end(), kEndAddress3);+  }++  {+    // We can find the new module+    const auto result = process_.FindModuleByAddress(150);+    ASSERT_THAT(result, HasNoError());+    EXPECT_EQ(result.value().file_path(), kNewModulePath);+    EXPECT_EQ(result.value().build_id(), kNewBuildId);+    EXPECT_EQ(result.value().start(), 150);+    EXPECT_EQ(result.value().end(), 250);++    // Intersecting modules are gone+    EXPECT_THAT(process_.FindModuleByAddress(148), HasError("Unable to find module for address"));+    EXPECT_THAT(process_.FindModuleByAddress(250), HasError("Unable to find module for address"));+    EXPECT_THAT(process_.FindModuleByAddress(270), HasError("Unable to find module for address"));+  }+}++TEST_F(ProcessDataModuleIntersectionTest, IntersectWithTwoModulesWithMatchingBorders) {+  ModuleInfo intersecting_module = CreateModule(kNewModulePath, kNewBuildId, 100, 300);+  process_.AddOrUpdateModuleInfo(intersecting_module);++  EXPECT_EQ(process_.GetMemoryMapCopy().size(), 3);++  // Non intersecting modules are still there+  {+    const auto result = process_.FindModuleByAddress(kStartAddress0);+    ASSERT_THAT(result, HasNoError());+    EXPECT_EQ(result.value().file_path(), kModulePath0);+    EXPECT_EQ(result.value().build_id(), kBuildId0);+    EXPECT_EQ(result.value().start(), kStartAddress0);+    EXPECT_EQ(result.value().end(), kEndAddress0);+  }++  {+    const auto result = process_.FindModuleByAddress(kStartAddress3);+    ASSERT_THAT(result, HasNoError());+    EXPECT_EQ(result.value().file_path(), kModulePath3);+    EXPECT_EQ(result.value().build_id(), kBuildId3);+    EXPECT_EQ(result.value().start(), kStartAddress3);+    EXPECT_EQ(result.value().end(), kEndAddress3);+  }++  {+    // We can find the new module+    const auto result = process_.FindModuleByAddress(150);+    ASSERT_THAT(result, HasNoError());+    EXPECT_EQ(result.value().file_path(), kNewModulePath);+    EXPECT_EQ(result.value().build_id(), kNewBuildId);+    EXPECT_EQ(result.value().start(), 100);+    EXPECT_EQ(result.value().end(), 300);+  }+}++TEST_F(ProcessDataModuleIntersectionTest, FullyInsideAnotherModuleAddressRange) {+  // address range in fully inside another module address range+  ModuleInfo intersecting_module = CreateModule(kNewModulePath, kNewBuildId, 110, 190);+  process_.AddOrUpdateModuleInfo(intersecting_module);++  EXPECT_EQ(process_.GetMemoryMapCopy().size(), 4);++  // Non intersecting modules are still there+  {+    const auto result = process_.FindModuleByAddress(kStartAddress0);+    ASSERT_THAT(result, HasNoError());+    EXPECT_EQ(result.value().file_path(), kModulePath0);+    EXPECT_EQ(result.value().build_id(), kBuildId0);+    EXPECT_EQ(result.value().start(), kStartAddress0);+    EXPECT_EQ(result.value().end(), kEndAddress0);+  }++  {+    const auto result = process_.FindModuleByAddress(kStartAddress2);+    ASSERT_THAT(result, HasNoError());+    EXPECT_EQ(result.value().file_path(), kModulePath2);+    EXPECT_EQ(result.value().build_id(), kBuildId2);+    EXPECT_EQ(result.value().start(), kStartAddress2);+    EXPECT_EQ(result.value().end(), kEndAddress2);+  }++  {+    const auto result = process_.FindModuleByAddress(kStartAddress3);+    ASSERT_THAT(result, HasNoError());+    EXPECT_EQ(result.value().file_path(), kModulePath3);+    EXPECT_EQ(result.value().build_id(), kBuildId3);+    EXPECT_EQ(result.value().start(), kStartAddress3);+    EXPECT_EQ(result.value().end(), kEndAddress3);+  }++  {+    // We can find the new module+    const auto result = process_.FindModuleByAddress(150);+    ASSERT_THAT(result, HasNoError());+    EXPECT_EQ(result.value().file_path(), kNewModulePath);+    EXPECT_EQ(result.value().build_id(), kNewBuildId);+    EXPECT_EQ(result.value().start(), 110);+    EXPECT_EQ(result.value().end(), 190);++    // Original module is gone+    EXPECT_THAT(process_.FindModuleByAddress(108), HasError("Unable to find module for address"));+    EXPECT_THAT(process_.FindModuleByAddress(190), HasError("Unable to find module for address"));+  }+}++TEST_F(ProcessDataModuleIntersectionTest, OverlapsWithEverything) {+  // address range in fully inside another module address range+  ModuleInfo intersecting_module = CreateModule(kNewModulePath, kNewBuildId, 10, 450);+  process_.AddOrUpdateModuleInfo(intersecting_module);++  EXPECT_EQ(process_.GetMemoryMapCopy().size(), 1);++  {+    const auto result = process_.FindModuleByAddress(kStartAddress0);+    ASSERT_THAT(result, HasNoError());+    EXPECT_EQ(result.value().file_path(), kNewModulePath);+    EXPECT_EQ(result.value().build_id(), kNewBuildId);+    EXPECT_EQ(result.value().start(), 10);+    EXPECT_EQ(result.value().end(), 450);+  }+}++TEST_F(ProcessDataModuleIntersectionTest, ReplaceFirstModule) {+  // address range in fully inside another module address range+  ModuleInfo intersecting_module = CreateModule(kNewModulePath, kNewBuildId, 10, 90);+  process_.AddOrUpdateModuleInfo(intersecting_module);++  EXPECT_EQ(process_.GetMemoryMapCopy().size(), 4);++  {+    const auto result = process_.FindModuleByAddress(50);+    ASSERT_THAT(result, HasNoError());+    EXPECT_EQ(result.value().file_path(), kNewModulePath);+    EXPECT_EQ(result.value().build_id(), kNewBuildId);+    EXPECT_EQ(result.value().start(), 10);+    EXPECT_EQ(result.value().end(), 90);++    // Original module is gone+    EXPECT_THAT(process_.FindModuleByAddress(90), HasError("Unable to find module for address"));+  }++  // Non intersecting modules are still there+  {+    const auto result = process_.FindModuleByAddress(kStartAddress1);+    ASSERT_THAT(result, HasNoError());+    EXPECT_EQ(result.value().file_path(), kModulePath1);+    EXPECT_EQ(result.value().build_id(), kBuildId1);+    EXPECT_EQ(result.value().start(), kStartAddress1);+    EXPECT_EQ(result.value().end(), kEndAddress1);+  }++  {+    const auto result = process_.FindModuleByAddress(kStartAddress2);+    ASSERT_THAT(result, HasNoError());+    EXPECT_EQ(result.value().file_path(), kModulePath2);+    EXPECT_EQ(result.value().build_id(), kBuildId2);+    EXPECT_EQ(result.value().start(), kStartAddress2);+    EXPECT_EQ(result.value().end(), kEndAddress2);+  }++  {+    const auto result = process_.FindModuleByAddress(kStartAddress3);+    ASSERT_THAT(result, HasNoError());+    EXPECT_EQ(result.value().file_path(), kModulePath3);+    EXPECT_EQ(result.value().build_id(), kBuildId3);+    EXPECT_EQ(result.value().start(), kStartAddress3);+    EXPECT_EQ(result.value().end(), kEndAddress3);+  }+}++TEST_F(ProcessDataModuleIntersectionTest, ReplaceLastModule) {+  // address range in fully inside another module address range

This doesn't apply to this test.

dimitry-

comment created time in 11 hours

Pull request review commentgoogle/orbit

Remove intersecting modules on AddOrUpdateModuleInfo

 TEST(ProcessData, RemapModule) {   } } +class ProcessDataModuleIntersectionTest : public ::testing::Test {+ protected:+  void SetUp() override {+    ProcessInfo info;+    info.set_name(kProcessName);+    process_.SetProcessInfo(info);+    process_.UpdateModuleInfos(initial_mapping_);+  }++  static constexpr const char* kProcessName = "Test Name";++  static constexpr const char* kModulePath0 = "test/file/path0";+  static constexpr const char* kBuildId0 = "build_id0";+  static constexpr uint64_t kStartAddress0 = 50;+  static constexpr uint64_t kEndAddress0 = 100;++  static constexpr const char* kModulePath1 = "test/file/path1";+  static constexpr const char* kBuildId1 = "build_id1";+  static constexpr uint64_t kStartAddress1 = 100;+  static constexpr uint64_t kEndAddress1 = 200;++  static constexpr const char* kModulePath2 = "test/file/path2";+  static constexpr const char* kBuildId2 = "build_id2";+  static constexpr uint64_t kStartAddress2 = 200;+  static constexpr uint64_t kEndAddress2 = 300;++  static constexpr const char* kModulePath3 = "test/file/path3";+  static constexpr const char* kBuildId3 = "build_id3";+  static constexpr uint64_t kStartAddress3 = 300;+  static constexpr uint64_t kEndAddress3 = 400;++  static constexpr const char* kNewModulePath = "test/file/path";+  static constexpr const char* kNewBuildId = "build_id";++  static ModuleInfo CreateModule(const std::string& module_path, const std::string& build_id,+                                 uint64_t start_address, uint64_t end_address) {+    ModuleInfo module_info;+    module_info.set_file_path(module_path);+    module_info.set_build_id(build_id);+    module_info.set_address_start(start_address);+    module_info.set_address_end(end_address);+    return module_info;+  }++  const std::vector<ModuleInfo> initial_mapping_{+      CreateModule(kModulePath0, kBuildId0, kStartAddress0, kEndAddress0),+      CreateModule(kModulePath1, kBuildId1, kStartAddress1, kEndAddress1),+      CreateModule(kModulePath2, kBuildId2, kStartAddress2, kEndAddress2),+      CreateModule(kModulePath3, kBuildId3, kStartAddress3, kEndAddress3)};++  ProcessData process_;+};++TEST_F(ProcessDataModuleIntersectionTest, IntersectWithTwoModules) {+  EXPECT_EQ(process_.GetMemoryMapCopy().size(), 4);++  ModuleInfo intersecting_module = CreateModule(kNewModulePath, kNewBuildId, 150, 250);+  process_.AddOrUpdateModuleInfo(intersecting_module);++  EXPECT_EQ(process_.GetMemoryMapCopy().size(), 3);++  // Non intersecting modules are still there+  {+    const auto result = process_.FindModuleByAddress(kStartAddress0);+    ASSERT_THAT(result, HasNoError());+    EXPECT_EQ(result.value().file_path(), kModulePath0);+    EXPECT_EQ(result.value().build_id(), kBuildId0);+    EXPECT_EQ(result.value().start(), kStartAddress0);+    EXPECT_EQ(result.value().end(), kEndAddress0);+  }++  {+    const auto result = process_.FindModuleByAddress(kStartAddress3);+    ASSERT_THAT(result, HasNoError());+    EXPECT_EQ(result.value().file_path(), kModulePath3);+    EXPECT_EQ(result.value().build_id(), kBuildId3);+    EXPECT_EQ(result.value().start(), kStartAddress3);+    EXPECT_EQ(result.value().end(), kEndAddress3);+  }++  {+    // We can find the new module+    const auto result = process_.FindModuleByAddress(150);+    ASSERT_THAT(result, HasNoError());+    EXPECT_EQ(result.value().file_path(), kNewModulePath);+    EXPECT_EQ(result.value().build_id(), kNewBuildId);+    EXPECT_EQ(result.value().start(), 150);+    EXPECT_EQ(result.value().end(), 250);++    // Intersecting modules are gone+    EXPECT_THAT(process_.FindModuleByAddress(148), HasError("Unable to find module for address"));+    EXPECT_THAT(process_.FindModuleByAddress(250), HasError("Unable to find module for address"));+    EXPECT_THAT(process_.FindModuleByAddress(270), HasError("Unable to find module for address"));+  }+}++TEST_F(ProcessDataModuleIntersectionTest, IntersectWithTwoModulesWithMatchingBorders) {+  ModuleInfo intersecting_module = CreateModule(kNewModulePath, kNewBuildId, 100, 300);+  process_.AddOrUpdateModuleInfo(intersecting_module);++  EXPECT_EQ(process_.GetMemoryMapCopy().size(), 3);++  // Non intersecting modules are still there+  {+    const auto result = process_.FindModuleByAddress(kStartAddress0);+    ASSERT_THAT(result, HasNoError());+    EXPECT_EQ(result.value().file_path(), kModulePath0);+    EXPECT_EQ(result.value().build_id(), kBuildId0);+    EXPECT_EQ(result.value().start(), kStartAddress0);+    EXPECT_EQ(result.value().end(), kEndAddress0);+  }++  {+    const auto result = process_.FindModuleByAddress(kStartAddress3);+    ASSERT_THAT(result, HasNoError());+    EXPECT_EQ(result.value().file_path(), kModulePath3);+    EXPECT_EQ(result.value().build_id(), kBuildId3);+    EXPECT_EQ(result.value().start(), kStartAddress3);+    EXPECT_EQ(result.value().end(), kEndAddress3);+  }++  {+    // We can find the new module+    const auto result = process_.FindModuleByAddress(150);+    ASSERT_THAT(result, HasNoError());+    EXPECT_EQ(result.value().file_path(), kNewModulePath);+    EXPECT_EQ(result.value().build_id(), kNewBuildId);+    EXPECT_EQ(result.value().start(), 100);+    EXPECT_EQ(result.value().end(), 300);+  }+}++TEST_F(ProcessDataModuleIntersectionTest, FullyInsideAnotherModuleAddressRange) {+  // address range in fully inside another module address range+  ModuleInfo intersecting_module = CreateModule(kNewModulePath, kNewBuildId, 110, 190);+  process_.AddOrUpdateModuleInfo(intersecting_module);++  EXPECT_EQ(process_.GetMemoryMapCopy().size(), 4);++  // Non intersecting modules are still there+  {+    const auto result = process_.FindModuleByAddress(kStartAddress0);+    ASSERT_THAT(result, HasNoError());+    EXPECT_EQ(result.value().file_path(), kModulePath0);+    EXPECT_EQ(result.value().build_id(), kBuildId0);+    EXPECT_EQ(result.value().start(), kStartAddress0);+    EXPECT_EQ(result.value().end(), kEndAddress0);+  }++  {+    const auto result = process_.FindModuleByAddress(kStartAddress2);+    ASSERT_THAT(result, HasNoError());+    EXPECT_EQ(result.value().file_path(), kModulePath2);+    EXPECT_EQ(result.value().build_id(), kBuildId2);+    EXPECT_EQ(result.value().start(), kStartAddress2);+    EXPECT_EQ(result.value().end(), kEndAddress2);+  }++  {+    const auto result = process_.FindModuleByAddress(kStartAddress3);+    ASSERT_THAT(result, HasNoError());+    EXPECT_EQ(result.value().file_path(), kModulePath3);+    EXPECT_EQ(result.value().build_id(), kBuildId3);+    EXPECT_EQ(result.value().start(), kStartAddress3);+    EXPECT_EQ(result.value().end(), kEndAddress3);+  }++  {+    // We can find the new module+    const auto result = process_.FindModuleByAddress(150);+    ASSERT_THAT(result, HasNoError());+    EXPECT_EQ(result.value().file_path(), kNewModulePath);+    EXPECT_EQ(result.value().build_id(), kNewBuildId);+    EXPECT_EQ(result.value().start(), 110);+    EXPECT_EQ(result.value().end(), 190);++    // Original module is gone+    EXPECT_THAT(process_.FindModuleByAddress(108), HasError("Unable to find module for address"));+    EXPECT_THAT(process_.FindModuleByAddress(190), HasError("Unable to find module for address"));+  }+}++TEST_F(ProcessDataModuleIntersectionTest, OverlapsWithEverything) {+  // address range in fully inside another module address range+  ModuleInfo intersecting_module = CreateModule(kNewModulePath, kNewBuildId, 10, 450);+  process_.AddOrUpdateModuleInfo(intersecting_module);++  EXPECT_EQ(process_.GetMemoryMapCopy().size(), 1);++  {+    const auto result = process_.FindModuleByAddress(kStartAddress0);+    ASSERT_THAT(result, HasNoError());+    EXPECT_EQ(result.value().file_path(), kNewModulePath);+    EXPECT_EQ(result.value().build_id(), kNewBuildId);+    EXPECT_EQ(result.value().start(), 10);+    EXPECT_EQ(result.value().end(), 450);+  }+}++TEST_F(ProcessDataModuleIntersectionTest, ReplaceFirstModule) {+  // address range in fully inside another module address range
  // Address range is fully inside the first module's address range
dimitry-

comment created time in 11 hours

Pull request review commentgoogle/orbit

Remove intersecting modules on AddOrUpdateModuleInfo

 class ProcessData final {   [[nodiscard]] const std::string& build_id() const;    void UpdateModuleInfos(absl::Span<const orbit_grpc_protos::ModuleInfo> module_infos);-  void AddOrUpdateModuleInfo(const orbit_grpc_protos::ModuleInfo& module_infos);++  // This method removes all modules with addresses intersecting with module_info.+  // Some background on this. The service currently does not send any unmap event+  // to the client, which means that if the client receives a module with mapping+  // intersecting with exiting mapping the old module was likely unloaded.

But could it happen that we have absolute addresses in the capture that refer to the unloaded module? Wouldn't it be a problem if we have removed the module from the mapping?

What would happen if we kept both? That then we wouldn't know how to treat absolute addresses that fall in the intersection hence making things even worse?

dimitry-

comment created time in 11 hours

Pull request review commentgoogle/orbit

Remove intersecting modules on AddOrUpdateModuleInfo

 TEST(ProcessData, RemapModule) {   } } +class ProcessDataModuleIntersectionTest : public ::testing::Test {+ protected:+  void SetUp() override {+    ProcessInfo info;+    info.set_name(kProcessName);+    process_.SetProcessInfo(info);+    process_.UpdateModuleInfos(initial_mapping_);+  }++  static constexpr const char* kProcessName = "Test Name";++  static constexpr const char* kModulePath0 = "test/file/path0";+  static constexpr const char* kBuildId0 = "build_id0";+  static constexpr uint64_t kStartAddress0 = 50;+  static constexpr uint64_t kEndAddress0 = 100;++  static constexpr const char* kModulePath1 = "test/file/path1";+  static constexpr const char* kBuildId1 = "build_id1";+  static constexpr uint64_t kStartAddress1 = 100;+  static constexpr uint64_t kEndAddress1 = 200;++  static constexpr const char* kModulePath2 = "test/file/path2";+  static constexpr const char* kBuildId2 = "build_id2";+  static constexpr uint64_t kStartAddress2 = 200;+  static constexpr uint64_t kEndAddress2 = 300;++  static constexpr const char* kModulePath3 = "test/file/path3";+  static constexpr const char* kBuildId3 = "build_id3";+  static constexpr uint64_t kStartAddress3 = 300;+  static constexpr uint64_t kEndAddress3 = 400;++  static constexpr const char* kNewModulePath = "test/file/path";+  static constexpr const char* kNewBuildId = "build_id";++  static ModuleInfo CreateModule(const std::string& module_path, const std::string& build_id,+                                 uint64_t start_address, uint64_t end_address) {+    ModuleInfo module_info;+    module_info.set_file_path(module_path);+    module_info.set_build_id(build_id);+    module_info.set_address_start(start_address);+    module_info.set_address_end(end_address);+    return module_info;+  }++  const std::vector<ModuleInfo> initial_mapping_{+      CreateModule(kModulePath0, kBuildId0, kStartAddress0, kEndAddress0),+      CreateModule(kModulePath1, kBuildId1, kStartAddress1, kEndAddress1),+      CreateModule(kModulePath2, kBuildId2, kStartAddress2, kEndAddress2),+      CreateModule(kModulePath3, kBuildId3, kStartAddress3, kEndAddress3)};++  ProcessData process_;+};++TEST_F(ProcessDataModuleIntersectionTest, IntersectWithTwoModules) {+  EXPECT_EQ(process_.GetMemoryMapCopy().size(), 4);++  ModuleInfo intersecting_module = CreateModule(kNewModulePath, kNewBuildId, 150, 250);+  process_.AddOrUpdateModuleInfo(intersecting_module);++  EXPECT_EQ(process_.GetMemoryMapCopy().size(), 3);++  // Non intersecting modules are still there+  {+    const auto result = process_.FindModuleByAddress(kStartAddress0);+    ASSERT_THAT(result, HasNoError());+    EXPECT_EQ(result.value().file_path(), kModulePath0);+    EXPECT_EQ(result.value().build_id(), kBuildId0);+    EXPECT_EQ(result.value().start(), kStartAddress0);+    EXPECT_EQ(result.value().end(), kEndAddress0);+  }++  {+    const auto result = process_.FindModuleByAddress(kStartAddress3);+    ASSERT_THAT(result, HasNoError());+    EXPECT_EQ(result.value().file_path(), kModulePath3);+    EXPECT_EQ(result.value().build_id(), kBuildId3);+    EXPECT_EQ(result.value().start(), kStartAddress3);+    EXPECT_EQ(result.value().end(), kEndAddress3);+  }++  {+    // We can find the new module+    const auto result = process_.FindModuleByAddress(150);+    ASSERT_THAT(result, HasNoError());+    EXPECT_EQ(result.value().file_path(), kNewModulePath);+    EXPECT_EQ(result.value().build_id(), kNewBuildId);+    EXPECT_EQ(result.value().start(), 150);+    EXPECT_EQ(result.value().end(), 250);++    // Intersecting modules are gone+    EXPECT_THAT(process_.FindModuleByAddress(148), HasError("Unable to find module for address"));+    EXPECT_THAT(process_.FindModuleByAddress(250), HasError("Unable to find module for address"));+    EXPECT_THAT(process_.FindModuleByAddress(270), HasError("Unable to find module for address"));+  }+}++TEST_F(ProcessDataModuleIntersectionTest, IntersectWithTwoModulesWithMatchingBorders) {+  ModuleInfo intersecting_module = CreateModule(kNewModulePath, kNewBuildId, 100, 300);+  process_.AddOrUpdateModuleInfo(intersecting_module);++  EXPECT_EQ(process_.GetMemoryMapCopy().size(), 3);++  // Non intersecting modules are still there+  {+    const auto result = process_.FindModuleByAddress(kStartAddress0);+    ASSERT_THAT(result, HasNoError());+    EXPECT_EQ(result.value().file_path(), kModulePath0);+    EXPECT_EQ(result.value().build_id(), kBuildId0);+    EXPECT_EQ(result.value().start(), kStartAddress0);+    EXPECT_EQ(result.value().end(), kEndAddress0);+  }++  {+    const auto result = process_.FindModuleByAddress(kStartAddress3);+    ASSERT_THAT(result, HasNoError());+    EXPECT_EQ(result.value().file_path(), kModulePath3);+    EXPECT_EQ(result.value().build_id(), kBuildId3);+    EXPECT_EQ(result.value().start(), kStartAddress3);+    EXPECT_EQ(result.value().end(), kEndAddress3);+  }++  {+    // We can find the new module+    const auto result = process_.FindModuleByAddress(150);+    ASSERT_THAT(result, HasNoError());+    EXPECT_EQ(result.value().file_path(), kNewModulePath);+    EXPECT_EQ(result.value().build_id(), kNewBuildId);+    EXPECT_EQ(result.value().start(), 100);+    EXPECT_EQ(result.value().end(), 300);+  }+}++TEST_F(ProcessDataModuleIntersectionTest, FullyInsideAnotherModuleAddressRange) {+  // address range in fully inside another module address range
  // Address range is fully inside another module's address range
dimitry-

comment created time in 11 hours

Pull request review commentgoogle/orbit

Move captures and presets from `%appdata%\OrbitProfiler` to `Documents\Orbit`

 std::filesystem::path CreateOrGetOrbitAppDataDir() {   return path; } +static std::filesystem::path GetDocumentsPath() {+#ifdef WIN32+  PWSTR ppszPath;+  HRESULT result = SHGetKnownFolderPath(FOLDERID_Documents, 0, nullptr, &ppszPath);++  if (result != S_OK) {+    CoTaskMemFree(ppszPath);+    LPTSTR lpBuffer = nullptr;+    FormatMessage(

Done.

dpallotti

comment created time in 11 hours

Pull request review commentgoogle/orbit

Add and send some `MetadataEvent`s, show in new capture log, show some as message boxes

 void OrbitApp::OnModulesSnapshot(uint64_t /*timestamp_ns*/, std::vector<ModuleIn   main_thread_executor_->Schedule([this]() { FireRefreshCallbacks(DataViewType::kLiveFunctions); }); } +void OrbitApp::OnMetadataEvent(const orbit_grpc_protos::MetadataEvent& metadata_event) {+  main_thread_executor_->Schedule([this, metadata_event]() {+    switch (metadata_event.event_case()) {+      case orbit_grpc_protos::MetadataEvent::kWarningEvent: {+        main_window_->AppendToCaptureLog(+            MainWindowInterface::CaptureLogSeverity::kWarning,+            GetCaptureTimeAt(metadata_event.warning_event().timestamp_ns()),+            metadata_event.warning_event().message());+      } break;++      case orbit_grpc_protos::MetadataEvent::kInfoEvent: {+        main_window_->AppendToCaptureLog(+            MainWindowInterface::CaptureLogSeverity::kInfo,+            GetCaptureTimeAt(metadata_event.info_event().timestamp_ns()),+            metadata_event.info_event().message());+      } break;++      case orbit_grpc_protos::MetadataEvent::kErrorEnablingOrbitApiEvent: {+        main_window_->AppendToCaptureLog(+            MainWindowInterface::CaptureLogSeverity::kSevereWarning,+            GetCaptureTimeAt(metadata_event.error_enabling_orbit_api_event().timestamp_ns()),+            metadata_event.error_enabling_orbit_api_event().message());++        if (!IsLoadingCapture()) {+          constexpr const char* kDontShowAgainErrorEnablingOrbitApiWarningKey =+              "DontShowAgainErrorEnablingOrbitApiWarning";+          main_window_->ShowWarningWithDontShowAgainCheckboxIfNeeded(+              "Could not enable Orbit API",+              metadata_event.error_enabling_orbit_api_event().message(),+              kDontShowAgainErrorEnablingOrbitApiWarningKey);+        }+      } break;++      case orbit_grpc_protos::MetadataEvent::kClockResolutionEvent: {+        constexpr uint64_t kClockResolutionWarningThresholdNs = 10 * 1000;+        uint64_t timestamp_ns = metadata_event.clock_resolution_event().timestamp_ns();+        const uint64_t clock_resolution_ns =+            metadata_event.clock_resolution_event().clock_resolution_ns();+        if (clock_resolution_ns == 0) {+          main_window_->AppendToCaptureLog(MainWindowInterface::CaptureLogSeverity::kSevereWarning,+                                           GetCaptureTimeAt(timestamp_ns),+                                           "Failed to estimate clock resolution.");+        } else if (clock_resolution_ns < kClockResolutionWarningThresholdNs) {+          main_window_->AppendToCaptureLog(+              MainWindowInterface::CaptureLogSeverity::kInfo, GetCaptureTimeAt(timestamp_ns),+              absl::StrFormat("Clock resolution is %u ns.", clock_resolution_ns));+        } else {+          std::string message =+              absl::StrFormat("Clock resolution is high (%u ns): some timings may be inaccurate.",+                              clock_resolution_ns);+          main_window_->AppendToCaptureLog(MainWindowInterface::CaptureLogSeverity::kSevereWarning,+                                           GetCaptureTimeAt(timestamp_ns), message);++          if (!IsLoadingCapture()) {+            constexpr const char* kDontShowAgainHighClockResolutionWarningKey =+                "DontShowAgainHighClockResolutionWarning";+            main_window_->ShowWarningWithDontShowAgainCheckboxIfNeeded(+                "High clock resolution", message, kDontShowAgainHighClockResolutionWarningKey);+          }+        }+      } break;

does this want to be a function?

dpallotti

comment created time in 12 hours

Pull request review commentgoogle/orbit

Add and send some `MetadataEvent`s, show in new capture log, show some as message boxes

 OrbitApp::~OrbitApp() { }  void OrbitApp::OnCaptureFinished(const CaptureFinished& capture_finished) {-  if (capture_finished.status() == CaptureFinished::kFailed) {-    SendErrorToUi("Capture Failed", capture_finished.error_message());-    ERROR("Capture Finished with error: %s", capture_finished.error_message());-  }+  main_thread_executor_->Schedule([this, capture_finished]() {+    switch (capture_finished.status()) {+      case orbit_grpc_protos::CaptureFinished::kSuccessful: {+        main_window_->AppendToCaptureLog(MainWindowInterface::CaptureLogSeverity::kInfo,+                                         GetCaptureTime(), "Capture finished.");+      } break;+      case orbit_grpc_protos::CaptureFinished::kFailed: {+        SendErrorToUi("Capture Failed", capture_finished.error_message());+        ERROR("Capture Finished with error: %s", capture_finished.error_message());+        main_window_->AppendToCaptureLog(+            MainWindowInterface::CaptureLogSeverity::kError, GetCaptureTime(),+            absl::StrFormat("Capture finished with error: %s.", capture_finished.error_message()));+      } break;+      case orbit_grpc_protos::+          CaptureFinished_Status_CaptureFinished_Status_INT_MIN_SENTINEL_DO_NOT_USE_:+        [[fallthrough]];

I think in case of completely empty case we do not want to use [[fallthrough]], it actually makes it harder to read.

I would reserve it only for cases where there is some work to be done and after that fallthrought is intended.

dpallotti

comment created time in 12 hours

Pull request review commentgoogle/orbit

Add and send some `MetadataEvent`s, show in new capture log, show some as message boxes

 namespace { ErrorMessageOr<absl::flat_hash_map<std::string, ModuleInfo>> GetModulesByPathForPid(int32_t pid) {   OUTCOME_TRY(module_infos, orbit_object_utils::ReadModules(pid));   absl::flat_hash_map<std::string, ModuleInfo> result;-  for (const ModuleInfo& module_info : module_infos) {+  for (ModuleInfo& module_info : module_infos) {

interesting. I would have expected compiler to complain on move here.

dpallotti

comment created time in 12 hours

Pull request review commentgoogle/orbit

Add and send some `MetadataEvent`s, show in new capture log, show some as message boxes

 class MainWindowInterface {                                const std::string& assembly,                                orbit_code_report::DisassemblyReport report) = 0; +  enum class CaptureLogSeverity { kInfo, kWarning, kSevereWarning, kError };+  virtual void AppendToCaptureLog(CaptureLogSeverity severity, std::string_view capture_time,

Done.

dpallotti

comment created time in 12 hours

Pull request review commentgoogle/orbit

Add and send some `MetadataEvent`s, show in new capture log, show some as message boxes

 std::string OrbitApp::GetCaptureTime() const {   return GetPrettyTime(absl::Microseconds(time)); } +std::string OrbitApp::GetCaptureTimeAt(uint64_t timestamp_ns) const {+  const TimeGraph* time_graph = GetTimeGraph();+  if (time_graph == nullptr) {+    return GetPrettyTime(absl::ZeroDuration());+  }+  const uint64_t capture_min_timestamp_ns = time_graph->GetCaptureMin();+  if (timestamp_ns < capture_min_timestamp_ns) {+    return GetPrettyTime(absl::ZeroDuration());

Hmm, I don't think we should have show negative times. It can happen here if GetCaptureMin() is still returning std::numeric_limits<uint64_t>::max(), but ideally we should always have non-negative times here (it otherwise indicates an error on our side).

dpallotti

comment created time in 12 hours

Pull request review commentgoogle/orbit

Move captures and presets from `%appdata%\OrbitProfiler` to `Documents\Orbit`

 std::filesystem::path CreateOrGetOrbitAppDataDir() {   return path; } +static std::filesystem::path GetDocumentsPath() {+#ifdef WIN32+  PWSTR ppszPath;+  HRESULT result = SHGetKnownFolderPath(FOLDERID_Documents, 0, nullptr, &ppszPath);

I don't think that exists.

dpallotti

comment created time in 12 hours

pull request commentgoogle/orbit

Add and send some `MetadataEvent`s, show in new capture log, show some as message boxes

the capture log automatically disappears on capture end when we automatically switch to the sampling tab

@pierricgimmig This is intended, the code for this is at the end of OrbitMainWindow::UpdateCaptureStateDependentWidgets. The reasoning is that the user has already seen the messages during the capture, when the log takes vertical space from the right but most of the data is on the left (unless you have instrumented many functions and you want to see all of them in the "Live" tab), while after the capture the messages are in most cases no longer interesting, and I would prefer to make space for all the data that is now on the right, without the user having the close the log every single time, which I believe would be very annoying.

dpallotti

comment created time in 12 hours

Pull request review commentgoogle/orbit

Add and send some `MetadataEvent`s, show in new capture log, show some as message boxes

 message CaptureStarted {   CaptureOptions capture_options = 5; } +message WarningEvent {+  uint64 timestamp_ns = 1;+  // This is a string, we use bytes to avoid UTF-8 validation.+  bytes message = 2;+}++message InfoEvent {+  uint64 timestamp_ns = 1;+  // This is a string, we use bytes to avoid UTF-8 validation.+  bytes message = 2;+}++message ErrorEnablingOrbitApiEvent {+  uint64 timestamp_ns = 1;+  // This is a string, we use bytes to avoid UTF-8 validation.+  bytes message = 2;+}++message ClockResolutionEvent {+  uint64 timestamp_ns = 1;+  uint64 clock_resolution_ns = 2;+}++message ErrorsWithPerfEventOpenEvent {+  uint64 timestamp_ns = 1;+}

We can pass an additional message or enum, but what level of detail do you have in mind? What class of events failed to open (e.g., sampling, dynamic instrumentation), or more specific? One problem with being specific is that basically every times there's one failure, there are many failure, so I feel like we would return either only the first one, or a ton of them.

dpallotti

comment created time in 12 hours

Pull request review commentgoogle/orbit

Add and send some `MetadataEvent`s, show in new capture log, show some as message boxes

 void OrbitMainWindow::SetupTargetLabel() {                    }); } +void OrbitMainWindow::SetupStatusBarLogButton() {+  // The Qt Designer doesn't seem to support adding children to a StatusBar.+  auto* capture_log_widget = new QWidget(statusBar());  // NOLINT

clang-tidy complains (rightly so) that the result of new is not passed to an owner.

dpallotti

comment created time in 12 hours

Pull request review commentgoogle/orbit

Move captures and presets from `%appdata%\OrbitProfiler` to `Documents\Orbit`

 std::filesystem::path CreateOrGetOrbitAppDataDir() {   return path; } +static std::filesystem::path GetDocumentsPath() {+#ifdef WIN32+  PWSTR ppszPath;+  HRESULT result = SHGetKnownFolderPath(FOLDERID_Documents, 0, nullptr, &ppszPath);++  if (result != S_OK) {+    CoTaskMemFree(ppszPath);+    LPTSTR lpBuffer = nullptr;+    FormatMessage(

FormatMessageA?

dpallotti

comment created time in 12 hours

Pull request review commentgoogle/orbit

Move captures and presets from `%appdata%\OrbitProfiler` to `Documents\Orbit`

 std::filesystem::path CreateOrGetOrbitAppDataDir() {   return path; } +static std::filesystem::path GetDocumentsPath() {+#ifdef WIN32+  PWSTR ppszPath;+  HRESULT result = SHGetKnownFolderPath(FOLDERID_Documents, 0, nullptr, &ppszPath);

SHGetKnownFolderPathA?

dpallotti

comment created time in 12 hours

PR opened google/orbit

Move captures and presets from %appdata%\OrbitProfiler to Documents\Orbit

Reverts the revert of https://github.com/google/orbit/pull/2379, fixes a bug with using https://github.com/google/orbit/pull/2441, uses SHGetKnownFolderPath to get the path to Documents, and makes some visual improvements.

+464 -7

0 comment

10 changed files

pr created time in 13 hours

Pull request review commentgoogle/orbit

Create trampoline and instrument function.

 TEST_F(RelocateInstructionTest, TrivialTranslation) {   EXPECT_FALSE(result.value().position_of_absolute_address.has_value()); } +class InstrumentFunctionTest : public testing::Test {+ protected:+  void SetUp() override {+    // Init Capstone disassembler.+    cs_err error_code = cs_open(CS_ARCH_X86, CS_MODE_64, &capstone_handle_);+    CHECK(error_code == CS_ERR_OK);+    error_code = cs_option(capstone_handle_, CS_OPT_DETAIL, CS_OPT_ON);+    CHECK(error_code == CS_ERR_OK);++    max_trampoline_size_ = GetMaxTrampolineSize();+  }++  void RunChild(int (*function_pointer)(), std::string_view function_name) {+    function_name_ = function_name;++    pid_ = fork();+    CHECK(pid_ != -1);+    if (pid_ == 0) {+      uint64_t sum = 0;+      while (true) {+        sum += (*function_pointer)();+      }+    }+  }++  AddressRange GetFunctionAddressRangeOrDie() {+    auto modules = orbit_object_utils::ReadModules(pid_);+    CHECK(!modules.has_error());+    std::string module_file_path;+    AddressRange address_range_code(0, 0);+    for (const auto& m : modules.value()) {+      if (m.name() == "UserSpaceInstrumentationTests") {+        module_file_path = m.file_path();+        address_range_code.start = m.address_start();+        address_range_code.end = m.address_end();+      }+    }+    CHECK(!module_file_path.empty());+    auto elf_file = orbit_object_utils::CreateElfFile(module_file_path);+    CHECK(!elf_file.has_error());+    auto syms = elf_file.value()->LoadDebugSymbols();+    CHECK(!syms.has_error());+    uint64_t address = 0;+    uint64_t size = 0;+    for (const auto& sym : syms.value().symbol_infos()) {+      if (sym.name() == function_name_) {+        address = sym.address() + address_range_code.start - syms.value().load_bias();+        size = sym.size();+      }+    }+    return {address, address + size};+  }++  void PrepareInstrumentation(std::string_view payload_function_name) {+    // Stop the child process using our tooling.+    CHECK(AttachAndStopProcess(pid_).has_value());++    // Inject the payload for the instrumentation.+    const std::string kLibName = "libUserSpaceInstrumentationTestLib.so";+    const std::string library_path = orbit_base::GetExecutableDir() / ".." / "lib" / kLibName;+    auto library_handle_or_error = DlopenInTracee(pid_, library_path, RTLD_NOW);+    CHECK(library_handle_or_error.has_value());+    void* library_handle = library_handle_or_error.value();+    auto payload_function_address_or_error =+        DlsymInTracee(pid_, library_handle, payload_function_name);+    CHECK(payload_function_address_or_error.has_value());+    payload_function_address_ = absl::bit_cast<uint64_t>(payload_function_address_or_error.value());++    // Get address of the function to instrument.+    const AddressRange address_range_code = GetFunctionAddressRangeOrDie();+    function_address_ = address_range_code.start;+    const uint64_t size_of_function = address_range_code.end - address_range_code.start;++    // Get memory for the trampoline.+    auto trampoline_or_error =+        AllocateMemoryForTrampolines(pid_, address_range_code, max_trampoline_size_);+    CHECK(!trampoline_or_error.has_error());+    trampoline_address_ = trampoline_or_error.value();++    // Copy the beginning of the function over into this process.+    constexpr uint64_t kMaxFunctionPrologBackupSize = 20;+    const uint64_t bytes_to_copy = std::min(size_of_function, kMaxFunctionPrologBackupSize);+    ErrorMessageOr<std::vector<uint8_t>> function_backup =+        ReadTraceesMemory(pid_, function_address_, bytes_to_copy);+    CHECK(function_backup.has_value());+    function_code_ = function_backup.value();+  }++  // Runs the child for a millisecond to assert it is still working fine, stops it, removes the+  // instrumentation, restarts and stops it again.+  void RestartAndRemoveInstrumentation() {+    MoveInstructionPointersOutOfOverwrittenCode(pid_, relocation_map_);++    CHECK(!DetachAndContinueProcess(pid_).has_error());+    std::this_thread::sleep_for(std::chrono::milliseconds(1));+    CHECK(AttachAndStopProcess(pid_).has_value());++    auto write_result_or_error = WriteTraceesMemory(pid_, function_address_, function_code_);+    CHECK(!write_result_or_error.has_error());++    CHECK(!DetachAndContinueProcess(pid_).has_error());+    std::this_thread::sleep_for(std::chrono::milliseconds(1));+    CHECK(AttachAndStopProcess(pid_).has_value());+  }++  void TearDown() override {+    cs_close(&capstone_handle_);++    // Detach and end child.+    CHECK(!DetachAndContinueProcess(pid_).has_error());+    kill(pid_, SIGKILL);+    waitpid(pid_, NULL, 0);+  }++  pid_t pid_;+  cs_insn* instruction_ = nullptr;+  csh capstone_handle_ = 0;+  uint64_t max_trampoline_size_ = 0;+  uint64_t trampoline_address_;+  uint64_t payload_function_address_;+  absl::flat_hash_map<uint64_t, uint64_t> relocation_map_;++  std::string function_name_;+  uint64_t function_address_;+  std::vector<uint8_t> function_code_;+};++// Function with an ordinary compiler synthesised prolog; performs some arithmetics. Most real world+// functions will look like this (starting with pushing the stack frame...). Most functions below+// are declared "naked", i.e. without the prolog and implemented entirely in assembly. This is done+// to also cover edge cases.+extern "C" int DoSomething() {+  std::random_device rd;+  std::mt19937 gen(rd());+  std::uniform_int_distribution<int> dis(1, 6);+  std::vector<int> v(10);+  std::generate(v.begin(), v.end(), [&]() { return dis(gen); });+  int sum = std::accumulate(v.begin(), v.end(), 0);+  return sum;+}++TEST_F(InstrumentFunctionTest, DoSomething) {+  RunChild(&DoSomething, "DoSomething");+  PrepareInstrumentation("TrivialLog");+  ErrorMessageOr<uint64_t> address_after_prolog_or_error =+      CreateTrampoline(pid_, function_address_, function_code_, trampoline_address_,+                       payload_function_address_, capstone_handle_, relocation_map_);+  EXPECT_THAT(address_after_prolog_or_error, HasNoError());+  ErrorMessageOr<void> result = InstrumentFunction(+      pid_, function_address_, address_after_prolog_or_error.value(), trampoline_address_);+  EXPECT_THAT(result, HasNoError());+  RestartAndRemoveInstrumentation();+}++// We will not be able to instrument this - the function is just four bytes long and we need five+// bytes to write a jump.+extern "C" __attribute__((naked)) int TooShort() {+  __asm__ __volatile__(+      "nop \n\t"+      "nop \n\t"+      "nop \n\t"+      "ret \n\t"+      :+      :+      :);+}++TEST_F(InstrumentFunctionTest, TooShort) {+  RunChild(&TooShort, "TooShort");+  PrepareInstrumentation("TrivialLog");+  ErrorMessageOr<uint64_t> result =+      CreateTrampoline(pid_, function_address_, function_code_, trampoline_address_,+                       payload_function_address_, capstone_handle_, relocation_map_);+  EXPECT_THAT(result, HasError("Unable to disassemble enough of the function to instrument it"));+  RestartAndRemoveInstrumentation();+}++// This function is just long enough to be instrumented (five bytes). It is also interesting in that+// the return statement is copied into the trampoline and executed from there.+extern "C" __attribute__((naked)) int LongEnough() {+  __asm__ __volatile__(+      "nop \n\t"+      "nop \n\t"+      "nop \n\t"+      "nop \n\t"+      "ret \n\t"+      :+      :+      :);+}++TEST_F(InstrumentFunctionTest, LongEnough) {+  RunChild(&LongEnough, "LongEnough");+  PrepareInstrumentation("TrivialLog");+  ErrorMessageOr<uint64_t> address_after_prolog_or_error =+      CreateTrampoline(pid_, function_address_, function_code_, trampoline_address_,+                       payload_function_address_, capstone_handle_, relocation_map_);+  EXPECT_THAT(address_after_prolog_or_error, HasNoError());+  ErrorMessageOr<void> result = InstrumentFunction(+      pid_, function_address_, address_after_prolog_or_error.value(), trampoline_address_);+  EXPECT_THAT(result, HasNoError());+  RestartAndRemoveInstrumentation();+}++// The rip relative address is translated to the new code position.+extern "C" __attribute__((naked)) int RipRelativeAddressing() {+  __asm__ __volatile__(+      "movq 0x03(%%rip), %%rax\n\t"+      "nop \n\t"+      "nop \n\t"+      "ret \n\t"+      ".quad 0x0102034200000000 \n\t"+      :+      :+      :);+}++TEST_F(InstrumentFunctionTest, RipRelativeAddressing) {+  RunChild(&RipRelativeAddressing, "RipRelativeAddressing");+  PrepareInstrumentation("TrivialLog");+  ErrorMessageOr<uint64_t> address_after_prolog_or_error =+      CreateTrampoline(pid_, function_address_, function_code_, trampoline_address_,+                       payload_function_address_, capstone_handle_, relocation_map_);+  EXPECT_THAT(address_after_prolog_or_error, HasNoError());+  ErrorMessageOr<void> result = InstrumentFunction(+      pid_, function_address_, address_after_prolog_or_error.value(), trampoline_address_);+  EXPECT_THAT(result, HasNoError());+  RestartAndRemoveInstrumentation();+}++// Unconditional jump to a 8 bit offset.+extern "C" __attribute__((naked)) int UnconditionalJump8BitOffset() {+  __asm__ __volatile__(+      "jmp label_unconditional_jmp_8_bit \n\t"+      "nop \n\t"+      "nop \n\t"+      "nop \n\t"+      "label_unconditional_jmp_8_bit: \n\t"+      "ret \n\t"+      :+      :+      :);+}++TEST_F(InstrumentFunctionTest, UnconditionalJump8BitOffset) {+  RunChild(&UnconditionalJump8BitOffset, "UnconditionalJump8BitOffset");+  PrepareInstrumentation("TrivialLog");+  ErrorMessageOr<uint64_t> address_after_prolog_or_error =+      CreateTrampoline(pid_, function_address_, function_code_, trampoline_address_,+                       payload_function_address_, capstone_handle_, relocation_map_);+  EXPECT_THAT(address_after_prolog_or_error, HasNoError());+  ErrorMessageOr<void> result = InstrumentFunction(+      pid_, function_address_, address_after_prolog_or_error.value(), trampoline_address_);+  EXPECT_THAT(result, HasNoError());+  RestartAndRemoveInstrumentation();+}++// Unconditional jump to a 32 bit offset.+extern "C" __attribute__((naked)) int UnconditionalJump32BitOffset() {+  __asm__ __volatile__(+      "jmp label_unconditional_jmp_32_bit \n\t"+      ".octa 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 \n\t"  // 256 bytes of zeros+      "label_unconditional_jmp_32_bit: \n\t"+      "ret \n\t"+      :+      :+      :);+}++TEST_F(InstrumentFunctionTest, UnconditionalJump32BitOffset) {+  RunChild(&UnconditionalJump32BitOffset, "UnconditionalJump32BitOffset");+  PrepareInstrumentation("TrivialLog");+  ErrorMessageOr<uint64_t> address_after_prolog_or_error =+      CreateTrampoline(pid_, function_address_, function_code_, trampoline_address_,+                       payload_function_address_, capstone_handle_, relocation_map_);+  EXPECT_THAT(address_after_prolog_or_error, HasNoError());+  ErrorMessageOr<void> result = InstrumentFunction(+      pid_, function_address_, address_after_prolog_or_error.value(), trampoline_address_);+  EXPECT_THAT(result, HasNoError());+  RestartAndRemoveInstrumentation();+}++// Call function at relative offset.+extern "C" __attribute__((naked)) int CallFunction() {+  __asm__ __volatile__(+      "call function_label\n\t"+      "ret \n\t"+      "function_label:\n\t"+      "nop \n\t"+      "ret \n\t"+      :+      :+      :);+}++TEST_F(InstrumentFunctionTest, CallFunction) {+  RunChild(&CallFunction, "CallFunction");+  PrepareInstrumentation("TrivialLog");+  ErrorMessageOr<uint64_t> address_after_prolog_or_error =+      CreateTrampoline(pid_, function_address_, function_code_, trampoline_address_,+                       payload_function_address_, capstone_handle_, relocation_map_);+  EXPECT_THAT(address_after_prolog_or_error, HasNoError());+  ErrorMessageOr<void> result = InstrumentFunction(+      pid_, function_address_, address_after_prolog_or_error.value(), trampoline_address_);+  EXPECT_THAT(result, HasNoError());+  RestartAndRemoveInstrumentation();+}++// The rip relative address is translated to the new code position.+extern "C" __attribute__((naked)) int ConditionalJump8BitOffset() {+  __asm__ __volatile__(+      "loop_label_jcc: \n\t"+      "xor %%eax, %%eax \n\t"+      "jnz loop_label_jcc \n\t"+      "nop \n\t"+      "nop \n\t"+      "ret \n\t"+      :+      :+      :);+}++TEST_F(InstrumentFunctionTest, ConditionalJump8BitOffset) {+  RunChild(&ConditionalJump8BitOffset, "ConditionalJump8BitOffset");+  PrepareInstrumentation("TrivialLog");+  ErrorMessageOr<uint64_t> address_after_prolog_or_error =+      CreateTrampoline(pid_, function_address_, function_code_, trampoline_address_,+                       payload_function_address_, capstone_handle_, relocation_map_);+  EXPECT_THAT(address_after_prolog_or_error, HasNoError());+  ErrorMessageOr<void> result = InstrumentFunction(+      pid_, function_address_, address_after_prolog_or_error.value(), trampoline_address_);+  EXPECT_THAT(result, HasNoError());+  RestartAndRemoveInstrumentation();+}++// The rip relative address is translated to the new code position.+extern "C" __attribute__((naked)) int ConditionalJump32BitOffset() {+  __asm__ __volatile__(+      "xor %%eax, %%eax \n\t"+      "jnz label_jcc_32_bit \n\t"+      "nop \n\t"+      "ret \n\t"+      ".octa 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 \n\t"  // 256 bytes of zeros+      "label_jcc_32_bit: \n\t"+      "ret \n\t"+      :+      :+      :);+}++TEST_F(InstrumentFunctionTest, ConditionalJump32BitOffset) {+  RunChild(&ConditionalJump32BitOffset, "ConditionalJump32BitOffset");+  PrepareInstrumentation("TrivialLog");+  ErrorMessageOr<uint64_t> address_after_prolog_or_error =+      CreateTrampoline(pid_, function_address_, function_code_, trampoline_address_,+                       payload_function_address_, capstone_handle_, relocation_map_);+  EXPECT_THAT(address_after_prolog_or_error, HasNoError());+  ErrorMessageOr<void> result = InstrumentFunction(+      pid_, function_address_, address_after_prolog_or_error.value(), trampoline_address_);+  EXPECT_THAT(result, HasNoError());+  RestartAndRemoveInstrumentation();+}++// Function can not be instrumented since it uses the unsupported loop instruction.+extern "C" __attribute__((naked)) int Loop() {+  __asm__ __volatile__(+      "mov $42, %%cx\n\t"+      "loop_label:\n\t"+      "loopnz loop_label\n\t"+      "ret \n\t"+      :+      :+      :);+}++TEST_F(InstrumentFunctionTest, Loop) {+  RunChild(&Loop, "Loop");+  PrepareInstrumentation("TrivialLog");+  ErrorMessageOr<uint64_t> result =+      CreateTrampoline(pid_, function_address_, function_code_, trampoline_address_,+                       payload_function_address_, capstone_handle_, relocation_map_);+  EXPECT_THAT(result, HasError("Relocating a loop instruction is not supported."));+  RestartAndRemoveInstrumentation();+}++// Check-fails if any parameter is not zero.+extern "C" int CheckIntParameters(u_int64_t p0, u_int64_t p1, u_int64_t p2, u_int64_t p3,+                                  u_int64_t p4, u_int64_t p5, u_int64_t p6, u_int64_t p7) {+  CHECK(p0 == 0 && p1 == 0 && p2 == 0 && p3 == 0 && p4 == 0 && p5 == 0 && p6 == 0 && p7 == 0);+  return 0;+}++// This test and the two tests below check for proper handling of parameters handed of the+// instrumented function. The payload that is called before the instrumented function is executed+// clobbers

Well played, well played...

danielfenner

comment created time in 13 hours

Pull request review commentgoogle/orbit

Create trampoline and instrument function.

 struct RelocatedInstruction {                                                                        uint64_t old_address,                                                                        uint64_t new_address); +// Strictly speaking the max tempoline size is a compile time constant, but we prefer to compute it+// here since this captures every change to the code constructing the trampoline.+[[nodiscard]] uint64_t GetMaxTrampolineSize();++// Creates a trampoline for the function at `function_address`. The trampoline is build at+// `trampoline_address`. The trampoline will call `payload_address` with `function_address` as a

What I meant is that AppendPayloadCode in Trampoline.cpp suggests that with "payload" you also mean the code in the trampoline that calls the payload, but from your comment it doesn't seem that that's intended. Apart from that, "payload" is slightly generic, it would be nice to find a term that hints better at "function called from the trampoline", but alas I don't have ideas. So yeah, that was my point. Maybe at least a comment on AppendPayloadCode (in addition to this one) could help.

danielfenner

comment created time in 13 hours

Pull request review commentgoogle/orbit

Improve memory track visualization with the stacked graph

 void GraphTrack::DrawLabel(Batcher& batcher, TextRenderer& text_renderer, Vec2 t   }    text_renderer.AddText(text.c_str(), text_box_position[0],-                        text_box_position[1] + layout_->GetTextOffset(), z, text_color, font_size,-                        text_box_size[0]);+                        text_box_position[1] + text_box_size[1] - layout_->GetTextBoxHeight() ++                            layout_->GetTextOffset(),+                        z, text_color, font_size, text_box_size[0]); } -void GraphTrack::AddValue(double value, uint64_t time) {-  values_[time] = value;-  max_ = std::max(max_, value);-  min_ = std::min(min_, value);-  value_range_ = max_ - min_;--  if (value_range_ > 0) inv_value_range_ = 1.0 / value_range_;-}+template <size_t Dimension>+void GraphTrack<Dimension>::DrawLegend(Batcher& batcher, TextRenderer& text_renderer,+                                       const std::array<std::string, Dimension>& series_names,+                                       const Color& legend_text_color, float z) {+  uint32_t font_size = layout_->CalculateZoomedFontSize(); -std::optional<std::pair<uint64_t, double>> GraphTrack::GetPreviousValueAndTime(-    uint64_t time) const {-  auto iterator_lower = values_.upper_bound(time);-  if (iterator_lower == values_.begin()) {-    return {};+  const float kSpaceBetweenLegendSymbolAndText = layout_->GetGenericFixedSpacerWidth();+  const float kSpaceBetweenLegendEntries = layout_->GetGenericFixedSpacerWidth() * 2;+  float legend_symbol_height = GetLegendHeight() / 2.f;+  float legend_symbol_width = legend_symbol_height;+  float x0 = pos_[0] + layout_->GetRightMargin();+  float y0 = pos_[1] - layout_->GetTrackTabHeight() - layout_->GetTextBoxHeight() / 2.f;++  for (size_t i = 0; i < Dimension; ++i) {+    batcher.AddShadedBox(Vec2(x0, y0 - legend_symbol_height / 2.f),+                         Vec2(legend_symbol_width, legend_symbol_height), z,+                         TimeGraph::GetColor(i));+    x0 += legend_symbol_width + kSpaceBetweenLegendSymbolAndText;++    float legend_text_width = text_renderer.GetStringWidth(series_names[i].c_str(), font_size);+    Vec2 legend_text_box_size(legend_text_width, layout_->GetTextBoxHeight());+    Vec2 legend_text_box_position(x0, y0 - layout_->GetTextBoxHeight() / 2.f);+    text_renderer.AddText(series_names[i].c_str(), legend_text_box_position[0],+                          legend_text_box_position[1] + layout_->GetTextOffset(), z,+                          legend_text_color, font_size, legend_text_box_size[0]);+    x0 += legend_text_width + kSpaceBetweenLegendEntries;   }-  --iterator_lower;-  return *iterator_lower;-}--float GraphTrack::GetHeight() const {-  float height = layout_->GetTrackTabHeight() + layout_->GetTextBoxHeight() +-                 layout_->GetSpaceBetweenTracksAndThread() + layout_->GetEventTrackHeight() +-                 layout_->GetTrackBottomMargin();-  return height; } -void GraphTrack::SetLabelUnitWhenEmpty(const std::string& label_unit) {-  if (!label_unit_.empty()) return;-  label_unit_ = label_unit;-}--void GraphTrack::SetValueDecimalDigitsWhenEmpty(uint8_t value_decimal_digits) {-  if (value_decimal_digits_.has_value()) return;-  value_decimal_digits_ = value_decimal_digits;-}+template <size_t Dimension>+void GraphTrack<Dimension>::DrawSeries(Batcher* batcher, uint64_t min_tick, uint64_t max_tick,+                                       float z) {+  auto entries_within_time_range = series_.GetEntriesInTimeRange(min_tick, max_tick);+  if (!entries_within_time_range.has_value()) return;++  // Draw rectanges according to the time series entries located in [min_tick, max_tick].+  double min = series_.min_series_value();+  double inverse_value_range = series_.inverse_value_range();+  auto it = entries_within_time_range.value().begin;+  uint64_t previous_time = it->first;+  std::array<float, Dimension> previous_normalized_values;+  std::transform(it->second.begin(), it->second.end(), previous_normalized_values.begin(),+                 [min, inverse_value_range](double value) {+                   return static_cast<float>((value - min) * inverse_value_range);+                 });++  for (++it; it != entries_within_time_range.value().end; ++it) {+    uint64_t current_time = it->first;+    DrawSingleSeriesEntry(batcher, previous_time, current_time, previous_normalized_values, z);++    previous_time = current_time;+    std::transform(it->second.begin(), it->second.end(), previous_normalized_values.begin(),+                   [min, inverse_value_range](double value) {+                     return static_cast<float>((value - min) * inverse_value_range);+                   });+  } -void GraphTrack::OnTimer(const orbit_client_protos::TimerInfo& timer_info) {-  constexpr uint32_t kDepth = 0;-  std::shared_ptr<TimerChain> timer_chain = timers_[kDepth];-  if (timer_chain == nullptr) {-    timer_chain = std::make_shared<TimerChain>();-    timers_[kDepth] = timer_chain;+  uint64_t time_of_first_entry_within_time_range = entries_within_time_range.value().begin->first;+  if (min_tick > series_.StartTimeInNs() && min_tick < time_of_first_entry_within_time_range) {+    const std::array<double, Dimension>& values = series_.GetValuesOfPreviousOrFirstEntry(min_tick);+    std::transform(values.begin(), values.end(), previous_normalized_values.begin(),+                   [min, inverse_value_range](double value) {+                     return static_cast<float>((value - min) * inverse_value_range);+                   });+    DrawSingleSeriesEntry(batcher, min_tick, time_of_first_entry_within_time_range,+                          previous_normalized_values, z);   }

Thanks Anton! I like your suggestion. But I'm not sure about the name GetEntriesInTimeRange.

For example if the time series contains three entries with timestamp 0, 10, and 20. And our time range is [5, 15]. From the method name it seems to return only the entry with timestamp 10, but actually it returns all three entires.

vickyliu-go4it

comment created time in 13 hours

Pull request review commentgoogle/orbit

Create trampoline and instrument function.

 ErrorMessageOr<RelocatedInstruction> RelocateInstruction(cs_insn* instruction, u   return result; } +uint64_t GetMaxTrampolineSize() {+  // The maximum size of a trampoline is constant. So the calculation can be cached on first call.+  static const uint64_t trampoline_size = []() -> uint64_t {+    MachineCode unused_code;+    AppendBackupCode(unused_code);+    AppendPayloadCode(0 /* payload_address*/, 0 /* function address */, unused_code);+    AppendRestoreCode(unused_code);+    unused_code.AppendBytes(std::vector<uint8_t>(kMaxRelocatedPrologSize, 0));+    auto result =+        AppendJumpBackCode(0 /*address_after_prolog*/, 0 /*trampoline_address*/, unused_code);+    CHECK(!result.has_error());++    // Round up to the next multiple of 32 so we get aligned jump targets at the beginning of the+    // each trampoline.+    return static_cast<uint64_t>(((unused_code.GetResultAsVector().size() + 31) / 32) * 32);+  }();++  return trampoline_size;+}++ErrorMessageOr<uint64_t> CreateTrampoline(pid_t pid, uint64_t function_address,+                                          const std::vector<uint8_t>& function,+                                          uint64_t trampoline_address, uint64_t payload_address,+                                          csh capstone_handle,+                                          absl::flat_hash_map<uint64_t, uint64_t>& relocation_map) {+  MachineCode trampoline;+  // Add code to backup register state, execute the payload and restore the register state.+  AppendBackupCode(trampoline);+  AppendPayloadCode(payload_address, function_address, trampoline);+  AppendRestoreCode(trampoline);++  // Relocate prolog into trampoline.+  OUTCOME_TRY(address_after_prolog,+              AppendRelocatedPrologCode(function_address, function, trampoline_address,+                                        capstone_handle, relocation_map, trampoline));++  // Add code for jump from trampoline back into function.+  OUTCOME_TRY(AppendJumpBackCode(address_after_prolog, trampoline_address, trampoline));++  // Copy trampoline into tracee.+  auto write_result_or_error =+      WriteTraceesMemory(pid, trampoline_address, trampoline.GetResultAsVector());+  if (write_result_or_error.has_error()) {+    return write_result_or_error.error();+  }++  return address_after_prolog;+}++ErrorMessageOr<void> InstrumentFunction(pid_t pid, uint64_t function_address,+                                        uint64_t address_after_prolog,+                                        uint64_t trampoline_address) {+  MachineCode jump;+  jump.AppendBytes({0xe9});+  ErrorMessageOr<int32_t> offset_or_error =+      AddressDifferenceAsInt32(trampoline_address, function_address + kSizeOfJmp);+  // This should not happen since the trampoline is allocated such that it is located in the +-2GB+  // range of the instrumented code.+  if (offset_or_error.has_error()) {+    return ErrorMessage(absl::StrFormat(+        "Unable to jump from instrumented function into trampoline since the locations are more "+        "then +-2GB apart. function_address: %#x trampoline_address: %#x",+        function_address, trampoline_address));+  }+  jump.AppendImmediate32(offset_or_error.value());+  // Overwrite the remaining byte to the next instruction with 'nop's. This is not strictly needed+  // but helps with debugging/disassembling.+  while (jump.GetResultAsVector().size() < address_after_prolog - function_address) {+    jump.AppendBytes({0x90});+  }+  auto write_result_or_error = WriteTraceesMemory(pid, function_address, jump.GetResultAsVector());+  if (write_result_or_error.has_error()) {+    return write_result_or_error.error();+  }+  return outcome::success();+}++void MoveInstructionPointersOutOfOverwrittenCode(+    pid_t pid, const absl::flat_hash_map<uint64_t, uint64_t>& relocation_map) {+  std::vector<pid_t> tids = orbit_base::GetTidsOfProcess(pid);+  for (pid_t tid : tids) {+    RegisterState registers;+    ErrorMessageOr<void> backup_or_error = registers.BackupRegisters(tid);+    FAIL_IF(backup_or_error.has_error(),+            "Failed to read registers in MoveInstructionPointersOutOfOverwrittenCode: /'%s/'",

done

danielfenner

comment created time in 13 hours

Pull request review commentgoogle/orbit

Create trampoline and instrument function.

 ErrorMessageOr<RelocatedInstruction> RelocateInstruction(cs_insn* instruction, u   return result; } +uint64_t GetMaxTrampolineSize() {+  // The maximum size of a trampoline is constant. So the calculation can be cached on first call.+  static const uint64_t trampoline_size = []() -> uint64_t {+    MachineCode unused_code;+    AppendBackupCode(unused_code);+    AppendPayloadCode(0 /* payload_address*/, 0 /* function address */, unused_code);+    AppendRestoreCode(unused_code);+    unused_code.AppendBytes(std::vector<uint8_t>(kMaxRelocatedPrologSize, 0));+    auto result =+        AppendJumpBackCode(0 /*address_after_prolog*/, 0 /*trampoline_address*/, unused_code);+    CHECK(!result.has_error());++    // Round up to the next multiple of 32 so we get aligned jump targets at the beginning of the+    // each trampoline.+    return static_cast<uint64_t>(((unused_code.GetResultAsVector().size() + 31) / 32) * 32);+  }();++  return trampoline_size;+}++ErrorMessageOr<uint64_t> CreateTrampoline(pid_t pid, uint64_t function_address,+                                          const std::vector<uint8_t>& function,+                                          uint64_t trampoline_address, uint64_t payload_address,+                                          csh capstone_handle,+                                          absl::flat_hash_map<uint64_t, uint64_t>& relocation_map) {+  MachineCode trampoline;+  // Add code to backup register state, execute the payload and restore the register state.+  AppendBackupCode(trampoline);+  AppendPayloadCode(payload_address, function_address, trampoline);+  AppendRestoreCode(trampoline);++  // Relocate prolog into trampoline.+  OUTCOME_TRY(address_after_prolog,+              AppendRelocatedPrologCode(function_address, function, trampoline_address,+                                        capstone_handle, relocation_map, trampoline));++  // Add code for jump from trampoline back into function.+  OUTCOME_TRY(AppendJumpBackCode(address_after_prolog, trampoline_address, trampoline));++  // Copy trampoline into tracee.+  auto write_result_or_error =+      WriteTraceesMemory(pid, trampoline_address, trampoline.GetResultAsVector());+  if (write_result_or_error.has_error()) {+    return write_result_or_error.error();+  }++  return address_after_prolog;+}++ErrorMessageOr<void> InstrumentFunction(pid_t pid, uint64_t function_address,+                                        uint64_t address_after_prolog,+                                        uint64_t trampoline_address) {+  MachineCode jump;+  jump.AppendBytes({0xe9});+  ErrorMessageOr<int32_t> offset_or_error =+      AddressDifferenceAsInt32(trampoline_address, function_address + kSizeOfJmp);+  // This should not happen since the trampoline is allocated such that it is located in the +-2GB+  // range of the instrumented code.+  if (offset_or_error.has_error()) {+    return ErrorMessage(absl::StrFormat(+        "Unable to jump from instrumented function into trampoline since the locations are more "+        "then +-2GB apart. function_address: %#x trampoline_address: %#x",+        function_address, trampoline_address));+  }+  jump.AppendImmediate32(offset_or_error.value());+  // Overwrite the remaining byte to the next instruction with 'nop's. This is not strictly needed

done

danielfenner

comment created time in 13 hours

Pull request review commentgoogle/orbit

Create trampoline and instrument function.

  #include "UserSpaceInstrumentationTestLib.h" +#include <stdio.h>++#include <chrono>+ int TrivialFunction() { return 42; }  uint64_t TrivialSum(uint64_t p0, uint64_t p1, uint64_t p2, uint64_t p3, uint64_t p4, uint64_t p5) {   return p0 + p1 + p2 + p3 + p4 + p5; }++// rdi, rsi, rdx, rcx, r8, r9, rax, r10+void ClobberParameterRegisters(uint64_t) {+  __asm__ __volatile__(+      "mov $0xffffffffffffffff, %%rdi\n\t"+      "mov $0xffffffffffffffff, %%rsi\n\t"+      "mov $0xffffffffffffffff, %%rdx\n\t"+      "mov $0xffffffffffffffff, %%rcx\n\t"+      "mov $0xffffffffffffffff, %%r8\n\t"+      "mov $0xffffffffffffffff, %%r9\n\t"+      "mov $0xffffffffffffffff, %%rax\n\t"+      "mov $0xffffffffffffffff, %%r10\n\t"+      :+      :+      :);+}++void ClobberXmmRegisters(uint64_t) {+  __asm__ __volatile__(+      "movdqu 0x3a(%%rip), %%xmm0\n\t"+      "movdqu 0x32(%%rip), %%xmm1\n\t"+      "movdqu 0x2a(%%rip), %%xmm2\n\t"+      "movdqu 0x22(%%rip), %%xmm3\n\t"+      "movdqu 0x1a(%%rip), %%xmm4\n\t"+      "movdqu 0x12(%%rip), %%xmm5\n\t"+      "movdqu 0x0a(%%rip), %%xmm6\n\t"+      "movdqu 0x02(%%rip), %%xmm7\n\t"+      "jmp .+0x12 \n\t"+      ".quad 0xffffffffffffffff, 0xffffffffffffffff \n\t"+      :+      :+      :);+}++void ClobberYmmRegisters(uint64_t) {+  __asm__ __volatile__(+      "vmovdqu 0x3a(%%rip), %%ymm0\n\t"+      "vmovdqu 0x32(%%rip), %%ymm1\n\t"+      "vmovdqu 0x2a(%%rip), %%ymm2\n\t"+      "vmovdqu 0x22(%%rip), %%ymm3\n\t"+      "vmovdqu 0x1a(%%rip), %%ymm4\n\t"+      "vmovdqu 0x12(%%rip), %%ymm5\n\t"+      "vmovdqu 0x0a(%%rip), %%ymm6\n\t"+      "vmovdqu 0x02(%%rip), %%ymm7\n\t"+      "jmp .+0x22 \n\t"+      ".quad 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff \n\t"+      :+      :+      :);+}++void TrivialLog(uint64_t function_address) {+  using std::chrono::system_clock;+  constexpr std::chrono::duration<int, std::ratio<1, 1000000>> k500Microseconds(500);+  static system_clock::time_point last_logged_event = system_clock::now() - 2 * k500Microseconds;+  static uint64_t skipped = 0;+  // Rate limit log output to once every three milliseconds.

Thanks, guess what I used before ...

danielfenner

comment created time in 13 hours

Pull request review commentgoogle/orbit

Create trampoline and instrument function.

 TEST_F(RelocateInstructionTest, TrivialTranslation) {   EXPECT_FALSE(result.value().position_of_absolute_address.has_value()); } +class InstrumentFunctionTest : public testing::Test {+ protected:+  void SetUp() override {+    // Init Capstone disassembler.+    cs_err error_code = cs_open(CS_ARCH_X86, CS_MODE_64, &capstone_handle_);+    CHECK(error_code == CS_ERR_OK);+    error_code = cs_option(capstone_handle_, CS_OPT_DETAIL, CS_OPT_ON);+    CHECK(error_code == CS_ERR_OK);++    max_trampoline_size_ = GetMaxTrampolineSize();+  }++  void RunChild(int (*function_pointer)(), std::string_view function_name) {+    function_name_ = function_name;++    pid_ = fork();+    CHECK(pid_ != -1);+    if (pid_ == 0) {+      uint64_t sum = 0;+      while (true) {+        sum += (*function_pointer)();+      }+    }+  }++  AddressRange GetFunctionAddressRangeOrDie() {+    auto modules = orbit_object_utils::ReadModules(pid_);+    CHECK(!modules.has_error());+    std::string module_file_path;+    AddressRange address_range_code(0, 0);+    for (const auto& m : modules.value()) {+      if (m.name() == "UserSpaceInstrumentationTests") {+        module_file_path = m.file_path();+        address_range_code.start = m.address_start();+        address_range_code.end = m.address_end();+      }+    }+    CHECK(!module_file_path.empty());+    auto elf_file = orbit_object_utils::CreateElfFile(module_file_path);+    CHECK(!elf_file.has_error());+    auto syms = elf_file.value()->LoadDebugSymbols();+    CHECK(!syms.has_error());+    uint64_t address = 0;+    uint64_t size = 0;+    for (const auto& sym : syms.value().symbol_infos()) {+      if (sym.name() == function_name_) {+        address = sym.address() + address_range_code.start - syms.value().load_bias();+        size = sym.size();+      }+    }+    return {address, address + size};+  }++  void PrepareInstrumentation(std::string_view payload_function_name) {+    // Stop the child process using our tooling.+    CHECK(AttachAndStopProcess(pid_).has_value());++    // Inject the payload for the instrumentation.+    const std::string kLibName = "libUserSpaceInstrumentationTestLib.so";+    const std::string library_path = orbit_base::GetExecutableDir() / ".." / "lib" / kLibName;+    auto library_handle_or_error = DlopenInTracee(pid_, library_path, RTLD_NOW);+    CHECK(library_handle_or_error.has_value());+    void* library_handle = library_handle_or_error.value();+    auto payload_function_address_or_error =+        DlsymInTracee(pid_, library_handle, payload_function_name);+    CHECK(payload_function_address_or_error.has_value());+    payload_function_address_ = absl::bit_cast<uint64_t>(payload_function_address_or_error.value());++    // Get address of the function to instrument.+    const AddressRange address_range_code = GetFunctionAddressRangeOrDie();+    function_address_ = address_range_code.start;+    const uint64_t size_of_function = address_range_code.end - address_range_code.start;++    // Get memory for the trampoline.+    auto trampoline_or_error =+        AllocateMemoryForTrampolines(pid_, address_range_code, max_trampoline_size_);+    CHECK(!trampoline_or_error.has_error());+    trampoline_address_ = trampoline_or_error.value();++    // Copy the beginning of the function over into this process.+    constexpr uint64_t kMaxFunctionPrologBackupSize = 20;+    const uint64_t bytes_to_copy = std::min(size_of_function, kMaxFunctionPrologBackupSize);+    ErrorMessageOr<std::vector<uint8_t>> function_backup =+        ReadTraceesMemory(pid_, function_address_, bytes_to_copy);+    CHECK(function_backup.has_value());+    function_code_ = function_backup.value();+  }++  // Runs the child for a millisecond to assert it is still working fine, stops it, removes the+  // instrumentation, restarts and stops it again.+  void RestartAndRemoveInstrumentation() {+    MoveInstructionPointersOutOfOverwrittenCode(pid_, relocation_map_);++    CHECK(!DetachAndContinueProcess(pid_).has_error());+    std::this_thread::sleep_for(std::chrono::milliseconds(1));+    CHECK(AttachAndStopProcess(pid_).has_value());++    auto write_result_or_error = WriteTraceesMemory(pid_, function_address_, function_code_);+    CHECK(!write_result_or_error.has_error());++    CHECK(!DetachAndContinueProcess(pid_).has_error());+    std::this_thread::sleep_for(std::chrono::milliseconds(1));+    CHECK(AttachAndStopProcess(pid_).has_value());+  }++  void TearDown() override {+    cs_close(&capstone_handle_);++    // Detach and end child.+    CHECK(!DetachAndContinueProcess(pid_).has_error());+    kill(pid_, SIGKILL);+    waitpid(pid_, NULL, 0);+  }++  pid_t pid_;+  cs_insn* instruction_ = nullptr;+  csh capstone_handle_ = 0;+  uint64_t max_trampoline_size_ = 0;+  uint64_t trampoline_address_;+  uint64_t payload_function_address_;+  absl::flat_hash_map<uint64_t, uint64_t> relocation_map_;++  std::string function_name_;+  uint64_t function_address_;+  std::vector<uint8_t> function_code_;+};++// Function with an ordinary compiler synthesised prolog; performs some arithmetics. Most real world+// functions will look like this (starting with pushing the stack frame...). Most functions below+// are declared "naked", i.e. without the prolog and implemented entirely in assembly. This is done+// to also cover edge cases.+extern "C" int DoSomething() {+  std::random_device rd;+  std::mt19937 gen(rd());+  std::uniform_int_distribution<int> dis(1, 6);+  std::vector<int> v(10);+  std::generate(v.begin(), v.end(), [&]() { return dis(gen); });+  int sum = std::accumulate(v.begin(), v.end(), 0);+  return sum;+}++TEST_F(InstrumentFunctionTest, DoSomething) {+  RunChild(&DoSomething, "DoSomething");+  PrepareInstrumentation("TrivialLog");+  ErrorMessageOr<uint64_t> address_after_prolog_or_error =+      CreateTrampoline(pid_, function_address_, function_code_, trampoline_address_,+                       payload_function_address_, capstone_handle_, relocation_map_);+  EXPECT_THAT(address_after_prolog_or_error, HasNoError());+  ErrorMessageOr<void> result = InstrumentFunction(+      pid_, function_address_, address_after_prolog_or_error.value(), trampoline_address_);+  EXPECT_THAT(result, HasNoError());+  RestartAndRemoveInstrumentation();+}++// We will not be able to instrument this - the function is just four bytes long and we need five+// bytes to write a jump.+extern "C" __attribute__((naked)) int TooShort() {+  __asm__ __volatile__(+      "nop \n\t"+      "nop \n\t"+      "nop \n\t"+      "ret \n\t"+      :+      :+      :);+}++TEST_F(InstrumentFunctionTest, TooShort) {+  RunChild(&TooShort, "TooShort");+  PrepareInstrumentation("TrivialLog");+  ErrorMessageOr<uint64_t> result =+      CreateTrampoline(pid_, function_address_, function_code_, trampoline_address_,+                       payload_function_address_, capstone_handle_, relocation_map_);+  EXPECT_THAT(result, HasError("Unable to disassemble enough of the function to instrument it"));+  RestartAndRemoveInstrumentation();+}++// This function is just long enough to be instrumented (five bytes). It is also interesting in that+// the return statement is copied into the trampoline and executed from there.+extern "C" __attribute__((naked)) int LongEnough() {+  __asm__ __volatile__(+      "nop \n\t"+      "nop \n\t"+      "nop \n\t"+      "nop \n\t"+      "ret \n\t"+      :+      :+      :);+}++TEST_F(InstrumentFunctionTest, LongEnough) {+  RunChild(&LongEnough, "LongEnough");+  PrepareInstrumentation("TrivialLog");+  ErrorMessageOr<uint64_t> address_after_prolog_or_error =+      CreateTrampoline(pid_, function_address_, function_code_, trampoline_address_,+                       payload_function_address_, capstone_handle_, relocation_map_);+  EXPECT_THAT(address_after_prolog_or_error, HasNoError());+  ErrorMessageOr<void> result = InstrumentFunction(+      pid_, function_address_, address_after_prolog_or_error.value(), trampoline_address_);+  EXPECT_THAT(result, HasNoError());+  RestartAndRemoveInstrumentation();+}++// The rip relative address is translated to the new code position.+extern "C" __attribute__((naked)) int RipRelativeAddressing() {+  __asm__ __volatile__(+      "movq 0x03(%%rip), %%rax\n\t"+      "nop \n\t"+      "nop \n\t"+      "ret \n\t"+      ".quad 0x0102034200000000 \n\t"+      :+      :+      :);+}++TEST_F(InstrumentFunctionTest, RipRelativeAddressing) {+  RunChild(&RipRelativeAddressing, "RipRelativeAddressing");+  PrepareInstrumentation("TrivialLog");+  ErrorMessageOr<uint64_t> address_after_prolog_or_error =+      CreateTrampoline(pid_, function_address_, function_code_, trampoline_address_,+                       payload_function_address_, capstone_handle_, relocation_map_);+  EXPECT_THAT(address_after_prolog_or_error, HasNoError());+  ErrorMessageOr<void> result = InstrumentFunction(+      pid_, function_address_, address_after_prolog_or_error.value(), trampoline_address_);+  EXPECT_THAT(result, HasNoError());+  RestartAndRemoveInstrumentation();+}++// Unconditional jump to a 8 bit offset.+extern "C" __attribute__((naked)) int UnconditionalJump8BitOffset() {+  __asm__ __volatile__(+      "jmp label_unconditional_jmp_8_bit \n\t"+      "nop \n\t"+      "nop \n\t"+      "nop \n\t"+      "label_unconditional_jmp_8_bit: \n\t"+      "ret \n\t"+      :+      :+      :);+}++TEST_F(InstrumentFunctionTest, UnconditionalJump8BitOffset) {+  RunChild(&UnconditionalJump8BitOffset, "UnconditionalJump8BitOffset");+  PrepareInstrumentation("TrivialLog");+  ErrorMessageOr<uint64_t> address_after_prolog_or_error =+      CreateTrampoline(pid_, function_address_, function_code_, trampoline_address_,+                       payload_function_address_, capstone_handle_, relocation_map_);+  EXPECT_THAT(address_after_prolog_or_error, HasNoError());+  ErrorMessageOr<void> result = InstrumentFunction(+      pid_, function_address_, address_after_prolog_or_error.value(), trampoline_address_);+  EXPECT_THAT(result, HasNoError());+  RestartAndRemoveInstrumentation();+}++// Unconditional jump to a 32 bit offset.+extern "C" __attribute__((naked)) int UnconditionalJump32BitOffset() {+  __asm__ __volatile__(+      "jmp label_unconditional_jmp_32_bit \n\t"+      ".octa 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 \n\t"  // 256 bytes of zeros+      "label_unconditional_jmp_32_bit: \n\t"+      "ret \n\t"+      :+      :+      :);+}++TEST_F(InstrumentFunctionTest, UnconditionalJump32BitOffset) {+  RunChild(&UnconditionalJump32BitOffset, "UnconditionalJump32BitOffset");+  PrepareInstrumentation("TrivialLog");+  ErrorMessageOr<uint64_t> address_after_prolog_or_error =+      CreateTrampoline(pid_, function_address_, function_code_, trampoline_address_,+                       payload_function_address_, capstone_handle_, relocation_map_);+  EXPECT_THAT(address_after_prolog_or_error, HasNoError());+  ErrorMessageOr<void> result = InstrumentFunction(+      pid_, function_address_, address_after_prolog_or_error.value(), trampoline_address_);+  EXPECT_THAT(result, HasNoError());+  RestartAndRemoveInstrumentation();+}++// Call function at relative offset.+extern "C" __attribute__((naked)) int CallFunction() {+  __asm__ __volatile__(+      "call function_label\n\t"+      "ret \n\t"+      "function_label:\n\t"+      "nop \n\t"+      "ret \n\t"+      :+      :+      :);+}++TEST_F(InstrumentFunctionTest, CallFunction) {+  RunChild(&CallFunction, "CallFunction");+  PrepareInstrumentation("TrivialLog");+  ErrorMessageOr<uint64_t> address_after_prolog_or_error =+      CreateTrampoline(pid_, function_address_, function_code_, trampoline_address_,+                       payload_function_address_, capstone_handle_, relocation_map_);+  EXPECT_THAT(address_after_prolog_or_error, HasNoError());+  ErrorMessageOr<void> result = InstrumentFunction(+      pid_, function_address_, address_after_prolog_or_error.value(), trampoline_address_);+  EXPECT_THAT(result, HasNoError());+  RestartAndRemoveInstrumentation();+}++// The rip relative address is translated to the new code position.+extern "C" __attribute__((naked)) int ConditionalJump8BitOffset() {+  __asm__ __volatile__(+      "loop_label_jcc: \n\t"+      "xor %%eax, %%eax \n\t"+      "jnz loop_label_jcc \n\t"+      "nop \n\t"+      "nop \n\t"+      "ret \n\t"+      :+      :+      :);+}++TEST_F(InstrumentFunctionTest, ConditionalJump8BitOffset) {+  RunChild(&ConditionalJump8BitOffset, "ConditionalJump8BitOffset");+  PrepareInstrumentation("TrivialLog");+  ErrorMessageOr<uint64_t> address_after_prolog_or_error =+      CreateTrampoline(pid_, function_address_, function_code_, trampoline_address_,+                       payload_function_address_, capstone_handle_, relocation_map_);+  EXPECT_THAT(address_after_prolog_or_error, HasNoError());+  ErrorMessageOr<void> result = InstrumentFunction(+      pid_, function_address_, address_after_prolog_or_error.value(), trampoline_address_);+  EXPECT_THAT(result, HasNoError());+  RestartAndRemoveInstrumentation();+}++// The rip relative address is translated to the new code position.+extern "C" __attribute__((naked)) int ConditionalJump32BitOffset() {+  __asm__ __volatile__(+      "xor %%eax, %%eax \n\t"+      "jnz label_jcc_32_bit \n\t"+      "nop \n\t"+      "ret \n\t"+      ".octa 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 \n\t"  // 256 bytes of zeros+      "label_jcc_32_bit: \n\t"+      "ret \n\t"+      :+      :+      :);+}++TEST_F(InstrumentFunctionTest, ConditionalJump32BitOffset) {+  RunChild(&ConditionalJump32BitOffset, "ConditionalJump32BitOffset");+  PrepareInstrumentation("TrivialLog");+  ErrorMessageOr<uint64_t> address_after_prolog_or_error =+      CreateTrampoline(pid_, function_address_, function_code_, trampoline_address_,+                       payload_function_address_, capstone_handle_, relocation_map_);+  EXPECT_THAT(address_after_prolog_or_error, HasNoError());+  ErrorMessageOr<void> result = InstrumentFunction(+      pid_, function_address_, address_after_prolog_or_error.value(), trampoline_address_);+  EXPECT_THAT(result, HasNoError());+  RestartAndRemoveInstrumentation();+}++// Function can not be instrumented since it uses the unsupported loop instruction.+extern "C" __attribute__((naked)) int Loop() {+  __asm__ __volatile__(+      "mov $42, %%cx\n\t"+      "loop_label:\n\t"+      "loopnz loop_label\n\t"+      "ret \n\t"+      :+      :+      :);+}++TEST_F(InstrumentFunctionTest, Loop) {+  RunChild(&Loop, "Loop");+  PrepareInstrumentation("TrivialLog");+  ErrorMessageOr<uint64_t> result =+      CreateTrampoline(pid_, function_address_, function_code_, trampoline_address_,+                       payload_function_address_, capstone_handle_, relocation_map_);+  EXPECT_THAT(result, HasError("Relocating a loop instruction is not supported."));+  RestartAndRemoveInstrumentation();+}++// Check-fails if any parameter is not zero.+extern "C" int CheckIntParameters(u_int64_t p0, u_int64_t p1, u_int64_t p2, u_int64_t p3,+                                  u_int64_t p4, u_int64_t p5, u_int64_t p6, u_int64_t p7) {+  CHECK(p0 == 0 && p1 == 0 && p2 == 0 && p3 == 0 && p4 == 0 && p5 == 0 && p6 == 0 && p7 == 0);+  return 0;+}++// This test and the two tests below check for proper handling of parameters handed of the+// instrumented function. The payload that is called before the instrumented function is executed+// clobbers

Doh, this comment misses the

danielfenner

comment created time in 13 hours