Невозможно отучить людей изучать самые ненужные предметы.
Введение в CSS
Преимущества стилей
Добавления стилей
Типы носителей
Базовый синтаксис
Значения стилевых свойств
Селекторы тегов
Классы
CSS3
Надо знать обо всем понемножку, но все о немногом.
Идентификаторы
Контекстные селекторы
Соседние селекторы
Дочерние селекторы
Селекторы атрибутов
Универсальный селектор
Псевдоклассы
Псевдоэлементы
Кто умеет, тот делает. Кто не умеет, тот учит. Кто не умеет учить - становится деканом. (Т. Мартин)
Группирование
Наследование
Каскадирование
Валидация
Идентификаторы и классы
Написание эффективного кода
Вёрстка
Изображения
Текст
Цвет
Линии и рамки
Углы
Списки
Ссылки
Дизайны сайтов
Формы
Таблицы
CSS3
HTML5
Блог для вебмастеров
Новости мира Интернет
Сайтостроение
Ремонт и советы
Все новости
Справочник от А до Я
HTML, CSS, JavaScript
Афоризмы о учёбе
Статьи об афоризмах
Все Афоризмы
Помогли мы вам |
Обращаю твое внимание на одну деталь: с текущей статьи я перехожу на Visual Studio 2019. Последняя версия датируется 17 сентября и имеет номер 16.7.5. Чтобы избежать возможных несостыковок, советую тебе тоже обновить «Студию».
Структуры очень популярны среди программистов. Позволяя объединить под одной крышей родственные данные, они делают листинг программы более наглядным и упрощают его понимание. Соответственно, идентификация структур при дизассемблировании облегчает анализ кода. К великому сожалению исследователей, структуры как таковые существуют только в исходном тексте программы и практически полностью «перемалываются» при ее компиляции, становясь неотличимыми от обычных, никак не связанных друг с другом переменных.
Рассмотрим пример, демонстрирующий уничтожение структур на стадии компиляции:
#include <stdio.h>
#include <string.h>
struct zzz
{
char s0[16];
int a;
float f;
};
void func(struct zzz y)
// Понятное дело, передачи структуры по значению лучше избегать,
// но здесь это сделано умышленно для демонстрации скрытого создания
// локальной переменной
{
printf("%s %x %fn", &y.s0[0], y.a, y.f);
}
int main()
{
struct zzz y;
strcpy_s(&y.s0[0], 14, "Hello,Sailor!"); // Для копирования строки
y.a = 0x666;// используется безопасная версия функции
y.f = (float)6.6;// Чтобы подавить возражение компилятора,
func(y);// указываем целевой тип
}
Результат компиляции этого кода с помощью Visual Studio 2019 для платформы x64 должен выглядеть так:
main proc near
; Члены структуры неотличимы от обычных локальных переменных
var_48 = xmmword ptr -48h
var_38 = qword ptr -38h
Dst = byte ptr -28h
var_18 = qword ptr -18h
var_10 = qword ptr -10h
sub rsp, 68h
mov rax, cs:__security_cookie
xor rax, rsp
mov [rsp+68h+var_10], rax
; Подготовка параметров для вызова функции
lea r8, Src; "Hello,Sailor!"
mov edx, 0Eh; SizeInBytes
lea rcx, [rsp+68h+Dst] ; Dst
; Вызов функции для копирования строки из сегмента данных в локальную
; переменную
call cs:__imp_strcpy_s
Следующая команда копирует одно вещественное число, находящееся в младших 32 битах источника, — константу __real@40d33333
(смотрим, чему она равна при объявлении в секции rdаta:
, в формате float
она будет равна 6.6) в младшие 32 бита приемника — 128-битного регистра XMM1
. Напомню, восемь регистров XMM0
были добавлены в расширение SSE и поэтому впервые появились в процессоре Pentium III.
movss xmm1, cs:__real@40d33333
; Помещаем указатель на строку в регистр RDX
lea rdx, [rsp+68h+var_48]
Далее с использованием инструкции MOVUPS
из расширения SSE копируются невыровненные куски по 16 бит. Таким образом, за раз копируются сразу восемь символов Unicode. Однако количество символов в строке вполне может быть не кратно восьми, поэтому используется именно эта инструкция — все остальные инструкции из расширения SSE оперируют с переменными, выровненными по 16-битным границам памяти. В ином случае они вызывают исключение.
; В регистр RCX помещаем форматную строку для функции printf
lea
rcx, _Format
; "%s %x %fn"
; Помещаем двойное слово (значение 0x666) в переменную типа DWORD
mov
dword ptr [rsp+68h+var_18], 666h ; --1
Следующая команда копирует строго двойное слово из памяти в регистр (у нас это XMM3
). Значение, сохраненное в копируемой области памяти: 6.
, выровнено по границе 16 бит и на самом деле равно 6.6. В случае копирования из памяти в регистр (подобно нашему примеру) обнуляется старшее двойное слово источника.
movsd xmm3, cs:__real@401a666660000000
; Помещаем значение 0x666 в 32-битный регистр
mov r8d, 666h
; Р?Р· переменной (СЃРј. метку --1) копируем РґРІРѕР№РЅРѕРµ слово РІ регистр
movsd xmm2, [rsp+68h+var_18]
Далее учетверенное слово (64 бит) копируется из регистра XMM3
расширения SSE в регистр общего назначения R9
, добавленный вместе с расширением x86-64. Ведь AMD64, по сути, представляет собой такое же расширение процессорной архитектуры x86, как и SSE.
movq r9, xmm3
Инструкция shufps
посредством битовой маски комбинирует и переставляет данные в 32-битных компонентах XMM-регистра. Таким образом, если представить 0E1h
в бинарном виде, получим 11100001b
. В соответствии с этой маской происходит трансформация всех четырех 32-битных частей регистра XMM2
.
shufps xmm2, xmm2, 0E1h
; Копирование нижней 32-битной части источника в приемник
movss xmm2, xmm1
; Копирует 128 бит из регистра в переменную
movaps [rsp+68h+var_48], xmm0
; В соответствии с маской перемешивает содержимое регистра (см. выше)
shufps xmm2, xmm2, 0E1h
; Две следующие инструкции помещают значение регистра в переменные,
; находящиеся в памяти
movsd [rsp+68h+var_18], xmm2
movsd [rsp+68h+var_38], xmm2
; Все параметры находятся на своих местах, вызываем функцию printf
call printf
xor eax, eax
mov rcx, [rsp+68h+var_10]
xor rcx, rsp; StackCookie
call __security_check_cookie
add rsp, 68h
retn
main endp
Компилятор сгенерировал довольно витиеватый код со множеством команд из расширения SSE. При этом он встроил функцию func
прямо в main
!
А теперь заменим структуру последовательным объявлением тех же самых переменных и рассмотрим пример, демонстрирующий сходство структур с обычными локальными переменными.
int main()
{
char s0[16];
int a;
float f;
strcpy_s(&s0[0], 14, "Hello,Sailor!");
a = 0x666;
f = (float)6.6;
printf("%s %x %fn", &s0[0], a, f);
}
И сравним результат компиляции с предыдущим:
Dst
= byte ptr -28hvar_18 = qword ptr -18h; Есть различие! Компилятор избавился от ненужных для выполнения переменных,; однако от этого не становится понятнее, принадлежат переменные структуре или нет
sub
rsp, 48h
mov
rax, cs:__security_cookie
xor
rax, rsp
mov
[rsp+48h+var_18], rax
; Готовим параметры
lea
r8, Src; "Hello,Sailor!"
mov
edx, 0Eh; SizeInBytes
lea
rcx, [rsp+48h+Dst] ; Dst
; Вызываем функцию копирования строки
call
cs:__imp_strcpy_s
; В XMM3 помещается значение 6.599999904632568 (подробно мы говорили,
; когда разбирали предыдущий листинг)
movsd
xmm3, cs:__real@401a666660000000
; Последующие инструкции продолжают готовить параметры для функции
lea
rdx, [rsp+48h+Dst]
movq
r9, xmm3
; В регистр RCX помещаем форматную строку для функции printf
lea
rcx, _Format
; "%s %x %fn"
; Помещаем значение 0x666 в младшие 32 бита регистра R8
mov
r8d, 666h
; Вызов функции printf
call
printf
xor
eax, eax
mov
rcx, [rsp+48h+var_18]
xor
rcx, rsp; StackCookie
call
__security_check_cookie
add
rsp, 48h
retnmain
endp
Без вызова дополнительных функций и передачи параметров дизассемблерный листинг заметно сократился. Остальной код остался идентичным предыдущему листингу.
|
|