Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Роббинс Д. - Отладка приложений для Microsoft .NET и Microsoft Windows - 2004

.pdf
Скачиваний:
353
Добавлен:
13.08.2013
Размер:
3.3 Mб
Скачать

ГЛАВА 7 Усложненные технологии неуправляемого кода в Visual Studio .NET

311

 

 

 

 

LEA

ECX , [EAX + 4]

 

 

PUSH

ECX

 

 

CALL

DWORD PTR [printf]

 

 

ADD ESP , 4

// Функция printf использует соглашение __cdecl.

 

}

 

 

 

}

Полный пример

Я описал все важные аспекты языка ассемблера процессоров Intel и, прежде чем перейти к рассмотрению окна Disassembly, хочу привести полный пример функ ции Win32 API. Листинг 7 4 представляет собой подробно описанный дизассем блированный код функции lstrcpyA из библиотеки KERNEL32.DLL системы Windows 2000 с установленным пакетом обновлений 2 (Service Pack 2). Функция lstrcpyA копирует одну строку в другую. Я выбрал ее потому, что она поясняет все описан ные мной в этой главе вопросы и потому что ее цель понятна. После точек с за пятой я привожу комментарии, которые попытался сделать максимально подроб ными.

Листинг 7-4. Полный пример на языке ассемблера: функция lstrcpyA

;Прототип функции:

;LPTSTR __stdcall lstrcpy ( LPTSTR lpString1 , LPCTSTR lpString2 )

_lstrcpyA@8:

; Стандартный пролог кадра стека.

PUSH

EBP

 

MOV

EBP

, ESP

;Конфигурирование блока SEH __try. По адресу 0x77E88000 содержится

;значение #1. Это стандартный способ указания блока __except.

;с EXCEPTION_EXECUTE_HANDLER.

PUSH

0FFFFFFFFh

PUSH

77E88000h

PUSH

OFFSET __except_handler3 (77E8615Bh)

MOV

EAX , DWORD PTR FS:[00000000h]

PUSH

EAX

MOV

DWORD PTR FS:[0] , ESP

;Вместо того чтобы зарезервировать в стеке пространство для дополнительных

;относящихся к SEH элементов при помощи "SUB ESP 8", генератор кода решил

;выполнить две команды PUSH. Идентификатор операции команды "PUSH ECX"

;состоит из одного байта(0x51), поэтому в данном случае это самый

;быстрый вариант.

PUSH ECX

PUSH ECX

;Сохранение используемых в функции регистров. EBX в функции не применяется,

;однако, возможно, он помещается в стек для оптимизации работы конвейера.

см. след. стр.

312 ЧАСТЬ II Производительная отладка

PUSH EBX

PUSH ESI

PUSH EDI

;В конце процесса настройки SEH сохраняется адрес начала

;блока try в стеке, после чего код входит в блок try.

MOV

DWORD

PTR

