Skip to content

Commit 4e270e8

Browse files
[release/8.0-staging] Fix broken debugger/debuggee startup handshake protocol on macOS26. (#118213)
Backport of #118120 * Add support for new startup handshake protocol over pipes instead of sempahores. * Remove NonBlocking runtime support. * Renames, logging and simplification. * Improve tracing. * Make open check non blocking. * Fold access into open calls and track ENOENT | ENOACCES * Review feedback. --------- Co-authored-by: lateralusX <[email protected]>
1 parent 28a27a4 commit 4e270e8

File tree

1 file changed

+252
-14
lines changed

1 file changed

+252
-14
lines changed

src/coreclr/pal/src/thread/process.cpp

Lines changed: 252 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,9 @@ extern "C"
103103
} \
104104
} while (false)
105105

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

108111
#ifdef __NetBSD__
@@ -1430,21 +1433,217 @@ static uint64_t HashSemaphoreName(uint64_t a, uint64_t b)
14301433
static const char *const TwoWayNamedPipePrefix = "clr-debug-pipe";
14311434
static const char* IpcNameFormat = "%s-%d-%llu-%s";
14321435

1433-
/*++
1434-
PAL_NotifyRuntimeStarted
1436+
#ifdef ENABLE_RUNTIME_EVENTS_OVER_PIPES
1437+
static const char* RuntimeStartupPipeName = "st";
1438+
static const char* RuntimeContinuePipeName = "co";
14351439

1436-
Signals the debugger waiting for runtime startup notification to continue and
1437-
waits until the debugger signals us to continue.
1440+
#define PIPE_OPEN_RETRY_DELAY_NS 500000000 // 500 ms
14381441

1439-
Parameters:
1440-
None
1442+
typedef enum
1443+
{
1444+
RuntimeEventsOverPipes_Disabled = 0,
1445+
RuntimeEventsOverPipes_Succeeded = 1,
1446+
RuntimeEventsOverPipes_Failed = 2,
1447+
} RuntimeEventsOverPipes;
14411448

1442-
Return value:
1443-
TRUE - successfully launched by debugger, FALSE - not launched or some failure in the handshake
1444-
--*/
1449+
typedef enum
1450+
{
1451+
RuntimeEvent_Unknown = 0,
1452+
RuntimeEvent_Started = 1,
1453+
RuntimeEvent_Continue = 2,
1454+
} RuntimeEvent;
1455+
1456+
static
1457+
int
1458+
OpenPipe(const char* name, int mode)
1459+
{
1460+
int fd = -1;
1461+
int flags = mode | O_NONBLOCK;
1462+
1463+
#if defined(FD_CLOEXEC)
1464+
flags |= O_CLOEXEC;
1465+
#endif
1466+
1467+
while (fd == -1)
1468+
{
1469+
fd = open(name, flags);
1470+
if (fd == -1)
1471+
{
1472+
if (mode == O_WRONLY && errno == ENXIO)
1473+
{
1474+
PAL_nanosleep(PIPE_OPEN_RETRY_DELAY_NS);
1475+
continue;
1476+
}
1477+
else if (errno == EINTR)
1478+
{
1479+
continue;
1480+
}
1481+
else
1482+
{
1483+
break;
1484+
}
1485+
}
1486+
}
1487+
1488+
if (fd != -1)
1489+
{
1490+
flags = fcntl(fd, F_GETFL);
1491+
if (flags != -1)
1492+
{
1493+
flags &= ~O_NONBLOCK;
1494+
if (fcntl(fd, F_SETFL, flags) == -1)
1495+
{
1496+
close(fd);
1497+
fd = -1;
1498+
}
1499+
}
1500+
else
1501+
{
1502+
close(fd);
1503+
fd = -1;
1504+
}
1505+
}
1506+
1507+
return fd;
1508+
}
1509+
1510+
static
1511+
void
1512+
ClosePipe(int fd)
1513+
{
1514+
if (fd != -1)
1515+
{
1516+
while (close(fd) < 0 && errno == EINTR);
1517+
}
1518+
}
1519+
1520+
static
1521+
RuntimeEventsOverPipes
1522+
NotifyRuntimeUsingPipes()
1523+
{
1524+
RuntimeEventsOverPipes result = RuntimeEventsOverPipes_Disabled;
1525+
char startupPipeName[MAX_DEBUGGER_TRANSPORT_PIPE_NAME_LENGTH];
1526+
char continuePipeName[MAX_DEBUGGER_TRANSPORT_PIPE_NAME_LENGTH];
1527+
int startupPipeFd = -1;
1528+
int continuePipeFd = -1;
1529+
size_t offset = 0;
1530+
1531+
LPCSTR applicationGroupId = PAL_GetApplicationGroupId();
1532+
1533+
PAL_GetTransportPipeName(continuePipeName, gPID, applicationGroupId, RuntimeContinuePipeName);
1534+
TRACE("NotifyRuntimeUsingPipes: opening continue '%s' pipe\n", continuePipeName);
1535+
1536+
continuePipeFd = OpenPipe(continuePipeName, O_RDONLY);
1537+
if (continuePipeFd == -1)
1538+
{
1539+
if (errno == ENOENT || errno == EACCES)
1540+
{
1541+
TRACE("NotifyRuntimeUsingPipes: pipe %s not found/accessible, runtime events over pipes disabled\n", continuePipeName);
1542+
}
1543+
else
1544+
{
1545+
TRACE("NotifyRuntimeUsingPipes: open(%s) failed: %d (%s)\n", continuePipeName, errno, strerror(errno));
1546+
result = RuntimeEventsOverPipes_Failed;
1547+
}
1548+
1549+
goto exit;
1550+
}
1551+
1552+
PAL_GetTransportPipeName(startupPipeName, gPID, applicationGroupId, RuntimeStartupPipeName);
1553+
TRACE("NotifyRuntimeUsingPipes: opening startup '%s' pipe\n", startupPipeName);
1554+
1555+
startupPipeFd = OpenPipe(startupPipeName, O_WRONLY);
1556+
if (startupPipeFd == -1)
1557+
{
1558+
if (errno == ENOENT || errno == EACCES)
1559+
{
1560+
TRACE("NotifyRuntimeUsingPipes: pipe %s not found/accessible, runtime events over pipes disabled\n", startupPipeName);
1561+
}
1562+
else
1563+
{
1564+
TRACE("NotifyRuntimeUsingPipes: open(%s) failed: %d (%s)\n", startupPipeName, errno, strerror(errno));
1565+
result = RuntimeEventsOverPipes_Failed;
1566+
}
1567+
1568+
goto exit;
1569+
}
1570+
1571+
TRACE("NotifyRuntimeUsingPipes: sending started event\n");
1572+
1573+
{
1574+
unsigned char event = (unsigned char)RuntimeEvent_Started;
1575+
unsigned char *buffer = &event;
1576+
int bytesToWrite = sizeof(event);
1577+
int bytesWritten = 0;
1578+
1579+
do
1580+
{
1581+
bytesWritten = write(startupPipeFd, buffer + offset, bytesToWrite - offset);
1582+
if (bytesWritten > 0)
1583+
{
1584+
offset += bytesWritten;
1585+
}
1586+
}
1587+
while ((bytesWritten > 0 && offset < bytesToWrite) || (bytesWritten == -1 && errno == EINTR));
1588+
1589+
if (offset != bytesToWrite)
1590+
{
1591+
TRACE("NotifyRuntimeUsingPipes: write(%s) failed: %d (%s)\n", startupPipeName, errno, strerror(errno));
1592+
goto exit;
1593+
}
1594+
}
1595+
1596+
TRACE("NotifyRuntimeUsingPipes: waiting on continue event\n");
1597+
1598+
{
1599+
unsigned char event = (unsigned char)RuntimeEvent_Unknown;
1600+
unsigned char *buffer = &event;
1601+
int bytesToRead = sizeof(event);
1602+
int bytesRead = 0;
1603+
1604+
offset = 0;
1605+
do
1606+
{
1607+
bytesRead = read(continuePipeFd, buffer + offset, bytesToRead - offset);
1608+
if (bytesRead > 0)
1609+
{
1610+
offset += bytesRead;
1611+
}
1612+
}
1613+
while ((bytesRead > 0 && offset < bytesToRead) || (bytesRead == -1 && errno == EINTR));
1614+
1615+
if (offset == bytesToRead && event == (unsigned char)RuntimeEvent_Continue)
1616+
{
1617+
TRACE("NotifyRuntimeUsingPipes: received continue event\n");
1618+
}
1619+
else
1620+
{
1621+
TRACE("NotifyRuntimeUsingPipes: received invalid event\n");
1622+
goto exit;
1623+
}
1624+
}
1625+
1626+
result = RuntimeEventsOverPipes_Succeeded;
1627+
1628+
exit:
1629+
1630+
if (startupPipeFd != -1)
1631+
{
1632+
ClosePipe(startupPipeFd);
1633+
}
1634+
1635+
if (continuePipeFd != -1)
1636+
{
1637+
ClosePipe(continuePipeFd);
1638+
}
1639+
1640+
return result;
1641+
}
1642+
#endif // ENABLE_RUNTIME_EVENTS_OVER_PIPES
1643+
1644+
static
14451645
BOOL
1446-
PALAPI
1447-
PAL_NotifyRuntimeStarted()
1646+
NotifyRuntimeUsingSemaphores()
14481647
{
14491648
char startupSemName[CLR_SEM_MAX_NAMELEN];
14501649
char continueSemName[CLR_SEM_MAX_NAMELEN];
@@ -1465,13 +1664,13 @@ PAL_NotifyRuntimeStarted()
14651664
CreateSemaphoreName(startupSemName, RuntimeStartupSemaphoreName, unambiguousProcessDescriptor, applicationGroupId);
14661665
CreateSemaphoreName(continueSemName, RuntimeContinueSemaphoreName, unambiguousProcessDescriptor, applicationGroupId);
14671666

1468-
TRACE("PAL_NotifyRuntimeStarted opening continue '%s' startup '%s'\n", continueSemName, startupSemName);
1667+
TRACE("NotifyRuntimeUsingSemaphores: opening continue '%s' startup '%s'\n", continueSemName, startupSemName);
14691668

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

@@ -1494,7 +1693,7 @@ PAL_NotifyRuntimeStarted()
14941693
{
14951694
if (EINTR == errno)
14961695
{
1497-
TRACE("sem_wait() failed with EINTR; re-waiting");
1696+
TRACE("NotifyRuntimeUsingSemaphores: sem_wait() failed with EINTR; re-waiting");
14981697
continue;
14991698
}
15001699
ASSERT("sem_wait(continueSem) failed: errno is %d (%s)\n", errno, strerror(errno));
@@ -1516,6 +1715,45 @@ PAL_NotifyRuntimeStarted()
15161715
return launched;
15171716
}
15181717

1718+
/*++
1719+
PAL_NotifyRuntimeStarted
1720+
1721+
Signals the debugger waiting for runtime startup notification to continue and
1722+
waits until the debugger signals us to continue.
1723+
1724+
Parameters:
1725+
None
1726+
1727+
Return value:
1728+
TRUE - successfully launched by debugger, FALSE - not launched or some failure in the handshake
1729+
--*/
1730+
BOOL
1731+
PALAPI
1732+
PAL_NotifyRuntimeStarted()
1733+
{
1734+
#ifdef ENABLE_RUNTIME_EVENTS_OVER_PIPES
1735+
// Test pipes as runtime event transport.
1736+
RuntimeEventsOverPipes result = NotifyRuntimeUsingPipes();
1737+
switch (result)
1738+
{
1739+
case RuntimeEventsOverPipes_Disabled:
1740+
TRACE("PAL_NotifyRuntimeStarted: pipe handshake disabled, try semaphores\n");
1741+
return NotifyRuntimeUsingSemaphores();
1742+
case RuntimeEventsOverPipes_Failed:
1743+
TRACE("PAL_NotifyRuntimeStarted: pipe handshake failed\n");
1744+
return FALSE;
1745+
case RuntimeEventsOverPipes_Succeeded:
1746+
TRACE("PAL_NotifyRuntimeStarted: pipe handshake succeeded\n");
1747+
return TRUE;
1748+
default:
1749+
// Unexpected result.
1750+
return FALSE;
1751+
}
1752+
#else
1753+
return NotifyRuntimeUsingSemaphores();
1754+
#endif // ENABLE_RUNTIME_EVENTS_OVER_PIPES
1755+
}
1756+
15191757
LPCSTR
15201758
PALAPI
15211759
PAL_GetApplicationGroupId()

0 commit comments

Comments
 (0)