Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: make hook option #8

Merged
merged 2 commits into from
Sep 8, 2022
Merged
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
20 changes: 19 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,32 @@ In addition, Dbgee frees you from the hassle of writing `launch.json` for VSCode

## Demos

The concept of Dbgee should be new and unfamiliar to you, so here are some demos to get you started.
Since the concept of Dbgee may be new to you, so here are some demos.

### Debug your program with zero configuration in Visual Studio Code

```shell
dbgee run -- ./cmd
```

<img alt="demo image" src="vscode-ext/images/DbgeeRunInVsCode.gif" width="850px">

### Find and attach to a process compiled from source files in the current directory, from all descendant processes of the given command

**Linux only**

```shell
dbgee run --hook-source-dir . -- ./any_run_script
```

<img alt="demo image" src="vscode-ext/images/DbgeeHookInVsCode.gif" width="850px">

### Configure your program to launch a debugger when it runs

```shell
dbgee set command
```

<img alt="demo image" src="vscode-ext/images/DbgeeSetInVsCode.gif" width="850px">

### Start a debug session with custom settings
Expand Down
108 changes: 44 additions & 64 deletions dbgee/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,9 @@ use debugger::Debugger;
use debugger_terminal::{DebuggerTerminal, Tmux, TmuxLayout, VsCode};
use file_helper::is_executable;
use log::debug;
#[cfg(target_os = "linux")]
use os::run_hook;
use os::{is_any_hook_condition_set, run_hook};

use std::{path::PathBuf, str};
use std::str;

use anyhow::{anyhow, bail, Context, Result};
use nix::sys::wait;
Expand Down Expand Up @@ -64,26 +63,28 @@ pub enum Subcommand {
Run(RunOpts),
Set(SetOpts),
Unset(UnsetOpts),
#[cfg(target_os = "linux")]
Hook(HookOpts),
}

/// Launches the debuggee, and attaches the specified debugger to it.
#[derive(Debug, StructOpt)]
#[structopt(
usage = "dbgee run [OPTIONS] -- <debuggee> [args-for-debuggee]...",
usage = "dbgee run [OPTIONS] -- <command> [args-for-command]...",
rename_all = "kebab"
)]
pub struct RunOpts {
/// Path to the debuggee process
/// Path to the process to launch. The debugger attaches to this <command>
/// unless any hook conditions are given.
#[structopt()]
pub debuggee: String,
pub command: String,

#[structopt(name = "args")]
pub debuggee_args: Vec<String>,
pub command_args: Vec<String>,

#[structopt(flatten)]
attach_opts: AttachOpts,

#[structopt(flatten)]
hook_opts: os::HookOpts,
}

// Positional arguments of SetOpts. `debugger::set_exec_to_dbgee` needs this constants
Expand Down Expand Up @@ -128,36 +129,6 @@ pub struct UnsetOpts {
pub debugger: Option<DebuggerOptValues>,
}

/// Run a command and attach a debugger to its child process which triggered the specified hook condition.
#[cfg_attr(not(target_os = "linux"), allow(unused))]
#[derive(Debug, StructOpt)]
#[structopt(rename_all = "kebab")]
pub struct HookOpts {
/// During running this command, any child process which triggered the specified hook condition will be attached.
#[structopt()]
pub command: String,

#[structopt(name = "args")]
pub command_args: Vec<String>,

#[structopt(short = "e", long)]
/// Attach to a process with the specified path
hook_executable: Option<PathBuf>,

#[structopt(short = "s", long)]
/// Attach to a process which is built from any of the given comma-separated source files.
/// A process binary must include DWARF debug information, which compilers usually emit for a debug build.
hook_source: Option<Vec<String>>,

#[structopt(short = "i", long)]
/// Attach to a process which is built from any files under the given directory.
/// A process binary must include DWARF debug information, which compilers usually emit for a debug build.
hook_source_dir: Option<PathBuf>,

#[structopt(flatten)]
attach_opts: AttachOpts,
}

