запись изображения в каталог не удалась

Некоторая ошибка времени выполнения переполнения возникает, когда моя программа на C++ пытается записать некоторые изображения .png в каталог.

Каталог, в который записываются изображения, указывается в качестве аргумента командной строки. Программа скомпилирована с gcc -ggdb3 -O3. Странно, что ошибка исчезает, если я меняю каталог на другой при повторном запуске или если я компилирую свою программу без оптимизации. Я смущен. Несмотря на то, что я могу получить изображения, созданные неоптимизированным исполняемым файлом или в другом каталоге, я сомневаюсь, что результаты надежны, поскольку оптимизированный исполняемый файл может иметь ошибку времени выполнения? Или возможно, что оптимизация создает исполняемый файл, подверженный ошибкам? Кто-нибудь может это объяснить?

Я попытался отладить оптимизированный исполняемый файл, поскольку он скомпилирован с помощью gcc -ggdb3 -O3, но место, где он выдает ошибку переполнения, не дает исходного кода, из которого я не могу получить какую-либо подсказку:

(гдб) бт

# 0 0x00007fbd29573fb5 в поднять () из /lib/libc.so.6

#1 0x00007fbd29575bc3 в abort() из /lib/libc.so.6

#2 0x00007fbd295b3228 в ?? () из /lib/libc.so.6

#3 0x00007fbd296402c7 в __fortify_fail() из /lib/libc.so.6

#4 0x00007fbd2963e170 в __chk_fail() из /lib/libc.so.6

#5 0x00007fbd2963d519 в ?? () из /lib/libc.so.6

#6 0x00007fbd295b7426 в _IO_default_xsputn() из /lib/libc.so.6

#7 0x00007fbd29586fdb в vfprintf() из /lib/libc.so.6

#8 0x00007fbd2963d5b9 в __vsprintf_chk() из /lib/libc.so.6

#9 0x00007fbd2963d500 в __sprintf_chk() из /lib/libc.so.6

#10 0x0000000000408695 в главном ()

(гдб) f 10

#10 0x0000000000408695 в главном ()

Текущий язык: авто; в настоящее время asm

(gdb) список

1 /build/buildd/glibc-2.9/build-tree/amd64-libc/csu/crtn.S: такого файла или каталога нет.

в /build/buildd/glibc-2.9/build-tree/amd64-libc/csu/crtn.S

(ГДБ)

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

* Обнаружено переполнение буфера *: /cis/home/tim/research/absurdity/absurditylinux/binio21/release/absurdity завершено

[Новая тема 0x7fbd2acd9740 (LWP 2347)]

======= Обратный след: =========

/lib/libc.so.6(__fortify_fail+0x37)[0x7fbd296402c7]

/lib/libc.so.6[0x7fbd2963e170]

/lib/libc.so.6[0x7fbd2963d519]

/lib/libc.so.6(_IO_default_xsputn+0x96)[0x7fbd295b7426]

/lib/libc.so.6(_IO_vfprintf+0x63b)[0x7fbd29586fdb]

/lib/libc.so.6(__vsprintf_chk+0x99)[0x7fbd2963d5b9]

/lib/libc.so.6(__sprintf_chk+0x80)[0x7fbd2963d500]

/cis/home/tim/research/absurdity/absurditylinux/binio21/release/absurdity[0x408695]

/lib/libc.so.6(__libc_start_main+0xe6)[0x7fbd2955f5a6]

/cis/home/tim/research/absurdity/absurditylinux/binio21/release/absurdity[0x4045d9]

======= Карта памяти: ========

00400000-00471000 r-xp 00000000 00:39 52084894 /cis/home/tim/research/absurdity/absurditylinux/binio21/релиз/absurdity

00671000-00672000 r--p 00071000 00:39 52084894 /cis/home/tim/research/absurdity/absurditylinux/binio21/release/absurdity

00672000-00673000 rw-p 00072000 00:39 52084894 /cis/home/tim/research/absurdity/absurditylinux/binio21/release/абсурдность

