Показаны сообщения с ярлыком gcc. Показать все сообщения
Показаны сообщения с ярлыком gcc. Показать все сообщения

3/26/2012

Визуализация разработки компилятора GCC в 1989-2012

Ребята с помощью утилиты Gource визуализировали процесс разработки компилятора gcc. 
На мой взгляд получилось и наглядно и красиво.

11/03/2011

Утечки памяти и ошибки доступа.

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

В данном посте речь пойдет о mudflap.
mudflap - это технология проверки указателей, основанная не на бинарной инструментации, как valgrind или dr. Memory, а на инструментации времени компиляции. Если в кратце, во время компиляции программы в исходный код делаются вставки, которые обспечивают различные проверки указателей,к которым осуществляется доступ.

Теперь разберем работу mudflap на конкретном примере.

Для того, чтобы можно было использовать mudflap, нужа поддержка этой технологии в компиляторе и собственно сама библиотека, выполняющая проверки: gcc должен  быть скомпилирован с опцией --enable-libmudflap, и в системе должна присутствовать библиотека libmudflap.so.

В качестве примера рассмотрим программу, котороя пытается записать в уже освобожденную память:
#include <malloc.h>

int main (void)
{
  const int chunk_size = 18;
  const int count  = 10000;
  int j = 0;
  void * ptrs [count];

  /* выделяем 10 000 кусочков памяти по 18 байт.*/
  for (j = 0; j < count; j++) {
    ptrs [j] = malloc (chunk_size);
  }

  /* освобождаем все, кроме первого и последнего */

  for (j = 1; j < count - 2; j++)
    if (ptrs [j])
      free (ptrs [j]);

  /* записываем в освобожденную память*/
  *(char*)ptrs [10] = 10;

  /* просим glibc сделать "дефрагментацию" выделенной памяти */
  malloc_trim (1);

  return 0;
}


Результатом работы программы будет Segmentation Fault.
Это происходит потому что при попытке записи в освобожденную память (на самом деле она не освобождается, а помечается как неиспользуемая) программа портит служебную информацию glibc, а именно связный список, который используется в glibc для управления памятью.

Чтобы отловить ошибку в программе нужно пересобрать программы с mudflap инструментацией:

gcc -fmudflapth -lmudflap -Wall -Wextra -g -ggdb3 buggy.c
Запускаем:

./a.out


В результате работы программа выдает следующее:

*******

mudflap violation 1 (check/write): time=1320343428.778161 ptr=0xe85c10 size=1

