Отладка экземпляра другого потока, изменяющего мои данные - PullRequest
2 голосов
/ 17 марта 2010

У меня огромный глобальный массив структур. Некоторые области массива привязаны к отдельным потокам, и эти потоки могут изменять свои области массива без использования критических разделов. Но есть одна специальная область массива, к которой могут иметь доступ все потоки. Код, который обращается к этим частям массива, должен тщательно использовать критические секции (каждый элемент массива имеет свою собственную критическую секцию), чтобы исключить возможность одновременной записи двух потоков в структуру.

Теперь у меня есть таинственная ошибка, которую я пытаюсь преследовать, она происходит непредсказуемо и очень редко. Кажется, что одна из структур заполняется каким-то неправильным номером. Одно очевидное объяснение состоит в том, что другому потоку случайно разрешили установить это число, когда его следует исключить из этого.

К сожалению, этот баг кажется практически невозможным. Элемент массива, в котором отображаются неверные данные, каждый раз отличается. Я хотел бы иметь возможность установить ловушку для ошибки следующим образом: я бы вошел в критическую секцию для элемента массива N, тогда я знаю, что никакой другой поток не сможет касаться данных, тогда (до Я выхожу из критической секции) устанавливаю какой-то флаг для инструмента отладки, говоря: «если какой-либо другой поток попытается изменить данные здесь, пожалуйста, разбейте и покажите мне патч исходного кода», но я подозреваю, что такого инструмента не существует. .. или нет? Или есть какая-то совершенно другая методология отладки, которую я должен использовать.

Ответы [ 4 ]

2 голосов
/ 17 марта 2010

Как насчет упаковки ваших данных в прозрачный мьютексированный класс? Затем вы можете применить дополнительную проверку состояния блокировки.

class critical_section;

template < class T >
class element_wrapper
{
public:
    element_wrapper(const T& v) : val(v) {}
    element_wrapper() {}
    const element_wrapper& operator = (const T& v) {
#ifdef _DEBUG_CONCURRENCY 
        if(!cs->is_locked())
            _CrtDebugBreak();
#endif
        val = v;
        return *this;
    }
    operator T() { return val; }
    critical_section* cs;
private:
    T val;
};

Что касается реализации критической секции:

class critical_section
{
public:
    critical_section() : locked(FALSE) {
        ::InitializeCriticalSection(&cs);
    }
    ~critical_section() {
        _ASSERT(!locked);
        ::DeleteCriticalSection(&cs);
    }
    void lock() {
        ::EnterCriticalSection(&cs);
        locked = TRUE;
    }
    void unlock() {
        locked = FALSE;
        ::LeaveCriticalSection(&cs);
    }
    BOOL is_locked() {
        return locked;
    }
private:
    CRITICAL_SECTION cs;
    BOOL locked;
};

На самом деле вместо пользовательского флага critical_section::locked можно использовать ::TryEnterCriticalSection (за которым следует ::LeaveCriticalSection в случае успеха), чтобы определить, принадлежит ли критический раздел. Хотя реализация выше почти так же хороша.

Таким образом, соответствующее использование будет:

typedef std::vector< element_wrapper<int> > cont_t;

void change(cont_t::reference x) { x.lock(); x = 1; x.unlock(); }

int main()
{
    cont_t container(10, 0); 

    std::for_each(container.begin(), container.end(), &change);
}
0 голосов
/ 17 марта 2010

Я думал об этом, крутя педали на работу. Один из возможных способов справиться с этим - сделать участки памяти доступными только для чтения, если к ним нет активного доступа и они не защищены из-за владения критическим разделом. Предполагается, что проблема вызвана записью потока в память, когда ему не принадлежит соответствующий критический раздел.

Есть довольно много ограничений, которые мешают ему работать. Наиболее важным является тот факт, что я думаю , что вы можете устанавливать привилегии только постранично (я считаю, 4K). Так что это, вероятно, потребует некоторых очень специфических изменений в вашей схеме распределения, чтобы вы могли сузить соответствующий раздел для защиты. Вторая проблема заключается в том, что он не будет ловить мошеннический поток, записывающий в память, если другой поток активно владеет критическим разделом. Но он поймал бы это и вызвал бы немедленное нарушение доступа, если бы критическая секция не принадлежала.

Идея состоит в том, чтобы изменить ваши вызовы EnterCriticalSection на:

EnterCriticalSection()
VirtualProtect( … PAGE_READWRITE … );

И измените вызовы LeaveCriticalSection на:

VirtualProtect( … PAGE_READONLY … );
LeaveCriticalSection()

В следующем фрагменте кода показан вызов VirtualProtect

int main( int argc, char* argv[] 1
{
  unsigned char *mem;
  int i;
  DWORD dwOld;

  // this assume 4K page size
  mem = malloc( 4096 * 10 );
  for ( i = 0; i < 10; i++ )
     mem[i * 4096] = i;
  // set the second page to be readonly.  The allocation from malloc is
  // not necessarily on a page boundary, but this will definitely be in
  // the second page.
  printf( "VirtualProtect res = %d\n",
          VirtualProtect( mem + 4096,
                          1, // ends up setting entire page
                          PAGE_READONLY, &dwOld ));
  // can still read it
  for ( i = 1; i < 10; i++ )
     printf( "%d ", mem[i*4096] );
  printf( "\n" );

  // Can write to all but the second page
  for ( i = 0; i < 10; i++ )
     if ( i != 1 ) // avoid second page which we made readonly
        mem[i] = 1;
  // this causes an access violation
  mem[4096] = 1;
}
0 голосов
/ 17 марта 2010

Ваша лучшая (самая быстрая) ставка по-прежнему заключается в пересмотре кода мьютекса. Как вы сказали, это очевидное объяснение - почему бы не попытаться действительно найти объяснение (с помощью логики) вместо дополнительных подсказок (с помощью кодирования), которые могут оказаться неубедительными? Если проверка кода не дает ничего полезного, вы все равно можете взять код мьютекса и использовать его для запуска теста. Первая попытка должна заключаться не в том, чтобы воспроизвести ошибку в вашей системе, а в том, чтобы обеспечить правильную реализацию мьютекса - реализовать потоки (начиная с 2 и выше), которые все пытаются снова и снова обращаться к одной и той же структуре данных со случайной небольшой задержкой в ​​каждом из их, чтобы они дрожали на временной шкале. Если этот тест приводит к глючному мьютексу, который вы просто не можете идентифицировать в коде, то вы стали жертвой какого-то архитектурно-зависимого эффекта (например, переупорядочение с помощью восстановления, несогласованность многоядерного кэша и т. Д.) И вам нужно найти другую реализацию мьютекса. Если в OTOH вы обнаружите явную ошибку в мьютексе, попробуйте использовать ее в своей реальной системе (используйте свой код, чтобы ошибка появлялась гораздо чаще), чтобы вы могли убедиться, что она действительно является причиной вашей первоначальной проблемы.

0 голосов
/ 17 марта 2010

Я знаю два способа обработки таких ошибок:

1) Снова и снова читайте код в поисках возможных ошибок. Я могу думать о двух ошибках, которые могут вызвать это: несинхронизированный доступ или запись по неправильному адресу памяти. Может быть, у вас есть больше идей.

2) Регистрация, ведение журнала. Добавьте много необязательных трассировок (OutputDebugString или файл журнала) в каждое критическое место, которое содержит достаточно информации - индексы, значения переменных и т. Д. Рекомендуется добавить эту трассировку с помощью некоторого #ifdef. Воспроизведите ошибку и попытайтесь понять из журнала, что происходит.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...