Дневники чайника. Чтива 0, виток0

Структурирование программы

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

Допустим, у вас родилась идея создать собственный "обработчик" "документов". Ну, предположим, текстовый редактор. В такой программе должны быть следующие возможности:

Дальше кто во что горазд. Даже этот минимальный набор возможностей, или, как говорят, функций программы, требует организации её кода. Можно, конечно, и всё подряд писать в одну кучу, но даже если вы сможете дописать хаотичную программу до полезного состояния, развивать новые возможности уже не получится, придётся переписать всё заново.

Обычно простое оконное приложение для форточек имеет примерно такую структуру:

вступление
вызов главной функции
завершение

   Главная функция:
       регистрация окна
       первая прорисовка окна
       цикл выборки сообщений Windows 
       возврат из главной функции

   Оконная процедура:
       в зависимости от сообщения, переданного системой, идёт:
       -  вызов функции открытия документа
       -  вызов функции сохранения документа
       -  вызов функции редактирования документа (если нажата буква, например)
       -  вызов стандартной системной функции обработки окна
       -  вызов системной функции завершения окна
       возврат из оконной процедуры


          Функция открытия документа:
          ...
          ...
          возврат из открытия документа 
      
          Функция сохранения документа:
          ...
          ...
          возврат из сохранения документа
      
          Функция редактирования документа:
          ...
          ...
          возврат из редактирования документа

Если вместо точек подставить код самих функций, а словесное описание заменить на вызовы соответствующих API, то получится программа =). Впрочем, не стоит забегать слишком далеко.

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

Однако не стоит путать функции с подпрограммами, и уж тем более с подпрограммами прерываний. Прерывания - особый вид подпрограмм (они имеют большую аппаратную поддержку: системная таблица, особые маш. коды и т.п.).

Подозреваю, что новичок не воспримет эту схему сразу же. Поэтому рекомендую взглянуть на неё еще раз после прочтения двух следующих статей.

Чтоб создавать такие приложения, лучше всего использовать команды:
Команда CALL
ПроисхождениеОт англ. слова call - звать, вызывать
Форматcall адрес или [указатель на адрес функции]
ДействиеСохраняет в стеке адрес следующей команды (для возврата) и переходит к выполнению указанной функции, то есть на адрес её первой команды.
ПримечаниеДля возврата из функции используется команда RET
Команда RET
ПроисхождениеОт англ. слова return - вернуться
Форматret (число)
ДействиеИзвлекает из стека значение и совершает переход по нему. Если было число, то после этого ещё и увеличивает ESP, тем самым чистит стек на указанное количество байт (а не значений).
ПримечаниеСледите за стеком, если вы не выровняли его перед выходом из функции, возврат будет ошибочным.

Кроме того, что эти команды структурируют вашу программу, они позволяют ещё и подключаться к основным возможностям Windows.

Дело в том, что программисты - очень ленивый народ. А МелкоМягкие делают всё, чтоб мы были ещё более ленивые. Почти всё, что нужно сделать в стандартной программе, уже есть в функциях Windows. Они называются API (Application Programming Interface, программный интерфейс приложений). Мы уже познакомились с API-функцией MessageBox и знаем, что другие вызываются так же.

Конечно, про лень я пошутил, потому что суть этих функций в уменьшении:

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

И надо сказать, что API неплохо справляются с этими задачами. Нужно только знать, как с ними обращаться.

Параметры функциям Win32-API передаются через стек. А значения они возвращают в регистр EAX и различные переменные. Есть хорошие справочники на этот счёт, и в следующих статьях я про них уже писал.

Для того чтоб отлаживать процедуры и API-функции (а также подпрограммы, прерывания, системные вызовы), нужно в отладчике выполнять пошаговую трассировку с заходом в функцию. В OllyDbg клавиша F7 как раз и назначена для этого (step into - шаг внутрь).

Вы уже должны были запомнить, что F8 выполняет одну строку кода программы. Когда в проге встретится команда "call", это значит, что будет вызвана функция. На подобной строке вы имеете возможность нажать F8, что приведёт к выполнению всех действий данной процедуры и возврату из неё. Если же вы нажмёте F7, то произойдёт прыжок на функцию (а точнее вход в неё) и отладка продолжится по одной машинной команде.