[EBP#18h]

,

ESP

AND

DWORD

PTR

[EBP#4] ,

0

 

;После выполнения всех параметров функция получает длину

;копируемой строки, являющейся вторым параметром функции.

;В EDI помещается адрес второго параметра копируемой строки.

MOV

EDI , DWORD PTR [EBP+0Ch]

;Функция lstrcpy будет просматривать 4 294 967 295 байт,

;пока не обнаружит терминальный символ NULL.

;Для подсчета итераций цикла команда REPNE SCAS использует регистр ECX.

OR

ECX ,

0FFFFFFFFH

; Обнуление EAX

(поиск символа NULL).

XOR

EAX

,

EAX

; Команда SCAS просматривает строку, пока не найдет символ NULL. REPNE SCAS BYTE PTR [EDI]

;Регистр ECX отсчитывался вниз от максимального значения, поэтому для

;нахождения длины строки нужно инвертировать все биты. Полученное число

;будет длиной строки с учетом символа NULL.

NOT ECX

;Команда REPNE SCAS увеличивала и EDI, поэтому, чтобы он снова

;указывал на начало строки, из EDI нужно вычесть ее длину.

SUB

EDI , ECX

;Длина строки помещается в регистр EDX. Он не был сохранен в начале

;этой функции, так как при вызове функций C/C++ это не требуется.

MOV

EDX , ECX

;Второй параметр помещается в регистр ESI. Именно этот регистр

;в командах работы со строками является операндом источника.

MOV

ESI ,

EDI

; В регистр EDI

помещается первый параметр — адрес строки приемника.

MOV

EDI

,

DWORD PTR [EBP+8]

;Второй параметр сохраняется в регистре EAX. Этот регистр

;также не требуется сохранять при вызове функций.

MOV

EAX , EDI

ГЛАВА 7 Усложненные технологии неуправляемого кода в Visual Studio .NET

313

 

 

;Длина строки была получена в байтах. Чтобы получить длину

;строки в двойных словах, надо разделить ее на 4. Если число

;символов нечетно, команда REP MOVS не сможет скопировать их все.

;Все оставшиеся байты копируются после REP MOVS.

SHR

ECX

,

2

 

 

 

 

;

Копирование второй строки#параметра

в

первую строку#параметр.

REP MOVS

DWORD

PTR [EDI] , DWORD

PTR

[ESI]

 

;

Загрузка сохраненной длины строки

в

регистр

ECX.

MOV

ECX

,

EDX

 

 

 

 

;В результате операции "логическое И" над длиной строки и числом 3

;будет найдено число байтов, которые еще не были скопированы.

AND ECX , 3

; Копирование оставшихся байт из одной строки в другую.

REP MOVS BYTE PTR [EDI] , BYTE PTR [ESI]

;Присваивание локальной переменной значения –1 показывает,

;что функция покидает этот блок try/except.

OR

DWORD

PTR

[EBP#4]

, 0FFFFFFFFh

;

Получение

предыдущего

кадра

SEH.

MOV

ECX ,

DWORD

PTR [EBP#10h]

;

Уничтожение кадра SEH.

 

 

MOV

DWORD

PTR

FS:[0] , ECX

; Восстановление регистров, сохраненных в стеке в начале функции.

POP

EDI

POP

ESI

POP

EBX

;Команда LEAVE эквивалентна следующей паре команд:

;MOV ESP , EBP

;POP EBP

LEAVE

;Удаление 8 байтов из стека (параметры)

;и возвращение в главную функцию.

;Функция lstrcpy использует соглашение __sdtcall.

RET 8

Окно Disassembly

Теперь вы имеете некоторые сведения об ассемблере, поэтому окно Disassembly отладчика Visual Studio .NET уже не должно вас пугать. Окно Disassembly обеспе чивает много возможностей, которые помогут вам в отладке. Ниже я поговорю о некоторых из них и о том, как сократить время, проводимое в окне Disassembly.

314 ЧАСТЬ II Производительная отладка

Навигация

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

Первый — использовать поле Address (адрес) в левом верхнем углу окна Disas sembly. Если вам известен нужный адрес, просто введите его и окажетесь в соот ветствующем месте кода. Поле Address может также интерпретировать символы

иконтекстную информацию, так что вы сможете попасть в нужное место, даже не зная его точного адреса.

Одна небольшая проблема заключается в том, что необходимость форматиро вания символов, описанную в разделе «Усложненный синтаксис точек прерыва ния», никто не отменял. Вы должны будете выполнять то же расширение имен, которое требуется при установке точки прерывания для системной или экспор тируемой функции. Например, если у вас загружены символы для KERNEL32.DLL

ивы хотите перейти в окне Disassembly к функции LoadLibrary, то, чтобы очутить ся в нужном месте, вам надо ввести в поле Address выражение {,,kernel32}_Load#

LibraryA@4.

Вам обязательно понравится то, что окно Disassembly поддерживает функцию «drag and drop». Если вы работаете с ассемблерным кодом и желаете быстро уз нать, к какой области памяти происходит обращение, вы можете выбрать адрес и перетащить его в поле Address. Когда вы отпустите кнопку, окно Disassembly авто матически перейдет к этому адресу.

Увлекшись окном Disassembly, можно легко забыть дорогу назад и заблудить ся. Чтобы вернуться к указателю команд, просто щелкните в окне Disassembly правой кнопкой и выберите пункт Show Next Statement (Показать следующий оператор). Окна исходного кода также поддерживают эту функцию. Очень полезной может оказаться еще одна возможность, отсутствовавшая в предыдущих версиях Visual Studio: поле Address теперь запоминает все адреса, к которым вы переходили, благодаря чему вы всегда сможете вернуться назад.

Просмотр параметров стека

В разделе «Усложненный синтаксис точек прерывания» я рассказал, как устанав ливать точки прерывания для системных и экспортируемых функций. Важность этих точек прерывания объясняется тем, что они позволяют узнать параметры, передающиеся в конкретную функцию. Чтобы показать, как просматривать эле менты стека, я хочу привести вполне реальный и простой пример.

При загрузке программы из Интернета вам хотелось бы иметь гарантию, что она не запускает тайком какие то другие программы. Для этого нужно отследить все вызовы функции CreateProcess. В этом примере я буду использовать програм му CMD.EXE (командная строка), однако ничто не мешает вам аналогично изучить любое другое приложение.

Сначала я запустил программу DUMPBIN.EXE из состава Visual Studio .NET с ключом командной строки /IMPORTS, чтобы узнать, что именно вызывает CMD.EXE. Вывод показал все неявно импортируемые DLL и вызываемые функции. Изучая

ГЛАВА 7 Усложненные технологии неуправляемого кода в Visual Studio .NET

315

 

 

импортируемые функции, я увидел, что CMD.EXE импортирует CreateProcessW из KERNEL32.DLL. Затем из среды Visual Studio .NET я открыл CMD.EXE как новое решение, выбрав в меню File пункт Open Solution (открыть решение) (CMD.EXE находится в каталоге %SYSTEMROOT%\System32). У меня были загружены симво лы (после прочтения книги у вас тоже всегда будут загружены нужные символы), поэтому мне нужно было установить точку прерывания для функции {,,kernel# 32}_CreateProcessW@40. После запуска CMD.EXE я написал в командной строке SOL.EXE

инажал Enter.

ВWindows 2000 с пакетом обновлений 2 точка прерывания для _CreateProcessW@40 останавливает программу по адресу 0x77E96F60 на команде PUSH EBP, участвую щей в создании стандартного кадра стека. Так как программа останавливается на первой команде функции CreateProcess, значит, в этот момент вершина стека со держит параметры и адрес возвращения из функции. Далее я открыл одно из че тырех окон Memory, выбрав подменю Debug\Windows\Memory. Открыв окно Memo ry, я щелкнул в нем правой кнопкой, выбрал в контекстном меню пункт 4 byte Integer (4 байтовые целочисленные значения) и настроил размер окна так, чтобы в нем помещались только два столбца: адреса стека и значения для каждого адреса. На конец, чтобы узнать, что находится в стеке, я ввел в поле Address «ESP», регистр указателя стека.

Отображение стека в окне Memory в начале функции CreateProcess показано на рис. 7 13. Первое значение, 0x4AD0728C, — это адрес возвращения; следующие 10 — параметры функции CreateProcess (табл. 7 10). Параметры CreateProcess за нимают 40 байт: каждый по 4 байта. Стек растет от старших адресов к младшим,

ипараметры помещаются в него справа налево, поэтому в окне Memory они по являются в том же порядке, что и в определении функции.

Рис. 7 13. Стек в окне Memory отладчика Visual Studio .NET

316 ЧАСТЬ II Производительная отладка

Табл. 7-10. Параметры, передаваемые программой CMD.EXE функции

CreateProcessW

Значение

Тип

Параметр

0x00134AA0

LPCWSTR

lpApplicationName

0x001344A8

LPWSTR

lpCommandLine

0x00000000

LPSECURITY_ATTRIBUTES

lpProcessAttributes

0x00000000

LPSECURITY_ATTRIBUTES

lpThreadAttributes

0x00000001

BOOL

bInheritHandles

0x00000000

DWORD

dwCreationFlags

0x00000000

LPVOID

lpEnvironment

0x 4AD228A0

LPCWSTR

lpCurrentDirectory

0x0012FBFC

LPSTARTUPINFO

lpStartupInfo

0x0012FB7C

LPPROCESS_INFORMATION

lpProcessInformation

 

 

 

Значения первых двух параметров можно узнать двумя способами. Первый — открыть второе окно Memory и указать адрес параметра в поле Address. Щелкни те в окне правой кнопкой, чтобы выбрать желаемый формат данных в верхней части контекстного меню. Более важные функции расположены в нижней части этого меню, где можно выбрать отображение значений, расположенных по адре сам памяти, в текстовом формате ANSI или Unicode. Второй и более простой спо соб — перетащить интересующий вас адрес в окно Watch. Для просмотра значе ния в окне Watch служит оператор приведения типа. Так, для просмотра парамет ра lpApplicationName нужно ввести в окне Watch выражение (wchar_t*)0x00134aa0.

Получить параметры функции оказалось в этом примере так легко потому, что я остановил функцию на первой команде, до того как функция смогла поместить в стек дополнительные элементы. Если вы желаете просмотреть параметры, на ходясь в теле функции, придется поработать. Облегчить эту задачу можно, поис кав положительные смещения от регистра EBP. И все же иногда лучше просто от крыть окно Memory и узнать все самому.

Команда Set Next Statement

Как и окна исходного кода, окно Disassembly поддерживает функцию Set Next Statement, доступную из контекстного меню, так что вы можете изменить EIP для выполнения другой команды. Работая над отладочными компоновками в окнах исходного кода, к функции Set Next Statement можно относиться более менее небрежно, однако в окне Disassembly она требует пристального внимания.

Главное условие корректного изменения EIP (чтобы это не привело к ошиб ке) — должное внимание ко стеку. Число помещенных в стек элементов должно соответствовать числу извлеченных элементов; если это не так, программа в ко нечном счете потерпит крах. Я не отпугиваю вас от изменения потока выполне ния программы — я поощряю вас на эксперименты с ним. Изменение потока выполнения при помощи функции Set Next Statement — очень мощный метод, позволяющий заметно ускорить отладку. Если вы позаботитесь о стеке, стек по заботится о вас.

Например, если вы хотите заново выполнить функцию, не вызвав немедлен ную ошибку, убедитесь, что изменение потока сохраняет стек в сбалансирован

ГЛАВА 7 Усложненные технологии неуправляемого кода в Visual Studio .NET

317

 

 

ном состоянии. В следующем примере я хочу дважды вызвать функцию, находя щуюся по адресу 0x00401005:

00401032 PUSH EBP

00401033 MOV EBP , ESP

00401035 PUSH 404410h

0040103A CALL 00401005h

0040103F ADD ESP , 4

00401042 POP EBP

00401043 RET

При повторной отладке этого дизассемблированного кода нужно гарантиро вать выполнение команды ADD по адресу 0x0040103F, чтобы стек был сбалансиро ванным. Как я указывал при обсуждении различных соглашений, в данном фраг менте вызывается функция с соглашением __cdecl, о чем говорит команда ADD сразу же после вызова. Поэтому для повторного вызова функции мне нужно установить указатель команд на адрес 0x00401035, чтобы выполнилась команда PUSH.

Исследование стека «вручную»

Окна Memory и Disassembly тесно связаны. Пытаясь определить, что делает фраг мент ассемблерного кода при помощи окна Disassembly, нужно держать откры тым окно Memory, чтобы видеть адреса и значения, над которыми производятся действия. Ассемблерные команды изменяют память, а значения памяти определяют выполнение ассемблерной программы; окна Disassembly и Memory позволяют наблюдать эту связь в динамике.

Само по себе содержимое окна Memory — просто набор чисел, особенно при крахе программы. Однако, объединив информацию двух окон, вы непременно начнете понимать причины неприятных ошибок. Совместное использование этих окон особенно важно при отладке оптимизированного кода, когда отладчик не может легко перемещаться по стеку. Для исправления ошибки вам придется ис следовать стек вручную.

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

После этого нужно открыть окна Memory и Disassembly. Введите в поле Address окна Memory название регистра ESP и прикажите отладчику отображать значения в формате двойных слов, щелкнув в окне правой кнопкой и выбрав пункт 4 byte Integer. Кроме того, лучше не «прикреплять» окно Memory к остальным блокам интерфейса IDE и уменьшить его, чтобы оно вмещало только адреса стека и один столбец 32 разрядных значений, потому что именно так представляет себе стек большинство людей. Когда вам встретится число, похожее на адрес одного из ваших загруженных модулей, перетащите его в поле Address окна Disassembly. Окно Disassembly покажет ассемблерный код по этому адресу, и если вы создавали свое приложение с полным набором отладочной информации, то сможете определить вызывающую функцию.

318 ЧАСТЬ II Производительная отладка

Если дамп по регистру ESP не содержит ничего похожего на адрес модуля, можно отобразить в окне Memory дамп памяти, начиная с регистра EBP, и выполнить то же самое. По мере освоения языка ассемблера вы станете замечать в дизассемб лированном коде и другую полезную информацию. На месте преступления часто можно обнаружить улики, подсказывающие, какой регистр использовать для по иска адреса возврата: ESP или EBP.

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

Отладка: фронтовые очерки

Что не так с GlobalUnlock? Всего лишь разыменование указателя.

Боевые действия

Меня попросили помочь одной группе в отладке очень неприятной про блемы — достаточно серьезной, чтобы это не позволяло выпустить програм му. Члены группы провели примерно месяц, пытаясь воспроизвести ошиб ку, и все еще не имели никакого понятия о ее причинах. Единственной подсказкой было то, что крах программы наступал только после вызова диалогового окна Print и изменения некоторых параметров. Вскоре после закрытия окна Print возникала ошибка в элементе управления сторонней фирмы. Стек вызовов показывал, что ошибка происходила в функции Global# Unlock.

Исход

Поначалу я сомневался, что при программировании для Win32 кто то еще использует функции, работающие с памятью через описатели (GlobalAlloc, GlobalLock, GlobalFree и GlobalUnlock). Однако, взглянув на дизассемблированный код элемента управления сторонней фирмы, я увидел, что его автор, оче видно, портировал элемент с 16 разрядного кода. Я сразу предположил, что в элементе управления неправильно используются API функции, работаю щие с памятью через описатели.

Для проверки этой гипотезы я установил точки прерывания на GlobalAlloc, GlobalLock и GlobalUnlock, чтобы найти в элементе управления места распре деления и обработки памяти. Как только выполнение программы прерва лось в элементе управления, я начал изучать, как он работает с описателя ми памяти. Все казалось нормальным, пока я не попробовал воспроизвес ти ошибку.

Через некоторое время после закрытия окна Print я заметил, что функ ция GlobalAlloc начинает возвращать описатели, заканчивающиеся на не четные числа, например на 5. В ОС Win32 для доступа к памяти через опи сатель выполняется разыменование указателя, поэтому я сразу же понял, что нахожусь у самой сути проблемы. Все описатели памяти в Win32 должны заканчиваться на 0, 4, 8 или C в шестнадцатеричной системе, потому что области памяти выравниваются по границам двойных слов. Значения опи

ГЛАВА 7 Усложненные технологии неуправляемого кода в Visual Studio .NET

319

 

 

сателей, возвращаемые GlobalAlloc, указывали на наличие какой то серьез ной проблемы.

Вооруженный этой информацией, руководитель проекта уже готов был звонить по телефону разработчикам элемента управления и требовать его исходный код, так как он был уверен, что именно в этом причина ошибки и задержки выпуска программы. Успокоив его, я объяснил, что это ничего не доказывает и что, пока мы не испортили жизнь ни в чем не повинным людям, мне нужно убедиться в том, что причиной ошибки является имен но элемент управления. Я продолжил изучать элемент управления и про вел несколько следующих часов, отслеживая все выполняемые им манипу ляции с описателями памяти. Работал он с описателями памяти правильно, поэтому у меня зародилась новая гипотеза: я предположил, что истинная проблема кроется в основном приложении. Обнаружение ошибки в элементе управления было просто случайным стечением обстоятельств.

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

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

При первоначальном просмотре код выглядел отлично, тем не менее я еще раз проанализировал и сверил с документацией MSDN каждую его строку. Через 10 минут ошибка была найдена. Разработчики сохраняли структуру PRINTDLG, применявшуюся для инициализации диалогового окна Print, при помощи API функции PrintDlg. Третье поле этой структуры, hDevMode, пред ставляет собой описатель памяти, выделяемой окном Print. Ошибка заклю чалась в том, что программисты работали с этим значением, как с обыч ным указателем, не разыменовывая описатель так, как нужно, и не вызывая для него функцию GlobalLock. Когда они изменяли значения в структуре DEVMODE, они по сути выполняли запись в таблицу глобальных описателей процесса. Эта таблица представляет собой блок памяти, в котором хранит ся информация обо всех описателях памяти, выделенной в куче. Из за за писи в таблицу глобальных описателей функция GlobalAlloc использовала неправильные смещения, получала в таблице неправильные значения и возвращала неверные указатели.

Полученный опыт

Первый урок: внимательно читайте документацию. Если в ней сказано, что структура данных является «глобальным перемещаемым объектом памяти», значит, работа с памятью осуществляется через описатели, и вам нужно

см. след. стр.

320 ЧАСТЬ II Производительная отладка

правильно выполнять их разыменование или вызывать для них функцию GlobalLock. Даже если 16 разрядная ОС Microsoft Windows 3.1 уже кажется древностью, некоторые архаизмы еще сохранились в Win32 API, и об этом нужно помнить.

Кроме того, я узнал, что таблица глобальных описателей хранится в памяти, разрешенной для записи. Если бы меня спросили об этом раньше, я, наверное, сказал бы, что такая важная структура ОС должна находиться в памяти с доступом только для чтения. Я могу только догадываться, что за ставило Microsoft пойти на это. С технической точки зрения, память, под держивающая описатели, нужна только для обратной совместимости, и все приложения Win32 должны использовать типы памяти, специфичные для Win32. Если бы таблица глобальных описателей была защищенной, то при каждом вызове функции, работающей с описателями, нужно было бы вы полнить два переключения контекста между пользовательским режимом и режимом ядра. Это очень накладно в плане ресурсов процессора, поэтому, наверное, Microsoft не защитила таблицу глобальных описателей именно по этой причине.

Наконец, я понял, что слишком долго изучал элемент управления. Всего для обнаружения ошибки мне понадобилось около семи часов. Однако я мог бы найти ее быстрее, если б обратил должное внимание на то, что ошибка возникала только при вызове окна Print, код которого находился в основ ном приложении.

Советы и хитрости

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

Порядок размещения значений в памяти

Порядок размещения значений (endianness) указывает, какой байт хранится по младшему адресу. Процессоры Intel располагают значения в памяти в прямом порядке, т. е. младшие байты хранятся по младшим адресам. Так, значение 0x1234 хранится в памяти как 0x34 0x12. Глядя на представление памяти в отладчике, очень важно выполнять соответствующие преобразования. Если вы изучаете узлы связ ного списка и один из указателей имеет на самом деле значение 0x12345678, то в окне Memory оно будет выведено в формате 0x78 0x56 0x34 0x12.

Термин «endian» впервые использовал Джонатан Свифт в «Путешествиях Гул ливера», а его компьютерное значение появилось благодаря Дэнни Коэну (Danny Cohen) в RFC (Request for Comments) 1980 года, посвященном порядку байтов. Если вас заинтересовала эта история, то работу Дэнни можно найти по адресу: http:// www.rdrop.com/~cary/html/endian_faq.html#danny_cohen.

«Мусорный» код

При изучении дампов в окне Disassembly нужно определить, действительно ли вы имеете дело с реальным кодом, но иногда это довольно затруднительно. Вот не

Соседние файлы в предмете Программирование на C++