Поймать странные арифметические ошибки указателя C - PullRequest
7 голосов
/ 02 февраля 2010

Недавно я столкнулся с очень хитрой ошибкой, в которой я забыл разыменовать указатель на строку (массив символов) и, таким образом, иногда перезаписывал один байт в стеке.

Плохо:

char ** str;
(*str) = malloc(10);
...
str[2] = 'a'; //overwrites 3 bytes from the location in which str is stored

Исправлено:

char ** str;
(*str) = malloc(10);
...
(*str)[2] = 'a'; 

GCC не выдавал предупреждений, и эта ошибка привела бы к очень серьезному и реальному эксплойту, поскольку значение, которое оно иногда перезаписывало, содержало размер буфера. Я поймал эту ошибку только потому, что мне повезло, и это вызвало очевидный сбой.

  • Кроме того, чтобы полагаться на удачу и / или никогда не использовать C для чего-либо, какие защитные приемы и приемы кодирования вы используете, чтобы ловить странные ошибки C?

  • Я думаю о переходе на Valgrind's MemCheck , кто-нибудь использовал это? Я подозреваю, что это не поймало бы эту ошибку. Кто-нибудь знает?

  • Существуют ли инструменты для обнаружения разыменования указателей или арифметических ошибок? Это вообще возможно?

UPDATE

Вот запрашиваемый пример кода, он не выдает никаких предупреждений.

#include <stdlib.h>

void test(unsigned char** byteArray){
    (*byteArray) = (unsigned char*)malloc(5);
    byteArray[4] = 0x0;
}

int main(void){
    unsigned char* str;
    test(&str);  
    return 0;
}

Компиляция не вызывает ошибок:

gcc -Wall testBug.c -o testBug

Работа вызывает ошибку сегмента:

./testBug
Segmentation fault

Это версия GCC, которую я использую:

gcc -v

Using built-in specs.
Target: i486-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 4.4.1-4ubuntu9' --with-bugurl=file:///usr/share/doc/gcc-4.4/README.Bugs --enable-languages=c,c++,fortran,objc,obj-c++ --prefix=/usr --enable-shared --enable-multiarch --enable-linker-build-id --with-system-zlib --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.4 --program-suffix=-4.4 --enable-nls --enable-clocale=gnu --enable-libstdcxx-debug --enable-objc-gc --enable-targets=all --disable-werror --with-arch-32=i486 --with-tune=generic --enable-checking=release --build=i486-linux-gnu --host=i486-linux-gnu --target=i486-linux-gnu
Thread model: posix
gcc version 4.4.1 (Ubuntu 4.4.1-4ubuntu9) 

Ответы [ 5 ]

3 голосов
/ 02 февраля 2010

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

char **outStr;
*outStr = malloc(10);
char *str = *outStr;
str[2] = 10;

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

1 голос
/ 02 февраля 2010

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

Помимо обнаружения ошибок в памяти, valgrind также помогает в обнаружении утечек памяти, используемых блоков памяти и т.д.

Даже IBM Rational Purify поможет вам в обнаружении таких ошибок. Хотя мой личный фаворит - Вальгринд.

1 голос
/ 02 февраля 2010

GCC должен дать вам

 warning: assignment makes pointer from integer without a cast

Нет?

1 голос
/ 02 февраля 2010

Я пользуюсь Valgrind, это спасатель!

valgrind --tool=memcheck -v ./yourapp

И MemCheck обнаружит, что у вас неверная запись с `str [2] = 'a'; ´.

0 голосов
/ 02 февраля 2010

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

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

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

...