Мне нужна помощь настоящего гуру C для анализа сбоя в моем коде. Не для исправления аварии; Я легко могу это исправить, но прежде чем сделать это, я хотел бы понять, как возможен этот сбой, поскольку он кажется мне совершенно невозможным.
Этот сбой происходит только на компьютере клиента, и я не могу воспроизвести его локально (поэтому я не могу пошагово просмотреть код с помощью отладчика), поскольку не могу получить копию базы данных этого пользователя. Моя компания также не позволит мне просто изменить несколько строк в коде и сделать пользовательскую сборку для этого клиента (поэтому я не могу добавить несколько строк printf и заставить его снова выполнить код), и, конечно, у клиента есть сборка без символы отладки. Другими словами, мои способности отладки очень ограничены. Тем не менее, я мог бы зафиксировать аварию и получить отладочную информацию. Однако, когда я смотрю на эту информацию, а затем на код, я не могу понять, как поток программы может когда-либо достичь рассматриваемой строки. Код должен был произойти сбой задолго до того, как попасть в эту строку. Я полностью потерян здесь.
Давайте начнем с соответствующего кода. Это очень маленький код:
// ... code above skipped, not relevant ...
if (data == NULL) return -1;
information = parseData(data);
if (information == NULL) return -1;
/* Check if name has been correctly \0 terminated */
if (information->kind.name->data[information->kind.name->length] != '\0') {
freeParsedData(information);
return -1;
}
/* Copy the name */
realLength = information->kind.name->length + 1;
*result = malloc(realLength);
if (*result == NULL) {
freeParsedData(information);
return -1;
}
strlcpy(*result, (char *)information->kind.name->data, realLength);
// ... code below skipped, not relevant ...
Вот и все. Вылетает в strlcpy. Я могу даже рассказать вам, как strlcpy действительно вызывается во время выполнения. На самом деле strlcpy вызывается со следующими параметрами:
strlcpy ( 0x341000, 0x0, 0x1 );
Зная это, довольно очевидно, почему strlcpy падает. Он пытается прочитать один символ из указателя NULL, и это, конечно, приведет к сбою. И поскольку последний параметр имеет значение 1, исходная длина должна была равняться 0. Мой код явно содержит ошибку здесь, он не может проверить, что данные имени имеют значение NULL. Я могу это исправить, нет проблем.
Мой вопрос:
Как этот код может вообще добраться до strlcpy?
Почему этот код не падает в операторе if?
Я попробовал это локально на моей машине:
int main (
int argc,
char ** argv
) {
char * nullString = malloc(10);
free(nullString);
nullString = NULL;
if (nullString[0] != '\0') {
printf("Not terminated\n");
exit(1);
}
printf("Can get past the if-clause\n");
char xxx[10];
strlcpy(xxx, nullString, 1);
return 0;
}
Этот код никогда не передается в оператор if. В операторе if происходит сбой, и это определенно ожидается.
Так может ли кто-нибудь придумать причину, по которой первый код может пройти этот оператор if без сбоев, если name-> data действительно NULL? Это абсолютно загадочно для меня. Это не кажется детерминированным.
Важная дополнительная информация:
Код между двумя комментариями действительно завершен , ничего не пропущено. Кроме того, приложение однопоточное , поэтому нет другого потока, который мог бы неожиданно изменить любую память в фоновом режиме. Платформой, на которой это происходит, является процессор PPC (G4, на случай, если он может сыграть какую-либо роль). И в случае, если кто-то задается вопросом о «kind», это происходит потому, что «information» содержит «union» с именем «kind», а name снова является структурой (kind - это union, каждое возможное значение union - это другой тип структуры); но здесь все это не должно иметь значения.
Я благодарен за любую идею здесь. Я еще более благодарен, если это не просто теория, но если есть способ, которым я могу проверить, действительно ли эта теория верна для клиента.
Решение
Я уже принял правильный ответ, но на тот случай, если кто-нибудь найдет этот вопрос в Google, вот что действительно произошло:
Указатели указывали на память, которая уже была освобождена. Освобождение памяти не приведет к нулю или к тому, что процесс вернет ее системе сразу. Таким образом, хотя память была ошибочно освобождена, она содержала правильные значения. Указанный указатель не равен NULL в то время, когда выполняется " if check ".
После этой проверки я выделяю новую память, вызывая malloc. Не уверен, что именно здесь делает malloc, но каждый вызов malloc или free может иметь далеко идущие последствия для всей динамической памяти виртуального адресного пространства процесса. После вызова malloc указатель фактически равен NULL. Так или иначе, malloc (или какой-то системный вызов, используемый malloc) обнуляет уже освобожденную память, в которой находится сам указатель (а не данные, на которые он указывает, сам указатель находится в динамической памяти). Обнуляя эту память, указатель теперь имеет значение 0x0, которое равно NULL в моей системе, и когда вызывается strlcpy, он, конечно, падает.
Так что настоящая ошибка, вызывающая это странное поведение, была в совершенно другом месте в моем коде. Никогда не забывайте: свободная память сохраняет свои ценности, но как долго вы не можете это контролировать. Чтобы проверить, есть ли в вашем приложении ошибка доступа к уже освобожденной памяти, просто убедитесь, что освобожденная память всегда обнуляется, прежде чем она будет освобождена. В OS X вы можете сделать это, установив переменную среды во время выполнения (не нужно ничего перекомпилировать). Конечно, это немного замедляет работу программы, но вы обнаружите эти ошибки гораздо раньше.