Невозможно отучить людей изучать самые ненужные предметы.
Введение в CSS
Преимущества стилей
Добавления стилей
Типы носителей
Базовый синтаксис
Значения стилевых свойств
Селекторы тегов
Классы
CSS3
    
    
Надо знать обо всем понемножку, но все о немногом.
Идентификаторы
Контекстные селекторы
Соседние селекторы
    
Дочерние селекторы
    
Селекторы атрибутов
 
Универсальный селектор
    
Псевдоклассы
  
Псевдоэлементы
    
Кто умеет, тот делает. Кто не умеет, тот учит. Кто не умеет учить - становится деканом. (Т. Мартин)
Группирование
Наследование
Каскадирование
Валидация
Идентификаторы и классы
Написание эффективного кода
    
Вёрстка
Изображения
Текст
Цвет
Линии и рамки
Углы
   
Списки
                                          
Ссылки
                                          
Дизайны сайтов
    
Формы
  
Таблицы
    
CSS3
                                         
HTML5
                                       
									Блог для вебмастеров
									Новости мира Интернет
                                    Сайтостроение
    
									Ремонт и советы
	
                                    Все новости
                                        
									
                                    Справочник от А до Я
    
									HTML, CSS, JavaScript
									
									
									Афоризмы о учёбе
									Статьи об афоризмах
									Все Афоризмы
									
									
