Skip to content
Merged
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
266 changes: 252 additions & 14 deletions src/coreclr/pal/src/thread/process.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ extern "C"
} \
} while (false)

// On macOS 26, sem_open fails if debugger and debugee are signed with different team ids.
// Use fifos instead of semaphores to avoid this issue, https://github.com/dotnet/runtime/issues/116545
#define ENABLE_RUNTIME_EVENTS_OVER_PIPES
#endif // __APPLE__

#ifdef __NetBSD__
Expand Down Expand Up @@ -1432,21 +1435,217 @@ static uint64_t HashSemaphoreName(uint64_t a, uint64_t b)
static const char *const TwoWayNamedPipePrefix = "clr-debug-pipe";
static const char* IpcNameFormat = "%s-%d-%llu-%s";

/*++
PAL_NotifyRuntimeStarted
#ifdef ENABLE_RUNTIME_EVENTS_OVER_PIPES
static const char* RuntimeStartupPipeName = "st";
static const char* RuntimeContinuePipeName = "co";

Signals the debugger waiting for runtime startup notification to continue and
waits until the debugger signals us to continue.
#define PIPE_OPEN_RETRY_DELAY_NS 500000000 // 500 ms

Parameters:
None
typedef enum
{
RuntimeEventsOverPipes_Disabled = 0,
RuntimeEventsOverPipes_Succeeded = 1,
RuntimeEventsOverPipes_Failed = 2,
} RuntimeEventsOverPipes;

Return value:
TRUE - successfully launched by debugger, FALSE - not launched or some failure in the handshake
--*/
typedef enum
{
RuntimeEvent_Unknown = 0,
RuntimeEvent_Started = 1,
RuntimeEvent_Continue = 2,
} RuntimeEvent;

static
int
OpenPipe(const char* name, int mode)
{
int fd = -1;
int flags = mode | O_NONBLOCK;

#if defined(FD_CLOEXEC)
flags |= O_CLOEXEC;
#endif

while (fd == -1)
{
fd = open(name, flags);
if (fd == -1)
{
if (mode == O_WRONLY && errno == ENXIO)
{
PAL_nanosleep(PIPE_OPEN_RETRY_DELAY_NS);
continue;
}
else if (errno == EINTR)
{
continue;
}
else
{
break;
}
}
}

if (fd != -1)
{
flags = fcntl(fd, F_GETFL);
if (flags != -1)
{
flags &= ~O_NONBLOCK;
if (fcntl(fd, F_SETFL, flags) == -1)
{
close(fd);
fd = -1;
}
}
else
{
close(fd);
fd = -1;
}
}

return fd;
}

static
void
ClosePipe(int fd)
{
if (fd != -1)
{
while (close(fd) < 0 && errno == EINTR);
}
}

static
RuntimeEventsOverPipes
NotifyRuntimeUsingPipes()
{
RuntimeEventsOverPipes result = RuntimeEventsOverPipes_Disabled;
char startupPipeName[MAX_DEBUGGER_TRANSPORT_PIPE_NAME_LENGTH];
char continuePipeName[MAX_DEBUGGER_TRANSPORT_PIPE_NAME_LENGTH];
int startupPipeFd = -1;
int continuePipeFd = -1;
size_t offset = 0;

LPCSTR applicationGroupId = PAL_GetApplicationGroupId();

PAL_GetTransportPipeName(continuePipeName, gPID, applicationGroupId, RuntimeContinuePipeName);
TRACE("NotifyRuntimeUsingPipes: opening continue '%s' pipe\n", continuePipeName);

continuePipeFd = OpenPipe(continuePipeName, O_RDONLY);
if (continuePipeFd == -1)
{
if (errno == ENOENT || errno == EACCES)
{
TRACE("NotifyRuntimeUsingPipes: pipe %s not found/accessible, runtime events over pipes disabled\n", continuePipeName);
}
else
{
TRACE("NotifyRuntimeUsingPipes: open(%s) failed: %d (%s)\n", continuePipeName, errno, strerror(errno));
result = RuntimeEventsOverPipes_Failed;
}

goto exit;
}

PAL_GetTransportPipeName(startupPipeName, gPID, applicationGroupId, RuntimeStartupPipeName);
TRACE("NotifyRuntimeUsingPipes: opening startup '%s' pipe\n", startupPipeName);

startupPipeFd = OpenPipe(startupPipeName, O_WRONLY);
if (startupPipeFd == -1)
{
if (errno == ENOENT || errno == EACCES)
{
TRACE("NotifyRuntimeUsingPipes: pipe %s not found/accessible, runtime events over pipes disabled\n", startupPipeName);
}
else
{
TRACE("NotifyRuntimeUsingPipes: open(%s) failed: %d (%s)\n", startupPipeName, errno, strerror(errno));
result = RuntimeEventsOverPipes_Failed;
}

goto exit;
}

TRACE("NotifyRuntimeUsingPipes: sending started event\n");

{
unsigned char event = (unsigned char)RuntimeEvent_Started;
unsigned char *buffer = &event;
int bytesToWrite = sizeof(event);
int bytesWritten = 0;

do
{
bytesWritten = write(startupPipeFd, buffer + offset, bytesToWrite - offset);
if (bytesWritten > 0)
{
offset += bytesWritten;
}
}
while ((bytesWritten > 0 && offset < bytesToWrite) || (bytesWritten == -1 && errno == EINTR));

if (offset != bytesToWrite)
{
TRACE("NotifyRuntimeUsingPipes: write(%s) failed: %d (%s)\n", startupPipeName, errno, strerror(errno));
goto exit;
}
}

TRACE("NotifyRuntimeUsingPipes: waiting on continue event\n");

{
unsigned char event = (unsigned char)RuntimeEvent_Unknown;
unsigned char *buffer = &event;
int bytesToRead = sizeof(event);
int bytesRead = 0;

offset = 0;
do
{
bytesRead = read(continuePipeFd, buffer + offset, bytesToRead - offset);
if (bytesRead > 0)
{
offset += bytesRead;
}
}
while ((bytesRead > 0 && offset < bytesToRead) || (bytesRead == -1 && errno == EINTR));

if (offset == bytesToRead && event == (unsigned char)RuntimeEvent_Continue)
{
TRACE("NotifyRuntimeUsingPipes: received continue event\n");
}
else
{
TRACE("NotifyRuntimeUsingPipes: received invalid event\n");
goto exit;
}
}

result = RuntimeEventsOverPipes_Succeeded;

exit:

if (startupPipeFd != -1)
{
ClosePipe(startupPipeFd);
}

if (continuePipeFd != -1)
{
ClosePipe(continuePipeFd);
}

return result;
}
#endif // ENABLE_RUNTIME_EVENTS_OVER_PIPES

