Skip to content

Commit 44f2fb0

Browse files
committed
openkal-macos: this kernel tells the duplicate apart by the second register
Duplicating the calling image is the one call whose result does not fit the convention the rest of this file meets. This kernel reports which image is which in the second register: both receive the same first value, and the second is zero in the original and one in the duplicate. The other kernel tells the duplicate by giving it a first value of zero, and this implementation was written against that. The consequence was not a failed call. Both images took the original's branch, so the duplicate carried on running the program instead of replacing itself, and the original waited for a program that never started --- which the conformance suite reported as "a copy of this program could not be started", four words away from where the fault was. This system's own C library hides the difference by forcing the duplicate's first value to zero in its wrapper. There is no wrapper here, so the difference is met rather than hidden.
1 parent 713e218 commit 44f2fb0

2 files changed

Lines changed: 46 additions & 7 deletions

File tree

src/process.cpp

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@ struct vector {
5555
}
5656
};
5757

58-
constexpr okm_long nr_fork = 2;
5958
constexpr okm_long nr_fchdir = 13;
6059

6160
} // namespace
@@ -86,14 +85,15 @@ int kal_process_spawn(kal_dir base,
8685
const okm_long ou = streams ? static_cast<okm_long>(streams->out) : 0;
8786
const okm_long er = streams ? static_cast<okm_long>(streams->err) : 0;
8887

89-
const okm_long child = okm::sys(nr_fork);
88+
bool is_duplicate = false;
89+
const okm_long child = okm::duplicate(is_duplicate);
9090
if (okm::failed(child)) return okm::translate(child);
9191

92-
// On this system the duplicate is distinguished by a second value the call
93-
// returns rather than by the first alone, and the register it arrives in
94-
// differs by architecture. What is portable between them is that the
95-
// duplicate observes a first value of zero, which is what is used.
96-
if (child == 0) {
92+
// The duplicate is distinguished by the second value the call returns and
93+
// not by the first: both images receive the same first value here. The
94+
// reason, and what happens to an implementation that tests the first alone,
95+
// are in src/sys.h beside the call.
96+
if (is_duplicate) {
9797
if (in != 0) okm::sys(okm::nr_dup2, in, 0);
9898
if (ou != 0) okm::sys(okm::nr_dup2, ou, 1);
9999
if (er != 0) okm::sys(okm::nr_dup2, er, 2);

src/sys.h

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,45 @@ inline okm_long sys(okm_long n, okm_long a = 0, okm_long b = 0, okm_long c = 0,
8383
#error "openkal-macos supports x86_64 and arm64"
8484
#endif
8585

86+
// Duplicating the calling image, which is the one call whose result does not
87+
// fit the convention above.
88+
//
89+
// This kernel reports which image is which in the *second* register: both
90+
// receive the same first value, and the second is zero in the original and one
91+
// in the duplicate. That is the BSD convention, and it is not the other
92+
// kernel's --- there the duplicate is told by receiving a first value of zero.
93+
// An implementation that tested the first value alone would have both images
94+
// take the original's branch, so the duplicate would carry on running the
95+
// program instead of replacing itself, and the original would wait for a
96+
// program that never starts.
97+
//
98+
// This system's own C library hides the difference by forcing the duplicate's
99+
// first value to zero in its wrapper. There is no wrapper here, so the
100+
// difference is met rather than hidden.
101+
inline okm_long duplicate(bool& is_duplicate) {
102+
#if defined(__aarch64__)
103+
register okm_long x16 __asm__("x16") = 2; // fork
104+
register okm_long x0 __asm__("x0") = 0;
105+
register okm_long x1 __asm__("x1") = 0;
106+
okm_long failed;
107+
__asm__ __volatile__("svc #0x80\n\tcset %2, cs"
108+
: "+r"(x0), "+r"(x1), "=r"(failed)
109+
: "r"(x16)
110+
: "memory", "cc");
111+
is_duplicate = x1 != 0;
112+
return failed ? -x0 : x0;
113+
#else
114+
okm_long first, second;
115+
unsigned char failed;
116+
__asm__ __volatile__("syscall"
117+
: "=a"(first), "=d"(second), "=@ccc"(failed)
118+
: "a"(2L | 0x2000000L)
119+
: "rcx", "r11", "memory", "cc");
120+
is_duplicate = second != 0;
121+
return failed ? -first : first;
122+
#endif
123+
}
124+
86125
// The numbers. They are the same on both architectures this implementation
87126
// supports, which is the reason the table is not per-architecture as it is on
88127
// the other kernel.

0 commit comments

Comments
 (0)