Мне нужна помощь настоящего гуру 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 без сбоя, если имя-> данные действительно NULL? Это вообще для меня загадочно. Это не кажется детерминированным.
Важная дополнительная информация:
Код между двумя комментариями действительно полный, ничего не упущено. Кроме того, приложение является однопоточным, поэтому нет другого потока, который мог бы неожиданно изменить какую-либо память в фоновом режиме. Платформой, на которой это происходит, является ЦП PPC (G4, если он может играть какую-либо роль). И в случае, если кто-то задается вопросом о «виде», это потому, что «информация» содержит «объединение» с именем «вид», а имя снова является структурой (вид — это объединение, каждое возможное значение объединения — это другой тип структуры); но это все не должно иметь большого значения здесь.
Я благодарен за любую идею здесь. Я еще больше благодарен, если это не просто теория, а если есть способ проверить, действительно ли эта теория верна для клиента.
Решение
Я уже принял правильный ответ, но на случай, если кто-нибудь найдет этот вопрос в Google, вот что произошло на самом деле:
Стрелки указывали на уже освобожденную память. Освобождение памяти не обнулит всю память и не заставит процесс сразу вернуть ее системе. Таким образом, несмотря на то, что память была освобождена ошибочно, она содержала правильные значения. Рассматриваемый указатель не равен NULL во время выполнения "if check".
После этой проверки я выделяю новую память, вызывая malloc. Не знаю, что именно здесь делает malloc, но каждый вызов malloc или free может иметь далеко идущие последствия для всей динамической памяти виртуального адресного пространства процесса. После вызова malloc указатель фактически равен NULL. Каким-то образом malloc (или какой-то системный вызов malloc использует) обнуляет уже освобожденную память, где находится сам указатель (не данные, на которые он указывает, сам указатель находится в динамической памяти). Обнулив эту память, указатель теперь имеет значение 0x0, что равно NULL в моей системе, и при вызове strlcpy он, конечно, рухнет.
Таким образом, настоящая ошибка, вызывающая такое странное поведение, находилась в совершенно другом месте моего кода. Никогда не забывайте: Освобожденная память сохраняет свои значения, но как долго это не зависит от вас. Чтобы проверить, есть ли в вашем приложении ошибка доступа к уже освобожденной памяти, просто убедитесь, что освобожденная память всегда обнуляется перед ее освобождением. В OS X вы можете сделать это, установив переменную среды во время выполнения (не нужно ничего перекомпилировать). Конечно, это немного замедляет работу программы, но вы обнаружите эти ошибки гораздо раньше.