Нажатие F7 на простой команде в коде ничем не отличается от F8.

Olly не умеет отлаживать системные вызовы. Вход в функцию с командой "sysenter" или "syscall" здесь невозможен (не больно-то вам и надо :).

Вы уже знаете почти всё о prax07 (на начальном уровне).

Давайте загрузим пример в Olly.

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


00401000  E8 59000000  call < jmp.kernel32.GetCommandLineA>  ;[GetCommandLineA

В первой же строчке кода вызывается API-функция. Но в данном случае это звучит слишком гордо - API-функция :).

Выполните её с заходом (F7). Сначала произойдёт вызов последней строки кода программы (её при сборке exe подставил линковщик из связанного файла kernel32.lib). А затем произойдёт прыжок на саму функцию. Взгляните, из чего она состоит (F7). Всего две команды:

mov EAX, [адрес]
ret

Предполагаю три вопроса:

  1. Что означает буква A в названии функции?
  2. Зачем вообще нужна функция из одной полезной строки?
  3. И что это за "jmp.kernel32."?

Ну, на первый вопрос ответить очень просто. "A" - означает ANSI (текстовая кодировка Win). После внедрения Unicode появились разные версии функций. Юникод по-другому называют WideChar (worldwide character-encoding - всемирная кодировка символов). Значит, если название функции заканчивается на W - это Unicode-версия API-функции. В технологии WinNT все функции Unicode, а ANSI-версии всего лишь перемычки к W-функциям.

Второй вопрос чуть сложнее. Действительно, зачем вообще вызывать такую хрень, почему нельзя сразу в нашей программе набрать одну-единственную строку из этой функции? Дело в том, что Винды формируют адресное пространство процесса с разными смещениями (адресами). Никто не знает, по какому адресу будет спроецирована та или иная внешняя функция, и адрес размещения командной строки в памяти тоже заранее не определён. Значит, нужен механизм исправления вызовов и перемещаемых адресов (relocation, fixup table - на слэнге релоки и фиксапы). Так что лучше пока доверить выяснение адреса командной строки самой операционной системе.

Третий вопрос напрямую вытекает из второго ответа. Раз заранее неизвестно, куда прыгать на функцию такую-то, удобнее создать таблицу, которая будет заполняться правильными адресами в памяти (import table). В exe-файле хранится информация обо всех функциях, которые будут вызваны программой извне (в PE-директории import). Загрузчик программ в форточках выполняет много работы по построению виртуального адресного пространства процесса, в том числе и правки всех необходимых переходов вызовов и прыжков.

Строка "call < jmp.kernel32.GetCommandLineA> " вызвала строку:
jmp     dword ptr [< kernel32.GetCommandLineA> ]

Такие строки как раз и переключают нас на API-функции. Здесь Оли потрудилась вставить название функции, на которую нас перебросит. Но на самом деле строка выглядит так:

jmp     dword ptr [00402000]

При команде jmp квадратные скобки (так же, как и при mov) означают, что операнд надо извлечь из указанного в них адреса.

Получается, что этот прыжок обращается к одной из ячеек таблицы импорта, заполненной после загрузки программы в память правильным адресом API-функции GetCommandLineA. Когда функция отработает, она вернёт нас командой ret к следующей строке после call (а не jmp).

Ещё вам нужно знать, что текстовые строки всегда оканчиваются нулевым байтом, это иногда называется ANSIz-строка (ANSI - кодировка Win, z - zero).
После выполнения этой функции:
В EAX окажется адрес командной строки в кавычках и аргументы за пределами кавычек. Пример:
"D:\tut\prax07.exe" аргументы


00401005   B9 00020000    mov     ecx, 200
0040100A   03C8           add     ecx, eax
После выполнения этих инструкций:
В ECX будет адрес командной строки + 512d

0040100C   40             inc     eax
0040100D   3BC1           cmp     eax, ecx
0040100F   74 26          je      short prax07.00401037
00401011   8038 22        cmp     byte ptr [eax], 22
00401014   75 F6          jnz     short prax07.0040100C

"short prax07." - к Ассемблеру не имеет отношения. Так Оли поясняет, что прыжок короткий (+128d -127d) и точка перехода - в рамках модуля "prax07".

