std :: deque,portaudio和AddressSanitizer

时间:2020-06-02 14:11:58

标签: c++

我有一个音乐播放器,可以生成模拟声音并使用一些音频库在PC上播放它。

在portaudio库中,我有一个线程来生成声音,然后将其推入出队列,说明在正确的时间过去了,并处理了另一帧声音。

portaudio在另一个线程中具有回调函数,该线程在适当的时候要求使用音频数据。

在此例行程序中,我从出站队列中获取数据(用作FIFO)并使用它们。

程序运行良好,持续了几秒钟/分钟,然后AddressSanitizer从双端队列捕获了堆缓冲区溢出。

这里有很多有关代码的知识。

产生声音并挤入出场的常规程序:

    void AudioDriverPortAudio::play(void* buffer, unsigned long int bufferSize)
{
  unsigned char* memory=(unsigned char*)buffer;

#ifdef XSID_PORTAUDIO_DEBUG
  cout << "AudioDriverPortAudio::play" << endl;
#endif  


  // insert the data at the end of queue
  for (unsigned long int i=0; i<bufferSize; i++) {
    memoryDeque.push_back(*memory);
    memory++;
  }  

  std::cerr << memoryDeque.size() << std::endl;

  int nSize;  // size of one sample
  if (config.precision == AudioConfig::BITS_8) nSize=1;
  else nSize=2;
  nSize*=config.channels;

  // delay needed by actual configuration
  unsigned int delay=1000*(bufferSize/nSize)/config.frequency;

  // compensate for not integer delay to use
  if (memoryDeque.size() > 3*bufferSize) delay++;

  // passed time from last call
  unsigned long int passedTime=std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count()-portTime;

  // calculate new delay to use
  signed int newDelay=delay-passedTime;

  if (memoryDeque.size()>bufferSize)
    if (newDelay>0) Pa_Sleep(newDelay);

  if (memoryDeque.size()>2*bufferSize) Pa_StartStream( stream );

  // remember the actual time
  portTime=std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();   
}

portaudio的回调功能:

int AudioDriverPortAudio:: portaudio_callback( const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, 
                               const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void *userData ) {
#ifdef XSID_PORTAUDIO_DEBUG
  cout << "portaudio_callback " << endl;
#endif
  //unsigned char value;
  unsigned char *out = (unsigned char*)outputBuffer;

  AudioDriverPortAudio* userD=(AudioDriverPortAudio *) userData;

  (void) inputBuffer; /* Prevent unused variable warning. */
  (void) timeInfo;    /* Prevent unused variable warning. */
  (void) statusFlags; /* Prevent unused variable warning. */
  (void) userData;    /* Prevent unused variable warning. */

  std::cerr << userD->memoryDeque.size() << " " << framesPerBuffer <<  std::endl;

  int nSize;  // size of one sample
  if (userD->lastConfig.precision == AudioConfig::BITS_8) nSize=1;
  else nSize=2;
  nSize*=userD->lastConfig.channels; 

  if (userD->memoryDeque.size()< nSize*framesPerBuffer) {
      std::cerr << "Under-run buffer";
      return 0;
  }    

   for (int i=0; i<nSize*framesPerBuffer; i++) {
      // prevent empty buffer usage  
      if (userD->memoryDeque.empty()) break;  
      *out++=userD->memoryDeque.front();  
      userD->memoryDeque.pop_front();
    }

  return paContinue;
}

这是错误:

  =================================================================
==136487==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x615000affc80 at pc 0x000000525ed0 bp 0x7fcda22a1bb0 sp 0x7fcda22a1ba0
READ of size 1 at 0x615000affc80 thread T35
76672
    #0 0x525ecf in AudioDriverPortAudio::portaudio_callback(void const*, void*, unsigned long, PaStreamCallbackTimeInfo const*, unsigned long, void*) ../src/audio/AudioDriverPortAudio.cpp:301
    #1 0x7fce04a52d7c  (/lib64/libportaudio.so.2+0x9d7c)
    #2 0x7fce04a54680  (/lib64/libportaudio.so.2+0xb680)
    #3 0x7fce04a5d895  (/lib64/libportaudio.so.2+0x14895)
    #4 0x7fce04a844e1 in start_thread (/lib64/libpthread.so.0+0x94e1)
    #5 0x7fce022b26a2 in clone (/lib64/libc.so.6+0x1016a2)