| Помогли мы вам | 
void MyFunc(double, struct XT) proc neararg_0 = qword ptr 8arg_8 = qword ptr 10hIDA распознала два аргумента, передаваемых функции. Однако не стоит безоговорочно этому доверять, если один аргумент (например, int64) передается в нескольких машинных словах, то IDA ошибочно примет его не за один, а за несколько аргументов! Поэтому результат, полученный IDA, надо трактовать так: функции передается не менее двух аргументов.
Впрочем, и здесь не все гладко! Ведь никто не мешает вызываемой функции залезать в стек материнской так далеко, как она захочет! Может быть, нам не передавали никаких аргументов вовсе, а мы самовольно полезли в стек и стянули что‑то оттуда. Хотя это случается в основном вследствие программистских ошибок из‑за путаницы с прототипами, считаться с такой возможностью необходимо. Когда‑нибудь вы все равно с этим встретитесь, так что будьте начеку.
Число, стоящее после arg, выражает смещение аргумента относительно начала кадра стека.
Извлекаем переданные аргументы из регистров процессора и размещаем их в памяти (при этом вспоминаем, что передавали из вызывающей функции):
mov     [rsp+arg_8], rdx    ; указатель на буферmovsd   [rsp+arg_0], xmm0   ; значение с плавающей запятойДалее инициализируем стек, подготавливаем регистры к работе, производим необходимые вычисления, затем кладем в регистры значения для передачи параметров функции printf:
mov
eax, 1imul
rax, 0mov
rcx, [rsp+28h+arg_8]add
rcx, raxmov
rax, rcxmov
r9, raxmov
rax, [rsp+28h+arg_8]mov
r8d, [rax+14h]movsd
xmm1, [rsp+28h+arg_0]movq
rdx, xmm1lea
rcx, _Format ; "%f,%x,%sn"call
printfОбрати внимание, перед вызовом printf программа в трех регистрах размещает значения параметров для передачи, а в четвертом регистре RCX (так же для передачи) помещает указатель на форматную строку спецификаторов  вывода: %f,%x,%sn. Функция printf, как известно, имеет переменное число аргументов, тип и количество которых как раз и задают спецификаторы. Вспомним: сперва в стек мы заносили указатель на строку, и действительно, крайний правый спецификатор %s обозначает вывод строки. Затем в стек заносилась переменная типа int и второй справа спецификатор, есть %х – вывод целого числа в шестнадцатеричной форме.
А вот затем идет последний спецификатор %f. Заглянув в руководство программиста по Visual C++, мы прочтем, что спецификатор %f выводит вещественное значение, которое в зависимости от типа может занимать и четыре байта (float), и восемь (double). В нашем случае оно явно занимает восемь байтов, следовательно, это double. Таким образом, мы восстановили прототип нашей функции, вот он:
cdecl MyFunc(double a, struct B b)Тип вызова cdecl означает, что стек вычищает вызывающая функция. Вот только, увы, подлинный порядок передачи аргументов восстановить невозможно. C++ Builder, кстати, так же вычищал стек вызывающей функцией, но самовольно изменял порядок передачи параметров.
Может показаться, что если программу собирали в C++ Builder, то мы просто изменяем порядок аргументов на обратный, вот и все. Увы, это не так просто. Если имело место явное преобразование типа функции в cdecl, то C++ Builder без лишней самодеятельности поступил бы так, как ему велели, и тогда бы обращение порядка аргументов дало бы неверный результат!
Впрочем, подлинный порядок следования аргументов в прототипе функции не играет никакой роли. Важно лишь связать передаваемые и принимаемые аргументы, что мы и сделали.
Обратите внимание: это стало возможно лишь при совместном анализе и вызываемой, и вызывающей функций! Анализ лишь одной из них ничего бы не дал!
Никогда не следует безоговорочно полагаться на достоверность строки спецификаторов. Поскольку спецификаторы формируются «вручную» самим программистом, тут возможны ошибки, подчас весьма трудноуловимые и дающие после компиляции чрезвычайно загадочный код!
Далее деинициализируем стек и закругляемся.
add     rsp, 28hretnКое‑какие продвижения уже есть — мы уверенно восстановили прототип нашей первой функции. Но это только начало. Еще много миль предстоит пройти… Если устал — передохни, тяпни кваса, поболтай с кем‑нибудь и продолжим на свежую голову. Мы приступаем еще к одной очень важной теме — сравнительному анализу разных типов вызовов функций и их реализации в разных компиляторах.
Начнем с изучения стандартного соглашения о вызове — stdcall. Рассмотрим следующий пример.
#include <string.h>int __stdcall MyFunc(int a, int b, const char* c){  return a + b + strlen(c);}int main(){  printf("%xn", MyFunc(0x666, 0x777, "Hello,World!"));}Вот, как должен выглядеть результат его компиляции в Visual C++ с отключенной оптимизацией, то есть ключом / (в ином случае компилятор так заоптимизирует код, что исчезнет всякая познавательная составляющая):
main    proc nearsub     rsp, 28hIDA хорошо нам подсказывает, что константа c содержит строку Hello,. Указатель на нее помещается в регистр R8, предназначенный для передачи целочисленных параметров или, собственно, указателей. Первым по порядку передается указатель на строку, заглянув в исходные тексты (благо они у нас есть), мы обнаружим, что это самый правый аргумент, передаваемый функции. Следовательно, перед нами вызов типа stdcall или cdecl, но не PASCAL.
lea     r8, c     ; "Hello,World!"mov     edx, 777h ; bmov     ecx, 666h ; aСледом помещаем в два 32-битных регистра EDX и ECX значения двух переменных 0x777 и 0x666, соответственно. Последнее – оказалось самым левым аргументом. Что показывает нам правильно восстановленный прототип функции. Но так бывает не всегда, IDA иногда ошибается.
call    MyFunc(int,int,char const *)Обрати внимание, после вызова функции отсутствуют команды очистки стека от занесенных в него аргументов. Если компилятор не схитрил и не прибегнул к отложенной очистке, то, скорее всего, стек очищает сама вызываемая функция, значит, тип вызова — stdcall (что, собственно, и требовалось доказать).
mov     edx, eaxТеперь, передаем возвращенное функцией значение следующей функции, как аргумент.
Эта следующая функция printf, и строка спецификаторов показывают, что переданный аргумент имеет тип int.
call    printfxor     eax, eaxadd     rsp, 28hretnmain endpТеперь рассмотрим функцию MyFunc:
; int __fastcall MyFunc(int a, int b, const char *c)int MyFunc(int, int, char const *) proc nearIDA пытается самостоятельно восстановить прототип функции и… обламывается. Иными словами, делает это не всегда успешно. Например, «Ида» ошибочно предположила тип вызова fastcall, хотя на самом деле – stdcall. Вспомним: fastcall на 32-битной платформе предполагает передачу параметров через регистры процессора, тогда как на платформе x64 первые четыре параметра всегда передаются через регистры процессора, независимо от указанного типа вызова.
var_18 = qword ptr -18hvar_10 = qword ptr -10harg_0  = dword ptr  8arg_8  = dword ptr  10harg_10 = qword ptr  18hПереданные аргументы из регистров помещаются в память, затем после инициализации стека происходит размещение числовых значений в регистрах, где происходит их сложение: add .
mov     [rsp+arg_10], r8mov     [rsp+arg_8], edxmov     [rsp+arg_0], ecxsub     rsp, 18hmov     eax, [rsp+18h+arg_8]mov     ecx, [rsp+18h+arg_0]add     ecx, eaxmov     eax, ecxПреобразование двойного слова (EAX) в учетверенное (RAX):
cdqeКопирование из стека указателя в строку, в регистр RCX и в переменную var_10. Далее инициализируем переменную var_18 значением -1, очевидно, она будет счетчиком.
mov     rcx, [rsp+18h+arg_10]mov     [rsp+18h+var_10], rcxmov     [rsp+18h+var_18], 0FFFFFFFFFFFFFFFFhloc_140001111:И действительно, на следующем шаге она увеличивается на единицу. К тому же здесь мы видим метку безусловного перехода. Похоже, вместо того, чтобы узнать длину строки посредством вызова библиотечной функции strlen, компилятор решил самостоятельно сгенерировать код. Что ж, флаг ему в руки!
inc     [rsp+18h+var_18]Значения переменных вновь копируются в регистры для проведения операций над ними.
mov     rcx, [rsp+18h+var_10]mov     rdx, [rsp+18h+var_18]cmp     byte ptr [rcx+rdx], 0Значения регистров RCX и RDX складываются, а сумма сравнивается с нулем. В случае, если выражение тождественно, то флаг ZF устанавливается в единицу, в обратном случае – в ноль. Инструкция JNZ проверяет флаг Z. Если он равен нулю, тогда происходит переход на метку loc_140001111, откуда блок кода начинает выполняться поновой.
jnz     short loc_140001111Когда флаг ZF равен единице, осуществляется выход из цикла и переход на следующую за ним инструкцию, которая накопленное в переменной‑счетчике число записывает в регистр RCX. После этого происходит сложение значений в регистрах RCX и RAX, как помним, в последнем содержится сумма двух переданных числовых аргументов.
mov     rcx, [rsp+18h+var_18]add     rax, rcxВ завершении функции происходит деинициализация стека:
add     rsp, 18hretnint MyFunc(int, int, char const *) endpВозвращение целочисленного аргумента на платформе x64 предусмотрено в регистре RAX.
На вывод программа печатает: de9.
Легко проверить: Hello, – 12 символов, то есть 0xC. Открой калькулятор в Windows:
0x666 + 0x777 + 0xC = 0xDE9
|  |  |