Отладка приложений

         

Доступ к параметрам, глобальным и локальным переменным


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

int g_iVal = 0 ;

void AccessGlobalMemory ( void )

_asm 

{

// Установить в глобальной переменной значение 48,059.

MOV g_iVal , OBBBBh

// Если символы загружены, окно Disassembly покажет

// MOV DWORD PTR [g_iVal (00403060)],OBBBBh.

// Если символы не загружены, окно Disassembly покажет

// MOV DWORD PTR [00403060],OBBBBh. 

}

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

void AccessParameter ( int iParam )

 {

_asm 

{

// Переместить значение iParam value в регистр ЕАХ.

MOV ЕАХ , iParam

// Если символы загружены, окно Disassembly будет показывать

// MOV ЕАХ,DWORD PTR [iParam].

// Если символы не загружены, окно Disassembly будет показывать 

// MOV ЕАХ,DWORD PTR [ЕВР+8].

 }

 } 

Если при отладке оптимизированного кода отображаются ссылки, которые имеют положительное смещение от регистра стека ESP, значит это функция, которая имеет FPO-данные. Поскольку во время жизни функции содержимое ESP может изменяться, то работа с параметрами немного затрудняется.
При работе с оптимизированным кодом нужно сохранять след элементов, помещаемых в стек, потому что ссылка [ESP+20H] в этой функции может быть такой же, как и предыдущая [ESP+SH]. В процессе отладки, при выполнении пошагового прохода через операции языка ассемблера оптимизированного кода, всегда можно заметить, где расположены параметры. Если используются стандартные кадры, локальные переменные имеют отрицательные смещения от ЕВР. Как показано в предыдущем разделе, инструкция SUB резервирует место в стеке. Следующий код содержит пример установки нового значения в локальной переменной:

void AccessLocalVariable ( void ) 

{

int iLocal ;

_asm

{

// Установить в локальную переменную значение 23. 

MOV iLocal ,'017h

// Если символы загружены, окно Disassembly покажет 

// MOV DWORD PTR [iLocal],017h.

// Если символы не загружены, окно Disassembly покажет

 // MOV [EBP-4],017h. 

}

 }

Если стандартные кадры не используются, то поиск локальных переменных может быть затруднен (если их вообще можно найти). Проблема состоит в том, что локальные переменные появляются как положительные смещения от ESP, точно так же, как и параметры. В этом случае нужно попробовать так подобрать инструкцию SUB, чтобы увидеть, сколько байт отведено под локальные переменные. Если смещение ESP больше, чем число байт, отведенных под локальные переменные, то эта ссылка, вероятно, указывает на параметр.

Стековые кадры немного сбивают с толку тех, кто первый раз с ними сталкивается, поэтому приведем заключительный пример, разъясняющий предмет. Следующий код — очень простая С-функция, показывающая, почему параметры имеют положительное смещение от ЕВР, а локальные переменные — отрицательное (для стандартных кадров стека). После С-функции приведен ее код дизассемблера (в том виде, в котором он был откомпилирован программой ASMer).

void AccessLocalsAndParamsExample ( int * pParaml , int * pParam2 } 

{

int iLocal1 = 3 ;

int iLocal2 = 0x42 ;

iLocal1 = *pParaml ;



iLocal2 = *pParam2 ; 

}

// Дизассемблерный код AccessLocalsAndParamsExample 

//с адресами стандартного пролога функции

00401097 PUSH EBP

00401098 MOV EBP , ESP

0040109A SUB ESP , 8

// int iLocal1 = 3 ;

0040109D MOV DWORD PTR [EBP-8h] , 3

// int iLocal2 = 0x42 ;

004010A4 MOV DWORD PTR [EBP-4h] , 42h

// iLocal1 = *pParaml ;

004010AB MOV EAX , DWORD PTR [EBP+8h]

004010AE MOV ECX , DWORD PTR [EAX]

004010BO MOV DWORD PTR [EBP-08h] , ECX

// iLocal2 = *pParam2 ;

004010B3 MOV EDX , DWORD PTR [EBP+OCh]

004010B6 MOV EAX , DWORD PTR [EDX]

004010B8 MOV DWORD PTR [EBP-4h] , EAX

// Стандартный эпилог функции

004010BB MOV ESP , EBP

004010BD POP EBP

004010BE RET

}

Если точка прерывания устанавливается в начале функции AccessLocalsAndParamsExample (по адресу 0x00401097), то будут отображены

значения стека и регистров, как показано на рис. 6.2.

Рис. 6.2. Стек перед прологом функции AccessLocalsAndParamsExample

Первые три инструкции языка ассемблера в AccessLocalsAndParamsExaraple составляют пролог функции. После выполнения пролога устанавливаются указатели стека (ESP) и базы (ЕВР), доступ к параметрам выполняется через положительные смещения от ЕВР, а к локальным переменным — через отрицательные смещения от ЕВР. На рис. 6.3 показаны значения указателей стека и базы после выполнения каждой инструкции пролога.

Рис. 6.3. Стек в течение и после выполнения пролога функции AccessLocalsAndParamsExample



Содержание раздела