00673000-00675000 rw-p 00673000 00:00 0

00943000-00964000 rw-p 00943000 00:00 0 [куча]

7fbd273f7000-7fbd29339000 rw-p 7fbd273f7000 00:00 0

7fbd29339000-7fbd29340000 r-xp 00000000 08:01 35791448 /lib/librt-2.9.so

7fbd29340000-7fbd2953f000 ---p 00007000 08:01 35791448 /lib/librt-2.9.so

7fbd2953f000-7fbd29540000 r--p 00006000 08:01 35791448 /lib/librt-2.9.so

7fbd29540000-7fbd29541000 rw-p 00007000 08:01 35791448 /lib/librt-2.9.so

7fbd29541000-7fbd296a9000 r-xp 00000000 08:01 35791428 /lib/libc-2.9.so

7fbd296a9000-7fbd298a9000 ---p 00168000 08:01 35791428 /lib/libc-2.9.so

7fbd298a9000-7fbd298ad000 r--p 00168000 08:01 35791428 /lib/libc-2.9.so

7fbd298ad000-7fbd298ae000 rw-p 0016c000 08:01 35791428 /lib/libc-2.9.so

7fbd298ae000-7fbd298b3000 рв-п 7fbd298ae000 00:00 0

7fbd298b3000-7fbd298c9000 r-xp 00000000 08:01 35790870 /lib/libgcc_s.so.1

7fbd298c9000-7fbd29ac9000 ---p 00016000 08:01 35790870 /lib/libgcc_s.so.1

7fbd29ac9000-7fbd29aca000 r--p 00016000 08:01 35790870 /lib/libgcc_s.so.1

7fbd29aca000-7fbd29acb000 rw-p 00017000 08:01 35790870 /lib/libgcc_s.so.1

7fbd29acb000-7fbd29ad3000 r-xp 00000000 08:01 71705955 /usr/lib/libgomp.so.1.0.0

7fbd29ad3000-7fbd29cd2000 ---p 00008000 08:01 71705955 /usr/lib/libgomp.so.1.0.0

7fbd29cd2000-7fbd29cd3000 r--p 00007000 08:01 71705955 /usr/lib/libgomp.so.1.0.0

7fbd29cd3000-7fbd29cd4000 rw-p 00008000 08:01 71705955 /usr/lib/libgomp.so.1.0.0

7fbd29cd4000-7fbd29d58000 r-xp 00000000 08:01 35791436 /lib/libm-2.9.so

7fbd29d58000-7fbd29f57000 ---p 00084000 08:01 35791436 /lib/libm-2.9.so

7fbd29f57000-7fbd29f58000 r--p 00083000 08:01 35791436 /lib/libm-2.9.so

7fbd29f58000-7fbd29f59000 rw-p 00084000 08:01 35791436 /lib/libm-2.9.so

7fbd29f59000-7fbd2a04a000 r-xp 00000000 08:01 71704918 /usr/lib/libstdc++.so.6.0.10

7fbd2a04a000-7fbd2a24a000 ---p 000f1000 08:01 71704918 /usr/lib/libstdc++.so.6.0.10

7fbd2a24a000-7fbd2a251000 r--p 000f1000 08:01 71704918 /usr/lib/libstdc++.so.6.0.10

7fbd2a251000-7fbd2a253000 rw-p 000f8000 08:01 71704918 /usr/lib/libstdc++.so.6.0.10

7fbd2a253000-7fbd2a266000 rw-p 7fbd2a253000 00:00 0

7fbd2a266000-7fbd2a27d000 r-xp 00000000 08:01 35791446 /lib/libpthread-2.9.so

7fbd2a27d000-7fbd2a47c000 ---p 00017000 08:01 35791446 /lib/libpthread-2.9.so

7fbd2a47c000-7fbd2a47d000 r--p 00016000 08:01 35791446 /lib/libpthread-2.9.so