#[derive(Debug, StructOpt)]
pub struct AttachOpts {
/// Debugger to launch. Choose one of "gdb", "lldb", "dlv", "stop-and-write-pid" and "python".
Expand Down Expand Up @@ -212,38 +183,35 @@ pub enum DebuggerOptValues {
}

pub fn run(opts: Opts) -> Result<i32> {
#[cfg(target_os = "linux")]
if let Subcommand::Hook(hook_opts) = opts.command {
return run_hook(hook_opts).map(|_| 0);
}

let (debuggee, debugger_type) = match opts.command {
Subcommand::Run(ref run_opts) => (&run_opts.debuggee, &run_opts.attach_opts.debugger),
Subcommand::Set(ref set_opts) => (&set_opts.debuggee, &set_opts.attach_opts.debugger),
Subcommand::Unset(ref unset_opts) => (&unset_opts.debuggee, &unset_opts.debugger),
#[cfg(target_os = "linux")]
Subcommand::Hook(_) => unreachable!(), // already handled
};
let mut debugger = build_debugger(debugger_type, debuggee)?;

if !is_executable(debuggee) {
bail!(
"the debugee (path: '{}') is not an executable file.",
debuggee
);
}

match opts.command {
Subcommand::Run(run_opts) => {
bail_if_not_executable(&run_opts.command)?;

if is_any_hook_condition_set(&run_opts.hook_opts) {
run_hook(
run_opts.command,
run_opts.command_args,
run_opts.hook_opts,
run_opts.attach_opts,
)
.context("Running with hook conditions failed")?;
return Ok(0);
}

let mut debugger = build_debugger(&run_opts.attach_opts.debugger, &run_opts.command)?;
let mut debugger_terminal = build_debugger_terminal(&run_opts.attach_opts.terminal);
let pid = debugger.run(
&run_opts.debuggee,
run_opts.debuggee_args.iter().map(String::as_str).collect(),
&run_opts.command,
run_opts.command_args.iter().map(String::as_str).collect(),
debugger_terminal.as_mut(),
)?;
Ok(wait_pid_exit(pid)?)
}

Subcommand::Set(set_opts) => {
bail_if_not_executable(&set_opts.debuggee)?;

let mut debugger = build_debugger(&set_opts.attach_opts.debugger, &set_opts.debuggee)?;
let mut debugger_terminal = build_debugger_terminal(&set_opts.attach_opts.terminal);
debugger.set(
&set_opts.debuggee,
Expand All @@ -252,15 +220,27 @@ pub fn run(opts: Opts) -> Result<i32> {
)?;
Ok(0)
}

Subcommand::Unset(unset_opts) => {
bail_if_not_executable(&unset_opts.debuggee)?;

let mut debugger = build_debugger(&unset_opts.debugger, &unset_opts.debuggee)?;
debugger.unset(&unset_opts.debuggee)?;
Ok(0)
}
#[cfg(target_os = "linux")]
Subcommand::Hook(_) => unreachable!(), // already handled
}
}

fn bail_if_not_executable(debuggee: &str) -> Result<()> {
if !is_executable(debuggee) {
bail!(
"the debugee (path: '{}') is not an executable file.",
debuggee
);
}
Ok(())
}

fn build_debugger(
debugger: &Option<DebuggerOptValues>,
debuggee: &str,
Expand Down
7 changes: 6 additions & 1 deletion dbgee/src/os.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ use cfg_if::cfg_if;
cfg_if! {
if #[cfg(target_os = "linux")] {
mod linux;
pub use self::linux::*;
use linux as os;
} else if #[cfg(target_os = "macos")] {
mod macos;
use macos as os;
}
}

pub use os::{is_any_hook_condition_set, run_hook, HookOpts};
51 changes: 45 additions & 6 deletions dbgee/src/os/linux.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,53 @@ use nix::{
unistd::Pid,
};
use object::{Object, ObjectSection};
use structopt::StructOpt;

use crate::{
build_debugger, build_debugger_terminal, file_helper::get_abspath, ErrorLogger, HookOpts,
build_debugger, build_debugger_terminal, file_helper::get_abspath, AttachOpts, ErrorLogger,
};

/// Run the action for subcommand `hook`.
pub fn run_hook(hook_opts: HookOpts) -> Result<()> {
let terminal = &mut build_debugger_terminal(&hook_opts.attach_opts.terminal);
#[derive(Debug, StructOpt)]
#[structopt(rename_all = "kebab")]
pub struct HookOpts {
#[structopt(short = "e", long)]
/// Attach not to <command> itself, but to a descendant process whose executable file is the specified path.
hook_executable: Option<PathBuf>,

#[structopt(short = "s", long)]
/// Attach not to <command> itself, but to a descendant process which is built from any of the given source files.
/// A process binary must include DWARF debug information, which compilers usually emit for a debug build.
hook_source: Option<Vec<String>>,

#[structopt(short = "i", long)]
/// Attach not to <command> itself, but to a descendant process which is built from any files under the given directory.
/// A process binary must include DWARF debug information, which compilers usually emit for a debug build.
hook_source_dir: Option<PathBuf>,
}

pub fn is_any_hook_condition_set(hook_opts: &HookOpts) -> bool {
let HookOpts {
hook_executable,
hook_source,
hook_source_dir,
} = hook_opts;
[
hook_executable.is_some(),
hook_source.is_some(),
hook_source_dir.is_some(),
]
.iter()
.any(|cond| *cond)
}

/// Run the action for subcommand `run` with hook conditions.
pub fn run_hook(
command: String,
command_args: Vec<String>,
hook_opts: HookOpts,
attach_opts: AttachOpts,
) -> Result<()> {
let terminal = &mut build_debugger_terminal(&attach_opts.terminal);

let hook_conditions = build_hook_conditions(
hook_opts.hook_executable,
Expand All @@ -37,7 +76,7 @@ pub fn run_hook(hook_opts: HookOpts) -> Result<()> {
)
.context("failed to build hook conditions")?;

let start_command_pid = spawn_traced_command(hook_opts.command, hook_opts.command_args)
let start_command_pid = spawn_traced_command(command, command_args)
.context("Failed to spawn the traced command")?;

// wait for a process triggering the hook condition
Expand Down Expand Up @@ -89,7 +128,7 @@ pub fn run_hook(hook_opts: HookOpts) -> Result<()> {
)
})?;
let mut debugger = build_debugger(
&hook_opts.attach_opts.debugger,
&attach_opts.debugger,
hooked_command_path
.to_str()
.ok_or_else(|| anyhow!("executable path is not a valid utf-8 str"))?,
Expand Down
31 changes: 31 additions & 0 deletions dbgee/src/os/macos.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use anyhow::Result;
use structopt::StructOpt;

use crate::AttachOpts;

////
// macOS does not support Hook option.
///

/// Run a command and attach a debugger to its child process which triggered the specified hook condition.
#[derive(Debug, StructOpt)]
#[structopt(rename_all = "kebab")]
pub struct HookOpts {
#[structopt(long, hidden = true)]
_dummy: bool,
}

pub fn is_any_hook_condition_set(_hook_opts: &HookOpts) -> bool {
// since macOS does not support any hook conditions
false
}

/// Run the action for subcommand `run` with hook conditions.
pub fn run_hook(
_command: String,
_command_args: Vec<String>,
_hook_opts: HookOpts,
_attach_opts: AttachOpts,
) -> Result<()> {
unimplemented!("macOS does not support hook conditions");
}
4 changes: 4 additions & 0 deletions vscode-ext/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ or when your program is launched by some script.

<img alt="demo image" src="vscode-ext/images/DbgeeRunInVsCode.gif" width="850px">

### (Linux only) Find and attach to a process compiled from source files in the current directory, from all descendant processes of the given command

<img alt="demo image" src="vscode-ext/images/DbgeeHookInVsCode.gif" width="850px">

### Configure your program to wait for a debug session, no matter by what means it is started

<img alt="demo image" src="vscode-ext/images/DbgeeSetInVsCode.gif" width="850px">
Expand Down
Binary file added vscode-ext/images/DbgeeHookInVsCode.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.