В этом цикле находится адрес закрывающей кавычки. Допустим, при загрузке что-то произошло и командная строка была повреждена, такое вполне возможно, даже мы сейчас можем вписать туда что угодно. Тогда этот цикл мог бы работать до нахождения следующего байта 22h в памяти. Но его там может и не быть. В таком случае произошла бы ошибка чтения вне адресного пространства программы. Чтобы избежать этой случайности, я добавил аварийный выход из этого цикла. Благодаря строчкам 0040100D и 0040100F цикл не сможет повториться более 511 раз. Я предположил, что 511 символов в пути к файлу и его имени вполне достаточно (но, может быть, я и не прав).
Цикл может прерваться по следующим причинам:
Текущий адрес стал на 512 байт больше начала командной строки. Тогда будет прыг на плохое сообщение.
Найден байт со значением 22h (символ кавычки). Если это так, в строке 00401014 прыжка не будет.


00401016   40             inc     eax
00401017   8038 00        cmp     byte ptr [eax], 0
0040101A   74 1B          je      short prax07.00401037
0040101C   8038 20        cmp     byte ptr [eax], 20
0040101F   74 F5          je      short prax07.00401016

В этом цикле выясняется, есть ли после кавычки байт 00 (конец строки) и байт 20h (символ пробела).

В зависимости от способа запуска примера могут быть разные комбинации. Если аргумента нет, может быть, строка закончится сразу нулём, а может, будет ещё пробел или два. Кроме того, нам всё равно нужно найти первый байт самого аргумента, и он может быть через несколько пробелов.

Учтите, prax07 - просто пример. В реальных условиях вероятны другие ситуации. Я за этот пример не отвечаю как за болванку для ваших программ. Но у меня на XP всё работало при любых обстоятельствах, которые я только мог придумать.
Цикл может прерваться по следующим причинам:
Был найден байт 0. Тогда будет прыг на плохое сообщение.
Не был найден байт 0 или 20h. В строке 0040101F прыжка не будет.


00401021   6A 00          push    0                         ; Style = MB_OK|MB_APPLMODAL
00401023   68 00304000    push    prax07.00403000         ; Title = "It's My first command line for Win32"
00401028   50             push    eax                       ; Text
00401029   6A 00          push    0                         ; hOwner = NULL
0040102B   E8 22000000    call   < jmp.&user32.MessageBoxA> ; MessageBoxA

Эти строки будут выполняться только при условии, что был найден байт со значением не 20h и не 00h после найденной закрывающей кавычки.

Сначала в стек укладывается четвёртый параметр с точки зрения функции. Данный параметр задаёт стиль (внешний вид) сообщения. При значении 0 сообщение будет без иконки и с кнопкой OK по середине.

Затем третий параметр (строка 00401023). Данный параметр является адресом строки текста с нулевым байтом на конце. Эта строка будет титульной (на верхней полоске).

Второй параметр (00401028) - тоже адрес строки текста с нулевым байтом на конце (само сообщение внутри бокса).

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

Окна у нас нет, сообщение вызывается само по себе - значит, передаём нулевое значение (что такое handle, вы узнаете из последующих статей).
После того, как в стек будут уложены все необходимые параметры, произойдёт вызов самой функции. Пользователь нажмёт кнопку OK на сообщении, и функция вернёт управление программе со следующей строки. Из стека будут убраны 4 параметра, которые мы передали функции.

Вы, возможно, забыли, но я, когда описывал директиву ".model", сказал, что о параметре "Stdcall" мы ещё поговорим. Вот теперь вы поймёте, что "уговор" Stdcall - это стандарт по вызову функций. По данному стандарту параметры передаются в обратном порядке (старший - высший, младший - ближний к вызову). И функция сама очищает из стека параметры, переданные ей. В форточках по этому уговору построены все API-функции, и мы будем использовать только его.


00401030   6A 00          push    0                         ; ExitCode = 0
00401032   E8 21000000    call   < jmp.kernel32.ExitProcess> ; ExitProcess

Сразу же после выполнения API MessageBox программа завершается вызовом ExitProcess с параметром 0.
После выполнения этих строк:
Программа завершится... Совсем.


