Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 68 additions & 74 deletions capture_service/android_application.cc

Large diffs are not rendered by default.

118 changes: 54 additions & 64 deletions capture_service/device_mgr.cc

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions capture_service/dive_client_cli.cc
Original file line number Diff line number Diff line change
Expand Up @@ -581,7 +581,7 @@ absl::StatusOr<GfxrCaptureControlFlow> TriggerGfxrCapture(

// GFXR relies on capture_android_trigger changing from false to true as the condition for
// beginning the capture. Thus, ensure the prop starts as false.
if (absl::Status status = adb.Run("shell setprop debug.gfxrecon.capture_android_trigger false");
if (absl::Status status = adb.Shell("setprop debug.gfxrecon.capture_android_trigger false");
!status.ok())
{
return Dive::InternalError(
Expand All @@ -597,7 +597,7 @@ absl::StatusOr<GfxrCaptureControlFlow> TriggerGfxrCapture(
break;
}

if (absl::Status status = adb.Run("shell setprop debug.gfxrecon.capture_android_trigger true");
if (absl::Status status = adb.Shell("setprop debug.gfxrecon.capture_android_trigger true");
!status.ok())
{
return Dive::InternalError(
Expand Down Expand Up @@ -763,7 +763,7 @@ absl::Status CmdGfxrCapture(const CommandContext& context)
absl::StrCat(Dive::kDeviceCapturePath, "/", context.options.gfxr_capture_file_dir);
context.mgr.GetDevice()
->Adb()
.Run(absl::StrFormat("shell rm -rf %s", on_device_capture_directory))
.Shell(absl::StrFormat("rm -rf %s", on_device_capture_directory))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would this fail if there is a space in on_device_capture_directory?

i see you have 2 overload Shell(), and this will trigger the

    absl::Status Shell(std::string_view command) const
    {
        return RunCommand("adb", {"-s", m_serial, "shell", std::string(command)}).status();
    }

which doesn't call "MakeUnixCommand?" on the command string?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it will fail.

It should be .Shell("rm", {"-rf", on_device_capture_directory}), however most of these are find-replace regex edit.
There's still a problem if on_device_capture_directory starts with - and rm think it is a parameter rather than a flag. However, I don't aim to solve every problem.

I'd like to have adb.Run(Adb::Remove{.recursive = true, .force = true, .path= on_device_capture_directory}), but that's a lot of code to write.

.IgnoreError();
};

Expand Down
5 changes: 3 additions & 2 deletions src/dive/os/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ target_link_libraries(dive_lib_os PUBLIC dive_src_includes)
add_library(
command_utils
command_utils.h
$<$<PLATFORM_ID:Windows>:command_utils_win32.cc>
$<$<PLATFORM_ID:Linux,Darwin>:command_utils.cc>
command_utils.cpp
$<$<PLATFORM_ID:Windows>:command_utils_win32.cpp>
$<$<PLATFORM_ID:Linux,Darwin>:command_utils_unix.cpp>
)
target_compile_definitions(
command_utils
Expand Down
209 changes: 209 additions & 0 deletions src/dive/os/command_utils.cpp
Copy link
Collaborator

@wangra-google wangra-google Jan 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a more general question: why do you put the platform dependent func in this file instead of the command_utils_.cpp? (for example, why not put MakeWindowsCommand() in src/dive/os/command_utils_win32.cpp)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MakeWindowsCommand is not platform dependent, it's just a string manipulation function which does string escape according to Windows rules.

MakeUnixCommand is additionally required to escape adb shell command correctly even on Windows.

Copy link
Collaborator Author

@hysw hysw Jan 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

e.g. Python expose both Windows and Linux path function, and you can run the following on Linux environment

import pathlib
print(str(pathlib.PureWindowsPath("C:/Program Files")))

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it seems that "MakeWindowsCommand" is only used by command_utils_win32.cpp? do you see possible usage for other platforms as well? I understand that MakeWindowsCommand is platform-independent logic (just string manipulation), If we moved MakeWindowsCommand to command_utils_win32.cpp, It would clearly signal that this logic is intended for the Windows platform (or targeting Windows).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name MakeWindowsCommand is clear enough that it is for Windows.

Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
/*
Copyright 2023 Google Inc.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2026

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lol, this is supposed to be the same file as command_utils.cc, I guess most of the content are changed


Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

#include "dive/os/command_utils.h"

#include <cctype>
#include <set>
#include <string>
#include <string_view>

#include "absl/base/no_destructor.h"

namespace Dive
{

namespace
{

constexpr int kMaxAscii = 127;
bool IsAscii(int c) { return 0 <= c && c <= kMaxAscii; }
bool IsBashKeyword(std::string_view str)
{
static absl::NoDestructor<std::set<std::string_view>> lut(std::set<std::string_view>(
{"if", "then", "elif", "else", "fi", "time", "for", "in", "until", "while", "do", "done",
"case", "esac", "coproc", "select", "function"}));
return lut->contains(str);
}

bool IsBashSafeCharacter(char c)
{
static const auto lut = []() {
constexpr std::string_view kShellSafeCharacters =
"+,-./0123456789:@ABCDEFGHIJKLMNOPQRSTUVWXYZ^_abcdefghijklmnopqrstuvwxyz";
std::array<bool, kMaxAscii + 1> result = {};
for (char c : kShellSafeCharacters)
{
result[c] = true;
}
return result;
}();
return IsAscii(c) && lut[c];
}

std::string BashEscapeImpl(std::string_view original)
{
constexpr std::string_view kSingleQuoteReplacement = R"('"'"')";
std::string escaped;
escaped.reserve(original.size() + 2);
escaped.push_back('\'');
for (char c : original)
{
if (c == '\'')
{
escaped.append(kSingleQuoteReplacement);
}
else
{
escaped.push_back(c);
}
}
escaped.push_back('\'');
return escaped;
}

} // namespace

std::string BashEscape(std::string_view original, bool force_escape)
{
if (original.empty())
{
return "\"\"";
}

if (!force_escape)
{
bool require_escape = IsBashKeyword(original);
for (char c : original)
{
if (!IsBashSafeCharacter(c))
{
require_escape = true;
}
}
if (!require_escape)
{
return std::string(original);
}
}

return BashEscapeImpl(original);
}

// See
// https://learn.microsoft.com/en-us/cpp/cpp/main-function-command-line-args?view=msvc-170#parsing-c-command-line-arguments
std::string WindowsCrtProgramEscape(std::string_view original)
{
bool require_escape = false;
for (char c : original)
{
switch (c)
{
case '<':
case '>':
case ':':
case '"':
case '|':
case '?':
case '*':
// Invalid character in program name.
return "\"\"";
case ' ':
case '\t':
// Documentation only says space and tab are seperator, so ignore newline and etc.
require_escape = true;
break;
default:
break;
}
}
if (!require_escape)
{
return std::string(original);
}
return "\"" + std::string(original) + "\"";
}

std::string WindowsCrtArgumentEscape(std::string_view original)
{
if (original.empty())
{
return "\"\"";
}

std::string escaped;
escaped.reserve(original.size() + 2);
escaped.push_back('"');
bool require_escape = false;
for (char c : original)
{
switch (c)
{
case ' ':
case '\f':
case '\n':
case '\r':
case '\t':
case '\v':
require_escape = true;
escaped.push_back(c);
break;
case '\\':
case '"':
require_escape = true;
escaped.push_back('\\');
escaped.push_back(c);
break;
default:
if (!IsAscii(c))
{
require_escape = true;
}
escaped.push_back(c);
break;
}
}
escaped.push_back('"');
if (!require_escape)
{
return std::string(original);
}
return escaped;
}

std::string MakeUnixCommand(std::string_view program, const std::vector<std::string>& args)
{
std::string command = BashEscape(program);
for (const auto& arg : args)
{
command.push_back(' ');
command.append(BashEscape(arg));
}
return command;
}

std::string MakeWindowsCommand(std::string_view program, const std::vector<std::string>& args)
{
std::string command = WindowsCrtProgramEscape(program);
for (const auto& arg : args)
{
command.push_back(' ');
command.append(WindowsCrtArgumentEscape(arg));
}
return command;
}

} // namespace Dive
38 changes: 38 additions & 0 deletions src/dive/os/command_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ limitations under the License.

#include <filesystem>
#include <string>
#include <string_view>
#include <thread>
#include <vector>

#include "absl/status/status.h"
#include "absl/status/statusor.h"
Expand All @@ -30,9 +32,22 @@ namespace Dive
absl::StatusOr<std::string> LogCommand(const std::string& command, const std::string& output,
int ret);

std::string BashEscape(std::string_view original, bool force_escape = false);
std::string WindowsCrtProgramEscape(std::string_view original);
std::string WindowsCrtArgumentEscape(std::string_view original);
std::string MakeWindowsCommand(std::string_view program, const std::vector<std::string>& args);
std::string MakeUnixCommand(std::string_view program, const std::vector<std::string>& args);

std::string MakeCommand(std::string_view program, const std::vector<std::string>& args);

// Runs a command line application.
// Returns the output of the command if it finished successfully, or error status otherwise
absl::StatusOr<std::string> RunCommand(const std::string& command);
inline absl::StatusOr<std::string> RunCommand(const std::string& program,
const std::vector<std::string>& args)
{
return RunCommand(MakeCommand(program, args));
}

// Returns the directory of the currently running executable.
absl::StatusOr<std::filesystem::path> GetExecutableDirectory();
Expand Down Expand Up @@ -74,6 +89,29 @@ class AdbSession
return absl::OkStatus();
}

absl::Status Shell(std::string_view program, const std::vector<std::string>& args) const
{
return RunCommand("adb", {"-s", m_serial, "shell", MakeUnixCommand(program, args)})
.status();
}
absl::Status Shell(std::string_view command) const
{
return RunCommand("adb", {"-s", m_serial, "shell", std::string(command)}).status();
}
absl::StatusOr<std::string> ShellAndGetResult(std::string_view program,
const std::vector<std::string>& args) const
{
return RunCommand("adb", {"-s", m_serial, "shell", MakeUnixCommand(program, args)});
}
absl::StatusOr<std::string> ShellAndGetResult(std::string_view command) const
{
return RunCommand("adb", {"-s", m_serial, "shell", std::string(command)});
}
absl::Status SetProp(std::string_view key, std::string_view value) const
{
return Shell("setprop", {std::string(key), std::string(value)});
}

private:
std::string m_serial;
std::vector<std::thread> m_background_threads;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

#include "dive/os/command_utils.h"

#include <cstdio>
#include <cstring>
#include <vector>
Expand All @@ -25,6 +23,7 @@ limitations under the License.
#include "absl/strings/ascii.h"
#include "absl/strings/str_format.h"
#include "dive/common/log.h"
#include "dive/os/command_utils.h"

#if defined(__APPLE__)
#include <mach-o/dyld.h>
Expand Down Expand Up @@ -55,6 +54,11 @@ absl::StatusOr<std::string> LogCommand(const std::string& command, const std::st
return output;
}

std::string MakeCommand(std::string_view program, const std::vector<std::string>& args)
{
return MakeUnixCommand(program, args);
}

absl::StatusOr<std::string> RunCommand(const std::string& command)
{
std::string output;
Expand Down Expand Up @@ -101,4 +105,4 @@ absl::StatusOr<std::filesystem::path> GetExecutableDirectory()
return absl::InternalError("Failed to get executable directory.");
}

} // namespace Dive
} // namespace Dive
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ absl::StatusOr<std::string> LogCommand(const std::string& command, const std::st
return output;
}

std::string MakeCommand(std::string_view program, const std::vector<std::string>& args)
{
return MakeWindowsCommand(program, args);
}

absl::StatusOr<std::string> RunCommand(const std::string& command)
{
std::string output;
Expand Down
2 changes: 1 addition & 1 deletion ui/analyze_window.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -540,7 +540,7 @@ absl::StatusOr<std::string> AnalyzeDialog::PushFilesToDevice(
const std::string remote_dir = "/sdcard/gfxr_captures_for_replay";

// Create the remote directory on the device.
RETURN_IF_ERROR(device->Adb().Run(absl::StrFormat("shell mkdir -p %s", remote_dir)));
RETURN_IF_ERROR(device->Adb().Shell(absl::StrFormat("mkdir -p %s", remote_dir)));

// Push the .gfxr file.
std::string local_gfxr_path = m_selected_capture_file_string.toStdString();
Expand Down
6 changes: 3 additions & 3 deletions ui/trace_window.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ absl::Status TraceDialog::StopPackageAndCleanup()
absl::StrCat(Dive::kDeviceCapturePath, "/",
m_gfxr_capture_file_directory_input_box->text().toStdString());
auto ret =
device->Adb().Run(absl::StrFormat("shell rm -rf %s", on_device_capture_directory));
device->Adb().Shell(absl::StrFormat("rm -rf %s", on_device_capture_directory));
m_gfxr_capture_button->setEnabled(false);
m_gfxr_capture_button->setText(kStart_Gfxr_Runtime_Capture);
}
Expand Down Expand Up @@ -804,7 +804,7 @@ void TraceDialog::OnGfxrCaptureClicked()
absl::Status ret;
if (m_gfxr_capture_button->text() == kRetrieve_Gfxr_Runtime_Capture)
{
ret = device->Adb().Run("shell setprop debug.gfxrecon.capture_android_trigger false");
ret = device->Adb().Shell("setprop debug.gfxrecon.capture_android_trigger false");
if (!ret.ok())
{
std::string err_msg = absl::StrCat("Failed to stop runtime gfxr capture ", m_cur_pkg,
Expand All @@ -822,7 +822,7 @@ void TraceDialog::OnGfxrCaptureClicked()
}
else if (m_gfxr_capture_button->text() == kStart_Gfxr_Runtime_Capture)
{
ret = device->Adb().Run("shell setprop debug.gfxrecon.capture_android_trigger true");
ret = device->Adb().Shell("setprop debug.gfxrecon.capture_android_trigger true");
if (!ret.ok())
{
std::string err_msg = absl::StrCat("Failed to start runtime gfxr capture ", m_cur_pkg,
Expand Down
Loading