Skip to content

Commit 913c282

Browse files
authored
Fix broken debugger/debuggee startup handshake protocol on macOS26. (#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.
1 parent cfe9056 commit 913c282

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
@@ -93,6 +93,9 @@ extern "C"
9393
} \
9494
} while (false)
9595

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

98101
#ifdef __NetBSD__
@@ -1401,21 +1404,217 @@ static uint64_t HashSemaphoreName(uint64_t a, uint64_t b)
14011404
static const char *const TwoWayNamedPipePrefix = "clr-debug-pipe";
14021405
static const char* IpcNameFormat = "%s-%d-%llu-%s";
14031406

1404-
/*++
1405-
PAL_NotifyRuntimeStarted
1407+
#ifdef ENABLE_RUNTIME_EVENTS_OVER_PIPES
1408+
static const char* RuntimeStartupPipeName = "st";
1409+
static const char* RuntimeContinuePipeName = "co";
14061410

1407-
Signals the debugger waiting for runtime startup notification to continue and
1408-
waits until the debugger signals us to continue.
1411+
#define PIPE_OPEN_RETRY_DELAY_NS 500000000 // 500 ms
14091412

1410-
Parameters:
1411-
None
1413+
typedef enum
1414+
{
1415+
RuntimeEventsOverPipes_Disabled = 0,
1416+
RuntimeEventsOverPipes_Succeeded = 1,
1417+
RuntimeEventsOverPipes_Failed = 2,
1418+
} RuntimeEventsOverPipes;
14121419

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

1439-
TRACE("PAL_NotifyRuntimeStarted opening continue '%s' startup '%s'\n", continueSemName, startupSemName);
1638+
TRACE("NotifyRuntimeUsingSemaphores: opening continue '%s' startup '%s'\n", continueSemName, startupSemName);
14401639

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

@@ -1465,7 +1664,7 @@ PAL_NotifyRuntimeStarted()
14651664
{
14661665
if (EINTR == errno)
14671666
{
1468-
TRACE("sem_wait() failed with EINTR; re-waiting");
1667+
TRACE("NotifyRuntimeUsingSemaphores: sem_wait() failed with EINTR; re-waiting");
14691668
continue;
14701669
}
14711670
ASSERT("sem_wait(continueSem) failed: errno is %d (%s)\n", errno, strerror(errno));
@@ -1487,6 +1686,45 @@ PAL_NotifyRuntimeStarted()
14871686
return launched;
14881687
}
14891688

1689+
/*++
1690+
PAL_NotifyRuntimeStarted
1691+
1692+
Signals the debugger waiting for runtime startup notification to continue and
1693+
waits until the debugger signals us to continue.
1694+
1695+
Parameters:
1696+
None
1697+
1698+
Return value:
1699+
TRUE - successfully launched by debugger, FALSE - not launched or some failure in the handshake
1700+
--*/
1701+
BOOL
1702+
PALAPI
1703+
PAL_NotifyRuntimeStarted()
1704+
{
1705+
#ifdef ENABLE_RUNTIME_EVENTS_OVER_PIPES
1706+
// Test pipes as runtime event transport.
1707+
RuntimeEventsOverPipes result = NotifyRuntimeUsingPipes();
1708+
switch (result)
1709+
{
1710+
case RuntimeEventsOverPipes_Disabled:
1711+
TRACE("PAL_NotifyRuntimeStarted: pipe handshake disabled, try semaphores\n");
1712+
return NotifyRuntimeUsingSemaphores();
1713+
case RuntimeEventsOverPipes_Failed:
1714+
TRACE("PAL_NotifyRuntimeStarted: pipe handshake failed\n");
1715+
return FALSE;
1716+
case RuntimeEventsOverPipes_Succeeded:
1717+
TRACE("PAL_NotifyRuntimeStarted: pipe handshake succeeded\n");
1718+
return TRUE;
1719+
default:
1720+
// Unexpected result.
1721+
return FALSE;
1722+
}
1723+
#else
1724+
return NotifyRuntimeUsingSemaphores();
1725+
#endif // ENABLE_RUNTIME_EVENTS_OVER_PIPES
1726+
}
1727+
14901728
LPCSTR
14911729
PALAPI
14921730
PAL_GetApplicationGroupId()

0 commit comments

Comments
 (0)