pc=0x7fcc8d0dc4f8 location=`buggy.c:22:21 (main)'

      /usr/lib/gcc/x86_64-pc-linux-gnu/4.5.3/libmudflap.so.0(__mf_check+0x38) [0x7fcc8d0dc4f8]

      ./a.out(main+0x301) [0x400da5]

      /lib64/libc.so.6(__libc_start_main+0xec) [0x7fcc8cd63e8c]

number of nearby objects: 0


Ясно видно где в исходном коде возникает проблема.

Для проверки, запустим программу под valgrind-ом, который выдает следующую информацию:
==26575== 

==26575== Invalid write of size 1

==26575==    at 0x4006E0: main (buggy.c:22)

==26575==  Address 0x51bc400 is 0 bytes inside a block of size 18 free'd

==26575==    at 0x4C27F6C: free (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so)

==26575==    by 0x4006C8: main (buggy.c:19)


valgrind как и mudflap, точно показал место ошибки в программе:
buggy.c: 22

Резюмирая можно обозначить плюсы и минусы mudflap:
"+" :
1. Более низкий overhead по сравнению с valgrind.
2. Лучшая переносимость: доступен для платформ ARM и MIPS.
3. Из-за того, что инструментация происходи во время выполнения, может находить более сложные ошибки и более точно показывать их в коде.

"-":
1. Поддержка кода, написанного только на C/C++.
2. Привязка к компилятору GCC.
3. Необходимость перекомпиляции.

Таким образом, mudflap вполне удобный тул, который позволяет искать утечки в памяти не используя valgrind, что особенно полезно для встраиваемых систем (ARM и MIPS), где на исполнение кода существенно влияют накладные расходы на инструментацию.

Больше информации о mudflap можно посмотреть тут : http://gcc.gnu.org/wiki/Mudflap_Pointer_Debugging

4/04/2010

gnat-gcc-4.4.3 в Gentoo Linux.

Кому интересно, я добавил ebuild для компилятора ады gnat-gcc-4.4.3 в gentoo Linux.
Теперь буду его тестировать.
Также планирую обновить пакеты xmlada и добавить пакет gprbuild.

9/08/2009

Предупреждения GCC

Этот пост небольшое дополнение к посту Белого Рыцаря, в котором речь идет об опциях, включающих те или иные предупреждения компилятора gcc.

Для того чтобы включить "все предупреждения, gcc нужно передать опции -Wall и -Wextra, однако, вопреки расхожему мнению, эти опции включают далеко не все предупреждения, которые может выдавать gcc.

А вот для того, чтобы посмотреть, какие предупреждения будут выключены или включены, вследствие использования той или иной опции, нужно выполнить команду (для -Wall):

gcc -c -Q -Wall --help=warnings

которая выдаст следующее:
Следующие ключи контролируют предупреждения компилятора:

-Wabi [выключено]


-Waddress [включено]


-Waggregate-return [выключено]


-Waliasing


-Walign-commons


-Wall


-Wampersand


-Warray-bounds [включено]


Список получится длинный и зависит от версии компилятора.
Эта же команда работает и для ключей оптимизации:
gcc -c -Q -O3 --help=optimizers

На выходе тоже получится длинный список включенных и отключенных возможностей оптимизации.

А если выполнить команду:

gcc -c -Wall --help=warnings

То можно посмотреть, что означает каждая из опций, контролирующих предупреждения:

Следующие ключи контролируют предупреждения компилятора:

-Wabi Предупреждать о различиях по сравнению с компиляцией при помощи компилятора, совместимого с ABI


-Waddress Warn about suspicious uses of memory addresses


-Waggregate-return Предупреждать о возвращении функциями
структур, объединений, массивов


-Waliasing Warn about possible aliasing of dummy arguments


-Walign-commons Warn about alignment of COMMON blocks


-Wall Включить все основные виды предупреждений.

Вот так.

2/02/2009

Перечислимые типы и строгая типизация.

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

Вот пример объявления перечислимого типа в языке Си:

typedef enum my_enum {
A, B, C
} my_enum_t;

Он состоит из трех элементов A,B,C.
Каждый элемент перечисления представляется с помощью типа int.
Фактически эта запись равносильна следующей (на большинстве платформ):

typedef int my_enum_t;
const my_enum_t A = 0;
const my_enum_t B = 1;
const my_enum_t C = 2;

Согласно стандарту ANSI C элементы перечисления по умолчанию нумеруются с 0.
Если какому-либо из элементов присвоить какое нибудь значение, то, все последующие элементы будут увеличиваться на 1, относительно этого значения.

Например:

typedef enum my_enum {
A = 500, B, C
} my_enum_t;

В таком перечислении значение A будет 500, B - 501, C - 502 и т.д.

К чему все это, спросите вы?
Пытаясь портировать одну небольшую и очень известную библиотеку,
на embeddedLinux для Архитектуры ARM9, я наткнулся на следующую проблему:
один и тот же код, скомпилированный компилятором одной и той же версии (gcc-4.1.1), с одними и теми же флагами отказывался работать.

Вот пример того кода, который не работал на ARM9:

1 #include <stdio.h>
2
3 typedef enum enum_A {
4 A1 = 0, A2, A3, A4
5 } enum_A_t;
6
7 typedef enum enum_B {
8 B1 = 1000000, B2, B3, B4, B5
9 } enum_B_t;
10
11 int main (int argc, char *argv[])
12 {
13 enum_A_t A;
14 enum_B_t B = B1;
15
16 A = B3;
17
18 printf ("A=%d, B=%d\n", sizeof (enum_A_t), sizeof (enum_B_t));
19 if (A > B)
20 printf ("Hello\n");
21 }

Ошибка состоит в том, что на x86 на консоль выводится слово HELLO, а на arm9 нет.
Как оказалось такую ситуацию можно повторить и на x86 архитектуре.

Где же ошибка:
На мой взгляд строки 16 и 20 - это просто надругательство над всем смыслом перечислимого типа. По моему мнению такое делать нельзя. Но компилятор gcc это прекрасно проглатывает, абсолютно никак не оповещя пользователя о возможных последствиях.

Ведь enum_A_t и enum_B_t это два абсолютно разных типа, с абсолютно разным диапазоном значений.
Такой код работает только потому, что элементы перечисления по сути являются элементами типа int.

Но что будет если gcc начнет оптимизировать?
Как оказалось, для экономии памяти на embedded платформе, в компиляторе была по-умолчанию включена опция -fshort-enums. При включении этой опции компилятор начинает оптимизировать перечисления, для того чтобы они занимали меньше памяти.
Как он это делает?

Просто берет и начинает использовать для хранения элементов перечисления не огромный 4-х байтовый целочисленный тип,а скажем однобайтовый char, или двухбайтовый short. Вот и в нашем случае, компилятор соптимизировал код так, что для хранения элементов типа enum_A_t использовался char, а для хранения элементов типа enum_B_t тип int.
Естественно сравнение в строке 19 всегда будет ложным.

Вывод.
Будьте внимательны при объявлении и использовании перечислений.
Такие ошибки трудноуловимы, и можно просидеть очень много времени в отладчике, пытаясь понять что же все таки не так.