Skip to content

Commit

Permalink
Fix IntelliJ Rider newest version stuck on macOS (#2409)
Browse files Browse the repository at this point in the history
* Fix IntelliJ Rider newest version stuck on macOS

* Update mirrord/layer/src/lib.rs

Co-authored-by: t4lz <[email protected]>

* lint

* lint

* tests

---------

Co-authored-by: t4lz <[email protected]>
  • Loading branch information
aviramha and t4lz authored Apr 30, 2024
1 parent 7992ec8 commit 1b84956
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 35 deletions.
1 change: 1 addition & 0 deletions changelog.d/2408.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix IntelliJ Rider newest version stuck on macOS
15 changes: 14 additions & 1 deletion mirrord/intproxy/protocol/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,19 @@ pub enum LayerToProxyMessage {
GetEnv(GetEnvVarsRequest),
}

/// Layer process information
#[derive(Encode, Decode, Debug)]
pub struct ProcessInfo {
/// Process ID.
pub pid: u32,
/// Process name.
pub name: String,
/// Command line
pub cmdline: Vec<String>,
/// Is layer loaded?
pub loaded: bool,
}

/// Unique `layer <-> proxy` session identifier.
/// New connection is established when the layer initializes or forks.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Encode, Decode)]
Expand All @@ -78,7 +91,7 @@ pub struct LayerId(pub u64);
#[derive(Encode, Decode, Debug)]
pub enum NewSessionRequest {
/// Layer initialized from its constructor, has a fresh state.
New,
New(ProcessInfo),
/// Layer re-initialized from a [`fork`](https://man7.org/linux/man-pages/man2/fork.2.html) detour.
/// It inherits state from its parent.
Forked(LayerId),
Expand Down
6 changes: 5 additions & 1 deletion mirrord/intproxy/src/layer_initializer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use mirrord_intproxy_protocol::{
};
use thiserror::Error;
use tokio::net::{TcpListener, TcpStream};
use tracing::info;

use crate::{
background_tasks::{BackgroundTask, MessageBus},
Expand Down Expand Up @@ -58,7 +59,10 @@ impl LayerInitializer {
self.next_layer_id.0 += 1;

let parent_id = match msg.inner {
LayerToProxyMessage::NewSession(NewSessionRequest::New) => None,
LayerToProxyMessage::NewSession(NewSessionRequest::New(process_info)) => {
info!(?process_info, "new session");
None
}
LayerToProxyMessage::NewSession(NewSessionRequest::Forked(parent)) => Some(parent),
other => return Err(LayerInitializerError::UnexpectedMessage(other)),
};
Expand Down
55 changes: 29 additions & 26 deletions mirrord/layer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ extern crate core;
use std::{
cmp::Ordering,
collections::{HashMap, HashSet},
ffi::OsString,
net::SocketAddr,
panic,
sync::OnceLock,
Expand All @@ -80,7 +79,7 @@ use error::{LayerError, Result};
use file::OPEN_FILES;
use hooks::HookManager;
use libc::{c_int, pid_t};
use load::ExecutableName;
use load::ExecuteArgs;
#[cfg(target_os = "macos")]
use mirrord_config::feature::fs::FsConfig;
use mirrord_config::{
Expand Down Expand Up @@ -151,27 +150,23 @@ fn setup() -> &'static LayerSetup {
// The following statics are to avoid using CoreFoundation or high level macOS APIs
// that aren't safe to use after fork.

/// Executable we're loaded to
static EXECUTABLE_NAME: OnceLock<ExecutableName> = OnceLock::new();
/// Executable information (name, args)
static EXECUTABLE_ARGS: OnceLock<ExecuteArgs> = OnceLock::new();

/// Executable path we're loaded to
static EXECUTABLE_PATH: OnceLock<String> = OnceLock::new();

/// Program arguments
static EXECUTABLE_ARGS: OnceLock<Vec<OsString>> = OnceLock::new();

/// Proxy Connection timeout
/// Set to 10 seconds as most agent operations timeout after 5 seconds
const PROXY_CONNECTION_TIMEOUT: Duration = Duration::from_secs(10);

/// Loads mirrord configuration and does some patching (SIP, dotnet, etc)
fn layer_pre_initialization() -> Result<(), LayerError> {
let given_process = EXECUTABLE_NAME.get_or_try_init(ExecutableName::from_env)?;
let given_process = EXECUTABLE_ARGS.get_or_try_init(ExecuteArgs::from_env)?;

EXECUTABLE_PATH.get_or_try_init(|| {
std::env::current_exe().map(|arg| arg.to_string_lossy().into_owned())
})?;
EXECUTABLE_ARGS.get_or_init(|| std::env::args_os().collect());
let config = LayerConfig::from_env()?;

#[cfg(target_os = "macos")]
Expand All @@ -193,7 +188,9 @@ fn layer_pre_initialization() -> Result<(), LayerError> {
binary,
EXECUTABLE_ARGS
.get()
.expect("EXECUTABLE_ARGS needs to be set!"),
.expect("EXECUTABLE_ARGS needs to be set!")
.args
.clone(),
);
tracing::error!("Couldn't execute {:?}", err);
return Err(LayerError::ExecFailed(err));
Expand Down Expand Up @@ -229,9 +226,17 @@ fn load_only_layer_start(config: &LayerConfig) {
.parse()
.expect("failed to parse internal proxy address");

let new_connection =
ProxyConnection::new(address, NewSessionRequest::New, PROXY_CONNECTION_TIMEOUT)
.expect("failed to initialize proxy connection");
let new_connection = ProxyConnection::new(
address,
NewSessionRequest::New(
EXECUTABLE_ARGS
.get()
.expect("EXECUTABLE_ARGS MUST BE SET")
.to_process_info(config),
),
PROXY_CONNECTION_TIMEOUT,
)
.expect("failed to initialize proxy connection");

unsafe {
// SAFETY
Expand Down Expand Up @@ -328,6 +333,10 @@ fn layer_start(mut config: LayerConfig) {

let debugger_ports = DebuggerPorts::from_env();
let local_hostname = trace_only || !config.feature.hostname;
let process_info = EXECUTABLE_ARGS
.get()
.expect("EXECUTABLE_ARGS MUST BE SET")
.to_process_info(&config);
let state = LayerSetup::new(config, debugger_ports, local_hostname);
SETUP.set(state).unwrap();

Expand All @@ -340,16 +349,7 @@ fn layer_start(mut config: LayerConfig) {

let _detour_guard = DetourGuard::new();
tracing::info!("Initializing mirrord-layer!");
tracing::trace!(
"Loaded into executable: {}, on pid {}, with args: {:?}",
EXECUTABLE_PATH
.get()
.expect("EXECUTABLE_PATH should be set!"),
std::process::id(),
EXECUTABLE_ARGS
.get()
.expect("EXECUTABLE_ARGS needs to be set!")
);
tracing::trace!(executable = ?EXECUTABLE_PATH.get(), args = ?EXECUTABLE_ARGS.get(), pid = std::process::id(), "Loaded into executable");

if trace_only {
tracing::debug!("Skipping new intproxy connection (trace only)");
Expand All @@ -358,9 +358,12 @@ fn layer_start(mut config: LayerConfig) {

unsafe {
let address = setup().proxy_address();
let new_connection =
ProxyConnection::new(address, NewSessionRequest::New, PROXY_CONNECTION_TIMEOUT)
.expect("failed to initialize proxy connection");
let new_connection = ProxyConnection::new(
address,
NewSessionRequest::New(process_info),
PROXY_CONNECTION_TIMEOUT,
)
.expect("failed to initialize proxy connection");
PROXY_CONNECTION
.set(new_connection)
.expect("setting PROXY_CONNECTION singleton")
Expand Down
50 changes: 43 additions & 7 deletions mirrord/layer/src/load.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use std::{
collections::HashSet,
ffi::OsString,
fmt::{self, Display},
sync::LazyLock,
};

use mirrord_config::{util::VecOrSingle, LayerConfig};
use mirrord_intproxy_protocol::ProcessInfo;
use tracing::trace;

use crate::error::LayerError;
Expand Down Expand Up @@ -32,20 +34,22 @@ static BUILD_TOOL_PROCESSES: LazyLock<HashSet<&str>> = LazyLock::new(|| {
"cargo-watch",
"debugserver",
"jspawnhelper",
"JetBrains.Debugger.Worker",
])
});

/// Credentials of the process the layer is injected into.
pub struct ExecutableName {
#[derive(Debug)]
pub struct ExecuteArgs {
/// Executable file name, for example `x86_64-linux-gnu-ld.bfd`.
exec_name: String,
/// Last part of the process name as seen in the arguments, for example `ld` extracted from
/// `/usr/bin/ld`.
invoked_as: String,
/// Executable arguments
pub(crate) args: Vec<OsString>,
}

impl ExecutableName {
impl ExecuteArgs {
/// Creates a new instance of this struct using [`std::env::current_exe`] and
/// [`std::env::args`].
pub(crate) fn from_env() -> Result<Self, LayerError> {
Expand All @@ -58,6 +62,7 @@ impl ExecutableName {
})
.ok_or(LayerError::NoProcessFound)?;

let args = std::env::args_os().collect();
let invoked_as = std::env::args()
.next()
.as_ref()
Expand All @@ -68,6 +73,7 @@ impl ExecutableName {
Ok(Self {
exec_name,
invoked_as,
args,
})
}

Expand All @@ -92,6 +98,21 @@ impl ExecutableName {
return false;
}

// ignore intellij debugger https://github.com/metalbear-co/mirrord/issues/2408
// don't put it in build tools since we don't want to SIP load on macOS. (leads to above
// issue)
if self
.exec_name
.as_str()
.ends_with("JetBrains.Debugger.Worker")
|| self
.invoked_as
.as_str()
.ends_with("JetBrains.Debugger.Worker")
{
return false;
}

!skip_processes
.iter()
.any(|name| name.as_ref() == self.exec_name || name.as_ref() == self.invoked_as)
Expand Down Expand Up @@ -119,9 +140,22 @@ impl ExecutableName {
LoadType::Skip
}
}

pub(crate) fn to_process_info(&self, config: &LayerConfig) -> ProcessInfo {
ProcessInfo {
pid: std::process::id(),
name: self.exec_name.clone(),
cmdline: self
.args
.iter()
.map(|arg| arg.to_string_lossy().to_string())
.collect(),
loaded: matches!(self.load_type(config), LoadType::Full),
}
}
}

impl Display for ExecutableName {
impl Display for ExecuteArgs {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} invoked as {}", self.exec_name, self.invoked_as)
}
Expand All @@ -137,7 +171,7 @@ mod sip {
static SIP_ONLY_PROCESSES: LazyLock<HashSet<&str>> =
LazyLock::new(|| HashSet::from(["sh", "bash", "env", "go", "dlv"]));

pub fn is_sip_only(given_process: &ExecutableName) -> bool {
pub fn is_sip_only(given_process: &ExecuteArgs) -> bool {
given_process.is_build_tool()
|| SIP_ONLY_PROCESSES.contains(given_process.exec_name.as_str())
|| SIP_ONLY_PROCESSES.contains(given_process.invoked_as.as_str())
Expand Down Expand Up @@ -177,9 +211,10 @@ mod tests {
#[case] skip_processes: &[&str],
#[case] skip_build_tools: bool,
) {
let executable_name = ExecutableName {
let executable_name = ExecuteArgs {
exec_name: exec_name.to_string(),
invoked_as: invoked_as.to_string(),
args: Vec::new(),
};

assert!(executable_name.should_load(skip_processes, skip_build_tools));
Expand All @@ -198,9 +233,10 @@ mod tests {
#[case] skip_processes: &[&str],
#[case] skip_build_tools: bool,
) {
let executable_name = ExecutableName {
let executable_name = ExecuteArgs {
exec_name: exec_name.to_string(),
invoked_as: invoked_as.to_string(),
args: Vec::new(),
};

assert!(!executable_name.should_load(skip_processes, skip_build_tools));
Expand Down

0 comments on commit 1b84956

Please sign in to comment.