static
BOOL
PALAPI
PAL_NotifyRuntimeStarted()
NotifyRuntimeUsingSemaphores()
{
char startupSemName[CLR_SEM_MAX_NAMELEN];
char continueSemName[CLR_SEM_MAX_NAMELEN];
Expand All @@ -1467,13 +1666,13 @@ PAL_NotifyRuntimeStarted()
CreateSemaphoreName(startupSemName, RuntimeStartupSemaphoreName, unambiguousProcessDescriptor, applicationGroupId);
CreateSemaphoreName(continueSemName, RuntimeContinueSemaphoreName, unambiguousProcessDescriptor, applicationGroupId);

TRACE("PAL_NotifyRuntimeStarted opening continue '%s' startup '%s'\n", continueSemName, startupSemName);
TRACE("NotifyRuntimeUsingSemaphores: opening continue '%s' startup '%s'\n", continueSemName, startupSemName);

// Open the debugger startup semaphore. If it doesn't exists, then we do nothing and return
startupSem = sem_open(startupSemName, 0);
if (startupSem == SEM_FAILED)
{
TRACE("sem_open(%s) failed: %d (%s)\n", startupSemName, errno, strerror(errno));
TRACE("NotifyRuntimeUsingSemaphores: sem_open(%s) failed: %d (%s)\n", startupSemName, errno, strerror(errno));
goto exit;
}

Expand All @@ -1496,7 +1695,7 @@ PAL_NotifyRuntimeStarted()
{
if (EINTR == errno)
{
TRACE("sem_wait() failed with EINTR; re-waiting");
TRACE("NotifyRuntimeUsingSemaphores: sem_wait() failed with EINTR; re-waiting");
continue;
}
ASSERT("sem_wait(continueSem) failed: errno is %d (%s)\n", errno, strerror(errno));
Expand All @@ -1518,6 +1717,45 @@ PAL_NotifyRuntimeStarted()
return launched;
}

/*++
PAL_NotifyRuntimeStarted

Signals the debugger waiting for runtime startup notification to continue and
waits until the debugger signals us to continue.

Parameters:
None

Return value:
TRUE - successfully launched by debugger, FALSE - not launched or some failure in the handshake
--*/
BOOL
PALAPI
PAL_NotifyRuntimeStarted()
{
#ifdef ENABLE_RUNTIME_EVENTS_OVER_PIPES
// Test pipes as runtime event transport.
RuntimeEventsOverPipes result = NotifyRuntimeUsingPipes();
switch (result)
{
case RuntimeEventsOverPipes_Disabled:
TRACE("PAL_NotifyRuntimeStarted: pipe handshake disabled, try semaphores\n");
return NotifyRuntimeUsingSemaphores();
case RuntimeEventsOverPipes_Failed:
TRACE("PAL_NotifyRuntimeStarted: pipe handshake failed\n");
return FALSE;
case RuntimeEventsOverPipes_Succeeded:
TRACE("PAL_NotifyRuntimeStarted: pipe handshake succeeded\n");
return TRUE;
default:
// Unexpected result.
return FALSE;
}
#else
return NotifyRuntimeUsingSemaphores();
#endif // ENABLE_RUNTIME_EVENTS_OVER_PIPES
}

LPCSTR
PALAPI
PAL_GetApplicationGroupId()
Expand Down
Loading