crash with MIDIPacketNext

212 Views Asked by At

I wrote an app ten years ago in Objective-C with PGMidi, which is a helper class for Core MIDI. PGMidi's input functions look like this:

static void PGMIDIReadProc(const MIDIPacketList *pktlist, void *readProcRefCon, void *srcConnRefCon) {
    PGMidiSource *source = arc_cast<PGMidiSource>(srcConnRefCon);
    [source midiRead:pktlist fromPort:source.name];
}

- (void) midiRead:(const MIDIPacketList *)pktlist fromPort:(NSString *)port {
    NSArray *delegates = self.delegates;
    for (NSValue *delegatePtr in delegates) {
        id<PGMidiSourceDelegate> delegate = (id<PGMidiSourceDelegate>)[delegatePtr pointerValue];
        [delegate midiSource:self midiReceived:pktlist fromPort:port];
    }
}

And that passes data to my app functions:

static NSMutableArray *packetToArray(const MIDIPacket *packet) {
    NSMutableArray *values = [[NSMutableArray alloc] initWithCapacity:3];
    NSNumber *value;
    for (int j=0; j<packet->length; j++) {
        value = [[NSNumber alloc] initWithUnsignedInt:packet->data[j]];
        [values addObject:value];
    }
    return values;
}

- (void)midiSource:(PGMidiSource *)midi midiReceived:(const MIDIPacketList *)packetList fromPort:(NSString *)port {
    const MIDIPacket *packet = &packetList->packet[0];
    for (int i=0; i<packetList->numPackets; ++i) {
        NSDictionary *data = [[NSDictionary alloc] initWithObjectsAndKeys:
            packetToArray(packet), @"values",
            port, @"port",
        nil];
        [self performSelectorOnMainThread:@selector(receiveMidiData:) withObject:data waitUntilDone:FALSE];
    
        packet = MIDIPacketNext(packet);
    }
}

This has always worked fine. But earlier this year I converted my app to Swift. I left the PGMidi class alone, but rewrote my app functions to this:

func packetToArray(_ packet: MIDIPacket) -> [Int] {
    var values = [Int]()
    let packetMirror = Mirror(reflecting: packet.data)
    let packetValues = packetMirror.children.map({ $0.value as? UInt8 })
    for i in 0..<Int(packet.length) {
        if (packetValues.count > i), let value = packetValues[i] {
            values.append(Int(value))
        }
    }
    return values
}

@objc func midiSource(_ midi: PGMidiSource, midiReceived packetList: UnsafePointer<MIDIPacketList>, fromPort port: String) {
    var packet = packetList.pointee.packet // this gets the first packet
    for _ in 0..<packetList.pointee.numPackets {
        let packetArray = App.packetToArray(packet)
        let data: [AnyHashable : Any] = [
            "values" : packetArray,
            "port" : port
        ]
        self.performSelector(onMainThread: #selector(receiveMidiData(_:)), with: data, waitUntilDone: false)

        packet = MIDIPacketNext(&packet).pointee
    }
}

This mostly works, but I'm getting some crashes on the MIDIPacketNext line. This isn't consistent and seems to happen when a lot of data is coming in, like from the expression wheel on a keyboard. One crash log included this:

Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x000000016b78914c
VM Region Info: 0x16b78914c is not in any region. Bytes after previous region: 37197 Bytes before following region: 360050356
REGION TYPE START - END [ VSIZE] PRT/MAX SHRMOD REGION DETAIL
Stack 16b6f8000-16b780000 [ 544K] rw-/rwx SM=PRV thread 6
---> GAP OF 0x15768000 BYTES
unused shlib __TEXT 180ee8000-180f2c000 [ 272K] r-x/r-x SM=COW ... this process

Another was slightly different, but pointed to the same line of code:

Exception Type:  EXC_BAD_ACCESS (SIGBUS)
Exception Subtype: KERN_PROTECTION_FAILURE at 0x000000016beb46cc
VM Region Info: 0x16beb46cc is in 0x16beb4000-0x16beb8000;  bytes after start: 1740  bytes before end: 14643
      REGION TYPE                 START - END      [ VSIZE] PRT/MAX SHRMOD  REGION DETAIL
      Stack                    16be2c000-16beb4000 [  544K] rw-/rwx SM=PRV  thread 5
--->  STACK GUARD              16beb4000-16beb8000 [   16K] ---/rwx SM=NUL  ... for thread 9
      Stack                    16beb8000-16bf40000 [  544K] rw-/rwx SM=PRV  thread 9

Should I be accessing the packets differently somehow to avoid this, or is there anything I can do to detect the error situation and avoid the crash?

2

There are 2 best solutions below

0
arlomedia On BEST ANSWER

The comment from Eugene Dudnyk led me to a solution. I rewrote my midiSource delegate function as follows, with the UnsafePointer extension added:

@objc func midiSource(_ midi: PGMidiSource, midiReceived packetList: UnsafePointer<MIDIPacketList>, fromPort port: String) {
    let packetList = packetList.loadUnaligned(as: MIDIPacket.self, count: Int(packetList.pointee.numPackets))
    for packet in packetList {
        let packetArray = App.packetToArray(packet)
        let data: [AnyHashable : Any] = [
            "values" : packetArray,
            "port" : port
        ]
        self.performSelector(onMainThread: #selector(receiveMidiData(_:)), with: data, waitUntilDone: false)
    }
}

extension UnsafePointer {
    func loadUnaligned<T>(as: T.Type, count: Int) -> [T] {
        assert(_isPOD(T.self)) // relies on the type being POD (no refcounting or other management)
        let buffer = UnsafeMutablePointer<T>.allocate(capacity: count)
        defer { buffer.deallocate() }
        memcpy(buffer, self, MemoryLayout<T>.size * count)
        return (0..<count).map({ index in buffer.advanced(by: index).pointee })
    }
}

No more crashes!

1
Bradley Ross On

My first thought is that you should be using MIDIReadBlock instead of MIDIReadProc. MIDIReadProc is deprecated and has problems with handling multiple threads, which is exactly what would happen with a high rate of data coming in. MIDIReadBlock is marked as deprecated, but it is actually the current method until you switch to the new functions for MIDI 2.0. See https://bradleyross.github.io/ObjectiveC-Examples/Documentation/BRossTools/FunctionalArea.html and https://bradleyross.github.io/ObjectiveC-Examples/Documentation/BRossTools/CoreMidi.html. There are also a number of CoreMIDI examples on GitHub using Swift. My examples use Objective-C instead of Swift. There are a lot of methods in CoreMIDI that have the same first part of the function name. Many of these methods are not only deprecated, but have problems. (The names of these functions do not have the words Block or Protocol.)The second set must be used for everything before the latest version of iOS and MacOS. They will have the word Block at the end. The third set will have the word Protocol in it and can only be used with the latest version of the operating systems. These are designed to handle both MIDI 1.0 and MIDI 2.0. This means that there are functions MIDIDestinationCreate, MIDIDestinationCreateWithBlock, and MIDIDestinationCreateWith Protocol. The root of the documentation mentioned above is https://bradleyross.github.io/ObjectiveC-Examples/Documentation/BRossTools/. We really need better documentation for these,