Very helpful information, thank you.
I also found that when I enable App Sandbox there's another thread running. Without app sandbox there's just one thread. Could that affect fork?
Crashing example that involves just fork with nothing else:
import Cocoa
class AppDelegate: NSObject, NSApplicationDelegate {
var window: NSWindow?
}
func app() {
let appDelegate = AppDelegate()
let application = NSApplication.shared
application.delegate = appDelegate
print("launch app")
let _ = NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv)
print("never here")
}
// without App Sandbox there's one thread at this point
// with App Sandbox there are two threads at this point
let pid = forkme()
if pid == -1 {
fatalError("error")
} else if pid == 0 {
print("after fork in child")
app()
print("exiting child")
} else {
print("after fork in parent")
usleep(10_000_000)
print("exiting parent")
}
C helper:
pid_t forkme(void) {
return fork();
}
Partial crash log:
System Integrity Protection: enabled
Crashed Thread: 0 Dispatch queue: com.apple.main-thread
Exception Type: EXC_BREAKPOINT (SIGTRAP)
Exception Codes: 0x0000000000000001, 0x000000019984afac
Termination Reason: Namespace SIGNAL, Code 5 Trace/BPT trap: 5
Terminating Process: exc handler [47238]
Application Specific Information:
crashed on child side of fork pre-exec
Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
0 libobjc.A.dylib 0x19984afac objc_initializeAfterForkError + 0
1 libobjc.A.dylib 0x199832958 initializeNonMetaClass + 1064
2 libobjc.A.dylib 0x1998325bc initializeNonMetaClass + 140
3 libobjc.A.dylib 0x19984cf14 initializeAndMaybeRelock(objc_class*, objc_object*, locker_mixin<lockdebug::lock_mixin<objc_lock_base_t>>&, bool) + 156
4 libobjc.A.dylib 0x1998321c8 lookUpImpOrForward + 884
5 libobjc.A.dylib 0x199831b64 _objc_msgSend_uncached + 68
6 For 0x104a68b68 app() + 76 (main.swift:43)
7 For 0x104a68488 main + 480 (main.swift:57)
8 dyld 0x199873f28 start + 2236
There's also this debug message in the console:
objc[47238]: +[NSResponder initialize] may have been in progress in another thread when fork() was called.
exiting parent
If I swap what's done in child and what's done in parent:
} else if pid == 0 {
print("after fork in child")
usleep(10_000_000)
print("exiting child")
} else {
print("after fork in parent")
app()
print("exiting parent")
}
then it works. So there's something I can and can't do in the child process? What are the rules?
In one of the links above you mentioned that fork is fundamentally incompatible with "higher level frameworks"? Does that include, say, XPC? or URLSession? or Network framework?
Basically I want to make the old style "treadless" app (understandably the threads created by OS or standard library are probably inevitable): create N sub processes, off-load work to them from the main process and communicate to those processes by some means (I'm considering between pipes, XPC, Network framework and URLSession but I am open to consider a better alternative!).
Post
Replies
Boosts
Views
Activity
The crash in the launched app looks like this:
Process: Preview [22445]
Path: /System/Applications/Preview.app/Contents/MacOS/Preview
...
System Integrity Protection: enabled
Crashed Thread: 0 Dispatch queue: com.apple.main-thread
Exception Type: EXC_BREAKPOINT (SIGTRAP)
Exception Codes: 0x0000000000000001, 0x00000001a554d1c0
Termination Reason: Namespace SIGNAL, Code 5 Trace/BPT trap: 5
Terminating Process: exc handler [22445]
Application Specific Signatures:
SYSCALL_SET_PROFILE
Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
0 libsystem_secinit.dylib 0x1a554d1c0 _libsecinit_appsandbox.cold.5 + 92
1 libsystem_secinit.dylib 0x1a554c3d0 _libsecinit_appsandbox + 1688
2 libsystem_trace.dylib 0x19994d81c _os_activity_initiate_impl + 64
3 libsystem_secinit.dylib 0x1a554bce4 _libsecinit_initializer + 80
4 libSystem.B.dylib 0x1a5562654 libSystem_initializer + 272
5 dyld 0x1998901d8 invocation function for block in dyld4::Loader::findAndRunAllInitializers(dyld4::RuntimeState&) const::$_0::operator()() const + 168
6 dyld 0x1998d1e94 invocation function for block in dyld3::MachOAnalyzer::forEachInitializer(Diagnostics&, dyld3::MachOAnalyzer::VMAddrConverter const&, void (unsigned int) block_pointer, void const*) const + 340
7 dyld 0x1998c51a4 invocation function for block in dyld3::MachOFile::forEachSection(void (dyld3::MachOFile::SectionInfo const&, bool, bool&) block_pointer) const + 528
8 dyld 0x1998702d8 dyld3::MachOFile::forEachLoadCommand(Diagnostics&, void (load_command const*, bool&) block_pointer) const + 296
9 dyld 0x1998c41cc dyld3::MachOFile::forEachSection(void (dyld3::MachOFile::SectionInfo const&, bool, bool&) block_pointer) const + 192
10 dyld 0x1998d1958 dyld3::MachOAnalyzer::forEachInitializer(Diagnostics&, dyld3::MachOAnalyzer::VMAddrConverter const&, void (unsigned int) block_pointer, void const*) const + 516
11 dyld 0x19988c85c dyld4::Loader::findAndRunAllInitializers(dyld4::RuntimeState&) const + 448
12 dyld 0x199895f6c dyld4::PrebuiltLoader::runInitializers(dyld4::RuntimeState&) const + 44
13 dyld 0x1998b07fc dyld4::APIs::runAllInitializersForMain() + 76
14 dyld 0x1998752d0 dyld4::prepare(dyld4::APIs&, dyld3::MachOAnalyzer const*) + 3480
15 dyld 0x199873e18 start + 1964
...
It's for macOS. In my experience the child process crashes if I enable sandbox in the main app. Minimal test:
Test.m
#include "Test.h"
#import <Cocoa/Cocoa.h>
#include <spawn.h>
void launchWithFork(void) {
pid_t pid = fork();
if (pid == -1) {
printf("error %d\n", pid);
} else if (pid == 0) {
execv("/System/Applications/Preview.app/Contents/MacOS/Preview", nil);
exit(0);
} else {
// parent
}
}
void launchWithSpawn(void) {
pid_t pid = 0;
int result = posix_spawn(&pid, "/System/Applications/Preview.app/Contents/MacOS/Preview", nil, nil, nil, nil);
printf("result: %d\n", result);
}
void launchWithTask(void) {
NSURL* url = [NSURL fileURLWithPath:@"/System/Applications/Preview.app/Contents/MacOS/Preview"];
NSError* error = nil;
NSTask* task = [NSTask launchedTaskWithExecutableURL:url arguments:@[] error:&error terminationHandler:nil];
NSLog(@"task %@, error: %@", task, error);
}
void launchWithWorkspace(void) {
NSURL* url = [NSURL fileURLWithPath:@"/System/Applications/Preview.app/Contents/MacOS/Preview"];
NSError* error = nil;
NSApplication* app = [NSWorkspace.sharedWorkspace launchApplicationAtURL:url options:0 configuration:@{} error:&error];
NSLog(@"task %@, error: %@", app, error);
}
Test.h
void launchWithFork(void);
void launchWithSpawn(void);
void launchWithTask(void);
void launchWithWorkspace(void);
Bridging header
#include "Test.h"
main.swift
launchWithFork()
//launchWithSpawn()
//launchWithTask()
//launchWithWorkspace()
RunLoop.current.run(until: .distantFuture)
Without app sandbox enabled all launching methods work. With app sandbox enabled only the last method works.
(I'm launching Preview.app for a test, in the actual app it will launch a different thing).
Can't you use google drive API directly from the QLExtension?
"The interpretation of any other octets within the data shall be defined by the manufacturer specified by the company identifier"
Where could I find the spec of Apple's manufacturerData?
Assuming signal propagating in "vacuum" approximation:
FSPL = ((4*pi*d*f) / c)^2 // Free-space path loss
rssi = txPowerLevel - FSPL
rssi0 = txPowerLevel - FSPL0 = 12 - 40 = -28 // rssi at 1 meter distance
// pi = 3.14, d = 1, f = 2.4E9, c = 3e8
Is this correct calculation?
(given the approximate nature of it of course, plus I'd need to apply some high frequency filter to get rid of noise).
Thank you! I guess if I want to drill into those manufacturer fields I'd have to hunt for those specs in many various places. Are you aware of a resource (a web page? json?, open database?) that attempts to summarise some frequently used manufacturerData fields in one place?
If I were to do it, I'd probably use json, something like this:
[
{
"id": "0x0123", // company identifier
"specification": "a url pointing to a particular specification",
"version: 1.3",
"fields": [
{ "size": 1, "type": "int", "name": "flags", "description": "Various flags" }, // bytes 0 ..< 1
{ "size": 1, "type": "byte", "name": "padding", "description": "-" }, // bytes 1 ..< 2
{ "size": 3, "type": "int", "name": "batteryLevel", "description": "Battery level" }, // bytes 2 ..< 5
]
},
{
"id": "0x0456", // company identifier
...
]
Would I be the first one to do this or is something like this already exist?
Nice. And if you don't want using ssh from Mac for some reason (e.g. you are paranoid and think that it might work over ssh as part of Apple magic but that doesn't mean it will work with other protocols) you can do the same for the blower part and launch it on mac:
class Model: ObservableObject {
@Published var text: String = ""
private var listenerQ: NWListener?
private var browserQ: NWBrowser?
func log(_ string: String) {
print(string)
text.append(string + "\n")
}
func startBrowser() -> NWBrowser {
log("browser will start")
let browser = NWBrowser(for: .bonjour(type: "_ssh._tcp", domain: nil), using: .tcp)
browser.stateUpdateHandler = { newState in
self.log("brower did change state, new: \(newState)")
}
browser.browseResultsChangedHandler = { results, changes in
self.log("brower did change results, new: \(results) \(changes)")
}
browser.start(queue: .main)
return browser
}
func stopBrowser(browser: NWBrowser) {
log("browser will stop")
browser.stateUpdateHandler = nil
browser.cancel()
}
func startStopBrowser() {
if let browser = browserQ {
browserQ = nil
stopBrowser(browser: browser)
} else {
browserQ = startBrowser()
}
}
var isBrowserStarted: Bool {
browserQ != nil
}
var isListenerStarted: Bool {
listenerQ != nil
} ...
}
Change the "_ssh._tcp" appropriately and don't forget to change the corresponding string in "Bonjour services" array in the plist of your iOS app.
I also used log instead and in addition to bare print so iPhone app can be self contained and show the log without Xcode:
struct ContentView: View {
@StateObject private var model = Model()
var body: some View {
VStack(alignment: .leading) {
Button(model.isListenerStarted ? "Stop Listener" : "Start Listener") {
model.startStopListener()
}.padding()
Button(model.isBrowserStarted ? "Stop Browser" : "Start Browser") {
model.startStopBrowser()
}.padding()
HStack {
Text(model.text)
.font(.caption2.monospaced())
Spacer()
}
Spacer()
}
.padding()
}
}
IMPORTANT It’s possible that this only works because of developer bits (the Mac having Xcode installed or the iPhone being in Developer Mode). I don’t have time to test this with a non-developer configuration. I encourage you try that for yourself.
This is very important indeed unless of course you want to make an app that works just for you or for people who wouldn't mind installing relevant components in case they are important. Just to check if it works in clean environment is somewhat a challenge, you'd either need to enable developer mode on the phone, install the app via Xcode and then disable developer mode, making sure it cleanly disabled all dev bits and pieces, or you'd need to make a TestFlight version of this tester app. Also make sure you do the actual test on the Mac that didn't have Xcode installed.
The open questions at this point:
does this work in "clean environment" (without developer bits on Mac and/or iPhone)? To be tested.
assuming it does (or it is not important to have environment clean), does it still work over USB if I leave wifi / bluetooth enabled? (This feature might unwanted, e.g. for security reasons. Or, equally, it could be desired).
assuming working over wifi/bluetooth is undesired for some reason (e.g. security), could we make it stop working when wifi/BT are enabled and I unplugged the USB cable?
(in other words set an option to disable all wireless connectivity similar to how it is possible disabling cellular connectivity in URLSession).
This is old but let me fill the gap for the benefit of future readers with the same question.
the other products definitely use usbmuxd, there's no other way.
connecting iPhone to ethernet via a powered adaptor or two looks very odd and not ergonomic for end users.
the cables themselves are not quite ergonomic either, if you can use wifi - go for it.
cables still have their use, I wish Apple to introduce a supported API or enhance one of existing API's (e.g. Network framework) to support connectivity over USB.
Hahaha, found a stupid mistake above, closing the issue.
Same issue under iOS.
Older API works (calls the callback):
func startMic1() {
let session = AVAudioSession.sharedInstance()
try! session.setCategory(.playAndRecord, mode: .default)
try! session.setPreferredSampleRate(44100)
try! session.setPreferredInputNumberOfChannels(1)
try! session.setPreferredIOBufferDuration(0.1)
try! session.setActive(true)
var desc = AudioComponentDescription(componentType: kAudioUnitType_Output,
componentSubType: kAudioUnitSubType_RemoteIO,
componentManufacturer: kAudioUnitManufacturer_Apple, componentFlags: 0, componentFlagsMask: 0)
let comp = AudioComponentFindNext(nil, &desc)
var unit: AudioUnit!
var err = AudioComponentInstanceNew(comp!, &unit)
precondition(err == noErr)
var enable: UInt32 = 1
err = AudioUnitSetProperty(unit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, 1, &enable, 4)
precondition(err == noErr)
var disable: UInt32 = 0
err = AudioUnitSetProperty(unit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, 0, &disable, 4)
precondition(err == noErr)
var cb = AURenderCallbackStruct(inputProc: { _, _, _, _, _, _ in
fatalError("callback")
}, inputProcRefCon: nil)
err = AudioUnitSetProperty(unit, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global, 1, &cb, 16)
precondition(err == noErr)
err = AudioUnitInitialize(unit)
precondition(err == noErr)
err = AudioOutputUnitStart(unit)
precondition(err == noErr)
// from now on the (UI) app continues its normal run loop
}
Newer API doesn't work (callback is not called):
func startMic2() {
let session = AVAudioSession.sharedInstance()
try! session.setCategory(.playAndRecord, mode: .default)
try! session.setPreferredSampleRate(44100)
try! session.setPreferredInputNumberOfChannels(1)
try! session.setPreferredIOBufferDuration(0.1)
try! session.setActive(true)
let compDesc = AudioComponentDescription(componentType: kAudioUnitType_Output,
componentSubType: kAudioUnitSubType_RemoteIO,
componentManufacturer: kAudioUnitManufacturer_Apple,
componentFlags: 0, componentFlagsMask: 0)
let unit = try! AUAudioUnit(componentDescription: compDesc, options: [])
unit.isInputEnabled = true
unit.isOutputEnabled = false
precondition(unit.canPerformInput)
unit.inputHandler = { flags, timeStamp, frameCount, bus in
fatalError("never gets here") // now the weird part - this is never called!
}
try! unit.allocateRenderResources()
try! unit.startHardware() // let's go!
print("mic should be working now... why it doesn't?")
// from now on the (UI) app continues its normal run loop
}
Looks like Apple audio bug to me.
The first version uses older API and it works ok (in that it calls the callback):
func startMic1(_ mic: AudioDeviceID) {
var desc = AudioComponentDescription(componentType: kAudioUnitType_Output, componentSubType: kAudioUnitSubType_HALOutput,
componentManufacturer: kAudioUnitManufacturer_Apple, componentFlags: 0, componentFlagsMask: 0)
let comp = AudioComponentFindNext(nil, &desc)
var unit: AudioUnit!
AudioComponentInstanceNew(comp!, &unit)
var enable: UInt32 = 1
AudioUnitSetProperty(unit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, 1, &enable, 4)
var disable: UInt32 = 0
AudioUnitSetProperty(unit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, 0, &disable, 4)
var device = mic
AudioUnitSetProperty(unit, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global, 0, &device, 8)
var cb = AURenderCallbackStruct(inputProc: { _, _, _, _, _, _ in
fatalError("callback")
}, inputProcRefCon: nil)
AudioUnitSetProperty(unit, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global, 1, &cb, 16)
AudioUnitInitialize(unit)
AudioOutputUnitStart(unit)
}
The second version utilises a newer API and doesn't work (doesn't call the callback):
func startMic2(_ mic: AudioDeviceID) {
let desc = AudioComponentDescription(componentType: kAudioUnitType_Output, componentSubType: kAudioUnitSubType_HALOutput,
componentManufacturer: kAudioUnitManufacturer_Apple, componentFlags: 0, componentFlagsMask: 0)
let unit = try! AUAudioUnit(componentDescription: desc, options: [])
unit.isInputEnabled = true
unit.isOutputEnabled = false
try! unit.setDeviceID(mic)
unit.inputHandler = { _, _, _, _ in
fatalError("callback")
}
try! unit.allocateRenderResources()
try! unit.startHardware()
}
Error handling removed for brevity.
It works, thank you!
#include <mach/mach.h>
#include <mach/mach_vm.h>
int safe_memcpy(void* dst, const void* src, size_t size) {
mach_vm_size_t outSize = 0;
kern_return_t err = mach_vm_read_overwrite(mach_task_self(), (mach_vm_address_t)src, size, (mach_vm_address_t)dst, &outSize);
if (err) return err;
assert(outSize == size);
return true;
}
void dumpByte(const char* address) {
unsigned char buffer = 0;
int err = safe_memcpy(&buffer, address, 1);
printf("error: %d, byte: %02x\n", err, buffer);
}
Note that this is a relatively slow call, so it’d be best to tweak your dumpByte abstraction to return a block of bytes.
yep, got you.
I want something quite simple:
void dumpByte(const char* address) {
char v = *address;
bool success = ......;
printf("success: %d, %0x\n", success, v);
}
The memory access can either succeed or fail with, say EXC_BAD_ACCESS.
I found this article of yours:
https://developer.apple.com/forums/thread/113742
Could you recommend a sample code to achieve that goal on macOS?
This is for debugging purposes and accessing wrong memory in this case if not an error.
Thank you.
The droid you’re looking for is the receivesCredentialSecurely property on the protection space.
Awesome! Will mark the question answered once I test it.