00401037   6A 00          push    0                         ; Style = MB_OK|MB_APPLMODAL
00401039   68 00304000    push    prax07.00403000         ; Title = "It's My first command line for Win32"
0040103E   68 25304000    push    prax07.00403025         ; Text = "Аргументы командной строки отсутствуют"
00401043   6A 00          push    0                         ; hOwner = NULL
00401045   E8 08000000    call   < jmp.user32.MessageBoxA> ; MessageBoxA

Если программа запускалась с ключом, то данное сообщение выводиться не будет. Сюда мы прыгнем, только если был превышен лимит в 512 символов или найден байт 0 без каких-либо полезных аргументов. Строка 00401037 в исходнике обозначается меткой "NO".


0040104A   6A 00          push    0                         ; ExitCode = 0
0040104C   E8 07000000    call   < jmp.kernel32.ExitProcess> ; ExitProcess

Здесь точно такой же выход, как при первом сообщении.
После выполнения этих строк:
Программа завершится.


00401051     CC          int3

Прерывание 3 - особое среди прерываний, оно вызывается одним байтом. Для того, чтоб простой отладчик мог останавливать программу после каждой строки кода, придумали такое прерывание. Оно также используется практически всеми отладчиками для установки Breakpoint'ов (BP - точка останова).

Здесь это прерывание используется для заполнения лишнего байта (чтоб выровнять код). Некоторые компиляторы заполняют такие байты командой nop (байт 90h). В принципе без разницы, что это будет за байт, так как это и не код, и не данные. Но в исключительных ситуациях (мало ли сбой какой) лучше, чтоб он был CCh. Допустим, произойдёт выполнение такого байта, тогда программа сразу же аварийно прекратится и не случится возможной серьёзной ошибки. В случае если загружен отладчик и в нём включена опция int3 breakpoint (или что-то в этом роде), отладчик перехватит программу, и мы увидим, из-за чего могла случиться такая ситуация.
Эта строка выполняться не должна.


00401052 FF25 0C204000  jmp dword ptr [< user32.MessageBoxA> ]      ;  user32.MessageBoxA
00401058 FF25 04204000  jmp dword ptr [< kernel32.ExitProcess> ]    ;  kernel32.ExitProcess
0040105E FF25 00204000  jmp dword ptr [< kernel32.GetCommandLineA> ];  kernel32.GetCommandLineA

Это код, но мы его не писали. Он взялся из прикреплённых файлов импорта к нашему исходнику (user32.lib и kernel32.lib).

Повторюсь, здесь происходят прыжки к API-функциям, адрес которых извлекается из таблицы импорта.

Кроме API-функций, в Import Table указываются адреса всех внешних функций. Ведь наша программа может состоять из нескольких файлов. Чаще всего так вызываются функции из динамически подключаемых библиотек (dll).

User32 и kernel32 это как раз и есть dll-файлы, они находятся в системной папке форточек и содержат основные API-функции. Правда, есть ещё и другие библиотеки dll, которые мы тоже будем использовать, но об этом в следующий раз.




Вся теория изложена.

Вы сейчас, скорее всего, мало что уяснили. Но главная цель, я надеюсь, достигнута - у вас должна быть целая куча вопросов. Я постараюсь ответить на часть из них в следующем витке.

Ещё одна полезная команда.
Команда TEST
ПроисхождениеОт англ. слова TEST - проверять
Форматtest приёмник, источник
ДействиеПроизводит логическое сравнение. Операнды не изменяет! Только флаги.
Если оба из сравниваемых битов в операндах равны 1, то результат равен 1.
Во всех остальных случаях результат - 0.
ZF=1, если в результате получился ноль!
ZF=0, если результат не ноль.
ПримечаниеОчень часто команду test используют для выяснения, обнулён ли регистр, например:
"test EAX,EAX" включает флаг нуля, если регистр был пуст.

Опять же логические команды удобно представлять так:

Биты операнда:   0101
             test
Биты операнда:   0011
                 ----
мнимый результат:0001

Примеры использования команды.

Выяснить, содержит ли старший байт регистра EAX хоть что-нибудь, кроме нулей, можно так:

test  EAX,0FF000000h

