| |
Copyright © 2000, 2001, 2002, 2003 The FreeBSD Documentation Project
Добро пожаловать в руководство FreeBSD для разработчиков. Этот документ находится в процессе написания и представляет собой результат работы множества людей. Многие секции еще не написаны, а некоторые из написанных требуют обновления. Если Вы хотите помочь этому проекту, напишите в Список рассылки Проекта Документации FreeBSD.
Последняя версия этого документа постоянно доступна с Всемирного Веб Сайта FreeBSD. Этот документ может также быть найден в множестве форматов с FTP Сервера FreeBSD или одного из множества зеркал.
Распространение и использование исходных (SGML DocBook) и 'скомпилированных' форм (SGML, HTML, PDF, PostScript, RTF и прочих) с модификацией или без оной, разрешены при соблюдении следующих соглашений:
Распространяемые копии исходного кода (SGML DocBook) должны сохранять вышеупомянутые объявления copyright, этот список положений и сохранять следующий отказ от права в первых строках этого файла немодифицированным.
Распространяемые копии скомпилированных форм (преобразование в другие DTD, конвертированные в PDF, PostScript, RTF и другие форматы) должны повторять вышеупомянутые объявления copyright, этот список положений и и следующий отказ в документации и/или других материалах, поставляемых с дистрибьюцией.
Important: ЭТА ДОКУМЕНТАЦИЯ ПОСТАВЛЯЕТСЯ ПРОЕКТОМ ДОКУМЕНТАЦИИ FREEBSD "КАК ЕСТЬ" И ЛЮБЫЕ ЯВНЫЕ ИЛИ НЕЯВНЫЕ ГАРАНТИИ, ВКЛЮЧАЯ, НО НЕ ОГРАНИЧИВАЯСЬ НЕЯВНЫМИ ГАРАНТИЯМИ, КОММЕРЧЕСКОЙ ЦЕННОСТИ И ПРИГОДНОСТИ ДЛЯ КОНКРЕТНОЙ ЦЕЛИ ОТРИЦАЮТСЯ. НИ В КОЕМ СЛУЧАЕ РЕГЕНТЫ ИЛИ УЧАСТНИКИ НЕ ДОЛЖНЫ БЫТЬ ОТВЕТСТВЕННЫМИ ЗА ЛЮБОЙ ПРЯМОЙ, КОСВЕННЫЙ, СЛУЧАЙНЫЙ, СПЕЦИАЛЬНЫЙ, ОБРАЗЦОВЫЙ ИЛИ ПОСЛЕДУЮЩИЙ УЩЕРБЫ (ВКЛЮЧАЯ, НО НЕ ОГРАНИЧИВАЯСЬ ПОСТАВКОЙ ТОВАРОВ ЗАМЕНЫ ИЛИ УСЛУГ; ПОТЕРЮ ДАННЫХ ИЛИ ИХ НЕПРАВИЛЬНУЮ ПЕРЕДАЧУ ИЛИ ПОТЕРИ; ПРИОСТАНОВЛЕНИЕ БИЗНЕСА), И ТЕМ НЕ МЕНЕЕ ВЫЗВАННЫЕ И В ЛЮБОЙ ТЕОРИИ ОТВЕТСТВЕННОСТИ, НЕЗАВИСИМО ОТ КОНТРАКТНОЙ, СТРОГОЙ ОТВЕТСТВЕННОСТИ, ИЛИ ПРАВОНАРУШЕНИИ (ВКЛЮЧАЯ ХАЛАТНОСТЬ ИЛИ ИНЫМ СПОСОБОМ), ВОЗНИКШЕМ ЛЮБЫМ ПУТЕМ ПРИ ИСПОЛЬЗОВАНИИ ЭТОГО ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ, ДАЖЕ ЕСЛИ БЫ БЫЛО СООБЩЕНО О ВОЗМОЖНОСТИ ТАКОГО УЩЕРБА.
Итак, у нас все есть. Система полностью установлена и вы готовы начать программировать. Но с чего начать? Что предоставляет FreeBSD? Что она может дать мне как программисту?
Вот те из некоторых вопросов, на которые пытается дать ответ эта глава. Конечно, программирование, как и любая другая область деятельности, имеет разные уровни профессионального мастерства. Для некоторых это хобби, для других это профессия. Информация в этой главе может в большей степени пригодиться начинающему программисту, но может также оказаться полезной программисту, делающему первые шаги на платформе FreeBSD.
Создать самую лучшую UNIX-подобную операционную систему, благодаря оригинальной идеологии программных средств, а также полезности, производительности и надежности.
Наша идеология может быть описана в следующих ключевых положениях
Не добавлять новой функциональности, кроме случаев, когда нельзя выполнить конкретную работу без нее.
Решить, чего в системе не будет, так же важно, как и определение того, чего в системе не будет. Не пытайтесь включить в систему все; лучше сделать систему расширяемой так, что дополнительные потребности могут быть реализованы в режиме совместимости.
Единственное, что может быть лучше обобщения на основе одного примера, это обобщение вообще без примеров.
Если проблема до конца не понята, наверное, лучше вовсе не давать ее решения.
Если вы можете сделать 90 процентов результата ценой 10 процентов работы, найдите более простое решение.
Старайтесь отделять сложные вещи.
Дайте механизм, а не правила. В частности, оставьте соглашения по пользовательскому интерфейсу клиенту.
Из Scheifler & Gettys: "X Window System"
Полный исходный код FreeBSD располагается в нашем общедоступном хранилище CVS. Исходный код обычно устанавливается в /usr/src, который содержит следующие подкаталоги.
Каталог | Описание |
---|---|
bin/ | Исходный код файлов из /bin |
contrib/ | Исходный код файлов программного обеспечения сторонних разработчиков. |
crypto/ | Исходный код DES |
etc/ | Исходный код файлов из каталога /etc |
games/ | Исходный код файлов из /usr/games |
gnu/ | Утилиты, подпадающие под действие GNU Public License |
include/ | Исходный код файлов из /usr/include |
kerberosIV/ | Исходный код Kerbereros version IV |
kerberos5/ | Исходный код Kerbereros version 5 |
lib/ | Исходный код файлов из /usr/lib |
libexec/ | Исходный код файлов из /usr/libexec |
release/ | Файлы, которые требуются для создания релиза FreeBSD |
sbin/ | Исходный код файлов из /sbin |
secure/ | Исходный код FreeSec |
share/ | Исходный код файлов из /usr/share |
sys/ | Исходный код ядра |
tools/ | Утилиты, используемые для поддержки и тестирования FreeBSD |
usr.bin/ | Исходный код файлов из /usr/bin |
usr.sbin/ | Исходный код файлов из /usr/sbin |
Эту главу написал James Raynard .
Модификации для включения в Руководство разработчика выполнил Murray Stokely <murray@FreeBSD.org>
.
Этот документ является вводным по использованию некоторых инструментов для программирования во FreeBSD, хотя многое отсюда будет подходить и для других версий Unix. Здесь не делается попытки описать процесс кодирования во все деталях. Большая часть документа не предполагает или предполагает незначительное знакомство с программированием, хотя надеемся, что он пригодится большинству программистов.
FreeBSD обеспечивает прекрасную среду для ведения разработок. С базовой системой поставляются компиляторы C, C++, языка Fortran и ассемблера, не говоря уж об интерпретаторе Perl и наборе классических утилит Unix, таких, как sed и awk. Если этого недостаточно, то в Коллекции портов имеется еще больше компиляторов и интерпретаторов. FreeBSD практически полностью соответствует таким стандартам, как POSIX и ANSI C, а также собственной идеологии BSD, так что возможно писать приложения, которые без особых или с незначительными модификациями будут компилироваться и работать на широком спектре платформ.
Однако, вся эта мощь может поначалу ошеломить, если ранее вы никогда не писали программы на платформе Unix. Этот документ поможет вам сделать первые шаги без углубления в сложные вопросы. Его предназначение - дать вам достаточно информации об основах, чтобы вы могли ориентироваться в документации.
В основном этот документ не требует или требует поверхностного знания программирования, хотя предполагается наличие общего представления об использовании Unix и желание учиться!
Программа представляет собой набор инструкций, которые предписывают компьютеру выполнить различные действия; иногда действие, которое нужно выполнить, зависит от того, что случилось в результате выполнения предыдущей операции. Этот раздел дает вам обзор двух основных способов, которыми вы можете выдавать инструкции, или ``команды'', как их обычно называть. В одном способе используется интерпретатор, а в другом компилятор. Так как человеческие языки слишком трудны для недвусмысленного понимания компьютером, команды обычно записываются на одном из специально разработанных языке программирования.
В случае использования интерпретатора язык представляет собой оболочку, в которой вы набираете команды, а оболочка их выполняет. Для более сложных программ вы можете набрать команды в файле и заставить интерпретатор загрузить файл и выполнить команды из него. Если что-то происходит не так, интерпретатор передает управление отладчику, с помощью которого вы решаете возникшую проблему.
Плюсом этого подхода является возможность видеть результаты выполнения ваших команд немедленно, а ошибки могут быть быстро исправлены. Самый большой минус проявляется, когда вы собираетесь поделиться с кем-нибудь вашими программами. Они должны иметь точно такой же интерпретатор или вы должны каким-то образом передать его, а тем нужно понять, как его использовать. Кроме того, пользователям может не понравиться перспектива оказаться в отладчике в случае нажатия не той клавиши! С точки зрения производительности, интерпретаторы могут использовать много памяти, и, как правило, генерируемый ими код не так эффективен, как код, генерируемый компиляторами.
По моему мнению, использование интерпретационных языков являются хорошим началом, если ранее вы не занимались программированием. Такой способ работы обычно используется с языками типа Lisp, Smalltalk, Perl и Basic. Можно также отметить, что оболочка Unix (sh, csh) является интерпретатором, и многие люди пишут ``скрипты'' для оболочек для облегчения выполнения различных ``доморощенных'' задач на своих машинах. На самом деле, частью философии Unix было предоставление множества маленьких вспомогательных программ, которые можно связать вместе в скриптах оболочки для выполнения полезных действий.
Ниже приводится список интерпретаторов, которые доступны в виде пакаджей FreeBSD, с кратким обсуждением некоторых самых популярных интерпретационных языков.
Для получения одного из этих пакаджей вам достаточно щелкнуть на ссылке, а затем выполнить команду
# pkg_add имя пакаджа
пользователем root. Для работы пакаджа, как правило, требуется полнофункциональная система FreeBSD версии 2.1.0 и выше!
Это сокращение от Beginner's All-purpose Symbolic Instruction Code. Разработан в 1950-х годах для обучения студентов университета программированию и в 1980-х поставлялся с каждым уважающим себя персональным компьютером; BASIC был первым языком программирования для многих программистов. Он лежит в основе Visual Basic.
Во FreeBSD имеются Bywater Basic Interpreter и Phil Cockroft's Basic Interpreter (ранее Rabbit Basic) в виде пакаджей FreeBSD.
Язык, который был разработан в конце 1950-х в качестве альтернативы ``вычислительным'' языкам, популярным в то время. Вместо того, чтобы опираться на вычисления, List опирается на списки; на самом деле название является сокращением от ``List Processing''. Очень популярен в области AI (Искусственного Интеллекта).
Lisp является очень мощным и многообразным языком, но он может оказаться достаточно большим и громоздким.
Во FreeBSD имеется GNU Common Lisp в виде пакаджа.
Очень часто используется системными администраторами для написания скриптов; также часто используется на серверах World Wide Web для написания CGI-скриптов.
Самая последняя версия (версия 5) поставляется с FreeBSD.
Диалект языка Lisp, более компактный и понятный, чем Common Lisp. Популярен в университетах, так как достаточно прост для обучения аспирантов в качестве первого языка и имеет достаточно высокий уровень для использования в исследовательской работе.
Во FreeBSD имеются пакаджи для Elk Scheme Interpreter, MIT Scheme Interpreter и SCM Scheme Interpreter.
Компиляторы достаточно сильно отличаются от интерпретаторов. Сначала вы записываете свой код в файл (или файлы), используя редактор. Затем вы запускаете компилятор и смотрите, воспринимает ли он вашу программу. Если она не компилируется, вы скрежещете зубами и возвращаетесь к редактированию; если код компилируется и программа выдается, вы можете запустить ее в приглашении оболочки или в отладчике для проверки правильности работоспособности. [1]
Обычно этот процесс не так непосредственен, как при использовании интерпретатора. Однако он позволяет вам выполнять множество действий, которые затруднительно или невозможно сделать в интерпретаторе, к примеру, написать код, тесно взаимодействующий с операционной системой--или даже написать собственную операционную систему! Это также полезно, если вам нужно написать очень эффективный код, так как компилятор может затратить время на оптимизацию кода, что может оказаться неудобным для интерпретатора. Кроме того, распространить программу, написанной для компилятора, обычно проще, чем для интерпретатора--вы можете просто предоставить копию выполнимого файла, полагая, что используется та же операционная система, что и у вас.
В число компиляционных языков входят Pascal, C и C++. C и C++ являются непрощающими ошибки языками, и больше подходят для опытных программистов; Pascal, с другой стороны, разрабатывался как язык для обучения, и хорош для начала. В базовую поставку системы FreeBSD поддержка Pascal не включена, однако в коллекции портов имеется компилятор GNU Pascal Compiler (gpc).
Так как цикл редактирование-компиляция-запуск-отладка неудобен при использовании отдельных программ, многие производители коммерческих компиляторов предлагают интегрированные среды разработки (сокращённо IDE - Integrated Development Environment). В базовую поставку FreeBSD IDE не входит, однако в дереве портов имеется evel/kdevelop, и в этих целях многие используют Emacs. Использование Emacs в качестве IDE обсуждается в Section 2.7.
Этот раздел касается только GNU-компилятора для C и C++, так как он поставляется вместе с системой FreeBSD. Он может быть вызван командами cc или gcc. Подробности по генерации программ с помощью интерпретатора зависят от конкретного интерпретатора и обычно хорошо описываются в документации и встроенной системе помощи интерпретатора.
Как только вы напишете свое творение, следующим шагом является преобразование его в нечто, что будет (надеемся!) запускать во FreeBSD. Это обычно происходит в несколько шагов, каждый из которых делается отдельной программой.
Предварительная обработка исходного кода для удаления комментариев и выполнения других хитростей, наподобие раскрытия макросов в языке C.
Синтаксическая проверка вашего кода на предмет выявления несоответствий правилам языка. Если таковые обнаружились, об этом будет сообщено!
Преобразование исходного кода в код на языке ассемблера--он очень близок к машинному коду, но все еще понятен человеку. Определенно. [2]
Преобразование ассемблерного кода в машинный код--да, мы имеем в виду биты и байты, здесь только нули и единицы.
Проверка на то, что вы использовали такие вещи, как функции и глобальные переменные правильно. Например, если вы обращались к несуществующей функции, на это будет указано.
Если вы пытаетесь получить выполнимый файл из нескольких файлов с исходными текстами, работа над тем, как их объединить.
Работа над генерацией того, что загрузчик сможет загрузить в память и запустить.
Наконец, запись выполнимого файла в файловую систему.
Термин компиляция часто используется для обозначения действий на шагах от 1 до 4--остальные шаги называют компоновкой. Иногда шаг 1 называют препроцессорной обработкой, а шаги 3-4 ассемблированием.
К счастью, почти все эти детали скрыты от вас, cc как конечная программа управляет за вас вызовом всех этих программ с правильными аргументами; простой вызов
% cc foobar.c
приведет к компиляции foobar.c посредством всех шагов выше. Если у вас имеется для компиляции больше одного файла, просто сделайте нечто вроде следующего
% cc foo.c bar.c
Заметьте, что синтаксическая проверка--это именно проверка синтаксиса. При этом не выполняется проверка на наличие сделанных вами логических ошибок, типа вхождения в бесконечный цикл или использования пузырьковой сортировки там, где вы хотели использовать двоичную. [3]
Имеется огромное количество параметров для cc, все они описаны на справочной странице. Вот несколько из самых важных, с примерами их использования.
Имя выходного файла. Если вы не используете этот параметр, cc сгенерирует выполнимый файл с именем a.out. [4]
Выполнить только компиляцию файла, без компоновки. Полезно для игрушечных программ, когда вы хотите просто проверить синтаксис, или при использовании Makefile.
В результате будет сгенерирован объектный файл (не выполнимый файл) с именем foobar.o. Он может быть скомпонован с другими объектными файлами для получения выполнимого файла.
Создать отладочную версию выполнимого файла. Этот параметр указывает компилятору поместить в выполнимый файл информацию о том, какая строка какого файла с исходным текстом какому вызову функции соответствует. Отладчик может использовать эту информацию для вывода исходного кода при пошаговом выполнении программы, что очень полезно; минусом является то, что вся эта дополнительная информация делает программу гораздо большей. Обычно вы компилируете с параметром -g при работе над программой, а затем, когда убедитесь в работоспособности программы, компилируете ``окончательную версию'' без параметра -g.
При этом будет сгенерирована отладочная версия программы. [5]
Создать оптимизированную версию выполнимого файла. Компилятор прибегает к различным ухищрениям для того, чтобы сгенерировать выполнимый файл, выполняющийся быстрее, чем обычно. После опции -O вы можете добавить число, указывающее качество оптимизации, но использование этого не защищено от ошибок оптимизации компилятора. Например, известно, что версия компилятора cc, поставляемая с FreeBSD версии 2.1.0, при некоторых условиях генерирует неверный код при использовании опции -O2.
Обычно оптимизацию используют при компиляции окончательной версии.
По этой команде будет создана оптимизированная версия программы foobar.
Следующие три флага заставят cc проверять ваш код на соответствие известному международному стандарту, часто называемому стандартом ANSI, хотя, строго говоря, это стандарт ISO.
Включить выдачу всех предупреждений, которые посчитают нужным выдать авторы cc. Несмотря на свое название, при этом включается выдача не всех предупреждений, на которые способен компилятор cc.
Выключить большинство, если не все, не-ANSI расширений языка C, имеющиеся в cc. Несмотря на название, этот параметр не гарантирует, что ваш код будет строго соответствовать стандарту.
Выключить все расширения C, имеющиеся в компиляторе cc, не соответствующие стандарту ANSI.
Без этих флагов компилятор cc позволит вам использовать некоторые нестандартные расширения языка. Некоторые из них весьма полезны, но не будут работать при использовании других компиляторов--фактически одним из назначений стандарта является обеспечение возможности писать код, который будет работать с любым компилятором в любой системе. Такой код называется переносимым кодом.
Вообще говоря, вы должны стараться писать как можно более переносимый код, иначе вам позже, может быть, придется полностью переписывать программу, чтобы заставить ее работать где-то еще--и кто знает, что вы будете использовать через несколько лет?
По этой команде после проверки на соответствие стандарту файла foobar.c будет сгенерирован выполнимый файл foobar.
Задает библиотеку функций, которая будет использоваться на этапе компоновки.
В качестве самого распространенного примера можно привести компиляцию программы, использующей некоторые математические функции на языке C. В отличие от других платформ, имеется отделенная от стандартной библиотека, и вам нужно указать компилятору, что ее нужно использовать.
При этом если библиотека называется libsomething.a, вы передаете программе cc аргумент -lsomething. Например, математическая библиотека называется libm.a, так что вы передаете программе cc аргумент -lm. Известной ``хитростью'' при работе с математической библиотекой является то, что в командной строке она должна быть указана в качестве последней библиотеки.
По этой команде библиотека математических функций будет скомпонована с foobar.
Если вы компилируете код на языке C++, вам нужно добавить параметр -lg++, или -lstdc++ при использовании FreeBSD 2.2 и выше, к аргументам командной строки для компоновки библиотеки функций C++. Либо вы можете запустить вместо cc компилятор c++, который сделает это за вас. Во FreeBSD c++ может быть вызван как g++.
% cc -o foobar foobar.cc -lg++ For FreeBSD 2.1.6 and earlier % cc -o foobar foobar.cc -lstdc++ For FreeBSD 2.2 and later % c++ -o foobar foobar.cc
В каждом из этих вариантов из файла foobar.cc с исходным кодом C++ будет создан выполнимый файл foobar. Заметьте, что в Unix-системах файлы с исходным кодом C++ традиционно оканчиваются на .C, .cxx или .cc, в отличие от стиля MS-DOS .cpp (который уже использовался для чего-то еще). gcc использует это для определения того, какой компилятор использовать; однако это ограничение больше не имеет силы, так что вы можете безнаказанно называть ваши файлы с кодом C++ .cpp!
sin()
и получаю такую ошибку. Что она
означает?2.4.1.2. Все замечательно, чтобы попрактиковаться в использовании параметра -lm, я написал вот такую простую программу. Все, что она делает, заключается в возведении числа 2.1 в шестую степень.
и я компилирую ее вот так:
как вы и сказали, но при ее запуске получаю вот что:
Это неправильный результат! Что происходит?
Когда компилятор видит, что вы вызываете функцию, он пытается найти для нее объявление. Если его нет, то предполагается, что функция возвращает значение типа int, что определенно не является тем, что вы имели в виду.
Объявления математических функций находятся в файле math.h. Если вы включаете этот файл, то компилятор сможет найти объявление и перестанет вытворять странные вещи с вашими расчетами!
После компиляции таким же образом, как и раньше, запустите программу снова:
Если вы используете какие-либо математические функции, всегда включайте файл math.h и не забудьте выполнить компоновку с математической библиотекой.
2.4.1.5. Хорошо, у меня получился выполнимый файл с именем foobar, я могу видеть его при запуске команды ls, но когда я набираю в командной строке foobar, выдается сообщение об отсутствии такого файла. Почему он не может быть найден?
В отличие от MS-DOS, Unix не просматривает текущий каталог, когда пытается найти выполнимый файл, который вы хотите запустить, если вы явно его укажете. Либо введите команду ./foobar, которая означает, что ``нужно запустить файл с именем foobar, располагающийся в текущем каталоге'', или измените вашу переменную окружения PATH, чтобы она выглядела примерно как
Точка в конце означает ``посмотреть в текущем каталоге, если в других ничего не найдено''.
2.4.1.7. Я откомпилировал мою программу, и сначала она работала нормально, но потом выдалось сообщение об ошибке с сообщением ``core dumped''. Что это означает?
Фраза core dump (сброс дампа) своим происхождением обязана ранним дням Unix, когда в качестве устройств для хранения данных в машинах использовалась память на магнитных сердечниках (core). В общем, если в программе при некоторых условиях возникала ошибка, система записывала содержимое памяти на магнитных сердечниках на диск в файл с именем core, в котором затем для обнаружения причины происшедшего мог покопаться программист.
Для анализа дампа воспользуйтесь программой gdb (посмотрите Section 2.6).
2.4.1.9. Когда моя программа сбрасывает дамп, она выдает сообщение ``segmentation fault''. Что это такое?
В основном это значит, что ваша программа пыталась выполнить в памяти какую-то недопустимую операцию; Unix построен так, что защищает операционную систему и другие программы от некорректно работающих программ.
Часто встречаемыми причинами этого являются:
Попытка записи в область памяти, на которую ссылается указатель со значением NULL, например
char *foo = NULL; strcpy(foo, "bang!");
Использование указателя, Using a pointer that hasn't been initialised, eg
char *foo; strcpy(foo, "bang!");
Указатель будет иметь некоторое случайное значение, которое, бывает, указывает на область памяти, недоступную вашей программе, и ядро будет прерывать вашу программу до того, как она успеет испортить что-то. Если вам не повезет, то он будет ссылаться на область где-то в вашей собственной программе, и испортит одну из ваших структур данных, что приводит к неожиданным сбоям.
Попытка выхода за границы массива, к примеру
int bar[20]; bar[27] = 6;
Попытка произвести запись в область памяти, доступную только в режиме чтения, например,
char *foo = "My string"; strcpy(foo, "bang!");
Компиляторы Unix часто размещают строковые выражения типа "My string" в областях памяти, доступных только для чтения.
Выполнение сомнительных операций с функциями malloc()
и
free()
, например
char bar[80]; free(bar);
или
char *foo = malloc(27); free(foo); free(foo);
Допущение какой-либо из этих ошибок не всегда приводит к ошибке, но допускать такие ошибки не нужно. Некоторые системы и компиляторы менее строги, чем другие, вот почему программы, прекрасно работающие в одной системе, могут не запускаться в другой.
2.4.1.10. Иногда при получении дампа памяти выдается сообщение ``bus error''. В моей книге про Unix утверждается, что это является признаком проблемы с оборудованием, но компьютер работает нормально. Это правда?
Нет, к счастью, нет (если, конечно, у вас действительно нет проблем с оборудованием...). Обычно это является еще одним способом сказать, что вы обращаетесь к памяти неправильным образом.
2.4.1.11. Эти дампы памяти, если подумать, могут оказаться весьма полезными, если я смогу получать их, когда захочу. Могу ли я это делать, или мне нужно ждать появления ошибки?
Да, просто, перейдя на другую консоль или xterm, выдайте команду
% ps
для определения идентификатора процесса, соответствующего вашей программе, и выполните команду
% kill -ABRT pid
где pid тем самым идентификатором процесса, который вы нашли.
Это полезно, если ваша программа, например, входит в бесконечный цикл. Если ваша программа перехватывает сигнал SIGABRT, есть несколько других сигналов, которые приводят к подобному результату.
Альтернативным способом является создание аварийного дампа из самой программы при
помощи вызова функции abort()
. Для дальнейшего изучения
обратитесь к странице справки по abort(3).
Если вы хотите создать аварийный дамп извне программы, но не хотите прекращения работы программы, то можете использовать программу gcore. Обратитесь к справочной странице по gcore(1) для получения более полной информации.
Когда вы работаете над простой программой, состоящей всего лишь из одного или двух файлов с исходным текстом, выдать команду
% cc file1.c file2.c
не так уж и затруднительно, но это быстро надоедает, если имеется несколько файлов--и к тому же это тормозит компиляцию.
Одним из способов избежать этого является использование объектных файлов и перекомпиляция только тех файлов с исходным текстом, которые были изменены. Так что мы может делать нечто вроде следующего:
% cc file1.o file2.o ... file37.c ...
если мы изменили файл file37.c, а другие файлы не изменяли с прошлой компиляции. Это может значительно ускорить компиляцию, но не решает проблемы с набором команд.
Либо мы можем написать скрипт на языке командного процессора для решения проблемы с набором команд, но при этом будет перекомпилироваться все, что делает этот способ неэффективным для большого проекта.
Что случится, если мы имеем дело с сотнями файлов исходных текстов? Что, если мы работаем в команде с другими людьми, которые забывают сообщать нам о том, что они изменили один из их файлов с исходным кодом, который мы используем?
Наверное, мы можем объединить оба этих приема и написать некий скрипт, который будет содержать волшебное правило, определяющее, нужна ли перекомпиляция файла с исходным текстом. Теперь все, что нам нужно, это программа, могущая понимать такие правила, так как это слишком сложно для командного процессора.
Эта программа называется make. Она читает файл, называемый make-файлом, указывающий, как различные файлы зависят друг от друга, и определяет, каким файлам требуются перекомпиляция, а каким нет. Например, правило может говорить нечто вроде ``если fromboz.o старее, чем fromboz.c, то это значит, что кто-то изменил fromboz.c, поэтому этот файл нужно перекомпилировать''. В make-файле также имеются правила, указывающие программе make, как именно перекомпилировать файл с исходным текстом, делая эту утилиту гораздо более мощным инструментом.
Make-файлы обычно располагаются в том же самом каталоге, что и исходные файлы, к которым они имеют отношение, и могут называться makefile, Makefile или MAKEFILE. Большинство программистов используют имя Makefile, так как при этом оно будет помещено в начало списка файлов каталога, где его легко увидеть. [6]
Вот очень простой make-файл:
foo: foo.c cc -o foo foo.c
Он содержит две строки, строку зависимости и строку создания.
Здесь строка зависимости состоит из имени программы (называемой целью (target)), за которой следует двоеточие, затем пробел и имя исходного файла. Когда программа make читает эту строку, она определяет, существует ли foo; если она существует, то сравниваются времена последней модификации foo и foo.c. Если foo не существует или она старее, чем foo.c, то затем смотрится строка создания для определения того, что же нужно предпринять. Другими словами, это правило для определения необходимости перекомпиляции foo.c.
Строка создания начинается с символа табуляции (нажатия клавиши tab) и команды, которую вы должны были бы набрать для создания foo, если бы работали в режиме командной строки. Если foo устарела или не существует, то make выполняет эту команду для ее создания. Другими словами, это правило, которое указывает, как именно перекомпилировать foo.c.
Итак, когда вы вводите команду make, она проверяет, что foo будет соответствовать последним изменениям в foo.c. Этот подход может быть развит в Makefile с сотнями целей--действительно, во FreeBSD возможно полностью перекопилировать операционную систему, просто набрав make world в соответствующем каталоге!
Другим полезным свойством make-файлов является то, что в целях не обязательно должны быть программы. Например, мы можем создать такой make-файл:
foo: foo.c cc -o foo foo.c install: cp foo /home/me
Мы можем указать программе make, какую цель мы собираемся выполнять, набрав:
% make target
По этой команде make будет смотреть только на эту цель и игнорировать все другие. Например, если мы наберем make foo с make-файлом выше, то make будет игнорировать цель install.
Если мы просто наберем make без параметров, то make всегда будет искать первую цель и затем остановится без обработки других целей. Так что если в нашем случае мы набрали make, то она перейдет к цели foo, перекомпилирует foo, если это нужно, а затем остановится без перехода к цели install.
Заметьте, что цель install на самом деле не зависит ни от чего! Это значит, что команда в следующей строке выполняется всегда, когда мы пытаемся выполнить эту цель, набирая make install. В этом случае foo будет копироваться в домашний каталог пользователя. Это часто используется в make-файлах приложений, для установки приложения в правильный каталог после успешной компиляции.
Это очень сложный для обсуждения предмет. Если вы не очень понимаете, как работает make, лучше всего написать простую программу типа ``hello world'', создать make-файл, подобный вышеописанному и поэкспериментировать. Затем попробуйте использовать более чем один исходный файл, или исходный файл, включающий include-файл. Здесь будет полезна команда touch--она изменяет дату файла без его редактирования.
Код на языке C часто начинается со списка включаемых файлов, например, stdio.h. Некоторые из этих файлов являются системными включаемыми файлами, а некоторые относятся к проекту, над которым вы сейчас работаете:
#include <stdio.h> #include "foo.h" int main(....
Для обеспечения того, что этот файл будет перекомпилирован в момент изменения foo.h, добавьте его в ваш Makefile:
foo: foo.c foo.h
Тогда, когда ваш проект станет всё больше и у вас появится всё больше и больше собственных include-файлов, которые нужно поддерживать, отслеживать все включаемые файлы и файлы, от них зависящие, будет затруднительно. Если вы измените include-файл, но забыли перекомпилировать все файлы, зависящие от него, результат будет непредсказуем. В gcc имеется параметр, позволяющий анализировать ваши файлы и выдавать перечень включаемых файлов и их зависимостей: -MM.
Если вы добавите следующее в ваш Makefile:
depend: gcc -E -MM *.c > .depend
и запустите make depend, то появится файл .depend, содержащий список объектных файлов, C-файлов и include-файлов:
foo.o: foo.c foo.h
Если вы изменяете foo.h, то при следующем запуске make все файлы, зависящие от foo.h, будут перекомпилированы.
Не забывайте выполнять make depend всякий раз при добавлении include-файла к одному из ваших файлов.
Make-файлы могут оказаться сложными для создания. К счастью, системы на основе BSD, такие, как FreeBSD, поставляются с несколькими очень мощными в составе системы. Одним очень хорошим примером является система портов FreeBSD. Вот основная часть типичного Makefile порта:
MASTER_SITES= ftp://freefall.cdrom.com/pub/FreeBSD/LOCAL_PORTS/ DISTFILES= scheme-microcode+dist-7.3-freebsd.tgz .include <bsd.port.mk>
Теперь, если мы перейдем в каталог с этим портом и наберем команду make, то случится вот что:
Делается проверка на наличие исходного кода этого порта в системе.
Если его нет, осуществляется FTP-соединение к URL, указанному в MASTER_SITES, для сгрузки исходного текста.
Вычисляется контрольная сумма исходных текстов и сравнивается с известной контрольной суммой оригинальной копии исходных текстов. Это делается для того, чтобы проверить целостность исходных текстов после перекачки.
Делаются все изменения, требуемые для того, чтобы исходный код работал во FreeBSD--этот процесс носит название патчинг (patching).
Выполняются все настройки, требуемые для исходных текстов. (Многие дистрибутивы Unix-программ пытаются определить, на какой версии Unix они компилируются и какие дополнительные возможности Unix имеются--эта информация информация задается в сценариях портов FreeBSD).
Компилируется исходный код программы. В частности, мы меняем каталог на тот, в который были распакованы исходные тексты и выполняем команду make--в собственном make-файле программы имеется необходимая для построения программы информация.
Теперь у нас есть откомпилированная версия программы. Если мы хотим, то можем ее протестировать; когда мы останемся довольными ей, то можем выдать команду make install. При этом программа и все необходимые для работы файлы будут скопированы в правильные каталоги; в базе данных о пакаджах будет сделана запись, чтобы порт мог быть позже с легкостью удален, если он нам не понравится.
Теперь, я думаю, вы согласитесь, что для сценария из четырех строк это выглядит очень впечатляюще!
Секрет заключен в последней строке, которая указывает программе make на включение системного make-файла с именем bsd.port.mk. Легко пропустить эту строчку, но именно в ней содержатся все эти хитрости--кто-то написал make-файл, указывающий программе make на выполнение всех вышеописанных действий (плюс еще многих мною не описанных, включая обработку ошибок, которые могут возникнуть) и любой может получить доступ к нему, просто добавив одну строчку в собственный make-файл!
Если вы хотите взглянуть на эти системный make-файлы, то они находятся в каталоге /usr/share/mk, но, наверное, лучше сначала набраться практики работы с make-файлами, так как они очень сложны (и если вы будете в них заглядывать, обязательно держите под рукой термос с крепким кофе!)
Make является очень мощным инструментом, и может делать гораздо больше, чем выполнять простые примеры, приведенные выше. К сожалению, имеется несколько различных версий make, и все они значительно отличаются друг от друга. Наверное, лучшим способом выяснить, что они могут делать, является чтение документации--надеемся, что это введение даст вам основные сведения, которые помогут вам в этом.
Версия make, поставляемая с FreeBSD, является Berkeley make; руководство находится в файле for it in /usr/share/doc/psd/12.make. Чтобы его просмотреть, выполните
% zmore paper.ascii.gz
в этом каталоге.
Многие приложения в Коллекции Портов используют GNU make, с которым поставляется хороший набор страниц ``info''. Если вы устанавливали какие-либо из таких портов, то GNU make будет автоматически установлен под именем gmake. Он также сам доступен в виде порта и пакаджа.
Чтобы просмотреть info-страницы по GNU make, вам придется отредактировать файл dir в каталоге /usr/local/info, добавив для них запись. Это означает добавление примерно такой строки
* Make: (make). The GNU Make utility.
в файл. Как только вы это сделаете, сможете набрать команду info и выбрать пункт make из меню (или в программе Emacs нажать C-h i).
Отладчик, поставляемый с FreeBSD, называется gdb (GNU debugger). Он запускается по команде
% gdb progname
хотя большинство предпочитает запускать его из Emacs. Вы можете сделать это так:
M-x gdb RET progname RET
Использование отладчика позволяет вам запустить программу в более контролируемом окружении. Как правило, вы сможете выполнить программу в пошаговом режиме построчно, изучать значения переменных, изменять их, указывать отладчику выполнять программу до определенного места и затем остановиться, и так далее. Вы даже можете подключиться к уже работающей программе или загрузить файл дампа для изучения причины ошибки в программе. Возможно даже отладить ядро, хотя это делается немножко хитрее, чем отладка пользовательских приложений, которым посвящен этот раздел.
В gdb имеется достаточно хорошая встроенная система помощи, а также набор info-страниц, так что в этом разделе упор будет делаться на несколько основных команд.
Наконец, если вы находите, что его выдача команд в стиле командной строки в текстовом режиме неудобна, то в коллекции портов для него имеется графический инструмент xxgdb.
Этот раздел предназначен быть введением в использование gdb и не покрывает специальные вопросы, такие, как отладка ядра.
Чтобы получить максимальный результат от использования gdb, вам нужно откомпилировать программу с параметром -g. Отладчик будет работать и без этой опции, но вы сможете увидеть только название текущей функции, но не ее исходный код. Если вы увидите такое сообщение:
... (no debugging symbols found) ...
при запуске gdb, то определите, что программа не была откомпилирована с опцией -g.
В приглашении gdb наберите команду break main. Это укажет отладчику пропустить предварительный
подготовительный код программы и начать сразу с вашего кода. Теперь выдайте команду run для запуска программы--она начнет выполняться с
подготовительного кода и затем будет остановлена отладчиком при вызове main()
. (Если вы когда-либо удивлялись, откуда вызывается main()
, то теперь вы должны знать!).
Теперь вы можете выполнить программу построчно по шагам, нажимая n. Если вы попали на вызов функции, то можете перейти в нее, нажав s. Оказавшись в вызове функции, вы можете вернуться из пошагового выполнения функции нажатием f. Вы можете также использовать команды up и down для просмотра вызывающей подпрограммы.
Вот простой пример того, как выявить ошибку в программе при помощи gdb. Это наша программа (с намеренно допущенной ошибкой):
#include <stdio.h> int bazz(int anint); main() { int i; printf("This is my program\n"); bazz(i); return 0; } int bazz(int anint) { printf("You gave me %d\n", anint); return anint; }
Эта программа устанавливает значение переменной i равным
5 и передает ее в функцию bazz()
, которая выводит переданное нами число.
При компиляции и запуске программы мы получили
% cc -g -o temp temp.c % ./temp This is my program anint = 4231
Это не то, что мы ожидали! Самое время посмотреть, что же происходит!
% gdb temp GDB is free software and you are welcome to distribute copies of it under certain conditions; type "show copying" to see the conditions. There is absolutely no warranty for GDB; type "show warranty" for details. GDB 4.13 (i386-unknown-freebsd), Copyright 1994 Free Software Foundation, Inc. (gdb) break main Пропуск начального кода Breakpoint 1 at 0x160f: file temp.c, line 9. gdb устанавливает точку останова вmain()
(gdb) run Запуск до вызоваmain()
Starting program: /home/james/tmp/temp Программа начинает работать Breakpoint 1, main () at temp.c:9 gdb останавливается вmain()
(gdb) n Переход к следующей строке This is my program Программа выводит (gdb) s Переход вbazz()
bazz (anint=4231) at temp.c:17 gdb выводит стек вызовов (gdb)
Минуточку! Как параметр anint оказался равным 4231? Разве мы не присвоили ему значение 5 в функции main()
? Давайте перейдем
к функции main()
и взглянем туда.
(gdb) up Переход вверх по стеку вызовов #1 0x1625 in main () at temp.c:11 gdb выводит стек вызовов (gdb) p i Вывод значения переменной i $1 = 4231 gdb выводит 4231
О боже! Судя по коду, мы забыли инициализировать переменную i. Вы хотели сделать вот что
... main() { int i; i = 5; printf("This is my program\n"); ...
но забыли про строку i=5;. Так как мы не присвоили начальное значение для i, то переменная принимает случайное значение, оказывающее в соответствующей области памяти при работе программы, и в нашем случае это оказалось число 4231.
Note: gdb выводит стек вызовов всякий раз, когда мы вызываем или возвращаемся из функции, даже если мы используем команды up и down для продвижения по стеку. При этом выводится имя функции и значения ее аргументов, что помогает нам отслеживать, где мы находимся и что происходит. (Стек является областью, где программа сохраняет информацию о передаваемых функциям аргументах и о том, куда нужно перейти после возврата из функции).
Файл дампа, вообще говоря, является файлом, содержащим полный образ процесса в момент его сбоя. В ``добрые старые времена'' программисты выводили шестнадцатеричные распечатки файлов дампа и корпели над справочниками по машинным кодам, но сейчас жизнь несколько облегчилась. В частности, во FreeBSD и других системах на основе 4.4BSD файлы дампа называются progname.core, а не просто core, для того, чтобы было понятнее, к какой программе относится соответствующий файл дампа.
Для исследования файла дампа запустите gdb обычным образом. Вместо того, чтобы выдавать команду break или run, наберите
(gdb) core progname.core
Если вы не в том же каталоге, что и файл дампа, то вам нужно сначала выполнить команду dir /path/to/core/file.
Вы должны увидеть нечто вроде следующего:
% gdb a.out GDB is free software and you are welcome to distribute copies of it under certain conditions; type "show copying" to see the conditions. There is absolutely no warranty for GDB; type "show warranty" for details. GDB 4.13 (i386-unknown-freebsd), Copyright 1994 Free Software Foundation, Inc. (gdb) core a.out.core Core was generated by `a.out'. Program terminated with signal 11, Segmentation fault. Cannot access memory at address 0x7020796d. #0 0x164a in bazz (anint=0x5) at temp.c:17 (gdb)
В этом случае программа называлась a.out, так что файл дампа
называется a.out.core. Мы можем видеть, что программа
завершилась аварийно из-за попытки доступа к области памяти, ей недоступной, в функции
bazz
.
Иногда бывает полезно иметь возможность просмотреть, как функция была вызвана, потому что в сложной программе проблема могла появиться в любом месте большого стека вызовов. Команда bt заставляет gdb выдать обратную трассировку стека вызовов:
(gdb) bt #0 0x164a in bazz (anint=0x5) at temp.c:17 #1 0xefbfd888 in end () #2 0x162c in main () at temp.c:11 (gdb)
При сбое программы была вызвана функция end()
; в этом
случае функция bazz()
была вызвана из main()
.
Одной из выдающихся особенностей gdb является то, что он может подключаться к программе, которая уже выполняется. Конечно, при этом предполагается, что у вас достаточно полномочий, чтобы это осуществить. Имеется проблема в ситуации, когда вы хотите выполнить трассировку порождаемого процесса, но отладчик позволяет вам трассировать только родительский.
В этой ситуации нужно запустить еще один отладчик gdb, воспользоваться командой ps для поиска идентификатора порожденного процесса и выполнить команду
(gdb) attach pid
в gdb, после чего отлаживать программу обычным образом.
``Это все хорошо'', думаете, наверное, вы, ``но к моменту, когда я все это сделаю, порожденный процесс уже завершит свою работу''. Может быть, и нет, дорогой читатель, и вот как это делается (согласно info-страницам программы gdb):
... if ((pid = fork()) < 0) /* _Always_ check this */ error(); else if (pid == 0) { /* child */ int PauseMode = 1; while (PauseMode) sleep(10); /* Wait until someone attaches to us */ ... } else { /* parent */ ...
Теперь всё, что вам нужно сделать, это подключиться к порождённому процессу,
установить значение переменной PauseMode в 0 и дождаться возврата из вызова функции sleep()
!
К сожалению, Unix-системы поставляются без какого бы то ни было включающего-все-что-вы-захотите-и-гораздо-больше одного-гигантского-пакета интегрированной среды разработки, которые имеются в других системах. [7] Однако имеется возможность настроить собственную среду. Она может быть не такой уж красивой, и не очень интегрированной, но вы можете настроить ее так, как вам этого хочется. И она бесплатна. И доступны ее исходный код.
И все это называется Emacs. Некоторые питают к нему отвращение, но многие его любят. Если вы из первых, то боюсь, что этот раздел мало вас заинтересует. Также вам потребуется некоторое количество памяти для его работы--я рекомендую 8 МБ для текстового режима и 16 МБ для X в качестве минимальных требований для получения удовлетворительной производительности.
Вообще говоря, Emacs является редактором с очень широкими возможностями по его настройке--действительно, он устроен так, что больше похож на операционную систему, чем на редактор! На самом деле многие разработчики и системные администраторы проводят практически все свое время в редакторе Emacs, выходя из него только для завершения работы с системой.
Здесь даже вкратце просто невозможно описать все, что может делать Emacs, но вот только некоторые из возможностей, представляющих интерес для разработчиков:
Очень мощный редактор, позволяющий выполнять поиск и замену как строк, так и регулярных выражений (шаблонов), переход к началу или концу блока, и так далее, и тому подобное.
Выпадающие меню и встроенная система помощи.
Синтаксическое выделение и формирование отступов в зависимости от языка.
Полная настраиваемость.
В Emacs вы можете компилировать и отлаживать программы.
При возникновении ошибки компиляции вы можете перейти к соответствующей строке исходного кода.
Дружественный интерфейс с программой info, используемой для чтения гипертекстовой документации GNU, включая документацию на сам Emacs.
Дружественный интерфейс с программой gdb, позволяющий перемещаться по исходному коду в соответствии с шагами выполнения программы.
Вы можете читать телеконференции Usenet и электронную почту во время компиляции программы.
И, несомненно, многое из того, что я не упомянул.
Emacs может быть установлен во FreeBSD как порт Emacs.
После того, как он будет установлен, запустите его и нажмите C-h t для изучения учебника по Emacs--это означает удерживание клавиши control и одновременным нажатием на клавишу h, с последующим отпусканием клавиши control и нажатием кнопки t. (Либо вы можете использовать мышь для выбора пункта Emacs Tutorial из меню Help).
Хотя в Emacs имеются меню, стоит изучить используемые клавиатурные команды, так как при редактировании чего-либо гораздо быстрее нажать несколько клавиш, чем искать мышь и потом щелкать в нужном месте. И, если мы поговорите с опытными пользователями Emacs, то вы обнаружите, что они часто иногда выдают фразы типа ``M-x replace-s RET foo RET bar RET'', так что полезно знать, что они означают. И, в любом случае, в Emacs имеется слишком много полезных функций, чтобы уместить их все в системе меню.
К счастью, достаточно легко найти клавиатурные сокращения, так как они выводятся рядом с пунктом меню. Я советую пользоваться пунктом меню, скажем, для открытия файла, до тех пор, пока вы не поймете, как это работает и почувствуете себя уверенно, и только затем попробуете выполнить C-x C-f. Когда вы будете этим удовлетворены, переходите к изучению следующей команды меню.
Если вы не можете запомнить, что делает конкретная комбинация клавиш, выберите пункт Describe Key из меню Help и наберите ее--Emacs скажет, что она делает. Вы можете также воспользоваться пунктом меню Command Apropos для поиска всех команд, которые содержат некоторое слово, вместе с клавиатурной комбинацией.
Кстати, выражение выше означает удержание клавиши Meta, нажатие x, отпускание клавиши Meta, набор replace-s (сокращение для replace-string--еще одной особенностью Emacs является возможность сокращать команды). нажатие клавиши return, набор foo (строки, которую вы хотите заменить), нажатие клавиши return, набор bar (строки, которая заменит foo) и нажатие return снова. Emacs выполнит операцию по поиску и замене, которую вы только что запросили.
Если вы интересуетесь, в какой точке земного шара находится клавиша Meta, то это особая клавиша, которая имеется на многих рабочих станциях Unix. К несчастью, на PC их нет, поэтому обычно это клавиша alt (или, если вам уж совсем не повезло, клавиша escape).
Да, чтобы выйти из Emacs, нажмите C-x C-c (это означает удержание клавиши control, нажатие на x, нажатие на c и отпускание клавиши control). Если у вас есть открытые несохранненные файлы, Emacs выдаст запрос на их сохранение. (Не принимайте во внимание документацию, в которой говорится о C-z как об обычном способе выхода из Emacs--при этом Emacs остается работать в фоновом режиме и действительно необходима в случае работы с системой, но которой нет виртуальных терминалов).
Emacs имеет огромные возможности; некоторые из них являются встроенными, некоторые нужно настроить.
Вместо того, чтобы использовать для настройки собственный макро-язык, в Emacs применяется версия Lisp, специально адаптированная для редакторов, известная под названием Emacs Lisp. Это может быть оказаться весьма полезным, если вы хотите начать изучать что-то типа Common Lisp, потому что он значительно меньше, чем Common Lisp (хотя все же весьма большой!).
Лучше всего изучать Emacs Lisp, скачав Учебник по Emacs
Однако для выполнения настройки Emacs нет нужды знать какой бы то ни было Lisp, так как я включил сюда пример файла .emacs, которого для начала должно хватить. Просто скопируйте его в свой домашний каталог и перезапустите Emacs, если он уже запущен; он считает команды из файла и (надеюсь) даст вам приемлемые начальные настройки.
К сожалению, здесь слишком много всего, чтобы объяснить подробно; однако на паре мест стоит остановиться подробнее.
Все, что начинается с символа ;, является комментарием и игнорируется Emacs.
В первой строке -*- Emacs-Lisp -*- нужна для того, чтобы мы могли редактировать сам файл .emacs в редакторе Emacs и имели все удобства редактирования текстов Emacs Lisp. Emacs обычно пытается угадать характер содержимого на основе имени файла, и этого корректно может не получиться в случае .emacs.
Клавиша tab в некоторых режимах работает как функция формирования отступа, поэтому при нажатия клавиши табуляции в текущей строке кода будет сформирован отступ. Если вы хотите разместить где-либо символ tab, то при нажатии клавиши tab удерживайте клавишу control.
This file supports syntax highlighting for C, C++, Perl, Lisp and Scheme, by guessing the language from the filename.
В Emacs уже имеется предопределенная функция с именем next-error
. В окне вывода результата компиляции она позволяет вам
передвигаться от одной ошибки компиляции к другой по нажатию M-n; мы определили дополнительную функцию previous-error
, которая позволяет переходить к следующей ошибке
по нажатию M-p. Самой приятной возможностью является то, что по
нажатию C-c C-c файл с исходным текстом, в котором произошла
ошибка, будет открыт на соответствующей строке.
Мы включили возможность Emacs работать как сервер, так что если вы делаете что-то вне Emacs и хотите отредактировать файл, то можете просто набрать
% emacsclient filename
и после этого сможете редактировать файл в вашем Emacs! [8]
Example 2-1. Примерный файл .emacs
;; -*-Emacs-Lisp-*- ;; This file is designed to be re-evaled; use the variable first-time ;; to avoid any problems with this. (defvar first-time t "Flag signifying this is the first time that .emacs has been evaled") ;; Meta (global-set-key "\M- " 'set-mark-command) (global-set-key "\M-\C-h" 'backward-kill-word) (global-set-key "\M-\C-r" 'query-replace) (global-set-key "\M-r" 'replace-string) (global-set-key "\M-g" 'goto-line) (global-set-key "\M-h" 'help-command) ;; Function keys (global-set-key [f1] 'manual-entry) (global-set-key [f2] 'info) (global-set-key [f3] 'repeat-complex-command) (global-set-key [f4] 'advertised-undo) (global-set-key [f5] 'eval-current-buffer) (global-set-key [f6] 'buffer-menu) (global-set-key [f7] 'other-window) (global-set-key [f8] 'find-file) (global-set-key [f9] 'save-buffer) (global-set-key [f10] 'next-error) (global-set-key [f11] 'compile) (global-set-key [f12] 'grep) (global-set-key [C-f1] 'compile) (global-set-key [C-f2] 'grep) (global-set-key [C-f3] 'next-error) (global-set-key [C-f4] 'previous-error) (global-set-key [C-f5] 'display-faces) (global-set-key [C-f8] 'dired) (global-set-key [C-f10] 'kill-compilation) ;; Keypad bindings (global-set-key [up] "\C-p") (global-set-key [down] "\C-n") (global-set-key [left] "\C-b") (global-set-key [right] "\C-f") (global-set-key [home] "\C-a") (global-set-key [end] "\C-e") (global-set-key [prior] "\M-v") (global-set-key [next] "\C-v") (global-set-key [C-up] "\M-\C-b") (global-set-key [C-down] "\M-\C-f") (global-set-key [C-left] "\M-b") (global-set-key [C-right] "\M-f") (global-set-key [C-home] "\M-<") (global-set-key [C-end] "\M->") (global-set-key [C-prior] "\M-<") (global-set-key [C-next] "\M->") ;; Mouse (global-set-key [mouse-3] 'imenu) ;; Misc (global-set-key [C-tab] "\C-q\t") ; Control tab quotes a tab. (setq backup-by-copying-when-mismatch t) ;; Treat 'y' or <CR> as yes, 'n' as no. (fset 'yes-or-no-p 'y-or-n-p) (define-key query-replace-map [return] 'act) (define-key query-replace-map [?\C-m] 'act) ;; Load packages (require 'desktop) (require 'tar-mode) ;; Pretty diff mode (autoload 'ediff-buffers "ediff" "Intelligent Emacs interface to diff" t) (autoload 'ediff-files "ediff" "Intelligent Emacs interface to diff" t) (autoload 'ediff-files-remote "ediff" "Intelligent Emacs interface to diff") (if first-time (setq auto-mode-alist (append '(("\\.cpp$" . c++-mode) ("\\.hpp$" . c++-mode) ("\\.lsp$" . lisp-mode) ("\\.scm$" . scheme-mode) ("\\.pl$" . perl-mode) ) auto-mode-alist))) ;; Auto font lock mode (defvar font-lock-auto-mode-list (list 'c-mode 'c++-mode 'c++-c-mode 'emacs-lisp-mode 'lisp-mode 'perl-mode 'scheme-mode) "List of modes to always start in font-lock-mode") (defvar font-lock-mode-keyword-alist '((c++-c-mode . c-font-lock-keywords) (perl-mode . perl-font-lock-keywords)) "Associations between modes and keywords") (defun font-lock-auto-mode-select () "Automatically select font-lock-mode if the current major mode is in font-lock-auto-mode-list" (if (memq major-mode font-lock-auto-mode-list) (progn (font-lock-mode t)) ) ) (global-set-key [M-f1] 'font-lock-fontify-buffer) ;; New dabbrev stuff ;(require 'new-dabbrev) (setq dabbrev-always-check-other-buffers t) (setq dabbrev-abbrev-char-regexp "\\sw\\|\\s_") (add-hook 'emacs-lisp-mode-hook '(lambda () (set (make-local-variable 'dabbrev-case-fold-search) nil) (set (make-local-variable 'dabbrev-case-replace) nil))) (add-hook 'c-mode-hook '(lambda () (set (make-local-variable 'dabbrev-case-fold-search) nil) (set (make-local-variable 'dabbrev-case-replace) nil))) (add-hook 'text-mode-hook '(lambda () (set (make-local-variable 'dabbrev-case-fold-search) t) (set (make-local-variable 'dabbrev-case-replace) t))) ;; C++ and C mode... (defun my-c++-mode-hook () (setq tab-width 4) (define-key c++-mode-map "\C-m" 'reindent-then-newline-and-indent) (define-key c++-mode-map "\C-ce" 'c-comment-edit) (setq c++-auto-hungry-initial-state 'none) (setq c++-delete-function 'backward-delete-char) (setq c++-tab-always-indent t) (setq c-indent-level 4) (setq c-continued-statement-offset 4) (setq c++-empty-arglist-indent 4)) (defun my-c-mode-hook () (setq tab-width 4) (define-key c-mode-map "\C-m" 'reindent-then-newline-and-indent) (define-key c-mode-map "\C-ce" 'c-comment-edit) (setq c-auto-hungry-initial-state 'none) (setq c-delete-function 'backward-delete-char) (setq c-tab-always-indent t) ;; BSD-ish indentation style (setq c-indent-level 4) (setq c-continued-statement-offset 4) (setq c-brace-offset -4) (setq c-argdecl-indent 0) (setq c-label-offset -4)) ;; Perl mode (defun my-perl-mode-hook () (setq tab-width 4) (define-key c++-mode-map "\C-m" 'reindent-then-newline-and-indent) (setq perl-indent-level 4) (setq perl-continued-statement-offset 4)) ;; Scheme mode... (defun my-scheme-mode-hook () (define-key scheme-mode-map "\C-m" 'reindent-then-newline-and-indent)) ;; Emacs-Lisp mode... (defun my-lisp-mode-hook () (define-key lisp-mode-map "\C-m" 'reindent-then-newline-and-indent) (define-key lisp-mode-map "\C-i" 'lisp-indent-line) (define-key lisp-mode-map "\C-j" 'eval-print-last-sexp)) ;; Add all of the hooks... (add-hook 'c++-mode-hook 'my-c++-mode-hook) (add-hook 'c-mode-hook 'my-c-mode-hook) (add-hook 'scheme-mode-hook 'my-scheme-mode-hook) (add-hook 'emacs-lisp-mode-hook 'my-lisp-mode-hook) (add-hook 'lisp-mode-hook 'my-lisp-mode-hook) (add-hook 'perl-mode-hook 'my-perl-mode-hook) ;; Complement to next-error (defun previous-error (n) "Visit previous compilation error message and corresponding source code." (interactive "p") (next-error (- n))) ;; Misc... (transient-mark-mode 1) (setq mark-even-if-inactive t) (setq visible-bell nil) (setq next-line-add-newlines nil) (setq compile-command "make") (setq suggest-key-bindings nil) (put 'eval-expression 'disabled nil) (put 'narrow-to-region 'disabled nil) (put 'set-goal-column 'disabled nil) ;; Elisp archive searching (autoload 'format-lisp-code-directory "lispdir" nil t) (autoload 'lisp-dir-apropos "lispdir" nil t) (autoload 'lisp-dir-retrieve "lispdir" nil t) (autoload 'lisp-dir-verify "lispdir" nil t) ;; Font lock mode (defun my-make-face (face colour &optional bold) "Create a face from a colour and optionally make it bold" (make-face face) (copy-face 'default face) (set-face-foreground face colour) (if bold (make-face-bold face)) ) (if (eq window-system 'x) (progn (my-make-face 'blue "blue") (my-make-face 'red "red") (my-make-face 'green "dark green") (setq font-lock-comment-face 'blue) (setq font-lock-string-face 'bold) (setq font-lock-type-face 'bold) (setq font-lock-keyword-face 'bold) (setq font-lock-function-name-face 'red) (setq font-lock-doc-string-face 'green) (add-hook 'find-file-hooks 'font-lock-auto-mode-select) (setq baud-rate 1000000) (global-set-key "\C-cmm" 'menu-bar-mode) (global-set-key "\C-cms" 'scroll-bar-mode) (global-set-key [backspace] 'backward-delete-char) ; (global-set-key [delete] 'delete-char) (standard-display-european t) (load-library "iso-transl"))) ;; X11 or PC using direct screen writes (if window-system (progn ;; (global-set-key [M-f1] 'hilit-repaint-command) ;; (global-set-key [M-f2] [?\C-u M-f1]) (setq hilit-mode-enable-list '(not text-mode c-mode c++-mode emacs-lisp-mode lisp-mode scheme-mode) hilit-auto-highlight nil hilit-auto-rehighlight 'visible hilit-inhibit-hooks nil hilit-inhibit-rebinding t) (require 'hilit19) (require 'paren)) (setq baud-rate 2400) ; For slow serial connections ) ;; TTY type terminal (if (and (not window-system) (not (equal system-type 'ms-dos))) (progn (if first-time (progn (keyboard-translate ?\C-h ?\C-?) (keyboard-translate ?\C-? ?\C-h))))) ;; Under UNIX (if (not (equal system-type 'ms-dos)) (progn (if first-time (server-start)))) ;; Add any face changes here (add-hook 'term-setup-hook 'my-term-setup-hook) (defun my-term-setup-hook () (if (eq window-system 'pc) (progn ;; (set-face-background 'default "red") ))) ;; Restore the "desktop" - do this as late as possible (if first-time (progn (desktop-load-default) (desktop-read))) ;; Indicate that this file has been read at least once (setq first-time nil) ;; No need to debug anything now (setq debug-on-error nil) ;; All done (message "All done, %s%s" (user-login-name) ".")
Итак, хорошо, если вы собираетесь программировать только на языках, уже описанных в файле .emacs (C, C++, Perl, Lisp и Scheme), но что случится, если вышел новый язык под названием ``whizbang'', в котором имеется масса привлекательных возможностей?
Первым делом нужно проверить, не поставляется ли с языком whizbang с файлами для Emacs, которые служат для описания языка. Обычно они имеют окончание .el, что означает ``Emacs Lisp''. Например, если whizbang является портом FreeBSDm мы можем поискать такие файлы командой
% find /usr/ports/lang/whizbang -name "*.el" -print
и установить их, скопировав их в каталог Lisp для Emacs вашей системы. Во FreeBSD 2.1.0-RELEASE это каталог /usr/local/share/emacs/site-lisp.
Так, для примера, если результат выполнения команды поиска выглядит как
/usr/ports/lang/whizbang/work/misc/whizbang.el
то мы выполним
# cp /usr/ports/lang/whizbang/work/misc/whizbang.el /usr/local/share/emacs/site-lisp
Затем нам нужно решить, какое расширение имеют файлы с исходными текстами на языке whizbang. Пусть, для примера, они все оканчиваются на .wiz. Нам нужно добавить запись в наш файл .emacs, чтобы Emacs смог использовать информацию из whizbang.el.
Найдите строку auto-mode-alist в файле .emacs и добавьте строку для whizbang, такую, как:
... ("\\.lsp$" . lisp-mode) ("\\.wiz$" . whizbang-mode) ("\\.scm$" . scheme-mode) ...
Это означает, что Emacs будет автоматически переходить в режим whizbang-mode
, когда вы редактируете файл, оканчивающийся на .wiz.
Чуть ниже вы найдете запись для font-lock-auto-mode-list.
Добавьте к ней whizbang-mode
следующего вида:
;; Auto font lock mode (defvar font-lock-auto-mode-list (list 'c-mode 'c++-mode 'c++-c-mode 'emacs-lisp-mode 'whizbang-mode 'lisp-mode 'perl-mode 'scheme-mode) "List of modes to always start in font-lock-mode")
Это означает, что Emacs всегда будет использовать режим font-lock-mode
(то есть выделение синтаксических конструкций) при
редактировании файлов .wiz.
И это все, что нужно. Если есть что-то, что вы хотите делать автоматически при
открытии файлов .wiz, то можете добавить строку whizbang-mode hook
(смотрите my-scheme-mode-hook
в качестве простого примера, который
добавляет auto-indent
).
Brian Harvey and Matthew Wright Simply Scheme MIT 1994. ISBN 0-262-08226-8
Randall Schwartz Learning Perl O'Reilly 1993 ISBN 1-56592-042-2
Patrick Henry Winston and Berthold Klaus Paul Horn Lisp (3rd Edition) Addison-Wesley 1989 ISBN 0-201-08319-1
Brian W. Kernighan and Rob Pike The Unix Programming Environment Prentice-Hall 1984 ISBN 0-13-937681-X
Brian W. Kernighan and Dennis M. Ritchie The C Programming Language (2nd Edition) Prentice-Hall 1988 ISBN 0-13-110362-8
Bjarne Stroustrup The C++ Programming Language Addison-Wesley 1991 ISBN 0-201-53992-6
W. Richard Stevens Advanced Programming in the Unix Environment Addison-Wesley 1992 ISBN 0-201-56317-7
W. Richard Stevens Unix Network Programming Prentice-Hall 1990 ISBN 0-13-949876-1
Эту главу написал Мюррей Стокели (Murray Stokely).
Эта глава описывает некоторые из проблем обеспечения безопасности, которые десятилетиями преследовали программистов Unix, а также несколько новых доступных инструментов, помогающих программистам избежать написания небезопасного кода.
Написание безопасных приложений требует весьма критического и пессимистического взгляда на жизнь. Приложения должны работать по принципу ``наименьших привилегий'', при котором никакой процесс не должен работать с привилегиями, превышающими минимально необходимый для выполнения своих функций минимум. Ранее проверенный код должен использоваться там, где только это возможно для избежания общих ошибок, которые могли быть уже исправлены другими.
Одной из неприятностей в среде Unix является легкость в предположении безопасности этого окружения. Приложения никогда не должны верить пользовательскому вводу (во всех его формах), ресурсам системы, межпроцессному взаимодействию или времени выполнения событий. Процессы Unix выполняются не синхронно, так что логические операции редко бывают атомарными.
Переполнения буфера появились вместе с появление архитектуры Фон-Неймана 1. Впервые широкую известность они получили в 1988 году вместе с Интернет-червем Мурса (Moorse). К сожалению, точно такая же атака повторилась и в наши дни. Из 17 бюллетеней безопасности CERT за 1999 год, 10 были непосредственно вызваны ошибкам в программном обеспечении, связанным с переполнениями буфера. Самые распространенные типы атак с использованием переполнения буфера основаны на разрушении стека.
Самые современные вычислительные системы используют стек для передачи аргументов процедурам и сохранения локальных переменных. Стек является буфером типа LIFO (последним вошел первым вышел) в верхней части области памяти процесса. Когда программа вызывает функцию, создается новая "граница стека". Эта граница состоит из аргументов, переданных в функцию, а также динамического количества пространства локальных переменных. "Указатель стека" является регистром, хранящим текущее положение вершины стека. Так как это значение постоянно меняется вместе с помещением новых значений на вершину стека, многие реализации также предусматривают "указатель границы", который расположен около начала стека, так что локальные переменные можно легко адресовать относительно этого значения. 1 Адрес возврата из функции также сохраняется в стеке, и это является причиной нарушений безопасности, связанных с переполнением стека, так как перезаписывание локальной переменной в функции может изменить адрес возврата из этой функции, потенциально позволяя злоумышленнику выполнить любой код.
Хотя атаки с переполнением стека являются самыми распространенными, стек можно также перезаписать при помощи атаки, основанной на выделении памяти (malloc/free) из "кучи".
Как и во многих других языках программирования, в C не выполняется автоматической проверки границ в массивах или указателях. Кроме того, стандартная библиотека C полна очень опасных функций.
strcpy (char *dest, const char *src) |
Может переполнить целевой буфер |
strcat (char *dest, const char *src) |
Может переполнить целевой буфер |
getwd (char *buf) |
Может переполнить буфер buf |
gets (char *s) |
Может переполнить буфер s |
[vf]scanf (const char *format, ...) |
Может переполнить свои аргументы. |
realpath (char *path, char resolved_path[]) |
Может переполнить буфер path |
[v]sprintf (char *str, const char *format, ...) |
Может переполнить буфер str. |
В следующем примере кода имеется ошибка переполнения буфера, предназначенная для перезаписи адреса возврата и обхода инструкции, следующей непосредственно за вызовом функции. (По мотивам 4)
#include <stdio.h> void manipulate(char *buffer) { char newbuffer[80]; strcpy(newbuffer,buffer); } int main() { char ch,buffer[4096]; int i=0; while ((buffer[i++] = getchar()) != '\n') {}; i=1; manipulate(buffer); i=2; printf("The value of i is : %d\n",i); return 0; }
Давайте посмотрим, как будет выглядеть образ процесса, если в нашу маленькую программу мы введем 160 пробелов.
[XXX figure here!]
Очевидно, что для выполнения реальных инструкций (таких, как exec(/bin/sh)), может быть придуман более вредоносный ввод.
Самым прямолинейным решением проблемы переполнения стека является использование только
памяти фиксированного размера и функций копирования строк. Функции strncpy
и strncat
являются частью
стандартной библиотеки C. Эти функции будут копировать не более указанного количества
байт из исходной строки в целевую. Однако у этих функций есть несколько проблем. Ни одна
из них не гарантирует наличие символа NUL, если размер входного буфера больше, чем
целевого. Параметр длины также по-разному используется в strncpy и strncat, так что для
программистов легко запутаться в правильном использовании. Есть также и значительная
потеря производительности по сравнению с strcpy
при
копировании короткой строки в большой буфер, потому что strncpy
заполняет символами NUL пространство до указанной
длины.
Для избежания этих проблем в OpenBSD была сделана другая реализация копирования
памяти. Функции strlcpy
и strlcat
гарантируют, что они они всегда терминируют целевую
строку нулевым символом, если им будет передан аргумент ненулевой длины. Более подробная
информация об этом находится здесь 6. Инструкции OpenBSD strlcpy
и strlcat
были во FreeBSD
начиная с 3.5.
К несчастью, все еще широко используется очень большой объем кода, который слепо копирует память без использования только что рассмотренных функций с проверкой границ. Однако есть другое решение. Существует несколько расширений к компилятору и библиотекам C/C++ для выполнения контроля границ во время выполнения.
Одним из таких добавлений является StackGuard, который реализован как маленький патч к генератору кода gcc. Согласно сайту StackGuard, http://immunix.org/stackguard.html:
"StackGuard распознает и защищает стек от атак, не позволяя изменять адрес возврата в стеке. При вызове функции StackGuard помещает вслед за адресом возврата сигнальное слово. Если после возврата из функции оно оказывается измененным, то была попытка выполнить атаку на стек, и программа отвечает на это генерацией сообщения о злоумышленнике в системном журнале, а затем прекращает работу."
"StackGuard реализован в виде маленького патча к генератору кода gcc, а именно процедур function_prolog() и function_epilog(). function_prolog() усовершенствована для создания пометок в стеке при начале работы функции, а function_epilog() проверяет целостность пометки при возврате из функции. Таким образом, любые попытки изменения адреса возврата определяются до возврата из функции."
Перекомпиляция вашего приложения со StackGuard является эффективным способом остановить большинство атак переполнений буфера, но все же полностью это проблемы не решает.
Механизмы на основе компилятора полностью бесполезны для программного обеспечения,
поставляемого в двоичном виде, которое вы не можете перекомпилировать. В этих ситуациях
имеется некоторое количество библиотек, в которых реализованы небезопасные функции
библиотеки C (strcpy
, fscanf
,
getwd
, и так далее..), обеспечивающие невозможность записи
после указателя стека.
libsafe
libverify
libparnoia
К сожалению, эти защиты имеют некоторое количество недостатков. Эти библиотеки могут защитить только против малого количества проблем, и не могут исправить реальные проблемы. Эти защиты могут не сработать, если приложение скомпилировано с параметром -fomit-frame-pointer. К тому же переменные окружения LD_PRELOAD и LD_LIBRARY_PATH могут быть переопределены/сняты пользователем.
Имеется по крайней мере 6 различных идентификаторов (ID), связанных с любым взятым процессом. Поэтому вы должны быть очень осторожны с тем, какие права имеет ваш процесс в каждый момент времени. В частности, все seteuid-приложения должны понижать свои привилегии, как только в них отпадает необходимость.
ID реального пользователя может быть изменен только процессом администратора. Программа login устанавливает его, когда пользователь входит в систему, и он редко меняется.
Эффективный ID пользователя устанавливается функциями exec()
, если у программы установлен бит seteuidt. Приложение
может выполнить вызов seteuid()
в любой момент для
установки эффективного ID пользователя в значение реального ID пользователя или
сохраняемого set-user-ID. Когда эффективный ID пользователя устанавливается функциями
exec()
, его предыдущее значение сохраняется в сохраняемом
set-user-ID.
Традиционно используемым методом ограничения доступа к процессу является использование
системного вызова chroot()
. Этот системный вызов меняет
корневой каталог, относительно которого определяются все остальные пути в самом процессе
и всех порожденных ими процессах. Для того, чтобы этот вызов был выполнен успешно,
процесс должен иметь право на выполнение (поиск) каталога, о котором идет речь. Новая
среда реально не вступит в силу, пока вы не выполните вызов chdir()
в вашей новой среде. Следует также отметить, что процесс
может с легкостью выйти из chroot-среды, если он имеет привилегии администратора. Это
может быть достигнуто созданием файлов устройств для чтения памяти ядра, подключением
отладчика к процессу вне узницы и многими другими способами.
Поведение системного вызова chroot()
можно некоторым
образом контролировать sysctl-переменной
kern.chroot_allow_open_directories. Когда эта переменная установлена в 0, chroot()
не сработает с ошибкой EPERM, если есть какие-либо
открытые каталоги. Если она установлена в значение по умолчанию, равное 1, то chroot()
не сработает с ошибкой EPERM, если есть какие-либо
открытые каталоги и процесс уже подвергнут вызову chroot()
.
Для всех других значений проверка открытости каталогов будет полностью опущена.
Концепция джейлов (Jail) расширяет возможности chroot()
,
ограничивая власть администратора созданием настоящих `виртуальных серверов'. Как только
тюремная камера создана, все сетевые коммуникации должны осуществляться через выделенный
адрес IP, а сила "привилегий пользователя root" в этой тюрьме довольно ограничена.
При работе внутри тюрьмы, любые проверки силы администратора в ядре при помощи вызова
suser()
будут оканчиваться неудачно. Однако некоторые
вызовы к suser()
были изменены на новый интерфейс suser_xxx()
. Эта функция отвечает за распознание и разрешение
доступа к власти администратора для процессов, не находящихся в неволе.
Процесс администратора внутри среды джейла имеет право:
Манипулировать привилегиями с помощью setuid
, seteuid
, setgid
, setegid
, setgroups
, setreuid
, setregid
и setlogin
Устанавливать ограничения на использование ресурсов при помощи setrlimit
Модифицировать некоторые sysctl-переменные (kern.hostname)
chroot()
Устанавливать следующие флаги на vnode: chflags
, fchflags
Устанавливать такие атрибуты vnode, как права доступа к файлу, изменять его владельца, группу, размер, время доступа и модификации.
Осуществлять привязку к привилегированному порту в области портов Интернет (порты с номерами < 1024)
Jail
является очень полезным инструментом для запуска
приложений в защищенном окружении, но есть и некоторые недостатки. На текущий момент к
формату suser_xxx
не преобразованы механизмы IPC, так что
такие приложения, как MySQL, не могут работать в джейле. Права администратора могут имеет
малую силу внутри джейла, но нет способа определить, что значит "малую".
Posix выпустил рабочий документ, который добавляет аудит событий, списки управления доступом, тонко настраиваемые привилегии, метки информации и жесткое управление доступом.
Этот документ находится в работе и находится в центре внимания проекта TrustedBSD. Некоторая начальная функциональность уже была добавлена во FreeBSD-current (cap_set_proc(3)).
Приложение никогда не должно полагать, что среда пользователя безопасна. Сюда включается (но этим не ограничено): ввод пользователя, сигналы, переменные среды, ресурсы, IPC, отображаемая в файл память, рабочий каталог файловой системы, дескрипторы файлов, число открытых файлов и прочее.
Никогда не думайте, что сможете предусмотреть все формы неправильного ввода, который может дать пользователь. Вместо этого ваше приложение должно осуществлять позитивную фильтрацию, пропуская только конечное множество возможных вариантов ввода, которые вы считаете безопасными. Неполная проверка данных была причиной многих нарушений защиты, особенно CGI-скриптов на веб-сайтах. Для имен файлов вам нужно уделять особое внимание путям ("../", "/"), символическим ссылкам и экранирующим символам оболочки.
В perl имеется такая очень полезная вещь, как "безупречный" (taint) режим, который
можно использовать для запрещения скриптам использовать данные, порожденные вне
программы, не безопасным способом. Этот режим проверяет аргументы командной строки,
переменные окружения, информацию локализации, результаты некоторых системных вызовов
(readdir()
, readlink()
, getpwxxx()
и весь файловый ввод.
Неожиданное поведение - это аномальное поведение, вызванное непредусмотренной зависимостью от относительной последовательности событий. Другими словами, программист неправильно предположил, что некоторое событие всегда случается перед другим.
Некоторые из широко распространенных причин возникновения таких проблем являются
сигналы, проверки доступа и открытия файлов. Сигналы по своей природе являются
асинхронными событиями, так что по отношению к ним нужно проявлять особое внимание.
Проверка доступа функцией access(2)
с последующим вызовом
open(2)
полностью не атомарно. Пользователи могут
переместить файлы в промежутке между двумя вызовами. Вместо этого привилегированное
приложение должно выполнить seteuid()
, а затем сразу
вызвать open()
. В тех же строках приложение должно всегда
устанавливать явно маску прав доступа (umask) перед вызовом функции open()
во избежание беспорядочных вызовов chmod()
.
Текст предоставил Poul-Henning Kamp <phk@FreeBSD.org>
.
В этой главе описываются различные рекомендации и требования, которые должны соблюдаться в дереве исходных текстов FreeBSD.
Июнь 1996.
Если некоторая часть дистрибутива FreeBSD поддерживается некоторым человеком или группой людей, они могут сообщить об этом миру, добавив строчку
MAINTAINER= email-addressesв файл Makefile, соответствующий этой части исходного кода.
Смысл этого в следующем:
Сопровождающий владеет кодом и отвечает за него. Это означает, что он несет ответственность за исправление ошибок и закрывает сообщения о проблемах, имеющих отношение к этой части кода, а в случае программного обеспечения, взятого из третьих источников, соответственно отвечает за отслеживание новых версий.
Изменения в каталогах, для которых известен сопровождающий, прежде чем они будут внесены, должны быть посланы ему на рассмотрение. Только если сопровождающий не отвечает в течение достаточно большого периода времени на несколько посланий по электронной почте, разрешается внести изменения без участия сопровождающего. Однако рекомендуется, чтобы вы попытались передать изменения на рассмотрение кому-либо еще, если это вообще возможно.
Конечно же, нельзя назначать человека или группу лиц сопровождающими, если они не согласны выполнять эту работу. С другой стороны, необязательно это должен быть конкретный коммиттер, это может быть и группа людей.
Текст предоставили Poul-Henning Kamp <phk@FreeBSD.org>
и
David O'Brien <obrien@FreeBSD.org>
.
Июнь 1996.
Некоторые части дистрибутива FreeBSD состоят из программного обеспечения, которое сопровождается вне проекта FreeBSD. По историческим причинам мы называем такое программное обеспечение контрибуцированным (contributed), или третьих сторон. Примерами этого могут служить утилиты perl, gcc и patch.
За последние несколько лет для работы с таким программным обеспечением использовались различные методы, и все они имели свои достоинства и недостатки. Абсолютно подходящего метода так и не нашлось.
По этой причине после некоторых дебатов был выбран и признан ``официальным'' один из этих методов, который необходимо применять в будущем при импортировании такого рода программного обеспечения. Более того, настоятельно рекомендуется с течением времени перевести существующее программное обеспечение третьих сторон на этот метод, так как он имеет значительные преимущества перед старым методом, включая возможность легкого получения diff-файлов относительно ``официальных'' версий исходных текстов кем угодно (даже не имеющим доступа к cvs). Это делает данный метод гораздо проще в использовании при необходимости выдачи изменений изначальным разработчикам такого программного обеспечения.
В конце концов, однако, это касается тех, кто делает реальную работу. Если использование этой модели в конкретном случае не подходит для пакета, с которым работает человек, могут быть сделаны и исключения только с согласия основной команды разработчиков и при общем одобрении других разработчиков. Возможность сопровождения пакета в будущем будет являться ключевым моментом при принятии решений.
Note: Из-за досадных ограничений в дизайне формата файлов RCS и использовании веток поставщика в CVS, мелкие, тривиальные и/или косметические изменения сильно не рекомендуется в файлах, которые все еще отслеживаются в ветке поставщика. Это касается и ``исправления орфографических ошибок'' как относящихся к категории ``косметических'' и избегаемых для файлов с версиями 1.1.x.x. Рост объема хранилища, вызванный изменением в один символ, может оказаться весьма большим.
В качестве примера того, как работает эта модель, будем использовать встраиваемый язык программирования TCL:
Каталог src/contrib/tcl содержит исходные тексты пакета в том виде, в котором они распространяются его создателями. Части, которые полностью не применимы во FreeBSD, могут быть удалены. В случае Tcl подкаталоги mac, win и compat были удалены перед операцией импортирования
Каталог src/lib/libtcl содержит только файл Makefile ``в стиле bmake'', который использует стандартные правила bsd.lib.mk make-файла для построения библиотеки и установки документации.
В каталоге src/usr.bin/tclsh размещаются make-файлы в стиле bmake, которые отвечают за построение и установку программы tclsh и связанных с ней справочных страниц при помощи стандартных правил из bsd.prog.mk.
Каталог src/tools/tools/tcl_bmake содержит несколько shell-скриптов, которые могут помочь при обновлении программного обеспечения tcl. Они не являются частью строящегося и инсталлируемого программного обеспечения.
Здесь важно то, что каталог src/contrib/tcl создавался в соответствии с правилами: Предполагается, что он содержит исходные тексты в том виде, в котором они распространяются (в соответствующей ветви поставщика CVS и без расширения ключевых слов RCS) с максимально малым количеством изменений, специфичных для FreeBSD. Утилита 'easy-import' на машине freefall поможет в импортировании, но если есть сомнения по поводу выполнения этой операции, то обязательно спросите совета и не действуйте слепо в расчете на то, что ``все сработает''. CVS не прощает ошибок импортирования и для ликвидации последствий больших ошибок требуются значительные усилия.
Из-за ранее отмеченных ограничений дизайна веток поставщиков в CVS требуется, чтобы ``официальные'' патчи от разработчика были сначала применены к распространяемым исходным текстам, а затем результат снова импортирован в ветку поставщика. Официальные патчи никогда не должны применяться к версии, извлеченной из хранилища FreeBSD, а затем ``коммититься'', так как это приведет к рассинхронизации дерева производителя и усложнит импортирование будущих версий, так как возникнут конфликты.
Так как многие пакеты содержат файлы, имеющие значение при обеспечении совместимости с другими, отличными от FreeBSD архитектурами и окружениями, то разрешается удалять части дистрибутивного дерева, не представляющие интереса для FreeBSD в целях уменьшения занимаемого дискового пространства. Файлы, содержащие замечания о юридических правах и информацию о релизе, касающуюся остальных файлов, удаляться не должны.
Если это видится легким, то файлы Makefile в стиле bmake могут быть сгенерированы из дистрибутивного дерева автоматически некоторой утилитой, чем-то, что позволит еще проще обновляться до новой версии. Если это будет сделано, то обязательно поместите эту утилиту (если необходимо) в каталог src/tools вместе с самим портом, чтобы она была доступна будущим сопровождающим лицам.
В каталог src/contrib/tcl должен быть добавлен файл FREEBSD-upgrade, в котором нужно перечислить такие вещи:
Какие файлы были оставлены
Где был взят оригинальный дистрибутив и/или на каком основном официальном сайте он находится.
Куда посылать патчи для разработчиков пакета
Возможно, обзор сделанных изменений, специфичных для FreeBSD.
Однако, пожалуйста, не импортируйте FREEBSD-upgrade вместе с исходными текстами этого программного обеспечения. Вместо этого вы должны выполнить команды cvs add FREEBSD-upgrade ; cvs ci после первоначального импортирования. Ниже дается пример описания из каталога src/contrib/cpio:
This directory contains virgin sources of the original distribution files on a "vendor" branch. Do not, under any circumstances, attempt to upgrade the files in this directory via patches and a cvs commit. New versions or official-patch versions must be imported. Please remember to import with "-ko" to prevent CVS from corrupting any vendor RCS Ids. For the import of GNU cpio 2.4.2, the following files were removed: INSTALL cpio.info mkdir.c Makefile.in cpio.texi mkinstalldirs To upgrade to a newer version of cpio, when it is available: 1. Unpack the new version into an empty directory. [Do not make ANY changes to the files.] 2. Remove the files listed above and any others that don't apply to FreeBSD. 3. Use the command: cvs import -ko -m 'Virgin import of GNU cpio v<version>' \ src/contrib/cpio GNU cpio_<version> For example, to do the import of version 2.4.2, I typed: cvs import -ko -m 'Virgin import of GNU v2.4.2' \ src/contrib/cpio GNU cpio_2_4_2 4. Follow the instructions printed out in step 3 to resolve any conflicts between local FreeBSD changes and the newer version. Do not, under any circumstances, deviate from this procedure. To make local changes to cpio, simply patch and commit to the main branch (aka HEAD). Never make local changes on the GNU branch. All local changes should be submitted to "cpio@gnu.ai.mit.edu" for inclusion in the next vendor release. obrien@FreeBSD.org - 30 March 1997
Иногда может быть необходимо включить некоторый нежелательный для нас файл в дерево исходных текстов FreeBSD. Например, если устройство требует загрузки в него некоторого маленького двоичного кода перед тем, как устройство заработает, и мы не имеем исходных текстов этого кода, то говорится, что двоичный файл является нежелательным. Для включения нежелательных файлов в дерево исходных текстов FreeBSD имеются следующие соглашения.
Любой файл, интерпретируемый или выполняемый системным(и) CPU, не в форме исходного кода, является нежелательным.
Любой файл с лицензией, ограничивающей более, чем BSD или GNU, является нежелательным.
Файл, содержащий загружаемые двоичные данные, используемые аппаратным обеспечением, не являются нежелательными, если только к нему не применимы условия (1) или (2). Он должен быть сохранен в нейтральном к архитектуре формате ASCII (рекомендуется применить утилиты file2c или uuencode).
Любой нежелательный файл требует особое согласие со стороны основной команды разработчиков до того, как он будет добавлен в хранилище CVS.
Нежелательные файлы помещаются в каталог src/contrib или src/sys/contrib.
Части одного модуля должны храниться вместе. Нет необходимости разбивать их, если только нет совместного использования с кодом, не являющимся нежелательным.
Объектные файлы именуются arch/filename.o.uu>.
Файлы ядра;
Должны всегда упоминаться в conf/files.* (для упрощения построения).
Должны всегда присутствовать в LINT, но основная команда разработчиков решает в каждом конкретном случае, должны ли они быть раскомментированы или нет. Конечно, позже основная команда разработчиков может изменить свое решение.
Вопрос о вхождении в состав релиза решается Группой Выпусков Релизов.
Файлы уровня пользователя:
Основная команда разработчиков решает, должен ли код стать частью выполнения команды make world.
Релиз инженер решает, войдут ли они в релиз.
Текст предоставили Satoshi Asami <asami@FreeBSD.org>
,
Peter Wemm <peter@FreeBSD.org>
, и David O'Brien <obrien@FreeBSD.org>
9 декабря 1996.
Если вы добавляете поддержку динамических библиотек к порту или другой части программного обеспечения, которая этой возможностью не обладает, то номера версий должны назначаться по нижеследующим правилам. Как правило, получающиеся номера не имеют ничего общего с номером релиза программного обеспечения.
При построении динамической библиотеки используются три принципа:
Начинаем с 1.0
Если есть изменение, которое имеет обратную совместимость, увеличиваем младший номер версии (заметьте, что системы ELF его игнорируют)
Если есть изменение, не соблюдающее совместимость, увеличиваем старший номер версии
К примеру, добавление функций и исправление ошибок приводит к увеличению младшего номера версии, а удаление функций, изменение синтаксиса вызова функции и тому подобные изменения приводят к изменению старшего номера версии.
Следуйте схеме нумерации версий в форме старший.младший (x.y). Наш динамический загрузчик формата a.out не умеет нормально работать с номерами версий в форме x.y.z. Любой номер версии после y (то есть третье число) полностью игнорируется при сравнении номеров версий динамических библиотек для определения того, с какой библиотекой осуществлять компоновку. Если есть две динамические библиотеки, отличающиеся только ``микро''-номером версии, то ld.so будет осуществлять компоновку с наибольшим номером. Другими словами: если вы компонуете с libfoo.so.3.3.3, то компоновщик запишет в заголовках только 3.3 и будет выполнять компоновку с любой библиотекой, начинающейся с libfoo.so.3.(все, что >= 3).(наибольшее из доступного).
Note: ld.so всегда будет использовать наибольшую ``младшую'' версию. Иными словами: он будет предпочитать использовать libc.so.2.2, а не libc.so.2.0, даже если программа изначально была скомпонована с libc.so.2.0.
Вдобавок наш динамический компоновщик ELF совсем не работает с младшими версиями. Однако все же нужно указывать старший и младший номер версии, а наши файлы Makefile ``сделают все как нужно'' в зависимости от типа системы.
Для библиотек не в составе портов, имеется наше соглашение на изменение номера версии динамической библиотеки только один раз между релизами. Кроме того, есть договоренность на изменение старшего номера динамической библиотеки только один раз между главными релизами ОС (например c 3.0 к 4.0). Когда вы делаете изменение в системной библиотеке, которое требует увеличения номера версии, посмотрите журналы коммитов изменений в файле Makefile. Коммиттер отвечает за то, что первое такое изменение с момента релиза приведет к обновлению номера версии динамической библиотеки в файле Makefile, а при других последующих изменениях этого бы не делалось.
Текст предоставили Paul Richards <paul@FreeBSD.org>
и
Jörg Wunsch <joerg@FreeBSD.org>
Вот некоторые указания по работе с отладкой ядра с аварийными дампами памяти. Как правило, вам нужно будет задать одно из устройств подкачки, перечисленных в файле /etc/fstab. Сброс образов памяти на устройства, не являющиеся устройствами подкачки, например, ленты, в данный момент не поддерживаются.
Note: Используйте команду dumpon(8) для указания ядру места, где нужно сохранять аварийные дампы. После настройки по команде swapon(8) раздела подкачки должна быть вызвана программа dumpon. Обычно это выполняется заданием переменной dumpdev в файле rc.conf(5). Если задана эта переменная, то после сбоя при первой многопользовательской перезагрузке будет автоматически запущена программа savecore(8). Она сохранит аварийный дамп ядра в каталог, заданный в переменной dumpdir файла rc.conf. По умолчанию каталогом для аварийных дампов является /var/crash.
Либо вы можете задать устройство для сброса образа памяти явно через параметр dump в строке config конфигурационного файла вашего ядра. Такой способ использовать не рекомендуется и он должен использоваться, только если вы хотите получать аварийные образы памяти ядра, которое аварийно завершает свою работу при загрузке.
Note: Далее термин gdb означает отладчик gdb, запущенный в ``режиме отладки ядра''. Переход в этот режим достигается запуском gdb с параметром -k. В режиме отладки ядра gdb изменяет своё приглашение на (kgdb).
Tip: Если вы используете FreeBSD версии 3 или более раннюю, вы должны выполнить усечение отладочного ядра командой strip, а не устанавливать большое отладочное ядро:
# cp kernel kernel.debug # strip -g kernelЭтот шаг не так уж и необходим, но рекомендуем. (Во FreeBSD 4 и более поздних релизах этот шаг выполняется автоматически в конце процесса построения ядра make.) Когда ядро усечено, автоматически или при помощи команд выше, вы можете установить его обычным образом, набрав make install.
Заметьте, что в старых версиях FreeBSD (до 3.1, не включая этот релиз), используется ядра в формате a.out, поэтому их таблицы символов должны располагаться постоянно в памяти. С большой таблицей символов в не усеченном отладочном ядре это излишняя трата. Последние релизы FreeBSD используют ядра в формате ELF, где это не является проблемой.
Если вы тестируете новое ядро, скажем, набирая имя нового ядра в приглашении загрузчика, но вам нужно загружать и работать с другим ядром, чтобы снова вернуться к нормальному функционированию, загружайте его только в однопользовательском режиме при помощи флага -s, указываемого при загрузке, а затем выполните такие шаги:
# fsck -p # mount -a -t ufs # so your filesystem for /var/crash is writable # savecore -N /kernel.panicked /var/crash # exit # ...to multi-user
Эта последовательность указывает программе savecore(8) на использование другого ядра для извлечения символических имен. Иначе она будет использовать ядро, работающее в данный момент и, скорее всего, ничего не сделает, потому что аварийный образ памяти и символы ядра будут отличаться.
А теперь, после сброса аварийного дампа, перейдите в каталог /sys/compile/WHATEVER и запустите команду gdb -k. Из программы gdb сделайте вот что:
symbol-file kernel.debug exec-file /var/crash/kernel.0 core-file /var/crash/vmcore.0и вуаля - вы можете отлаживать аварийный дамп, используя исходные тексты ядра точно также, как вы это делаете с любой другой программой.
Вот журнал команд сеанса работы gdb, иллюстрирующий эту процедуру. Длинные строки были разорваны для улучшения читабельности и для удобства строки были пронумерованы. Все остальное является трассировкой ошибки, реально возникнувшей во время работы над драйвером консоли pcvt.
1:Script started on Fri Dec 30 23:15:22 1994 2:# cd /sys/compile/URIAH 3:# gdb -k kernel /var/crash/vmcore.1 4:Reading symbol data from /usr/src/sys/compile/URIAH/kernel ...done. 5:IdlePTD 1f3000 6:panic: because you said to! 7:current pcb at 1e3f70 8:Reading in symbols for ../../i386/i386/machdep.c...done. 9:(kgdb) where 10:#0 boot (arghowto=256) (../../i386/i386/machdep.c line 767) 11:#1 0xf0115159 in panic () 12:#2 0xf01955bd in diediedie () (../../i386/i386/machdep.c line 698) 13:#3 0xf010185e in db_fncall () 14:#4 0xf0101586 in db_command (-266509132, -266509516, -267381073) 15:#5 0xf0101711 in db_command_loop () 16:#6 0xf01040a0 in db_trap () 17:#7 0xf0192976 in kdb_trap (12, 0, -272630436, -266743723) 18:#8 0xf019d2eb in trap_fatal (...) 19:#9 0xf019ce60 in trap_pfault (...) 20:#10 0xf019cb2f in trap (...) 21:#11 0xf01932a1 in exception:calltrap () 22:#12 0xf0191503 in cnopen (...) 23:#13 0xf0132c34 in spec_open () 24:#14 0xf012d014 in vn_open () 25:#15 0xf012a183 in open () 26:#16 0xf019d4eb in syscall (...) 27:(kgdb) up 10 28:Reading in symbols for ../../i386/i386/trap.c...done. 29:#10 0xf019cb2f in trap (frame={tf_es = -260440048, tf_ds = 16, tf_\ 30:edi = 3072, tf_esi = -266445372, tf_ebp = -272630356, tf_isp = -27\ 31:2630396, tf_ebx = -266427884, tf_edx = 12, tf_ecx = -266427884, tf\ 32:_eax = 64772224, tf_trapno = 12, tf_err = -272695296, tf_eip = -26\ 33:6672343, tf_cs = -266469368, tf_eflags = 66066, tf_esp = 3072, tf_\ 34:ss = -266427884}) (../../i386/i386/trap.c line 283) 35:283 (void) trap_pfault(&frame, FALSE); 36:(kgdb) frame frame->tf_ebp frame->tf_eip 37:Reading in symbols for ../../i386/isa/pcvt/pcvt_drv.c...done. 38:#0 0xf01ae729 in pcopen (dev=3072, flag=3, mode=8192, p=(struct p\ 39:roc *) 0xf07c0c00) (../../i386/isa/pcvt/pcvt_drv.c line 403) 40:403 return ((*linesw[tp->t_line].l_open)(dev, tp)); 41:(kgdb) list 42:398 43:399 tp->t_state |= TS_CARR_ON; 44:400 tp->t_cflag |= CLOCAL; /* cannot be a modem (:-) */ 45:401 46:402 #if PCVT_NETBSD || (PCVT_FREEBSD >= 200) 47:403 return ((*linesw[tp->t_line].l_open)(dev, tp)); 48:404 #else 49:405 return ((*linesw[tp->t_line].l_open)(dev, tp, flag)); 50:406 #endif /* PCVT_NETBSD || (PCVT_FREEBSD >= 200) */ 51:407 } 52:(kgdb) print tp 53:Reading in symbols for ../../i386/i386/cons.c...done. 54:$1 = (struct tty *) 0x1bae 55:(kgdb) print tp->t_line 56:$2 = 1767990816 57:(kgdb) up 58:#1 0xf0191503 in cnopen (dev=0x00000000, flag=3, mode=8192, p=(st\ 59:ruct proc *) 0xf07c0c00) (../../i386/i386/cons.c line 126) 60: return ((*cdevsw[major(dev)].d_open)(dev, flag, mode, p)); 61:(kgdb) up 62:#2 0xf0132c34 in spec_open () 63:(kgdb) up 64:#3 0xf012d014 in vn_open () 65:(kgdb) up 66:#4 0xf012a183 in open () 67:(kgdb) up 68:#5 0xf019d4eb in syscall (frame={tf_es = 39, tf_ds = 39, tf_edi =\ 69: 2158592, tf_esi = 0, tf_ebp = -272638436, tf_isp = -272629788, tf\ 70:_ebx = 7086, tf_edx = 1, tf_ecx = 0, tf_eax = 5, tf_trapno = 582, \ 71:tf_err = 582, tf_eip = 75749, tf_cs = 31, tf_eflags = 582, tf_esp \ 72:= -272638456, tf_ss = 39}) (../../i386/i386/trap.c line 673) 73:673 error = (*callp->sy_call)(p, args, rval); 74:(kgdb) up 75:Initial frame selected; you cannot go up. 76:(kgdb) quit 77:# exit 78:exit 79: 80:Script done on Fri Dec 30 23:18:04 1994
Комментарии к вышеприведенному журналу:
Это дамп, взятый при помощи DDB (смотри ниже), поэтому комментарий к аварийному останову имеет именно вид ``because you said to!'' и трассировка стека глубока; однако изначальной причиной перехода в DDB была аварийная остановка при возникновению ошибки страницы памяти.
Это местонахождение функции trap()
в трассировке
стека.
Принудительное использование новой границы стека; теперь это не нужно. Предполагается, что границы стека указывают на правильное расположение, даже в случае аварийного останова. Глядя на строку исходного кода 403, можно сказать, что весьма вероятно, что либо виноват доступ по указателю ``tp'', либо был выход за границы массива.
Похоже, что виноват указатель, но он является допустимым адресом.
Однако, очевидно, что он указывает на мусор, так что мы нашли нашу ошибку! (Для тех, кто не знаком с этой частью кода: tp->t_line служит для хранения режима канала консольного устройства, и это должно быть достаточно маленькое целое число.)
Возможно также и исследование аварийного дампа ядра при помощи такого графического отладчика, как ddd (вам потребуется установить порт devel/ddd, чтобы использовать отладчик ddd). Добавьте флаг -k к командной строке ddd, которую вы обычно используете для его вызова. Например;
# ddd -k /var/crash/kernel.0 /var/crash/vmcore.0
После этого у вас должно получиться исследование аварийного дампа при помощи графического интерфейса ddd.
Что делать, если ядро аварийно завершает работу, хотя этого вы не хотели и поэтому командой config -g его не компилировали? Здесь не всё ещё потеряно. Не паникуйте!
Конечно, вам нужно включить создание аварийных дампов. Смотрите выше, что вы должны для этого сделать.
Перейдите в каталог конфигурации ядра (/usr/src/sys/arch/conf) и отредактируйте ваш конфигурационный файл. Раскомментируйте (или добавьте, если она не существует) такую строку:
makeoptions DEBUG=-g #Build kernel with gdb(1) debug symbols
Перестройте ядро. Из-за изменения метки времени в Makefile будут перестроены и некоторые другие объектные файлы, например, trap.o. К некоторому счастью, добавление опции -g не изменит все и вся в генерируемом коде, так что в конце концов вы получите новое ядро с тем же кодом, что и сбоящее ядро, за исключением наличия отладочной информации. По крайней мере, вы можете сравнить старый и новый размеры ядер командой size(1). Если они не совпадают, то вам придется отказаться от вашей затеи.
Исследуйте дамп так, как это описано выше. Отладочной информации может не хватать в некоторых местах, как это можно видеть в трассировке стека примера выше, когда некоторые функции выводятся без номеров строк и списка аргументов. Если вам нужно больше отладочной информации, удалите соответствующие объектные файлы, снова перекомпилируйте ядро и повторите сеанс работы gdb -k, пока не получите достаточно подробную информацию.
Не гарантируется, что всё это будет работать, однако в большинстве случаев всё работает прекрасно.
Хотя gdb -k является отладчиком не реального времени с высокоуровневым пользовательским интерфейсом, есть несколько вещей, которые он сделать не сможет. Самыми важными из них являются точки останова и пошаговое выполнение кода ядра.
Если вам нужно выполнять низкоуровневую отладку вашего ядра, то на этот случай имеется отладчик реального времени, который называется DDB. Он позволяет устанавливать точки останова, выполнять функции ядра по шагам, исследовать и изменять переменные ядра и прочее. Однако он не может использовать исходные тексты ядра и имеет доступ только к глобальным и статическим символам, а не ко всей отладочной информации, как в gdb.
Чтобы отконфигурировать ваше ядро для включения DDB, добавьте строчку с параметром
options DDBв ваш конфигурационный файл, и перестройте ядро. (Обратитесь к Руководству по FreeBSD для выяснения подробностей о конфигурации ядра FreeBSD).
Note: Если у вас устаревшая версия загрузочных блоков, то отладочная информация может оказаться не загруженной. Обновите блоки загрузки; самые новые загружают символы для DDB автоматически.
После того, как ядро с DDB запущено, есть несколько способов войти в DDB. Первый, и самый простой, способ заключается в наборе флага загрузки -d прямо в приглашении загрузчика. Ядро будет запущено в режиме отладки и войдет в DDB до выполнения процедуры распознавания каких бы то ни было устройств. Поэтому вы можете выполнить отладку даже функций распознавания/присоединения устройств.
Вторым способом является переход в режим отладчика сразу после загрузки системы. Есть два простых способа этого добиться. Если вы хотите перейти в отладчик из командной строки, просто наберите команду:
# sysctl debug.enter_debugger=ddb
Либо, если вы работаете за системной консолью, можете воспользоваться определенной комбинацией клавиш. По умолчанию для перехода в отладчик используется комбинация Ctrl+Alt+ESC. Для драйвера syscons эта последовательность может быть изменена, и в некоторых распространяемых раскладках это сделано, так что обязательно выясните правильную комбинацию. Для последовательных консолей имеется параметр, позволяющий использовать последовательность BREAK на канале консоли для входа в DDB (options BREAK_TO_DEBUGGER в конфигурационном файле ядра). По умолчанию этого не делается, так как существует множество последовательных адаптеров, которые ошибочно генерируют последовательность BREAK, к примеру, при отключении кабеля.
Третий способ заключается во входе в DDB при возникновении любой аварийной ситуации, если ядро его использует. По этой причине не очень умно конфигурировать ядро с DDB для машины, которая работает без присмотра.
Команды DDB примерно повторяют некоторые команды gdb. Первым делом вам, наверное, нужно задать точку останова:
b function-name b address
Значения по умолчанию воспринимаются в шестнадцатеричном виде, но чтобы отличать их от имен символов; шестнадцатеричные числа, начинающиеся с букв a-f, должны предваряться символами 0x (это опционально для других чисел). Разрешены простые выражения, например: function-name + 0x103.
Чтобы продолжить работу прерванного ядра, просто наберите:
c
Чтобы получить трассировку стека, задайте:
trace
Note: Заметьте, что при входе в DDB по специальной комбинации, ядро в данный момент обслуживает прерывание, так что трассировка стека может не дать вам много информации.
Если вы хотите убрать точку останова, введите
del del address-expression
В первом варианте команда будет исполнена сразу же по достижении точки останова, а текущая точка останова будет удалена. Во второй форме можно удалить любую точку останова, однако вам нужно будет указать ее точный адрес; его можно получить из:
show b
Чтобы выполнить один шаг ядра, попробуйте:
s
При этом будет осуществляться пошаговое выполнение функций, однако вы можете трассировать их с помощью DDB, пока не будет достигнуто соответствие возвращаемому значению:
n
Note: Это отличается от команды next отладчика gdb; это похоже на команду gdb finish.
Чтобы выводить значения в памяти, используйте, (к примеру):
x/wx 0xf0133fe0,40 x/hd db_symtab_space x/bc termbuf,10 x/s stringbufдля доступа к данным типа слово/полуслово/байт и вывода в шестнадцатеричном/десятичном/символьном виде. Число после запятой означает счетчик объектов. Чтобы вывести следующие 0x10 объектов, просто укажите:
x ,10
Подобным же образом используйте
x/ia foofunc,10для дизассемблирования и вывода первых 0x10 инструкций функции
foofunc
вместе с их адресом относительно начала foofunc
.Чтобы изменить значения в памяти, используйте команду write:
w/b termbuf 0xa 0xb 0 w/w 0xf0010030 0 0
Модификатор команды (b/h/w) указывает на размер записываемых данных, первое следующее за ним выражение является адресом для записи, а оставшаяся часть интерпретируется как данные для записи в доступные области памяти.
Если вам нужно узнать текущее содержимое регистров, используйте:
show reg
Альтернативно вы можете вывести содержимое одного регистра по команде, скажем,
p $eaxи изменить его по:
set $eax new-value
Если вам нужно вызвать некоторую функцию ядра из DDB, просто укажите:
call func(arg1, arg2, ...)
Будет выведено возвращаемое значение.
Для вывода суммарной статистики по всем работающим процессам в стиле команды ps(1) воспользуйтесь такой командой:
ps
Теперь вы узнали, почему ядро работает с ошибками и хотите выполнить перезагрузку. Запомните, что в зависимости от влияния предыдущих ошибок, не все части ядра могут работать так, как ожидается. Выполните одно из следующих действий для закрытия и перезагрузки вашей системы:
panic
Это приведет к созданию дампа ядра и перезагрузке, так что позже вы можете проанализировать дамп на более высоком уровне при помощи gdb. Как правило, эта команда должна следовать за другой командой continue.
call boot(0)
Это может оказаться хорошим способом для корректного закрытия работающей системы,
sync()
для всех дисков и напоследок перезагрузка. Пока
интерфейсы диска и файловой системы в ядре не повреждены, это может быть самым правильным
способом закрытия системы.
call cpu_reset()
Это последнее средство при аварии и практически то же самое, что нажатие Большой Красной Кнопки.
Если вам нужен краткий справочник по командам, просто наберите:
help
Однако настоятельно рекомендуем отпечатать копию страницы справочника по ddb(4) при подготовке к сеансу отладки. Помните, что трудно читать онлайновое руководство при пошаговом выполнении ядра.
Эта возможность поддерживается во FreeBSD начиная с версии 2.2, и она на самом деле очень удобна.
В GDB уже давно имеется поддержка удаленной отладки. Это делается при помощи весьма простого протокола по последовательному каналу. В отличие от других методов, описанных выше, для этого вам требуется наличие двух машин. Одна из них является хостом, предоставляющим ресурсы для отладки, включая все исходные тексты и копию ядра со всеми символами в нем, а другая является целевой машиной, на которой запущена та же копия того же ядра (но без отладочной информации).
Вы должны настроить исследуемое ядро при помощи команды config -g, включить DDB в конфигурацию и откомпилировать его обычным образом. Это даст большой бинарный файл из-за отладочной информации. Скопируйте это ядро на целевую машину, усеките отладочную информацию командой strip -x и загрузите это ядро с использованием параметра загрузки -d. Подключите последовательный канал целевой машины, имеющий установленные флаги "flags 080" на соответствующем устройстве sio к любому последовательному каналу отладочного хоста. А теперь на отладочной машине перейдите в каталог компиляции целевого ядра и запустите gdb:
% gdb -k kernel GDB is free software and you are welcome to distribute copies of it under certain conditions; type "show copying" to see the conditions. There is absolutely no warranty for GDB; type "show warranty" for details. GDB 4.16 (i386-unknown-freebsd), Copyright 1996 Free Software Foundation, Inc... (kgdb)
Проинициализируйте сеанс удаленной отладки (предполагается, что используется первый последовательный порт) такой командой:
(kgdb) target remote /dev/cuaa0
Теперь на целевом хосте (тот, который перешел в DDB даже до начала процесса обнаружения устройств) наберите:
Debugger("Boot flags requested debugger") Stopped at Debugger+0x35: movb $0, edata+0x51bc db> gdb
DDB ответит следующим:
Next trap will enter GDB remote protocol mode
Каждый раз, когда вы будете набирать gdb, режим будет меняться между удаленным GDB и локальным DDB. Чтобы немедленно вызвать следующее прерывание, просто наберите s (step). Ваш хостирующий GDB получит управление над целевым ядром:
Remote debugging using /dev/cuaa0 Debugger (msg=0xf01b0383 "Boot flags requested debugger") at ../../i386/i386/db_interface.c:257 (kgdb)
Вы можете работать в этом сеансе точно также, как и в любом другом сеансе GDB, включая полный доступ к исходным текстам, запуск его в режиме gud-mode внутри окна Emacs (что даёт вам автоматический вывод исходного кода в другом окне Emacs) и тому подобное.
При отладке аварийного останова системы, которое произошло в модуле, или при использовании GDB в режиме удаленного доступа к машине, использующей динамические модули, вам нужно указать GDB, как получить информацию о символах в этих модулях.
Первым делом вам нужно построить модуль (или модули) с включением отладочной информации:
# cd /sys/modules/linux # make clean; make COPTS=-g
Если вы используете GDB в режиме удаленного доступа, то для определения того, куда был загружен модуль, можете запустить команду kldstat на целевой машине:
# kldstat Id Refs Address Size Name 1 4 0xc0100000 1c1678 kernel 2 1 0xc0a9e000 6000 linprocfs.ko 3 1 0xc0ad7000 2000 warp_saver.ko 4 1 0xc0adc000 11000 linux.ko
Если вы отлаживаете аварийный дамп, вам потребуется просмотреть список linker_files начиная с linker_files->tqh_first и следовать указателям link.tqe_next до тех пор, пока не найдете запись с тем filename, который вы ищете. Элемент address этой записи является адресом загрузки модуля.
Затем вам нужно определить смещение текстового сегмента модуля:
# objdump --section-headers /sys/modules/linux/linux.ko | grep text 3 .rel.text 000016e0 000038e0 000038e0 000038e0 2**2 10 .text 00007f34 000062d0 000062d0 000062d0 2**2
То, что вы ищете, является секцией .text, в примере выше это секция 10. Четвертое числовое поле (всего шестое по счёту) является смещением текстовой секции внутри файла. Добавьте это смещение к адресу загрузки, чтобы получить адрес, на который был перемещён код модуля. В нашем примере мы получим 0xc0adc000 + 0x62d0 = c0ae22d0. Воспользуйтесь командой add-symbol-file в GDB для указания отладчику на модуль:
(kgdb) add-symbol-file /sys/modules/linux/linux.ko 0xc0ae22d0 add symbol table from file "/sys/modules/linux/linux.ko" at text_addr = 0xc0ae22d0? (y or n) y Reading symbols from /sys/modules/linux/linux.ko...done. (kgdb)
Теперь вы должны получить доступ ко всем символам в модуле.
Так как для работы DDB вам требуется драйвер консоли, то в случае неисправностей самого драйвера консоли все становится гораздо сложнее. Вы можете вспомнить об использовании последовательной консоли (либо с исправленными загрузочными блоками, либо при указании флага -h в приглашении Boot:) и подключить обычный терминал к первому последовательному порту. DDB работает с любым отконфигурированным драйвером консоли, в том числе с последовательной консолью.
Предоставил Jörg Wunsch <joerg@FreeBSD.org>
Note: Перед тем, как читать этот раздел, вы должны усвоить материал раздела Руководства о ``конфигурации ядра''.
Использование параметров ядра в основном описано в разделе о ``конфигурации ядра'' в Руководстве FreeBSD. Там же имеется описание ``устаревших'' и параметров ``в новом стиле''. Конечной целью является постепенный перевод всех поддерживаемых параметров ядра к новому стилю, так чтобы для тех, кто корректно выполняют команду make depend в каталоге компиляции ядра после запуска config(8), процесс построения автоматически принимал модифицированные параметры и перекомпилировал только те файлы, которые необходимы. Удаление старого каталога компиляции при каждом перезапуске config(8), как это еще происходит сейчас, затем может быть убрано.
В своей основе параметр ядра является не более чем определение макроса препроцессора C для процесса компиляции ядра. Чтобы сделать построение полностью настраиваемым через опции процессом, соответствующая часть исходных текстов ядра (или файла .h ядра) должна быть написана с упором на концепцию параметров, то есть чтобы значения, используемые по умолчанию, могли быть переопределены параметрами конфигурации. Это обычно делается примерно так:
#ifndef THIS_OPTION #define THIS_OPTION (некоторое значение по умолчанию) #endif /* THIS_OPTION */
Этим способом администратор, задающий другое значение для параметра в своем конфигурационном файле, избегает использования значения по умолчанию и заменяет его новым значением. Более того, новое значение будет подставлено в исходный код во время работы препроцессора, так что это должно быть правильное выражение языка C в контексте использования значения по умолчанию.
Также возможно создание параметров, которые не принимают определенного значения, а просто включают или выключают некоторую часть кода, обрамляя его в
#ifdef THAT_OPTION [здесь ваш код] #endif
Простое упоминание THAT_OPTION в конфигурационном файле (со значением или без него) приведет к включению соответствующего кода.
Те, кто знаком с языком C, могут сказать, что все может считаться как ``config option'', там где есть по крайней мере одна строчка #ifdef... Однако вряд ли многие будут писать
options notyet,notdef
в своих конфигурационных файлах и потом удивляться, почему компиляция ядра не проходит. :-)
Более точно, использование уникальных имен для параметров делает очень трудным отслеживание их использования в дереве исходных текстов ядра. В использовании схемы параметров в новом стиле имеется рациональное зерно, когда каждый параметр помещается в отдельный файл .h в каталоге компиляции ядра, который по соглашению называется opt_foo.h. Таким образом, могут быть применены обычные зависимости в Makefile, и утилита make может определить, что нужно перекомпилировать при изменении определенного параметра.
Параметры при использовании механизма в старом стиле имеют одно преимущество для локальных изменений или для экспериментальных параметров, которые имеют короткий срок жизни: так как весьма легко добавить новую строку #ifdef к исходному коду ядра, то это уже превращается в параметр конфигурации ядра. В таком случае администратор, использующий параметры таким образом, несет ответственность за знание влияния этого параметра (и может быть, за принудительную перекомпиляцию вручную частей ядра). Как только перевод всех поддерживаемых опций будет сделан, программа config(8) будет выдавать предупреждение о не поддерживаемой опции, появившейся в конфигурационном файле, однако она будет включать ее в файл Makefile ядра.
Во-первых, отредактируйте файл sys/conf/options (или sys/<arch>/conf/options.<arch>, например sys/i386/conf/options.i386), и выберите файл opt_foo.h, в котором лучше всего поместить вашу новую опцию.
Если имеется что-то, уже похожее на предназначение новой опции, выберите это. Например, опции, изменяющие общее поведение подсистемы SCSI, могут быть помещены в opt_scsi.h. По умолчанию простое упоминание опции в соответствующем файле опций, скажем, FOO, приводит к тому, что ее значение помещается в соответствующий файл opt_foo.h. Это может быть переопределено в правой части правила указанием другого имени файла.
Если файла opt_foo.h для предполагаемой новой опции еще нет, придумайте новое имя. Сделайте его значимым и прокомментируйте новый раздел в файле options[.<arch>]. Утилита config(8) автоматически воспримет изменения и создаст этот файл при следующем своем запуске. Большинство опций должно оказаться в заголовочном файле..
Размещение слишком большого количества опций в одном файле opt_foo.h приведет к перестроению слишком большого количества файлов ядра при изменении одной из опций в конфигурационном файле.
Наконец, определите, какие файлы ядра зависят от новой опции. Если только вы не только что придумали вашу опцию и она еще нигде не упоминается, то команда
% find /usr/src/sys -type f | xargs fgrep NEW_OPTIONвам поможет ее найти. Сделайте это и отредактируйте все эти файлы, а также добавьте
#include "opt_foo.h"вверху до всех строк #include <xxx.h>. Эта последовательность очень важна, так как опции могут переопределять значения по умолчанию из обычных включаемых файлов, если эти значения по умолчанию даются в форме
#ifndef NEW_OPTION #define NEW_OPTION (что-то) #endifв обычном заголовке.
Добавление опции, которая переопределяет что-то в системном заголовочном файле (то есть файле, находящемся в каталоге /usr/include/sys/), практически всегда ошибочно. opt_foo.h не может быть включен в те файлы, потому что это изменит заголовки более серьезно, но если он не включен, то в местах его включения может получиться рассогласованность значений для этой опции. Да, такие прецеденты имеют место и сейчас, но это их не оправдывает.
[1] Dave A Patterson and John L Hennessy, 1998, 1-55860-428-6, Morgan Kaufmann Publishers, Inc., Computer Organization and Design: The Hardware / Software Interface, 1-2.
[2] W. Richard Stevens, 1993, 0-201-56317-7, Addison Wesley Longman, Inc., Advanced Programming in the Unix Environment, 1-2.
[3] Marshall Kirk McKusick, Keith Bostic, Michael J Karels, and John S Quarterman, 1996, 0-201-54979-4, Addison-Wesley Publishing Company, Inc., The Design and Implementation of the 4.4 BSD Operating System, 1-2.
[1] |
Если вы запускаете ее из оболочки, то можете получить аварийный дамп памяти. |
[2] |
Строго говоря, cc на этом шаге преобразует исходный код в собственный машинно-независимый p-код, а не в язык ассемблера. |
[3] |
Если вы не знаете, то двоичная сортировка эффективна, а пузырьковая неэффективна. |
[4] |
Причины этого следует искать в исторических летописях. |
[5] |
Заметьте, что мы не использовали флаг -o для указания имени выполнимого файла, поэтому получим выполнимый файл с именем a.out. Получение отладочной версии с именем foobar оставляется читателю в качестве упражнения! |
[6] |
Они не используют вариант MAKEFILE, так как заглавные буквы зачастую используются для файлов с документацией, как README. |
[7] |
В коллекции портов имеется несколько мощных свободно распространяемых IDE, таких, как, например, KDevelop. |
[8] |
Многие пользователи Emacs указывают emacsclient в качестве значения своей переменной окружения EDITOR, поэтому это происходит всякий раз, когда им нужно отредактировать файл. |
Этот, и другие документы, могут быть скачаны с ftp://ftp.FreeBSD.org/pub/FreeBSD/doc/.
По вопросам связанными с FreeBSD, прочитайте документацию прежде чем писать в <questions@FreeBSD.org>.
По вопросам связанным с этой документацией, пишите <doc@FreeBSD.org>.
По вопросам связанным с русским переводом документации, пишите <frdp@FreeBSD.org.ua>.
Закладки на сайте Проследить за страницей |
Created 1996-2024 by Maxim Chirkov Добавить, Поддержать, Вебмастеру |