0x615000affc80 is located 0 bytes to the right of 512-byte region [0x615000affa80,0x615000affc80)
allocated by thread T34 (QThread) here:
    #0 0x7fce04c94a97 in operator new(unsigned long) (/lib64/libasan.so.5+0x10fa97)
    #1 0x51d45e in __gnu_cxx::new_allocator<unsigned char>::allocate(unsigned long, void const*) /usr/include/c++/9/ext/new_allocator.h:114
    #2 0x51d3d6 in std::allocator_traits<std::allocator<unsigned char> >::allocate(std::allocator<unsigned char>&, unsigned long) /usr/include/c++/9/bits/alloc_traits.h:444
    #3 0x51d2c9 in std::_Deque_base<unsigned char, std::allocator<unsigned char> >::_M_allocate_node() /usr/include/c++/9/bits/stl_deque.h:620
    #4 0x5227be in void std::deque<unsigned char, std::allocator<unsigned char> >::_M_push_back_aux<unsigned char const&>(unsigned char const&) /usr/include/c++/9/bits/deque.tcc:492
    #5 0x5220fb in std::deque<unsigned char, std::allocator<unsigned char> >::push_back(unsigned char const&) /usr/include/c++/9/bits/stl_deque.h:1579
    #6 0x525681 in AudioDriverPortAudio::play(void*, unsigned long) ../src/audio/AudioDriverPortAudio.cpp:181
    #7 0x4f9e02 in Player::playAudio(void*, unsigned long) ../src/Player.cpp:620
    #8 0x4fb07e in Player::playJob(QThread*) ../src/Player.cpp:783
    #9 0x4fb29a in PlayerThread::run() ../src/Player.cpp:811
    #10 0x7fce02795e25  (/lib64/libQt5Core.so.5+0xc0e25)

Thread T35 created by T34 (QThread) here:
    #0 0x7fce04bbf955 in pthread_create (/lib64/libasan.so.5+0x3a955)
    #1 0x7fce04a65f47  (/lib64/libportaudio.so.2+0x1cf47)

Thread T34 (QThread) created by T0 here:
    #0 0x7fce04bbf955 in pthread_create (/lib64/libasan.so.5+0x3a955)
    #1 0x7fce0279597e in QThread::start(QThread::Priority) (/lib64/libQt5Core.so.5+0xc097e)

SUMMARY: AddressSanitizer: heap-buffer-overflow ../src/audio/AudioDriverPortAudio.cpp:301 in AudioDriverPortAudio::portaudio_callback(void const*, void*, unsigned long, PaStreamCallbackTimeInfo const*, unsigned long, void*)
Shadow bytes around the buggy address:
  0x0c2a80157f40: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c2a80157f50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c2a80157f60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c2a80157f70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c2a80157f80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0c2a80157f90:[fa]fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c2a80157fa0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c2a80157fb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c2a80157fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c2a80157fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c2a80157fe0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
  Shadow gap:              cc
==136487==ABORTING

基本上是这样说的:

* out ++ = userD-> memoryDe​​que.front();

使缓冲区溢出内部创建的数据:

memoryDe​​que.push_back(* memory);

因此,当我在输入缓冲区中获取数据时,通过推入双端队列将其复制(作为值,而不是引用),然后将从双端队列中再次读取该值,并将其作为值放入输出pulseaudio库中,我没弄错为什么双端队列在内部会引起错误。

如果我不使用AddressSanitizer运行,我会发现某些时候声音开始是错误的,再过几次,就会出现分段错误。

这种方法(具有双端队列和回调函数)即使在SDL中也可以使用,但我没有遇到这种问题。

仅作为参考,通过和使用的声音可以单声道或立体声方式为8位或16位。 它以 unsigned char 进行管理,因此,例如,对于16位立体声的pulseaudio期望有4个值,这就是为什么nSize * framesPerBuffer值是从回调函数中的双端队列中获取的。

任何提示是什么问题?

谢谢

编辑: 忘记定义:

std::deque<unsigned char> memoryDeque;  

edit2:

播放音频(它会调用相应的库:SDL,PortAudio ..):

void Player::playAudio(void* pBuf, unsigned long int bufSize)
{
    audioDriverMutex.lock();
    AudioDrivers::getDriver()->play(pBuf,bufSize);
    audioDriverMutex.unlock();
}

播放作业,其中会生成声音,然后将其传递到音频库:

void Player::playJob(QThread* thread)
{
    while ( playJobActive )
    {
         if ( ++currentBuffer == 2 )
            currentBuffer = 0;
        fillBuffer(pBuffer[currentBuffer],multiBufferSize);
        playAudio(pBuffer[currentBuffer],multiBufferSize);

        if ( endOfSong )
        {
            break;
        }
    }
}

edit3:

感谢所有人。 我上了这个课,现在问题就解决了:

#include <deque>
#include <mutex>

using namespace std;

template <typename T> class safedeque {
  public:  
    void clear()  {
     lock_guard<mutex> lock(q_mutex); 
      memoryDeque.clear();        
    }

    bool empty() {
      lock_guard<mutex> lock(q_mutex);   
      return memoryDeque.empty();
    }    

    std::size_t size() {
      lock_guard<mutex> lock(q_mutex);  
      return memoryDeque.size();
    }    

    void push_back(const T& __x) {
      lock_guard<mutex> lock(q_mutex); 
      memoryDeque.push_back(__x);
    }

    void pop_front() {
      lock_guard<mutex> lock(q_mutex); 
      memoryDeque.pop_front();
    }

    T front() {
      lock_guard<mutex> lock(q_mutex); 
      return memoryDeque.front();       
    }

  private:
    deque<T> memoryDeque;
    mutex q_mutex;
};

0 个答案:

没有答案