Выбрать главу

Имя узла системы устанавливается с помощью системного вызова sethostname()[7], и имя домена NIS (YP)[8] — посредством системного вызова setdomainname().

#include <unistd.h>

int sethostname(const char * name, size_t len);

int setdomainname(const char * name, size_t len);

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

6.3. Совместимость

Приложения, которые скомпилированы с заголовочными файлами из библиотеки glibc и привязанные к одной из ее версий, будут работать и с более поздними версиями библиотеки. Эта обратная совместимость обычно означает, что программисту не придется пересобирать свои приложения только из-за того, что выпущена новая версия glibc.

Существуют практические ограничения для обратной совместимости. Во-первых, смешивание объектов из разных версий glibc в одном исполняемом файле может иногда работать, но специально для такой совместимости ничего не предпринимается. Следует отметить, что это касается динамически загружаемых, а также статически связанных объектов. Во-вторых, приложение должно использовать только стандартные возможности glibc. Приложение, которое зависит от побочных эффектов ошибок или основано на неопределенном поведении одной из версий glibc, может не работать с более поздними версиями этой библиотеки. Приложение, компонуемое с приватными символами glibc (обычно они имеют префикс а_), также вряд ли будет работать с более новыми версиями glibc.

Обратная совместимость поддерживается тогда, когда задействованы символы, разработанные специально для соответствия стандартам версий. Когда разработчики glibc хотят внести несовместимое изменение в glibc, они сохраняют оригинальную реализацию или пишут совместимую реализацию данного интерфейса и помечают его более старым номером версии glibc. Затем они реализуют новый интерфейс (который может отличаться по семантике, сигнатуре или и тем, и другим) и помечают его новым номером версии glibc. Приложения, построенные на базе старой версии glibc, используют старый интерфейс, а приложения, построенные на основе новой версии — новый интерфейс.

Большинство других библиотек поддерживают совместимость, включая номер версии в имя библиотеки и позволяя множеству разных версий быть установленными одновременно. Например, инструментальные наборы GTK+ 1.2 и GTK+ 2.0 могут быть одновременно установлены в одной системе, каждый со своим собственным набором заголовочных и библиотечных файлов, путем встраивания в путь к заголовочным файлам и файлам библиотек имени версии.

Раздел стандарта по наименованию разделяемых библиотек в Linux включает старший номер версии для возможности установки в системе множества версий библиотеки. Это используется не очень часто, поскольку в одной системе невозможно скомпоновать новые приложения с множеством версий библиотеки; это просто обеспечивает поддержку лишь обратной совместимости для существующих приложений, построенных на более старых системах. На практике разработчикам требуется собирать приложения со многими версиями одной и той же библиотеки, поэтому большинство основных библиотек содержат в своем названии и номер версии.

Глава 7

Средства отладки использования памяти

Несмотря на то что С бесспорно является стандартным языком программирования в системах Linux, он имеет ряд особенностей, не дающих программистам возможности писать код, не содержащий тонких ошибок, которые впоследствии очень сложно отладить. Утечки памяти (когда память, выделенная с помощью malloc(), никогда не освобождается посредством free()) и переполнение буфера (например, запись за пределы массива) — наиболее распространенные и трудные для обнаружения программные ошибки. Недогрузка буфера (вроде записи перед началом массива) — менее распространенное, но обычно еще более тяжелое для отслеживания явление. В этой главе представлены несколько средств отладки, которые могут значительно упростить обнаружение и изоляцию упомянутых проблем.

7.1. Код, содержащий ошибки

 1: / * broken.с* /

 2:

 3: #include <stdlib.h>

 4: #include <stdio.h>

 5: #include <string.h>

 6:

 7: char global[5];

 8:

 9: int broken(void){

10:  char *dyn;

11:  char local[5];

12:

13:  /* Для начала немного перезаписать буфер */

14:  dyn = malloc(5);

15:  strcpy(dyn, "12345");

16:  printf ("1: %s\n", dyn);

17:  free(dyn);

18:

19:  /* Теперь перезаписать буфер изрядно */

20:  dyn = malloc(5);

21:  strcpy(dyn, "12345678");

22:  printf("2: %s\n", dyn);

23:

24:  /* Пройти перед началом выделенного с помощью malloc локального буфера */

25:  * (dyn-1) ='\0';

26:  printf ("3: %s\n", dyn);

27:  /* обратите внимание, что указатель не освобожден! */

28:

29:  /* Теперь двинуться после переменной local */

30:  strcpy(local, "12345");

31:  printf ("4: %s\n", local);

32:  local[-1] = '\0';

33:  printf("5: %s\n", local);

34:

35:  /* Наконец, атаковать пространство данных global */

36:  strcpy(global, "12345");

37:  printf ("6: %s\n", global);

38:

39:  /* И записать поверх пространства перед буфером global */

40:  global[-1] = '\0';

41:  printf("7: %s\n", global);

42:

43:  return 0;

44: }

45:

46: int main (void) {

47:  return broken();

48: }

В этой главе мы рассмотрим проблемы в показанном выше сегменте кода. Этот код разрушает три типа областей памяти: память, выделенную из динамического пула памяти (кучи) с помощью malloc(), локальные переменные размещенные в стеке программы и глобальные переменные, хранящиеся в отдельной области памяти, которая была статически распределена при запуске программы[9]. Для каждого класса памяти эта тестовая программа выполняет запись за пределами зарезервированной области памяти (по одному байту) и также сохраняет байт непосредственно перед зарезервированной областью. К тому же в коде имеется утечка памяти, что позволит продемонстрировать, как с помощью различных средств отследить эти утечки.

Несмотря на то что в представленном коде кроется много проблем, в действительности, он работает нормально. Не означает ли это, что проблемы подобного рода не важны? Ни в коем случае! Переполнение буфера часто приводит к неправильному поведению программы задолго до фактического его переполнения, а утечки памяти в программах, работающих длительное время, приводят к пустой растрате ресурсов компьютера. Более того, переполнение буфера является классическим источником уязвимостей безопасности, как описано в главе 22.

вернуться

7

Несмотря на имя, которое может ввести в заблуждение, этот системный вызов устанавливает имя узла, а не имя Internet-хоста машины.

вернуться

8

Сетевые информационные службы (Network Information Services — NIS) предоставляют для машин в сети механизм совместного использования информации, такой как пользовательские имена и пароли. Ранее они назывались "желтыми страницами" (Yellow Pages — YP). Имя домена NIS — часть этого механизма, который реализуется за пределами ядра системы с тем исключением, что доменное имя хранится в структуре utsname.

вернуться

9

К сожалению, ни одно из средств, описанных в этой главе, не может отследить ошибки в памяти, связанные с глобальными переменными; для этого нужна помощь компилятора. В первом издании этой книги рассматривался инструмент под названием Checker, который представлял собой модифицированную версию компилятора gcc, однако он больше не поддерживается. К официальному компилятору gcc добавлена новая технология под названием mudflap, которая описана в текущем руководстве по gcc.