взломать макет памяти - PullRequest
11 голосов
/ 30 июня 2010

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

#include <stdio.h>
void makeArray();
void printArray();
int main(){
        makeArray();
        printArray();
        return 0;
}
void makeArray(){
    int array[10];
    int i;
    for(i=0;i<10;i++)
        array[i]=i;
}
void printArray(){
    int array[10];
    int i;  
    for(i=0;i<10;i++)
        printf("%d\n",array[i]);
}

идея в том, что если две функции имеют одинаковый размер записи активации в сегменте стека, она будет работать и печатать числа от 0 до 9 ... но на самом деле она печатает что-то подобное

134520820
-1079626712
0
1
2
3
4
5
6
7

всегда есть эти два значения в начале ... кто-нибудь может объяснить это ??? Я использую GCC в Linux

точная лекция URL , начиная с 5: 15

Ответы [ 3 ]

23 голосов
/ 30 июня 2010

Извините, но в этом куске кода абсолютно ничего умного, и люди, которые его используют, очень глупы.


Приложение:

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

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

Но, если у вас есть это знание, вам, вероятно, это сойдет с рук.Это не то, что я бы предложил никому, кроме ветеранов, но я вижу, что это имеет место в очень ограниченных ситуациях, и, честно говоря, я, несомненно, иногда был несколько более ... прагматичным ..., чем следовало быбыл в моей карьере: -)

Теперь вернемся к вашему обычному программированию ...


Это непереносимо между архитектурами, компиляторами, выпусками компиляторов и, возможно,даже уровни оптимизации в одной и той же версии компилятора, а также неопределенное поведение (чтение неинициализированных переменных).

Лучше всего, если вы хотите понять, что это - проверять код ассемблера, выводимый компилятором.

Но в целом вам лучше всего забыть об этом и написать код стандарта.


Например, эта транскрипция показывает, как gcc может вести себя по-разному на разных уровнях оптимизации:

pax> gcc -o qq qq.c ; ./qq
0
1
2
3
4
5
6
7
8
9

pax> gcc -O3 -o qq qq.c ; ./qq
1628373048
1629343944
1629097166
2280872
2281480
0
0
0
1629542238
1629542245

На высоком уровне оптимизации gcc (то, что я люблю называть его безумным уровнем оптимизации), это makeArray функция.По сути, выяснилось, что массив не используется, и поэтому он оптимизировал свою инициализацию.

_makeArray:
        pushl   %ebp            ; stack frame setup
        movl    %esp, %ebp

                                ; heavily optimised function

        popl    %ebp            ; stack frame tear-down

        ret                     ; and return

Я немного удивлен, что gcc вообще оставил там заглушку функции.

Обновление: , как указывает Николас Найт в комментарии, функция остается, поскольку она должна быть видима компоновщику - из-за статической функции gcc также удаляет заглушку.

Если вы проверяете код ассемблера на уровне оптимизации ниже 0, это дает подсказку (это не фактическая причина - см. Ниже).Изучите следующий код, и вы увидите, что установка фрейма стека отличается для двух функций, несмотря на тот факт, что они имеют одинаковые передаваемые параметры и одинаковые локальные переменные:

subl    $48, %esp     ; in makeArray
subl    $56, %esp     ; in printArray

Это потому, чтоprintArray выделяет дополнительное пространство для хранения адреса строки формата printf и адреса элемента массива, по четыре байта каждый, что учитывает разницу в восемь байтов (два 32-разрядных значения).

Этонаиболее вероятное объяснение того, что ваш массив в printArray() отключен двумя значениями.

Вот две функции на уровне оптимизации 0 для вашего удовольствия: -)

_makeArray:
        pushl   %ebp                     ; stack fram setup
        movl    %esp, %ebp
        subl    $48, %esp
        movl    $0, -4(%ebp)             ; i = 0
        jmp     L4                       ; start loop
L5:
        movl    -4(%ebp), %edx
        movl    -4(%ebp), %eax
        movl    %eax, -44(%ebp,%edx,4)   ; array[i] = i
        addl    $1, -4(%ebp)             ; i++
L4:
        cmpl    $9, -4(%ebp)             ; for all i up to and including 9
        jle     L5                       ; continue loop
        leave
        ret
        .section .rdata,"dr"
LC0:
        .ascii "%d\12\0"                 ; format string for printf
        .text

_printArray:
        pushl   %ebp                     ; stack frame setup
        movl    %esp, %ebp
        subl    $56, %esp
        movl    $0, -4(%ebp)             ; i = 0
        jmp     L8                       ; start loop
L9:
        movl    -4(%ebp), %eax           ; get i
        movl    -44(%ebp,%eax,4), %eax   ; get array[i]
        movl    %eax, 4(%esp)            ; store array[i] for printf
        movl    $LC0, (%esp)             ; store format string
        call    _printf                  ; make the call
        addl    $1, -4(%ebp)             ; i++
L8:
        cmpl    $9, -4(%ebp)             ; for all i up to and including 9
        jle     L9                       ; continue loop
        leave
        ret

Обновление: Как отмечает Родди в комментарии.это не является причиной вашей конкретной проблемы, поскольку в этом случае массив фактически находится в одной и той же позиции в памяти (%ebp-44 с %ebp одинаковым для двух вызовов).Я пытался указать, что две функции с одним и тем же списком аргументов и одинаковыми локальными параметрами не обязательно должны иметь одинаковую компоновку фрейма стека.

Все, что потребуется, - это поменять местами printArrayрасположение его локальных переменных (включая любые временные значения, не явно созданные разработчиком), и у вас возникнет эта проблема.

4 голосов
/ 30 июня 2010

Никогда, никогда, никогда, никогда, никогда не делай ничего подобного.Это не будет работать надежно.Вы получите странные ошибки.Это далеко от переносимости.

Пути, которыми он может потерпеть неудачу:

.1.Компилятор добавляет дополнительный скрытый код

DevStudio, в режиме отладки добавляет вызовы функций, которые проверяют стек для обнаружения ошибок стека.Эти вызовы перезапишут то, что было в стеке, и, следовательно, потеряют ваши данные.

.2.Кто-то добавляет вызов Enter / Exit

Некоторые компиляторы позволяют программисту определять функции, вызываемые при входе и выходе функции.Как (1) они используют пространство стека и перезаписывают то, что уже есть, теряя данные.

.3.Прерывания

В main (), если вы получаете прерывание между вызовами makeArray и printArray, вы потеряете ваши данные.Первое, что происходит при обработке прерывания, - это сохранить состояние процессора.Обычно для этого нужно поместить регистры и флаги ЦП в стек, и, как вы уже догадались, перезаписать свои данные.

.4.Компиляторы умны

Как вы уже видели, массив в makeArray находится по адресу, отличному от адреса в printArray.Компилятор поместил свои локальные переменные в разные позиции в стеке.Он использует сложный алгоритм, чтобы решить, куда поместить переменную - в стеке, в регистре и т. Д., И на самом деле не стоит пытаться выяснить, как это делает компилятор, так как следующая версия компилятора может сделать это другим способом.

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

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

4 голосов
/ 30 июня 2010

Вероятно, GCC генерирует код, который не передает аргументы в стек при вызове функции, вместо этого он выделяет дополнительное пространство в стеке. Аргументы для вашего вызова функции 'printf', "% d \ n" и array [i] занимают 8 байт в стеке, первый аргумент - указатель, а второй - целое число. Это объясняет, почему есть два целых числа, которые печатаются неправильно.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...