Если в EAX старший байт содержит не нулевое значение, то ZF=0.

Выяснить, включён ли БИТ номер 4 в восьмибитном регистре AL, можно так:

test  AL,  00010000b

Флаг нуля выключится, только если в AL бит номер 4 будет в положении 1. Не забывайте: биты нумеруются от нуля, 000?0000 - потому и номер 4.

Обратите особое внимание, что флаг нуля меняется так:
Если результат не ноль, флаг нуля опускается.
Если результат ноль, флаг нуля поднимается.

Устали? Отдохните как следует, потому что сейчас будет экзамен за виток0. По себе знаю, что разбирать чужие закорючки значительно сложнее, чем писать свои, но вы всё-таки постарайтесь разобрать следующие два примера. Предполагаю, что у вас уйдёт на это несколько часов. Зато когда вы всё разберёте, вы почувствуете новые возможности в своей голове, я вам обещаю.

Матрос, беги, тебя вызывает Земля на удалённый экзамен. Удачи, малыш.

prax08.asm
(использование стека для хранения данных и передачи параметров)

Cкопируйте файл prax05.asm и назовите prax08.asm.

Измените секцию данных в исходнике:

    .data
	MsgBoxCaption db "It's the first your program for Win32",0
	MsgBoxText    db "Assembler language for Windows is a fable!",0

Замените на:

    .data
	MsgBoxCaption db "It's the first your debuging for Win32",0
	MsgBoxText    db "_ssembler language for Windows is a fable!",0

Удалите две строки в секции кода из того же исходника:

	invoke MessageBox, NULL, addr MsgBoxText, addr MsgBoxCaption, MB_OK
	invoke ExitProcess, NULL

И впишите на их место такой код:

    mov    EAX, offset MsgBoxCaption
    mov    EBX, offset MsgBoxText

    mov    byte ptr [EBX],41     ; Эта команда подменяет код символа "_" в строке MsgBoxText

    push   EAX
    push   EBX

    push   0
    push   EAX
    push   EBX
    push   0
    call   MessageBox
; ------------------------
    pop    EAX
    pop    EBX

    push   0
    push   EAX
    push   EBX
    push   0
    call   MessageBox
; ------------------------
    pop    EBX
    pop    EAX

    push   0
    push   EAX
    push   EBX
    push   0
    call   MessageBox
; ------------------------
    push   0
    call   ExitProcess

Цель программы - вывести три одинаковых сообщения.
С заголовком - "It's the first your debuging for Win32".
И текстом - "Assembler language for Windows is a fable!"

Задания:

  1. Найдите в этом коде 3 ошибки, которые не позволяют достичь цели. Первую ошибку исправляйте добавлением одной буквы (Ответы в конце).
  2. Полностью разберите и откомментируйте каждую строку кода.
  3. Ответьте, почему сообщения не вываливаются все сразу, а появляются по одному?



prax09.asm
(программа с несколькими функциями)

      .386
      .model flat, stdcall
      option casemap :none   ; case sensitive
;#########################################################################
      include \masm32\include\windows.inc
      include \masm32\include\user32.inc
      include \masm32\include\kernel32.inc

      includelib \masm32\lib\user32.lib
      includelib \masm32\lib\kernel32.lib
;#########################################################################
.data
  MsgCaptionNO   db "Нечего обрабатывать!",0
  MsgTextNO      db "Аргументы командной строки отсутствуют",0
  MsgCaptionYES  db "Обработанный результат:",0
	
  Crypt_String   byte 100h dup (00) ; Слева от dup - количество, справа - что повторять
;#########################################################################
    .code

start:
	call   Main        ; вызов главной функции
	push   0           ; пустой параметр для процедуры выхода
	call   ExitProcess ; вызов API-функции выхода
;=========================================================================
Main proc                     ; начало главной функции
        call   Take_Arguments ; вернёт в EAX указатель на ключ командной строки, если нет - EAX=0.

        test   eax,eax        ; проверить EAX на ноль
        jnz    Next1
        call   Message
        ret

Next1: 	call   Crypt
	call   Message
	ret
Main endp                     ; конец главной функции
;=========================================================================

