Skip to content

Commit df8f6a5

Browse files
[release/9.0-staging] Fix broken debugger/debuggee startup handshake protocol on macOS26. (#118212)
* 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 a6f3819 commit df8f6a5

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
@@ -105,6 +105,9 @@ extern "C"
105105
} \
106106
} while (false)
107107

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

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

1435-
/*++
1436-
PAL_NotifyRuntimeStarted
1438+
#ifdef ENABLE_RUNTIME_EVENTS_OVER_PIPES
1439+
static const char* RuntimeStartupPipeName = "st";
1440+
static const char* RuntimeContinuePipeName = "co";
14371441

1438-
Signals the debugger waiting for runtime startup notification to continue and
1439-
waits until the debugger signals us to continue.
1442+
#define PIPE_OPEN_RETRY_DELAY_NS 500000000 // 500 ms
14401443

1441-
Parameters:
1442-
None
1444+
typedef enum
1445+
{
1446+
RuntimeEventsOverPipes_Disabled = 0,
1447+
RuntimeEventsOverPipes_Succeeded = 1,
1448+
RuntimeEventsOverPipes_Failed = 2,
1449+
} RuntimeEventsOverPipes;
14431450

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

1470-
TRACE("PAL_NotifyRuntimeStarted opening continue '%s' startup '%s'\n", continueSemName, startupSemName);
1669+
TRACE("NotifyRuntimeUsingSemaphores: opening continue '%s' startup '%s'\n", continueSemName, startupSemName);
14711670

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

@@ -1496,7 +1695,7 @@ PAL_NotifyRuntimeStarted()
14961695
{
14971696
if (EINTR == errno)
14981697
{
1499-
TRACE("sem_wait() failed with EINTR; re-waiting");
1698+
TRACE("NotifyRuntimeUsingSemaphores: sem_wait() failed with EINTR; re-waiting");
15001699
continue;
15011700
}
15021701
ASSERT("sem_wait(continueSem) failed: errno is %d (%s)\n", errno, strerror(errno));
@@ -1518,6 +1717,45 @@ PAL_NotifyRuntimeStarted()
15181717
return launched;
15191718
}
15201719

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

0 commit comments

Comments
 (0)