7fbd2a47d000-7fbd2a47e000 rw-p 00017000 08:01 35791446 /lib/libpthread-2.9.so

7fbd2a47e000-7fbd2a482000 rw-p 7fbd2a47e000 00:00 0

7f

Программа получила сигнал SIGABRT, прервана.

[Переключение на поток 0x7fbd2acd9740 (LWP 2347)]

0x00007fbd29573fb5 в поднять () из /lib/libc.so.6

Очень ценю вашу помощь!

Спасибо и привет!


@@UPDATE@@: Ребята, вы правы! Я увеличил размер массива символов для длинного имени файла, и теперь все в порядке!

Исполняемый файл находится в /cis/home/tim/research/absurdity/absurditylinux/binio21/release/absurdity. Каталог, который не работает, указывается в качестве аргумента командной строки --result-path=../results1/FrancContinuity1/noise0/train-imgs, который далее хранится в global.result_path.

Не могли бы вы, ребята, сказать мне, как вы подозреваете, что это проблема, о которой вы упомянули? Всегда ли функции __sprintf_chk() и __vsprintf_chk() вызываются функцией sprintf()?

Вот код.

Часть 1:

      char filename[50];
      sprintf(filename, "%s/%d_%d.png", global.result_path, train_samples[n].label, train_samples[n].label==1 ? ++nb_pos : ++nb_neg);
      train_samples[n].write_png(filename);

Часть 2:

class Global { //parameters of program
public:
  int niceness; //The process scheduling priority
  int random_seed; //The seed for the random sequence used in the computation
  char result_path[1024]; //Where to store the generated results (images, logs, etc.)
...
}

Global global;

person Tim    schedule 02.09.2009    source источник
comment
У вас есть код? Мне кажется, у вас проблема с аргументами вызова sprintf. например, может быть, строка, которую вы печатаете, слишком мала?   -  person hookenz    schedule 03.09.2009
comment
Спасибо! Пожалуйста, смотрите мое редактирование в конце исходного сообщения.   -  person Tim    schedule 03.09.2009


Ответы (4)


Какова длина имени каталога и какова длина буфера, в котором вы пытаетесь его сохранить? Вы не дали нам много информации... как насчет того, чтобы показать код? Возможно, вызов sprintf где-то в main() и объявления любых задействованных переменных?

Редактировать: похоже, что имя файла должно быть большим массивом, учитывая ваш входной каталог и имя файла, которое вы к нему добавляете! Быстрое исправление: попробуйте объявить его, скажем, 1500 символов вместо 50. Лучшее исправление: поскольку вы используете C++, посмотрите на классы std::string и ostringstream, которые будут изменять размер, чтобы предотвратить переполнение буфера.

Чтобы ответить на ваши последующие вопросы:

«../» в вашем пути к результату не должен превращаться в абсолютный путь.

Моя догадка, что sprintf() была задействована, была удачной догадкой, основанной на сообщении «переполнение буфера» и последних нескольких строках в трассировке gdb. Я не очень хорошо знаком с внутренним устройством glibc, но, возможно, __sprintf_chk() и __vsprintf_chk() являются вариантами проверки переполнения буфера sprintf()?

person Jim Lewis    schedule 03.09.2009
comment
Спасибо! Пожалуйста, смотрите мое обновление в конце оригинального поста. - person Tim; 03.09.2009
comment
Спасибо! Немного любопытно, почему без оптимизации исполняемый файл работает нормально, не сообщается о переполнении буфера? - person Tim; 03.09.2009
comment
@Tim: Эта ошибка приводит к тому, что известно как неопределенное поведение, что означает: он может давать сбой, он может работать нормально, он может заставить демонов вылететь из вашего носа. Вероятно, компилятор изменил расположение данных в оптимизированной версии, поэтому при переполнении буфера он затирает что-то еще, вызывая сообщение об ошибке, которое вы видели. - person Jim Lewis; 03.09.2009