Take_Arguments proc           ; начало функции получения аргумента командной строки
; Вход  - ничего.
; Выход - EAX= указатель на аргументы командной строки или 0.

	call   GetCommandLine        
	mov    ECX,512d
        add    ECX,EAX               

    unquote:	
	inc    EAX                   
        cmp    EAX,ECX
        jz     NO_Arg
        cmp    byte ptr[EAX],22h  
    jnz unquote                   

    Arg_search:
	inc    EAX                  
        cmp    byte ptr[EAX],0   
        jz     NO_Arg            
                                 
        cmp    byte ptr[EAX],20h
    jz  Arg_search

        ret

NO_Arg:
	xor eax,eax
	ret    

Take_Arguments endp            ; конец функции получения аргумента командной строки
;-------------------------------------------------------------------------

Crypt proc                     ; начало функции шифрования
; Вход  - в EAX должен быть указатель на строку, 
;         заканчивающуюся нулём (это и есть параметр функции).
; Выход - в EAX будет указатель на шифрованную строку.
; Используется глобальная переменная Crypt_String.

	push   EBX
	push   ECX
	push   EDX

	mov    EBX, offset Crypt_String
	mov    ECX,00001010b

   Crypt_Loop:
	movzx  EDX, byte ptr [EAX]
	test   EDX,EDX
	jz     Fin_Crypt
        xor    DL,CL
        mov    byte ptr [EBX],DL
        inc    EAX
        inc    EBX
        xor    CL, 00000101b
   jmp Crypt_Loop

Fin_Crypt:
        pop    EDX
        pop    ECX
        pop    EBX

	mov    EAX, offset Crypt_String
	ret

Crypt endp                     ; конец функции шифрования
;-------------------------------------------------------------------------

Message proc                   ; начало функции вывода сообщений
; Вход  - EAX=0 или указатель на строку текста, заканчивающуюся нулём.
; Выход - ничего.
; Используются глобальные переменные: 
; MsgCaptionYES, Crypt_String, MsgCaptionNO, MsgTextNO.

        test   EAX,EAX
        jz     No_Arguments

       	push   0                     
	push   offset MsgCaptionYES ; offset - директива
	push   EAX                  
	push   0                    
	call   MessageBox           
        ret

No_Arguments:
   
        push   0
	push   offset MsgCaptionNO  ; адрес заголовка
	push   offset MsgTextNO     ; адрес текста сообщения
	push   0                    ; нет родительского окна
	call   MessageBox           ; вызов API-функции вывода сообщения на экран
	ret

Message endp                   ; конец функции вывода сообщений
;-------------------------------------------------------------------------

end start

Задания:

  1. Нарисуйте поэтажную схему вызовов функций в этом примере.
  2. Откомментируйте все строки кода так, чтобы вы поняли всю программу.
  3. Измените способ шифровки.

Собственно говоря, я надеюсь, вы сами справитесь со всеми заданиями, но для проверки скажу, что в prax08.asm были расставлены следующие ошибки:

  1. В строке "mov byte ptr [EBX],41" я "забыл" поставить определитель системы счисления, букву h. Такого рода ошибки возникают очень часто, и вылавливать их сложнее, чем грубые опечатки. Программа собирается, как вы её просите - с десятичным значением 41. И если бы значение не выводилось как строка, только в отладчике можно было бы определить, какая команда вызывает сбой.
  2. Во втором участке кода значения из стека вынимаются не в том порядке, которого требует цель программы. Если мы хотим восстановить значение регистров, то нам нужно выталкивать их в обратном порядке (сначала "pop EBX", а затем "pop EAX").
  3. На третьем участке кода мы пытаемся ещё раз вытолкнуть значения, которых в стеке уже нет. Под конец я решил написать мутное объяснение. Здесь важно понимать, что каждый раз, как вы копируете что-то в стек, регистр ESP уменьшается, и каждый раз, как вы что-то достаёте, ESP увеличивается. Несмотря на то, что после выталкивания в памяти копированное значение остаётся (до следующего заполнения стека), значение можно считать утерянным. Указатель стека сместился после выталкивания - значит, в стеке этого значения больше нет. :)

Послесловие

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

P.S. Дальше - больше.

Bitfry

<<предыдущая глава     следующая глава>>

Вернуться на главную

Hosted by uCoz