intermittent EXC_BAD_ACCESS in static audio-analysis function

2

I am writing an iOS app that uses live audio analysis. It has an intermittent crash (after ~5 min, only on the Simulator. While this means it's not of concern for a shipping app, it sure is a pain for development, and besides, I'll sleep better at night if I track it down. The crash always happens in the same place, in a static function in my audio-analysis code: The crash has happened here:

struct Tone {
// (other stuff)

// THIS is the problem function:
static bool dbCompare(Tone const& l, Tone const& r) { return l.db < r.db; }
  }
};

And also here:

Tone const* findTone(double minfreq = 70.0, double maxfreq = 700.0) const {
    if (m_tones.empty()) { m_oldfreq = 0.0; return NULL; }
    double db = std::max_element(m_tones.begin(), m_tones.end(), Tone::dbCompare)->db;
    Tone const* best = NULL;
    double bestscore = 0;
    for (tones_t::const_iterator it = m_tones.begin(); it != m_tones.end(); ++it) {

    // ON THIS LINE. Is my iterator somehow becoming invalid?:
        if (it->db < db - 20.0 || it->freq < minfreq || it->age < Tone::MINAGE) continue;
        if (it->freq > maxfreq) break;
        double score = it->db - std::max(180.0, std::abs(it->freq - 300.0)) / 10.0;
        if (m_oldfreq != 0.0 && std::fabs(it->freq/m_oldfreq - 1.0) < 0.05) score += 10.0;
        if (best && bestscore > score) break;
        best = &*it;
        bestscore = score;
    }
    m_oldfreq = (best ? best->freq : 0.0);
    return best;
}

(Ok, this fix, below, is wrong per @Justin. I'm leaving it here for completeness, for now at least). In an attempt to survive any null pointers that might get passed in, I added some checks and attempts at error-handling:

struct Tone {
    static bool dbCompare(Tone const& l, Tone const& r) {
    try {
      if (&l == NULL || &r == NULL) throw 1;
      return l.db < r.db;
      throw 1;
    }
    catch(int e) {
      NSLog(@"AUDIO ERROR: Pointer invalid. %d", e);
    }
    return 0;
  }
};

No dice, it still fails on the same line. Am I misunderstanding try-catch-throw? Is the error just happening further back up the pipeline?

This bit of code is from the open-source Rock-Band-style game Performous, and their game doesn't have this problem. (It's also not for the iPhone.) So this gives me hope that I can eliminate the Simulator crash...

EDIT 2 Per @Justin I am running with GuardMalloc. Simulator 5.0 gives immediate crash— there seem to be some SO questions about this but for now I'm running the 4.3 simulator, which gives the crash in the normal fashion, but with nothing else I know how to use:

#0  0x94f38c97 in malloc_error_break ()
#1  0x94efa4ce in szone_error ()
#2  0x94efc35f in allocate_pages ()
#3  0x94f013cc in allocate_pages_securely ()
#4  0x94f019b8 in szone_malloc_should_clear ()
#5  0x94f0266b in szone_malloc ()
#6  0x00331cc3 in GMmalloc_zone_malloc ()
#7  0x94f38edb in malloc_set_zone_name ()
#8  0x94f394a8 in _malloc_initialize ()
#9  0x94f3986b in malloc ()
#10 0x00002ef0 in start ()
#11 0x00013f1d in std::_List_const_iterator<Tone> std::max_element<std::_List_const_iterator<Tone>, bool (*)(Tone const&, Tone const&)>(std::_List_const_iterator<Tone>, std::_List_const_iterator<Tone>, bool (*)(Tone const&, Tone const&)) at /Developer/of_007_iphone/apps/cwi007/SingXO/SingXO/Pitch/pitch.hh:25
#12 0x000139b2 in Analyzer::findTone(double, double) const ()
#13 0x00011950 in -[AppDelegate timerCallback:] ()
#14 0x00115308 in -[CCTimer update:] ()
#15 0x0011e124 in -[CCScheduler tick:] ()
#16 0x0014993a in -[CCDirectorIOS drawScene] ()
#17 0x0014bda4 in -[CCDirectorDisplayLink mainLoop:] ()
#18 0x007baa88 in CA::Display::DisplayLink::dispatch(unsigned long long, unsigned long long) ()
#19 0x007babcd in CA::Display::EmulatorDisplayLink::callback(__CFRunLoopTimer*, void*) ()
#20 0x019c88c3 in __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ ()
#21 0x019c9e74 in __CFRunLoopDoTimer ()
#22 0x019262c9 in __CFRunLoopRun ()
#23 0x01925840 in CFRunLoopRunSpecific ()
#24 0x01925761 in CFRunLoopRunInMode ()
#25 0x020bb1c4 in GSEventRunModal ()
#26 0x020bb289 in GSEventRun ()
#27 0x00beac93 in UIApplicationMain ()
#28 0x00002fb6 in main ()

I also get this in the console:

GuardMalloc[MyApp-723]: *** mmap(size=2097152) failed (error code=12)
*** error: can't allocate region
*** set a breakpoint in malloc_error_break to debug

Oh, and of course, here's the function that NOW seems to be the likely culprit:

- (void)timerCallback:(NSTimer *)timer {
  Tone const * tone = audio->analyzer->findTone();
  if (tone && tone->db > kToneMinDB) {
    self.currentPitch = tone->freq;
    self.currentVolume = tone->stabledb;
    self.currentNoteName = [NSString stringWithCString:scale->getNoteStr(tone->freq).c_str() encoding:NSUTF8StringEncoding];
//    NSLog(@"Current Note Name in timerCallback: %@", currentNoteName);
    self.currentNoteFunction = [pitchFilter getPitchFunctionFromPitchName:currentNoteName];
  } else {
    self.currentNoteName = @"--";
    self.currentNoteFunction = -1;
  }

}

I don't understand how the program is entering the if (tone) block without a valid tone, or conversely how it can crash if the tone is valid. Help?

(This is getting long. Should I delete some of it in the service of a more-focused question?)

Things I'm thinking: 1. It's a threading issue ::shudder:: where one thread is changing the audio data while another one is reading it. 2. Something in the timerCallback function is corrupting, I dunno, the stack because it's alloc'ing too often. This function gets called a lot, but not an extreme amount; it's scheduled for every 150 ms.

Not sure why either of these would be an issue in the simulator but not on the device, though.

c++
ios
audio
reference
objective-c++
asked on Stack Overflow Jan 30, 2012 by buildsucceeded • edited May 23, 2017 by Community

2 Answers

1

A C++ reference can never be 0/NULL. If it were, you're already well into Undefined Territory. You will have to move your tests up in the execution chain if the parameters may be 0.

That is to say, the only well formed version of dbCompare looks like this:

struct Tone {
    static bool dbCompare(Tone const& l, Tone const& r) {
        return l.db < r.db;
    }
};

Update

Your iterator would be invalid if it were resized while you iterate -- I don't see that happening in this implementation. Are you resizing the container from another thread?

Also, try running it with GuardMalloc - it aims to make these issues obvious to you. By failing sooner, it will be more local to the implementation (in many cases).

answered on Stack Overflow Jan 30, 2012 by justin • edited Jan 30, 2012 by justin
1

The ' best = &*it' line looks odd to me. Is the container storing by reference or value? It seems like you might be returning the address of temporary storage. That could cause problems down the line.

answered on Stack Overflow Jan 31, 2012 by AShelly

User contributions licensed under CC BY-SA 3.0