Я давно привык везде использовать snprintf. Научитесь любить это. Он может по-прежнему не записывать правильный файл, но, по крайней мере, он не оставит дыру в безопасности.

Затем, когда вы начнете задаваться вопросом, почему ваша программа создает файлы с именем "this_is_a_long_file_na", вы можете вернуться и исправить это, чтобы использовать либо буфер PATH_MAX, либо буфер динамического размера, созданный malloc. snprintf поможет вам найти правильный размер, если буфер должен быть больше.

Или вы можете переключиться на C++ и использовать std::string.

person Zan Lynx    schedule 03.09.2009
comment
+1 за ссылку PATH_MAX и упоминание о том, что динамическое выделение или std::string определенно было бы лучше - person Matthew Iselin; 03.09.2009

Ну, вы используете sprintf для печати в буфер 'filename[50]', который, конечно, имеет длину 50. Теперь строка, которую вы печатаете, представляет собой буфер размером 1024, это кажется мне потенциальной проблемой. Что происходит, когда global.result_path длиннее 50 (на самом деле даже меньше, так как вы также печатаете целые числа), вы получаете переполнение.

Попробуйте использовать С++ std::string и std::stringstream, т.е.:

//Part 1:

std::stringstream ss;
ss << global.result_path << /* other data */;
train_samples[n].write_png(ss.str().c_str());

//Part 2:

class Global
{
    std::string result_path;
    ...
}

С приведенным выше кодом вам никогда не придется беспокоиться о переполнении буфера символов или других подобных неприятных вещах.

person DeusAduro    schedule 03.09.2009

путь_результата слишком мал.

Просто измените result_path на 1024. В некоторых системах определен макрос MAX_PATH. Я бы также изменил sprintf на snprintf с размером sizeof(result_path).

Функция snprintf() аналогична sprintf(), за исключением того, что задается длина буфера. Это предотвращает переполнение буфера.

Возвращаемое значение — количество записанных символов. Если выходные данные были усечены из-за ограничения buff_size, то возвращаемое значение представляет собой количество символов (не включая конечный '\0'), которые были бы записаны в окончательную строку, если бы было достаточно места.

Как я узнал, что у вас проблемы со sprintf, так это по обратной трассировке.

i.e.

(gdb) bt

#0 0x00007fbd29573fb5 in raise () from /lib/libc.so.6

#1 0x00007fbd29575bc3 in abort () from /lib/libc.so.6

#2 0x00007fbd295b3228 in ?? () from /lib/libc.so.6

#3 0x00007fbd296402c7 in __fortify_fail () from /lib/libc.so.6

#4 0x00007fbd2963e170 in __chk_fail () from /lib/libc.so.6

#5 0x00007fbd2963d519 in ?? () from /lib/libc.so.6

#6 0x00007fbd295b7426 in _IO_default_xsputn () from /lib/libc.so.6

#7 0x00007fbd29586fdb in vfprintf () from /lib/libc.so.6

#8 0x00007fbd2963d5b9 in __vsprintf_chk () from /lib/libc.so.6

#9 0x00007fbd2963d500 in __sprintf_chk () from /lib/libc.so.6

#10 0x0000000000408695 in main ()

т. е. у вас есть функция main в вашем коде. и __sprintf_chk - это то, где он идет вверх животом. Вы должны были вызывать sprintf. После этого оно умерло. Итак, я предполагаю, что вы передавали плохие аргументы. Единственный способ, которым sprintf может умереть так ужасно, — это переполнение буфера. Так что это хорошее предположение, что строка, которую вы печатаете, слишком мала. Используйте snprintf, и это будет намного безопаснее. Затем вы можете распечатать, чтобы отладить результат. Если бы вы это сделали, вы бы сразу увидели, что буфер слишком мал, так как result_path был бы усечен до 50 символов, и программа не рухнула бы (по крайней мере, на тот момент :).

person hookenz    schedule 03.09.2009